79 lines (70 loc) · 5.35 KB
 1@use "theme" as *;
 2@use "vars" as v;
 3
 4@supports selector(:has(*)) {
 5  @media (max-width: v.mq(sm)) {
 6    html:has(.menu-mobile[open]) { overflow: hidden; }
 7  }
 8}
 9
10.site-header-outer { position: sticky; top: 0; z-index: 100; }
11// The blur lives on this inner strip, NOT on .site-header-outer: the fixed
12// .menu-panel is a sibling inside the outer, and an ancestor with
13// backdrop-filter would form a backdrop root and kill the panel's own blur.
14.site-header-bar { background: color-mix(in srgb, theme(bg) 90%, transparent); @include blur-surface(8px); border-bottom: 1px solid theme(border); }
15.site-header { height: v.$header-height; box-sizing: border-box; justify-content: space-between; align-items: center; }
16.header-left { display: flex; align-items: center; }
17
18.site-title { margin: 0; font-size: 1rem; font-weight: 600; }
19.site-title a { color: theme(heading); }
20
21.menu-desktop { display: flex; gap: 1.5rem; align-items: center; }
22.menu-desktop a { color: theme(muted); font-size: 0.875rem; font-weight: 500; transition: color 0.15s ease; }
23.menu-desktop a:hover { color: theme(text); }
24.menu-desktop a:focus-visible { color: theme(text); }
25.menu-desktop a.active { color: theme(heading); }
26
27.menu-mobile { display: none; position: relative; z-index: 10000; }
28// Both labels occupy the same grid cell and cross-fade; the cell keeps the
29// width of the wider "[ menu ]", so the swap doesn't shift the layout.
30.menu-toggle { cursor: pointer; list-style: none; user-select: none; display: inline-grid; place-items: center; padding: 0.5rem; color: theme(text); font-family: "Silkscreen", monospace; font-size: 0.7rem; letter-spacing: 0.05em; }
31.menu-toggle::-webkit-details-marker { display: none; }
32.menu-toggle::before, .menu-toggle::after { grid-area: 1 / 1; transition: opacity 0.2s ease; }
33.menu-toggle::before { content: "[ menu ]"; }
34.menu-toggle::after { content: "[ x ]"; opacity: 0; }
35.menu-mobile[open] .menu-toggle::before { opacity: 0; }
36.menu-mobile[open] .menu-toggle::after { opacity: 1; }
37
38// Same blur strength as .site-header-bar so the two surfaces read as one.
39// align-items/justify-content live here, not in the :has([open]) rule below:
40// [open] is removed the instant the menu closes, so anything not covered by
41// the opacity/display transition would snap back immediately and "teleport"
42// while the panel is still fading out.
43// The box spans the full screen (header included) purely so its content
44// centers against the true viewport middle instead of the shorter area
45// below the header, which read as too low. The header itself must stay
46// uncovered, so the visible surface (blur/tint/border) is a separate layer
47// (::before) that starts below it, not the box's own background.
48// pointer-events: none - the box's own hit area now reaches over the header
49// (see above), which would otherwise block clicks on [ x ] there even though
50// nothing is painted over it. Re-enabled on the actual visible/clickable
51// pieces below.
52.menu-panel { position: fixed; inset: 0; z-index: 9999; display: none; opacity: 0; align-items: center; justify-content: center; pointer-events: none; transition: opacity 0.25s ease, display 0.25s allow-discrete; }
53// Same blur strength as .site-header-bar so the two surfaces read as one.
54// No backdrop-filter transition here - recomputing blur every animation
55// frame is far more expensive in Gecko/non-WebKit engines than computing it
56// once at full strength.
57.menu-panel::before { content: ""; position: absolute; top: v.$header-height; left: 0; right: 0; bottom: 0; z-index: -1; background: color-mix(in srgb, theme(bg) 90%, transparent); @include blur-surface(8px); border-top: 1px solid theme(border); pointer-events: auto; }
58.menu-panel > * { pointer-events: auto; }
59.site-header-outer:has(.menu-mobile[open]) .menu-panel { display: flex; opacity: 1; @starting-style { opacity: 0; } }
60@media (prefers-reduced-motion: reduce) { .menu-panel { transition: none; } }
61
62.menu-list { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
63.menu-list a { display: flex; align-items: baseline; gap: 0.5rem; font-size: 1.1rem; font-weight: 500; color: theme(muted); padding: 0.5rem 0; transition: color 0.15s ease; }
64.menu-list__num { font-size: 0.65rem; font-weight: 400; color: theme(muted); font-variant-numeric: tabular-nums; letter-spacing: 0.05em; opacity: 0.35; transition: color 0.15s ease, opacity 0.15s ease; }
65.menu-list a.active .menu-list__num { opacity: 1; }
66.menu-list a:focus-visible { color: theme(accent); }
67.menu-list a.active { color: theme(heading); }
68@media (hover: hover) {
69  .menu-list a:hover .menu-list__num { opacity: 1; }
70}
71
72// Same shared partial as the footer's switcher, pinned to the panel's bottom-right.
73.menu-panel .theme-switcher { position: absolute; right: 1rem; bottom: calc(1rem + env(safe-area-inset-bottom, 0px)); }
74// Mirrored on the bottom-left - reachable one-handed (left thumb) without
75// reaching back up to the toggle in the header.
76.menu-panel__close { position: absolute; left: 1rem; bottom: calc(1rem + env(safe-area-inset-bottom, 0px)); border: none; background: none; padding: 0; font-family: "Silkscreen", monospace; font-size: 0.7rem; letter-spacing: 0.05em; color: theme(muted); cursor: pointer; transition: color 0.15s ease; }
77@media (hover: hover) { .menu-panel__close:hover, .menu-panel__close:focus-visible { color: theme(accent); } }
78
79@media (max-width: v.mq(sm)) { .menu-desktop { display: none; } .menu-mobile { display: block; } }