/* ============================================================================
   STARFALL TERMINAL — terminal-anim.css
   ----------------------------------------------------------------------------
   Layer 3: motion. Loaded last, purely additive — nothing here changes layout,
   everything animates only transform / opacity / filter / background-position,
   and every effect has a reduced-motion and a lite-device off-switch.

   01. Keyframes
   02. Deep field        aurora orbs + star dust
   03. Type motion       shimmer, per-word hero entrance, kicker draw-in
   04. Reveal system     stagger + directional variants
   05. Card life         pointer spotlight, lift, border sweep
   06. Buttons           sheen sweep, pulse ring
   07. Marquee bands     kinetic separators
   08. Tiers / signals   rotating conic border, entry pulse, price flash
   09. Chrome            header clock, progress sweep, scroll parallax
   10. Guards
   ========================================================================== */

/* ==========================================================================
   01. KEYFRAMES
   ========================================================================== */
@keyframes animOrb {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  33%  { transform: translate3d(4vw, -3vh, 0) scale(1.12); }
  66%  { transform: translate3d(-3vw, 2vh, 0) scale(0.95); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}
@keyframes animTwinkle {
  0%, 100% { opacity: 0.12; transform: scale(0.7); }
  50%      { opacity: 0.85; transform: scale(1.15); }
}
@keyframes animShimmer {
  to { background-position: 220% 50%; }
}
@keyframes animDraw {
  from { transform: scaleX(0); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}
@keyframes animRise {
  from { opacity: 0; transform: translateY(0.7em) rotate(1.4deg); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: none; }
}
@keyframes animSheen {
  from { transform: translateX(-120%) skewX(-18deg); }
  to   { transform: translateX(260%) skewX(-18deg); }
}
@keyframes animBand {
  to { transform: translate3d(-50%, 0, 0); }
}
@keyframes animSpinAng {
  to { --ang: 360deg; }
}
@keyframes animCorner {
  0%, 100% { transform: scale(1); opacity: 0.85; }
  50%      { transform: scale(1.14); opacity: 1; }
}
@keyframes animWipe {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
@keyframes animPing {
  0%   { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.55); }
  70%  { box-shadow: 0 0 0 14px rgba(var(--accent-rgb), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0); }
}
@keyframes animFlashUp {
  0%   { background: rgba(var(--up-rgb), 0.28); }
  100% { background: transparent; }
}
@keyframes animFlashDown {
  0%   { background: rgba(var(--down-rgb), 0.28); }
  100% { background: transparent; }
}
@keyframes animBreathe {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* ==========================================================================
   02. DEEP FIELD
   --------------------------------------------------------------------------
   Three blurred aurora lobes drift behind the grid, and a star field twinkles.
   Transform-only drift keeps this off the main thread; the blur is skipped on
   weak devices (see guards) because it is the expensive part.
   ========================================================================== */
.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.5;
  will-change: transform;
  animation: animOrb linear infinite;
  animation-duration: 34s;
}
.orb--a {
  width: 46vw; height: 46vw;
  left: -8vw; top: -6vh;
  background: radial-gradient(circle at 35% 35%, rgba(var(--accent-rgb), 0.5), transparent 68%);
}
.orb--b {
  width: 40vw; height: 40vw;
  right: -6vw; top: 24vh;
  background: radial-gradient(circle at 60% 40%, rgba(56, 189, 248, 0.34), transparent 70%);
  animation-duration: 46s;
  animation-direction: reverse;
}
.orb--c {
  width: 52vw; height: 52vw;
  left: 26vw; bottom: -14vh;
  background: radial-gradient(circle at 50% 50%, rgba(139, 92, 246, 0.3), transparent 72%);
  animation-duration: 58s;
}

.dust { position: absolute; inset: 0; }
.dust i {
  position: absolute;
  top: var(--y);
  left: var(--x);
  width: var(--s);
  height: var(--s);
  border-radius: 50%;
  background: #fff;
  opacity: 0.2;
  animation: animTwinkle var(--dur) var(--ease) infinite;
  animation-delay: var(--delay);
  will-change: opacity, transform;
}

/* ==========================================================================
   03. TYPE MOTION
   ========================================================================== */
.grad-text {
  background-size: 220% 100% !important;
  animation: animShimmer 7s linear infinite;
}

/* words of the hero headline land one after another when the page is ready */
body.anim-ready .hero__title .anim-word,
.anim-ready .hero__title .anim-word {
  animation: animRise 0.85s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 90ms + 120ms);
}
.hero__title .anim-word { display: inline-block; will-change: transform, opacity; }

/* section kickers underline themselves as they arrive */
.sec__kicker i {
  transform-origin: left center;
}
.sec__kicker.is-in i,
.rv.is-in .sec__kicker i {
  animation: animDraw 0.7s var(--ease) both;
}

/* ==========================================================================
   04. REVEAL SYSTEM
   ========================================================================== */
.rv { transition-delay: var(--rv-delay, 0ms); }

.rv--left  { transform: translateX(-38px); }
.rv--right { transform: translateX(38px); }
.rv--zoom  { transform: scale(0.94); }
.rv--up    { transform: translateY(46px); }

/* ==========================================================================
   05. CARD LIFE
   --------------------------------------------------------------------------
   Every card grows a pointer spotlight and a gold edge that lights up on
   hover. term-anim.js writes --mx / --my / --ang while the pointer is inside.
   ========================================================================== */
.panel,
.bento__card,
.tier,
.say,
.kpi,
.mkt,
.sigside > *,
.partner > *,
.faq__item {
  position: relative;
  transition: transform 0.42s var(--ease), border-color 0.42s var(--ease),
              box-shadow 0.42s var(--ease);
}
.panel:hover,
.bento__card:hover,
.kpi:hover,
.say:hover {
  transform: translateY(-4px);
  border-color: var(--line-gold);
  box-shadow: var(--shadow-2), 0 26px 60px -34px rgba(var(--accent-rgb), 0.85);
}

/* the spotlight is one pseudo-element per card, driven by two CSS vars.
   `.bento__card::after` already owns a corner glow in terminal.css, so the
   spotlight uses ::before there instead. */
.panel::after,
.tier::after,
.say::after,
.kpi::after,
.bento__card::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  background: radial-gradient(
    220px circle at var(--mx, 50%) var(--my, 0%),
    rgba(var(--accent-rgb), 0.16),
    transparent 62%
  );
}
.panel:hover::after,
.tier:hover::after,
.say:hover::after,
.kpi:hover::after,
.bento__card:hover::before { opacity: 1; }

/* kept above the spotlight so card content stays readable. The seat badges are
   excluded: they are pills pinned to the top-right corner of the card, and this
   rule used to win on specificity (.tier > * beats .tier__badge), so the pill
   came back into the flow as a full-width bar that overlapped the seat name
   underneath it. */
.panel > *,
.tier > *:not(.tier__badge, .plan__badge),
.say > *,
.kpi > *,
.bento__card > * { position: relative; z-index: 1; }

/* an edge that lights up from wherever the pointer entered */
.panel::before,
.kpi::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s var(--ease);
  padding: 1px;
  background: conic-gradient(
    from var(--ang, 0deg),
    transparent 0deg,
    rgba(var(--accent-rgb), 0.8) 42deg,
    transparent 100deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
.panel:hover::before,
.kpi:hover::before { opacity: 0.9; }

/* ==========================================================================
   06. BUTTONS
   ========================================================================== */
.btn { position: relative; overflow: hidden; }
.btn::after {
  content: '';
  position: absolute;
  top: -60%;
  bottom: -60%;
  width: 42%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.32), transparent);
  transform: translateX(-140%) skewX(-18deg);
  pointer-events: none;
  opacity: 0;
}
.btn:hover::after,
.btn:focus-visible::after {
  opacity: 1;
  animation: animSheen 0.85s var(--ease);
}
.btn--gold { animation: animBreathe 4.4s var(--ease) infinite; }

/* ==========================================================================
   07. MARQUEE BANDS
   --------------------------------------------------------------------------
   Kinetic separators between sections. The track is doubled by term-anim.js so
   the -50% keyframe loops seamlessly, exactly like the tape used to.
   ========================================================================== */
.band {
  position: relative;
  overflow: hidden;
  padding: 14px 0;
  border-block: 1px solid var(--line);
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.5), rgba(var(--accent-rgb), 0.05), rgba(0, 0, 0, 0.5));
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.band__track {
  display: flex;
  align-items: center;
  width: max-content;
  animation: animBand 34s linear infinite;
  will-change: transform;
}
.band--rev .band__track { animation-direction: reverse; }
.band:hover .band__track { animation-play-state: paused; }
/* two identical copies: the -50% keyframe lands exactly on the second one */
.band__dup { display: inline-flex; align-items: center; }
.band__item {
  display: inline-flex;
  align-items: center;
  gap: 26px;
  padding-right: 26px;
  font: 800 clamp(22px, 4.4vw, 54px)/1 var(--display);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.16);
}
.band__item b {
  font-weight: 800;
  color: var(--accent);
  -webkit-text-stroke: 0;
  text-shadow: 0 0 26px rgba(var(--accent-rgb), 0.5);
}
.band__item s {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  text-decoration: none;
  box-shadow: 0 0 12px var(--accent);
}

/* ==========================================================================
   08. TIERS / SIGNALS
   ========================================================================== */
/* the "most taken" tier gets a slow rotating border */
.tier--hot {
  position: relative;
  overflow: hidden;
}
.tier--hot::before {
  content: '';
  position: absolute;
  inset: -40%;
  z-index: 0;
  background: conic-gradient(from var(--ang, 0deg), transparent 0deg, rgba(var(--accent-rgb), 0.5) 60deg, transparent 140deg);
  animation: animSpinAng 6s linear infinite;
  opacity: 0.5;
}
.tier--hot > *:not(.tier__badge, .plan__badge) { position: relative; z-index: 1; }

/* a new signal lands with a pulse instead of appearing */
.sig.is-new { animation: animPing 1.1s var(--ease); }

/* price cells flash brighter on a live update */
.quote__px.flash-up, .mrow__px.flash-up { animation: animFlashUp 0.9s ease-out; }
.quote__px.flash-down, .mrow__px.flash-down { animation: animFlashDown 0.9s ease-out; }

/* the tier card corner glow breathes so the bento grid always has something
   moving in it, even before the pointer arrives */
.bento__card::after { animation: animCorner 6.5s var(--ease) infinite; }
.bento__card:nth-child(even)::after { animation-delay: 2.2s; }
.bento__card:nth-child(3n)::after { animation-delay: 4.1s; }

/* a one-shot wipe across the top edge of each terminal panel on reveal */
.panel .anim-wipe {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  overflow: hidden;
  pointer-events: none;
}
.panel .anim-wipe i {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.95), transparent);
  animation: animWipe 1.4s var(--ease) 1 both;
}

/* ==========================================================================
   09. CHROME
   ========================================================================== */
.chip--clock {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.14em;
  color: var(--ink-2);
}
.chip--clock i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: animBreathe 2.2s var(--ease) infinite;
}

.head__prog i {
  background: linear-gradient(90deg, var(--accent), var(--accent-2), #fff, var(--accent));
  background-size: 300% 100%;
  animation: animShimmer 3.4s linear infinite;
}

/* the hero HUD chips get one extra layer of very slow float */
.floater { animation-duration: 9s; }

/* section titles ease sideways as the page scrolls (set by term-anim.js) */
.sec__title { transform: translate3d(0, calc(var(--par, 0) * -1px), 0); }
.has-par .sec__title { will-change: transform; }

/* the pointer spotlight that sits above the background shell */
.spot {
  position: fixed;
  z-index: 3;
  width: 520px;
  height: 520px;
  margin: -260px 0 0 -260px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  background: radial-gradient(circle, rgba(var(--accent-rgb), 0.09), transparent 62%);
  will-change: transform;
}
body.spot-on .spot { opacity: 1; }

/* ==========================================================================
   10. GUARDS
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .orb,
  .dust i,
  .grad-text,
  .btn--gold,
  .btn::after,
  .panel::before,
  .tier--hot::before,
  .sig.is-new,
  .chip--clock i,
  .head__prog i,
  .band__track,
  .panel .anim-wipe i,
  .bento__card::after,
  .hero__title .anim-word {
    animation: none !important;
  }
  .rv, .rv--left, .rv--right, .rv--zoom, .rv--up { transition: none !important; }
  .hero__title .anim-word { opacity: 1 !important; filter: none !important; transform: none !important; }
  .spot { display: none; }
  .band__track { transform: translate3d(0, 0, 0) !important; }
}

/* phones: a smaller blur is a much cheaper blur */
@media (max-width: 700px) {
  .orb { filter: blur(46px); opacity: 0.42; }
  .band { padding: 10px 0; }
}

/* weak devices: no blur, fewer moving pixels */
body.is-lite .orb { display: none; }
body.is-lite .dust i:nth-child(n + 13) { display: none; }
body.is-lite .spot { display: none; }
body.is-lite .band__track { animation-duration: 60s; }
body.is-lite .tier--hot::before { display: none; }

/* touch devices have no pointer to follow */
@media (hover: none) {
  .spot { display: none; }
}

/* ------------------------------------------------------------------------
   Navigation fade: a quick curtain instead of a hard page swap.
   ---------------------------------------------------------------------- */
body.is-leaving {
  opacity: 0.25;
  filter: blur(3px);
  transform: scale(0.994);
  transition: opacity 0.19s var(--ease), filter 0.19s var(--ease), transform 0.19s var(--ease);
  pointer-events: none;
}

/* the header clock is the first thing to go when space runs out */
@media (max-width: 1120px) {
  .chip--clock { display: none; }
}

@media print {
  .band,
  .spot,
  .orb,
  .dust { display: none !important; }
}

/* ==========================================================================
   11. INTRO CHOREOGRAPHY (v11)
   --------------------------------------------------------------------------
   The boot screen hands the page over by adding `is-booted` to <body>
   (js/term-ui.js). Everything below hangs off that class, so the intro cannot
   run behind the loader and cannot replay while the visitor scrolls: the
   header lands first, the hero column follows word by word, and the terminal
   rises last with the artwork fading up underneath.
   ========================================================================== */
body.is-booted .head { animation: introHead 0.8s var(--ease) both; }
body.is-booted .hero__art { animation: introArt 1.6s var(--ease) both; }
body.is-booted .hero__stage { animation: introStage 1.15s var(--ease) 0.16s both; }
body.is-booted .hero__copy > * { animation: introUp 0.9s var(--ease) both; }
body.is-booted .hero__copy > *:nth-child(1) { animation-delay: 0.04s; }
body.is-booted .hero__copy > *:nth-child(2) { animation-delay: 0.1s; }
body.is-booted .hero__copy > *:nth-child(3) { animation-delay: 0.18s; }
body.is-booted .hero__copy > *:nth-child(4) { animation-delay: 0.26s; }
body.is-booted .hero__copy > *:nth-child(5) { animation-delay: 0.32s; }
body.is-booted .hero__copy > *:nth-child(6) { animation-delay: 0.38s; }
body.is-booted .hero__copy > *:nth-child(7) { animation-delay: 0.44s; }
body.is-booted .hero__copy > *:nth-child(n + 8) { animation-delay: 0.5s; }
body.is-booted .rail { animation: introRail 1s var(--ease) 0.5s both; }

@keyframes introHead {
  from { opacity: 0; transform: translateY(-16px); }
  to { opacity: 1; transform: none; }
}
@keyframes introUp {
  from { opacity: 0; transform: translateY(20px); filter: blur(7px); }
  to { opacity: 1; transform: none; filter: none; }
}
@keyframes introStage {
  from { opacity: 0; transform: translateY(28px) scale(0.985); }
  to { opacity: 1; transform: none; }
}
@keyframes introArt {
  from { opacity: 0; transform: scale(1.04); }
  to { opacity: 0.17; transform: none; }
}
@keyframes introRail {
  from { opacity: 0; transform: translateX(10px); }
  to { opacity: 1; transform: none; }
}

/* a slow drift so the hero artwork is never completely static */
body.is-booted .hero__art img:first-child { animation: artDrift 22s ease-in-out infinite; }
body.is-booted .hero__art img:last-child { animation: artDrift 28s ease-in-out infinite reverse; }
@keyframes artDrift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(1.6%, -2.4%, 0); }
}

/* the locked signal cards get one highlight pass, then settle */
.sig--locked .sig__veil::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 35%, rgba(255, 255, 255, 0.1) 50%, transparent 65%);
  animation: lockSweep 4.6s ease-in-out infinite;
}
@keyframes lockSweep {
  0% { transform: translateX(-120%); }
  55%, 100% { transform: translateX(120%); }
}

@media (prefers-reduced-motion: reduce) {
  body.is-booted .head,
  body.is-booted .hero__art,
  body.is-booted .hero__stage,
  body.is-booted .hero__copy > *,
  body.is-booted .rail,
  body.is-booted .hero__art img:first-child,
  body.is-booted .hero__art img:last-child,
  .sig--locked .sig__veil::after {
    animation: none !important;
  }
  body.is-booted .hero__art { opacity: 0.17; }
}

body.is-lite .hero__art { display: none; }
body.is-lite .sig--locked .sig__veil::after { display: none; }
