Unify header navigation and page padding across all pages

- Add Home link to every page's navigation header (index, recipe,
  cook history, import)
- Move landing page padding (36px 48px 80px) into base main.container
  CSS so all pages share the same consistent spacing
- Remove per-page padding overrides from .lnd-page and .rp-page
This commit is contained in:
2026-05-27 21:13:48 -04:00
parent 0eb6e6ea3c
commit 541dc0e7ce
+63 -61
View File
@@ -146,7 +146,7 @@ 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; max-width: 1120px; }"
, "main.container { padding: 36px 48px 80px; 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, .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; }"
@@ -155,14 +155,13 @@ page title content =
, "a:hover { color: var(--roux-accent); }"
, "a[role=button] { --pico-color: var(--roux-accent); }"
, ""
, "/* Navbar */"
, "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; 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; }"
, "/* Header navigation */"
, ".roux-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 0.5px solid rgba(61,44,30,0.18); padding-bottom: 12px; margin-bottom: 32px; font-size: 12px; letter-spacing: 0.12em; color: #6B5847; text-transform: uppercase; }"
, ".roux-header-logo { font-family: \"Fraunces\", serif; font-size: 22px; font-weight: 500; letter-spacing: 0.08em; color: var(--roux-text); text-decoration: none; }"
, ".roux-header-links { display: flex; gap: 28px; }"
, ".roux-header-links a { color: #6B5847; text-decoration: none; }"
, ".roux-header-links a:hover { color: var(--roux-accent); }"
, ".roux-header-links a.active { color: var(--roux-accent); }"
, ""
, "/* Search input */"
, "input[type=search] { background: var(--roux-bg); color: var(--roux-text); border-color: rgba(61, 44, 30, 0.2); }"
@@ -423,7 +422,7 @@ searchJs =
, "// Set active tab style based on hash"
, "function updateActiveNav() {"
, " const mode = getSortMode();"
, " document.querySelectorAll('.roux-navbar a').forEach(a => {"
, " document.querySelectorAll('.roux-header-links a').forEach(a => {"
, " a.classList.toggle('active', a.getAttribute('href') === '#' + mode);"
, " });"
, "}"
@@ -737,18 +736,33 @@ sseIndexJs =
, "})();"
]
-- ---------------------------------------------------------------------------
-- Shared header navigation
-- ---------------------------------------------------------------------------
-- | Render the shared header navigation with the landing-page style.
renderHeader :: [(Text, Text, Bool)] -> Html
renderHeader links =
H.nav ! A.class_ "roux-header" $ do
H.a ! A.class_ "roux-header-logo" ! A.href "/" $ "ROUX"
H.div ! A.class_ "roux-header-links" $
mapM_ renderLink links
where
renderLink (href, label, True) = H.a ! A.class_ "active" ! A.href (H.toValue href) $ H.toHtml label
renderLink (href, label, False) = H.a ! A.href (H.toValue href) $ H.toHtml label
-- | Render the recipe index page -- shell with embedded JSON + JS rendering.
indexPage :: SortMode -> [Idx.RecipeInfo] -> ByteString
indexPage _mode recipes =
page "Roux — Recipes" $ 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"
H.li $ H.a ! A.href "#tags" $ "By Tag"
H.li $ H.a ! A.href "#course" $ "By Course"
H.li $ H.a ! A.href "/import" $ "Import"
H.li $ H.a ! A.href "/cook-history" $ "Cook History"
renderHeader
[ ("/", "Home", False)
, ("#alpha", "A-Z", True)
, ("#tags", "By Tag", False)
, ("#course", "By Course", False)
, ("/import", "Import", False)
, ("/cook-history", "Cook History", False)
]
-- Search input
H.input
! A.type_ "search"
@@ -810,12 +824,12 @@ fileUploadJs =
importPage :: Maybe Text -> ByteString
importPage merror =
page "Roux \8212 Import Recipe" $ 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 "/recipes" $ "Recipes"
H.li $ H.a ! A.class_ "active" ! A.href "/import" $ "Import"
H.li $ H.a ! A.href "/cook-history" $ "Cook History"
renderHeader
[ ("/", "Home", False)
, ("/recipes", "Recipes", False)
, ("/import", "Import", True)
, ("/cook-history", "Cook History", False)
]
case merror of
Just err -> H.p ! A.style "color: var(--roux-accent);" $ H.toHtml err
Nothing -> pure ()
@@ -899,13 +913,8 @@ formatDay = T.pack . Time.formatTime Time.defaultTimeLocale "%b %e"
recipePageStyles :: Text
recipePageStyles =
T.unlines
[ ".rp-page { max-width: 980px; margin: 0 auto; padding: 36px 48px 80px; }"
, ".rp-nav { display: flex; justify-content: space-between; align-items: center; border-bottom: 0.5px solid rgba(61,44,30,0.18); padding-bottom: 12px; margin-bottom: 20px; font-size: 12px; letter-spacing: 0.12em; color: #6B5847; text-transform: uppercase; }"
, ".rp-logo { font-family: \"Fraunces\", serif; font-size: 22px; font-weight: 500; letter-spacing: 0.08em; color: var(--roux-text); text-decoration: none; }"
, ".rp-nav-links { display: flex; gap: 28px; }"
, ".rp-nav-links a { color: #6B5847; text-decoration: none; }"
, ".rp-nav-links a:hover { color: var(--roux-accent); }"
, ""
[ ".rp-page { max-width: 980px; margin: 0 auto; }"
, ".rp-course { font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--roux-accent); opacity: 0.65; margin: 0 0 4px; }"
, ".rp-title { font-size: 38px; line-height: 1.05; font-family: \"Fraunces\", serif; font-weight: 500; margin: 0; }"
, ".rp-title.roux-recipe-title:hover::after { content: '\\270E'; font-size: 0.7rem; opacity: 0.3; margin-left: 0.5rem; vertical-align: super; }"
@@ -965,7 +974,7 @@ recipePageStyles =
, ".rp-tag { font-size: 12px; color: var(--roux-accent); border: 1px solid rgba(184,92,56,0.25); border-radius: 4px; padding: 2px 10px; }"
, ".rp-history-section { border-top: 0.5px solid rgba(61,40,23,0.15); padding-top: 24px; margin-top: 0; }"
, ""
, "@media (max-width: 720px) { .rp-page { padding: 24px 24px 60px; } .rp-meta-strip { flex-direction: column; align-items: flex-start; } .rp-meta-strip-actions { width: 100%; justify-content: flex-end; } .rp-body-grid { grid-template-columns: 1fr; gap: 32px; } .rp-title { font-size: 30px; } }"
, "@media (max-width: 720px) { .rp-meta-strip { flex-direction: column; align-items: flex-start; } .rp-meta-strip-actions { width: 100%; justify-content: flex-end; } .rp-body-grid { grid-template-columns: 1fr; gap: 32px; } .rp-title { font-size: 30px; } }"
]
-- | Render the body of a recipe page (no page shell) -- mockup-compliant layout.
@@ -973,13 +982,13 @@ renderRecipe :: FilePath -> Recipe -> [CookLog.CookEntry] -> Html
renderRecipe filename recipe entries = do
H.style $ H.toHtml recipePageStyles
H.div ! A.class_ "rp-page" $ do
-- Nav (matching landing page style)
H.nav ! A.class_ "rp-nav" $ do
H.a ! A.class_ "rp-logo" ! A.href "/" $ "ROUX"
H.div ! A.class_ "rp-nav-links" $ do
H.a ! A.href "/recipes" $ "Recipes"
H.a ! A.href "/import" $ "Import"
H.a ! A.href "/cook-history" $ "Cook History"
-- Nav
renderHeader
[ ("/", "Home", False)
, ("/recipes", "Recipes", False)
, ("/import", "Import", False)
, ("/cook-history", "Cook History", False)
]
let meta = recipeMetadata recipe
course = metaCourse meta
desc = metaDescription meta
@@ -1307,12 +1316,12 @@ isRight = not . isLeft
cookHistoryPage :: (Text -> Text) -> [(Text, [CookLog.CookEntry])] -> ByteString
cookHistoryPage lookupTitle grouped =
page "Cook History" $ 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 "/recipes" $ "Recipes"
H.li $ H.a ! A.href "/import" $ "Import"
H.li $ H.a ! A.class_ "active" ! A.href "/cook-history" $ "Cook History"
renderHeader
[ ("/", "Home", False)
, ("/recipes", "Recipes", False)
, ("/import", "Import", False)
, ("/cook-history", "Cook History", True)
]
H.h1 "Cook History"
mapM_ (renderMonthGroup lookupTitle) grouped
@@ -1341,14 +1350,8 @@ renderHistoryEntry lookupTitle entry = do
landingStyles :: Text
landingStyles =
T.unlines
[ ".lnd-page { max-width: 980px; margin: 0 auto; padding: 36px 48px 80px; }"
, ".lnd-nav { display: flex; justify-content: space-between; align-items: center; border-bottom: 0.5px solid rgba(61,44,30,0.18); padding-bottom: 12px; margin-bottom: 32px; font-size: 12px; letter-spacing: 0.12em; color: #6B5847; text-transform: uppercase; }"
, ".lnd-logo { font-family: \"Fraunces\", serif; font-size: 22px; font-weight: 500; letter-spacing: 0.08em; }"
, ".lnd-nav-links { display: flex; gap: 28px; }"
, ".lnd-nav-links a { color: #6B5847; text-decoration: none; }"
, ".lnd-nav-links a.active { color: #B85C38; }"
, ".lnd-nav-links a:hover { color: #B85C38; }"
, ""
[ ".lnd-page { max-width: 980px; margin: 0 auto; }"
, ".lnd-shuffle { background: rgba(184,92,56,0.08); border-radius: 12px; padding: 20px 24px; display: flex; justify-content: space-between; align-items: center; gap: 24px; margin-bottom: 36px; }"
, ".lnd-shuffle-meta { font-size: 12px; letter-spacing: 0.12em; color: #8A7560; text-transform: uppercase; margin-bottom: 4px; }"
, ".lnd-shuffle h3 { font-family: \"Fraunces\", serif; font-size: 20px; font-weight: 500; margin: 0; }"
@@ -1379,7 +1382,7 @@ landingStyles =
, ".lnd-img-5 { background: linear-gradient(135deg, #C7A878, #8E6B3D 60%, #5A4520); }"
, ".lnd-img-6 { background: linear-gradient(135deg, #B5D4A1, #7AA862 60%, #4D7038); }"
, ""
, "@media (max-width: 720px) { .lnd-page { padding: 24px 24px 60px; } .lnd-shelf-row { grid-template-columns: 1fr 1fr; } .lnd-shuffle { flex-direction: column; align-items: flex-start; } .lnd-shuffle-actions { width: 100%; justify-content: flex-end; } }"
, "@media (max-width: 720px) { .lnd-shelf-row { grid-template-columns: 1fr 1fr; } .lnd-shuffle { flex-direction: column; align-items: flex-start; } .lnd-shuffle-actions { width: 100%; justify-content: flex-end; } }"
]
-- | Inline JavaScript for the landing page shuffle and navigation.
@@ -1417,13 +1420,12 @@ landingPage allRecipes recentlyCooked recentlyAdded =
H.style $ H.toHtml landingStyles
H.div ! A.class_ "lnd-page" $ do
-- Nav
H.nav ! A.class_ "lnd-nav" $ do
H.span ! A.class_ "lnd-logo" $ "ROUX"
H.div ! A.class_ "lnd-nav-links" $ do
H.a ! A.class_ "active" $ "Home"
H.a ! A.href "/recipes" $ "Recipes"
H.a ! A.href "/import" $ "Import"
H.a ! A.href "/cook-history" $ "Cook History"
renderHeader
[ ("/", "Home", True)
, ("/recipes", "Recipes", False)
, ("/import", "Import", False)
, ("/cook-history", "Cook History", False)
]
-- Shuffle card
H.div ! A.class_ "lnd-shuffle" $ do
H.div $ do