/* ===========================================================================
   Encounters Smash Battles
   Dark theme, locked. One accent (amber). One radius system (sharp, 0px).
   Type: Archivo (display + body), JetBrains Mono (HUD / data).
   =========================================================================== */

/* ---------- fonts ---------- */
@font-face { font-family: "Archivo"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("assets/fonts/archivo-400.woff2") format("woff2"); }
@font-face { font-family: "Archivo"; font-style: normal; font-weight: 500;
  font-display: swap; src: url("assets/fonts/archivo-500.woff2") format("woff2"); }
@font-face { font-family: "Archivo"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("assets/fonts/archivo-700.woff2") format("woff2"); }
@font-face { font-family: "Archivo"; font-style: normal; font-weight: 900;
  font-display: swap; src: url("assets/fonts/archivo-900.woff2") format("woff2"); }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("assets/fonts/jbmono-400.woff2") format("woff2"); }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("assets/fonts/jbmono-700.woff2") format("woff2"); }

/* ---------- tokens ---------- */
:root {
  --void:      #07090E;
  --stone:     #10141B;
  --stone-2:   #171D26;
  --edge:      #262E3A;
  --edge-soft: #1B222C;
  --bone:      #EAE8E3;
  --muted:     #7E8798;
  --amber:     #FBAE3C;
  --amber-ink: #120D04;
  --damage:    #FF4242;

  --display: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --data: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;

  --shell: min(1240px, 90vw);
  --nav-h: 68px;
  --r: 0px;                 /* one radius system: sharp */

  --ease: cubic-bezier(.16,1,.3,1);
}

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--void);
  color: var(--bone);
  font-family: var(--display);
  font-size: 17px;
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

a { color: inherit; }
img { max-width: 100%; display: block; }
button { font: inherit; }

:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 3px;
}

.shell { width: var(--shell); margin-inline: auto; }

/* Pointer wake. Above everything including the champion dialog, since it is the
   cursor. The canvas composites its own segments additively, so the element
   itself needs no blend mode and nothing below it is forced onto its own layer. */
.fx-trail {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: 200;
  pointer-events: none;
}

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

/* Sections that anchor links target must clear the sticky nav. */
[id] { scroll-margin-top: calc(var(--nav-h) + 24px); }

/* ---------- shared type ---------- */
.eyebrow {
  font-family: var(--data);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--amber);
  margin: 0;
}

.h-sec {
  font-size: clamp(30px, 4.4vw, 54px);
  font-weight: 900;
  letter-spacing: -.03em;
  line-height: 1.02;
  margin: 0;
  text-wrap: balance;
}

.lede {
  color: var(--muted);
  font-size: clamp(16px, 1.4vw, 18px);
  line-height: 1.65;
  max-width: 54ch;
  margin: 18px 0 0;
  /* Keeps a single word off a line of its own. Unlike `balance`, which evens
     every line and would reflow the whole paragraph, this only pulls a word
     back from a short last line. Best-effort in the engine and ignored where
     unsupported, so nothing depends on it — see the studio lede, which is also
     bound with a no-break space at the join it actually breaks at. */
  text-wrap: pretty;
}

section { padding: clamp(72px, 9vw, 132px) 0; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 10px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 14px 26px;
  border-radius: var(--r);
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform .13s var(--ease), box-shadow .13s var(--ease),
              filter .16s var(--ease), background-color .18s var(--ease),
              border-color .18s var(--ease), color .18s var(--ease);
}
/* Top-lit. The buttons read as if seen from directly above with the light
   directly above them too, so the falloff runs from the centre outward rather
   than top to bottom. Every edge is identical, which is the point.
     --glow  spread of the inner ambient light
     --rim   width of the uniform band inset around the full perimeter
   The radial is centred at 50% 50% exactly. Nudging it upward would read as a
   light source in front of the object and put the top-to-bottom difference
   back. Corners stay sharp, matching the logo's own. */
.btn-amber, .btn-chrome {
  --glow: 16px;
  --rim: 6px;
  border: 0;
  background-repeat: no-repeat;
}

.btn-amber {
  color: var(--amber-ink);
  background-image: radial-gradient(115% 150% at 50% 50%, #FFCB68 0%, #F7A72C 54%, #E08E0D 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,.55),
    inset 0 0 var(--glow) 0 rgba(255,255,255,.30),
    inset 0 0 0 var(--rim) rgba(255,255,255,.10),
    0 0 0 1px rgba(0,0,0,.48),
    0 0 30px -8px rgba(0,0,0,.70);
}

.btn-chrome {
  color: #14171D;
  background-image: radial-gradient(115% 150% at 50% 50%, #FFFFFF 0%, #EDEDEA 54%, #D2D2CC 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,.95),
    inset 0 0 var(--glow) 0 rgba(255,255,255,.70),
    inset 0 0 0 var(--rim) rgba(255,255,255,.35),
    0 0 0 1px rgba(0,0,0,.42),
    0 0 30px -8px rgba(0,0,0,.66);
}

/* Nothing travels vertically, which would reintroduce a light direction. The
   states work the light instead: it blooms as the face comes toward the
   viewer, contracts as it is pushed away. */
.btn-amber:hover, .btn-chrome:hover { --glow: 26px; filter: brightness(1.05); }

.btn-amber:active, .btn-chrome:active {
  --glow: 7px;
  transform: scale(.988);
  filter: brightness(.94);
}

/* The global focus ring is amber, which is invisible on an amber button. These
   sit on light faces, so the ring goes outside, onto the dark page. */
.btn:focus-visible { outline: 3px solid var(--bone); outline-offset: 3px; }

/* Brand marks sit inside the label, so they scale with the button's own type
   rather than needing a size per variant. currentColor keeps them on the right
   ink for whichever face they land on. */
.btn-ico { width: 1.2em; height: 1.2em; fill: currentColor; flex: none; }

.btn-lg { font-size: 15px; padding: 17px 32px; }

/* `.drawer a` is more specific than `.btn-amber`, so it wins the colour cascade
   and leaves that CTA label grey on amber. Take it back explicitly rather than
   with an inline style. `.nav-links a.btn` is a guard: the nav CTA sits outside
   `.nav-links` today, but it lived inside it until recently and had the same
   defect there. */
.nav-links a.btn, .drawer a.btn { color: var(--amber-ink); }

/* The nav CTA is half the height of the hero pair, so the glow and rim scale
   down with it or they swallow the face. */
.nav-cta { --glow: 10px; --rim: 4px; }
.nav-cta:hover { --glow: 16px; }
.nav-cta:active { --glow: 5px; }

/* ===========================================================================
   NAV
   =========================================================================== */
.nav {
  position: sticky; top: 0; z-index: 60;
  height: var(--nav-h);
  background: rgba(7,9,14,.82);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--edge-soft);
}
.nav-in {
  width: var(--shell); margin-inline: auto; height: 100%;
  display: flex; align-items: center; gap: 24px;
}
.brand { flex: none; display: block; }
.brand img { height: 34px; width: auto; }

/* Rule between the mark and the tabs, so the two read as one left-hand group. */
.nav-rule { flex: none; width: 1px; height: 26px; background: var(--edge); }

.nav-links { display: flex; align-items: center; gap: 26px; }
.nav-links a {
  text-decoration: none;
  font-family: var(--data);
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color .18s var(--ease);
}
.nav-links a:hover, .nav-links a[aria-current="true"] { color: var(--bone); }

.nav-social { display: flex; gap: 14px; align-items: center; margin-left: auto; }
.nav-social a { color: var(--muted); display: grid; place-items: center; transition: color .18s var(--ease); }
.nav-social a:hover { color: var(--bone); }
.nav-social svg { width: 17px; height: 17px; fill: currentColor; }

.nav-cta { padding: 11px 20px; font-size: 12.5px; }

/* mobile drawer trigger */
.nav-burger {
  display: none;
  margin-left: auto;
  width: 42px; height: 42px;
  background: transparent; border: 1px solid var(--edge);
  color: var(--bone); cursor: pointer;
  place-items: center; border-radius: var(--r);
}
.nav-burger svg { width: 18px; height: 18px; fill: currentColor; }

.drawer {
  position: fixed; inset: var(--nav-h) 0 auto 0; z-index: 55;
  background: var(--stone);
  border-bottom: 1px solid var(--edge);
  padding: 10px 0 22px;
  display: none;
}
.drawer.open { display: block; }
.drawer a {
  display: block; padding: 15px 5vw;
  font-family: var(--data); font-size: 13px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--bone); text-decoration: none;
  border-bottom: 1px solid var(--edge-soft);
}
.drawer .drawer-cta { margin: 18px 5vw 0; width: calc(100% - 10vw); }

/* Divider between one block of the page and the next. Same construction as the
   rule under the hero mark: a hairline that carries its own light, rather than
   a flat border that would read as another edge of the band above it. */
.sec-rule {
  width: var(--shell); height: 1px;
  margin: clamp(46px, 5.4vw, 78px) auto 0;
  background: linear-gradient(90deg,
    transparent 0%, var(--edge) 16%, var(--edge) 84%, transparent 100%);
}
.sec-rule + section { padding-top: clamp(46px, 5.4vw, 78px); }

/* ===========================================================================
   HERO
   =========================================================================== */
.hero {
  position: relative;
  min-height: calc(100dvh - var(--nav-h));
  display: flex; flex-direction: column; justify-content: flex-end;
  overflow: hidden;
  isolation: isolate;
}

.hero-stage { position: absolute; inset: 0; z-index: -2; background: #05070B; }
.hero-stage video,
.hero-stage img.hero-fallback {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .9s var(--ease);
}
.hero-stage video.is-live,
.hero-stage img.hero-fallback.is-live { opacity: 1; }

/* The tube. Opaque, and painted straight over whichever of the poster and the
   video is showing, so the plain footage underneath is already the fallback:
   without WebGL, or under reduced motion, this element is never created. It
   fades in on its first drawn frame rather than flashing black first. */
.crt {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity .45s var(--ease);
}
.crt.is-live { opacity: 1; }

/* Centred, since the hero copy is centred. Darkest through the middle where the
   logo sits, easing off at the edges so the footage still reads. Plus a bottom
   fade into the stat band. */
.hero-scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(115% 85% at 50% 46%, rgba(5,7,11,.56) 0%, rgba(5,7,11,.74) 42%,
                    rgba(5,7,11,.88) 78%, rgba(5,7,11,.95) 100%),
    linear-gradient(to bottom, rgba(5,7,11,.70) 0%, transparent 24%,
                    transparent 54%, rgba(5,7,11,.93) 100%);
}

.hero-in {
  width: var(--shell); margin-inline: auto;
  padding: clamp(56px, 8vh, 96px) 0 clamp(40px, 6vh, 64px);
  max-width: var(--shell);
  flex: 1;
  display: flex; flex-direction: column; justify-content: center;
  align-items: center;
  text-align: center;
}

.hero-logo { width: min(620px, 82vw); height: auto; margin: 0 auto 30px;
  filter: drop-shadow(0 8px 34px rgba(0,0,0,.8)); }

/* Rule between the mark and the buttons. A flat hairline in --edge disappears
   against moving footage, so it carries its own light: bone at the shoulders,
   a touch of the accent at the centre, out to nothing at both ends. */
.hero-rule {
  display: block;
  width: min(240px, 46vw); height: 1px;
  margin: 0 auto 30px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(234,232,227,.30) 24%,
    rgba(251,174,60,.62) 50%,
    rgba(234,232,227,.30) 76%,
    transparent 100%);
}

/* ---------- Hero figure ----------
   Sits at the same layer as the scrim but later in the DOM, so the footage
   behind it stays dimmed while the archer himself does not.

   The right edge is pinned to the logo's left edge: the logo is
   min(620px, 82vw) wide and centred, so half of it is min(310px, 41vw). The
   figure grows leftwards out of frame from there and never crosses the copy,
   whatever the viewport does. A background image rather than an <img> so the
   220KB is not downloaded at the widths where the rule below hides it. */
.hero-figure {
  position: absolute; z-index: -1;
  bottom: 0;
  right: calc(50% + min(310px, 41vw));

  /* --gutter is what is left between the logo and the left edge. Sizing off it
     shows 78% of the figure and runs the rest off frame, which is the most that
     can be cropped before the frame edge reaches his face: the ponytail starts
     12% in, the face at 19%. So he shrinks on narrow screens rather than
     cropping closer, and is never a headless torso in the corner. */
  --gutter: calc(50vw - min(310px, 41vw));
  width: calc(var(--gutter) / .78);
  aspect-ratio: 1300 / 1374;
  max-height: min(84%, 780px);

  background: url("assets/img/hero-archer.webp") no-repeat bottom right / contain;
  pointer-events: none;
  will-change: transform;
}

/* Sinks away as the hero leaves, so the layer separates from the footage. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-figure {
      animation: hero-figure-drift linear both;
      animation-timeline: view();
      animation-range: exit 0% exit 100%;
    }
  }
}
@keyframes hero-figure-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-46px, 34px, 0); }
}

.hero-cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 0; justify-content: center; }

/* ---------- HUD stat band, fused to the hero's bottom edge ---------- */
.hud {
  position: relative; z-index: 2;
  border-top: 1px solid var(--edge);
  background: rgba(7,9,14,.72);
  backdrop-filter: blur(10px);
}
/* Five equal cells across the full viewport, not the shell — this band is the
   one full-bleed element on the page, which suits it: it is the instrument
   panel under the hero rather than another column of the document. Each cell is
   read top down — what it measures, then the figure — because the label is the
   question and a number without one is noise. The dividers are cell borders, so
   they run the full height of the row and the band reads as a panel rather than
   as a toolbar. */
.hud-in {
  width: 100%; margin: 0;
  display: grid; grid-template-columns: repeat(4, 1fr);
}
/* Both sides padded the same, so the first cell's left padding is the band's own
   gutter and no cell needs a special case.

   The label sits beside the figure rather than over it, in a fixed narrow column
   that wraps it to a word a line. Fixed rather than content-sized on purpose:
   auto columns would each take their own width and the five figures would start
   at five different offsets across the band, which is the one thing a row of
   readouts cannot do. The rule spans both columns underneath. */
.stat {
  position: relative;
  padding: 26px clamp(16px, 2.2vw, 34px) 22px;
  display: grid;
  grid-template-columns: 70px minmax(0, 1fr);
  column-gap: clamp(10px, 1vw, 15px);
  row-gap: 16px;
  align-items: center;
}
.stat-wire { grid-column: 1 / -1; }
/* Gradient hairlines, the same construction as the hero and section rules, so
   the dividers fade out at both ends instead of butting into the band's edges. */
.stat + .stat::before {
  content: ""; position: absolute; left: 0; top: 8%; bottom: 8%; width: 1px;
  background: linear-gradient(to bottom, transparent, var(--edge) 20%, var(--edge) 80%, transparent);
}
.stat-l {
  margin: 0;
  font-family: var(--data); font-size: 10.5px; letter-spacing: .19em;
  line-height: 1.5;
  text-transform: uppercase; color: var(--muted);
}

.stat-fig { margin: 0; min-width: 0; }

.stat-n {
  font-family: var(--data); font-weight: 700;
  /* Tabular figures matter twice over here: the columns line up, and the digits
     do not jitter while the count runs. */
  font-variant-numeric: tabular-nums;
  font-size: clamp(27px, 2.9vw, 42px);
  line-height: 1; letter-spacing: -.04em;
  color: var(--amber);
  display: flex; align-items: baseline;
}
/* `min` is a unit and steps back from the figure. `M+` is a magnitude — part of
   the number itself — so it stays at the figure's size and colour and sits
   tight against the digits. */
.stat-n .u { font-size: .42em; color: var(--muted); letter-spacing: 0; margin-left: 3px; }

/* The wire. A dim line the width of the cell with a single bright packet running
   along it — three channels reading out side by side, the reason the figures
   above them look live. Deliberately not a chart: no axis and no scale, so there
   is nothing here to mistake for data the page does not have.

   The three run out of phase through negative delays, which also start them
   mid-travel rather than all leaving the gate together on load. */
.stat-wire {
  position: relative; overflow: hidden;
  height: 1px; background: var(--edge-soft);
}
.stat-wire::after {
  content: ""; position: absolute; inset-block: 0; left: 0; width: 30%;
  background: linear-gradient(90deg, transparent, var(--amber), transparent);
}
@media (prefers-reduced-motion: no-preference) {
  .stat-wire::after { animation: wire 3.4s linear infinite; }
  .stat:nth-child(2) .stat-wire::after { animation-delay: -.85s; }
  .stat:nth-child(3) .stat-wire::after { animation-delay: -1.7s; }
  .stat:nth-child(4) .stat-wire::after { animation-delay: -2.5s; }
}
/* From fully off the left of the cell to fully off the right: the element is
   30% of the cell, so 100% of the cell is 333% of itself. */
@keyframes wire {
  from { transform: translateX(-100%); }
  to   { transform: translateX(333%); }
}

/* Four across holds to about 1060px and then runs out — and it is the label
   that binds, not the figure, so shrinking the numbers would not buy anything.
   Four is even, so the step down is a plain two by two with no orphan slot to
   design around; below 860 the band stacks outright.

   The dividers turn with the grid at each step. Here that means a side rule
   only on the right-hand cell of each row, and one across the top of the
   second. */
@media (min-width: 861px) and (max-width: 1060px) {
  .hud-in { grid-template-columns: repeat(2, 1fr); }
  .stat:nth-child(odd)::before { content: none; }
  .stat:nth-child(n+3)::after {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px;
    background: linear-gradient(to right, var(--edge), transparent 78%);
  }
}

/* ===========================================================================
   LATEST RELEASE
   Media column carries a main capture with a gallery strip beneath it; the copy
   sits alongside. Clicking a thumb swaps the main image.
   =========================================================================== */
.release { border-bottom: 1px solid var(--edge-soft); }

.release-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.62fr) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 60px);
  align-items: start;
}

.release-main {
  margin: 0;
  position: relative;
  border: 1px solid var(--edge);
  background: var(--stone);
  overflow: hidden;
}
.release-main img {
  width: 100%; height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
  transition: opacity .5s var(--ease);
}
/* One frame at zero as the src is swapped, so the change reads as a dissolve
   rather than a cut. */
.release-main img.is-swapping { opacity: 0; transition: none; }

/* The clip sits over its own first frame, so there is nothing to see until it
   has something to show and nothing missing if it never does. */
.release-clip {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity .45s var(--ease);
  pointer-events: none;
}
.release-clip.is-live { opacity: 1; }

.release-gallery {
  list-style: none;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 10px;
  margin: 10px 0 0;
  padding: 0;
}
.rel-thumb {
  display: block; width: 100%; padding: 0;
  border: 1px solid var(--edge);
  background: var(--stone);
  border-radius: var(--r);
  cursor: pointer;
  overflow: hidden;
  transition: border-color .18s var(--ease), transform .18s var(--ease);
}
/* height:auto for the same reason the champion cards need it: the width and
   height attributes would otherwise beat aspect-ratio. */
.rel-thumb img {
  width: 100%; height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
  opacity: .62;
  transition: opacity .18s var(--ease);
}
.rel-thumb:hover img, .rel-thumb:focus-visible img { opacity: 1; }
.rel-thumb:hover { border-color: var(--muted); transform: translateY(-2px); }
.rel-thumb.is-on { border-color: var(--amber); }
.rel-thumb.is-on img { opacity: 1; }

/* The one thumb that is footage rather than a still says so. A triangle in a
   disc, drawn rather than shipped, sitting on the art at low contrast until the
   slide is the live one. */
.rel-thumb.is-clip { position: relative; }
.rel-thumb.is-clip::after {
  content: "";
  position: absolute; top: 50%; left: 50%;
  width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50%;
  background: rgba(7, 9, 14, .58) no-repeat center / 8px 9px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 9'%3E%3Cpath d='M1 0.6 7 4.5 1 8.4Z' fill='%23fff'/%3E%3C/svg%3E");
  background-position: 55% 50%;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .22);
  opacity: .82;
  transition: opacity .18s var(--ease);
  pointer-events: none;
}
.rel-thumb.is-clip.is-on::after,
.rel-thumb.is-clip:hover::after { opacity: 1; }

.release-copy { padding-top: 4px; }
.release-date {
  font-family: var(--data); font-size: 11px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--amber);
  margin: 0 0 12px;
}
/* The date and the build string: facts, so they are set as data, and the
   version keeps its own case because "V.7.31.26.3p" is a name, not a label. */
.release-meta {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px 14px;
  font-family: var(--data); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
  margin: 14px 0 0;
}
.release-meta .release-ver {
  text-transform: none; letter-spacing: .08em;
  color: color-mix(in srgb, var(--muted) 78%, transparent);
}
.release-link {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 20px;
  font-family: var(--data); font-size: 12px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--amber); text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
  padding-bottom: 3px;
}
.release-link:hover { border-bottom-color: var(--amber); }

@media (max-width: 1000px) {
  .release-grid { grid-template-columns: 1fr; }
  .release-copy { order: -1; }
}
@media (max-width: 560px) {
  .release-gallery { gap: 7px; }
  .rel-thumb.is-clip::after { width: 18px; height: 18px; margin: -9px 0 0 -9px; }
}

/* ===========================================================================
   SCENE DECORATIONS
   The islands, champions and clouds that used to stack in the hero are spread
   down the page instead, one or two per section, held in the margins outside
   the content column. Each drifts as its section passes through the viewport.

     --deco-img   the artwork
     --deco-w     width, as a share of the viewport
     --deco-from  where it starts, relative to its resting position
     --deco-to    where it ends

   A view() timeline drives each one against its own progress through the
   viewport rather than the document, so they stay independent of each other and
   of page length. No scroll handler, nothing on the main thread.
   =========================================================================== */
section { position: relative; }
section > .shell { position: relative; z-index: 1; }

.deco {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  width: var(--deco-w, 18vw);
  aspect-ratio: var(--deco-ar, 1);
  background-image: var(--deco-img);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: center;
  opacity: var(--deco-op, .5);
  will-change: transform;
}

/* Clouds keep their black backing and screen-blend it away, the same as they
   did in the hero. On a near-black page that also means they read as light
   rather than as a grey box. */
.deco-cloud-a, .deco-cloud-b { mix-blend-mode: screen; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .deco {
      animation: deco-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
  }
}
@keyframes deco-drift {
  from { transform: translate3d(0, var(--deco-from, 70px), 0); }
  to   { transform: translate3d(0, var(--deco-to, -70px), 0); }
}

/* Positions are chosen so each piece sits beside a section's empty space rather
   than behind its copy. Measured after placing: text over every decoration
   clears WCAG AA. */
.deco-torii {
  --deco-img: url("assets/img/parallax/island-torii.webp");
  --deco-w: 24vw; --deco-ar: 1.45; --deco-op: .30;
  left: -11vw; top: 46%;                 /* beside the demo box, below the lede */
}
.deco-cloud-a {
  --deco-img: url("assets/img/parallax/cloud-far.webp");
  --deco-w: 20vw; --deco-ar: 1.75; --deco-op: .42;
  --deco-from: 110px; --deco-to: -110px;
  right: -6vw; top: 26%;
}
.deco-tech {
  --deco-img: url("assets/img/parallax/island-tech.webp");
  --deco-w: 22vw; --deco-ar: 2.5; --deco-op: .34;
  right: -9vw; top: 3%;
}
.deco-cloud-b {
  --deco-img: url("assets/img/parallax/cloud-near.webp");
  --deco-w: 24vw; --deco-ar: 1.76; --deco-op: .40;
  --deco-from: 130px; --deco-to: -130px;
  right: -8vw; bottom: 4%;
}
/* The studio's left column ends well above the right one, so the brawler goes
   into that gap rather than over the bios, where its fire glow was dropping
   body copy to 2.9:1. */
.deco-brawler {
  --deco-img: url("assets/img/parallax/fighter-brawler.webp");
  --deco-w: 15vw; --deco-ar: .9; --deco-op: .46;
  --deco-from: 90px; --deco-to: -90px;
  left: 2vw; bottom: 10%;
}

/* ---------- switched off ----------
   The hero figure and the side decorations are held back for now. Everything
   above stays as it was and the artwork stays in assets/, so deleting these two
   lines brings the whole scene back exactly as it was measured and placed.
   Both are background images on hidden boxes, so nothing downloads meanwhile. */
.hero-figure { display: none; }
.deco { display: none; }

/* ===========================================================================
   WHAT MAKES IT DIFFERENT
   One two-column reading rather than a head with a row of three under it: the
   argument and the stage it is made on down the left, the three points down
   the right. The section is named for the edge of the stage, so the stage is
   the illustration — an object on the page here, and the same place in play in
   the band below.

   No figures anywhere: an earlier version carried per-hit percentages that had
   no source, and nothing has replaced them.
   =========================================================================== */
.kb { border-bottom: 1px solid var(--edge-soft); }

/* Copy against the figure across the top, the three points as a row beneath
   them. Stacking the points down a second column made the section as tall as
   its two columns put together; across the foot they cost one band of height
   instead of three. */
.kb-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, .74fr);
  gap: clamp(24px, 3.4vw, 60px);
  align-items: center;
}

.kb-head .h-sec { margin-top: 12px; max-width: 15ch; }
.kb-head .lede { max-width: 44ch; }

/* Centred on the copy rather than set to the foot of it: the figure is a
   portrait where the island was a landscape, so it reads as standing beside the
   argument instead of sitting under it. */
.kb-fig {
  width: 100%; height: auto;
  justify-self: end;
  max-width: 460px;
}

.pillars {
  grid-column: 1 / -1;
  margin-top: clamp(30px, 3.8vw, 58px);
  display: grid; grid-template-columns: repeat(3, 1fr);
}
.pillar {
  padding: clamp(18px, 2vw, 26px) clamp(18px, 2.2vw, 34px) 0;
  border-top: 1px solid var(--edge);
  border-left: 1px solid var(--edge-soft);
}
.pillar:first-child { border-left: 0; padding-left: 0; }
.pillar:last-child { padding-right: 0; }

.pillar-l {
  margin: 0 0 14px;
  font-family: var(--data); font-size: 10px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--muted);
}

.pillar h3 {
  margin: 0 0 12px;
  font-size: clamp(19px, 1.7vw, 23px); font-weight: 900;
  letter-spacing: -.02em; line-height: 1.15;
  text-wrap: balance;
}
.pillar p { margin: 0; color: var(--muted); font-size: 15.5px; line-height: 1.7; max-width: 48ch; }

/* ===========================================================================
   THE KICK
   A short band between sections. One fighter has just launched the other, who
   travels off frame as the band scrolls past.

   It does not take the scroll over. There is no sticky stage and nothing pins;
   the band scrolls at the same rate as everything else and the flight is driven
   by its own passage through the viewport. `contain` on a subject shorter than
   the scrollport is exactly the window where the whole band is visible, so the
   flight starts as it clears the bottom edge and finishes as it reaches the top.
   No scroll handler, and only `transform` animates.

   The two never touch. The kicker is planted and completely still; the hit has
   already happened, and what you are watching is the aftermath.

   The plate is a capture from the game, sky included, so the whole scene is one
   photograph with two cutouts on it. Both fighters are placed in percentages of
   the plate, never of the viewport, so the picture scales as one thing.
   =========================================================================== */
.kick {
  /* The plate is 1.6842:1 and its standing plane sits 57.5% down, so the grass
     is 0.3414 x the plate's width below its top edge. Pinning that to --grass
     is what keeps the framing identical at every size: pick how wide the plate
     is and where the ground should sit, and the rest follows. */
  --plate-w: 100vw;
  --plate-x: 0vw;
  --grass: 72%;

  position: relative;
  /* The `contain` range is the viewport minus the band, so a band that nearly
     fills a short window would play the whole flight in a few dozen pixels of
     scroll. The vh term only bites there. */
  height: min(clamp(300px, 32vw, 520px), 62vh);
  padding: 0;
  overflow: hidden;
  /* Behind the plate, matching its haze — insurance against a sub-pixel seam at
     the band's edges, never actually seen. */
  background: #C9D8E2;
  view-timeline: --kick block;
}

.kick-ground {
  position: absolute;
  left: var(--plate-x);
  width: var(--plate-w);
  top: calc(var(--grass) - var(--plate-w) * 0.3414);
}
.kick-plate { display: block; width: 100%; height: auto; }

/* Feet on the standing plane, 57.5% down the plate, and out at the lip of the
   platform — the grass ends at 58.2%, so his standing foot at 48% puts him a
   stride from the drop and his reach right over it. */
.kick-a {
  position: absolute; left: 38.55%; bottom: 42.5%;
  width: 21%; height: auto;
  filter: drop-shadow(0 14px 22px rgba(20,40,60,.30));
}

/* Airborne, past the lip, and already clear of the kicker's reach before he has
   travelled at all. */
.kick-b {
  position: absolute; left: 64%; bottom: 48%;
  width: 19%; height: auto;
  filter: drop-shadow(0 20px 34px rgba(20,40,60,.32));
  will-change: transform;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .kick-b {
      animation: kick-fly linear both;
      animation-timeline: --kick;
      /* `cover` runs from the instant the band's top edge crosses the bottom of
         the viewport to the instant its bottom edge leaves the top, so the
         flight is already under way the moment any of the band is on screen and
         is exactly halfway when the band is centred. `contain` waited for the
         whole band to be visible first, which on a short band is most of the
         way up the screen. */
      animation-range: cover;
    }
  }
}

/* Distance in percentages of his own width, so the same short arc holds at
   every size and he is still fully in frame at the end of it. Rotation and a
   shallow rise carry the read: a flat slide looks like a cutout being pushed
   sideways, an arc looks like something that was hit. */
@keyframes kick-fly {
  from { transform: translate(0, 0) rotate(-6deg); }
  to   { transform: translate(55%, -22%) rotate(14deg); }
}

/* A portrait viewport shows two thirds of the plate, so something has to go.
   It is the torii, off to the left: the frame is pulled right to hold the lip
   of the platform and both fighters through the whole flight, which is the
   part carrying the idea. The band shortens to match.

   Keyed on shape rather than width, because a tablet held upright and a
   browser window of the same width need opposite treatments. */
@media (max-aspect-ratio: 1 / 1) {
  .kick {
    --plate-w: 150vw;
    --plate-x: -48vw;
    height: clamp(240px, 66vw, 440px);
  }
}

/* ===========================================================================
   ROSTER
   =========================================================================== */

/* ---------- the tape ----------
   A continuous run of narrow cards, full-bleed so it reads as something passing
   through the page rather than a box sitting on it. It crawls on its own and
   stops under the pointer; app.js drives the offset and wraps it modulo one
   pass of the roster.

   The open card's panel is an overlay, not an expansion, because the track's
   layout width has to stay constant for that wrap to stay exact. */
.tape {
  --tape-w: 200px;
  --tape-h: 620px;
  --tape-gap: 10px;
  --tp-w: 520px;          /* keep TAPE_PANEL_W in app.js in step with this */
  margin-top: 34px;
}
.tape-view {
  overflow: hidden;
  cursor: grab;
  touch-action: pan-y;          /* horizontal is ours, vertical stays the page's */
  user-select: none; -webkit-user-select: none;
}
.tape-view.is-dragging { cursor: grabbing; }

.tape-track { display: flex; align-items: stretch; will-change: transform; }

.tape-item {
  position: relative;
  flex: none;
  width: var(--tape-w); height: var(--tape-h);
  margin-right: var(--tape-gap);   /* margin, not gap: one pass has to tile exactly */
}
.tape-item.is-open { z-index: 6; }

/* At rest a card is the portrait and nothing else. The name arrives with the
   hover, along with the panel. */
.ti-card {
  position: relative; display: block;
  width: 100%; height: 100%;
  padding: 0; overflow: hidden;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--stone);
  font: inherit; color: inherit; text-align: left;
  cursor: inherit;
  transition: border-color .2s var(--ease);
}
/* Full bleed. The sources are square, so filling a 1:3 card shows a third of
   their width: measured across the roster, that is about the narrowest crop
   that still holds a whole face, which is what sets the card's 200px width. */
.ti-card img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 50%;
  background: var(--stone-2);
  filter: saturate(.92) brightness(.84);
  transition: filter .3s var(--ease), transform .5s var(--ease);
}
.ti-card:hover img, .ti-card:focus-visible img,
.tape-item.is-open .ti-card img { filter: none; transform: scale(1.03); }
.tape-item.is-open .ti-card { border-color: var(--accent, var(--amber)); }

/* Accent edge, also held back until the card is the one being read. */
.ti-card::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--accent, var(--amber));
  opacity: 0; transition: opacity .2s var(--ease);
}

.ti-label {
  position: absolute; inset: auto 0 0 0;
  padding: 44px 14px 14px;
  background: linear-gradient(to top, rgba(7,9,14,.94) 0%, rgba(7,9,14,.72) 46%, rgba(7,9,14,0) 100%);
  opacity: 0; transition: opacity .2s var(--ease);
}
.ti-name { display: block; font-size: 17px; font-weight: 900; letter-spacing: -.02em; line-height: 1.15; }
.ti-role {
  display: block; margin-top: 5px;
  font-family: var(--data); font-size: 9px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
}
/* Roster number, the way a strip of film carries frame numbers. */
.ti-no {
  position: absolute; top: 11px; right: 12px;
  font-family: var(--data); font-size: 9.5px; letter-spacing: .12em;
  color: var(--bone);
  opacity: 0; transition: opacity .2s var(--ease);
  text-shadow: 0 1px 6px rgba(0,0,0,.9);
}
.ti-card:hover .ti-label, .ti-card:focus-visible .ti-label,
.tape-item.is-open .ti-label,
.ti-card:hover .ti-no, .ti-card:focus-visible .ti-no,
.tape-item.is-open .ti-no,
.ti-card:hover::after, .ti-card:focus-visible::after,
.tape-item.is-open .ti-card::after { opacity: 1; }

/* ---------- the open card's panel ---------- */
.tape-panel {
  position: absolute; top: 0; left: 100%; right: auto;
  width: var(--tp-w);
  /* Hugs its kit rather than matching the card, so a three ability champion
     does not open a panel two thirds empty. */
  height: auto; max-height: 100%;
  z-index: 6;
  padding: 22px 24px 24px;
  background: var(--stone-2);
  border: 1px solid var(--edge);
  border-top: 3px solid var(--accent, var(--amber));
  box-shadow: 0 26px 64px -30px #000;
  overflow-y: auto; overscroll-behavior: contain;
  text-align: left;
  animation: tp-in .18s var(--ease) both;
}
/* app.js sets `left` in pixels from the measured rects; this only picks which
   side the entrance comes from, so a panel that opened leftwards does not slide
   in from under the card. */
.tape-panel.is-left { animation-name: tp-in-left; }
@keyframes tp-in      { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }
@keyframes tp-in-left { from { opacity: 0; transform: translateX(8px); }  to { opacity: 1; transform: none; } }

/* The portrait, square and uncropped, beside the identity: the tape card can
   only ever show a third of its width, so this is where it is actually seen. */
.tp-head { display: flex; align-items: center; gap: 16px; }
.tp-face {
  flex: none; width: 84px; height: 84px;
  aspect-ratio: 1; object-fit: cover;
  border: 1px solid var(--edge);
  border-top: 2px solid var(--accent, var(--amber));
  background: var(--stone);
}
.tp-id { min-width: 0; }
.tp-id b { display: block; font-size: 23px; font-weight: 900; letter-spacing: -.02em; line-height: 1.1; }
.tp-id span, .tp-id i {
  display: flex; align-items: center; gap: 6px;
  font-style: normal; font-family: var(--data);
  letter-spacing: .16em; text-transform: uppercase;
}
.tp-id span { margin-top: 8px; font-size: 10.5px; color: var(--accent, var(--amber)); }
.tp-id i { margin-top: 5px; font-size: 9.5px; color: var(--muted); }
.tp-id svg { width: 12px; height: 12px; fill: currentColor; flex: none; opacity: .85; }
.tp-lore { margin: 12px 0 12px; font-size: 13px; line-height: 1.56; color: var(--muted); }

/* One column now that the card is tall enough to hold a full kit in one.
   Measured across the roster: the longest comes to well under the card height. */
.tp-kit { border-top: 1px solid var(--edge-soft); padding-top: 4px; }

/* Same ability markup as the dialog, at tape scale. The passive and the ultimate
   carry a glyph rather than a letter: they are not keys pressed in sequence, and
   marking them apart is what makes the numbered slots read as a sequence. */
.tape-panel .ab { gap: 13px; padding: 7px 0; border-bottom: 1px solid var(--edge-soft); }
.tape-panel .ab:last-child { border-bottom: 0; }
.tape-panel .ab-key { width: 27px; height: 27px; font-size: 10px; }
.tape-panel .ab-key svg { width: 14px; height: 14px; fill: currentColor; }
.tape-panel .ab-passive .ab-key { color: var(--muted); }
.tape-panel .ab-ult .ab-key {
  background: var(--accent, var(--amber)); border-color: var(--accent, var(--amber));
  color: #0C0A06;
}
.tape-panel .ab-name { font-size: 9px; margin-bottom: 3px; }
.tape-panel .ab p { font-size: 12.5px; line-height: 1.5; }

/* ---------- champion dialog ---------- */
.cd-backdrop {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(4,6,10,.86);
  backdrop-filter: blur(8px);
  display: none; place-items: center;
  padding: 20px;
}
.cd-backdrop.open { display: grid; }
.cd {
  background: var(--stone); border: 1px solid var(--edge);
  border-radius: var(--r);
  max-width: 740px; width: 100%; max-height: 86vh;
  overflow-y: auto; position: relative;
}
.cd-top {
  display: flex; gap: 20px; padding: 26px; align-items: center;
  border-bottom: 1px solid var(--edge);
  background: linear-gradient(105deg, color-mix(in srgb, var(--accent) 20%, transparent), transparent 62%);
}
.cd-top img {
  width: 104px; height: 104px; object-fit: cover; flex: none;
  border: 1px solid var(--edge); border-radius: var(--r);
}
.cd-title h3 { margin: 0 0 6px; font-size: 28px; font-weight: 900; letter-spacing: -.03em; }
.cd-title .role {
  font-family: var(--data); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--accent);
}
.cd-title .world {
  font-family: var(--data); font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted); margin-top: 8px;
}
.cd-lore {
  padding: 22px 26px; color: var(--muted); font-size: 15.5px;
  line-height: 1.7; border-bottom: 1px solid var(--edge); margin: 0;
}
.cd-kit { padding: 6px 26px 26px; }
.ab { display: flex; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--edge-soft); }
.ab:last-child { border-bottom: 0; }
.ab-key {
  font-family: var(--data); font-size: 11px; font-weight: 700;
  flex: none; width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: var(--r); border: 1px solid var(--edge);
  color: var(--accent); background: var(--void);
}
.ab-key.ult { background: var(--accent); color: #0C0A06; border-color: var(--accent); }
.ab-name {
  font-family: var(--data); font-size: 9.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.ab p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--bone); opacity: .92; }
.cd-close {
  position: absolute; top: 16px; right: 16px;
  width: 36px; height: 36px;
  border-radius: var(--r); border: 1px solid var(--edge);
  background: var(--void); color: var(--bone); cursor: pointer;
  display: grid; place-items: center;
}
.cd-close svg { width: 15px; height: 15px; fill: currentColor; }
.cd-close:hover { border-color: var(--bone); }

/* ===========================================================================
   MODES  (three equal upright frames, one per way to play)
   =========================================================================== */
.modes {
  margin-top: clamp(38px, 4.6vw, 60px);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
/* The frames are the same width because the columns are equal, and the same
   height because the grid stretches them. The shot sets the top of each one and
   the words sit under it, so nothing is ever printed over the picture — which
   also means there is no scrim to tune and no contrast to measure. */
.mode {
  position: relative; overflow: hidden;
  border: 1px solid var(--edge);
  background: var(--stone);
  border-radius: var(--r);
  display: flex; flex-direction: column;
}
/* 4:3 keeps the whole shot top to bottom and takes the ~29% off the sides that
   an upright frame needs. The sources are 1.87:1, so cropping the other way
   would have cut the action out of the top and bottom of every one. */
.mode-media {
  aspect-ratio: 4 / 3;
  background: var(--void);
  overflow: hidden;
  border-bottom: 1px solid var(--edge);
}
.mode-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

.mode-txt { padding: 22px clamp(20px, 1.7vw, 26px) 26px; }
.mode h3 { margin: 0 0 8px; font-size: clamp(19px, 1.6vw, 22px); font-weight: 700; letter-spacing: -.02em; }
.mode p { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.6; }

/* Progression is a note on all three, so it sits under them as a line rather
   than in a frame of its own competing with the modes for the same attention.
   Full width, like the section's own lede above it. */
.modes-note {
  margin: clamp(18px, 2vw, 26px) 0 0;
  color: var(--muted);
  font-size: 15px; line-height: 1.7;
  text-wrap: pretty;
}
.modes-note strong { color: var(--bone); font-weight: 700; }

/* This section argues across the full measure rather than in a narrow column:
   the frames under it are full width, so the words that introduce them are too. */
#modes .lede { max-width: none; }

/* ===========================================================================
   BADGES
   Twenty-five tiles, so the section earns its place by being dense: a small
   piece of art, a name, and the rarity tier, in as many columns as the width
   allows. Anything more per tile — the description, the win counts — and it
   stops being a wall of badges and becomes a table of twenty-five rows.

   Grouped by how they are won, because that is the only ordering that carries
   meaning: the ranked set is a ladder, the summer set is closed.
   =========================================================================== */
.badges { border-top: 1px solid var(--edge-soft); }

.badge-head .h-sec { margin-top: 12px; max-width: 20ch; }

.badge-set { margin-top: clamp(38px, 4.6vw, 64px); }

/* Name, then what wins it, with the count set hard right against the rule. */
.badge-set-h {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline; column-gap: 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--edge);
}
.badge-set-h h3 {
  margin: 0;
  font-family: var(--data); font-size: 11px; letter-spacing: .24em;
  font-weight: 400; text-transform: uppercase; color: var(--amber);
}
.badge-set-h p { margin: 0; color: var(--muted); font-size: 14px; }
.badge-set-n {
  font-family: var(--data); font-size: 11px; letter-spacing: .12em;
  color: var(--muted); font-variant-numeric: tabular-nums;
}

/* auto-fill rather than a column count: the sets are 15, 7 and 3, and any fixed
   count leaves one of them ragged. The tiles keep their size and the row simply
   takes as many as fit. */
.badge-grid {
  list-style: none; margin: 0; padding: clamp(20px, 2.4vw, 30px) 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: clamp(18px, 2vw, 28px) clamp(10px, 1.4vw, 18px);
}
.badge { text-align: center; }
.badge img {
  width: clamp(60px, 6vw, 84px); height: auto;
  margin: 0 auto 10px;
  /* The art is a lit gem on a dark plate and already reads as an object, so it
     needs no frame. The shadow only stops it floating on the page ground. */
  filter: drop-shadow(0 6px 14px rgba(0,0,0,.55));
}
.badge-n {
  margin: 0;
  font-size: 12.5px; line-height: 1.35; font-weight: 500;
  color: var(--bone);
  text-wrap: balance;
}

/* ===========================================================================
   STUDIO
   Two people and no photographs of them, so the section is built as credits
   rather than as a team grid: an index, a name at display weight, the role in
   mono, and the bio in its own column. The rules carry the structure.
   =========================================================================== */
.studio { border-top: 1px solid var(--edge-soft); }
/* The description runs the full shell rather than a reading measure. It is
   three sentences, so at this width it sets to two or three long lines instead
   of a narrow column in an otherwise empty row — scoped here rather than on
   `.lede`, since every other one on the page is deliberately measured. */
.studio-head { max-width: none; }
.studio-head .h-sec { margin-top: 12px; }
.studio-head .lede { max-width: none; }

/* The studio's own mark, in the studio's own colours — the one element on the
   page that is not amber, which is correct: a logo is not a palette decision.
   Its red is #FE3A3B, close enough to --damage that it does not read as a
   foreign colour arriving from nowhere.

   Set in the heading line, so it is a lockup with the name rather than a badge
   floating above it. Sized in `em` off the heading, so it tracks the display
   clamp instead of needing a size of its own at each width. */
.studio-title {
  display: flex; align-items: center;
  gap: clamp(8px, .9vw, 14px);
}
.studio-mark { flex: none; width: .92em; height: .92em; }

/* Two people, side by side, rather than stacked rows. Both are read at once that
   way, which is the truth of a two-person studio — neither is the second entry
   in a list, which is also why there are no index numerals on them.

   Each is a card rather than a band between rules. Two surfaces carry the
   division on their own, so the section needs one hairline instead of three. */
.credits {
  list-style: none; margin: clamp(32px, 4vw, 56px) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 1.6vw, 22px);
}
.credit {
  display: grid;
  align-content: start;
  gap: clamp(12px, 1.4vw, 18px);
  padding: clamp(24px, 2.8vw, 38px);
  background: var(--stone);
  border: 1px solid var(--edge-soft);
  /* A rule down the left edge, in the accent the role beneath the name already
     uses. Set on the border rather than a pseudo-element so it stays flush with
     the card's own corners; box-sizing keeps the two cards the same width. */
  border-left: 2px solid var(--amber);
}
.credit-name h3 {
  margin: 0;
  font-size: clamp(28px, 3.4vw, 44px); font-weight: 900;
  letter-spacing: -.035em; line-height: 1;
}
.credit-role {
  margin: 10px 0 0;
  font-family: var(--data); font-size: 10.5px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--amber);
}
.credit-bio {
  margin: 0;
  color: var(--muted); font-size: 15.5px; line-height: 1.7;
  max-width: 46ch;
}

/* The third entry is not a person, so it is not a credit row — but it takes the
   credits' two columns so the whole section hangs off one structure. It used to
   take their old three, with the first left empty to clear the index gutter;
   with the pair now side by side that indent pointed at nothing above it. */
.studio-contact {
  margin-top: clamp(28px, 3.4vw, 44px);
  padding-top: clamp(24px, 3vw, 40px);
  border-top: 1px solid var(--edge);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  column-gap: clamp(28px, 4vw, 64px); row-gap: 0;
}
.studio-contact-l {
  grid-column: 1;
  margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -.01em;
}
.studio-contact-b {
  grid-column: 2; grid-row: 1;
  margin: 0 0 14px; color: var(--muted); font-size: 15.5px; line-height: 1.7;
  max-width: 46ch;
}
.studio-contact-a {
  grid-column: 2;
  display: inline-flex; align-items: center; gap: 8px;
  justify-self: start;
  font-family: var(--data); font-size: 13px; letter-spacing: .04em;
  color: var(--amber); text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--amber) 40%, transparent);
  padding-bottom: 3px;
  transition: border-color .18s var(--ease);
}
.studio-contact-a:hover { border-bottom-color: var(--amber); }
.studio-contact-a svg { width: 13px; height: 13px; fill: currentColor; flex: none; }

/* ===========================================================================
   CLOSING CTA
   =========================================================================== */
.close {
  position: relative; overflow: hidden; isolation: isolate;
  text-align: center;
  border-top: 1px solid var(--edge);
}
.close-media { position: absolute; inset: 0; z-index: -2; }
.close-media img { width: 100%; height: 100%; object-fit: cover; }
/* Banded rather than even: an even scrim dark enough for the heading crushes
   what is behind it to black, which is the opposite of putting anything there.
   So it runs heavy across the heading and the button and opens below them.

   The bands are percentages of the section, so they move when its height does.
   Removing the platform took the section from about 900px to 450 and slid the
   open band straight under the heading, which measured 2.99:1 — below AA, and
   invisible without re-measuring. Retuned here, and the foot of the band still
   ends lighter than its head rather than closing on a bottom shadow. */
.close-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(110% 94% at 50% 40%, rgba(7,9,14,0.20) 0%, rgba(7,9,14,0.44) 82%),
    linear-gradient(to bottom, rgba(7,9,14,0.68) 0%, rgba(7,9,14,0.64) 64%,
                    rgba(7,9,14,.34) 86%, rgba(7,9,14,.30) 100%);
}
.close h2 {
  font-size: clamp(32px, 5.6vw, 68px); font-weight: 900;
  letter-spacing: -.035em; line-height: 1; margin: 0 auto;
  max-width: 16ch; text-wrap: balance;
}

/* The heading is a statement again, not a link. The button under it is the
   call to action, which is where a visitor looks for one. */
.close-cta { margin-top: clamp(24px, 3.4vw, 40px); }

/* The clip sits where the still is, under the same scrim. It fades in once it
   can play through, so there is never a black frame or a jump. */
.close-media video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .5s var(--ease);
}
.close-media video.is-live { opacity: 1; }

/* Without the platform under it the section is heading and button only, so it
   takes its own padding rather than the page's — a closing band, not a full
   section of nothing. */
.close { padding: clamp(64px, 8vw, 108px) 0; }

/* ===========================================================================
   FOOTER
   =========================================================================== */
footer { background: var(--stone); border-top: 1px solid var(--edge); padding: 54px 0 40px; }
.foot-in {
  width: var(--shell); margin-inline: auto;
  display: grid; grid-template-columns: repeat(3, auto) 1fr;
  gap: 34px; align-items: start;
}
.foot-col h4 {
  font-family: var(--data); font-size: 10px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 15px; font-weight: 400;
}
.foot-links { display: flex; flex-direction: column; gap: 10px; }
.foot-links a {
  font-size: 14.5px; color: var(--bone); opacity: .74; text-decoration: none;
  display: flex; align-items: center; gap: 9px;
  transition: opacity .16s var(--ease);
}
.foot-links a:hover { opacity: 1; }
.foot-links svg { width: 15px; height: 15px; fill: currentColor; flex: none; }
.legal { font-size: 12.5px; color: var(--muted); line-height: 1.75; max-width: 46ch; justify-self: end; }

/* ===========================================================================
   MOTION  (entrance reveals, driven by IntersectionObserver)
   =========================================================================== */
/* Gated on .js: without the script, nothing is ever hidden. */
.js .reveal { opacity: 0; transform: translateY(22px); }
.js .reveal.in { opacity: 1; transform: none; transition: opacity .7s var(--ease), transform .7s var(--ease); }
.js .reveal-d1.in { transition-delay: .07s; }
.js .reveal-d2.in { transition-delay: .14s; }
.js .reveal-d3.in { transition-delay: .21s; }

/* ===========================================================================
   RESPONSIVE
   =========================================================================== */
@media (max-width: 1000px) {
  /* Below this the logo takes the full width and the figure has nothing left
     to stand in but the copy, so it goes rather than being squeezed. */
  .deco, .hero-figure { display: none; }

  /* One column: copy, then the figure, then the three points stacked. Each
     point carries its own bottom padding there — otherwise the rule hugs the
     copy above it and sits away from the label below, the wrong way round. */
  .kb-grid { grid-template-columns: 1fr; gap: clamp(28px, 5vw, 48px); }
  .kb-head .h-sec { max-width: 20ch; }
  .kb-fig { justify-self: center; max-width: 400px; }
  .pillars { grid-template-columns: 1fr; }
  .pillar {
    border-left: 0;
    padding: clamp(18px, 3.6vw, 26px) 0 clamp(18px, 4vw, 28px);
  }
  .pillar:last-child { padding-bottom: 0; }

  /* The blurb goes, so the count has to take over its column rather than sit in
     its own `auto` one — two auto columns pack both to the left and the count
     ends up trailing the name instead of hard right against the rule. */
  .badge-set-h { grid-template-columns: auto minmax(0, 1fr); }
  .badge-set-h p { display: none; }
  .badge-set-n { justify-self: end; }
  .badge-grid { grid-template-columns: repeat(auto-fill, minmax(86px, 1fr)); }

  /* One column. The cards keep their own edges, so nothing else changes. */
  .credits { grid-template-columns: 1fr; }
  .studio-contact { grid-template-columns: 1fr; gap: 12px; }
  .studio-contact-l, .studio-contact-b, .studio-contact-a { grid-column: 1; }
  .studio-contact-b { grid-row: auto; }
  .foot-in { grid-template-columns: repeat(3, 1fr); }
  .legal { grid-column: 1 / -1; justify-self: start; margin-top: 8px; }
}

@media (max-width: 860px) {
  :root { --shell: 90vw; }
  .nav-links, .nav-social, .nav-rule, .nav-cta { display: none; }
  .nav-burger { display: grid; }

  /* Collapsing to content height leaves the scene no room, so the islands end
     up behind the stat band. Give the hero a floor instead. */
  .hero { min-height: 78dvh; }
  .hero-in { padding: 56px 0 44px; }

  /* Decorations are hidden at this width, see the .deco block. */

  /* One column, and the dividers turn with it: a vertical border between cells
     that are now stacked would draw a line down the side of the band. */
  /* Stacked, the band comes back inside the shell. Full bleed on a phone would
     sit it a few pixels off the margin every other section is hung from, which
     is far more visible than the width it gains.

     A full-width row also has room for the label on one line, so it takes the
     space and the figure sets to the right of it — a readout rather than a
     stack. */
  .hud-in {
    grid-template-columns: 1fr;
    width: var(--shell); margin-inline: auto;
  }
  .stat {
    padding: 16px 0;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 20px; row-gap: 10px;
  }
  .stat-fig { justify-self: end; }
  .stat + .stat::before {
    left: 0; right: 0; top: 0; bottom: auto; width: auto; height: 1px;
    background: linear-gradient(to right, var(--edge), transparent 78%);
  }
  .stat-n { font-size: 32px; }

  /* One column. The media keeps its 4:3, so the shot stays whole and simply
     gets wider as the frame does. */
  .modes { grid-template-columns: 1fr; }

  /* Shorter cards, and no panel: below the width where one fits, hover just
     stops the tape and the dialog carries the detail. */
  .tape { --tape-w: 150px; --tape-h: 465px; --tape-gap: 8px; }

  /* Filter pills wrapped to three rows on a phone. Scroll them instead. */

  .cd-top { flex-direction: column; text-align: center; }
  .foot-in { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 520px) {
  body { font-size: 16px; }
  .tape { --tape-w: 128px; --tape-h: 396px; }
  .foot-in { grid-template-columns: 1fr; }
  .hero-cta { width: 100%; }
  .hero-cta .btn { flex: 1 1 100%; }
}

/* ===========================================================================
   REDUCED MOTION
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js .reveal, .js .reveal.in { opacity: 1; transform: none; }
  /* The tape holds still; app.js starts it paused. Dragging still works. */
}

@media (prefers-reduced-transparency: reduce) {
  .nav { background: var(--void); backdrop-filter: none; }
  .hud { background: var(--void); backdrop-filter: none; }
  .btn-ghost { background: var(--stone-2); backdrop-filter: none; }
}
