/* =====================================================================
   BANGBOWBING OS — THE 3D LAYER (ledger item 51, Q17).
   Loaded after os/scene.css so it can neutralise the ONE thing the 2D
   scene does that the renderer now owns: the CSS parallax translate.
   Everything else — the hotspots, the VHS overlays, the camera focus box
   — is the 2D stylesheet's, untouched.

   TWO RULES THIS FILE MUST NEVER BREAK, both regressions from the chair's
   first pass and both asserted in scene3d-test.mjs:

   1. #scene-stage's SIZE is scene.css's business and nobody else's. Its
      cover maths — width: max(100vw, 100vh*1600/1019) plus the photo's
      aspect-ratio — is what makes every percent coordinate in the level
      mean something. Nothing here may set width, height, aspect-ratio,
      inset, position, top, left, margin or padding on it. Only the
      `transform` is re-stated, and it keeps the SAME scale(1.035) as the
      2D rule so the framing is identical across the swap.

   2. The resting hotspot captions stay at opacity .55 (os/scene.css
      NOTE-2: legible label + REC dot without a pointer, ~6.6:1 contrast).
      3D does NOT force them opaque and must not touch .spot-vhs at all.

   Nothing here applies until os/scene3d.js has actually mounted and put
   `is-3d` on <body>, so a dead CDN leaves the 2D scene exactly as it was.
   ===================================================================== */

/* The canvas also carries this box as an INLINE style, set in scene3d.js at
   creation. That is deliberate belt-and-braces: a <canvas> with no CSS falls
   back to its intrinsic size — the drawing buffer, i.e. the stage times the
   device pixel ratio — and lays out IN FLOW, which is what pushed the stage
   out of shape. Absolute from the first paint, it cannot affect layout. */
.scene3d-canvas {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  z-index: 1;               /* over the photo background, under #scene-spots (2) */
  pointer-events: none;
  opacity: 0;
  /* the first frame is the same photograph from the same camera, so this
     is a cross-fade between two identical pictures — the swap is invisible */
  transition: opacity 420ms var(--ease, ease);
}
.scene3d-canvas.is-in { opacity: 1; }

/* ===================================================================
   DESKTOP LAYOUT ONLY (>= 760px). Everything in this block restates a
   property os/scene.css sets on #scene-stage, and #scene-stage is laid
   out in TWO COMPLETELY DIFFERENT WAYS either side of 760px:

     >= 760px  the stage is position:absolute at top/left 50% and CENTRED
               by transform: translate(-50%, -50%) scale(1.035).
     <  760px  the stage is position:relative, in normal flow inside the
               #scene horizontal pan scroller, with NO centring translate
               (scene.css's @media (max-width: 759px) rule).

   An unscoped `body.is-3d #scene-stage { transform: translate(-50%,-50%) }`
   therefore wins over the phone rule and yanks the stage — photo, canvas
   and all — half its own width and height off the top-left of a scroller
   that is not expecting to be centred: the chair measured a stage rect of
   left -1277, top -389 at 375x812, i.e. entirely off screen, and
   elementFromPoint returned #scene's black background.

   So NOTHING in this file touches the stage below 760px. The phone keeps
   os/scene.css's transform exactly, including its --px/--py vars: the
   gyro nudge there is +-7px/+-5px, far too small to fight the camera, and
   leaving the rule alone is worth more than removing it.
   =================================================================== */
@media (min-width: 760px) {
  /* The renderer does the parallax now: the stage stops translating, or the
     camera move and the CSS move would double up. Same scale as scene.css's
     own centring rule — this changes the stage's TRANSFORM, never its box. */
  body.is-3d #scene-stage {
    transform: translate(-50%, -50%) scale(1.035);
    will-change: auto;
  }

  /* dusk is lit, not filtered: the hemisphere/sun/street-lamp rig replaces
     the brightness+hue grade, and the warm pools come from real lights */
  body.is-3d.is-night #scene-stage { filter: none; }

  /* the level is modelled now (Q17), so the photo behind the canvas is not
     part of the picture — the canvas is opaque and covers it, and the CSS
     grade over the top is pulled right back so it reads as a whisper of
     film, not a filter over a render that is already lit. os/os.css's #crt
     grain still applies. Below 760px the photo is what shows whenever the
     loop parks, so its own grade and night filter are left alone. */
  body.is-3d #scene-stage::after { opacity: 0.30; }
}

/* the raycast knows when the pointer is actually on an object, not merely
   inside its projected rectangle — say so */
#scene.is-pointing { cursor: pointer; }

/* the iridescent sweep is a fullscreen photo shader — it would fight the
   renderer for the same rectangle, so it retires while 3D is up */
body.is-3d #scene-stage > canvas:not(.scene3d-canvas) { display: none; }

/* ------------------------------------------------------ caption stacking ---
   The nine VHS captions hang off nine objects a few metres apart, so at rest
   they pile up. os/scene3d.js's de-overlap pass publishes a vertical nudge as
   --cap-y and this folds it into .spot-vhs's existing transform, right next
   to clampTag()'s --cap-x. It is the ONLY thing this file says about a
   caption: no opacity, no visibility, no colour — the resting .55 and the
   hover/focus reveal stay entirely os/scene.css's (NOTE-2). The two rules
   below mirror scene.css's rest and hot transforms exactly, with --cap-y
   added; --cap-y defaults to 0px, so a caption the pass never touches sits
   precisely where the 2D sheet put it. */
body.is-3d .spot-vhs {
  --cap-y: 0px;
  transform: translate(calc(-50% + var(--cap-x)), calc(4px + var(--cap-y)));
}
body.is-3d .spot.is-hot .spot-vhs,
body.is-3d .spot:focus-visible .spot-vhs {
  transform: translate(calc(-50% + var(--cap-x)), var(--cap-y));
}

/* the hotspots are re-positioned in percent by Vector3.project() every frame;
   a transition on those percentages would lag the camera by a frame. This
   touches .spot's transition-property ONLY — no opacity, no visibility, and
   nothing at all on .spot-vhs (see rule 2 at the top of this file). */
body.is-3d .spot { transition-property: opacity, filter; }

@media (prefers-reduced-motion: reduce) {
  /* 3D never mounts under reduced motion (os/scene3d.js gates on it); this
     is only here so a forced mount cannot animate anything */
  .scene3d-canvas { transition: none; }
}
