/* ============================================================
   518 Official — HOME (index.html) page layout
   css/pages/index.css?v=5 — loads AFTER tokens.css + site.css.
   Layout only: never restyles the shared shell, never overrides
   tokens, never declares :root. Motion hooks (data-hero /
   data-reveal / data-reveal-group / data-marquee / data-tilt /
   data-vslot) are NOT pre-hidden here — the no-JS floor stays
   fully readable.

   Palette law honored: cobalt (--rizz*) is surfaces, rules and
   accents only; every piece of running text is ivory or smoke.
   DROP AMBER (--drop*) appears ONLY inside 07 Apparel — the
   page's one commerce surface. Neutral rgba(10,10,10,…) scrims
   are neutrals, not brand color.

   FACE-SAFE CROP LAW: every image in an object-fit:cover slot
   carries its measured object-position (the .rizz-focal-* and
   .rizz-*-poster rules below). Flyers are NEVER cover-cropped —
   .rizz-feat-flyer and .rizz-night-frame use object-fit: contain.

   TOC:
   00 Page rhythm            05 Recent nights (real archive)
   01 Hero                   06 Media rail
   02 Territory marquee      07 Apparel · live store (amber)
   03 What RIZZ is + rail    08 Services lanes
   04 Featured night         09 Closing CTA
   10 Roster relay band      11 Announce dialog
   ============================================================ */

/* ---------- 00 Page rhythm ----------
   Home breathes wider than the interior pages: the opening frame
   of the brand, not a brochure grid. */
.rizz-air { padding-block: clamp(4.75rem, 9.5vw, 9rem); }

/* Section index mark — sits inside the shared .eyebrow as a flex
   item; the shell rule is never redefined, only this child. */
.s-idx {
  font-family: var(--font-logo);
  font-size: 1.05em;
  line-height: 1;
  letter-spacing: 0.1em;
  color: var(--ivory);
}

/* Archive tag — a factual label on a real, past night (it replaced
   the old dashed DEMO badge: nothing on this page is demo any more).
   Solid hairline, no brand color spent on it. */
.rizz-tag {
  display: inline-block;
  padding: 0.3rem 0.65rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-80);
}

/* Inline link inside running copy — cobalt COPY is --rizz-text only. */
.rizz-link {
  color: var(--rizz-text);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

.rizz-link:hover { color: var(--ivory); }

/* ---------- 01 Hero ---------- */
.rizz-hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  padding-top: calc(var(--header-h) + var(--sp-6));
}

/* ---------- Hero rotator ----------
   Five silent clips crossfade behind the headline. Every state is a
   class: production CSP is style-src 'self' with no 'unsafe-inline',
   so an inline style attribute would simply be discarded.

   The poster is the floor. Each slide ships a real <img>; the <video>
   has no source until the rotator attaches one, so with JS off — or on
   a slow link, or if a clip 404s — the hero is a finished still, not a
   black box. */
.rizz-hero-stage { position: absolute; inset: 0; z-index: var(--z-bg); overflow: hidden; }

.rizz-hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease);
}

.rizz-hero-slide.is-live { opacity: 1; }

.rizz-hero-slide video,
.rizz-hero-slide .rizz-hero-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* FACE-SAFE CROP LAW, per slide. These are full-bleed cover slots, so a
   guessed centre decapitates people at some viewport. Every value below
   was MEASURED off that slide's own poster with the house Vision tool,
   not chosen by eye:
     1 street  0 faces (subject walks away)      -> centre is safe
     2 green   1 face  -> 45.4% 27.1%
     3 stage   4 faces -> 64.3% 26.4%
     4 dj      3 faces -> 39.7% 15.2%
     5 garage  0 faces here, but the frame it came from measured 67.4% 36%
   Re-measure if any clip is ever recut. */
.rizz-hero-slide:nth-child(1) video,
.rizz-hero-slide:nth-child(1) .rizz-hero-poster { object-position: 50% 50%; }
.rizz-hero-slide:nth-child(2) video,
.rizz-hero-slide:nth-child(2) .rizz-hero-poster { object-position: 45.4% 27.1%; }
.rizz-hero-slide:nth-child(3) video,
.rizz-hero-slide:nth-child(3) .rizz-hero-poster { object-position: 64.3% 26.4%; }
.rizz-hero-slide:nth-child(4) video,
.rizz-hero-slide:nth-child(4) .rizz-hero-poster { object-position: 39.7% 15.2%; }
.rizz-hero-slide:nth-child(5) video,
.rizz-hero-slide:nth-child(5) .rizz-hero-poster { object-position: 67.4% 36%; }

/* The clip sits over its own poster once it can actually play, so a
   refused autoplay leaves the still rather than a blank frame. */
.rizz-hero-slide video { z-index: 1; }

/* REDUCED MOTION: the rotator never starts and holds the first still.
   Belt and braces — kill the crossfade here too, so nothing animates
   even if a slide class changes for any other reason. */
@media (prefers-reduced-motion: reduce) {
  .rizz-hero-slide { transition: none; }
}

/* Grade: bottom lift for copy legibility, a left wall so the type
   has ground, and one controlled cobalt bloom off the light side.
   The dark scrims are NEUTRALS (rgba 10,10,10) — allowed. */
.rizz-grade {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  pointer-events: none;
  /* OPENED UP 2026-08-18. The previous grade muted the whole plate (0.80 at
     26%, 0.44 at 58%, plus a 0.92 left wall) because the headline was ~139px
     of Archivo Black and needed that much cover. The headline is now less
     than half that size and capped at 18ch, so the scrim only has to protect
     the bottom-left corner it actually sits in — the rest of the film is the
     point of the page and is left alone.
     Contrast is still measured, not assumed: see the check in the commit. */
  background:
    linear-gradient(to top,
      rgba(10, 10, 10, 0.94) 0%,
      rgba(10, 10, 10, 0.74) 20%,
      rgba(10, 10, 10, 0.30) 42%,
      rgba(10, 10, 10, 0.06) 66%,
      rgba(10, 10, 10, 0.22) 100%),
    linear-gradient(100deg,
      rgba(10, 10, 10, 0.74) 0%,
      rgba(10, 10, 10, 0.30) 34%,
      rgba(10, 10, 10, 0) 64%),
    radial-gradient(115% 78% at 84% 6%,
      color-mix(in srgb, var(--rizz) 14%, transparent), transparent 58%);
}

/* Fine scanline texture — barely there, routed through the token. */
.rizz-grain {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  pointer-events: none;
  opacity: 0.45;
  background-image: repeating-linear-gradient(
    0deg,
    color-mix(in srgb, var(--ivory) 3%, transparent) 0 1px,
    rgba(0, 0, 0, 0) 1px 3px
  );
}

.rizz-hero-inner {
  position: relative;
  z-index: var(--z-content);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* TRIMMED 2026-08-19. Owner: "its not bad on the header just minimize it a
     tiny bit" — the copy block, not the structure. Rhythm only: the stack gap
     stepped --sp-5 -> --sp-4 and the top pad --sp-8 -> --sp-7. Both give the
     film back height ABOVE the copy (the block is bottom-anchored, so the
     bottom pad is left alone and the type keeps its ground). Measured at
     1440x900 with the ceiling trims below: block 586.7px -> 519.0px
     (-11.5%), 65.2% -> 57.7% of the hero. 360 and 390 lose only rhythm,
     no type: 776.7 -> 712.7 and 750.8 -> 686.8, both strictly better. */
  gap: var(--sp-4);
  padding-block: var(--sp-7) clamp(1.75rem, 5vh, 3rem);
}

.hero-kicker {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: clamp(0.9rem, 2vw, 1.1rem);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ivory-80);
}

.hero-kicker::before {
  content: "";
  display: block;
  width: clamp(58px, 9vw, 112px);
  height: 3px;
  background: var(--rizz);
  margin-bottom: var(--sp-3);
}

/* Page-scoped step down from --fs-hero: Archivo Black runs wide and
   both poster lines are 13 characters — this ceiling keeps each line
   one line from the 320px floor to the 1280px max.

   CINDY 2026-08-17 — the floor moved 2.25rem → 1.9rem, measured, because
   the old floor was the thing that broke the poster:
     320px  9.7vw = 31.0px, so the FLOOR bound at 36px and "The Night Has"
            measured 221px of ink in a 280px box only by wrapping to TWO
            lines — four lines of h1 instead of two.
     360px  the floor bound again at 36px: ink 315px in a 315px box. Zero
            margin — one metric change from wrapping.
   At 1.9rem the floor stops binding above ~313px, so 360 now renders at
   34.9px (ink 305 in 315 — real margin) and 320 renders at 30.4px on one
   line. 390px and up are untouched: 9.7vw already clears the new floor,
   so every measured width from 390 to 1280 is byte-identical to before. */
.rizz-h1 {
  /* LOWERED 2026-08-18. Was clamp(1.9rem, 9.7vw, 8.25rem) — at 1440 that
     is ~139px of Archivo Black across two lines, which buried the film it
     sits on. The film is the main event on this page; the headline names
     it and gets out of the way.

     TRIMMED AGAIN 2026-08-19, ceiling only: 3.9rem -> 3.5rem (62.4px ->
     56px at 1440). The 1.75rem floor and the 5.2vw slope are untouched, so
     every width up to ~1077px renders byte-identical — 360, 390, 768 and
     1024 all still measure exactly what they measured before. Only the
     widths that were pinned to the ceiling move. The MATCHING CEILING IN
     THE SHORT-VIEWPORT OVERRIDE BELOW MOVED WITH IT; changing one and not
     the other is what defeated an earlier attempt. */
  font-size: clamp(1.75rem, 5.2vw, 3.5rem);
  max-width: 18ch;
}

.rizz-h1 .hero-line { display: block; }

.hero-lead {
  /* Page-scoped step down from --fs-lead — ceiling AND slope, which is why
     this comment does not reuse the h1's wording. The token is
     clamp(1.0625rem, 1.4vw, 1.3rem). The 1.0625rem floor is untouched, so
     360, 390, 768 and 1024 all still compute 17px and small screens do not
     get worse. But the slope moves too (1.4vw -> 1.25vw), so unlike
     .rizz-h1 above — where the floor and the 5.2vw slope really are
     untouched and only the ceiling drops — the lead steps down through
     roughly 1215-1360px as well, not just at the wide end (1440:
     20.16px -> 18px). Measure stays 48ch: widening it to buy back a line
     would spread the copy across the film, which is the thing being fixed. */
  font-size: clamp(1.0625rem, 1.25vw, 1.125rem);
  color: var(--ivory-80);
  max-width: 48ch;
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-top: var(--sp-2);
}

/* The proof line — three checkable facts, no adjectives.
   PHOTOGRAPHY FLOOR (JUAN, measured 2026-08-17): --smoke is a 5.09:1 token
   on flat --bg, but this line sits on the brand film at 12.8px and measured
   3.03:1 at 390 / 4.52:1 at 1440 against the lit pixels under its own
   glyphs — under AA on a phone. It was also the one line in the hero
   quieter than the .hero-meta row beneath it, which was already --ivory-60.
   At --ivory-60 it measures 5.41:1 at 390.
   OVER PHOTOGRAPHY THE TERTIARY FLOOR IS --ivory-60, NEVER --smoke. */
.hero-note {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: clamp(0.8rem, 1.7vw, 0.9rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ivory-60);
}

.hero-meta {
  width: 100%;
  margin-top: var(--sp-3);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line-strong);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-6);
}

.hero-meta li {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-60);
}

/* Scroll cue — desktop only, 44px+ target, keyboard reachable. */
.rizz-cue {
  display: none;
  position: absolute;
  right: var(--gutter);
  bottom: clamp(1.75rem, 5vh, 3rem);
  z-index: var(--z-content);
  width: 44px;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding-block: var(--sp-2);
  text-decoration: none;
  /* PHOTOGRAPHY FLOOR (JUAN 2026-08-17): 13px --smoke over the brand film
     is the same under-AA case as .hero-note above. --ivory-60 over
     photography, --smoke only over flat ground. */
  color: var(--ivory-60);
  transition: color var(--dur-1) var(--ease);
}

.rizz-cue:hover { color: var(--ivory); }

.rizz-cue-text {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  writing-mode: vertical-rl;
}

.rizz-cue-line {
  position: relative;
  width: 1px;
  height: 62px;
  background: var(--line-strong);
  overflow: hidden;
}

.rizz-cue-line::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 26px;
  background: var(--rizz-hot);
  animation: rizz-cue-drop 2.4s var(--ease-io) infinite;
}

@keyframes rizz-cue-drop {
  0%   { transform: translateY(-100%); }
  60%  { transform: translateY(240%); }
  100% { transform: translateY(240%); }
}

@media (min-width: 1080px) {
  .rizz-cue { display: flex; }
}

/* Short desktop viewports: keep the primary CTA above the fold. */
@media (min-width: 980px) and (max-height: 940px) {
  .rizz-hero-inner { gap: var(--sp-3); }

  /* CINDY 2026-08-17 — `min(13vh, 9.7vw)` replaced a bare `13vh`. The
     short-viewport override sized the poster from HEIGHT alone, so on a
     laptop that is short AND narrow it ran wider than the column and the
     two 13-character lines broke in half. Measured at 900px tall:
        980 ×900   112px type, 981px of ink in a 900px container → 2 lines
       1024 ×900   112px type, 981px of ink in a 944px container → 2 lines
       1280 ×900   fits (1200px container) — and still fits after
     The `min()` simply lets the base rule's own 9.7vw width guard win
     whenever width is the tighter constraint; every viewport where 13vh
     was already the smaller number renders exactly as before. */
  /* LOWERED 2026-08-18 with the base rule. This override exists so a short
     laptop viewport does not push the poster off-screen — it still does
     that, just at the new scale. Sizing off 13vh was what produced ~106px
     of Archivo Black at 1440x900 and buried the film. */
  /* Ceiling stepped 3.9rem -> 3.5rem in lockstep with the base rule above. */
  .rizz-h1 { font-size: clamp(1.75rem, min(7.5vh, 5.2vw), 3.5rem); }

  .hero-kicker::before { margin-bottom: var(--sp-2); }

  .hero-meta {
    margin-top: var(--sp-2);
    padding-top: var(--sp-3);
  }
}

/* ---------- 02 Territory marquee ----------
   No-JS floor: a centered, wrapping word wall. motion.js may
   convert [data-marquee] into a seamless horizontal loop. */
.rizz-marq {
  padding-block: clamp(2.5rem, 5vw, 4rem);
  overflow: hidden;
}

.rizz-marq-wrap { margin-inline: auto; }

.rizz-marq-label {
  text-align: center;
  color: var(--smoke);
  margin-bottom: var(--sp-5);
}

.rizz-marq-track {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.7rem 0;
}

.rizz-marq-track li {
  font-family: var(--font-logo);
  font-size: clamp(1.15rem, 2.3vw, 1.7rem);
  line-height: 1.15;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ivory-60);
  white-space: nowrap;
}

/* Quiet archival dot — cobalt stays rare (decorative, no contrast
   concern). */
.rizz-marq-track .rizz-sep {
  color: var(--smoke);
  font-size: 0.45em;
  display: inline-block;
  vertical-align: 0.45em;
  margin-inline: clamp(1.1rem, 2.6vw, 2.4rem);
}

/* ---------- 03 What RIZZ is + the counted rail ---------- */
.rizz-what-grid {
  display: grid;
  gap: var(--sp-6);
}

@media (min-width: 980px) {
  .rizz-what-grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    column-gap: clamp(2.5rem, 6vw, 6rem);
    align-items: start;
  }
}

.rizz-what-head { margin-bottom: 0; }

.rizz-what-title { font-size: var(--fs-display); }

.rizz-what-copy {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.rizz-what-body {
  color: var(--ivory-80);
  max-width: 58ch;
}

/* The counted rail — every numeral is countable from a source file:
   the area code, three flyer-verified nights, eleven live store
   pieces, four lanes. Nothing estimated, nothing about attendance. */
.rizz-rail {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-top: clamp(2rem, 4vw, 3rem);
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-7) var(--sp-6);
}

.rizz-rail .stat-label { max-width: 22ch; }

/* The rail's own honesty line — where the numbers come from and what
   drifts. Sits under the rail, quiet. */
.rizz-rail-note {
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
  color: var(--ivory-60);
  font-size: var(--fs-small);
  max-width: 68ch;
}

@media (min-width: 980px) {
  .rizz-rail {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--sp-7) 0;
  }

  .rizz-rail .stat {
    padding-inline: clamp(1rem, 2.6vw, 2rem);
    border-left: 1px solid var(--line-strong);
  }

  .rizz-rail .stat:first-child {
    padding-left: 0;
    border-left: 0;
  }
}

/* ---------- 04 Featured night ----------
   The archive moment — the ONE .haze on this page. Cobalt is wash,
   rule and surface only; every running line is ivory or smoke. */
.rizz-feat { border-top: 1px solid var(--line); }

/* Extend .haze rather than setting a background: a section background
   paints over .haze::before (z-index --z-bg) and kills the wash. The
   ivory lift ends on a fixed length so the arrival reads identically
   at every band height. */
.rizz-feat.haze::before {
  background:
    radial-gradient(75% 60% at 18% 0%, var(--rizz-haze), transparent 68%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ivory) 4%, transparent) 0, transparent 220px);
}

/* Framed artifact — archival frame ground, darker than the panels it
   holds (the .flyer-card law), whole card one tap target. */
.rizz-feat-card {
  position: relative;
  display: grid;
  gap: var(--sp-5);
  padding: clamp(1rem, 2.6vw, 1.75rem);
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-2), inset 0 1px 0 color-mix(in srgb, var(--ivory) 6%, transparent);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease);
}

.rizz-feat-card:hover {
  border-color: var(--line-rizz);
  box-shadow: var(--glow-rizz-soft), var(--shadow-2);
}

.rizz-feat-card .rizz-tag { justify-self: start; }

@media (min-width: 900px) {
  .rizz-feat-card {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: clamp(1.5rem, 4vw, 3rem);
    align-items: center;
  }

  .rizz-feat-card .rizz-tag {
    position: absolute;
    top: clamp(1rem, 2.6vw, 1.75rem);
    right: clamp(1rem, 2.6vw, 1.75rem);
    z-index: var(--z-raise);
  }
}

/* REAL FLYER — the crew's own artwork, shown whole.
   FLYERS ARE NEVER COVER-CROPPED: object-fit is contain and the
   height cap only scales, it never crops. Cropping a flyer destroys
   the information it exists to carry.

   CINDY 2026-08-17 — the FRAME now shrink-wraps the artwork. It used to
   be `width: 100%` with the picture letterboxed inside by object-fit,
   which meant the mats grew with the column while the art stayed pinned
   to the 62vh height cap. Measured empty gradient per side, at 900px tall:
     390px    0px   (the width cap bound — art filled the frame)
     600px   57px
     700px  100px
     820px  152px   ← worst: a 700px-wide bordered panel holding 372px of
                      flyer, reading as a broken frame, not a mat
    1440px   45px   (the only width where it read as a designed mat)
   The frame is now sized to the artwork instead: its width is whichever
   is smaller of the column and "the height cap, converted through the
   flyer's own aspect ratio". The picture then always fills that width at
   height:auto, so the mat is a constant 12px of padding on all four sides
   at every viewport — no horizontal bars when the height cap binds, no
   vertical bars when the column binds. `object-fit: contain` stays as the
   backstop that makes a crop impossible here no matter what else changes.

   --flyer-ar is the intrinsic ratio of the artwork this frame holds; it
   is the img's own width/height attributes (800 × 1200) expressed as one
   number, and it moves if the featured flyer is ever swapped. */
.rizz-feat-flyer {
  --flyer-cap: clamp(340px, 62vh, 660px);
  --flyer-ar: calc(800 / 1200);
  display: block;
  width: min(100%, calc(var(--flyer-cap) * var(--flyer-ar) + var(--sp-3) * 2 + 2px));
  margin-inline: auto;
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background:
    linear-gradient(165deg, var(--panel) 0%, var(--bg-2) 70%);
}

.rizz-feat-flyer picture { display: block; }

.rizz-feat-flyer img {
  display: block;
  width: 100%;
  height: auto;
  max-height: var(--flyer-cap);
  object-fit: contain;
  border-radius: var(--r-xs);
}

.rizz-feat-label {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--rizz-text);
  margin-bottom: var(--sp-3);
}

.rizz-feat-title {
  font-size: clamp(1.7rem, 3.6vw, 2.7rem);
  margin-bottom: var(--sp-5);
}

/* Stretched link — whole frame is the tap target (house Click
   Protocol). The card is position:relative; z-index 5 clears every
   non-interactive layer inside. */
.rizz-feat-link {
  color: var(--ivory);
  text-decoration: none;
}

.rizz-feat-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
}

.rizz-feat-link:focus-visible { outline: none; }

.rizz-feat-card:has(.rizz-feat-link:focus-visible) {
  outline: 2px solid var(--ivory);
  outline-offset: 3px;
}

.rizz-facts { border-top: 1px solid var(--line); }

/* CINDY 2026-08-17 — the fact list stacks on a phone. The two-column form
   spent 6.5rem (104px) of a 310px card on a one-word term, leaving 190px
   for the value: "Daytime BBQ · party 9PM · performance 12AM · fireworks
   viewing from Centre St Pub" broke to FOUR ragged lines, and the venue
   line to three. Stacked, the same values get the full 310px and run two
   lines. The side-by-side reading returns at 560px, where the value
   column is wide enough to be worth the trade. */
.rizz-fact {
  display: grid;
  gap: var(--sp-2);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--line);
}

@media (min-width: 560px) {
  .rizz-fact {
    grid-template-columns: 6.5rem 1fr;
    gap: var(--sp-4);
  }
}

.rizz-fact dt {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--smoke);
  padding-top: 0.22rem;
}

.rizz-fact dd {
  margin: 0;
  font-size: var(--fs-body);
  color: var(--ivory);
}

.rizz-feat-note {
  margin-top: var(--sp-4);
  color: var(--ivory-60);
  font-size: var(--fs-small);
  max-width: 52ch;
}

.rizz-feat-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
  min-height: 44px;
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--rizz-text);
}

.rizz-feat-cta span { transition: transform var(--dur-2) var(--ease); }

.rizz-feat-card:hover .rizz-feat-cta span { transform: translateX(5px); }

/* ---------- 05 Recent nights (the real archive strip) ---------- */
.rizz-nights {
  display: grid;
  gap: var(--sp-5);
}

@media (min-width: 700px) {
  .rizz-nights { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .rizz-nights { grid-template-columns: repeat(3, 1fr); }
}

.rizz-night {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  height: 100%;
  padding: var(--sp-4);
  background: var(--flyer-ground);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--ivory) 5%, transparent);
  transition:
    border-color var(--dur-1) var(--ease),
    transform var(--dur-2) var(--ease),
    box-shadow var(--dur-2) var(--ease);
}

.rizz-night:hover {
  border-color: var(--line-rizz);
  transform: translateY(-3px);
  box-shadow: var(--shadow-1);
}

/* Flyer frame — CONTAIN, never cover. Fixed aspect so the three cards
   line up; the letterboxing is deliberate, the crop would be a lie. */
.rizz-night-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 4;
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: linear-gradient(165deg, var(--panel) 0%, var(--bg-2) 72%);
  overflow: hidden;
}

.rizz-night-frame picture {
  display: block;
  width: 100%;
  height: 100%;
}

.rizz-night-frame img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* No flyer still on file for one night — a designed type panel, so the
   card never borrows another night's artwork. */
.rizz-night-frame--type {
  flex-direction: column;
  gap: var(--sp-3);
  text-align: center;
  background:
    repeating-linear-gradient(0deg,
      color-mix(in srgb, var(--ivory) 2%, transparent) 0 1px,
      rgba(0, 0, 0, 0) 1px 4px),
    radial-gradient(120% 90% at 50% 112%,
      color-mix(in srgb, var(--rizz) 26%, transparent), transparent 62%),
    linear-gradient(165deg, var(--panel) 0%, var(--bg-2) 70%);
}

.rizz-night-type-kicker,
.rizz-night-type-date {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ivory-60);
}

.rizz-night-type-name {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 4.4vw, 2.4rem);
  line-height: 1.02;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ivory) 88%, transparent);
}

.rizz-night-date {
  margin-top: var(--sp-2);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--rizz-text);
}

.rizz-night-title { font-size: clamp(1.3rem, 2.4vw, 1.7rem); }

.rizz-night-link {
  color: var(--ivory);
  text-decoration: none;
}

.rizz-night-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
}

.rizz-night-link:focus-visible { outline: none; }

.rizz-night:has(.rizz-night-link:focus-visible) {
  outline: 2px solid var(--ivory);
  outline-offset: 3px;
}

.rizz-night-venue {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--smoke);
}

.rizz-night-copy {
  color: var(--ivory-60);
  font-size: var(--fs-small);
  max-width: 52ch;
}

/* Source honesty line — why this card has no flyer image. */
.rizz-night-src {
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line);
  color: var(--smoke);
  font-size: var(--fs-small);
}

.rizz-night-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: auto;
  padding-top: var(--sp-3);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--rizz-text);
}

.rizz-night-cta span { transition: transform var(--dur-2) var(--ease); }

.rizz-night:hover .rizz-night-cta span { transform: translateX(5px); }

.rizz-nights-foot {
  margin-top: var(--sp-6);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-5);
}

/* The next date is NOT announced — this line is the whole forward
   promise the data model permits. No month, no venue, no CTA. */
.rizz-nights-next {
  color: var(--ivory-60);
  font-size: var(--fs-small);
  max-width: 56ch;
}

/* ---------- 06 Media rail ----------
   Real 518 footage only. The recap HAS AUDIO, so it is a poster-only
   link into media.html — it never autoplays and is never a backdrop. */
.rizz-media-grid {
  display: grid;
  gap: var(--sp-5);
}

.rizz-media-tile {
  display: block;
  text-decoration: none;
}

.rizz-media-frame {
  display: block;
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease);
}

.rizz-media-frame--still img,
.rizz-media-frame .vslot-poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-3) var(--ease);
}

/* FACE-SAFE: promo-518-b poster measures 51.8% 18.7%. The recap and
   club posters are crop-safe (50% 50% — the browser default).
   The <video> carries the identical focal: today the tile runs at the
   clip's native 16:9 so nothing crops, but the moment a breakpoint gives
   this frame any other aspect, a poster-only focal means the framing
   JUMPS the instant the film fades in over it. Poster and playing frame
   are one picture or they are a glitch. */
.rizz-media-poster-b,
.rizz-media-tile--lead .vslot video { object-position: 51.8% 18.7%; }

.rizz-media-tile:hover .rizz-media-frame,
.rizz-media-tile:focus-visible .rizz-media-frame {
  border-color: var(--line-rizz);
  box-shadow: var(--glow-rizz-soft);
}

.rizz-media-tile:hover .rizz-media-frame--still img,
.rizz-media-tile:hover .vslot-poster { transform: scale(1.03); }

/* Play glyph — this tile is a content video with sound, so it reads as
   something you press, not something already running. The caption text
   ("Video · sound on") carries the same information for AT. */
.rizz-media-play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  padding-left: 4px;
  border-radius: var(--r-pill);
  background: rgba(10, 10, 10, 0.62);
  border: 1px solid var(--line-strong);
  color: var(--ivory);
  font-size: 1.15rem;
  line-height: 1;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.rizz-media-tile:hover .rizz-media-play {
  background: rgba(10, 10, 10, 0.78);
  border-color: var(--line-rizz);
}

/* The vertical clip keeps a portrait box on small screens. */
.rizz-media-tile--tall .rizz-media-frame { aspect-ratio: 3 / 4; }

@media (min-width: 900px) {
  .rizz-media-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    align-items: stretch;
  }

  .rizz-media-tile--lead { grid-column: 1; grid-row: 1; }
  .rizz-media-tile--sub  { grid-column: 1; grid-row: 2; }

  /* The vertical clip spans both rows and fills the column height —
     aspect-ratio hands over to the grid, cover keeps it centered. */
  .rizz-media-tile--tall {
    grid-column: 2;
    grid-row: 1 / span 2;
    display: flex;
    flex-direction: column;
  }

  .rizz-media-tile--tall .rizz-media-frame {
    flex: 1 1 auto;
    aspect-ratio: auto;
    min-height: 0;
  }
}

.rizz-media-cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
  padding-block: var(--sp-3);
  min-height: 44px;
}

.rizz-media-kind {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--rizz-text);
}

.rizz-media-name {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ivory-80);
}

/* The honesty caption — whose footage this is, and what it does NOT
   claim (no clip is attributed to a named night). */
.rizz-media-note {
  margin-top: var(--sp-4);
  color: var(--ivory-60);
  font-size: var(--fs-small);
  max-width: 64ch;
}

.rizz-media-foot { margin-top: var(--sp-6); }

/* ---------- 07 Apparel · the live 51-8 store ----------
   THE commerce surface — the only place amber exists on this page.
   Text ON amber is --bg (near-black); amber copy on dark grounds is
   --drop-text. Never ivory on amber (1.99:1 — fails). */
.rizz-drop-flag {
  display: inline-block;
  padding: 0.35rem 0.7rem;
  margin-bottom: var(--sp-4);
  background: var(--drop);
  border-radius: var(--r-xs);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--bg);
}

/* Lookbook frames — a SHOOT, not packshots. No price slab, no
   "add to bag", no per-SKU claim anywhere on this page. */
/* CINDY 2026-08-17 — the lookbook now steps 1 → 2 → 3 on the SAME
   breakpoints as .rizz-nights above it (700 / 1024). It used to jump
   straight to three columns at 700px, and that one step made the page's
   only real product photography its smallest pictures. Measured frame
   widths, before → after:
     600px   546 → 546   (unchanged, one column)
     700px   196 → 310   — against 273px flyer thumbnails in the SAME
                           column count directly above it
     768px   217 → 340   — measured beside 303px night flyers: the
                           lookbook was losing to the archive strip
     820px   233 → 364
     900px   257 → 400
    1024px   299 → 299   (unchanged, three columns from here up)
   A 546 → 196 collapse across a single pixel of viewport was the harshest
   transition on the page; the matched rhythm's worst step is 450 → 299.
   Three items in two columns leave the third alone on row two — that is
   the same 2+1 the nights grid runs at these widths, so the two sections
   now read as one system instead of two. */
.rizz-shots {
  display: grid;
  gap: var(--sp-5);
}

@media (min-width: 700px) {
  .rizz-shots { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .rizz-shots { grid-template-columns: repeat(3, 1fr); }
}

.rizz-shot { height: 100%; }

.rizz-shot-frame {
  display: block;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel);
}

.rizz-shot-frame picture {
  display: block;
  width: 100%;
  height: 100%;
}

.rizz-shot-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* FACE-SAFE crop law — measured focal points, binding.
   apparel-garage-04 is a back-shot: crop-safe, no rule needed. */
.rizz-focal-garage-01  { object-position: 45.1% 19.9%; }
.rizz-focal-capitol-01 { object-position: 57.3% 20.2%; }

.rizz-shot-cap {
  display: block;
  padding-top: var(--sp-3);
  font-family: var(--font-label);
  font-weight: 500;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-60);
}

/* Commerce honesty — prices drift and this site has no cart. */
.rizz-store-note {
  margin-top: var(--sp-6);
  color: var(--ivory-60);
  font-size: var(--fs-small);
  max-width: 64ch;
}

.rizz-drop-foot {
  margin-top: var(--sp-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}

/* Amber commerce CTA — values via tokens only. Hover fill steps to
   the lighter amber token; text stays --bg both ways (8.6:1+). */
.rizz-btn-drop {
  background: var(--drop);
  color: var(--bg);
}

.rizz-btn-drop:hover {
  background: var(--drop-text);
  color: var(--bg);
  box-shadow: 0 0 28px color-mix(in srgb, var(--drop) 30%, transparent);
  transform: translateY(-1px);
}

/* ---------- 08 Services lanes ---------- */
.rizz-lanes-head-main { margin-bottom: var(--sp-5); }

.rizz-lanes-title { font-size: var(--fs-display); }

.rizz-lanes-lead {
  font-size: var(--fs-lead);
  color: var(--ivory-60);
  max-width: 46ch;
}

@media (min-width: 980px) {
  .rizz-lanes-head {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    column-gap: clamp(2.5rem, 6vw, 6rem);
    align-items: end;
  }

  .rizz-lanes-head-main { margin-bottom: 0; }

  .rizz-lanes-lead {
    margin-bottom: 0.7rem;
    max-width: 38ch;
  }
}

.rizz-lanes {
  border-top: 1px solid var(--line);
  margin-top: clamp(2rem, 5vw, 3.5rem);
}

.rizz-lanes > li { border-bottom: 1px solid var(--line); }

/* CINDY 2026-08-17 — the lane row stacks its arrow on a phone.
   The three-column form (num | body | arrow) held its shape all the way
   down, and at 390px the arrow column plus two gutters took 160px of a
   350px row: the body ran at 190px, so "Event Production" and
   "Apparel & Merch" both broke across two lines and every lane paragraph
   ran four lines at 30 characters. Dropping the arrow to its own cell
   under the copy hands the body 310px — the titles come back to one line
   at 390 and the copy reads at ~48 characters. The arrow keeps its
   translate on hover either way; it is decorative (aria-hidden), so its
   position carries no meaning to move. */
.rizz-lane {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  padding: clamp(1.25rem, 3vw, 2rem) var(--sp-3);
  text-decoration: none;
  transition:
    background var(--dur-1) var(--ease),
    box-shadow var(--dur-2) var(--ease),
    padding-left var(--dur-2) var(--ease);
}

@media (min-width: 560px) {
  .rizz-lane {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--sp-4) var(--sp-5);
  }
}

.rizz-lane:hover,
.rizz-lane:focus-visible {
  background: var(--panel);
  box-shadow: inset 3px 0 0 var(--rizz);
  padding-left: var(--sp-5);
}

.rizz-lane-num {
  align-self: start;
  padding-top: 0.35em;
  font-family: var(--font-logo);
  font-size: clamp(1.15rem, 2vw, 1.5rem);
  line-height: 1;
  color: var(--smoke);
  min-width: 2ch;
}

.rizz-lane-body { display: block; }

.rizz-lane-title { font-size: clamp(1.5rem, 3vw, 2.3rem); }

.rizz-lane-copy {
  margin-top: 0.45rem;
  max-width: 56ch;
  color: var(--ivory-60);
  font-size: var(--fs-small);
}

.rizz-lane-arrow {
  grid-column: 2;
  justify-self: end;
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  line-height: 1;
  color: var(--smoke);
  transition: transform var(--dur-2) var(--ease), color var(--dur-1) var(--ease);
}

@media (min-width: 560px) {
  .rizz-lane-arrow {
    grid-column: 3;
    justify-self: auto;
  }
}

.rizz-lane:hover .rizz-lane-arrow,
.rizz-lane:focus-visible .rizz-lane-arrow {
  transform: translateX(6px);
  color: var(--ivory);
}

/* ---------- 09 Closing CTA ----------
   The page's one .haze is spent on the featured night, so the closing
   lift is an ivory bloom — token-routed, no second cobalt wash. */
.rizz-closing {
  padding-block: clamp(5.5rem, 11vw, 10rem);
  background-image: radial-gradient(70% 55% at 50% 42%,
    color-mix(in srgb, var(--ivory) 6%, transparent) 0%,
    transparent 70%);
}

.rizz-close { text-align: center; }

.rizz-close-kicker {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 0.875rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ivory-80);
  margin-bottom: var(--sp-5);
}

.rizz-close-kicker::before {
  content: "";
  display: block;
  width: 26px;
  height: 2px;
  background: var(--rizz-line);
  margin: 0 auto var(--sp-4);
}

.rizz-close-title {
  font-size: var(--fs-display);
  max-width: 16ch;
  margin-inline: auto;
}

.rizz-close-lead {
  margin-top: var(--sp-5);
  font-size: var(--fs-lead);
  color: var(--ivory-80);
  max-width: 44ch;
  margin-inline: auto;
}

.rizz-close-ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-7);
}

.rizz-close-area {
  margin-top: var(--sp-6);
  font-family: var(--font-label);
  font-weight: 500;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--smoke);
}

/* ---------- 10 Roster relay band + shared relay controls ----------
   .roster-relay scopes the form-control idiom (borrowed measure-for-
   measure from book.css §04 — same fills, same focus ring, same AA
   math) to the two roster forms on this page: the announce dialog's
   and this band's. index.html does not load book.css, so these
   controls are declared here, SCOPED, never as bare .f-input — the
   book page stays the owner of the global names. */
.roster-relay .f-field { display: grid; gap: var(--sp-3); }

.roster-relay .f-label {
  display: block;
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-80);
}

.roster-relay .f-label .req { color: var(--ivory-60); }

.roster-relay .f-input {
  width: 100%;
  min-height: 48px;
  padding: 0.8rem 1rem;
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  color: var(--ivory);
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.roster-relay .f-input:hover {
  border-color: color-mix(in srgb, var(--ivory) 40%, transparent);
}

.roster-relay .f-input:focus { border-color: var(--ivory); outline: none; }

.roster-relay .f-input:focus-visible {
  outline: 2px solid var(--ivory);
  outline-offset: 2px;
}

.roster-relay .f-input::placeholder {
  color: color-mix(in srgb, var(--ivory) 50%, transparent);
}

/* Error = words + border, never color alone (book.css law; cobalt is
   the house attention color — no red exists in this palette). */
.roster-relay .f-field.is-invalid .f-input { border-color: var(--rizz-hot); }

.roster-relay .f-err {
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--rizz-text);
}

/* The honest line is REQUIRED copy, not fine print: 15px smoke holds
   AA (5.09:1 on --bg, 4.81:1 on --panel — tokens.css measurements). */
.roster-honest {
  margin-top: var(--sp-4);
  font-size: var(--fs-small);
  color: var(--smoke);
  max-width: 52ch;
}

.roster-status {
  margin-top: var(--sp-3);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--ivory-80);
}

.roster-status:empty { display: none; }

/* [hidden] must keep winning: an author display value overrides the UA
   [hidden]{display:none} rule, so restate it (the link only appears
   after announce.js composes a draft). */
.roster-again[hidden] { display: none; }

.roster-again {
  display: inline-block;
  margin-top: var(--sp-2);
  font-size: var(--fs-small);
  color: var(--rizz-text);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.roster-again:hover { color: var(--ivory); }

/* The band itself — narrow, quiet, one job. */
.rizz-roster .sect-head { margin-bottom: var(--sp-6); }

.rizz-roster-row {
  display: grid;
  gap: var(--sp-4);
  align-items: end;
}

.rizz-roster-btn { white-space: nowrap; }

@media (min-width: 640px) {
  .rizz-roster-row { grid-template-columns: minmax(0, 1fr) auto; }
  /* keep the 48px input and 48px+ button on one baseline */
  /* stretch, not a fixed min-height: the input renders 55.6px (padding +
     line box beats min-height 48) and a 50px button left a 5.6px seam on
     the one row where they sit side by side */
  .rizz-roster-btn { align-self: stretch; }
}

/* ---------- 11 Announce dialog ----------
   Editorial card over a dimmed, blurred page — not a cookie banner.
   Cobalt only (--rizz*): this is a teaser, not commerce, so amber is
   banned here twice over. Sits above the fixed header (--z-header 800)
   and below the skip link (1000). The [hidden] attribute is the state
   machine: announce.js flips it, CSS never fights it. */
.announce-layer {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: grid;
  place-items: center;
  padding: clamp(0.75rem, 3vw, 2rem);
}

.announce-layer[hidden] { display: none; }

.announce-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 10, 10, 0.78); /* neutral scrim, not brand color */
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
}

/* Scroll lock while open — the exact menu-open pair from site.css:
   body overflow for the simple case, html:has() for the real one
   (html carries overflow-x: clip, so body alone never propagates).
   overflow-y only: sticky stays alive. Page-owned class, so it lives
   here, not in the shared shell sheet. */
body.announce-open { overflow: hidden; }
html:has(> body.announce-open) { overflow-y: hidden; }

.announce-card {
  position: relative;
  width: 100%;
  max-width: 30rem;
  max-height: calc(100dvh - 1.5rem);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-top: 2px solid var(--rizz);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2), var(--glow-rizz-soft);
  padding: clamp(1.25rem, 4vw, 2.25rem);
}

.announce-card:focus { outline: none; }
.announce-card:focus-visible { outline: 2px solid var(--ivory); outline-offset: 2px; }

/* Close — a real button, 44px floor, top-right, always above content. */
.announce-close {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 2;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  background: var(--panel-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  color: var(--ivory);
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.announce-close:hover { border-color: var(--ivory); }

.announce-close:focus-visible { outline: 2px solid var(--ivory); outline-offset: 2px; }

/* Art slot — HIDDEN until announce.js proves the image loads, so the
   text-only card is the shipped default, not an error state. When the
   real picture lands it renders object-fit: contain on the flyer
   ground: promo art is NEVER cover-cropped (flyer law). */
.announce-art { display: none; }

.announce-card.has-art .announce-art {
  display: block;
  background: var(--flyer-ground);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  margin-bottom: var(--sp-5);
}

.announce-card.has-art .announce-art img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 34dvh; /* the card must still fit the form on one screen */
  object-fit: contain;
}

.announce-kicker { margin-bottom: var(--sp-3); }

.announce-title {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 6vw, 2.4rem);
  line-height: var(--lh-tight);
  color: var(--ivory);
  /* breathing room from the absolute 44px close button */
  padding-right: 2.5rem;
}

.announce-lead {
  margin-top: var(--sp-4);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ivory-80);
  max-width: 46ch;
}

.announce-form {
  margin-top: var(--sp-5);
  display: grid;
  gap: var(--sp-4);
}

.announce-cta { justify-self: start; }

.announce-form .roster-honest { margin-top: 0; }

.announce-skip { margin-top: var(--sp-5); }

.announce-later {
  background: none;
  border: none;
  padding: 0.5rem 0; /* keeps the target tall without reading as a button */
  min-height: 44px;
  font-family: var(--font-label);
  font-weight: 500;
  font-size: var(--fs-small);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--smoke);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.announce-later:hover { color: var(--ivory); }
.announce-later:focus-visible { outline: 2px solid var(--ivory); outline-offset: 2px; }

/* With art proven, desktop runs art beside copy; mobile stays stacked. */
@media (min-width: 768px) {
  .announce-card.has-art {
    max-width: 56rem;
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: var(--sp-6);
    align-items: start;
  }
  .announce-card.has-art .announce-art { margin-bottom: 0; }
  .announce-card.has-art .announce-art img { max-height: 62dvh; }
}

/* Entrance — declared ONLY under no-preference, so OS reduced motion
   gets an instant, fully usable dialog with zero further work. The
   session Pause control (:root.motion-paused, site.js) flattens it the
   same way. No exit animation: close is immediate by design. */
@media (prefers-reduced-motion: no-preference) {
  .announce-layer:not([hidden]) .announce-backdrop {
    animation: announce-fade var(--dur-2) var(--ease) both;
  }
  .announce-layer:not([hidden]) .announce-card {
    animation: announce-rise var(--dur-3) var(--ease) both;
  }
  :root.motion-paused .announce-layer:not([hidden]) .announce-backdrop,
  :root.motion-paused .announce-layer:not([hidden]) .announce-card {
    animation: none;
  }
}

@keyframes announce-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes announce-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Reduced motion ----------
   site.css already flattens transitions/animations globally; this
   makes the scroll cue's intent explicit and keeps it readable. */
@media (prefers-reduced-motion: reduce) {
  .rizz-cue-line::after {
    animation: none;
    transform: none;
    height: 100%;
    opacity: 0.55;
  }
}
