/* ============================================================================
   splash.css — shared styles for the four splash-screen concepts
   (Molt, Three Worlds One Shape, Registration, Constellation).

   Used only by js/splash/*.js and splash-demo.html. The real boot flow
   (index.html .boot-splash) does NOT load or depend on this file.

   Design contract (see splash-concepts-fable.md):
   - Every concept is "resolved-state-first": the final lockup exists as a
     static layer (.splash-resolved) from frame zero; the animation layer
     (.splash-anim) merely choreographs the arrival. Skip / early-ready /
     reduced-motion all just reveal the resolved layer.
   - Animations only run once the module adds .play to the anim layer.
     Reduced-motion is decided in JS (opts.reducedMotion ?? matchMedia), so
     there is deliberately NO @media (prefers-reduced-motion) block here —
     the demo's "Reduced motion" toggle must be able to force either state.
   - Palette: bg #1b1c2a, violet #8f7cf0, teal #5ec8c0, gold #e0b15a,
     edge grey #c8c8d8, muted #a6a7bd.
   ========================================================================== */

.splash-root {
  position: relative;
  overflow: hidden;
  background: #1b1c2a;
  --sp-violet: #8f7cf0;
  --sp-teal: #5ec8c0;
  --sp-gold: #e0b15a;
  --sp-edge: #c8c8d8;
  --sp-muted: #a6a7bd;
  --sp-bg: #1b1c2a;
}

/* --- Real boot integration (js/main.js) ------------------------------------
   The pre-boot placeholder in index.html is a bare brand-color field (the
   0-frame every concept starts from), with the spinner only fading in if
   module loading itself runs long. main.js then mounts a randomly chosen
   concept into a full-bleed .boot-splash-stage. */
.boot-splash.pre-boot {
  position: fixed; inset: 0; min-height: 0; background: #1b1c2a; z-index: 40;
}
.boot-splash.pre-boot .spinner {
  opacity: 0;
  animation: spin 0.8s linear infinite, sp-fade-in 300ms ease-out 1.2s forwards;
}
.boot-splash-stage { position: fixed; inset: 0; z-index: 40; }
/* Waiting spinner beneath the held lockup when loading outlasts the beat. */
.boot-splash-stage .boot-wait {
  position: absolute; left: 50%; top: calc(50% + 128px); translate: -50% 0;
  z-index: 3; opacity: 0;
  animation: spin 0.8s linear infinite, sp-fade-in 300ms ease-out forwards;
}
@keyframes sp-fade-in { to { opacity: 1; } }

/* Stacked full-bleed layers */
.splash-layer {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
}

/* --- The shared resolved lockup: icon + wordmark ------------------------- */
.splash-lockup { display: flex; flex-direction: column; align-items: center; gap: 18px; }
.splash-icon { width: 110px; height: 110px; }
.splash-icon svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* Mirrors the app's .reveal-name exactly (css/app.css) so the splash and every
   in-app celebratory reveal are the same object at rest. */
.splash-word {
  font-size: 1.8rem; font-weight: 700; letter-spacing: -0.01em; text-align: center;
  background: linear-gradient(90deg, var(--sp-violet), var(--sp-teal));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}

/* Resolved layer: opaque bg so revealing it fully covers the anim layer. */
.splash-resolved { background: var(--sp-bg); opacity: 0; z-index: 2; }
.splash-resolved.show { opacity: 1; transition: opacity 150ms ease; }
.splash-resolved.show.rm { transition-duration: 200ms; }   /* reduced-motion fade */
.splash-anim { z-index: 1; }
.splash-anim.done { visibility: hidden; }

/* Full-bleed SVG stage used by concepts 2–4 (and reduced-motion stills). */
.splash-stage-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

/* Wordmark fade-up ("fades up + rises 6px"), used by concepts 2–4. */
.splash-anim .sp-word-in { opacity: 0; }
.splash-anim.play .sp-word-in {
  animation: sp-word-in 250ms ease-out both;
  animation-delay: var(--word-delay, 700ms);
}
@keyframes sp-word-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================================
   Concept 1 — MOLT
   Letters fly in from depth wearing domain skins, snap into place, shed the
   skins to one word-spanning gradient; the icon signs above (arc draw).
   ========================================================================== */

.molt-wordstack {
  position: relative; white-space: nowrap;
  /* Match .splash-word metrics; kerning/ligatures off so the 12 separate
     skin spans align pixel-exactly with the single final word. */
  font-size: 1.8rem; font-weight: 700; letter-spacing: 0;
  font-kerning: none; font-variant-ligatures: none;
}
/* Final word: one element, one gradient across the whole word (reads as one
   object, not twelve letters). Hidden until the molt reveals it. */
.molt-final {
  opacity: 0;
  letter-spacing: 0;             /* cross-fades with the skin word at ls:0 … */
}
.splash-anim.play .molt-final {
  animation: molt-final-in 150ms ease-out 450ms both,
             molt-tighten 150ms cubic-bezier(0.4, 0, 0.2, 1) 600ms both;
}
@keyframes molt-final-in { from { opacity: 0; } to { opacity: 1; } }
/* … then contracts into the final tight tracking — the word pulls into itself. */
@keyframes molt-tighten { from { letter-spacing: 0; } to { letter-spacing: -0.01em; } }

/* Skin word: 12 spans stacked exactly over the final word (same font/size/
   weight, ls:0 → pixel-aligned). */
.molt-skins {
  position: absolute; inset: 0; text-align: center; white-space: nowrap;
  letter-spacing: 0; pointer-events: none;
}
.molt-skins span {
  position: relative; display: inline-block;
  background-color: var(--skin-dim);
  background-image: var(--skin-motif);
  background-size: 14px 14px;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  opacity: 0;
  will-change: transform, filter, opacity;
}
.splash-anim.play .molt-skins span {
  /* fly-in from depth (scale+blur fake the Z), then molt away */
  animation: molt-fly 350ms cubic-bezier(0.2, 0.9, 0.3, 1) var(--fly-delay) both,
             molt-shed 130ms ease-out var(--shed-delay) forwards;
}
@keyframes molt-fly {
  0%   { opacity: 0;
         transform: translate(var(--fx), var(--fy)) scale(var(--fs));
         filter: blur(var(--fb)); }
  25%  { opacity: 1; }
  85%  { transform: translate(0, -2px) scale(1.015); filter: blur(0); } /* 2px overshoot */
  100% { opacity: 1; transform: translate(0, 0) scale(1); filter: blur(0); }
}
@keyframes molt-shed { to { opacity: 0; } }

/* The shed husk: a ghost of the letter in its skin color lifts off upward. */
.molt-skins span::after {
  content: attr(data-ch);
  position: absolute; left: 0; top: 0; width: 100%;
  color: var(--skin-solid);
  -webkit-text-fill-color: currentColor;   /* undo inherited transparent fill */
  background: none;
  opacity: 0; pointer-events: none;
}
.splash-anim.play .molt-skins span::after {
  animation: molt-husk 220ms ease-out var(--shed-delay) forwards;
}
@keyframes molt-husk {
  0%   { opacity: 0.35; transform: translateY(0); }
  100% { opacity: 0;    transform: translateY(-7px); }
}

/* Domain skins: low-saturation base tint + tiny hand-drawn SVG motif, both
   clipped to the letterform. Four skins cycled across twelve letters. */
.molt-skins .skin-thermo {
  --skin-dim: #8a7148; --skin-solid: #e0b15a;
  --skin-motif: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14'><circle cx='7' cy='7' r='4.6' fill='none' stroke='%23e0b15a' stroke-width='1' opacity='.85'/><path d='M7 7 L9.6 4.2' stroke='%23e0b15a' stroke-width='1.2'/></svg>");
}
.molt-skins .skin-cycle {
  --skin-dim: #4a7d78; --skin-solid: #5ec8c0;
  --skin-motif: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14'><path d='M0 7 q3.5 -6 7 0 t7 0' fill='none' stroke='%235ec8c0' stroke-width='1.3'/></svg>");
}
.molt-skins .skin-market {
  --skin-dim: #665e9e; --skin-solid: #8f7cf0;
  --skin-motif: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14'><path d='M0 11 l3.5 -3.5 l2.5 1.8 l3.5 -5 l4.5 2.4' fill='none' stroke='%238f7cf0' stroke-width='1.3'/></svg>");
}
.molt-skins .skin-circuit {
  --skin-dim: #62647a; --skin-solid: #c8c8d8;
  --skin-motif: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14'><path d='M2 12 V7 H7 V2 M7 7 h5' fill='none' stroke='%23c8c8d8' stroke-width='1.1'/><circle cx='12' cy='7' r='1.3' fill='%23c8c8d8'/></svg>");
}

/* Molt's icon: appears 750ms+ as the quiet signature — arc draws, triangle
   fades in with it (so the held frame is the identical shared lockup). */
.molt-icon .molt-tri { opacity: 0; }
.splash-anim.play .molt-icon .molt-tri { animation: molt-tri-in 150ms ease-out 750ms both; }
@keyframes molt-tri-in { from { opacity: 0; } to { opacity: 1; } }
.molt-icon .molt-arc { stroke-dasharray: 310; stroke-dashoffset: 310; }
.splash-anim.play .molt-icon .molt-arc { animation: sp-arc-draw 200ms ease-out 750ms both; }
@keyframes sp-arc-draw { to { stroke-dashoffset: 0; } }

/* ============================================================================
   Concept 2 — THREE WORLDS, ONE SHAPE
   Three domain vignettes distill to dots; the dots fly to the icon's vertices;
   edges close the triangle; the arc sweeps; the structure signs the wordmark.
   ========================================================================== */

/* Vignettes: single-stroke line drawings, drawn on then retracted.
   All paths carry pathLength="100" so one dash rule fits every shape. */
.tw-vig path {
  fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 100; stroke-dashoffset: 100;
}
.splash-anim.play .tw-vig path {
  animation: tw-draw 90ms ease-out var(--vd) both,
             tw-retract 230ms cubic-bezier(0.55, 0, 0.8, 0.4) var(--vr) both;
}
@keyframes tw-draw    { to { stroke-dashoffset: 0; } }
@keyframes tw-retract { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -100; } }

/* The whole vignette condenses toward its centroid as its strokes retract. */
.tw-vig-inner { transform-box: fill-box; transform-origin: center; }
.splash-anim.play .tw-vig-inner {
  animation: tw-condense 250ms cubic-bezier(0.5, 0, 0.75, 0.6) var(--vr) both;
}
@keyframes tw-condense {
  from { transform: scale(1); opacity: 1; }
  to   { transform: scale(0.12); opacity: 0; }
}

/* The distilled dot: appears at the vignette centroid, then travels a gentle
   arc (mid-keyframe bows the straight line) to its icon vertex; 1.15× pop on
   arrival — the click-into-place. Driven by per-dot px vars. */
.tw-dot { opacity: 0; }
.splash-anim.play .tw-dot {
  animation: tw-dot-in 120ms ease-out var(--di) both,
             tw-travel 250ms cubic-bezier(0.3, 0.7, 0.3, 1) var(--dt) both;
}
@keyframes tw-dot-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes tw-travel {
  0%   { transform: translate(var(--x0), var(--y0)) scale(1); }
  55%  { transform: translate(var(--xm), var(--ym)) scale(1); }
  90%  { transform: translate(var(--x1), var(--y1)) scale(1.15); }
  100% { transform: translate(var(--x1), var(--y1)) scale(1); }
}

/* Triangle edges close (650–800ms, chasing), then the arc sweeps beneath. */
.tw-edge { stroke-dasharray: 212; stroke-dashoffset: 212; }
.splash-anim.play .tw-edge { animation: tw-edge-draw 50ms linear var(--ed) both; }
@keyframes tw-edge-draw { to { stroke-dashoffset: 0; } }
.tw-arc { stroke-dasharray: 310; stroke-dashoffset: 310; }
.splash-anim.play .tw-arc { animation: sp-arc-draw 120ms ease-out var(--arc-delay, 820ms) both; }

/* Reduced-motion still (optional richer frame): vignettes static + faint. */
.tw-still .tw-vig path { stroke-dasharray: none; stroke-dashoffset: 0; }
.tw-still { opacity: 0.15; }

/* ============================================================================
   Concept 3 — REGISTRATION
   Three misregistered tracing sheets glide into alignment; the strokes all
   three share — the icon skeleton — ignite; every surface stroke cancels.
   ========================================================================== */

.rg-sheet {
  color: var(--sheet-color);
  opacity: 0;
  transform-box: view-box;
  transform-origin: var(--ox) var(--oy);   /* rotate about the icon center */
  will-change: transform, opacity;
}
.splash-anim.play .rg-sheet {
  animation: rg-sheet-in 150ms ease-out both,
             rg-register 400ms cubic-bezier(0.22, 1, 0.36, 1) var(--rd) both;
}
@keyframes rg-sheet-in { from { opacity: 0; } to { opacity: 0.28; } }
@keyframes rg-register {
  from { transform: rotate(var(--rot)) translate(var(--dx), var(--dy)); }
  to   { transform: none; }
}

/* Sheet strokes: skeleton (the shared invariant) + surface dressing. */
.rg-sheet path, .rg-sheet line, .rg-sheet circle {
  fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round;
}
/* At registration the stacked translucent skeletons are replaced by the true
   icon; the sheets' own skeleton strokes step aside… */
.splash-anim.play .rg-skel-use { animation: rg-fade-out 150ms ease-out 560ms both; }
@keyframes rg-fade-out { to { opacity: 0; } }
/* …and every non-shared surface stroke cancels — dropping first to a 2% ghost
   (the surfaces were real; abstraction discards them knowingly), then to 0. */
.splash-anim.play .rg-surface { animation: rg-cancel 340ms linear 560ms both; }
@keyframes rg-cancel {
  0%   { opacity: 1; }
  44%  { opacity: 0.07; }   /* ≈2% on screen after the sheet's 0.28 multiplier */
  80%  { opacity: 0.07; }
  100% { opacity: 0; }
}

/* The ignited icon: full-opacity nodes/edges/arc with a soft 1.06× pulse. */
.rg-icon { opacity: 0; transform-box: fill-box; transform-origin: center; }
.splash-anim.play .rg-icon {
  animation: rg-ignite 150ms ease-out 560ms both,
             rg-pulse 220ms ease-in-out 560ms both;
}
@keyframes rg-ignite { from { opacity: 0; } to { opacity: 1; } }
@keyframes rg-pulse { 0% { transform: scale(1); } 55% { transform: scale(1.06); } 100% { transform: scale(1); } }

/* Reduced-motion still: the doc's preferred rich fallback — the three sheets
   already registered, faint, beneath the ignited icon. */
.rg-still .rg-sheet { opacity: 0.10; animation: none; transform: none; }

/* ============================================================================
   Concept 4 — CONSTELLATION
   A fixed field of dim noise-dots; three ignite at the icon's vertices; the
   triangle and arc draw; the field breathes out and dims — structure is
   FOUND in the noise, and the noise never fully vanishes.
   ========================================================================== */

/* Noise field: fades in, drifts ±2px once (700ms, then rests — the drift
   stops as the structure settles), and steps down in opacity as each edge
   draws: 30% → 18% → 10% → resting ~8%. Dots never travel to form the mark. */
.cst-field { opacity: 0; }
.splash-anim.play .cst-field {
  animation: cst-field-in 150ms ease-out both,
             cst-dim 350ms linear 350ms both;
}
@keyframes cst-field-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes cst-dim {      /* multiplies per-dot base opacities (~0.15–0.3) */
  0%   { opacity: 1; }
  17%  { opacity: 1; }    /* step after edge 1 … */
  33%  { opacity: 0.6; }
  50%  { opacity: 0.6; }  /* … edge 2 … */
  66%  { opacity: 0.4; }
  83%  { opacity: 0.4; }  /* … edge 3, then settle */
  100% { opacity: 0.27; }
}
.cst-dot { fill: var(--sp-muted); }
.splash-anim.play .cst-dot {
  animation: cst-drift 700ms ease-out var(--dd, 0ms) forwards;
}
@keyframes cst-drift { from { transform: translate(0, 0); } to { transform: translate(var(--jx), var(--jy)); } }

/* Three vertex dots ignite in sequence: gold, teal, violet — scale to node
   size, take color, glow. The eye snaps to them; nothing moves. */
.cst-vertex { transform-box: fill-box; transform-origin: center; }
.splash-anim.play .cst-vertex {
  animation: cst-ignite 130ms cubic-bezier(0.3, 0.7, 0.3, 1.3) var(--vi) both;
}
@keyframes cst-ignite {
  from { transform: scale(1);   fill: var(--sp-muted); filter: none; }
  to   { transform: scale(2.2); fill: var(--node-color);
         filter: drop-shadow(0 0 5px var(--node-color)); }
}

/* Edges chase around the triangle, arc sweeps, icon pulses once. */
.cst-edge { stroke-dasharray: 212; stroke-dashoffset: 212; }
.splash-anim.play .cst-edge { animation: cst-edge-draw 60ms ease-out var(--ed) both; }
@keyframes cst-edge-draw { to { stroke-dashoffset: 0; } }
.cst-arc { stroke-dasharray: 310; stroke-dashoffset: 310; }
.splash-anim.play .cst-arc { animation: sp-arc-draw 120ms ease-out 530ms both; }
.cst-icon { transform-box: fill-box; transform-origin: center; }
.splash-anim.play .cst-icon { animation: cst-pulse 160ms ease-in-out 610ms both; }
@keyframes cst-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } }

/* Reduced-motion still: faint starfield behind the resolved lockup — a
   constellation IS a still image. */
.cst-still .cst-field { opacity: 0.27; animation: none; }
