From 7fe0013a5c4ff6c311c320d04ccc6b6b34fb8448 Mon Sep 17 00:00:00 2001 From: luxick Date: Tue, 16 Jun 2026 14:00:32 +0200 Subject: [PATCH] Sticky Header --- assets/style.css | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/assets/style.css b/assets/style.css index 3976f44..140ca82 100644 --- a/assets/style.css +++ b/assets/style.css @@ -36,8 +36,18 @@ --font-xs: 0.75rem; --font-sm: 0.85rem; + + /* Height of the sticky top header. Single source of truth for the + sidebar/TOC top offsets and anchor scroll-padding. Hardcoded (the + desktop header is a single-row grid); revisit if the header wraps. */ + --header-h: 3.25rem; } +/* Anchor / TOC jumps land below the sticky header instead of behind it. + Harmless on mobile view mode (header not sticky there) — adds only a + small gap above the target. */ +html { scroll-padding-top: var(--header-h); } + /* === Base === */ body { background: var(--bg); @@ -95,6 +105,10 @@ main { padding: var(--space-5) var(--space-4); width: 100%; flex: 1; min-width: centre stays reserved even when search is hidden in editor mode. Mobile (≤1100px) collapses to a two-row layout — see responsive block below. */ header { + position: sticky; + top: 0; + z-index: 40; + background: var(--bg); padding: var(--space-3) var(--space-4); border-bottom: var(--border-dashed); display: grid; @@ -487,13 +501,18 @@ button.fab { display: none; } /* === Sidebar === */ .sidebar { position: sticky; - top: var(--space-4); + /* Park below the sticky header. The space-2 buffer also absorbs the small + difference between --header-h and the real rendered header height, so the + sidebar is already at its pinned offset at scroll 0 — no pre-pin travel. + Do NOT add margin-top: a top margin sits above the pin point and makes + the sidebar visibly jump up by that margin when scrolling starts. */ + top: calc(var(--header-h) + var(--space-2)); align-self: start; - max-height: calc(100vh - 2rem); + max-height: calc(100vh - var(--header-h) - var(--space-4)); display: flex; flex-direction: column; gap: var(--space-4); - margin-top: var(--space-5); + padding-top: 1rem; } aside.sidebar:empty { display: none; } @@ -507,7 +526,7 @@ aside.sidebar:empty { display: none; } static via the .panel-sidebar density class. */ .toc { position: fixed; - top: var(--space-4); + top: calc(var(--header-h) + var(--space-2)); right: var(--space-4); width: 14rem; max-height: calc(100vh - 6rem); @@ -679,6 +698,11 @@ aside.sidebar:empty { display: none; } /* Single-row mobile header: logo + Up icon are compact so search can take the middle flex column, with actions on the right. */ header { grid-template-columns: auto 1fr auto; } + /* Mobile view mode: header scrolls away with the page (out of scope to + stick it). It re-sticks only in edit mode so SAVE/CANCEL stay reachable + on long documents. */ + header { position: static; } + body.edit-mode header { position: sticky; top: 0; } .search-form { width: 100%; max-width: none; justify-self: stretch; } /* Sidebar on mobile is a floating overlay toggled by the FAB. The aside itself is the scroll container; children render at natural height. */