/* ==========================================================================
   Card section — the sticky stage the 3D card is rendered into.
   Geometry, lighting and scroll mapping live in js/card-3d.js.

   Tuning knob: --track-length is the scrollable distance beyond the sticky
   viewport (the JSX handoff's `scrollHeight` was 400vh total = 100vh + 300vh).
   Longer = slower rotation.
   ========================================================================== */

.card-3d {
  --track-length: 320vh;
  /* Scroll progress 0..1, written every frame by js/card-3d.js. The default
     matters: with --p undefined the beats' opacity maths is invalid and every
     beat renders at once. */
  --p: 0;

  position: relative;
  height: calc(100svh + var(--track-length));
  /* The handoff specifies a black page behind it; use the hero's bed so the
     two sections meet without a seam. */
  background: var(--black-deep);
}

/* The section's own top edge, dissolving out of the hero. Anchored to the
   section rather than to the sticky child, so it scrolls away as the stage
   pins — mirroring --grad-hero-foot from the other side of the seam. */
.card-3d::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /* Kept clear of the title card, which sits at 17% of the stage: a taller
     fade would dim the type while the section is rising into view. */
  height: 15vh;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(6, 5, 4, 1) 0%,
    rgba(6, 5, 4, .72) 34%,
    rgba(6, 5, 4, .3) 66%,
    rgba(6, 5, 4, 0) 100%);
}

.card-3d__sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
}

/* --- Light ----------------------------------------------------------------
   Three background layers, all behind the canvas: the card itself is lit by
   the scene's own lights, and these are the air that light travels through.

   The handoff's single corner wash read as a gold blob sitting in the top-left
   for no reason. This is an overhead source instead: broad ambience, a shaft
   descending into the frame, and a pool that tracks the card as it falls.
   ------------------------------------------------------------------------- */

/* Light holds off until the card arrives (it fades in from p 0.15), then grows
   downward out of the top of the frame. Before that the section is pure black
   behind the title card. */
.card-3d__beam,
.card-3d__pool {
  /* In as the card arrives, out again as it leaves, so the section ends on
     black — no glow or backlight surviving into the section below. The fade
     out tracks the card's own sink (hidden by ~0.97). */
  --glow: min(
    clamp(0, calc((var(--p) - 0.05) * 8), 1),
    clamp(0, calc((0.97 - var(--p)) * 9), 1));
  opacity: var(--glow);
}

/* The shaft: a column of light widening as it descends. */
.card-3d__beam {
  position: absolute;
  inset: -8% -20% 0;
  pointer-events: none;
  transform-origin: 50% 0;
  transform: scaleY(calc(0.52 + var(--glow) * 0.48));
  /* Built from radials rather than a clipped, blurred polygon: the polygon's
     taper stayed legible as an edge however hard it was blurred, and a
     full-viewport blur is expensive to composite on every scroll frame. */
  background:
    radial-gradient(ellipse 20% 40% at 50% 0%,
      rgba(236, 196, 120, 0.22), rgba(0, 0, 0, 0) 72%),
    radial-gradient(ellipse 40% 62% at 50% 22%,
      rgba(221, 173, 93, 0.13), rgba(0, 0, 0, 0) 76%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22%);
}

/* The pool travelling with the card: its centre is driven by --p, so the light
   follows the descent instead of sitting still. */
.card-3d__pool {
  position: absolute;
  left: 50%;
  top: calc(20% + var(--p) * 54%);
  width: 66vh;
  height: 66vh;
  transform: translate(-50%, -50%);
  pointer-events: none;
  background: radial-gradient(circle,
    rgba(226, 178, 98, 0.20) 0%,
    rgba(226, 178, 98, 0.07) 38%,
    rgba(0, 0, 0, 0) 70%);
}

/* ==========================================================================
   Narrative beats
   Each beat owns a window in progress space: --in, --out, and --rate (the
   reciprocal of the fade ramp, so the maths stays multiplication — dividing by
   a custom property is not universally supported).

     --vis    visible now?   fade in AND fade out, whichever is smaller
     --enter  arriving?      fade in only, so the drift settles and stays put

   Windows overlap by design: one beat is still dissolving as the next arrives.
   ========================================================================== */

.card-3d__beats {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.beat {
  --enter: clamp(0, calc((var(--p) - var(--in)) * var(--rate)), 1);
  --vis: min(var(--enter), clamp(0, calc((var(--out) - var(--p)) * var(--rate)), 1));

  position: absolute;
  opacity: var(--vis);
  transform: translate(calc((1 - var(--enter)) * var(--dx, 0px)),
                       calc((1 - var(--enter)) * 20px));
  text-shadow: var(--shadow-body);
}

/* --- Title card ---------------------------------------------------------- */

.beat--intro {
  /* --in is negative on purpose. While the section is still rising into view
     progress is pinned at 0, so a window starting at 0 leaves a full viewport
     of dead black. Starting below zero means the title card is already at full
     opacity as the section arrives. */
  --in: -0.10;
  --out: 0.14;
  --rate: 14;

  left: var(--gutter);
  right: var(--gutter);
  top: 17%;
  text-align: center;
}

.beat__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-16);
  margin-bottom: var(--space-26);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: rgba(217, 169, 58, .82);
}

.beat__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(38px, 5vw, 68px);
  line-height: 1.04;
  letter-spacing: -0.02em;
  color: var(--bone);
}

/* The metallic fill, reused from the hero's accent line. */
.beat__title em {
  font-style: normal;
  background: var(--grad-metal);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

.beat__lede {
  max-width: 52ch;
  margin: var(--space-26) auto 0;
  font-size: 17px;
  font-weight: 300;
  line-height: 1.7;
  color: var(--bone-body);
  text-wrap: pretty;
}

/* --- Side beats ---------------------------------------------------------- */

.beat--side { max-width: 440px; }

.beat--1 { --in: 0.18; --out: 0.40; --rate: 16; --dx: -30px; left: var(--gutter);  top: 18%; }
.beat--2 { --in: 0.36; --out: 0.58; --rate: 16; --dx:  30px; right: var(--gutter); top: 29%; }
.beat--3 { --in: 0.54; --out: 0.76; --rate: 16; --dx: -30px; left: var(--gutter);  top: 39%; }
.beat--4 { --in: 0.66; --out: 0.84; --rate: 16; --dx:  30px; right: var(--gutter); top: 49%; }

/* Right-hand beats mirror: text ranges right, the rule sits on the outside. */
.beat--2,
.beat--4 { text-align: right; }

.beat__index {
  display: flex;
  align-items: center;
  gap: var(--space-14);
  margin-bottom: var(--space-20);
}

.beat--2 .beat__index,
.beat--4 .beat__index { justify-content: flex-end; }

.beat__rule {
  width: 40px;
  height: 1px;
  background: var(--gold-deep);
}

.beat__num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 24px;
  letter-spacing: 0.06em;
  font-variant-numeric: lining-nums;
  font-feature-settings: 'lnum' 1, 'onum' 0;
  color: var(--gold);
}

.beat__heading {
  margin: 0 0 var(--space-16);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(32px, 2.9vw, 44px);
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--gold-figure);
}

/* The promise: the line that has to land on its own. */
.beat__lead {
  margin: 0 0 var(--space-14);
  font-family: var(--font-ui);
  font-size: clamp(18px, 1.5vw, 21px);
  font-weight: 400;
  line-height: 1.45;
  color: var(--bone);
  text-wrap: pretty;
}

/* The mechanism, set quieter so it reads as support rather than as a second
   headline. */
.beat__copy {
  margin: 0;
  font-size: 16px;
  font-weight: 300;
  line-height: 1.7;
  color: rgba(246, 241, 230, .68);
  text-wrap: pretty;
}

/* The proof line: hard numbers, in the site's fine-print register. */
.beat__meta {
  margin: var(--space-20) 0 0;
  padding-top: var(--space-14);
  border-top: 1px solid rgba(217, 169, 58, .16);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(217, 169, 58, .78);
}

/* WebGL canvas mounts here. */
.card-3d__stage {
  position: absolute;
  inset: 0;
}

/* Lens falloff at the frame edges. Sits above the canvas so it shapes the card
   too, but below the beats (z-index 2) so the type stays crisp. */
.card-3d__sticky::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(125% 88% at 50% 46%,
    rgba(0, 0, 0, 0) 40%, rgba(3, 2, 2, 0.62) 100%);
}

.card-3d__stage canvas {
  display: block;
}

/* --- Responsive ----------------------------------------------------------
   Under ~900px there is no room beside the card, so the stage takes the upper
   frame and every beat lands in the same place below it — they crossfade
   through one another instead of alternating sides. */

@media (max-width: 900px) {
  /* The stage is the WebGL canvas, and here it covers only the top 48% of the
     screen so the beats have the lower half to themselves. That edge was a
     hard one: the card was sliced off mid-descent by a straight line, and the
     starfield stopped dead at the same place, which together read as an opaque
     box sitting behind the type. Fading the canvas out over its last quarter
     turns the same edge into the card sinking out of the light — which is what
     the descent is meant to look like — and lets the stars thin out instead of
     ending. */
  .card-3d__stage {
    inset: 0 0 52% 0;
    -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
  }

  /* Serial windows, not overlapping ones. On desktop the four side beats each
     own a different corner, so a beat still dissolving while the next arrives
     reads as a cross-dissolve. Stacked into one slot below, that same overlap
     superimposes two paragraphs on the same lines — beats 3 and 4 overlap by
     0.10 of progress and are BOTH at full opacity around p = 0.70. Here each
     beat ends exactly where the next begins, so only one is ever on screen.
     The faster --rate keeps the hold time long despite the shorter windows. */
  .beat--1 { --in: 0.160; --out: 0.335; --rate: 24; }
  .beat--2 { --in: 0.335; --out: 0.510; --rate: 24; }
  .beat--3 { --in: 0.510; --out: 0.685; --rate: 24; }
  .beat--4 { --in: 0.685; --out: 0.860; --rate: 24; }

  /* 66vh is 557px on a 390px-wide phone, so the "pool" stopped being a pool and
     became an even wash across the whole screen. Bound it by the short edge as
     well, which keeps roughly the desktop pool-to-card ratio. */
  .card-3d__pool {
    width: min(66vh, 92vw);
    height: min(66vh, 92vw);
  }

  .beat--intro { top: 11%; }

  .beat--side {
    --dx: 0px;
    left: var(--gutter);
    right: var(--gutter);
    top: auto;
    bottom: 5%;
    max-width: none;
    text-align: center;
  }

  .beat--2,
  .beat--4 { text-align: center; }

  .beat__index { justify-content: center; }

  /* 02 and 04 carry the rule after the numeral so it sits outboard when they
     are right-aligned; centred (and in the static grid) that reads inconsistent,
     so flip them back to rule-then-numeral. */
  .beat--2 .beat__index,
  .beat--4 .beat__index { flex-direction: row-reverse; }
}

@media (max-width: 768px) {
  /* Same 3.2 screen-heights of scroll as the desktop track, but a screen-height
     on a phone is a much shorter flick, so the sequence dragged. 240vh keeps
     every beat its own window while tightening the whole descent by a third. */
  .card-3d { --track-length: 240vh; }
}

@media (max-width: 600px) {
  .beat__heading { font-size: 30px; }
  .beat__copy { font-size: 16px; }
  .beat__num { font-size: 20px; }
}

/* --- Static fallback -----------------------------------------------------
   js/card-3d.js adds .is-live once the card is really rendering and motion is
   allowed. Without it — no JS, no WebGL, or prefers-reduced-motion — the beats
   cannot reveal in sequence, so show them all as a static grid beneath the
   card and let the section size to its content. */

.card-3d:not(.is-live) { height: auto; }

.card-3d:not(.is-live) .card-3d__sticky {
  position: relative;
  height: auto;
}

.card-3d:not(.is-live) .card-3d__stage {
  position: relative;
  inset: auto;
  height: 62svh;
}

.card-3d:not(.is-live) .card-3d__beats {
  position: static;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-56) var(--space-38);
  padding: var(--space-56) var(--gutter);
}

.card-3d:not(.is-live) .beat {
  position: static;
  opacity: 1;
  transform: none;
  max-width: none;
  inset: auto;
}

.card-3d:not(.is-live) .beat--intro {
  grid-column: 1 / -1;
  text-align: center;
}

.card-3d:not(.is-live) .beat--side,
.card-3d:not(.is-live) .beat--2,
.card-3d:not(.is-live) .beat--4 { text-align: left; }

.card-3d:not(.is-live) .beat__index { justify-content: flex-start; }

.card-3d:not(.is-live) .beat--2 .beat__index,
.card-3d:not(.is-live) .beat--4 .beat__index { flex-direction: row-reverse; }

/* Short viewports: the beats are text-height, not viewport-height, so on a
   shallow window they need trimming rather than repositioning. */
@media (min-width: 901px) and (max-height: 780px) {
  .beat__lead { font-size: 17px; }
  .beat__copy { font-size: 15px; line-height: 1.6; }
  .beat__heading { font-size: clamp(28px, 2.4vw, 36px); }
  .beat__meta { margin-top: var(--space-16); }
}
