/* ============================================================
   518 Official — V1 SHARED SHELL + SYSTEM
   site.css?v=2 — load AFTER tokens.css.
   Contract: docs/RIZZ-SHELL.md. Pages add ONLY layout-specific
   CSS in css/pages/<page>.css. Never restyle the shell per page.
   Brand color routes through tokens.css ONLY — the cobalt/amber
   names (--rizz*, --drop*) or their transform aliases. The neutral
   rgba(10,10,10,…) scrims below are neutrals, not brand color.

   TOC:
   01 Reset & base            07 Buttons
   02 Typography              08 Section header pattern
   03 Layout & containers     09 Cards (panel / flyer / stat)
   04 Skip link & focus       10 Lazy embed (click-to-load)
   05 Header + nav            11 Video slot (.vslot)
   06 Overlay menu            12 Utilities
                              13 Reduced motion / print
                              14 Motion layer (JS-applied classes)
   ============================================================ */

/* ---------- 01 Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

html {
  overflow-x: clip; /* house law: clip NOT hidden — keeps position:sticky alive */
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
  -webkit-text-size-adjust: 100%;
}

body {
  overflow-x: clip;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--ivory);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body.menu-open { overflow: hidden; }

/* SCROLL LOCK LANDS ON THE ROOT, NOT ON <body>.
   `body { overflow: hidden }` only reaches the viewport when the ROOT's
   overflow computes to `visible`, and on this site it never does — html
   carries `overflow-x: clip` (line 27, the sticky house law). So every
   body-level lock class above was inert, and the page scrolled freely
   behind the menu overlay, the gallery viewer and the events lightbox.
   Measured on gallery.html: two wheel notches over the OPEN viewer moved
   the page from y=360 to y=1552.

   This is only half the fix. Lenis scrolls PROGRAMMATICALLY in response
   to the wheel, and a programmatic scroll ignores overflow — so each
   modal must also call window.lenis.stop() / .start() (js/gallery.js
   does; js/site.js's menu and js/events-carousel.js's lightbox still
   need it). The rule below is what holds the page still when Lenis is
   absent, which is the whole reduced-motion path.

   overflow-y only: overflow-x stays `clip` so sticky survives. */
html:has(> body.menu-open),
html:has(> body.gal-open),
html:has(> body.lb-open) { overflow-y: hidden; }

img, picture, video, canvas, svg { display: block; max-width: 100%; }

img { height: auto; }

input, button, textarea, select { font: inherit; color: inherit; }

ul[class], ol[class] { list-style: none; padding: 0; }

hr.rule { border: 0; border-top: 1px solid var(--line); }

::selection { background: var(--rizz); color: var(--ivory); }

/* ---------- 02 Typography ---------- */
h1, h2, h3, .display {
  font-family: var(--font-display);
  font-weight: 400;              /* Archivo Black ships 400 only */
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: var(--lh-tight);
  color: var(--ivory);
  text-wrap: balance;
}

h1 { font-size: var(--fs-display); }
h2 { font-size: var(--fs-title); }
h3 { font-size: var(--fs-h3); }

h4, h5, h6 {
  font-family: var(--font-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: var(--fs-h4);
  color: var(--ivory);
}

/* Tighter track than the base 0.01em: at hero scale Archivo Black wants to
   knit tight for poster density/authority, not spread. Smaller h2/h3 keep 0.01em. */
.hero-title { font-size: var(--fs-hero); letter-spacing: -0.01em; }

p { text-wrap: pretty; }

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

/* Inline links: ivory text, cobalt underline. NEVER raw-cobalt body text —
   cobalt copy uses --rizz-text only (see tokens.css contrast law). */
a {
  color: var(--ivory);
  text-decoration-color: var(--rizz-hot);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  transition: text-decoration-thickness var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

a:hover { text-decoration-thickness: 2px; }

strong { color: var(--ivory); font-weight: 600; }

/* ---------- 03 Layout & containers ---------- */
.container {
  width: min(100% - (2 * var(--gutter)), var(--w-max));
  margin-inline: auto;
}

.container--wide   { width: min(100% - (2 * var(--gutter)), var(--w-wide)); }
.container--narrow { width: min(100% - (2 * var(--gutter)), var(--w-narrow)); }

.sect { padding-block: var(--sect-pad); }

.sect--tight { padding-block: calc(var(--sect-pad) * 0.6); }

/* Alternating depth band. --bg-2 against --bg is a 4/255 step (1.03:1) and is
   perceptually invisible on its own, so the band ARRIVES with a thin lift of
   light at its top edge and settles back toward the page ground at its bottom.
   Fixed pixel lengths, not percentages: this is an edge event, so it must read
   the same on an 800px band and a 1900px one.

   Authored as background-color + background-image LONGHANDS on purpose. Page
   sheets load after this one and override .sect--band's background-image to add
   their own wash; with the shorthand that would also reset the band colour to
   transparent and the band would vanish. Longhands degrade gracefully.

   Contrast verified at every position text can occupy: the lightest point a
   section-head eyebrow can reach is 3.5% ivory (a .sect--tight band at 360px),
   where --smoke measures 4.65:1 — AA holds. Cards keep their elevation because
   content starts one --sect-pad below the top edge, where the lift has decayed
   past --panel. */
.sect--band {
  background-color: var(--bg-2);
  background-image:
    linear-gradient(to top,
      color-mix(in srgb, var(--bg) 92%, transparent) 0,
      transparent 140px),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ivory) 4.5%, transparent) 0,
      transparent 200px);
  border-block: 1px solid var(--line);
}

.grid { display: grid; gap: var(--sp-6); }

@media (min-width: 768px) {
  .grid-2 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
}

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

main { display: block; }

/* Page top padding when a page has no full-bleed hero under the header */
.page-top { padding-top: calc(var(--header-h) + var(--sp-7)); }

/* ---------- 04 Skip link & focus ---------- */
.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: var(--z-skip);
  padding: 0.8rem 1.2rem;
  background: var(--rizz);
  color: var(--ivory);
  font-family: var(--font-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  text-decoration: none;
  border-radius: var(--r-sm);
  transform: translateY(-250%);
}

.skip-link:focus-visible { transform: none; }

/* No border-radius here: the outline rounds to each element's own corner
   radius on modern browsers, so the ring hugs the surface instead of
   snapping every focused .btn/.lazy-embed to a fixed 2px corner. */
:focus-visible {
  outline: 2px solid var(--ivory);
  outline-offset: 3px;
}

/* ---------- 05 Header + nav ---------- */
.site-header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: var(--z-header);
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: linear-gradient(to bottom, rgba(10, 10, 10, 0.62), rgba(10, 10, 10, 0));
  border-bottom: 1px solid transparent;
  transition:
    background var(--dur-2) var(--ease),
    border-color var(--dur-2) var(--ease),
    backdrop-filter var(--dur-2) var(--ease);
}

.site-header.is-solid {
  background: rgba(10, 10, 10, 0.78);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: var(--line);
}

/* While the overlay menu is open the header sits above it; go clean-dark */
.menu-open .site-header {
  background: rgba(10, 10, 10, 0.96);
  border-bottom-color: var(--line);
}

.header-inner {
  width: min(100% - (2 * var(--gutter)), var(--w-wide));
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

.logo {
  display: inline-flex;      /* 22px text box -> 44px target. inline-flex keeps the
                                width hugging the wordmark, and .header-inner is
                                already align-items:center so it stays optically set. */
  align-items: center;
  min-height: 44px;
  font-family: var(--font-logo);
  font-size: 1.6rem;
  letter-spacing: 0.08em;
  line-height: 1;
  color: var(--ivory);
  text-decoration: none;
  white-space: nowrap;
}

/* 518 Official wordmark: "RIZZ" carries the ivory weight, "518" is the charged
   suffix — cobalt TEXT token (7.24:1; ~18px caps need AA-normal, contrast
   law), slightly smaller, tracked open, tucked after the name. Markup:
   <a class="logo" …>RIZZ<span class="logo-518">518</span></a> */
.logo { font-weight: 700; }      /* Space Grotesk bold — the wordmark cut */

/* THE REAL MARK, 2026-08-18. The header carried a typed "518 Official" until
   the client's own logo arrived. The artwork is black on transparent —
   invisible on a near-black site — so it ships recoloured to --ivory as a
   derivative; images/brand/ keeps both that and a cobalt version for the
   favicon. Height is capped so the 44px header target is the anchor, not
   the artwork's own square. */
.logo-mark {
  display: block;
  height: 48px;      /* the artwork is square with "Events" tucked into the
                        mark — below ~44px that word stops reading */
  width: auto;
}

@media (max-width: 767px) {
  .logo-mark { height: 40px; }
}

/* superseded by .logo-mark — kept only for the footer wordmark */
.logo .logo-518 {
  color: var(--rizz-text);
  font-weight: 500;              /* numerals sit lighter than the name */
  font-size: 0.78em;
  letter-spacing: 0.14em;
  margin-left: 0.34em;
}

.site-nav { margin-left: auto; }

.site-nav ul {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  list-style: none;
  padding: 0;
}

.site-nav a {
  position: relative;
  display: inline-block;
  padding: 0.5rem 0.1rem;
  /* A nav label is one unit. "The Wall" is the only two-word item and it is
     what actually broke: when the row ran out of room the li shrank and the
     label folded onto a second line INSIDE the header. Never wrap a label —
     if the row is short the gap ramp below gives way first. */
  white-space: nowrap;
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-80);
  text-decoration: none;
  transition: color var(--dur-1) var(--ease);
}

.site-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.1rem;
  height: 2px;
  background: var(--rizz-line);   /* hover/current underline reads as light, not a near-black smear */
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-2) var(--ease);
}

.site-nav a:hover { color: var(--ivory); }

.site-nav a:hover::after,
.site-nav a[aria-current="page"]::after { transform: scaleX(1); }

.site-nav a[aria-current="page"] { color: var(--ivory); }

/* Adds to .header-inner's 24px gap, so this is 56px of clear air between the
   nav run and the cobalt fill — at a mere 8px the separation would equal the
   32px inter-nav gap and Book RIZZ would read as a seventh nav item rather
   than the site's one persistent conversion accent. Below 980 the max-width
   block overrides this to auto and the hamburger takes over.
   nowrap: the button never wraps; the nav gap gives way first. */
.header-cta { margin-left: var(--sp-6); white-space: nowrap; }

/* RIZZ now runs NINE nav items + the Book RIZZ CTA. The band below was
   written for SIX, and the three extra items broke the row.

   Measured at the base 0.95rem the nine labels intrinsically run 541px, so
   the header needs logo 48 + inner-gap 24 + 541 + 8·gap + cta-margin +
   cta 108 to fit the gutter box (viewport − 2 × 40px). At the old
   clamp(1rem, 2.2vw, 2rem) gap that wanted ~1058px — so from 980 (the
   hamburger switch itself) to 1031 the row overflowed and "The Wall" folded
   onto a second line. A classic 15px scrollbar drags that band up to a
   995–1046px WINDOW, which is ordinary laptop territory.

   Two measured steps now carry the row from the switch to base spacing at
   1200. The first also steps the label to 14px — above the house 13px label
   floor — which buys ~43px of run and keeps the item gap near 20px, wide
   enough that the labels' own 0.14em tracking still reads as separate words.
   Slack at the 980 switch is ~56px; the row never wraps again anywhere in
   360–1600. Keep the CTA's clear air (inner gap + this margin) at roughly
   twice the nav gap or Book RIZZ starts reading as a tenth nav item. */
@media (min-width: 980px) and (max-width: 1079.98px) {
  .site-nav a { font-size: 0.875rem; }
  .site-nav ul { gap: clamp(1rem, 1.9vw, 1.45rem); }
  .header-cta { margin-left: var(--sp-4); }   /* 16 + 24 inner = 40px of air */
}

@media (min-width: 1080px) and (max-width: 1199.98px) {
  .site-nav ul { gap: clamp(1.45rem, 2.4vw, 2rem); }
  .header-cta { margin-left: var(--sp-5); }
}

/* Hamburger — hidden on desktop.
   flex:none is load-bearing: the bars are absolutely positioned, so the
   button's flex min-content is 0 and a wide wordmark (Space Grotesk 700)
   could squeeze the 44px target to nothing at the 360px floor. */
.nav-toggle {
  display: none;
  flex: none;
  position: relative;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.nav-toggle .bar {
  position: absolute;
  left: 10px;
  width: 24px;
  height: 2px;
  background: var(--ivory);
  transition: transform var(--dur-2) var(--ease), opacity var(--dur-1) var(--ease);
}

.nav-toggle .bar:nth-child(1) { top: 15px; }
.nav-toggle .bar:nth-child(2) { top: 21px; }
.nav-toggle .bar:nth-child(3) { top: 27px; }

.nav-toggle.is-open .bar:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle.is-open .bar:nth-child(2) { opacity: 0; }
.nav-toggle.is-open .bar:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 979.98px) {
  .site-nav { display: none; }
  .nav-toggle { display: block; order: 6; }
  .header-cta { margin-left: auto; order: 5; }
  /* 44px, not 40px — this is the primary mobile conversion CTA and it was
     under the house floor by explicit override. */
  .header-cta.btn { padding: 0.7rem 1.05rem; font-size: 0.88rem; min-height: 44px; }
}

@media (max-width: 767.98px) {
  :root { --header-h: 4rem; }
  .logo { font-size: 1.4rem; }
}

/* ---------- 06 Overlay menu (<980px) ---------- */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-menu);          /* below header: logo/CTA/toggle stay usable */
  display: flex;
  flex-direction: column;
  /* Spacing tuned so the full stack (6 RIZZ nav items + the conversion foot:
     Book RIZZ, interim email, social placeholders) clears the fold at rest on
     a real mobile Safari viewport. Tight gaps + top padding here and in
     .menu-overlay nav ul / .menu-foot buy that headroom.
     No touch-target changes; every link keeps its 44px min. */
  gap: var(--sp-4);
  padding: calc(var(--header-h) + var(--sp-2)) var(--gutter) var(--sp-6);
  background: rgba(10, 10, 10, 0.96);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  opacity: 0;
  visibility: hidden;              /* removes from tab order + AT when closed */
  transition: opacity var(--dur-2) var(--ease), visibility 0s linear var(--dur-2);
  overflow-y: auto;
}

.menu-overlay.is-open {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* Trade list gap for link height: this is the site's ONLY navigation below
   980px and every link is a protected 44px target. The tight sp-1 (4px) gap
   keeps the whole stack plus the conversion foot above the fold; the 44px
   min-heights are never touched. */
.menu-overlay nav ul {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  list-style: none;
  padding: 0;
}

.menu-overlay nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-display);
  font-size: clamp(2rem, 8vw, 3rem);
  text-transform: uppercase;
  line-height: 1.05;
  color: var(--ivory-80);
  text-decoration: none;
  transition: color var(--dur-1) var(--ease), padding-left var(--dur-2) var(--ease);
}

.menu-overlay nav a:hover { color: var(--ivory); }

.menu-overlay nav a[aria-current="page"] {
  color: var(--ivory);
  padding-left: var(--sp-4);
  box-shadow: inset 4px 0 0 var(--rizz-line);   /* "you are here" bar arrives as light */
}

.menu-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  border-top: 1px solid var(--line);
  padding-top: var(--sp-4);
}

.menu-foot a { text-decoration: none; }

/* Contact rows in the mobile menu foot: real links (the interim email) AND
   inert "landing soon" social placeholders share this treatment. 44px min —
   the money row must never be an under-floor target. */
.menu-foot .menu-contact {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--smoke);
  font-size: var(--fs-small);
}

@media (min-width: 980px) {
  .menu-overlay { display: none; }
}

/* ---------- 07 Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-height: 48px;
  padding: 1rem 1.9rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 1rem;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  text-decoration: none;
  cursor: pointer;
  transition:
    background var(--dur-1) var(--ease),
    border-color var(--dur-1) var(--ease),
    color var(--dur-1) var(--ease),
    box-shadow var(--dur-2) var(--ease),
    transform var(--dur-1) var(--ease);
}

/* Shared :focus-visible already paints the ring; no per-button override. */

.btn:active { transform: translateY(1px); }

.btn--sm { min-height: 44px; padding: 0.7rem 1.15rem; font-size: 0.9rem; }
.btn--lg { min-height: 56px; padding: 1.2rem 2.4rem; font-size: 1.05rem; }

.btn-solid {
  background: var(--rizz);
  color: var(--ivory);
}

.btn-solid:hover {
  background: var(--rizz-hot);
  box-shadow: var(--glow-rizz);
  transform: translateY(-1px);   /* rise on hover; :active sinks 1px — 2px tactile travel */
}

.btn-solid:active { background: var(--rizz-deep); }

.btn-ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ivory);
}

.btn-ghost:hover {
  border-color: var(--ivory);
  background: color-mix(in srgb, var(--ivory) 6%, transparent);
  transform: translateY(-1px);   /* matches solid: rise on hover, sink on press */
}

/* ---------- 08 Section header pattern ---------- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 0.875rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--smoke);
}

.eyebrow::before {
  content: "";
  width: 26px;
  height: 2px;
  background: var(--rizz-line);   /* lit accent (3.79:1), not the 3.0:1 fill — the tick reads crisp on black */
  flex: none;
}

.eyebrow--center { justify-content: center; }

/* Head-to-content gap breathes in step with --sect-pad instead of a fixed 3rem,
   so section titles don't feel cramped at 1440/1920 where the section runs 8rem. */
.sect-head { margin-bottom: clamp(var(--sp-6), 4vw, var(--sp-8)); }

.sect-head .eyebrow { margin-bottom: var(--sp-4); }

.sect-title { font-size: var(--fs-title); }

.sect-head--center { text-align: center; }
.sect-head--center .eyebrow { justify-content: center; }

.sect-head .sect-lead {
  margin-top: var(--sp-4);
  font-size: var(--fs-lead);
  color: var(--ivory-80);   /* matches .lead — every lead paragraph shares one presence level */
  max-width: 58ch;
}

.sect-head--center .sect-lead { margin-inline: auto; }

/* ---------- 09 Cards ---------- */
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: clamp(1.25rem, 3vw, 2rem);
  /* Catch-light on the top edge — on a #0A0A0A ground a drop shadow carries
     no light, so black-glass elevation comes from a hairline of ivory up top. */
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--ivory) 5%, transparent);
}

.panel--raise { background: var(--panel-2); }

/* Framed flyer artifact — event flyers displayed as archival objects.
   Flyer art lives INSIDE the frame; frame chrome stays black/ivory. */
.flyer-card {
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  padding: var(--sp-4);
  /* Depth shadow + top-edge catch-light so the archival frame reads as glass. */
  box-shadow: var(--shadow-2), inset 0 1px 0 color-mix(in srgb, var(--ivory) 6%, transparent);
}

.flyer-card img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}

.flyer-card .flyer-meta {
  padding-top: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.flyer-card .flyer-tag {
  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);   /* was --smoke: 13px at 0.22em tracking is below the
                               house floor for smoke-grey (14px+). 5.2:1 -> 7.2:1,
                               zero layout shift. */
}

/* Stat block — Space Grotesk numerals, ivory (raw-cobalt numerals stay under
   AA-normal; cobalt copy is --rizz-text only) */
.stat { display: flex; flex-direction: column; gap: var(--sp-2); }

.stat .stat-num {
  font-family: var(--font-logo);
  font-size: var(--fs-stat);
  line-height: 1;
  color: var(--ivory);
}

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

.stat::before {
  content: "";
  width: 26px;
  height: 2px;
  background: var(--rizz-line);   /* lit accent tick, matches the eyebrow mark */
  margin-bottom: var(--sp-2);
}

/* ---------- 10 Lazy embed (click-to-load YouTube facade) ---------- */
.lazy-embed {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--bg);
  cursor: pointer;
}

.lazy-embed img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.85;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-3) var(--ease);
}

.lazy-embed:hover img { opacity: 1; transform: scale(1.03); }

.lazy-embed .play-glyph {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: rgba(10, 10, 10, 0.62);
  border: 1px solid var(--line-strong);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}

.lazy-embed .play-glyph::after {
  content: "";
  margin-left: 6px;
  border-left: 20px solid var(--ivory);
  border-top: 12px solid transparent;
  border-bottom: 12px solid transparent;
}

.lazy-embed:hover .play-glyph,
.lazy-embed:focus-visible .play-glyph {
  background: var(--rizz);
  box-shadow: var(--glow-rizz);
  transform: scale(1.06);   /* the affordance blooms with the cobalt — it's the thing you click */
}

.lazy-embed .embed-label {
  position: absolute;
  left: 1rem;
  bottom: 0.9rem;
  padding: 0.4rem 0.7rem;
  background: rgba(10, 10, 10, 0.6);
  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);
}

.embed-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--bg);
}

.embed-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ---------- 11 Video slot (.vslot) ----------
   Poster <img> is the in-flow base layer and sizes the box; the <video>
   sits above at opacity 0 and only fades in (.is-live, set by site.js)
   once it can actually play. If media/web files are absent or 404,
   nothing breaks — the page ships looking finished on the poster. */
.vslot {
  position: relative;
  overflow: hidden;
  background: var(--panel);
  border-radius: var(--r-md);
}

.vslot--bare { border-radius: 0; }

.vslot .vslot-poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.vslot video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease);
}

.vslot.is-live video { opacity: 1; }

/* Full-bleed variant: parent must be position:relative */
.vslot--fill {
  position: absolute;
  inset: 0;
  border-radius: 0;
  z-index: var(--z-bg);
}

.vslot--fill .vslot-poster { position: absolute; inset: 0; }

/* ---------- 12 Utilities ---------- */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.measure { max-width: var(--w-copy); }

.muted { color: var(--smoke); }

.soft { color: var(--ivory-60); }

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

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

/* Cobalt haze wash — decorative section backdrop, use sparingly */
.haze {
  position: relative;
}

.haze::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-bg);
  background: radial-gradient(60% 50% at 50% 0%, var(--rizz-haze), transparent 70%);
  pointer-events: none;
}

/* ---------- Footer ---------- */
/* Same arrival lift as .sect--band — the footer shares --bg-2 with the bands,
   so against a closing section on --bg it previously had only its 1px hairline
   to separate it. Content starts one --sp-9 (96px) down, where the lift has
   decayed to 1.7% and --smoke still measures 4.81:1. */
.site-footer {
  background-color: var(--bg-2);
  background-image: linear-gradient(to bottom,
    color-mix(in srgb, var(--ivory) 4%, transparent) 0,
    transparent 170px);
  border-top: 1px solid var(--line);
  padding: var(--sp-9) 0 var(--sp-6);
}

.foot-grid {
  display: grid;
  gap: var(--sp-7);
}

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

@media (min-width: 980px) {
  .foot-grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; }
}

.foot-brand .logo { font-size: 1.9rem; }

.foot-brand .foot-tag {
  margin-top: var(--sp-4);
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-60);
}

.foot-brand .foot-role {
  margin-top: var(--sp-2);
  font-size: var(--fs-small);
  color: var(--smoke);
}

.foot-head {
  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);   /* was --smoke: 13px at 0.22em is below the smoke
                               floor. NAVIGATE / CONTACT / FOLLOW & STREAM were
                               the faintest strokes on the site. 5.4:1 -> 7.2:1. */
  margin-bottom: var(--sp-4);
}

/* 17 footer links per page rendered as 16px inline boxes — the 0.65rem gap was
   dead space, so live hit pitch was 16px in a 35px row. Gap becomes padding:
   net pitch 45.6px, footer grows ~10px per row. */
.foot-list {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.foot-list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ivory-80);
  text-decoration: none;
  font-size: var(--fs-small);
}

.foot-list a:hover { color: var(--ivory); text-decoration: underline; }

.foot-legal {
  margin-top: var(--sp-8);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-6);
  justify-content: space-between;
  color: var(--smoke);
  font-size: var(--fs-small);
}

/* Global motion Pause/Play control (WCAG 2.2.2) — injected by site.js between
   the footer grid and the legal line. Uses the shared .btn btn-ghost btn--sm
   tokens; .btn--sm keeps this at the 44px target floor. No new brand colors. */
.motion-pref { margin-top: var(--sp-7); }

/* ---------- 13 Reduced motion / print ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  html { scroll-behavior: auto; }

  .vslot video { display: none; } /* site.js also skips playback */
}

/* Session Pause control (WCAG 2.2.2): mirror OS reduced motion for CSS-driven
   motion when the user pauses without an OS setting. JS-driven effects are torn
   down by motion.js and the decorative videos are removed by site.js; this only
   quiets any CSS animation/transition, matching the OS-reduced parity above. */
html.motion-paused *,
html.motion-paused *::before,
html.motion-paused *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

html.motion-paused .vslot video { display: none; }

@media print {
  .site-header, .menu-overlay, .nav-toggle, .skip-link,
  .vslot video, .lazy-embed .play-glyph { display: none !important; }

  body { background: #fff; color: #000; }

  a { color: #000; text-decoration: underline; }

  .site-footer { background: #fff; border-top: 1px solid #000; }
}

/* ---------- 14 Motion layer (JS-applied classes only) ----------
   These rules used to be written into a runtime <style id="motion-css">
   by motion.js. That single dynamic <style> was the ONE thing making
   `style-src 'unsafe-inline'` load-bearing in vercel.json — it cannot be
   hash-pinned (built at runtime) and cannot be nonced (no server). No
   HTML on this site carries a style attribute or an inline <style>, and
   GSAP/Lenis write through the CSSOM (element.style.*), which CSP does
   not gate. So the rules live here now and 'unsafe-inline' can be dropped
   from style-src. REPORTED, not applied — vercel.json is not this seat's
   file.

   The no-JS floor is unchanged: EVERY selector below is gated on a class
   that only motion.js (or Lenis) ever applies — .lenis, .mo-still,
   .mo-clip, .mq-vp, .mq-on, .mq-track, .mq-set-start, .mq-tail. With JS
   off, not one of them matches, so nothing here can pre-hide or reflow
   content. Page CSS must never author these class names (verified: only
   comments reference them). */

/* Lenis owns scrolling while active; CSS smooth would fight it. */
html.lenis, html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto !important; }

/* transform hygiene: elements GSAP drives must not also CSS-transition */
.mo-still { transition: none !important; }

/* clip host for hero-image scale settles */
.mo-clip { overflow: hidden !important; }

/* generated marquee viewport (data-marquee sat on the list itself) */
.mq-vp { width: 100%; }

/* marquee loop mode */
.mq-on {
  overflow: hidden !important;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.mq-track {
  display: flex !important;
  flex-wrap: nowrap !important;
  justify-content: flex-start !important;
  width: max-content;
  transform: translate3d(0, 0, 0);
}

/* Seam control for ::before-separated rails (media/events). Geometry comes
   from the page's own live ::before via custom properties set in
   motion.js syncSeam() — never hardcoded here. The fallbacks below are the
   last-resort values only; the live page always supplies its own. */
.mq-track li.mq-set-start::before { content: none !important; }

.mq-track li.mq-tail::after {
  content: "";
  flex: none;
  width: var(--mq-seam-w, 7px);
  height: var(--mq-seam-h, 7px);
  margin-left: var(--mq-seam-gap, clamp(1.1rem, 2.6vw, 2.4rem));
  /* motion.js carried a literal #2B4BE0 here as a third fallback. In a
     stylesheet that always loads after tokens.css, var(--rizz) can never
     fail to resolve, so the literal is dead — and a brand hex duplicated
     outside tokens.css is exactly what the palette law forbids. Dropped. */
  background: var(--mq-seam-bg, var(--rizz));
  transform: var(--mq-seam-tf, rotate(45deg));
}

/* ---------- CREW COVERAGE (VP Cash, homepage) ----------------------------
   A vertical 9:16 clip. Cap the height so a portrait video cannot run the
   whole viewport on desktop, and centre it against the page's own ground. */
.rizz-crewfilm {
  margin: 0 auto;
  max-width: 34rem;
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
.rizz-crewfilm-video {
  width: 100%;
  height: auto;
  max-height: 78vh;
  display: block;
  background: #0a0a0a;
  border: 1px solid rgba(255,255,255,.12);
}
.rizz-crewfilm-tag {
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-size: .68rem;
}
.rizz-crewfilm-name { opacity: .68; }
.rizz-crewfilm-cap {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  align-items: baseline;
  justify-content: space-between;
  font-size: .82rem;
}
@media (max-width: 640px) {
  .rizz-crewfilm { max-width: none; }
  .rizz-crewfilm-video { max-height: none; }
}
