/* Stage effects (js/fx): only what the animations need. Decorative layers never take pointer events. */
.fx-layer { position: absolute; pointer-events: none; }
.fx-anim { will-change: transform, opacity; }

/* js/fx/gate.js: a released page not yet revealed this session shows the closed frame until the curtain parts. */
.fx-closed [data-reveal=open] .curtain-closed { display: block; }
.fx-closed [data-reveal=open] .curtain-open { display: none; }
.fx-closed [data-reveal=open] .hero-copy { --text: var(--stage-curtain-text); --accent: var(--gold); --halo: var(--halo-dark); }
.fx-closed [data-reveal=open] .hero-copy::before { opacity: 0; }
.fx-closed [data-reveal=open] .hero :is(.subject, .status, .btn-cta) { opacity: 0; }
[data-reveal=open] .hero-copy::before { transition: opacity .4s; }

.hero-spill.fx-on { display: block; position: absolute; opacity: 0; background: radial-gradient(closest-side, var(--stage-lamp), transparent); }
.fx-glow { position: absolute; z-index: -1; border-radius: calc(var(--u) * 20); background: var(--gold); filter: blur(calc(var(--u) * 14)); pointer-events: none; }
/* .hero .btn: outranks .stage .btn { position: relative } below and in css/stage.css, which put Skip in the flow. */
.hero .btn.fx-skip { position: absolute; z-index: 2; top: calc(var(--u) * 12); right: calc(var(--u) * 16); min-width: 44px; min-height: 44px; }

/* Countdown digits (js/fx/countdown.js): each in a clipped cell, so a sliding digit never moves its neighbours. */
.countdown[hidden] { display: none; }
.cd-cell { display: inline-block; position: relative; clip-path: inset(0); }
.cd-cell > span { display: inline-block; }
.cd-cell > .cd-old { position: absolute; left: 0; top: 0; }
.fx-say { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- ambient (js/fx, docs/ANIMATION-SPEC.md 5 to 13): only opacity and transforms change ---------- */
/* Snow: fixed to the viewport, after .scene in the page, so above the plates and below main. */
.fx-snow { position: fixed; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }

/* Lights: a bloom per point, centred on it; additive. Low power: three keyframe variants instead of per-frame maths. */
.scene .fx-light { position: absolute; translate: -50% -50%; pointer-events: none; mix-blend-mode: screen; opacity: 0; }
.fx-lamp { border-radius: 50%; background: radial-gradient(closest-side, var(--stage-lamp), transparent); }
.scene .fx-window { object-fit: contain; } /* .scene .fx-*: outranks .scene-band img */
.fx-low-a { animation: fx-flicker 4.3s ease-in-out infinite; }
.fx-low-b { animation: fx-flicker 5.9s ease-in-out infinite; }
.fx-low-c { animation: fx-flicker 7.1s ease-in-out infinite; }
@keyframes fx-flicker { 0%, 100% { opacity: .8; } 23% { opacity: .9; } 41% { opacity: .72; } 44% { opacity: .66; } 47% { opacity: .78; } 70% { opacity: .86; } }

/* Chairlift: each chair hangs from the top of its hanger; dark mode draws it at 70% brightness (fixed, not animated). */
.scene .fx-chair { position: absolute; left: calc(var(--u) * -17); top: 0; width: calc(var(--u) * 34); height: auto; transform-origin: 50% 0; pointer-events: none; will-change: transform, opacity; }
/* The phone plate draws the lift at about half the desktop scale, so its chairs are half as wide. */
@media (max-width: 699px) { .scene .fx-chair { left: calc(var(--u) * -8.5); width: calc(var(--u) * 17); } }
:root[data-mode=dark] .fx-chair { filter: brightness(.7); }
@media (prefers-color-scheme: dark) { :root:not([data-mode]) .fx-chair { filter: brightness(.7); } }

/* Sky, dark mode only: stars twinkle and the moon halo breathes. */
.fx-star { position: absolute; border-radius: 50%; background: var(--stage-snow); animation: fx-twinkle ease-in-out infinite alternate; }
.fx-halo { position: absolute; aspect-ratio: 1; translate: -50% -50%; border-radius: 50%; background: radial-gradient(closest-side, color-mix(in srgb, var(--stage-snow) 45%, transparent), transparent); animation: fx-halo 7s ease-in-out infinite alternate; }
.fx-star.fx-still { animation: none; opacity: .7; }
.fx-halo.fx-still { animation: none; opacity: .4; }
:root[data-mode=light] :is(.fx-star, .fx-halo) { display: none; }
@media (prefers-color-scheme: light) { :root:not([data-mode]) :is(.fx-star, .fx-halo) { display: none; } }
@keyframes fx-twinkle { from { opacity: .2; } to { opacity: 1; } }
@keyframes fx-halo { from { opacity: .35; transform: scale(1); } to { opacity: .5; transform: scale(1.06); } }

/* The ESA Times: the shadow is twice as dark at half opacity (the same pixels at rest), so the hover can deepen it. */
.paper-shadow { opacity: .5; box-shadow: 0 calc(var(--u) * 10) calc(var(--u) * 24) color-mix(in srgb, var(--sky) 90%, transparent); }
.fx-paper-wait :is(.paper-sheet, .paper-shadow) { opacity: 0; }
.fx-hover :is(.paper-sheet, .paper-shadow) { transition: transform .32s cubic-bezier(.3, .7, .3, 1), opacity .32s cubic-bezier(.3, .7, .3, 1); }
.fx-hover:hover :is(.paper-sheet, .paper-shadow) { transition-duration: .24s; }
.fx-hover:hover .paper-sheet { transform: translateY(calc(var(--u) * -4)) rotate(-.3deg); }
.fx-hover:hover .paper-shadow { opacity: .72; transform: translateY(calc(var(--u) * 6)) scale(1.04); }

/* The grid's box (arcade.grid) in page terms: css/stage.css measured its left from the screen's left edge as if that were the page centre. */
.arcade-grid { left: calc(50% - var(--u) * 296); }
@media (max-width: 699px) { .arcade-grid { left: calc(50% - var(--u) * 130); } }
/* Arcade title screen: the play button breathes with a ring going out behind it; a press flashes. */
.fx-arcade .arcade-play { animation: fx-pulse 2.4s ease-in-out infinite alternate; }
.fx-ring { position: absolute; z-index: -1; inset: calc(var(--u) * -2); border: calc(var(--u) * 2) solid var(--gold); border-radius: 50%; pointer-events: none; animation: fx-ring 1.8s ease-out infinite; }
.fx-flash, .fx-lit { position: absolute; z-index: 2; pointer-events: none; opacity: 0; }
.fx-flash { border-radius: 50%; background: radial-gradient(closest-side, var(--stage-snow), color-mix(in srgb, var(--gold) 70%, transparent) 45%, transparent); }
.fx-lit { background: radial-gradient(closest-side, color-mix(in srgb, var(--stage-snow) 80%, transparent), transparent); }
@keyframes fx-pulse { to { transform: scale(1.04); } }
@keyframes fx-ring { from { transform: scale(1); opacity: .55; } to { transform: scale(1.55); opacity: 0; } }

/* Section entrances (js/fx/entrances.js): hidden only once the script has added .fx, and only until each comes in. */
.fx [data-enter]:not(.fx-in) { opacity: 0; }

/* Buttons and links: a gold glow and an arrow nudge on hover, a small press. Touch screens get the press only; reduced motion, colour only. */
@media (prefers-reduced-motion: no-preference) {
  .stage .btn { position: relative; isolation: isolate; transition: color .15s, background-color .15s, border-color .15s, scale .16s, translate .16s; }
  .stage .btn:active { scale: .97; translate: 0 calc(var(--u) * 1); transition-duration: .15s, .15s, .15s, .08s, .08s; }
  .stage .btn-arrow { transition: translate .24s cubic-bezier(.3, .7, .3, 1); }
  .stage .text-link { position: relative; }
  .stage .text-link::after { content: ""; position: absolute; left: 0; right: 0; bottom: calc(var(--u) * -3); height: calc(var(--u) * 1.5); background: currentColor; scale: 0 1; transform-origin: 0 50%; transition: scale .2s ease-out; }
  @media (hover: hover) {
    .stage .btn::after { content: ""; position: absolute; z-index: -1; inset: 0; border-radius: inherit; box-shadow: 0 0 calc(var(--u) * 16) color-mix(in srgb, var(--gold) 55%, transparent); opacity: 0; transition: opacity .24s ease-out; pointer-events: none; }
    .stage .btn:hover::after { opacity: 1; transition-duration: .18s; }
    .stage :is(.btn, .text-link):hover .btn-arrow { translate: calc(var(--u) * 4) 0; transition-duration: .18s; }
    .stage .text-link:hover::after { scale: 1 1; }
  }
}
