/* ==========================================================================
   IX · HERO — canvas layer for the hero background interaction, plus the
   transform-only depth stack.

   The shipped CSS plus-grid (.rb-hero__grid) stays in the markup as the
   no-JS / no-motion ground truth. js/areas/hero.js inserts a <canvas>
   directly after it — same inset, same stacking slot, so the z-order is
   unchanged: art < veil < grid/canvas < content.

   The handover is class-driven, not variant-driven: .rb-hero--canvas is
   added by JS only AFTER the canvas has painted its first frame, so the
   background can never be blank — not on JS failure, not on reduced motion,
   not in the 'still' or 'depth' variants. body[data-rbs-hero] alone must
   never hide the CSS grid.

   The Tint knob writes --rbs-hero-tint; the canvas samples it (with a
   fallback to teal-500) via getComputedStyle — no color is hardcoded in JS.
   ========================================================================== */

.rb-hero__gridcanvas {
  position: absolute;
  inset: 2.7% 3.5% 0;               /* must match .rb-hero__grid exactly */
  z-index: var(--rb-sys-z-base);
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--rb-sys-duration-2) var(--rb-sys-ease-swift);
}

/* The CSS grid keeps its full opacity by default; only the live-canvas class
   swaps the two layers. Both fade across 220ms — the canvas paints the same
   pixels as the CSS grid at rest, so the swap reads as nothing at all.
   ('constellation' is the exception by design: it paints a network instead
   of graph paper, so the same 220ms swap is a real dissolve.) */
.rb-hero__grid {
  transition: opacity var(--rb-sys-duration-2) var(--rb-sys-ease-swift);
}

.rb-hero--canvas .rb-hero__gridcanvas { opacity: 1; }
.rb-hero--canvas .rb-hero__grid      { opacity: 0; }

/* Belt-and-braces: the canvasless variants never show a canvas, even if a
   stale node survives a teardown race. */
body[data-rbs-hero="still"] .rb-hero__gridcanvas,
body[data-rbs-hero="depth"] .rb-hero__gridcanvas { display: none; }
body[data-rbs-hero="still"] .rb-hero__grid,
body[data-rbs-hero="depth"] .rb-hero__grid       { opacity: 1; }

/* ==========================================================================
   DEPTH STACK — variant 'depth'.

   No canvas and no new elements: the four layers the hero already ships ride
   the cursor at four depths. js/areas/hero.js writes two unitless numbers on
   the section (--rbs-hero-px / --rbs-hero-py, −1…1) and nothing else; every
   transform below is that number multiplied by the Parallax throw and by the
   layer's own depth factor. Both properties fall back to 0, so a JS failure,
   a parked loop or a teardown leaves the hero exactly where it shipped.

   Depths, back to front:
     art  −0.30   the photograph, dragging against the move
     veil −0.12   the pane — nearly fixed, it's the reference everything else
                  is measured against
     grid +0.55   the graph paper, riding forward on the veil
     card  +1.00  the product art, inside its frame (the frame itself only
                  leans — see 'Card lean' below)

   The copy column is deliberately NOT in the stack. A headline that swims
   under the cursor is a headline nobody finishes reading.

   `translate3d` promotes each layer on its own, so no will-change is needed;
   the JS drives every frame, so no transition is either — a transition here
   would only add a second, uncontrolled lag on top of the Follow lag knob.
   ========================================================================== */

body[data-rbs-hero="depth"] .rb-hero {
  --rbs-hero-throw: var(--rbs-hero-shift, 10px);
}

/* Reduced motion keeps the response — it answers the user's own hand, which
   is the least vestibular kind of movement there is — but at a third of the
   travel. js/areas/hero.js drops the idle drift at the same time, and stands
   the whole variant down at the 'static' level. */
body[data-rbs-hero="depth"]:not([data-rbs-motion-level="authored"]) .rb-hero {
  --rbs-hero-throw: calc(var(--rbs-hero-shift, 10px) * 0.34);
}

/* The photograph and the product art both carry a hair of scale so a 10px
   throw can never expose the edge of their own box. */
body[data-rbs-hero="depth"] .rb-hero__art {
  transform:
    scale(1.03)
    translate3d(
      calc(var(--rbs-hero-px, 0) * var(--rbs-hero-throw, 10px) * -0.3),
      calc(var(--rbs-hero-py, 0) * var(--rbs-hero-throw, 10px) * -0.3),
      0
    );
}

body[data-rbs-hero="depth"] .rb-hero__veil {
  transform: translate3d(
    calc(var(--rbs-hero-px, 0) * var(--rbs-hero-throw, 10px) * -0.12),
    calc(var(--rbs-hero-py, 0) * var(--rbs-hero-throw, 10px) * -0.12),
    0
  );
}

body[data-rbs-hero="depth"] .rb-hero__grid {
  transform: translate3d(
    calc(var(--rbs-hero-px, 0) * var(--rbs-hero-throw, 10px) * 0.55),
    calc(var(--rbs-hero-py, 0) * var(--rbs-hero-throw, 10px) * 0.55),
    0
  );
}

/* The frontmost layer is the art INSIDE the product card, not the card box:
   .rb-media already clips, so the image slides behind its own rounded frame,
   and the card element stays free for the reveal system's entrance (which
   owns `transform` on .rb-hero__media while it plays). */
body[data-rbs-hero="depth"] .rb-hero__media > img {
  transform:
    scale(1.04)
    translate3d(
      calc(var(--rbs-hero-px, 0) * var(--rbs-hero-throw, 10px)),
      calc(var(--rbs-hero-py, 0) * var(--rbs-hero-throw, 10px)),
      0
    );
}

/* Card lean — a fraction of a degree of perspective on the frame itself, so
   the card reads as a held object rather than a flat plate. Pointer-driven,
   so it stays behind the hover/fine gate; on touch the stack translates from
   the idle drift alone and the card simply doesn't lean. */
body[data-rbs-hero="depth"] .rb-hero__media {
  --rbs-hero-lean-x: 0deg;
  --rbs-hero-lean-y: 0deg;
  transform:
    perspective(1100px)
    rotateX(var(--rbs-hero-lean-x))
    rotateY(var(--rbs-hero-lean-y));
}

@media (hover: hover) and (pointer: fine) {
  body[data-rbs-hero="depth"] .rb-hero__media {
    --rbs-hero-lean-x: calc(var(--rbs-hero-py, 0) * var(--rbs-hero-tilt, 0.8deg) * -1);
    --rbs-hero-lean-y: calc(var(--rbs-hero-px, 0) * var(--rbs-hero-tilt, 0.8deg));
  }
}

/* ==========================================================================
   PLEXUS ECHO — variant 'constellation'.

   Needs no CSS of its own: it rides the same canvas node, the same inset and
   the same 220ms handover as 'trace' and 'magnet', and its ink and tint are
   sampled from --rb-hero-grid-ink / --rbs-hero-tint like theirs. It is listed
   here only so the next reader doesn't go looking for the rules.
   ========================================================================== */
