/* Reactive states and morphing components. Composition lives in scene.css,
   skin in app.css. Everything here is driven by data-* attributes or the
   runtime variables declared in tokens.css. */

/* ── Registration capacity drives the whole surface. ── */
html.env-warning .registration-state[data-state="open"] { animation: warn-pulse 1.2s ease-in-out infinite; }
html.env-full .stats { box-shadow: inset 0 0 0 1px var(--ember); }
html[data-capacity="hot"] .stat:first-child strong,
html[data-capacity="critical"] .stat:first-child strong { text-shadow: 0 0 44px color-mix(in srgb, var(--ember) calc(var(--env-scarlet) * 90%), transparent); }
html[data-capacity="full"] .stat:first-child strong { color: var(--ember); }

/* Pointer parallax, mouse only. Gated behind a fine pointer so a finger swiping
   the page never drags the interface along with it. Uses the standalone
   `translate` property on purpose: `transform` is already owned by the GSAP
   scroll timelines and the drift keyframes, and `translate` composes with it. */
@media (hover: hover) and (pointer: fine) {
  body::after, .hero::before, .hero-marks {
    translate: calc(var(--ptr-dx) * -7px) calc(var(--ptr-dy) * -5px);
    transition: translate .3s var(--ease-out);
  }
  .hero-body { translate: calc(var(--ptr-dx) * 3px) calc(var(--ptr-dy) * 2px); }
}

.reactive-home { position: relative; }
.bits-scene { position: relative; isolation: isolate; }
.bits-echo-line {
  position: absolute; left: var(--gutter); right: var(--gutter); top: 18px;
  height: 1px; transform-origin: left center; opacity: 0; pointer-events: none; z-index: 4;
  background: linear-gradient(90deg, var(--ember), transparent 70%);
}
.bits-char, .bits-word-inner { display: inline-block; will-change: transform, filter, opacity; }
.bits-word { display: inline-block; overflow: hidden; vertical-align: bottom; }
.bits-space { white-space: pre; }
.bits-gradual-blur {
  position: fixed; inset: auto 0 0; height: 22vh; z-index: 6; pointer-events: none;
  background: linear-gradient(transparent, color-mix(in srgb, var(--coal) 55%, transparent));
  backdrop-filter: blur(calc(var(--scroll-blur) * 10px));
  -webkit-mask-image: linear-gradient(transparent, #000 70%);
  mask-image: linear-gradient(transparent, #000 70%);
  opacity: calc(.12 + var(--scroll-home) * .3);
}
.bits-glare { position: relative; overflow: hidden; }
.bits-glare::after {
  content: ''; position: absolute; inset: -40%; pointer-events: none; opacity: 0;
  background: radial-gradient(circle at var(--glare-x) var(--glare-y), color-mix(in srgb, var(--paper) 28%, transparent), transparent 42%);
  mix-blend-mode: screen; transition: opacity .25s;
}
.bits-glare:hover::after { opacity: 1; }

/* Count-up numbers. boot.js writes .count-value and animateCounts toggles .tick. */
.count-value { display: inline-block; }
.count-value.tick { animation: digit-lock .55s var(--ease-out) both; }

/* Scroll bands drive the ambient layers only. Sections themselves stay fully
   opaque — fading the copy out on scroll is what made the page feel like it was
   dissolving instead of scrolling. */
html[data-scroll-band="prize"] .hero::before { opacity: calc(.3 - var(--echo-hero-out) * .16); }
html[data-scroll-band="entry"] .prize-scene { background-position: center calc(var(--echo-prize-out) * 12%); }

/* Type echoes scroll and pointer without moving layout. */
.ash-whisper, .live-whisper { transition: opacity .35s, filter .35s, transform .35s; }
.ash-whisper.is-flicker, .live-whisper.is-flicker { opacity: .35; filter: blur(3px); transform: translateY(-2px); }
.bits-spark {
  position: fixed; z-index: 5; pointer-events: none; opacity: 0;
  color: color-mix(in srgb, var(--ember) 70%, var(--paper));
  font-size: clamp(10px, 1.6vw, 16px);
  text-shadow: 0 0 12px var(--glow);
  animation: spark-drift 2.6s ease-out both;
}

/* Ghost team name — the team name is typed into the scene, not just a field. */
.ghost-type {
  position: relative; z-index: 0; pointer-events: none;
  max-width: 100%; margin: 16px 0 8px; overflow: hidden;
  font-family: var(--number-font); font-weight: 700;
  font-size: clamp(28px, 4.6vw, 54px); line-height: 1; letter-spacing: .06em;
  text-transform: uppercase; white-space: nowrap; text-overflow: ellipsis;
  color: color-mix(in srgb, var(--ember) 42%, transparent);
  transition: color .25s;
}
.ghost-type.has-value { color: color-mix(in srgb, var(--paper) 55%, transparent); }
.ghost-type.is-locked { color: var(--ember); }
.ghost-lock { position: relative; z-index: 2; margin: 0 0 12px; font: 12px var(--number-font); letter-spacing: .2em; color: var(--ember); }

/* Stage morph components. */
.reactive-stage { position: relative; z-index: 1; isolation: isolate; }
.stage-choices {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;
  margin: 0 0 18px; padding: 0; min-height: 0; background: transparent;
}
.stage-option {
  min-width: 0; min-height: 112px;
  display: grid; place-content: center; gap: 6px; text-align: center;
  padding: 18px 12px; cursor: pointer; color: var(--paper);
  border: 1px solid var(--line); background: color-mix(in srgb, var(--panel) 92%, transparent);
  transition: transform .2s, opacity .2s, border-color .2s, box-shadow .2s, background .2s;
}
.stage-option:hover, .stage-option[data-state="hover"] {
  border-color: var(--ember); transform: translateY(-2px);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--ember) 14%, transparent);
}
.stage-option.is-active, .stage-option[data-state="selected"] {
  border-color: var(--ember);
  background: linear-gradient(160deg, color-mix(in srgb, var(--ember-dark) 55%, transparent), var(--panel));
  box-shadow: inset 0 0 0 1px var(--ember), 0 0 24px color-mix(in srgb, var(--ember) 16%, transparent);
}
.stage-option.is-dimmed { opacity: .42; transform: none; }
/* The picked seat says what it is (2026-09-13), in the same attribute-driven way the
   filled fields say `data-check`. Choosing a seat is a choice, not a finished
   registration, and a mark that reads as the second one costs somebody an evening. */
.stage-option[data-picked] { position: relative; }
.stage-option.is-active[data-picked]::after {
  content: attr(data-picked); position: absolute; top: 10px; right: 10px;
  padding: 2px 8px; border: 1px solid var(--ember);
  font-size: 11px; letter-spacing: .08em; color: var(--ember);
}
.stage-en { font-family: var(--number-font); font-size: clamp(16px, 2.4vw, 26px); font-weight: 700; letter-spacing: .1em; overflow-wrap: anywhere; }
.stage-zh { font-size: 13px; color: var(--muted); }
.stage-detail { margin-top: 8px; padding-top: 18px; overflow: hidden; border-top: 1px solid var(--line-hot); animation: stage-in .45s ease both; }

/* Roster nodes complete the scene as seats fill. */
.roster-scene { display: grid; gap: 10px; margin: 18px 0 28px; }
.roster-node {
  position: relative; display: grid; grid-template-columns: 14px 1fr auto;
  gap: 8px 14px; align-items: center; padding: 16px 18px;
  border: 1px solid var(--line); background: var(--panel);
  transition: transform .35s, border-color .35s, background .35s, box-shadow .35s;
}
.roster-node[data-phase="empty"] { opacity: .7; }
.roster-node[data-phase="verified"], .roster-node[data-phase="connected"] {
  border-color: var(--ember);
  background: linear-gradient(105deg, color-mix(in srgb, var(--ember-dark) 40%, transparent), var(--panel));
  box-shadow: 0 0 24px color-mix(in srgb, var(--ember) 12%, transparent);
  animation: node-in .55s cubic-bezier(.2, .9, .2, 1) both;
}
.node-signal { width: 8px; height: 8px; border-radius: 50%; background: color-mix(in srgb, var(--paper) 25%, transparent); }
.roster-node[data-phase="connected"] .node-signal, .roster-node[data-phase="verified"] .node-signal {
  background: var(--ember); box-shadow: 0 0 12px var(--ember); animation: signal 1.4s ease-in-out infinite;
}
.roster-node strong { grid-column: 2; }
.roster-node small { grid-column: 2; color: var(--muted); font-family: var(--number-font); letter-spacing: .14em; }
.roster-node span:last-child { grid-column: 3; grid-row: 1 / span 2; text-align: right; color: var(--muted); font-size: 13px; }

html[data-quality="REDUCED"] .ghost-type { font-size: 24px; }
html[data-quality="REDUCED"] .stage-option.is-dimmed { opacity: .5; }

@keyframes stage-in { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes node-in { from { opacity: 0; transform: translateX(-16px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes signal { 50% { transform: scale(1.4); opacity: .55; } }
@keyframes warn-pulse { 50% { opacity: .55; text-shadow: 0 0 12px var(--ember); } }
@keyframes spark-drift {
  0% { opacity: 0; transform: translateY(8px) scale(.8); }
  20% { opacity: .85; }
  100% { opacity: 0; transform: translateY(-48px) scale(1.1); }
}

@media (max-width: 720px) {
  .ghost-type { font-size: clamp(22px, 8vw, 36px); }
  .bits-gradual-blur { height: 16vh; }
}

@media (prefers-reduced-motion: reduce) {
  .stage-option, .roster-node, .entry-morph li { transition: none !important; animation: none !important; }
  .bits-gradual-blur, .bits-echo-line, .bits-spark, .bits-glare::after { display: none !important; }
  body::after, .hero::before, .hero-marks, .hero-body { translate: none !important; transition: none !important; }
}
