/*
 * The forest and its firefighters (bomberos/forest.js, firefighter.js),
 * on the child's page and on the teacher's projector (host.html).
 * forest.js sizes everything from the box it is given: --patch (one
 * patch's side), --crew-size and --zone-cols (zone cards per row), so the
 * page only has to give .forest a definite width and height.
 * Decorative motion (flames, bobbing firefighters, fuses) only runs while
 * a round is live (.is-live); the one-shot effects (jets, names, steam,
 * animals) say who did what, so with reduced motion they still appear,
 * just without moving.
 */
.forest {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.forest-zones {
  display: grid;
  grid-template-columns: repeat(var(--zone-cols, 1), auto);
  gap: 10px;
  justify-content: center;
  align-content: center;
}

/* ---- a zone: badge row, patches, crew ---- */
.forest-zone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 4px 6px 2px;
  border-radius: 18px;
  background: rgba(255, 255, 255, .42);
  box-shadow: 0 3px 0 rgba(70, 40, 90, .1);
}
.forest-zone.is-mine { box-shadow: 0 0 0 4px var(--my-color, var(--purple)); }
.forest-zone-head {
  display: flex; align-items: center; gap: 6px; height: 26px; min-width: 0;
  color: var(--purple-dark); font-weight: bold; font-size: 14px;
}
.forest-zone-badge {
  flex: none; display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 50%; background: #fff; font-size: 15px; line-height: 1;
}
.forest-zone-names { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.forest-grid {
  display: grid;
  grid-template-columns: repeat(6, var(--patch, 56px));
  gap: 2px;
  justify-content: center;
}
.forest-crew {
  display: flex; justify-content: space-around; align-items: flex-start;
  min-height: calc(var(--crew-size, 62px) * .92 + 20px);
  padding-top: 2px;
}

/* A short, wide box (a phone on its side): the squad stands beside its
   patches instead of under them (forest.js decides, .is-crew-side). */
.forest.is-crew-side .forest-zone { display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto; column-gap: 4px; }
.forest.is-crew-side .forest-zone-head { grid-column: 1 / -1; }
.forest.is-crew-side .forest-crew { flex-direction: column; flex-wrap: wrap; justify-content: center; min-height: 0; padding: 0; gap: 2px; }

/* ---- the minimap: every zone, in my zone's badge row ---- */
.forest-minimap { flex: none; display: flex; gap: 3px; margin-left: auto; padding: 2px 4px; border-radius: 8px; background: rgba(255, 255, 255, .7); }
.forest-minimap[hidden] { display: none; }
.forest-mini-zone { display: grid; grid-template-columns: repeat(var(--cols), 3px); gap: 0; outline: 1px solid rgba(97, 71, 118, .25); }
.forest-mini-zone.is-mine { outline: 2px solid var(--my-color, var(--purple)); }
.forest-mini-zone i { width: 3px; height: 3px; background: #7cc47f; }
.forest-mini-zone i.is-fire { background: #f26b2e; }
.forest-mini-zone i.is-ash { background: #4a3e3b; }

/* ---- patches and fires ---- */
.forest-patch {
  position: relative;
  width: var(--patch, 56px);
  height: var(--patch, 56px);
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  cursor: default;
}
.forest-patch-art, .forest-patch .ff-patch { display: block; width: 100%; height: 100%; }
.forest-patch.is-burning { cursor: pointer; }
/* My aim: a ring in my own water colour. */
.forest-patch.is-aimed { border-color: #fff; box-shadow: 0 0 0 4px var(--my-color, var(--purple)); z-index: 1; }

/* The fuse under a fire: it burns down to the moment the patch turns to
   ash, yellow to red. forest.js sets it every poll; the transition (a
   poll long) runs it smoothly in between. */
.forest-fuse {
  position: absolute; left: 14%; right: 14%; bottom: 6%; height: max(5px, 9%);
  border-radius: 999px; background: rgba(51, 40, 60, .45); overflow: hidden; pointer-events: none;
  box-shadow: 0 0 0 1.5px #fff;
}
.forest-fuse[hidden] { display: none; }
.forest-fuse i {
  display: block; height: 100%; border-radius: inherit; transform-origin: left center;
  background: #ffd25c; transition: transform 1.3s linear, background .4s;
}
.forest-fuse.is-fresh i { transition: none; }
/* Nearly burnt down: the patch throbs red. */
.forest.is-live .forest-patch.is-danger { animation: ffDanger .5s ease-in-out infinite alternate; }
@keyframes ffDanger { from { box-shadow: 0 0 0 0 rgba(217, 46, 46, .85); } to { box-shadow: 0 0 0 6px rgba(217, 46, 46, .15); } }
.forest-patch.is-aimed.is-danger { box-shadow: 0 0 0 4px var(--my-color, var(--purple)); }

/* Ash: the patch is gone for good. */
.forest-patch.is-ash { cursor: default; }
.ff-ash-smoke { transform-box: fill-box; transform-origin: 50% 100%; }
.forest.is-live .ff-ash-smoke { animation: ffAshSmoke 3.2s ease-in-out var(--fire-phase, 0s) infinite; }
.forest.is-live .ff-ash-ember { animation: ffAshEmber 1.7s ease-in-out infinite alternate; }
@keyframes ffAshSmoke { 0% { opacity: 0; transform: translateY(4px) scaleY(.7); } 40% { opacity: .9; } 100% { opacity: 0; transform: translateY(-6px) scaleY(1.1); } }
@keyframes ffAshEmber { to { opacity: .2; } }
.forest-patch.is-burning-down { animation: ffBurnDown .9s ease-out; }
@keyframes ffBurnDown { 0% { filter: brightness(1.6) saturate(1.4); } 20% { transform: translateX(-2px) rotate(-1.5deg); } 40% { transform: translateX(2px) rotate(1.5deg); } 60% { transform: translateX(-1px); } 100% { filter: none; transform: none; } }

.ff-flame { transform-box: fill-box; transform-origin: 50% 100%; }

/* A small settle at water arrival; the effects layer supplies the ripples. */
.forest-patch.is-hit { animation: ffShake .35s ease-in-out var(--impact-delay); }
.forest-patch.is-out { animation: ffSaved .9s ease-out .3s; }
.forest-patch.is-igniting { animation: ffIgnite .6s ease-out; }
@keyframes ffShake { 25% { transform: translateX(-1px) rotate(-.5deg); } 75% { transform: translateX(1px) rotate(.5deg); } }
@keyframes ffSaved { 0% { filter: brightness(1.12); } 100% { filter: none; } }
@keyframes ffIgnite { 0% { transform: scale(.85); filter: brightness(1.8); } 100% { transform: none; filter: none; } }

/* ---- the firefighters ---- */
.forest-ff { display: flex; flex-direction: column; align-items: center; width: var(--crew-size, 62px); --ff-color: #45a8e8; }
.forest-ff-art { display: block; width: var(--crew-size, 62px); height: calc(var(--crew-size, 62px) * .92); }
.forest-ff-art .ff { width: 100%; height: 100%; overflow: visible; }
/* The name tag in the child's own water colour: the same colour as their
   jet and as the name that pops up on the fire they hit. */
.forest-ff-name {
  font-size: 12px; font-weight: bold; color: #fff; background: var(--ff-color);
  border-radius: 999px; padding: 0 7px; max-width: calc(var(--crew-size, 62px) + 24px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.forest-ff.is-me .forest-ff-name { box-shadow: 0 0 0 2px #fff; }
/* The water left: three tanks under the player (level 2). */
.forest-ff-tanks { display: block; width: min(44px, calc(var(--crew-size, 62px) + 4px)); aspect-ratio: 5 / 2; margin: 1px 0 2px; }
.forest-ff-tanks[hidden] { display: none; }
.forest-ff-tanks .ff-tanks { display: block; width: 100%; height: 100%; }
.forest-ff.is-me .forest-ff-tanks { width: min(54px, calc(var(--crew-size, 62px) + 8px)); }
.forest-ff.is-refilling { opacity: .55; }
.forest.is-live .forest-ff:not(.is-refilling) .forest-ff-art { animation: ffBob 1.6s ease-in-out infinite; }
.forest.is-live .forest-ff:nth-child(2n) .forest-ff-art { animation-delay: -.8s; }
.forest.is-live .forest-ff.is-spraying .forest-ff-art { animation: ffSpray .45s ease-in-out 2; animation-delay: 0s; }
@keyframes ffBob { 50% { transform: translateY(-2px); } }
@keyframes ffSpray { 45% { transform: translateX(var(--ff-recoil)) rotate(var(--ff-tilt)); } }

/* ---- the effects layer ---- */
.forest-fx { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; z-index: 2; }
/* The steady stream every firefighter sprays while the round runs. */
.forest-stream {
  fill: none; stroke: var(--jet); stroke-width: 7; stroke-linecap: round; opacity: .55;
  stroke-dasharray: 9 7;
}
.forest-stream.is-me { stroke-width: 9; opacity: .75; }
.forest.is-live .forest-stream { animation: ffStream .45s linear infinite; }
@keyframes ffStream { to { stroke-dashoffset: -32; } }
/* A right answer: a pulsing jet down the same arc. All three strokes are
   revealed together, so highlights never run ahead of the water. */
.forest-jet-stream path, .forest-jet-reveal { fill: none; stroke-linecap: round; }
.forest-jet-outline { stroke: #695080; stroke-width: 15; opacity: .25; stroke-dasharray: 10 7 3 5; }
.forest-jet-body { stroke: var(--jet); stroke-width: 12; opacity: .94; stroke-dasharray: 10 7 3 5; animation: ffWaterPulse .18s ease-in-out 6 alternate; }
.forest-jet-flow { stroke: #fffaf1; stroke-width: 2.2; opacity: .8; stroke-dasharray: 4 8; animation: ffFlow .28s linear infinite; }
.forest-jet-reveal { stroke: white; stroke-width: 30; stroke-dasharray: 100; stroke-dashoffset: 100; animation: ffJetArrive var(--impact-delay) ease-out forwards, ffJetLeave .32s ease-in var(--jet-release-delay) forwards; }
@keyframes ffJetArrive { to { stroke-dashoffset: 0; } }
@keyframes ffJetLeave { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -100; } }
@keyframes ffFlow { to { stroke-dashoffset: -12; } }
@keyframes ffWaterPulse { to { stroke-width: 8; stroke-dashoffset: -25; } }
.forest-spray-bead { fill: var(--jet); stroke: #fffaf1; stroke-width: .9; opacity: 0; animation: ffSprayBead .36s linear var(--bead-delay) 2; }
@keyframes ffSprayBead { 8%, 80% { opacity: .9; } 100% { opacity: 0; } }
.forest-splash-crown { fill: var(--jet); stroke: #fffaf1; stroke-width: 1.3; opacity: 0; animation: ffSplashCrown .32s ease-out var(--impact-delay) 2; }
@keyframes ffSplashCrown { 0% { opacity: 0; transform: scale(.35,.2); } 25% { opacity: .85; transform: scale(.85,1); } 100% { opacity: 0; transform: translateY(7px) scale(1.3,.6); } }
.forest-ripple { fill: none; stroke: var(--jet); stroke-width: 2; opacity: 0; transform-origin: center; transform-box: fill-box; animation: ffRipple .65s ease-out var(--impact-delay) forwards; }
.forest-ripple-light { stroke: #fffaf1; stroke-width: 1.2; animation-delay: calc(var(--impact-delay) + 35ms); }
@keyframes ffRipple { from { opacity: .85; transform: scale(.3); } to { opacity: 0; transform: scale(1.6); } }
.forest-drop { opacity: 0; animation: ffDrop .7s ease-out var(--drop-delay) forwards; }
.forest-drop path { fill: var(--jet); stroke: #fffaf1; stroke-width: .7; }
@keyframes ffDrop { 0% { opacity: 1; transform: translate(0,0) scale(.55); } 40% { opacity: 1; transform: translate(calc(var(--dx) * .55), calc(var(--dy) - 7px)) rotate(var(--drop-turn)); } 100% { opacity: 0; transform: translate(var(--dx), calc(var(--dy) + 14px)) rotate(var(--drop-turn)) scale(.5); } }
@keyframes ffFade { 0%, 70% { opacity: .8; } 100% { opacity: 0; } }

.forest-spark { fill: none; stroke: #f0952a; stroke-width: 4; stroke-linecap: round; stroke-dasharray: 6 8; opacity: 0; animation: ffSpark .9s ease-out forwards; }
@keyframes ffSpark { 0% { opacity: 1; stroke-dashoffset: 60; } 100% { opacity: 0; stroke-dashoffset: 0; } }

/* Who watered it: the shooter's name, in their colour, over the fire. */
.forest-tag {
  position: absolute; z-index: 3; transform: translate(-50%, -100%);
  display: flex; align-items: center; gap: 4px; max-width: 140px;
  background: #fffaf1; color: #614776; font-weight: bold; font-size: 13px;
  padding: 2px 8px; border-radius: 999px; border-bottom: 3px solid var(--tag);
  white-space: nowrap; pointer-events: none; opacity: 0;
  box-shadow: 0 2px 0 rgba(70, 40, 90, .12);
  animation: ffTag 1.4s ease-out .25s forwards;
}
.forest-tag svg { width: 12px; height: 14px; flex: none; fill: var(--tag); }
.forest-tag span { overflow: hidden; text-overflow: ellipsis; }
@keyframes ffTag { 0% { opacity: 0; transform: translate(-50%, -70%) scale(.85); } 18%, 70% { opacity: 1; transform: translate(-50%, -110%); } 100% { opacity: 0; transform: translate(-50%, -140%); } }

/* Steam where a fire went out. */
.forest-steam { position: absolute; z-index: 3; width: 60px; height: 54px; margin: -42px 0 0 -30px; pointer-events: none; }
.forest-steam svg { display: block; width: 100%; height: 100%; overflow: visible; }
.forest-steam-wisp { opacity: 0; animation: ffSteam 1s ease-out var(--wisp-delay) forwards; }
.forest-steam-wisp path { fill: none; stroke-linecap: round; }
.forest-steam-edge { stroke: #b1dae0; stroke-width: 6; opacity: .35; }
.forest-steam-core { stroke: #fffaf1; stroke-width: 3; }
@keyframes ffSteam { 0% { opacity: 0; transform: translateY(7px) scale(.8); } 25% { opacity: .9; } 100% { opacity: 0; transform: translateY(-14px); } }

/* Dark smoke where a patch burnt down. */
.forest-smoke { position: absolute; z-index: 3; width: 70px; height: 60px; margin: -50px 0 0 -35px; pointer-events: none; }
.forest-smoke svg { display: block; width: 100%; height: 100%; overflow: visible; }
.forest-puff { fill: #5d5352; opacity: 0; transform-box: fill-box; transform-origin: center; animation: ffPuff 1.4s ease-out var(--puff-delay) forwards; }
@keyframes ffPuff { 0% { opacity: 0; transform: scale(.3); } 25% { opacity: .85; } 100% { opacity: 0; transform: translateY(-22px) scale(1.5); } }

/* The forest's animals: one hops out of a saved patch with a heart, one
   runs off a patch that burns down. */
.forest-critter {
  position: absolute; z-index: 4; width: var(--critter-size, 34px); height: var(--critter-size, 34px);
  margin: calc(var(--critter-size, 34px) * -.6) 0 0 calc(var(--critter-size, 34px) * -.5); pointer-events: none;
}
.forest-critter .ff-critter { display: block; width: 100%; height: 100%; overflow: visible; }
.forest-critter.is-saved { animation: ffCritterHop 2.2s ease-out .45s both; }
.forest-critter.is-fleeing { animation: ffCritterFlee 1.6s ease-in .2s both; }
.forest-heart {
  position: absolute; left: 50%; top: -30%; transform: translateX(-50%);
  color: #ef5aa8; font-size: calc(var(--critter-size, 34px) * .55); line-height: 1; opacity: 0;
  animation: ffHeart 1.6s ease-out .8s forwards;
}
@keyframes ffCritterHop {
  0% { opacity: 0; transform: translateY(10px) scale(.4); }
  15% { opacity: 1; transform: translateY(-14px) scale(1.05); }
  25% { transform: translateY(0) scale(1); }
  45% { transform: translate(calc(var(--hop) * .4), -12px); }
  60% { transform: translate(calc(var(--hop) * .7), 0); }
  80% { opacity: 1; transform: translate(var(--hop), -8px); }
  100% { opacity: 0; transform: translate(calc(var(--hop) * 1.2), 0); }
}
@keyframes ffCritterFlee {
  0% { opacity: 0; transform: scale(.5); }
  15% { opacity: 1; transform: translateY(-8px) scale(1); }
  100% { opacity: 0; transform: translate(var(--hop), -4px); }
}
@keyframes ffHeart { 0% { opacity: 0; transform: translate(-50%, 4px) scale(.5); } 30% { opacity: 1; transform: translate(-50%, -6px) scale(1.1); } 100% { opacity: 0; transform: translate(-50%, -22px); } }

.forest-recovery { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }

/* Combined fire: compact central flame, soft clustered layers and the
   aliens' purple outline. It adds layers as its fuse burns down. */
.ff-fire-tongue, .ff-fire-heart { transform-box: fill-box; transform-origin: 50% 100%; }
.forest.is-live .ff-fire-tongue { animation: ffFireFlicker var(--fire-pace) ease-in-out var(--fire-phase) infinite alternate; }
.forest.is-live .ff-fire-heart { animation: ffFireHeart .57s ease-in-out var(--fire-phase) infinite alternate-reverse; }
.forest.is-live .ff-fire-embers { animation: ffFireEmbers 1.8s ease-out var(--fire-phase) infinite; }
.ff-fire-smoke { fill: #78716a; opacity: .18; }
.forest.is-live .ff-fire-smoke { animation: ffFireSmoke 2.5s ease-out var(--fire-phase) infinite; }
.forest-patch.is-hit .ff-fire-tongue { animation: ffFireKnock .55s ease-out var(--impact-delay); }
@keyframes ffFireFlicker { from { transform: scale(.94, .96) skewX(-5deg); } to { transform: scale(1.06, 1.12) skewX(5deg); } }
@keyframes ffFireHeart { to { transform: scale(.94, 1.045) translateY(-.5px); } }
@keyframes ffFireEmbers { 0%, 15% { opacity: 0; transform: translateY(2px); } 35% { opacity: .85; } 100% { opacity: 0; transform: translate(2px, -8px); } }
@keyframes ffFireSmoke { 0% { opacity: 0; transform: translateY(2px); } 30% { opacity: .22; } 100% { opacity: 0; transform: translate(2px, -7px); } }
@keyframes ffFireKnock { 35% { transform: scale(.8, .72) skewX(-5deg); } 100% { transform: scale(.96, .95); } }
/* Keep the old flames visible until the water actually arrives, then let
   steam reveal the healthy ground. */
.forest-fire-exit { position: absolute; inset: 0; pointer-events: none; z-index: 1; transform-origin: 50% 80%; animation: ffFireOut .65s ease-out forwards; }
.forest-fire-exit .ff-fire-tongue, .forest-fire-exit .ff-fire-heart { animation: none !important; }
@keyframes ffFireOut { 0%, 42% { opacity: 1; transform: none; } 100% { opacity: 0; transform: scale(.25, .12) translateY(10px); } }

/* Small wind movements keep the living land quiet beneath the fire. */
.forest.is-live .ff-land-life { transform-box: fill-box; transform-origin: center bottom; animation: ffLandBreeze 3.8s ease-in-out var(--land-phase, 0s) infinite alternate; }
@keyframes ffLandBreeze { to { transform: translateY(-.6px) rotate(2deg); } }

/* Red wind-whipped licks and amber forks move against the central flame. */
.ff-fire-side { transform-box: fill-box; transform-origin: center bottom; }
.forest.is-live .ff-fire-side-a { animation: ffSideFlame .43s ease-in-out var(--fire-phase) infinite alternate; }
.forest.is-live .ff-fire-side-b { animation: ffSideFlame .61s ease-in-out var(--fire-phase) infinite alternate-reverse; }
@keyframes ffSideFlame { from { transform: scale(.87, .92) skewX(-7deg); } to { transform: scale(1.04, 1.15) skewX(8deg); } }

@media (prefers-reduced-motion: reduce) {
  .forest.is-live .ff-fire-tongue, .forest.is-live .ff-fire-heart, .forest.is-live .ff-fire-side,
  .forest.is-live .ff-fire-embers, .forest.is-live .ff-fire-smoke, .forest.is-live .ff-land-life,
  .forest.is-live .ff-ash-smoke, .forest.is-live .ff-ash-ember,
  .forest.is-live .forest-ff .forest-ff-art, .forest.is-live .forest-ff.is-spraying .forest-ff-art,
  .forest-patch.is-hit, .forest-patch.is-out, .forest-patch.is-igniting, .forest-patch.is-burning-down,
  .forest-patch.is-hit .ff-fire-tongue,
  .forest.is-live .forest-stream, .forest-jet-flow, .forest-jet-body { animation: none; }
  /* Still shown, just still: the streams, the jet, the name, the danger,
     the fuse (redrawn every poll), the animals. */
  .forest.is-live .forest-patch.is-danger { animation: none; box-shadow: 0 0 0 4px rgba(217, 46, 46, .8); }
  .forest-fuse i { transition: none; }
  .forest-spray-bead { display: none; animation: none; }
  .forest-splash-crown { animation: none; opacity: .5; }
  .forest-jet-reveal { animation: none; stroke-dashoffset: 0; }
  .forest-drop { animation: none; opacity: .7; transform: translate(var(--dx), var(--dy)); }
  .forest-ripple, .forest-steam-wisp, .forest-puff { animation: none; opacity: .5; }
  .forest-tag, .forest-critter.is-saved, .forest-critter.is-fleeing { animation: ffFade 1.7s steps(1) forwards; }
  .forest-heart { animation: none; opacity: 1; }
  .ff-fire-embers { opacity: .65; }
  .forest-fire-exit { display: none; }
}
