/* ============================================================================
   NOVA · GL STAGE — the layer
   ----------------------------------------------------------------------------
   Where the stage sits is decided HERE and nowhere in the JS. Give the
   container `position: fixed; inset: 0` and it is a full-bleed layer; give it a
   column in the hero and it is contained. That separation is what lets one
   scene be a page background on one page and a panel on another.
   ========================================================================= */

.gl-stage {
  position: relative;
  display: block;
  width: 100%;
  /* No intrinsic height — the container decides. */
  background: var(--bg-base, #08090A);
  overflow: hidden;
}
.gl-stage[data-theme-gl="light"] { background: #FCFCFD; }
.gl-stage[data-theme-gl="warm"]  { background: #F0EDE6; }

.gl-stage__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* A visual, never an obstacle — it must not eat a click or a swipe even when
     it covers the viewport. */
  pointer-events: none;
}

/* ---- Fallback ----------------------------------------------------------
   No WebGL2, or a lost context. The composition still has to hold, so CSS
   paints the same picture: a dark slab at a slight angle with a contact
   shadow. Flat, but the same photograph rather than an empty rectangle. */
.gl-stage::before {
  content: "";
  position: absolute;
  left: 58%; top: 50%;
  width: min(46vh, 38%);
  aspect-ratio: 1.59 / 1;
  transform: translate(-50%, -50%) perspective(1200px) rotateY(-19deg) rotateX(6deg);
  background: #15191E;
  border-radius: 22px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.30),
    inset 0 0 0 1px rgba(255,255,255,.09),
    0 26px 50px rgba(0,0,0,.60),
    0 70px 90px rgba(0,0,0,.38);
  opacity: 1;
  transition: opacity 500ms linear;
  pointer-events: none;
}
.gl-stage[data-stage="webgl"]::before { opacity: 0; }

/* ---- Lab harness ------------------------------------------------------- */
.gs-lab { margin: 0; min-height: 100dvh; background: #08090A; color: #F2F3F5;
  font: 400 15px/1.55 var(--font-sans, -apple-system, BlinkMacSystemFont,
        'Segoe UI', Helvetica, Arial, sans-serif); -webkit-font-smoothing: antialiased; }

/* CONTAINED, not full-bleed: the stage is a column in the hero, which is the
   composition the references use — type holds one side, object the other. */
.gs-hero { position: relative; min-height: 100dvh; display: grid; align-items: center;
  grid-template-columns: 1fr; }
@media (min-width: 900px) { .gs-hero { grid-template-columns: 1fr 1.15fr; } }
.gs-copy { padding: 0 clamp(20px, 5vw, 72px); z-index: 2; }
.gs-copy h1 { margin: 0; font-size: clamp(34px, 5vw, 72px); line-height: 1.0;
  letter-spacing: -0.038em; font-weight: 620; }
.gs-copy p { margin: 16px 0 0; max-width: 30ch; color: #9BA1A9; }
.gs-slot { position: relative; min-height: 56vh; }
@media (min-width: 900px) { .gs-slot { min-height: 78vh; } }
.gs-slot .gl-stage { position: absolute; inset: 0; }

/* a long track below, so scroll-driven progress can be exercised for real */
.gs-track { height: 220vh; position: relative; }
.gs-track span { position: sticky; top: 50%; display: block; text-align: center;
  color: #6B7178; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; }

.gs-panel { position: fixed; right: 14px; bottom: 14px; z-index: 10;
  display: grid; gap: 9px; padding: 14px 16px; min-width: 252px;
  background: rgba(10,12,16,.88); border: 1px solid rgba(255,255,255,.14);
  border-radius: 12px; font-size: 12px; color: #E8EDF3; }
.gs-panel h2 { margin: 0 0 2px; font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: #7E8A97; font-weight: 600; }
.gs-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.gs-row label { color: #B6C0CB; }
.gs-row input[type="range"] { width: 116px; }
.gs-row select, .gs-row button { font: inherit; color: #E8EDF3; background: #171B21;
  border: 1px solid rgba(255,255,255,.16); border-radius: 7px; padding: 4px 9px;
  min-height: 26px; cursor: pointer; }
.gs-stat { margin-top: 3px; padding-top: 9px; border-top: 1px solid rgba(255,255,255,.10);
  color: #7E8A97; font-variant-numeric: tabular-nums; display: grid; gap: 3px; }
.gs-stat b { color: #CFE2F2; font-weight: 600; }


/* ============================================================================
   CONTENT ABOVE THE STAGE — load-bearing, do not remove
   ----------------------------------------------------------------------------
   A full-bleed stage is `position: fixed; z-index: 0`. Page content is NOT
   positioned by default, so it paints in the normal flow — BELOW any positioned
   z-index:0 element. Without this rule the canvas covers the copy: the text is
   present, correctly coloured and at full opacity, and simply invisible.

   This rule used to live in plane.css and was lost the moment that stylesheet
   was unlinked along with the dart section, which took the hero headline down
   with it. It is a requirement of the STAGE, so it belongs with the stage.

   The other half of the same problem is in primitives.css: the page colour has
   to sit on <html>, because an opaque <body> paints above body's own fixed
   children. Both are needed; neither is sufficient alone.

   The nav (z-index 40) and the sticky CTA (45) already clear the stage.
   ========================================================================= */
main, .footer { position: relative; z-index: 1; }

/* ---- Full-bleed mode --------------------------------------------------
   A stage fixed to the viewport, behind every bit of page content. This is how
   an overlay scene like the dart runs: the canvas is transparent and the page
   shows through it.

   Two things make it safe, and both are load-bearing elsewhere in the CSS:
     · the page colour lives on <html>, not <body> — an opaque <body> paints
       ABOVE body's own fixed children, which made an earlier full-bleed layer
       invisible while being perfectly positioned and drawn
     · `main, .footer { position: relative; z-index: 1 }` keeps content above it
   Both already exist in primitives.css and plane.css respectively. */
.gl-stage--bleed {
  position: fixed;
  inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  background: none;          /* an overlay has no ground of its own */
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-slow, 420ms) var(--ease-out, ease);
}
.gl-stage--bleed[data-stage="webgl"] { opacity: 1; }
/* The bleed stage is an overlay, so it must never paint the slab fallback. */
.gl-stage--bleed::before { content: none; }

@media (prefers-reduced-motion: reduce) {
  .gl-stage--bleed { transition: none; }
}

/* ============================================================================
   THE STANDARD · the slab's section on the homepage
   ----------------------------------------------------------------------------
   This section is the scene's TRACK — its position drives the slab's progress —
   so it needs real height for the turn to have room. The copy holds the left
   and the object holds the right, which is the composition the references use.

   There is no element for the object: it is drawn by the full-bleed stage
   BEHIND this content. The right column exists only to reserve the space, which
   is why it is empty and aria-hidden.
   ========================================================================= */
.slabsec { position: relative; }
.slabsec__inner { display: grid; gap: var(--s-8); align-items: center; }
@media (min-width: 900px) {
  .slabsec__inner { grid-template-columns: 1fr 1.1fr; gap: var(--s-16); }
}
.slabsec__copy { position: relative; z-index: 1; }
/* Height, not content. Tall enough that the slab's three beats can play out
   over a comfortable scroll rather than snapping past. */
.slabsec__slot { min-height: 52vh; }
@media (min-width: 900px) { .slabsec__slot { min-height: 72vh; } }

@media (max-width: 899px) {
  /* Stacked: the copy first, then the object's space below it, so a white
     headline never sits on top of a lit object on a narrow screen. */
  .slabsec__slot { min-height: 58vh; }
}

@media (prefers-reduced-motion: reduce) {
  /* The stage renders a single composed frame under reduced motion, so the
     runway can be shorter — there is no turn to play out. */
  .slabsec__slot { min-height: 40vh; }
}

/* ============================================================================
   THE HERO FALLBACK
   ----------------------------------------------------------------------------
   The slab is the hero object, and the stage is deferred to idle-after-paint so
   it cannot delay the headline. That leaves a window — short on a fast
   connection, permanent if WebGL never runs — where the hero's right column
   would otherwise be empty.

   So CSS paints the slab: squircle silhouette, an inset edge highlight standing
   in for the chamfer, and a contact shadow. Flat, but the same composition. It
   stands down only when <html> gains .gl-live, which chrome.js sets after the
   stage reports mode === 'webgl' — never on the mere intention to load it.
   ========================================================================= */
.hero__fallback {
  position: relative;
  justify-self: center;
  width: min(100%, 440px);
  aspect-ratio: 1.25 / 1;
  /* A suggestion of mass, not a shape. The real object is an assembly of
     curved vanes; drawing a card here would promise a silhouette that never
     arrives and then visibly swap. */
  background: none;
  opacity: 1;
  transition: opacity 520ms linear;
}
html.gl-live .hero__fallback { opacity: 0; }

@media (max-width: 899px) {
  /* Stacked layout: keep it, but shorter, so it never pushes the CTA under the
     fold on a phone. */
  .hero__fallback { width: min(100%, 380px); aspect-ratio: 1.7 / 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero__fallback { transition: none; }
}

/* ---- The close ---------------------------------------------------------
   The last section before the footer. Deliberately sparse: one action, and the
   reassurance as text rather than a second button competing with it. */
.close__cta {
  margin-top: var(--s-8);
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3);
}


/* ============================================================================
   THE NARRATIVE BEATS
   ----------------------------------------------------------------------------
   Almost empty on purpose. The story is told by the object transforming; these
   exist to give it SCROLL DISTANCE and to say one short thing each.

   Type sits left and stays narrow, so the object keeps the right-hand negative
   space and can pass behind the line ends without ever fighting the text. The
   lines are set at reading size rather than display size — a beat is a caption
   for the object, not a headline competing with it.
   ========================================================================= */
.formtrack { position: relative; }

.beat {
  min-height: 92vh;
  display: grid;
  align-items: center;
}
@media (min-width: 900px) { .beat { min-height: 100vh; } }

.beat__line {
  max-width: 15ch;
  font-size: clamp(21px, 2.5vw, 30px);
  line-height: 1.32;
  letter-spacing: -0.015em;
  font-weight: 560;
  color: var(--text-primary);
  /* Nothing behind the text but the page. A panel here would be exactly the
     "glass cards everywhere" the direction rules out. */
}

.beat--close { min-height: 88vh; }
.beat__head { max-width: 14ch; }
.beat__cta {
  margin-top: var(--s-8);
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3);
}

@media (prefers-reduced-motion: reduce) {
  /* The stage renders one composed frame, so the runway has no morph to play
     out and the beats only need room to be read. */
  .beat { min-height: 62vh; }
}
