/* Pawball 5.0 - ambient motion layer
   Every rule here is additive: no existing component is restyled, only
   enhanced with slow, quiet movement. All animations honour the shared
   --pb-play switch (paused while the tab is hidden) and are wiped entirely
   by the global reduced-motion rule in theme.css. Transform, translate,
   opacity and background-position only - nothing that forces layout. */

body { --pb-play: running; }
body.pb-hidden { --pb-play: paused; }

/* ---------- seasonal wallpaper and atmosphere ----------
   Two fixed compositor layers crossfade only after the next image loads.
   The seasonal atmosphere remains behind the whole interface, so it cannot
   move layout or cover controls. */
/* the seasonal scene is physically anchored: one stable base image per
   two-month period, no ken-burns drift, so the world never glides as time
   changes. Daypart mood comes from the lighting wash below, not motion. A
   gentle scale keeps the edges bled off-screen for responsive cropping. */
.pb-amb { overflow: hidden; contain: paint; transition: filter 2.8s ease; }
.pb-wallpaper-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  transform: scale(1.05);
  /* Slow, deliberate crossfade between every daypart / seasonal wallpaper,
     long enough to hide small scene-to-scene alignment differences. */
  --pb-wallpaper-fade: 2.8s;
  transition: opacity var(--pb-wallpaper-fade) ease;
  will-change: opacity, transform;
  /* Restored from v5.1.48: a near-imperceptible zoom/pan so the photographed
     park itself breathes continuously, independent of daypart/weather - the
     single biggest "always something moving" cue the older build had that
     this one had lost. */
  animation: pbWallDrift 46s ease-in-out infinite alternate;
  animation-play-state: var(--pb-play, running);
}
.pb-wallpaper-layer.is-active { opacity: 1; }
@keyframes pbWallDrift {
  0%   { transform: scale(1.05) translate3d(0, 0, 0); }
  50%  { transform: scale(1.08) translate3d(-1.3%, .8%, 0); }
  100% { transform: scale(1.06) translate3d(1.2%, -.7%, 0); }
}

/* ---------- continuous light drift ----------
   Two soft sun fields and a faint cloud shadow cross the scene on their own
   slow clocks, completely independent of the day's dice-rolled weather (see
   ambient.js EFFECT_CATALOGUE) - restored from v5.1.48, where this constant
   drift (not the weather particles) was doing most of the work of making
   the park feel alive on every visit, not just on days with named weather. */
.pb-amb-sun,
.pb-amb-sun2 {
  position: absolute;
  inset: -12%;
  pointer-events: none;
  animation-play-state: var(--pb-play, running);
  will-change: transform, opacity;
}
.pb-amb-sun {
  background: radial-gradient(46% 38% at 24% 12%, rgba(255, 244, 200, .55), rgba(255, 244, 200, 0) 70%);
  animation: pbSunA 30s ease-in-out infinite alternate;
}
.pb-amb-sun2 {
  background: radial-gradient(52% 44% at 78% 26%, rgba(255, 250, 226, .45), rgba(255, 250, 226, 0) 72%);
  animation: pbSunB 38s ease-in-out infinite alternate;
}
@keyframes pbSunA {
  0%   { opacity: .08; transform: translate3d(-4%, -1.6%, 0); }
  100% { opacity: .5; transform: translate3d(4.5%, 2.4%, 0); }
}
@keyframes pbSunB {
  0%   { opacity: .45; transform: translate3d(3.8%, 1.8%, 0); }
  100% { opacity: .06; transform: translate3d(-4%, -1.5%, 0); }
}
/* A broad shaft of sunlight sweeping the park - v5.1.48 baseline intensity,
   not pre-emptively dimmed. mix-blend-mode: overlay so it reads as light
   crossing the photographed scene rather than a flat tinted panel. */
.pb-amb-beam {
  position: absolute;
  inset: -15% -35%;
  pointer-events: none;
  background: linear-gradient(72deg, rgba(255, 250, 215, 0) 40%, rgba(255, 250, 215, .55) 50%, rgba(255, 250, 215, 0) 60%);
  mix-blend-mode: overlay;
  animation: pbBeam 27s ease-in-out infinite alternate;
  animation-play-state: var(--pb-play, running);
  will-change: transform, opacity;
}
@keyframes pbBeam {
  0%   { transform: translate3d(-22%, 0, 0); opacity: .45; }
  50%  { opacity: .95; }
  100% { transform: translate3d(22%, 0, 0); opacity: .5; }
}
.pb-amb-cloud {
  position: absolute;
  top: -20%;
  left: -45%;
  width: 65%;
  height: 90%;
  pointer-events: none;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(46, 66, 48, .2), rgba(46, 66, 48, 0) 70%);
  filter: blur(30px);
  animation: pbCloud 58s linear infinite;
  animation-play-state: var(--pb-play, running);
  will-change: transform, opacity;
}
@keyframes pbCloud {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  12%  { opacity: .65; }
  50%  { transform: translate3d(120%, 6%, 0); opacity: .45; }
  88%  { opacity: .65; }
  100% { transform: translate3d(230%, 0, 0); opacity: 0; }
}
/* ---------- daypart lighting ----------------------------------------------
   The separate Morning / Afternoon / Evening wallpaper artwork carries the
   time-of-day look (sun position, sky, warmth, shadows) as authored. There is
   no global colour grade, glow pane or separate overnight visual mode. */

.pb-season-atmosphere,
.pb-season-particles {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Particles ride above the daypart glow (z-index 1) so weather - snow above all
   - is never washed out by the evening/overnight tint. The whole .pb-amb layer
   still sits behind the interface (z-index 0), so this never covers the UI. */
.pb-season-particles { z-index: 2; }
/* Atmosphere veils (mist / haze / warm-light / breeze / heat-shimmer) are
   restored: every main screen sits behind this same .pb-amb layer, and these
   five effects previously rendered nothing at all when the day's weather
   selected them, leaving the game feeling static on a lot of days. Opacity
   values stay deliberately low so the veil reads as weather, never as a
   wash over text/portraits/controls. */
.pb-season-atmosphere {
  opacity: 0;
  transition: opacity 1.5s ease;
}
.pb-fx-mist .pb-season-atmosphere {
  opacity: .38;
  background: linear-gradient(180deg, transparent 42%, rgba(232, 239, 235, .8) 76%, rgba(226, 235, 232, .46));
}
.pb-fx-haze .pb-season-atmosphere {
  opacity: .32;
  background: linear-gradient(180deg, rgba(222, 231, 239, .32), transparent 35%, rgba(202, 215, 225, .55));
}
.pb-fx-warm-light .pb-season-atmosphere {
  opacity: .38;
  background: radial-gradient(28% 38% at 82% 46%, rgba(255, 207, 112, .7), rgba(255, 207, 112, 0) 76%);
}
.pb-fx-breeze .pb-season-atmosphere {
  opacity: .24;
  background: linear-gradient(104deg, transparent 25%, rgba(241, 246, 237, .5) 50%, transparent 75%);
  animation: pbSeasonBreeze 18s ease-in-out infinite alternate;
  animation-play-state: var(--pb-play, running);
}
.pb-fx-heat-shimmer .pb-season-atmosphere {
  opacity: .2;
  background: repeating-linear-gradient(90deg, transparent 0 46px, rgba(255, 244, 202, .28) 48px 50px, transparent 52px 94px);
  animation: pbHeatShimmer 11s ease-in-out infinite alternate;
  animation-play-state: var(--pb-play, running);
}
.pb-fx-sunbeam .pb-season-atmosphere {
  opacity: .34;
  background: linear-gradient(72deg, transparent 34%, rgba(255, 239, 174, .46) 48%, transparent 63%);
}
@keyframes pbSeasonBreeze {
  from { transform: translate3d(-7%, 0, 0); }
  to { transform: translate3d(7%, -1%, 0); }
}
@keyframes pbHeatShimmer {
  from { transform: translate3d(-3px, 0, 0) scaleY(1); }
  to { transform: translate3d(3px, -2px, 0) scaleY(1.006); }
}

/* Storm intensity layer (see ambient.js isStormBoosted): an already-rainy or
   snowy autumn/winter day occasionally renders as a heavier downpour with a
   soft, distant flash - never a screen-wide strobe. Applies to whichever
   ambient host currently carries the class (main layer or the match screen's
   own host), so this needs no per-screen duplicate. */
.pb-fx-storm::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background: rgba(255, 255, 255, .5);
  opacity: 0;
  animation: pbStormFlicker 17s ease-in-out infinite;
  animation-play-state: var(--pb-play, running);
}
@keyframes pbStormFlicker {
  0%, 92%, 97%, 100% { opacity: 0; }
  93% { opacity: .16; }
  94% { opacity: .03; }
  95% { opacity: .1; }
}

.pb-season-particle {
  position: absolute;
  left: var(--x, 50%);
  top: var(--y, -5%);
  width: var(--size, 4px);
  height: var(--size, 4px);
  border-radius: 50%;
  opacity: 0;
  background: rgba(255, 255, 255, .75);
  animation-play-state: var(--pb-play, running);
  will-change: transform, opacity;
}
.pb-motion-fall { top: -6%; animation: pbSeasonFall var(--duration, 26s) linear var(--delay, 0s) infinite; }
.pb-motion-rain { top: -8%; animation: pbSeasonRain var(--duration, 20s) linear var(--delay, 0s) infinite; }
.pb-motion-rise { animation: pbSeasonRise var(--duration, 26s) ease-in-out var(--delay, 0s) infinite; }
.pb-motion-drift { animation: pbSeasonDrift var(--duration, 25s) ease-in-out var(--delay, 0s) infinite; }
.pb-motion-glint { animation: pbSeasonGlint var(--duration, 24s) ease-in-out var(--delay, 0s) infinite; }
@keyframes pbSeasonFall {
  0% { transform: translate3d(0, -8vh, 0) rotate(0); opacity: 0; }
  10%, 86% { opacity: var(--opacity, .35); }
  100% { transform: translate3d(var(--sway, 30px), 112vh, 0) rotate(240deg); opacity: 0; }
}
@keyframes pbSeasonRain {
  0% { transform: translate3d(0, -10vh, 0) rotate(8deg); opacity: 0; }
  12%, 82% { opacity: var(--opacity, .28); }
  100% { transform: translate3d(-24px, 114vh, 0) rotate(8deg); opacity: 0; }
}
@keyframes pbSeasonRise {
  0%, 100% { transform: translate3d(0, 12px, 0); opacity: 0; }
  18%, 78% { opacity: var(--opacity, .3); }
  85% { transform: translate3d(var(--sway, 22px), var(--rise, -52px), 0); opacity: 0; }
}
@keyframes pbSeasonDrift {
  0%, 100% { transform: translate3d(-12px, 5px, 0); opacity: 0; }
  15%, 85% { opacity: var(--opacity, .3); }
  50% { transform: translate3d(var(--sway, 34px), var(--rise, -36px), 0); opacity: var(--opacity, .3); }
}
@keyframes pbSeasonGlint {
  0%, 14%, 86%, 100% { opacity: 0; transform: scale(.7); }
  34%, 66% { opacity: var(--opacity, .38); transform: scale(1); }
}
.pb-particle-snow { background: rgba(255, 255, 255, .95); box-shadow: 0 0 8px rgba(255, 255, 255, .75); }
.pb-particle-petals {
  background: #F2CBD4;
  border-radius: 65% 35% 60% 40%;
  box-shadow: 0 1px 3px rgba(111, 70, 79, .32);
}
.pb-particle-drizzle { height: calc(var(--size, 1px) * 9); border-radius: 3px; background: rgba(218, 232, 240, .58); }
.pb-particle-pollen,
.pb-particle-sunbeam,
.pb-particle-warm-particles { background: #FFF2B8; box-shadow: 0 0 10px rgba(255, 226, 145, .62); }
.pb-particle-fireflies { background: #FFD76F; box-shadow: 0 0 13px rgba(255, 210, 91, .85); }
.pb-particle-frost,
.pb-particle-dew,
.pb-particle-damp { background: rgba(238, 249, 255, .9); box-shadow: 0 0 11px rgba(224, 244, 255, .8); }
/* Manual preview-only legacy particles. Automatic mode never selects these. */
.pb-particle-motes { background: rgba(253, 240, 205, .8); box-shadow: 0 0 5px rgba(253, 240, 205, .4); }

/* Restored distant bird: a tiny silhouette crossing the top of the sky,
   well behind the interface. Year-round daylight wildlife (see
   ambient.js spawnBird). */
.pb-bird {
  position: absolute;
  left: -6vw;
  width: var(--size, 13px);
  height: auto;
  color: rgba(42, 56, 42, .5);
  pointer-events: none;
  animation: pbBirdFly var(--dur, 20s) linear forwards;
  animation-play-state: var(--pb-play, running);
  will-change: transform;
}
.pb-bird svg { display: block; width: 100%; height: auto; overflow: visible; }
.pb-bird .wing {
  transform-origin: 50% 60%;
  animation: pbFlap .5s ease-in-out infinite alternate;
  animation-play-state: var(--pb-play, running);
}
@keyframes pbBirdFly {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  4%   { opacity: 1; }
  50%  { transform: translate3d(55vw, var(--dip, 14px), 0); }
  96%  { opacity: 1; }
  100% { transform: translate3d(112vw, 0, 0); opacity: 0; }
}
@keyframes pbFlap {
  from { transform: scaleY(1); }
  to   { transform: scaleY(.25); }
}

/* ---------- portraits quietly alive ----------
   1-2px independent float using the standalone translate property, so it
   composes with any existing transform (hover scales, pitch crops) instead
   of overriding it. Durations/delays randomised inline by ambient.js. */
.pb-float {
  animation: pbAvFloat var(--pb-f-dur, 4.6s) ease-in-out var(--pb-f-delay, 0s) infinite;
  animation-play-state: var(--pb-play, running);
}
@keyframes pbAvFloat {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -2px; }
}

/* ---------- pitch light ----------
   One broad, soft band of sunlight crosses the grass and returns, under the
   existing warm-haze ::after so the desaturation treats it like part of the
   turf. Avatars sit at z-index 1 and are never touched. */
.pitch::before {
  content: "";
  position: absolute;
  inset: -6% -30%;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(78deg, rgba(255, 252, 230, 0) 38%, rgba(255, 252, 230, .38) 50%, rgba(255, 252, 230, 0) 62%);
  animation: pbPitchLight 13s ease-in-out infinite alternate;
  animation-play-state: var(--pb-play, running);
  will-change: transform;
}
@keyframes pbPitchLight {
  0%   { transform: translate3d(-16%, 0, 0); }
  100% { transform: translate3d(16%, 0, 0); }
}

/* ---------- new-message glow ----------
   ambient.js tags only the newest genuinely-appended bubble; the glow is a
   box-shadow transition (class on for ~1s, then removed), so it plays once,
   fades naturally, and can never loop or fight the slide-in animation. */
.bubble.me, .bubble.them { transition: box-shadow .55s ease; }
.bubble.me.pb-msg-glow {
  box-shadow: var(--shadow-card), 0 0 0 2px rgba(78, 158, 94, .5), 0 0 20px rgba(78, 158, 94, .55);
}
.bubble.them.pb-msg-glow {
  box-shadow: var(--shadow-card), 0 0 0 2px rgba(217, 169, 60, .55), 0 0 20px rgba(236, 202, 128, .65);
}

/* ---------- reduced-motion carve-out ----------
   theme.css collapses every animation to .01ms under Reduce Motion. The
   ambient layer is the product here, not decoration on top of it, so its
   gentle, non-vestibular movement (slow opacity pools, drifting light,
   1-2px floats) is restored at full length. Anything brisk stays killed. */
@media (prefers-reduced-motion: reduce) {
  .pb-wallpaper-layer { animation: none !important; transform: scale(1.05); transition: none !important; }
  .pb-amb-sun, .pb-amb-sun2, .pb-amb-beam, .pb-amb-cloud { display: none !important; }
  .pb-bird { display: none !important; }
  .pb-season-particle { display: none !important; }
  .pb-fx-breeze .pb-season-atmosphere,
  .pb-fx-heat-shimmer .pb-season-atmosphere { animation: none !important; }
  .pb-fx-storm::after { animation: none; opacity: 0; }
  .pb-float { animation-duration: var(--pb-f-dur, 4.6s) !important; animation-iteration-count: infinite !important; }
  .pitch::before { animation-duration: 13s !important; animation-iteration-count: infinite !important; }
  .bubble.me, .bubble.them { transition-duration: .55s !important; }
}

/* ============================================================ */
/* world layer: match tension and interface ambience            */
/* ============================================================ */

/* Near-match emphasis is static. Match panels, pressure bars and controls no
   longer brighten or pulse on a recurring clock.
   border-color removed by request - with the dashboard reset's 1px
   transparent border-inline (components.css), this drew two thin vertical
   lines down the sides of the Next Match card whenever a match was 0-3
   days out. */

/* ---------- dog idle life ----------
   One dog at a time settles its weight: a small dip and lean, then still. */
.pb-settle {
  animation: pbSettle .8s ease-in-out 1;
  animation-play-state: var(--pb-play, running);
}
@keyframes pbSettle {
  0%, 100% { translate: 0 0; rotate: 0deg; }
  40%      { translate: 0 1.6px; rotate: -1.4deg; }
  70%      { translate: 0 .4px; rotate: .6deg; }
}

/* ---------- ambient audio toggle (HUD) ---------- */
#pb-audio-btn.muted { opacity: .55; }
#pb-audio-btn .pb-spk-on { display: block; }
#pb-audio-btn .pb-spk-off { display: none; }
#pb-audio-btn.muted .pb-spk-on { display: none; }
#pb-audio-btn.muted .pb-spk-off { display: block; }

/* world-layer reduced-motion carve-out (same rationale as above) */
@media (prefers-reduced-motion: reduce) {
  .pb-settle { animation-duration: .8s !important; }
}

/* ============================================================ */
/* v5.1.31 refinement pass                                      */
/* ============================================================ */

/* ---------- thread scroll depth cues ----------
   Overlays share the thread's grid cell (.convo row 4), so they hug its
   edges with no measuring. Visible only while more conversation exists in
   that direction; a soft ink gradient, never a hard line. */
/* explicit placement: overlapping a cell must never push the auto-placed
   thread or composer into a phantom row. */
.convo > #convo-head { grid-row: 1; grid-column: 1; }
.convo > #scene-notice { grid-row: 2; grid-column: 1; }
.convo > #scene-schedule { grid-row: 3; grid-column: 1; }
.convo > #convo-thread { grid-row: 4; grid-column: 1; }
.convo > #compose-wrap { grid-row: 5; grid-column: 1; }
.pb-fade-top,
.pb-fade-bottom {
  grid-row: 4;
  grid-column: 1;
  z-index: 2;
  height: 26px;
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s ease;
}
.pb-fade-top {
  align-self: start;
  background: linear-gradient(180deg, rgba(40, 60, 35, .16), rgba(40, 60, 35, 0));
}
.pb-fade-bottom {
  align-self: end;
  background: linear-gradient(0deg, rgba(40, 60, 35, .14), rgba(40, 60, 35, 0));
}
.pb-fade-top.on,
.pb-fade-bottom.on { opacity: 1; }

/* ---------- typing preview in the conversation list ----------
   While a contact composes, their row reads "typing…" in a live green -
   the reply can be felt arriving from anywhere on the dashboard. */
.msg-item.pb-typing-live .preview {
  color: var(--ok);
  font-style: italic;
  font-weight: 600;
}

/* ---------- send button rest / ready states ----------
   The resting/armed look now has ONE owner: .btn-send / .btn-send.is-ready
   in components.css, driven by syncComposer(). This file used to dim the
   button a second time (saturate + opacity) on top of that and on top of
   :disabled, and the three together read as a broken control rather than an
   inactive one. .pb-idle is still applied by ambient.js and still marks the
   empty state for anything that wants to hook it - it simply no longer
   restyles the button. */
.btn-send.pb-idle { filter: none; opacity: 1; }
.btn-send.pb-idle:hover { filter: none; opacity: 1; }

/* The authored wallpapers own their own light; there is no full-viewport
   daypart or overnight veil. */

/* ============================================================
   Stage 1 studio atmosphere
   Existing wallpaper requests, seasonal classes and animation contracts are
   retained. This pass only grades their presentation behind the dark shell.
   ============================================================ */

.pb-amb {
  isolation: isolate;
  background: var(--black);
}
.pb-amb::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(rgba(167, 134, 255, .022) 1px, transparent 1px),
    linear-gradient(90deg, rgba(167, 134, 255, .022) 1px, transparent 1px),
    radial-gradient(circle at 52% 18%, rgba(122, 76, 242, .13), transparent 38%),
    linear-gradient(180deg, rgba(5, 5, 6, .24), rgba(5, 5, 6, .62));
  background-size: 64px 64px, 64px 64px, auto, auto;
}
.pb-wallpaper-layer {
  filter: grayscale(.78) saturate(.42) brightness(.24) contrast(1.08);
}
.pb-amb-sun {
  background: radial-gradient(46% 38% at 24% 12%, rgba(122, 76, 242, .2), rgba(122, 76, 242, 0) 70%);
}
.pb-amb-sun2 {
  background: radial-gradient(52% 44% at 78% 26%, rgba(167, 134, 255, .14), rgba(167, 134, 255, 0) 72%);
}
.pb-amb-beam {
  background: linear-gradient(72deg, rgba(122, 76, 242, 0) 40%, rgba(122, 76, 242, .18) 50%, rgba(122, 76, 242, 0) 60%);
  mix-blend-mode: screen;
}
.pb-amb-cloud {
  background: radial-gradient(50% 50% at 50% 50%, rgba(5, 5, 6, .52), rgba(53, 18, 110, 0) 70%);
}
.pb-fx-mist .pb-season-atmosphere {
  background: linear-gradient(180deg, transparent 42%, rgba(53, 18, 110, .3) 76%, rgba(5, 5, 6, .32));
}
.pb-fx-haze .pb-season-atmosphere {
  background: linear-gradient(180deg, rgba(122, 76, 242, .12), transparent 35%, rgba(5, 5, 6, .44));
}
.pb-fx-warm-light .pb-season-atmosphere {
  background: radial-gradient(28% 38% at 82% 46%, rgba(167, 134, 255, .25), rgba(167, 134, 255, 0) 76%);
}
.pb-fx-breeze .pb-season-atmosphere {
  background: linear-gradient(104deg, transparent 25%, rgba(245, 243, 239, .08) 50%, transparent 75%);
}
.pb-fx-heat-shimmer .pb-season-atmosphere {
  background: repeating-linear-gradient(90deg, transparent 0 46px, rgba(167, 134, 255, .08) 48px 50px, transparent 52px 94px);
}
.pb-fx-sunbeam .pb-season-atmosphere {
  background: linear-gradient(72deg, transparent 34%, rgba(167, 134, 255, .15) 48%, transparent 63%);
}
.pb-season-particle {
  background: rgba(245, 243, 239, .5);
}
.pitch::before {
  background: linear-gradient(78deg, rgba(122, 76, 242, 0) 38%, rgba(167, 134, 255, .16) 50%, rgba(122, 76, 242, 0) 62%);
}
.bubble.me.pb-msg-glow,
.bubble.them.pb-msg-glow {
  box-shadow:
    var(--shadow-card),
    0 0 0 1px rgba(167, 134, 255, .42),
    0 0 22px rgba(122, 76, 242, .26);
}
.pb-fade-top {
  background: linear-gradient(180deg, rgba(5, 5, 6, .52), rgba(5, 5, 6, 0));
}
.pb-fade-bottom {
  background: linear-gradient(0deg, rgba(5, 5, 6, .5), rgba(5, 5, 6, 0));
}
