/* ══════════════════════════════════════════════════════════════════════════
   THE STACK — custom 3D hero for /services/tech (Software & Engineering).

   WHAT IT IS
   ----------
   A machined slab, built from seven plates standing at real depths in a CSS
   3D perspective, seen nearly edge-on from slightly above. On load the plates
   arrive exploded and apart, and CLOSE into one solid object from the bottom
   up while the whole slab turns toward the viewer. You watch the system get
   assembled. Then, every 8.4s, a red light climbs the seams bottom-to-top —
   a change moving up through the layers, which is what shipping looks like.

   The plates carry the page's own subject on their front edges:
   runbook · infra · policy · data · jobs · api · interface. The runbook is
   the BOTTOM plate on purpose — this page's argument is that the record is
   what makes a handoff possible, so it is the thing everything stands on.

   WHY THIS AND NOT THE OTHER THINGS
   ---------------------------------
   No photograph, anywhere: Ethan, 2026-08-09, on both tech pages — "don't
   use a photo at all... make a custom animation for the page." So this is
   not a treatment applied to a picture (that is /technology's "The Build",
   ART-GUIDE §5) — it is an object built from nothing.
   No video loop (rejected on this site 2026-08-08). No node-and-line network
   or particle field (the generic-AI cliché, removed from services/ai.html
   twice and from /technology once). No scrolling filename ledger (the
   "record" hero this file replaces — Ethan: "can't read anything and the
   animation is shit").

   WHY NO CANVAS / WebGL
   ---------------------
   The scene is 28 opaque, axis-aligned, flat-shaded quads with no textures,
   no lighting model and no per-pixel work. The compositor draws exactly this
   for free from CSS transforms. A WebGL context would add DPR handling,
   resize handling, context-loss handling and a shader compile — for 28 quads
   the browser already knows how to draw. Labels stay DOM text, so they are
   subpixel-crisp at DPR 1/2/3 for zero texture bytes.

   FRAME BUDGET
   ------------
   Target 60fps => 16.67ms/frame for the whole page. Steady state here is ONE
   transform write on .stk-deck per rAF and nothing else: no layout reads, no
   style recalc on children, no paint. Every other moving part (the seat
   entry, the landing flash, the deploy pulse) is a compositor-side CSS
   animation on transform/opacity only. Geometry is read once, in JS, on
   build and on resize.

   Layer count: 7 plates x 4 quads (front, top, side, pulse) = 28, plus the
   floor sheen, the cast shadow and one red radial = 31. Zero image bytes —
   nothing here downloads.

   COLOUR AND UNITS, STATED
   ------------------------
   Authored directly in sRGB. The plate material is built from the locked
   palette ONLY: ink #0a0a0a, bone rgb(245,241,232) at low alpha for the lit
   faces, red #c8102e for the seams. No new hue is introduced — deliberately
   not the steel rgba(120,140,170,..) of .fx-steel, which belongs to
   /technology's hero and would blur the two pages together.
   Object geometry in CSS px, type in rem, angles in deg.

   ALWAYS DARK, BOTH THEMES. Light theme flips --ink to a cream and redefines
   --on-black to #1a1510; a hero that pins only its background and not the
   token set paints a near-black headline on near-black. The full dark token
   set is re-declared below. Same convention as .page-hero / .sv-close in
   site.css.

   REDUCED MOTION / NO-JS: the plates' resting state IS the seated state, so
   the still frame is the finished object, never an empty box. Every
   animation is gated behind html.js-motion, which the page's head snippet
   only sets when JS + IntersectionObserver + full motion are all available.
   ══════════════════════════════════════════════════════════════════════════ */

.stk-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: #0a0a0a;
  padding: clamp(6.5rem, 12vw, 9.5rem) 0 clamp(4rem, 7vw, 6rem);
  /* T-1095: 86svh -> 72svh. See the hero-height note in
     css/media-hero-movement.css — the five heroes are now deliberately
     different heights instead of five full-screen slabs. */
  min-height: 72vh;
  min-height: 72svh;
  display: flex;
  align-items: center;

  /* Full dark token set, re-declared — see header note. */
  --ink: #0a0a0a;
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245,241,232,.58);
  --on-black-faint: rgba(245,241,232,.52);
  --red-text: #e8455a;

  /* ── OBJECT GEOMETRY ──────────────────────────────────────────────
     One place. The JS reads none of these; it only composes the deck's
     rotation, so changing the slab's proportions here needs no JS edit.

     THE CONSTRAINT THAT DECIDES EVERYTHING HERE, measured rather than
     guessed: a plate's top face eats D*sin(rx) of vertical screen space.
     If that exceeds the pitch, every top face swallows the plate below it
     and the stack collapses into one big lit surface with slivers hanging
     off it — which is exactly what the first two builds did (D=158, rx=26
     => 69px of top face against a 34px pitch).

     So either the plates are TIGHT (gap of a few px, only the top plate
     shows a real top face, everything below reads as a stepped front edge —
     a solid block that has been sliced) or they are far enough apart to
     show their own top faces. This hero wants both: it opens exploded, at
     3.4x pitch, where every top face is visible, and CLOSES to tight. That
     contrast is the entry animation's whole payload.

     Settled: D*sin(20deg) = 61px of top face, pitch 30px, gap 4px. */
  --stk-w: 500px;   /* plate width  (X) */
  --stk-t: 26px;    /* plate thickness / front-face height (Y) */
  --stk-d: 190px;   /* plate depth   (Z) */
  --stk-p: 30px;    /* pitch: thickness + 4px of air between plates */
}

.stk-hero .inner { position: relative; z-index: 3; width: 100%; }

/* ── AMBIENT GROUND ────────────────────────────────────────────────────
   ONE red radial behind the object (ART-GUIDE §6: exactly one dominant
   glow, red under ~15% of the frame). Static — the object carries the
   motion, an ambient breath here would be below the perception threshold
   and buy nothing. */
.stk-hero__ground {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 46% 52% at 72% 46%, rgba(200,16,46,.20) 0%, rgba(200,16,46,.07) 42%, transparent 68%),
    radial-gradient(ellipse 70% 60% at 20% 8%, rgba(245,241,232,.045) 0%, transparent 62%),
    /* T-1086: DIRECTIONAL, not flat. This used to be a bottom-anchored wash
       over nothing; there is a photograph underneath it now, so the scrim is
       spent on the left where the headline and lede sit and runs out by 82%,
       leaving the desk, the screens and the gimbal plainly visible on the
       right where the slab stands. Flat darkening here would put the black
       box straight back. */
    linear-gradient(100deg,
      rgba(10,10,10,.93) 0%, rgba(10,10,10,.80) 26%,
      rgba(10,10,10,.46) 50%, rgba(10,10,10,.16) 70%, transparent 82%),
    linear-gradient(to bottom, rgba(10,10,10,.55) 0%, transparent 26%, transparent 66%, #0a0a0a 100%);
}

/* ── THE HERO PHOTOGRAPH (T-1086) ──────────────────────────────────────
   Sits BELOW .stk-hero__ground (z-index 0 vs the ground's own 0, ordered by
   source position) and below the scene at z-index 1, so the slab is always
   in front of the room. Never inside .stk-scene — see the transform-style
   warning above; an <img> in there is fine but any filter/opacity on an
   ancestor of .stk-deck would flatten the slab, so it is kept a sibling.

   Anchored high (object-position y 6%) because the Ronin gimbal head starts
   at y~12% of the source and losing it loses the best part of the frame.
   The source is 1400x1050 and the element renders at ~74% of the viewport,
   so at 1920 it draws at ~1420px — native, no upscale. */
.stk-hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 58% 6%;
  z-index: 0;
  opacity: .78;
  filter: saturate(.94) contrast(1.04);
  pointer-events: none;
}
@media (min-width: 940px) {
  .stk-hero__photo {
    left: auto; right: 0;
    width: 74%;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,.4) 15%, #000 38%);
            mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,.4) 15%, #000 38%);
  }
}
/* ── T-1120: THE HERO COPY WAS NOT READABLE UNDER 940px ────────────────
   This is the first thing anyone sees on this page and it measured, on real
   painted pixels at 390:

     .stk-eyebrow   2.36:1   (median ground rgb(79,71,60), needs 4.5)
     .stk-lede      2.01:1   worst-case ground rgb(145,137,126)
     .stk-h1        1.73:1   worst-case ground rgb(189,185,177)

   At 1440 the same elements pass (4.93 / 6.06 median) because the photo is
   masked to the right 74% and the copy sits on near-ink. Full-bleed under
   940px removed that protection and the scrim did not make it up: the old
   `to bottom` ramp was only .30 at 22% and .66 at 52%, and the copy block
   lands right across that stretch — over a lit window and two monitors.

   Fixed the way ART-GUIDE §1 prescribes, not by blacking the hero out
   (Ethan, 2026-08-13, objected to exactly that): the photo steps down one
   notch, the scrim is spent where the copy actually is rather than evenly,
   and the headline and lede take the documented Dispatch text-shadows. The
   top 38% — the slab, the gimbal, the room — keeps its exposure. */
@media (max-width: 939px) {
  /* Full bleed under the copy at this width, so the picture is pulled back and
     the ground scrim below takes over the legibility from the bottom up.

     .42, not the .34 the first pass tried. Darkening the WHOLE frame to fix
     three elements is how a hero ends up "blacked out" — the objection this
     photograph was added to answer. The copy gets a local plate instead
     (below), so the room can stay lit. */
  .stk-hero__photo { object-position: 62% 4%; opacity: .42; }
  .stk-hero__ground {
    background:
      radial-gradient(ellipse 46% 52% at 72% 30%, rgba(200,16,46,.18) 0%, rgba(200,16,46,.06) 42%, transparent 68%),
      linear-gradient(to bottom,
        rgba(10,10,10,.52) 0%, rgba(10,10,10,.32) 20%,
        rgba(10,10,10,.70) 46%, rgba(10,10,10,.90) 66%,
        rgba(10,10,10,.96) 84%, #0a0a0a 100%);
  }
  /* THE PLATE FOLLOWS THE COPY, NOT THE VIEWPORT.
     Deepening the page-height ramp moved the eyebrow's ground from
     rgb(79,71,60) to only rgb(72,49,49) — 2.36:1 to 3.09:1, still short of
     4.5 — because a `to bottom` ramp has to guess where the copy is, and
     this hero's copy block moves with the headline's wrap. An ellipse
     anchored on the copy itself darkens exactly the region that needs it
     and nothing else. Feathered to transparent by 84%, so there is no box
     edge anywhere — it is a scrim, not a card. */
  .stk-copy { position: relative; }
  .stk-copy::before {
    content: ""; position: absolute;
    inset: -1.8rem -1.6rem -1.4rem -1.6rem;
    z-index: -1; pointer-events: none;
    background: radial-gradient(ellipse 118% 104% at 32% 48%,
      rgba(10,10,10,.88) 0%, rgba(10,10,10,.74) 46%,
      rgba(10,10,10,.34) 70%, transparent 86%);
  }
  /* ART-GUIDE §1 "over busier photos" — the Dispatch masthead pattern. */
  .stk-h1 { text-shadow: 0 2px 28px rgba(10,10,10,.65); }
  .stk-lede,
  .stk-eyebrow { text-shadow: 0 1px 14px rgba(10,10,10,.75); }
}

/* T-1086: the eyebrow is an inline-flex with a 36px rule as ::before and
   line-height:1. At 390 the string "TECHNOLOGY · SOFTWARE & ENGINEERING"
   needs 392px against 358px of column, so it wrapped — and at line-height 1
   the second line ran straight through the rule. Measured, not guessed. */
@media (max-width: 520px) {
  .stk-eyebrow {
    flex-wrap: wrap;
    align-items: center;
    row-gap: 2px;
    line-height: 1.5;
    letter-spacing: .16em;
    font-size: 10px;
    gap: 10px;
  }
  .stk-eyebrow::before { flex: 0 0 28px; }
}

/* T-1118, 2026-08-14 — THE EYEBROW WAS 2.16:1 AT 390.
   Measured off the rendered preview, not eyeballed. Under 940px the
   photograph goes full bleed behind the copy, and the eyebrow lands at ~64%
   of the hero — which on this frame is the lit window and the monitors, the
   brightest thing in the picture. Against that it measured 2.16:1, and 4.58:1
   even against the darker pixels in the same band. AA wants 4.5:1. The
   headline (10.4:1) and lede (5.9:1) were always fine; it is only the small
   mono line that failed, because it is 10px and it is red.

   Two changes, both cheap and neither of which dims the photograph further:
   the copy band of the scrim is deepened between 45% and 80% (where the
   eyebrow and headline sit, not across the whole frame), and the copy gets
   the SAME text-shadow guarantee .page-hero copy already carries in
   site.css:1581 — .stk-hero is not a .page-hero, so it never inherited it.
   The scrim itself is corrected in the max-width:767px block further down,
   which is the one that actually applies at this width. */
.stk-eyebrow,
.stk-h1,
.stk-lede {
  text-shadow: 0 2px 34px rgba(0,0,0,.95), 0 2px 8px rgba(0,0,0,.9), 0 0 2px rgba(0,0,0,.8);
}

/* ══════════════════════════════════════════════════════════════════════
   THE SCENE
   .stk-scene / .stk-stage / .stk-deck must never carry opacity, filter,
   mask, clip-path, mix-blend-mode or contain:paint. Any one of them forces
   transform-style to flat and the slab collapses to a flat picture. This is
   the same constraint documented for /technology's hero in ART-GUIDE §5 and
   it is load-bearing, not stylistic.
   ══════════════════════════════════════════════════════════════════════ */
.stk-scene {
  position: absolute;
  z-index: 1;
  inset: 0 0 0 auto;
  width: 62%;
  pointer-events: none;
}

.stk-stage {
  position: absolute;
  inset: 0;
  /* 1800px, not the 1250px this started at. Perspective distance is measured
     against the object's WIDTH: at 1250px a 500px-wide plate converges hard
     enough that the near end is drawn visibly lower and larger than the far
     end, and the whole slab reads as sheared rather than turned. 1800px is
     roughly 3.6x the plate width — enough depth cue to be unmistakably 3D,
     not so much that it distorts. */
  perspective: 1800px;
  perspective-origin: 50% 44%;
}

.stk-deck {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  /* Resting pose. JS overrides this inline every frame while the hero is on
     screen; this value is what no-JS and reduced-motion visitors see, and it
     is deliberately the SETTLED pose, not the entry pose.

     20deg of down-tilt against a 178px depth is the pair that makes this
     read as a solid object rather than a row of bars — see the note on
     --stk-d above for the arithmetic. If either value changes, BASE_RX in
     js/tech-stack-hero.js changes with it: they must match, or the first
     frame JS writes is a visible jump away from the pose a no-JS visitor
     was already shown.

     ORDER: rotateX THEN rotateY. Both orders were rendered side by side
     before choosing. rotateX-then-rotateY keeps the STACK axis vertical on
     screen and lets the plate edges recede, which is what looking down at a
     stack of plates actually does. The reverse keeps the plate edges
     horizontal but tips the stack axis, so the slab reads as leaning. */
  transform: rotateX(21deg) rotateY(-17deg);
}

/* ── ONE PLATE ─────────────────────────────────────────────────────────
   Three faces make the volume: front (the edge you read), top (the surface
   that catches the light) and left side (visible because the deck yaws
   negative). At this camera the top face is what sells the solidity; the
   side face only has to stop you seeing into the object at the left edge. */
.stk-plate {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--stk-w);
  height: var(--stk-t);
  margin-left: calc(var(--stk-w) / -2);
  margin-top: calc(var(--stk-t) / -2);
  transform-style: preserve-3d;
  /* Seated. --i is 0 at the top plate, 6 at the bottom. */
  transform: translate3d(0, calc((var(--i) - 3) * var(--stk-p)), 0);
}

/* FRONT — the read face. A vertical fall-off from a lit top edge to a dark
   bottom, which is what a milled metal edge does under a high key light. */
.stk-plate__f {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(245,241,232,.20) 0%,
      rgba(245,241,232,.085) 38%,
      rgba(245,241,232,.03) 76%,
      rgba(245,241,232,.012) 100%),
    #141619;
  box-shadow: inset 0 1px 0 rgba(245,241,232,.24);
}

/* The seam: a red hairline along the bottom of every front face. Static and
   dim at rest (.22); the deploy pulse is a separate layer that blooms over
   it, so the two never fight for the same opacity. */
.stk-plate__f::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, rgba(200,16,46,0) 0%, rgba(232,69,90,.95) 16%, rgba(232,69,90,.95) 84%, rgba(200,16,46,0) 100%);
  opacity: .40;
}

/* TOP — extends backward from the front face's top edge into the screen.
   rotateX(90deg) about the top edge maps the element's +Y (down) onto -Z
   (away), so this lies flat as a surface. Brightest face: it faces the key. */
.stk-plate__t {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--stk-w);
  height: var(--stk-d);
  transform-origin: 50% 0%;
  transform: rotateX(90deg);
  /* Brightest face — it points at the key. The gradient runs FRONT (top of
     this element, which is the plate's leading edge) to BACK, so the lit
     edge is the one nearest the viewer and each plate separates cleanly
     from the one behind it. A red term rides the front edge so the object
     sits in the page's palette instead of reading as neutral grey plastic;
     it is the same red as the seams, at a fraction of the alpha. */
  background:
    linear-gradient(to bottom,
      rgba(200,16,46,.10) 0%,
      rgba(200,16,46,.02) 22%,
      rgba(200,16,46,0) 46%),
    linear-gradient(to bottom,
      rgba(245,241,232,.235) 0%,
      rgba(245,241,232,.125) 30%,
      rgba(245,241,232,.055) 66%,
      rgba(245,241,232,.022) 100%),
    #171a1e;
}

/* SIDE — the left cheek, revealed by the deck's negative yaw. Darkest face:
   it turns away from the key. */
.stk-plate__s {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--stk-d);
  height: var(--stk-t);
  transform-origin: 0% 50%;
  transform: rotateY(-90deg);
  background:
    linear-gradient(to bottom, rgba(245,241,232,.09) 0%, rgba(245,241,232,.02) 100%),
    #0e1013;
}

/* PULSE CARRIER — sits just in front of the front face and carries both the
   one-shot landing flash (::before) and the repeating deploy pulse (::after).
   Two pseudo-elements rather than two animations on one element, because two
   animations touching opacity on the same box means the last one declared
   silently wins. */
.stk-plate__g {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 10px;
  transform: translateZ(1px);
  pointer-events: none;
}
.stk-plate__g::before,
.stk-plate__g::after {
  content: "";
  position: absolute;
  left: 6%;
  right: 6%;
  bottom: 0;
  height: 10px;
  border-radius: 50%;
  background: radial-gradient(ellipse 60% 100% at 50% 100%, rgba(232,69,90,.95) 0%, rgba(200,16,46,.45) 40%, transparent 74%);
  opacity: 0;
}

/* LABEL — engraved into the front face, not a diagram legend. Kept quiet
   (.42 bone, 10px, wide tracking) so it reads as a marking on the object. */
.stk-plate__l {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%) translateZ(1px);
  font-family: var(--font-mono);
  font-size: 9.5px;
  line-height: 1;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: rgba(245,241,232,.58);
  white-space: nowrap;
}
/* The base plate is the page's point — let it read a step brighter, in red. */
.stk-plate[data-base] .stk-plate__l { color: rgba(232,69,90,.92); }

/* ── FLOOR ─────────────────────────────────────────────────────────────
   A cast shadow and a smear of sheen under the slab. Both are flat 2D
   elements OUTSIDE the preserve-3d chain (siblings of .stk-deck, not
   children), so their filters cannot flatten anything. */
.stk-floor {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 560px;
  height: 150px;
  margin: 0 0 0 -280px;
  transform: translateY(118px);
  pointer-events: none;
}
/* The contact shadow. Sits under the base plate and grounds the object —
   without it the slab floats in a void and the whole thing reads as a
   graphic rather than a thing with weight. */
.stk-floor::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 38% 26% at 50% 22%, rgba(0,0,0,.95) 0%, rgba(0,0,0,.55) 44%, transparent 74%);
  filter: blur(16px);
}
/* Bounce: the red key spilling onto the surface the slab stands on. Kept
   under the shadow's footprint so it reads as light on a floor, not as a
   second glow floating in front of the object. */
.stk-floor::after {
  content: "";
  position: absolute;
  left: 20%;
  right: 20%;
  top: 10%;
  height: 46%;
  background: radial-gradient(ellipse 60% 100% at 50% 0%,
    rgba(200,16,46,.20) 0%,
    rgba(200,16,46,.07) 46%,
    transparent 80%);
  filter: blur(12px);
}

/* ══════════════════════════════════════════════════════════════════════
   COPY
   ══════════════════════════════════════════════════════════════════════ */
.stk-copy { max-width: 560px; }

.stk-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 11px;
  line-height: 1;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 1.35rem;
}
.stk-eyebrow::before {
  content: "";
  width: 36px;
  height: 1px;
  background: var(--red);
  display: inline-block;
}

.stk-h1 {
  font-family: var(--font-display);
  font-weight: 400;
  /* T-1041: was clamp(2.3rem, 5.1vw, 3.9rem) */
  font-size: clamp(2rem, 4vw, 3.15rem);
  line-height: 1.08;
  letter-spacing: -.02em;
  color: var(--on-black);
  margin: 0 0 1.35rem;
}
.stk-h1 em { font-style: italic; color: var(--red-text); }

.stk-lede {
  font-family: var(--font-display);
  font-weight: 400;
  /* was clamp(1.02rem, 1.5vw, 1.22rem) */
  font-size: clamp(.94rem, 1.2vw, 1.06rem);
  line-height: 1.55;
  color: var(--on-black-mute);
  margin: 0 0 2rem;
  max-width: 30em;
}
.stk-lede em { font-style: italic; color: var(--on-black); }

.stk-ctas { display: flex; flex-wrap: wrap; gap: .8rem 1rem; align-items: center; }

/* ── ENTRY: THE COPY ───────────────────────────────────────────────────
   Shares the object's beat rather than running its own. Opacity finishes
   early so the block is legible for most of its travel — the same split
   that /technology's hero needed (ART-GUIDE §5: transform and opacity on
   one strongly front-loaded curve produces a crossfade with no build). */
html.js-motion .stk-copy > * {
  opacity: 0;
  transform: translateY(14px);
}
html.js-motion .stk-hero.is-live .stk-copy > * {
  animation:
    stkCopyRise 780ms cubic-bezier(.2,.72,.26,1) both,
    stkFade 340ms linear both;
}
html.js-motion .stk-hero.is-live .stk-eyebrow { animation-delay: 120ms; }
html.js-motion .stk-hero.is-live .stk-h1      { animation-delay: 220ms; }
html.js-motion .stk-hero.is-live .stk-lede    { animation-delay: 340ms; }
html.js-motion .stk-hero.is-live .stk-ctas    { animation-delay: 460ms; }

@keyframes stkCopyRise { to { transform: translateY(0); } }
@keyframes stkFade { from { opacity: 0; } to { opacity: 1; } }

/* ══════════════════════════════════════════════════════════════════════
   ENTRY: THE OBJECT
   The plates arrive spread apart to 3.4x their pitch, pushed forward in Z
   and turned a few degrees off axis, and close from the BOTTOM UP — the
   base seats first and each plate above lands on the one below. Bottom-up
   is semantic here (the runbook is the foundation) and it also puts the
   first movement in the middle of the frame, which is where the eye is.

   Timing: first movement at 120ms, last plate seated at ~1.5s. That is the
   whole point — ART-GUIDE §5's diagnosis of the rejected /technology hero
   was that ambient loops slower than the perception threshold read as
   nothing happening. This is deliberately front-loaded and needs no
   interaction to fire.
   ══════════════════════════════════════════════════════════════════════ */
html.js-motion .stk-plate { opacity: 0; }

html.js-motion .stk-hero.is-live .stk-plate {
  animation:
    stkSeat 1080ms cubic-bezier(.19,.72,.24,1) both,
    stkFade 400ms linear both;
  animation-delay: calc(120ms + (6 - var(--i)) * 62ms);
}

@keyframes stkSeat {
  from {
    transform:
      translate3d(0, calc((var(--i) - 3) * var(--stk-p) * 3.4), calc((3 - var(--i)) * 30px))
      rotateY(calc((var(--i) - 3) * 4deg))
      rotateZ(calc((var(--i) - 3) * -1.1deg));
  }
  to {
    transform: translate3d(0, calc((var(--i) - 3) * var(--stk-p)), 0);
  }
}

/* The landing flash — one shot, fired as each plate seats, so the assembly
   has an impact instead of just easing to a stop. 78% of the seat duration
   puts it right as the plate closes the gap, not after it. */
html.js-motion .stk-hero.is-live .stk-plate__g::before {
  animation: stkLand 520ms cubic-bezier(.16,1,.3,1) both;
  animation-delay: calc(120ms + (6 - var(--i)) * 62ms + 840ms);
}
@keyframes stkLand {
  0%   { opacity: 0;   transform: scaleX(.86); }
  18%  { opacity: 1;   transform: scaleX(1.02); }
  100% { opacity: 0;   transform: scaleX(1); }
}

/* ══════════════════════════════════════════════════════════════════════
   THE DEPLOY PULSE — the idle beat.
   A red light climbs the seams bottom-to-top: 6 x 96ms of travel plus a
   ~500ms flare = under 1.1s of activity in an 8.4s cycle, so the object is
   at rest 87% of the time and there is still a discrete, legible event for
   anyone who lingers. Comfortably inside ART-GUIDE §2's "no loop under 8s".

   It starts at 2.4s, after the entry has finished, so the two never overlap
   and read as one confused shimmer.
   ══════════════════════════════════════════════════════════════════════ */
html.js-motion .stk-hero.is-live .stk-plate__g::after {
  animation: stkPulse 8400ms linear infinite;
  animation-delay: calc(2400ms + (6 - var(--i)) * 96ms);
}
@keyframes stkPulse {
  0%    { opacity: 0; transform: scaleX(.9); }
  1.4%  { opacity: 1; transform: scaleX(1.03); }
  7%    { opacity: 0; transform: scaleX(1); }
  100%  { opacity: 0; transform: scaleX(1); }
}

/* ══════════════════════════════════════════════════════════════════════
   RESPONSIVE
   ══════════════════════════════════════════════════════════════════════ */
/* Every breakpoint below keeps the desktop's thickness-to-gap ratio (a gap of
   3-4px against a 17-22px plate). The first build scaled width and depth down
   but left the pitch alone, so the gap grew to WIDER than the plate was thick
   and the slab fell back apart into floating slats — on exactly the devices
   least able to make sense of it. Scale all four together or not at all. */
@media (max-width: 1180px) {
  .stk-hero { --stk-w: 410px; --stk-t: 22px; --stk-d: 150px; --stk-p: 26px; }
  .stk-scene { width: 56%; }
  .stk-copy { max-width: 480px; }
}

/* Stacked layout: object above, copy below. The object stops bleeding off
   the right edge and becomes a centred piece with the copy under it. */
@media (max-width: 900px) {
  .stk-hero {
    display: block;
    min-height: 0;
    padding: calc(var(--header-h) + clamp(2rem, 7vw, 3.5rem)) 0 clamp(3rem, 8vw, 4.5rem);
    --stk-w: 356px; --stk-t: 20px; --stk-d: 128px; --stk-p: 24px;
  }
  .stk-scene {
    position: relative;
    inset: auto;
    width: 100%;
    height: 300px;
    margin-bottom: clamp(1.5rem, 5vw, 2.5rem);
  }
  .stk-stage { perspective: 900px; perspective-origin: 50% 46%; }
  .stk-copy { max-width: none; }
  .stk-floor { width: 420px; margin-left: -210px; transform: translateY(112px); }
}

/* ART-GUIDE §3 — thin the stack under 768px. The labels are the first thing
   to go: at this plate width they would be 7px and read as grey mush rather
   than as markings. The side cheek goes too (a 4px sliver is not volume,
   just an artefact), and the floor sheen with it. What stays: the object,
   its assembly, and the deploy pulse. */
@media (max-width: 767px) {
  .stk-hero { --stk-w: 268px; --stk-t: 17px; --stk-d: 98px; --stk-p: 20px; }
  .stk-scene { height: 248px; }
  .stk-plate__l { display: none; }
  .stk-plate__s { display: none; }
  .stk-floor::after { display: none; }
  .stk-floor { width: 320px; margin-left: -160px; transform: translateY(92px); }
  /* T-1118: this gradient was TRANSPARENT from 30% to 74%, and on this layout
     the copy column starts at 49% (eyebrow) and runs to ~85% (buttons). So
     every line of hero copy under 768px sat on bare photograph with no scrim
     under it at all — measured 2.16:1 on the eyebrow at 390, against a lit
     window. This block, not the 939px one above it, is what applies there.
     The picture is left alone through the top 40% (the gimbal, the desk and
     the screens, which is the part worth seeing) and the ramp is spent
     entirely on the copy band below it. */
  .stk-hero__ground {
    background:
      radial-gradient(ellipse 66% 30% at 50% 24%, rgba(200,16,46,.18) 0%, rgba(200,16,46,.06) 44%, transparent 70%),
      linear-gradient(to bottom,
        rgba(10,10,10,.5) 0%, transparent 26%,
        rgba(10,10,10,.44) 42%, rgba(10,10,10,.86) 55%,
        rgba(10,10,10,.92) 74%, #0a0a0a 100%);
  }
}

/* ══════════════════════════════════════════════════════════════════════
   LIGHT THEME — pinned dark, tokens and all. See header note.
   ══════════════════════════════════════════════════════════════════════ */
[data-theme="light"] .stk-hero {
  background: #0a0a0a;
  --ink: #0a0a0a;
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245,241,232,.58);
  --on-black-faint: rgba(245,241,232,.52);
  --red-text: #e8455a;
}

/* ══════════════════════════════════════════════════════════════════════
   REDUCED MOTION — explicit per-layer kill, listing every animated
   selector this file adds (ART-GUIDE §2 rule 5). Nothing freezes mid-frame:
   the plates rest seated, the copy rests visible, and both pulse layers
   hide rather than stopping at whatever opacity they happened to hold.
   ══════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .stk-plate,
  .stk-plate__g::before,
  .stk-plate__g::after,
  .stk-copy > * {
    animation: none !important;
    transition: none !important;
    will-change: auto !important;
  }
  .stk-plate { opacity: 1 !important; transform: translate3d(0, calc((var(--i) - 3) * var(--stk-p)), 0) !important; }
  .stk-copy > * { opacity: 1 !important; transform: none !important; }
  .stk-plate__g::before,
  .stk-plate__g::after { opacity: 0 !important; }
  .stk-deck { transform: rotateX(21deg) rotateY(-17deg) !important; }
}
