/* The 3D device hero (brief 2026-10-27, section 6; js/device-hero.js). css/studio.css styles the static hero: the
   full-bleed .dh-video under the .dh-media::after veil, the copy centered over it. That static form is what phones,
   reduced motion and browsers without WebGL keep. On a wide screen js/device-hero.js adds .dh--3d once the 3D scene
   is ready: the section becomes a tall scroll stage, .dh-sticky holds the screen while the stage scrolls by, and a
   canvas inside .dh-media draws the device with the same video on its screen. .dh--live (as soon as the scene is
   ready, at rest at the top) shows the canvas over the flat video; the swap is invisible because the device's screen
   then covers the window exactly as the flat video does, and the canvas fades in over it for a fifth of a second
   while holding that picture; .dh--solo then hides the flat video (opacity, so the browser keeps decoding it for the
   screen) and its still, and lays black under the device, and only then does the camera move. The script moves the
   copy (transform, opacity), fades the veil (--dh-veil) as you pull back, and raises the device up and out at the end.

   The stage: 240vh tall, so 140vh of scrolling. The section after it rises into view over the stage's last 40vh
   (--dh-tail) and draws above it, so its headline comes up close under the device while the device is still held,
   as in REV's frames at scroll 1300 to 1600; when the stage lets go, both leave at the page's speed. */
.dh--3d { height: var(--dh-stage, 240vh); }
.dh--3d + * { position: relative; z-index: 1; margin-top: calc(var(--dh-tail, 40vh) * -1); }
.dh--3d .dh-sticky { position: sticky; top: 0; height: 100vh; height: 100svh; min-height: 0; }
.dh--3d .dh-media::after { opacity: var(--dh-veil, 1); }
.dh--3d .dh-copy { will-change: transform, opacity; }
.dh-canvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; opacity: 0; transition: opacity .2s linear; pointer-events: none; }
.dh--live .dh-canvas { opacity: 1; }
/* Once the device has taken over, only black is left around it, however the page got there (David 10/27: "Sometimes
   the image of the desk shows up behind the laptop"). The still (.dh-poster) goes with the flat video: css/studio.css
   hides it only with .is-playing, which js/studio.js adds only to a video it gave a source itself, and the engine gives
   the hero video its source whenever it is ready first (a page opened below the hero, at /#packages or on a reload,
   or a load event that comes late), so the still stayed under the canvas, which is transparent wherever the device is
   not drawn. The canvas also lays its own black under the device, so no layer below it can ever show through. */
.dh--solo .dh-video { opacity: 0; }
.dh--solo .dh-poster { visibility: hidden; }
.dh--solo .dh-canvas { background: #000; }
/* The screen's own video (data-dh-screen): drawn only on the device's screen, never on the page. */
.dh-screen-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; pointer-events: none; }
