/* ============================================================
   ACS Website — Global Design System
   Alternative Concept Studio (umbrella company)
   Token Architecture: primitive → semantic → component
   Cascade layers: reset → tokens → base → layout → components → pages → utilities
   Brand decisions logged in: ACS-Website/docs/brand-decisions.md
   ============================================================ */

/* --- Layer order ------------------------------------------ */
@layer reset, tokens, base, layout, components, pages, utilities;

/* ============================================================
   TIER 1: PRIMITIVE TOKENS
   Raw values. Never used directly in components.
   ============================================================ */
@layer tokens {
  :root {

    /* --- Colour primitives --------------------------------- */

    /* Neutral ramp */
    --primitive-neutral-950: #0D0D0D;
    --primitive-neutral-900: #1b1b1b;   /* confirmed from teardown: Epic UI bg */
    --primitive-neutral-850: #222222;
    --primitive-neutral-800: #2A2A2A;
    --primitive-neutral-700: #333333;
    --primitive-neutral-600: #3A3A3A;
    --primitive-neutral-400: #787878;
    --primitive-neutral-300: #A0A0A0;
    --primitive-neutral-200: #C8C8C8;
    --primitive-neutral-100: #E8E8E8;
    --primitive-neutral-000: #FFFFFF;

    /* ACS umbrella accent — warm champagne gold
       Rationale: neutral across all LOBs, suggests quality, heritage, studio craft.
       #D4BC96 on #1b1b1b: luminance ratio ≈ 8.8:1 — WCAG AAA ✓ */
    --primitive-gold-600: #B89A6E;
    --primitive-gold-500: #C8A87A;
    --primitive-gold-400: #D4BC96;
    --primitive-gold-300: #E2D0B0;
    --primitive-gold-100-a15: rgba(212, 188, 150, 0.15);

    /* eSport Simulation accent — Pantone 485 C (Andy-confirmed) */
    --primitive-esport-red: #DA291C;
    --primitive-esport-red-dim: rgba(218, 41, 28, 0.18);
    --primitive-esport-red-glow: rgba(218, 41, 28, 0.35);

    /* ACT accent — indigo (from ACT-Website) */
    --primitive-act-indigo: #5865F7;
    --primitive-act-indigo-dim: rgba(88, 101, 247, 0.15);
    --primitive-act-indigo-glow: rgba(88, 101, 247, 0.30);

    /* Alternative Photographic accent — AP gold (#C8A96A, codified brand colour)
       Updated from #B0A898 warm silver per Serge/Andy sign-off. */
    --primitive-ap-silver: #C8A96A;
    --primitive-ap-silver-dim: rgba(200, 169, 106, 0.12);
    --primitive-ap-silver-glow: rgba(200, 169, 106, 0.20);

    /* Carol Campbell Art accent — CCA teal (from CCA Design Style Guide)
       Updated from #C4826A terracotta per Rachel/Andy sign-off. */
    --primitive-cca-terra: #4A8FA8;
    --primitive-cca-terra-dim: rgba(74, 143, 168, 0.15);
    --primitive-cca-terra-glow: rgba(74, 143, 168, 0.30);

    /* Pickled Poems accent — deeper, cooler green
       Updated from #6B9E7A sage per Julie/Andy sign-off. */
    --primitive-pp-sage: #4A8A60;
    --primitive-pp-sage-dim: rgba(74, 138, 96, 0.12);
    --primitive-pp-sage-glow: rgba(74, 138, 96, 0.28);

    /* --- DISPLAY VARIANTS -----------------------------------
       A display variant is a brand colour re-rendered as INK: lightened
       until small type set in it clears WCAG AA against a near-black
       ground, at FULL opacity. See "TIER 2b: DISPLAY-ACCENT TIER" below
       for the rule, the ground it is measured against, and when a
       variant may be used instead of the canonical colour.

       These are primitives. They are not used directly in components —
       components consume the --display-accent-* semantic aliases.

       Measured on PURE BLACK (tools/hero-legibility-gate.py, 2026-10-13):

         slide    canonical  CR     display    CR      floor
         esport   #DA291C    4.32   #F23A2C    5.42    >= 5.0  (NEW)
         act      #5865F7    4.62   #8B98FF    8.03    >= 5.0
         ap       #C8A96A    9.34   #D4BE8C   11.54    >= 5.0
         cca      #4A8FA8    5.79   #7EC0D8   10.42    >= 5.0
         pp       #4A8A60    5.10   #7AB090    8.44    >= 5.0

       Four of these five shipped from 2026-07-20 as undocumented inline
       hex with the comment "lightened for legibility on dark". eSport was
       the single gap — the only slide setting a RAW brand colour as type,
       at 4.32 on pure black and 2.06:1 against its actual photograph.
       Eleanor ruled that not acceptable (2026-10-13) and ruled the fix as
       filling a hole in existing practice, not as a brand-colour change.

       WHY #F23A2C AND NOT A PALER RED. Lightening in HSL at constant
       saturation desaturates perceptually — the failure mode Eleanor named
       as "pale salmon". Holding the contrast target at 5.4 and sweeping
       saturation shows chroma is free to recover (CIELAB C*, canonical
       = 83.2):
         #E34D41  S74  C* 69.5  dC* -13.7   dHue -3.8
         #E8483A  S79  C* 74.6  dC*  -8.6   dHue -2.6   <- worked example
         #F23A2C  S88  C* 85.1  dC*  +1.9   dHue -1.2   <- adopted
         #FF1A09  S100 C*101.7  dC* +18.5   dHue +1.9
       #F23A2C reaches the SAME 5.42 as the worked example while staying
       perceptually the same red as Pantone 485 C — brighter, not paler —
       and sits beside the canonical CTA button on its own slide without
       reading as a second red. The worked example loses 8.6 chroma units.
       Hue, chroma and contrast are all measured, none are eyeballed. */
    --primitive-esport-red-display: #F23A2C;
    --primitive-act-indigo-display: #8B98FF;
    --primitive-ap-silver-display:  #D4BE8C;
    --primitive-cca-terra-display:  #7EC0D8;
    --primitive-pp-sage-display:    #7AB090;

    /* --- Typography primitives ----------------------------- */
    --font-display: 'Bebas Neue', 'Impact', 'Arial Narrow', sans-serif;
    --font-body:    'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;

    /* Type scale — fluid where used in clamp() */
    --size-11: 0.6875rem;
    --size-12: 0.75rem;
    --size-14: 0.875rem;
    --size-15: 0.9375rem;
    --size-16: 1rem;
    --size-18: 1.125rem;
    --size-20: 1.25rem;
    --size-24: 1.5rem;
    --size-32: 2rem;
    --size-40: 2.5rem;
    --size-48: 3rem;
    --size-64: 4rem;
    --size-80: 5rem;
    --size-96: 6rem;
    --size-120: 7.5rem;

    /* --- Spacing (8px base) -------------------------------- */
    --sp-1:   8px;
    --sp-2:  16px;
    --sp-3:  24px;
    --sp-4:  32px;
    --sp-5:  40px;
    --sp-6:  48px;
    --sp-8:  64px;
    --sp-10: 80px;
    --sp-12: 96px;
    --sp-16: 128px;

    /* --- Motion -------------------------------------------- */
    --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
    --ease-in-out:   cubic-bezier(0.45, 0, 0.55, 1);
    --dur-fast:      150ms;
    --dur-normal:    250ms;
    --dur-slow:      400ms;
    --dur-carousel:  700ms;
  }


  /* ============================================================
     TIER 2: SEMANTIC TOKENS
     Named by purpose, not by value.
     ============================================================ */
  :root {

    /* Backgrounds */
    --bg-base:     var(--primitive-neutral-900);   /* #1b1b1b — page base */
    --bg-surface:  var(--primitive-neutral-850);   /* #222222 — lifted surface */
    --bg-elevated: var(--primitive-neutral-800);   /* #2a2a2a */
    --border:      var(--primitive-neutral-600);   /* #3a3a3a */
    --border-muted: var(--primitive-neutral-700);  /* #333333 */

    /* Text */
    --text-primary:   var(--primitive-neutral-000);  /* #ffffff */
    --text-secondary: var(--primitive-neutral-300);  /* #a0a0a0 */
    --text-muted:     var(--primitive-neutral-400);  /* #787878 */

    /* ACS umbrella accent */
    --accent:         var(--primitive-gold-500);      /* #C8A87A — decorative/bg */
    --accent-hover:   var(--primitive-gold-400);      /* #D4BC96 — hover */
    --accent-text:    var(--primitive-gold-400);      /* #D4BC96 — text AA on dark */
    --accent-dim:     var(--primitive-gold-100-a15);  /* transparent for bg badges */
    --focus-ring:     var(--primitive-gold-400);      /* #D4BC96 */

    /* ========================================================
       TIER 2b: DISPLAY-ACCENT TIER
       ========================================================
       THE RULE, IN ONE SENTENCE
       A brand colour used as INK for small text on a dark or
       photographic ground is set in its DISPLAY VARIANT, never in the
       canonical colour. A brand colour used as a SURFACE — a button
       fill, a rule, a badge, a glow, anything where the colour is the
       subject rather than the ink — stays canonical, always.

       "Small" means below the WCAG large-text threshold of 24px (or
       18.66px bold). The hero eyebrow is 11px, so it is small text and
       its floor is 4.5:1, not 3:1.

       THE FLOOR
       A display variant must reach >= 5:1 against a near-black ground at
       FULL opacity. Five, not 4.5, deliberately: it is a design floor
       that leaves the AA floor of 4.5 some room, so a variant does not
       fall out of compliance the first time a backdrop moves or a
       photograph is replaced.

       WHAT "NEAR-BLACK GROUND" MEANS, AND WHY IT IS NOT A FICTION
       A contrast figure is only meaningful against a stated ground, and
       on a photographic hero that ground is whatever the photograph
       happens to be — which is why four of the five eyebrows passed on
       flat colour and three of five failed against their own pictures.
       So the ground is GUARANTEED rather than assumed: see the local
       scrim on .slide-eyebrow::before, which puts a near-black backdrop
       under the type regardless of the image. The scrim is the reason
       this tier can quote one number per colour instead of one number
       per photograph.

       Eleanor's ruling, 2026-10-13. Her analysis of why neither lever
       works alone is load-bearing and is recorded on the scrim rule.

       SCOPE. The STANDARD is estate-wide for small accent text on dark
       or photographic grounds. The IMPLEMENTATION here is the umbrella
       hero eyebrows only — the five brand sites are separate queued
       work and must not be changed from here.

       ESCAPE HATCH, DOCUMENTED
       To set canonical brand colour as small type anyway, set
       --slide-accent locally to the canonical token and record the
       reason at the override site. It will not be caught by the build,
       only by the gate: tools/hero-legibility-gate.py measures the
       eyebrow and will report the real figure. Do not instead relax the
       floor in this block — the floor is the standard, the override is
       the exception, and the two must stay distinguishable. */
    --display-accent-esport: var(--primitive-esport-red-display);  /* #F23A2C  5.42 */
    --display-accent-act:    var(--primitive-act-indigo-display);  /* #8B98FF  8.03 */
    --display-accent-ap:     var(--primitive-ap-silver-display);   /* #D4BE8C 11.54 */
    --display-accent-cca:    var(--primitive-cca-terra-display);   /* #7EC0D8 10.42 */
    --display-accent-pp:     var(--primitive-pp-sage-display);     /* #7AB090  8.44 */

    /* Eyebrow scrim — the guaranteed near-black ground the tier above is
       measured against. Parsed out of this file by the gate; see the
       scrim rule on .slide-eyebrow::before for the full rationale. */
    --eyebrow-scrim-alpha: 0.96;
    --eyebrow-scrim: rgba(0, 0, 0, 0.96);

    /* Layout */
    --nav-height:       120px;
    --max-width:        1440px;
    --content-width:    1200px;
    --page-padding:     clamp(24px, 5vw, 80px);
    --section-pad-y:    clamp(80px, 10vw, 128px);
    --card-radius:      6px;
    --card-gap:         24px;
  }

  @media (max-width: 767px) {
    :root {
      --nav-height:    80px;
      --section-pad-y: 64px;
    }
  }
}

/* ============================================================
   RESET
   ============================================================ */
@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }

  html {
    scroll-behavior: smooth;
    text-size-adjust: 100%;
    -webkit-text-size-adjust: 100%;
  }

  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
  }

  img, picture, video, canvas, svg { display: block; max-width: 100%; }
  input, button, textarea, select { font: inherit; }
  p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
  ul[role="list"], ol[role="list"] { list-style: none; }
}

/* ============================================================
   BASE
   ============================================================ */
@layer base {
  body {
    background-color: var(--bg-base);
    color: var(--text-primary);
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 1.7;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  h1, h2, h3 {
    font-family: var(--font-body);
    color: var(--text-primary);
    line-height: 1.1;
    letter-spacing: -0.02em;
  }
  h1 { font-size: clamp(32px, 5vw, 56px); font-weight: 700; }
  h2 { font-size: clamp(28px, 4vw, 44px); font-weight: 700; }
  h3 { font-size: 22px; font-weight: 600; line-height: 1.3; }
  p  { color: var(--text-secondary); }
  strong { color: var(--text-primary); font-weight: 600; }

  a {
    color: var(--accent-text);
    text-decoration: none;
    transition: color var(--dur-fast) ease;
  }
  a:hover {
    color: var(--accent-hover);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  a:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
    border-radius: 2px;
  }

  /* Skip to content — accessibility first element in body */
  .skip-link {
    position: fixed;
    top: 12px;
    left: 12px;
    z-index: 9999;
    padding: 10px 18px;
    background: var(--accent);
    color: #0a0a0a;
    border-radius: 4px;
    font-size: 14px;
    font-weight: 600;
    transform: translateY(-200%);
    transition: transform var(--dur-fast) ease;
    text-decoration: none;
  }
  .skip-link:focus { transform: translateY(0); }
  .skip-link:hover { text-decoration: none; }

  /* Display font class — Bebas Neue role */
  .font-display {
    font-family: var(--font-display);
    letter-spacing: 0.03em;
    line-height: 1.0;
    font-weight: 400; /* Bebas Neue has only one weight */
  }
}

/* ============================================================
   LAYOUT UTILITIES
   ============================================================ */
@layer layout {
  .container {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: 0 var(--page-padding);
  }

  .container--wide {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 0 var(--page-padding);
  }

  .section {
    padding: var(--section-pad-y) 0;
  }

  /* Push body content below fixed header — hero and all sections start clear of nav */
  #main-content {
    padding-top: var(--nav-height);
  }

  /* Visually hidden but accessible */
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}

/* ============================================================
   TIER 3: COMPONENT TOKENS + COMPONENTS
   ============================================================ */
@layer components {

  /* ---- Eyebrow label ------------------------------------ */
  .eyebrow {
    font-family: var(--font-body);
    font-size: var(--size-11);
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-text);
    margin-bottom: var(--sp-3);
    display: block;
  }

  /* ---- Buttons ------------------------------------------ */
  --btn-height:        48px;
  --btn-padding-x:     28px;
  --btn-radius:        4px;
  --btn-font-size:     15px;
  --btn-font-weight:   500;
  --btn-transition:    background-color var(--dur-fast) ease,
                       color var(--dur-fast) ease,
                       border-color var(--dur-fast) ease,
                       box-shadow var(--dur-fast) ease;

  .btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: var(--btn-height, 48px);
    padding: 0 var(--btn-padding-x, 28px);
    border-radius: var(--btn-radius, 4px);
    font-family: var(--font-body);
    font-size: var(--btn-font-size, 15px);
    font-weight: var(--btn-font-weight, 500);
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    border: 1.5px solid transparent;
    transition:
      background-color var(--dur-fast) ease,
      color var(--dur-fast) ease,
      border-color var(--dur-fast) ease,
      box-shadow var(--dur-fast) ease;
    white-space: nowrap;
  }
  .btn:hover { text-decoration: none; }
  .btn:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
  }

  /* Primary — dark text on accent gold
     #0a0a0a on #C8A87A ≈ 7.4:1 — WCAG AAA ✓ */
  .btn-primary {
    background: var(--accent);
    color: #0a0a0a;
    border-color: var(--accent);
  }
  .btn-primary:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: #0a0a0a;
    box-shadow: 0 0 20px rgba(200, 168, 122, 0.3);
  }

  /* Ghost — white text on transparent */
  .btn-ghost {
    background: transparent;
    color: var(--text-primary);
    border-color: rgba(255, 255, 255, 0.3);
  }
  .btn-ghost:hover {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.6);
    color: var(--text-primary);
  }

  /* LOB-accented button — used inside biz sections */
  .btn-lob {
    background: var(--lob-accent, var(--accent));
    color: var(--lob-btn-text, #ffffff);
    border-color: var(--lob-accent, var(--accent));
  }
  .btn-lob:hover {
    filter: brightness(1.15);
    color: var(--lob-btn-text, #ffffff);
  }
}

/* ============================================================
   NAVIGATION
   ============================================================ */
@layer components {
  .site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 200;
    height: var(--nav-height);
    background: transparent;
    transition:
      background-color var(--dur-normal) ease,
      border-bottom-color var(--dur-normal) ease,
      backdrop-filter var(--dur-normal) ease;
    border-bottom: 1px solid transparent;
  }
  .site-header.is-scrolled {
    background: rgba(27, 27, 27, 0.92);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom-color: var(--border-muted);
  }

  .nav-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--nav-height);
    gap: var(--sp-4);
  }

  /* Logo image link */
  .nav-wordmark {
    display: flex;
    align-items: center;
    text-decoration: none;
    flex-shrink: 0;
  }
  .nav-wordmark:hover { text-decoration: none; }

  /* Nav logo — replaces text lockup.
     height: 88px renders the full stacked mark at doubled size within the 120px nav bar
     (16px top/bottom via align-items: center); width: auto preserves the
     1778:1000 canvas aspect ratio (~1.78:1, content ~1.98:1 at 1709:863).
     At 88px rendered height, 2× display density requires 176px source — source is 1000px, crisp.
     Mobile (≤767px): scaled to 56px / 80px nav-height — keeps doubling intent within 390px viewport.
     Source: Brand Guidelines/logonew.png → images/logonew.webp (Q10 WebP lossy,
     RGBA, transparent background preserved.  Previously lossless; re-encoded at
     Q10 per Andy rule 2026-07-31.  If logo artifacts are visible, set
     WEBP_QUALITY=80 and re-run scripts/web-prep-webp.py --site acs-website). */
  .nav-logo {
    height: 88px;
    width: auto;
  }

  /* Desktop nav links */
  .nav-links {
    display: flex;
    align-items: center;
    gap: 32px;
  }
  .nav-link {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
    letter-spacing: 0.02em;
    transition: color var(--dur-fast) ease;
    position: relative;
    padding: 4px 0;
  }
  .nav-link:hover { color: var(--text-primary); text-decoration: none; }
  .nav-link::after {
    content: '';
    position: absolute;
    bottom: -2px;
    left: 0;
    right: 0;
    height: 1.5px;
    background: var(--accent);
    border-radius: 1px;
    transform: scaleX(0);
    transition: transform var(--dur-fast) ease;
    transform-origin: left;
  }
  .nav-link:hover::after { transform: scaleX(1); }

  /* Hamburger */
  .nav-hamburger {
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 40px;
    height: 40px;
    background: none;
    border: 1.5px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    padding: 8px;
    color: var(--text-primary);
    transition: border-color var(--dur-fast) ease, background-color var(--dur-fast) ease;
  }
  .nav-hamburger:hover {
    border-color: var(--text-secondary);
    background: var(--bg-elevated);
  }
  .nav-hamburger:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
  }
  .hamburger-bar {
    display: block;
    width: 18px;
    height: 1.5px;
    background: currentColor;
    border-radius: 1px;
    transition: transform var(--dur-normal) ease, opacity var(--dur-normal) ease;
    transform-origin: center;
  }

  @media (max-width: 767px) {
    .nav-hamburger { display: flex; }

    /* Scale logo down proportionally — 56px keeps the doubling intent on 390px viewports */
    .nav-logo { height: 56px; }

    .nav-links {
      position: absolute;
      top: var(--nav-height);
      left: 0;
      right: 0;
      flex-direction: column;
      align-items: stretch;
      gap: 0;
      background: rgba(27, 27, 27, 0.97);
      border-bottom: 1px solid var(--border);
      padding: 8px 0;
      display: none;
      backdrop-filter: blur(12px);
      -webkit-backdrop-filter: blur(12px);
    }
    .nav-links.is-open { display: flex; }

    .nav-link {
      padding: 16px var(--page-padding);
      font-size: 16px;
    }
    .nav-link::after { display: none; }
  }
}

/* ============================================================
   HERO CAROUSEL
   ============================================================ */
@layer components {

  /* TIER 3: Carousel component tokens */
  :root {
    --carousel-height: calc(100svh - var(--nav-height));
    --carousel-min-height: 600px;
    --carousel-dot-size: 8px;
    --carousel-dot-active-w: 28px;
    --carousel-transition: opacity var(--dur-carousel) ease, visibility var(--dur-carousel) ease;
  }

  .hero-carousel {
    position: relative;
    height: var(--carousel-height);
    min-height: var(--carousel-min-height);
    overflow: hidden;
    background: var(--bg-base);
  }

  /* Track holds all slides stacked */
  .carousel-track {
    position: absolute;
    inset: 0;
  }

  /* Individual slides — cross-fade opacity transition */
  .carousel-slide {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    padding-bottom: clamp(80px, 10vw, 120px);
    opacity: 0;
    visibility: hidden;
    transition: var(--carousel-transition);
    /* Background image slot — swap here when real imagery exists */
    /* background-image: url('/images/hero-[lob].jpg'); */
    background-size: cover;
    background-position: center;
  }
  .carousel-slide.is-active {
    opacity: 1;
    visibility: visible;
  }

  /* Gradient overlay for text legibility on all slides */
  .carousel-slide::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
      linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.3) 50%, rgba(0,0,0,0.1) 100%),
      linear-gradient(to right, rgba(0,0,0,0.4) 0%, transparent 60%);
    z-index: 1;
    pointer-events: none;
  }

  /* SLIDE BACKGROUNDS
     All 5 slides: real imagery wired (2026-07-20).
     The .carousel-slide rule already sets background-size: cover; background-position: center.
     Gradient overlay for text legibility lives on .carousel-slide::before — not here. */
  .slide--esport {
    /* Source: Hero Images/EsportSimulation-Hero.png 3840x2400 → hero-esport-v2.webp
       (WebP Q80, method 6, 3840x2400, 205 KB, 0.182 bpp, sRGB ICC embedded). 2026-10-13 */
    background-color: #0E0505;
    background-image: url('../images/hero-esport-v2.webp');
  }
  .slide--act {
    /* Source: Hero Images/AlternativeConceptTechnologies-Hero.png 3840x2400 → hero-act-v2.webp
       (WebP Q80, method 6, 3840x2400, 293 KB, 0.261 bpp, sRGB ICC embedded). 2026-10-13
       Known, accepted: at viewport heights ≤1080 the Von Mises bar's "8.85" tick (source y≈2265)
       falls below the cover crop. No background-position override — `center 75%` still clips it at
       1920x1080 and 1366x768, and `bottom` costs up to 578 source rows off the streamline panel. */
    background-color: #05060F;
    background-image: url('../images/hero-act-v2.webp');
  }
  /* .slide--act .slide-eyebrow text-shadow removed 2026-10-13. The ACT hero went dark-ground, so the
     "pale CAD/FEA UI background" premise behind the 2026-07-20 hack no longer holds: at 1440x900 the
     eyebrow zone is pure black and the indigo (#8B98FF @ .9) measures a uniform 6.58:1, with the
     shadow a measured no-op (6.58:1 either way). Reinstate it if ACT ever reverts to a pale hero.
     Not fixed by this change, and not an ACT-specific fault: at 390x844 the cover crop scales by
     height, so streamlines pass behind the eyebrow and ~33% of its inked pixels fall under 4.5:1.
     The shadow did not solve that either (~27% under 4.5:1 with it). Eyebrow-accent contrast at
     phone widths is an estate-wide question and belongs to Eleanor, not to a per-slide hack.
     RESOLVED 2026-10-13 — Eleanor ruled it, and the fix is the eyebrow scrim plus the
     display-accent tier (see TIER 2b and .slide-eyebrow::before). Streamlines still pass behind
     the ACT eyebrow at 390; they now pass behind a guaranteed near-black ground, so they no
     longer reach the type. The figure this note used to quote for canonical eSport red, 3.63:1,
     was measured THROUGH the old `opacity: 0.9`; at full opacity the same colour on pure black
     is 4.32:1. Both are correct, they are different states, and the opacity is now gone.
     Measured with tools/hero-legibility-gate.py. */
  /* AP: real imagery — Serge's bottom-heavy gradient override on ::before.
     The global ::before (bottom + left vignette) is replaced for this slide only.
     Spec v2 (Serge's own amendment, 2026-10-13): bottom-heavy, no full-field mask,
     no centred vignette, reaching transparent at 80% rather than 50%. */
  .slide--ap {
    /* Source: Team Inbox/AP-Hero.png → hero-ap-v2.webp (Q10 WebP, full res, 21 KB).
       KNOWN STALE: this is the old broken 1432x681 Q10 export. Andy is exporting a
       replacement. The gradient amendment below is correct independently of which
       image lands in this slot — it is derived from the TEXT BOX geometry, not from
       the photograph. Do not re-encode this file as part of a gradient change. */
    background-color: #0A0906;
    background-image: url('../images/hero-ap-v2.webp');
  }
  .slide--ap::before {
    /* Serge's spec: bottom-heavy only — do not reintroduce the right-to-left side gradient */
    /* AMENDED 2026-10-13 by Serge, correcting his own spec. v1 was
         linear-gradient(to top, rgba(10,10,10,0.88) 0%, transparent 50%)

       READ THE COORDINATES CAREFULLY — `to top` runs the gradient line from
       the BOTTOM edge, so a stop at 50% sits at slide y 0.50 and a stop at
       80% sits at slide y 0.20. v1 therefore washed the bottom HALF of the
       slide and left the top half bare; v2 washes the bottom 80% and leaves
       the top 20% bare.

       WHY THAT WAS WRONG. The text block does not sit in a fixed place: it is
       bottom-aligned in a box whose height is max(100svh - nav, 600px), so
       its top edge moves with the viewport. Measured span of .slide-content
       as a fraction of slide height, AP slide, live in Chromium 2026-10-13:
         1366x768  0.055-0.815     1536x864   0.168-0.839
         1280x800  0.125-0.824     1440x900   0.207-0.846
         1920x1080 0.355-0.875     2560x1440  0.531-0.909
       The share of that block with NO overlay at all under v1, and under v2:
         viewport     v1 bare    v2 bare
         1366x768      58.5%      19.0%     <- worst case
         1280x800      53.7%      10.7%     <- Serge's "54%" reproduces here
         1536x864      49.5%       4.7%
         1440x900      45.9%       0.0%
         1920x1080     27.8%       0.0%
       So on a common 1280x800 or 1366x768 desktop the overlay contributed
       nothing to roughly half the height of the text it exists to serve, and
       the copy was sitting on bare photograph.

       MEASURED COST AT THE TOP OF FRAME: nil. The ramp is still FULLY
       TRANSPARENT over the top 20% of the slide (gradient positions 80-100%),
       which is above everything the slide draws at every viewport measured
       above, so the sky and horizon of any AP photograph are untouched.

       MEASURED BENEFIT, mean background luminance under the AP text boxes,
       v1 -> v2, via tools/hero-legibility-gate.py:
         1440x900  descriptor  0.04664 -> 0.02373   0.97 stops darker
         1440x900  headline    0.30733 -> 0.18627   0.72 stops
         390x844   descriptor  0.01051 -> 0.00722   0.54 stops
         390x844   headline    0.14632 -> 0.06730   1.12 stops
       and in gated terms, headline CR p5 1.52 -> 2.32 at 1440x900 and
       1.30 -> 2.84 at 390x844; descriptor CR p5 3.88 -> 5.95 at 1440x900.
       NOTE: Serge's brief said "roughly 1.7 stops". That figure does not
       reproduce — the largest of the four above is 1.12. The amendment is
       right and the direction is right; the quoted magnitude was not, and
       the measured numbers are recorded here instead. Flagged 2026-10-13.

       The 0.88 -> 0.92 bottom alpha is part of the same amendment.
       composite() in tools/hero-legibility-gate.py models this gradient and
       asserts this exact declaration — edit the two together or the gate
       stops and tells you. */
    background: linear-gradient(to top, rgba(10,10,10,0.92) 0%, transparent 80%);
  }
  /* CCA: real imagery wired (2026-07-20). Placeholder teal-gradient retired. Global ::before vignette handles text legibility. */
  .slide--cca {
    /* Source: Hero Images/CarolCampbellArt-Hero.png 3840x2400 → hero-cca-v2.webp
       (WebP Q80, method 6, 3840x2400, 190 KB, 0.169 bpp, sRGB ICC embedded). 2026-10-13 */
    background-color: #060C0E;
    background-image: url('../images/hero-cca-v2.webp');
  }
  /* PP: real imagery */
  .slide--pp {
    /* Source: Hero Images/PickledPoems-Hero-RETOUCHED.png 3840x2400 → hero-pp-v2.webp
       (WebP Q80, method 6, 3840x2400, 319 KB, 0.284 bpp, sRGB ICC embedded). 2026-10-13
       SOURCE FILE MATTERS HERE. `PickledPoems-Hero.png` still holds the canonical-sounding
       name and is the REJECTED asset: it carries a hard-edged, stippled dark quadrilateral
       on the firelit skin at x 2597-2637, y 353-406. The retouched deliverable replaces it
       with continuous skin. Confirmed by pixels, not by filename — that region is the ONLY
       difference between the two files (1,539 px over delta 8, bbox exactly as above), and
       a row profile through it steps 83->51 in 4px in the rejected file against a smooth
       101->91->82->72->61->54 ramp in the retouched one. Re-verify after any re-encode:
       the repair is a smooth low-gradient region, which is where a lossy codec could
       reintroduce a patch. At Q80 it did not — the encoded region tracks the retouched PNG
       at RMSE 2.62 (against 11.98 for the rejected one) and is only 1.31x the whole-image
       mean codec error, so the codec did not single it out. */
    background-color: #060C08;
    background-image: url('../images/hero-pp-v2.webp');
  }
  /* PP crop rule — Julie's §6 context test, driven off ASPECT RATIO. 2026-10-13.
     ==================================================================
     RULE, IN ONE LINE: wherever the hero box is portrait or square, shift
     the PP crop right so the written sheet is whole; where the box is too
     narrow to hold her face and the whole sheet at once, drop the face and
     lead with hands and paper.

     ------------------------------------------------------------------
     1. WHAT JULIE RULED (2026-10-13)

     "At every viewport where Lucy's face is shown, the object she is
     attending to must be whole and legible in the same frame. Not partial.
     A half-sheet does not satisfy it."

     and, as an equally valid outcome:

     "If a frame cannot hold her face and the parchment together, the fix is
     not a contextless Lucy — it is to stop showing her face. A crop that
     leads with hands and paper and loses the face entirely is fully
     on-brand; §5.3 asks for exactly that register."

     She states the CONDITION. The threshold, the trigger and the mechanism
     below are Vera's, and are derived from the geometry, not from devices.

     ------------------------------------------------------------------
     2. THE GEOMETRY. WHY ASPECT RATIO IS THE HONEST TRIGGER

     The source is 3840x2400 — exactly 1.6:1. `background-size: cover`
     scales by whichever axis is binding:

       box aspect >= 1.6   scales by WIDTH.  The full source width is in
                           frame. Nothing can be cut horizontally. Desktop.
       box aspect <  1.6   scales by HEIGHT. The full source HEIGHT is in
                           frame and a vertical column of width
                           v = box_aspect / 1.6 survives, centred.

     So the fault is not "small screens". It is "a box narrower than 1.6:1",
     and the old `max-width: 767px` trigger was a proxy for that which
     happened to catch phones and miss portrait tablets. It is replaced.

     The hero box is NOT the viewport:
       box_w = viewport_w
       box_h = max(viewport_h - var(--nav-height), var(--carousel-min-height))
             = max(viewport_h - (120 | 80 at <=767px), 600)
     Measured live in Chromium: 390x844 -> 390x764, 344x882 -> 344x802,
     768x1024 -> 768x904, 375x667 -> 375x600 (clamped), 1920x1080 -> 1920x960.

     ------------------------------------------------------------------
     3. FEATURE POSITIONS, RE-MEASURED FROM images/hero-pp-v2.webp ITSELF

     As fractions of SOURCE WIDTH (x) and SOURCE HEIGHT (y). These supersede
     the first-pass figures in the 73% version of this comment, which
     under-read the sheet's right edge by 4.4 points and the face by ~3
     points on each side. That error is what let the 73% crop ship with the
     sheet's folded corner cut off.

       face, skin silhouette      x 46.5 - 69.0      y  2 - 42
       eyes, outer corner to
         outer corner             x 55.5 - 68.5      y 15 - 25
       mouth                      x 58.5 - 65.0      y 32 - 38
       hand + fingers             x 61.0 - 79.0      y 48 - 72
       WRITTEN SHEET (the object
         of §6 attention)         x 72.4 - 84.4      y 35.5 - 58
       sheaf of pages, gilt edge  x 66.0 - 80.0      y 57 - 70
       glass jar                  x 81.0 - 97.0      y 76 - 100
       hearth flare               x 85.0 - 100.0     y  0 - 100

     The sheet is 12.0 points of source width wide. The face is 22.5. They
     are 3.4 points apart. Every number below follows from those three.

     ------------------------------------------------------------------
     4. THE WINDOW EQUATION

     A percentage background-position is a proportional ALIGNMENT, not a
     translation: p% of the image is pinned to p% of the box. With visible
     width fraction v, the visible source window is

       left  = (1 - v) * p          right = left + v

     CENTRED (p = 50%) the window holds the whole sheet only while
     0.5 + v/2 >= 0.844, i.e. v >= 0.688, i.e. BOX ASPECT >= 1.101.
     Anything narrower than that cuts the sheet. That is the fault, exactly.

     ------------------------------------------------------------------
     5. BAND A — THE TRIGGER, AND WHY 1/1

     The fault boundary is box aspect 1.101. Converting to the viewport
     aspect a media query can actually see:

       vp_aspect_at_fault = 1.101 * (1 - nav / viewport_h)

     which evaluates to 0.969 at H=1000, 0.983 at H=1120, 1.004 at H=1366,
     1.018 at H=1600. So for every viewport shorter than 1308 CSS px
     the fault boundary sits BELOW a viewport aspect of 1.0, and

       @media (max-aspect-ratio: 1/1)

     covers it completely. "Portrait or square" is the rule, which is also
     the sentence a human can hold in their head.

     DESKTOP IS PROVABLY OUT OF RANGE, and this was proven by RUNTIME A/B
     rather than asserted: 1920x1080 is aspect 1.778, which is 78% above the
     trigger; 1440x900 and 1280x800 are 1.6 (60% above); 1366x768 is 1.779.
     The before/after renders at 1920x1080 are sha256-identical. See
     Owner's Inbox/2026-10-13-hero-gate/.

     RESIDUAL, STATED RATHER THAN HIDDEN: a viewport TALLER than 1308 px
     and within 3.5% of square (e.g. 1600x1590) sits just above the trigger
     and just below the fault boundary. There the centred crop clips about
     0.4 points of the sheet's folded corner — roughly 9 device px. No real
     device has that shape. Raising the trigger to 11/10 would close it at
     the cost of reframing genuinely landscape viewports Julie has signed
     off, which is not a trade worth making for a 9px corner.

     ------------------------------------------------------------------
     6. BAND A — WHY 79% AND NOT ANOTHER VALUE

     Two constraints bite from opposite sides, and the band is narrow:

       sheet whole:     right >= 0.844   =>  p >= (0.844 - v) / (1 - v)
       both eyes whole: left  <= 0.555   =>  p <= 0.555 / (1 - v)

     Both are satisfiable only while v >= 0.844 - 0.555 = 0.289, i.e. box
     aspect >= 0.4624. That is a hard geometric floor, not a preference.

     Within the band, the value that splits the remaining slack EQUALLY
     between the two constraints is left = (1.399 - v)/2. At the tightest
     mainstream phone in band A (412x915, v = 0.3084) that gives p = 78.9%;
     at 390x844 (v = 0.319) it gives 79.3%. 79% serves the whole band.

     Candidates were rendered in Chromium at 390x844 and compared at 4x:
       73% -> window 49.7-81.6%. The sheet's folded corner is CUT by the
              frame edge. This is what shipped this morning and it is the
              defect. Rejected.
       77% -> 52.4-84.3%. Corner touching the edge, no margin. Rejected.
       79% -> 53.8-85.7%. Sheet whole with 1.3 points of margin; eye corner
              1.7 points (21 px) inside the left edge. CHOSEN.
       81% -> 55.2-86.4%. Eye corner 0.3 points (4 px) from the edge. Too
              tight — one device profile away from a cut eye.
       83% -> 56.5-87.2%. Cuts the outer corner of the near eye. Rejected.

     STABILITY ACROSS BAND A, measured off the live page (window = source
     x %, with the sheet at 72.4-84.4 and the near eye corner at 55.5):

       1024x1366  v 51.4%  38.4 - 89.8      390x844  v 31.9%  53.8 - 85.7
        768x1024  v 53.1%  37.1 - 90.2      393x852  v 31.8%  53.9 - 85.7
        834x1194  v 48.5%  40.7 - 89.2      430x932  v 31.5%  54.1 - 85.6
        375x667   v 39.1%  48.1 - 87.2      360x800  v 31.2%  54.3 - 85.6
        360x640   v 37.5%  49.4 - 86.9      412x915  v 30.8%  54.6 - 85.5
        320x568   v 33.3%  52.7 - 86.0     <- band A floor is v 28.9%

     Every row holds the whole sheet and both eyes. The band-A floor at
     v 28.9% is 6.2% below the tightest real device in it (412x915).

     ------------------------------------------------------------------
     7. BAND B — WHERE THE FRAME CANNOT HOLD BOTH

     Below v 28.9% the arithmetic above has no solution, and the renders
     agree: at 344x882 (Z Fold cover screen, v 26.8%) every position that
     makes the sheet whole cuts the near eye in half, which is precisely
     the "reads as a rendering fault" failure Julie named. So band B takes
     her second direction and stops showing the face.

     This needs a zoom, not just a shift: with `cover` on a portrait box the
     FULL source height is always in frame, so the face cannot be excluded
     by position alone. `background-size: 650% auto` makes the displayed
     image 6.5x the box width, which fixes the visible width fraction at
     1/6.5 = 15.38% AT EVERY VIEWPORT IN THE BAND — so the horizontal
     framing, which is the axis the face/sheet separation lives on, does not
     drift at all:

       left  = (1 - 0.1538) * 0.84 = 71.1%      (face ends at 69.0)
       right =                       86.5%      (sheet ends at 84.4)

     giving 2.1 points of clearance from the face and 1.3 / 2.1 points of
     margin around the sheet, constant. The permissible range for p_x is
     82.1% - 85.6%; 84% is its midpoint.

     The vertical axis absorbs the drift instead, because it has the slack:
     visible height fraction = 0.246 / box_aspect, which runs 51.2% at the
     top of the band to 82% at box aspect 0.30. p_y = 72% is set from the
     WORST case (the widest box in band B, aspect 0.472), where it puts the
     window top at 34.5% against a sheet top of 35.5%. Every narrower box in
     the band gets MORE headroom, never less.

     BAND B TRIGGER. The geometric floor of band A is box aspect 0.4624,
     which is a viewport aspect of 0.416 - 0.425 across the heights where it
     can occur. 5/12 = 0.4167 sits inside that range and splits the two
     nearest real devices almost symmetrically: 344x882 is 0.390 (6.4%
     below, band B) and 412x915 / 360x800 are 0.450 (8.0% above, band A).

     BAND B COVERAGE LIMIT: at box aspect below 0.246 the 650% zoom no
     longer covers vertically and `background-color: #060C08` would show
     through top and bottom. That is a 200x813 box. Stated for completeness;
     `background-color` means it degrades to a dark band, not a white flash.

     ------------------------------------------------------------------
     8. CONTRAST. RE-GATED, NOT ASSUMED

     Julie accepts the legibility of this hero, but both crops move the
     backdrop under the copy, so both were re-measured with
     tools/hero-legibility-gate.py rather than carried over. The gate now
     reads these very declarations out of this file (see §9) so its numbers
     are about the frame the site actually serves.

     ------------------------------------------------------------------
     9. THE GATE READS THIS BLOCK. DO NOT MOVE IT BEHIND AN ABSTRACTION

     tools/hero-legibility-gate.py parses global.css for `background-size`
     and `background-position` on `.carousel-slide` and `.slide--*`, and for
     `--nav-height` / `--carousel-min-height`, evaluating the enclosing
     @media conditions against the viewport it is measuring. There is no
     second copy of these numbers to keep in sync, and that is deliberate:
     the previous version of the gate hardcoded `center` and therefore
     returned PASS on a frame the site did not serve.

     It FAILS CLOSED. If you write a background-position or background-size
     the parser does not understand, or wrap one in an @media condition it
     cannot evaluate, the gate refuses to measure that slide and says so. It
     will never silently assume `center` again. If you need a construct it
     does not support, teach the parser — do not work around it.

     ------------------------------------------------------------------
     10. ESCAPE HATCHES, DOCUMENTED

     (a) New PP hero with the narrative object elsewhere: re-measure the
         feature table in §3 off the new file, re-derive p from §6, re-run
         the gate, re-render 390x844 / 768x1024 / 344x882. Do not reach for
         a separate mobile asset first.
     (b) Band B not wanted for a future hero: delete the 5/12 block. The
         system falls back to band A everywhere, which is a valid, if
         tighter, outcome at every aspect ratio.
     (c) A separate art-directed mobile asset IS pre-authorised by Julie,
         but only once a recrop has been shown to fail. It is a second file
         to keep in sync with every future hero change.
     (d) Any of this can be waived per slide with the gate's `--waive`,
         which still prints the real numbers. Waiving is a Business Lead
         decision, recorded here in this comment. */
  @media (max-aspect-ratio: 1/1) {
    /* BAND A — portrait or square box. Shift right so the written sheet is
       whole. Shorthand, not the `background-position-x` longhand: `center`
       is restated so the vertical framing is explicit and cannot drift if
       the base rule changes, and it avoids the longhand's
       shorthand-interaction edge cases. */
    .slide--pp { background-position: 79% center; }
  }
  @media (max-aspect-ratio: 5/12) {
    /* BAND B — the box is too narrow to hold her face and the whole sheet.
       Julie's second direction: stop showing the face, lead with hands and
       paper. Must stay AFTER band A in source order — same specificity, so
       source order is what makes it win. */
    .slide--pp {
      background-size: 650% auto;
      background-position: 84% 72%;
    }
  }

  /* Slide content */
  .slide-content {
    position: relative;
    z-index: 2;
    /* padding-top: var(--nav-height) removed — hero now starts below the fixed nav
       via #main-content { padding-top: var(--nav-height) }, so this offset is no longer needed */
    padding-top: 0;
    width: 100%;
  }

  .slide-eyebrow {
    font-family: var(--font-body);
    font-size: var(--size-11);
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--slide-accent, var(--accent-text));
    margin-bottom: var(--sp-2);
    display: block;
    /* Hug the text. The scrim below is anchored to THIS box, so the box
       must be the text's box and not the content column's — otherwise the
       scrim would be 1296px wide on desktop behind a 143px word. */
    width: fit-content;
    position: relative;
    /* `opacity: 0.9` removed 2026-10-13 (Eleanor). It bought nothing on type
       that is already subdued at 11px/500 and cost between +0.92 and +2.21
       ratio points on pure black across the five slides:
         esport +0.92   act +1.46   ap +2.21   cca +1.97   pp +1.54
       Measured against the real photographs rather than pure black the gain
       is Eleanor's +0.61 to +1.99 — same change, different ground, both
       figures reproduce. It also removed a stacking context, which is what
       lets the scrim below sit at z-index -1 without being trapped. */
  }

  /* EYEBROW SCRIM — a guaranteed near-black ground for the eyebrow band.
     ==================================================================
     Eleanor's ruling, 2026-10-13. She specified the REQUIREMENT and was
     explicit that the device is Vera's.

     THE REQUIREMENT, IN ONE LINE: an eyebrow's on-black contrast figure
     must BE its real figure, whatever photograph is behind it.

     ------------------------------------------------------------------
     1. WHY THE EYEBROW AND NOT THE OTHER TWO

     It is structurally the least-protected text on every slide. It is
     the smallest type (11px, so its WCAG floor is the full 4.5:1), and
     because .carousel-slide is `align-items: flex-end` it sits at the
     TOP of a bottom-anchored block — the weakest part of a ramp running
     rgba(0,0,0,0.85) at the bottom of the slide to 0.1 at the top. The
     headline and descriptor sit lower, in the strong part of it.

     Measured, eSport at 390x844 before this change: eyebrow #D0281D as
     composited, luminance 0.1502, against a local backdrop of 0.0472 —
     2.06:1. Red type on red ground at under half AA.

     ------------------------------------------------------------------
     2. WHY NEITHER LEVER WORKS ALONE. THIS IS THE LOAD-BEARING PART

     COLOUR ALONE FAILS. Against the measured deck there is no red that
     reaches AA on the eSport photograph without going to pale salmon,
     at which point it is no longer the brand's colour.

     BACKDROP ALONE FAILS. With the canonical #DA291C the ceiling is
     4.32:1 — and that is at literally pure black. There is no backdrop
     darker than black, so no scrim of any strength can lift canonical
     red to AA. The colour HAS to move too.

     THE PROOF THAT THE BACKDROP DOMINATES IS PP. Its eyebrow is an
     already well-lightened green that measures 8.44:1 on black, and
     2.48:1 against its own photograph. Across the deck, three of five
     fail against their pictures while four of five pass against flat
     colour. The variable is the picture, not the palette.

     Hence: scrim AND display variant, as one change. Either alone is
     measurably insufficient and the measurements are above.

     ------------------------------------------------------------------
     3. THE DEVICE, AND WHY IT IS NOT A BOX

     A box-shadow halo on a pseudo-element anchored to the text box.

     `width: fit-content` on .slide-eyebrow makes that box hug the actual
     word, so the scrim scales with the text — "Alternative Concept
     Technologies" and "Pickled Poems" each get a correctly-sized one,
     at every viewport, with no per-slide tuning and nothing to keep in
     sync when copy changes.

     The reason it does not read as a box or a bar:

     (a) IT HAS NO EDGE, BECAUSE A TRUE GAUSSIAN HAS NO EDGE. The core
         is a plain filled rect and `filter: blur(13px)` is what the
         viewer actually sees. A Gaussian ramp has no stop and no
         plateau boundary, so there is nothing in the silhouette for the
         eye to lock onto.

     (b) ITS CORNERS FALL AWAY FASTER THAN ITS EDGES. Blurring a rect
         rounds the profile in both axes at once, so the silhouette is a
         soft ellipse. On a photographic hero that reads as a vignette
         or a lens shadow — an atmospheric darkening behind a word,
         which is a thing photographs do.

     (c) IT IS SMALL AND IT IS LOCAL. Roughly 100px tall and a few tens
         of px wider than one line of type, versus a full-bleed band
         which would unavoidably read as furniture.

     THIS WAS SETTLED BY RENDERING, NOT BY REASONING, AND THE FIRST
     ANSWER WAS WRONG. The device shipped first as a box-shadow halo
     (spread 1.5em, blur 2.8em). At 1x it looked fine. At 3x on ACT at
     390 it was plainly a rounded rectangle, with its right edge cutting
     across the streamlines. Lengthening the blur did NOT fix it and
     could not: box-shadow needs spread >= blur/2 to avoid a step at the
     core, and raising spread grows the SOLID region, so the shape gets
     more box-like as it gets softer. A four-point sweep (ramps of 31,
     61, 88 and 121px) showed a visible boundary at every one of them.
     Two other devices were then rendered and compared on the two worst
     slides: this blur, and a full-bleed band with a 5em vertical
     feather and a masked fade to the right. The band works and is
     rejected anyway — it dims far more of the photograph, and faint
     horizontal banding is perceptible across the top of the frame.

     Rejected: a box-shadow halo (measured above — reads as a rounded
     rect); a filled band (reads as a bar); a rounded chip behind the
     text (reads as a badge, and changes the brand's voice); extending
     the slide's bottom gradient upward to reach the eyebrow (the AP
     ::before amendment above is the worked example of why that fails —
     the block is bottom-anchored, so its top edge moves with the
     viewport and a fixed-percentage stop cannot track it).

     Also ruled out by Eleanor, with reasons, and not to be revisited:
     raising type size or weight to reach the 3:1 large-text threshold
     (--size-11 is 11px against a 24px threshold, so the eyebrow would
     end up larger than the descriptor it introduces and the hierarchy
     would invert); and reinstating the text-shadow, already measured as
     a no-op on dark ground (6.58:1 either way — see the ACT note above).

     ------------------------------------------------------------------
     4. WHAT THE GUARANTEE IS WORTH, IN NUMBERS. MEASURED, NOT DERIVED

     The core alpha is 0.96, so an UNBLURRED core over a pure-white
     photograph would leave 255 x 0.04 = 10.2, relative luminance
     0.0031. The blur attenuates the core's peak alpha, so that figure
     is a floor the real device has to be checked against rather than
     assumed to meet — which is why the core is inset 2em vertically
     rather than hugging the type. A core much shorter than the blur
     radius loses its plateau and the guarantee goes with it.

     Measured in Chromium against a FORCED PURE-WHITE hero
     (tools/render-hero-proofs.py eyebrow, with the image and the
     ::before gradients removed), which is the real worst case:

       no scrim at all             backdrop L 1.00000   eSport 3.88:1
       this scrim, blurred         backdrop L 0.00335   eSport 5.04:1
       (an ideal flat 0.96 core    backdrop L 0.00310)

     So the blur costs 0.00025 of backdrop luminance against the ideal,
     and nothing that matters. A contrast ratio against L=0.00335
     instead of L=0 is 0.05/0.05335 = 93.7% of the on-black figure.

     "Its on-black figure IS its real figure" is therefore true to
     within 6.3% for ANY image that could ever be dropped into these
     slots — including a white one — and to within about 1% for the five
     actually in them. The 5:1 tier floor delivers no worse than 4.69:1
     in that limit, which still clears AA; eSport, the tightest colour
     on the deck, measured 5.04:1 against a pure-white hero.

     That bound is the whole point of quoting one contrast number per
     colour in the display-accent tier instead of one per photograph.

     ------------------------------------------------------------------
     5. THE GATE MEASURES THIS, AND IT IS MEASURED TWICE

     tools/hero-legibility-gate.py parses --eyebrow-scrim-alpha out of
     this file and applies it over the eyebrow rect, and prints the bare
     figure beside the scrimmed one so the photograph's real
     contribution stays visible. It fails closed if it cannot find the
     declaration.

     That model alone would be circular — it would assert the mechanism
     and never the shipped state, and would report green even if the
     scrim never painted. So the gate's eyebrow row is cross-checked
     against REAL composited pixels sampled out of a Chromium render by
     tools/render-hero-proofs.py. Eleanor asked for a falsifier and that
     browser-side reading is it: if PP still fails at desktop once the
     scrim is in, the device is not delivering what she specified and
     the colour variants need revisiting.

     ------------------------------------------------------------------
     6. ESCAPE HATCHES, DOCUMENTED

     (a) Scrim unwanted on a future slide: set
         `--eyebrow-scrim: transparent` on that .slide--* . The type
         stays in its display variant and the gate's bare column becomes
         the real figure — which is exactly the state this rule exists
         to fix, so do it only with a Business Lead's sign-off recorded
         at the override site.
     (b) Softer or stronger: --eyebrow-scrim-alpha is the intended knob,
         and section 4 above is the procedure for re-deriving the
         guarantee after moving it — RE-MEASURE against a white hero,
         do not recompute from the alpha, because the blur attenuates
         it. Below about 0.85 the guarantee is gone (255 x 0.15 = 38 is
         the point where a white backdrop stops being near-black).
         If you change the blur radius, change the core inset with it:
         the inset must stay large relative to the radius or the core
         loses its plateau. At blur 13px an inset of 1.2em measured
         backdrop L 0.00348 against the real AP hero versus 0.00126 at
         2em — a third of the protection, for 0.8em.
     (c) A hero whose eyebrow zone is reliably dark anyway still gets
         the scrim. Uniformity across the deck is worth more than the
         few ratio points a per-slide exemption would save, and a
         carousel cross-fades between these slides in place. */
  .slide-eyebrow::before {
    content: '';
    position: absolute;
    /* Solid core. The 2em vertical inset is NOT breathing room — it is
       what keeps a plateau in the middle of the core after the blur
       below, and therefore what keeps the guarantee in section 4. It is
       sized against the blur radius, not against the type. */
    inset: -2em -2.4em;
    background: var(--eyebrow-scrim);
    /* The device. A true Gaussian, so the silhouette has no boundary to
       read as a box — see section 3. Not a box-shadow: that was tried,
       measured and rejected, and the reason it cannot work is recorded
       there. Keep this in step with the inset above. */
    filter: blur(13px);
    /* Behind the eyebrow's own text. .slide-eyebrow is position:relative
       with no z-index so it forms no stacking context; the nearest one is
       .slide-content (z-index 2), which puts this above the slide image
       and its ::before gradient, and below every text layer. */
    z-index: -1;
    pointer-events: none;
  }

  .slide-headline {
    font-family: var(--font-display);
    font-size: clamp(64px, 10vw, 140px);
    line-height: 1.0;
    color: var(--text-primary);
    margin-bottom: var(--sp-3);
    letter-spacing: 0.02em;
    max-width: 900px;
    /* Animate in when slide becomes active */
    transform: translateY(16px);
    opacity: 0;
    transition:
      transform var(--dur-slow) var(--ease-out) 100ms,
      opacity var(--dur-slow) ease 100ms;
  }

  .slide-descriptor {
    font-size: clamp(16px, 2vw, 20px);
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.75);
    max-width: 540px;
    margin-bottom: var(--sp-5);
    transform: translateY(12px);
    opacity: 0;
    transition:
      transform var(--dur-slow) var(--ease-out) 200ms,
      opacity var(--dur-slow) ease 200ms;
  }

  .slide-ctas {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    transform: translateY(10px);
    opacity: 0;
    transition:
      transform var(--dur-slow) var(--ease-out) 300ms,
      opacity var(--dur-slow) ease 300ms;
  }

  /* Animate content in when slide is active */
  .carousel-slide.is-active .slide-headline,
  .carousel-slide.is-active .slide-descriptor,
  .carousel-slide.is-active .slide-ctas {
    transform: translateY(0);
    opacity: 1;
  }

  /* No animation when reduced-motion is set */
  @media (prefers-reduced-motion: reduce) {
    .carousel-slide { transition: none; }
    .slide-headline, .slide-descriptor, .slide-ctas {
      transform: none !important;
      opacity: 1 !important;
      transition: none;
    }
  }

  /* LOB eyebrow ink — DISPLAY VARIANTS, one per slide.
     These were four ad-hoc hex values and one raw brand colour until
     2026-10-13. They are now the display-accent tier: named, measured and
     documented in TIER 2b at the top of this file, where the rule and the
     >= 5:1 floor live. Do not write a hex here — add a variant to the tier
     and alias it, so the floor is checked in one place.
     --slide-accent is consumed by exactly one declaration, .slide-eyebrow
     `color`, which is what made the tier possible to name honestly. */
  .slide--esport   { --slide-accent: var(--display-accent-esport); }
  .slide--act      { --slide-accent: var(--display-accent-act); }
  .slide--ap       { --slide-accent: var(--display-accent-ap); }
  .slide--cca      { --slide-accent: var(--display-accent-cca); }
  .slide--pp       { --slide-accent: var(--display-accent-pp); }

  /* CTA button variants per slide accent */
  .slide--esport   .btn-slide { background: #DA291C; color: #fff; border-color: #DA291C; }
  .slide--esport   .btn-slide:hover { background: #F03020; border-color: #F03020; }
  .slide--act      .btn-slide { background: #5865F7; color: #fff; border-color: #5865F7; }
  .slide--act      .btn-slide:hover { background: #6B7AFF; border-color: #6B7AFF; }
  /* AP gold: dark text — #0A0A0A on #C8A96A ≈ 7.6:1 WCAG AAA ✓ */
  .slide--ap       .btn-slide { background: #C8A96A; color: #0A0A0A; border-color: #C8A96A; }
  .slide--ap       .btn-slide:hover { background: #D8B97A; border-color: #D8B97A; }
  /* CCA teal: dark text — #0A0A0A on #4A8FA8 ≈ 5.5:1 WCAG AA ✓
     Rachel-confirmed, Eleanor-mandated accessibility fix (white was 3.63:1, failed AA). */
  .slide--cca      .btn-slide { background: #4A8FA8; color: #0A0A0A; border-color: #4A8FA8; }
  .slide--cca      .btn-slide:hover { background: #5EA5BE; border-color: #5EA5BE; }
  /* PP green: dark text — #0A0A0A on #4A8A60 ≈ 5.09:1 WCAG AA ✓
     (white on #4A8A60 ≈ 4.12:1 fails AA; dark text applied) */
  .slide--pp       .btn-slide { background: #4A8A60; color: #0A0A0A; border-color: #4A8A60; }
  .slide--pp       .btn-slide:hover { background: #5A9A70; border-color: #5A9A70; }

  /* --- Carousel controls --------------------------------- */
  .carousel-controls {
    position: absolute;
    bottom: 32px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
  }

  .carousel-arrow {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    background: rgba(255,255,255,0.08);
    border: 1.5px solid rgba(255,255,255,0.18);
    border-radius: 50%;
    cursor: pointer;
    color: var(--text-primary);
    transition: background-color var(--dur-fast) ease, border-color var(--dur-fast) ease;
  }
  .carousel-arrow:hover {
    background: rgba(255,255,255,0.18);
    border-color: rgba(255,255,255,0.40);
  }
  .carousel-arrow:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
  }

  /* Dot indicators */
  .carousel-dots {
    display: flex;
    align-items: center;
    gap: 8px;
  }

  .carousel-dot {
    height: var(--carousel-dot-size);
    width: var(--carousel-dot-size);
    border-radius: 99px;
    background: rgba(255,255,255,0.28);
    border: none;
    cursor: pointer;
    transition:
      width var(--dur-normal) var(--ease-out),
      background-color var(--dur-normal) ease;
    padding: 0;
  }
  .carousel-dot.is-active {
    width: var(--carousel-dot-active-w);
    background: var(--text-primary);
  }
  .carousel-dot:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
  }

  /* Pause button — WCAG 2.2.2 */
  .carousel-pause {
    position: absolute;
    top: calc(var(--nav-height) + 16px);
    right: 24px;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    background: rgba(0,0,0,0.4);
    border: 1.5px solid rgba(255,255,255,0.2);
    border-radius: 50%;
    cursor: pointer;
    color: var(--text-secondary);
    transition: background-color var(--dur-fast) ease, color var(--dur-fast) ease;
  }
  .carousel-pause:hover {
    background: rgba(0,0,0,0.7);
    color: var(--text-primary);
  }
  .carousel-pause:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 3px;
  }

  /* Slide counter */
  .carousel-counter {
    position: absolute;
    bottom: 38px;
    right: 24px;
    z-index: 10;
    font-size: 12px;
    font-weight: 500;
    color: rgba(255,255,255,0.45);
    letter-spacing: 0.08em;
    font-family: var(--font-body);
    pointer-events: none;
  }

  @media (max-width: 600px) {
    .slide-ctas { gap: 12px; }
    .slide-ctas .btn { font-size: 14px; padding: 0 20px; height: 44px; }
    .carousel-counter { display: none; }
  }
}

/* ============================================================
   BUSINESS LINE SECTIONS
   ============================================================ */
@layer pages {

  /* Full-bleed showcase modules — one per LOB */
  .biz-section {
    position: relative;
    min-height: 70vh;
    display: flex;
    align-items: center;
    overflow: hidden;
    /* LOB accent exposed as custom property for child elements */
    --lob-accent: var(--accent);
    --lob-accent-glow: rgba(200, 168, 122, 0.25);
    --lob-accent-dim: rgba(200, 168, 122, 0.12);
    --lob-btn-text: #0a0a0a;
  }

  /* Background gradient layer — placeholder until imagery is supplied
     SWAP: add background-image: url('/images/[lob].jpg') to the variant class */
  .biz-section::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
      linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.20) 70%, rgba(0,0,0,0.05) 100%);
    z-index: 1;
    pointer-events: none;
  }

  .biz-section--esport {
    background-color: #110404;
    background-image:
      radial-gradient(ellipse 80% 60% at 80% 50%, rgba(218,41,28,0.30) 0%, transparent 65%);
    --lob-accent: #DA291C;
    --lob-accent-glow: rgba(218,41,28,0.25);
    --lob-accent-dim: rgba(218,41,28,0.12);
    --lob-btn-text: #ffffff;
    border-top: 1px solid rgba(218,41,28,0.15);
  }
  .biz-section--act {
    background-color: #06070F;
    background-image:
      radial-gradient(ellipse 70% 60% at 20% 50%, rgba(88,101,247,0.28) 0%, transparent 60%);
    --lob-accent: #5865F7;
    --lob-accent-glow: rgba(88,101,247,0.25);
    --lob-accent-dim: rgba(88,101,247,0.12);
    --lob-btn-text: #ffffff;
    border-top: 1px solid rgba(88,101,247,0.12);
  }
  /* AP: accent updated to #C8A96A (AP gold); dark btn text for contrast */
  .biz-section--ap {
    background-color: #0B0A08;
    background-image:
      radial-gradient(ellipse 60% 55% at 75% 45%, rgba(200,169,106,0.18) 0%, transparent 55%);
    --lob-accent: #C8A96A;
    --lob-accent-glow: rgba(200,169,106,0.20);
    --lob-accent-dim: rgba(200,169,106,0.10);
    --lob-btn-text: #0A0A0A;
    border-top: 1px solid rgba(200,169,106,0.10);
  }
  /* CCA: terracotta retired; CCA teal (#4A8FA8) from CCA Design Style Guide.
     Dark btn text — #0A0A0A on #4A8FA8 ≈ 5.5:1 WCAG AA ✓ (Eleanor-mandated fix). */
  .biz-section--cca {
    background-color: #060C0E;
    background-image:
      radial-gradient(ellipse 75% 65% at 25% 55%, rgba(74,143,168,0.28) 0%, transparent 60%);
    --lob-accent: #4A8FA8;
    --lob-accent-glow: rgba(74,143,168,0.22);
    --lob-accent-dim: rgba(74,143,168,0.12);
    --lob-btn-text: #0A0A0A;
    border-top: 1px solid rgba(74,143,168,0.12);
  }
  /* PP: deeper cooler green (#4A8A60); dark btn text — #0A0A0A on #4A8A60 ≈ 5.09:1 WCAG AA ✓ */
  .biz-section--pp {
    background-color: #060C08;
    background-image:
      radial-gradient(ellipse 65% 55% at 70% 50%, rgba(74,138,96,0.25) 0%, transparent 55%);
    --lob-accent: #4A8A60;
    --lob-accent-glow: rgba(74,138,96,0.20);
    --lob-accent-dim: rgba(74,138,96,0.10);
    --lob-btn-text: #0A0A0A;
    border-top: 1px solid rgba(74,138,96,0.10);
  }

  /* Content container
     NOTE: horizontal padding comes from .container--wide (layout layer).
     Use padding-top/bottom here only — shorthand would override the horizontal
     padding from the earlier cascade layer. */
  .biz-content {
    position: relative;
    z-index: 2;
    padding-top: var(--section-pad-y);
    padding-bottom: var(--section-pad-y);
    width: 100%;
  }

  .biz-inner {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(48px, 6vw, 96px);
    align-items: center;
  }

  /* Right-aligned layout: content in second column */
  .biz-inner--right .biz-text { grid-column: 2; grid-row: 1; }
  .biz-inner--right .biz-visual { grid-column: 1; grid-row: 1; }

  @media (max-width: 767px) {
    .biz-inner,
    .biz-inner--right {
      grid-template-columns: 1fr;
    }
    .biz-inner--right .biz-text { grid-column: 1; grid-row: 1; }
    .biz-inner--right .biz-visual { grid-column: 1; grid-row: 2; }
  }

  .biz-eyebrow {
    font-size: var(--size-11);
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--lob-accent);
    margin-bottom: var(--sp-2);
    display: block;
    opacity: 0.9;
  }

  .biz-heading {
    font-family: var(--font-display);
    font-size: clamp(48px, 7vw, 88px);
    line-height: 1.0;
    color: var(--text-primary);
    margin-bottom: var(--sp-3);
    letter-spacing: 0.02em;
  }

  .biz-desc {
    font-size: clamp(15px, 1.5vw, 18px);
    line-height: 1.65;
    color: rgba(255,255,255,0.70);
    margin-bottom: var(--sp-5);
    max-width: 480px;
  }

  .biz-ctas {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
  }

  .biz-status {
    font-size: 12px;
    color: rgba(255,255,255,0.40);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  /* Visual slot — real imagery wired 2026-07-20.
     .biz-visual clips the img via aspect-ratio + overflow:hidden. */
  .biz-visual {
    aspect-ratio: 16 / 10;
    border-radius: 6px;
    overflow: hidden;
    position: relative;
  }

  .biz-visual-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  /* Accent line */
  .biz-accent-bar {
    display: block;
    width: 40px;
    height: 3px;
    background: var(--lob-accent);
    border-radius: 2px;
    margin-bottom: var(--sp-3);
    opacity: 0.8;
  }

  /* Reveal animation — toggled by IntersectionObserver in main.js */
  .reveal {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
  }
  .reveal.is-visible {
    opacity: 1;
    transform: translateY(0);
  }
  .reveal-delay-1 { transition-delay: 80ms; }
  .reveal-delay-2 { transition-delay: 160ms; }
  .reveal-delay-3 { transition-delay: 240ms; }
  .reveal-delay-4 { transition-delay: 320ms; }

  @media (prefers-reduced-motion: reduce) {
    .reveal, .reveal.is-visible {
      opacity: 1;
      transform: none;
      transition: none;
    }
  }
}

/* ============================================================
   FOOTER
   ============================================================ */
@layer components {
  .site-footer {
    background: var(--primitive-neutral-950);
    border-top: 1px solid var(--border-muted);
    padding: 48px 0 32px;
  }

  .footer-grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: var(--sp-8);
    margin-bottom: var(--sp-6);
  }

  /* Footer logo — replaces ACS text lockup (footer-brand-mark + footer-tagline removed 2026-07-20).
     height matches the desktop header logo (88px); 56px on mobile. */
  .footer-brand-link {
    display: inline-flex;
    text-decoration: none;
    transition: opacity var(--dur-fast) ease;
  }
  .footer-brand-link:hover {
    opacity: 0.8;
    text-decoration: none;
  }

  .footer-logo {
    height: 88px;
    width: auto;
  }

  @media (max-width: 767px) {
    .footer-logo { height: 56px; }
  }

  .footer-heading {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-secondary);
    margin-bottom: var(--sp-3);
  }

  .footer-links {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }

  .footer-link {
    font-size: 14px;
    color: var(--text-muted);
    text-decoration: none;
    transition: color var(--dur-fast) ease;
  }
  .footer-link:hover { color: var(--text-primary); text-decoration: none; }

  .footer-link--live::after {
    content: '↗';
    margin-left: 4px;
    font-size: 11px;
    opacity: 0.6;
  }

  .footer-bottom {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding-top: 24px;
    border-top: 1px solid var(--border-muted);
  }

  .footer-copy {
    font-size: 13px;
    color: var(--text-muted);
    margin: 0;
  }

  /* .footer-legal and .footer-legal-link retained for future use
     when Privacy Policy and Terms pages exist. */
  .footer-legal {
    display: flex;
    gap: 24px;
  }

  .footer-legal-link {
    font-size: 12px;
    color: var(--text-muted);
    text-decoration: none;
    transition: color var(--dur-fast) ease;
  }
  .footer-legal-link:hover { color: var(--text-secondary); text-decoration: none; }

  @media (max-width: 900px) {
    .footer-grid { grid-template-columns: 1fr 1fr; }
  }

  @media (max-width: 560px) {
    .footer-grid { grid-template-columns: 1fr; gap: var(--sp-5); }
    .footer-bottom {
      flex-direction: column;
      align-items: flex-start;
      gap: var(--sp-2);
    }
  }
}
