/* ProLine — Gate 2 cinematic layer.
   Everything here is additive and scoped to .cinema, which js/motion.js sets
   only on pointer-capable desktop widths with motion allowed. Without that
   class the page is the approved Gate 1 static build, unchanged. */

/* ============================ STAGE ============================ */
.stage { --p: 0; }

.cinema .stage { height: 260vh; }
.cinema .hero { position: sticky; top: 0; height: 100svh; min-height: 0; }

/* Normalised beat gates. One source of truth for the storyboard checkpoints:
   0.10 signal · 0.22 fragments · 0.36 foundations · 0.52 insight
   0.68 intelligence · 0.82 action · 0.94 handoff */
.stage {
  --g-signal:  clamp(0, calc((var(--p) - .06) / .12), 1);
  --g-frag:    clamp(0, calc((var(--p) - .14) / .12), 1);
  --g-cross:   clamp(0, calc((var(--p) - .44) / .12), 1);
  --g-core:    clamp(0, calc((var(--p) - .60) / .12), 1);
  --g-clarify: clamp(0, calc((var(--p) - .74) / .12), 1);
  --g-settle:  clamp(0, calc((var(--p) - .88) / .10), 1);
}

/* ============================ LEFT WORLD ============================ */
/* 0.10 — the signal starts inside the logo and draws toward the chart arrow. */
.cinema .hero__signal path {
  stroke-dasharray: 1;
  stroke-dashoffset: calc(1 - var(--g-signal));
}
.js .hero__signal path { stroke-dashoffset: 1; }
.js .cinema .hero__signal path { stroke-dashoffset: calc(1 - var(--g-signal)); }

/* 0.22 — fragments align toward the seam. Nodes stay compositionally stable:
   they close a short distance, they do not fly across the frame. */
.cinema .frag { transform: translate(calc(var(--dx) * var(--g-frag) * 1px), calc(var(--dy) * var(--g-frag) * 1px)); }
.cinema .frag-link { stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--g-frag)); }

/* Slow depth drift while the stage is on screen. Suspended off-screen by the
   observer in motion.js, and never present in reduced motion. */
.cinema .drift .hero__constellation { animation: drift 22s var(--e-out) infinite alternate; }
@keyframes drift { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-1.2%, .8%, 0); } }

/* ============================ SEAM AND CORE ============================ */
/* 0.52 — the signal crosses. The seam widens into an aperture, then settles. */
.cinema .hero__seam {
  width: calc(1px + var(--g-cross) * 1px);
  opacity: calc(.48 + var(--g-cross) * .42);
}
.hero__aperture { position: absolute; left: -45px; top: 50%; translate: 0 -50%; width: 90px; height: 120px; }
.cinema .hero__aperture { opacity: calc(var(--g-cross) * (1 - var(--g-settle) * .35)); }
.cinema .hero__aperture .cross { stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--g-cross)); }
.cinema .hero__aperture .head { transform: translateX(calc(var(--g-cross) * 90px)); }

/* 0.68 — the intelligence core strengthens on the seam itself: the aperture is
   the core, so nothing has to be parked on top of the copy. */
.cinema .core { opacity: var(--g-core); }
.cinema .core__edge { stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--g-core)); }

/* 0.82 — the light world clarifies. */
.cinema .hero__light { filter: brightness(calc(1 + var(--g-clarify) * .035)); }

/* ============================ BEAT STACK ============================ */
/* Default (no cinematic — reduced motion, touch, narrow, or no script): the
   four beats are a compact list in normal document flow, so the narrative is
   never locked behind animation.
   With the cinematic: they become a timed stack layered over the concept card,
   which hands over at 0.30. Both states occupy the same box, so nothing
   reflows when the mode changes. */
.hero__stack { position: relative; }

.beats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-1); margin-top: var(--s-2); }
.beat {
  display: grid; gap: 2px; align-content: center;
  padding: var(--s-1) var(--s-2); border-radius: var(--r-control);
  background: rgb(255 255 255 / 55%);
  box-shadow: inset 0 0 0 1px var(--line-light);
}
.beat .n { font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: .1em; color: var(--blue-500); }
.beat h3 { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-small); line-height: var(--lh-snug); color: var(--text-dark); }
.beat p { display: none; font-size: var(--fs-small); color: var(--text-dim); }
.beat .pv { display: none; }

.cinema .hero__card { opacity: calc(1 - clamp(0, calc((var(--p) - .26) / .08), 1)); }
.cinema .beats { display: block; position: absolute; inset: 0; margin: 0; }
.cinema .beat {
  position: absolute; inset: 0;
  gap: var(--s-1); padding: var(--s-3); border-radius: var(--r-card);
  background: rgb(255 255 255 / 70%);
  box-shadow: inset 0 0 0 1px var(--line-light), var(--depth-1);
  opacity: var(--o, 0);
  translate: 0 calc((1 - var(--o, 0)) * 10px);
}
.cinema .beat h3 { font-size: var(--fs-h3); }
.cinema .beat p, .cinema .beat .pv { display: block; }
.cinema .beat .pv { display: inline; }

/* Each beat holds until the next one takes over; the last one holds to rest. */
.cinema .beat-1 { --o: calc(clamp(0, calc((var(--p) - .30) / .06), 1) - clamp(0, calc((var(--p) - .46) / .06), 1)); }
.cinema .beat-2 { --o: calc(clamp(0, calc((var(--p) - .46) / .06), 1) - clamp(0, calc((var(--p) - .62) / .06), 1)); }
.cinema .beat-3 { --o: calc(clamp(0, calc((var(--p) - .62) / .06), 1) - clamp(0, calc((var(--p) - .76) / .03), 1)); }
.cinema .beat-4 { --o: clamp(0, calc((var(--p) - .79) / .03), 1); }

/* 0.94 — the next-section cue. CTAs never move or fade. */
.hero__cue { display: none; }
.cinema .hero__cue {
  display: flex; align-items: center; gap: var(--s-1);
  margin-top: var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: var(--tr-label);
  text-transform: uppercase; color: var(--text-dim);
  opacity: var(--g-settle);
}
.hero__cue i { width: 22px; height: 1px; background: currentColor; }

/* ============================ LOADER ============================ */
.loader {
  --lp: 0;
  position: fixed; inset: 0; z-index: 100;
  contain: paint;
  /* no background here: .loader__cover already paints the field, and painting
     the same three-layer gradient twice is pure cost at load */
}
.loader[hidden] { display: none; }

/* The loader field already is the hero's dark world, so the only thing that has
   to be revealed is the light world. The cover's right edge retracts to the
   seam — the aperture opening — and the remaining left half then fades against
   an identical background, which reads as a hold rather than a dissolve. */
.loader__cover { position: absolute; inset: 0; background: var(--field-dark); clip-path: inset(0 0 0 0); }
.loader.is-out { pointer-events: none; opacity: 0; transition: opacity 220ms linear 480ms; }
.loader.is-out .loader__cover { clip-path: inset(0 calc(100% - var(--seam)) 0 0); transition: clip-path 520ms var(--e-signal); }
.loader.is-out .loader__field, .loader.is-out .loader__status { opacity: 0; transition: opacity 200ms linear; }

/* The loader's grid matches the hero's, so its logo occupies the hero logo box
   and the hand-off is a hold, not a jump. */
.loader__stage { position: absolute; inset: 0; }
/* left/top/width are written by motion.js from the hero logo's measured box */
.loader__grid { position: absolute; }
.loader__logo { opacity: clamp(0, calc((var(--lp) - .50) / .22), 1); }
.loader__seam {
  position: absolute; inset-block: 6%; left: var(--seam); width: 2px;
  background: linear-gradient(180deg, transparent, var(--cyan-400) 28%, var(--cyan-400) 72%, transparent);
  box-shadow: 0 0 24px 2px rgb(34 213 245 / 45%);
  opacity: clamp(0, calc((var(--lp) - .74) / .20), 1);
  scale: 1 clamp(0, calc((var(--lp) - .74) / .20), 1);
}
.loader__field { position: absolute; inset: 0; width: 100%; height: 100%; }
.loader__field .mark { opacity: calc(clamp(0, calc(var(--lp) / .12), 1) * .55); }
.loader__field .dot { transform: translate(calc(var(--dx) * (1 - clamp(0, calc((var(--lp) - .12) / .24), 1)) * 1px), calc(var(--dy) * (1 - clamp(0, calc((var(--lp) - .12) / .24), 1)) * 1px)); }
.loader__field .bar { opacity: clamp(0, calc((var(--lp) - .20) / .16), 1); }
.loader__field .link { stroke-dasharray: 1; stroke-dashoffset: calc(1 - clamp(0, calc((var(--lp) - .29) / .28), 1)); }
.loader__status {
  margin-top: var(--s-4);
  font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: var(--tr-label);
  text-transform: uppercase; color: var(--text-muted);
  opacity: clamp(0, calc((var(--lp) - .16) / .2), 1);
}

/* ============================ REDUCED MOTION ============================ */
/* The complete static composition, immediately. No pinning, no travel, no
   line-drawing dependency, and every beat present in normal document flow. */
@media (prefers-reduced-motion: reduce) {
  .stage, .cinema .stage { height: auto; }
  .cinema .hero { position: static; height: auto; min-height: 100svh; }
  .cinema .hero__signal path, .js .hero__signal path { stroke-dashoffset: 0; }
  .cinema .frag { transform: none; }
  .cinema .frag-link, .loader__field .link { stroke-dashoffset: 0; }
  .cinema .hero__aperture, .cinema .core { opacity: 1; }
  .cinema .hero__card { opacity: 1; }
  .cinema .hero__light { filter: none; }
  .cinema .hero__constellation { animation: none; }

  /* The timed stack collapses back to the compact document-flow list. */
  .cinema .beats { position: static; display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-1); margin-top: var(--s-2); }
  .cinema .beat { position: static; inset: auto; opacity: 1; translate: none; padding: var(--s-1) var(--s-2); border-radius: var(--r-control); box-shadow: inset 0 0 0 1px var(--line-light); }
  .cinema .beat h3 { font-size: var(--fs-small); }
  .cinema .beat p, .cinema .beat .pv { display: none; }
  .cinema .hero__cue { opacity: 1; }
  .loader { display: none; }
}
