diff --git a/static/css/base.css b/static/css/base.css index 6790eb4..70771c9 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -278,15 +278,11 @@ html { line-height: var(--line-height); -webkit-text-size-adjust: 100%; scroll-behavior: smooth; - /* clip (not hidden) — prevents horizontal scroll at the viewport level - without creating a scroll container, so position:sticky still works. */ - overflow-x: clip; } body { margin: 0; padding: 0; - overflow-x: clip; background-color: var(--bg); color: var(--text); transition: background-color var(--transition-fast), diff --git a/static/css/components.css b/static/css/components.css index fa6dda1..01aba4d 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -543,12 +543,28 @@ nav.site-nav { using `aria-hidden="true"` (set by toc.js). The transition still works because we keep `max-height: 0` for the visual collapse. */ .toc-nav { - overflow: hidden; - max-height: 80vh; + /* Fill the sticky sidebar's remaining height and scroll when the + outline is taller than the viewport. The subtracted ~2.6rem + mirrors #toc's own max-height budget (layout.css) minus the + .toc-header row (label + progress rule + its bottom margin). */ + max-height: calc(100vh - var(--nav-height, 4rem) - 3rem - 2.6rem); + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; transition: max-height 0.3s ease; + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} +.toc-nav::-webkit-scrollbar { width: 6px; } +.toc-nav::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; } #toc.is-collapsed .toc-nav { max-height: 0; + /* overflow:hidden so the outline is clipped (not scrolled) while + the max-height transition runs down to 0. */ + overflow: hidden; } #toc.is-collapsed .toc-nav a, #toc.is-collapsed .toc-nav button { diff --git a/static/css/layout.css b/static/css/layout.css index 592a40a..0d90988 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -5,10 +5,16 @@ The outer shell. Wide enough for TOC + body + sidenotes. ============================================================ */ -body { +/* Body is plain block — keeps the sticky nav header (a direct body + child) working reliably on iOS WebKit, where position: sticky on a + direct flex/grid child silently degrades to static. The sticky-footer + math (full-viewport min-height + flex column to push footer down) + moves to .page-shell, which wraps everything below the nav. */ + +.page-shell { display: flex; flex-direction: column; - min-height: 100vh; + min-height: calc(100dvh - var(--nav-height, 4rem)); } /* ============================================================ @@ -17,7 +23,14 @@ body { (Nav styles live in components.css) ============================================================ */ -body > header { +/* Site-nav header only — exclude the essay-frontmatter
that + essay/reading/blog templates emit as a body-level sibling so its + monogram and figure columns can span full viewport width. Without + the :not() guard, the essay header inherits the sticky / nav-bg / + border-bottom chrome meant only for the top navigation, painting + the wrong color band over the page and pinning the frontmatter to + the viewport top. */ +body > header:not(.essay-frontmatter) { width: 100%; border-bottom: 1px solid var(--border); background-color: var(--bg-nav); @@ -78,18 +91,20 @@ body > header { /* ============================================================ STANDALONE PAGES (no #content wrapper) essay-index, blog-index, tag-index, page, blog-post, search — - these emit #markdownBody as a direct child of . Without - the #content flex-row wrapper there is no centering; fix it here. + these emit #markdownBody as a direct child of .page-shell. + Without the #content flex-row wrapper there is no centering; + fix it here. (Was body > #markdownBody before the page-shell + wrapper was introduced to keep iOS sticky working.) ============================================================ */ -body > #markdownBody { +.page-shell > #markdownBody { align-self: center; padding: 2rem var(--page-padding); flex: 1 0 auto; } @media (max-width: 680px) { - body > #markdownBody { + .page-shell > #markdownBody { padding: 1.25rem var(--page-padding); } } @@ -99,7 +114,7 @@ body > #markdownBody { FOOTER ============================================================ */ -body > footer { +.page-shell > footer { width: 100%; border-top: 1px solid var(--border); padding: 1.5rem var(--page-padding); @@ -109,6 +124,7 @@ body > footer { display: flex; justify-content: space-between; align-items: center; + margin-top: auto; } .footer-left { @@ -217,7 +233,7 @@ body > footer { } /* Footer: stack vertically so three sections don't fight for width. */ - body > footer { + .page-shell > footer { flex-direction: column; align-items: center; gap: 0.3rem; diff --git a/static/css/reading.css b/static/css/reading.css index e0de556..746be61 100644 --- a/static/css/reading.css +++ b/static/css/reading.css @@ -48,9 +48,10 @@ body.reading-mode { } /* Reading body: narrower than the essay default (800px → ~62ch). - Since reading.html emits body > #markdownBody (no #content grid), - the centering is handled by the existing layout.css rule. */ -body.reading-mode > #markdownBody { + reading.html emits #markdownBody as a direct child of .page-shell + (no #content grid); centering is handled by the matching + .page-shell > #markdownBody rule in layout.css. */ +body.reading-mode .page-shell > #markdownBody { max-width: 62ch; } diff --git a/templates/default.html b/templates/default.html index 1bc0ebb..f1846f8 100644 --- a/templates/default.html +++ b/templates/default.html @@ -12,8 +12,10 @@ $if(search)$ $endif$ +
$body$ $partial("templates/partials/footer.html")$ +