/* The Zen of Venn — viewer stage.
 * Colors and easing curves arrive as CSS custom properties set by
 * src/viewer/theme.ts from @zov/core's tokens.ts — see that file's
 * comment for why no hex digit belongs in this file.
 *
 * No `var(--zov-*, #hex)` fallback values here on purpose (Bianca's gate
 * on PR #83: an earlier version of this file carried six hand-copied hex
 * literals as fallbacks — `guards/scan.ts`'s HEX_GLOBS doesn't scan
 * `.css`, so nothing would have caught them drifting from tokens.ts).
 * Every var() below is left to resolve against ITS OWN property's CSS
 * initial value until theme.ts's `applyTheme()` sets the real one — that
 * call happens at module-eval time in a `type="module"` script, which
 * runs before `DOMContentLoaded` and, on this page's own markup (no
 * blocking stylesheet delaying the parser, no synchronous script ahead
 * of it), before the browser's first paint. The residual risk of a
 * fallback-free var() (an unstyled flash if a future markup change
 * introduces something that paints before the module script runs) is
 * judged smaller than the guaranteed-forever risk of a second,
 * guard-blind copy of the palette. */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  height: 100%;
  background: var(--zov-ground);
  color: var(--zov-ink);
  font-family:
    'Baloo 2',
    system-ui,
    -apple-system,
    'Segoe UI',
    Roboto,
    sans-serif;
}

.stage {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--zov-ground);
  transition: background 600ms var(--zov-ease-turn, ease);
}

/* Act 1 — the token bloom. Plays immediately, pure chrome, implies
 * nothing about the network result in flight behind it. */
.bloom-token {
  width: 72px;
  height: 72px;
  border-radius: 999px;
  background: var(--zov-identity);
  opacity: 0;
  transform: scale(0.2);
}

.stage[data-state='bloom'] .bloom-token {
  animation: token-bloom 900ms var(--zov-ease-glide-out, ease) forwards;
}

@keyframes token-bloom {
  0% {
    opacity: 0;
    transform: scale(0.2);
  }
  60% {
    opacity: 1;
    transform: scale(1.15);
  }
  100% {
    opacity: 0.9;
    transform: scale(1);
  }
}

/* Act 2 — the assembly. Only ever shown once the envelope has passed
 * every read-route check; a doc that fails never reaches this markup. */
.venn-mount {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
}

.stage[data-state='assembling'] .venn-mount,
.stage[data-state='settled'] .venn-mount {
  opacity: 1;
}

.venn-mount svg {
  /* Headroom for a real venn's own canvas dimensions, which can run far
   * larger than this suite's fixtures (390x400) — at a 1024x768 test
   * viewport, 90vmin is 691px, well above either fixture's intrinsic
   * size, so NEITHER of e2e/viewer.spec.ts's committed screenshot
   * baselines ever exercises this cap at all (Ferruccio's gate, PR #83,
   * nit — "the dead rule the baselines can't see"). Left in place
   * rather than removed: it is real, load-bearing headroom for a
   * document this suite has simply never tried, not dead code — but the
   * gap in coverage is real too, named here rather than silently
   * assumed covered. */
  max-width: 90vmin;
  max-height: 90vmin;
  cursor: grab;
}

.venn-mount svg:active {
  cursor: grabbing;
}

/* Each top-level painted layer (a circle, a region, a label group) the
 * assembly cascades in carries this class; arrival.ts sets the stagger
 * via --zov-cascade-delay, computed from the document's own set count —
 * "cascade timing derived from the actual set count" per the plan. */
.cascade-piece {
  opacity: 0;
  transform: scale(0.85);
  animation: assemble 520ms var(--zov-ease-glide-out, ease) forwards;
  animation-delay: var(--zov-cascade-delay, 0ms);
}

@keyframes assemble {
  0% {
    opacity: 0;
    transform: scale(0.85);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* Closed — a definitive denial. The stage cools to cream and shows one
 * quiet line, distinct from the transient state below on purpose. */
.status-line {
  position: relative;
  z-index: 1;
  font-size: 1.1rem;
  opacity: 0;
  text-align: center;
  padding: 0 1.5rem;
}

.stage[data-state='closed'] {
  background: var(--zov-ground);
}

.stage[data-state='closed'] .status-line {
  animation: settle-in 700ms var(--zov-ease-turn, ease) forwards;
  cursor: pointer;
}

/* Transient — a hiccup, never the closed copy, never the closed color
 * treatment. A gentle breathing pulse rather than a settle, so it reads
 * as "still trying," not "done." */
.stage[data-state='transient'] .status-line {
  animation: breathe 1600ms var(--zov-ease-turn, ease) infinite;
  opacity: 0.85;
}

@keyframes breathe {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}

@keyframes settle-in {
  0% {
    opacity: 0;
    transform: translateY(6px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.landing {
  max-width: 40rem;
  margin: 4rem auto;
  padding: 0 1.5rem;
  text-align: center;
}

.landing h1 {
  color: var(--zov-identity);
  font-size: 2.5rem;
}

.landing-note {
  opacity: 0.75;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
