/* Hallmark · macrostructure: Workbench · H1 hero knobs: display=2-line, ticker=marquee, cta=chamfer-pair
 * theme: custom · vibe: "the same engineering, printed in selenium — green where it asks to be touched" · paper: oklch(98.5% 0.004 250) / oklch(16% 0.012 250)
 * display: Chalet · body: Chalet · outlier: Chivo Mono · axes: light+dark / geometric-sans / toned-mono ~250° + accent 139°
 * studied: yes · DNA-source: tokens.css (mono variant) · genre: modern-minimal · v0.11.0-mono
 */

/* =========================================================================
   generic — design tokens, MONO variant

   A DROP-IN REPLACEMENT for tokens.css, not an extension. Same token names,
   same file structure, same rules at the bottom. Type, space, motion, corner
   and nav tokens are copied unchanged — only colour is redesigned.

   Swap the stylesheet link and nothing else:
     <link rel="stylesheet" href="./tokens.mono.css">

   Verified 2026-08-22 at 371c067, and no longer true: page.css now holds
   three paint values of its own, all inside the min-width:992px scrolled-nav
   block -- the chip ground, its --color-interactive override and a white
   hover wash. The identity still moves from here; those three do not follow
   it. (The #000 and rgba(0,0,0,.) further down are MASK stops -- mask-image
   reads alpha only and never paints -- so they are hue-free by construction
   and survive the mono swap untouched.)
   ========================================================================= */

:root {
  /* --- Colour: the palette, complete -----------------------------------

     The mechanism is carried over intact from the chromatic system, because
     the mechanism is what the design IS; only the two values change.

     One ground, one ink. NO grey ramp — the 80-point lightness gap between
     paper and ink is deliberately empty, exactly as the 37-point gap between
     RPA's darks and brights was. Secondary emphasis is element OPACITY on the
     ink (--opacity-* below), never a separate neutral. Borders ARE the ink.

     This is the part a mono translation gets wrong by "improving" it into a
     five-step grey scale. Do not. If something needs to sit back, lower its
     opacity; if it needs a new value, it needs a reason first.

     Every ratio below is measured sRGB WCAG 2.1, computed not eyeballed.  */

  color-scheme: dark;

  /* v0.9.0-mono: the neutrals now carry ONE low-chroma hue, ~250° (steel /
     graphite / selenium). Strict 0-chroma greys read as unconsidered
     defaults — a wireframe, not a print. A silver-gelatin print toned in
     selenium shifts cool in exactly this way: "black and white" to anyone
     asked, never neutral under instruments. The cold cast is a decision
     about THIS subject — machined chamfers, mono labels, workbench
     structure — where warmth would argue with the material. One hue family
     across both themes and every NEUTRAL (v0.10.0: the interactive accent
     below is 139° by design), so the theme toggle reads as one
     print under two lights, not two brands. The dark theme's ground and
     texture carry slightly MORE chroma than the light theme's, because
     chroma reads weaker on dark surfaces — the aim is the same cast, not a
     stronger one.

     Directions considered and REJECTED, so switching later is cheap:
       - Warm letterpress: cream paper ~oklch(97% .01 90) with a sepia-black
         ink. Rejected: it is the stock generated-design cream, and warmth
         fights the machined, engineered surfaces this system is made of.
       - Split-tone: warm paper in light, cool ground in dark. Rejected: the
         near-symmetric inversion (17.97:1 / 17.73:1) is a stated design
         point, and two temperature identities turn the theme toggle into a
         rebrand.                                                           */

  --color-paper:      oklch(16.0% 0 0);  /* #090E12 — the ground
                                             is a cast, findable only beside
                                             a true grey                    */
  --color-ink:        oklch(97.0% 0.006 250);  /* #F2F5F9 — 17.73:1 on paper;
                                             C .012, a blue-black the way
                                             carbon ink is, never navy      */
  /* v0.11.0-mono: the INTERACTIVE layer takes the chromatic system's own
     bright, #92FF73 — tokens.css's --color-ink, quoted verbatim (owner
     instruction, 2026-08-22, confirmed by their own canvas edits: they set
     `background-color: #92FF73` on .btn--primary themselves). The green is
     a quotation, not a foreign value: the mono variant borrows its
     sibling's voice exactly where the page asks to be touched.

     The contrast problem it forces: #92FF73 is a high-lightness value —
     1.20:1 as text on the light ground, 15.49:1 on the dark. So the pure
     value is used only where its lightness works FOR it, and a deep cut of
     the same hue covers the one place it cannot go:

       fills   pure #92FF73 under dark ink, BOTH themes. The pair is two
               literals (green + the light-theme ink) that deliberately do
               NOT flip with the theme — 15.01:1 wherever it appears, and
               the owner's exact value is on the page in both themes.
       focus   dark theme pure #92FF73 (15.49:1 on ground); light theme
               oklch(48% .14 139.2) #2B6F13, the same hue at a lightness
               the light ground can carry (5.93:1). One hue, two cuts —
               coherent, not case-by-case.

     Readings REJECTED (why the others lose):
       - Green as text on the light ground: 1.20:1, unreadable. The naive
         reading of "make clickable green" ships an invisible light theme.
       - Fill inversion in light (ink chip, green label): passes at 15.01:1
         but contradicts the owner's own gesture — they reached for the
         FILL — and makes the primary read as a different component per
         theme.
       - A darkened green fill in light with pure reserved for dark: keeps
         the ratios but the owner's exact value then never appears in the
         light theme, and the fill needs no darkening — its label passes.
       - Re-pointing --color-rule or --alt-periwinkle at the green to reach
         the ticker/tabs/brand: those tokens also paint every non-inter-
         active border and the whole texture layer; green would spread to
         things that are not clickable, which kills the affordance.

     FOCUS COLLISION, resolved: v0.9.0 made focus the palette's one accent
     (steel, C .045). Two accents fighting is worse than either alone, so
     the steel accent is RETIRED — green owns the accent role outright and
     focus joins its family (the two cuts above). The ~250° tint survives
     only as the neutral cast on ground/ink/texture, which is a cast, not
     an accent. Every interactive element on the page shows green at focus
     through the :focus-visible rule at the bottom of this file; the
     primary CTAs show it at rest through the accent pair.

     REACH — what this token surface drives. page.css consumes
     --color-accent/-ink in exactly one place, .btn--primary
     (page.css:65-66); --color-focus everywhere via :focus-visible; and
     --color-interactive (v0.11.0) through the seam page.css cut for the
     owner's other three placements: .nav__brand, .ticker (text + border),
     .tabs__title. Every seam consumer falls back to its pre-seam paint
     when the token is unset — which is how tokens.css keeps its exact
     appearance with no edit (verified: 0 computed-colour diffs across all
     308 elements of the chromatic page, before vs after the seam).
     Still ink at rest, DELIBERATELY: .nav__links a / .footer__col a
     (inherit ink; underline is currentColor), .btn--ghost (frame IS
     --color-rule), .theme-switch (paper/ink pairs, draft-mono/build.py).
     The owner painted none of them; wiring any later is one
     var(--color-interactive, ...) in its own rule.                       */
  --color-accent:     oklch(90.5% 0.205 139.2);  /* #92FF73 — the quotation.
                                             As a fill: 15.49:1 edge on the
                                             dark ground; 1.20:1 edge on the
                                             light, where the chamfered
                                             shape and the 15.01:1 label
                                             carry the affordance (WCAG
                                             1.4.1: colour is never the
                                             sole signal — shape + label
                                             always present).              */
  --color-accent-ink: oklch(18.0% 0 0);      /* on the green fill. A LITERAL,
                                             not var(--color-ink): the pair
                                             must not flip in dark mode or
                                             the label goes 1.14:1.
                                             NEUTRALISED 2026-08-28 (owner):
                                             was oklch(18% 0.012 250), a navy
                                             cast on every hovered button.
                                             Same lightness, so the measured
                                             15.01:1 against the accent is
                                             unchanged — chroma does not move
                                             relative luminance here. */
  --color-interactive: oklch(90.5% 0.205 139.2);  /* #92FF73 pure — 15.49:1 on
                                             cut, the accent at TEXT grade:
                                             5.93:1 on paper (≥4.5 as text
                                             — .nav__brand and .tabs__title
                                             are text, and pure #92FF73 is
                                             1.20:1 on this ground), 3.03:1
                                             against the ink. Feeds the
                                             page.css seam: .nav__brand,
                                             .ticker text + border (border
                                             is non-text, needs 3:1 — 5.93
                                             clears it), .tabs__title.     */
  /* The scrolled nav chip inverts: its ground is the INK, not the paper, so
     the accent tuned for the paper is the wrong value inside it. page.css
     reads this name only inside that chip.

     This token is NOT theme-invariant, which is the trap I fell into first.
     "On ink" says nothing about lightness on its own -- the ink is near-black
     in the light theme and near-WHITE in the dark one, so the chip's ground
     flips with the theme exactly like every other surface. Pinning one value
     for both shipped #92FF73 on #F2F5F9 in the dark theme: 1.14:1, measured,
     an invisible nav. So it is the INVERSE of --color-interactive, restated
     per theme: pure green here (15.01:1 on #0E1217), the deep cut in the dark
     blocks below (5.60:1 on #F2F5F9). */

  --color-focus:      var(--color-interactive);  /* the ring borrows the
                                             accent — one green, one family
                                             of jobs; an alias so the two
                                             cannot drift apart. Split only
                                             if focus ever needs its own
                                             value.                        */

  /* No derived colour tokens, same as the source system: no second neutral
     for muted text, none for dividers. Reuse the ink and lower the element's
     opacity. Borders are `1px solid var(--color-rule)`.                    */
  --color-rule:       var(--color-ink);   /* borders ARE the ink */

  /* Text at these opacities, composited over paper, measured both themes:
       0.75  light 8.25:1   dark 10.06:1   — .muted, .footer__col a:hover
       0.60  light 4.83:1   dark  6.76:1   — .marker, .footer__legal .label
       0.20  non-text only  — container rails, empty media fill, the CTA ring
     --opacity-tertiary is 0.6 here, NOT the 0.3 tokens.css ships. 0.3 on
     this near-neutral pair lands at 1.99:1 (light) and 2.50:1 (dark), and
     both of its consumers are TEXT, so 0.3 would ship a failing text/ground
     pair. 0.6 is the lowest value clearing 4.5:1 in BOTH themes. The cost is
     real and worth naming: the markers sit closer to the muted step than
     they did in colour. That is the honest price of the contrast gate on a
     palette whose tint is too faint to buy contrast.                       */
  --opacity-secondary: 0.75;
  --opacity-tertiary:  0.6;
  --opacity-faint:     0.2;

  /* Section variants. In colour these rotated the BRIGHT while the ground
     stayed put. In mono there is no bright to rotate — see the .section--*
     rules at the bottom of this file for what they do instead and why.

     The three --alt-* names survive because page.css and index.html read
     --alt-periwinkle BY NAME (four rules in page.css plus one
     getComputedStyle call in index.html's canvas field). They resolve to one
     shared TEXTURE TONE: the single value in this file that is neither paper
     nor ink.

     It exists because page.css runs .hero-mark at `opacity: 1` — a hardcoded
     full-strength element the opacity mechanism cannot reach. At the ink, a
     34vw wordmark at full strength stops being a watermark and becomes the
     page. The tone is one value serving three non-text roles (hero
     watermark, dot lattice, CTA ring), NOT the first rung of a grey ramp;
     nothing reads type in it.                                             */
  --alt-periwinkle:   oklch(48.0% 0 0);        /* TRUE GREY — owner ruling
                                             2026-08-28. C was .022, which at
                                             --dot-alpha .22 over the ground
                                             composited to rgb(29,31,33): a
                                             cool cast of four points across
                                             the channel range, named
                                             periwinkle and read as grey. The
                                             ground went neutral the same day,
                                             so the last chroma on the page
                                             was this one. The NAME is kept
                                             because page.css and the canvas
                                             field both read it by name; in
                                             mono it was already vestigial —
                                             --alt-pink and --alt-red alias
                                             it, and none of the three is a
                                             hue any more.                  */
  --alt-pink:         var(--alt-periwinkle);   /* hue names are vestigial in mono — one tone */
  --alt-red:          var(--alt-periwinkle);
  /* Unused by page.css and index.html; kept so the token surface matches
     tokens.css exactly. A literal, not a role, so it is not theme-flipped. */
  --ground-black:     oklch(0% 0 0);      /* #000000 */

  /* ---------------------------------------------------------------------
     Everything below this line is COPIED UNCHANGED from tokens.css.
     Do not redesign the type scale or the spacing here — a mono variant
     that also moves the type is two changes wearing one filename.
     --------------------------------------------------------------------- */

  /* --- Type -------------------------------------------------------------
     Chalet for display AND body (rig.ai uses one family for both).
     Chivo Mono carries every label, chip, ticker and button.
     Scale is rig.ai's own: linear 0.1rem steps at the label end, then
     multiplicative jumps for display.                                    */

  /* @font-face lives in fonts.css — import it BEFORE this file.
     Fallbacks are metric-matched so `font-display: swap` does not reflow
     the hero. Not subsetted: the source sets the no-subset bit.            */
  --font-display: "Chalet", "Chalet Fallback", ui-sans-serif, system-ui, sans-serif;
  --font-body:    "Chalet", "Chalet Fallback", ui-sans-serif, system-ui, sans-serif;
  /* The wordmark face. The nav lockup's wordmark is Host Grotesk 500 outlined
     to paths, so any live-text setting of the name — the footer mark — has to
     name the same family or the two marks read as two brands. */
  --font-wordmark: "Host Grotesk", "Chalet", ui-sans-serif, system-ui, sans-serif;
  --font-outlier: "Chivo Mono", ui-monospace, monospace;

  --text-3xs:     0.6rem;    /*  9.6px · mono label   */
  --text-2xs:     0.7rem;    /* 11.2px · mono label   */
  --text-xs:      0.8rem;    /* 12.8px · mono label   */
  --text-sm:      0.9rem;    /* 14.4px · body small   */
  --text-base:    1rem;      /* 16px   · body         */
  --text-md:      1.2rem;    /* 19.2px · body large   */
  --text-lg:      1.35rem;   /* 21.6px · sub-heading  */
  --text-xl:      2rem;      /* 32px   · heading      */
  --text-2xl:     3.2rem;    /* 51.2px · heading      */
  --text-3xl:     4rem;      /* 64px   · heading      */
  --text-display: clamp(2.2rem, 9vw, 7rem);   /* 112px cap — rig's H1 at 1440 */
  /* Wordmark: a distinct display ROLE, not a rung on the type scale — never
     reuse it for headings. Viewport-driven so the mark spans the content
     inset edge to edge. 24.8vw is measured against Chalet's advance widths
     for the template's own 7-glyph mark; page JS refits the ink to the inset
     for any other text length, so the token is the no-JS fallback and the
     starting size, not a per-word constant. */
  /* Section heading: 3.5x body, measured off rig.ai (56px against a 16px
     body, fixed at every width). rig owns TYPE in the provenance table;
     medusa owns STRUCTURE. This token briefly held medusa's 40px, which was
     a provenance error — the grid came from medusa, the type never should
     have. Restored 2026-08-20. */
  --text-section: 3.5rem;   /* 56px */

  /* Footer ticker: its own display ROLE, not a rung on the scale. Measured
     on coffee-tech.com's "Get in touch" marquee: 148.45px at a 1440 viewport
     = 10.31vw, weight 500. --text-display caps at 112px, so the ticker could
     not reach the reference size without either raising that cap (which would
     move every H1 on the page) or minting this. Same reasoning as
     --text-wordmark below: viewport-driven, capped so it cannot run away on
     an ultrawide. */
  --text-ticker: clamp(3rem, 10.31vw, 9.5rem);

  --text-wordmark: max(2.2rem, 24.8vw);  /* no upper cap — the mark grows with the page; 2.2rem floor only guards very narrow viewports */

  /* Air above and below the footer wordmark, each side, inside
     .footer__mark-slot. The height ceiling on .footer__mark is
     100cqh - 2 * this, so the mark takes the whole slot bar a constant
     margin instead of a constant SHARE of it -- a share scales the air with
     the window, which is backwards, and at 2079x1020 it was reserving 128px
     of a 459px slot and holding the mark to 28% of its column.
     MUST STAY A LITERAL px VALUE. The fit script in index.html parseFloats
     this token to apply the same ceiling in JS; a rem value would parse as
     its own number (24px written as 1.5rem parses as 1.5) and the two
     ceilings would silently disagree. */
  --nav-shell: 5.2rem;   /* the fixed nav's occupied height: 1.4rem of
                            padding either side of a 2.4rem bar. Anything that
                            has to start below the nav reads this, so the two
                            cannot drift. */
  --mark-air: 24px;

  /* Leading and tracking move in opposite directions as size grows.
     This pairing is the signature — carry it, not just the sizes.        */
  --leading-label:   1.4;
  --leading-body:    1.65;   /* observed 1.60-1.70 */
  --leading-heading: 0.92;   /* observed 0.92 at 56px, 0.95 at 32px, 1.05 at 21.6px */
  --leading-display: 0.88;   /* observed at 112px       */

  --tracking-label:   0.15em;   /* opens as type shrinks: 0.1em to 0.2em */
  --tracking-body:    normal;
  --tracking-heading: -0.03em;
  --tracking-display: -0.04em;  /* closes as type grows */

  /* 400 — OWNER RULING, 2026-08-28, superseding the note below. The owner set
     the hero H1 to Chalet's own cut and asked for every heading to match it.

     What the old note said, and it was right about the mechanism: every
     heading on rig.ai is Chalet at 700, and rig ships the 400 cut ONLY, so
     the browser SYNTHESISES that bold — one face, weight 400, asked for 700.
     We ship the same single cut, so 700 reproduced the reference's synthetic
     bold exactly. It ended "do not fix this by dropping to 400; the heaviness
     is the look."

     The heaviness is no longer the look. 400 is Chalet as drawn, with no
     synthesis — which is what the hero has been showing since the owner set
     it (font-weight: 100 there selected nothing thinner, because no such cut
     exists, and browsers do not synthesise lighter). This value is what makes
     the rest of the page agree with it. */
  --weight-display: 400;

  /* --- Space ------------------------------------------------------------
     4-pt scale, by observed frequency on rig.ai:
     32px(87) 24(68) 12(61) 8(50) 20(46) 16(41) 48(16) 4(12) 64(10) 128(10) */

  --space-3xs: 0.25rem;   /*   4px */
  --space-2xs: 0.5rem;    /*   8px */
  --space-xs:  0.75rem;   /*  12px */
  --space-sm:  1rem;      /*  16px */
  --space-md:  1.25rem;   /*  20px */
  --space-lg:  1.5rem;    /*  24px */
  --space-xl:  2rem;      /*  32px */
  --space-2xl: 3rem;      /*  48px */
  --space-3xl: 4rem;      /*  64px */
  --space-4xl: 8rem;      /* 128px */

  --inset-page: 10%;      /* content inset, 8-12% observed across widths */

  /* Content CAPS; it does not scale forever. Measured on medusajs.com: the
     container is 1280px at 1440 AND at 1920 — the inset grows, the content
     does not. A percentage-only inset keeps widening the measure past the
     point where a line is readable, and it is the single biggest reason a
     rebuild "doesn't look like" a reference at desktop widths.
     Below this width the percentage governs; above it, the cap does.      */
  /* 984px — the reference's column. Owner, 2026-08-28.

     ⛔ 1102 WAS A MEASUREMENT ERROR, and this is the shape of it: the
     reference's cards carry a scroll animation that leaves them at
     `transform: scale(1.12)`, and getBoundingClientRect() reports the
     RENDERED box, so every reading came back 984 x 1.12 = 1102. offsetWidth,
     which is the layout box, says 984 at every width. A transformed element
     measured by its client rect is not measured at its own width. */
  --content-max: 61.5rem;   /* 984px */
  /* THE PAGE GUTTER, one definition for everything that lines up with the
     content column. It is NOT --inset-page: the inset is a percentage, and
     past --content-max the column stops growing while the percentage keeps
     going, so the two diverge on any wide window. Measured at 2079px wide:
     sections sat at 399px (the cap doing the work) while the nav and the
     whole footer sat at 208px (10%), which is why the bar read as a
     different column from the text under it. They agreed only at 1440,
     which is where every earlier measurement was taken.
     max() picks whichever is larger: the percentage on a narrow window, the
     centring remainder once the column has hit its cap. The 100% resolves
     against each user's own containing block -- all of them are the full
     page width here, so they all land on the same number. */
  --gutter-page: max(var(--inset-page), (100% - var(--content-max)) / 2);

  /* Media slots are deliberately over-sized and cropped. On medusajs.com the
     product shots are 1680px wide inside a 1280px frame — the image is too
     big for its box on purpose, which is what makes a screenshot read as a
     detail of something larger rather than a picture pasted in. Set to 100%
     for a project whose imagery must be seen whole.                        */
  --media-overscan: 130%;

  /* --- Corners and rules ------------------------------------------------
     ROUNDED, not chamfered. The cut-corner idiom was carried over from
     rig.ai and the owner rejected it outright: "this bullshit corner
     language, which I do not agree to". The corner language is now the one
     on superpower.com -- full pills for controls, a soft radius on cards.
     --chamfer stays defined at 0 so --clip-chamfer resolves to a no-op
     polygon instead of every consumer needing an edit; the radii below do
     the actual work. Measured on the reference at 1440: its CTA computes
     border-radius 9368px, i.e. a full pill.                              */

  --chamfer: 0px;
  /* Dot field. A square-dot lattice: --dot-size squares on a --dot-gap
     pitch, drawn in the section's own bright. Two values, because the
     texture has to survive a rebrand of the scale without being re-derived
     by hand. */
  --dot-size: 4px;
  /* 24, up from 16 — owner ruling 2026-08-28: the lattice read as too tight.
     The PITCH moves, the dot does not: a 4px square on a 24px pitch keeps the
     same weight of mark and simply gives it more ground, where growing the
     dot with the gap would have traded one texture for a coarser one.
     It also costs less — the field goes from ~3,280 dots on a 1280 hero to
     ~1,450, and both the entrance and the pointer warp walk that lattice. */
  --dot-gap:  24px;
  /* Read by the canvas field as well as the CSS one, so both versions of the
     texture stay the same weight. */
  /* Peak dot opacity. Dropped from 0.18 when the field stopped being a
     centre vignette and started covering the whole hero: the old value only
     ever landed at full strength on a small middle patch, so the same number
     over the full area reads heavier than it did. Lower per dot, more dots
     visible — the field sits further back and covers more, which is the
     trade the owner asked for. */
  /* Ticker slash. The words take --color-ink (near-white here); the slashes
     take the accent, so the pair reads white-on-dark with green punctuation.
     page.css falls back to --color-focus when this is undefined, which is
     what the chromatic identity gets -- it has no white to pair against. */
  --ticker-slash: oklch(90.5% 0.205 139.2);

  --dot-alpha: 0.22;

  /* Hero watermark colour. A knob of its OWN, not --alt-periwinkle, because
     the mark and the dot lattice were sharing that one value and pull in
     opposite directions: the owner wants the lattice lighter-but-present and
     the mark heavier. The mark's opacity is already 1, so colour is the only
     lever it has left.

     The ceiling is set by the H1, which crosses the mark over 45% of its
     area (measured; the lede does NOT overlap it at all -- 0px -- so it
     constrains nothing, and the CTAs both paint their own ground). This is
     the LIGHT value, so the mark gains weight by going DARKER: 3.75:1
     against the ground, up from 2.37, with H1-over-mark still 4.80. */
  --hero-mark-color: oklch(54.0% 0.022 250);   /* #65707B */

  /* none, not a zero-chamfer polygon. A polygon() clip is a RECTANGLE at
     --chamfer: 0, and a clip-path cuts the rounded corners straight back off
     -- the pill computed border-radius 9999px and still rendered square.
     Every consumer keeps `clip-path: var(--clip-chamfer)` and is now a no-op. */
  --clip-chamfer: none;

  --radius-card:  16px;   /* superpower.com's floating surfaces read 16px */
  --radius-pill:  9999px; /* full pill -- the reference's own CTA geometry */
  --radius-input: 12px;

  --rule-hair: 1px;
  --rule-fine: 2px;

  --shadow-card: none;   /* nothing in the reference set uses shadow */

  /* --- Motion -----------------------------------------------------------
     Signature from noth.in, rebuilt without GSAP: text reveals are line
     masks -- lines split at runtime slide up (translateY 100% -> 0) from
     behind overflow-hidden wrappers over --dur-reveal, staggered
     --stagger-line per line, fired once at --reveal-start. Link
     underlines wipe on the same curve over --dur-wipe; the wordmark
     squashes over --dur-squash and springs back over --dur-spring
     (sampled elastic keyframes in page.css). Durations here ARE
     noth.in's slower scale; --dur-micro/short/long stay the utility
     scale for everything that is not the signature.                    */

  --dur-micro: 120ms;
  --dur-short: 220ms;
  --dur-long:  420ms;

  /* signature motion -- role-named like --text-wordmark, not new rungs
     on the utility scale above */
  --dur-reveal:   1000ms;  /* line-mask slide + the minority opacity reveal */
  --stagger-line: 50ms;    /* per-line delay = line index * this            */
  --dur-wipe:     600ms;   /* link underline wipe                           */
  --dur-squash:   600ms;   /* wordmark squash phase                         */
  --dur-spring:   1800ms;  /* wordmark spring-back; animation total = squash + spring */

  --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in:       cubic-bezier(0.55, 0.085, 0.68, 0.53);   /* ~ power2.in    */
  --ease-in-out:   cubic-bezier(0.77, 0, 0.175, 1);         /* ~ power4.inOut */
  --ease-overshoot: cubic-bezier(0.34, 1.56, 0.64, 1);      /* ~ elastic.out  */

  /* signature eases */
  --ease-reveal: cubic-bezier(0.86, 0, 0.07, 1);      /* mask slide + wipe */
  --ease-fade:   cubic-bezier(0.165, 0.84, 0.44, 1);  /* opacity reveal    */
  --ease-squash: cubic-bezier(0.645, 0.045, 0.355, 1);/* squash phase; mirrored as a
     literal inside @keyframes mark-squish (page.css) -- keyframe bodies do not
     reliably substitute var() for timing functions */

  --reveal-start:    95%;   /* reveals fire at 95% of viewport height */

  /* --- Navbar (superpower.com) ------------------------------------------ */
  --nav-pad-y:         var(--space-lg);   /* 24px at rest */
  --nav-pad-y-scroll:  var(--space-sm);   /* 16px scrolled */
  /* THE BAR TRACKS THE CONTENT COLUMN. This was a flat 4.5rem (72px), which
     at the 1440 design width is 5% a side against the content's 10% -- so the
     bar sat 72px wider than the text on each side and read as a different
     column from everything under it. Routing it through --inset-page makes
     one number govern both, at every width. The scrolled chip is unaffected:
     it is min(840px, ...), so the inset only takes over on a narrow window. */
  --nav-pad-x:         var(--gutter-page);
  /* The dead centre track in .nav__bar. The lockup is 1.5em tall on a
     1081x208 viewBox, so its ink is ~7.8em wide; 9.5em is that plus air on
     both sides. Nothing is laid out in this track — the absolutely centred
     .nav__brand sits over it — so it is what stops the links and the CTA
     from reaching the logo at any window width. */
  --nav-brand-track:   9.5em;
  --nav-transition:    250ms;           /* superpower's measured 0.25s */
  --nav-z:             9999;
}

/* =========================================================================
   ONE PALETTE. Dark is not the default — it is the only thing there is.

   Owner instruction, 2026-08-27: make the dark version the default and remove
   the light version completely. A default implies an alternative, so there is
   no alternative here: no light palette, no prefers-color-scheme fork, no
   [data-theme] override, no stored preference, no toggle.

   The five values that used to be restated in the two dark blocks are now the
   only definitions, in :root above: --color-paper, --color-ink,
   --color-interactive, --hero-mark-color, --alt-periwinkle, plus
   color-scheme: dark.

   --color-accent and --color-accent-ink are unchanged and were deliberately
   never part of the fork: the CTA pair is a green fill with a dark label at
   15.01:1, which had to read identically on both grounds. It now has one
   ground and still reads.

   Contrast on the surviving palette: ink on paper 17.73:1, --color-interactive
   on paper 15.49:1, --hero-mark-color on paper 3.84:1 with H1-over-mark 4.62.
   These are the figures the deleted dark blocks carried; nothing was
   recomputed, because nothing changed except which block they live in.
   ========================================================================= */

/* =========================================================================
   Section variants — DELIBERATE NO-OPS in mono.

   Option taken: (b) they collapse, and the page reads as one surface.
   Option rejected: (a) ground steps — paper / near-black / pure.

   Why. Two reasons, and the first is not mine to overrule.

   1. The single ground is an owner decision, recorded in tokens.css
      (2026-08-22): "a single ground reads as one surface with the accent
      moving across it rather than as six pages." Ground steps would reverse
      that ruling. A palette swap is not the place to relitigate it.

   2. What rotated was the BRIGHT, and mono has exactly one. Substituting a
      ground rotation is not a translation of the mechanism, it is a
      different mechanism wearing the same class names — and it would mint
      two extra neutrals per theme, which is the grey ramp this file exists
      to refuse. Ground steps small enough to stay tasteful (say #090E12 to
      #000) read as banding on a gradient, not as a section boundary; steps
      large enough to read as sections need a second ink to stay legible.

   So the classes stay in the markup, stay named here, and change nothing.
   Everything they used to carry is now carried by the structure that was
   always doing most of the work: the container rails, the chamfers, the
   rules, and the section rhythm. Re-hueing later is a one-line change per
   rule, which is exactly why the rules are kept rather than deleted.

   Empty rules are valid CSS and cost one selector match. They are the
   documentation.
   ========================================================================= */
.section--pink       { /* no-op in mono — see above */ }
.section--red        { /* no-op in mono — see above */ }
.section--periwinkle { /* no-op in mono — see above */ }

/* .section--black is the ONE class the chromatic system lets change the
   ground: it drops a section to --ground-black so the page can mark a hard
   cut. In mono it is a no-op too, and for a reason the others do not have.
   The chromatic page cuts to black FROM navy — two different surfaces. Here
   the dark theme's ground is already #090E12, so "cut to black" is a 5%
   step nobody sees, and in the light theme it would invert a single band to
   near-black, which is not a cut, it is a different page stapled into the
   middle of this one. A cut needs somewhere to cut TO, and a two-value
   palette has spent both values. Named, not deleted, for the same reason as
   the three above. */
.section--black      { /* no-op in mono — see above */ }

/* Motion runs by default — even under OS Reduce Motion. Deliberate trade:
   every reference site (noth.in, coffee-tech, rig.ai) drives motion from JS
   and never consults the media query, so honouring it made this the only
   still page in the set. The calm path is an explicit page-level switch,
   not the OS setting: <html data-motion="reduced"> restores it. */
:root[data-motion="reduced"] {
  --dur-micro: 1ms; --dur-short: 1ms; --dur-long: 150ms;
}

:where(a, button, [tabindex]):focus-visible {
  outline: var(--rule-fine) solid var(--color-focus);
  outline-offset: 2px;
}

/* THE CAP HAS TO BE ABLE TO WIN. --gutter-page is max(--inset-page, half the
   leftover past --content-max), so at 1280 the 10% inset (128px) beat the
   1102 cap's 89px and held the column at 1024. At 6% the cap governs from
   about 1150 up, which is where the reference's own column stops moving.
   Desktop only: below 1024 the inset is untouched. */
@media (min-width: 1024px) {
  :root { --inset-page: 6%; }
}
