diff --git a/src/Roux/Html.hs b/src/Roux/Html.hs
index d437cae..cc3afb8 100644
--- a/src/Roux/Html.hs
+++ b/src/Roux/Html.hs
@@ -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
diff --git a/src/Roux/Parser.hs b/src/Roux/Parser.hs
index c8e2f85..314efe9 100644
--- a/src/Roux/Parser.hs
+++ b/src/Roux/Parser.hs
@@ -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).