/* ORA motion (29-Sep-2026): proof & motion layer promoted from the Opus lab (02, 03, 07, 08).
   Layers on top of site.css + funnel.css; never edits them. Doctrine: transform + opacity on the
   compositor, no scroll-scrubbing, no pinning. Everything here is progressive enhancement.
   Files: motion.js (view transitions + hero tilt), beat-gl.js (lit closing beat), scrubber.js (Case 001). */

/* =====================================================================
   1. Cross-page View Transitions (lab 03)
   Both documents opt in; Chrome 126+ / Safari 18.2+ morph, everything else just navigates.
   The header and the Install pill share names, so they sit perfectly still across pages.
   The product-page hero is named here; the matching lineup row on index.html is named by
   motion.js only for the row that was clicked (a name must be unique per page).
   ===================================================================== */
@view-transition{ navigation:auto; }
/* names apply only while a transition is being captured (html.vt-on, set by motion.js on pageswap
   and by the head hook on pagereveal): a named element is a stacking context and a backdrop root,
   which would flatten the frosted nav pill's blur if it were left on. vt-hero = the product hero is on
   screen (a morph from off-screen, e.g. a footer link, reads as a glitch) */
html.vt-on .nav-logo{ view-transition-name:ora-logo; }
html.vt-on .nav-pill{ view-transition-name:ora-pill; }
html.vt-on .nav-install{ view-transition-name:ora-install; }
html.vt-on .nav-burger{ view-transition-name:ora-burger; }
html.vt-on.vt-hero .ppd-hero .ppd-stage > :first-child{ view-transition-name:ora-hero-media; }
html.vt-on.vt-hero .ppd-hero .ppd-name{ view-transition-name:ora-hero-id; }
::view-transition-group(*){ animation-duration:.46s; animation-timing-function:cubic-bezier(0.16,1,0.3,1); }
::view-transition-old(root){ animation:.22s ease-out both ora-vt-out; }
::view-transition-new(root){ animation:.34s .08s cubic-bezier(0.16,1,0.3,1) both ora-vt-in; }
/* text and pills keep their proportions while the box morphs (the default fit-to-width would blow "W¹" up to "W¹ Web" width) */
::view-transition-old(ora-hero-id),::view-transition-new(ora-hero-id),
::view-transition-old(ora-pill),::view-transition-new(ora-pill){ height:100%; width:auto; }
@keyframes ora-vt-out{ to{ opacity:0; } }
@keyframes ora-vt-in{ from{ opacity:0; transform:translateY(10px); } }
/* a page that arrived by a view transition must already show its hero (and a still header):
   the load-time blur-up would otherwise fade in what the morph just placed */
html.vt-in .nav-row,
html.vt-in .nav-sub,
html.vt-in .ppd-hero .reveal,
html.vt-in .ppd-hero .ppd-stage > :first-child{ opacity:1!important; visibility:visible!important; transform:none!important; filter:none!important; }
@media (prefers-reduced-motion:reduce){
  @view-transition{ navigation:none; }
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){ animation:none!important; }
}

/* =====================================================================
   2. The closing black beat, lit (lab 02)
   funnel.css paints the plate (::before) and the black scrim (::after). beat-gl.js slots a
   canvas between them; it fades in only once the first lit frame is drawn, so the plain plate
   is always what shows without WebGL, on touch, under reduced motion, or off-screen.
   ===================================================================== */
.closebeat.plate > .cb-gl{ position:absolute; inset:0; width:100%; height:100%; display:block; opacity:0; transition:opacity .9s var(--ease); pointer-events:none; }
.closebeat.plate > .cb-gl.on{ opacity:1; }
.cb-live{ display:block; margin:0; max-width:46ch; text-align:center; font-size:.8rem; line-height:1.5; color:rgba(255,255,255,.62); }
.cb-live[hidden]{ display:none; }
.cb-live b{ color:#fff; font-weight:400; }
.cb-live .dot{ display:inline-block; vertical-align:.08em; width:7px; height:7px; margin-right:9px; border-radius:50%; background:var(--accent); animation:ora-ping 2.4s var(--ease) infinite; }
@keyframes ora-ping{ 0%{ box-shadow:0 0 0 0 rgba(255,94,31,.55); } 70%,100%{ box-shadow:0 0 0 9px rgba(255,94,31,0); } }

/* M¹ Mirror does not load funnel.css; its closing beat carries the same plate treatment,
   scoped to .cb-own so it can never fight funnel.css on the pages that do load it */
.closebeat.cb-own{ position:relative; overflow:hidden; background:#000; color:#fff; margin-top:clamp(56px,9vh,120px); padding-block:clamp(90px,16vh,200px); }
.closebeat.cb-own::before{ content:""; position:absolute; inset:0; background:url(ds/plate-reflective.webp) no-repeat right center / cover; opacity:.95; }
.closebeat.cb-own::after{ content:""; position:absolute; inset:0; background:linear-gradient(90deg,#000 0%,rgba(0,0,0,.86) 38%,rgba(0,0,0,.35) 70%,rgba(0,0,0,.15) 100%); }
.closebeat.cb-own .cb-in{ position:relative; z-index:1; display:flex; flex-direction:column; align-items:center; text-align:center; gap:clamp(18px,3vh,30px); }
.closebeat.cb-own .cb-line{ margin:0; font-size:clamp(1.9rem,4vw,3.8rem); font-weight:300; letter-spacing:-.025em; line-height:1.08; max-width:20ch; }
.closebeat.cb-own .cb-sub{ margin:0; font-size:clamp(1rem,1.25vw,1.25rem); color:rgba(255,255,255,.6); max-width:46ch; line-height:1.45; }
.closebeat.cb-own .cb-ctas{ display:flex; gap:12px; flex-wrap:wrap; justify-content:center; margin-top:8px; }
.closebeat.cb-own .cb-ghost{ color:#fff; border-color:rgba(255,255,255,.4); }
.closebeat.cb-own .cb-lockup{ height:clamp(28px,3.2vw,44px); width:auto; opacity:.92; margin-top:clamp(20px,4vh,40px); }
@media (max-width:760px){
  .closebeat.cb-own::before{ background-image:url(ds/plate-reflective-900.webp); background-position:78% center; }
  .closebeat.cb-own::after{ background:linear-gradient(180deg,rgba(0,0,0,.55) 0%,rgba(0,0,0,.82) 45%,rgba(0,0,0,.9) 100%); }
}

/* =====================================================================
   3. Camera vs ORA split scrubber in Case 001 (lab 07)
   One side-by-side video drawn twice into one canvas, so the halves cannot drift.
   Without JS the slot keeps its plain <video>; scrubber.js swaps it for this UI.
   ===================================================================== */
.case-media.mo-split{ position:relative; touch-action:pan-y; user-select:none; -webkit-user-select:none; cursor:ew-resize; }
.mo-split .mo-sp-poster,.mo-split canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.mo-split .mo-sp-poster{ object-fit:cover; }
.mo-split .mo-sp-poster[hidden]{ display:none; }
.mo-sp-div{ position:absolute; top:0; bottom:0; left:0; width:0; z-index:2; will-change:transform; pointer-events:none; }
.mo-sp-div::before{ content:""; position:absolute; top:0; bottom:0; left:-1px; width:2px; background:#fff; box-shadow:0 0 12px rgba(0,0,0,.45); }
.mo-sp-knob{ position:absolute; top:50%; left:0; width:44px; height:44px; margin:-22px 0 0 -22px; padding:0; border:0; border-radius:50%; background:#fff; color:var(--ink); display:grid; place-items:center; pointer-events:auto; cursor:ew-resize; box-shadow:0 8px 20px rgba(0,0,0,.35); }
.mo-sp-knob:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
.mo-sp-knob svg{ width:20px; height:20px; }
.mo-sp-tag{ position:absolute; top:12px; z-index:3; font-size:.64rem; letter-spacing:.08em; text-transform:uppercase; line-height:1.3; background:rgba(0,0,0,.6); color:#fff; border-radius:100px; padding:4px 9px; pointer-events:none; }
.mo-sp-tag.l{ left:12px; } .mo-sp-tag.r{ right:12px; }
.mo-sp-tag b{ color:var(--accent); font-weight:500; }
.mo-sp-ph{ position:absolute; left:12px; bottom:12px; right:84px; z-index:3; pointer-events:none; display:flex; }
.mo-sp-ph[hidden]{ display:none; }
.mo-sp-ph span{ display:inline-block; font-size:.62rem; letter-spacing:.06em; text-transform:uppercase; line-height:1.35; background:#fff3ec; color:#b8430f; border:1px solid #ffd2bd; border-radius:12px; padding:4px 9px; }
.mo-sp-pp{ position:absolute; right:12px; bottom:12px; z-index:3; font:inherit; font-size:.7rem; color:#fff; background:rgba(0,0,0,.6); border:1px solid rgba(255,255,255,.22); border-radius:100px; padding:5px 11px; cursor:pointer; }
.mo-sp-pp:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
@media (max-width:560px){ .mo-sp-ph span{ font-size:.56rem; letter-spacing:.04em; padding:3px 8px; } .mo-sp-tag{ font-size:.58rem; } }

/* =====================================================================
   4. Hero tilt (lab 08): max 3°, fine pointers only, off under reduced motion.
   motion.js drives the individual `rotate` property, so it composes with (never fights)
   the GSAP entrance, which owns `transform` on the same element.
   ===================================================================== */
.mo-glass{ position:absolute; z-index:1; left:.47%; top:.25%; width:98.75%; height:96.05%; border-radius:2.4cqw; overflow:hidden; pointer-events:none; }
.mo-glass i{ position:absolute; left:-60%; top:-60%; width:220%; height:220%; background:radial-gradient(closest-side,rgba(255,255,255,.22),rgba(255,255,255,0) 62%); opacity:0; transition:opacity .5s var(--ease); will-change:transform; }
.mo-tilt-on .mo-glass i{ opacity:1; }

@media (prefers-reduced-motion:reduce){
  .closebeat.plate > .cb-gl{ display:none; }
  .cb-live .dot{ animation:none; }
  .mo-glass{ display:none; }
}

/* 29-Sep (Kv): the comparison clip is portrait; a stretched slot cropped it to a square and lost the effect.
   The slot now holds each half's own 9:16 frame, so nothing is cropped. */
.case-card{ grid-template-columns:minmax(0,1fr) min(360px,40%); }
.case-card .case-media.mo-split{ align-self:start; aspect-ratio:9/16; min-height:0; height:auto; width:100%; max-width:100%; }
@media(max-width:960px){
  .case-card{ grid-template-columns:1fr; }
  .case-card .case-media.mo-split{ width:min(100%,360px); justify-self:center; aspect-ratio:9/16; }
}
