/* EGOTRIP — the video-first experience.

   One full-viewport stage that carries the whole journey:

     pre-play hero  ->  full-screen playback with in-playback text
                    ->  end-slate conversion (countdown + access form)

   The film never pauses for text. Every overlay is a DOM layer sitting
   above the <video>, driven off the film's own currentTime in
   experience.js, so type is frame-locked to the picture and survives a
   scrub. Nothing here ever changes the film's audio — the client's
   direction is the original track, untouched.

   Typography follows the spec's playback rules: minimalist grotesque,
   all caps, white with a dark drop-glow so it holds over both the blown
   highlights of a flashbulb and the near-black of a club interior. */

/* ---------- Tokens ---------- */

:root {
  /* The brand display face, same as the rest of the site. The earlier
     build set the in-playback copy in a neutral grotesque on the theory
     that footage wants a "film" face and the brand face is the site's
     voice. On screen that read as two different products: the hero
     wordmark and the end slate are Clash Display, so every cue between
     them arrived in a face the visitor had not seen and would not see
     again. One voice from first frame to last. Medium and Semibold are
     both already preloaded, so this costs nothing. */
  --xp-type: "Clash Display", "Helvetica Neue", Helvetica, Arial,
             system-ui, sans-serif;

  /* ---- The type palette ----
     NOT #fff. Pure white is the only pure white anywhere on the page —
     the wordmark, the kicker and the countdown are all drawn from a
     cool chrome family — and over blown highlights (a flashbulb, a
     white-lit stage) it has nowhere left to go, so the glyph edges
     dissolve into the frame. These two values are lifted straight off
     the end slate, which means the cues, the mark and the clock are
     finally the same colour. */
  --xp-ink: #eef2f7;        /* = .xp-end-wordmark */
  --xp-ink-dim: #aab5c1;    /* = .xp-end-k */

  /* Hero/slate glow. Kept as it was: that type sits on a held frame
     under a heavy scrim, where a wide bloom reads as depth. */
  --xp-glow:
    0 1px 2px rgba(0, 0, 0, 0.75),
    0 2px 10px rgba(0, 0, 0, 0.6),
    0 6px 44px rgba(0, 0, 0, 0.5);

  /* Cue shadow. The hero glow was being used here too, and its 44px
     bloom is what made the cues read as burned-in subtitles: a dark
     halo tracing every letter, moving with the type, over a moving
     picture. Contrast is the scrim's job (see .xp-cue::before); this is
     edge definition only — one hairline and one short, soft fall. */
  --xp-cue-shadow:
    0 1px 1px rgba(0, 0, 0, 0.58),
    0 0 16px rgba(0, 0, 0, 0.40);

  /* 8 frames at 24fps — the spec's fade-in. The out is a snap, not a
     fade: text leaves on the audio cut. */
  --xp-in: 0.333s;
  --xp-out: 0.12s;
}

/* ---------- The stage ---------- */

/* Fixed and full-bleed while the experience owns the screen. The film is
   object-fit: cover inside it, so nothing reflows between states — the
   layout is identical before the first frame and after the last. */
#experience {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: #000;
  overflow: hidden;
  /* Lets the page beneath take over once the experience is dismissed. */
  transition: opacity 0.9s var(--ease);
}

#experience.is-gone { display: none; }

/* Fallback presentation: no film, reduced motion, or ?skip=1. The stage
   stops being an overlay and becomes an ordinary section, so the page
   scrolls normally and the end slate reads as the invitation it is. */
body.no-experience #experience {
  position: relative;
  inset: auto;
  z-index: 1;
  min-height: 100svh;
  display: grid;
  place-items: center;
}
body.no-experience .xp-video,
body.no-experience .xp-poster,
body.no-experience .xp-cues,
body.no-experience .xp-hero,
body.no-experience .xp-ui { display: none; }

/* ---------- Film and poster ----------
   The poster paints first so the hero copy never lands on empty black,
   then the real opening frame fades over it once the film can render
   one. Both are absolutely positioned at the same size: no layout shift
   at any point in the handover. */

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

.xp-poster {
  background: #000 center / cover no-repeat;
  /* Held back so the still reads as the film's own grade, not as a
     brighter stock photograph sitting under it. */
  filter: saturate(0.9) brightness(0.72) contrast(1.06);
  transform: scale(1.04);
}

.xp-video {
  opacity: 0;
  transition: opacity 0.6s var(--ease);
  /* A 5% push-in used only by the "redo points 3,4,5" variant. Kept as a
     variable so the zoom can ride on top of whatever else is set. */
  transform: scale(var(--xp-zoom, 1));
  transform-origin: center;
  will-change: transform;
}
.xp-video.is-ready { opacity: 1; }

/* The grade: a bottom-weighted scrim and a soft vignette. It is what
   makes lower-third type legible over a bright crowd without dimming the
   whole frame, and it is never animated — a pumping scrim reads as a
   fault, not as an effect. */
.xp-grade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(0, 0, 0, 0.72) 0%, rgba(0, 0, 0, 0.26) 26%,
                    rgba(0, 0, 0, 0) 52%),
    radial-gradient(130% 95% at 50% 42%, rgba(0, 0, 0, 0) 42%, rgba(0, 0, 0, 0.5) 100%);
}

/* Pre-play the scrim is heavier: the hero headline has to carry, and the
   still behind it is holding rather than moving. */
#experience[data-state="idle"] .xp-grade {
  background:
    linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.45) 40%,
                    rgba(0, 0, 0, 0.3) 100%),
    radial-gradient(120% 90% at 50% 46%, rgba(0, 0, 0, 0.12) 30%, rgba(0, 0, 0, 0.72) 100%);
}

/* ---------- Pre-play hero ---------- */

.xp-hero {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: clamp(1.4rem, 4vh, 2.4rem);
  padding: var(--gut);
  text-align: center;
  transition: opacity 0.7s var(--ease);
}

.xp-hero[hidden] { display: none; }
.xp-hero.is-out { opacity: 0; pointer-events: none; }

/* ---- The mark on the opening frame ----
   Same treatment as the end slate: the rotating film with its dead
   frame cropped (13.3% above the mark, 11.1% below — measured), so the
   flex gap below is the true optical gap. */
.xp-hero-logo,
.xp-hero-logo-fallback {
  --logo-w: min(32vh, 52vw);
  width: var(--logo-w);
  height: auto;
  margin-top: calc(var(--logo-w) * -0.0997);
  margin-bottom: calc(var(--logo-w) * -0.0832);
  filter: drop-shadow(0 28px 50px rgba(0, 0, 0, 0.7));
}
.xp-hero-logo { animation: xp-float 6s ease-in-out infinite; }

.xp-hero-wordmark {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.6rem, 4vw, 3.1rem);
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  line-height: 1;
  color: #eef2f7;
  text-transform: uppercase;
  margin: 0;
  text-shadow: var(--xp-glow);
  /* The mark carries a filter, so it paints above a plain sibling that
     follows it; lift the wordmark back over it. */
  position: relative;
  z-index: 1;
}

.xp-hero-h {
  font-family: var(--xp-type);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--xp-ink);
  font-size: clamp(1.6rem, 5.6vw, 4.6rem);
  line-height: 1.04;
  letter-spacing: 0.06em;
  /* No negative margin: the browser trims the trailing letter-space
     when centring, so compensating for it shifts the line right. */
  text-shadow: var(--xp-glow);
  max-width: 18ch;
}

/* The deck's tagline, restored under the headline: the one line the
   pre-play frame carries besides the question itself. */
.xp-hero-tag {
  margin-top: calc(clamp(1.4rem, 4vh, 2.4rem) * -0.45);
  font-family: var(--xp-type);
  font-weight: 500;
  font-size: clamp(0.56rem, 1.4vw, 0.72rem);
  letter-spacing: 0.34em;
  /* The chrome off-white at the same 72%, so the tagline reads as a
     dimmer version of the wordmark above it rather than a cooler one. */
  color: rgba(238, 242, 247, 0.74);
  text-shadow: var(--xp-glow);
}

/* The trigger. A ring that breathes rather than a button that flashes —
   it has to read as an invitation on a cinematic frame, not as a CTA. */
.xp-enter {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  padding: 1.05rem 2.2rem;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 2px;
  font-family: var(--xp-type);
  font-weight: 500;
  font-size: 0.64rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--xp-ink);
  background: rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  overflow: hidden;
  transition: color 0.45s var(--ease), border-color 0.45s var(--ease);
}

/* The pulse lives on a pseudo-element so the button itself never scales:
   a scaling tap target drifts under the pointer. */
.xp-enter::before {
  content: "";
  position: absolute;
  inset: -1px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 2px;
  animation: xp-pulse 2.8s var(--ease) infinite;
  pointer-events: none;
}

/* Gold sweeps up from the bottom on hover, matching the invitation's
   submit so the two triggers feel like one system. */
.xp-enter::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--gold);
  transform: translateY(101%);
  transition: transform 0.55s var(--ease);
  z-index: -1;
}
.xp-enter:hover,
.xp-enter:focus-visible {
  color: #000;
  border-color: var(--gold);
  outline: none;
}
.xp-enter:hover::after,
.xp-enter:focus-visible::after { transform: translateY(0); }
.xp-enter:hover::before,
.xp-enter:focus-visible::before { animation: none; opacity: 0; }

.xp-enter .xp-tri {
  font-size: 0.7em;
  transform: translateY(0.04em);
}

@keyframes xp-pulse {
  0%   { transform: scale(1);    opacity: 0.55; }
  70%  { transform: scale(1.14); opacity: 0;    }
  100% { transform: scale(1.14); opacity: 0;    }
}

/* ---------- In-playback text ----------
   Every cue is one of these. Opacity, letter-spacing and scale are all
   written from JS off the film's clock, so the rules here are structure
   and placement only — no transitions, or they would fight the per-frame
   values and smear the snap-off. */

.xp-cues {
  position: absolute;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  overflow: hidden;
}

.xp-cue {
  position: absolute;
  opacity: 0;
  font-family: var(--xp-type);
  /* Semibold, not Bold. 600 is what the previous build set every display
     heading in (.hero-h, .exp-h) and what the wordmark and end slate
     carry, and it is the weight that is preloaded. 700 in the grotesque
     was compensating for a face with far less presence at caps. */
  font-weight: 600;
  text-transform: uppercase;
  color: var(--xp-ink);
  text-shadow: var(--xp-cue-shadow);
  line-height: 1.06;
  /* Tracking is set here and never animated. It used to be written per
     frame from 0.15em to 0.25em, per the spec's "micro tracking
     expansion" — but letter-spacing is a layout property, so growing it
     re-wrapped the line while the cue was on screen. A phrase near a
     wrap boundary visibly re-laid-out mid-read. Fixed tracking removes
     that entirely; the breathing comes from the scale drift instead.

     The variable lets the big tones opt down.

     0.2em was a grotesque value — Helvetica caps need opening up. Clash
     Display is drawn with its own spacing and the brand sets its caps
     between -0.015em (.exp-h) and 0.14em (the wordmark); at 0.2em the
     letters stop belonging to the same word. 0.06em is the small
     positive nudge caps want over moving footage, and because every
     line now sets NARROWER than it did, nothing that fitted before can
     wrap now. */
  --trk: 0.06em;
  letter-spacing: var(--trk);
  /* No negative margin to "reclaim" the trailing letter-space: the
     browser already trims it when centring, so compensating for it
     pushed every centred line half the tracking to the right — measured
     at +5px of true centre. Removing it puts all nine cues at 0px. */

  /* This existed only to out-run 0.2em of tracking, which had pushed the
     letter gap up to the word gap ("BE THERE." setting as "BETHERE.").
     At 0.06em the face's own word space is nearly right; this is the
     last touch of separation. */
  word-spacing: 0.06em;
  /* letter-spacing is no longer written per frame — see above. */
  will-change: opacity, transform;
  transform-origin: center;
}

/* ---- The scrim ----
   What actually makes a cue legible. Film titling has never relied on
   outlining the glyphs; it darkens the area the type sits in and leaves
   the type clean. So: a soft elliptical fall of black behind the block,
   wider than the text and fading to nothing well before its edge.

   It is a CHILD of the cue, so it inherits the cue's per-frame opacity
   for free — the scrim arrives and leaves with its text, frame for
   frame, and there is nothing extra for experience.js to drive. The cue
   sets opacity every frame, which makes it a stacking context, so
   z-index:-1 is contained by it and can never fall behind the film. */
.xp-cue::before {
  content: "";
  position: absolute;
  inset: -0.9em -2.4em;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(58% 62% at 50% 50%,
    rgba(0, 0, 0, 0.46) 0%,
    rgba(0, 0, 0, 0.30) 52%,
    rgba(0, 0, 0, 0.10) 76%,
    rgba(0, 0, 0, 0) 100%);
}

/* The lower third already sits inside .xp-grade's 0.72 black, so a
   second scrim there would stack into a visible dark band. */
.xp-cue[data-pos="lower-left"]::before,
.xp-cue[data-pos="lower-center"]::before {
  background: radial-gradient(60% 70% at 50% 50%,
    rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.12) 60%, rgba(0, 0, 0, 0) 100%);
}

/* A cue carrying plate:true lands on the film's own cut to black, where
   there is nothing to separate the type from. */
.xp-cue[data-plate]::before { background: none; }

/* When a line has to break, split it evenly rather than dropping a
   single orphaned word onto a line of its own. */
.xp-cue p { margin: 0; text-wrap: balance; }
.xp-cue p + p { margin-top: 0.22em; }

/* -- Placements, straight off the sync matrix -- */

/* Lower-left third: cues 1 and 5. Sits inside the page gutter and above
   the scrub bar's hover zone so the two never collide. */
.xp-cue[data-pos="lower-left"] {
  left: var(--gut);
  right: var(--gut);
  bottom: clamp(3.4rem, 11vh, 7rem);
  text-align: left;
  font-size: clamp(0.95rem, 2.5vw, 2.1rem);
  max-width: 24ch;
}

/* Centred but held low on the frame: same vertical band as the
   lower-left third, centre-aligned. No ch-based measure — these cues
   carry 0.2em of tracking, which a ch unit does not account for. */
.xp-cue[data-pos="lower-center"] {
  left: var(--gut);
  right: var(--gut);
  bottom: clamp(3.4rem, 11vh, 7rem);
  text-align: center;
  font-size: clamp(0.95rem, 2.5vw, 2.1rem);
}

/* Dead centre: cues 2, 3 and 6. */
.xp-cue[data-pos="center"] {
  left: var(--gut);
  right: var(--gut);
  top: 50%;
  translate: 0 -50%;
  text-align: center;
  font-size: clamp(1.15rem, 3.6vw, 3rem);
}

/* Above the film's own "REAL. / NOT VIRTUAL. / NOT AI." card: cue 4 sits
   over the native type, not on it. Anchored by its BOTTOM edge, because
   what matters is the gap to the card below — a top anchor lets the block
   grow downwards into it as soon as the line wraps or the type scales.
   The card's top edge measures ~29% of the frame on a wide viewport and
   lower still on a phone, so 26% clears it everywhere. */
.xp-cue[data-pos="above-center"] {
  left: var(--gut);
  right: var(--gut);
  bottom: 74%;
  text-align: center;
  font-size: clamp(1.1rem, 3.3vw, 2.7rem);
}

/* Cue 5's block is weighted left and runs wider than cue 1's: its two
   written lines have to stay two lines, or the "lower-third cinematic
   block" becomes a four-line paragraph.

   No ch-based measure here. These cues carry up to 0.25em of tracking,
   which a ch unit knows nothing about — 42ch of text sets about 55ch
   wide once the letter-spacing lands, and wraps anyway. The gutters are
   the constraint, and the size is picked to fit the longest line
   between them. */
.xp-cue[data-tone="block"] {
  max-width: none;
  font-size: clamp(0.8rem, 1.9vw, 1.5rem);
  line-height: 1.3;
  /* Long caps lines at small size are the one place a little extra
     tracking still earns its keep. */
  --trk: 0.09em;
}

/* A multi-line statement: two full sentences that have to hold their
   own line at centre. The standard centre size would wrap a 40-character
   line inside the gutters, so this sits a step down — still display
   type, sized to the longest line it carries. */
.xp-cue[data-tone="statement"] {
  font-size: clamp(0.95rem, 2.6vw, 2.2rem);
  line-height: 1.25;
  --trk: 0.08em;
}

/* The lowercase "i" the deck set inside an all-caps line ("...ANOTHER
   iPHONE?"). text-transform on the cue would swallow it. */
.xp-cue .xp-low { text-transform: lowercase; }

/* Phase One gets the full frame: three words carrying the product
   promise, not a caption. */
.xp-cue[data-tone="hero"] {
  /* Display scale, so the brand's own display tracking: .hero-h set the
     biggest type on the previous build at -0.02em. Clash Display
     Semibold at 7rem wants closing up, not opening out. */
  --trk: -0.015em;
  font-size: clamp(2.2rem, 9vw, 7.5rem);
  line-height: 0.98;
  /* Negative tracking eats into the word space, so this gives it back. */
  word-spacing: 0.16em;
}

/* The "redo 3,4,5" variant: one huge word at a time, centre screen. */
.xp-cue[data-tone="kinetic"] {
  --trk: -0.015em;
  font-size: clamp(1.8rem, 8vw, 7rem);
  line-height: 1;
}
.xp-cue[data-tone="kinetic"] .xp-w {
  display: inline-block;
  opacity: 0;
  will-change: opacity, transform;
}

/* -- The black plate --
   Cue 6 lands on the film's own cut to black. The plate covers the full
   frame there, which both matches the edit and guarantees the corrected
   punctuation is the only version of that line on screen — the cut as
   delivered burns in "DO YOU WANT TO BE ME" without the question mark. */
.xp-plate {
  position: absolute;
  inset: 0;
  z-index: 29;
  background: #000;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}

/* ---------- Playback chrome ----------
   Hidden while the film runs; summoned by pointer movement and gone
   again after a beat, so the frame is clean by default. */

.xp-ui {
  position: absolute;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.xp-ui.is-shown { opacity: 1; }
.xp-ui > * { pointer-events: auto; }

/* The scrub bar. A hairline that thickens under the pointer; the filled
   portion is the film's progress, the faint one is what is buffered. */
.xp-scrub {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2.2rem;
  display: flex;
  align-items: flex-end;
  cursor: pointer;
  touch-action: none;
}

.xp-scrub-track {
  position: relative;
  width: 100%;
  height: 2px;
  background: rgba(255, 255, 255, 0.2);
  transition: height 0.25s var(--ease);
}
.xp-scrub:hover .xp-scrub-track,
.xp-scrub.is-dragging .xp-scrub-track { height: 4px; }

.xp-scrub-buf,
.xp-scrub-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  height: 100%;
}
.xp-scrub-buf { background: rgba(255, 255, 255, 0.28); }
.xp-scrub-fill { background: var(--gold); }

/* The head only exists under the pointer — a permanent dot on a teaser
   film reads as a player, which is the opposite of the intent. */
.xp-scrub-head {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  border-radius: 50%;
  background: var(--gold);
  transform: translateY(-50%) scale(0);
  transition: transform 0.25s var(--ease);
}
.xp-scrub:hover .xp-scrub-head,
.xp-scrub.is-dragging .xp-scrub-head { transform: translateY(-50%) scale(1); }

/* Skip, top right, deliberately quiet. */
.xp-skip {
  position: absolute;
  top: clamp(1rem, 2.4vw, 1.8rem);
  right: var(--gut);
  font-size: 0.56rem;
  letter-spacing: 0.26em;
  margin-right: -0.26em;
  color: rgba(255, 255, 255, 0.6);
  padding: 0.5rem 0;
  transition: color 0.35s var(--ease);
}
.xp-skip:hover,
.xp-skip:focus-visible { color: #fff; outline: none; }

/* Nothing left to skip to once the slate is up. */
#experience[data-state="end"] .xp-skip { display: none; }

/* A host that cannot answer Range requests cannot be seeked, so the
   scrub bar is withdrawn rather than left there doing nothing (or worse,
   re-pulling the whole film on every drag). Skip still works. */
#experience.no-seek .xp-scrub { display: none; }

/* ---------- End slate ----------
   Fades in over the final logo resolve while the film is still running
   underneath. It holds the real invitation: one form on the page, shown
   here during the experience and as an ordinary section without it. */

.xp-end {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: clamp(2rem, 6vh, 4rem) var(--gut) clamp(2rem, 6vh, 4rem);
  opacity: 0;
  visibility: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  text-align: center;
  /* No bottom clearance for the mute control any more: that was for the
     scrolling form. The outro is centred and the control sits in the far
     corner, so they cannot collide — and reserving 3.5rem for nothing
     was squeezing the spacing on exactly the laptop heights where it is
     tightest. */
  /* Solid black, as the live site's outro is. A translucent scrim left
     the logo film's own black frame showing as a lit rectangle over the
     footage; on black it disappears, and the chrome mark and the
     gradient date read exactly as they do live. */
  background: #000;
  transition: opacity 1.1s var(--ease), visibility 0s linear 1.1s;
}
.xp-end.is-in {
  opacity: 1;
  visibility: visible;
  transition: opacity 1.1s var(--ease), visibility 0s;
}

/* Without the film the slate is the page: no scrim, no overlay scroll. */
body.no-experience .xp-end {
  position: relative;
  inset: auto;
  opacity: 1;
  visibility: visible;
  overflow: visible;
  background: none;
  transition: none;
  width: 100%;
}

.xp-end-inner {
  width: 100%;
  max-width: 54rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* One rhythm for the whole slate, scaled to the viewport so it stays
     airy on a desktop and still fits a short laptop. Every gap below is
     this value — there are no ad-hoc margins between the lines. */
  gap: clamp(1.2rem, 6.2vh, 4rem);
}

/* ---- The outro, matching the live site ----
   The turning mark, the wordmark, the launch line and the clock.
   styles.css already loads these faces; intro.css declared its own
   family names ("ClashDisplay-Semibold") and is not on this page, so
   the same files are referenced through --display. */

/* The logo film is 1441x1080 and the mark does not fill it: measured,
   13.3% of the frame is empty above the mark and 11.1% below. Left
   uncropped that dead frame becomes invisible padding, which is why the
   spacing read as unpredictable — the gap you set is not the gap you
   see. The negative margins below remove exactly that much, so the flex
   gap above is the true optical gap. Both are computed from the logo's
   own width, so they stay correct at every size.

   frame height = width / (1441/1080) = width * 0.7494
   dead top     = 13.3% of that = width * 0.0997
   dead bottom  = 11.1% of that = width * 0.0832 */
.xp-end-logo,
.xp-end-logo-fallback {
  --logo-w: min(44vh, 68vw);
  width: var(--logo-w);
  height: auto;
  margin-top: calc(var(--logo-w) * -0.0997);
  margin-bottom: calc(var(--logo-w) * -0.0832);
  filter: drop-shadow(0 34px 60px rgba(0, 0, 0, 0.65));
}
.xp-end-logo { animation: xp-float 6s ease-in-out infinite; }

/* styles.css sets `img { display: block }`, which outranks the UA rule
   for the hidden attribute — so `<img hidden>` renders anyway. That is
   what put a second, static mark underneath the playing one. Restore
   the attribute's meaning for every image on this page. */
img[hidden] { display: none !important; }

@keyframes xp-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.6%); }
}

.xp-end-wordmark {
  font-family: var(--display);
  font-size: clamp(1.8rem, 4.2vw, 3.4rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  color: #eef2f7;
  text-transform: uppercase;
  margin: 0;
  /* The mark carries a filter, which paints it in the positioned layer
     — above a plain sibling that follows it in the DOM. Without this the
     wordmark is drawn behind it. */
  position: relative;
  z-index: 1;
}

.xp-end-k {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(0.9rem, 1.9vw, 1.4rem);
  letter-spacing: 0.55em;
  text-indent: 0.55em;
  color: #aab5c1;
  text-transform: uppercase;
  margin: 0;
}

/* The date carries the brand's chrome treatment: a lit gradient clipped
   to the glyphs, with the drop-shadows that give it depth on black. */
.xp-end-date {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.8rem, 4.2vw, 3.4rem);
  letter-spacing: 0.06em;
  line-height: 1;
  margin: 0;
  color: #7e7e84;
  background: linear-gradient(153deg,
    rgba(255,255,255,1) 0%, rgba(255,255,255,1) 35%, rgba(224,226,228,1) 46%,
    rgba(95,98,104,1) 54%, rgba(113,115,117,1) 57%, rgba(231,233,236,1) 63%,
    rgba(231,233,236,1) 79%, rgba(98,102,109,1) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  filter:
    drop-shadow(0 1px 0 rgba(255, 255, 255, 0.45))
    drop-shadow(0 6px 14px rgba(0, 0, 0, 0.75))
    drop-shadow(0 16px 44px rgba(0, 0, 0, 0.55));
}

.xp-end-cd {
  display: flex;
  gap: clamp(1.2rem, 4vw, 3rem);
  margin: 0;
}
.xp-cd-cell { display: flex; flex-direction: column; gap: 0.5em; align-items: center; }
.xp-cd-num {
  font-family: var(--display);
  font-size: clamp(1.2rem, 2.9vw, 2.3rem);
  font-weight: 600;
  letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
  /* The chrome off-white, not pure white: the last stray #fff on the
     slate, sitting two lines under a #eef2f7 wordmark. */
  color: var(--xp-ink);
  line-height: 1;
}
.xp-cd-label {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(0.5rem, 0.85vw, 0.66rem);
  letter-spacing: 0.5em;
  padding-left: 0.5em;
  color: #aab5c1;
  text-transform: uppercase;
}

@media (prefers-reduced-motion: reduce) {
  .xp-end-logo { animation: none; }
}

/* Short screens: the mark gives up height first — it is the one element
   that can lose size without the slate losing meaning. */
@media (max-height: 44rem) {
  .xp-end-inner { gap: clamp(0.9rem, 5vh, 2rem); }
  .xp-end-logo, .xp-end-logo-fallback { --logo-w: min(34vh, 52vw); }
  .xp-end-wordmark, .xp-end-date { font-size: clamp(1.4rem, 3.4vw, 2.2rem); }
  .xp-end-k { font-size: clamp(0.72rem, 1.5vw, 1rem); }
}

/* ---------- Page chrome around the stage ----------
   "Hide all UI during play." The bar is present on the pre-play frame
   and gone from the first frame of the film; the mute control is the
   one thing that stays, bottom right, per the playback spec. */

/* The stage is fixed at z-index 90, so the bar has to be lifted over it
   to exist at all on the pre-play frame. */
#topbar {
  z-index: 96;
  transition: opacity 0.6s var(--ease), background 0.55s var(--ease),
              backdrop-filter 0.55s var(--ease), border-color 0.55s var(--ease),
              padding 0.55s var(--ease);
}
body.xp-started #topbar {
  opacity: 0;
  pointer-events: none;
}

/* The toggle is dead weight before there is anything to mute. */
#sound-toggle { z-index: 95; }
/* Held well back while the film runs: it is the only control on screen
   and it should not compete with the picture. It lifts to full on hover
   or keyboard focus, so it is still easy to find and operate — and it
   only ever changes the sound, never the playback. */
body.xp-started #sound-toggle { opacity: 0.3; }
body.xp-started #sound-toggle:hover,
body.xp-started #sound-toggle:focus-visible { opacity: 1; }
/* Once the slate is up the film is over and the control is retiring
   anyway; keep it legible for the moment it is still there. */
body.xp-started #experience[data-state="end"] ~ #sound-toggle { opacity: 0.55; }
body.no-experience #sound-toggle { display: none; }

/* Once the film has ended its audio is over, so the mute control has
   nothing to act on. Retire it rather than leave a dead switch on the
   slate. Scrubbing back into the film brings it back. */
body.xp-film-ended #sound-toggle {
  opacity: 0;
  pointer-events: none;
}

/* On a phone the labelled toggle is ~120px wide and floats over the
   right-hand third of the slate's form fields, where it eats taps meant
   for the input behind it. Drop to the bare meter: same control, corner
   sized, nothing to collide with. */
@media (max-width: 767px) {
  #sound-toggle { padding: 0.65rem; gap: 0; }
  #sound-toggle .sound-label {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
}

/* The footer only exists on the no-film path — otherwise the fixed stage
   covers it and it is just weight at the bottom of the document. */
body:not(.no-experience) #foot { display: none; }

/* The stage owns the viewport until the fallback says otherwise. */
body:not(.no-experience) { overflow: hidden; height: 100%; }

/* The custom cursor has nothing to do over a film running full-bleed. */
body.xp-playing #cursor { opacity: 0; }

/* Grain and vignette belong to the site, not to the film — the film
   carries its own grade. */
body:not(.no-experience) .grain,
body:not(.no-experience) .vignette { display: none; }

/* ---------- Responsive ---------- */

/* Tablet and below: the lower-third blocks need the extra measure, and
   centre cues cannot afford the big desktop tracking. */
@media (max-width: 1023px) {
  .xp-cue { --trk: 0.05em; }
  /* Media queries carry NO specificity, so `.xp-cue { --trk }` above
     loses to `.xp-cue[data-tone="..."]` in the base sheet — the two
     long-copy tones would keep their desktop tracking all the way down
     to a phone, which is precisely what orphaned words last time. They
     are re-stated here at matching specificity so the breakpoint wins. */
  .xp-cue[data-tone="statement"],
  .xp-cue[data-tone="block"] { --trk: 0.07em; }
  .xp-cue[data-pos="lower-left"] { max-width: 28ch; }
  .xp-cue[data-tone="block"] { max-width: none; }
}

@media (max-width: 767px) {
  /* 14ch forced a third line and orphaned "THEM." on its own. 18ch is
     the narrowest measure that keeps the headline to two lines at
     390px, which is the break it takes on a laptop too. */
  .xp-hero-h { max-width: 18ch; letter-spacing: 0.04em; }
  .xp-enter { padding: 1rem 1.7rem; font-size: 0.58rem; letter-spacing: 0.24em; }

  /* A phone is portrait and the film is landscape: cover crops it hard,
     so type moves off the extreme edges and the lower third lifts clear
     of the home indicator. */
  .xp-cue[data-pos="lower-left"] {
    bottom: calc(clamp(3.4rem, 9vh, 5rem) + env(safe-area-inset-bottom, 0px));
    max-width: none;
    font-size: clamp(0.92rem, 4.6vw, 1.4rem);
  }
  /* Sized to hold its line on a phone rather than wrap. At 4.6vw the
     line broke and left "WATCH." orphaned on a line of its own, which
     reads worse than slightly smaller type. */
  .xp-cue[data-pos="lower-center"] {
    bottom: calc(clamp(3.4rem, 9vh, 5rem) + env(safe-area-inset-bottom, 0px));
    font-size: clamp(0.85rem, 4.2vw, 1.4rem);
  }
  /* 0.2em of tracking is a display value. At phone sizes it adds about
     a third again to every line, which re-wrapped all of the authored
     breaks and orphaned "BIGGEST", "2026", "VIDEO.", "ACCESS." and
     "THERE." onto lines of their own. Tracking comes down so the lines
     written as lines stay lines. */
  .xp-cue { --trk: 0.04em; }
  /* Same specificity point as the tablet block above. */
  .xp-cue[data-tone="statement"],
  .xp-cue[data-tone="block"] { --trk: 0.05em; }
  /* The scrim's horizontal reach is set in em, so on a phone it would
     run past the gutters and clip against .xp-cues' overflow. */
  .xp-cue::before { inset: -0.8em -1.2em; }
  /* 4.6vw -> 4.4vw. Clash Display sets a little wider than the grotesque
     it replaced, which put "EVERYONE WANTS TO BE SOMEONE." 4px over the
     measure at 390px and re-broke an authored line. 4.4vw clears it with
     margin to spare at 390 and 414, and still fits at 360 and 320 — the
     type is 4% smaller, which is not a visible difference, and a held
     line is worth far more than 4% of size. */
  .xp-cue[data-pos="center"] { font-size: clamp(0.95rem, 4.4vw, 1.9rem); }
  /* A portrait phone crops the film's sides, not its height, so the
     native card sits lower in the viewport and there is room to spare. */
  .xp-cue[data-pos="above-center"] { bottom: 70%; font-size: clamp(1rem, 5.2vw, 1.7rem); }
  .xp-cue[data-tone="block"] { font-size: clamp(0.85rem, 4vw, 1.25rem); max-width: none; }
  /* Deliberately NOT sized to preserve the desktop line breaks. These
     two cues carry 34-40 character lines; holding them as single lines
     at 390px needs ~12px type running edge to edge, which is below a
     comfortable subtitle size and reads as cramped. Larger type that
     re-breaks into short, balanced lines is the better phone setting —
     compared side by side, it is not close. The sentences stay grouped
     because each is its own paragraph. */
  .xp-cue[data-tone="statement"] { font-size: clamp(0.95rem, 4.3vw, 1.3rem); line-height: 1.3; }
  .xp-cue[data-tone="kinetic"] { font-size: clamp(1.7rem, 13vw, 3.4rem); }
  .xp-cue[data-tone="hero"] { font-size: clamp(1.9rem, 14vw, 3.6rem); }
  .xp-hero-tag { letter-spacing: 0.26em; }

  .xp-skip { top: calc(clamp(0.9rem, 2.4vw, 1.8rem) + env(safe-area-inset-top, 0px)); }
  .xp-scrub { bottom: env(safe-area-inset-bottom, 0px); }
  .xp-end { padding-top: clamp(3.5rem, 12vh, 6rem); }
}

/* Landscape phone: height is the scarce axis. The hero stack has to
   compress or the trigger falls off the bottom of the frame. */
@media (max-height: 34rem) and (orientation: landscape) {
  .xp-hero { gap: 1.4rem; }
  .xp-hero-h { font-size: clamp(1.3rem, 5vw, 2.2rem); }
  .xp-cue[data-pos="lower-left"],
  .xp-cue[data-pos="lower-center"] { bottom: 2.2rem; font-size: clamp(0.85rem, 2.6vw, 1.2rem); }
  .xp-end { padding-top: 3.5rem; }
  .xp-end-mark { width: 2.2rem; margin-bottom: 0.7rem; }
}

/* ---------- Reduced motion ----------
   The experience is motion by definition, so it is not offered at all —
   script falls through to the static slate. These rules only make sure
   nothing animates if the preference is set mid-session. */
@media (prefers-reduced-motion: reduce) {
  .xp-enter::before { animation: none; }
  .xp-poster { transform: none; }
  #experience,
  .xp-hero,
  .xp-end,
  .xp-video { transition-duration: 0.01ms; }
}
