/*
 * ericville26 — design tokens
 * ===========================
 * Adopted from design_handoff_ericville26/design/v26.css. Values are copied
 * exactly: several are deliberately off-grid (13.5px, -.028em, .42s, 14%/62%)
 * and must not be rounded to a 4/8px grid.
 *
 * The handoff's single stylesheet is split across two files here:
 *
 *   css/v26.css              this file — tokens and the section/theme scopes.
 *                            Loads EARLY, before every component stylesheet,
 *                            so all of them resolve the new values.
 *   css/v26-base.css         the global base: page ground, paper grain, type
 *                            ladder. Loads LAST — see the note in that file.
 *   css/v26-components.css   the component primitives (.bar, .grid, .cell,
 *                            .btn, .tag, .slab, .inverse, .shead …), scoped
 *                            under .ev26 so they only style markup built for
 *                            ericville26.
 *
 * Why the split: the prototype's class names are generic, and this theme
 * already uses six of them for other things — .tag alone appears 89 times on
 * the Lab page, .inverse is a wp-block-group modifier, and bare `nav` rules
 * intended for the new header were collapsing the existing one. Unscoped, the
 * component layer restyles markup nobody asked it to touch. Scoping keeps this
 * stage additive, exactly as intended. Stage 11 drops the .ev26 scope once no
 * ericville25 markup is left to protect.
 */

:root{
/* ── the families ────────────────────────────────────────────────────────────
 * #1484 phase 2. Source Serif 4 + Instrument Sans -> ONE superfamily covering
 * display, UI and code. The reasoning is in plan/design/1482-typography-proposal.md
 * §2B and Eric picked it: the reason the site reads as harmonious should be
 * that it visibly IS one system, and Plex is a technology company's typeface
 * whose serif and neo-grotesque share their proportions exactly.
 *
 * Three facts worth writing down because they contradict what was expected:
 *
 * 1. THIS IS THE LIGHTER OPTION, not the heavier one. The audit warned Plex
 *    would cost more because Plex Serif is static on Google Fonts. Measured by
 *    fetching every woff2 in the LATIN subset (what a visitor actually
 *    downloads): Source Serif 4 + Instrument Sans = 6 faces, 363.0 KB; Plex
 *    Sans + Plex Serif = 7 faces, 159.2 KB. Source Serif 4's variable file
 *    carries opsz AND wght AND ital and is ~130 KB per style; five static Plex
 *    Serif cuts are ~25 KB each. The trimmed request (400/600/700 + 400i/700i,
 *    per the audit's own cost analysis) is what makes this true -- do not
 *    widen it to the full 7+7 without re-measuring.
 * 2. THE SANS GAINS A REAL ITALIC. Instrument Sans was enqueued with no italic
 *    axis at all, so every sans italic on the site was a browser-synthesised
 *    oblique (the audit found 7 such nodes). Plex Sans is variable 100..700 on
 *    BOTH axes, verified against the live css2 API: 12 @font-face blocks, six
 *    subsets x {roman, italic}, all `font-weight: 100 700`.
 * 3. --mono IS NOT ENQUEUED SITEWIDE. See ericville26_page_renders_code() in
 *    inc/headers/asset-loader.php: the mono face loads only on singular pages
 *    that actually contain a code/preformatted/verse block or a <pre>/<code>/
 *    <kbd>/<samp> tag. On today's content that is exactly one page. The
 *    fallback chain below is what makes the rule safe everywhere else -- a
 *    <code> on a page without the enqueue still sets in ui-monospace, which is
 *    what it did before this ticket. */
--serif:"IBM Plex Serif",Georgia,serif;--sans:"IBM Plex Sans",system-ui,sans-serif;
--mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;

/* Neutrals — cooled and cleaned up from the original warm set. The page ground
   sits under a mint gradient on the home page and a paper grain everywhere, and
   a brown-leaning grey muddied both. These read as paper without the tint. */
--bg:#F2F4F2;
/* #1500 — Eric: "each grey color on the page should have a very slight tint
   to it that's aligned with the accent color," including black text. These
   five tokens used to be flat literals shared by every section regardless of
   which accent that section carries — a true, context-blind grey reads as
   the ONE thing on an otherwise accent-driven page that belongs to no
   section. color-mix toward var(--accent) fixes that with no per-section
   overrides needed: --accent already resolves differently under each
   [data-section], and var() substitution is resolved at the point of use,
   so every one of these picks up whichever accent is active on the element
   that reads it. Percentages are light on purpose ("very slight") and rise
   slightly for the already-lower-contrast tokens (--faint, --line) since a
   tint has to be a little stronger there to read at all against a small
   fill. in oklab, not srgb, for a clean perceptual blend that doesn't muddy
   a near-black or near-white toward grey-brown the way naive RGB mixing can. */
--ink:color-mix(in oklab,#0B0F0D 96%,var(--accent) 4%);
--text:color-mix(in oklab,#2B322E 95%,var(--accent) 5%);
--muted:color-mix(in oklab,#565E58 93%,var(--accent) 7%);
--faint:color-mix(in oklab,#868D87 91%,var(--accent) 9%);
--line:color-mix(in oklab,#E1E5E1 94%,var(--accent) 6%);
--line-strong:color-mix(in oklab,#C8CEC8 92%,var(--accent) 8%);
--edge:color-mix(in oklab,#0B0F0D 96%,var(--accent) 4%);

/* ── Elevation: surfaces are relationships, not colours ─────────────────────
 * --paper used to be a fixed #FBFCFA and --sunken a fixed #E7EBE7. Both were
 * picked against the one cool ground (--bg) every page had at the time, and
 * both are correct there. They are wrong the moment the ground changes: a cool
 * near-white pasted on the home page's warm wash reads grey and dirty, and it
 * would fail again on the next coloured ground or over a photograph.
 *
 * A surface has to know what it is over. So elevation is now expressed as an
 * ALPHA relationship to whatever is underneath rather than an absolute fill:
 *
 *   --paper   a translucent LIFT   (raise: white light falling on the surface)
 *   --sunken  a translucent RECESS (lower: shadow collecting in the surface)
 *
 * The ground shows through, so a card is automatically warm on the warm home
 * wash, cool on the neutral section pages, and darker-than-black in dark mode
 * — one rule, one relationship, no per-ground overrides.
 *
 * The alphas are tuned so that OVER --bg the two tokens still resolve to very
 * nearly the old literals (76% white over #F2F4F2 = #FBFCFA; 5% ink over it =
 * #E6EAE6 ≈ #E7EBE7), which is what keeps every already-approved page visually
 * unchanged while the home page stops fighting its ground.
 *
 * Not backdrop-filter: this repo has already removed an animated gradient and
 * a transitioned backdrop-filter for measured jank, and six cards' worth of
 * blur would buy the same problem back for an effect plain alpha delivers.
 * See plan/design/1421-surface-ground.md. */
--lift:#FFFFFF;--lift-a:76%;
--recess:#0B0F0D;--recess-a:5%;
/* Cast-shadow ink. Separate from --ink because --ink INVERTS in dark mode
   (it becomes the light text colour) and a shadow must stay dark in both. */
--shade:#0B0F0D;
--paper:color-mix(in srgb,var(--lift) var(--lift-a),transparent);
--sunken:color-mix(in srgb,var(--recess) var(--recess-a),transparent);
/* The other half of "raised": a translucent surface needs a little cast shadow
   to read as ABOVE the ground rather than as a wash on it. Tinted with --ink
   so it darkens correctly on a warm ground instead of adding neutral grey.
   Deliberately not a border — no boxes drawn back around the cards. */
--raise:0 1px 2px color-mix(in srgb,var(--shade) 5%,transparent),
        0 14px 34px -22px color-mix(in srgb,var(--shade) 24%,transparent);
--raise-hi:0 2px 4px color-mix(in srgb,var(--shade) 6%,transparent),
        0 26px 52px -26px color-mix(in srgb,var(--shade) 30%,transparent);

/* Accents — one confident colour per section, saturated enough to feel current
   without going neon. No purple, by request. */
--accent:#0AA06E;--accent-soft:#E2F3EC;--on-accent:#FBFCFA;

--grain-op:.12;--grain:url("../assets/noise1.jpg");
--ease:cubic-bezier(.2,.7,.2,1);
/* A little overshoot on the way in and out, so the header's collapse reads as
   a movement rather than a dissolve. */
--spring:cubic-bezier(.34,1.4,.5,1);
--fast:.18s;--med:.32s;--slow:.7s;
--col:1120px;--gut:44px;--r-s:2px;--r-m:12px;--r-l:20px;
/* ── header glass: the one surface the page passes UNDER ────────────────────
 * Same model as --lift / --recess above, one step further. A card is an opaque
 * lift; the header is the surface you can see THROUGH, so it is a material at
 * a partial alpha plus a blur that diffuses whatever is behind it.
 *
 * --frost is that material, and it is NOT --bg. The bar used to be
 * color-mix(--bg 14%, transparent): a 14% veil of the ground over the ground,
 * which is by definition the ground again. That is why it read as a flat pale
 * band with no material to it — mixing more --bg can only ever wash toward
 * grey, never toward ice.
 *
 * LIGHT: a cooled white. Real frost scatters short wavelengths, so ice reads
 * blue-white even over a warm ground; neutral --lift would go flat on the home
 * page's warm wash, which is exactly the failure being fixed. The cool bias is
 * small (a 6-point blue lead over red) — enough to read as a different, colder
 * material than the paper below it, not enough to read as blue chrome.
 *
 * DARK: the cool near-black that the dark card colour already resolves to
 * (--lift 5% over --bg = #171B19). A 5% white lift is too weak to be a pane, so
 * in dark the glass is a dark material at a high alpha instead: it still lifts
 * off the near-black ground, and it heavily attenuates any photograph that
 * scrolls under it rather than letting it smear through grey. Materials flip,
 * alphas do not — the same pairing --lift / --recess already uses.
 *
 * --hdr-hi is the specular top edge on the shrunk pill (see v26-components).
 *
 * COST, measured, not assumed: 179 rAF frames of scroll on /lab/ at 1440@2x
 * under a 6x CPU throttle — no backdrop-filter 243ms of main-thread task time,
 * blur 20 (the old shrunk value) 1347ms, blur 26 1360ms, blur 40 1528ms. The
 * bill is for HAVING the filter at all (5.5x the no-filter baseline); the
 * radius is nearly free until it is not. 26px costs ~1% over the value already
 * shipping and stays inside the noise; 40px costs 13% and doubles the >20ms
 * frames, so it is the ceiling and we sit well under it. */
--frost:#F2FAFD;--hdr-hi:26%;
--hdr-blur:16px;--hdr-tint:72%;--hdr-sat:1.7;--hdr-pad:18px;--hdr-radius:0px;--hdr-inset:0px;
/* Was a hardcoded rgba(11,15,13,.16) — --ink written out longhand, so it could
   not follow the theme into dark. --shade is the cast-shadow ink and is black
   in dark, same fix #1421 made to the .feature shadows. */
--hdr-shadow:none;--hdr-line:0px;
/* ── the frost BAND, at rest (#1464) ────────────────────────────────────────
 * #1432 gave the at-rest bar a real material. Eric's correction: at rest it
 * should not be a BAR at all — "no background except for additional grainy
 * blur texture that gives it an icy look", "a gradiated frost texture that
 * fades out and extends further below the bottom of the nav".
 *
 * So the resting header stops being a box with a fill and an edge and becomes
 * a BAND the page passes INTO:
 *   - --hdr-tint no longer applies at rest at all. The at-rest fill is gone;
 *     the token now belongs entirely to the shrunk pill, and the :root value
 *     is the pill's own 72%/1.7 so the two states cannot drift apart.
 *   - --hdr-shadow goes to none at rest. A cast shadow is an EDGE marker, and
 *     the whole point is that there is no edge. (The shrunk pill, which is a
 *     real floating object, keeps its shadow and its --hdr-hi highlight.)
 *   - --hdr-fade is how far the band overhangs the bar's own bottom, so the
 *     frost keeps going after the nav has stopped. The effect therefore
 *     cannot live on .bar's box — see the pseudo-element pair in
 *     v26-components.css. --hdr-blur-reach is the fraction of that overhang
 *     the (expensive) blur is allowed to cover; the (free) material tail
 *     covers all of it.
 *   - --hdr-band-sat is .96, NOT --hdr-sat's 1.5. With a fill this thin, a
 *     saturation boost has almost no frost of its own to tint and simply
 *     amplifies whatever accent wash is underneath: the first render of this
 *     turned the Art page's rose wash into a hot pink glow. Frost SCATTERS;
 *     it desaturates. Barely.
 *
 * --hdr-ice is the band's peak alpha and --hdr-grain the pill's, and both are
 * the icy tooth. The page has had a paper grain since #1421 (body::after,
 * z-index 9) and the header sits at z-index 200, so the grain STOPS DEAD at
 * the chrome — a horizontal texture seam we had simply stopped seeing. The
 * header is re-grained with THE SAME ASSET, var(--grain), on a tighter tile:
 * 380px is paper, 200px is frost. No new asset.
 *
 * The two states need it delivered differently, and that is not arbitrary:
 *   BAND (at rest): as a luminance MASK. There is nothing opaque behind it to
 *     blend with, so the grain has to BE the material's alpha. See the long
 *     note in v26-components.css.
 *   PILL (scrolled): as a blended layer, which only works here because the
 *     pill actually paints something to blend with. --hdr-grain-tone is the
 *     one thing the page grain needs that the paper never did: noise1.jpg has
 *     a mean luminance of 92/255 and a long dark tail, so multiplying it over
 *     a 240-level pane at any useful amplitude turns bright ice into grey
 *     cloth (measured: mean 240 -> 188). contrast(.25) brightness(1.9) lifts
 *     the mean to ~225 and clips the tail, and the same visible texture then
 *     costs 7 levels of brightness instead of 52. Dark needs no correction —
 *     overlay on a near-black ground lifts rather than subtracts — so
 *     --hdr-grain-tone is `none` there, and the blend follows body::after:
 *     multiply in light, overlay in dark.
 *
 * Mask stencils below use rgb(0 0 0/x). Those are ALPHA, not colour — a mask
 * has no hue and cannot be tokenised; every design colour is still a token.
 *
 * #1485: --hdr-grain below is the PILL's amplitude only (the band's tooth is
 * --hdr-ice — a mask takes no filter, §10.5 of the design note) and it drops
 * .50 -> .26 light / .32 -> .17 dark. --hdr-grain-size STAYS 200px because it
 * is the BAND's tile; the pill now has its own --hdr-pill-grain-size (620px).
 * Nothing at rest moves in #1485. */
/* #1500 — Eric: "the texture on the header and lab subnav is too gritty."
 * --hdr-grain is the SHRUNK PILL's noise amplitude only (the band's own
 * texture runs through --hdr-ice above, untouched here) — and the Lab chip
 * tray (v26-lab.css .lab-index::after) reads this exact same token, so one
 * change softens both surfaces the report named. .16 -> .10 is roughly a
 * third quieter, not a removal: --hdr-pill-tint (the frost fill's own
 * opacity) is the token doing the pill's actual legibility work per #1485's
 * measurement below, and is untouched — --hdr-grain was cut hard once
 * already precisely so it would be safe to keep cutting without relitigating
 * contrast. Re-verified after this change: worst-case nav label (/lab/ dark,
 * /art/ light) still clears AA.
 * Round 2 — "soften the background even more," confirmed scoped to this same
 * surface (not --hdr-pill-tint, which stays untouched for the contrast
 * reason above, and not the sitewide --grain-op paper texture). .10 -> .06. */
--frost-band:#F2FAFD;--hdr-ice:.40;--hdr-band-sat:.96;--hdr-fade:56px;--hdr-blur-reach:.55;--hdr-grain:.06;--hdr-grain-size:200px;--hdr-grain-blend:multiply;--hdr-grain-tone:grayscale(1) contrast(.25) brightness(1.9);

/* ── the scrolled pill's two dials ───────────────────────────────────────────
 * #1480. Eric: "the scrolling version is now not glassy enough; make it more
 * transparent and textured."
 *
 * --hdr-pill-tint is the SHRUNK pill's --hdr-tint. It is a separate token from
 * :root's --hdr-tint above for one blunt reason: since #1464 the at-rest bar
 * paints no fill at all, so :root's --hdr-tint is dead for the header, and the
 * live 72% was hard-coded inside .bar.big.shrunk in v26-components.css. The
 * #1480 brief pointed at :root:126 and would have tuned a value that changes
 * nothing. The dial now lives here, where the note says it does.
 *
 * The texture dial needs no new token: --hdr-grain / --hdr-grain-blend /
 * --hdr-grain-tone are ALREADY pill-only. The at-rest band's tooth is
 * --hdr-ice (a mask takes no filter, #1464 §10.5) and the only thing the band
 * shares is --hdr-grain-size. So --hdr-grain is raised in place, below.
 *
 * SEQUENCE MATTERS, and this is the whole method of #1480: the grain blends
 * against whatever the tint lets through, so a lower tint changes how the SAME
 * amplitude reads. Tint was landed first (72% -> 48/56%), then grain was tuned
 * against that landing, not independently. If you move the tint again, re-look
 * at the grain; do not assume it composes.
 *
 * Why 48% light and 56% dark, and not one number:
 *   - LIGHT. --frost is #F2FAFD, a near-white. Lowering its alpha over a
 *     26px-blurred backdrop costs contrast slowly, because the ink on the pill
 *     is dark and almost anything behind it is lighter than the ink. 48% is
 *     where the blurred page stops being a rumour and starts being visible
 *     THROUGH the glass (the /art/ linocuts read as shapes, not as a grey
 *     slab) while the pill still reads as one continuous object.
 *   - DARK. --frost is #181D1C, a dark pane whose actual JOB (#1432) is to
 *     ATTENUATE bright photography sliding under white nav labels. Alpha there
 *     is legibility, not decoration: cream artwork at 232 behind 13px white
 *     text is the worst ground on the site, and it only exists in the scrolled
 *     state. Dark therefore gets a smaller cut (72 -> 56) than light, and the
 *     glassiness it gives up is bought back by the grain, which in dark is an
 *     `overlay` LIFT and reads far more strongly per unit of opacity.
 *
 * WHY THE TINT STOPS AT 48 AND NOT LOWER. Rendered ladder on /art/ (the pill
 * over full-bleed linocuts, the only ground on the site with real detail under
 * the chrome), 72 / 60 / 48 / 38: at 38 the pane visibly breaks into TWO panes
 * again — the ends over black ink read as a different, darker material than
 * the middle. That is #1432's original failure returning, and it is the floor.
 * 48 is one clear step above it. #1432's "50% reads as almost nothing" does
 * NOT transfer to this state: at rest the pane sat on a flat section wash, so
 * 50% of a near-white over a near-white was a no-op. Scrolled, there is real
 * content behind it and 48% is unmistakably glass.
 *
 * --hdr-grain-tone IS RE-CHECKED AT THE NEW AMPLITUDE, not inherited. The
 * worry was that a naive bump would crush highlights. It does not, and the
 * reason is worth writing down: the tone filter runs BEFORE `opacity`, so its
 * clip fraction (~8% of the asset's pixels reach 255 at contrast(.25)
 * brightness(1.9)) is a property of the recipe alone and does not grow with
 * amplitude. Raising `brightness` to protect the mean is the move that WOULD
 * crush it — brightness(2.05) clips ~30% of the asset to flat white. So the
 * recipe is left exactly as #1464 tuned it, and only the amplitude moves.
 * Measured on the flattest 220x40 patch of the light pill, /, 1440@2x:
 *
 *     tint 72 grain .34 (before)   mean 233.0   grain-SD 4.54   <- #1464 got 4.63
 *     tint 48 grain .34            mean 229.2   grain-SD 4.48
 *     tint 48 grain .46            mean 227.8   grain-SD 6.31
 *     tint 48 grain .60            mean 226.3   grain-SD 8.19
 *
 * The mean barely moves across the whole ladder (229 -> 226 for +83% texture),
 * which is the tone correction doing its job at amplitudes it was not tuned
 * for. .50 lands ~7.0 SD, roughly 2.3x the page's own paper grain. Dark is the
 * same ladder inverted — `overlay` LIFTS, mean 30.3 -> 36.0 across .20/.30/.42
 * — and .42 crossed from frost into sensor noise on a near-black pane, so dark
 * sits at .32.
 *
 * ── #1483: 48% -> 34%, AND #1480'S FLOOR OF 38 NO LONGER APPLIES ────────────
 * Eric: "more translucent while being accessible." The value above is #1480's
 * history; the live number is 34%. The reason a number #1480 called the FLOOR
 * is now four points above where the pill ships is not that #1480 was wrong —
 * it is that the floor is a property of the pill's SIZE, and the pill changed.
 *
 * #1480's failure at 38 was pane discontinuity: over the /art/ linocuts the
 * ends read as a darker material than the middle, so the eye saw two panes.
 * That failure is a function of how much ground the pane covers and how hard
 * the transition across it is. The pill is now 2.7x the area and its backdrop
 * is smeared 30px instead of 26, so the tonal transitions across it are longer
 * and shallower. The ladder was therefore re-rendered at the NEW geometry, on
 * the same ground (/art/, 1440):
 *
 *     44  glass, but still clearly a pale slab over the prints
 *     40  the three prints resolve as tonal blocks
 *     36  the cut lines start to read as shapes
 *     34  SHIPPED — the artwork reads through as soft shapes and the pane is
 *         still one continuous object end to end
 *     30  the left third over the black ink begins to read as its own tone
 *     26  the floor: a hard vertical step in the PANE at the print boundaries,
 *         the two-pane failure back again
 *
 * So the floor moved 38 -> ~28 and the shipped value moved 48 -> 34, which is
 * the same one-clear-step-above-the-floor margin #1480 chose. Contrast is what
 * bounds it from the other side and it is not close: worst nav label across 28
 * scrolled captures is 5.36:1 (/art/ dark 1440), worst wordmark 8.94:1, both
 * on the same median-luminance ground sampling #1480 used.
 *
 * GRAIN IS DELIBERATELY UNTOUCHED. Eric explicitly deprioritised texture this
 * round ("don't worry about the texture as much"), and it was checked rather
 * than assumed: at 34% over a 30px blur the .50/.32 tooth is still plainly
 * visible on the pane in every render, so there was nothing to fix. The
 * light/dark amplitude gap stays as #1464 set it.
 *
 * ── #1485: 34% -> 52%, AND THE NUMBER GOING UP IS NOT A REVERSAL ────────────
 * Eric asked for LESS texture and MORE transparency in the same sentence. On
 * this pill those are the same request, because of a compositing fact nobody
 * had measured before: THE GRAIN LAYER IS HALF THE PANE'S OPACITY.
 *
 * The pill's grain (v26-components, .bar.big.shrunk::after) is a bright layer
 * — noise1.jpg through --hdr-grain-tone has a mean of ~225 — painted with
 * mix-blend-mode over a pane that is only PARTLY opaque. Where a backdrop's
 * alpha is ab, CSS composites a blend as (1-ab)*Cs + ab*B(Cb,Cs): the blend
 * only applies to the covered fraction, and the rest of the source colour is
 * painted STRAIGHT ON. At a 34% pane that is 66% of a near-white layer landing
 * as a plain pale veil. Measured on /essay/ (the dark banner under the nav,
 * the worst light ground on the site), holding everything else fixed:
 *
 *     --hdr-grain .50  ->  pane luminance 160     nav label 4.75:1
 *     --hdr-grain .26  ->  pane luminance 130     nav label 3.19:1   AA FAIL
 *
 * So cutting the noise in half also halved the pane's coverage and dropped a
 * surface through the AA floor. The texture was doing legibility work, and no
 * previous round knew it. (This also explains a result that looks backwards:
 * LOWERING --hdr-grain makes the pill DARKER over a dark ground, not lighter.)
 *
 * The fix is to move that work out of the noise and into the material: cut the
 * grain hard and give the tint back exactly what the veil was contributing.
 * Same total coverage, delivered as clean frost instead of as sand. Rendered
 * side by side on /art/ (the linocuts, the hardest ground for glassiness), the
 * artwork reads through 52%/.26 AT LEAST AS WELL as through 34%/.50 — better,
 * if anything, because there is no noise competing with the cut lines. Every
 * measured number improves or holds (28 scrolled samples, 6 surfaces x 2
 * themes, median-luminance ground sampling):
 *
 *                          #1483 as shipped      #1485
 *     worst nav label      4.32:1 (/lab/ dark)   4.54:1 (/lab/ dark)
 *     worst wordmark       7.74:1 (/art/ dark)   8.12:1 (/art/ dark)
 *     /essay/ light pane   160.2                 160.2   (identical)
 *     fine-grain SD        6.25 light            2.44    (-61%)
 *
 * DARK GOES 44 -> 50 for the same reason, and dark ALSO gets genuinely more
 * transparent out of it: in dark the grain is an `overlay` LIFT, so removing it
 * takes pale off a near-black pane — the dark pill's measured luminance drops
 * 4-6 levels on every surface (/lab/ 48.0 -> 43.2) even with the higher tint.
 *
 * THE FRAGMENTATION FLOOR MOVED UP, not down: re-rendered on /art/ at the new
 * grain and the new (narrower) pill, the pane starts breaking into two tones at
 * ~32 and is plainly broken at 26, where #1483 measured the floor at 26. Less
 * noise means less to disguise a tonal step, and a narrower pane means the step
 * is shorter and steeper. It is moot here — the binding floor is now CONTRAST,
 * at ~49, not fragmentation at 32 — but do not inherit "the floor is 26".
 *
 * IF ERIC ASKS FOR MORE TRANSPARENCY AGAIN, this is the wall and it is not the
 * tint: /lab/ dark (white labels over a bright photograph) and /essay/ light
 * (dark labels over the navy banner) both sit within ~0.3 of AA at 52/50. The
 * lever left is the GROUND, not the glass — e.g. the nav labels gaining their
 * own scrim on those two templates. Do not spend it on the tint. */
--hdr-pill-tint:44%;

/* ── the scrolled pill's GEOMETRY ────────────────────────────────────────────
 * #1483. Eric: "the floating header variation needs to feel more like glass;
 * should be thicker/taller, more padded, and more translucent while being
 * accessible. Also, since the default state has no background, please make the
 * width closer to the floating version so the transition is less jarring
 * width-wise."
 *
 * Four dials, all previously hard-coded literals inside .bar.big.shrunk. They
 * are here, next to --hdr-pill-tint, because they are one setting: the pill's
 * transparency only reads as glass at a size that gives the eye enough pane to
 * see through. #1480 learned the hard way that a pill dial hidden in the
 * component file is a dial the next brief will fail to find.
 *
 * --hdr-pill-pad  8px -> 15px. This is the pill's --hdr-pad, i.e. the .wrap's
 *   vertical padding, and it is the ONLY thing that moves the pill's height:
 *   the row's tallest child is .lockup at a fixed 34px (the nav's --nav-ctl is
 *   30px in this state and does not reach it). So the pill was 34+2x8 = 50px
 *   and is now 34+2x15 = 64px, against the at-rest row's 34+2x18 = 70px. That
 *   is deliberate: the shrink is now 6px, not 20px, so "shrunk" is a change of
 *   posture rather than a different object, and 15px of air above and below a
 *   30px control is what makes a 64px lozenge read as a PANE with something
 *   floating in it rather than a sleeve wrapped round the nav.
 *
 * --hdr-pill-inset  14px -> 16px per side, and the 1080px cap is gone; see
 *   --hdr-pill-max. Combined with dropping the pill's `max-width:none` on
 *   .wrap (v26-components), this is the width fix: the pill is now the BAND
 *   MINUS A HAIRLINE rather than a 1080px lozenge floating 180px inside it.
 *   At 1440 the box goes 1080 -> 1408 against an at-rest band of 1440, so the
 *   width the transition has to travel collapses from 360px to 32px.
 *
 *   THE PART THAT IS NOT OBVIOUS: widening the pill does not move the content.
 *   Because .wrap keeps its own max-width:var(--col) and padding:0 var(--gut)
 *   in BOTH states now, and both the pill and .wrap are centred, the wordmark
 *   and the nav land on exactly the same x in the scrolled state as at rest
 *   wherever the column binds (>= 1120 + 2x--gut). Below that the content
 *   shifts inward by exactly --hdr-pill-inset and no more. The old rule did
 *   the opposite: max-width:none + a 22px pad meant the content's x depended
 *   on the pill's width, so any width change dragged the type with it.
 *
 * --hdr-pill-max  the pill stops growing at 1440px. Not a column: the content
 *   column is --col and .wrap still owns it. This is only a ceiling on the
 *   GLASS, so that on a display much wider than the design viewport the header
 *   is a wide floating bar rather than an unbounded 2500px stadium. Under
 *   1472px of viewport it never binds, which is every viewport in the capture
 *   ladder.
 *
 * --hdr-pill-blur  26px -> 30px. Bought deliberately to pay for the tint drop
 *   below: at 34% the pane passes far more of the backdrop, and what keeps it
 *   reading as FROST rather than as a tinted transparency is that what it
 *   passes is more thoroughly smeared. Rendered side by side on /art/, blur 26
 *   at tint 34 lets the linocut's cut lines through almost crisply — a veil
 *   over a picture; 30 turns the same ground into soft tonal shapes.
 *
 *   AND #1464's "the radius is nearly free" DOES NOT SURVIVE THE SIZE CHANGE —
 *   this is the one number in this ticket that contradicts an inherited
 *   finding, so it is written down loudly. A backdrop-filter is billed by
 *   AREA, and the pill's area went 1080x50 -> 1408x64, i.e. 2.7x. Re-measured
 *   on the pill's own worst-case scroll shape (§ #1483 in the design note):
 *
 *       before, 1080x50 @ blur 26      1041 ms   0 frames > 20 ms
 *       new size, 1408x64 @ blur 26    1223 ms   0        (+17.5%, the AREA)
 *       new size, 1408x64 @ blur 30    1298 ms   0        (+24.7%)
 *       new size, 1408x64 @ blur 34    1356/1369 ms  0 then 1 frame > 20 ms
 *
 *   So the radius now costs ~2% per pixel instead of ~0.1%, and 34 was the
 *   first configuration in this ticket to drop a frame at all. 30 is where the
 *   look stops improving and the bill has not started: it is visually
 *   indistinguishable from 34 in a side-by-side and 5% cheaper, with zero
 *   frames over 20 ms. Do not raise it further without re-running the trace.
 *
 * ── #1485: --hdr-pill-max 1440px -> var(--col) ──────────────────────────────
 * Eric: "the default global header width is still full width, and the floating
 * one now has strange side padding." Both halves of that are one bug, and it is
 * not the outside gap it looks like — it is that THE GLASS AND THE CONTENT IT
 * HOLDS WERE SIZED BY DIFFERENT RULES. The pill was viewport-minus-16 capped at
 * 1440; .wrap inside it is var(--col) centred. Two independently centred boxes,
 * so the padding between them is an accident of the viewport. Measured on /ux/:
 *
 *     viewport   pill      gap outside   wordmark inset FROM THE PILL'S EDGE
 *     1280       1248       16            108
 *     1440       1408       16            188
 *     1512       1440       36            204      <- Eric's screenshot
 *     1920       1440      240            204
 *
 * The inner number is the complaint: at 1512 the pill carried 204px of empty
 * glass before its own logo and another 204 after its nav, and that figure
 * CHANGED AS YOU RESIZED. The outside gap is the smaller half and only goes
 * arbitrary above 1472 (36/80/144/240 — matching nothing, not --gut, not the
 * inset).
 *
 * Setting the cap to var(--col) makes the pill's box IDENTICAL to .wrap's box
 * at every viewport >= --col + 2*--hdr-pill-inset. So:
 *
 *   - the inside gutter is exactly --gut (44px) on every screen, by
 *     construction, and can never drift again;
 *   - the pill's ends land on the page's own content column — the same edge the
 *     essay banner, the work grid and every section align to — instead of
 *     landing nowhere;
 *   - the type still does not move (#1483's win is untouched: pill and .wrap
 *     are both centred and now literally the same box);
 *   - AND IT ANSWERS "THE DEFAULT IS STILL FULL WIDTH" honestly, because at
 *     rest there is no box: what the eye reads as the header's width at rest is
 *     the wordmark-to-nav span, i.e. --col. The pill now narrows onto exactly
 *     that. #1483 made the GLASS nearly full-bleed, which matched the invisible
 *     band rather than the visible content, and that mismatch is what read as
 *     "strange padding".
 *
 * Below --col + 2*inset the pill is 100% - 2*inset exactly as #1483 left it
 * (992 at 1024, 736 at 768, 355 at 375) — that branch is unchanged.
 *
 * --hdr-pill-grain-size  NEW, and it exists because --hdr-grain-size is SHARED
 *   with the at-rest band's mask (#1480 §11.1 noted the sharing and left it).
 *   #1485 needed a much larger tile on the pill — 620px instead of 200px, which
 *   is above the 431px asset's native width, so the texture upscales into slow
 *   soft undulation instead of fine sand ("more like wavy glass", Eric's own
 *   words) — and the at-rest band must not move. Same lesson as
 *   --hdr-pill-tint: a pill dial that is secretly a shared dial is a trap.
 *   Rendered ladder (light, /, 1440@2x, 3x zoom on the pane): 200px is sand at
 *   any amplitude; 440px mottles; 620px is the first size that reads as waves;
 *   820 and 1400 are wavier but at 1120px of pill the tile stops repeating
 *   enough to read as a material and starts reading as blotches. */
--hdr-pill-pad:15px;--hdr-pill-inset:16px;--hdr-pill-max:var(--col);--hdr-pill-blur:30px;--hdr-pill-grain-size:620px;
/* ── #1486: --hdr-label-scrim — the glass got more transparent, so the TYPE
 *    grew its own ground ─────────────────────────────────────────────────────
 *
 * #1485 §13.8 q2 predicted this exact moment and named the fix: "if Eric asks
 * for more transparency again, the lever is the GROUND, not the glass … a
 * scrim behind the nav labels". This is that scrim. It exists because the
 * binding constraint on --hdr-pill-tint stopped being the material (the
 * two-pane fragmentation floor) and became CONTRAST — measured, the pill
 * cannot go below 52%/50% without dropping /lab/ dark under AA, and it can go
 * to 44%/42% WITH this.
 *
 * WHY A TEXT-SHADOW AND NOT A SECOND LAYER ON THE PILL. A scrim drawn as a
 * box — another fill behind the nav, or simply a higher --hdr-pill-tint —
 * would spend exactly the transparency this ticket just bought. The two are
 * different problems: the GLASS should pass more of what is behind it
 * everywhere, and the TYPE needs a ground only where it happens to sit. A
 * shadow is shaped like the glyphs, so it is the only scrim that costs
 * nothing on the 22 of 24 surfaces that never needed one.
 *
 * WHY var(--bg) IS THE WHOLE MECHANISM, in one token, with no dark override.
 * --bg is near-white in light and near-black in dark, and the failure cases
 * are mirror images: in light, dark labels over a bright-but-busy ground; in
 * dark, white labels over a bright photograph. A halo the colour of the
 * theme's own page ground pushes the pixels immediately around each glyph
 * back TOWARD that ground in both directions at once. Its second property is
 * the important one: where the ground is already near --bg — which is every
 * calm section wash, i.e. most of the site — the halo is INVISIBLE, because
 * it is painting the colour that is already there. It self-targets. Nothing
 * had to be conditioned on a template or a section.
 *
 * THE FOUR STOPS ARE TWO JOBS, and were landed separately on measurement:
 *   1px @100% + 3px @92%   the scrim proper. The ring of pixels touching the
 *                          glyph becomes the page ground, so the letterform's
 *                          own edge contrast is the theme's, not the pill's.
 *   7px @70% + 13px @45%   a pool between and around the words. Without this
 *                          stop the *glyph-adjacent* ratio recovers ~1.1 but
 *                          the whole-label-box ratio recovers only ~0.5, i.e.
 *                          the fix reads as real to the eye and not to the
 *                          conservative sampler. The 13px stop is what makes
 *                          BOTH measures clear AA (see the design note §14.4).
 *
 * NOT the wordmark/logomark: measured at the shipped 44%/42%, the mark's worst
 * ground is 8.33:1 (/art/ light) against a 4.5 floor and a 3:1 non-text floor.
 * It does not need one, and giving it a drop-shadow would mean composing with
 * the #1456 invert(1) chain for no legibility gain.
 * NOT the active nav pill: it carries its own opaque accent fill, and a halo
 * inside a filled lozenge is a visible artefact. Turned off explicitly.
 * NOT at rest: the at-rest band has been out of scope since #1464 and its
 * labels measure 9:1+; the rule is scoped to .bar.big.shrunk. */
--hdr-label-scrim:0 0 1px var(--bg),
                  0 0 3px color-mix(in srgb,var(--bg) 92%,transparent),
                  0 0 7px color-mix(in srgb,var(--bg) 70%,transparent),
                  0 0 13px color-mix(in srgb,var(--bg) 45%,transparent);
--disp-weight:700;--disp-track:-.028em;

/* ── THE TYPE LADDER ─────────────────────────────────────────────────────────
 * #1484 phase 1. The audit (plan/design/1482-typography-proposal.md §1b)
 * measured 55 distinct type styles across 32 distinct sizes on seven pages,
 * with Instrument Sans alone painting at 11/11.5/12/12.5/13/13.5/14/14.5/15/
 * 15.5/16/16.5/17/18/19/26. Half-pixel neighbours are not levels of hierarchy;
 * they are three files written on three days. That pile — not the font pairing
 * — is what reads as "chaotic and thoughtless", and no font swap touches it.
 *
 * ONE GEOMETRIC LADDER, ratio 1.2, anchored at 16px:
 *
 *   11 · 13.5 · 16 · 19 · 23 · 28 · 33 · 40 · 48 · 58 · 69 · 83 · 99 · 118
 *
 * Fourteen rungs. The first eight are static text tokens (--t-label .. --t-max)
 * because body copy must not move with the viewport. The upper six are only
 * ever reached through a clamp, so they are delivered as the eight named
 * display tokens below, each of which interpolates between two rungs OF THIS
 * SAME LADDER — there are no numbers in the display range that are not on it.
 * (16 × 1.2ⁿ = 11.1, 13.3, 19.2, 23.0, 27.6, 33.2, 39.8, 47.8, 57.3, 68.8,
 * 82.6, 99.1, 118.9 — the rounding is to the nearest half pixel, and 118 is
 * where the existing hero ceiling already sat, which is why the anchor is 16
 * and the ratio is 1.2 rather than something imported from outside.)
 *
 * WHY 14 AND NOT THE AUDIT'S SUGGESTED 7. §1b proposed a 7-rung ladder as a
 * default. Seven is right for the TEXT range and that is what the eight static
 * tokens nearly are (--t-max is the hinge into display). It is wrong for the
 * whole site, because this site has a 118px hero, a 99px section numeral and
 * an 11px eyebrow on the same page: one ladder of seven rungs spanning 11→118
 * needs a ratio of 1.49, at which body copy and its own caption are two steps
 * apart with nothing usable in between. Extending the SAME ratio upward instead
 * of coarsening it is what keeps 13.5 and 16 both available and still stops
 * 14 / 14.5 / 15 / 15.5 from existing.
 *
 * ROLE, NOT NEAREST NUMBER. A declaration is moved to the rung its ROLE sits
 * on, not to whichever rung is arithmetically closest — that rule is what
 * stopped this pass from re-creating the pile under new names. Labels and
 * eyebrows are --t-label; captions, bylines, nav, buttons, chips and a card's
 * supporting line are --t-meta; paragraphs are --t-body; decks, reading prose
 * and card titles are --t-lead; and so on up.
 *
 * COLOUR IS STILL NOT A LEVER (the theme's standing rule, restated by #1450).
 * Everything below is family, weight and size only. */
--t-label:11px;      /* eyebrows, micro labels, tags, uppercase caps          */
--t-meta:13.5px;     /* captions, bylines, metadata, nav, buttons, card copy  */
--t-body:16px;       /* body copy, form controls, list copy                   */
--t-lead:19px;       /* decks, reading prose, card titles, h4                 */
--t-title:23px;      /* h3, card headline, product name and price, blockquote */
--t-head:28px;       /* feature titles, pull quotes at rest                   */
--t-sub:33px;        /* h2, article prose h2                                  */
--t-max:40px;        /* big numerals, the hinge into the display range        */

/* The display continuation. Every endpoint is a ladder rung; only the growth
 * rate differs, and it differs because these headlines have different measures
 * and different jobs, not because they were written on different days. */
/* THE HERO CEILING, RE-MEASURED AGAINST PLEX SERIF'S OWN ADVANCE WIDTHS (#1484
 * phase 2, Eric's decision #2). ~104px was an estimate made against Source
 * Serif's metrics in the proposal doc; the brief said not to paste it in, and
 * it turns out the premise underneath it was wrong too.
 *
 * WHAT THE PROPOSAL GOT WRONG: it said Plex makes "I design tools people have
 * to use." wrap to two lines "where it currently fits comfortably". It does
 * not currently fit. Measured on the live page, that line needs a font-size of
 * 70.9px to sit on ONE line inside the h1's 1032px column at 1440 — it has
 * been two lines at every display size this site has ever shipped, in both
 * faces. A one-line claim is not on the table at any hero size, so it cannot
 * be the thing the ceiling is protecting.
 *
 * WHAT ACTUALLY BREAKS. The headline is five stacked lines and the h1 reserves
 * the height of the TALLEST, so the number that matters is where an EXTRA line
 * appears anywhere in the set. Line-box counts, live, 1440, same layout:
 *
 *     Source Serif 4 @118   2 / 1 / 1 / 2 / 2
 *     IBM Plex Serif @118   2 / 2 / 1 / 2 / 2   <- "I make chaos legible."
 *     IBM Plex Serif @113   2 / 2 / 1 / 2 / 2      breaks in two
 *     IBM Plex Serif @112   2 / 1 / 1 / 2 / 2   <- the threshold
 *
 * So the real ceiling is 112px, not 104, and the line at risk is the short
 * punchy alternate rather than the claim.
 *
 * WHY 99 AND NOT 112. 99 is the ladder rung (16 x 1.2^13 = 99.1); 112 is not a
 * rung, and spending the ladder's discipline on the single loudest element on
 * the site is exactly the failure this ticket exists to remove. It also buys
 * 13px of headroom that is worth having, because these five lines are a
 * CUSTOMISER FIELD (ericville26_headline_lines) — the constraint is content
 * that can change without touching code, so sitting one clear step under the
 * measured threshold is the same margin discipline #1480/#1485 used on the
 * header glass. Verified at 99: 2 / 1 / 1 / 2 / 2 at 1440, 1280 and 1024, and
 * no overflow at any viewport.
 *
 * The 8.6vw growth rate is unchanged, so the ceiling now binds from ~1151px of
 * viewport instead of ~1372 — i.e. the hero stops growing at the content
 * column rather than 250px past it, which is a small gain rather than a cost. */
--t-d-hero:clamp(48px,8.6vw,99px);    /* the home hero, the one loudest thing */
--t-d-page:clamp(48px,6.6vw,83px);    /* a page-owning h1 (home-hero variant) */
--t-d-claim:clamp(33px,5vw,58px);     /* /ux/'s statement head, 17ch measure  */
--t-d-head:clamp(40px,4.6vw,58px);    /* h1 and the article head              */
--t-d-numeral:clamp(58px,7.2vw,83px); /* /ux/'s method counter (decorative)   */
--t-d-sect:clamp(33px,3.4vw,48px);    /* commerce page heads                  */
--t-d-card:clamp(28px,3.2vw,40px);    /* flagship card titles, CTA heads, stats */
--t-d-quote:clamp(23px,2.7vw,33px);   /* pull quotes                          */

/* Tracking, one value per rung. The audit's bug #2 was ONE role — Source Serif
 * 40px/600 — declared in two files with -.024em (-0.96px) and -.025em (-1px).
 * Both were the same intent typed twice; there is now one number and both
 * consumers (/ux/'s flagship card title and its CTA head) read it from here. */
--tr-label:.09em;    /* the uppercase rung, and the ONLY positive tracking    */
--tr-ui:-.005em;     /* buttons, nav, small sans controls                     */
--tr-crumb:.01em;    /* breadcrumb rails (the only positive tracking off --tr-label) */
--tr-lead:-.018em;   /* --t-lead serif titles                                 */
--tr-title:-.02em;   /* --t-title serif titles and pull quotes                */
--tr-head:-.025em;   /* --t-head / --t-sub / --t-d-card serif heads           */
--tr-stat:-.035em;   /* big sans numerals (.stat, /ux/'s figures)             */

/* ── the section-header ladder ───────────────────────────────────────────────
 * #1450. Eric on the shipped values: "way too small; stylistically cool but
 * functionally ineffective ... also need clearer differentiation between H2
 * and H3."
 *
 * Both halves of that were true, and they had the same cause. Every landing
 * page wrote its own copy of the same declaration — sans / 14.5px / 700 —
 * once in v26-components.css for .shead h3 and again in v26-lab.css,
 * v26-shop.css, v26-art.css and v26-ux.css to promote h2 to the same look.
 * Five copies of one number is why the group header sat BELOW body size
 * (14.5 < 16px) and why the sub-group label under it (13px) landed within
 * 1.12x of its own parent. A section header a reader has to hunt for is not
 * doing the one job it has.
 *
 * THE FIX IS A RATIO, NOT A NUDGE. Two rungs, defined once, here:
 *
 *   --shead-*      the GROUP header  (Projects, Essays, The work, Where to
 *                  find it) — whatever tag the document outline gives it.
 *   --shead-sub-*  the SUB-GROUP label nested inside one (Tools, Machines).
 *
 * 26/16.5 = 1.58, plus a 700->600 weight step. For scale, the base ladder
 * steps h1->h2 at 1.76 and h2->h3 at 1.42, so 1.58 is not a new idea imported
 * from outside — it is this family's own interval, and it puts the pair
 * unambiguously on different rungs.
 *
 * COLOUR IS NOT A LEVER. The standing rule in this theme is that hierarchy
 * comes from family, weight and size; both rungs are var(--ink), so the
 * distinction survives dark mode, a coloured wash and a greyscale print.
 * Family stays sans on both — sans is what marks *structure* here, and serif
 * is reserved for the names of content (card titles, article heads). Spending
 * the family axis between h2 and h3 would break that larger contract to solve
 * the smaller problem, so size and weight carry it instead.
 *
 * The clamp is written as vw+px rather than plain vw so the curve is flat at
 * both ends: 22px at 375 and 768 (where 26px would push "From the Maker
 * Community" to three lines), 24px at 1024, 26px from ~1183 up. The sub rung
 * is a flat 16.5px — it is a label, and a label that scales is just noise
 * moving. 16.5 is off-grid on purpose, like the 13.5/14.5 it replaces.
 *
 * WHAT THESE DO NOT TOUCH: the article scale. css/v26-article.css sets
 * .ev26-article .prose h2/h3 to its own 31/23px and uses --lh only for the
 * MARGINS around them, so it shares no token with this ladder and no essay,
 * case study or project page moves. That separation was checked before these
 * were added, and it is the reason they are named --shead-* rather than
 * --h2-* : these are the section-header COMPONENT's numbers, not the
 * document's.
 *
 * ── #1484: RE-BASED ONTO THE LADDER, RATIO DISCIPLINE INTACT ────────────────
 * #1450's numbers (26 / 16.5) were correct but they were their own scale: 26
 * is not a rung, 16.5 is not a rung, and the pair therefore could not be
 * checked against anything. Both endpoints now sit ON the ladder, and the
 * ratio #1450 fought for gets BIGGER, not smaller:
 *
 *   group header   26   -> --t-head 28   (ceiling)   floor 22 -> --t-title 23
 *   sub-group      16.5 -> --t-body 16
 *   ratio at 1440  1.58 -> 1.75          at 375  1.33 -> 1.44
 *
 * plus the same 700->600 weight step, untouched. The clamp keeps #1450's curve
 * shape for the same reason it had it — flat at both ends, 23px at 375 AND at
 * 768 (where the ceiling would push "From the Maker Community" to three
 * lines), reaching 28px from ~1183 up — only re-solved for the new endpoints:
 * slope 5px over 415px of viewport = 1.2vw, intercept 23 - 1.2% × 768 = 13.8. */
--shead-size:clamp(23px,1.2vw + 13.8px,28px);
/* #1484 phase 2, Eric's decision #1: the group header is SERIF at 600, so the
 * site has ONE heading voice and the sans is reserved for UI, labels and
 * metadata. (See the family change in v26-components.css -- the weight lives
 * here because it is a rung property.) The SUB rung deliberately stays sans:
 * it is a LABEL dividing a group ("Tools", "Machines"), and a label is exactly
 * what decision #1 assigns to the sans. #1450's worry that family must not be
 * the lever BETWEEN the two rungs is answered rather than broken -- size still
 * carries 1.75x and the weight step is still there; the family is now carrying
 * a THIRD distinction on top, not replacing one. */
--shead-weight:600;--shead-track:-.022em;--shead-lh:1.14;
--shead-sub-size:var(--t-body);
--shead-sub-weight:600;--shead-sub-track:.012em;--shead-sub-lh:1.2;

/* ── the top accent wash: one strength dial per section ─────────────────────
 * #1458. The wash layer itself is a SINGLE rule (html body::before in
 * v26-base.css) that takes its hue from --accent, so every section is warmed
 * by its own colour without the layer being written more than once. This is
 * the one number that cannot be shared, and here is why.
 *
 * A wash is an alpha, but what the eye judges is the RESULT — how far the top
 * of the page has moved away from its own ground. Home's amber at 26% over
 * warm paper is a glow. The same 26% of #1A56DB is a materially bigger move,
 * because that blue is both darker (oklab L .505 vs amber's .563) and far more
 * chromatic (C .212 vs .125): it lands at dC +.047 against home's +.034 and
 * reads as a page that has been tinted rather than lit.
 *
 * So the alphas below are not tuned by eye to five different targets — they
 * are solved so that every section makes the SAME move as home does. Matched
 * on chroma gain, which is the axis that decides between "lit" and "tinted"
 * (a lightness drop just reads as shading, which is what a wash is):
 *
 *   home  #A8620E  26%   (the reference: dC +.0338, dL -.105)
 *   art   #DB4A38  23%   vermilion carries more chroma per unit alpha
 *   shop  #0E8C9E  26%   teal is the least chromatic accent; parity with home
 *   ux    #1A56DB  20%   the outlier, per above
 *   lab   #0AA06E  20%   jade, and the :root default (see below)
 *
 * DARK NEEDS NO SECOND SET, and that is a finding rather than an omission.
 * The dark accents are already luminance-normalised against each other — they
 * are the light tints of each hue (#7CA6F5, #F2887A, #4FC0D2, #3ECF98), all
 * within oklab L .727–.769 and C .104–.146 — so the chroma-matched solution in
 * dark is 22–32% for every section, i.e. flat. The light-mode numbers all sit
 * at or under their dark targets, so re-using them in dark errs quiet, never
 * heavy. Verified per section rather than assumed.
 *
 * The :root value is LAB's. header.php makes "lab" the fallback section for
 * every page that is not home/ux/art/shop, so lab has no [data-section] block
 * of its own and inherits the root accent — that is pre-existing and correct,
 * and inventing a fifth accent for it would have been a change nobody asked
 * for. The wash follows the same rule: lab's strength IS the default. */
--wash-a:20%;
}
/* Home is its own identity, not a borrowed section accent. It used to default
   to Lab jade, which is why the whole front page read teal-and-green. Warm
   amber sits away from ux blue, art vermilion and shop cyan, and reads as the
   front door rather than as one of the sections. */
/* Home also owns its GROUND, not just its accent.
 * The warm wash used to be the only warm thing on the page: it faded out into
 * the cool #F2F4F2 every other page uses, so the middle of the front page was
 * a tan-to-cold-grey transition that read as dust, and the six work cards sat
 * on a cool ground under a warm hero. Home's ground is warm paper; the wash is
 * just a warmer concentration of it at the top. Because every raised surface
 * is now derived from the ground rather than fixed, this ONE token moves the
 * cards, the wells, the badges and the footer with it — which is the whole
 * point of the change. Same chroma budget as the cool neutral, opposite side
 * of the hue circle. */
/* Home accent was a vivid amber (#A8620E); desaturated toward a muted clay/
   taupe at the same lightness and roughly hue, per Eric's "something slightly
   more neutral" -- less saturated, not a different colour family. */
[data-section="home"]{--bg:#F4F2ED;--accent:#8C6B4F;--accent-soft:#EFE7DD;--wash-a:26%}
/* UX blue nudged toward cerulean: hue shifted from ~221deg to ~207deg at the
   same saturation/lightness (R down, G up slightly, B roughly held), per
   Eric's "slightly less red in it". */
[data-section="ux"]{--accent:#0F64DB;--accent-soft:#E4EBFB;--wash-a:20%}
[data-section="art"]{--accent:#DB4A38;--accent-soft:#FBE8E4;--wash-a:23%}
[data-section="shop"]{--accent:#0E8C9E;--accent-soft:#E0F1F3;--wash-a:26%}
[data-theme="dark"]{
--bg:#0F1211;
/* Same relationship, inverted materials. Light still lifts (white at a low
   alpha over near-black lands on the old #171B19), shadow still recedes (black
   at 34% over #0F1211 lands on the old #0B0E0D), and --shade goes fully black
   because a cast shadow on a dark ground has nowhere else to go. No second set
   of component rules — only these three numbers change. */
--lift:#FFFFFF;--lift-a:5%;
--recess:#000000;--recess-a:34%;
--shade:#000000;
/* #1500 — same accent-aligned tint as the light neutrals above, mirrored.
   --accent below is THIS block's own dark-mode default; every
   [data-theme="dark"][data-section="X"] override further down still wins
   per-section since these are read at point-of-use, not baked in here. */
--ink:color-mix(in oklab,#EDF1EE 96%,var(--accent) 4%);
--text:color-mix(in oklab,#BFC6C1 95%,var(--accent) 5%);
--muted:color-mix(in oklab,#8B938D 93%,var(--accent) 7%);
--faint:color-mix(in oklab,#69716B 91%,var(--accent) 9%);
--line:color-mix(in oklab,#232825 94%,var(--accent) 6%);
--line-strong:color-mix(in oklab,#333A36 92%,var(--accent) 8%);
--edge:color-mix(in oklab,#333A36 92%,var(--accent) 8%);
--accent:#3ECF98;--accent-soft:#12241D;--on-accent:#0F1211;--grain-op:.06;
/* The glass material flips with the theme, the alphas do not. See the header
   glass note above. */
--frost:#181D1C;--hdr-hi:11%;
/* Grain in dark: same flip body::after already makes. multiply on a near-black
   ground subtracts light that is not there and the texture disappears, so the
   grain becomes an overlay and lifts instead. Amplitude drops with it — an
   overlay on near-black is a much louder operation than a multiply on paper,
   which is why body::after runs .12 light / .06 dark. Same ratio here. */
/* The BAND's material is not --frost. In dark, --frost is #181D1C, a dark
   pane chosen in #1432 to ATTENUATE photography sliding under the pill. The
   band has no attenuating job — there is nothing behind it at scroll 0 but
   the section wash — and a dark veil over a near-black ground is invisible,
   which would leave dark mode with no frost at all. Ice on a dark window
   scatters light TOWARD you, so the band's material is a cold pale instead,
   at a much lower alpha (.11 against light's .40) because on a near-black
   ground a little goes a very long way. */
--frost-band:#BFD8DA;--hdr-ice:.11;--hdr-grain:.10;--hdr-grain-blend:overlay;--hdr-grain-tone:none;
/* #1480, the pill's own two dials in dark. See the long note in :root.
   Tint is cut less than light (56 vs 48) because in dark the pane is doing
   legibility work, not decoration — it is the only thing between white 13px
   nav labels and the cream of a linocut. Grain is raised more in relative
   terms (.20 -> .34) because `overlay` on a near-black pane LIFTS, so the
   texture buys back the glassiness the tint could not spend.
   #1483: 56 -> 44, and it is still cut less than light (44 vs 34) for exactly
   the reason above — this pane is the only thing between white 13px labels and
   a cream linocut, so its alpha is a legibility budget, not a look. It was
   spent against the measurement rather than by eye: on the dark ladder
   (/art/ 1440 dark and /shop/ 375 dark, the two hardest grounds) the nav
   label reads 6.29 : 5.81 : 5.51 : 5.27 : 4.97 at 56 : 50 : 46 : 42 : 38, so
   38 is where AA's 4.5:1 stops having a real margin. 44 lands at 5.36:1 —
   a 12-point cut that is unmistakably glassier and still ~1.2x the floor.
   #1485: 44 -> 50, and in dark this buys transparency rather than spending it.
   The grain layer here is an `overlay` LIFT painted at (1 - pane alpha) onto a
   near-black pane, so cutting --hdr-grain .32 -> .17 takes pale OFF the glass;
   six points of extra tint is less pale than the veil it replaced. Measured
   pane luminance drops on every dark surface (/lab/ 48.0 -> 43.2, /art/ 67.9
   -> 64.8, /essay/ 35.7 -> 29.7) while the worst nav label IMPROVES 4.32 ->
   4.54:1 (/lab/ dark, white labels over a bright photograph — still the
   binding case in dark, and now the binding case for the whole change). See
   the #1485 block above --hdr-pill-tint in :root for the compositing reason. */
--hdr-pill-tint:42%;
}
[data-theme="dark"][data-section="home"]{--bg:#121110;--accent:#C9A379;--accent-soft:#241D14}
[data-theme="dark"][data-section="ux"]{--accent:#6EACF2;--accent-soft:#141B2B}
[data-theme="dark"][data-section="art"]{--accent:#F2887A;--accent-soft:#2A1A17}
[data-theme="dark"][data-section="shop"]{--accent:#4FC0D2;--accent-soft:#0F2429}
