/*
  OzzyFest — styles.css  ·  the "Rally Press" skin
  My component + layout + motion-state layer. Tokens, @font-face, reset and the
  reveal-guard live inline in <head>; this file consumes those tokens.

  The concept: the page is a limited-run vintage comic / risograph RALLY POSTER
  coming off a newsprint press. The signature craft is print itself —
    · the STAR mark printed with visible CMYK MISREGISTRATION (an offset gold
      "light" plate, an on-register red key plate, and a deep-red comic ink line);
    · printer's REGISTRATION crosshairs + a CMYK COLOR-CONTROL BAR as recurring
      press furniture (graphic only, never invented words);
    · tonal BEN-DAY HALFTONE (a static radial-gradient dot grid, GPU-cheap — no
      DOM dots, no per-node filters) as the ink, and as the fill of the "one" star;
    · MISREGISTERED offset-ghost display type.
  It opens loud and warm, holds ONE deliberately SILENT beat of gravity at the
  DIPG section (the press goes quiet — no halftone, no color bar, no ghost, the
  only place deep-red is a mood and not just ink), then detonates into the
  signature — one halftone star multiplying into a crowd of red stars that fills
  the frame — and lands on the ask. Red is belonging; gold is the light in the
  star; deep-red is the ink; cream is the newsprint. Mobile-first; every
  text/bg pair is WCAG-AA verified in the head ledger, and every dot field is
  painted behind type or masked away from any running text.
  — Ralph Dubreus
*/

@layer layout, components, motion, polish;

/* =====================================================================
   LAYER: layout
   ===================================================================== */
@layer layout {

  .sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
  }

  .skip-link {
    position: fixed; top: var(--sp-3); left: var(--sp-3); z-index: 100;
    padding: 0.7em 1.1em; background: var(--ink); color: var(--cream);
    font-family: var(--font-body); font-weight: 600; font-size: 0.9rem;
    border-radius: var(--r-md); text-decoration: none;
    transform: translateY(calc(-100% - var(--sp-4)));
    transition: transform var(--dur-standard) var(--ease-out-quart);
  }
  .skip-link:focus-visible { transform: translateY(0); }

  main { display: block; }

  /* Section rhythm — generous vertical air, fluid gutters */
  .about, .dipg, .help, .site-footer {
    padding-inline: var(--gutter);
    padding-block: clamp(5rem, 3rem + 12vw, 12rem);
    position: relative;
  }

  .section-head { margin-bottom: clamp(2.5rem, 1.5rem + 4vw, 5rem); }

  .prose { max-width: var(--measure); }
  .prose p + p { margin-top: 1.25em; }

  /* ---- HERO layout — a poster plate: content left, oversized star bleeding right ---- */
  .hero {
    /* Fallback for engines without small-viewport units; modern browsers take the
       svh line below and override it. */
    min-height: 100vh;
    /* Size to the SMALL viewport (toolbars PRESENT) so the layout reserves for
       mobile-Safari's bottom toolbar and the CTA never lands behind it on load. */
    min-height: 100svh;
    padding-inline: var(--gutter);
    padding-block: clamp(5rem, 4rem + 6vw, 8rem) var(--sp-7);
    display: grid; align-content: center;
    position: relative; isolation: isolate;
    overflow: hidden;                  /* the star can bleed past the plate edge */
  }
  .hero__grid {
    display: grid; gap: clamp(1rem, 0.5rem + 2vw, 1.75rem) var(--gutter);
    grid-template-columns: 1fr;
    grid-template-areas:
      "eyebrow"
      "title"
      "accent"
      "star"
      "sub"
      "meta"
      "cta";
    align-items: start;
  }
  .hero__eyebrow { grid-area: eyebrow; }
  .hero__title   { grid-area: title; }
  .hero__accent  { grid-area: accent; justify-self: start; }
  .hero__sub     { grid-area: sub; }
  .hero__meta    { grid-area: meta; }
  .hero__cta     { grid-area: cta; justify-self: start; }
  .hero__star    { grid-area: star; justify-self: center; }
  .hero__scrollhint {
    position: absolute; left: var(--gutter); bottom: clamp(1.25rem, 1rem + 1vw, 2rem);
  }

  @media (min-width: 60rem) {
    .hero__grid {
      grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
      grid-template-areas:
        "eyebrow star"
        "title   star"
        "accent  star"
        "sub     star"
        "meta    ."
        "cta     .";
      column-gap: clamp(2rem, 1rem + 4vw, 6rem);
      align-items: center;
    }
    /* the oversized star runs the full height of the type column and BLEEDS
       off the right plate edge — a die-cut poster move. overflow:hidden on
       .hero clips it cleanly. */
    .hero__star {
      justify-self: end; align-self: center;
      grid-row: 1 / -1;
      margin-right: calc(var(--gutter) * -0.55);
      translate: 5% 0;
    }
    .hero__eyebrow { align-self: end; }
  }

  /* ---- HERO mobile fit ----
     On phones the hero must read through the CTA within the SMALL viewport (with
     Safari's bottom toolbar present). Tighten the vertical rhythm here: trim the
     block padding and the row gaps so the centred stack clears the fold. The star-
     and display-type trims live in the components layer (after their base rules, so
     they win). Desktop (≥60rem) and tablet (40–60rem) are untouched. */
  @media (max-width: 40rem) {
    .hero {
      padding-block: clamp(1.5rem, 1rem + 2vw, 2.5rem) clamp(2.25rem, 1.75rem + 2vw, 3rem);
    }
    .hero__grid { row-gap: clamp(0.6rem, 0.35rem + 1vw, 1rem); }
  }
  /* Narrowest phones (≤384px): pull the rhythm in another notch. */
  @media (max-width: 24rem) {
    .hero { padding-block: 1.35rem 1.35rem; }
    .hero__grid { row-gap: 0.5rem; }
  }

  /* ---- ABOUT layout ---- */
  @media (min-width: 60rem) {
    .about { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); column-gap: var(--gutter); align-items: start; }
    .about .section-head { margin-bottom: 0; }
    .about__body { grid-column: 2; }
  }

  /* ---- DIPG layout: title left, body right, void ---- */
  @media (min-width: 60rem) {
    .dipg { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); column-gap: var(--gutter); align-items: end; }
    .dipg .section-head { margin-bottom: 0; }
  }

  /* ---- HELP layout ---- */
  @media (min-width: 60rem) {
    .help { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.4fr); column-gap: var(--gutter); align-items: start; }
    /* Sticky bounded to the section: it is a direct grid child, so it can only
       travel within .help's own block — it never bleeds into the next section. */
    .help .section-head { position: sticky; top: 18vh; margin-bottom: 0; align-self: start; }
  }
}

/* =====================================================================
   LAYER: components
   ===================================================================== */
@layer components {

  /* =========================================================
     PRESS FURNITURE — the recurring print signature
     ========================================================= */

  /* CMYK color-control strip (printer's plate bar). Hard-stop gradient = one
     GPU-cheap element, crisp chips, palette-legal colours only. */
  .colorbar {
    --bar-h: 0.62rem;
    height: var(--bar-h); width: clamp(4.5rem, 3rem + 8vw, 8rem);
    background: linear-gradient(90deg,
      var(--red)      0 25%,
      var(--deep-red) 25% 50%,
      var(--gold)     50% 75%,
      var(--ink)      75% 100%);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 26%, transparent);
    border-radius: 1px;
  }
  .colorbar--v { width: var(--bar-h); height: clamp(6rem, 4rem + 12vw, 12rem);
    background: linear-gradient(180deg,
      var(--red)      0 25%,
      var(--deep-red) 25% 50%,
      var(--gold)     50% 75%,
      var(--ink)      75% 100%); }

  /* Printer's registration crosshair. A mask over a solid ink fill so it
     inherits the press-ink colour and stays crisp at any size. */
  .regmark {
    width: 1.4rem; height: 1.4rem; display: block;
    background: color-mix(in srgb, var(--ink) 62%, transparent);
    -webkit-mask: var(--reg) center / contain no-repeat;
            mask: var(--reg) center / contain no-repeat;
    --reg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-width='1.1'%3E%3Ccircle cx='12' cy='12' r='4.6'/%3E%3Cpath d='M12 0.5 V8 M12 16 V23.5 M0.5 12 H8 M16 12 H23.5'/%3E%3C/g%3E%3C/svg%3E");
  }

  /* =========================================================
     TYPE PRIMITIVES
     ========================================================= */

  /* Eyebrow = a red press slug, stamped with the star mark. Uppercase, tight
     ink. The star bullet threads the mark through every section head. */
  .eyebrow {
    font-family: var(--font-body);
    font-size: var(--t-eyebrow); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.22em;
    color: var(--red);                 /* red on cream = 4.60:1 (AA normal text) */
    display: inline-flex; align-items: center; gap: 0.6em;
  }
  .eyebrow::before {
    content: ""; flex: none;
    width: 0.9em; height: 0.9em;
    background: var(--red);
    -webkit-mask: var(--star-mask) center / contain no-repeat;
            mask: var(--star-mask) center / contain no-repeat;
  }
  .eyebrow span { margin-inline: 0.1em; opacity: 0.6; }

  /* Section head gets a CMYK color bar above it — the press motif on every beat. */
  .section-head::before {
    content: ""; display: block;
    height: 0.55rem; width: clamp(4.5rem, 3rem + 7vw, 7.5rem);
    margin-bottom: clamp(1rem, 0.6rem + 1.2vw, 1.6rem);
    background: linear-gradient(90deg,
      var(--red) 0 25%, var(--deep-red) 25% 50%, var(--gold) 50% 75%, var(--ink) 75% 100%);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 26%, transparent);
    border-radius: 1px;
  }

  .h2 {
    font-family: var(--font-display);
    font-optical-sizing: none;
    font-variation-settings: "opsz" 72, "wght" 560, "SOFT" 24, "WONK" 1;
    font-size: var(--t-h2); line-height: 1.0;
    letter-spacing: -0.03em; color: var(--ink);
    text-wrap: balance;
    /* misregistered second plate — a warm red ghost offset down-right */
    text-shadow: 0.035em 0.03em 0 color-mix(in srgb, var(--red) 40%, transparent);
  }

  .prose p { color: var(--ink); }
  .prose strong { font-weight: 700; }
  .prose--lead p { font-size: var(--t-lead); line-height: 1.4; letter-spacing: -0.01em; }

  .link {
    color: var(--red); text-decoration: none;   /* red on cream = 4.60:1 (AA normal text) */
    background-image: linear-gradient(var(--red), var(--red));
    background-size: 100% 2px; background-position: 0 100%; background-repeat: no-repeat;
    padding-bottom: 1px; font-weight: 600;
    transition: background-size var(--dur-standard) var(--ease-out-quart);
  }
  .link:hover { background-size: 0% 2px; background-position: 100% 100%; }

  /* =========================================================
     THE STAR — printed with CMYK misregistration
     ========================================================= */
  .star { width: 100%; height: auto; overflow: visible; }
  .halftone-dot { fill: var(--red); }         /* Ben-Day dots inside the pattern */

  /* The on-register RED KEY plate + a confident deep-red comic INK line.
     non-scaling-stroke decouples ink weight from star size so the line reads
     the same at hero scale and crowd scale. */
  .star__solid {
    fill: var(--red);
    stroke: var(--deep-red); stroke-linejoin: round;
    vector-effect: non-scaling-stroke; stroke-width: 1.6px;
  }
  .star--hero .star__solid { stroke-width: 3.25px; }

  /* The misregistered GOLD "light" plate, offset up-left (transform set on the
     <use> in markup, in the star's 0–100 user units so it scales with the star). */
  .star__ghost { fill: var(--gold); }

  /* The gold light dropped inside the star. */
  .star__inner { fill: var(--gold); transform-box: fill-box; transform-origin: center; transform: scale(0.4); }

  /* The HALFTONE star (the "one"): cream newsprint ground, red Ben-Day dots,
     deep-red ink outline — the vibe, front and center in the signature. */
  .star__ground { fill: var(--cream); }
  .star__dots   { fill: url(#halftone-red); }
  .star__ink {
    fill: none; stroke: var(--deep-red); stroke-linejoin: round;
    vector-effect: non-scaling-stroke; stroke-width: 2.5px;
  }

  /* The heart — warm secondary, threaded through the crowd, with its own ink. */
  .heart { width: 100%; height: auto; overflow: visible; }
  .heart-body { fill: var(--red); stroke: var(--deep-red); stroke-width: 2; stroke-linejoin: round; }

  /* =========================================================
     HERO — the poster plate
     ========================================================= */
  .hero__title {
    font-family: var(--font-display);
    font-optical-sizing: none;
    font-variation-settings: "opsz" 144, "wght" 620, "SOFT" 36, "WONK" 1;
    font-size: var(--t-h1);
    line-height: 1.02;                 /* splash-page tight */
    letter-spacing: -0.045em; color: var(--ink);
    text-transform: uppercase;
    padding-block: 0.16em 0.24em;      /* box bigger than ink on top + bottom (clip-guard) */
    margin-block: -0.16em -0.24em;     /* cancel so layout doesn't move */
    /* misregistered red plate ghost — the print signature on the headline */
    text-shadow: 0.04em 0.036em 0 color-mix(in srgb, var(--red) 42%, transparent);
  }
  .hero__title .split-line {
    display: block; overflow: visible;
    padding-inline: 0.1em; margin-inline: -0.1em;   /* right/left ink + ghost overhang guard */
  }
  /* The word is the transformed element on entrance; its OWN box must be bigger
     than the ink (and the offset ghost) so real-Safari's transform-clip can't
     slice descenders or the ghost. */
  .hero__title .word {
    display: inline-block; white-space: pre; overflow: visible;
    padding: 0.06em 0.12em 0.2em; margin: -0.06em -0.12em -0.2em;
  }
  /* The mobile display-type + star trims (incl. the narrow-phone headline lockup)
     live in the consolidated HERO mobile-fit block at the end of this layer — kept
     after the base rules so they win on source order. */

  /* "Fight Like Ozzy" — a red comic CAPTION banner, deep-red inked, hand-placed
     with a slight tilt. White on red = 4.99:1 (AA). A filled box, so the italic
     descender sits inside the padding — no clip. */
  .hero__accent {
    font-family: var(--font-display); font-style: italic;
    font-optical-sizing: none;
    font-variation-settings: "opsz" 60, "wght" 560;
    font-size: clamp(1.35rem, 0.9rem + 2.3vw, 2.6rem);
    line-height: 1.1; color: var(--white);
    letter-spacing: 0.002em;
    display: inline-block;
    background: var(--red);
    padding: 0.14em 0.62em 0.28em;
    rotate: -1.6deg;
    box-shadow:
      0 0 0 2.5px var(--deep-red),                                   /* comic ink keyline */
      0.16em 0.2em 0 color-mix(in srgb, var(--gold) 65%, transparent); /* misregistered gold plate behind */
  }

  .hero__sub {
    font-size: var(--t-lead); line-height: 1.45; color: var(--ink);
    max-width: 30ch; font-weight: 480;
  }

  .hero__meta {
    font-family: var(--font-body);
    font-size: 0.86rem; font-weight: 560; line-height: 1.55;
    letter-spacing: 0.01em; color: var(--ink-soft); max-width: 40ch;
  }

  /* The hero star, with a warm tonal HALFTONE bloom printed behind it (dots
     densest at the core, fading out) — masked so it never reaches the type. */
  .hero__star { width: clamp(12rem, 8rem + 20vw, 24rem); position: relative; isolation: isolate; }
  .hero__star::before {
    content: ""; position: absolute; z-index: -1; pointer-events: none;
    inset: -46% -42% -42% -46%;
    background-image:
      radial-gradient(var(--dot-red) var(--dot-r), transparent calc(var(--dot-r) + 0.7px)),
      radial-gradient(var(--dot-red) var(--dot-r), transparent calc(var(--dot-r) + 0.7px));
    background-size: var(--dot-pitch) var(--dot-pitch);
    background-position: 0 0, calc(var(--dot-pitch) / 2) calc(var(--dot-pitch) / 2);
    -webkit-mask: radial-gradient(closest-side, #000 16%, rgba(0,0,0,0.5) 46%, transparent 74%);
            mask: radial-gradient(closest-side, #000 16%, rgba(0,0,0,0.5) 46%, transparent 74%);
  }

  /* Hero press furniture — graphic only (no invented words). */
  .hero__reg { position: absolute; z-index: 2; pointer-events: none; opacity: 0.7; }
  .hero__reg--tl { top: clamp(1rem, 0.5rem + 1.5vw, 2rem); left: clamp(0.9rem, 0.4rem + 1.2vw, 1.9rem); }
  .hero__reg--br { right: clamp(1rem, 0.5rem + 1.5vw, 2rem); bottom: clamp(1rem, 0.5rem + 1vw, 2rem); }
  /* Color-control bar down the LEFT print margin (clear of the bleeding star).
     Held off the viewport edge so it reads as a full CMYK chip framed in cream,
     never a clipped bleed — its right edge still clears the type gutter at every
     desktop width (bar right ≈ 2.1rem at 60rem vs a ≥2.4rem content gutter). */
  .hero__colorbar { position: absolute; z-index: 2; pointer-events: none;
    top: 50%; left: clamp(1.3rem, 0.7rem + 1.3vw, 2.5rem); translate: 0 -50%; opacity: 0.9; }
  @media (max-width: 60rem) { .hero__colorbar, .hero__reg--br { display: none; } }

  .hero__scrollhint {
    font-family: var(--font-body); font-size: 0.72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.28em; color: var(--ink-soft);
    display: inline-flex; align-items: center; gap: 0.6em;
  }
  .hero__scrollhint::before {
    content: ""; width: 2.4rem; height: 2px; background: var(--red); opacity: 0.8;
  }

  /* =========================================================
     HERO mobile fit — the star + display-type trims that let the full hero
     (through the CTA) clear the SMALL viewport with Safari's toolbar present.
     Placed AFTER the base hero rules so they win on source order (same layer,
     same specificity). Every rule is max-width scoped, so desktop is untouched.
     The LCP guard is preserved (headline/accent/sub only change size — they stay
     opacity:1, transform-only entrance), and the star's CMYK-misregistration +
     halftone treatments stay intact: the ghost plate is in 0–100 userspace and
     the ::before bloom is %-inset, so both scale down WITH the smaller star.
     ========================================================= */
  @media (max-width: 40rem) {
    /* The star is the hero's biggest space consumer; shrink it so the stack fits —
       prominent, just smaller (was ~192–214px here). */
    .hero__star { width: clamp(5.5rem, 4rem + 11vw, 9.5rem); }
    /* non-scaling-stroke holds the ink at a constant screen weight; on the smaller
       star the 3.25px key line reads heavy, so trim it back to the delicate comic
       weight the desktop star shows. */
    .star--hero .star__solid { stroke-width: 2px; }

    /* Nudge the display type down a step so the two-line lockups hold and the
       vertical budget closes — composed, not cramped. */
    .hero__title  { font-size: clamp(2.5rem, 1.4rem + 5vw, 3.3rem); }
    .hero__accent { font-size: clamp(1.2rem, 0.9rem + 1.8vw, 1.7rem); }
    .hero__sub    { font-size: clamp(1.02rem, 0.95rem + 0.4vw, 1.2rem); line-height: 1.4; }
  }

  /* Narrowest phones (≤384px): another notch, and drop the DECORATIVE scroll hint
     to reclaim its bottom band — it is aria-hidden, so nothing is lost to AT. */
  @media (max-width: 24rem) {
    .hero__star   { width: clamp(3.75rem, 2.75rem + 7vw, 6rem); }
    .hero__title  { font-size: clamp(2.2rem, 1rem + 5vw, 2.8rem); letter-spacing: -0.05em; }
    .hero__sub    { line-height: 1.35; }
    .hero__meta   { font-size: 0.8rem; line-height: 1.45; }
    .hero__scrollhint { display: none; }
  }

  /* =========================================================
     ABOUT
     ========================================================= */
  /* Selector raised to `.prose p.about__call` (0,2,1) so `color:var(--red)` wins
     over `.prose p{color:var(--ink)}` (0,1,1) inside the SAME components layer —
     without it, higher specificity painted the call INK and orphaned the deep-red
     misregister shadow. Every other property (block flex, the 1.1em top-margin gap
     fix folded into the shorthand, the negative right/bottom clip-guard margins,
     the star bullet) is unchanged; still in `components`, so it stays behind the
     motion/polish layers exactly as before. */
  .prose p.about__call {
    font-family: var(--font-display); font-optical-sizing: none;
    font-variation-settings: "opsz" 60, "wght" 620, "SOFT" 20, "WONK" 1;
    font-style: italic; font-size: clamp(1.75rem, 1.2rem + 2.4vw, 3rem);
    color: var(--red); letter-spacing: -0.02em; line-height: 1.05;
    /* Block-level flex (NOT inline-flex): a block box reliably reserves its top
       margin above the call even when the preceding <p> is wrapped into SplitText
       .reveal-line BLOCK boxes on the full-motion path — inline-level vertical
       margins were unreliable there and the line rode up into the last body line.
       Internal baseline flex for the star bullet + text is unchanged. */
    display: flex; align-items: baseline; gap: 0.4em;
    /* Top = the real ~1.1em reserving gap, folded INTO the shorthand so it survives
       (a separate `margin-top:1.1em` above the shorthand was being clobbered to 0).
       Negative right/bottom stay the italic overhang + descender + ghost clip-guard. */
    padding: 0 0.32em 0.26em 0; margin: 1.1em -0.32em -0.26em 0;
    text-shadow: 0.03em 0.028em 0 color-mix(in srgb, var(--deep-red) 34%, transparent);
  }
  .about__call::before {
    content: ""; align-self: center; flex: none;
    width: 0.72em; height: 0.72em; translate: 0 0.02em;
    background: var(--red);
    -webkit-mask: var(--star-mask) center / contain no-repeat;
            mask: var(--star-mask) center / contain no-repeat;
  }

  /* =========================================================
     DIPG — the one SILENT beat. The press goes quiet: no color bar, no star
     bullet, no ghost. Deep-red is a mood here, earned, then it resolves.
     ========================================================= */
  .dipg .section-head::before { display: none; }
  .dipg .eyebrow { color: var(--deep-red); }        /* deep-red on cream = 10.8:1 */
  .dipg .eyebrow::before { background: var(--deep-red); }  /* the one star that stays grave */
  .dipg .h2 {
    font-variation-settings: "opsz" 60, "wght" 500, "SOFT" 8, "WONK" 0;
    text-shadow: none;                              /* no misregister on the sober beat */
    letter-spacing: -0.02em;
  }
  .dipg__statement {
    border-inline-start: 3px solid var(--deep-red);
    padding-inline-start: var(--sp-4);
  }
  .mark {
    color: var(--deep-red); font-weight: 700;       /* deep-red on cream = 10.8:1 */
    font-variation-settings: "wght" 560;
    white-space: nowrap;
  }
  /* At ≤320px the nowrap phrase can crowd the statement's border + padding; let
     it wrap at that width ONLY so it can never overflow the clipped x-axis. */
  @media (max-width: 20rem) {
    .mark { white-space: normal; }
  }
  .dipg__resolve {
    margin-top: 1.4em;
    font-family: var(--font-display); font-optical-sizing: none;
    font-variation-settings: "opsz" 48, "wght" 440, "SOFT" 8, "WONK" 0;
    font-style: italic; font-size: var(--t-lead); color: var(--ink);
    letter-spacing: -0.01em;
    padding-bottom: 0.14em; margin-bottom: -0.14em;
  }

  /* =========================================================
     SIGNATURE — one becomes many
     ========================================================= */
  .signature {
    position: relative;
    min-height: 100svh;
    background: var(--cream);
    overflow: hidden;
    isolation: isolate;
  }
  /* Tonal halftone printed across the whole stage — the ink field the crowd
     gathers on. Masked to a vignette that keeps the CENTER clear (the cry lives
     there) and fades at the edges. Painted once, static: GPU-cheap. */
  .signature::before {
    content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
    background-image:
      radial-gradient(var(--dot-red) var(--dot-r), transparent calc(var(--dot-r) + 0.7px)),
      radial-gradient(var(--dot-red) var(--dot-r), transparent calc(var(--dot-r) + 0.7px));
    background-size: var(--dot-pitch) var(--dot-pitch);
    background-position: 0 0, calc(var(--dot-pitch) / 2) calc(var(--dot-pitch) / 2);
    -webkit-mask: radial-gradient(ellipse 78% 74% at 50% 50%, transparent 0 26%, #000 54%, #000 82%, transparent 100%);
            mask: radial-gradient(ellipse 78% 74% at 50% 50%, transparent 0 26%, #000 54%, #000 82%, transparent 100%);
    /* Give the big static dot-field its OWN raster layer. It never changes, but it
       sits inside the pinned section: with the desktop transform-pin it would be
       re-rasterised as the pin translate updates, and any in-section repaint (crowd
       bloom) could dirty it too. Promoted, it is painted once and only re-composited
       — no per-frame raster of a full-viewport gradient. Visually identical. */
    transform: translateZ(0);
  }
  .signature__stage {
    position: relative;
    min-height: 100svh;
    display: grid; place-items: center;
    padding: var(--gutter);
  }

  /* gold festival light kindling above the crowd — light only, never a fill */
  .sig-glow {
    --glow-flicker: 0.65;
    position: absolute; z-index: 0; pointer-events: none;
    left: 50%; top: -6%; translate: -50% 0;
    width: min(120%, 60rem); aspect-ratio: 1;
    background: radial-gradient(closest-side,
        color-mix(in srgb, var(--gold) 42%, transparent) 0%,
        color-mix(in srgb, var(--gold) 20%, transparent) 40%,
        transparent 72%);
    filter: blur(8px);
  }
  .sig-glow::after {
    content: ""; position: absolute; inset: 18%;
    border-radius: 50%;
    background: radial-gradient(closest-side,
        color-mix(in srgb, var(--gold) 55%, transparent) 0%,
        color-mix(in srgb, var(--gold) 22%, transparent) 46%,
        transparent 74%);
    opacity: var(--glow-flicker);
    /* The slow warm-breath drives THIS layer's opacity via --glow-flicker. Give it
       its own compositor layer so, where the parent blur is dropped (touch, below),
       the breath re-composites at a new opacity instead of repainting the gold
       gradient each frame. Inert on desktop (the parent filter flattens it). */
    transform: translateZ(0);
  }

  /* Real-iOS: the gold bloom is SCALED across the scrub (0.7 → 1.06). A filter:blur
     under an animating scale forces the entire blurred bitmap to re-rasterise every
     frame — the single most expensive per-frame paint in the pinned sequence on a
     phone. The radial-gradients above are already soft (both fade to transparent
     well inside their box), so on touch devices drop the blur: the scale then runs
     as a pure composited transform and the glow reads the same. Desktop keeps the
     8px blur (no reported jank there; GPUs rasterise it cheaply). */
  @media (hover: none) and (pointer: coarse) {
    .sig-glow { filter: none; }
  }

  /* the crowd fills the whole stage; nodes are placed by per-node CSS vars */
  .crowd { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
  /* Depth is carried by ONE static, cheap-to-composite stage shadow pool behind
     the ranks — not by 22 animated per-node drop-shadow filters (each of those
     re-rasterises every scrubbed frame and drops mid-tier mobile below 60fps). */
  .crowd::before {
    content: ""; position: absolute; inset: 0; z-index: -1;
    background: radial-gradient(ellipse 66% 58% at 50% 58%,
        color-mix(in srgb, var(--ink) 8%, transparent) 0%,
        color-mix(in srgb, var(--ink) 3%, transparent) 46%,
        transparent 74%);
  }
  .crowd__node {
    position: absolute;
    left: var(--x); top: var(--y);
    translate: -50% -50%;              /* individual transform prop — leaves `transform` free for GSAP scale */
    rotate: var(--r, 0deg);
    width: calc(var(--s, 1) * clamp(2.6rem, 2rem + 5.4vw, 4.75rem));
  }
  /* Per-node crispness is a fine-pointer-only, NON-animating luxury; suppressed
     the instant the crowd animates (.crowd--live). Coarse pointers never pay it. */
  @media (pointer: fine) {
    .crowd__node { filter: drop-shadow(0 4px 10px color-mix(in srgb, var(--ink) 14%, transparent)); }
  }
  .crowd__node--lead {
    width: calc(var(--s, 1) * clamp(4rem, 3rem + 8vw, 7rem));   /* the origin reads larger */
    z-index: 2;
  }
  /* Hearts sit a hair warmer so they read among the stars rather than as noise —
     gold is LIGHT, never a fill: only a 5% whisper here so the hearts stay
     warm-RED, never skewing salmon during the bloom. */
  .crowd__node[data-kind="heart"] .heart-body { fill: color-mix(in srgb, var(--red) 95%, var(--gold)); }

  .signature__cry {
    position: relative; z-index: 3;
    display: grid; justify-items: center; gap: clamp(0.75rem, 0.4rem + 1.4vw, 1.4rem);
    text-align: center; width: 100%; max-width: 34rem;
    padding: clamp(1.5rem, 1rem + 3vw, 3rem);
  }
  /* A soft cream haze guarantees the cry stays legible over the crowd + the
     halftone without reading as a panel — the stage center is kept sparse too. */
  .signature__cry::before {
    content: ""; position: absolute; inset: -10% -16%; z-index: -1;
    background: radial-gradient(ellipse at center,
        var(--cream) 46%, color-mix(in srgb, var(--cream) 60%, transparent) 68%, transparent 84%);
  }
  /* On phones this ::before is a `farthest-corner` ellipse (no explicit size), so
     its horizontal radius is ~0.707×box-width — NOT half-width. That pushed the two
     flank crowd stars (--x:10 --y:53 and --x:90 --y:56) to gradient-param ≈0.61/0.69,
     so the earlier fade that only reached transparent at 68% still washed cream over
     them and filmed them pale-pink even though every node is opacity 1. Fix is haze-
     only: hold a SOLID opaque cream core out to 50% (param 0.50) — that fully backs
     the cry ink, which reaches only param ≤0.49 at the widest lines "FIGHTS"/"ALONE",
     keeping red-on-cream 4.60:1 AA and masking any stray halftone behind the text —
     then complete the fade to FULLY transparent by 58%. The whole fade (0.50→0.58)
     lands in the GAP between the text (≤0.49) and the flank stars (0.61/0.69): both
     stars now sit in fully-clear space and render solid red. Desktop (≥40rem) keeps
     its own wider base haze, untouched. */
  @media (max-width: 40rem) {
    .signature__cry::before {
      inset: -8% -4%;
      background: radial-gradient(ellipse at center,
          var(--cream) 50%,
          transparent 58%);
    }
  }
  .signature__title {
    font-family: var(--font-display);
    font-optical-sizing: none;
    font-variation-settings: "opsz" 144, "wght" 640, "SOFT" 34, "WONK" 1;
    font-size: var(--t-cry); line-height: 1.0; color: var(--red);
    letter-spacing: -0.04em; text-transform: uppercase;
    padding-block: 0.16em 0.26em; margin-block: -0.16em -0.26em;    /* descender clip-guard top+bottom */
    text-shadow: 0.032em 0.03em 0 color-mix(in srgb, var(--deep-red) 42%, transparent);
  }
  .signature__title .split-line {
    display: block; overflow: visible;
    padding-inline: 0.12em; margin-inline: -0.12em;                 /* right/left ink + ghost overhang guard */
  }
  .signature__title .word {
    display: inline-block; white-space: pre; overflow: visible;
    padding: 0.06em 0.12em 0.22em; margin: -0.06em -0.12em -0.22em; /* transformed-word clip-guard, four sides */
  }

  /* =========================================================
     HELP / actions — plate-numbered ledger with ink rules
     ========================================================= */
  .actions { display: grid; }
  .action {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "no   main"
      "cta  cta";
    column-gap: clamp(1rem, 0.5rem + 2vw, 2.5rem); row-gap: 1rem;
    align-items: start;
    padding-block: clamp(1.75rem, 1.25rem + 2vw, 3rem);
    border-top: 2px solid color-mix(in srgb, var(--ink) 22%, transparent);
  }
  .action:last-child { border-bottom: 2px solid color-mix(in srgb, var(--ink) 22%, transparent); }
  .action__no {
    grid-area: no;
    font-family: var(--font-body); font-weight: 800;
    font-size: 1rem; letter-spacing: 0.02em; color: var(--red);
    padding-top: 0.5em;
  }
  .action__main { grid-area: main; }
  .action__title {
    font-family: var(--font-display); font-optical-sizing: none;
    font-variation-settings: "opsz" 54, "wght" 560, "SOFT" 20, "WONK" 0;
    font-size: clamp(1.75rem, 1.2rem + 2.4vw, 3rem); line-height: 1.02;
    letter-spacing: -0.025em; color: var(--ink); margin-bottom: 0.35em;
  }
  .action__desc { color: var(--ink); max-width: 44ch; }
  .action__meta {
    grid-area: cta; justify-self: start; align-self: center;
    font-family: var(--font-body); font-size: 0.8rem; font-weight: 700;
    letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft);
  }

  @media (min-width: 40rem) {
    .action {
      grid-template-columns: auto 1fr auto;
      grid-template-areas: "no main cta";
      align-items: center; column-gap: clamp(1.5rem, 0.5rem + 3vw, 4rem);
    }
    .action__no { padding-top: 0; align-self: start; }
    .action__meta { justify-self: end; }
  }

  /* =========================================================
     BUTTONS
     ========================================================= */
  .btn {
    display: inline-flex; align-items: center; gap: 0.6em;
    font-family: var(--font-body); font-weight: 700; font-size: 1rem;
    letter-spacing: 0.01em; text-decoration: none;
    padding: 0.85em 1.4em; border-radius: var(--r-pill);
    transition: background-color var(--dur-standard) var(--ease-out-quart),
                border-color var(--dur-standard) var(--ease-out-quart),
                color var(--dur-standard) var(--ease-out-quart),
                box-shadow var(--dur-standard) var(--ease-out-quart);
  }
  .btn__arrow { width: 1.15em; height: 1.15em; transition: transform var(--dur-standard) var(--ease-spring-out); }

  /* Primary give button — red field, white label, deep-red comic ink keyline. */
  .action .btn { grid-area: cta; justify-self: start; }
  @media (min-width: 40rem) { .action .btn { justify-self: end; } }
  .btn--give { background: var(--red); color: var(--white); box-shadow: 0 0 0 2.5px var(--deep-red); }   /* verified 4.99:1 */
  .btn--give:hover { background: color-mix(in srgb, var(--red) 90%, black); }
  .btn--give:hover .btn__arrow { transform: translateX(0.25em); }
  .btn--give:active { transform: scale(0.98); }

  /* Ghost button — the hero CTA into the actions. Red ink outline on cream. */
  .btn--ghost {
    color: var(--red); box-shadow: inset 0 0 0 2px var(--red);
    padding-block: 0.7em;
  }
  .btn--ghost:hover { background: var(--red); color: var(--white); box-shadow: inset 0 0 0 2px var(--red); }
  .btn--ghost:hover .btn__arrow { transform: translateX(0.25em); }
  .btn--ghost:active { transform: scale(0.98); }

  /* =========================================================
     FOOTER — the colophon / end-of-print-run
     ========================================================= */
  .site-footer {
    background: var(--cream); color: var(--ink);
    display: grid; gap: clamp(1.5rem, 1rem + 2vw, 2.5rem);
    padding-block: clamp(4rem, 3rem + 6vw, 8rem) clamp(3rem, 2rem + 3vw, 5rem);
    position: relative; isolation: isolate;
  }
  /* A faint halftone ink band along the very bottom of the run, masked below
     the text so contrast is never touched. */
  .site-footer::before {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background-image:
      radial-gradient(var(--dot-red) var(--dot-r), transparent calc(var(--dot-r) + 0.7px)),
      radial-gradient(var(--dot-red) var(--dot-r), transparent calc(var(--dot-r) + 0.7px));
    background-size: var(--dot-pitch) var(--dot-pitch);
    background-position: 0 0, calc(var(--dot-pitch) / 2) calc(var(--dot-pitch) / 2);
    -webkit-mask: linear-gradient(180deg, transparent 62%, #000 100%);
            mask: linear-gradient(180deg, transparent 62%, #000 100%);
    opacity: 0.7;
  }
  .footer__cry {
    font-family: var(--font-display); font-optical-sizing: none;
    font-variation-settings: "opsz" 60, "wght" 600, "SOFT" 26, "WONK" 1;
    font-size: clamp(1.7rem, 1.1rem + 2.6vw, 3.1rem); line-height: 1.12;
    letter-spacing: -0.03em; color: var(--ink); max-width: 24ch;
    text-transform: uppercase;
    display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0.2em 0.6em;
    padding-bottom: 0.12em; margin-bottom: -0.12em;
    text-shadow: 0.028em 0.026em 0 color-mix(in srgb, var(--red) 34%, transparent);
  }
  /* Three red star stamps leading the final cry — the mark, one last time. */
  .footer__stars { display: inline-flex; gap: 0.35em; align-self: center; translate: 0 -0.02em; }
  .footer__stars i {
    width: 0.82rem; height: 0.82rem; background: var(--red);
    -webkit-mask: var(--star-mask) center / contain no-repeat;
            mask: var(--star-mask) center / contain no-repeat;
  }
  .footer__stars i:nth-child(2) { background: var(--deep-red); }
  .footer__stars i:nth-child(3) { background: var(--gold); }
  .footer__meta { display: grid; gap: 0.9em; max-width: 52rem; }
  .footer__credit { font-size: 0.95rem; line-height: 1.6; color: var(--ink); }
  .footer__disclaimer { font-size: 0.9rem; line-height: 1.6; color: var(--ink-soft); }
  .footer__sign { font-size: 0.9rem; font-weight: 700; color: var(--ink); }
  /* Press colophon: a color bar flanked by registration marks closes the run. */
  .footer__press { display: flex; align-items: center; gap: clamp(0.75rem, 0.4rem + 1vw, 1.5rem); }
  .footer__press .regmark { width: 1.15rem; height: 1.15rem; opacity: 0.7; }
}

/* =====================================================================
   LAYER: motion  (initial states JS animates from; all gated to js + motion)
   ===================================================================== */
@layer motion {

  html.js:not(.reduced) [data-reveal="up"] { transform: translateY(1.4rem); }

  /* HERO — LCP-safe entrances. The headline words + the caption banner PAINT at
     full opacity on the first frame; their entrance is transform-only. */
  html.js:not(.reduced) .hero__title .word { transform: translateY(0.42em); }   /* opacity stays 1 */
  /* The caption keeps its printed tilt via the individual `rotate` property
     (set in components); GSAP only owns `transform` (translateY), so the two
     compose instead of stacking into a double rotation. */
  html.js:not(.reduced) .hero__accent      { transform: translateY(0.5rem); }   /* opacity stays 1 */
  html.js:not(.reduced) .hero__eyebrow     { opacity: 0; transform: translateY(1.2rem); }
  /* .hero__sub is the LCP element on narrow viewports, so it must PAINT at FCP:
     transform-only entrance (opacity stays 1), mirroring the title/accent. */
  html.js:not(.reduced) .hero__sub         { transform: translateY(1.2rem); }
  html.js:not(.reduced) .hero__meta        { opacity: 0; transform: translateY(1.2rem); }
  html.js:not(.reduced) .hero__cta         { opacity: 0; transform: translateY(0.8rem); }
  html.js:not(.reduced) .hero__star        { opacity: 0; }
  html.js:not(.reduced) .hero__scrollhint  { opacity: 0; }
  html.js:not(.reduced) .hero__reg,
  html.js:not(.reduced) .hero__colorbar    { opacity: 0; }

  /* SIGNATURE — the JS-motion "seed" state: the crowd is collapsed so Motion Lead
     can grow it from the one halftone star; the gold light is a faint ember; the
     cry waits to resolve. The STILL / reduced / no-JS state (these rules absent)
     is the full crowd, placed and opaque. */
  html.js:not(.reduced) .crowd__node        { opacity: 0; transform: scale(0.001); }
  html.js:not(.reduced) .sig-glow           { opacity: 0.18; transform: scale(0.7); }
  html.js:not(.reduced) .signature__title .word { opacity: 0; transform: translateY(0.5em); }
  html.js:not(.reduced) .signature__eyebrow { opacity: 0; }

  /* will-change promoted ONLY while the pinned signature is active; released after. */
  html.js:not(.reduced) .crowd--live .crowd__node,
  html.js:not(.reduced) .crowd--live [data-crowd-lead] { will-change: transform, opacity; }
  html.js:not(.reduced) .crowd--live .sig-glow { will-change: opacity, transform; }

  /* While the crowd animates, drop the per-node cast shadow so no frame pays 22
     filter re-rasterisations — the static stage pool holds the depth. */
  html.js:not(.reduced) .crowd--live .crowd__node { filter: none; }
}

/* =====================================================================
   LAYER: polish  (selection, scrollbar, focus, reduced-motion, print)
   ===================================================================== */
@layer polish {

  ::selection { background: color-mix(in srgb, var(--red) 26%, transparent); color: var(--ink); }

  :focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px; border-radius: 3px;
  }
  :focus:not(:focus-visible) { outline: none; }

  /* Styled scrollbar */
  html { scrollbar-color: color-mix(in srgb, var(--red) 55%, var(--cream)) transparent; scrollbar-width: thin; }
  ::-webkit-scrollbar { width: 11px; height: 11px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--red) 45%, var(--cream));
    border-radius: var(--r-pill);
    border: 3px solid var(--cream);
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--red); }

  /* Lenis */
  html.lenis, html.lenis body { height: auto; }
  .lenis.lenis-smooth { scroll-behavior: auto !important; }
  .lenis.lenis-stopped { overflow: clip; }

  /* The origin "one" in the STILL fallback (no-JS or reduced-motion): the motion
     layer's establish-alone beat never plays there, so make the lead unmistakably
     the source without animation — raised off the bottom band and enlarged so it
     reads as the star the crowd grew from, not just another crowd node. Overrides
     the RESOLVED top/width, not the inline --x/--y/--s the full-motion path reads,
     so the animated establish-alone beat is left pixel-identical. */
  html.no-js .crowd__node--lead,
  html.reduced .crowd__node--lead {
    top: 84%;   /* dropped from 78% so "the one" clears the cry haze's lower fade and pops decisively under ALONE — still larger + halftone-distinct */
    width: calc(1.32 * clamp(4rem, 3rem + 8vw, 7rem));
  }

  /* ---- prefers-reduced-motion: a COMPLETE, warm static state ----
     The full crowd of stars is present, the gold light is lit, the cry is shown,
     all press furniture printed. Zero oscillation, no pin, natural flow. */
  @media (prefers-reduced-motion: reduce) {
    * , *::before, *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001ms !important;
      scroll-behavior: auto !important;
    }
    .crowd__node, .sig-glow,
    .signature__title .word, .signature__eyebrow {
      opacity: 1 !important; transform: none !important;
    }
    .sig-glow { opacity: 0.7 !important; }
    .hero__title .word, .hero__eyebrow, .hero__sub,
    .hero__meta, .hero__cta, .hero__star, .hero__scrollhint,
    .hero__reg, .hero__colorbar, [data-reveal] {
      opacity: 1 !important; transform: none !important;
    }
    /* the caption keeps its printed tilt even with transforms neutralised */
    .hero__accent { opacity: 1 !important; transform: none !important; rotate: -1.6deg !important; }
  }

  @media print {
    .hero__star, .sig-glow, .hero__scrollhint, .hero__reg, .hero__colorbar,
    .signature::before, .site-footer::before, .hero__star::before { display: none; }
    .signature { min-height: auto; }
    .crowd { position: static; display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; }
    .crowd__node { position: static; translate: none; rotate: none; width: 3rem; filter: none; }
    .hero__title, .h2, .signature__title, .footer__cry { text-shadow: none; }
  }
}
