/* =========================================================================
   xSERVA — cinematic layer
   =========================================================================

   The pinned scrollytelling stage, the holographic panel, and the simulated
   telemetry readouts. Loaded only by pages that use them.

   ONE STRUCTURAL CHANGE FROM THE PREVIOUS BUILD, and it matters. The stage
   used to be pinned with `position: sticky`, which silently dies if ANY
   ancestor sets an overflow other than visible — the single most common way
   this effect ships broken, and the reason the old file carried a warning
   comment about it. ScrollTrigger's `pin` instead swaps the element to
   position:fixed against a spacer it inserts and manages itself, so the
   sticky-ancestor trap no longer applies. Nothing here may set `position` on
   .cine-sticky: GSAP owns that property, and a CSS value fights the pin.
   ========================================================================= */

/* ---------------------------------------------------------- the stage ---- */
.cine { position: relative; padding-block: 0; }
.cine-track { position: relative; }

.cine-sticky {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;          /* contains the glows; safe now that pin != sticky */
}

.cine-inner { position: relative; z-index: 2; width: 100%; }

/* The ambient glows behind the stage. Absolutely positioned, blurred, and
   never animated — a 32rem blurred radial repainting on every scroll frame is
   the most expensive thing on the page and reads as nothing. */
.cine-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  pointer-events: none;
  z-index: 0;
}

.cine-grid {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}
@media (min-width: 768px) {
  .cine-grid { grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); }
}

/* ------------------------------------------------------------- the rail -- */
/* The list of beats, with a progress line running down it. The line is drawn
   as two stacked elements rather than as a gradient so GSAP can scale the fill
   on the Y axis — scaling is compositor work, animating a gradient stop is
   not. */
.cine-rail {
  position: relative;
  list-style: none;
  margin: 0;
  padding-left: var(--space-4);
}
.cine-rail-track {
  position: absolute;
  left: 7px;
  top: 6px;
  bottom: 6px;
  width: 2px;
  border-radius: 2px;
  background: var(--glass-border);
  overflow: hidden;
}
.cine-rail-fill {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--accent), var(--accent-violet));
  transform: scaleY(0);
  transform-origin: 50% 0;
}

.cine-step {
  position: relative;
  padding-block: var(--space-2);
  opacity: 0.42;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.cine-step.lit { opacity: 1; }

/* The node on the rail. Its filled state is the only place on this site where
   a change of colour is the whole signal, so it also changes size — a reader
   who cannot separate cyan from grey still sees which beats are behind them. */
.cine-step::before {
  content: '';
  position: absolute;
  left: calc(var(--space-4) * -1 + 2px);
  top: calc(var(--space-2) + 0.55em);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--surface-1);
  border: 2px solid var(--glass-border-2);
  transition: border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.cine-step.lit::before {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.18);
  box-shadow: 0 0 14px rgba(79, 209, 255, 0.6);
}

.cine-step h3 { font-size: var(--step-1); margin-bottom: 4px; }
.cine-step p { font-size: 0.94rem; max-width: 46ch; }

.cine-eyebrow {
  display: block;
  font-size: 0.7rem;
  font-weight: 620;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 4px;
}

/* ------------------------------------------------------------ the scenes -- */
/* All scenes occupy one grid cell so they cross-fade in place instead of the
   column growing and shrinking to whichever is tallest. */
.cine-stage { position: relative; display: grid; }
.cine-scene { grid-area: 1 / 1; }
.cine-card { width: 100%; }

/* Stacked fallback: below 768px, and under reduced motion, every scene is in
   normal flow and visible. motion.js applies .active to all of them in that
   case, so this is the state the section rests in when nothing is driving it. */
@media (max-width: 767px) {
  .cine-stage { display: block; }
  .cine-scene { margin-bottom: var(--space-3); }
  .cine-sticky { min-height: 0; display: block; padding-block: var(--space-5); }
}
/* Reduced mode never builds the pin, so the stage stays in normal flow with
   every scene readable. Attribute-keyed so the Motion control can turn the
   pinned version back on; the media query below covers the no-JS case. */
:root[data-motion="reduced"] .cine-stage { display: block; }
:root[data-motion="reduced"] .cine-scene { margin-bottom: var(--space-3); opacity: 1 !important; visibility: visible !important; transform: none !important; }
:root[data-motion="reduced"] .cine-sticky { min-height: 0; display: block; padding-block: var(--space-5); }
:root[data-motion="reduced"] .cine-step { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) .cine-stage { display: block; }
  :root:not([data-motion="full"]) .cine-scene { margin-bottom: var(--space-3); opacity: 1 !important; visibility: visible !important; transform: none !important; }
  :root:not([data-motion="full"]) .cine-sticky { min-height: 0; display: block; padding-block: var(--space-5); }
  :root:not([data-motion="full"]) .cine-step { opacity: 1; }
}

/* ------------------------------------------------------------- the holo -- */
/* The panel the scenes are drawn on. A card that has to read as an instrument
   rather than as a marketing tile: darker than the page, a bright top rim, and
   a fine inner grid that stops well short of the text. */
.holo {
  position: relative;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.012)),
    var(--surface-1);
  border: 1px solid var(--glass-border-2);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  isolation: isolate;
}
.holo::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(79, 209, 255, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(79, 209, 255, 0.05) 1px, transparent 1px);
  background-size: 28px 28px;
  mask-image: radial-gradient(ellipse 70% 70% at 70% 0%, #000, transparent 70%);
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at 70% 0%, #000, transparent 70%);
}
.holo > * { position: relative; z-index: 1; }

/* The sheen. One slow pass, and only on panels that opt in — every panel doing
   this at once is a disco, not an instrument. */
.holo-sheen::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, 0.07) 48%, transparent 58%);
  transform: translateX(-100%);
  animation: holo-sweep 7s ease-in-out infinite;
}
@keyframes holo-sweep {
  0%, 62% { transform: translateX(-100%); }
  86%, 100% { transform: translateX(100%); }
}
/* The sheen sweeps a highlight across the panel — travel, and on a loop. */
:root[data-motion="reduced"] .holo-sheen::after { animation: none; opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) .holo-sheen::after { animation: none; opacity: 0; }
}

.holo pre {
  margin: 10px 0 0;
  font: 400 0.82rem/1.7 var(--font-mono);
  color: var(--text-secondary);
  white-space: pre-wrap;
}

/* --------------------------------------------------------- telemetry ----- */
.telemetry {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: var(--space-2);
}
.tele-cell {
  position: relative;
  background: var(--surface-inset);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-md);
  padding: 12px 14px 0;
  overflow: hidden;
}
.tele-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.72rem;
  font-weight: 560;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.tele-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--state-ok);
  box-shadow: 0 0 8px var(--state-ok);
  flex: none;
}
.tele-dot[data-state="warn"] { background: var(--state-warn); box-shadow: 0 0 8px var(--state-warn); }
.tele-dot[data-state="deny"] { background: var(--state-deny); box-shadow: 0 0 8px var(--state-deny); }

.tele-value {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 1.5rem;
  font-weight: 620;
  letter-spacing: -0.03em;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.tele-unit { font-size: 0.8rem; font-weight: 400; color: var(--text-muted); letter-spacing: 0; }

.tele-spark { display: block; width: 100%; height: 26px; margin-top: 8px; }
.tele-line { fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
/* A flat tinted fill, not a url(#gradient): the sparkline SVGs are inlined by
   a PHP helper on several pages and none of them carries a <defs>, so a
   gradient reference would resolve to nothing and the area under the line
   would simply vanish. */
.tele-fill { fill: var(--accent); opacity: 0.14; stroke: none; }

/* The honesty line under every strip. Deliberately not styled down to
   invisibility: it says these numbers are a simulation, and if it is too quiet
   to read it is not doing its job. */
.tele-note {
  font-size: 0.76rem;
  color: var(--text-muted);
  margin-top: var(--space-2);
  font-style: italic;
  max-width: none;
}

/* ------------------------------------------------------ directional in --- */
/* Used by pages that want an entrance from a side rather than from below.
   GSAP reads the class and picks the axis; these only need to exist so the
   resting state is correct when motion never runs. */
.reveal-l, .reveal-r, .reveal-scale { will-change: transform; }
