/* ═══════════════════════════════════════════════════════════════════
   psfx-weather.css — environment ambience by location.

   The canvas holds the particles (one layer, one rAF loop in psfx-weather.js).
   Everything here is either a static tint or a compositor-only transform, so
   the ambience costs almost nothing when particles are switched off.
   ═══════════════════════════════════════════════════════════════════ */

/* particle layer — behind the message column, above the backdrop */
.psfx-weather-canvas {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: .75;
}

/* ── environment tint ─────────────────────────────────────────────── */
/* a wash in the colour of the place: rain-blue, ember-orange, snow-white.
   Static gradient, no animation — cheap enough to always leave on. */
#pscRoot.fx-env-on .psc-bg::after {
  background:
    linear-gradient(180deg,
      rgba(var(--fx-env-rgb, 140,155,195), .10) 0%,
      rgba(var(--fx-env-rgb, 140,155,195), .04) 45%,
      transparent 100%),
    linear-gradient(180deg, rgba(8,0,14,.15) 0%, rgba(8,0,14,.5) 55%, rgba(8,0,14,.92) 100%);
}

/* ── per-environment character ────────────────────────────────────── */
/* small touches that make each place read differently beyond its particles */

/* storm — the scene flickers, well under the 3-flashes-per-second limit and
   with a gentle luminance change rather than a hard white flash */
#pscRoot[data-env="storm"] .psc-bg { animation: fxStormFlicker 9s ease-in-out infinite; }
@keyframes fxStormFlicker {
  0%, 92%, 100% { filter: brightness(var(--atmo-bright, 1)); }
  94%           { filter: brightness(calc(var(--atmo-bright, 1) * 1.14)); }
  96%           { filter: brightness(var(--atmo-bright, 1)); }
  98%           { filter: brightness(calc(var(--atmo-bright, 1) * 1.08)); }
}

/* fire — a warm breathing hearth glow from the lower edge */
#pscRoot[data-env="fire"] .psc-shell::before {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(120% 60% at 50% 118%, rgba(255,150,70,.22), transparent 70%);
  animation: fxHearth 4.5s ease-in-out infinite;
}
@keyframes fxHearth { 0%,100% { opacity: .7 } 50% { opacity: 1 } }

/* club — a slow colour wash, like lights moving across the room */
#pscRoot[data-env="club"] .psc-shell::before {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(80% 60% at 20% 30%, rgba(210,90,255,.16), transparent 60%),
              radial-gradient(80% 60% at 80% 70%, rgba(90,190,255,.14), transparent 60%);
  animation: fxClubDrift 12s ease-in-out infinite alternate;
}
@keyframes fxClubDrift { from { transform: translate3d(-3%, 0, 0) } to { transform: translate3d(3%, 0, 0) } }

/* space — a still, cold vignette; nothing should drift much out there */
#pscRoot[data-env="space"] .psc-bg::before { --atmo-vignette: .6; }

/* bath / fog — soften the backdrop as if through steam */
#pscRoot[data-env="bath"] .psc-bg,
#pscRoot[data-env="fog"] .psc-bg { filter: brightness(var(--atmo-bright,1)) blur(1.5px); }

/* ── LEGIBILITY GUARD ─────────────────────────────────────────────── */
/* particles and washes must never cost text contrast. The bubble keeps its own
   opaque-enough backing so prose stays readable over any environment. */
#pscRoot.fx-env-on .bubble { backdrop-filter: none; }
#pscRoot.fx-env-on .chat-msg .bubble { background-color: rgba(26,8,38,.82); }

/* ── RESTRAINT ────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .psfx-weather-canvas { display: none; }
  #pscRoot[data-env="storm"] .psc-bg { animation: none; }
  #pscRoot[data-env="fire"] .psc-shell::before,
  #pscRoot[data-env="club"] .psc-shell::before { animation: none; opacity: .8; }
}
body.psfx-calm .psfx-weather-canvas { display: none; }
body.psfx-calm #pscRoot[data-env="storm"] .psc-bg { animation: none; }
body.psfx-calm #pscRoot[data-env="fire"] .psc-shell::before,
body.psfx-calm #pscRoot[data-env="club"] .psc-shell::before { animation: none; opacity: .7; }

@media (max-width: 820px) {
  .psfx-weather-canvas { opacity: .6; }   /* lighter touch on a small screen */
}

/* ═══ EXTREME WEATHER — scene-level atmosphere ═══════════════════════
   Particles alone don't sell a hurricane. These add the light and motion of
   the sky itself. All transform/opacity/filter on a single layer, and every
   luminance change stays gentle and under the 3-flash-per-second limit. */

/* HURRICANE / TORNADO — the whole scene is being shoved around */
#pscRoot[data-env="hurricane"] .psc-bg,
#pscRoot[data-env="tornado"] .psc-bg { animation: fxLurch 7s ease-in-out infinite; }
@keyframes fxLurch {
  0%,100% { transform: scale(1.03) translate3d(0,0,0); }
  30%     { transform: scale(1.05) translate3d(-1.2%, .4%, 0); }
  65%     { transform: scale(1.04) translate3d(1.0%, -.3%, 0); }
}
#pscRoot[data-env="tornado"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: linear-gradient(105deg, transparent 30%, rgba(120,110,95,.16) 50%, transparent 70%);
  animation: fxDebrisSweep 3.4s linear infinite;
}
@keyframes fxDebrisSweep { from{transform:translateX(-40%)} to{transform:translateX(40%)} }

/* LIGHTNING — storms, thundersnow, hurricanes get real strikes */
#pscRoot[data-env="storm"] .psc-shell::after,
#pscRoot[data-env="hurricane"] .psc-shell::after,
#pscRoot[data-env="thundersnow"] .psc-shell::after {
  content:''; position:absolute; inset:0; z-index:2; pointer-events:none;
  background: radial-gradient(120% 80% at 50% -10%, rgba(210,225,255,.55), transparent 60%);
  opacity:0; animation: fxStrike 11s ease-out infinite;
}
@keyframes fxStrike {          /* two beats per cycle — never more than 3/sec */
  0%,88%,100% { opacity:0 }
  89%  { opacity:.85 }
  90.5%{ opacity:.10 }
  92%  { opacity:.55 }
  94%  { opacity:0 }
}

/* BLIZZARD / WHITEOUT — visibility itself collapses */
#pscRoot[data-env="blizzard"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * 1.1)) contrast(.82) saturate(.6); }
#pscRoot[data-env="blizzard"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: rgba(225,238,255,.16); animation: fxWhiteout 8s ease-in-out infinite;
}
@keyframes fxWhiteout { 0%,100%{opacity:.35} 50%{opacity:.9} }

/* SANDSTORM — a wall of grit rolling through */
#pscRoot[data-env="sandstorm"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * .92)) sepia(.35) contrast(1.1); }
#pscRoot[data-env="sandstorm"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: linear-gradient(100deg, rgba(215,175,110,.30), rgba(190,150,90,.14) 50%, rgba(215,175,110,.30));
  background-size:200% 100%; animation: fxGrit 6s linear infinite;
}
@keyframes fxGrit { from{background-position:0% 0} to{background-position:200% 0} }

/* FIRESTORM — the sky is burning */
#pscRoot[data-env="firestorm"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * 1.06)) saturate(1.35) hue-rotate(-8deg); }
#pscRoot[data-env="firestorm"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: radial-gradient(140% 90% at 50% 115%, rgba(255,90,25,.42), rgba(255,140,40,.14) 45%, transparent 75%);
  animation: fxRoar 3.2s ease-in-out infinite;
}
@keyframes fxRoar { 0%,100%{opacity:.72} 50%{opacity:1} }

/* ASHFALL — colour drains out of the world */
#pscRoot[data-env="ashfall"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * .8)) saturate(.42) contrast(1.06); }
#pscRoot[data-env="ashfall"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: linear-gradient(180deg, rgba(90,70,68,.34), transparent 70%);
}

/* ACID RAIN — sickly green cast */
#pscRoot[data-env="acidrain"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * .9)) hue-rotate(48deg) saturate(1.2); }

/* HAIL — hard, cold, high-contrast light */
#pscRoot[data-env="hail"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * 1.05)) contrast(1.16) saturate(.75); }

/* HEATWAVE — the air itself distorts */
#pscRoot[data-env="heatwave"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * 1.1)) saturate(1.25); animation: fxSwelter 5s ease-in-out infinite; }
@keyframes fxSwelter { 0%,100%{transform:scale(1.02) skewX(0deg)} 50%{transform:scale(1.03) skewX(.25deg)} }

/* AURORA — slow ribbons of colour across the sky */
#pscRoot[data-env="aurora"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: radial-gradient(90% 55% at 30% 12%, rgba(110,255,190,.24), transparent 62%),
              radial-gradient(80% 50% at 72% 20%, rgba(140,150,255,.20), transparent 60%);
  animation: fxAurora 16s ease-in-out infinite alternate;
}
@keyframes fxAurora {
  from { transform: translate3d(-4%, 0, 0) scaleY(1); opacity:.7 }
  to   { transform: translate3d(4%, 2%, 0) scaleY(1.18); opacity:1 }
}

/* ECLIPSE — the light goes out */
#pscRoot[data-env="eclipse"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * .52)) saturate(.6) contrast(1.1); }
#pscRoot[data-env="eclipse"] .psc-shell::before {
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: radial-gradient(120% 100% at 50% 40%, transparent 25%, rgba(40,0,20,.62) 100%);
}

/* METEOR — deep night with a violet cast */
#pscRoot[data-env="meteor"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * .72)) saturate(1.1) hue-rotate(-12deg); }

/* MONSOON — heavy, drowning grey-blue */
#pscRoot[data-env="monsoon"] .psc-bg { filter: brightness(calc(var(--atmo-bright,1) * .82)) saturate(.85); }

/* ── RESTRAINT: every one of these is motion, so all of it stops ── */
@media (prefers-reduced-motion: reduce) {
  #pscRoot[data-env] .psc-bg,
  #pscRoot[data-env] .psc-shell::before,
  #pscRoot[data-env] .psc-shell::after { animation: none !important; }
}
body.psfx-calm #pscRoot[data-env] .psc-bg,
body.psfx-calm #pscRoot[data-env] .psc-shell::before,
body.psfx-calm #pscRoot[data-env] .psc-shell::after { animation: none !important; }

/* ── Three.js 3D particle layer ─────────────────────────────────────
   Sits where the 2D canvas would; only one of the two is ever active. */
.psfx-three-canvas {
  position: absolute; inset: 0; z-index: 1;
  width: 100% !important; height: 100% !important;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .psfx-three-canvas { display: none; } }
body.psfx-calm .psfx-three-canvas { display: none; }
@media (max-width: 820px) { .psfx-three-canvas { opacity: .8; } }