/* ============================================================
   ABC — CINEMATIC LAYER (v6)
   The homepage as one continuous take. Camera language built in CSS 3D:
   a .stage is a perspective viewport, a .rig is the camera. Depth is real,
   so parallax is a by-product of perspective rather than tuned offsets.

   Non-negotiables carried from v4/v5:
   · content is in the DOM and readable in every state (no JS, reduced motion)
   · transform/opacity only in scroll loops
   · practical content gets NO camera movement
   · text NEVER cross-fades in place (it double-exposes) — it rolls
   ============================================================ */

/* Scenes staged on a dark ground set their text colour ONCE, here. Copy blocks
   must never each remember to do it — .crane-copy, .gather-copy and .finale-ask
   all shipped inheriting --ink and rendered near-black on near-black. */
.overture, .crane, .gather, .finale-scene { color: var(--bone); }

.stage { position: relative; perspective: 1400px; perspective-origin: 50% 45%; overflow: clip; }
.rig { transform-style: preserve-3d; will-change: transform; }

/* ---------- intro: curtain up (first visit only, skippable) ---------- */
.curtain {
  position: fixed; inset: 0; z-index: 9000; background: var(--night);
  display: grid; place-items: center; gap: 1rem;
  transition: transform 1s var(--out), opacity .5s ease .5s;
}
.curtain img { width: clamp(90px, 12vw, 150px); height: auto; opacity: 0; transform: scale(.86); }
.curtain small { font-size: .72rem; letter-spacing: .34em; text-transform: uppercase; opacity: 0; color: var(--yellow); }
.js .curtain img { animation: mark-in .9s var(--out) forwards; }
.js .curtain small { animation: mark-in .7s var(--out) .35s forwards; }
@keyframes mark-in { to { opacity: 1; transform: none; } }
.curtain.is-up { transform: translateY(-101%); opacity: 0; pointer-events: none; }
.curtain-skip {
  position: absolute; bottom: 2rem; right: 2rem; min-height: 44px; padding: .5rem 1.1rem;
  background: none; border: 1px solid var(--line-light); border-radius: 999px; color: var(--bone);
  font: inherit; font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; cursor: pointer;
}
.no-js .curtain, .reduce .curtain { display: none; }

/* ============================================================
   OPENING · the film carries the statement (scenes 01 + 02 merged)
   ============================================================ */
/* scenes.css still carries the previous overture (a padded grid with
   align-content:end). Reset it explicitly — otherwise the sticky frame is
   laid out at the BOTTOM of this 300vh section and inset by --pad. */
.overture { position: relative; display: block; padding: 0; min-height: 0; background: var(--night); align-content: initial; }
.js:not(.reduce) .overture { height: 300vh; }
.overture-fix { position: relative; min-height: 100svh; min-height: 100vh; overflow: clip; }
.js:not(.reduce) .overture-fix { position: sticky; top: 0; height: 100vh; }
.overture .stage { position: absolute; inset: 0; }
/* the rig carries a transform, so it MUST be sized — a transformed element
   becomes the containing block for its absolutely positioned children */
.overture .rig { position: absolute; inset: 0; }
.js:not(.reduce) .overture .rig { transform: scale(calc(1.02 + var(--pp, 0) * .06)); }
.overture-video { position: absolute; inset: 0; }
.overture-video video { width: 100%; height: 100%; object-fit: cover; }
.overture-wash {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(rgba(9,10,16,.34), transparent 30%, transparent 46%, rgba(9,10,16,.80) 88%),
    radial-gradient(110% 80% at 14% 100%, rgba(9,10,16,.5), transparent 56%);
}
.overture-copy { position: absolute; left: var(--pad); right: var(--pad); bottom: clamp(3rem, 9vh, 6.5rem); z-index: 2; }
.overture-eyebrow { display: flex; align-items: center; gap: .9rem; margin: 0 0 1rem; font-size: .75rem; letter-spacing: .22em; text-transform: uppercase; font-weight: 720; }
.overture-eyebrow i { font-style: normal; width: 8px; height: 8px; border-radius: 50%; background: var(--red); }
.overture-title { font-size: clamp(2.6rem, 8vw, 6.6rem); font-weight: 790; line-height: .92; text-transform: uppercase; font-variation-settings: "wdth" 80; margin: 0; }
.overture-title em { color: var(--yellow); }

/* the caption is a masked window; lines ROLL through it, one readable at a time */
.caption { position: relative; min-height: 3.2em; margin-top: .5rem; overflow: hidden; }
.cap-track { position: absolute; left: 0; top: 0; right: 0; will-change: transform; }
.cap {
  display: flex; align-items: center; gap: .3em; margin: 0;   /* flex eats the space between <b> and <em> */
  font-size: clamp(1.15rem, 2.8vw, 2.3rem); line-height: 1.15;
}
.cap b { font-weight: 780; text-transform: uppercase; font-variation-settings: "wdth" 84; }
.cap em { font-family: var(--serif); color: var(--yellow); }
.no-js .caption, .reduce .caption { min-height: 0; height: auto; overflow: visible; }
.no-js .cap-track, .reduce .cap-track { position: static; }
.no-js .cap, .reduce .cap { height: auto; margin-bottom: .25rem; }
.overture-definition {
  max-width: 68ch;
  margin-top: .7rem;
  color: color-mix(in srgb, var(--bone) 90%, transparent);
  font-size: clamp(.92rem, 1.25vw, 1.08rem);
  line-height: 1.55;
}

.overture-actions { margin-top: 1.4rem; display: flex; flex-wrap: wrap; gap: .8rem 1rem; }
.js:not(.reduce) .overture-actions { opacity: 0; will-change: opacity, transform; }
.overture-ticks { position: absolute; right: var(--pad); bottom: 1.5rem; z-index: 3; display: flex; gap: .35rem; }
.overture-ticks i { width: 26px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.28); transition: background .4s; }
.overture[data-phase="1"] .overture-ticks i:nth-child(1),
.overture[data-phase="2"] .overture-ticks i:nth-child(-n+2),
.overture[data-phase="3"] .overture-ticks i:nth-child(-n+3),
.overture[data-phase="4"] .overture-ticks i { background: var(--yellow); }
.no-js .overture-ticks, .reduce .overture-ticks { display: none; }

.video-control {
  position: absolute; z-index: 5; right: var(--pad); top: calc(var(--header-h) + 1.2rem);
  display: inline-flex; align-items: center; gap: .6rem; min-height: 44px; padding: .5rem 1rem;
  background: color-mix(in srgb, var(--night) 42%, transparent); color: var(--bone);
  border: 1px solid color-mix(in srgb, var(--bone) 36%, transparent); border-radius: 999px;
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 700;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.video-control i { width: 8px; height: 8px; border-radius: 2px; background: var(--yellow); }
.video-control.is-paused i { border-radius: 50%; background: var(--red); }

/* ============================================================
   THE CRANE · arrival on stage (scene 03)
   ============================================================ */
.crane { position: relative; background: var(--night); }
.js:not(.reduce) .crane { height: 300vh; }
.crane-fix { position: relative; min-height: 100vh; overflow: clip; }
.js:not(.reduce) .crane-fix { position: sticky; top: 0; height: 100vh; }
.crane .stage { position: absolute; inset: 0; perspective: 1100px; perspective-origin: 50% 38%; }
.crane-floor { position: absolute; inset: 0; display: grid; place-items: center; transform-style: preserve-3d; }
.crane-floor figure {
  margin: 0; width: 86%; aspect-ratio: 3 / 2; overflow: hidden; border-radius: 14px;
  box-shadow: 0 60px 120px -40px rgba(0,0,0,.9); transform-origin: 50% 100%; position: relative;
}
.js:not(.reduce) .crane-floor figure {
  transform:
    rotateX(calc((1 - min(var(--pp,0) * 1.35, 1)) * 20deg))
    translateZ(calc(min(var(--pp,0) * 1.35, 1) * 120px))
    scale(calc(.82 + min(var(--pp,0) * 1.35, 1) * .34));
}
.crane-floor img { width: 100%; height: 112%; object-fit: cover; object-position: center 34%; }
.js:not(.reduce) .crane-floor img { transform: translateY(calc(min(var(--pp,0) * 1.35, 1) * -8%)); }
.no-js .crane-floor figure, .reduce .crane-floor figure { width: 92%; }
.no-js .crane-floor img, .reduce .crane-floor img { height: 100%; transform: none; }
.crane-floor figure::after { content: ""; position: absolute; inset: 0; background: linear-gradient(rgba(9,10,16,.25), transparent 30%, transparent 68%, rgba(9,10,16,.7)); }
.crane-floor figcaption {
  position: absolute; right: 1rem; bottom: .8rem; z-index: 3;
  font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; font-weight: 650;
  color: var(--bone); text-shadow: 0 1px 8px rgba(0,0,0,.7);
}
.crane .ghost {
  top: 4%; left: 50%; font-size: clamp(6rem, 20vw, 17rem); z-index: 4;
  transform: translate(-50%, 0) translateZ(240px) scale(calc(1 - min(var(--pp,0),1) * .12));
  opacity: calc(.7 - min(var(--pp,0),1) * .55);
}
.no-js .crane .ghost, .reduce .crane .ghost { transform: translate(-50%, 0); opacity: .45; }
/* copy over photography always gets its own ground */
.crane-scrim {
  position: absolute; inset: auto 0 0 0; height: 62%; z-index: 4; pointer-events: none;
  background: linear-gradient(transparent, rgba(8,9,14,.55) 42%, rgba(8,9,14,.88));
  opacity: calc(min(max(var(--pp,0) - .45, 0) * 3, 1));
}
.no-js .crane-scrim, .reduce .crane-scrim { opacity: 1; }
.crane-copy {
  position: absolute; left: var(--pad); bottom: clamp(2.5rem, 8vh, 6rem); z-index: 5; max-width: 34rem;
  opacity: calc(min(max(var(--pp,0) - .55, 0) * 3.2, 1));
  transform: translateY(calc((1 - min(max(var(--pp,0) - .55, 0) * 3.2, 1)) * 24px));
}
.no-js .crane-copy, .reduce .crane-copy { opacity: 1; transform: none; }
.crane-copy h2 { font-size: clamp(2rem, 5vw, 4rem); text-transform: uppercase; font-variation-settings: "wdth" 84; margin-bottom: .8rem; }
.proof-stats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .8rem;
  margin: 1.15rem 0 0; padding-top: 1rem; border-top: 1px solid var(--line-light);
}
.proof-stats span { font-size: .69rem; line-height: 1.25; letter-spacing: .1em; text-transform: uppercase; font-weight: 680; }
.proof-stats b { display: block; margin-bottom: .2rem; color: var(--yellow); font-size: clamp(1.35rem, 2.5vw, 2rem); line-height: 1; letter-spacing: 0; }
/* Same reason as the finale: a crane move needs depth to descend through.
   On a phone it degrades to a floating card between two voids, with the
   figcaption landing on the eyebrow. Stack it: photograph, then the copy. */
@media (max-width: 900px) {
  .js:not(.reduce) .crane { height: auto; }
  .js:not(.reduce) .crane-fix { position: static; height: auto; min-height: 0; }
  .crane .stage { position: relative; inset: auto; perspective: none; padding: clamp(3rem,9vw,5rem) var(--pad); }
  .crane-floor { position: relative; inset: auto; display: block; }
  .crane-floor figure { width: 100%; }
  .js:not(.reduce) .crane-floor figure { transform: none; }
  .js:not(.reduce) .crane-floor img { transform: none; height: 100%; }
  .crane .ghost, .crane-scrim, .crane-note { display: none; }
  .crane-copy { position: relative; left: auto; bottom: auto; margin-top: 2.2rem; opacity: 1; transform: none; max-width: none; }
}

/* The homepage ends its narrative with two short handoffs. Classes and GIDF
   still have clear routes without becoming separate full-screen acts. */
.home-pathways { padding-block: clamp(3.4rem, 7vw, 6rem); }
.home-pathways-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem 3rem;
  margin-bottom: clamp(1.8rem, 3vw, 2.8rem);
}
.home-pathways-head .script-note { margin: 0; color: var(--blue); }
.home-pathways-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border-block: 1.5px solid var(--ink); }
.home-pathways article { padding: clamp(1.8rem, 4vw, 3.5rem) clamp(0rem, 3vw, 3rem); }
.home-pathways article + article { border-left: 1.5px solid var(--ink); padding-left: clamp(1.5rem, 4vw, 4rem); }
.home-pathways h2 { margin: .6rem 0 1rem; font-size: clamp(2.2rem, 4.8vw, 4rem); text-transform: uppercase; font-variation-settings: "wdth" 84; }
.home-pathways article > p:not(.label) { max-width: 46ch; }
.home-pathways .button-row { margin-top: 1.4rem; }
@media (max-width: 760px) {
  .proof-stats { gap: .55rem; }
  .proof-stats span { font-size: .62rem; letter-spacing: .06em; }
  .home-pathways-head { display: block; }
  .home-pathways-head .script-note { margin-top: .7rem; }
  .home-pathways-grid { grid-template-columns: 1fr; }
  .home-pathways article { padding: 2.2rem 0; }
  .home-pathways article + article { border-left: 0; border-top: 1.5px solid var(--ink); padding-left: 0; }
}
.crane-note {
  position: absolute; right: calc(var(--pad) * 1.2); top: calc(var(--header-h) + 2.5rem); z-index: 5;
  background: var(--yellow); color: var(--ink); padding: .5rem .9rem; border-radius: 4px;
  font-family: var(--serif); font-style: italic; transform: rotate(-2.4deg);
  opacity: calc(1 - min(var(--pp,0) * 2.2, 1));
}
.no-js .crane-note, .reduce .crane-note { opacity: 1; }

/* ============================================================
   THE LETTER · lateral track into daylight (scene 05)
   ============================================================ */
.letter .stage { perspective: 1200px; overflow: visible; }
.letter-mark { width: clamp(110px, 13vw, 190px); height: auto; margin-top: 2rem; }
.js:not(.reduce) .letter-mark { transform: translateZ(170px) translateX(calc((var(--p, .5) - .5) * -70px)); }

/* ============================================================
   THE CURTAIN CALL · the company bows in (scene 06)
   ============================================================ */
/* CURTAIN CALL — the whole company at once: seven across the front row, six
   stepped in behind. Flex (not grid) so a single nth-child rule can offset the
   back row by half a cell; a grid would need thirteen explicit placements. */
.line-up {
  list-style: none; margin: 1.6rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--lu-gap, .8rem); perspective: 1000px;
  --lu-cols: 7; --lu-gap: .8rem;
  --lu-cell: calc((100% - (var(--lu-cols) - 1) * var(--lu-gap)) / var(--lu-cols));
}
.line-up li { width: var(--lu-cell); }
/* a wrapped two-line name would otherwise leave its card short of the others
   in the same row — the card fills the line's height instead */
.line-up .card { height: 100%; }
/* the back row steps half a cell across — a line-up, not a spreadsheet */
.line-up li:nth-child(8) { margin-left: calc(var(--lu-cell) / 2 + var(--lu-gap) / 2); }
/* .card's 1.5rem padding is sized for text cards; here it mats a quarter of
   every portrait away. In a line-up the portrait IS the card. */
.line-up .card {
  display: block; padding: 0; overflow: hidden; transform-style: preserve-3d;
  text-decoration: none; color: inherit; background: var(--night-2);
  transform: rotateX(var(--tx, 0deg)) rotateY(var(--ty, 0deg));
  transition: transform .5s var(--out);
}
.line-up figure { margin: 0; aspect-ratio: 4/5; overflow: hidden; background: var(--night-2); }
.line-up img { width: 100%; height: 100%; object-fit: cover; display: block; }
.js:not(.reduce) .line-up img { transition: transform 1s var(--out); }
@media (hover: hover) and (pointer: fine) {
  .js:not(.reduce) .line-up .card:hover img { transform: scale(1.06); }
}
.line-up span { display: block; padding: .6rem .7rem .7rem; font-size: .72rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; font-variation-settings: "wdth" 92; opacity: .88; }
.js:not(.reduce) [data-scene].is-on .line-up li { animation: bow .7s var(--out) backwards; animation-delay: calc(var(--i, 0) * 85ms); }
@keyframes bow { from { opacity: .001; transform: translateY(26px) rotateX(6deg); } }
/* the half-step only reads in the 7-across formation; below that the company
   becomes an even contact sheet and the offset would just look like a mistake */
@media (max-width: 1100px) { .line-up { --lu-cols: 5; } .line-up li:nth-child(8) { margin-left: 0; } }
@media (max-width: 640px) { .line-up { --lu-cols: 3; --lu-gap: .55rem; } }

/* ============================================================
   CUT TO DAYLIGHT · the wipe (scene 07)
   ============================================================ */
.cut { position: relative; }
.js:not(.reduce) .cut { height: 170vh; }
.cut-fix { position: relative; min-height: 100vh; overflow: clip; }
.js:not(.reduce) .cut-fix { position: sticky; top: 0; height: 100vh; }
/* Content sits high, not centred. The cut follows the company scene on the same
   --night ground, so a centred panel opens with ~480px of empty dark that reads
   as one long void under the portraits rather than the start of a new scene.
   Both panels share the alignment, or the wipe would shift the type sideways. */
.cut-night, .cut-day {
  position: absolute; inset: 0; display: grid; align-content: start;
  padding: calc(var(--header-h) + clamp(2rem, 7vh, 4.5rem)) var(--pad) 0;
}
.cut-night { background: var(--night); color: var(--bone); }
.cut-day { background: var(--yellow); color: var(--ink); }
.js:not(.reduce) .cut-day { clip-path: inset(0 calc((1 - min(var(--pp,0) * 2.1, 1)) * 100%) 0 0); }
.no-js .cut-night, .reduce .cut-night { display: none; }
.no-js .cut-fix, .reduce .cut-fix { min-height: 0; padding: clamp(3.4rem, 6.5vw, 6rem) 0; }
.no-js .cut-day, .reduce .cut-day { position: relative; inset: auto; }
.cut-day .label { color: var(--blue); }
/* The day panel carries a lead, a three-step list and a button; the night
   panel carries two lines. Without this the "before" state is a full viewport
   of empty dark — the gap readers notice under the company portraits. */
.cut-night .ghost {
  right: var(--pad); bottom: 5vh; z-index: 0;
  font-size: clamp(5rem, 17vw, 15rem);
  -webkit-text-stroke-color: color-mix(in srgb, var(--bone) 22%, transparent);
}

/* ============================================================
   THE GATHERING · frames assemble out of depth (scene 08)
   ============================================================ */
.gather { position: relative; background: var(--blue); }
.js:not(.reduce) .gather { height: 260vh; }
.gather-fix { position: relative; min-height: 100vh; display: grid; align-content: center; padding: clamp(3rem,7vw,6rem) var(--pad); overflow: clip; }
.js:not(.reduce) .gather-fix { position: sticky; top: 0; height: 100vh; }
.gather .stage { position: absolute; inset: 0; perspective: 1300px; perspective-origin: 62% 45%; }
/* The collage gets its own column. Percentage-placed frames and the copy
   otherwise share one horizontal band, and the narrower the viewport the
   further the photographs slide under the text. A column can't collide. */
.collage { position: absolute; inset: 0 0 0 47%; transform-style: preserve-3d; }
.collage figure {
  position: absolute; margin: 0; overflow: hidden; border-radius: 10px;
  box-shadow: 0 40px 80px -36px rgba(0,0,0,.75);
  /* each frame arrives from its own distance, at its own moment — pure CSS */
  --k: clamp(0, calc((var(--pp, 0) - var(--start)) / .34), 1);
  opacity: var(--k);
  transform: translateZ(calc((1 - var(--k)) * var(--depth))) translateY(calc((1 - var(--k)) * 40px));
}
.collage img { width: 100%; height: 100%; object-fit: cover; }
.collage figcaption {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 1.3rem .8rem .6rem;
  background: linear-gradient(transparent, rgba(8,9,14,.72));
  font-size: .7rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 650; color: var(--bone);
}
.no-js .collage figure, .reduce .collage figure { opacity: 1; transform: none; }
/* 42vw keeps the copy inside its own half at every width down to the 1100px
   stack point — a fixed 34rem crosses into the collage column near 1100px. */
.gather-copy { position: relative; z-index: 3; max-width: min(34rem, 42vw); }
@media (max-width: 1100px) {
  .js:not(.reduce) .gather { height: auto; }
  .js:not(.reduce) .gather-fix { position: static; height: auto; }
  .gather .stage { position: relative; inset: auto; perspective: none; }
  /* Below the stack point the collage drops BELOW the copy, so there is no
     second column left to stay out of — and 42vw then squeezes the whole
     block into the left 42% of the screen (164px of a 390px phone, with the
     headline, the date row and the CTA all wrapping inside it). Give the copy
     the full measure back. */
  .gather-copy { max-width: 34rem; }
  .collage { position: relative; inset: auto; display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; margin-top: 2rem; }
  /* The inline aspect-ratio on the Kathak frame (2/3) beats this rule on
     specificity, so it stayed portrait in a 2-col grid: ragged rows, and the
     two bottom captions printed over each other. Force one ratio here. */
  /* `position: static` here left every figcaption anchoring to the collage
     instead of its own frame, so all four captions stacked in one corner and
     printed over each other. Keep the figure positioned; the inline top/right
     offsets are ignored in grid flow anyway. The inline aspect-ratio on the
     Kathak frame (2/3) also beats this rule on specificity — force one ratio
     so the grid rows stay level. */
  .collage figure { position: relative; inset: auto !important; width: auto !important; opacity: 1; transform: none; aspect-ratio: 3/2 !important; }
}

/* ============================================================
   THE ROAD · vertical scroll becomes lateral travel (scene 09)
   ============================================================ */
.road { position: relative; background: var(--bone); color: var(--ink); }
.road-fix { position: relative; padding: clamp(3rem,7vw,5rem) 0; }
.road.is-tracked .road-fix { position: sticky; top: 0; height: 100vh; display: grid; align-content: center; overflow: clip; }
.road .postcard-strip { will-change: transform; }
.road.is-tracked .postcard-strip { overflow: visible; cursor: default; }
.road-rule { position: absolute; left: var(--pad); right: var(--pad); bottom: 8vh; height: 2px; background: rgba(0,0,0,.12); }
.road-rule i { display: block; height: 100%; background: var(--blue); transform-origin: left; transform: scaleX(var(--k, 0)); }
.road:not(.is-tracked) .road-rule { display: none; }

/* ============================================================
   THE FINALE · pull back to reveal the whole stage (scene 10)
   ============================================================ */
.finale-scene { position: relative; background: var(--night); }
.js:not(.reduce) .finale-scene { height: 260vh; }
.finale-fix { position: relative; min-height: 100vh; overflow: clip; }
.js:not(.reduce) .finale-fix { position: sticky; top: 0; height: 100vh; }
.finale-scene .stage { position: absolute; inset: 0; perspective: 1500px; perspective-origin: 50% 46%; }
.finale-wide { position: absolute; inset: 0; display: grid; place-items: center; transform-style: preserve-3d; }
.finale-wide figure { margin: 0; width: 92%; aspect-ratio: 16/9; overflow: hidden; border-radius: 12px; box-shadow: 0 60px 120px -50px rgba(0,0,0,.95); position: relative; }
.finale-wide img { width: 100%; height: 100%; object-fit: cover; }
.js:not(.reduce) .finale-wide figure {
  transform: translateZ(calc(min(var(--pp,0) * 1.3, 1) * -420px)) translateY(calc(min(var(--pp,0) * 1.3, 1) * -4vh));
}
.finale-wide figure::after { content: ""; position: absolute; inset: 0; background: linear-gradient(rgba(9,10,16,.2), transparent 34%, rgba(9,10,16,.85)); }
.finale-wide figcaption { position: absolute; right: 1rem; bottom: .7rem; z-index: 2; font-size: .7rem; letter-spacing: .16em; text-transform: uppercase; font-weight: 650; color: var(--bone); opacity: .8; }
.finale-house {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(105% 90% at 50% 46%, transparent 30%, rgba(6,7,11,.94) 82%);
  opacity: calc(min(var(--pp,0) * 1.5, 1));
}
.no-js .finale-house, .reduce .finale-house { opacity: .55; }
.finale-scene .ghost {
  left: 50%; bottom: 4%; font-size: clamp(7rem, 26vw, 24rem); z-index: 3;
  transform: translate(-50%, 0) scale(calc(1 + min(var(--pp,0),1) * .16));
  opacity: calc(.16 + min(var(--pp,0),1) * .34);
}
.no-js .finale-scene .ghost, .reduce .finale-scene .ghost { transform: translate(-50%, 0); opacity: .3; }
.finale-ask {
  position: absolute; left: var(--pad); right: var(--pad); bottom: clamp(3rem, 10vh, 8rem); z-index: 4; max-width: 40rem;
  opacity: calc(min(max(var(--pp,0) - .42, 0) * 2.6, 1));
  transform: translateY(calc((1 - min(max(var(--pp,0) - .42, 0) * 2.6, 1)) * 22px));
}
.no-js .finale-ask, .reduce .finale-ask { opacity: 1; transform: none; }
.finale-ask h2 { font-size: clamp(2.2rem, 6vw, 5rem); text-transform: uppercase; font-variation-settings: "wdth" 82; margin-bottom: .9rem; }
/* A pull-back needs depth to retreat into. A 390px-wide phone has none, so the
   staged version just reads as two long voids. Stack it instead: photograph,
   then the ask — the same beat, composed for the format. */
@media (max-width: 900px) {
  .js:not(.reduce) .finale-scene { height: auto; }
  .js:not(.reduce) .finale-fix { position: static; height: auto; min-height: 0; }
  .finale-scene .stage { position: relative; inset: auto; perspective: none; padding: clamp(3rem,9vw,5rem) var(--pad); }
  .finale-wide { position: relative; inset: auto; display: block; }
  .finale-wide figure { width: 100%; }
  .js:not(.reduce) .finale-wide figure { transform: none; }
  .finale-house { display: none; }
  .finale-scene .ghost { display: none; }
  .finale-ask { position: relative; left: auto; right: auto; bottom: auto; margin-top: 2.2rem; opacity: 1; transform: none; }
}

/* ---------- micro-interaction: 3D tilt toward the cursor ---------- */
[data-tilt] { transform: rotateX(var(--tx, 0deg)) rotateY(var(--ty, 0deg)); transition: transform .5s var(--out); }

/* ---------- reduced motion: every scene composes, nothing moves ---------- */
.reduce .rig, .reduce .letter-mark { transform: none !important; }

/* the company link now lives in the section head, so the line-up is the last
   thing in the scene and no empty block trails it */
.meet-link { margin-top: 1rem; }
.meet-link a { color: var(--yellow); font-weight: 700; font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; }

/* ============================================================
   INNER PAGES · the same camera, at chapter scale
   ------------------------------------------------------------
   The homepage is a narrative and can afford pinned scenes. These six pages
   are where people look up dates, prices and timetables, so the camera is
   confined to the hero — the one place with nothing to read underneath it.
   Everything below a hero (agendas, timetables, prices, folds) keeps the
   v4 contract: content complete on arrival, no camera movement.
   ============================================================ */

/* The hero becomes a perspective viewport. No new markup: the existing
   .inner-hero-visual / .gidf-stage figures are the things with depth. */
.inner-hero, .gidf-hero { perspective: 1400px; perspective-origin: 64% 40%; }
.inner-hero-visual, .gidf-stage { transform-style: preserve-3d; }

/* ARRIVAL — the hero photograph cranes down onto the page: raked back and
   held away, then levelling as it settles. An entrance animation, not a
   scrubbed one, so the composition is complete the moment it finishes and
   never depends on how far you scrolled. */
@keyframes hero-crane {
  from { opacity: 0; transform: rotateX(13deg) translateZ(-280px) translateY(26px); }
  to   { opacity: 1; transform: none; }
}
.js:not(.reduce) [data-scene] .inner-hero-visual > figure,
.js:not(.reduce) [data-scene] .gidf-main {
  animation: hero-crane 1.15s var(--out) backwards; animation-delay: .12s;
  transform-origin: 50% 100%;
}
/* the secondary frame arrives a beat later and from further out, so the two
   read as one move in depth rather than two objects fading */
.js:not(.reduce) [data-scene] .gidf-float {
  animation: hero-crane 1.2s var(--out) .38s backwards;
}

/* A cutout is a performer, not a picture: it steps FORWARD out of the dark
   rather than craning down like a framed photograph. The depth goes on the
   container — the cutout itself already consumes --p for its drift, and two
   rules writing the same transform would fight every frame. */
@keyframes hero-step {
  from { opacity: 0; transform: translateZ(-340px) translateY(18px); }
  to   { opacity: 1; transform: none; }
}
.js:not(.reduce) [data-scene] .inner-hero-visual.contain {
  animation: hero-step 1.1s var(--out) .12s backwards;
}
.no-js .inner-hero-visual.contain, .reduce .inner-hero-visual.contain { animation: none; opacity: 1; transform: none; }

/* the ghost word sits BEHIND the photography in real Z, so it parallaxes by
   perspective rather than by a hand-tuned offset. Transform only — animating
   its opacity to a keyframe value would pop when the animation hands back. */
.inner-hero .ghost, .gidf-hero .ghost { transform: translateZ(-520px); }
.js:not(.reduce) .inner-hero .ghost, .js:not(.reduce) .gidf-hero .ghost {
  animation: ghost-settle 1.4s var(--out) backwards;
}
@keyframes ghost-settle {
  from { transform: translateZ(-900px); }
  to   { transform: translateZ(-520px); }
}

/* nothing above may depend on JS or motion being available */
.no-js .inner-hero-visual > figure, .reduce .inner-hero-visual > figure,
.no-js .gidf-main, .reduce .gidf-main,
.no-js .gidf-float, .reduce .gidf-float { animation: none; opacity: 1; transform: none; }
.no-js .inner-hero .ghost, .reduce .inner-hero .ghost,
.no-js .gidf-hero .ghost, .reduce .gidf-hero .ghost { animation: none; transform: none; }

/* ---------- signature 1: photo groups assemble out of depth ----------
   The homepage collage assembles frames from their own distances. Here the
   same idea runs as a staggered entrance (galleries are browsed, not scrubbed).
   :not([data-scrub]) is load-bearing — those figures already write transform
   from --p every frame, and two rules on one transform fight. */
.gallery, .media-led { perspective: 1200px; }
.gallery .frame, .media-led .frame { transform-style: preserve-3d; }
@keyframes frame-assemble {
  from { opacity: 0; transform: translateZ(-560px) translateY(30px); }
  to   { opacity: 1; transform: none; }
}
.js:not(.reduce) [data-scene].is-on .gallery .frame:not([data-scrub]),
.js:not(.reduce) [data-scene].is-on .media-led .frame:not([data-scrub]) {
  animation: frame-assemble .95s var(--out) backwards;
  animation-delay: calc(var(--step, 0) * var(--beat));
}
.no-js .gallery .frame, .reduce .gallery .frame,
.no-js .media-led .frame, .reduce .media-led .frame { animation: none; opacity: 1; transform: none; }

/* ---------- signature 2: the daylight wipe, on the page it belongs to ----------
   The homepage cuts into daylight for the classes chapter. The Classes page
   repeats the gesture once, on the free-trial block. A wipe, never a dissolve. */
@keyframes daylight-wipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
.js:not(.reduce) .page-learn #trial.is-on { animation: daylight-wipe 1.05s var(--out) backwards; }
.no-js .page-learn #trial, .reduce .page-learn #trial { animation: none; clip-path: none; }

/* ============================================================
   FESTIVAL · the two staged beats (v6.2)
   The page was eight stacked blocks with entrance fades. These two turn it
   into a sequence you travel through. #practical stays completely still —
   it is the page's lookup surface.
   ============================================================ */

/* ---------- Edition Five: a pinned reveal ---------- */
.edition-scene { position: relative; }
.edition-scene .edition-poster { transform-origin: 50% 60%; }
/* --p runs 0→1 as the section crosses the viewport; the poster straightens and
   pushes in through the middle of that range, settling before it leaves */
.js:not(.reduce) .edition-scene .edition-poster {
  transform:
    rotate(calc(-3.4deg + clamp(0, calc((var(--p,0) - .18) / .42), 1) * 1.8deg))
    scale(calc(.9 + clamp(0, calc((var(--p,0) - .18) / .42), 1) * .1));
}
.no-js .edition-scene .edition-poster, .reduce .edition-scene .edition-poster { transform: rotate(-1.6deg); }

/* ---------- Four editions: lateral travel ---------- */
.edition-strip {
  display: flex; gap: 1.2rem; padding: 1.4rem var(--pad) 2rem;
  overflow-x: auto; scroll-snap-type: x proximity;
  scrollbar-width: thin; scrollbar-color: var(--muted) transparent;
}
.road.is-tracked .edition-strip { overflow: visible; will-change: transform; }
.edition-card {
  flex: 0 0 clamp(19rem, 31vw, 28rem); scroll-snap-align: start; margin: 0;
  background: color-mix(in srgb, var(--bone) 10%, transparent);
  border: 1.5px solid color-mix(in srgb, var(--bone) 24%, transparent);
  border-radius: 10px; padding: 1.5rem 1.4rem 1.6rem;
}
.edition-card time { display: block; font-size: clamp(2.4rem, 4vw, 3.6rem); font-weight: 800; font-variation-settings: "wdth" 78; line-height: .9; color: var(--yellow); }
.edition-card small { display: block; margin: .8rem 0 .5rem; font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; font-weight: 750; opacity: .8; }
.edition-card h3 { font-size: 1.24rem; text-transform: uppercase; font-variation-settings: "wdth" 90; margin-bottom: .55rem; }
.edition-card p { font-size: .94rem; line-height: 1.55; opacity: .9; margin: 0; }
.edition-quote { display: grid; align-content: center; }
.edition-quote p { font-family: var(--serif); font-style: italic; font-size: 1.3rem; line-height: 1.4; opacity: 1; }
.edition-quote cite { display: block; margin-top: 1rem; font-style: normal; font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; opacity: .75; }

/* The shared .road sets a bone ground (the homepage archive is on paper).
   The festival's past-editions track keeps its cobalt: the cards are drawn in
   bone-on-blue and lose their edges entirely on a light ground. */
#archive.road { background: var(--blue); color: var(--bone); }
#archive.road .road-rule { background: none; }
#archive.road .road-rule { border-radius: 2px; }
#archive.road .road-rule { background: color-mix(in srgb, var(--bone) 18%, transparent); }
#archive.road .road-rule i { background: var(--yellow); }
#archive.road .intro-copy { opacity: .9; }

/* ============================================================
   THE SHOWCASE REEL (v6.3) · looping stage footage on the festival page
   ============================================================ */
.reel-grid {
  margin-top: 1.8rem; display: grid; gap: 1rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  perspective: 1200px;
}
.reel-tile {
  margin: 0; position: relative; overflow: hidden; border-radius: 10px;
  background: var(--night-2); transform-style: preserve-3d;
  box-shadow: 0 40px 80px -40px rgba(0,0,0,.85);
}
.reel-tile video {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9;
  object-fit: cover; background: var(--night-2);
}
.reel-tile figcaption {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  padding: 1.6rem 1rem .8rem;
  background: linear-gradient(transparent, rgba(8,9,14,.82));
  font-size: .7rem; letter-spacing: .12em; text-transform: uppercase; font-weight: 700;
}
.reel-tile figcaption small { font-size: .7rem; letter-spacing: .14em; opacity: .72; font-weight: 650; }
/* the tiles assemble out of depth, same gesture as the photo gallery */
.js:not(.reduce) [data-scene].is-on .reel-tile {
  animation: frame-assemble .95s var(--out) backwards;
  animation-delay: calc(var(--step, 0) * var(--beat));
}
.no-js .reel-tile, .reduce .reel-tile { animation: none; opacity: 1; transform: none; }
@media (max-width: 760px) { .reel-grid { grid-template-columns: 1fr; } }

/* ============================================================
   THE SHOWCASE REEL (v6.5) · ten acts, cross-faded, on the festival page
   The stills list is the real content: it is what shows with no JavaScript and
   under reduced motion, and it stays in the accessibility tree either way.
   ============================================================ */
.reel { margin: 1.8rem 0 0; }
.reel-frame { display: none; position: relative; aspect-ratio: 16/9; border-radius: 12px; overflow: hidden; background: #000; box-shadow: 0 50px 100px -50px rgba(0,0,0,.9); }
.js:not(.reduce) .reel-frame { display: block; }
.reel-v { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; will-change: opacity, transform; transform: scale(1.05); backface-visibility: hidden; }
.reel-v.live { opacity: 1; }
.reel-wash { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(transparent 56%, rgba(8,9,14,.86)); }
.reel-cap { position: absolute; left: clamp(14px,2.5vw,30px); bottom: clamp(14px,2.5vw,28px); z-index: 3; }
.reel-cap b { display: block; font-size: clamp(1.4rem,3.2vw,2.6rem); text-transform: uppercase; font-variation-settings: "wdth" 82; line-height: 1; }
.reel-cap small { display: block; margin-top: .4rem; font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; opacity: .82; }
.reel-ticks { position: absolute; right: clamp(14px,2.5vw,30px); bottom: clamp(16px,2.5vw,30px); z-index: 3; display: flex; gap: 4px; }
.reel-ticks i { width: 16px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.26); transition: background .3s; }
.reel-ticks i.on { background: var(--yellow); }

/* the stills: the whole content, always in the DOM */
.reel-stills { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0,1fr)); }
.reel-stills li { position: relative; border-radius: 10px; overflow: hidden; background: var(--night-2); }
.reel-stills img { display: block; width: 100%; height: auto; }
.reel-stills span { position: absolute; left: 0; right: 0; bottom: 0; padding: 1.4rem .9rem .8rem; background: linear-gradient(transparent, rgba(8,9,14,.85)); }
.reel-stills b { display: block; font-size: .9rem; text-transform: uppercase; font-variation-settings: "wdth" 88; }
.reel-stills small { display: block; margin-top: .15rem; font-size: .7rem; letter-spacing: .14em; text-transform: uppercase; opacity: .78; }
@media (max-width: 760px) { .reel-stills { grid-template-columns: 1fr; } }
/* once the reel is running the stills stay for screen readers but leave the page */
.js:not(.reduce) .reel-stills { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* one clip per edition in the four-editions track — the archive shown, not just told */
.edition-film { margin: 0 0 1rem; border-radius: 8px; overflow: hidden; background: var(--night-2); }
.edition-film video { display: block; width: 100%; height: auto; aspect-ratio: 16/9; object-fit: cover; background: var(--night-2); }
/* Guest solos at GIDF are lit as a single figure on a wide dark stage — true to
   the room, but a speck in a card. A modest punch-in makes the performer read
   without losing detail (960px source shown at ~380px). */
.edition-film { position: relative; }
.edition-film video { transform: scale(1.3); transform-origin: 50% 42%; }
.edition-film figcaption {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 1.2rem .7rem .55rem;
  background: linear-gradient(transparent, rgba(8,9,14,.85));
  font-size: .7rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 700;
}

/* ============================================================
   FILM HERO (v6.7) · festival page
   Replaces a hero that was 53% empty cobalt with imagery at 18%. The wash keeps
   the brand colour and guarantees the type's ground, so no clip can make the
   headline unreadable (measured 58–78 luminance behind it across every clip).
   ============================================================ */
.film-hero { position: relative; min-height: 86vh; display: grid; align-items: end;
  padding: calc(var(--header-h) + 3rem) var(--pad) clamp(3rem, 7vh, 5rem); overflow: clip;
  background: var(--blue); color: var(--bone); }
.film-hero-media { position: absolute; inset: 0; z-index: 0; }
.film-v { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .7s linear; will-change: opacity; }
.film-v.live { opacity: 1; }
/* with no JS the first video never gets a source, so its poster is the hero */
.no-js .film-v:first-child, .reduce .film-v:first-child { opacity: 1; }
.film-hero-wash { position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(100deg, color-mix(in srgb, var(--blue) 92%, transparent) 0%,
      color-mix(in srgb, var(--blue) 72%, transparent) 34%,
      color-mix(in srgb, var(--night) 35%, transparent) 62%,
      color-mix(in srgb, var(--night) 55%, transparent) 100%),
    linear-gradient(transparent 50%, rgba(8,9,14,.6)); }
.film-hero-copy { position: relative; z-index: 3; max-width: 46rem; }
/* One stack, so the space between them is a gap rather than two guessed offsets.
   Positioned separately they overlapped by 4–6px at most sizes — the stamp's
   height moves with its clamped type, so no fixed `top` for the credit holds. */
.film-hero-meta { position: absolute; z-index: 4; right: var(--pad);
  top: calc(var(--header-h) + clamp(1rem, 4vh, 2.6rem));
  display: grid; justify-items: end; gap: clamp(.9rem, 2.2vh, 1.6rem); }
/* the base .date-stamp is justify-self:start; without overriding it the stamp
   rides the container's LEFT edge, which moves as the credit text below changes
   width — a 74px horizontal swing per clip. Pin both children right instead. */
.film-hero-meta > * { justify-self: end; }
.film-hero .date-stamp { position: relative; margin: 0; transform: rotate(2.6deg); }
.film-hero-now { position: relative; margin: 0; text-align: right;
  font-size: .7rem; letter-spacing: .16em; text-transform: uppercase; opacity: .85; }


.film-hero-now b { display: block; font-size: .95rem; letter-spacing: .02em; margin-bottom: .15rem; }
.film-hero-cue { position: absolute; z-index: 4; left: var(--pad); bottom: 1.1rem; margin: 0;
  display: flex; align-items: center; gap: .6rem; font-size: .58rem; letter-spacing: .2em;
  text-transform: uppercase; opacity: .7; }
.film-hero-cue i { display: block; width: 1px; height: 24px; background: currentColor; }
.js:not(.reduce) .film-hero-cue i { animation: cue-drop 1.9s ease-in-out infinite; }
@keyframes cue-drop { 0%,100% { transform: scaleY(.4); transform-origin: top; opacity: .4 }
  50% { transform: scaleY(1); transform-origin: top; opacity: 1 } }
.no-js .film-hero-now, .reduce .film-hero-now, .no-js .film-hero-cue { display: none; }
@media (max-width: 760px) {
  .film-hero { min-height: 78vh; }
  /* in flow, under the buttons — parked absolutely at bottom-left it lands on
     "What to expect" (caught by the buried-controls guard) */
  /* the stack drops into flow after the copy; the credit is hidden on phones */
  .film-hero-meta { position: relative; right: auto; top: auto; justify-items: start; margin-top: 1.4rem; }
  .film-hero-meta > * { justify-self: start; }
  .film-hero .date-stamp { transform: rotate(-2.4deg); }
  .film-hero-now, .film-hero-cue { display: none; }
}
