/* ============================================================================
 * chazon · elements · v26.css
 * Loaded AFTER elements.css. Pure additive — overrides nothing destructive.
 *
 *   1. Section life states (active / prebuffer / sleep)
 *   2. Reaction-Lab apparatus icons in mode buttons
 *   3. Reaction-FX overlay animations
 *   4. Section atmospheric backgrounds (8 sections)
 *   5. Mobile tuning
 *   6. Reduced-motion fallback
 * ============================================================================ */

/* ============================================================================
 * 1 · SECTION LIFE
 *   Sleep sections opt out of layout and paint using content-visibility.
 *   Their animations + transitions are forcibly paused so off-screen GPU
 *   work hits zero. Prebuffer keeps the DOM warm but pauses motion. Active
 *   gets the full painted-and-animated treatment.
 *
 *   Note: we intentionally DO NOT set content-visibility on .s-active or
 *   .s-prebuffer — only sleep. That avoids any "skeleton" placeholder
 *   flash during scrolling.
 * ============================================================================ */

section.s-sleep {
  /* Browser may skip layout, paint, AND style for descendants of this box.
   * Massive perf win on long pages with 24 sections like ours. */
  content-visibility: auto;
  /* Hint at the rendered height so the scrollbar isn't jumpy when
   * sleep-sections "wake up". 100vh is a safe rough average for our
   * sections; the browser uses this only when content-visibility is
   * actually hiding the section. */
  contain-intrinsic-size: 1px 100vh;
}

section.s-sleep *,
section.s-sleep *::before,
section.s-sleep *::after {
  /* Paused animations cost almost nothing, vs running animations which
   * pin the GPU at 60Hz. */
  animation-play-state: paused !important;
}

section.s-prebuffer *,
section.s-prebuffer *::before,
section.s-prebuffer *::after {
  /* Prebuffered: assets decoded, but motion still paused — instant
   * transition to active when user scrolls. */
  animation-play-state: paused !important;
}

section.s-active {
  /* No special declarations — the section just runs normally. This rule
   * exists for selector specificity / clarity. */
}

/* When the user has reduced-motion enabled, treat prebuffer essentially
 * the same as active (no point pausing what isn't moving). */
@media (prefers-reduced-motion: reduce) {
  section.s-prebuffer *,
  section.s-prebuffer *::before,
  section.s-prebuffer *::after {
    animation-play-state: running !important;
  }
}

/* ============================================================================
 * 2 · REACTION-LAB MODE BUTTONS — apparatus icons
 *   Small WebP icon prepended to "Science / Learn / Play" tab buttons.
 *   Idle floating animation; subtly brighter on hover; glow on active.
 * ============================================================================ */

.rxn-mode-btn {
  /* Make room for the icon */
  display: inline-flex !important;
  align-items: center;
  gap: 0.4em;
  position: relative;
}

.rxn-mode-icon {
  width: 18px;
  height: 18px;
  display: inline-block;
  object-fit: contain;
  opacity: 0.65;
  filter: drop-shadow(0 0 6px rgba(230, 184, 0, 0.0));
  transition: opacity 220ms ease, filter 320ms ease, transform 320ms ease;
  animation: rxnIconFloat 4.8s ease-in-out infinite;
  /* Slight stagger between the three buttons so they don't bob in unison */
  animation-delay: var(--icon-bob-delay, 0s);
  pointer-events: none;
  will-change: transform;
}

.rxn-mode-btn:nth-child(1) .rxn-mode-icon { --icon-bob-delay: 0s;    }
.rxn-mode-btn:nth-child(2) .rxn-mode-icon { --icon-bob-delay: -1.6s; }
.rxn-mode-btn:nth-child(3) .rxn-mode-icon { --icon-bob-delay: -3.2s; }

.rxn-mode-btn:hover .rxn-mode-icon {
  opacity: 0.95;
  filter: drop-shadow(0 0 8px rgba(230, 184, 0, 0.35));
}

.rxn-mode-btn.active .rxn-mode-icon {
  opacity: 1;
  filter: drop-shadow(0 0 10px rgba(230, 184, 0, 0.6));
  transform: scale(1.06);
}

@keyframes rxnIconFloat {
  0%, 100% { transform: translateY(0)    rotate(0deg); }
  50%      { transform: translateY(-2px) rotate(-1.5deg); }
}

/* ============================================================================
 * 3 · REACTION-FX OVERLAY
 *   When a reaction completes, a single FX sprite overlays the result
 *   card based on the meta tags. Lives ~2.4s, never blocks clicks.
 *
 *   Per-FX motion variants: light bursts radially, crystal grows in,
 *   bubbles drift upward, swirl rotates, etc.
 * ============================================================================ */

.rxn-fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: inherit;
  z-index: 5;
  opacity: 0;
  /* Soft cross-fade in/out — keyframes below override per-FX */
}

.rxn-fx-img {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 70%;
  max-width: 360px;
  transform: translate(-50%, -50%);
  display: block;
  filter: drop-shadow(0 0 24px rgba(230, 184, 0, 0.35));
  mix-blend-mode: screen;
}

.rxn-fx-glow {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 110%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(0.6);
  background: radial-gradient(circle at center,
              rgba(255, 220, 130, 0.18) 0%,
              rgba(255, 220, 130, 0.06) 30%,
              transparent 60%);
  opacity: 0;
  mix-blend-mode: screen;
}

/* universal "go" trigger — applies the base in/out */
.rxn-fx.rxn-fx-go {
  animation: rxnFxOuter 2400ms ease-out forwards;
}
.rxn-fx.rxn-fx-go .rxn-fx-glow {
  animation: rxnFxGlow 2400ms ease-out forwards;
}

@keyframes rxnFxOuter {
  0%   { opacity: 0; }
  10%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes rxnFxGlow {
  0%   { opacity: 0;    transform: translate(-50%, -50%) scale(0.4); }
  18%  { opacity: 0.95; transform: translate(-50%, -50%) scale(1.1); }
  60%  { opacity: 0.55; transform: translate(-50%, -50%) scale(1.4); }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(1.8); }
}

/* ---- Per-FX image motion variants ---- */

/* Light — radiant burst, scales out aggressively */
.rxn-fx-light.rxn-fx-go  .rxn-fx-img { animation: rxnFxLight  2400ms cubic-bezier(.12,.66,.36,1) forwards; }
@keyframes rxnFxLight {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.3); filter: drop-shadow(0 0 0 rgba(255,200,80,0)) brightness(2); }
  18%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05); filter: drop-shadow(0 0 38px rgba(255,200,80,0.9)) brightness(1.8); }
  60%  { opacity: 0.7; transform: translate(-50%, -50%) scale(1.18); filter: drop-shadow(0 0 24px rgba(255,200,80,0.5)) brightness(1.2); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.3); }
}

/* Spark — quick jittery flash */
.rxn-fx-spark.rxn-fx-go  .rxn-fx-img { animation: rxnFxSpark  2400ms steps(8, end) forwards; }
@keyframes rxnFxSpark {
  0%   { opacity: 0;    transform: translate(-50%, -50%) scale(0.5) rotate(0deg); }
  6%   { opacity: 1;    transform: translate(-50%, -52%) scale(1)   rotate(8deg); }
  18%  { opacity: 0.6;  transform: translate(-51%, -49%) scale(1.1) rotate(-6deg); }
  30%  { opacity: 1;    transform: translate(-49%, -51%) scale(1.05) rotate(4deg); }
  60%  { opacity: 0.4;  transform: translate(-50%, -50%) scale(1.15) rotate(-2deg); }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(1.2) rotate(0deg); }
}

/* Crystal — slow grow + faint rotation */
.rxn-fx-crystal.rxn-fx-go .rxn-fx-img { animation: rxnFxCrystal 2400ms cubic-bezier(.2,.7,.3,1) forwards; }
@keyframes rxnFxCrystal {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(0.25) rotate(-12deg); filter: drop-shadow(0 0 0 rgba(160,220,255,0)); }
  35%  { opacity: 0.95; transform: translate(-50%, -50%) scale(0.95) rotate(0deg); filter: drop-shadow(0 0 22px rgba(160,220,255,0.6)); }
  80%  { opacity: 0.8; transform: translate(-50%, -50%) scale(1.02) rotate(3deg); }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(1.05) rotate(5deg); }
}

/* Bubbles — drift up + fade */
.rxn-fx-bubbles.rxn-fx-go .rxn-fx-img { animation: rxnFxBubbles 2400ms ease-out forwards; }
@keyframes rxnFxBubbles {
  0%   { opacity: 0;   transform: translate(-50%, 10%) scale(0.7); }
  20%  { opacity: 1;   transform: translate(-50%, -30%) scale(0.95); }
  70%  { opacity: 0.7; transform: translate(-50%, -85%) scale(1.05); }
  100% { opacity: 0;   transform: translate(-50%, -120%) scale(1.1); }
}

/* Steam — slow upward billow */
.rxn-fx-steam.rxn-fx-go .rxn-fx-img { animation: rxnFxSteam 2400ms ease-out forwards; filter: drop-shadow(0 0 30px rgba(230,240,255,0.4)); }
@keyframes rxnFxSteam {
  0%   { opacity: 0;   transform: translate(-50%, 0%) scale(0.6); }
  25%  { opacity: 0.8; transform: translate(-50%, -25%) scale(0.95); }
  70%  { opacity: 0.6; transform: translate(-50%, -65%) scale(1.15); }
  100% { opacity: 0;   transform: translate(-50%, -95%) scale(1.3); }
}

/* Smoke — denser, slower drift, slight rotation */
.rxn-fx-smoke.rxn-fx-go .rxn-fx-img { animation: rxnFxSmoke 2400ms ease-out forwards; mix-blend-mode: normal; filter: brightness(0.85) drop-shadow(0 0 18px rgba(80,80,90,0.4)); }
@keyframes rxnFxSmoke {
  0%   { opacity: 0;   transform: translate(-50%, 0%) scale(0.55) rotate(0deg); }
  30%  { opacity: 0.7; transform: translate(-50%, -20%) scale(0.9) rotate(2deg); }
  75%  { opacity: 0.5; transform: translate(-50%, -55%) scale(1.1) rotate(-3deg); }
  100% { opacity: 0;   transform: translate(-50%, -90%) scale(1.2) rotate(4deg); }
}

/* Swirl — full rotation + scale */
.rxn-fx-swirl.rxn-fx-go .rxn-fx-img { animation: rxnFxSwirl 2400ms cubic-bezier(.4,.2,.4,1) forwards; }
@keyframes rxnFxSwirl {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(0.4) rotate(0deg); }
  20%  { opacity: 1;   transform: translate(-50%, -50%) scale(1) rotate(180deg); }
  70%  { opacity: 0.7; transform: translate(-50%, -50%) scale(1.05) rotate(540deg); }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(1.1) rotate(720deg); }
}

/* Field — magnetic pulse: scale-in, soft tremor, fade */
.rxn-fx-field.rxn-fx-go .rxn-fx-img { animation: rxnFxField 2400ms ease-out forwards; filter: drop-shadow(0 0 26px rgba(120,180,255,0.55)); }
@keyframes rxnFxField {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(0.3); }
  20%  { opacity: 1;   transform: translate(-50%, -50%) scale(1); }
  35%  { opacity: 0.85; transform: translate(-50.5%, -50%) scale(1.02); }
  50%  { opacity: 0.9; transform: translate(-49.5%, -50%) scale(1.04); }
  80%  { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(1.15); }
}

/* ============================================================================
 * 4 · SECTION ATMOSPHERIC BACKGROUNDS
 *   Eight sections opt-in to a single environment tile painted as ::after.
 *   Heavily blurred, low opacity, sits behind all content via z-index. Only
 *   loaded when the section is on/near screen (.lab-bg-on class controls).
 *
 *   The tile path is read from the data-lab-bg attribute set by JS.
 * ============================================================================ */

section.has-lab-bg {
  /* Ensure layered children can stack above the ::after layer */
  position: relative;
  isolation: isolate;
}

section.has-lab-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: none;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0;
  filter: blur(28px) brightness(0.55) saturate(0.6);
  transition: opacity 800ms ease;
  mix-blend-mode: screen;
}

/* Lazy resolve: only when the section is awake do we set background-image.
 * The image URL is computed via the data-lab-bg attribute. This means a
 * sleeping section never decodes its bitmap. */
section.has-lab-bg.lab-bg-on::after {
  opacity: 0.085;
}

/* Per-section image bindings, driven by the data-attribute. CSS-only,
 * one rule per environment tile. */
section[data-lab-bg="window"].lab-bg-on::after        { background-image: url("lab-environment/window.webp?v=20260517vv"); }
section[data-lab-bg="holo-display"].lab-bg-on::after  { background-image: url("lab-environment/holo-display.webp?v=20260517vv"); }
section[data-lab-bg="tesla-coil"].lab-bg-on::after    { background-image: url("lab-environment/tesla-coil.webp?v=20260517vv"); }
section[data-lab-bg="instruments"].lab-bg-on::after   { background-image: url("lab-environment/instruments.webp?v=20260517vv"); }
section[data-lab-bg="aquarium"].lab-bg-on::after      { background-image: url("lab-environment/aquarium.webp?v=20260517vv"); }
section[data-lab-bg="bookshelf"].lab-bg-on::after     { background-image: url("lab-environment/bookshelf.webp?v=20260517vv"); }
section[data-lab-bg="periodic-table"].lab-bg-on::after{ background-image: url("lab-environment/periodic-table.webp?v=20260517vv"); }
section[data-lab-bg="element-jars"].lab-bg-on::after  { background-image: url("lab-environment/element-jars.webp?v=20260517vv"); }
section[data-lab-bg="portal"].lab-bg-on::after        { background-image: url("lab-environment/portal.webp?v=20260517vv"); }

/* Make sure section content sits above the ::after */
section.has-lab-bg > * { position: relative; z-index: 1; }

/* ============================================================================
 * 5 · MOBILE TUNING
 *   On phones (<= 768px) we halve the section-bg opacity, drop the FX size,
 *   and ensure mode-icons remain crisp. We do NOT disable the system — the
 *   atmosphere is part of the identity — just make it polite.
 * ============================================================================ */

@media (max-width: 768px) {
  section.has-lab-bg.lab-bg-on::after {
    opacity: 0.05;
    filter: blur(20px) brightness(0.5) saturate(0.55);
  }
  .rxn-fx-img {
    width: 80%;
    max-width: 240px;
  }
  .rxn-mode-icon {
    width: 16px;
    height: 16px;
  }
  /* Tighter intrinsic-size hint for mobile: sections are taller relative
   * to viewport, so default 100vh is too generous and produces scroll-
   * height inflation. */
  section.s-sleep {
    contain-intrinsic-size: 1px 140vh;
  }
}

/* Very small screens: tiles get even more subtle so the eye can rest
 * on the cards, not the backdrop. */
@media (max-width: 480px) {
  section.has-lab-bg.lab-bg-on::after {
    opacity: 0.035;
  }
}

/* ============================================================================
 * 6 · REDUCED-MOTION FALLBACK
 *   Users who set "reduce motion" in their OS get a clean static version:
 *   FX images appear and fade, but don't bounce/swirl/burst. Mode-icons
 *   stop their floating bob. Section-tiles still appear (they're static).
 * ============================================================================ */

@media (prefers-reduced-motion: reduce) {
  .rxn-mode-icon { animation: none !important; }
  .rxn-fx.rxn-fx-go .rxn-fx-img,
  .rxn-fx.rxn-fx-go .rxn-fx-glow {
    animation: rxnFxOuter 1500ms ease-in-out forwards !important;
    transform: translate(-50%, -50%) scale(1) !important;
  }
}

/* ============================================================================
 * 7 · DEFENSE — keep z-index sane for sections that already use absolute
 *   children (phone-section, body-section, etc.). The has-lab-bg ::after
 *   shouldn't cover those because we made them z-index: 1, but for
 *   sections where they may NOT be transparent layers, ensure ::after
 *   never trumps them.
 * ============================================================================ */

#phone, #body, #helix, #earth-explorer {
  /* These sections have their own complex stacks (canvas, parts grids).
   * Make absolutely sure ::after sits behind. */
}
#phone.has-lab-bg::after,
#body.has-lab-bg::after,
#helix.has-lab-bg::after,
#earth-explorer.has-lab-bg::after {
  z-index: -1;
}
