/* ============================================================
   conor.bio — DESIGN TOKENS
   Single source of truth. No raw values anywhere else in the
   codebase. Any hex, px, or ratio outside this file is a defect.

   TBD means Claude Code MUST ASK. It does not mean "choose
   something reasonable."
   ============================================================ */

:root {

  /* ---------- COLOR -------------------------------------------
     Four values. There is no fifth.
     Alabaster is the ground everywhere on the site. Carbon Black
     is ink, and is a ground in exactly one place: the error plate.
     That exclusivity is what makes an error impossible to miss.
     Do not introduce tints, shades, or alpha variants.
     ---------------------------------------------------------- */

  /* Primitives — raw values. Never referenced by a component, only by
     the semantic tokens below. Two tokens sharing a hex (--ground and
     --ink-inverse, --ink and --error-plate) used to be the same literal
     written twice, so changing the ground would have silently dragged
     every piece of inverse type with it. */
  --alabaster:     #E5E0DC;
  --carbon:        #1B1B18;

  /* Semantic — what components reference. */
  --ground:        var(--alabaster);  /* Alabaster Grey — every page, every section */
  --ink:           var(--carbon);     /* Carbon Black — body, headings, rules, boxes */
  --ink-quiet:     #4F4F49;  /* captions, labels, metadata, values  6.3:1 */
  --ink-disabled:  #8A857E;  /* disabled controls only              2.8:1 */
  --ink-locked:    var(--ink-disabled);  /* a client-portal section greyed out until it unlocks (Reviews, before the wedding day). Lighter than --ink-quiet; same grey as --ink-disabled, 2.8:1. */
  --ink-inverse:   var(--alabaster);  /* type on dark: the error plate, and type over photographs (homepage hero fork, 13.2:1 on the plate) */

  /* Fifth value, by direct instruction — the "four values, no fifth"
     line above no longer holds literally. Quiet text's dark-ground
     counterpart: any --ink-quiet role (captions, labels, metadata)
     that lands on an --ink ground uses this instead of --ink-quiet
     itself, which is unreadable there (0.9:1). Used on the reviews
     carousel plate and the selected Videos tab's stage label. */
  --ink-quiet-inverse: #A5A099;  /* quiet text on --ink grounds, 6.7:1 */

  /* TEXT OPACITY IS BANNED SITEWIDE.
     No opacity: 0.x on text. No rgba() with fractional alpha on
     text. Quiet text is --ink-quiet or it does not exist. */

  /* --accent REINTRODUCED — AMENDMENTS ROUND 4 §5, restated as
     "unchanged": #983A1E, "appears on Request a Quote only, nowhere
     else." This is not the same value as the blue that was removed
     for good ("never blue on the page" — #295C7E; not blue, this one
     stays removed). #983A1E is the ORIGINAL terracotta from before
     this project ever went blue (see BRIEF.md §2's anti-cliché
     table) — a real color again, not a restoration of the thing that
     was banned.

     Scope, since two different "Request a Quote" surfaces already
     exist and neither is accent-colored: the header's pinned CTA
     (.header-cta) and AMENDMENTS ROUND 4 §2's new mobile sticky CTA
     are BOTH explicitly specified as --ink fill in their own
     sections of that amendment — restating accent here doesn't
     silently override either of those explicit, separate
     instructions. Read as scoped to the actual Request a Quote FORM
     section's submit button (still unbuilt, stage-placeholder) —
     the one surface named "Request a Quote" that isn't already
     spoken for by an explicit ink instruction elsewhere. Flagged as
     an interpretation, not silently assumed.

     ORPHANED AGAIN, stage 8 build: once that submit button was
     actually built, direct instruction ("make the send quote request
     button ink colored") put it on --ink instead — see .quote-submit
     in styles/site.css. No surface on the site currently uses
     --accent. Not deleted, same treatment already given to
     --font-accent's own orphaning below — a live decision could
     still reuse the token; a guess shouldn't quietly remove it. */
  --accent: #983A1E;


  /* ---------- TYPE ---------------------------------------------
     Two families, chosen in Phase 0 via /font-preview.html. Both
     self-hosted — see FONT FACES below.

     Display face survives 96px at 0.85 leading without descenders
     colliding with caps below. Verified against "Packages" (has a
     descender).

     Body face gap checked at both 600 and 700 against 400 in a
     running-prose link (My Approach copy, block 4 of the preview).
     700 read as a clearly visible gap; 600 was closer to a medium
     weight and less reliable as the sole non-color cue prose links
     depend on. 700 is the value in use.

     FACE ASSIGNMENT — was "exhaustive, no exceptions"; seven exceptions
     now stand, each confirmed after live preview in font-preview.html:
       --font-display (Gloock)       --t-section (56/40) — but see
                                      --font-heading below: every actual
                                      section heading on this page has
                                      moved to it instead, so Gloock
                                      currently has no live usage.
                                      Not removed (font file, @font-face,
                                      preload, token all still here) in
                                      case that's a scope call worth
                                      revisiting rather than a final
                                      decision — flagged, not deleted.
       --font-heading (Living Uncut Sans Stencil Semi-Bold)
                                      "Put all section headings in the
                                      same font as packages" — every
                                      <h2> section heading (Packages, My
                                      Approach, and whatever's built
                                      next), all caps + --track-caps.
                                      Same file as --font-masthead
                                      (reused deliberately, like
                                      --font-nav-active is reused for
                                      two different roles rather than
                                      adding a face for each) but its
                                      own token: this is a "heading"
                                      role, --font-masthead is
                                      specifically the masthead H1 +
                                      header nav + prices, a different
                                      role that happens to share a face.
       --font-body (Host Grotesk)    EVERYTHING else not named below:
                                      body prose, all 15px caps labels
                                      not otherwise assigned, info
                                      clusters, section links, form
                                      labels, footer stacks, the song
                                      list. Also, for now, prose links
                                      and the masthead credential links
                                      — see --font-accent below.
       --font-masthead (Living Uncut Sans Stencil Semi-Bold)
                                      The /weddings (and /events, by
                                      the "identical" rule) masthead
                                      heading; the sitewide header ROW 1
                                      primary nav ONLY (Weddings / Events
                                      / Music / Links / Contact), all
                                      caps + --track-caps; AND package
                                      prices ("From $X", .pkg-price-col)
                                      — "the heavier stencil one," all
                                      caps + --track-caps. Row 2's REST
                                      state stays --font-body, sentence
                                      case — "those links were supposed
                                      to stay in host grotesk," a
                                      correction after row 2 briefly
                                      picked this up along with row 1 —
                                      but its ACTIVE state (the section
                                      currently under the sticky header,
                                      tracked by scroll position — see
                                      .header-row2-link.is-active in
                                      site.css) now reuses this same
                                      face, all caps + --track-caps, per
                                      a later explicit instruction. Not
                                      the dropdown
                                      panels beneath Links/Contact
                                      (social links, copy-to-copy
                                      emails) — a secondary disclosure
                                      layer, kept in --font-body/
                                      sentence case for legibility.
       --font-nav-active (Living Uncut Sans Extra Black, 900, non-
                                      stencil) Row 1 primary nav item
                                      when active: aria-current="page"
                                      (Weddings on /weddings) or an open
                                      Links/Contact accordion
                                      ([aria-expanded="true"]). All
                                      caps. Also one half of Request a
                                      Quote's perpetual letter-by-letter
                                      font-swap (see .header-cta /
                                      .overlay-cta in site.css) — reuses
                                      this same "attention" face rather
                                      than adding a sixth.
       --font-serif (Aujournuit)     One half of Request a Quote's
                                      font-swap, italic. Only static
                                      Regular file shipped — italic
                                      here is a synthetic/faux slant
                                      (font-style: italic on a face
                                      registered font-style: normal),
                                      confirmed acceptable rather than
                                      holding for a true italic cut.
                                      No longer package titles — see
                                      --font-pkg-title below. No longer
                                      What Couples Say review names
                                      either (.review-name) — the "put
                                      the name in the serif font"
                                      exception was reverted when the
                                      section was rebuilt; names are
                                      --font-body again, same as
                                      everything else not named here.
       --font-pkg-title (Living Uncut Sans Stencil, 400)
                                      Package titles (.pkg-name) — "make
                                      the package titles be in the uncut
                                      font, maybe a nice medium stencil
                                      or medium reg." No true "medium"
                                      weight exists in this family;
                                      picked the stencil cut at its
                                      lighter 400 weight, distinct from
                                      --font-masthead/--font-heading's
                                      Semi-Bold stencil so titles read
                                      related but not identical to
                                      headings. No longer What Couples
                                      Say review occasions either
                                      (.review-event) — same revert as
                                      --font-serif above; occasions are
                                      --font-body again too.
       --font-accent (ParaRubber)    Currently unused, and now
                                      orphaned rather than "for now"
                                      unused: it was applied wherever
                                      --accent appeared as a text
                                      color (prose links, the masthead
                                      credential links), and --accent
                                      itself has since been removed
                                      from the site permanently ("never
                                      blue on the page"). Not deleted
                                      here — the token still describes
                                      a real, distinct typeface choice
                                      that was confirmed live — but its
                                      original reason to exist is gone;
                                      flagged, not silently dropped.
     An eighth face, or any of these seven outside their assigned roles,
     is a defect.
     ---------------------------------------------------------- */

  --font-display:  'Gloock', serif;
  --font-body:     'Host Grotesk', sans-serif;
  --weight-body:   400;
  --weight-strong: 700;  /* the heavier of the two body weights */

  /* Confirmed after live preview in font-preview.html's "accent
     face" role ("ok i like pararubber"), back when --accent still
     existed as a text color — see FACE ASSIGNMENT above. Self-hosted:
     single file, single style, no weight/width variants to worry
     about (unlike --font-masthead's history). */
  --font-accent: 'ParaRubber', sans-serif;

  /* Masthead heading only. Was Gloock at --t-display, then briefly
     Allura (a script, reverted), now Living Uncut Sans Stencil
     Semi-Bold — confirmed after side-by-side preview in
     font-preview.html ("gun it in stencil semibold. love it."). Self-
     hosted, same pattern as Gloock: local file, @font-face below,
     preloaded in weddings.html's <head>. Named for the ROLE, not the
     specific style, since this is the third different face this one
     role has used during exploration — a name tied to one style
     would need renaming again next time it changes. */
  --font-masthead: 'Living Uncut Sans Stencil Semi-Bold', sans-serif;

  /* Every section heading, sitewide — see FACE ASSIGNMENT above.
     Deliberately the same file as --font-masthead ("put all section
     headings in the same font as packages") but its own token: a
     "heading" role, distinct from --font-masthead's specific
     (masthead H1 + header nav + prices) role, even though they
     currently resolve to the same face. */
  --font-heading: 'Living Uncut Sans Stencil Semi-Bold', sans-serif;

  /* Header nav "active" state + one half of the Request a Quote
     font-swap — see FACE ASSIGNMENT above. The boldest weight in the
     Living Uncut Sans family; not a stencil cut (no stencil weight
     bolder than Semi-Bold exists). */
  --font-nav-active: 'Living Uncut Sans Extra Black', sans-serif;

  /* One half of the Request a Quote font-swap — see FACE ASSIGNMENT
     above. A serif, despite the sans-serif fallback stack some
     earlier tooling (font-preview.html) used for it — corrected
     here. No longer package titles (see --font-pkg-title below —
     "make the package titles be in the uncut font"). */
  --font-serif: 'Aujournuit', serif;

  /* Package titles only ("Ceremony Magic," "Vows + Vibes," "The Full
     Journey") — "the uncut font, maybe a nice medium stencil or
     medium reg." Living Uncut Sans has no true "medium" weight file;
     picked the stencil cut at its lighter (400) weight, distinct from
     --font-masthead/--font-heading's Semi-Bold stencil, so titles
     read related but not identical to headings. First real use of
     this file — it existed only as a font-preview.html candidate
     before. */
  --font-pkg-title: 'Living Uncut Sans Stencil', sans-serif;

  /* Scale — desktop (>= 768px) */
  --t-label:    15px;  /* floor. Nothing on this site is smaller. */
  --t-body:     18px;
  --t-lead:     30px;
  --t-section:  56px;
  --t-display:  96px;

  /* Not on the 15/18/30/56/96 ladder — a layout-driven size, not a
     scale step. The masthead heading is sized so its widest line
     matches the credentials table's own width beneath it (664px at
     1440), at the 1440px reference width the rest of this brief's
     examples use.

     Recomputed again after the copy changed to "Your / Wedding, /
     Scored." (from "Your Day / Deserves / Its Score") — "Wedding," is
     now the widest line (663.97px at 149.89px font-size, converged
     via the same binary-search-on-664px-target technique as before).
     Confirmed explicitly that the size should grow to fit rather than
     holding at the old 146px out of caution ("that will mean text can
     get bigger, please let it"). No wrapping at this size. Desktop
     only — mobile keeps --t-display-m unchanged, its own column is
     much narrower and was not asked to fill full-width. */
  --t-masthead-heading: 149.89px;

  /* Heading font-size as a fraction of its column width: 149.89px /
     664px, the two numbers above. Lets the heading shrink with its
     column below 1440 instead of overflowing under the photo. */
  --masthead-heading-fit: 22.57;

  /* The boxed/split/width-fill Packages heading experiment is gone —
     "scratch the packages formatting all together." Plain single-line
     --t-section/--t-section-m now, no dedicated token. */

  /* Scale — mobile (< 768px) */
  --t-label-m:    15px;
  --t-body-m:     18px;
  --t-lead-m:     26px;
  --t-section-m:  40px;
  /* Was 56px. Mobile fixes pass: masthead heading only (the sole
     consumer of this token — .masthead-line, site.css), bumped to
     72px on direct instruction. Section headings are unaffected,
     they use --t-section-m (40px) above, unchanged. Verified "Wedding,"
     (the widest masthead line) still clears the 350px mobile content
     column at this size: 319px rendered, 31px to spare. */
  --t-display-m:  72px;

  --lh-body:     1.55;
  --lh-display:  0.85;
  --lh-label:    1.3;

  --track-display:  -0.04em;
  --track-body:     -0.01em;
  --track-caps:     +0.10em;

  --measure:  68ch;   /* hard cap on running prose, regardless of cell width */

  /* Client portal contract (clients/portal.html). The scroll box is US
     Letter, 8.5 x 11 in. The contract's main heading is 24px on every
     viewport, on direct instruction; it is not a step on the scale above. */
  --paper-ratio:       17 / 22;
  --t-contract-title:  24px;

  /* 15px is the floor because hierarchy is carried by caps,
     tracking, and color — not by size. Two text sizes exist below
     display: 15 and 18. Do not add a 12, 13, or 14. */


  /* ---------- GRID ---------------------------------------------
     Edge to edge at every width. Nothing is centred in a capped
     container. Spans are identical from tablet up, so the
     component remap table only needs two columns.
     ---------------------------------------------------------- */

  /* >= 1180px */
  --cols:     12;
  --margin:   48px;
  --gutter:   16px;
  /* column = 97.33px at 1440 viewport */

  /* 768–1179px — same 12 columns, tighter */
  --margin-t: 32px;
  --gutter-t: 12px;

  /* < 768px — six columns */
  --cols-m:   6;
  --margin-m: 20px;
  --gutter-m: 12px;
  /* column = 48.33px at 390 viewport */


  /* ---------- SPACE --------------------------------------------
     Nine steps, 8px base. Dividers are banned, so the RATIO
     between section gap and heading gap was the only thing
     grouping the page, deliberately held at 4:1 — direct
     instruction ("cut gaps between sections in half") halves
     --section-gap/-m to --s6/--s5 below, which cuts that ratio to
     2:1 instead. --heading-gap is unchanged: the instruction named
     the gap BETWEEN sections, not heading-to-content, and a smaller
     section gap still reads as clearly larger than the heading gap
     at 2:1 — flagged as a ratio change, not silently preserved.

     --s0 added below, off the 8px base: a half-step, explicitly
     requested for the What Couples Say name→event gap (4px, tighter
     than quote→name's 8px) rather than reusing --s1 for both and
     losing that distinction. */

  --s0:   4px;
  --s1:   8px;
  --s2:  16px;
  --s3:  24px;
  --s4:  32px;
  --s5:  48px;
  --s6:  64px;
  --s7:  96px;
  --s8: 128px;
  --s9: 192px;

  --section-gap:      var(--s6);  /*  64 — between sections (was --s8/128) */
  /* Was --s5 (48px). Mobile fixes pass, direct instruction ("section
     gap 72px") — doesn't land on an --s step (between --s6/64 and
     --s7/96), written as a raw value here since tokens.css is the
     one place that's allowed. */
  --section-gap-m:    72px;
  --heading-gap:      var(--s4);  /*  32 — heading to its content */
  --heading-gap-m:    var(--s3);  /*  24 — see .section-heading in
     site.css: this token was already correct, the mobile bug was
     .section-heading's own margin-bottom (hardcoded to --s5) stacking
     with each section's row-gap on top of it. Fixed at the rule, not
     here. */
  /* UNUSED as of the mobile fixes pass — "take indents out of links
     and contact sections within menu" removed its one consumer
     (.mobile-overlay-sublinks in site.css). Not deleted — same
     flag-don't-silently-remove practice this file already applies to
     --font-display and others; a later screen could still want this
     exact indent value back. */
  --expand-indent:    24px;


  /* ---------- RULE + SHAPE -------------------------------------
     AMENDMENTS ROUND 2 §1 — reverses the original "rules enclose,
     never divide" ban. Dividers are back, but SCOPED: rules appear
     in exactly TWELVE places and nowhere else —
       1. Between header row 1 and row 2
       2. Below header row 2
       3. The credentials block (outer box + cross inside, four
          cells sharing borders — a table, not four plates)
       4. Around every photograph, --rule-pad-photo padding
       5. Around each package row
       6. Around the three package rows as a group (nested inside
          5, --rule-gap channel)
       7. Form fields
       8. The Song List scroll container
       9. My Approach's decade timeline — RETIRED, AMENDMENTS ROUND 8.
          The timeline (and the rule it described — the horizontal
          line the 8 decade nodes sat on, plus the 1px box around its
          hover/tap popup) is gone, replaced by the reviews carousel,
          which adds no rule of its own. Location 9 stays listed, now
          empty, rather than silently renumbering 10-12 below.
       10. Above the header (top edge of .header-row1)
       11. Top of the footer (top edge of .site-footer)
       12. Bottom of the footer (bottom edge of .site-footer)
       13. Song List's 8 decade-filter buttons — 1px outline, rest
           state only (.songs-decade in site.css). AMENDMENTS ROUND 8
           §3, same override path as 9-12: explicit instruction
           ("rest — 1px --ink outline"), not a judgment call.
       14. My Approach's reviews carousel (.approach-reviews-viewport)
           — box around the slide content, reversing location 9's
           replacement ("this component adds no rule of its own").
           Added by explicit instruction, same override path as
           9-13, --shape-mid-a (see site.css).
       15. Between each row of the Song List (.song-row in site.css)
           — border-bottom: var(--rule), none on the last row.
           AMENDMENTS ROUND 9, same override path as 9-14: explicit
           instruction ("put divider lines in song list"), confirmed
           directly this reverses the ROUND 5-era decision at that
           same rule ("No row separators... the artist column in
           --ink-quiet creates a visual channel that guides the eye
           without any lines") rather than being a judgment call.
       16. Mobile menu overlay (.mobile-overlay-stack in site.css) —
           border-bottom under each top-level item (the three plain
           links, and each of the Links/Contact groups as a whole,
           i.e. after their sublinks, not between a group's own label
           and its sublinks). Mobile fixes pass, direct instruction
           ("put divider lines between links").
       17. Desktop header dropdowns — Links and Contact popups
           (.header-dropdown-inner in site.css). border-bottom between
           every item (none on the last), --rule-pad replacing the
           former --s2 margin-only spacing — same "rule to text" value
           as every other divided list on the site (Song List,
           Packages), not a new one. Direct instruction ("add divider
           lines in contact and links popup modals").
     AMENDMENTS ROUND 7 — locations 10-12 added by explicit
     instruction ("add a divider line at [the footer's] top and
     bottom. also quickly add one on top of header"), the same
     override path as location 9: this list was previously exhaustive
     at nine and a tenth was stated to be a stop condition, not a
     judgment call, so the addition is logged here rather than
     silently made. Note the footer badge band still gets NO border
     of its own — the badges moved into the Contact column this same
     round (see BRIEF.md AMENDMENTS ROUND 7), and the footer's own
     top/bottom rules above already bracket the whole footer,
     badges included.
     No section dividers otherwise. No decorative rules outside this
     list — if a thirteenth place seems to need one, that's a stop
     condition, not a judgment call.

     Every rule in the codebase MUST reference --rule, never a
     hardcoded `1px solid ...` — --rule: none is the escape hatch
     that reverses all eight at once, and a hardcoded value would
     silently survive that switch.
     ---------------------------------------------------------- */
  --rule:        1px solid var(--ink);  /* set to `none` to revert all eight places at once */
  --rule-width:  1px;
  --rule-gap:      12px;  /* rule to rule — nested channels (Packages' outer box around the three rows) */
  --rule-pad:      24px;  /* rule to text (Packages row padding, credentials cells) */
  --rule-pad-photo: 0;  /* AMENDMENTS ROUND 3 §1 — "photographs fill
                            their rule box," edge to edge, no inset.
                            Was 12px (itself already a change from an
                            original 4px). One-token change, verified:
                            every photo (.masthead-image,
                            .packages-piano-strip, .approach-photo)
                            references this token directly, no
                            hardcoded per-component padding found. */

  /* AMENDMENTS ROUND 5 — THE ORGANIC SHAPE SYSTEM. Replaces flat
     --radius: 0. "The only hard angles on the site are divider
     lines." Every photograph, border, box and cell otherwise gets
     irregular corners derived from the blob mark (§5, SIGNATURE
     ELEMENT). Intensity is inverse to interactivity: photographs
     (most-used-but-least-interactive) are the most organic; form
     fields (highest-precision hit area) are the least.

     Nine shapes, three levels of three variants each — this is the
     COMPLETE vocabulary, no tenth shape exists. Pixel values, never
     percentages: a percentage scales with the element, so a wide
     photograph would go oval while a small field stayed nearly
     square. Variants are ASSIGNED per element (see BRIEF.md's
     assignment table), never chosen at render time — no
     randomisation, no rotation logic, no nth-child cycling.

     Native <select>, <input type="date">, and iOS button chrome
     still need explicit resets — now to --shape-firm-a, not to 0. */

  /* SOFT/MID/FIRM widened per direct follow-up instruction ("apply a
     bit more blobbiness everywhere... to all things currently getting
     any blob treatment"), after the DECADE BLOB SET re-spread above
     read as more distinctly organic. Same idea applied to this older
     4-value px system: each tier's spread-around-its-own-mean was
     scaled up, magnitude scaled up slightly less, tier order (soft >
     mid > firm) and which corner is dominant per shape both held
     fixed — this is a widen, not a reshape. Multiplier tapers by
     tier, by explicit usability instruction ("how much depends on
     usability"): soft/mid ~1.5x spread (photos and containers have
     no precision constraint); firm ~2.2x spread but clamped to 2–18px
     — still well under half of the 44px --tap-target-min these
     controls sit on (a pill would need 22px), so a --shape-firm
     control still reads as a firm rounded-rect, not a blob. Verified
     at 390/1440 against every one of the 13 site.css usages,
     including the reviews-carousel arrows and the checkbox-adjacent
     email/search/quote fields — see DEVIATIONS for the one
     usability-driven exception (checkbox itself, unaffected — it was
     never on this token system, stays --shape-none/square). */
  /* SOFT — photographs, hero imagery. Most organic. */
  --shape-soft-a:  106px 10px 74px 30px;
  --shape-soft-b:  18px 95px 25px 69px;
  --shape-soft-c:  81px 24px 107px 4px;

  /* MID — video player, tables, containers, cells. */
  --shape-mid-a:   30px  6px 24px 12px;
  --shape-mid-b:    6px 35px 12px 23px;
  --shape-mid-c:   23px 17px 35px  6px;

  /* FIRM — form fields, buttons, inputs. Least organic. */
  --shape-firm-a:  13px  2px  9px  4px;
  --shape-firm-b:   2px 15px  4px  9px;
  --shape-firm-c:   8px  6px 15px  2px;

  /* KEY — mobile sticky CTA only; reads as a black key, deliberately
     outside the --shape-* family. A one-element exception, not a
     tenth shape (see BRIEF.md §17 assignment table). */
  --shape-key:      0 0 4px 4px;
  /* The white-key notch: a rectangle removed from the bottom-right
     corner while the key is alabaster (.mobile-sticky-cta.is-alt,
     site.css). Same one-element exception as --shape-key. */
  --key-notch-w:    120px; /* 20px of it sits over the page margin (--margin-m), so 100px shows over the plate/photo */
  --key-notch-h:    13px;
  --key-w-m:        203px;  /* homepage keys (side sheet + Request a Quote), both the same width: the Request a Quote key's natural width (202.5px) rounded up */
  --home-key-shift: 50px;   /* homepage keys moved apart: sheet down, Request a Quote up, by this much */
  --key-notch-r:    4px;  /* radius on the notch's inner corner and where it meets the bottom edge */

  /* NONE — divider lines, internal table cells. The only true
     straight edges left on the site; that's the point. */
  --shape-none:     0;

  /* DECADE BLOB SET (AMENDMENTS ROUND 8 §3) — Song List's 8 decade-
     filter buttons, one shape per decade, assigned and fixed (never
     randomised, never rotated — see .songs-decade in site.css). "The
     nine shapes... is the COMPLETE vocabulary, no tenth shape exists"
     above predates this round; these 8 are a second, separate closed
     set the source document explicitly authorizes adding ("add them
     to tokens.css as a closed set... derived from the blob mark
     family, --shape-soft intensity"), not a violation of that
     original close. PROVISIONAL — pixel quadruples in the same
     magnitude range as --shape-soft-a/b/c above, picked for visual
     distinctness across all 8, not measured against any specific
     reference the way --shape-soft/mid/firm's own values were.

     History, for whoever reads this next: this round-tripped through
     an 8-value percentage/elliptical version plus a static per-button
     rotation, then back to this — plain 4-value px corner-radius, no
     rotation — by direct instruction ("revert... just vary corner
     radius"). Simple corner-radius is the standing form; don't
     reintroduce the elliptical/rotated version without being asked
     again. */
  --blob-50s:  72px 24px 56px 32px;
  --blob-60s:  28px 68px 24px 60px;
  --blob-70s:  60px 32px 72px 20px;
  --blob-80s:  20px 56px 28px 68px;
  --blob-90s:  64px 36px 20px 56px;
  --blob-00s:  32px 60px 68px 24px;
  --blob-10s:  56px 20px 36px 72px;
  --blob-20s:  24px 64px 32px 60px;
  /* Performance Log oscilloscope grain (log/scope.js): each blob grain's
     square cell. By direct instruction ("way finer ... grain size"). */
  --scope-cell: 4px;

  /* No shadows. No gradients. No backdrop-filter. No translucency
     of any kind — the previous site's frosted panels are the
     single largest thing being removed. */

  /* iOS/Android's own documented minimum comfortable tap-target
     size — not on the --s scale (44 falls between --s4's 32px and
     --s5's 48px, matches neither), and not a design choice to round
     off: AMENDMENTS ROUND 4's mobile sticky CTA states it directly. */
  --tap-target-min: 44px;


  /* ---------- STATES -------------------------------------------
     Hover is gated to pointer devices. Touch gets no hover and no
     active state — this is deliberate, not an oversight.
     ---------------------------------------------------------- */

  /* Content links (inside running prose). Was var(--accent), then
     --ink with a weight-only distinction and no underline at all.
     AMENDMENTS ROUND 4 §5, restated: "--ink, permanent 1px underline,
     0.15em offset" — an actual, sitewide change from the no-underline
     state, not a restatement of it. --link-weight stays: nothing said
     to remove the weight distinction, and it doesn't conflict with
     also having an underline now. */
  --link-color:            var(--ink);
  --link-decoration:       underline;
  --link-weight:           var(--weight-strong);

  /* Now the sitewide .prose-link value too, not just the one Videos
     external-link exception it was introduced for — that exception
     is no longer visually distinct from the base rule as a result
     (both underline the same way now), which is fine; nothing said
     to differentiate them further. Thickness reuses --rule-width
     (the site's one hairline value). */
  --link-underline-offset: 0.15em;

  /* Navigation links (header, footer, menu, cards) */
  --nav-color:             var(--ink);

  /* AMENDMENTS ROUND 2 §2's color-only hover (--ink to --accent) was
     later removed entirely ("take away blue hover state") — no
     --hover-color token anymore, hover has no color effect anywhere
     in the header. */

  /* Focus */
  --focus-ring:            2px solid var(--ink);
  --focus-offset:          2px;
  /* Dark-ground counterpart — the --ink ring is invisible on an
     --ink plate (reviews carousel controls). Same width/offset,
     --ink-inverse instead. */
  --focus-ring-inverse:    2px solid var(--ink-inverse);

  /* Active — none. No pressed state exists. */

  /* Disabled */
  --disabled-color:        var(--ink-disabled);
  --disabled-outline:      1px solid var(--ink-disabled);

  /* Error — the errored field INVERTS its ground. The only filled
     dark plate on the site. */
  --error-plate:           var(--carbon);
  --error-plate-text:      var(--ink-inverse);
  --error-message-color:   var(--ink);
  --error-message-weight:  var(--weight-strong);


  /* ---------- MOTION -------------------------------------------
     AMENDMENTS ROUND 2 §4 — EXHAUSTIVE LIST, fully replaced. Two
     entries, neither of which is the original two. Adding a third
     requires the designer's approval, not an agent's judgement —
     a third has since been approved directly ("that's motion entry
     THREE. Log it in BRIEF.md") — see entry 3 below, not an
     agent's judgement call.

     NOTHING ON THIS SITE ANIMATES ON SCROLL. No reveal-on-view,
     no parallax, no fade-up. Not on headings, not on images,
     not on sections.

     The masthead reveal is REMOVED — heading and clusters appear
     immediately, no mask, no stagger, no fade. The accordion is
     REMOVED too — LINKS/CONTACT sublinks are always expanded, never
     collapsed (see BRIEF.md mobile overlay + header dropdown specs).
     ---------------------------------------------------------- */

  --ease:  cubic-bezier(0.16, 1, 0.3, 1);

  /* 1 — Section links pop in. Header row 2, left to right, on page
     load, once. Never on scroll-back — there is no scroll trigger,
     same as everything else on this list. */
  --popin-duration:  300ms;
  --popin-stagger:    60ms;

  /* 2 — Request a Quote's perpetual letter-by-letter font-swap
     (.cta-letter/@keyframes cta-font-swap in site.css) reuses this
     same cycle length. Was a color pulse (--pulse-color, since
     removed — "take away the blue and the pulse"); the duration
     token's name and its "one entry that doesn't stop after first
     paint" role both carry over unchanged, only the effect it drives
     is new. */
  --pulse-duration:  2400ms;

  /* 3 — My Approach's decade timeline nodes (cols 1-6, replaces the
     floral photo): rest -> active is a width/height-only transition
     on the node dot, --timeline-node-size to --timeline-node-size-
     active (see MY APPROACH TIMELINE below). Explicitly approved as
     a third MOTION entry, not inferred. */
  --timeline-transition: 500ms;

  /* 4 — Header row 2's active-section link (.header-row2-link.is-
     active in site.css): --font-body/sentence-case to --font-masthead/
     all-caps+--track-caps and back, tracking scroll position (and a
     direct click on the link itself) via js/header.js. This IS
     "animates on scroll" by the letter of the rule above — explicitly
     requested anyway
     ("add an active state to the 2nd row links where the section
     we're on is all caps stencil semibold... animated... morph into
     new font and back when it passes"), so treated as a fourth
     explicit exception rather than silently declining it. Reuses
     --timeline-transition/--ease rather than a new duration token —
     same "rest -> active tied to scroll position" class of motion as
     entry 3. Font-family and text-transform are both non-interpolable
     (static font files, no variable-weight axis to animate through) —
     they snap at the transition's midpoint the same way .cta-letter's
     font-swap already does (see @keyframes cta-font-swap); only
     letter-spacing actually eases. "Morph" is sold by that combination
     (spacing spreads/collapses smoothly around a hard font/case cut),
     not a true glyph blend — that isn't achievable with these font
     files, flagged rather than faked with a banned opacity crossfade.

     All four wrapped in prefers-reduced-motion: reduce — the pop-in
     becomes instant (no stagger, no fade), the CTA font-swap stops
     entirely, the timeline node resize becomes instant (no
     transition), and the row 2 active-state swap becomes instant too
     (no eased letter-spacing).

     Entry 3 (the timeline node resize) is now dead code — the
     timeline itself is retired, AMENDMENTS ROUND 8 — but stays listed
     rather than renumbering 4 out from under its own comment above.

     5 — My Approach's reviews carousel (replaces the timeline in the
     same cols 1-6 slot): manual arrows plus auto-advance, sliding
     .approach-reviews-track via `transform: translateX(...)`, reusing
     --timeline-transition/--ease (no new duration token for the
     motion itself — only --reviews-autoplay-interval, the dwell time
     between auto-advances, is new; see MY APPROACH — REVIEWS
     CAROUSEL above). Auto-advance pauses on hover (gated to
     hover-capable pointers, same convention as everywhere else on
     this list) and on focus, and stops permanently — not just pauses
     — the first time either arrow is pressed. The amendment that
     specified this called it "motion entry four"; by the time it was
     built, entry 4 above (the row 2 active-link morph) already
     existed, so this is 5, renumbered here rather than silently
     colliding with it. Wrapped in prefers-reduced-motion: reduce —
     auto-advance never starts at all, transform transition is
     instant, only the arrows work.

     7 — Mobile sticky CTA, "the key" (weddings/events, <768px): it
     slides off the right edge and back as js/sticky-cta.js toggles
     .is-offscreen by which section sits under it. DIRECTION-
     ASYMMETRIC — a key strikes fast and stops dead, returns slower
     and springs. The transition sits on the state class, not the
     base, so the direction switch needs no JS: .is-offscreen (going
     out) uses the strike pair, the base rule (coming back) uses the
     release pair. Transform-translate only; the CTA's colour swap
     stays on --ease/--timeline-transition and is not part of the
     strike. Wrapped in prefers-reduced-motion: reduce — no
     transition. Confirmed as the seventh entry: entry 6 (sheet
     scramble, below) was the last one logged. */
  /* The strike — accelerating away, no overshoot. A key hitting the
     keybed doesn't bounce past it; the felt absorbs it. */
  --ease-strike:      cubic-bezier(0.55, 0, 1, 0.45);
  --duration-strike:  200ms;
  /* The release — slower, settling past rest and back. */
  --ease-release:     cubic-bezier(0.34, 1.56, 0.64, 1);
  --duration-release: 450ms;


  /* ---------- HEADER -------------------------------------------
     Desktop (>=768px, unchanged by AMENDMENTS ROUND 2 or 4): row 1
     pins (per the explicit override earlier in this project's
     history — see git log, not the original brief), row 2 pins right
     below it.

     Mobile (<768px) — AMENDMENTS ROUND 4 §1, replacing ROUND 2 §7's
     three-band/192px-pinned structure entirely: two rows now, not
     three-bands-within-row-2.
       row 1  --header-row1-m (64px), wordmark + hamburger, SCROLLS AWAY
       row 2  --header-row2-m (36px), section links only, horizontally
              scrollable, PINS — Request a Quote is no longer part of
              row 2 at all; it moved to a floating sticky button (see
              MOBILE STICKY CTA below).
     --header-links-m and --header-cta-row-m (the old wrapped-links-
     then-CTA-row pair) are retired along with the structure they
     measured — nothing left references them. --header-row2-m is load-
     bearing again (was a stale, unused 32px value before this
     rebuild, since the actual old layout used the other two tokens
     instead) — 36px per this round's explicit spec.
     ---------------------------------------------------------- */

  --header-row1:      88px;
  --header-row1-m:    64px;
  --header-row2:      56px;
  --header-row2-m:    36px;

  /* Applies at every width now, not just mobile — see site.css's
     .header-wordmark img. Was 22px ("make header icon bigger" — this
     was the actual target, not the hamburger). --wordmark-height
     (48px, a since-orphaned desktop-only value from before the
     lettering-only wordmark) removed rather than left dead. */
  --wordmark-height-m:    32px;

  /* .header-row1's items align by their bottom edge (align-items:
     flex-end — deliberate for the nav/signup text), but the taller
     wordmark image's own vertical midpoint sits above the nav text's
     midpoint when both just share a bottom edge. Measured offset to
     bring the two midpoints level: navMid(30) - wordmarkMid(23) = 7,
     at the current --wordmark-height-m (32px) and --t-label (15px)
     nav text. Recompute if either changes. */
  --wordmark-offset:      7px;

  --signup-height:    40px;

  /* Was a raw 24px in site.css — moved here per "no raw values
     outside tokens.css" while touching this rule anyway. NOT the
     "make header icon bigger" target after all — that meant the
     wordmark (see --wordmark-height-m below), reverted to its
     original size. */
  --hamburger-size:   24px;

  /* Mobile fixes round 3 — hamburger rebuilt from the single rotating
     line (see .header-hamburger's own history in site.css) back to
     three bars, thicker and more condensed than the sitewide 1px
     --rule-width hairline (a different, unrelated value — this is an
     icon stroke weight, not a divider). No existing token covered
     either value; both picked directly by the designer from two
     concrete pixel options (2px/5px vs 3px/4px), not guessed. */
  --hamburger-bar-weight: 2px;
  --hamburger-bar-gap:    5px;


  /* The Kippenberger stamped-stack component (enclosed / overhang /
     unboxed / empty variants) is RETIRED. Everywhere it used to
     appear — mobile menu overlay, LINKS/CONTACT dropdowns, footer,
     /card, /wipa, /jar, /music release list, 404 nav — the
     replacement is: 15px caps, --ink for the item, --ink-quiet for
     any value beneath it, separated by --s3. No enclosure, no
     overhang, no empty boxes. Hierarchy by caps, color, and space —
     the same mechanism as the info clusters. See BRIEF.md §4. */


  /* ---------- KEYS (the signature) ------------------------------
     A single piano-key rectangle lifted from wordmark_keys.
     Three keys = ceremony / cocktail hour / reception.

       CEREMONY MAGIC     filled 1 of 3
       VOWS & VIBES       filled 2 of 3
       THE FULL JOURNEY   filled 3 of 3

     Outlined = 1px --ink, no fill.  Filled = solid --ink.

     PERMITTED LOCATIONS — exhaustive:
       /weddings Packages rows
       /weddings Videos tabs
       the conversion path (form prefill, /thanks) when the lead
         came from /weddings
       favicon (three filled)
       the two one-sheet PDFs
       /music release formations

     NOT on /events — events have no fixed stages.
     NOT as a checkbox, bullet, list marker, or decoration.
     ---------------------------------------------------------- */

  --key-width:    14px;
  --key-height:   19px;   /* the wordmark_keys rectangle ratio */
  --key-gap:       6px;

  /* /music formation constraints:
       rotations   0deg and 90deg ONLY
       adjacency   every key shares a full edge with another
       count       5 per formation
       lattice     snaps to a square grid = key short edge
       seed        derived from release title, generated at BUILD
                   time so a given release always renders the same
                   formation. Never randomised at runtime. */


  /* ---------- PHOTOGRAPHY --------------------------------------
     Six ratios. An image at any other ratio is a defect.
     Every <img> declares width and height attributes matching its
     ratio. No CSS background-image for content photography.
     No filters, no grade — grain and warmth as shot.

     SUPERSEDED — the exception below no longer applies. My Approach's
     left/"floral" photo (.approach-photo--video) that this exception
     was written for has been replaced entirely by the decade timeline
     (see BRIEF.md My Approach, RULE + SHAPE location 9 above) — there
     is no non-ratio photo crop left on the page. Kept as history:
     Checked all 5x5 permitted-ratio pairings against the right
     photo (--ratio-portrait, unchanged) for an exact bottom-edge
     match first — the closest, --ratio-video, still missed by
     ~11-16px, and a fluid 12-column grid means even a bespoke fixed
     ratio can't close that gap at every viewport width (the two
     columns scale at different rates as the page resizes). By
     explicit instruction, this one photo's height was measured
     and set directly at runtime so its bottom always matched the
     right photo's exactly, at every width — cropped via
     object-fit:cover same as any other photo, just not to one of
     the five ratios then in use.
     ---------------------------------------------------------- */

  --ratio-cine:      2.39 / 1;   /* homepage hero, desktop */
  --ratio-video:     16 / 9;     /* video player, /card clip */
  --ratio-portrait:  4 / 5;      /* homepage hero mobile, My Approach panels */
  --ratio-square:    1 / 1;      /* card family, /music artwork */
  --ratio-tall:      1 / 2;      /* testimonial spanner */
  --ratio-stripe:    3 / 1;      /* What Couples Say mobile — thin horizontal stripe (desktop uses --ratio-tall) */

  /* HOMEPAGE HERO FORK — UNUSED. The WEDDINGS / EVENTS words over the
     photograph (and the 40px/72px mobile rows) were replaced by direct
     instruction ("Bookings" label + dashed arrows to the header links).
     Kept, not deleted, same practice as the retired timeline tokens:
     --hero-fork-center (24.3%) is the measured eye line of the
     book_desk.webp 2.39:1 crop (source y 335 -> frame y 195 of 803),
     which the replacement may still want. */
  --hero-fork-size-m:  40px;
  --hero-fork-row-m:   72px;
  --hero-fork-center:  24.3%;
  --hero-desk-pos-y:   50.542%;  /* object-position y of the y=140 crop of book_desk.webp (140 / (1080 - 803)); the homepage photo now fills the viewport instead of a 2.39:1 frame, so this only matters above ~1.78:1 */

  /* HOMEPAGE INTRO ARROWS: dash pattern of the curved arrows from the
     second intro popup to the header links (or, below 768px, the
     hamburger). Given as "1px dashed"; the dash/gap lengths are not, so
     these are picked, flagged. Stroke width is --rule-width. */
  --arrow-dash:       6px 5px;

  /* HOMEPAGE INTRO ARROW (js/intro.js). The arrow forms one dash at a
     time and evaporates one dash at a time from its origin. Steps are
     proposed values, approved: forming 20ms per dash (~1.4s for the
     ~70-dash arrow), evaporating 12ms per dash (~0.85s). Head: the
     old marker's chevron, 12 long, 7 either side of the axis. */
  --arrow-form-step:       20ms;
  --arrow-evaporate-step:  12ms;
  --arrow-head-len:        12px;
  --arrow-head-half:       7px;

  /* Bookings arrows + label: "dashed arrows thicker." Arrow line 2px
     (was --rule-width, 1px; 3px was tried and reduced to 2px). Each
     dash has a solid --ink-inverse halo underlay, --bookings-halo
     outward — removed once, restored by direct instruction ("give it
     its stroke back") at 8px, then halved to 4px ("just too thick"). */
  --arrow-stroke:     2px;
  --bookings-halo:    4px;

  /* CURSORS. The two .cur files in the repo root. Hand is the
     cursor everywhere (including over links and buttons, where the
     UA pointer used to show); grabbed replaces it while the mouse
     is down (:active) and while a dialog is being dragged.
     Hotspots are read from the .cur headers (hand 14,2 / grabbed
     14,5), not chosen.
     Absolute paths: url() inside a custom property resolves where
     it is used, so a relative path would break per stylesheet.
     Filenames are case-sensitive in production: Grabbed32-32.cur has
     a capital G, hand32-32.cur is lowercase. */
  --cursor-hand:      url('/hand32-32.cur') 14 2, auto;
  --cursor-grabbed:   url('/Grabbed32-32.cur') 14 5, grabbing;

  /* Two-hands cursor over the packages image (desktop, js/piano-hands.js):
     the native cursor is hidden and two copies of hand32-32.png follow
     the pointer, both rotated --hand-rotate about the hotspot (the
     .cur's 14,2), the second mirrored and --hands-gap below the first. */
  --hand-size:        32px;
  --hand-hot-x:       14px;
  --hand-hot-y:       2px;
  --hand-rotate:      -90deg;
  --hands-gap:        50px;

  --hero-peek:      240px;       /* photo visible below the masthead fold */
  --hero-peek-m:    120px;

  /* Placeholder slots render as a --ink-disabled field printing
     the slot name and required ratio. Never a plain grey box. */


  /* ---------- VOICE LOGO (weddings + events masthead, >= 768px) ------
     The Voice logo grows to fill the room under the masthead's last
     block; by direct instruction ("make logo 150px smaller") it is
     then drawn this much NARROWER than that room allows, scaled
     proportionally. --voice-logo-shrink-h is the same cut as a height
     (shrink x the image's 702 / 1243 ratio), which is what the layout
     actually subtracts. Not on the --s scale. Mobile is unaffected:
     there the logo is simply the column's width. */
  --voice-logo-shrink:   150px;
  --voice-logo-shrink-h: calc(var(--voice-logo-shrink) * 702 / 1243);

  /* ---------- PACKAGES KEYS --------------------------------------
     The ■□□ / ■■□ / ■■■ glyphs in Packages column 7. Not on the
     --s* spacing scale (6px falls between nothing on it) — a
     component-specific pair given directly in spec, not a guess. */
  --pkg-key-size:  14px;
  --pkg-key-gap:    6px;

  /* ---------- SONG LIST --------------------------------------------
     BRIEF states these two literally: "600px scroll container" and
     a "20px square" custom checkbox. Neither is on the --s scale (600
     is off it entirely; 20 falls between --s2's 16 and --s3's 24) —
     given directly in spec, not guessed. */
  --songlist-height: 600px;
  --checkbox-size:    20px;

  /* Decade filter (AMENDMENTS ROUND 8 §3). --t-decade-label (24px) is
     given directly in spec ("put decade text in 24px uncut stencil"),
     off the --t-* type scale entirely (between --t-body 18 and
     --t-lead 30) — a component-specific size, not a guess, same
     convention as --pkg-key-size above.

     --songlist-decade-btn is MEASURED, not guessed — "the table's
     height drives the button height, report what that works out to."
     Measured live at 1440px: the grid starts at 314.34375px from the
     viewport top (fixed by the heading/search/filter rows above it,
     none of which change height across desktop widths) and needs to
     reach .songs-index's bottom edge at 798.75px — a required total
     grid height of 484.40625px. 4 rows, 3 gaps at --s2 (16px = 48px
     total), leaves 436.40625px for 4 button rows: 109.1015625px each,
     kept at full precision (same convention as --t-masthead-heading
     elsewhere in this file — an exact-fit value isn't rounded off).
     Same value used at mobile too — the alignment constraint itself
     is desktop-only (sidebar/table are side by side only there), but
     nothing in the source document asks for a different mobile size,
     so the computed number carries over rather than inventing a
     second one. */
  --songlist-decade-btn:   109.1015625px;

  /* ---------- MY APPROACH TIMELINE (RETIRED, AMENDMENTS ROUND 8) ----
     The interactive decade timeline these three tokens sized is gone
     — replaced by the reviews carousel (see MY APPROACH — REVIEWS
     CAROUSEL below). Flagged rather than deleted, same as
     --font-accent elsewhere in this file: nothing currently
     references these, but they document a real, previously-shipped
     component rather than a guess, so they stay as history instead
     of vanishing silently. RULE + SHAPE location 9 (that section,
     above) is retired along with them — the carousel adds no rule of
     its own. */
  --timeline-node-size:        10px;  /* rest state, 1px outline, no fill */
  --timeline-node-size-active: 20px;  /* active state, filled --ink */
  --timeline-popup-width: calc(3 * var(--col-w) + 2 * var(--grid-gutter));

  /* ---------- WHAT COUPLES SAY (RETIRED, AMENDMENTS ROUND 8) --------
     The section this sized — "560px, fixed, four panels sharing top
     and bottom edges" — is removed; its three reviews moved into My
     Approach as a carousel (see below). Flagged, not deleted, same
     reasoning as MY APPROACH TIMELINE above. */
  --reviews-panel-height: 560px;

  /* ---------- MY APPROACH — REVIEWS CAROUSEL (AMENDMENTS ROUND 8 §2)
     Replaces the decade timeline in the same cols 1-6 slot. Type
     sizes/colors/tracking all reuse existing tokens (--t-body,
     --t-label, --ink, --ink-quiet, --track-caps, --s1/--s0 for the
     two attribution gaps — see .approach-review-attr in site.css);
     nothing new needed there. Two new values below:

     --reviews-autoplay-interval — how long each slide holds before
     auto-advancing. NOT given in spec; no token on this site covers
     "carousel dwell time" (--pulse-duration is a different, faster
     cycle for the CTA font-swap). 6000ms picked as a common default
     and flagged — confirm or override.

     Shape: the two arrow buttons use --shape-firm-b (prev) /
     --shape-firm-c (next) — reusing the FIRM family per the
     amendment's own instruction ("interactive, so they get the least
     organic level"), distinct variants per the site's existing
     paired-element convention (e.g. .reviews-photo--couples/songs
     took different --shape-soft variants). Logged in BRIEF.md's shape
     assignment table.

     MOTION — the amendment's own text calls this "motion entry four,"
     but tokens.css already had a fourth entry (the header row 2
     active-link font morph) by the time this was implemented; this is
     really entry FIVE. Renumbered here, not silently — see MOTION
     below. No arrow hover state is specified anywhere in the
     amendment (only rest states for the carousel's arrows — contrast
     with §3's explicit rest/hover/active table for the decade filter
     buttons, a different, still-held component), so none is invented
     here: rest + the sitewide focus-visible ring only. */
  --reviews-autoplay-interval: 6000ms;

  /* ---------- HEADER DIALOGS (Links / Contact / Newsletter / Log) --
     The desktop header's Links, Contact, Newsletter and Performance
     Log open in a draggable "nostalgic" window: --ink ground,
     --ink-inverse features, close box top right, grain texture on the
     title bar (dense) and on the frame around the panel (sparse).
     Gradients are banned, so the grain is an inline-SVG dot tile, not
     a repeating gradient. The dots are --ink-quiet-inverse's own
     literal (#A5A099, written out here because a data URI cannot read
     a custom property — change both together).
     --dialog-drag-threshold: touch/pen must move this far before a
     drag starts (CLAUDE.md gesture rule, ~10px); mouse drags at once.
     --dialog-w-embed: Substack's own embed snippet is 480 wide, and
     its 480x320 shape gives the ratio. */
  --dialog-grain-bar:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2' height='2'%3E%3Crect width='1' height='1' fill='%23A5A099'/%3E%3C/svg%3E");
  --dialog-grain-frame: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='1' fill='%23A5A099'/%3E%3C/svg%3E");
  --dialog-drag-threshold: 10;  /* px, unitless: read by js/dialog.js */
  --dialog-w-embed:     480px;
  --dialog-embed-ratio: 3 / 2;
  /* Below 768px the embed-size window (the homepage intro popups) is
     65% of its full-width size, by direct instruction. Width only:
     text stays at --t-label/--t-body-m, so the window grows taller. */
  --dialog-embed-scale-m: 0.65;
  /* ZOOM RECTS (js/dialog.js `zoom`, the homepage intro popups and the
     Performance Log): vintage-OS window open/minimise. 1px outline
     rects (--rule-width, --ink, no fill) step between a taskbar slot,
     centred on the viewport's bottom edge, and the window's rect, over
     --duration-strike. Slot size by direct instruction. */
  --zoom-slot-w: 128px;
  --zoom-slot-h: 16px;

  /* ---------- FOOTER ---------------------------------------------
     AMENDMENTS ROUND 7 — the four footer lists (Contact/Follow/For
     Planners/More) previously sat at staggered vertical positions per
     BRIEF.md ("first item, second, second-to-last, last"); that
     stagger is reversed by explicit instruction ("align the heading
     for each footer section") and the four headings now share one
     row. The --footer-row token that realized the old stagger is
     removed as dead weight, not left behind unused.

     --footer-badge-height is a new raw value: no existing token
     covered "badge size," so this was asked rather than guessed —
     40px chosen directly, matching --signup-height. The Knot/GigSalad
     badges are square source assets, so height alone (width: auto)
     is enough to size both equally. */
  --footer-badge-height: 60px; /* 1.5x the original 40px, by direct instruction */

  /* /events masthead photo, below 768px: the crop moves the image up
     75px from its 31% anchor, by direct instruction. */
  --events-hero-shift-m: 75px;

}


/* ============================================================
   FONT FACES.
   Gloock is self-hosted (subsetted to Latin, from Google's own
   "latin" unicode-range split of the css2 API response).

   Host Grotesk is self-hosted from fonts/HostGrotesk-Variable.woff2
   (converted from the OFL variable TTF in fonts/Host_Grotesk/, full
   glyph set, no subsetting). It was loaded from Google Fonts until
   Google's latin/latin-ext subsets turned out not to include the
   arrows U+2192 / U+2197, so those fell through to the system
   fallback (Apple Color Emoji on iOS). Only the two weights this
   site actually uses (--weight-body 400, --weight-strong 700) are
   declared, each pinning the variable axis to that instance.

   Gloock is font-display: block — it's the masthead, above the
   fold on first paint, and preloaded, so the invisible-text wait
   is brief and a flash of a fallback serif would be worse.

   Host Grotesk uses font-display: swap — it carries all running
   body copy, most of it below the fold or not first-paint-critical,
   so showing the fallback sans immediately and swapping in is the
   better tradeoff than holding text invisible.
   ============================================================ */

@font-face {
  font-family: 'Gloock';
  src: url('/fonts/gloock-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Host Grotesk';
  src: url('/fonts/HostGrotesk-Variable.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Host Grotesk';
  src: url('/fonts/HostGrotesk-Variable.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Living Uncut Sans Stencil Semi-Bold';
  src: url('/fonts/Living-Uncut-Sans-Semi-Bold-stencil.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: block; /* masthead heading is above-the-fold, LCP-adjacent — same reasoning as Gloock */
}

@font-face {
  font-family: 'ParaRubber';
  src: url('/fonts/ParaRubber-Medium.otf') format('opentype');
  font-weight: 500;
  font-style: normal;
  font-display: swap; /* scattered inline text (links), not an LCP-critical element like the masthead — swap avoids blocking render for it */
}

@font-face {
  font-family: 'Living Uncut Sans Extra Black';
  src: url('/fonts/Living-Uncut-Sans-Extra-Black.woff2') format('woff2');
  font-weight: 900;
  font-style: normal;
  font-display: swap; /* nav "active" state + part of an already-visible CTA's animation, not itself an LCP element */
}

@font-face {
  font-family: 'Aujournuit';
  src: url('/fonts/Aujournuit-Regular.woff2') format('woff2'),
       url('/fonts/Aujournuit-Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Living Uncut Sans Stencil';
  src: url('/fonts/Living-Uncut-Sans-Regular-stencil.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* package titles, not an LCP element */
}


/* ============================================================
   BREAKPOINTS — three. Not ten.
   The codebase currently has 479/480, 600, 620, 700/701,
   767/768/769, and 1023. All of them collapse into these.
   ============================================================ */

/* < 768px   — 6 columns, 20px margin, 12px gutter
   768–1179  — 12 columns, 32px margin, 12px gutter
   >= 1180   — 12 columns, 48px margin, 16px gutter          */


/* ============================================================
   HOVER GATING — required, not optional.
   Without this, touch devices get a sticky hover box that
   persists until the next tap.
   ============================================================ */

@media (hover: hover) and (pointer: fine) {
  /* all :hover rules live here and nowhere else */
}


/* ============================================================
   REDUCED MOTION
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  /* masthead reveal: no transform, no stagger, immediately visible
     accordion: instant toggle, no transition                    */
}
