/* The brand's licensed display face, served by Adobe Fonts.

   ⚠ THIS ONE MAY NOT BE SELF-HOSTED, and it is the first exception to the
   rule the other two themes state at length. Adobe's terms require the
   font to be delivered from their CDN with the project's kit; downloading
   the woff2 and serving it ourselves — which is what drga does with
   Gelasio and Nunito Sans, for GDPR reasons — would breach the licence.
   BACKLOG has carried "paid fonts per client, licensing case by case" as
   a deferred question; this is the case, and the answer is that a licensed
   face brings its own delivery and we do not get to choose it.

   What that costs, stated so nobody rediscovers it:
   - A third-party request per visitor, which is exactly the GDPR posture
     self-hosting was adopted to avoid. It is the client's licence and the
     client's decision; their privacy policy has to name Adobe.
   - `font-display` is the kit's setting, not ours — it cannot be reached
     from this file. Adobe ships `auto` by default, which blocks text for
     up to 3s; this kit has been switched to `swap` (verified in the served
     CSS, all eight faces). Check it on every future kit: the default is
     the bad one, and nothing in our code can correct it.
   - The kit is domain-locked. Every domain the site is ever served from —
     the preview, the staging URL, the live one — has to be listed in the
     Adobe project, or the face silently does not load.

   `@import` rather than a `<link>` in the page head, deliberately: the
   markup is shared by every client (PRINCIPLES §1) and must not learn one
   client's font vendor. The cost is a chained request — the browser finds
   this only after parsing the theme file — which is the honest price of
   keeping the whole look in one poured file. The day the admin grows a
   per-theme <head> slot, a preload belongs there. */
@import url("https://use.typekit.net/zay0cfm.css");

/* ==========================================================================
   THEME: INGRADE
   ==========================================================================
   Second theme, and the first dark one: yellow on a near-neutral black, with
   the art made of the whole visible spectrum.

   Professional video and audio calibration — televisions, projectors and
   monitors, measured in the customer's own room. That is a trade whose
   entire product is precision someone else cannot verify by eye, so the
   personality is exact and answerable: nothing decorative, nothing that
   overstates. It is also the reason the theme is the opposite of drga's in
   almost every value — a joiner sells unhurried hands, a calibrator sells a
   number.

   ⚠ EVERY VALUE HERE IS PROVISIONAL UNTIL THE CI LANDS. The colours and the
   faces were chosen for the temperament above, not from a brand book. That
   is deliberate rather than lazy: the theme's job right now is to prove the
   base survives a second brand at all (BACKLOG, "Build the second theme on
   a genuinely two-colour brand"), and it cannot do that while it waits for
   artwork. When the CI arrives it lands in this file and nowhere else —
   which is the claim the whole system is built on, and the cheapest possible
   way to test it.

   Worth saying now, because it will be the obvious question later: a
   calibration brand has an unusually literal palette available to it — a
   neutral greyscale that is genuinely neutral, with one measured accent.
   Whether the CI goes there is a decision, not a default, and this file is
   where it would be made.

   Levers pulled (vs. drga):
   - yellow primary + magenta secondary, near-neutral black surfaces
   - geometric grotesk headings; buttons speak in the body face, not the
     display one (drga makes every CTA a serif brand moment — here they are
     plainly functional, which is the same token proving it works both ways)
   - tighter radii (instrument, not handcraft) and a 2px border
   - denser vertical rhythm, wider container
   - brisk motion: quart easing, shorter travel, less stagger
   - no panels: `--panel-inset: 0` runs every block edge to edge
   - pill buttons (`--radius-button`), bare tracked-out eyebrows, no
     sliding nav line (`data-nav-slider="off"` on the header instance)
   - hover brightens instead of darkening: `primary-700` sits above the
     brand in lightness, because on black a darkening hover reads as the
     light going out
   - the whole gamut: the art is a spectrum rather than a brand colour,
     because a display's gamut is literally what this trade sells
   - the premium layer (section 7): gradient headlines, glass header,
     iridescent button glow, a footer that closes on a rising glow — the
     reference art's vocabulary, switched on entirely through optional
     tokens
   ========================================================================== */

/* Inter is self-hosted for the reason the drga theme records at length: a
   font stack resolves to a different face on every OS, and the whole base
   is built on a page that looks the same everywhere. One variable file per
   subset covers 400–700, so body (400) and bold (600) come from a single
   download. latin-ext carries the Czech diacritics; the vietnamese, greek
   and cyrillic subsets Google offers are dropped — no template needs them
   and an unused subset is bytes a visitor pays for.

   It is the fallback now rather than the body face: Industry took both
   roles when the CI landed. Kept, and kept self-hosted, precisely because
   the licensed face is the one that can fail — see the note at the top. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../assets/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../assets/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* 1. COLOR — yellow on a near-neutral black.
     The first dark theme, which is a much bigger ask of the base than a
     second palette: every surface token flips, and several of them stop
     meaning what their name says. The notes below record what each one had
     to become, because a later reader will otherwise re-derive them.

     The ramp is hand-tuned around the brand colour rather than generated.
     #facc15 is a *light* colour, so it sits at 600 with only tints above it
     and the shades below carrying hover and scrim duty — the scale stays
     ordered, it is simply compressed at the top. Its natural text colour is
     near-black, which is the opposite of every previous theme and is why
     `--color-text-on-primary` is dark here. */
  --color-primary-50:  #fefce8;
  --color-primary-100: #fef9c3;
  --color-primary-200: #fef08a;
  --color-primary-300: #fde047;
  --color-primary-400: #fbbf24;
  --color-primary-500: #facc15;
  --color-primary-600: #facc15;   /* the brand */
  /* 700 is a *surface*: the button's hover, and the colour a link hover
     takes on the page. On a dark theme its direction inverts — a hover
     that darkens gold on black reads as the light going out, so 700 sits
     *above* the brand in lightness and the button brightens under the
     pointer instead. The ramp is ordered by role, not by lightness, and
     this step is where the difference shows. Measured: the dark label
     reads 13.5:1 on it, a hovered link 12.9:1 on the page. 800 and 900
     stay the dark *inks* for the 100/200 chips — that separation is what
     fixed the tags, see the CHANGELOG. */
  --color-primary-700: #ffe566;   /* hover surface + link hover — brighter, not darker */
  --color-primary-800: #6b4a10;   /* ink on the 100 chip — 6.52 */
  --color-primary-900: #3d2a05;   /* ink on the 200 chip — 10.83; and scrims */

  --color-primary: var(--color-primary-600);
  /* Coral — the second brand colour, from the warm end of the spectrum the
     trade actually works in: colour temperature is the thing a calibrator
     measures, and gold-to-coral is that scale made visible. It marks (the
     statistics, the pull-quote's rule) and it joins the glow — the button
     shadows mix it under the gold, which is what makes them iridescent
     instead of monochrome. Reads 7.1:1 on the page and 6.5:1 on the alt
     band, so the mark and the word are one colour and `-strong` repeats
     it. */
  --color-secondary: #e879f9;
  --color-secondary-strong: var(--color-secondary);

  /* Surfaces. `--color-surface-inverse` is the token that stops meaning what
     it says: on a light theme it is "the dark slab", and here everything is
     dark. It cannot become the light slab, because it is what the hero scrim,
     the photo wash and the embed scrim are mixed from — point it at a pale
     colour and every photograph gets washed white under light text. So it is
     the *deepest* black instead, and the consequence is real and recorded in
     the CHANGELOG: an inverse block no longer separates itself from the page
     by contrast. On this theme the band that does that job is the gold one. */
  /* Tells the browser which way this page runs, so the parts it draws
     itself — a select's open list, autofill, the caret, the default
     scrollbar — stop arriving in light mode over a black page. */
  --color-scheme: dark;

  --color-surface:         #000000;
  --color-surface-alt:     #0d0d0f;
  --color-surface-inverse: #000000;

  --color-text:            #f0efeb;
  --color-text-muted:      #9a9892;
  --color-text-on-primary: #1a1705;
  --color-text-on-inverse: #f0efeb;

  /* 2px of this on a black page, which is the reason it is lighter than a
     light theme's border is dark: a hairline drawn in "slightly off the
     surface" disappears entirely on black. */
  --color-border: #2a2a2c;

  /* Feedback states, and the reason the four-token shape survives being
     turned inside out. `-soft` is not "a pale tint", it is "the notice's
     background"; `-strong` is not "a dark colour", it is "text that reads on
     that background". Name a role and it inverts for free — soft becomes a
     dark tint and strong becomes a light text, and no component changed.

     What did need thought is `warning`. It is amber, and on this theme amber
     *is* the brand — a caution notice in gold on a black page is a brand
     mark. So warning leans orange, far enough from #facc15 that the two
     cannot be confused, and the same rule as ever applies: recognisable
     first, brand-tinted second. */
  --color-success:        #4ec27d;
  --color-success-soft:   #11251a;
  --color-success-strong: #8bdaa8;
  --color-success-on:     #06150c;

  --color-warning:        #e8802b;
  --color-warning-soft:   #2a1a0c;
  --color-warning-strong: #f5ab68;
  --color-warning-on:     #1a0d03;

  --color-error:          #ef6257;
  --color-error-soft:     #2b1210;
  --color-error-strong:   #ffa79e;
  --color-error-on:       #1c0604;

  --color-info:           #58b6c9;
  --color-info-soft:      #0e2429;
  --color-info-strong:    #96d6e3;
  --color-info-on:        #05161a;

  /* 2. TYPOGRAPHY — the brand's own display face over a neutral UI sans.
     Industry is the CI's letter and no longer provisional; it replaced
     Space Grotesk, which was standing in for exactly this temperament.

     The fallback is `Inter` rather than a platform sans, and that is a
     decision worth keeping: the kit can fail in ways the self-hosted files
     cannot — an unlisted domain, a blocked third party, Adobe down — and
     when it does, headings land on the face the page has already
     downloaded. No extra request to cover a failure, and a page that
     degrades to one consistent sans rather than to whatever the OS offers.

     Body is Industry too — the kit ships 400 and 600, so the licence
     covers it and the brand speaks in one letter throughout. That is the
     CI's call and it is defensible: a single voice reads as a designed
     site rather than an assembled one. It does carry a cost, and the
     cost is paid in tracking rather than in a second face — see
     `--tracking-body` below. Inter stays behind it as the fallback for
     both roles: the kit can fail in ways self-hosted files cannot — an
     unlisted domain, a blocked third party, Adobe down — and when it does
     the page lands on a face it has already downloaded, one consistent
     sans rather than whatever the OS offers. */
  --font-heading: "industry", "Inter", system-ui, sans-serif;
  --font-body:    "industry", "Inter", system-ui, sans-serif;
  /* Buttons stay in the body face. drga points this at the display serif so
     every CTA is a small brand moment; here the opposite is right — a firm
     whose product is precision sells competence, and a button that performs
     undercuts it. The two themes together are what proves the token is a
     real choice rather than a default nobody moved. */
  --font-button:  var(--font-body);

  /* 600, not the 700 every other theme uses, and the reason is the scale
     rather than the face. At 3.5rem and 6rem a bold Industry stops reading
     as confident and starts reading as loud — the reference language holds
     its display type light and lets size carry the weight. Compared side by
     side at both ends: 700 was heavier than the page's own air, 600 keeps
     the card titles firm (Industry is a sturdy letter even here) while the
     headline turns elegant. */
  --font-weight-heading: 600;
  --font-weight-body:    400;
  --font-weight-bold:    600;

  --text-xs:   0.75rem;
  --text-sm:   0.875rem;
  --text-base: 1rem;
  --text-lg:   clamp(1.125rem, 1.05rem + 0.4vw, 1.25rem);
  --text-xl:   clamp(1.25rem, 1.1rem + 0.8vw, 1.5rem);
  --text-2xl:  clamp(1.5rem, 1.25rem + 1.4vw, 2rem);
  /* The display sizes run well past drga's. The first cut kept them
     "slightly under" out of restraint, and against the reference that
     restraint was the tell that this was a recoloured page: on black,
     type is the architecture — the sections have no boxes left, so the
     scale of the heading is what says a chapter starts here. Section
     titles reach 3.5rem, the hero 6rem; both stay svh-capped in the hero
     by the block's own rules, so a laptop is not asked to pay for it. */
  --text-3xl:  clamp(2rem, 1.4rem + 2.8vw, 3.5rem);
  --text-4xl:  clamp(2.5rem, 1.5rem + 4.6vw, 4.75rem);
  --text-5xl:  clamp(2.75rem, 1.6rem + 5.2vw, 6rem);

  --leading-tight:   1.15;
  /* Looser than every other theme. Light text on black wants more room
     between lines than dark text on white — the glow of each line closes
     the gap optically — and the airiness the reference reads as premium is
     as much line-height as it is section padding. */
  --leading-normal:  1.7;
  /* A grotesk has short extenders and can sit line-on-line where a serif
     cannot; drga needs 1.08 for exactly the opposite reason. */
  --leading-display: 1;

  /* Tracking OPENS here, where every other theme tightens — the exception
     the form itself predicts ("loosen for condensed faces"). Industry is
     squarish and narrow-fitting, so the negative display tracking that
     makes a wide grotesk read as drawn made this one read as compressed:
     the counters closed up and long Czech words turned into blocks.
     Slightly positive lets the letterforms stand as the ones the brand
     chose, and at 6rem the air between them is what reads as premium. */
  --tracking-tight:   0.005em;
  --tracking-display: 0.01em;
  /* And the body, which is the same face doing a job it was not drawn
     for — see the note at `--font-body`. This is the value that buys the
     paragraph back: 0.02em at 16px is about a third of a pixel per letter,
     invisible as spacing and decisive for reading speed. */
  --tracking-body:    0.02em;

  /* 3. SHAPE — engineered, not handcrafted.
     Every step halves drga's: 4/8/12/16 against 4/12/16/24. The 4px grid
     leaves no room between 4 and 8, which is the grid doing its job — a
     radius scale with 6px in it is a scale nobody can reproduce. */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-xl:   16px;
  --radius-full: 999px;
  /* No panels. The blocks run edge to edge as full-bleed bands — the
     screen-like composition the reference art uses, and the opposite of
     drga's inset-slab language. Both tokens go together: an inset of zero
     with rounded corners reads as a rendering fault, and rounded corners
     with no gutter have nothing to round against. */
  --radius-panel: 0;
  --panel-inset: 0;
  /* Every button is a pill — the reference art's control shape. Only the
     buttons: the form stays on `--radius-md`, because a pill textarea is
     broken and a half-pill form reads as two kits (see the note at `.btn`
     in components.css). */
  --radius-button: var(--radius-full);
  /* 1.25, so a 16px label gets a 20px line box — an even number, which is
     the only way half-leading lands the text on the button's centre line
     rather than 0.6px above it. See the note at `.btn`. It also makes the
     label's box exactly the icon's 1.25em, so the arrow and the word are
     the same height in the row. The large button pays 0.5px for it in the
     other direction, which is below what an eye resolves; the header CTA,
     which is on screen at every scroll position, is the one that had to be
     exact. */
  --btn-line-height: 1.25;
  /* And the optical half of the same problem, measured on the capitals
     rather than on the line box: at 20px the labels sat 1.5px above the
     button's centre. 0.075em is that, and it is smaller than the tag's
     0.13em on purpose — a tag is all-caps and uses none of the descent
     below the baseline, a button label is sentence case and uses some of
     it, so it needs less of a nudge. */
  --btn-label-shift: 0.075em;

  /* 2px, where drga is 1px. Borders are the theme's structural voice: a
     hairline suggests, a 2px line states. This is the token most likely to
     find a component that assumed a hairline — which is why it is set here
     rather than left at the safe value. */
  --border-width: 2px;

  /* 4. SPACE & DENSITY — airy. The first cut of this theme went denser
     than drga on the logic that brisk hands keep a tight page; against
     the reference art that read as crowded, not brisk. Premium is space:
     the darkness only reads as depth when blocks float in it, so the
     rhythm now opens *past* drga's (9rem ceiling) rather than under it.
     The ceiling arrives at ~1970px, still above the container width, so a
     block never has full vertical rhythm while its side margins are gone
     (BACKLOG, "Full HD is the width least likely to show a spacing bug")
     — moot at inset 0, but the curve stays honest if a panel ever comes
     back. */
  --space-unit:    0.25rem;
  --section-space: clamp(4.5rem, 2.2rem + 7vw, 12rem);
  /* Narrower than drga's, which is the opposite of what a full-bleed theme
     suggests and is the point: with the panels gone there is nothing to
     hold the eye but the column itself, and a wide measure on a black page
     turns every section into a field of scattered text. The bands run edge
     to edge; the reading stays on axis. */
  --container-max: 72rem;

  /* 5. SHADOWS — the token that a dark theme quietly makes useless. A shadow
     is a darker version of the surface, and there is nothing darker than
     this page: the same layered figures that read as "lifted" on drga's warm
     white are invisible here, and raising the opacity only makes a black
     smudge on black.

     Kept rather than set to `none`, at a higher opacity that does its work
     under things that actually float above the page — the header pill, the
     dialog, the carousel buttons — where the shadow is doing separation
     rather than elevation. Depth between a card and the page it sits on is
     carried by `--color-surface-alt` and the border instead, which is how a
     dark interface has to do it. */
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.45);
  --shadow-md: 0 2px 6px rgb(0 0 0 / 0.45), 0 8px 20px rgb(0 0 0 / 0.40);
  --shadow-lg: 0 4px 12px rgb(0 0 0 / 0.45), 0 20px 48px rgb(0 0 0 / 0.55);

  /* 6. MOTION — brisk hands. Quart rather than expo: expo spends most of
     its time almost still, which is what makes drga read as unhurried, and
     it is exactly what this theme should not read as. Reveals are shorter
     and travel less; the cascade is nearly a single move. Still eased,
     still designed — nothing snaps (PRINCIPLES §12). */
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-spring:    cubic-bezier(0.34, 1.4, 0.64, 1);
  --motion-easing:  var(--ease-out-quart);

  --motion-duration:      160ms;
  --motion-duration-slow: 320ms;

  --reveal-duration:       600ms;
  --reveal-duration-slow:  750ms;
  --reveal-distance:        16px;
  --reveal-stagger:         70ms;
  --reveal-stagger-group:   70ms;
  --reveal-stagger-line:    90ms;

  /* 7. PREMIUM LAYER — the reference art's language: gradient type, glow,
     glass. All of it optional core tokens (see theme-template.css); drga
     and neutral set none of these and render exactly as before.

     Headlines are champagne — white falling to pale gold. Endpoints are
     solid colours on purpose, so they can be measured: the dimmest stop
     is #8fd7ee, which reads 8.0:1 on the page and 7.3:1 on the alt band —
     display type needs 3. The hero title's lines each restart the
     gradient (see the note in components.css), so the sweep is nearly
     horizontal to make that read as intent. */
  /* White holds most of the line and the colour arrives late. The first cut
     put the violet at the midpoint, and a headline whose middle is already
     tinted reads as *dimmer* rather than as coloured — the eye takes the
     whole line's brightness from its darkest half. White now runs to 62%
     and both coloured stops sit in the last third, so the colour is a
     glance off the end of the words rather than a wash over them. */
  --gradient-heading: linear-gradient(100deg,
    #ffffff 0%,
    #fdfcf7 62%,
    #fbeab0 82%,
    #ffd45e 100%);
  --heading-ink: transparent;

  /* The bar hugs its contents: logo, a deliberate 76px of air, the links,
     the button. A rail across the whole page is a navigation *system*;
     this is a small object floating over the page, which is what the
     reference language does and what suits a site with five destinations.
     The distance was walked up rather than guessed — 48 still read as a
     gap between two things standing side by side, 64 was close, and at 76
     the mark stops being the first item in the row and becomes the thing
     the row hangs off. Off the `--space-*` ladder but on the 4px grid,
     which is where the rule actually lives (PRINCIPLES §6): the steps
     jump 64 → 80 and this sits between them on purpose. */
  --header-pill-width: fit-content;
  --header-pill-gap: calc(var(--space-16) + var(--space-3));
  /* A highlight, not an outline — the same 10% gold the glass cards use,
     so every pane on the page catches light the same way. The page's own
     border token is a drawn line: correct on a light page, far too present
     around something meant to be barely there. Brightens as a whole when
     the pointer arrives, which is the bar acknowledging you before any
     single link does. */
  --header-pill-border: var(--card-border);
  --header-pill-border-hover: rgb(250 204 21 / 0.28);
  /* 20px at 16px — an even line box, so the labels land on the bar's
     centre line. The body's 1.7 gives 27.2 and put them 3.2px high, which
     is the sort of thing that reads as "something is off" long before
     anyone can say what. */
  --nav-line-height: 1.25;
  /* 18px at 12px — the even box that puts a tag's word and an eyebrow on
     their own centre line. The body's 1.7 gives 20.4 and both sat 0.7px
     high. Same arithmetic as the two tokens above; a different value only
     because these labels are set at 12px rather than 16. */
  --leading-ui: 1.5;
  /* Optical centring for uppercase labels in a badge. Industry reserves
     4px of descent at 12px that all-caps text never uses, so the word sat
     high inside the tag — measured from the ink, not from the line box,
     which is why an even leading alone did not fix it.

     0.13em, not the 0.2em this started at. The first value was calibrated
     against a sample that happened to carry diacritics ("Návody"), so the
     acute counted into the ink and the correction came out 0.8px too far —
     which then pushed every accent-free label ("HDR") that far below the
     centre. **Calibrate on cap height, never on the ink of one string**:
     accents belong nearer the top edge, that is what they are for, and a
     badge is centred when its CAPITALS are centred. Measured on all four
     tags after: caps 0.02px off, accented or not. */
  --label-ink-shift: 0.13em;
  /* Hover is brighter than the current item, which is the right way round
     on a dark page: the current item states a fact quietly, the pointer
     lights the way. Same shade the buttons brighten to on hover, so the
     whole theme agrees on what "brighter" means. */
  --nav-link-hover-ink: var(--color-primary-700);

  /* The nav marker is a terminal caret, not an underline. It does not
     grow from the left — it appears from just below the word, narrow and
     centred under it, and blinks the way a DOS prompt blinks while it
     waits to be read. On a site whose product is measurement, the
     navigation may as well behave like an instrument.

     1.06s because that is twice Windows' own caret blink interval (530ms),
     which is the rhythm anyone who has ever waited at a prompt already
     has in their hands. `steps(1, end)` keeps the edges hard — a caret
     that fades in and out is a glow, not a cursor. */
  --nav-marker-width: var(--space-3);
  --nav-marker-rest-width: var(--space-3);
  --nav-marker-align: auto;
  --nav-marker-rest-opacity: 0;
  --nav-marker-rest-translate: 0 var(--space-1);
  --nav-marker-animation: nav-caret-blink 1.06s steps(1, end) infinite;
  /* Down off the word. Tight under the letters the caret reads as an
     underline; with air between them it reads as a mark sitting on the
     line below, which is where a prompt actually waits. Costs the link
     no height — see the note in motion.css. */
  --nav-marker-offset: var(--space-1);
  /* `auto` on the left rides the links right, against the button; the
     16px on the right is the air between the last link and it. Without
     it they meet with only the link's own padding in between, and the
     run of words reads as if it were part of the button. */
  --header-nav-margin: auto var(--space-4);

  /* Liquid glass. The pill and the fullscreen menu wash are translucent
     with the page blurred through them. The alpha is set against the
     *lightest* thing that can pass underneath — the gold CTA band and the
     hero glow, not the black page at rest: nav text over 72% black glass
     on solid #facc15 still measures 7.2:1. Err high; glass that fails does
     so on exactly the band a visitor is most likely to be reading over. */
  --glass-surface: rgb(0 0 0 / 0.72);
  --glass-wash: rgb(0 0 0 / 0.8);
  --glass-filter: blur(20px) saturate(140%);

  /* **The primary button is glass with the brand tinting it, not a slab of
     brand.** It was a solid gold pill with a sheen — a lit object, and it
     worked, but on a page whose whole art is light passing through things
     it was the one element that light stopped at. Glass belongs to this
     theme already: the header pill, the fullscreen wash and the cards are
     all the page seen through something. The button was the exception, and
     an exception is what it read as.

     The tint is gold at 16% rather than a neutral grey, because the tint is
     the only thing left saying which button this is. Under it the page
     shows through blurred; over it a sheen off the top edge, which is what
     stops a translucent surface reading as a hole.

     16% is a measured ceiling, not a taste: it is the most tint the label
     survives on the brightest thing this page can put behind a button —
     the hero's lamp at full breath. See the ink note below. */
  /* **The four hues light is made of in this theme, named once.** The
     hero's lamp passes through them in this order, and every glow on the
     page — the buttons below, and anything that lights up later — is built
     from the same four in the same order, which is what makes a page of
     separate effects read as one material.

     They are named for their place in a glow rather than for their colour,
     and that is deliberate: retint the brand and these four are the whole
     of what has to move. The literals are here, not scattered through
     `box-shadow` values where a hue change would mean finding all of them.
     `--glow-fringe` is the one that belongs to no ramp — it is the violet
     the hero's spectrum reaches at its far end. */
  --glow-edge:   var(--color-primary-700);
  --glow-core:   var(--color-primary);
  --glow-mid:    #ffd93d;
  --glow-fringe: #ff5a1f;

  /* **Solid gold, and a warm halo — restored on 2026-07-30 at Dissi's call
     after the liquid-glass version was seen next to it.** Kept as a note
     rather than reverted silently, because the glass argument was a good
     one and someone will make it again.

     What glass did: a colourless surface at 5% white over a
     `saturate(200%)` backdrop, so the button took its colour from whatever
     it lay on — violet over the hero's lamp, cyan over the other end,
     nearly clear on the page. Technically the more interesting object, and
     the reason it lost is that it made the primary action the quietest
     thing in the block. A call to action that borrows its colour from the
     background cannot also be the loudest thing on it.

     So the fill is the brand again, and that took no code: the base reads
     `--btn-surface` with `--color-primary` as its fallback and `--btn-ink`
     with `--color-text-on-primary`, so *deleting* the glass tokens is the
     whole restore. Only the glow had to be written back.

     Two things did not come with it, both dropped rather than forgotten:
     `--btn-filter`, because a backdrop blur under an opaque fill is an
     expensive no-op; and the travelling hue ring, because on a gold button
     an `oklch` sweep puts green and cyan around a yellow object twice a
     minute, which reads as a fault rather than as light.

     The glow is written in this theme's `--glow-*` tokens, not in the hexes
     the July version carried (`#f5c451`, `#ef7d5a`). Those were the palette
     before the brand moved to `#facc15`; pasting them back would have
     restored the look and quietly reintroduced two colours belonging to no
     ramp. Same halo, current palette, one place to change it. */
  --btn-sheen: linear-gradient(to bottom,
    rgb(255 255 255 / 0.35),
    rgb(255 255 255 / 0) 55%);

  /* Rest is "felt not seen": a hairline of the brand, a close warm bloom, a
     wider one a step along the spectrum, and a real shadow under it all —
     a lit object still occludes what is behind it. */
  --shadow-button:
    0 0 0 1px color-mix(in srgb, var(--glow-core) 30%, transparent),
    0 4px 18px color-mix(in srgb, var(--glow-core) 28%, transparent),
    0 10px 40px color-mix(in srgb, var(--glow-fringe) 22%, transparent),
    0 2px 6px rgb(0 0 0 / 0.5);

  /* Hover widens and warms it, and the surface brightens with it through
     `--color-primary-700`, which on this dark theme sits *above* the brand
     in lightness. The outermost ring is the one hue belonging to no ramp —
     the far end of the hero's spectrum — because three hues are what make a
     bloom read as light rather than as a coloured smudge. */
  --shadow-button-hover:
    0 0 0 1px color-mix(in srgb, var(--glow-edge) 55%, transparent),
    0 6px 26px color-mix(in srgb, var(--glow-core) 50%, transparent),
    0 12px 52px color-mix(in srgb, var(--glow-fringe) 40%, transparent),
    0 18px 72px color-mix(in srgb, var(--color-secondary) 22%, transparent),
    0 2px 8px rgb(0 0 0 / 0.5);

  /* **The ghost answers by drawing its own outline.**

     The default is a wash of the page's own light — 15% white over the hero
     art — which on a page made of light is the one gesture that says
     nothing: a translucent film over a glow reads as the glow getting
     hazier, not as a control responding.

     So the inside stays transparent and the edge appears. Glow was tried
     here first and it is the primary's language: gold bloom around a lit
     object, which is what a *filled* button is. A ghost button is an
     outline waiting to be drawn — answering with the same bloom made the
     pair read as two lit objects at two brightnesses instead of as two
     kinds of control.

     **`--border-width` is 2px on this theme, and that is why this works
     with no layout shift.** `.btn` already carries a transparent border of
     that width for exactly this purpose, so the hover colours something
     that was always occupying the space. Nothing reflows, nothing moves a
     pixel sideways, and the button is the same size lit as unlit.

     The shadow comes back with it, and only the black one. A bordered
     button is an object again — it has an edge, so it can sit above the
     page and cast. What it does not have is light of its own, which is the
     whole distinction from the primary. */
  --btn-ghost-hover-surface: transparent;
  --btn-ghost-hover-border: var(--color-primary);
  --shadow-button-ghost-hover: 0 8px 24px rgb(0 0 0 / 0.5);


  /* **The page closes on the same light it opens with.** A glow rising
     behind the footer's wordmark, and the colour in it is the hero's
     travelling spectrum rather than a single brand tint — the two ends of
     the page are then the same idea seen twice, which is what makes a site
     feel composed rather than assembled.

     Same three-layer trick as the hero and for the same reason: the band
     multiplies with a radial that decides where light is allowed, so the
     colour exists only inside the glow and reaches black at its edges.

     **The floor under it is opaque, and the first attempt that left it
     fading to `transparent` is worth recording.** `multiply` needs
     something to multiply *with*: where the backdrop has no alpha there is
     nothing to darken the band against, so the spectrum paints at full
     strength. Inheriting the base's seam-free fade therefore turned the
     entire footer into a saturated rainbow slab — the mask was working
     perfectly and had nothing underneath it to work on. The floor now runs
     opaque from the page's own surface colour at the top to a shade deeper
     at the foot, which keeps the join seamless the honest way: by *being*
     the colour above it rather than by disappearing.

     It runs at 36s where the hero runs at 24. The footer is read on the way
     out, often while the reader is already leaving, and matching the hero's
     pace would have the two racing each other in the corner of the eye —
     slower here reads as the same light further away.

     ⚠ The base's warning applies and is why the peak is where it is: the
     footer's muted small print sits *inside* this, no audit can see a
     background-image, and the numbers were measured by hand — rasterising
     the stack and reading pixels under every text band at four phases of
     the walk.

     **The shape rises from the foot, the same as the hero's, and that is
     what let the peak go up rather than down.** A glow centred in the
     middle of the footer put its brightest part exactly where the muted
     tagline and address sit, so every attempt to make it visible failed the
     measurement — 52% peak read 2.47 on the address, 34% still failed at
     0.84 of requirement, and 22% was all that cleared. Anchored at the
     bottom edge the constraint changes shape entirely: the text stops at
     85.7% of the footer's height and the last 84px are padding, so the
     light has somewhere to be full-strength where nothing has to be read
     over it.

     Hence 78% at the foot against the old 22%, over a vertical radius of
     32% rather than 60%: brighter *and* smaller, which is the trade that
     was not available while it was centred. The ramp is steep on purpose —
     the tightest text is the legal row at 82.7%, about 56% of the way out
     along the radius, and the mask has to be down to 0.07 by then. Measured
     across four phases of the walk: twelve bands, no failures, tightest
     5.04 where 4.5 is owed, and the foot itself reads as saturated colour
     while the head is within a few units of the page's own black. */
  /* **A warm glow closes the page again — restored 2026-07-30 at Dissi's
     call.** Read the note above as the history it now is: the flat black
     was the right answer to the *travelling* footer, which broke three
     times and was rightly abandoned. This is not that. It is the static
     radial the theme carried in July, which never broke and was only lost
     as collateral when the spectrum went.

     One difference from the July value, and it keeps the argument the flat
     black won on. That version's floor was `#1c1a17` — a warm dark lifting
     the footer off a `#121110` page. The page is `#000000` now, on the best
     reason available (black level is what this trade calibrates), so a
     floor of its own colour would put a visible warm band where there used
     to be none. **The floor therefore starts as the page's own surface and
     only warms toward the foot**, which is the join being invisible by
     construction rather than by measurement — the same standard the flat
     version set, met by a gradient instead of by a flat colour.

     Hues come from the `--glow-*` tokens rather than the July literals
     (`#f5c451`, `#ef7d5a`), which belong to the palette the theme had
     before the brand moved to `#facc15`.

     ⚠ The base's warning applies and this is the part that is not
     negotiable: the footer's muted small print sits *inside* this and no
     audit can see a background-image, so the peak is a measured number.
     Rasterised the whole stack and sampled the worst pixel under every text
     node in the block — fourteen of them, no failures. The tight end is
     where the glow and the small type meet:

       address row (14px, muted)   5.33   needs 4.5
       tagline (20px, muted)       5.54   needs 4.5
       legal row + its links       6.43   needs 4.5
       contact pills              14.27   needs 4.5

     Under a point of headroom on the address row, which is the number to
     re-derive before the peak, the radius or the floor moves — any of the
     three, and 16% is not a value to nudge by eye. */
  --footer-backdrop:
    radial-gradient(70% 60% at 50% 42%,
      color-mix(in srgb, var(--glow-core) 16%, transparent),
      color-mix(in srgb, var(--glow-fringe) 7%, transparent) 55%,
      rgb(0 0 0 / 0) 78%),
    linear-gradient(to top, #17130b, var(--color-surface) 72%);
  --footer-watermark-color: rgb(255 255 255 / 0.055);

  /* White, and barely there. The default mixes the brand into the surface,
     which is a whisper for a muted primary and a shout for this one — a
     yellow word two hundred pixels tall behind the contact details, easily
     the loudest thing in the block. It is a watermark, so it should read as
     a texture in the surface rather than as the logo printed large: no hue
     of its own, and low enough that the eye finds the phone number first.
     The mask above it already fades the lower half, so this is the value at
     its strongest point. */

  /* The eyebrow sheds its chip: no pill, no dot, just a tracked-out gold
     word. The chip is drga's voice — a made object with an edge; this
     theme's voice is light on black, and a bordered box around ten
     letters reads as UI, not as typography. Wide tracking does the
     "label, not sentence" work the pill used to do. */
  --eyebrow-padding: 0;
  --eyebrow-border-width: 0;
  --eyebrow-surface: transparent;
  --eyebrow-dot: none;
  --eyebrow-tracking: 0.16em;
  --eyebrow-ink: var(--color-primary);

  /* The tag follows the eyebrow, and for the argument stated there.

     The default is a filled badge in `primary-100`, which is a near-white
     in every ramp — quiet as a tint of a light page, and on this one the
     brightest element in the card, louder than the heading above it. It
     read as a UI sticker applied to the design rather than as part of it.

     So it becomes an outline: the brand's own colour at reading strength,
     a hairline of it at a fifth, and the faintest wash inside so the shape
     still holds when several sit in a row. Pill rather than the default
     4px, because this theme's made objects are pills — the buttons say so
     and a small square badge next to them reads as a different kit.

     Measured, because a tinted surface over black is not the black the ink
     was checked against — the 8% wash lifts it to rgb(20 16 2) and the
     hover's 16% to rgb(40 33 3). Ink against those: **12.39 at rest,
     12.76 hovered**, at 12px bold where 4.5 is owed. The hover comes out
     slightly higher rather than lower because `primary-700` is *above* the
     brand in lightness on this theme, which is the same inversion the
     buttons rely on.

     The hover answers with light rather than with a heavier fill — the edge
     goes to 45% and the wash doubles, the word stays put — which is what an
     outlined control has instead of a background to darken. */
  --tag-radius: var(--radius-full);
  --tag-surface: color-mix(in srgb, var(--color-primary) 8%, transparent);
  --tag-edge: color-mix(in srgb, var(--color-primary) 22%, transparent);
  --tag-ink: var(--color-primary);
  --tag-tracking: 0.04em;
  --tag-hover-surface: color-mix(in srgb, var(--color-primary) 16%, transparent);
  --tag-hover-edge: color-mix(in srgb, var(--color-primary) 45%, transparent);
  --tag-hover-ink: var(--color-primary-700);

  /* Pictures need an edge on a dark page — see `--media-edge` in
     components.css for why this is a token and not a fix. White rather than
     the brand: a gold hairline round a photograph reads as a frame someone
     chose, and the job here is only to say where the picture stops. Ten
     percent is enough against black and invisible against a bright
     photograph, which is the correct behaviour in both cases — the line is
     there for the pictures that need it. */
  --media-edge: rgb(255 255 255 / 0.1);

  /* The download card's icon tile. The default is `primary-100` behind
     `primary-700`, which on a light ramp is a dark glyph on a pale tint and
     here was a pale glyph on a near-white square — 1.17:1, invisible. This
     theme's 700 is deliberately *above* the brand in lightness (hovers
     brighten on black), so it can never be an ink on a light surface. Same
     treatment as the tag: a dim wash of the brand with the brand on top.
     Measures **11.97** against the 3 a non-text graphic owes, up from 1.17. */
  --file-icon-surface: color-mix(in srgb, var(--color-primary) 10%, transparent);
  --file-icon-ink: var(--color-primary);

  /* Cards lose their chrome entirely. On a light page a card is a made
     object — border, surface, shadow; in the dark the same data floats:
     icon, title, text, centred on the page's axis. The rounded media
     keeps its shape (the clip stays), so a reference is a floating
     picture with a caption, not a box. Hover answers with a faint gold
     ring instead of the near-white default, which on transparent chrome
     would flash out of nowhere. */
  /* Cards are glass, not boxes and not nothing. The chromeless pass was
     right to delete drga's border and wrong about what replaces it: with
     the page lit from behind, content floating on absolutely nothing has
     no substance, and six weightless columns read as a text document. A
     pane of dark glass with a lit top edge gives the same content a body
     without giving it a box — and it only exists because there is now
     light behind it to catch. The border is a highlight, not an outline:
     brighter on top, fading down, which is how a real edge catches a
     source below it. */
  --card-surface: rgb(255 255 255 / 0.035);
  --card-border: rgb(250 204 21 / 0.1);
  --card-border-hover: rgb(250 204 21 / 0.34);
  --card-filter: blur(14px) saturate(130%);
  --card-shadow: inset 0 1px 0 rgb(255 255 255 / 0.06),
                 0 18px 44px rgb(0 0 0 / 0.45);
  --card-align: center;
  /* Everything else that has an axis takes the same one: the carousel's
     controls, a lone contact form, and the fullscreen menu. A theme that
     centres its sections and then leaves its navigation flush left has two
     axes and reads as unfinished. */
  --carousel-nav-align: center;
  --contact-solo-align: center;
  --form-footer-align: center;
  /* A focused field is lit, not haloed. The default ring is drawn in
     `primary-100` — a near-white, which on this page put a cream band
     around the field and read as a rendering fault rather than as focus.
     A tight gold ring carries the state; the soft outer glow is the
     theme's own light doing what it does everywhere else. */
  --field-focus-ring:
    0 0 0 2px rgb(250 204 21 / 0.38),
    0 0 18px rgb(250 204 21 / 0.22);
  --nav-open-align: center;
  --nav-open-text-align: center;
  /* In the fullscreen menu the colour is the whole signal: no pill behind
     the current item, no caret under it. Five words at display size on an
     otherwise empty screen do not need a second and third way of saying
     which one you are on. */
  --nav-open-link-surface: transparent;
  --nav-open-marker-display: none;

  /* The FAQ takes the same panel language: questions as a stack of glass
     cards, not a list ruled off by hairlines. Pointed at the card tokens
     rather than repeating their values, so "what a panel is" is defined
     once in this file — change the card and the FAQ follows. */
  --accordion-gap: var(--space-3);
  --accordion-rule: transparent;
  --accordion-panel-border: var(--card-border);
  --accordion-panel-width: var(--border-width);
  --accordion-radius: var(--radius-lg);
  --accordion-surface: var(--card-surface);
  --accordion-filter: var(--card-filter);
  --accordion-shadow: var(--card-shadow);
  --accordion-inset: var(--space-6);

  /* The scrim for a lit section — the inverse of the photo default, which
     is built to veil a picture and would erase a glow. Light through the
     middle where the display heading sits (3:1 territory) and firm at the
     edges where the eyebrow and the small print land.

     Measured on the CTA, which carries the strongest glow on the page, by
     compositing both gradients at each text band's actual position rather
     than by eye: eyebrow 10.83 (needs 4.5), heading 6.68 at the gradient's
     dimmest stop (needs 3), lead 4.90 (needs 4.5). The lead is the tight
     one — 0.40 over AA — and it is tight because it sits closest to the
     glow's centre. Any brighter core and that is the number that fails
     first, so re-measure *it* before touching the art. */
  --section-scrim: linear-gradient(
    to bottom,
    rgb(0 0 0 / 0.82) 0%,
    rgb(0 0 0 / 0.62) 26%,
    rgb(0 0 0 / 0.5) 50%,
    rgb(0 0 0 / 0.66) 76%,
    rgb(0 0 0 / 0.88) 100%);

  /* The hero's light breathes. Sixteen seconds and eight percent — slow
     and shallow enough that nobody watches it happen, which is the only
     speed at which a page feels alive rather than animated. */
  /* **The negative delays are load-bearing, not a detail.** These are
     eased and infinite, so at t=0 every one of them is at the slow end of
     its own curve — and t=0 is exactly when the entrance is playing. The
     boot would finish its 1.15s of movement and hand over to three
     animations that had barely started moving, which reads as the page
     seizing up a second after it opens. It was reported twice as "it
     sticks", and this is the second cause; the first was the wash stuck
     on the spring's timing.

     Each offset is roughly a quarter of its own period, which is where an
     ease-in-out is travelling fastest. So the glow is already in full
     motion at the moment the entrance lets go of it, and nothing on the
     page is ever still.

     13 and 24 — the breath eased and turning, the spectrum linear and
     never turning. They share no useful factor, so the shape is a
     different size at every point of the colour's pass and the pair never
     settles into a loop the eye can learn, which is the whole difference
     between this and a screensaver. */
  --hero-ambient:
    hero-glow-breathe 13s ease-in-out -3.25s infinite alternate,
    hero-rainbow 24s linear -6s infinite;

  /* **The glow stops growing at Full HD.** Its horizontal radius was a
     percentage of the block, so on a 21:9 or 32:9 desktop the lamp grew
     with the furniture: at 3440px the lit area was 1.8× the width it has
     on the screen it was composed on, and a source that spans the whole
     wall stops reading as a source. A light has a size; a background does
     not, and that is the difference this token defends.

     `--glow-basis` is one per cent of the viewport, but never more than
     one per cent of Full HD. Multiply it by any of the figures below and
     the glow is identical to itself up to 1920px and frozen in absolute
     pixels beyond it — no breakpoint, so nothing jumps at a threshold,
     and the breathing survives at every width because it scales the
     basis rather than being clamped against it.

     `vw` rather than `%` because the two operands of a `min()` in a
     gradient radius have to be the same kind, and `%` there means the
     box while `px` means itself. The hero is full-bleed, so `vw` and the
     box agree within the width of a scrollbar. A hero that ever stops
     being full-bleed is the one thing that would make this drift. */
  --glow-max-width: 1920px;
  --glow-basis: min(1vw, calc(var(--glow-max-width) / 100));

  /* The lamp's resting size — the bloom that carries the colour, and the
     white source at its foot. The first pair is multiplied by
     `--glow-breath` at the point of use, which is the only thing the
     animation touches; the source does not breathe, because a light that
     changes size at its centre reads as a flicker rather than as a
     breath. `*-spread` values are numbers of basis units (so the cap
     applies); `*-rise` values are percentages of the block's height. */
  --glow-spread: 86;
  --glow-rise: 84%;
  --glow-core-spread: 26;
  --glow-core-rise: 19%;
  /* Nearly the whole screen. The default deliberately leaves a slice of
     the next block showing; that is right for a photograph, whose crop is
     the content, and wrong for light, which needs the full frame to read
     as a source instead of a band. 92 rather than 100: a hero that exactly
     fills the viewport reads as a slide, and the page loses the one cue
     that says there is more below it. */
  --hero-min-height: 92svh;

  /* Centred, and the foot goes back to the head's rhythm with it.

     The default pins the text to the bottom so the top of a photograph —
     the part worth seeing — is not spent on empty sky. There is no sky
     here. The art is a glow rising from the foot, so the room the default
     protects is the room this theme has the least reason to keep, and the
     text sat in the lower third of a near-black screen with a third of it
     empty above. Centred, the headline and the glow are one object.

     The 4rem of header allowance stays in the head and is not answered in
     the foot, which is the asymmetry the pair is documented to keep: the
     pill is really there, so the middle of what the reader sees is 2rem
     below the middle of the block. */
  --hero-align: safe center;
  --hero-foot: min(var(--section-space), 14svh);

  /* The screen switches on.

     The trade this client is in is the picture: a display measured, set
     and handed back correct. So the hero does not fade in or drift into
     place, it *powers up* — the way the object they work on does. The
     beats are a CRT's own, in order and at a CRT's speed, because the
     gesture only reads if it is quick: a dot, a line snapping open across
     the middle, the raster unfolding with an overshoot, and the phosphor
     settling out of white into colour. Everything is over in 1.15s.

     Written as `--hero-entrance`, which REPLACES the base's settle rather
     than joining it (blocks.css): both own `scale`. The lead-in below then
     holds the reveal cascade until the frame exists — text arriving over a
     screen that has not switched on yet would undo the whole idea.

     The filter ramp is what makes it light rather than a wipe.
     `contrast(0)` flattens the art to a single mid grey and `brightness`
     drives that grey to white, so the opening line is white-hot whatever
     the art underneath happens to be — it does not depend on the gradient
     having anything bright at the block's centre line, which it does not.
     Both return to identity, so the last frame is the art untouched.

     No easing on the shorthand: the keyframes set their own per segment
     (see them below — it is the difference between an unfold you can see
     and one you cannot). `linear` is the honest thing to hand a keyframe
     list that eases itself; a curve named here would apply only to the
     segments that declined to state one, which is none of them. */
  /* The screen still switches on — see the beat-by-beat note below. What
     changed under it is that the art now includes a photograph, and the
     boot plays over the whole layer because the layer is one thing: the
     spectrum can only tint the picture by multiplying with it *inside the
     same background stack*, and anything in that stack shares the stack's
     animation. A separately fading image is possible and costs the
     tinting; that trade is written up in BACKLOG rather than decided here. */
  --hero-entrance:
    hero-boot 1.15s linear both,
    hero-boot-wash 1.15s linear both;
  --hero-scrim-entrance: hero-boot-scrim 1.15s linear both;
  /* **The two effects overlap on purpose.** 0.42s is not the end of the
     entrance, it is the moment the raster has finished opening (scaleY
     0.98 at 0.4s) while the phosphor is still settling out of white — so
     the text lights up *with* the screen brightening rather than after it,
     and the page reads as one event instead of two in a queue.

     The earlier value was the end of the raster, 0.667s, and it was
     correct at the time for a reason that has since stopped being true:
     the headline's lines used to slide up at full opacity from behind a
     mask, so "started" and "readable" were the same instant and the scrim
     had to be complete before either. The text now fades from zero on a
     curve that is deliberately lazy at the start, which buys a window
     where it is on screen and not yet legible.

     **The duty is unchanged, only its trigger is** — the scrim must be at
     full strength before the text can be *read*, not before it exists.
     Measured at the crossing rather than argued: the scrim completes at
     0.575s (it was pulled 95ms earlier to pay for this overlap — see its
     keyframes) with the headline at 0.04 opacity, which is nothing anyone
     reads, and the headline does not pass 0.3 until 0.78s. 205ms of
     margin, on a background that never changes again after 0.575s.

     Anything that makes the text arrive faster — a shorter duration, a
     less lazy curve, an earlier lead-in — eats that window, and these are
     the numbers to re-derive when it happens. The three of them are one
     mechanism: lead-in, curve, and the scrim's own finish. */
  --hero-lead-in: 0.42s;

  /* The rule of light above centred section headers — the reference
     language's chapter mark. Gold falling to coral, with a soft bloom. */
  --heading-rule-display: block;
  --heading-rule: linear-gradient(90deg,
    rgb(250 204 21 / 0) 0%,
    rgb(250 204 21 / 0.9) 35%,
    rgb(250 204 21 / 0.6) 65%,
    rgb(250 204 21 / 0) 100%);
  --heading-rule-glow: 0 0 24px rgb(250 204 21 / 0.55);

  /* Numbers at display size — with the boxes gone, the statistics are
     the About block's second voice and they speak in coral. 3xl, not 4xl:
     at 4xl the three of them no longer shared a row, and a stacked column
     of giant numbers read as three shouts instead of one measured line. */
  --stat-size: var(--text-3xl);
}

/* The hero glow's moving parts, referenced by `--hero-ambient`.

   **Registered properties, because the thing that has to move is inside
   the gradient.** `scale` and `filter` on this layer belong to the
   entrance, and the ambient slot is documented as additive precisely so
   it cannot take them. `background-size` was the first attempt and it is
   the wrong tool here: it scales the whole image, so the hot core grew
   with the bloom and the source drifted off the edge it is supposed to
   sit on. A custom property inside the gradient moves one radius, or one
   origin, and touches nothing else. `@property` is what makes it
   interpolate at all — an unregistered custom property has no type, so it
   would jump from one keyframe to the next.

   **Two animations, deliberately co-prime.** Thirteen seconds and
   twenty-four: the breath and the colour's pass come back into phase
   every 312s rather than every cycle, so the mix never quite repeats and
   the light never settles into a loop the eye can learn. One animation
   doing both would be a pattern within a few seconds of watching.
   `--hero-ambient` takes a list, so this costs the base nothing.

   Amplitudes are larger than the old eight percent, which was chosen so
   nobody would watch it happen. That was right for a band low in the
   frame and wrong for a source: this is meant to be looked at. Still slow
   enough to be felt rather than followed, and reduced motion stops all of
   it with everything else.

   **The breath is one multiplier, not two animated radii, and the reason
   is that the resting size has to stay adjustable.** Both radii used to
   be registered properties written literally into the keyframes, which
   made the keyframes the authority on how big the lamp is — and a
   keyframe cannot be retuned by a media query, so a value that has to
   change on a small screen (see below) could not change at all. Animating
   a plain number and multiplying it in leaves `--glow-spread` and
   `--glow-rise` as ordinary tokens that anything may set, at the cost of
   nothing: percentage × number is a percentage, number × number is a
   number, and the swing is identical to the one it replaces (86 → 77.8…
   96.8, 84% → 76…94.5%, against the old 78…98 and 76…94%).

   It is also one registered property where there were two, and the two
   were always in lockstep — they are one breath, and they now say so. */
@property --glow-breath { syntax: "<number>";     inherits: true; initial-value: 1; }
@property --rainbow-x   { syntax: "<percentage>"; inherits: true; initial-value: 0%; }


@keyframes hero-glow-breathe {
  from { opacity: 1;    --glow-breath: 0.905; }
  to   { opacity: 0.96; --glow-breath: 1.125; }
}

/* **The lamp comes down on a small phone, and it is the text that says
   so rather than the screen.** The guarantee row stacks to three lines
   below about 410px, and the content column then fills a block that has
   not grown with it: the bottom of the text sits at 87% of the block on
   a 390×844 screen and 88.3% on a 375×812 one, which is inside the lit
   half rather than above it.

   **This was already failing before the glow was extended, and that is
   the finding worth keeping.** The old rise measures 3.81 at 375×812
   against the 4.5 the row owes (PRINCIPLES §11) — the theme's recorded
   contrast figures were taken on a desktop viewport, where the same row
   sits at 80% and reads 8 and up. A block whose art is a lamp at the foot
   and whose text is centred puts those two on a collision course as soon
   as the text is tall enough, and no amount of desktop measuring can see
   it.

   **The source is cut harder than the bloom, and that is what the
   measurements asked for.** The worst case is 360×640, where the foot
   padding falls to 72px and the text ends 90% of the way down: the white
   core's own radius (19% of a 707px block is 134px) reaches past the
   text, so it is the core rather than the bloom that is doing the
   damage. Traded against each other at equal lamp height — 80px of
   L>0.08 — cutting the core buys more than cutting the rise: 19%/32%
   reads 5.18, 14%/40% reads 5.35, and the second keeps a taller bloom.
   Both beat 12%/52% at 4.30, which fails.

   The pair below is measured, not chosen. Swept across the colour pass:
   5.31 at 360×640 and 6.81 at 375×812, against 3.10 and 3.81 for the old
   art at the same two sizes. Above the query nothing is touched — 5.46 at
   1024×730, 5.98 at 1366×768, 10.96 at 1920×1080, 13.75 at 3440×1440.

   A scrim change was the obvious alternative and it was measured and
   rejected: holding the scrim firm through the 84–94% band is the same
   set of pixels as the lamp's visible top edge, so it bought the
   contrast back by undoing the extension — the bright zone came out
   *shorter* than before the work started (104px against 128px at 1440).

   30rem rather than 26: the breakpoint is a proxy for a text length, so
   it is set wide of the case that fails. Above 480px nothing measures as
   needing it, and below it a lower lamp is defensible on its own terms —
   a phone has less room for a light to rise into. **The number moves if
   the guarantee texts get longer**, since a row that stacks at a wider
   viewport brings the failure back with it.

   **The second clause is the one width alone would have missed, and it
   is the more important of the two.** A landscape phone is 740px wide
   and 414 tall: the block comes out 473px, the text ends 58px above its
   foot, and the full lamp reads 3.24 — worse than the old art managed at
   the same size (4.47, itself not a pass). Nothing about that viewport is
   narrow; what it is, is *short*. The content column has a fixed height
   and the block does not, so it is the room between them that decides
   this, and room runs out with the viewport's height rather than its
   width. With the compact pair it reads 7.27.

   45rem of height, and the number was walked rather than guessed:
   1024×660 reads 4.49 on the full lamp — a fail by a hundredth, on a
   laptop — 700 reads 4.94, 730 reads 5.46 and 800 reads 6.56. 720px is
   where the margin stops being thin. Common laptops keep the full lamp
   (1366×768 reads 5.98; the row fits on one line at that width, which is
   most of why), and this is the height twin of the trap already in the
   BACKLOG: *check at ~660px of viewport height, not only at 1080*. It is
   the same block that taught us that one. */
@media (max-width: 30rem), (max-height: 45rem) {
  :root {
    --glow-rise: 40%;
    --glow-core-rise: 14%;
  }
}

/* **A spectrum travelling right to left, not a colour changing in place.**
   Lifted from the onboarding device cards in the mediadb project, where
   it runs as a hover ring: an oversized multi-hue band whose
   `background-position` is walked one image-width, forever. Two things
   make it work and both are easy to get wrong.

   **The ramp starts and ends on the same colour**, so the loop has no
   seam — walk the position by exactly the background-size and the frame
   after the last is the first. The stops are also mirrored about the
   middle (gold, amber, coral, violet, coral, amber, gold), which means
   the band reads the same travelling either way and nothing about it
   says "this is a strip being dragged".

   **`linear`, and no `alternate`.** An eased or alternating spectrum
   would stall at the turn, and a band that pauses is a band you can see
   the edges of. Continuous motion at a constant rate is what makes it
   read as light rather than as an animation.

   24s where mediadb uses 3.5s: that is a hover — a deliberate answer to
   something the reader just did, and it can afford to be quick. This one
   nobody asked for and nobody should catch moving.

   The angle is 100deg rather than 90 for the same reason it is there: a
   band tilted slightly off the horizontal never lines its colour
   boundaries up with the edge of anything.

   **290.909% is arithmetic, not taste, and the 320% it replaces had a
   seam.** A background position of P% offsets the image by
   `(container − image) × P`, and the image here is 320% of the container
   — so the travel is `−2.2 × P` container-widths, not `P`. Walking to
   320% therefore moved the band 7.04 widths, which is 2.2 tiles: the
   last frame sat a fifth of a tile away from the first and snapped back
   to it, once every 24 seconds. Measured at the glow's centre, gold
   (208 172 126) jumped to coral (189 128 133) — 44 of 255 on one
   channel. Two whole tiles is `2 × 3.2 / 2.2 = 2.909…`, and the same
   measurement across the loop then comes back within one unit, which is
   rounding.

   The pace is kept rather than the number: two tiles in 24s is 9% slower
   than what the seam was buying, which is inside what anyone can see.

   Worth knowing how it survived: the loop *was* checked, by comparing
   contrast figures at 0% and 320% — and they matched, because gold and
   coral are the same luminance. A hue seam is invisible to a luminance
   test. Compare the pixels. */
@keyframes hero-rainbow {
  to { --rainbow-x: 300%; }
}

/* The switch-on referenced by `--hero-entrance`. Percentages of 1.15s:

     0     dot       a bright point at the centre of a black screen
     10    line      it snaps open sideways — 115ms, the fastest thing
                     on this site, and it has to be: a beam does not ease
     18    hold      one beat of a line and nothing else. Without it the
                     two openings read as a single diagonal wipe
     58    raster    unfolds vertically to full size, overshooting and
                     settling, still washed out to white
     100   colour    contrast and brightness return to identity, which
                     runs on under the arriving text

   **Each segment carries its own easing, and that is the whole reason
   this works.** With the house expo-out over the lot, the raster was
   through 91% of its travel 113ms into a 390ms segment — measured, not
   guessed — so the unfolding nobody could see was the one beat the
   effect is named after. Expo-out is a curve for something *arriving*;
   these are four different physical events and only the first of them is
   an arrival.

     — the beam: expo-out. A beam does not ease, and this is the one
       place on the site where instant is correct.
     — the hold: `linear` between two identical frames, so nothing can
       drift during it.
     — the unfold: `--ease-spring`, the system's own overshoot curve. It
       supplies the bounce instead of an extra keyframe at 1.05 — a
       raster snapping slightly past its size and settling is hardware
       behaviour, and this is the one place in the theme where a spring
       is honest rather than playful.
     — the phosphor: expo-out again, because that IS an arrival, and its
       long tail lets the colour keep settling under the first line of
       the headline.

   **The curves are written out rather than referenced, and they have to
   be.** `animation-timing-function: var(--ease-spring)` inside a keyframe
   is silently ignored — the declaration is dropped and the segment falls
   back to whatever the `animation` shorthand said, which here is
   `linear`. It fails invisibly: the animation still runs, still lands on
   every keyframe, and only the shape between them is wrong, so it reads
   as "the spring is too subtle" rather than as a bug. Probed rather than
   assumed — same element, same keyframes, one via `var()` and one
   literal: 0.2 against 0.6416 at the same instant. Both values below are
   copies of this file's own tokens, 30 lines up:

     cubic-bezier(0.16, 1, 0.3, 1)     = --ease-out-expo
     cubic-bezier(0.34, 1.4, 0.64, 1)  = --ease-spring

   Copies inside the theme that defines them, which is where values are
   allowed to live (PRINCIPLES §2) — but they are copies, so a theme that
   retunes its easing has to come back here.

   `scale` and not `height`: the layer is a full-size background box, so
   there is nothing to reflow and the whole thing composites. The block's
   own `overflow: hidden` clips the spring's overshoot.

   The x-scale never quite reaches 0 — a zero-width box is a box the
   compositor may drop entirely, and the first frame of an entrance is the
   one frame that cannot be missing.

   **Geometry and light are two animations, and they have to be.** They
   were one, and the seam showed: keyframes bind every property to the same
   segment boundaries, so the filter was forced onto the spring's timing.
   The spring is right for the unfold and wrong for the wash — the raster
   is at full size by 0.4s and only wobbling 5% after that, while the
   filter was still crawling back to identity at 1.15s. For roughly half a
   second the screen was a large flat light plane with nothing moving on
   it, which reads exactly as a stall. Splitting them lets the light
   recover on its own curve while the spring keeps its clean segment.

   `--hero-entrance` takes an animation *list*, so this costs the base
   nothing — no second slot, no core change. */
/* The art's arrival: one property, one segment, the headline's own curve.
   `filter` and not `opacity` — see the note at `--hero-entrance` for why
   the obvious property is the one that silently does nothing here. */

/* The footer's band walks two whole tiles, which at 200% of the box is
   exactly 400% of position — the same arithmetic the hero's band uses and
   for the same reason: land on a fraction of a tile and the loop snaps.
   Size and position both live here rather than in tokens, so reduced motion
   leaves one still spectrum across the footer instead of a broken walk. */


@keyframes hero-boot {
  0% {
    scale: 0.03 0.005;
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);   /* expo-out */
  }
  10% {
    scale: 1 0.005;
    animation-timing-function: linear;
  }
  18% {
    scale: 1 0.005;
    animation-timing-function: cubic-bezier(0.34, 1.4, 0.64, 1); /* spring */
  }
  58% { scale: 1 1; }
  100% { scale: 1 1; }
}

/* The light, on its own curve.

   `contrast(0)` is what guarantees the opening line is white whatever the
   art beneath it happens to be — the art here is dark at the block's
   centre line, so without it the beam would open black. That is its whole
   job, and its job ends the moment the line stops being a line: a tube
   switching on shows an over-exposed *picture*, not a grey card, and a
   flattened raster is the one thing in this sequence that never looked
   like hardware.

   So contrast is held flat only while the line is held, then recovers on
   expo-out — 0.85 by roughly a third of the way through the unfold, so the
   art is legibly itself while the raster is still opening around it.

   **The light lands when the geometry lands, and the tail after it is
   gone.** Brightness used to fade on to the very end, on the reasoning
   that phosphor settling under the arriving text is the nice part. It is
   not, once the art is a blended stack: a wash over a wide, soft glow is
   a large pale plane, and holding it past 0.4s — where the raster stops —
   left roughly a third of a second in which the only thing changing was
   a number nobody can see. Reported, twice, as the background stopping
   for a moment. The rule this settles is more general than the fix: **an
   effect that outlasts the movement it belongs to reads as a stall, not
   as a tail.** Everything is at identity by 70%, and what carries the
   page from there is the text arriving and the glow breathing. */
@keyframes hero-boot-wash {
  0% {
    filter: contrast(0) brightness(2.6);
    animation-timing-function: linear;
  }
  18% {
    filter: contrast(0) brightness(2.6);
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);   /* expo-out */
  }
  58% {
    filter: contrast(0.97) brightness(1.1);
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);   /* expo-out */
  }
  70% {
    filter: contrast(1) brightness(1);
  }
  100% {
    filter: contrast(1) brightness(1);
  }
}

/* The scrim arriving with the content, referenced by
   `--hero-scrim-entrance`. Full strength at 50% — 0.575s.

   The scrim IS the measured contrast (PRINCIPLES §11), so the rule it has
   to satisfy is that **it must be complete before the text can be read**.
   It used to end at 58%, aligned exactly with `--hero-lead-in`, because
   the text then arrived at full opacity from behind a mask and "on screen"
   and "readable" were one instant. The text now fades in and the entrance
   starts before the scrim finishes on purpose (see `--hero-lead-in`), so
   the alignment is gone and the rule needs its own margin instead.

   Bought here rather than by starting the text later, because the overlap
   is the effect and the scrim is the cheap side of the trade: finishing
   95ms earlier costs the art a sliver of the moment it has the screen to
   itself, and buys the guarantee nearly twice the room. Measured after the
   move — scrim complete at 0.575s with the headline at **0.04** opacity,
   and the headline does not pass 0.3 until 0.78s: **205ms of margin**.
   Before the move it was 0.12 and 110ms, which was true but too close to a
   number nobody should have to trust to the millisecond.

   `linear`, unlike everything else in this theme: it runs underneath a
   spring, and easing both makes the wash arrive in a lump somewhere in
   the middle of the unfold. */
@keyframes hero-boot-scrim {
  0%   { opacity: 0; }
  26%  { opacity: 0; }
  50%  { opacity: 1; }
  100% { opacity: 1; }
}

/* The caret referenced by `--nav-marker-animation`. Only the off-half is
   written: 0% and 100% then inherit the state's own opacity, so the
   keyframes work on hover (1) without hard-coding it here. */
@keyframes nav-caret-blink {
  50% { opacity: 0; }
}
