/**
 * css/battle-fx.css - field effects on the battle screen (js/ui/battle.js, DECISIONS 307).
 * Loaded after css/game.css (it builds on .battle-sprite, .status-tag, .dmg-pop, .move-btn).
 *
 *  1. Weather: a light overlay per kind over the arena (rain streaks, a warm sun glare, sand
 *     haze, hail) and a badge in a free corner, "Rain · 3 turns". The Gen 8-9 games show the
 *     weather the same way next to the field; the older ones only in the text box.
 *  2. Hits with a look of their own: recoil / crash jolts the attacker back with a white-red
 *     flash, weather damage is a gust, Curse a purple flicker, Future Sight a psychic ring.
 *  3. Two-turn moves: the Pokémon flies up out of view (Fly, Bounce), sinks into the ground
 *     (Dig), dips under the water with a ripple (Dive) or fades (Shadow Force) and comes back
 *     when it strikes; a Solar Beam-style charge glows. The locked move card.
 *  4. The new tags in the status boxes (Charging, Must recharge, Cursed, Destiny Bond,
 *     Protected, Future Sight in N).
 * Everything stays out of the way (pointer-events: none on the overlays) and nothing moves
 * under prefers-reduced-motion: the weather is then a still tint, the hits have no jolt.
 */

/* ------------------------------------------------------------------------- */
/* 1. Weather                                                                 */
/* ------------------------------------------------------------------------- */

/* Over the Pokémon (z 1, but later in the DOM than the sprite stages), under the status boxes
   (z 2), the thrown ball (z 3) and the message panel (z 4), so no text ever sits under it. */
.weather-fx {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.weather-fx:not([data-kind]) { display: none; }
.weather-fx.on { opacity: 1; }
/* Two particle layers, oversized so the slant and the scroll never show an edge. Each scrolls by
   exactly one tile (--tile), so the loop is seamless. */
.weather-fx::before,
.weather-fx::after {
  content: "";
  position: absolute;
  inset: -40% -30%;
  background-repeat: repeat;
  will-change: transform;
}
@keyframes wfx-fall { from { transform: translateY(calc(-1 * var(--tile, 120px))); } to { transform: translateY(0); } }
@keyframes wfx-drift { from { transform: translateX(calc(-1 * var(--tile, 100px))); } to { transform: translateX(0); } }
@keyframes wfx-shimmer { from { opacity: 0.55; } to { opacity: 1; } }

/* Rain: a darker sky and thin streaks falling at a slant. */
.weather-fx[data-kind="rain"] { background: linear-gradient(180deg, rgba(28, 44, 78, 0.26), rgba(28, 44, 78, 0.08)); }
.weather-fx[data-kind="rain"]::before {
  --tile: 120px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cpath d='M6 8v16M30 60v14M52 20v16M74 88v14M96 40v16M114 72v14M18 100v12M84 4v12M64 52v12M40 96v14' stroke='white' stroke-opacity='.55' stroke-width='1.3' stroke-linecap='round'/%3E%3C/svg%3E");
  background-size: 120px 120px;
  rotate: 12deg;
  animation: wfx-fall 0.5s linear infinite;
}
.weather-fx[data-kind="rain"]::after {
  --tile: 80px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80'%3E%3Cpath d='M10 6v10M34 40v10M58 16v10M70 58v10M22 64v10M46 70v8' stroke='white' stroke-opacity='.35' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E");
  background-size: 80px 80px;
  rotate: 12deg;
  animation: wfx-fall 0.75s linear infinite;
}

/* Harsh sunlight: a warm glare from the top-left corner with soft rays that breathe. */
.weather-fx[data-kind="sun"] {
  background:
    radial-gradient(ellipse at 8% -8%, rgba(255, 248, 214, 0.7), rgba(255, 214, 120, 0.26) 28%, rgba(255, 180, 80, 0.07) 58%, transparent 78%),
    linear-gradient(180deg, rgba(255, 200, 110, 0.1), rgba(255, 170, 60, 0.04));
}
.weather-fx[data-kind="sun"]::before {
  inset: 0;
  background: repeating-conic-gradient(from 90deg at 6% -6%, rgba(255, 244, 200, 0.2) 0deg 3deg, transparent 3deg 9deg);
  -webkit-mask-image: radial-gradient(ellipse at 6% -6%, #000 8%, transparent 68%);
  mask-image: radial-gradient(ellipse at 6% -6%, #000 8%, transparent 68%);
  will-change: opacity;
  animation: wfx-shimmer 3.2s ease-in-out infinite alternate;
}
.weather-fx[data-kind="sun"]::after { display: none; }

/* Sandstorm: a dusty haze and specks blown across the field. */
.weather-fx[data-kind="sand"] { background: linear-gradient(180deg, rgba(190, 146, 76, 0.28), rgba(214, 170, 95, 0.18)); }
.weather-fx[data-kind="sand"]::before {
  --tile: 100px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cg fill='rgb(250,222,170)' fill-opacity='.8'%3E%3Ccircle cx='8' cy='12' r='1.3'/%3E%3Ccircle cx='40' cy='30' r='1'/%3E%3Ccircle cx='76' cy='8' r='1.2'/%3E%3Ccircle cx='20' cy='64' r='1.1'/%3E%3Ccircle cx='58' cy='80' r='1.4'/%3E%3Ccircle cx='90' cy='52' r='1'/%3E%3Ccircle cx='34' cy='92' r='1'/%3E%3C/g%3E%3Cpath d='M48 46h12M6 84h9M70 28h8' stroke='rgb(250,222,170)' stroke-opacity='.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-size: 100px 100px;
  rotate: 10deg;
  animation: wfx-drift 0.9s linear infinite;
}
.weather-fx[data-kind="sand"]::after {
  --tile: 60px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Cg fill='rgb(240,205,140)' fill-opacity='.5'%3E%3Ccircle cx='6' cy='8' r='.9'/%3E%3Ccircle cx='30' cy='22' r='.8'/%3E%3Ccircle cx='50' cy='44' r='.9'/%3E%3Ccircle cx='18' cy='50' r='.8'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 60px 60px;
  rotate: 6deg;
  animation: wfx-drift 1.5s linear infinite;
}

/* Hail: a cold tint and white pellets falling fast. Snow (Snowscape, GN-03) shares the look. */
.weather-fx[data-kind="hail"], .weather-fx[data-kind="snow"] { background: linear-gradient(180deg, rgba(196, 226, 255, 0.2), rgba(196, 226, 255, 0.08)); }
.weather-fx[data-kind="hail"]::before, .weather-fx[data-kind="snow"]::before {
  --tile: 110px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='110' height='110'%3E%3Cg fill='white' fill-opacity='.85'%3E%3Ccircle cx='12' cy='14' r='2.2'/%3E%3Ccircle cx='58' cy='40' r='1.8'/%3E%3Ccircle cx='92' cy='10' r='2'/%3E%3Ccircle cx='30' cy='78' r='2.4'/%3E%3Ccircle cx='76' cy='88' r='1.8'/%3E%3Ccircle cx='100' cy='62' r='2.2'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 110px 110px;
  rotate: 8deg;
  animation: wfx-fall 0.8s linear infinite;
}
.weather-fx[data-kind="hail"]::after {
  --tile: 70px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='70' height='70'%3E%3Cg fill='white' fill-opacity='.6'%3E%3Ccircle cx='10' cy='20' r='1.3'/%3E%3Ccircle cx='44' cy='8' r='1.2'/%3E%3Ccircle cx='30' cy='50' r='1.3'/%3E%3Ccircle cx='62' cy='40' r='1.1'/%3E%3Ccircle cx='56' cy='64' r='1.2'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 70px 70px;
  rotate: 8deg;
  animation: wfx-fall 1.2s linear infinite;
}

/* The tint a weather hit glows in (battle.js puts data-weather on the arena; its className
   belongs to arenaClass()). */
.battle-arena[data-weather="rain"] { --weather-tint: rgba(140, 190, 255, 0.95); }
.battle-arena[data-weather="sun"] { --weather-tint: rgba(255, 200, 90, 0.95); }
.battle-arena[data-weather="sand"] { --weather-tint: rgba(226, 186, 110, 0.95); }
.battle-arena[data-weather="hail"] { --weather-tint: rgba(200, 236, 255, 0.95); }

/* The badge: top-left on a desktop (the foe's box is top-right, yours bottom-left), under the
   spectator pill when there is one. On a phone the boxes swap corners, so it goes top-right. */
.weather-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px 3px 7px;
  border-radius: 999px;
  background: rgba(14, 21, 38, 0.86);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: var(--shadow-sm, 0 4px 12px rgba(0, 0, 0, 0.3));
  color: var(--text, #eef2fb);
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  cursor: default;
  animation: wbadge-in 0.3s ease-out both;
}
.weather-badge-icon { display: inline-flex; flex: none; }
.weather-badge-turns { font-weight: 600; color: var(--muted, #b8c0d4); }
.weather-badge-label + .weather-badge-turns::before { content: '\00B7\00A0'; }
.weather-badge[data-kind="rain"] .weather-badge-icon { color: #8ec5ff; }
.weather-badge[data-kind="sun"] .weather-badge-icon { color: #ffd36b; }
.weather-badge[data-kind="sand"] .weather-badge-icon { color: #e3b873; }
.weather-badge[data-kind="hail"] .weather-badge-icon { color: #cdeeff; }
.battle-arena:has(> .watch-pill) .weather-badge { top: 42px; }
.battle.has-tower .battle-arena:has(> .watch-pill) .weather-badge { top: 74px; }
@keyframes wbadge-in { from { opacity: 0; translate: 0 -6px; } to { opacity: 1; translate: 0 0; } }

/* ------------------------------------------------------------------------- */
/* 2. Hits with a look of their own (battle.js playLineFx, DAMAGE_FX)          */
/* ------------------------------------------------------------------------- */

/* Recoil / crash: the attacker is knocked back (your Pokémon to the lower left, the foe to the
   upper right - away from where it lunged) with a white flash that turns red. ~350 ms. */
.fx-recoil { animation: fx-recoil-you 0.36s ease-out both; }
.battle-sprite.opp .fx-recoil { animation-name: fx-recoil-opp; }
@keyframes fx-recoil-you {
  0% { translate: 0 0; filter: none; }
  18% { translate: -10px 4px; filter: brightness(2.4) saturate(0); }
  45% { translate: -7px 3px; filter: brightness(1.1) drop-shadow(0 0 6px rgba(255, 70, 70, 0.95)); }
  100% { translate: 0 0; filter: none; }
}
@keyframes fx-recoil-opp {
  0% { translate: 0 0; filter: none; }
  18% { translate: 10px -4px; filter: brightness(2.4) saturate(0); }
  45% { translate: 7px -3px; filter: brightness(1.1) drop-shadow(0 0 6px rgba(255, 70, 70, 0.95)); }
  100% { translate: 0 0; filter: none; }
}
/* Sandstorm / hail damage: a gust sways it, glowing in the weather's colour. */
.fx-weather { animation: fx-gust 0.36s ease-in-out both; }
@keyframes fx-gust {
  0%, 100% { translate: 0 0; filter: none; }
  30% { translate: -5px 0; filter: brightness(0.9) drop-shadow(0 0 7px var(--weather-tint, rgba(226, 186, 110, 0.95))); }
  65% { translate: 4px 0; filter: drop-shadow(0 0 5px var(--weather-tint, rgba(226, 186, 110, 0.95))); }
}
/* Curse / Destiny Bond: a dark purple flicker, like the ghost-type hits of the games. */
.fx-curse { animation: fx-curse 0.4s linear both; }
@keyframes fx-curse {
  0%, 100% { opacity: 1; filter: none; }
  20%, 60% { opacity: 0.55; filter: brightness(0.55) drop-shadow(0 0 8px rgba(150, 80, 220, 0.95)); }
  40%, 80% { opacity: 1; filter: drop-shadow(0 0 4px rgba(150, 80, 220, 0.7)); }
}
/* Future Sight lands: a psychic-pink ring closes in on the target, then the usual hit shake. */
.fx-ring {
  position: absolute;
  left: 50%;
  bottom: 28%;
  z-index: 3;
  width: 90px;
  height: 90px;
  margin-left: -45px;
  border-radius: 50%;
  border: 3px solid rgba(248, 88, 136, 0.9);
  box-shadow: 0 0 12px rgba(248, 88, 136, 0.7), inset 0 0 10px rgba(248, 88, 136, 0.5);
  pointer-events: none;
  animation: fx-ring 0.42s ease-in forwards;
}
@keyframes fx-ring {
  0% { opacity: 0; scale: 1.8; }
  35% { opacity: 1; }
  100% { opacity: 0; scale: 0.5; }
}
/* A charge line ("Venusaur absorbed light!"): one bright pulse. */
.fx-charge { animation: fx-charge 0.42s ease-out both; }
@keyframes fx-charge {
  0%, 100% { filter: none; }
  40% { filter: brightness(1.7) drop-shadow(0 0 10px rgba(255, 230, 130, 0.95)); }
}
/* The floating numbers for the new causes (css/game.css has attack, poison, recoil, ...). */
.dmg-pop.cause-crash { color: #cfd4de; font-size: 15px; }
.dmg-pop.cause-weather { color: #f1d49a; }
.dmg-pop.cause-curse, .dmg-pop.cause-destiny { color: #c9a2ff; }
.dmg-pop.cause-future { color: #ff9cc2; }

/* ------------------------------------------------------------------------- */
/* 3. Two-turn moves                                                          */
/* ------------------------------------------------------------------------- */

/* The classes sit on the sprite STAGE (like catch-in), so a redraw of the sprite cannot undo
   them. The exit's last frame holds for as long as the class stays: the Pokémon is gone until
   battle.js swaps it for fx-return-<kind> when it strikes. */
.battle-sprite.fx-hide-sky .sprite-wrap { animation: fx-fly-up 0.45s cubic-bezier(0.5, 0, 0.9, 0.4) both; }
.battle-sprite.fx-hide-underground .sprite-wrap,
.battle-sprite.fx-hide-underwater .sprite-wrap { animation: fx-sink 0.5s ease-in both; }
.battle-sprite.fx-hide-vanished .sprite-wrap { animation: fx-vanish 0.45s ease-in both; }
.battle-sprite.fx-return-sky .sprite-wrap { animation: fx-swoop 0.45s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
.battle-sprite.fx-return-underground .sprite-wrap,
.battle-sprite.fx-return-underwater .sprite-wrap { animation: fx-rise 0.45s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
.battle-sprite.fx-return-vanished .sprite-wrap { animation: fx-appear 0.4s ease-out both; }
@keyframes fx-fly-up {
  0% { translate: 0 0; opacity: 1; }
  25% { translate: 0 6px; opacity: 1; }
  100% { translate: 0 -320px; opacity: 0; }
}
@keyframes fx-swoop {
  0% { translate: 0 -320px; opacity: 0; }
  60% { translate: 0 8px; opacity: 1; }
  100% { translate: 0 0; opacity: 1; }
}
/* Sinking: it moves down by its own (scaled) height while the bottom is clipped away by the
   same amount, so it disappears through the line its feet stand on. */
@keyframes fx-sink {
  0% { translate: 0 0; clip-path: inset(0 0 0 0); opacity: 1; }
  15% { translate: 0 -6px; clip-path: inset(0 0 0 0); }
  100% { translate: 0 130%; clip-path: inset(0 0 100% 0); opacity: 1; }
}
@keyframes fx-rise {
  0% { translate: 0 130%; clip-path: inset(0 0 100% 0); }
  70% { translate: 0 -8px; clip-path: inset(0 0 0 0); }
  100% { translate: 0 0; clip-path: inset(0 0 0 0); }
}
@keyframes fx-vanish {
  0% { opacity: 1; filter: none; }
  100% { opacity: 0; filter: brightness(0.3) blur(3px); }
}
@keyframes fx-appear {
  0% { opacity: 0; filter: brightness(0.3) blur(3px); }
  100% { opacity: 1; filter: none; }
}
/* What stays on the field meanwhile, at its feet: a shadow under a Pokémon in the sky, a mound
   of loose earth over a digger, rings on the water over a diver. */
.battle-sprite.fx-hide-sky::after,
.battle-sprite.fx-hide-underground::after,
.battle-sprite.fx-hide-underwater::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 14px;
  z-index: 2;
  width: 76px;
  height: 16px;
  margin-left: -38px;
  border-radius: 50%;
  pointer-events: none;
}
.battle-sprite.fx-hide-sky::after { background: radial-gradient(ellipse, rgba(0, 0, 0, 0.3), transparent 70%); animation: fx-shadow-in 0.45s ease-out both; }
.battle-sprite.fx-hide-underground::after { background: radial-gradient(ellipse at 50% 70%, rgba(120, 86, 48, 0.85), rgba(120, 86, 48, 0.35) 55%, transparent 72%); animation: fx-mound 0.5s ease-out both; }
.battle-sprite.fx-hide-underwater::after {
  border: 2px solid rgba(220, 240, 255, 0.75);
  box-shadow: 0 0 0 5px rgba(220, 240, 255, 0.18);
  animation: fx-ripple 1.6s ease-out 0.3s infinite;
}
@keyframes fx-shadow-in { from { opacity: 0; scale: 1.4; } to { opacity: 1; scale: 0.8; } }
@keyframes fx-mound { from { opacity: 0; scale: 0.4 0.4; } to { opacity: 1; scale: 1; } }
@keyframes fx-ripple { 0% { opacity: 0.9; scale: 0.4; } 100% { opacity: 0; scale: 1.5; } }
/* Charging up (Solar Beam, Skull Bash, Sky Attack ...): a soft glow behind it until it fires. */
.battle-sprite.fx-charging::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 8%;
  z-index: -1;
  width: 150px;
  height: 150px;
  margin-left: -75px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 238, 160, 0.55), rgba(255, 210, 90, 0.16) 45%, transparent 70%);
  pointer-events: none;
  animation: fx-charge-pulse 1.4s ease-in-out infinite;
}
@keyframes fx-charge-pulse { 0%, 100% { opacity: 0.6; scale: 0.92; } 50% { opacity: 1; scale: 1.05; } }

/* The locked attack bar: one wide card, "Continue: Solar Beam" or "Recharge", gently pulsing in
   its type colour so it reads as the one thing to press. */
.move-btn.locked-move { grid-column: 1 / -1; }
.battle-actions:not(.locked) .move-btn.locked-move { animation: locked-glow 1.6s ease-in-out infinite; }
.move-btn.locked-move .move-btn-meta { font-size: 11.5px; }
@keyframes locked-glow {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--move-color) 65%, transparent); }
}
/* Their lines in the message panel (light background): weather in a sky blue, a charge in amber. */
.battle-log-line.kind-weather, .battle-msg-line.kind-weather { color: #1f6d8f; }
.battle-log-line.kind-charge, .battle-msg-line.kind-charge { color: #8a5a00; }
/* The Bag drawer: "Use [R]" on the ball the R key throws. */
.battle-sub .btn-label { display: inline-flex; align-items: center; gap: 6px; }

/* ------------------------------------------------------------------------- */
/* 4. Tags in the status boxes (next to Seeded / Confused / Iced)            */
/* ------------------------------------------------------------------------- */
.status-tag.charging { background: #c98a12; color: #1f1400; text-transform: none; }
.status-tag.recharging { background: #8a5a2b; text-transform: none; }
.status-tag.cursed { background: #4b2a6e; color: #e6d4ff; text-transform: none; }
.status-tag.destiny { background: #2f2447; color: #d9c8ff; border: 1px solid rgba(190, 160, 255, 0.4); text-transform: none; }
.status-tag.protected { background: #2a8f86; text-transform: none; }
.status-tag.future { background: #c93c6e; text-transform: none; }
/* Taunt / trapped, and the side-wide screens with their turns (Reflect 4, Tailwind 2 ...). */
.status-tag.taunted { background: #a8432f; text-transform: none; }
.status-tag.trapped { background: #5a4a3a; text-transform: none; }
.status-tag.side { background: #2c5f9e; text-transform: none; }

/* ------------------------------------------------------------------------- */
/* Phones (the 600 px rules of css/game.css)                                  */
/* ------------------------------------------------------------------------- */
@media (max-width: 600px) {
  .weather-badge { left: auto; right: 8px; top: 8px; font-size: 0.72rem; padding: 2px 8px 2px 6px; }
  .battle-arena:has(> .watch-pill) .weather-badge { top: 38px; }
  .battle.has-tower .weather-badge { top: 40px; }
  .battle.has-tower .battle-arena:has(> .watch-pill) .weather-badge { top: 74px; }
  .fx-ring { width: 70px; height: 70px; margin-left: -35px; }
  .battle-sprite.fx-charging::before { width: 120px; height: 120px; margin-left: -60px; }
}
/* Narrow phones: "Sandstorm · 5 turns" came within 1 px of the foe's box at 360 px and ran into
   it below that. The icon, the arena's weather and the tooltip still say which weather it is. */
@media (max-width: 380px) {
  .weather-badge-label { display: none; }
  .weather-badge-label + .weather-badge-turns::before { content: none; }
}

/* ------------------------------------------------------------------------- */
/* Reduced motion: still pictures only                                        */
/* ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .weather-fx { transition: none; }
  .weather-fx::before, .weather-fx::after, .weather-badge { animation: none !important; }
  .fx-recoil, .fx-weather, .fx-curse, .fx-charge { animation: none !important; }
  .fx-ring { display: none; }
  .battle-sprite.fx-charging::before, .battle-sprite[class*="fx-hide-"]::after { animation: none !important; }
  /* Gone at once while hidden, back at once when it strikes. */
  .battle-sprite[class*="fx-hide-"] .sprite-wrap { animation: none !important; opacity: 0; }
  .battle-sprite[class*="fx-return-"] .sprite-wrap { animation: none !important; }
  .battle-actions:not(.locked) .move-btn.locked-move { animation: none; }
}
