/* ==========================================================================
   SORZIA - scene.css  (v6 - Optical Destiny Lens)

   Scene space: the 980x880 viewBox in index.html.
   Lens centre (600, 430).  Glass r = 240.  Chip arc radius 400.

   The hero runs a 12 second cinematic loop driven by scene.js, which sets
   data-phase on .optic-stage:

     idle -> charge -> transmit -> decode -> emit -> reveal -> settle

   Everything below reacts to that attribute. Nothing here animates on its
   own timeline except slow ambience, so the story always reads in order.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Stage
   -------------------------------------------------------------------------- */
.optic-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 980 / 880;
  --px: 0;
  --py: 0;
}

.optic-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  transform: translate3d(calc(var(--px) * 9px), calc(var(--py) * 6px), 0);
  transition: transform 1100ms var(--ease-out);
  will-change: transform;
}

/* --------------------------------------------------------------------------
   2. Ambience - always running, always slow
   -------------------------------------------------------------------------- */
.op-aura {
  --aura: 1;
  transform-origin: 600px 430px;
  animation: auraBreathe 14s var(--ease) infinite;
}

@keyframes auraBreathe {
  0%, 100% { transform: scale(var(--aura));                opacity: 0.82; }
  50%      { transform: scale(calc(var(--aura) * 1.05));   opacity: 1; }
}

/* Ring planes turn slowly in opposite directions. The tilt lives on the
   inner <ellipse> as an attribute, so these CSS rotations compose with it. */
.op-ring-a { transform-origin: 600px 430px; animation: spin 150s linear infinite; }
.op-ring-b { transform-origin: 600px 430px; animation: spin 96s linear infinite reverse; }
.op-track  { transform-origin: 600px 430px; animation: spin 220s linear infinite; }
.op-glyphs { transform-origin: 600px 430px; animation: spin 340s linear infinite; }

@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

.op-glyphs text {
  font-family: "Segoe UI Symbol", "Apple Symbols", "Noto Sans Symbols 2", var(--font-ui);
  font-size: 19px;
  text-anchor: middle;
  fill: rgba(212, 173, 89, 0.42);
}

.op-chamber {
  transform-origin: 600px 430px;
  animation: chamberBreathe 8s var(--ease) infinite;
}

@keyframes chamberBreathe {
  0%, 100% { transform: scale(1);    opacity: 0.86; }
  50%      { transform: scale(1.06); opacity: 1; }
}

.op-core { transform-origin: 600px 430px; }

/* A slow specular sweep across the glass - the "liquid" read. */
.op-specular {
  transform-origin: 600px 430px;
  animation: specSweep 16s var(--ease) infinite;
}

@keyframes specSweep {
  0%, 100% { transform: translate(0, 0) rotate(0deg);      opacity: 0.5; }
  50%      { transform: translate(140px, 90px) rotate(8deg); opacity: 1; }
}

.op-caustic { animation: causticShift 11s var(--ease) infinite; }

@keyframes causticShift {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1; }
}

.op-glint { animation: glintPulse 9s var(--ease) infinite; }

@keyframes glintPulse {
  0%, 100% { stroke-opacity: 0.30; }
  50%      { stroke-opacity: 0.68; }
}

/* Optical dust in the foreground. */
.dust-mote {
  animation: dustFloat var(--dur, 24s) ease-in-out infinite;
  animation-delay: var(--d, 0s);
  transform-origin: center;
}

@keyframes dustFloat {
  0%   { transform: translate3d(0, 0, 0);        opacity: 0; }
  16%  { opacity: 0.85; }
  50%  { transform: translate3d(34px, -30px, 0); opacity: 0.4; }
  84%  { opacity: 0.6; }
  100% { transform: translate3d(66px, -74px, 0); opacity: 0; }
}

/* --------------------------------------------------------------------------
   3. The three systems, hidden until the lens decodes
   --------------------------------------------------------------------------
   This is how the visual states that SORZIA is more than one method: during
   decode the Vedic orbits, the Nadi flow paths and the numerology grid each
   surface in turn, then collapse into a single core burst.
   -------------------------------------------------------------------------- */
.sys { opacity: 0; }

.sys-num text {
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 500;
  text-anchor: middle;
  fill: rgba(231, 201, 125, 0.75);
}

[data-phase="decode"] .sys-vedic { animation: sysFlash 1.25s var(--ease) 0s   1; }
[data-phase="decode"] .sys-bnn   { animation: sysFlash 1.25s var(--ease) .18s 1; }
[data-phase="decode"] .sys-num   { animation: sysFlash 1.25s var(--ease) .36s 1; }

@keyframes sysFlash {
  0%   { opacity: 0;    transform: scale(1.06); }
  26%  { opacity: 0.95; transform: scale(1); }
  62%  { opacity: 0.7;  transform: scale(0.97); }
  100% { opacity: 0;    transform: scale(0.8); }
}

.sys { transform-origin: 600px 430px; }

/* --------------------------------------------------------------------------
   4. Phase reactions
   -------------------------------------------------------------------------- */
/* CHARGE - the lens leans in slightly before the question arrives. */
[data-phase="charge"] .op-chamber,
[data-phase="transmit"] .op-chamber { opacity: 1; }

/* DECODE - the whole instrument reacts for about a second. */
[data-phase="decode"] .op-core     { animation: coreDecode 1.25s var(--ease) 1; }
[data-phase="decode"] .op-collapse { animation: collapseIn 1.1s var(--ease) 1; }
[data-phase="decode"] .op-track    { animation: spin 4s linear infinite; }
[data-phase="decode"] .op-index    { animation: indexAlign 1.1s var(--ease) 1; }
[data-phase="decode"] .op-aura     { animation: auraSurge 1.25s var(--ease) 1; }

/* compress, then release */
@keyframes coreDecode {
  0%   { transform: scale(1);    opacity: 1; }
  34%  { transform: scale(0.38); opacity: 1; }
  52%  { transform: scale(2.05); opacity: 1; }
  74%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

@keyframes collapseIn {
  0%   { transform: scale(1.62); stroke-opacity: 0; }
  26%  { stroke-opacity: 0.85; }
  66%  { transform: scale(0.12); stroke-opacity: 0.9; }
  100% { transform: scale(0.12); stroke-opacity: 0; }
}

.op-collapse { transform-origin: 600px 430px; }
.op-index    { transform-origin: 600px 430px; }

@keyframes indexAlign {
  0%   { transform: rotate(0deg);  stroke-opacity: 0.45; }
  55%  { stroke-opacity: 1; }
  100% { transform: rotate(90deg); stroke-opacity: 0.45; }
}

@keyframes auraSurge {
  0%, 100% { transform: scale(1);    opacity: 0.85; }
  46%      { transform: scale(1.14); opacity: 1; }
}

/* --------------------------------------------------------------------------
   5. Light threads - the left-to-right story
   -------------------------------------------------------------------------- */
.thread-track path {
  fill: none;
  stroke-width: 1.2;
  opacity: 0.13;
  transition: opacity 600ms var(--ease);
}

.thread-track path.is-warm   { opacity: 0.34; }
.thread-track path.is-active { opacity: 0.9; }

/* The travelling pulse. pathLength="100" means one cycle is exactly one
   traversal whatever the real curve length is. Only the live thread runs. */
.thread-live path {
  fill: none;
  stroke: var(--gold-lift);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 16 100;
  stroke-dashoffset: 116;
  opacity: 0;
  filter: drop-shadow(0 0 6px rgba(242, 222, 172, 0.8));
}

.thread-live path.is-live {
  animation: threadTravel 1.9s cubic-bezier(0.45, 0, 0.25, 1) 1;
}

/* Outbound light is quicker — the answer leaves faster than the question
   arrived, and each pulse is launched to land just before its console row. */
.thread-out .thread-live path.is-live { animation-duration: 1.5s; }

@keyframes threadTravel {
  0%   { stroke-dashoffset: 116; opacity: 0; }
  12%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { stroke-dashoffset: 16;  opacity: 0; }
}

/* --------------------------------------------------------------------------
   6. Question chips
   --------------------------------------------------------------------------
   Not all equal: one active, two warm, the rest faint. That hierarchy is
   what makes the left side read as incoming signals rather than a menu.
   -------------------------------------------------------------------------- */
.chip-column {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.chip-column .chip-slot {
  position: absolute;
  right: var(--slot-right, 76%);
  top: var(--slot-top, 20%);
  transform: translateY(-50%);
  pointer-events: auto;
}

.chip-column .chip {
  opacity: 0.42;
  transform: scale(0.965);
  transition:
    opacity 700ms var(--ease),
    transform 700ms var(--ease-out),
    border-color 700ms var(--ease),
    box-shadow 700ms var(--ease);
}

.chip-column .chip.is-warm { opacity: 0.72; transform: scale(0.985); }

/* The active question drifts toward the lens and lights up. */
.chip-column .chip.is-active {
  opacity: 1;
  transform: translateX(14px) scale(1.04);
  border-color: rgba(231, 201, 125, 0.75);
  box-shadow:
    var(--edge-strong),
    0 0 30px -6px rgba(212, 173, 89, 0.75),
    0 10px 26px -16px rgba(0, 0, 0, 0.95);
}

.chip-column .chip:hover { opacity: 1; }

/* --------------------------------------------------------------------------
   7. No-JavaScript fallback
   --------------------------------------------------------------------------
   The sequence is what dims the chips and holds the console back. With no
   script to run it, none of that would ever be undone — so present the
   finished state instead of a permanently half-lit hero.
   -------------------------------------------------------------------------- */
html:not(.js) .chip-column .chip     { opacity: 1; transform: none; }
html:not(.js) .thread-track path     { opacity: 0.34; }
html:not(.js) .console-row,
html:not(.js) .console-synthesis     { opacity: 1; transform: none; }
html:not(.js) .row-tick              { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   7. Insight emergence cards (journey page result rail)
   -------------------------------------------------------------------------- */
.insight-stack {
  display: grid;
  gap: 0.75rem;
}

.insight-card {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.85rem;
  padding: 0.95rem 1.05rem;
  border-radius: var(--r-md);
  overflow: hidden;
  transition:
    transform var(--dur-3) var(--ease-out),
    opacity var(--dur-3) var(--ease-out),
    border-color var(--dur-2) var(--ease),
    box-shadow var(--dur-2) var(--ease);
}

.insight-card h3 {
  font-family: var(--font-ui);
  font-size: var(--step-0);
  font-weight: 600;
  letter-spacing: 0.004em;
  line-height: 1.3;
}

.insight-card p {
  margin-top: 0.18rem;
  font-size: var(--step--1);
  line-height: 1.48;
  color: var(--text-muted);
}

.insight-card > .icon-chevron { color: var(--gold-mid); opacity: 0.62; }

/* Entrance: pushed outward from the chamber, left to right. */
.js .insight-card {
  opacity: 0;
  transform: translateX(-26px) scale(0.97);
}

.js .insight-card.is-revealed {
  opacity: 1;
  transform: none;
}

/* Safety net: if a visitor has reduced motion on, show them from CSS so
   they never depend on a script running at all. */
@media (prefers-reduced-motion: reduce) {
  .js .insight-card { opacity: 1; transform: none; }
}

/* Continuous sequential highlight — one card at a time, forever.
   --cycle is set by scene.js to (number of cards x 3s), so a stack of four
   and a stack of five both give every card exactly one turn. */
.insight-card.is-cycling {
  animation: cardCycle var(--cycle, 12s) var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 3s);
}

@keyframes cardCycle {
  0%, 30%, 100% {
    border-color: var(--line);
    box-shadow: var(--edge), var(--shadow-1);
    transform: translateX(0);
  }
  6%, 22% {
    border-color: rgba(224, 191, 107, 0.60);
    box-shadow: var(--edge-strong), var(--shadow-2), 0 0 34px -10px rgba(208, 169, 74, 0.75);
    transform: translateX(-9px);
  }
}

/* Light sweep across the active card. */
.insight-card .sweep {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 30%, rgba(244, 228, 188, 0.14) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: sweepAcross var(--cycle, 12s) var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 3s);
}

@keyframes sweepAcross {
  0%, 4%    { transform: translateX(-100%); }
  20%, 100% { transform: translateX(100%); }
}

.insight-legend {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.55rem;
  font-size: var(--step--1);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-bright);
}

.insight-legend .pip {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold-bright);
  box-shadow: 0 0 10px rgba(208, 169, 74, 0.9);
  animation: pipBlink 3s var(--ease) infinite;
}

@keyframes pipBlink {
  0%, 100% { opacity: 0.32; }
  50%      { opacity: 1; }
}

/* --------------------------------------------------------------------------
   8. Analysis engine (how-it-works page)
   -------------------------------------------------------------------------- */
.engine { position: relative; padding-block: var(--sp-5); }

.engine-track {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(0.75rem, 2vw, 1.6rem);
  align-items: start;
}

.engine-node {
  position: relative;
  display: grid;
  justify-items: center;
  text-align: center;
  gap: 0.55rem;
}

.engine-orb {
  position: relative;
  display: grid;
  place-items: center;
  width: clamp(104px, 13vw, 152px);
  aspect-ratio: 1;
  border-radius: 50%;
  color: var(--gold-bright);
  background:
    radial-gradient(circle at 50% 34%, rgba(90, 79, 232, 0.24), transparent 62%),
    radial-gradient(circle at 50% 50%, rgba(16, 26, 44, 0.92), rgba(4, 7, 15, 0.96));
  border: 1px solid rgba(208, 169, 74, 0.30);
  box-shadow: var(--edge), 0 0 48px -18px rgba(208, 169, 74, 0.85), var(--shadow-2);
}

.engine-orb .icon { width: 42%; height: 42%; stroke-width: 1.2; }

/* Two rings, counter-rotating — "systems in motion". */
.engine-orb::before,
.engine-orb::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.engine-orb::before {
  inset: -11px;
  border: 1px dashed rgba(208, 169, 74, 0.32);
  animation: orbSpin 44s linear infinite;
}

.engine-orb::after {
  inset: -21px;
  border: 1px solid rgba(126, 200, 255, 0.13);
  border-top-color: rgba(208, 169, 74, 0.48);
  animation: orbSpin 30s linear infinite reverse;
}

@keyframes orbSpin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.engine-node h3 {
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin-top: 0.9rem;
}

.engine-node p {
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 24ch;
}

.engine-step {
  font-size: 0.68rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold-bright);
  opacity: 0.82;
}

/* Connectors span exactly the column gap, centred on the orbs. */
.engine-link {
  position: absolute;
  top: calc(clamp(104px, 13vw, 152px) / 2);
  left: 100%;
  width: clamp(0.75rem, 2vw, 1.6rem);
  height: 2px;
  transform: translateY(-50%);
  color: var(--gold-bright);
  overflow: hidden;
}

.engine-link::before {
  content: "";
  position: absolute;
  inset: 0;
  transform-origin: left;
  background: linear-gradient(90deg, rgba(212, 173, 89, 0.10), rgba(231, 201, 125, 0.9));
}

/* ==========================================================================
   Engine relay
   --------------------------------------------------------------------------
   One continuous analytical pass, not four independent decorations:

     Vedic activates -> signal travels -> BNN reacts -> signal travels ->
     Numerology activates -> signal travels -> Synthesis brightens and emits

   A 12s cycle with each stage offset by 2.6s, so the hand-off is always
   visible and the loop settles before it begins again.
   ========================================================================== */
.engine-track .engine-node:nth-child(1) { --stage: 0; }
.engine-track .engine-node:nth-child(2) { --stage: 1; }
.engine-track .engine-node:nth-child(3) { --stage: 2; }
.engine-track .engine-node:nth-child(4) { --stage: 3; }

.engine-orb {
  animation: stageActivate 12s var(--ease-premium) infinite;
  animation-delay: calc(var(--stage, 0) * 2.6s);
}

@keyframes stageActivate {
  0%, 34%, 100% {
    border-color: rgba(212, 173, 89, 0.30);
    box-shadow: var(--edge), 0 0 48px -18px rgba(212, 173, 89, 0.85), var(--shadow-2);
  }
  6%, 22% {
    border-color: rgba(231, 201, 125, 0.85);
    box-shadow:
      var(--edge-strong),
      0 0 60px -10px rgba(212, 173, 89, 1),
      0 0 0 1px rgba(231, 201, 125, 0.28),
      var(--shadow-2);
  }
}

.engine-orb .icon {
  animation: stageIcon 12s var(--ease-premium) infinite;
  animation-delay: calc(var(--stage, 0) * 2.6s);
}

@keyframes stageIcon {
  0%, 34%, 100% { transform: scale(1);    opacity: 0.72; }
  8%, 20%       { transform: scale(1.08); opacity: 1; }
}

/* The rings only spin up while their own stage is working. */
.engine-orb::before {
  animation: orbSpin 44s linear infinite, stageRing 12s var(--ease-premium) infinite;
  animation-delay: 0s, calc(var(--stage, 0) * 2.6s);
}

@keyframes stageRing {
  0%, 34%, 100% { opacity: 0.5; }
  8%, 22%       { opacity: 1; }
}

/* The hand-off itself: light crosses the gap to the next stage. */
.engine-link::before {
  transform: scaleX(0);
  opacity: 0;
  animation: linkTravel 12s var(--ease-premium) infinite;
  animation-delay: calc(var(--stage, 0) * 2.6s + 1.05s);
}

@keyframes linkTravel {
  0%        { transform: scaleX(0); opacity: 0; }
  3%        { opacity: 1; }
  11%       { transform: scaleX(1); opacity: 1; }
  20%       { transform: scaleX(1); opacity: 0; }
  100%      { transform: scaleX(1); opacity: 0; }
}

/* Stage four is the answer: it emits outward rather than passing along. */
.engine-track .engine-node:nth-child(4) .engine-orb::after {
  animation: orbSpin 30s linear infinite reverse, stageEmit 12s var(--ease-premium) infinite;
  animation-delay: 0s, calc(3 * 2.6s);
}

@keyframes stageEmit {
  0%, 30%, 100% { transform: scale(1);    opacity: 0.55; }
  8%            { transform: scale(1.14); opacity: 1; }
  22%           { transform: scale(1.3);  opacity: 0; }
}

@media (max-width: 860px) {
  .engine-track { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
  .engine-link { display: none; }
}

@media (max-width: 460px) {
  .engine-track { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   9. Insight crystal
   -------------------------------------------------------------------------- */
.crystal-wrap { display: grid; place-items: center; padding: var(--sp-3) 0; }

.crystal {
  width: clamp(78px, 9vw, 116px);
  overflow: visible;
  animation: crystalBreathe 6.5s var(--ease) infinite;
}

@keyframes crystalBreathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-8px) scale(1.03); }
}

.crystal-facet-a { animation: facetGlow 5s var(--ease) infinite; }
.crystal-facet-b { animation: facetGlow 5s var(--ease) infinite 1.6s; }

@keyframes facetGlow {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* --------------------------------------------------------------------------
   10. Journey question cluster (how-it-works page)
   -------------------------------------------------------------------------- */
.question-cluster { display: grid; gap: 0.6rem; align-content: center; }

.question-focus {
  position: relative;
  padding: 1.15rem 1.3rem;
  border-radius: var(--r-lg);
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.25;
  color: var(--ivory);
  background: linear-gradient(168deg, rgba(24, 36, 68, 0.92), rgba(9, 16, 33, 0.92));
  border: 1px solid rgba(224, 191, 107, 0.52);
  box-shadow: var(--edge-strong), 0 0 44px -14px rgba(208, 169, 74, 0.72), var(--shadow-2);
  animation: focusGlow 6s var(--ease) infinite;
}

@keyframes focusGlow {
  0%, 100% { box-shadow: var(--edge-strong), 0 0 34px -16px rgba(208, 169, 74, 0.58), var(--shadow-2); }
  50%      { box-shadow: var(--edge-strong), 0 0 58px -10px rgba(208, 169, 74, 0.95), var(--shadow-2); }
}

.question-ghost {
  justify-self: start;
  padding: 0.5rem 0.9rem;
  border-radius: var(--r-md);
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--text-muted);
  background: rgba(255, 255, 255, 0.032);
  border: 1px solid rgba(255, 255, 255, 0.09);
  animation: chipFloat 11s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * -1.8s);
}

.question-ghost.right { justify-self: end; }

/* --------------------------------------------------------------------------
   11. Result rail (how-it-works page)
   -------------------------------------------------------------------------- */
.result-rail { display: grid; gap: 0.6rem; align-content: center; }

.result-card {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.75rem;
  align-items: center;
  padding: 0.8rem 0.95rem;
}

.result-card h3 { font-family: var(--font-ui); font-size: var(--step-0); font-weight: 600; }
.result-card p { font-size: var(--step--1); line-height: 1.4; color: var(--text-muted); }

/* --------------------------------------------------------------------------
   12. Ambient ornament
   -------------------------------------------------------------------------- */
.orbit-ornament {
  position: absolute;
  inset: auto;
  pointer-events: none;
  opacity: 0.45;
  animation: orbSpin 180s linear infinite;
}


/* ==========================================================================
   13. Living instruments
   --------------------------------------------------------------------------
   These replace the square illustrations. They are inline SVG so they sit ON
   the card surface rather than in a framed box, and every one carries slow,
   meaningful motion: analysis, not a loading spinner.
   ========================================================================== */
.viz { display: block; width: 100%; height: 100%; overflow: visible; }

/* The field the instrument sits in: no border, no plate — light falloff only,
   so the diagram emerges from the card instead of being pasted onto it. */
.viz-field {
  display: block;
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(72% 72% at 50% 46%, rgba(98, 85, 199, 0.11), transparent 70%),
    radial-gradient(52% 52% at 50% 50%, rgba(212, 173, 89, 0.07), transparent 72%);
  -webkit-mask-image: radial-gradient(78% 78% at 50% 50%, #000 58%, transparent 100%);
  mask-image: radial-gradient(78% 78% at 50% 50%, #000 58%, transparent 100%);
}

@keyframes vzSpin { to { transform: rotate(360deg); } }

.vz-ring, .vz-orbit, .vz-sweep, .vz-core { transform-origin: 120px 120px; }

.vz-ring-1 { animation: vzSpin 64s linear infinite; }
.vz-ring-2 { animation: vzSpin 46s linear infinite reverse; }
.vz-ring-3 { animation: vzSpin 92s linear infinite; }

.vz-orbit-a { animation: vzSpin 19s linear infinite; }
.vz-orbit-b { animation: vzSpin 28s linear infinite reverse; }
.vz-orbit-c { animation: vzSpin 37s linear infinite; }

.vz-sweep { animation: vzSpin 15s linear infinite; opacity: 0.6; }

.vz-core { animation: vzBreathe 7s var(--ease) infinite; }

@keyframes vzBreathe {
  0%, 100% { transform: scale(1);    opacity: 0.84; }
  50%      { transform: scale(1.09); opacity: 1; }
}

.vz-spark { opacity: 0; animation: vzSpark 9s var(--ease) infinite; animation-delay: var(--d, 0s); }

@keyframes vzSpark {
  0%, 100% { opacity: 0; }
  6%       { opacity: 1; }
  18%      { opacity: 0; }
}

/* ---- Nadi: sequence, relationship, validation ------------------------- */
.vz-line {
  stroke-opacity: 0.15;
  animation: vzLine 9s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 0.34s);
}

@keyframes vzLine {
  0%, 100% { stroke-opacity: 0.15; }
  8%       { stroke-opacity: 0.95; }
  24%      { stroke-opacity: 0.3; }
}

.vz-link {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  opacity: 0;
  animation: vzDraw 9s var(--ease) infinite 2.4s;
}

@keyframes vzDraw {
  0%        { stroke-dashoffset: 100; opacity: 0; }
  8%        { opacity: 1; }
  36%       { stroke-dashoffset: 0;   opacity: 1; }
  60%, 100% { stroke-dashoffset: 0;   opacity: 0; }
}

.vz-marker { animation: vzMarker 9s var(--ease) infinite; }

@keyframes vzMarker {
  0%        { transform: translateY(0);    opacity: 0; }
  10%       { opacity: 1; }
  86%       { transform: translateY(90px); opacity: 0.9; }
  100%      { transform: translateY(90px); opacity: 0; }
}

/* ---- Numerology: numbers, pattern, interpretation --------------------- */
.vz-cell, .vz-digit {
  opacity: 0.42;
  animation: vzCell 10s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 0.4s);
}

.vz-digit { font-family: var(--font-num); font-weight: 500; }

@keyframes vzCell {
  0%, 100% { opacity: 0.42; }
  10%      { opacity: 1; }
  28%      { opacity: 0.55; }
}

.vz-pattern {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  opacity: 0;
  animation: vzPattern 10s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 5s);
}

@keyframes vzPattern {
  0%        { stroke-dashoffset: 100; opacity: 0; }
  6%        { opacity: 0.9; }
  30%       { stroke-dashoffset: 0;   opacity: 0.9; }
  48%, 100% { stroke-dashoffset: 0;   opacity: 0; }
}

/* ---- Synthesis: three streams into one ------------------------------- */
.vz-stream {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: vzStream 11s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 0.5s);
}

@keyframes vzStream {
  0%   { stroke-dashoffset: 100; stroke-opacity: 0.14; }
  32%  { stroke-dashoffset: 0;   stroke-opacity: 0.75; }
  72%  { stroke-opacity: 0.28; }
  100% { stroke-dashoffset: 0;   stroke-opacity: 0.14; }
}

.vz-converge {
  animation: vzConverge 11s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * 1.3s);
}

@keyframes vzConverge {
  0%        { transform: translateX(0);    opacity: 0; }
  14%       { opacity: 1; }
  76%       { transform: translateX(88px); opacity: 0.6; }
  100%      { transform: translateX(88px); opacity: 0; }
}

/* Hover lifts the instrument a little — discovery, not decoration. */
.card-link:hover .viz,
.system-block:hover .viz { filter: brightness(1.1) saturate(1.05); }
.card-link:hover .vz-core { transform: scale(1.07); }

/* Phones keep the primary motion and drop the decorative extras. */
@media (max-width: 720px) {
  .vz-spark, .vz-orbit-c { display: none; }
  .vz-ring-3 { animation-duration: 140s; }
}


/* ==========================================================================
   14. Phone lens state
   --------------------------------------------------------------------------
   Not a resized desktop SVG. Three things change:

   1. The viewBox crops to the instrument (set in scene.js) so no width is
      wasted on the margins the desktop threads used to occupy.
   2. The decorative aura is scaled to 0.62 so its paint stays inside that
      crop. At full size it reached r=470 in an 750-unit box, which is why
      the lens spilled past both screen edges and got clipped by .hero.
   3. overflow is clipped to the viewBox, so decoration can never enlarge
      the layout box again.

   Everything structural stays visible: glass, bevel, both ring planes,
   glyph band, core, and the analytical layers.
   ========================================================================== */
@media (max-width: 899px) {
  .optic-svg { overflow: hidden; }
  .op-aura { --aura: 0.62; }

  /* keep the instrument comfortably within the screen */
  .optic-stage { width: min(100%, 92vw); margin-inline: auto; }
}

/* ==========================================================================
   15. Phone chip rail depth
   --------------------------------------------------------------------------
   Seven equal chips read as a menu. Weighting them by distance from the
   active one turns the rail into incoming signals, and the edge mask stops
   it looking like a row cut off by the screen.
   ========================================================================== */
@media (max-width: 899px) {
  .chip-column {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
  }

  .chip-column .chip          { opacity: 0.38; }
  .chip-column .chip.is-warm  { opacity: 0.66; }
  .chip-column .chip.is-active { opacity: 1; }
}
