@import url("../fonts/fonts.css");

/* ============================================================
   Vanguard Barbell - v1
   Structure: Barbell Brigade's order (pricing early), Westside's
   hero anatomy, PWRBLD's callout + card components.
   Measured in reference/reports/build-spec.md.
   Headline face locked to Bebas Neue 2026-08-03.
   ============================================================ */

:root {
  /* Sampled from assets/gym-photos/01 - 32,461 saturated-red pixels
     off the plates and platform; modal value #B50007.             */
  --red-deep: #b50007;   /* the literal gym red - non-text only (2.80:1) */
  --red:      #dc1420;   /* CTA fill - white on it 5.04:1 */
  --red-text: #ee2b37;   /* red TEXT - 4.75:1 on --ink */

  --ink:   #0a0a0a;
  --coal:  #131210;      /* warm-biased panel, not neutral grey */
  --paper: #e8e4dc;
  --dim:   rgba(232, 228, 220, 0.66);
  --faint: rgba(232, 228, 220, 0.42);
  --rule:  rgba(232, 228, 220, 0.14);

  /* The competition plate colour code, straight off his own floor:
     25kg red · 20kg blue · 15kg yellow · 10kg green. Every lifter reads
     these as weights without being told; everyone else just sees colour.
     Used for the marquee markers - available for more. */
  --kg25: #d8232a;
  --kg20: #2664c4;
  --kg15: #e4c229;
  --kg10: #2f9e57;

  --f-display: "Bebas Neue", "Barlow Condensed", sans-serif;
  --f-label:   "Barlow Condensed", "Inter", sans-serif;
  --f-body:    "Inter", system-ui, sans-serif;

  --gut: clamp(1.25rem, 5vw, 4.5rem);
  --maxw: 1240px;
  --hdr: 74px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--f-body);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
img { display: block; max-width: 100%; }
a { color: inherit; }
:where(a, button, summary):focus-visible { outline: 2px solid var(--red-text); outline-offset: 3px; }

/* `.shell` owns inline padding. Section classes must set padding-BLOCK
   only - the `padding` shorthand would silently zero it. */
.shell { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gut); }

.skip { position: absolute; left: -9999px; background: var(--red); color: #fff; padding: .75rem 1rem; z-index: 200; }
.skip:focus { left: 1rem; top: 1rem; }

/* ---------- shared type ---------- */

.eyebrow {
  font-family: var(--f-label);
  font-size: 13px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 .9rem;
  display: flex; align-items: center; gap: .75rem;
}
.eyebrow::after { content: ""; height: 1px; flex: 1; max-width: 4.5rem; background: var(--red-deep); }
.eyebrow--plain::after { display: none; }

.h2 {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(2.1rem, 4.6vw, 3.9rem);
  line-height: 1; letter-spacing: .01em; text-transform: uppercase;
  margin: 0; text-wrap: balance;
}
.lede { max-width: 54ch; color: var(--dim); margin: 1rem 0 0; font-size: clamp(15.5px, 1.05vw, 17px); }
.lede b { color: var(--paper); font-weight: 500; }

.btn {
  display: inline-block;
  /* `z-index: 0` opens a stacking context so the ::before fill can sit at
     -1 - behind the label, but still inside the button. */
  position: relative; z-index: 0; overflow: hidden;
  font-family: var(--f-label);
  font-size: 15px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; text-decoration: none;
  padding: .95rem 1.6rem; border: 2px solid transparent;
  transition: border-color .18s ease, color .18s ease, transform .18s ease;
}
.btn::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  transform: scaleX(0); transform-origin: left;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.btn:hover, .btn:focus-visible { transform: translateY(-2px); }
.btn:hover::before, .btn:focus-visible::before { transform: scaleX(1); }

/* Red is reserved for the PRIMARY action. A ghost button that fills red on
   hover reads as the main CTA the moment you touch it, which is exactly
   backwards - so secondary fills warm grey and only primary moves in red. */
.btn--primary { background: var(--red); color: #fff; border-color: var(--red); }
.btn--primary::before { background: #9e0006; }
.btn--primary:hover, .btn--primary:focus-visible { border-color: #9e0006; }

.btn--ghost { color: var(--paper); border-color: var(--rule); }
.btn--ghost::before { background: #242220; }
.btn--ghost:hover, .btn--ghost:focus-visible { border-color: var(--paper); color: var(--paper); }

/* ============================================================
   Header + mobile nav
   ============================================================ */

.masthead {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--hdr);
  display: flex; align-items: center;
  transition: background-color .25s ease, border-color .25s ease;
  border-bottom: 1px solid transparent;
}
.masthead[data-stuck] {
  background: rgba(10, 10, 10, .92);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--rule);
}
.masthead__in {
  width: 100%; max-width: var(--maxw); margin-inline: auto;
  padding-inline: var(--gut);
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
}

.brand { display: flex; align-items: center; gap: .8rem; text-decoration: none; min-height: 44px; }
.brand__emblem {
  width: clamp(34px, 4vw, 44px); height: auto;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.brand:hover .brand__emblem { transform: scale(1.09); }
/* Wordmark is set in the site face, never the logo's blackletter -
   that register belongs to the room and arrives via photography. */
.brand__word {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.05rem, 1.4vw, 1.3rem); line-height: .92;
  letter-spacing: .04em; text-transform: uppercase;
}
.brand__word span { display: block; }
.brand__word span + span { color: var(--dim); }

.nav { display: none; align-items: center; gap: 2rem; }
@media (min-width: 900px) { .nav { display: flex; } }
.nav a {
  font-family: var(--f-label); font-size: 15px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; text-decoration: none;
  padding-block: .3rem;
}
/* Underline wipes in from the left and out to the right, so leaving a link
   reads as a different gesture from arriving on it. */
.nav a:not(.btn) { position: relative; }
.nav a:not(.btn)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--red); transform: scaleX(0); transform-origin: right;
  transition: transform .28s cubic-bezier(.2,.7,.3,1);
}
.nav a:not(.btn):hover::after,
.nav a:not(.btn):focus-visible::after { transform: scaleX(1); transform-origin: left; }
.nav .btn { padding: .6rem 1.15rem; font-size: 13.5px; }
/* Ghost until the header sticks, so the hero holds a single red CTA */
.nav .btn--primary { background: transparent; color: var(--paper); border-color: var(--rule); }
.nav .btn--primary:hover, .nav .btn--primary:focus-visible { background: var(--red); border-color: var(--red); color: #fff; }
.masthead[data-stuck] .nav .btn--primary { background: var(--red); border-color: var(--red); color: #fff; }
.masthead[data-stuck] .nav .btn--primary:hover { background: var(--red-deep); border-color: var(--red-deep); }

/* burger */
.burger {
  display: flex; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; padding: 0 10px;
  background: none; border: 0; cursor: pointer;
}
@media (min-width: 900px) { .burger { display: none; } }
.burger span { display: block; height: 2px; background: var(--paper); transition: transform .22s ease, opacity .18s ease; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.drawer {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(10, 10, 10, .985);
  padding: calc(var(--hdr) + 2rem) var(--gut) 2rem;
  display: flex; flex-direction: column; gap: .35rem;
  transform: translateY(-100%);
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
  overflow-y: auto;
}
.drawer[data-open] { transform: none; }
@media (prefers-reduced-motion: reduce) { .drawer { transition: none; } }
.drawer a {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(2rem, 9vw, 2.9rem); line-height: 1.12;
  text-transform: uppercase; text-decoration: none; letter-spacing: .015em;
  padding-block: .3rem; border-bottom: 1px solid var(--rule);
}
.drawer a:last-of-type { border-bottom: 0; }
.drawer .btn { margin-top: 1.5rem; align-self: flex-start; }
body[data-locked] { overflow: hidden; }

/* ============================================================
   Hero
   ============================================================ */

.hero { position: relative; min-height: 100svh; display: flex; align-items: flex-end; overflow: hidden; }
.hero__media { position: absolute; inset: 0; }
.hero__media img {
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  filter: brightness(1.14) contrast(1.06) saturate(1.08);
}
.hero__media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to right, rgba(10,10,10,.94) 0%, rgba(10,10,10,.80) 32%, rgba(10,10,10,.30) 60%, rgba(10,10,10,.16) 100%),
    linear-gradient(to top, rgba(10,10,10,.78) 0%, rgba(10,10,10,.08) 46%, rgba(10,10,10,.42) 100%);
}
@media (max-width: 799px) {
  .hero__media::after {
    background: linear-gradient(to top,
      rgba(10,10,10,.97) 0%, rgba(10,10,10,.93) 40%, rgba(10,10,10,.62) 62%,
      rgba(10,10,10,.18) 80%, rgba(10,10,10,.55) 100%);
  }
}
.hero__inner {
  position: relative; z-index: 10; width: 100%;
  padding-block: clamp(7rem, 14vh, 11rem) clamp(2rem, 5vw, 3.5rem);
}
@media (max-width: 799px) {
  /* Backing tied to the TEXT BLOCK, not the crop - a new photo drops in
     without re-tuning. The kicker sits ~83% up this box, so the gradient
     must span the full height, not a bottom-anchored slice. */
  .hero__inner::before {
    content: ""; position: absolute; inset: 0; z-index: -1;
    background: linear-gradient(to top,
      rgba(10,10,10,.97) 0%, rgba(10,10,10,.95) 72%, rgba(10,10,10,.88) 88%, rgba(10,10,10,0) 100%);
  }
}
/* rem, never ch - `ch` measures the BODY font, not the display face */
.hero__copy { max-width: 34rem; }
@media (min-width: 800px) { .hero__copy { max-width: min(52rem, 60%); } }

.hero h1 {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(2.55rem, 5.6vw, 5.25rem);
  line-height: .98; letter-spacing: .01em; text-transform: uppercase;
  margin: 0; text-wrap: balance;
}
.hero__sub {
  max-width: 46ch; margin: clamp(1.1rem, 2.2vw, 1.6rem) 0 0;
  font-size: clamp(15px, 1.05vw, 17px); font-weight: 300; line-height: 1.5; color: var(--dim);
}
.hero__sub b { font-weight: 500; color: var(--paper); }
@media (max-width: 799px) { .hero__sub { font-weight: 400; color: rgba(232,228,220,.82); } }

.cta-row { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: clamp(1.5rem, 3vw, 2.25rem); }
@media (max-width: 599px) {
  .cta-row { display: grid; grid-template-columns: 1fr; gap: .6rem; }
  .cta-row .btn { text-align: center; }
}

.stats {
  display: flex; flex-wrap: wrap; gap: clamp(1rem, 2.5vw, 2.5rem);
  margin: clamp(2rem, 4.5vw, 3.25rem) 0 0;
  padding-top: clamp(1.1rem, 2vw, 1.5rem);
  border-top: 1px solid var(--rule); list-style: none;
}
.stats li {
  font-family: var(--f-label); font-size: 14px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--dim);
}
.stats b { color: var(--red-text); font-weight: 700; }

/* ============================================================
   Sections
   ============================================================ */

.section { padding-block: clamp(3.75rem, 9vw, 7rem); }
.section--rule { border-top: 1px solid var(--rule); }
.section__head { margin-bottom: clamp(2rem, 4.5vw, 3.25rem); }

/* --- 2. four callouts (PWRBLD grid) --- */
.callouts { display: grid; gap: 1px; background: var(--rule); border-block: 1px solid var(--rule); }
@media (min-width: 620px) { .callouts { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .callouts { grid-template-columns: repeat(4, 1fr); } }
/* These are the four things JJ asked to lead with, so they're built as panels
   rather than text in a grid: raised off the page background, a red rule along
   the top edge, and an oversized ghost numeral for weight. */
.callout {
  background: var(--coal);
  padding: clamp(1.7rem, 2.8vw, 2.1rem) clamp(1.35rem, 2.4vw, 1.85rem) clamp(1.9rem, 3vw, 2.2rem);
  min-height: 240px; position: relative; overflow: hidden;
  display: flex; flex-direction: column;   /* top-aligned: see the h3 min-height */
  transition: background-color .3s ease;
}
/* Always-on top rule; it deepens and the card lifts on hover. Cards sit in a
   1px-gap grid, so every state has to live INSIDE the cell - a transform on
   the card itself would break the seams. */
.callout::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--red-deep);
  transition: background-color .3s ease;
}
.callout:hover { background: #1b1917; }
.callout:hover::after { background: var(--red); }

/* The index sits IN FLOW as the first flex item, not absolutely in a corner,
   so nothing can crowd it. Still from data-no, so it is never selectable or
   read aloud. */
.callout::before {
  content: attr(data-no);
  font-family: var(--f-label); font-size: 12px; font-weight: 700;
  letter-spacing: .2em; color: var(--red-text);
  margin-bottom: 1.15rem;
  pointer-events: none; user-select: none;
  transition: color .3s ease;
}
.callout:hover::before { color: var(--paper); }

/* Two lines of heading are RESERVED whether a heading needs them or not, so
   all four body paragraphs start on the same line. Without this, "Open 24/7,
   365" sets one line while "Competition equipment" sets two, and the four
   cards read as four different layouts. */
.callout h3 {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.8rem, 2.4vw, 2.2rem);
  line-height: 1; letter-spacing: .02em; text-transform: uppercase;
  margin: 0 0 .85rem; min-height: 2em;
}
.callout p {
  margin: 0; font-size: 15.5px; line-height: 1.55;
  color: rgba(232, 228, 220, .74);
}
/* One column below 620px: every heading fits one line there, so the reserved
   second line would just be a hole. */
@media (max-width: 619px) {
  .callout { min-height: 0; }
  .callout h3 { min-height: 0; }
}

/* --- 2b. ad-phrase marquee ------------------------------------------------
   Every line here is lifted verbatim from a live Meta creative (see
   context/ADS.md). It is the first thing below the fold precisely because
   that is where someone arriving from an ad checks they're in the right
   place. Change the copy here only alongside the ad copy.

   The track holds TWO identical sets and travels exactly -50%, so the loop
   seam lands on the start of set two. Both sets must stay identical in width
   or the loop will visibly jump.                                          */
.marquee { overflow: hidden; border-block: 1px solid var(--rule); background: var(--coal); }
.marquee__track { display: flex; width: max-content; padding-block: .95rem; }
.marquee__set {
  display: flex; align-items: center; gap: clamp(1.5rem, 3vw, 2.75rem);
  margin: 0; padding: 0 clamp(1.5rem, 3vw, 2.75rem) 0 0; list-style: none;
}
.marquee__set li {
  display: flex; align-items: center; gap: clamp(1.5rem, 3vw, 2.75rem);
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.1rem, 2.1vw, 1.55rem); line-height: 1;
  letter-spacing: .045em; text-transform: uppercase;
  color: var(--dim); white-space: nowrap;
}
/* Markers run the plate colour code - 25 / 20 / 15 / 10 kg, in order. */
.marquee__set li::after {
  content: ""; flex: none; width: 7px; height: 7px;
  background: var(--kg25); transform: rotate(45deg);
}
.marquee__set li:nth-child(4n+2)::after { background: var(--kg20); }
.marquee__set li:nth-child(4n+3)::after { background: var(--kg15); }
.marquee__set li:nth-child(4n+4)::after { background: var(--kg10); }
.marquee__set li:first-child { color: var(--paper); }

/* --- 3. memberships (PWRBLD cards) --- */
.prices { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
@media (min-width: 700px) { .prices { grid-template-columns: repeat(3, 1fr); } }
.price {
  background: var(--ink); padding: clamp(1.9rem, 3vw, 2.5rem);
  display: flex; flex-direction: column; gap: .5rem; min-height: 300px;
  position: relative; transition: background-color .3s ease;
}
.price::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: var(--red); transform: scaleX(0); transform-origin: left;
  transition: transform .38s cubic-bezier(.2,.7,.3,1);
}
.price:hover { background: var(--coal); }
.price:hover::after { transform: scaleX(1); }
.price__name {
  font-family: var(--f-label); font-size: 13px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--faint); margin: 0;
}
.price__fig {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(3.2rem, 6vw, 4.4rem); line-height: .88; letter-spacing: .01em;
  color: var(--red-text); margin: .45rem 0 0;
  font-variant-numeric: tabular-nums;
}
.price__fig small { font-size: .34em; letter-spacing: .1em; color: var(--dim); margin-left: .25em; }
.price ul { margin: .55rem 0 0; padding: 0; list-style: none; display: grid; gap: .3rem; }
.price li { font-size: 14.5px; color: var(--dim); }
.price .btn { margin-top: auto; align-self: flex-start; }
/* The feature card already sits on --coal, so the shared :hover would be a
   no-op on it - it needs its own, one step lighter. The red edge is an inset
   shadow, not a border: a border would push this cell out of the 1px grid. */
.price--feature { background: var(--coal); box-shadow: inset 0 0 0 2px var(--red); }
.price--feature:hover { background: #1b1917; }
.price--feature .price__fig { color: var(--red); }
.price__flag {
  font-family: var(--f-label); font-size: 11px; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink); background: var(--paper);
  align-self: flex-start; padding: .12rem .45rem; margin-bottom: .3rem;
}
.prices__note { margin: 1.1rem 0 0; font-size: 14.5px; color: var(--faint); }

/* --- 3b. the other tiers, behind a toggle --------------------------------
   Rendered as a compact list rather than three more cards on purpose: if the
   secondary options open as equals, the toggle has bought nothing. */
.more {
  margin-top: 1.4rem;
  display: inline-flex; align-items: center; gap: .7rem;
  background: none; border: 0; padding: .7rem 0; cursor: pointer;
  font-family: var(--f-label); font-size: 14px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
  transition: color .25s ease;
}
.more:hover { color: var(--paper); }
.more__n {
  font-size: 11px; color: var(--red-text);
  border: 1px solid currentColor; border-radius: 999px;
  min-width: 1.5em; padding: .1em .35em; text-align: center; letter-spacing: 0;
}
.more__chev {
  width: 8px; height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.more[aria-expanded="true"] .more__chev { transform: rotate(-135deg) translate(-2px, -2px); }

.plans { margin-top: .35rem; border-top: 1px solid var(--rule); }
.plans[hidden] { display: none; }
.plan {
  display: grid; gap: .15rem .5rem; padding: 1.15rem 0;
  border-bottom: 1px solid var(--rule); align-items: baseline;
}
@media (min-width: 700px) {
  .plan { grid-template-columns: 1fr auto 1.4fr auto; gap: 1.5rem; }
}
.plan__name {
  margin: 0; font-family: var(--f-label); font-size: 13px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--paper);
}
.plan__fig {
  margin: 0; font-family: var(--f-display); font-weight: 400;
  font-size: 1.9rem; line-height: 1; color: var(--red-text);
  font-variant-numeric: tabular-nums;
}
.plan__fig small { font-size: .4em; letter-spacing: .1em; color: var(--dim); margin-left: .25em; }
.plan__note { margin: 0; font-size: 14.5px; color: var(--dim); }
.plan__go {
  font-family: var(--f-label); font-size: 13px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; text-decoration: none;
  color: var(--paper); white-space: nowrap;
  border-bottom: 2px solid var(--rule); padding-bottom: 2px;
  transition: border-color .25s ease;
}
.plan__go:hover { border-bottom-color: var(--red); }

/* --- 4. equipment --- */
.split { display: grid; gap: clamp(2rem, 5vw, 3.5rem); align-items: start; }
@media (min-width: 900px) { .split { grid-template-columns: 1.05fr .95fr; } }
.spec { margin: 0; display: grid; gap: 0; border-top: 1px solid var(--rule); }
.spec > div {
  position: relative;
  display: grid; gap: .15rem .5rem;
  padding: 1rem 0 1rem .9rem;
  border-bottom: 1px solid var(--rule);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background-color .25s ease;
}
/* Red spine marks the row driving the stage. Absolute, not a border, so
   the row's text never shifts between states. */
.spec > div::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--red); transform: scaleY(0); transform-origin: top;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.spec > div:hover { background: rgba(232, 228, 220, .05); }
.spec > div:hover::before { transform: scaleY(1); }
.spec dd { transition: color .25s ease; }
.spec > div:hover dd { color: var(--paper); }
@media (min-width: 520px) { .spec > div { grid-template-columns: 8.5rem 1fr; gap: 1.25rem; align-items: baseline; } }

/* --- 4b. photo stage ------------------------------------------------------
   Eleven stacked photos; JS shows one at a time and cycles within whichever
   category the hovered/tapped row selects. Images carry data-src and are
   hydrated per category, so landing on the page costs one photo, not eleven. */
.stage {
  position: relative; margin: 0; overflow: hidden;
  background: var(--coal); border: 1px solid var(--rule);
  aspect-ratio: 3 / 4;
}
@media (min-width: 900px) {
  .stage { aspect-ratio: 4 / 5; position: sticky; top: calc(var(--hdr) + 2rem); }
}
.stage__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0;
}
.stage__img[data-on] { opacity: 1; }

/* Leading red edge sweeps across when the CATEGORY changes - a different,
   sharper gesture than the slow crossfade used between shots in one set. */
.stage__wipe { position: absolute; inset: 0; z-index: 3; pointer-events: none; overflow: hidden; }
.stage__wipe::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 45%;
  background: linear-gradient(90deg,
    rgba(10,10,10,0) 0%, rgba(181,0,7,.55) 55%, var(--red) 78%, rgba(255,255,255,.9) 100%);
  transform: translateX(-160%); opacity: 0;
}

/* Covers the image so a click anywhere advances, but sits BELOW the caption
   so the dot buttons stay clickable. */
.stage__advance {
  position: absolute; inset: 0; z-index: 2;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.stage__cap {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: 1.6rem .9rem .8rem;
  background: linear-gradient(to top, rgba(10,10,10,.9), rgba(10,10,10,0));
  pointer-events: none;                 /* only the dots inside take clicks */
}
.stage__count {
  font-family: var(--f-label); font-size: 12px; font-weight: 700;
  letter-spacing: .16em; color: var(--faint); flex: none;
}
.stage__count b { color: var(--paper); font-weight: 700; }
.stage__dots { display: flex; gap: .3rem; pointer-events: auto; }
/* 14x30 hit area around a 2px bar. Narrow because eleven of them have to fit
   a 335px stage - the photo itself is the full-size "next" target. */
.stage__dots button {
  display: flex; align-items: center;
  width: 14px; height: 30px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
.stage__dots button::before {
  content: ""; display: block; width: 100%; height: 2px; background: var(--faint);
  transition: background-color .3s ease, transform .3s ease;
}
.stage__dots button:hover::before { background: var(--paper); }
.stage__dots button[aria-current]::before { background: var(--red); transform: scaleY(2); }
.spec dt {
  font-family: var(--f-label); font-size: 13px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--red-text);
}
.spec dd { margin: 0; font-size: 15.5px; color: var(--dim); }
.spec__soon dt { color: var(--faint); }

/* --- 5. who trains here --- */
.who { position: relative; overflow: hidden; }
.who__media { position: absolute; inset: 0; }
.who__media img { width: 100%; height: 100%; object-fit: cover; filter: brightness(.9); }
.who__media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to right, rgba(10,10,10,.95) 0%, rgba(10,10,10,.86) 45%, rgba(10,10,10,.55) 100%);
}
/* A left-to-right scrim does nothing on a narrow screen - the copy spans the
   full width there, so it needs a flat wash instead. */
@media (max-width: 899px) {
  .who__media::after {
    background: linear-gradient(to bottom, rgba(10,10,10,.90) 0%, rgba(10,10,10,.93) 100%);
  }
}
.who .shell { position: relative; z-index: 2; }
.who__copy { max-width: 48rem; }

/* The four disciplines, set as display type. Hovering one dims the rest, so
   the list reads as a set of choices rather than four labels. */
.disc { margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 0; list-style: none; }
.disc li {
  display: flex; align-items: baseline; gap: clamp(.9rem, 2vw, 1.6rem);
  padding: clamp(.5rem, 1.2vw, .8rem) 0;
  border-top: 1px solid var(--rule);
  transition: opacity .3s ease, transform .35s cubic-bezier(.2,.7,.3,1);
}
.disc li:last-child { border-bottom: 1px solid var(--rule); }
.disc i {
  font-family: var(--f-label); font-style: normal;
  font-size: 12px; font-weight: 700; letter-spacing: .18em;
  color: var(--faint); flex: none;
  transition: color .3s ease;
}
.disc span {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(2.1rem, 5.2vw, 3.6rem); line-height: 1;
  letter-spacing: .015em; text-transform: uppercase;
}
@media (hover: hover) {
  .disc:hover li { opacity: .32; }
  .disc li:hover { opacity: 1; transform: translateX(clamp(.5rem, 1.5vw, 1.1rem)); }
  .disc li:hover i { color: var(--red-text); }
}

/* --- 6. location --- */
.loc { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }
@media (min-width: 900px) { .loc { grid-template-columns: 1fr 1fr; align-items: start; } }
.addr { font-style: normal; }
.addr__line {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.7rem, 3.4vw, 2.5rem); line-height: 1.04;
  letter-spacing: .015em; text-transform: uppercase; margin: 0 0 .6rem;
}
.addr__meta { color: var(--dim); margin: 0; }
.hours { margin-top: 2rem; border-top: 1px solid var(--rule); padding-top: 1.25rem; }
.hours dt {
  font-family: var(--f-label); font-size: 12px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--faint);
}
.hours dd {
  margin: .3rem 0 0;
  font-family: var(--f-display); font-size: 2rem; line-height: 1;
  letter-spacing: .02em; text-transform: uppercase;
}

/* --- 7. footer CTA --- */
.final { position: relative; overflow: hidden; text-align: center; }
.final__media { position: absolute; inset: 0; }
.final__media img { width: 100%; height: 100%; object-fit: cover; }
.final__media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(10,10,10,.92) 0%, rgba(10,10,10,.80) 50%, rgba(10,10,10,.95) 100%);
}
.final .shell { position: relative; z-index: 2; }
.final .eyebrow { justify-content: center; }
.final .eyebrow::after { display: none; }
.final .h2 { margin-inline: auto; }
.final .lede { margin-inline: auto; }
.final .cta-row { justify-content: center; }

.foot { border-top: 1px solid var(--rule); padding-block: 2.5rem 3rem; }
.foot__in { display: grid; gap: 1.5rem; }
@media (min-width: 760px) { .foot__in { grid-template-columns: 1fr auto; align-items: center; } }
.foot p { margin: 0; font-size: 14px; color: var(--faint); }
.foot nav { display: flex; flex-wrap: wrap; gap: 1.25rem; }
.foot nav a {
  font-family: var(--f-label); font-size: 13px; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase; text-decoration: none; color: var(--dim);
  display: inline-flex; align-items: center; min-height: 44px;  /* touch target */
}
.foot nav a:hover { color: var(--paper); }

/* ============================================================
   Motion

   Hover and focus states live with their components above. This block
   is load- and scroll-driven motion only.

   THE RULE THIS SECTION IS BUILT AROUND: no animation may be capable of
   leaving content invisible. Two mechanisms enforce it -
     1. anything that starts at opacity:0 is scoped to `.js` (set by the
        inline script in <head>), so a JS failure means visible, not blank;
     2. index.html sets data-seen on every reveal after 3s regardless.
   Keyframe animations need neither: they run to completion on their own.
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {

  /* --- scroll reveals --- */
  .js .reveal { opacity: 0; transform: translateY(16px); transition: opacity .6s ease, transform .6s cubic-bezier(.2,.7,.3,1); }
  .js .reveal[data-seen] { opacity: 1; transform: none; }

  /* Stagger: the container stays visible and hands the reveal to its
     children. --i is assigned by JS; var(--i, 0) means losing that script
     costs the stagger, not the content. */
  .js .reveal--stagger { opacity: 1; transform: none; }
  .js .reveal--stagger > * {
    opacity: 0; transform: translateY(14px);
    transition: opacity .55s ease, transform .55s cubic-bezier(.2,.7,.3,1);
    transition-delay: calc(var(--i, 0) * 65ms);
  }
  .js .reveal--stagger[data-seen] > * { opacity: 1; transform: none; }

  /* Background photography: fade the layer, settle the scale. No translate -
     these are inset:0 layers and any shift would open a gap at the edge. */
  .js .reveal--media { opacity: 0; transition: opacity .9s ease; }
  .js .reveal--media[data-seen] { opacity: 1; }
  .js .reveal--media img { transform: scale(1.07); transition: transform 1.6s cubic-bezier(.2,.7,.3,1); }
  .js .reveal--media[data-seen] img { transform: none; }

  /* Section headings use the hero's clipped reveal, so the whole page speaks
     one language rather than a hero plus some content. */
  .js .reveal .h2 {
    clip-path: inset(0 0 100% 0); transform: translateY(20px);
    transition: clip-path .95s cubic-bezier(.2,.7,.3,1) .08s,
                transform .95s cubic-bezier(.2,.7,.3,1) .08s;
  }
  .js .reveal[data-seen] .h2 { clip-path: inset(0 0 -25% 0); transform: none; }

  /* The closing wall parallaxes instead of settling from a scale - one
     transform per element, so the two effects can never fight. */
  .js .final__media.reveal--media img,
  .js .final__media.reveal--media[data-seen] img {
    position: absolute; left: 0; top: -14%; height: 128%;
    transform: translateY(calc(var(--par, 0) * 1%));
  }

  /* The rule beside a section label draws itself in. */
  .js .reveal .eyebrow::after,
  .js .reveal--stagger .eyebrow::after {
    transform: scaleX(0); transform-origin: left;
    transition: transform .7s .2s cubic-bezier(.2,.7,.3,1);
  }
  .js .reveal[data-seen] .eyebrow::after,
  .js .reveal--stagger[data-seen] .eyebrow::after { transform: scaleX(1); }

  /* --- hero, arriving ---
     Keyframes rather than observer state: an animation runs to completion
     once the stylesheet loads, so the hero cannot stall half-revealed. */
  .js .hero__copy > * { animation: rise .85s cubic-bezier(.2,.7,.3,1) both; }
  .js .hero__copy > :nth-child(1) { animation-delay: .10s; }
  .js .hero__copy > :nth-child(2) { animation-name: unmask; animation-duration: 1.05s; animation-delay: .20s; }
  .js .hero__copy > :nth-child(3) { animation-delay: .46s; }
  .js .hero__copy > :nth-child(4) { animation-delay: .60s; }
  .js .hero__copy > :nth-child(5) { animation-delay: .72s; }
  .js .hero__copy > :nth-child(6) { animation-delay: .84s; }

  .hero__media img { animation: lights-on 2600ms linear both; }

  /* --- hero, leaving ---
     --out runs 0→1 across the first screen of scroll. Unset resolves to 0,
     so no JS means no drift - never a hero stranded at zero opacity. */
  .hero__inner {
    opacity: calc(1 - var(--out, 0));
    transform: translateY(calc(var(--out, 0) * -3rem));
  }

  /* --- photo stage ---
     Two different motions for two different events: a slow crossfade plus a
     push-in between shots inside one category (ambient), and a hard red
     sweep when the category itself changes (a response to your input). */
  .stage__img { transition: opacity .85s ease; }
  .stage__img[data-on] { animation: drift 9s ease-out both; }
  .stage[data-switch] .stage__wipe::before { animation: sweep .62s cubic-bezier(.45,0,.2,1); }

  /* --- marquee ---
     Fallback only: the script replaces this with a scroll-reactive loop.
     31s matches the script's 0.052 px/ms at desktop width. */
  .marquee__track { animation: slide 31s linear infinite; }
  .marquee:hover .marquee__track { animation-play-state: paused; }

  /* --- drawer links ---
     Safe to start at 0: the drawer is [hidden] until JS opens it. */
  .js .drawer a {
    opacity: 0; transform: translateY(12px);
    transition: opacity .3s ease, transform .35s cubic-bezier(.2,.7,.3,1);
  }
  .js .drawer[data-open] a {
    opacity: 1; transform: none;
    transition-delay: calc(var(--i, 0) * 45ms + 80ms);
  }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
/* Rests at -25% rather than 0 so descenders and letter-spacing never clip. */
@keyframes unmask {
  from { opacity: 0; clip-path: inset(0 0 100% 0); transform: translateY(26px); }
  to   { opacity: 1; clip-path: inset(0 0 -25% 0); transform: none; }
}
@keyframes slide {
  from { transform: none; }
  to   { transform: translateX(-50%); }
}
/* Slow push in, so a still phone photo reads as footage of the room. */
@keyframes drift {
  from { transform: scale(1.005); }
  to   { transform: scale(1.085); }
}
@keyframes sweep {
  0%   { transform: translateX(-160%); opacity: 0; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { transform: translateX(265%); opacity: 0; }
}
/* A fluorescent tube coming on: ONE strike, then a long steady warm-up.
   Timing is per-keyframe - the strike and dropout are fast (the tube
   catching), the ramp to full is slow (the gas warming). The earlier
   version dipped twice at 1100ms, which read as dropped frames rather
   than a light. Measured curve of this one: .20 → .74 → .30 → 1.14,
   a single direction change, then monotonic for the remaining ~2.1s. */
@keyframes lights-on {
  0%   { filter: brightness(.20) contrast(1.22) saturate(.88); transform: scale(1.055);
         animation-timing-function: cubic-bezier(.15,.85,.3,1); }
  9%   { filter: brightness(.74) contrast(1.14) saturate(.94);
         animation-timing-function: cubic-bezier(.5,0,.75,.2); }
  17%  { filter: brightness(.30) contrast(1.20) saturate(.90);
         animation-timing-function: cubic-bezier(.22,.55,.2,1); }
  100% { filter: brightness(1.14) contrast(1.06) saturate(1.08); transform: scale(1); }
}

/* Reduced motion keeps every hover state - it just arrives instantly and
   without travel. Colour is not a vestibular trigger; movement is. */
@media (prefers-reduced-motion: reduce) {
  .btn:hover, .btn:focus-visible,
  .who__list li:hover,
  .brand:hover .brand__emblem { transform: none; }
  .btn::before, .callout::after, .price::after,
  .nav a:not(.btn)::after,
  .spec > div::before, .burger span { transition: none; }
}
