fix: tighten typography, fix parser brace leak, polish UI
Build and Deploy / build-and-deploy (push) Successful in 1m40s

- Drop italics on cookware/timer tags — they were over-emphasizing
  roughly a third of step text. Now plain with slight opacity.
- Fix parser bug: @salt{large pinch} braces no longer leak through
  as raw text. Non-numeric quantities are consumed silently,
  ingredient appears without displayed quantity.
- Remove duplicate '\342\206\227 Original' link from marginalia SOURCE block.
  Title-adjacent link is sufficient.
- Add tags to marginalia column for visual balance.
- Constrain content to 1120px max-width for a printed-cookbook feel.
- Add .container h2 CSS specificity override to beat Pico CSS defaults.
  Section labels (INGREDIENTS, METHOD) now properly render in Fraunces.
- Switch navbars from <div> to <nav> so Pico's own nav ul{list-style:none}
  suppresses gray square bullets. Add explicit li{list-style:none} override.
- Constrain content to 1120px max-width so parchment frames the page.
This commit is contained in:
2026-05-20 22:31:23 -04:00
parent 752918c1af
commit 1b7f7b61aa
2 changed files with 18 additions and 14 deletions
+17 -13
View File
@@ -147,21 +147,21 @@ page title content =
, ".quicksand { font-family: \"Quicksand\", sans-serif; font-optical-sizing: auto; font-style: normal; }"
, ""
, "body { background: var(--roux-bg); color: var(--roux-text); font-family: \"Quicksand\", sans-serif; }"
, "main.container { padding-top: 1.5rem; }"
, "main.container { padding-top: 1.5rem; max-width: 1120px; }"
, "h1, h3, .roux-heading { font-family: \"Fraunces\", serif; font-optical-sizing: auto; }"
, "h1 { font-size: 2rem; font-weight: 500; margin: 0 0 0.5rem; line-height: 1.15; color: var(--roux-text); letter-spacing: -0.01em; }"
, "h2 { font-family: \"Fraunces\", serif; font-size: 0.75rem; font-weight: 600; margin: 0 0 1rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--roux-text); opacity: 0.6; }"
, "h2, .container h2 { font-family: \"Fraunces\", serif; font-size: 0.75rem; font-weight: 600; margin: 0 0 1rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--roux-text); opacity: 0.6; }"
, "h3 { font-size: 1rem; font-weight: 500; margin: 1.5rem 0 0.5rem; color: var(--roux-text); text-transform: uppercase; opacity: 0.75; }"
, "a { color: var(--roux-text); text-decoration: none; transition: color 0.15s; }"
, "a:hover { color: var(--roux-accent); }"
, "a[role=button] { --pico-color: var(--roux-accent); }"
, ""
, "/* Navbar */"
, ".roux-navbar { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 0; margin-bottom: 1rem; border-bottom: 1px solid rgba(61, 44, 30, 0.1); }"
, "nav.roux-navbar { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 0; margin-bottom: 1rem; border-bottom: 1px solid rgba(61, 44, 30, 0.1); }"
, ".roux-navbar .roux-logo { font-family: \"Fraunces\", serif; font-size: 1.1rem; font-weight: 500; color: var(--roux-text); text-decoration: none; }"
, ".roux-navbar .roux-logo:hover { color: var(--roux-text); }"
, ".roux-navbar ul { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }"
, ".roux-navbar li { margin: 0; padding: 0; }"
, ".roux-navbar li { margin: 0; padding: 0; list-style: none; }"
, ".roux-navbar a { font-size: 0.8rem; letter-spacing: 0.04em; text-transform: uppercase; }"
, ".roux-navbar a.active { color: var(--roux-accent); font-weight: 500; }"
, ""
@@ -221,9 +221,9 @@ page title content =
, ".roux-course { font-size: 0.6rem; color: var(--roux-accent); opacity: 0.65; margin: 0 0 0.15rem; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 500; }"
, ".roux-desc { font-size: 0.85rem; line-height: 1.6; color: var(--roux-text); margin: 0 0 1.5rem; max-width: 640px; opacity: 0.85; }"
, ""
, ".roux-ingredient-tag { border-bottom: 1.5px solid rgba(191, 148, 40, 0.35); font-weight: 500; }"
, ".roux-cookware-tag { font-style: italic; opacity: 0.75; }"
, ".roux-timer-tag { border-bottom: 1px dashed rgba(61, 44, 30, 0.25); font-style: italic; }"
, ".roux-ingredient-tag { border-bottom: 1.5px solid rgba(191, 148, 40, 0.35); }"
, ".roux-cookware-tag, .roux-timer-tag { opacity: 0.85; }"
, ".roux-ingredient-tag .qty { font-size: 0.8rem; color: var(--roux-text); opacity: 0.55; }"
, ".roux-comment-tag { opacity: 0.5; font-style: italic; font-size: 0.85em; }"
, ".tag { display: inline-block; border: 1px solid rgba(122, 154, 122, 0.35); padding: 0.1rem 0.5rem; margin: 0.1rem; border-radius: 4px; font-size: 0.7rem; color: var(--roux-sage); background: transparent; }"
, ".r-section-header { display: block; font-family: \"Fraunces\", serif; font-size: 0.85rem; font-weight: 500; color: var(--roux-text); opacity: 0.5; margin: 1rem 0 0.5rem; letter-spacing: 0.01em; border-top: 0.5px solid rgba(61, 44, 30, 0.06); padding-top: 0.75rem; }"
@@ -431,7 +431,7 @@ sseIndexJs =
indexPage :: SortMode -> [Idx.RecipeInfo] -> ByteString
indexPage _mode recipes =
page "Roux — Recipes" $ do
H.div ! A.class_ "roux-navbar" $ do
H.nav ! A.class_ "roux-navbar" $ do
H.ul $ H.li $ H.a ! A.class_ "roux-logo" ! A.href "/" $ "Roux"
H.ul $ do
H.li $ H.a ! A.href "#alpha" $ "A-Z"
@@ -468,7 +468,7 @@ indexPage _mode recipes =
importPage :: Maybe Text -> ByteString
importPage merror =
page "Roux \8212 Import Recipe" $ do
H.div ! A.class_ "roux-navbar" $ do
H.nav ! A.class_ "roux-navbar" $ do
H.ul $ H.li $ H.a ! A.href "/" $ "\8592 Back"
H.ul $ H.li $ H.strong "Import Recipe"
case merror of
@@ -489,7 +489,7 @@ importPage merror =
importResultPage :: ImportError -> ByteString
importResultPage (ImportError msg) =
page "Roux \8212 Import Error" $ do
H.div ! A.class_ "roux-navbar" $ do
H.nav ! A.class_ "roux-navbar" $ do
H.ul $ H.li $ H.a ! A.href "/" $ "\8592 Back"
H.ul $ H.li $ H.a ! A.href "/import" $ "Try again"
H.h2 "Import failed"
@@ -520,7 +520,7 @@ recipePage info =
-- | Render the body of a recipe page (no page shell).
renderRecipe :: Recipe -> Html
renderRecipe recipe = do
H.div ! A.class_ "roux-navbar" $ do
H.nav ! A.class_ "roux-navbar" $ do
H.ul $ H.li $ H.a ! A.class_ "roux-logo" ! A.href "/" $ "Roux"
H.ul $ H.li $ H.a ! A.href "/" $ "Recipes"
let meta = recipeMetadata recipe
@@ -555,9 +555,13 @@ renderRecipe recipe = do
case metaSource meta of
Just url -> H.div ! A.class_ "note" $ do
H.h2 "Source"
H.a ! A.href (H.toValue url) ! A.target "_blank" ! A.rel "noopener noreferrer" $ do
"↗ Original recipe"
H.p $ H.toHtml url
Nothing -> pure ()
let recipeTags = metaTags meta
unless (null recipeTags) $ do
H.div ! A.class_ "note" $ do
H.h2 "Tags"
H.p $ H.toHtml (T.intercalate " · " recipeTags)
unless (null notes) $ do
H.h2 "Notes"
mapM_ renderMarginalNote notes
+1 -1
View File
@@ -296,7 +296,7 @@ pQuantityBody = do
[] -> return Nothing -- empty braces
_ -> case parseQuantityText (T.pack content) of
Just q -> return (Just q)
Nothing -> P.parserFail ("invalid quantity: " <> content)
Nothing -> return Nothing -- non-numeric quantity, consume silently
{- | Parse a quantity text like @2@, @1%kg@, @=1%tsp@, @1\/2%tbsp@, or
@1,1\/2cups@ (unit without @%@ separator).