/* ==========================================================================
   TRACK EXPERIENCE — arch-depth carousel, single always-focused view.
   The centered track is always the hero and the focus panel below it is
   always populated; there is no separate rest state. Browsing: drag,
   arrow keys, the ‹ › buttons, or clicking a side card. No wheel, no
   hover-pan — both fought the user rather than helping.
   Pixel values below (gap, margins, the 75%/2.5 push-pull constants) were
   tuned empirically against a 250px card and are load-bearing — changing
   card width will shift the whole layout.
   ========================================================================== */

.track-experience {
  /* --card-w is the LAYOUT STEP: how far apart consecutive cards sit. The card
     BOX is 1.85x that, and the hero renders at scale 1 rather than being scaled
     up from a small box — a card rasterised at 250px and then blown up 1.85x
     loses most of its detail. A negative right margin pulls the advance back to
     --card-w so the arch spacing is unchanged. --hero-w adds the 1.0588
     perspective foreshortening at translateZ(100px) under perspective:1800px. */
  --card-w: 250px;
  --card-box: calc(var(--card-w) * 1.85);
  --arc-h: 640px;             /* .track-arc-viewport min-height */
  --hero-w: calc(var(--card-box) * 1.0588);
  position: relative;
  /* MEASURED: this section's content is about 1044px tall. At min-height:100svh
     with justify-content:center, any viewport taller than that centres the
     content and splits the surplus above and below — on a 1290px-tall window
     that is ~123px of dead space between the hero video and "Enter the Tracks",
     and another ~123px before the newsletter. On a 900px viewport the content is
     taller than the box so centring never engages and the gap does not exist,
     which is why it only shows on large displays.
     flex-start pins the intro directly under the hero, and min-height:auto lets
     the section be exactly as tall as it needs. The arch geometry is untouched —
     it is driven by --card-w, --arc-h and the focus panel's negative margin, none
     of which are affected by how the section box is sized. */
  min-height: auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-block: var(--space-8);
  /* Where an anchor jump to #tracks parks this section, measured from the top of
     the viewport. Driven by the LIVE nav height that js/nav.js publishes as
     --nav-h, never by a hardcoded number.

     The direction matters and only one direction is safe. Land the section BELOW
     the nav's bottom edge and the strip between them shows whatever precedes it
     in the document, which at this point in the page is the last rows of the
     hero video — a thin band of moving footage under the nav's hairline. Land it
     slightly ABOVE and the section just tucks a few pixels under an opaque bar,
     invisibly: it carries 32px of its own padding-top, so none of its content is
     hidden either way. Hence minus 4px rather than exactly the nav height.

     THIS WAS HARDCODED AT 86px AND IT WAS WRONG. 86 came from measuring the nav
     at 89px — but that measurement was taken with the Google Fonts request
     blocked, so it was the fallback font's line box, not the real one. With the
     real webfonts the bar is shorter, the section landed ~8px BELOW it, and a
     five-pixel strip of hero video sat under the hairline. Proof it was the
     video and not a gradient: across two settled frames 0.3s apart, that strip
     changed by 2.10 levels while the bar above it changed by 0.000.

     The 72px fallback is only reachable with JavaScript off, and errs downward
     on purpose — too small merely tucks the section further under an opaque bar,
     too large puts the hero back on screen. */
  scroll-margin-top: calc(var(--nav-h, 72px) - 4px);
  overflow: hidden;
  background: var(--color-black);
  opacity: 0;
  transition: opacity var(--motion-slow) var(--ease-standard);
}
.track-experience.is-visible { opacity: 1; }

/* THE REVEAL'S MOVEMENT LIVES ON THE WRAPPER, NOT ON THE SECTION.
   It used to be transform: translateY(24px) on .track-experience itself. That
   moves the SECTION BOX, not just what you see in it: the section is laid out
   at the hero's bottom edge but painted 24px lower, so it vacates a 24px strip
   at exactly the seam between the two — and the thing showing through that strip
   is the last 24px of the hero video. Clicking MUSIC lands you right there, with
   the fixed nav's hairline immediately above it, so the strip reads as a gap
   under the line with video moving in it. (The same offset also made the section
   overlap the newsletter below it by 24px, which is where the other end of the
   discrepancy went.)
   Moving the transform one level in fixes it structurally rather than by
   retuning: .track-arc-wrap holds every child of the section, so the entrance
   looks identical, but the section's own box now stays exactly where it is laid
   out and there is no strip to vacate. Safe for the carousel's 3D: perspective
   is set on .track-arc-viewport, which is BELOW this element, so nothing in the
   arch's transform chain passes through here. */
.track-arc-wrap {
  position: relative;
  padding-block: var(--space-4);
  transform: translateY(24px);
  transition: transform var(--motion-slow) var(--ease-standard);
}
.track-experience.is-visible .track-arc-wrap { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .track-arc-wrap { transform: none; transition: none; }
}

.track-arc-viewport {
  position: relative;
  overflow: visible;
  min-height: var(--arc-h);
  display: flex;
  align-items: flex-start;
  padding-top: var(--space-6);
  perspective: 1800px;
  cursor: grab;
}
.track-arc-viewport.is-dragging { cursor: grabbing; }

.track-arc {
  display: flex;
  align-items: center;
  gap: 0.75px;
  list-style: none;
  margin: 0;
  padding: 0 0 50px;
  touch-action: pan-y;
  user-select: none;
  transform-style: preserve-3d;
}

.track-card {
  position: relative;
  flex: 0 0 auto;
  width: var(--card-box);
  margin-right: calc(var(--card-w) - var(--card-box)); /* negative: restores the step */
  aspect-ratio: 1 / 1;
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  cursor: pointer;
  padding: 0;
  transition: filter 0.4s ease;
}

/* will-change is applied ONLY while the carousel is actually moving.
   Left on permanently it promotes every card to its own composited layer and
   Chrome caches a raster scale for that layer — so a card that arrives at the
   centre keeps the low-resolution raster it was given while it was a small,
   scaled-down side card, and looks soft until something forces a repaint.
   Scoping the hint to the animating state lets each card re-rasterise at its
   settled size. */
.track-arc.is-animating .track-card { will-change: transform, filter; }
.track-arc.is-animating { will-change: transform; }

/* First paint only: cards would otherwise flash at full brightness and then
   visibly dim over 0.4s as their filters transition in from nothing. */
.track-arc.is-priming .track-card { transition: none; }
.track-card__media {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
}
.track-card__still { background: var(--bg-surface); }
/* Video sits on top of the still and is invisible until the track rolls, so a
   buffered-but-paused video can never replace the sharper still. */
.track-card__video { opacity: 0; transition: opacity 0.35s ease; }
.track-card.is-rolling .track-card__video { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .track-card__video { transition: none; }
}
.track-card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; }


/* ---- Settled hero overlay: the sharpness fix ----
   `perspective` on .track-arc-viewport and `transform-style: preserve-3d` on
   .track-arc place every card in one shared 3D rendering context, and Chrome
   rasterises that whole context once at a fixed scale. Measured on a 2560x1440
   display at DPR 1 (variance-of-Laplacian on the settled hero): a card inside
   the 3D context reads 612 where the identical image outside it reads 1087.
   Removing EITHER the perspective or the preserve-3d restores it (1072 / 1068),
   so it is the ancestor chain that holds the card there — giving the card its
   own 2D transform does NOT get it out, which is why the earlier flatten fix
   did not land.

   So the settled hero is painted here instead: a sibling of .track-arc-viewport
   and therefore outside the 3D context entirely. Measured 1056 = 0.97x of a
   plain untransformed image. The in-row card is left in place at opacity 0 so
   hit-testing, focus and the arch geometry are all untouched. */
.track-hero-layer {
  position: absolute;
  top: 0; left: 0;
  box-sizing: border-box;
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  pointer-events: none;   /* clicks fall through to the real card underneath */
  visibility: hidden;
  z-index: 900;
  overflow: hidden;
}
.track-hero-layer.is-showing { visibility: visible; }
.track-hero-layer img,
.track-hero-layer video {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  -webkit-user-drag: none;
}
/* The overlay carries the hero's video too, for the same reason it carries the
   still: a video left in the card is back inside the 3D rendering context and
   pays the same rasterisation cost. Only one of the two ever decodes at a
   time — see syncLayerVideo() in track-experience.js. Timing must match
   VIDEO_FADE_MS. */
.track-hero-layer video { opacity: 0; transition: opacity 0.35s ease; }
.track-hero-layer.is-rolling video { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .track-hero-layer video { transition: none; }
}


/* ---- Intro heading: permanent, sits just above the hero card ----
   The hero card is scaled up, so its top edge overflows the carousel viewport
   and would collide with the type. Required clearance was measured at nine
   widths (250px card -> 138px, 190 -> 104..111, 164 -> 92, 151 -> 87) and fits
   `0.48 * card + 18px` within a few px. Add the gap we actually want to see
   (24px) and the whole thing self-scales — no per-breakpoint overrides. */
.track-experience__intro {
  text-align: center;
  margin-bottom: calc(var(--card-w) * 0.48 + 42px); /* clearance + 24px gap */
}
.track-experience__intro .label { display: block; margin-bottom: var(--space-2); }
.track-experience__intro h2 {
  font-family: var(--font-display-stencil);
  margin: 0;
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  line-height: 1;
}
/* Streaming-status note. Bottom margin stays 0 on purpose: the clearance gap to
   the hero card is owned by .track-experience__intro's margin-bottom formula
   above, and a margin here would stack with it and open a second gap. */
.track-experience__intro .track-experience__note {
  max-width: 60ch;
  margin: var(--space-3) auto 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text-secondary);
}

/* ---- Focus panel: in-place, below the carousel (not a fullscreen takeover) ---- */
/* The panel is pulled up into the slack the enlarged hero leaves at the bottom
   of the carousel viewport: slack = arc-height - (card * 1.425) + 4px, measured.
   Expressed as a formula so it stays flush at every card size instead of
   needing a hand-tuned value per breakpoint. */
.track-focus-panel {
  max-width: 760px;
  margin: calc(var(--card-w) * 1.425 - var(--arc-h) - 4px) auto 0;
  text-align: center;
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
  transition: opacity 0.4s ease, transform 0.4s ease, margin 0.4s ease;
}
.track-focus-panel.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }

.track-focus-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: var(--hero-w);
  max-width: 100%;
  margin: 0 auto var(--space-3);
}
.track-focus-nav__counter {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: var(--tracking-label);
  color: var(--text-muted);
  display: flex;
  align-items: baseline;
  gap: 0.45em;
}
.track-focus-nav__index { color: var(--track-accent, var(--text-primary)); }
.track-focus-nav__sep { opacity: 0.5; }
.track-focus-nav__btn {
  background: transparent;
  border: none;
  color: var(--text-primary);
  width: 38px; height: 38px;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 1.05rem;
  display: flex; align-items: center; justify-content: center;
}
.track-focus-nav__btn:hover:not(:disabled) { color: var(--color-bone); }
.track-focus-nav__btn:disabled { opacity: 0.25; cursor: not-allowed; }

.track-focus-panel__eyebrow { display: block; margin-bottom: 4px; color: var(--track-accent, var(--text-muted)); font-family: var(--font-mono); font-size: var(--fs-caption); letter-spacing: var(--tracking-label); text-transform: uppercase; }
/* The title is carried by the artwork itself, so it is hidden visually but kept
   in the DOM (.sr-only) so screen readers and search engines still get it. */
.track-focus-panel__title.sr-only { margin: 0; }
.track-focus-panel__desc { color: var(--text-secondary); font-size: 0.95rem; line-height: 1.5; margin: 0 auto var(--space-2); max-width: 60ch; }

.track-sample-player { display: flex; align-items: center; gap: var(--space-3); max-width: 360px; margin: 0 auto var(--space-2); }
.track-sample-player__btn {
  width: 36px; height: 36px;
  flex-shrink: 0;
  border: 1px solid var(--track-accent, var(--color-white));
  background: transparent;
  /* Glyph takes the accent too, not just the ring — otherwise the button reads
     as outlined-in-the-artwork-colour rather than as part of the artwork. */
  color: var(--track-accent, var(--text-primary));
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.track-sample-player__btn:hover:not(:disabled) { background: var(--track-accent, var(--color-white)); color: var(--color-black); }
.track-sample-player__btn:disabled { opacity: 0.4; cursor: not-allowed; }
.track-sample-player__bar { flex: 1; height: 3px; background: var(--color-gray-600); position: relative; }
.track-sample-player__bar-fill { position: absolute; inset: 0; width: 0%; background: var(--track-accent, var(--color-white)); transition: width 0.15s linear; }
.track-sample-player__status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

.track-focus-panel__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-bottom: var(--space-2); }
/* Tighter than the global .btn so all five actions sit on one row. */
.track-focus-panel__actions .btn { font-size: 0.78rem; padding: 0.55rem 0.9rem; }
.track-focus-panel__actions .btn[disabled],
.track-focus-panel__actions .btn.is-placeholder { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
.track-focus-panel__actions-note {
  font-family: var(--font-body);
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--text-muted);
  max-width: none;
  margin: var(--space-2) auto 0;
}

.track-focus-panel__close {
  display: block;
  margin: var(--space-2) auto 0;
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  cursor: pointer;
}
.track-focus-panel__close:hover { color: var(--text-primary); }

/* ---- Responsive ---- */
@media (max-width: 1024px) {
  .track-experience { --card-w: 190px; }
}

@media (max-width: 768px) {
  .track-experience { min-height: auto; padding-block: var(--space-12); }
  .track-experience { --card-w: min(42vw, 190px); --arc-h: 420px; }
}

/* ---- Short windows ----------------------------------------------------------
   V2HANDOFF 44 open item 4 / 45 open item 4: the carousel is cut off at common
   laptop heights. Closed Aug 23 2026.

   WHAT WAS ACTUALLY WRONG, because it is not what it looks like. Nothing here
   is viewport-relative: .track-experience is min-height:auto on purpose (the
   note at the head of this file says why), so the arch is the same size on a
   720px window as on a 1290px one. js/deep-field-bg.js restPoint() measures the
   PAINTED UNION of .track-arc li plus .track-focus-panel, centres it in the room
   under the masthead, and when there is not enough room it pins the top and lets
   the bottom fall off the screen. That last part is a DELIBERATE decision, in as
   many words at restPoint():

     "the card is the thing being looked at, and losing the last line of fine
      print off the bottom is the better failure than beheading the artwork"

   THAT DECISION IS NOT BEING REVERSED HERE, and it must not be. Music has one
   stop and the stepper glides you back onto it, so the lost tail is not merely
   below the fold, it is unreachable by scrolling. Sharing the loss between top
   and bottom instead would behead the artwork AND still hide the tail. The only
   honest fix is to stop overflowing: make the union small enough to fit.

   MEASURED Aug 23 2026, Playwright/Chromium at 1440 wide, union height against
   --card-w, which is the only variable that moves it:

       --card-w   union    needs (union + 92 masthead)
         250px     761px      853px      <- ships above 880px tall
         235px     733px      825px
         225px     714px      806px
         215px     695px      787px
         205px     676px      768px
         195px     658px      750px
         190px     648px      740px

   That is linear at 1.883px of union per px of card. The three steps below are
   NOT set to the bare arithmetic, though, for a reason worth knowing about:

   THE UNION SITS ABOUT 18px LOWER BEFORE THE ENTRANCE HAS PLAYED. restPoint()
   subtracts the pending translateY(24px) that .track-arc-wrap carries until
   .is-visible lands, which is right, but the two states do not agree exactly
   and the section is landed on in the earlier one. MEASURED at the rest point,
   union top, same page, same width:

       1440x900   before the title card hands over   133      after   115
       1366x768                                      135              117
       1280x720                                      135              117

   The cards are still at opacity 0 in the earlier state, so nothing is visibly
   cut there and this is not a bug report. It does mean an arithmetic-tight
   value clips for the half second before the reveal, so each step below is one
   notch smaller than the settled arithmetic needs and clears BOTH states.

   VERIFIED Aug 23 2026, parked on Music with flick-shaped wheel bursts (a
   scripted scrollIntoView does not run a leg and lands about 18px off), read
   off window.__deepField().card after the title card hands over:

       1440x900   card-w 250  union 761   bottom 876/900   24px spare
       1440x860   card-w 220  union 704   bottom 846/860   14px spare
       1440x800   card-w 178  union 626   bottom 776/800   24px spare
       1366x768   card-w 178  union 626   bottom 743/768   25px spare
       1280x720   card-w 152  union 577   bottom 694/720   26px spare

   1440x900 is the untouched shipped case and is listed to show the fix does not
   reach it. Zero console errors and zero horizontal overflow at all five. The
   1280x720 capture was looked at: the counter row, the eyebrow, the blurb, the
   sample player, all five buttons, the disclaimer and the close control are all
   on screen, which is the whole complaint closed.

   IF THE 18px IS EVER RECONCILED in restPoint(), every step here can go back up
   by roughly ten pixels. That is the only headroom left on the table, and it is
   in js/deep-field-bg.js, not in this file.

   --arc-h IS NOT IN THIS BLOCK, and adding it would do nothing. Lowering it
   shrinks .track-arc-viewport and makes the focus panel's negative top margin
   less negative by exactly the same amount, because that margin is derived from
   it: calc(var(--card-w) * 1.425 - var(--arc-h) - 4px). The panel's absolute
   position, and so the union, is unchanged. It is only the viewport's own
   min-height, and the cards already exceed it. Measured, not assumed.

   WHY min-width: 769px ON ALL THREE. Below 769 the block above already sets
   --card-w: min(42vw, 190px), which is width-relative on purpose, and
   js/deep-field-bg.js switches itself off entirely. Without this guard a phone
   held in landscape is short enough to match these queries and would take a
   fixed pixel card in place of its fluid one.

   The head of this file warns that the push-pull constants were tuned against a
   250px card and that changing the width shifts the layout. That is the intent
   here, and it is the same move this file already makes twice at 1024 and 768;
   the arch simply gets smaller, and a smaller hero card also RISES less, so the
   140px of headroom .ksd-bleed holds for it (V2HANDOFF 31 item 1) is never at
   risk from this direction. */
@media (min-width: 769px) and (max-height: 880px) {
  .track-experience { --card-w: 220px; }
}
@media (min-width: 769px) and (max-height: 805px) {
  .track-experience { --card-w: 178px; }
}
@media (min-width: 769px) and (max-height: 749px) {
  .track-experience { --card-w: 152px; }
}

@media (prefers-reduced-motion: reduce) {
  .track-card { transition: none; }
  .track-experience__intro,
  .track-focus-panel { transition: none; }
}
