/* ════════════════════════════════════════════════════════════════════
   PAGE — PROJECT DETAIL (shared by page-detail-1941-nash-ambassador +
   page-detail-1937-imperial-black-betty). ONE builder, ×2 pages, ONE stylesheet.

   Authority: WOW_CONTRACTS page:detail + picks.json page:detail (G001 board +
   G003 grafts) + DESIGN.md token block + the locked slice
   (section-page-detail.jpg). The project case file, rendered in stamped steel:
     · a bleed-under-headline detail HERO (matches the board's title-over-photo)
     · a single scrolling stack: OVERVIEW / THE PROCESS / BEFORE & AFTER /
       GALLERY / SPECIFICATIONS (the 5-tab nav was killed — David found it
       unintuitive + the Overview/Specs tabs near-redundant)
     · a mono spec plate (YEAR/MAKE/MODEL/BODY/TYPE/COMPLETED) that odometer-rolls
     · a CONTAINED echo of the cell-4 resurrection (real stage stills) in PROCESS
     · the §1.7 scroll/drag before-after seam (ONLY where a real pair exists)
     · a photo contact-sheet GALLERY (their weekly-photo-documentation model as UI)
     · the real per-car YouTube build playlist + SEE THE NEXT PROJECT

   Scoped under the page-root .detail so nothing leaks into shared chrome.
   Palette sampled to tokens: base #222222 · Signal Red #C2231B · radius 0px.
   Alive at t0 (no scroll-reveal-gated content); tight, never laggy; hooks the
   shared .tungsten-idle for ambient life. Cohesive with the home cells — same
   plate/mono/stamped-steel vocabulary, same --resto seam idiom.
   ════════════════════════════════════════════════════════════════════ */

/* the subpage <main> sits under the fixed nav; give the hero room to bleed up */
.detail { --plate-edge: rgba(255,255,255,0.12); --plate-face: #2c2c2c; }

/* ─────────────────────────────────────────────────────────────────────
   1 · DETAIL HERO — the case file cover. Title bleeds OVER a full-bleed car
   photo (board: text-left, photo-right, both under one dark ground). On the
   board the photo is a right-hand bleed; we honour that at desktop and stack
   to a full-bleed banner on mobile. Kills the default .section padding.
   ───────────────────────────────────────────────────────────────────── */
.detail-hero {
  position: relative;
  padding-block: 0;
  background: var(--ink-900);
  overflow: hidden;
  isolation: isolate;
}
.detail-hero__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  align-items: stretch;
  min-height: clamp(30rem, 46vw, 40rem);
  padding-top: var(--nav-h);           /* clear the fixed nav */
}
/* the photo lane — full-bleed to the right screen edge. Explicitly pinned to
   row 1 so it fills the FULL hero height in column 2 (the title overlaps it in
   the same cell via .detail-hero__inner spanning both columns). */
.detail-hero__media {
  position: relative;
  grid-column: 2; grid-row: 1;
  margin-right: calc((100vw - min(100vw, var(--maxw-wide))) / -2 - var(--gutter));
  overflow: hidden;
}
.detail-hero__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center 42%;
  /* the ONE consistent grade: slight desat + warm lift (DESIGN.md) */
  filter: saturate(0.9) contrast(1.03) brightness(1.02);
}
/* left→right scrim so the title reads over the photo's left flank + a bottom
   feather so the spec-chip line sits on calm ground (T2.19 — dissolve, no seam) */
.detail-hero__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(90deg,
      var(--ink-900) 0%, rgba(34,34,34,0.86) 30%,
      rgba(34,34,34,0.30) 56%, rgba(34,34,34,0) 74%),
    linear-gradient(0deg,
      rgba(20,20,20,0.55) 0%, rgba(20,20,20,0) 34%);
}
/* the warm tungsten worklight glow — hooks the shared idle; NEVER on the photo */
.detail-hero__glow {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(60% 80% at 78% 30%,
    rgba(255,176,96,0.16) 0%, rgba(255,150,70,0.05) 40%, transparent 72%);
  mix-blend-mode: screen;
}
.detail-hero__inner {
  position: relative; z-index: 2;
  grid-column: 1 / -1; grid-row: 1;
  align-self: center;
  padding-block: var(--space-xl) var(--space-l);
  max-width: min(38rem, 60vw);
}
.detail-hero__file {
  display: inline-flex; align-items: center; gap: 0.7em;
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--slate-400);
  margin-bottom: var(--space-m);
}
.detail-hero__file::before {
  content: ""; width: 2.2rem; height: 1px; background: var(--support); flex: none;
}
.detail-hero__file .is-status { color: var(--breath); }
.detail-hero__title {
  font-family: var(--font-display); font-weight: 800;
  font-size: var(--step-hero); line-height: 0.92;
  letter-spacing: 0.005em; text-transform: uppercase;
  color: var(--breath);
  margin-bottom: var(--space-m);
}
.detail-hero__title .em { font-style: normal; color: var(--accent-text); }  /* H1A-red word */
.detail-hero__spec {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.55em 1.1em;
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--slate-400);
}
.detail-hero__spec span { display: inline-flex; align-items: center; gap: 1.1em; }
.detail-hero__spec span::after {
  content: "·"; color: var(--support); opacity: 0.7;
}
.detail-hero__spec span:last-child::after { content: none; }

/* ─────────────────────────────────────────────────────────────────────
   2 · STACKED PANELS — the case file is now ONE scrolling page (the tab bar +
   NEXT button were killed: David found the 5-tab interface unintuitive and the
   Overview/Specifications tabs near-redundant). The five panels — overview →
   process → before&after → gallery → specs — stack top-to-bottom and read as a
   composed scroll. A hairline rule + the red mono section-marker (promoted from
   the old no-JS-only .detail-panel__label) separate the panels so the stack
   reads as distinct sections, not one undifferentiated column.
   ───────────────────────────────────────────────────────────────────── */
.detail-body { background: var(--ink-900); }
.detail-panel { padding-block: var(--space-xl); }
/* hairline separator between stacked sections (skip the first) */
.detail-panel + .detail-panel { border-top: 1px solid var(--ink-700); }

/* the mono section marker — was no-JS-only; now it's the visible eyebrow that
   heads every stacked panel so the scroll reads section-by-section. The red
   accent + rule echoes the .detail-sec-head__eyebrow idiom. */
.detail-panel__label {
  display: inline-flex; align-items: center; gap: 0.7em;
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--accent-text); margin-bottom: var(--space-m);
}
.detail-panel__label::before {
  content: ""; width: 1.8rem; height: 1px; background: var(--accent);
}

.detail-sec-head {
  display: flex; align-items: baseline; gap: 0.9em; flex-wrap: wrap;
  margin-bottom: var(--space-l);
}
.detail-sec-head__eyebrow {
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--slate-400);
  display: inline-flex; align-items: center; gap: 0.7em;
}
.detail-sec-head__eyebrow::before {
  content: ""; width: 1.8rem; height: 1px; background: var(--support);
}
.detail-h2 {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--step-section); line-height: 1; text-transform: uppercase;
  letter-spacing: 0.01em; color: var(--breath);
}

/* ── 3a · OVERVIEW panel — narrative left, spec plate + playlist right ── */
.detail-overview {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(2rem, 4vw, 4rem); align-items: start;
}
.detail-lede p { color: var(--text-on-dark); max-width: 42ch; }
.detail-lede p + p { margin-top: var(--space-m); }
/* ── the AWARDS card — MOVED out of the left story lede and re-seated in the
   right aside, directly UNDER the build playlist (David: the lone playlist card
   left a dead ~25% bottom-right corner; the awards fill it). It now reads as a
   PAIRED plate with the playlist — a full 1px plate border (matching
   .detail-playlist) instead of the old story-pullquote left-bar, with a compact
   red accent stripe at the top-left corner keeping the signal-red idiom. Shrunk:
   tighter block padding + smaller chips so it seats cleanly under the playlist. */
.detail-lede__award {
  margin-top: var(--space-m);
  padding: var(--space-s) var(--space-m);
  background: var(--ink-850);
  border: 1px solid var(--ink-700);
  position: relative;
}
.detail-lede__award::before {
  content: ""; position: absolute; left: 0; top: 0; width: 3px; height: 2.2rem;
  background: var(--accent);
}
.detail-lede__award-eyebrow {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--accent-text); display: block; margin-bottom: 0.55em;
}
/* the award chips read as ONE tidy cluster — never an orphaned "Best in Class"
   dropping alone (David flagged the lone-chip wrap + the dead gap beside it). The
   chips balance across the row via flex; tightened tracking/padding lets the five
   awards sit without a half-empty last line. */
.detail-lede__award ul { list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem 0.4rem; }
.detail-lede__award li {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.03em;
  text-transform: uppercase; color: var(--breath); white-space: nowrap;
  padding: 0.3em 0.5em; background: #232323; border: 1px solid var(--ink-700);
}
.detail-lede__award-cite {
  margin-top: 0.6rem; font-family: var(--font-body); font-size: 0.72rem;
  color: var(--slate-400); font-style: normal; line-height: 1.4;
}

/* ── the SPEC PLATE — mono ruled table; values odometer-roll when the specs
   section scrolls into view (was: on spec-tab open) ── */
.spec-plate {
  border: 1px solid var(--ink-700);
  background: linear-gradient(180deg, #262626 0%, #202020 100%);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.03), 0 14px 32px -22px #000;
}
.spec-plate__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.85rem 1.1rem; border-bottom: 1px solid var(--ink-700);
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--slate-400);
}
.spec-plate__head-id { color: var(--accent-text); }
.spec-plate__row {
  display: grid; grid-template-columns: 8.5rem 1fr;
  border-bottom: 1px solid var(--ink-700);
}
.spec-plate__row:last-child { border-bottom: 0; }
.spec-plate__key,
.spec-plate__val {
  font-family: var(--font-mono); font-size: 0.82rem; letter-spacing: 0.08em;
  text-transform: uppercase; padding: 0.72rem 1.1rem;
}
.spec-plate__key { color: var(--slate-400); border-right: 1px solid var(--ink-700); }
.spec-plate__val { color: var(--breath); font-weight: 500; }
/* ── the REAL odometer: each character in its OWN boxed steel cell; the JS
   spins each cell's digit column L→R and lands hard on the true glyph (build it
   real or don't). Non-numeric glyphs (spaces, etc.) render as plain inline text —
   only 0-9 get a boxed spinning cell. ── */
.spec-plate__val .odo {
  display: inline-flex; align-items: stretch; gap: 2px;
  vertical-align: baseline;
}
.spec-plate__val .odo__digit {
  position: relative; display: inline-block; overflow: hidden;
  width: 0.72em; height: 1.28em; line-height: 1.28em;
  background: #191919;
  border: 1px solid var(--ink-700);
  box-shadow: inset 0 6px 8px -6px #000, inset 0 -6px 8px -6px #000;  /* drum curvature */
  text-align: center;
}
.spec-plate__val .odo__strip {
  display: block; will-change: transform;
  /* JS sets the transition per-cell for the staggered L→R landing */
}
.spec-plate__val .odo__cell {
  display: block; width: 100%; height: 1.28em; line-height: 1.28em;
  font-variant-numeric: tabular-nums;
}
/* the machined seam across the drum centre — reads as a real counter wheel */
.spec-plate__val .odo__digit::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: rgba(0,0,0,0.55); transform: translateY(-0.5px); z-index: 2; pointer-events: none;
}
.spec-plate__val .odo__sep {
  display: inline-block; padding: 0 0.06em; line-height: 1.28em;
}

/* the OVERVIEW right column — the build PLAYLIST lives here (the story is the
   left lede). The full spec table was pulled from OVERVIEW so it no longer
   duplicates the SPECIFICATIONS tab (T2.25 — no two tabs doing the same job). */
.detail-overview__aside { display: flex; flex-direction: column; }
.detail-overview__aside-eyebrow {
  font-family: var(--font-mono); font-size: var(--step--1);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--slate-400); margin-bottom: var(--space-m);
  display: inline-flex; align-items: center; gap: 0.7em;
}
.detail-overview__aside-eyebrow::before {
  content: ""; width: 1.8rem; height: 1px; background: var(--support);
}

/* ── the BUILD PLAYLIST plate (board: bottom-right) ──
   WHOLE tile is one hit target now (David: "I just assumed clicking on anywhere
   on that tile would bring me somewhere" → new tab into the build playlist, T2.38).
   The tile is positioned so a single full-bleed link overlay covers stage + meta;
   the poster play-plate stays a visual affordance (its own link handles the
   lazy-iframe-vs-YouTube fork; the overlay is the fallback for the rest of the
   tile). Relative so the overlay pins to the whole plate. */
.detail-playlist {
  position: relative;
  border: 1px solid var(--ink-700); background: var(--ink-850);
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  transition: border-color var(--dur-fast) var(--ease-out);
}
/* the full-tile link — floats OVER the whole plate (stage + meta are visual only,
   so covering them is fine) EXCEPT the play control, which is lifted above it so
   its click still mounts the inline player. Anywhere else on the tile → new tab. */
.detail-playlist__tilelink {
  position: absolute; inset: 0; z-index: 2;
  /* no visible box — the whole plate is the target */
}
/* the poster's play link fills the stage and sits ABOVE the tile-link; both target
   the same playlist in a new tab, so wherever the visitor clicks the tile they land
   on the build playlist (David's whole-tile call). */
.detail-playlist__play { z-index: 3; }
@media (hover: hover) {
  .detail-playlist:hover { border-color: var(--accent-text); }
}
.detail-playlist__stage {
  /* R87c (David's "rethink this"): the poster fills the FULL card height —
     when the meta column runs taller than 16:9 (Betty), the fixed ratio left
     a dead ink band under the car inside the card. Stretch wins; the 16:9
     preference only matters stacked (see the ≤760 block). */
  position: relative; height: 100%; min-height: 13rem; overflow: hidden;
  background: #111; border-right: 1px solid var(--ink-700);
}
.detail-playlist__stage iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* poster fallback / no-JS: a framed still + a stamped red play plate that links out */
.detail-playlist__poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.9) contrast(1.03) brightness(0.96); }
.detail-playlist__play {
  position: absolute; inset: 0; display: grid; place-items: center; z-index: 2;
}
.detail-playlist__play-plate {
  width: 4.4rem; height: 3.1rem; display: grid; place-items: center;
  background: var(--accent); border-radius: var(--radius);
  box-shadow: 0 4px 0 var(--terracotta-deep);
  transition: transform 140ms var(--ease-out), box-shadow 140ms var(--ease-out);
}
.detail-playlist__play-plate::after {
  content: ""; width: 0; height: 0; margin-left: 4px;
  border-style: solid; border-width: 0.68rem 0 0.68rem 1.05rem; border-color: transparent transparent transparent #fff;
}
/* ambient red-ring pulse — powered-on at rest (WOW: play plates not too-YouTube) */
.detail-playlist__play::before {
  content: ""; position: absolute; width: 5.4rem; height: 5.4rem; border-radius: 50%;
  border: 2px solid var(--accent); opacity: 0; z-index: 1;
  animation: play-ring 3.4s ease-out infinite;
}
@keyframes play-ring {
  0% { transform: scale(0.72); opacity: 0.55; }
  70% { opacity: 0; }
  100% { transform: scale(1.12); opacity: 0; }
}
@media (hover: hover) {
  .detail-playlist__play:hover .detail-playlist__play-plate { transform: translateY(3px); box-shadow: 0 1px 0 var(--terracotta-deep); }
}
@media (prefers-reduced-motion: reduce) { .detail-playlist__play::before { animation: none; } }
.detail-playlist__meta { padding: var(--space-m); display: flex; flex-direction: column; }
.detail-playlist__title {
  font-family: var(--font-display); font-weight: 700; font-size: 1.35rem;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--breath); margin-bottom: 0.5em;
}
.detail-playlist__desc { font-size: var(--step--1); color: var(--slate-400); line-height: 1.55; }
.detail-playlist__count {
  margin-top: auto; padding-top: var(--space-s);
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--breath);
}
.detail-playlist__count b { color: var(--accent-text); font-weight: 500; }

/* ── 3b · THE PROCESS panel — contained echo of the cell-4 resurrection.
   A pinned, scroll-scrubbed stage that steps through FOUND→STRIPPED→REBUILT→
   DELIVERED using REAL stage stills of THIS car's process (honestly framed as
   "the shop's process, stage by stage" — NOT four cars pretending to be one).
   Same --resto idiom as cell-4 for cohesion; lighter (a stepped cross-dissolve,
   not a seam-wipe, unless a real same-car BEFORE/AFTER pair drives it). ── */
.proc {
  --resto: 62%;   /* rest = a composed mid-process frame; content-complete pre-pin */
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  /* R87b: start-aligned + CSS-sticky stage. The GSAP pin died — pinning a grid
     child whose row grew (numbers joined the rail) made the stage JUMP to its
     lock point (pin-spacer vs centered row). Sticky is native: the stage docks
     under the nav and the rail scrolls past it, no snap, every width. */
  gap: clamp(1.5rem, 3vw, 3rem); align-items: start;
  /* R87d: the walk needs track — the grid row stretches to this floor and the
     sticky stage rides docked through all of it, so each frame owns a real
     third of the scroll instead of a handful of pixels. Desktop only (the
     ≤960 stack resets it).
     R126 (David): 145vh + a docked rail froze the WHOLE page for ~850px of
     scroll — "it just locks everything down… removes the whole sense of
     progression entirely." The walk now owns ONE viewport of real travel:
     the rail scrolls past the docked photo the entire time, and the scrub
     (detail.js) ends at the photo's own sticky release. */
  min-height: 100vh;
}
.proc__stage {
  position: sticky; top: calc(var(--nav-h) + 1.1rem);
  aspect-ratio: 16 / 10; overflow: hidden;
  border: 1px solid var(--ink-700); background: #141414;
}
/* R103 (David) froze the rail sticky so the gauge stayed in view to 100% —
   but R126 (David) ruled the freeze killed the page's sense of progression
   ("the site isn't scrolling through the photos… it locks everything down").
   The rail now SCROLLS (default static) and gauge visibility falls out of
   the shorter 100vh track instead: the walk ends at the photo's sticky
   release, and the rail is short enough that the gauge is still on screen
   when 100% lands. Only the photo docks — every width. */
.proc__frame { position: absolute; inset: 0; }
.proc__frame img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; filter: saturate(0.9) contrast(1.03) brightness(1.0);
}
/* stepped cross-dissolve: each frame's opacity is gated by a per-frame class the
   JS toggles from floor(--resto/25). Base opacity 0 so only the active stage shows. */
.proc__frame { opacity: 0; transition: opacity 0.5s var(--ease-out); }
.proc__frame.is-active { opacity: 1; }
.proc.proc--nojs .proc__frame { position: relative; opacity: 1; aspect-ratio: 16/10; }
/* the tungsten worklight over the stage — shared idle, warm layer only */
.proc__glow {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: radial-gradient(70% 90% at 30% 20%, rgba(255,168,92,0.14), transparent 70%);
  mix-blend-mode: screen;
}
/* the stage label plate, bottom-left, over calm ground (T2.8 corner rule) */
.proc__stagetag {
  position: absolute; left: 0; bottom: 0; z-index: 3;
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--breath); background: color-mix(in srgb, var(--ink-deep) 85%, transparent);
  padding: 0.5em 0.9em; border-top: 1px solid var(--ink-700); border-right: 1px solid var(--ink-700);
}
.proc__stagetag b { color: var(--accent-text); font-weight: 500; margin-right: 0.6em; }

.proc__rail { display: flex; flex-direction: column; gap: 0; }
.proc__caseplate {
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent-text); margin-bottom: var(--space-m);
  display: inline-flex; align-items: center; gap: 0.7em;
}
.proc__caseplate::before { content: ""; width: 1.6rem; height: 1px; background: var(--accent); }
.proc__steps { list-style: none; }
.proc__step {
  position: relative; display: grid; grid-template-columns: 3.2rem 1fr;
  gap: 0.9rem; padding: 0.85rem 0; align-items: start;
}
.proc__step + .proc__step { border-top: 1px solid var(--ink-700); }
.proc__step-num {
  font-family: var(--font-mono); font-size: 0.78rem; color: var(--slate-500);
  padding-top: 0.15em; transition: color var(--dur-fast) var(--ease-out);
}
.proc__step-title {
  font-family: var(--font-display); font-weight: 700; font-size: 1.35rem;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--slate-400);
  transition: color var(--dur-fast) var(--ease-out);
}
.proc__step-desc { font-size: var(--step--1); color: var(--slate-400); margin-top: 0.15em; opacity: 0.85; }
.proc__step.is-active .proc__step-num { color: var(--accent-text); }
.proc__step.is-active .proc__step-title { color: var(--breath); }
.proc__step.is-passed .proc__step-title { color: var(--support); }
/* red node marker on the active step */
.proc__step::before {
  content: ""; position: absolute; left: -1px; top: 1.15rem; width: 3px; height: 0;
  background: var(--accent); transition: height var(--dur-fast) var(--ease-out);
}
.proc__step.is-active::before { height: 1.6rem; }

/* the rebuild gauge — RESTORATION 0→100%, rides --resto (cell-4 idiom) */
.proc__gauge { margin-top: var(--space-l); }
.proc__gauge-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 0.5rem; }
.proc__gauge-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--slate-400); }
.proc__gauge-pct { font-family: var(--font-display); font-weight: 800; font-size: 1.6rem; color: var(--breath); line-height: 1; }
.proc__gauge-pct .unit { color: var(--accent-text); font-size: 1rem; }
.proc__gauge-track { height: 6px; background: #191919; border: 1px solid var(--ink-700); overflow: hidden; }
.proc__gauge-fill { height: 100%; width: var(--resto); background: linear-gradient(90deg, var(--terracotta-deep), var(--accent)); }

/* ── 3c · BEFORE & AFTER panel — the §1.7 seam. AFTER is the base; BEFORE is
   clipped from the right by --ba; a red seam-line rides the clip edge. Drag OR
   scroll-scrub via JS; keyboard range for a11y. AR locked to the pair.
   ONLY built where a hand-confirmed real pair exists; else the panel carries an
   honest "no matched pair" note + points at the process scrub. ── */
.ba { --ba: 50%; max-width: 62rem; margin-inline: auto; }
.ba__stage {
  position: relative; aspect-ratio: 3 / 2; overflow: hidden;
  border: 1px solid var(--ink-700); background: #141414; touch-action: pan-y;
}
.ba__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.9) contrast(1.03) brightness(1.0); user-select: none; -webkit-user-drag: none; }
/* Registration — the matched David-enhanced pair (P0317-de before / P0583-de
   after) are both 3:2 (2000×1334), both shot/graded from the LEFT (front-left,
   rear-right) so the car reads as ONE vehicle changing state across the seam —
   NOT a mirror. (The prior P0317.webp before was horizontally flipped vs David's
   master, making the two cars face opposite ways at the seam — killed here.)
   Both sit at 50% 50% in the 3/2 stage (minimal cover-crop); no per-side scale
   needed. Honest BEFORE/AFTER tags stay as the safety read. */
.ba__img--after  { object-position: 50% 50%; }
.ba__img--before { object-position: 50% 50%; clip-path: inset(0 var(--ba) 0 0); will-change: clip-path; }
.ba__seam { position: absolute; top: 0; bottom: 0; left: calc(100% - var(--ba)); width: 0; z-index: 3; pointer-events: none; }
.ba__seam-line { position: absolute; top: 0; bottom: 0; left: -1px; width: 2px; background: var(--accent); box-shadow: 0 0 12px 1px rgba(194,35,27,0.5); }
.ba__grip {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 2.6rem; height: 2.6rem; display: grid; place-items: center;
  background: var(--accent); border-radius: var(--radius); box-shadow: 0 3px 0 var(--terracotta-deep);
}
.ba__grip::before, .ba__grip::after {
  content: ""; width: 0; height: 0; border-style: solid;
}
.ba__grip::before { border-width: 0.34rem 0.44rem 0.34rem 0; border-color: transparent #fff transparent transparent; margin-right: 0.35rem; }
.ba__grip::after  { border-width: 0.34rem 0 0.34rem 0.44rem; border-color: transparent transparent transparent #fff; }
/* BEFORE/AFTER tags — small chips seated UNDER the letters, bottom-anchored.
   NOTE: sections.css's own `.ba__tag { position:absolute; top: var(--space-s) }`
   (the homepage cell-4 seam uses the same class names) LEAKS onto this page — it
   loads before detail.css, so without an explicit `top` here the tag inherits
   BOTH top AND bottom and stretches to the full stage height (David saw them as
   full-height dark bars). `top: auto` releases the top edge so the chip collapses
   to its content and sits on the bottom line. Verified live: 545px bar → ~28px
   chip. Keyboard/drag interactions are untouched. */
.ba__tag {
  position: absolute; top: auto; bottom: 0.7rem; height: auto; z-index: 4;
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--breath); background: color-mix(in srgb, var(--ink-deep) 82%, transparent);
  padding: 0.4em 0.7em; border: 1px solid var(--ink-700);
}
.ba__tag--before { left: 0.7rem; }
.ba__tag--after  { right: 0.7rem; }
/* transparent native range over the stage for keyboard + a11y */
.ba__range {
  position: absolute; inset: 0; z-index: 5; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: ew-resize;
}
/* idle breath on the seam at rest (before drag/scrub owns it) */
.ba__seam-line { animation: ba-seam-breath 4.2s ease-in-out infinite; }
.ba.is-active .ba__seam-line { animation: none; opacity: 1; }
@keyframes ba-seam-breath { 0%,100% { opacity: 0.9; } 50% { opacity: 0.6; } }
@media (prefers-reduced-motion: reduce) { .ba__seam-line { animation: none; } }
.ba__caption { margin-top: var(--space-m); text-align: center; font-size: var(--step--1); color: var(--slate-400); }
.ba__caption b { color: var(--breath); font-weight: 500; }
/* honest "no pair" note (Nash) — never fakes a pair */
.ba-note {
  max-width: 44rem; margin-inline: auto; text-align: center;
  padding: var(--space-l); border: 1px dashed var(--ink-700); background: var(--ink-850);
}
.ba-note p { color: var(--slate-400); }
.ba-note p + p { margin-top: var(--space-s); }

/* ── 3d · GALLERY panel — the photo contact-sheet (their weekly-photo model as
   UI). A film-strip grid of stamped negatives; tungsten sheen crawl at rest. ── */
.contact-sheet {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
  background: var(--ink-deep); padding: 6px; border: 1px solid var(--ink-700);
}
/* each cell is now a <button> (was an <a href="…raw.webp"> that opened the raw
   file in a new tab — T2.38 violation). The full-size src moved to data-full and
   a click opens the in-page lightbox. Reset button chrome so it renders exactly
   like the old cell. */
.contact-sheet__cell {
  position: relative; overflow: hidden; aspect-ratio: 3 / 2;
  background: #191919; display: block;
  width: 100%; margin: 0; padding: 0; border: 0;
  font: inherit; color: inherit; text-align: inherit;
  cursor: zoom-in; -webkit-appearance: none; appearance: none;
}
.contact-sheet__cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.contact-sheet__cell img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.88) contrast(1.04) brightness(0.98);
  transition: transform 0.5s var(--ease-out), filter 0.4s var(--ease-out);
}
/* A contact sheet is a DISCIPLINED uniform grid of identical negatives (T2.6 —
   organic or disciplined, nothing between; a real film contact sheet is uniform).
   Every cell is the same 3:2 frame; no span variants (they read as a half-reasoned
   stagger). The .cell--wide/--tall hooks are neutralised to keep the metaphor true. */
.contact-sheet__cell--wide,
.contact-sheet__cell--tall { grid-column: auto; grid-row: auto; }
.contact-sheet__no {
  position: absolute; top: 0.35rem; left: 0.4rem; z-index: 2;
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.08em;
  color: var(--breath); text-shadow: 0 1px 2px #000; opacity: 0.85;
}
/* the tungsten sheen crawl — a slow diagonal highlight; compositor-cheap transform */
.contact-sheet__cell::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(115deg, transparent 38%, rgba(255,200,140,0.12) 50%, transparent 62%);
  transform: translateX(-120%);
  animation: sheen-crawl 9s ease-in-out infinite;
}
.contact-sheet__cell:nth-child(2n)::after { animation-delay: 2.2s; }
.contact-sheet__cell:nth-child(3n)::after { animation-delay: 4.4s; }
@keyframes sheen-crawl { 0% { transform: translateX(-120%); } 55%,100% { transform: translateX(120%); } }
@media (hover: hover) {
  .contact-sheet__cell:hover img { transform: scale(1.04); filter: saturate(0.95) contrast(1.05) brightness(1.05); }
}
@media (prefers-reduced-motion: reduce) { .contact-sheet__cell::after { animation: none; } }
.contact-sheet__caption { margin-top: var(--space-m); font-size: var(--step--1); color: var(--slate-400); text-align: center; }

/* ── 3e · GALLERY LIGHTBOX — clicking a contact-sheet cell opens the full image
   in an in-page overlay (never a raw-webp tab). Fixed full-viewport backdrop with
   the image centred; a stamped close plate + prev/next chevrons; body scroll is
   locked (.lb-open) while it's up. Alive only on open (JS toggles [hidden]). ── */
body.lb-open { overflow: hidden; }
.lightbox {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; place-items: center;
}
.lightbox[hidden] { display: none; }
.lightbox__backdrop {
  position: absolute; inset: 0;
  background: rgba(10,10,10,0.9);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  animation: lb-fade 0.22s var(--ease-out);
}
.lightbox__frame {
  position: relative; z-index: 1;
  max-width: min(92vw, 78rem); max-height: 88vh;
  display: flex; align-items: center; justify-content: center;
  animation: lb-rise 0.24s var(--ease-out);
}
.lightbox__img {
  display: block; max-width: 100%; max-height: 88vh;
  object-fit: contain; border: 1px solid var(--ink-700);
  background: #141414; box-shadow: 0 24px 60px -20px #000;
}
.lightbox__caption {
  position: absolute; left: 0; bottom: -1.9rem; z-index: 2;
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--slate-400);
}
.lightbox__close {
  position: absolute; top: -1.1rem; right: -1.1rem; z-index: 3;
  width: 2.6rem; height: 2.6rem; display: grid; place-items: center;
  font-size: 1.5rem; line-height: 1; color: var(--breath);
  background: var(--ink-deep); border: 1px solid var(--ink-700);
  border-radius: var(--radius); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 3rem; height: 3rem; display: grid; place-items: center;
  font-size: 1.4rem; line-height: 1; color: var(--breath);
  background: color-mix(in srgb, var(--ink-deep) 78%, transparent);
  border: 1px solid var(--ink-700); border-radius: var(--radius); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.lightbox__nav--prev { left: -1.4rem; }
.lightbox__nav--next { right: -1.4rem; }
.lightbox__close:focus-visible,
.lightbox__nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) {
  .lightbox__close:hover { background: var(--accent); color: #fff; }
  .lightbox__nav:hover { background: var(--accent); }
}
@keyframes lb-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes lb-rise { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .lightbox__backdrop, .lightbox__frame { animation: none; }
}
@media (max-width: 760px) {
  .lightbox__nav--prev { left: 0.3rem; }
  .lightbox__nav--next { right: 0.3rem; }
  .lightbox__close { top: 0.3rem; right: 0.3rem; }
}

/* ─────────────────────────────────────────────────────────────────────
   4 · SEE THE NEXT PROJECT — stamped band, plate-press CTA. Bookends the file.
   ───────────────────────────────────────────────────────────────────── */
.detail-next {
  border-top: 1px solid var(--ink-700);
  background: var(--ink-850);
  /* R109: the see-next link sat 20px off the footer — same flush-bottom class
     as the projects closing plate; the band breathes now. */
  padding-block: var(--space-l);
}
/* "Next in the file" label KILLED in markup (David: redundant once See-the-Next
   lives inside the single viewport). The band is now just the See-Next action,
   left-aligned so it reads as the closing line of the case file. */
.detail-next__inner { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-m); flex-wrap: wrap; }
.detail-next__link {
  display: inline-flex; align-items: center; gap: 0.8em;
  font-family: var(--font-display); font-weight: 700; font-size: clamp(1.3rem, 2.4vw, 1.9rem);
  text-transform: uppercase; letter-spacing: 0.03em; color: var(--breath);
  transition: color var(--dur-fast) var(--ease-out);
}
.detail-next__link .next-model { color: var(--accent-text); }
.detail-next__link .arrow { transition: transform var(--dur-fast) var(--ease-out); }
@media (hover: hover) {
  .detail-next__link:hover { color: var(--breath); }
  .detail-next__link:hover .arrow { transform: translateX(5px); }
}

/* ─────────────────────────────────────────────────────────────────────
   5 · RESPONSIVE — desktop RECOMPOSED, not squished (T2.31). Grids re-grid.
   ───────────────────────────────────────────────────────────────────── */
@media (max-width: 960px) {
  .detail-overview { grid-template-columns: 1fr; gap: var(--space-l); }
  /* R103 (David): the stacked mobile proc walked its frames while the photo
     was still below the fold ("the image already swaps before I even get to
     see it"). Mobile now mirrors the desktop dock: FLEX column (sticky needs
     the whole container as its box — a 1fr grid row traps it in its own row),
     photo ordered FIRST and docked under the nav, the rail + gauge ride up
     beneath it while the frames walk. The scrub's end bound (detail.js) parks
     the gauge just under the docked photo at 100%. */
  .proc {
    display: flex;
    flex-direction: column;
    gap: var(--space-l);
    /* no track floor here: the mobile walk length is content-driven (the rail
       riding up under the docked photo IS the ride) — a taller container only
       adds a dead dark tail after the gauge parks. */
    min-height: 0;
    /* R121: give back the outer spacing the rail's negative margin (below)
       borrows — the next panel stays where it always was. --proc-lock is set
       by detail.js (18px + gauge height) at ≤960 only. */
    padding-bottom: var(--proc-lock, 0px);
  }
  .proc__stage {
    order: -1;
    width: 100%;
    top: calc(var(--nav-h) + 0.6rem);
    z-index: 2;             /* the rail slides UNDER the docked photo */
    transition: top var(--dur-fast) var(--ease-out), box-shadow 0.4s var(--ease-out);
  }
  /* R108 (David, "hell no"): rail content sliding under the docked photo
     RE-EMERGED in the dead strip above it (where the hidden nav was) — spec
     rows floating over the photo, cut mid-cell, step copy peeking below.
     A solid shop-floor shadow-block welds to the photo's top edge (box-shadow
     escapes the stage's overflow:hidden; the rail rows are exactly stage-width,
     so the gutters never carry content) — what passes beneath STAYS beneath.
     R116 (David's live catch): scoped to the ACTIVE walk — unscoped, the block
     painted before the photo ever docked and beheaded the section heading.
     R120 (David, "disappears oddly"): is-active still popped the block on at
     the trigger's first pixel, while the section heading was mid-exit in that
     very strip — it vanished in one frame. The mask now keys on .is-masked
     (detail.js flips it past 10% of the walk, heading long gone, first rail
     re-emergence not until ~30%) and FADES via the box-shadow transition.
     The 2px spread swallows the sub-pixel slivers that rasterized at the
     mask's exact stage-edge seams on 3x screens. */
  .proc.is-masked .proc__stage {
    box-shadow: 0 calc(-1 * (var(--nav-h) + 0.7rem)) 0 2px var(--ink-900);
  }
  /* and the photo hugs the top when the nav slides away (blog-gauge pattern) */
  .nav.is-hidden ~ .detail .proc__stage { top: 0.6rem; }
  .proc__rail { position: static; }   /* mobile: the rail RIDES, only the photo docks */
  /* R121 (David, IMG_3988): "this is where it should lock the photo in" — after
     the gauge parked at 100%, further scroll kept sliding the rail UNDER the
     docked photo until the sticky ran out, covering the bar and the 100%.
     The sticky photo releases when the proc CONTENT box bottom meets the photo
     bottom; pulling the rail's margin box up by exactly (18px + gauge height)
     makes that release land on the precise frame where the gauge sits 18px
     under the photo — from there the whole locked frame travels off as one. */
  .proc__rail { margin-bottom: calc(-1 * var(--proc-lock, 0px)); }
  /* R121b (David, "a piece of 03 left bar red edge"): the active-step marker
     sits at left:-1px — 1px of it pokes past the rail row's edge into the
     gutter, where the docked photo (exactly row-width) can't cover it. Mobile
     keeps the marker fully inside the row so under-photo rows stay invisible. */
  .proc__step::before { left: 0; }
  .detail-playlist { grid-template-columns: 1fr; }
  /* R98 (David's Betty bleed screenshot): min-height 0 is LOAD-BEARING — the
     base min-height:13rem transfers through aspect-ratio into a 369.8px
     min-WIDTH at 390, shoving the poster past the card's right border. */
  .detail-playlist__stage { border-right: 0; border-bottom: 1px solid var(--ink-700); height: auto; min-height: 0; aspect-ratio: 16 / 9; }
  .contact-sheet { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 760px) {
  .detail-hero__grid { grid-template-columns: 1fr; min-height: 0; padding-top: calc(var(--nav-h) + 0.5rem); }
  /* mobile: the photo becomes a full-bleed banner UNDER the title block */
  .detail-hero__media {
    position: relative; grid-column: 1; grid-row: 2;
    margin-right: calc(var(--gutter) * -1); margin-left: calc(var(--gutter) * -1);
    aspect-ratio: 16 / 11; min-height: 0;
  }
  .detail-hero__img { position: absolute; }
  .detail-hero__inner { grid-row: 1; max-width: none; padding-block: var(--space-l) var(--space-m); }
  .detail-hero__scrim {
    background:
      linear-gradient(180deg, var(--ink-900) 0%, rgba(34,34,34,0.2) 100%);
  }
  .contact-sheet { grid-template-columns: repeat(2, 1fr); }
  .ba__stage { aspect-ratio: 4 / 3; }
  .detail-next__inner { flex-direction: column; align-items: flex-start; gap: var(--space-xs); }
}

/* solo/reduced-motion: reveal handled by main.js; all panels are stacked +
   visible. The proc/ba/spec floors are content-complete at their rest
   --resto/--ba. */
