/* ===================================================================
   MOTION — section seams and the photographic layer.
   Loaded by index.html AFTER site.css + editorial.css.

   Scope, deliberately narrow: this file adds ONLY what the site did not
   already have. The shipped `.rv` / `.rv.in` reveal observer
   (site.css:215, driven from index.html) still owns every in-section
   entrance — cards, tiers, headings. Layering a second entrance on top
   of it would double-animate the whole page. So nothing here touches
   .card / .ptier / h2.

   What it adds:
     1. SEAMS between sections — the boundary you scroll across now has
        a direction instead of being a hard join.
     2. A PHOTOGRAPHIC layer (grade, Ken Burns, parallax, wipe).
   (A carrier rail was here too; removed 2026-08-13 — see below.)

   The rules behind the seams (HyperFrames motion doctrine):
     · VECTOR LAW — how a section exits determines how the next enters:
       same axis, same direction, cut mid-motion. A section never
       decides how it leaves; the one after it does (see motion.js).
     · THE CURRENT — one dominant direction, LEFT. Other vectors are
       RESERVED: Z-forward once (proof), Z-back once (pricing, the
       payoff), UP once (finale). Spending one means something.
     · NO IDLE WOBBLE — nothing here loops. Every animation resolves.
   =================================================================== */

:root{
  --e-p3-in:   cubic-bezier(.550,.055,.675,.190);  /* power3.in  */
  --e-p4-in:   cubic-bezier(.755,.050,.855,.060);  /* power4.in  */
  --e-p4-out:  cubic-bezier(.230,1,.320,1);        /* power4.out */
  --e-expo-out:cubic-bezier(.190,1,.220,1);        /* expo.out   */
  --travel: clamp(56px, 10vw, 200px);   /* partial travel, ~10% of frame */
}

/* ── Seam machinery ───────────────────────────────────────────────
   Three wrappers, injected at runtime by motion.js so index.html's
   markup stays clean. Blur and opacity must not tween on the same
   element, and blur belongs on the wrapper, never the children:
     .scene -> filter   .scene-in -> transform   .scene-op -> opacity  */
.scene{
  position:relative;
  /* Opaque ground: a mid-motion cut opens a window where summed opacity
     is < 1, and the page would flash through it without this. */
  background:var(--bg);
  /* Partial travel moves content sideways; on a narrow viewport that
     lands outside the viewport and every page gains a horizontal
     scrollbar — the exact bug editorial.css:76 warns about. `clip`
     rather than `hidden` contains it WITHOUT creating a scroll
     container, so the page keeps one scroll context. */
  overflow-x:clip;
}
.scene-in{ will-change:transform; }
.scene-op{ will-change:opacity; }

/* Entries. Each ignites MID-PATH (0.35 opacity, part-way through the
   travel) — a binary 0->1 pops, and starting from rest after a cut is
   a dead beat. */
@keyframes sm-in-x{ from{transform:translate3d(var(--travel),0,0)} to{transform:translate3d(0,0,0)} }
@keyframes sm-in-y{ from{transform:translate3d(0,var(--travel),0)} to{transform:translate3d(0,0,0)} }
@keyframes sm-in-op{ from{opacity:.35} to{opacity:1} }
@keyframes sm-in-zpush{ from{transform:scale(.82)} to{transform:scale(1)} }
@keyframes sm-in-zpull{ from{transform:scale(1.18)} to{transform:scale(1)} }
@keyframes sm-in-zop{ from{opacity:.15} to{opacity:1} }
@keyframes sm-blur-in{ from{filter:blur(9px)} to{filter:blur(0)} }

.scene.in-x .scene-in{ animation:sm-in-x .50s var(--e-p4-out) both; }
.scene.in-x .scene-op{ animation:sm-in-op .34s linear both; }
.scene.in-y .scene-in{ animation:sm-in-y .50s var(--e-p4-out) both; }
.scene.in-y .scene-op{ animation:sm-in-op .34s linear both; }
.scene.in-zpush .scene-in{ animation:sm-in-zpush .52s var(--e-expo-out) both; }
.scene.in-zpush .scene-op{ animation:sm-in-zop .52s var(--e-expo-out) both; }
.scene.in-zpush        { animation:sm-blur-in .52s var(--e-expo-out) both; }
.scene.in-zpull .scene-in{ animation:sm-in-zpull .54s var(--e-expo-out) both; }
.scene.in-zpull .scene-op{ animation:sm-in-zop .54s var(--e-expo-out) both; }
.scene.in-zpull        { animation:sm-blur-in .54s var(--e-expo-out) both; }

/* Exits. Mirrored eases — exit power4.in against entry power4.out over
   the same distance are the two halves of one power4.inOut, so velocity
   matches exactly at the cut. The fade completes at ~53% of the travel
   so the element is gone before it can smear, but the last fading pixel
   dies AT the cut: a gap where nothing moves reads as dead air. */
@keyframes sm-out-x{ from{transform:translate3d(0,0,0)} to{transform:translate3d(calc(var(--travel)*-1),0,0)} }
@keyframes sm-out-y{ from{transform:translate3d(0,0,0)} to{transform:translate3d(0,calc(var(--travel)*-1),0)} }
@keyframes sm-out-zpush{ from{transform:scale(1)} to{transform:scale(1.14)} }
@keyframes sm-out-zpull{ from{transform:scale(1)} to{transform:scale(.86)} }
@keyframes sm-out-op{ from{opacity:1} to{opacity:0} }
@keyframes sm-blur-out{ from{filter:blur(0)} to{filter:blur(9px)} }

.scene.out-x .scene-in{ animation:sm-out-x .34s var(--e-p4-in) both; }
.scene.out-y .scene-in{ animation:sm-out-y .34s var(--e-p4-in) both; }
.scene.out-x .scene-op,
.scene.out-y .scene-op{ animation:sm-out-op .18s linear both; }
.scene.out-zpush .scene-in{ animation:sm-out-zpush .20s var(--e-p3-in) both; }
.scene.out-zpull .scene-in{ animation:sm-out-zpull .20s var(--e-p3-in) both; }
/* Z exits hold at 0.15 rather than 0 — the swap happens at peak blur,
   and a fully transparent outgoing side would show the cut. */
.scene.out-zpush .scene-op,
.scene.out-zpull .scene-op{ animation:sm-out-op .20s linear both; opacity:.15; }
.scene.out-zpush,
.scene.out-zpull{ animation:sm-blur-out .20s var(--e-p3-in) both; }

/* ── Carrier ──────────────────────────────────────────────────────
   REMOVED 2026-08-13 (owner's call). The rail was a fixed hairline in the
   right gutter with a scroll-driven accent bead, meant to give the eye one
   continuous object across a seam. In practice it read as a stray line
   crossing the section seams, so it is gone — CSS here and its builder in
   motion.js. Don't reinstate it without the same conversation. */

/* ── Brand mark ───────────────────────────────────────────────────
   The header lockup. `color` feeds the hub via currentColor, so the
   mark tracks the canvas across all four skins without a second asset;
   the arc keeps its sweep gradient, which is the brand constant. */
.brand{ display:flex; align-items:center; gap:10px; }
.brandmark{ flex:0 0 auto; color:var(--tx); display:block; }
@media(max-width:520px){ .brandmark{ width:25px; height:25px; } }

/* ══════════════════════════════════════════════════════════════════
   PHOTOGRAPHIC LAYER
   Provenance for every file: media/LICENSES.json. Public
   domain / CC0 only — no attribution obligation.

   The grade is CSS and is never baked into the file: one asset then
   serves all four skins (a file graded for Bone looks wrong on Aurora)
   and the source stays unmodified and auditable. Desaturate + accent
   tint at soft-light is what pulls photos shot by different people in
   different colour temperatures into one set.

   Three nested elements, each owning ONE transform, because stacking
   parallax and Ken Burns on a single element makes them fight:
     .ph -> frame/clip/grade   .ph-par -> parallax   img -> Ken Burns
   ══════════════════════════════════════════════════════════════════ */
.ph{ position:relative; overflow:hidden; background:var(--panel2); isolation:isolate;
     border-radius:var(--r-card,18px); }
.ph-par{ position:absolute; inset:-9% 0; will-change:transform; }
.ph img{ display:block; width:100%; height:100%; object-fit:cover;
         filter:saturate(.56) contrast(1.07); will-change:transform; }
.ph::before{ content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
             background:var(--accent); mix-blend-mode:soft-light; opacity:.45; }
/* Scrim in the canvas colour, so the photo dissolves into the page
   instead of sitting on it as a rectangle. The three-stop ramp is a
   LEGIBILITY requirement, not a look: a single stop left copy sitting
   on the dark engine at unreadable contrast on the Bone skin. */
.ph::after{ content:""; position:absolute; inset:0; z-index:2; pointer-events:none;
            background:linear-gradient(to top,
              var(--bg) 6%,
              color-mix(in srgb, var(--bg) 88%, transparent) 34%,
              color-mix(in srgb, var(--bg) 40%, transparent) 58%,
              transparent 82%); }
.ph.ph-plain::after{ background:linear-gradient(to top, var(--bg) 1%, transparent 55%); }

/* Camera with intent: establish wide, travel, ARRIVE. It resolves and
   stops — a looping Ken Burns would be the banned idle wobble. */
@keyframes kenburns{
  from{ transform:scale(1.14) translate3d(var(--kx,-2%), var(--ky,1.5%), 0); }
  to  { transform:scale(1) translate3d(0,0,0); }
}
.scene.is-live .ph img{ animation:kenburns 6.5s var(--e-expo-out) both; }

/* The photo enters on the same vector as everything else — the reveal
   edge travels LEFT, riding the current. A photo that fades in while
   the page cuts sideways is the tell that imagery was bolted on. */
@keyframes ph-wipe-left{ from{clip-path:inset(0 0 0 100%)} to{clip-path:inset(0 0 0 0)} }
.ph-wipe{ clip-path:inset(0 0 0 100%); }
.scene.is-live .ph-wipe{ animation:ph-wipe-left .62s var(--e-p4-out) both; }

/* ── The engine plate ─────────────────────────────────────────────
   Contained, NOT full-bleed, and that is a resolution decision rather
   than a design one: the only public-domain 1JZ-GTE available is
   1023x685 native. Rendered inside the column it is never upscaled.
   Going full-bleed would need the CC BY-SA 3072px alternative, which
   carries attribution AND share-alike on derivatives. */
.engine-grid{ display:grid; grid-template-columns:1.02fr .98fr; gap:30px; align-items:center; }
@media(max-width:900px){ .engine-grid{ grid-template-columns:1fr; } }
.engine-plate{ aspect-ratio:1020/558; width:100%; }
.ph-pair{ display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-top:16px; }
.ph-inset{ height:160px; }
.ph-cap{ font-size:.82rem; color:var(--mut); margin-top:8px; line-height:1.45; }
@media(max-width:700px){ .ph-pair{ grid-template-columns:1fr; } .ph-inset{ height:190px; } }

/* the dyno pull — a DATA graphic, correctly drawn: no photograph can
   show a torque curve. Stroke lengths are measured in motion.js. */
.dyno-wrap{ border:1px solid var(--line); border-radius:var(--r-card,18px);
            background:linear-gradient(180deg,rgba(127,110,180,.055),rgba(127,110,180,.014));
            padding:16px 18px 8px; }
.dyno-head{ display:flex; justify-content:space-between; align-items:baseline;
            font-size:.85rem; color:var(--mut); margin-bottom:4px; gap:10px; }
.dyno-head b{ font-family:var(--serif); font-weight:400; font-size:1.02rem; color:var(--tx); }
.dyno{ width:100%; }
.dyno .axis{ stroke:var(--line); }
.dyno .grid{ stroke:var(--line); stroke-dasharray:2 6; }
.dyno .hp{ stroke:var(--accent); stroke-width:2.4; }
.dyno .tq{ stroke:var(--amber); stroke-width:2.4; }
.dyno .lbl{ fill:var(--mut); font-family:var(--sans); font-size:13px; }
.dyno-key{ display:flex; gap:14px; flex-wrap:wrap; font-size:.78rem; color:var(--mut); padding:0 0 8px; }
.dyno-key i{ display:inline-block; width:14px; height:2px; vertical-align:middle;
             margin-right:6px; border-radius:2px; }
@keyframes dyno-draw{ to{ stroke-dashoffset:0; } }
.draw{ stroke-dasharray:var(--len,1000); stroke-dashoffset:var(--len,1000); }
.scene.is-live .draw{ animation:dyno-draw var(--dw,1.3s) var(--e-expo-out) var(--dd,0s) both; }

/* ── Reduced motion: the doctrine still applies, the motion does not.
   Everything resolves to its composed final frame. The site already
   honours this query in six places; this is the seventh. ── */
@media(prefers-reduced-motion:reduce){
  .scene, .scene-in, .scene-op, .ph img, .ph-par, .ph-wipe, .draw{
    animation:none!important; transition:none!important;
    opacity:1!important; transform:none!important; filter:none!important;
  }
  .ph-wipe{ clip-path:none!important; }
  .draw{ stroke-dasharray:none!important; stroke-dashoffset:0!important; }
}
