@font-face {
  font-family: "Fredoka";
  src: local("Comic Sans MS");
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  /* user-select:none (further down on html,body) stops actual text
     selection, but on iOS/Chrome mobile a long-press over an image or plain
     content can still pop the native "Copiar / Pesquisar / Traduzir"
     callout menu independently of that — that's a different browser
     feature (image/element context callout, not text selection) with its
     own switch. Applied app-wide, not just html/body, since the plot art
     is a stack of <img>s a long-press directly lands on. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
}

/* form controls (button, input...) get their own UA-stylesheet cursor in
   some browsers instead of inheriting — force them back in line so the
   custom cursor shows everywhere, not just over plain divs/spans.
   -webkit-appearance/appearance: none strips iOS Safari's native <button>
   chrome (a glossy gray rounded-pill bezel it paints UNDER any custom
   background/border you give the button) — every button in the app already
   sets its own explicit background/border, so this only ever removes an
   invisible-on-desktop, iOS-only extra layer, never a look we rely on. */
button, input, select, textarea, a {
  cursor: inherit;
  -webkit-appearance: none;
  appearance: none;
}

html, body {
  width: 100%;
  height: 100%;
  /* 100dvh after the 100% fallback (ignored by browsers that don't know
     "dvh", picked up by ones that do) — plain 100% resolves against the
     LAYOUT viewport on mobile Safari, which stays sized as if its address
     bar/bottom bar were fully collapsed even while they're actually shown.
     Everything pinned with position:fixed (the toolbar, HUD, etc.) is
     measured against that same oversized box, so it was landing below the
     real visible area — worse here than most sites, since html/body's own
     overflow:hidden below means the page can never scroll, and it's a
     scroll gesture that would normally make Safari collapse that chrome
     and correct the mismatch. dvh tracks the actual visible viewport
     directly instead. */
  height: 100dvh;
  overflow: hidden;
  background: #bfe8ff;
  font-family: "Fredoka", "Comic Sans MS", "Segoe UI", sans-serif;
  user-select: none;
  cursor: url('../interface/seta_seta_cursor.png') 20 2, auto;
  /* Drops the browser's own double-tap-to-zoom gesture app-wide (pinch-zoom
     itself is blocked at the JS level in main.js — see that file's own
     comment on why CSS/the viewport meta tag alone isn't enough on iOS).
     #farm-viewport/#visit-viewport already had touch-action:none for the
     drag-to-pan fix; this is the same idea for the rest of the page
     (toolbar, panels, HUD) that isn't covered by those two elements. */
  touch-action: manipulation;
}

/* ---------- Farm stage (flat illustrated map) ---------- */
#farm-viewport {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 45%, #cdeeff 0%, #a9dfff 70%, #8fd0f5 100%);
  overflow: hidden;
  /* Without this, a touch-drag here can get eaten by the browser's own
     gesture recognizer (page scroll/pull-to-refresh/pinch) before
     pannable.js's own pointermove handler ever sees it — same reasoning as
     #visit-viewport below. Doesn't affect mouse at all. */
  touch-action: none;
}

#farm-stage {
  position: absolute;
  top: 50%;
  left: 50%;
  /* sized off the LARGER viewport dimension so the (square) stage always
     overflows both axes — basing it on the smaller dimension meant wide,
     short windows never overflowed horizontally, only vertically */
  width: calc(max(100vw, 100vh) * var(--zoom, 1.12));
  height: calc(max(100vw, 100vh) * var(--zoom, 1.12));
  transform: translate(-50%, -50%) translate(var(--pan-x, 0px), var(--pan-y, 0px));
  cursor: grab;
}
#farm-stage.panning {
  cursor: grabbing;
}

#farm-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

#plot-layer {
  position: absolute;
  inset: 0;
  /* #farm-stage sets cursor:grab for the drag-to-pan background — reset it
     here so it doesn't bleed into plots, popups and buttons layered on top */
  cursor: url('../interface/seta_seta_cursor.png') 20 2, auto;
}

/* ---------- Planting slots ---------- */
.plot-slot {
  position: absolute;
  transform: translate(-50%, -50%);
}
/* Back to the original look (flat white fill + hard inset ring, rounded
   square) per direct feedback — the organic blob/soft-glow version didn't
   land. Still lives on a pseudo-element rather than .plot-slot's own
   background, purely so it can be a bit BIGGER than .plot-slot's actual box
   (JS-set width/height, see farmLayout.js's SLOT_SIZE_PCT) without growing
   that box itself — every plot's crop art/water mark/pest worms are
   percentage-sized against it, so resizing it directly would inflate all of
   those right along with it. ::before spilling past that box (and, since
   it isn't pointer-events:none, still bubbling clicks up to .plot-slot's
   own handler) gets the bigger highlight without that side effect. The
   spill is deliberately modest: plots on this grid sit as close as ~4.2%
   apart center-to-center, so there's genuinely little room before
   neighboring highlights would start overlapping. */
.plot-slot::before {
  content: '';
  position: absolute;
  inset: -12%;
  border-radius: 12%;
  background: rgba(255, 255, 255, 0.45);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.7) inset;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: auto;
}
.plot-slot.hovered::before {
  opacity: 1;
}
/* Every plant PNG is drawn at a different aspect ratio (a tall corn stalk
   vs. a short squat bush), so a flat width/height + margin-top offset (the
   old approach) assumed they all share the same proportions and put each
   species' root at a different spot. Anchoring the box's own bottom edge to
   the slot's vertical center, then using object-position to pin each
   sprite's bottom against that box edge, makes every species' root land on
   the same point regardless of its PNG's own aspect ratio. */
.plot-crop-icon {
  position: absolute;
  left: 50%;
  bottom: 46%;
  width: 165%;
  height: 165%;
  --plant-scale: 1; /* plantAt() sets the real per-instance value */
  transform: translateX(-50%) scale(var(--plant-scale));
  /* scale from the base, not the middle — plantAt() applies a random
     per-plant scale on top of this transform, and scaling from center would
     shift the root away from the anchored bottom edge by half the scale
     delta, which is why same-species plants used to land inconsistently
     from one slot to the next. The wind-sway rotation below shares this
     same bottom anchor for the same reason: rotating from the middle would
     swing the trunk/stem sideways instead of just the leaves at the top,
     which read as "the whole plant sliding" rather than swaying in a
     breeze — anchoring at the root keeps the base planted and only the
     top actually moves. */
  transform-origin: 50% 100%;
  object-fit: contain;
  object-position: 50% 100%;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.35));
  /* Mobile Safari has a known repaint bug where a `filter` change from a
     classList.toggle (.sick/.bright below) doesn't visibly repaint an
     element sitting inside a `transform`'d ancestor (here, #farm-stage's
     own pan/zoom) unless something else forces it — the DOM/class state is
     already correct (curing a pest already updates plot.pested server-side
     and client-side instantly), it just doesn't get drawn until some other
     repaint happens to sweep it up, e.g. a full page reload. will-change
     hints the browser to keep this property paint-ready instead of
     caching a stale composited layer for it. */
  will-change: filter;
  /* the sprite renders well outside the (small) slot hitbox — it needs to
     be the actual hover/click target itself, not just the dirt-hole square
     underneath it, since visually that's what the player is pointing at */
  pointer-events: auto;
  transition: transform 0.15s ease;
  /* very subtle — a real breeze nudge, not a cartoon wobble. Duration and
     start point both vary per plot (see plantAt()'s own animation-delay),
     so a full grid of crops doesn't sway in unison like a marching band. */
  animation: plant-sway 4.5s ease-in-out infinite;
}
.plot-crop-icon.hidden {
  display: none;
}
/* ±1.2deg, rooted at the base (see transform-origin above) — enough to
   read as "the leaves are catching a breeze" without ever looking like the
   whole plant (trunk included) is tipping over. */
@keyframes plant-sway {
  0%, 100% { transform: translateX(-50%) scale(var(--plant-scale)) rotate(-1.2deg); }
  50% { transform: translateX(-50%) scale(var(--plant-scale)) rotate(1.2deg); }
}

/* Watered dirt patch — an irregular soft blob, not a clean circle, and kept
   subtle (low opacity, blurred edge) so it reads as damp soil, not a sticker.
   (Reverted back to this dark version — the blue "puddle" redesign was a
   miss, this dark-soil look was already right.) */
.water-mark {
  position: absolute;
  left: 48%;
  top: 58%;
  width: 88%;
  height: 68%;
  transform: translate(-50%, -50%) rotate(-7deg);
  background: radial-gradient(ellipse at 38% 35%, rgba(30, 17, 6, 0.62), rgba(30, 17, 6, 0.28) 65%, transparent 100%);
  border-radius: 58% 42% 47% 53% / 44% 58% 42% 56%;
  filter: blur(1.5px);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.7s ease;
  z-index: 1;
}
/* ---------- Armed-tool cursor (água / fertilizante / antipraga / praga) ---------- */
/* Only água replaces the native cursor (body.tool-cursor-active, set only
   for REPLACE_CURSOR_TOOLS in toolbar.js) — fertilizante/antipraga/praga
   keep the real cursor visible and just float a small icon beside it, see
   .armed-tool-cursor-icon's default (non .replace-cursor) rules below.
   Shared class, not one shared element: toolbar.js's own #tool-cursor-icon
   (água/fertilizante/antipraga, the player's own farm) and visitFarm.js's
   #visit-pest-cursor-icon (praga, raiding someone else's farm) are two
   separate elements in two separate modules — same look, different tool. */
body.tool-cursor-active,
body.tool-cursor-active * {
  cursor: none !important;
}
/* Default look: small, unrotated, offset beside wherever the (still-
   visible) native cursor actually is. */
.armed-tool-cursor-icon {
  position: fixed;
  left: 0;
  top: 0;
  width: 46px;
  height: 46px;
  object-fit: contain;
  /* right edge sits well left of the cursor point, not to its right and
     not crowding the pointer tip — calc(-100% ...) is relative to the
     icon's own width, so this stays correct regardless of the width above */
  transform: translate(calc(-100% - 20px), 6px);
  pointer-events: none;
  z-index: 999;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.4));
}
.armed-tool-cursor-icon.hidden {
  display: none;
}
/* água only: full-size tilted "pouring can" that takes the native cursor's
   place entirely (body.tool-cursor-active hides it) instead of sitting
   beside it. */
.armed-tool-cursor-icon.replace-cursor {
  width: 115px;
  height: 115px;
  transform: translate(-8%, -78%) rotate(-18deg);
}
/* One-shot "pour" tip on an actual successful water action — see
   playWaterPourFx() in toolbar.js. Dips further forward than the resting
   -18deg tilt above, then springs back, like the can is tipping down and
   catching itself. Scoped to .replace-cursor since only água ever gets the
   "pouring" class. */
.armed-tool-cursor-icon.replace-cursor.pouring {
  animation: tool-cursor-pour 0.45s ease-out;
}
@keyframes tool-cursor-pour {
  0% { transform: translate(-8%, -78%) rotate(-18deg); }
  35% { transform: translate(-6%, -68%) rotate(-52deg); }
  100% { transform: translate(-8%, -78%) rotate(-18deg); }
}
/* Coarse-pointer (mobile) equivalent — see toolbar.js's flashToolIconAt().
   No persistent "armed" icon on touch (nothing to follow a hover position
   that doesn't exist), so instead this pops up centered right on the plot
   that was just acted on and fades back out — a one-shot confirmation, not
   a standing overlay. */
.armed-tool-cursor-icon.flash-over-target {
  width: 56px;
  height: 56px;
  transform: translate(-50%, -50%);
  animation: tool-cursor-flash 0.7s ease-out forwards;
}
@keyframes tool-cursor-flash {
  0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0; }
  20% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
  40% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

.water-mark.wet {
  opacity: 1;
}
.plot-crop-icon.bright {
  filter: brightness(1.45) drop-shadow(0 3px 3px rgba(0, 0, 0, 0.35));
  /* ready to harvest — swap the pointing-finger cursor for the open-hand one */
  cursor: url('../interface/seta_mao_cursor.png') 24 5, pointer;
}
.plot-crop-icon.sick {
  /* pest infestation — pauses growth until cured with antipraga */
  filter: sepia(0.65) saturate(0.55) brightness(0.8) hue-rotate(-12deg) drop-shadow(0 3px 3px rgba(0, 0, 0, 0.35));
}

/* Two crawling worms on top of a pested plant — interface/minhoca.png is a
   4-frame horizontal sprite strip (350x50, so 87.5x50 = 1.75:1 per frame).
   background-size:400% fits all 4 frames side by side; stepping
   background-position-x from 0% to 100% in 4 even jumps (steps(4)) cycles
   through them like a flipbook instead of smearing/sliding between frames. */
.plot-pest-worms {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}
.plot-pest-worms.hidden {
  display: none;
}
/* Static single frame (frame 0 of interface/minhoca.png, same
   background-size:400% crop as .pest-cursor-icon-frame below) instead of
   stepping through all 4 — the stepped version (steps(4,start) cycling
   background-position-x) was frame-accurate but still read as the worm
   "disappearing"/relocating to the player, most likely sub-pixel rounding
   at this small a rendered size landing a hair off a frame boundary at
   certain zoom levels. left/top are the ONLY thing that places a worm and
   they never change — the only animated property now is a small in-place
   rotation (see pest-worm-wiggle), which can't move or hide it. */
.pest-worm {
  position: absolute;
  width: 34%;
  height: 19.4%; /* keeps the 1.75:1 per-frame aspect */
  background-image: url('../interface/minhoca.png');
  background-size: 400% 100%;
  background-position: 0 0;
  background-repeat: no-repeat;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3));
  transform-origin: 50% 80%;
  animation: pest-worm-wiggle 1.4s ease-in-out infinite;
}
.pest-worm-1 {
  left: 18%;
  top: 56%;
  --pest-worm-mirror: 1;
}
.pest-worm-2 {
  left: 52%;
  top: 63%;
  --pest-worm-mirror: -1;
  animation-delay: -0.5s;
}
/* the mirroring (scaleX) used to be a plain static `transform` on
   .pest-worm-2 — now that transform itself is what's animating (the
   wiggle), it has to be baked into every keyframe instead via a custom
   property, or worm2's mirror would get overwritten by the animation. */
@keyframes pest-worm-wiggle {
  0%, 100% { transform: scaleX(var(--pest-worm-mirror)) rotate(0deg); }
  50% { transform: scaleX(var(--pest-worm-mirror)) rotate(4deg); }
}

/* Static (non-animated) crop of interface/minhoca.png down to just its
   first frame — used as the "praga" tool's cursor icon in visitFarm.js's
   raid toolbar, riding along .armed-tool-cursor-icon's positioning. Same
   background-size:400% technique as .pest-worm above, just held at frame 0
   instead of stepping through all 4. */
.pest-cursor-icon-frame {
  background-image: url('../interface/minhoca.png');
  background-size: 400% 100%;
  background-position: 0 0;
  background-repeat: no-repeat;
}

/* ---------- Watering fx: a few drops fall from the can onto the plot ----------
   Plain CSS teardrop (gradient + lopsided border-radius), not a rendered
   3D icon — reads clearly as a water drop even at this small a size, which
   the GLB render didn't. */
.water-drop-fx {
  position: absolute;
  width: 0.3%;
  height: 0.43%;
  background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.95) 0%, rgba(180, 225, 255, 0.95) 30%, rgba(90, 170, 230, 0.95) 65%, rgba(35, 110, 180, 0.9) 100%);
  border-radius: 50% 50% 50% 50% / 65% 65% 35% 35%;
  box-shadow: 0 0 2px rgba(20, 80, 150, 0.5);
  transform: translate(-50%, -260%);
  pointer-events: none;
  z-index: 6;
  opacity: 0;
  animation: water-drop-fall 0.55s ease-in forwards;
}
@keyframes water-drop-fall {
  0% { transform: translate(-50%, -260%) scale(0.6); opacity: 0; }
  20% { opacity: 1; }
  85% { transform: translate(-50%, -20%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, 0%) scale(0.65); opacity: 0; }
}
/* a quick expanding ring where the drops actually land, timed to start
   right as the first drops hit — sells the "splash" moment the falling
   drops alone didn't have */
.water-splash-fx {
  position: absolute;
  width: 7%;
  height: 3.5%;
  transform: translate(-50%, -50%);
  border: 2px solid rgba(150, 210, 255, 0.85);
  background: rgba(180, 225, 255, 0.35);
  border-radius: 50%;
  pointer-events: none;
  z-index: 5;
  opacity: 0;
  animation: water-splash-ring 0.45s ease-out 0.32s forwards;
}
@keyframes water-splash-ring {
  0% { transform: translate(-50%, -50%) scale(0.3); opacity: 0.9; }
  100% { transform: translate(-50%, -50%) scale(2); opacity: 0; }
}

/* ---------- Fertilizer fx: a few green particles rise off the plant ----------
   Plain CSS glow dots (radial-gradient), same lightweight approach as the
   water drop above — kept to a handful of particles and ~1s so it reads as
   a quick "growth boost" cue, not visual clutter. */
.fertilizer-particle-fx {
  position: absolute;
  width: 0.55%;
  height: 0.55%;
  background: radial-gradient(circle at 40% 35%, rgba(230, 255, 200, 0.95) 0%, rgba(160, 230, 110, 0.9) 45%, rgba(90, 180, 60, 0.75) 100%);
  border-radius: 50%;
  box-shadow: 0 0 5px rgba(140, 220, 90, 0.75);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 6;
  opacity: 0;
  animation: fertilizer-rise 3s ease-out forwards;
}
@keyframes fertilizer-rise {
  0% { transform: translate(-50%, -50%) translateY(0) scale(0.6); opacity: 0; }
  10% { opacity: 1; }
  75% { opacity: 0.9; }
  100% { transform: translate(-50%, -50%) translateY(-100px) translateX(var(--drift, 0px)) scale(0.25); opacity: 0; }
}

/* ---------- Antipraga fx: same idea as fertilizer above, red instead of
   green (cure cue rather than growth cue) ---------- */
.antipraga-particle-fx {
  position: absolute;
  width: 0.55%;
  height: 0.55%;
  background: radial-gradient(circle at 40% 35%, rgba(255, 220, 210, 0.95) 0%, rgba(235, 120, 100, 0.9) 45%, rgba(190, 50, 40, 0.8) 100%);
  border-radius: 50%;
  box-shadow: 0 0 5px rgba(220, 70, 55, 0.75);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 6;
  opacity: 0;
  animation: fertilizer-rise 3s ease-out forwards;
}

/* ---------- Harvest fx: "+N" and a shrinking plant copy rise & fade ---------- */
.harvest-fx-coins {
  position: absolute;
  transform: translate(-50%, -120%);
  font-size: 16px;
  font-weight: 800;
  color: #ffd23f;
  text-shadow: 0 2px 0 #5b3d20, 0 0 6px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  z-index: 12;
  white-space: nowrap;
  animation: harvest-coins-rise 0.9s ease-out forwards;
}
@keyframes harvest-coins-rise {
  0% { transform: translate(-50%, -120%) scale(0.8); opacity: 0; }
  15% { transform: translate(-50%, -140%) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -280%) scale(1); opacity: 0; }
}

.harvest-fx-icon {
  position: absolute;
  /* smaller than the grown plant sprite (~5.5% of stage width) — these
     percentages resolve against the stage, not a plot-slot, since the fx
     element is appended straight to #plot-layer */
  width: 4%;
  height: 4%;
  object-fit: contain;
  transform: translate(-50%, -100%);
  pointer-events: none;
  z-index: 11;
  animation: harvest-icon-rise 0.6s ease-in forwards;
}
@keyframes harvest-icon-rise {
  0% { transform: translate(-50%, -100%) scale(0.9); opacity: 1; }
  100% { transform: translate(-50%, -300%) scale(0.35); opacity: 0; }
}

/* Two of these fly up per antipraga cure (see spawnWormFleeFx in main.js) —
   same rise/shrink/fade shape as .harvest-fx-icon above, just faster and
   covering more distance ("sobem rápido", fleeing, not floating), and
   cropped to minhoca.png's first frame the same static way
   .pest-cursor-icon-frame is (percentages resolve against the stage, same
   reasoning as .harvest-fx-icon's own comment, since this is also appended
   straight to #plot-layer). */
.worm-flee-fx {
  position: absolute;
  width: 4%;
  height: 2.3%;
  background-image: url('../interface/minhoca.png');
  background-size: 400% 100%;
  background-position: 0 0;
  background-repeat: no-repeat;
  transform: translate(-50%, -100%);
  pointer-events: none;
  z-index: 11;
  animation: worm-flee-rise 0.5s ease-in forwards;
}
@keyframes worm-flee-rise {
  0% { transform: translate(-50%, -100%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -420%) scale(0.5); opacity: 0; }
}

.plot-status-popup {
  position: absolute;
  transform: translate(-50%, -190%);
  background: #3a2716;
  color: #fff8e8;
  font-size: 11px;
  font-weight: 700;
  padding: 5px 10px;
  border-radius: 999px;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4);
  z-index: 8;
  opacity: 1;
  transition: opacity 0.2s ease;
}
.plot-status-popup.hidden {
  opacity: 0;
}

/* Fixed-position (follows the mouse via clientX/clientY, same convention
   as #animal-placement-cursor) rather than plot-anchored like
   .plot-status-popup above — shown after a 1s dwell on a ready-to-harvest
   plot (see armPlantHoverTooltip in main.js), since clicking a ready plot
   harvests it instantly with no confirmation step of its own. */
#plant-hover-tooltip {
  position: fixed;
  transform: translate(14px, 14px);
  background: #3a2716;
  color: #fff8e8;
  font-size: 11px;
  font-weight: 600;
  padding: 8px 12px;
  border-radius: 10px;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
  z-index: 17;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
#plant-hover-tooltip.hidden {
  display: none;
}
.plant-hover-tooltip-name {
  font-size: 13px;
  font-weight: 800;
  margin-bottom: 1px;
}
.plant-hover-tooltip-ready {
  color: #a8e08a;
}

/* Shared "wiggle" keyframes, reused across every multi-part animal by role
   (see .part-* below, and ANIMAL_LAYERS in src/animalRender.js) rather than
   redefined per animal. */
@keyframes peacock-head-bob {
  0%, 100% { transform: rotate(0deg); }
  30% { transform: rotate(-4deg); }
  65% { transform: rotate(3deg); }
}
@keyframes peacock-tail-sway {
  0%, 100% { transform: rotate(0deg) scaleX(1); }
  50% { transform: rotate(1.5deg) scaleX(1.015); }
}
/* a dog's tail wags much faster and wider than a peacock's slow fan-sway */
@keyframes dog-tail-wag {
  0%, 100% { transform: rotate(-6deg); }
  50% { transform: rotate(10deg); }
}
/* continuous sway, not a rare twitch — noticeably alive without being
   frantic; the alpha-dilated ear art (see animais/partes/vaquinha_orelha*)
   gives this enough overlap margin at the base to swing this far without
   the seam against the body showing */
@keyframes ear-sway {
  0%, 100% { transform: rotate(-6deg); }
  50% { transform: rotate(5deg); }
}
/* a wing flap is a faster, wider swing than any of the sway/bob keyframes
   above — both wings share this shape (see .part-wing/.part-wing-alt
   below), each one animating around its OWN measured shoulder-join
   origin so they read as flapping together, not mirrored copies of a
   single motion */
@keyframes wing-flap {
  0%, 100% { transform: rotate(-8deg); }
  50% { transform: rotate(6deg); }
}
/* Generic fallback for any part suffix animal_recipe.py doesn't recognize
   by name but that ends in "Mexe" (see MOVE_MARKER there) — e.g.
   fenix_peitoMexe.png animates as a chest with zero code changes here,
   purely because of how the file is named. A modest wobble in between
   ear-sway and head-bob in size, since it has to look reasonable on a
   part shaped like anything. */
@keyframes generic-wiggle {
  0%, 100% { transform: rotate(-3.5deg); }
  50% { transform: rotate(3deg); }
}
/* "-subtle" variants: server/animal_recipe.py assigns these instead of the
   full-amplitude keyframes above when an auto-measured part has only a
   thin overlap margin at its join (a real, measured case: bufalo's neck
   seam showed background through it at peacock-head-bob's full -4/+3deg
   swing, confirmed by diffing rendered/rotated pixels, not eyeballed —
   dropped to imperceptible at this reduced amplitude instead). Same
   wobble shape, small enough that a thin seam never opens far enough to
   show anything behind it. tail-fan/tail-tip aren't here because their
   base keyframes (peacock-tail-sway, ~1.5deg) are already this subtle —
   see NEEDS_SUBTLE_VARIANT in animal_recipe.py. */
@keyframes subtle-head-bob {
  0%, 100% { transform: rotate(0deg); }
  30% { transform: rotate(-1.4deg); }
  65% { transform: rotate(1deg); }
}
@keyframes subtle-tail-wag {
  0%, 100% { transform: rotate(-2deg); }
  50% { transform: rotate(3deg); }
}
@keyframes subtle-ear-sway {
  0%, 100% { transform: rotate(-2deg); }
  50% { transform: rotate(1.5deg); }
}
@keyframes subtle-wing-flap {
  0%, 100% { transform: rotate(-3deg); }
  50% { transform: rotate(2deg); }
}
@keyframes subtle-generic-wiggle {
  0%, 100% { transform: rotate(-1.5deg); }
  50% { transform: rotate(1.2deg); }
}

/* ---------- Bichos comprados/colocados (src/animalRender.js) ----------
   Static placement handled the same way throughout: left/top pick the
   point on the map, translate(-50%, -N%) anchors that point at the feet —
   N comes from --anchor-pct (set per-instance from ANIMAL_LAYERS'
   anchorPct, since a sitting dog's paws and a standing cow's hooves don't
   land at the same fraction of their own art as the peacock's feet do).
   All animation lives on children instead of fighting over this element's
   own transform. */
#placed-animals-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.placed-animal {
  --anchor-pct: 95%;
  position: absolute;
  transform: translate(-50%, calc(-1 * var(--anchor-pct)));
  /* #placed-animals-layer is pointer-events:none (so the layer itself never
     blocks clicks meant for plots underneath) — this explicit "auto" opts
     each animal back in individually. Clickable (opens #animal-info-panel
     — see main.js) whenever nothing is armed; main.js's delegated handler
     ignores the click while an animal placement is in progress instead of
     this being pointer-events:none, so a click that lands exactly on an
     existing animal while armed still reaches the placement handler
     underneath. */
  pointer-events: auto;
  cursor: pointer;
  z-index: 2;
}
.placed-animal-bob-wrap {
  position: relative;
  width: 100%;
  height: 100%;
}
.placed-animal-part {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}
/* transform-origin for every animated part below is set inline, per part,
   by animalRender.js (ANIMAL_LAYERS' `origin`) — it's the one thing that
   genuinely differs per animal (where a tail/ear/head actually attaches to
   THAT animal's own art), so it can't live in a role-shared rule here the
   way the animation name/timing can. */
.part-body,
.part-horn {
  /* no animation — horns are rigid, body is the static base every other
     layer sits on top of */
}
.part-head {
  animation: peacock-head-bob 2.6s ease-in-out infinite;
}
.part-head-subtle {
  animation: subtle-head-bob 3.1s ease-in-out infinite;
}
.part-tail-fan {
  animation: peacock-tail-sway 4s ease-in-out infinite;
}
.part-tail-wag {
  animation: dog-tail-wag 0.7s ease-in-out infinite;
}
.part-tail-wag-subtle {
  animation: subtle-tail-wag 1.1s ease-in-out infinite;
}
.part-tail-tip {
  animation: peacock-tail-sway 3.2s ease-in-out infinite;
}
.part-ear {
  animation: ear-sway 4.5s ease-in-out infinite;
}
.part-ear-subtle {
  animation: subtle-ear-sway 4.5s ease-in-out infinite;
}
.part-ear-alt {
  animation: ear-sway 5.2s ease-in-out infinite;
  animation-delay: 0.4s;
}
.part-ear-alt-subtle {
  animation: subtle-ear-sway 5.2s ease-in-out infinite;
  animation-delay: 0.4s;
}
.part-wing,
.part-wing-alt {
  animation: wing-flap 1.4s ease-in-out infinite;
}
.part-wing-subtle,
.part-wing-alt-subtle {
  animation: subtle-wing-flap 1.4s ease-in-out infinite;
}
.part-wiggle {
  animation: generic-wiggle 2.8s ease-in-out infinite;
}
.part-wiggle-subtle {
  animation: subtle-generic-wiggle 2.8s ease-in-out infinite;
}
/* single-image animals (kind "simples") have no separate parts to animate.
   A pure scale pulse reads as a static picture inflating/deflating — dead,
   not alive, since nothing actually MOVES, it just grows and shrinks in
   place symmetrically. Combining a slight side-to-side rotation (pivoting
   from the feet, so it reads as a weight shift, not a tilt in midair) with
   a much smaller scale — and offsetting their peaks instead of syncing
   them — reads as a standing animal idly shifting its weight instead of a
   balloon. No extra art needed, same as before. */
.placed-animal-simple {
  transform-origin: 50% 90%;
  animation: animal-idle-sway 3.6s ease-in-out infinite;
}
@keyframes animal-idle-sway {
  0%, 100% { transform: rotate(-1.6deg) scale(1); }
  40% { transform: rotate(0.4deg) scale(1.012); }
  70% { transform: rotate(1.4deg) scale(1.02); }
}

/* ---------- Fixed milk cow (src/milkCow.js) ----------
   Reuses .placed-animal/.placed-animal-part/.part-* as-is (those are
   generic, not tied to the animais.txt catalog) — this is just a second,
   always-present instance built outside that system entirely, positioned
   at a hardcoded spot instead of wherever a player dragged an animal to. */
.milk-cow-actionable,
.milk-bucket-actionable {
  cursor: pointer;
}
.milk-bucket {
  position: absolute;
  transform: translate(-50%, -100%);
  object-fit: contain;
  z-index: 2;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35));
  pointer-events: auto;
}
/* full bucket gets a small "ready to collect" pulse — same idea as the
   ready-to-harvest .plot-crop-icon.bright glow, just a scale pulse instead
   since there's no filter/brightness change to hang the emphasis on here */
.milk-bucket.milk-ready {
  animation: milk-bucket-ready-pulse 1.6s ease-in-out infinite;
}
@keyframes milk-bucket-ready-pulse {
  0%, 100% { transform: translate(-50%, -100%) scale(1); }
  50% { transform: translate(-50%, -100%) scale(1.12); }
}
/* .steal-label's own CSS anchors it to a sized parent via left:50%/
   bottom:108% (each crop plot's own .plot-slot box) — this one isn't
   nested inside anything sized, it's a sibling of the bucket on plotLayer
   with its own explicit left/top (set inline, same coordinate as the
   bucket) — so it needs its own offset upward from that point instead. */
.milk-steal-label {
  bottom: auto;
  transform: translate(-50%, -260%);
}

/* ---------- Zone editor (admin-only, src/zoneEditor.js) ----------
   catcher+svg live inside #plot-layer (same %-coordinate space as
   .plot-slot/.placed-animal), so they pan/zoom with the map exactly like
   everything else drawn on it — points always stay attached to the map
   location they were clicked on. The control panel is a normal
   position:fixed HUD-style card instead, so it stays put on screen
   regardless of how the map underneath is panned. */
#zone-editor-catcher {
  position: absolute;
  inset: 0;
  z-index: 12; /* above plots/animals (z-index 2) so every click here lands on this, not on them */
  cursor: crosshair;
}
#zone-editor-catcher.hidden {
  display: none;
}
#zone-editor-catcher svg {
  width: 100%;
  height: 100%;
  display: block;
}
.zone-editor-polygon {
  fill: rgba(80, 200, 120, 0.35);
  stroke: #2f7d32;
  stroke-width: 0.3;
}
.zone-editor-inprogress {
  fill: none;
  stroke: #ffcc00;
  stroke-width: 0.4;
  stroke-dasharray: 1 0.6;
}
.zone-editor-point {
  fill: #ffcc00;
  stroke: #7a5a00;
  stroke-width: 0.15;
}
.zone-editor-label {
  fill: #1a3d1a;
  font-size: 4px;
  font-weight: 800;
  text-anchor: middle;
  dominant-baseline: middle;
  pointer-events: none;
}
#zone-editor-panel {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 60;
  width: min(90vw, 280px);
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 3px solid #6b4423;
  border-radius: 14px;
  padding: 12px;
  box-shadow: 0 6px 0 rgba(0, 0, 0, 0.25), 0 12px 24px rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: inherit;
}
#zone-editor-panel.hidden {
  display: none;
}
#zone-editor-title {
  font-weight: 800;
  font-size: 14px;
  color: #3a2716;
}
#zone-editor-hint {
  font-size: 11px;
  color: #5b3d20;
  line-height: 1.35;
}
#zone-editor-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 130px;
  overflow-y: auto;
}
.zone-editor-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: rgba(255, 255, 255, 0.45);
  border-radius: 6px;
  padding: 4px 8px;
  font-weight: 700;
  font-size: 12px;
  color: #5b3d20;
}
.zone-editor-remove-btn {
  border: none;
  background: none;
  color: #8a2f2f;
  font-weight: 800;
  cursor: pointer;
  padding: 0 4px;
}
#zone-editor-draw-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
#zone-editor-footer {
  display: flex;
  gap: 6px;
  margin-top: 4px;
}

/* ---------- Animal placement UX (armedAnimal, see main.js) ----------
   Two pieces, both driven by src/animalZones.js's client-side mirror of the
   server's zone/distance rules: a dimming overlay that darkens everywhere
   outside the pen (coarse, static — just the zone shape) and a small badge
   that follows the cursor with ✅/❌ (fine-grained — also accounts for the
   min-distance-from-other-animals rule, which the static overlay can't
   show since it depends on where animals already are). Both only ever
   render while an animal is armed. */
#animal-zone-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 5;
}
#animal-zone-overlay.hidden {
  display: none;
}
#animal-zone-overlay path {
  fill: rgba(10, 20, 10, 0.55);
}
#animal-placement-cursor {
  position: fixed;
  z-index: 17;
  transform: translate(14px, 14px);
  font-size: 22px;
  line-height: 1;
  pointer-events: none;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.45));
}
#animal-placement-cursor.hidden {
  display: none;
}

/* ---------- Plant info panel (top of screen, click a growing plant) ---------- */
/* interface/gui/informacao.png (300x200) is a plain frame with no painted
   divisions — icon on the left, text stacked on the right, both placed
   against the art's measured-empty interior (cream area runs roughly
   x 7-93%, y 20-80%; corners round off past that so content is inset a
   little further to stay clear). */
#plant-info-panel,
#animal-info-panel {
  position: fixed;
  /* below the armed-tool/seed indicator, with enough clearance that its
     close button never sits under it — that pill can wrap to two lines
     with the longer seed messages and used to overlap this panel's top */
  top: 118px;
  left: 50%;
  transform: translateX(-50%);
  /* above #armed-seed-indicator (z-index 15) so its close button is always
     clickable even when both happen to be visible/overlapping at once */
  z-index: 16;
  width: min(85vw, 400px);
  aspect-ratio: 300 / 200;
}
#plant-info-panel.hidden,
#animal-info-panel.hidden {
  display: none;
}
#plant-info-bg,
#animal-info-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
#plant-info-close,
#animal-info-close {
  position: absolute;
  /* re-measured directly against interface/gui/informacao.png's actual
     pixel data (300x200): the art is fully transparent above y=32
     (16%) on the right side — the previous top:6% floated the button in
     that empty margin, well above the frame's rounded top-right corner
     entirely (visible as a gap in the user's screenshot). The corner's
     opaque arc runs roughly y=32..50 (16%-25%), x=272..285 (91%-95%);
     this centers the button on that arc instead. */
  top: 15%;
  right: 4%;
  z-index: 2;
  width: 28px;
  height: 28px;
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
  /* the button itself must stay visually bare — its default UA background/
     border (a white-ish box) was showing through past the ::before circle's
     rounded corners */
  background: none;
  border: none;
  padding: 0;
  /* centers the "✕" glyph inside the circle regardless of exactly where
     within the button box the click lands — see the app-wide close-button
     hitbox fix below */
  display: flex;
  align-items: center;
  justify-content: center;
}
#plant-info-close::before,
#animal-info-close::before {
  content: '';
  /* z-index:-1 is load-bearing, not decoration: an absolutely-positioned
     z-index:auto box paints AFTER plain inline content (the button's own
     "✕" text) per normal stacking order, so without this the circle
     covered the X completely instead of sitting behind it */
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a2f2f;
  background: #f5a5a5;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#plant-info-img {
  position: absolute;
  left: 8%;
  top: 22%;
  width: 28%;
  height: 56%;
  object-fit: contain;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.3));
}
/* Same box as #plant-info-img, but a plain container (not an <img>) since
   it holds the stacked .animal-icon-part layers instead of one flat image
   — see animalRender.js's renderAnimalIcon. */
#animal-info-icon {
  position: absolute;
  left: 8%;
  top: 22%;
  width: 28%;
  height: 56%;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.3));
  /* explicit, not relying on DOM order — #animal-info-body (the "Retirar
     animal" button's box) must never end up painted under this, whatever
     the viewport/content-length combination */
  z-index: 1;
}
#plant-info-body,
#animal-info-body {
  position: absolute;
  z-index: 2;
  left: 40%;
  /* was top:20%/height:60% — fine at desktop widths, but every row's
     font-size is fixed px while the panel itself shrinks on narrow
     viewports (width: min(85vw, 400px)), so the same text stack takes up
     a growing share of a shrinking box. On mobile widths that tipped past
     60%, and justify-content:center + overflow:hidden clipped it evenly
     off BOTH ends at once — the plant name's top and the delete button's
     bottom (worse for taller plant art like milho3.png, since its portrait
     aspect ratio left less spare vertical space to begin with). More
     height here is a flat safety margin, not fitted to any one plant. */
  top: 9%;
  width: 52%;
  height: 84%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  overflow: hidden;
}
#plant-info-progress-row {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 3px;
}
#plant-info-progress-track {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  border: 1px solid #6b4423;
  background: #f6e4b5;
  overflow: hidden;
}
#plant-info-progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(180deg, #b8f0a0 0%, #8fdd6f 100%);
  transition: width 0.3s ease;
}
#plant-info-progress-label {
  font-size: 9px;
  font-weight: 700;
  color: #5b3d20;
  flex-shrink: 0;
}
/* A real 3D game button — pill-shaped, bordered, pressable — same family
   as the app's other action buttons (.ranking-action-btn, the shop's
   "Comprar" button), not a trash-can emoji + underlined web link. No icon:
   the red/brown "careful action" palette plus the confirm step underneath
   already read as "this removes something" without needing a bin glyph. */
#plant-info-delete-btn,
#animal-info-remove-btn {
  align-self: center;
  margin-top: 4px;
  width: 100%;
  border: 2px solid #8a2f2f;
  border-radius: 999px;
  background: linear-gradient(180deg, #f7b8b8 0%, #e57c7c 100%);
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.45), 0 3px 0 rgba(0, 0, 0, 0.2);
  color: #5a1c1c;
  font-weight: 800;
  font-size: 10px;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
  padding: 6px 8px;
  transition: transform 0.08s ease;
}
#plant-info-delete-btn:active,
#animal-info-remove-btn:active {
  transform: scale(0.95);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25);
}
#plant-info-delete-confirm.hidden,
#plant-info-delete-btn.hidden,
#animal-info-remove-confirm.hidden,
#animal-info-remove-btn.hidden {
  display: none;
}
#plant-info-delete-confirm,
#animal-info-remove-confirm {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 3px;
}
#plant-info-delete-confirm-text,
#animal-info-remove-confirm-text {
  font-size: 9px;
  font-weight: 700;
  color: #7a3b1f;
}
#plant-info-delete-confirm button,
#animal-info-remove-confirm button {
  border-radius: 6px;
  font-weight: 800;
  font-size: 9px;
  padding: 2px 7px;
  line-height: 1.4;
}
#plant-info-delete-cancel,
#animal-info-remove-cancel {
  border: 2px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
}
#plant-info-delete-confirm-btn,
#animal-info-remove-confirm-btn {
  border: 2px solid #8a2f2f;
  background: linear-gradient(180deg, #f5a5a5 0%, #e57c7c 100%);
  color: #5a1c1c;
}
#plant-info-name,
#animal-info-name {
  font-size: 16px;
  font-weight: 800;
  color: #3a2716;
  line-height: 1.15;
  margin-bottom: 2px;
  /* A single unbreakable word (every plant name here is one Portuguese
     word, no spaces) can't wrap onto a second line under normal text
     rules, and as a flex child its min-width:auto lets it grow past the
     parent's own width instead of shrinking to fit — #plant-info-body's
     overflow:hidden doesn't reliably catch that in every browser/font
     combination (this only ever showed up on a real phone, not tested here
     against the same font — see #plant-info-body's own comment). Letting
     it break mid-word if it truly has to removes the escape hatch outright,
     regardless of exactly how wide any given font renders it. */
  overflow-wrap: break-word;
  word-break: break-word;
}
/* value/exp/time all read as one consistent "icon + figure" hierarchy under
   the title, instead of the value/time pair and the exp line having
   slightly different treatment */
#plant-info-value,
#plant-info-exp,
#plant-info-time {
  display: flex;
  align-items: center;
  font-size: 12px;
  font-weight: 700;
  color: #5b3d20;
  line-height: 1.25;
  overflow-wrap: break-word;
  word-break: break-word;
}
#plant-info-exp {
  color: #a9790a;
}
#plant-info-status {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.2;
  margin-top: 2px;
}
#plant-info-status:empty {
  display: none;
}
#plant-info-status img {
  width: 16px;
  height: 16px;
  object-fit: contain;
  flex-shrink: 0;
}
#plant-info-status .status-pest {
  color: #7a3b1f;
}

/* ---------- Loading ---------- */
#loading {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg, #bfe8ff 0%, #eaf8d8 100%);
  z-index: 100;
  transition: opacity .5s ease;
}
#loading.hidden {
  opacity: 0;
  pointer-events: none;
}
#loading-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
#loading-title {
  font-size: 22px;
  font-weight: 700;
  color: #5b3d20;
  text-shadow: 0 2px 0 #fff8e8;
}
#loading-bar {
  width: 260px;
  height: 16px;
  background: #fff8e8;
  border: 3px solid #5b3d20;
  border-radius: 10px;
  overflow: hidden;
}
#loading-bar-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(180deg, #9be564, #6fbf3c);
  transition: width .2s ease;
}

/* ---------- Login / register ---------- */
/* Same gate role as #loading (full-screen, same sky gradient, same
   z-index) — shown instead of the farm when there's no valid session, and
   handed off to boot() once login/register succeeds. */
#auth-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: url('../interface/base/wallpaper.jpg') center center / cover no-repeat;
  z-index: 100;
  /* on a short window the card + "criar conta"/"entrar" link below it can
     be taller than the viewport — scroll instead of clipping the link
     off-screen with no way to reach it */
  overflow-y: auto;
  /* wallpaper.jpg has "Terralume" painted into the art itself, roughly in
     the top quarter (the readable name is fully clear by ~28% of the
     image's own height — measured directly off the file). On a desktop
     browser window (wide, closer to the image's own ~16:9 shape) that
     maps to about the same ~28-30% of viewport height, so this default
     is sized for that case: just enough to clear the name without a big
     empty gap above the card. Phones get their own larger override right
     below (see that rule's own comment for why phones need more). */
  padding: max(90px, 29vh) 16px 24px;
  box-sizing: border-box;
}
#auth-overlay.hidden {
  display: none;
}
/* Plain flex column: .auth-frame (the real card) on top, the
   login/register switch-link below it as a normal sibling — NOT inside
   the frame, so it always lands right after the card regardless of how
   tall the card's own content (field count differs between login and
   register) ends up being. */
.auth-card {
  width: min(90vw, 380px);
  /* horizontal-only: left/right auto centers it (redundant with
     #auth-overlay's own justify-content:center, harmless); top/bottom
     deliberately NOT auto — an auto top/bottom margin here is a flex
     item, and #auth-overlay is a single-line flex container, so a vertical
     auto margin would center this in whatever space is left BELOW its own
     padding-top instead of sitting right at it. That extra push is exactly
     what made the card sit lower than the padding value alone suggested. */
  margin: 0 auto;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.auth-card.hidden {
  display: none;
}
/* Same wood-plank card family as #ranking-panel/#friends-panel/
   #settings-panel (see that shared rule) — previously this was a
   screenshot-art PNG (login.png/registro.png) with transparent inputs
   positioned by hand-measured percentages on top of it. That doesn't
   reflow: on a narrow phone the fixed aspect-ratio either shrank the tap
   targets below a usable size or left them badly out of position. A real
   flex-column of real input boxes instead sizes itself to its own content
   at any viewport width, no per-field percentage math to maintain. */
.auth-frame {
  width: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 28px 24px 24px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 4px solid #6b4423;
  border-radius: 20px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.35);
}
.auth-title {
  text-align: center;
  font-size: 26px;
  font-weight: 800;
  color: #5b3d20;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
.auth-subtitle {
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  color: #8a6a3f;
  margin-top: -8px;
  margin-bottom: 4px;
}
.auth-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.auth-field-label {
  font-size: 12px;
  font-weight: 700;
  color: #6b4423;
  padding-left: 2px;
}
.auth-input {
  width: 100%;
  box-sizing: border-box;
  border: 2px solid #6b4423;
  border-radius: 10px;
  background: #fffaf0;
  padding: 11px 12px;
  font-family: inherit;
  font-size: 16px; /* >=16px keeps iOS Safari from auto-zooming in on focus */
  font-weight: 700;
  color: #5b3d20;
  outline: none;
}
.auth-input:focus {
  border-color: #3a5a1f;
  box-shadow: 0 0 0 3px rgba(58, 90, 31, 0.2);
}
.auth-input::placeholder {
  color: #b89968;
  font-weight: 600;
}
.auth-submit {
  margin-top: 6px;
  padding: 13px 10px;
  border: 2px solid #3a5a1f;
  border-radius: 12px;
  background: linear-gradient(180deg, #9be564, #6fbf3c);
  color: #24380f;
  font-family: inherit;
  font-size: 16px;
  font-weight: 800;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  cursor: pointer;
}
.auth-submit:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
}
.auth-submit:disabled {
  opacity: 0.6;
  cursor: default;
}
.auth-error {
  text-align: center;
  font-size: 12.5px;
  font-weight: 700;
  color: #c0392b;
  line-height: 1.3;
}
.auth-error:empty {
  display: none;
}
/* Sits directly on the photographic wallpaper, not on the cream card — a
   text-shadow alone wasn't reliable contrast against a busy background
   image (sky, grass, whatever's behind it varies by viewport size/crop).
   A solid pill behind the text guarantees the same contrast everywhere,
   same reasoning as any other badge/chip in this UI. */
.auth-switch-link {
  display: block;
  margin: 14px auto 0;
  border: none;
  padding: 7px 16px;
  border-radius: 999px;
  background: rgba(255, 250, 240, 0.92);
  font-size: 12.5px;
  font-weight: 700;
  color: #3a5a1f;
  text-decoration: underline;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

/* ---------- HUD ---------- */
/* interface/gui/perfil.webp (1494x658) supplies the whole card — avatar box,
   name plank, coin/star pills (icons painted in) and the EXP tag + bar
   track. Positions below are that art's own layout, measured as
   percentages so they stay aligned at any rendered size (same pattern as
   the toolbar/shop/bag panels). */
#hud {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 10;
  width: min(46vw, 420px);
  aspect-ratio: 1494 / 658;
}
#hud-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
/* no avatar art yet — left as an empty placeholder over the art's dashed box */
/* Level-gated avatar: a chicken portrait (interface/avatar/chicken tier
   N.png) plus a short nickname caption, both inside this same box footprint
   — the caption sits *within* the box (not below the whole perfil.png card)
   since there's no real room left in the frame's art beneath it, but it
   still reads as "under the picture" the way it was asked for. */
#hud-avatar {
  position: absolute;
  left: 8.1%;
  top: 12.16%;
  width: 21.49%;
  height: 51.67%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
#hud-avatar-img {
  width: 100%;
  height: 62%;
  margin-top: 16%;
  object-fit: contain;
}
#hud-avatar-label {
  width: 100%;
  height: 22%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1.05;
  color: #5b3d20;
  text-align: center;
  padding: 0 2px;
}
#hud-farm-name {
  position: absolute;
  left: 33.47%;
  top: 12.16%;
  width: 51.87%;
  height: 16.26%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 6%;
  color: #fff8e8;
  font-weight: 700;
  font-size: 14px;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
#hud-coins,
#hud-level {
  position: absolute;
  top: 37.39%;
  height: 21.88%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 15px;
  color: #5b3d20;
  pointer-events: none;
}
#hud-coins {
  left: 41.3%;
  width: 20.55%;
}
#hud-level {
  left: 73.02%;
  width: 19.68%;
}
#hud-exp-track {
  position: absolute;
  left: 28.78%;
  top: 68.54%;
  width: 36.28%;
  height: 12.92%;
  border-radius: 999px;
  overflow: hidden;
}
#hud-exp-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(180deg, #9be564, #6fbf3c);
  transition: width 0.2s ease;
}
#hud-exp-text {
  position: absolute;
  left: 67.54%;
  top: 68.54%;
  width: 19.41%;
  height: 12.92%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 11px;
  color: #5b3d20;
  pointer-events: none;
}
/* hidden at max level, when exp-value shows "MÁX" instead of a fraction —
   every other .hidden usage in this file is scoped per-element, so a bare
   .hidden class does nothing without these */
#exp-slash.hidden,
#exp-next.hidden {
  display: none;
}
/* premium/real-money item shop entry point — no room left inside perfil.png
   itself below the EXP row, so this sits just under the whole HUD card */
/* Hover (lift) + press (sink) feedback shared by the three round HUD
   icons — :active also fires on a touch tap, which is what makes this work
   on mobile too even though the game hasn't been ported there yet. */
#hud-money-shop,
#hud-ranking-btn,
#hud-friends-btn,
#hud-events-btn,
#hud-animals-btn {
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
#hud-money-shop:hover,
#hud-ranking-btn:hover,
#hud-friends-btn:hover,
#hud-events-btn:hover,
#hud-animals-btn:hover {
  transform: translateY(-2px) scale(1.05);
}
#hud-money-shop:active,
#hud-ranking-btn:active,
#hud-friends-btn:active,
#hud-events-btn:active,
#hud-animals-btn:active {
  transform: scale(0.94);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25);
}
#hud-money-shop {
  /* NOT padding:% here — percentage padding resolves against the
     containing block's width on every side (including top/bottom), which
     blew this out to a tall rectangle instead of a square icon button */
  position: absolute;
  left: 6%;
  top: 103%;
  width: 15%;
  aspect-ratio: 1;
  border-radius: 12px;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.5), 0 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
}
#hud-money-shop img,
#hud-ranking-btn img,
#hud-friends-btn img,
#hud-events-btn img,
#hud-animals-btn img {
  max-width: 72%;
  max-height: 72%;
  object-fit: contain;
}
/* Order (top to bottom) is now: dinheiro (premium chest) / loja de animais /
   amigos / ranking / eventos — each button kept its own id and spacing step
   (43% apart, same as before), just with the top% values reassigned to the
   new order instead of reordering the DOM/JS wiring. */
#hud-ranking-btn {
  position: absolute;
  left: 6%;
  top: 232%;
  width: 15%;
  aspect-ratio: 1;
  border-radius: 12px;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.5), 0 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
}
#hud-friends-btn {
  position: absolute;
  left: 6%;
  top: 189%;
  width: 15%;
  aspect-ratio: 1;
  border-radius: 12px;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.5), 0 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* pending-friend-request counter — "+N", shown on #hud-friends-btn while
   there's at least one incoming request waiting. Kept updated by a
   background poll in friends.js independent of whether the Amigos panel
   is even open, so it's visible the moment a request arrives. */
#hud-friends-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 20px;
  height: 20px;
  padding: 0 4px;
  border-radius: 999px;
  background: #d9483d;
  border: 2px solid #fff3d9;
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#hud-friends-badge.hidden {
  display: none;
}
/* Eventos Ativos — now the LAST icon (see the reordering comment above
   #hud-ranking-btn). Only shown while at least one event from evento.txt is
   currently running (see main.js's initEventsPanel). */
#hud-events-btn {
  position: absolute;
  left: 6%;
  top: 275%;
  width: 15%;
  aspect-ratio: 1;
  border-radius: 12px;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.5), 0 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
}
#hud-events-btn.hidden {
  display: none;
}
/* Loja de animais — now the 2nd icon, right under the premium chest */
#hud-animals-btn {
  position: absolute;
  left: 6%;
  top: 146%;
  width: 15%;
  aspect-ratio: 1;
  border-radius: 12px;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.5), 0 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* ---------- Ranking / busca / amigos panel ---------- */
/* #friends-panel/#events-panel/#animal-shop-panel share this exact visual —
   same wood-plank card family as the rest of this UI, just a different
   container element */
#ranking-panel,
#friends-panel,
#events-panel,
#animal-shop-panel,
#diamond-shop-panel,
#settings-panel {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 20;
  width: min(90vw, 480px);
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 4px solid #6b4423;
  border-radius: 20px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.35);
  overflow: hidden;
  font-family: inherit;
}
#ranking-panel.hidden,
#friends-panel.hidden,
#events-panel.hidden,
#animal-shop-panel.hidden,
#diamond-shop-panel.hidden,
#settings-panel.hidden {
  display: none;
}
/* shared header look — the ranking panel, the friends panel, and the
   friend-search modal all use these same three classes instead of one
   panel's IDs, since duplicate IDs would otherwise show up three times in
   the DOM at once (ranking.js/friends.js each build their own header) */
.panel-header {
  position: relative;
  display: flex;
  align-items: center;
  padding: 14px 52px 10px 14px;
  border-bottom: 3px solid #d9a45c;
  background: rgba(255, 255, 255, 0.25);
  flex-shrink: 0;
}
/* only the ranking/friends panel headers are actually draggable (see
   makeDraggable() calls in ranking.js/friends.js) — the search modal's own
   .panel-header doesn't get this class, so its cursor doesn't imply a
   dragability that isn't wired up */
.draggable-header {
  /* no cursor override here on purpose — it inherits the game's own custom
     cursor from body instead of the browser's native grab/grabbing, which
     is what the user asked for (the OS cursor showing up mid-drag read as
     a bug: "sai do cursor do jogo") */
  touch-action: none; /* same reasoning as .chat-window-header — needed for setPointerCapture dragging */
}
.panel-title {
  font-weight: 800;
  font-size: 16px;
  color: #5b3d20;
}
/* The visible circle is drawn on ::before, NOT on this button itself — a
   button with border-radius:50% directly on it silently drops clicks in
   its own corners in this browser (same finding documented on .panel-arrow
   below: elementFromPoint sampling showed the rounded box's corners fall
   through to whatever's stacked underneath). Keeping the button itself a
   plain square makes its whole box — corners included — a real click
   target, while ::before still paints the exact same circle look. This is
   the actual fix for "clicar em qualquer área do X" — flex-centering the
   glyph alone doesn't touch the corner-click problem. */
.panel-close-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 0;
  width: 32px;
  height: 32px;
  color: #5b3d20;
  font-size: 15px;
  font-weight: 800;
  background: none;
  border: none;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.panel-close-btn::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a2f2f;
  background: #f5a5a5;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}

#ranking-body,
#friends-panel-body,
#events-panel-list,
#animal-shop-body,
#diamond-shop-body,
#settings-panel-body {
  padding: 12px 16px 16px;
  overflow-y: auto;
  /* explicit, not left to default: overflow-y:auto alone makes the UA treat
     overflow-x as an implicit 'auto' too (per spec, when one axis isn't
     'visible' the other stops being 'visible' as well) — anything that
     happens to overflow horizontally (e.g. a tooltip popping out near the
     edge) was silently growing an ugly horizontal scrollbar across the
     whole panel instead of just being clipped */
  overflow-x: hidden;
  position: relative; /* containing block for .panel-toast */
}

/* ---------- Eventos Ativos panel ---------- */
.event-row {
  padding: 10px 12px;
  border: 2px solid #d9a45c;
  border-radius: 10px;
  background: #fffaf0;
  margin-bottom: 8px;
}
.event-row-name {
  font-weight: 800;
  font-size: 14px;
  color: #5b3d20;
  margin-bottom: 3px;
}
.event-row-period {
  font-size: 11px;
  font-weight: 600;
  color: #8a6a3f;
  margin-bottom: 4px;
}
.event-row-bonus {
  font-size: 12px;
  font-weight: 700;
  color: #2f7d32;
}

#ranking-search-row {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 2px solid #6b4423;
  border-radius: 10px;
  background: #fffaf0;
  padding: 6px 10px;
  margin-bottom: 10px;
}
#ranking-search-icon {
  font-size: 15px;
}
#ranking-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 14px;
  color: #5b3d20;
  outline: none;
}
#ranking-search-input::placeholder {
  color: #b89968;
}

#ranking-sort-row {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
}
.ranking-sort-btn {
  flex: 1;
  padding: 6px 4px;
  border: 2px solid #6b4423;
  border-radius: 8px;
  background: #fff3d9;
  color: #5b3d20;
  font-family: inherit;
  font-weight: 600;
  font-size: 12px;
}
.ranking-sort-btn.active {
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
}
.ranking-sort-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

#ranking-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 120px;
}
#ranking-loading,
#ranking-empty {
  padding: 20px 0;
  text-align: center;
  color: #8a6a3f;
  font-size: 13px;
}
.ranking-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 8px;
  border: 2px solid #d9a45c;
  border-radius: 10px;
  background: #fffaf0;
  font-family: inherit;
  text-align: left;
}
.ranking-row:hover {
  background: #ffe9b8;
}
.ranking-row.me {
  border-color: #6fbf3c;
  background: #eaffdb;
}
/* the row used to just BE this flex line directly — pulled out into its
   own child so a row can grow a second line (.ranking-row-pets) below it
   without disturbing this line's own layout */
.ranking-row-main {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ranking-row-avatar {
  width: 34px;
  height: 34px;
  object-fit: contain;
  flex-shrink: 0;
}
.ranking-row-name {
  flex: 1;
  min-width: 0;
  font-weight: 700;
  font-size: 13px;
  color: #5b3d20;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ranking-row-level,
.ranking-row-coins,
.ranking-row-animals {
  font-size: 12px;
  color: #7a5a35;
  white-space: nowrap;
}
/* "what makes them strong" strip — only present when the server actually
   sent top_animals (a row with nothing but Comuns-tier animals, or none
   at all, renders no strip, same as before this existed). Small chips,
   not full shop-card-sized icons — this is a glance, not a browse. */
.ranking-row-pets {
  display: flex;
  gap: 4px;
  padding-left: 42px; /* aligns under the name, past the avatar column */
}
.ranking-pet-chip {
  position: relative;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  border: 1px solid #c98a1f;
  border-radius: 5px;
}
.ranking-pet-chip-icon {
  position: absolute;
  inset: 2px;
}
.ranking-pet-chip-icon .animal-icon-part {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.ranking-pet-chip-count {
  position: absolute;
  bottom: -3px;
  right: -3px;
  font-size: 7px;
  font-weight: 800;
  color: #5b3d20;
  background: #fff3d9;
  border: 1px solid #c98a1f;
  border-radius: 4px;
  padding: 0 2px;
  line-height: 1.3;
}

#ranking-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 10px;
}
/* .panel-arrow (from the shop/bag panels) is position:absolute with per-id
   top/left/right — overridden back to a normal relatively-positioned flex
   item here so the same circular button visual can sit inline in a plain
   row instead of floating over fixed art coordinates */
#ranking-pager .panel-arrow {
  position: relative;
  top: auto;
  left: auto;
  right: auto;
  transform: none;
}
#ranking-page-label {
  font-weight: 700;
  font-size: 13px;
  color: #5b3d20;
}

#ranking-detail {
  margin-top: 12px;
  padding: 10px;
  border: 2px dashed #6b4423;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.4);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
#ranking-detail.hidden {
  display: none;
}
#ranking-detail-avatar {
  width: 44px;
  height: 44px;
  object-fit: contain;
}
#ranking-detail-info {
  flex: 1;
  min-width: 120px;
}
#ranking-detail-name {
  font-weight: 800;
  font-size: 14px;
  color: #5b3d20;
}
#ranking-detail-stats {
  font-size: 12px;
  color: #7a5a35;
}
/* detail card has no inline avatar to align past (its own avatar is a
   separate #ranking-detail-avatar sibling, not part of this flow) — the
   row strip's padding-left:42px assumption doesn't apply here */
#ranking-detail-info .ranking-row-pets {
  padding-left: 0;
  margin-top: 4px;
}
#ranking-detail-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}
.ranking-detail-note {
  font-size: 12px;
  font-weight: 600;
  color: #7a5a35;
}
.ranking-action-btn {
  padding: 7px 10px;
  border: 2px solid #6b4423;
  border-radius: 8px;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
  font-family: inherit;
  font-weight: 700;
  font-size: 12px;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
}
.ranking-action-btn.secondary {
  background: #f5a5a5;
  border-color: #8a2f2f;
}
.ranking-action-btn.small {
  padding: 5px 8px;
  font-size: 11px;
}
.ranking-action-btn:disabled {
  opacity: 0.5;
}

.panel-toast {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  background: #5b3d20;
  color: #fff3d9;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  z-index: 5;
  max-width: 90%;
  text-align: center;
}
.panel-toast.hidden {
  display: none;
}

.friends-section {
  margin-bottom: 14px;
}
.friends-section-title {
  font-weight: 800;
  font-size: 13px;
  color: #5b3d20;
  margin-bottom: 6px;
}
.friends-empty {
  font-size: 12px;
  color: #8a6a3f;
  padding: 8px 0;
}

/* ---------- Configurações (src/settings.js) ----------
   Gear icon fixed to the screen's top-right corner — deliberately its own
   element, not a 6th icon stacked into #hud, since #hud itself lives in the
   top-LEFT corner (see #hud's own position:fixed rule). Same round-button
   look as the #hud icons, just built standalone since it isn't laid out as
   a percentage inside that card's own box. */
#settings-btn {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 15;
  width: 52px;
  height: 52px;
  border-radius: 12px;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.5), 0 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  line-height: 1;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
#settings-btn:hover {
  transform: translateY(-2px) scale(1.05);
}
#settings-btn:active {
  transform: scale(0.94);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25);
}

.settings-slider-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.settings-slider-label {
  flex: 0 0 auto;
  width: 42%;
  font-size: 12px;
  font-weight: 700;
  color: #5b3d20;
}
.settings-slider {
  flex: 1 1 auto;
  accent-color: #6fbf3c;
}
.settings-slider-value {
  flex: 0 0 auto;
  width: 38px;
  text-align: right;
  font-size: 12px;
  font-weight: 700;
  color: #5b3d20;
}
#settings-panel-body .ranking-action-btn {
  margin-top: 2px;
}
.settings-account-row {
  display: flex;
  align-items: center;
}

/* ---------- Loja de animais (src/animalShop.js) ----------
   Real shop cards (icon on top, name/price below, action button at the
   bottom) instead of the cramped 34px list-row icon this used to reuse
   from the friends panel — that read as "tiny and ugly" for something
   meant to show off the animal you're about to buy.
   Panel is deliberately wider than the other list-style panels (see the
   override right below) and the cards themselves a good bit smaller than
   the first pass — with only 3 animals in the catalog, "Comprar"/"Seus
   bichos"/"Já no mapa" each showing 3 big cards in a narrow column stacked
   the panel into a tall, cramped scroll ("muito comprida e pouco larga").
   auto-fit (not auto-fill) is what actually lets 3 cards stretch to fill a
   wide row instead of auto-fill reserving extra empty column tracks that
   just sit there blank. */
#animal-shop-panel {
  width: min(94vw, 620px);
}
.animal-shop-grid {
  display: grid;
  /* fixed 4 columns, not auto-fit — auto-fit's column count depended on
     live container width (minmax(92px,150px) only fit 3 at this panel's
     actual width), requested explicitly as always-4 regardless of width */
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.animal-shop-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 8px 6px 6px;
  background: linear-gradient(180deg, #fff6e0 0%, #ffe6ad 100%);
  border: 3px solid #d9a760;
  border-radius: 12px;
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.6), 0 2px 3px rgba(0, 0, 0, 0.15);
}
.animal-shop-card-icon {
  position: relative;
  width: 64px;
  height: 64px;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.3));
}
/* Shared by every "show this animal as one icon" spot (shop cards, armed
   indicator, animal-info panel) — see animalRender.js's renderAnimalIcon.
   Every part of a given animal shares the same canvas, so stacking them
   with identical position+object-fit:contain reproduces the full animal,
   statically, without needing a separate flattened image per animal. */
.animal-icon-part {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}
.animal-shop-card-name {
  font-weight: 800;
  font-size: 12px;
  color: #3a2716;
}
.animal-shop-card-badge {
  font-size: 10px;
  font-weight: 700;
  color: #7a5a35;
  text-align: center;
}
.animal-shop-card-btn {
  width: 100%;
  margin-top: 3px;
  padding: 5px 4px;
  border-radius: 8px;
  border: 2px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
  color: #5b3d20;
  font-family: inherit;
  font-weight: 700;
  font-size: 11px;
  cursor: pointer;
  transition: transform 0.1s ease;
}
.animal-shop-card-btn:hover:not(:disabled) {
  background: linear-gradient(180deg, #fff0cc 0%, #ffdf9e 100%);
}
.animal-shop-card-btn:active:not(:disabled) {
  transform: scale(0.96);
}
.animal-shop-card-btn:disabled {
  opacity: 0.5;
  pointer-events: none;
}
.animal-shop-card-btn.hidden {
  display: none;
}

/* "Fortalece seu ranking + protege contra roubo" — só em cards de compra
   de bicho Raros+/diamante (ver animal.protected em animalRender.js).
   Cor destacada por ser um benefício, não um dado neutro tipo o badge de
   preço logo abaixo. */
.animal-shop-card-desc {
  font-size: 9px;
  font-weight: 700;
  color: #8a5a1a;
  text-align: center;
  line-height: 1.25;
}

/* Confirmação inline antes de comprar um bicho — mesmo padrão visual de
   #animal-info-remove-confirm (botão de ação alterna com essa linha em vez
   de abrir um modal separado), reaproveitado aqui pra loja normal e pra
   Loja de Diamantes. */
.animal-shop-card-confirm {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  width: 100%;
  margin-top: 3px;
}
.animal-shop-card-confirm.hidden {
  display: none;
}
.animal-shop-card-confirm span {
  flex-basis: 100%;
  font-size: 9px;
  font-weight: 700;
  color: #7a3b1f;
  text-align: center;
}
.animal-shop-card-confirm-cancel,
.animal-shop-card-confirm-yes {
  flex: 1;
  min-width: 0;
  padding: 4px 2px;
  border-radius: 8px;
  font-family: inherit;
  font-weight: 800;
  font-size: 10px;
  line-height: 1.3;
  cursor: pointer;
  transition: transform 0.1s ease;
}
.animal-shop-card-confirm-cancel:active,
.animal-shop-card-confirm-yes:active {
  transform: scale(0.95);
}
.animal-shop-card-confirm-cancel {
  border: 2px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
}
.animal-shop-card-confirm-yes {
  border: 2px solid #8a2f2f;
  background: linear-gradient(180deg, #f5a5a5 0%, #e57c7c 100%);
  color: #5a1c1c;
}

/* Distinct color per section, so "Comprar"/"Seus bichos"/"Já no mapa" read
   as three different states at a glance instead of three identical rows —
   gold = for sale, blue = yours but not placed yet, green = already live
   on the map. Matches the card color treatments right below. */
.animal-shop-section-title-buy {
  color: #8a5a1a;
  border-bottom: 2px solid #d9a45c;
  padding-bottom: 4px;
}
.animal-shop-section-title-owned {
  color: #2f5f7a;
  border-bottom: 2px solid #6fb3d9;
  padding-bottom: 4px;
}
.animal-shop-section-title-placed {
  color: #4a6635;
  border-bottom: 2px solid #7fa85c;
  padding-bottom: 4px;
}

/* "Seus bichos" — owned but not placed yet: cooler blue tone so it doesn't
   read as just another for-sale card with different text underneath. */
.animal-shop-card-owned {
  background: linear-gradient(180deg, #eaf6ff 0%, #c6e6fb 100%);
  border-color: #5a9bc0;
}
.animal-shop-card-owned .animal-shop-card-badge {
  color: #2f5f7a;
}
.animal-shop-card-owned .animal-shop-card-btn {
  border-color: #2f5f7a;
  background: linear-gradient(180deg, #cdeaff 0%, #97cdf0 100%);
  color: #1c3a4a;
}
.animal-shop-card-owned .animal-shop-card-btn:hover:not(:disabled) {
  background: linear-gradient(180deg, #e0f3ff 0%, #aedaf5 100%);
}

/* "já no mapa" — muted green, purely informational (no button, hence the
   reduced bottom padding). A small ribbon confirms it's already live
   instead of leaving this card looking like a plain, cheaper copy of the
   gold shop card. */
.animal-shop-card-readonly {
  padding-bottom: 10px;
  position: relative;
  background: linear-gradient(180deg, #eef4e2 0%, #d3e2bd 100%);
  border-color: #7fa85c;
}
.animal-shop-card-readonly .animal-shop-card-icon {
  opacity: 0.88;
}
.animal-shop-card-readonly::after {
  content: '✓ na fazenda';
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  background: #5a7a3f;
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  padding: 2px 8px;
  border-radius: 999px;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
  white-space: nowrap;
}

/* "⚡ Itens Especiais" (Loja de Diamantes power-ups, src/diamondShop.js's
   premiumItemCardEl) — reuses .animal-shop-card as its base (same gold
   card, same .animal-shop-card-desc/-badge/-confirm-* pieces), only the
   "already active" state needs new rules: same muted-green treatment as
   .animal-shop-card-readonly above (that's the established "already
   have/using this" color in this panel), but WITH the buy button swapped
   for a countdown instead of the readonly's static "✓" ribbon. */
.premium-item-card-active {
  background: linear-gradient(180deg, #eef4e2 0%, #d3e2bd 100%);
  border-color: #7fa85c;
}
.premium-item-card-icon-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}
.premium-item-card-icon-emoji {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 40px;
}
.premium-item-card-countdown {
  margin-top: 4px;
  font-size: 10px;
  font-weight: 800;
  color: #4a6635;
  text-align: center;
}
/* Stacked now, not a single row: the top line (avatar/name/stats + remove)
   plays the same role as before, with the "Visitar plantação" button as
   its own full-width line underneath — easier to spot and to hit than a
   small icon squeezed in next to the remove button. */
.friend-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px 8px;
  border: 2px solid #d9a45c;
  border-radius: 10px;
  background: #fffaf0;
  margin-bottom: 6px;
  width: 100%;
}
.friend-row-top {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
/* the clickable avatar+name+stats area inside a .friend-row that opens
   chat — a separate <button> from .friend-row itself (which also holds the
   remove-friend button as a sibling), since a button can't nest inside
   another button */
.friend-row-clickable {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  border: none;
  background: none;
  padding: 0;
  font-family: inherit;
  text-align: left;
}
.friend-row-clickable:hover .friend-row-name-row {
  text-decoration: underline;
}
.friend-row-avatar {
  width: 34px;
  height: 34px;
  object-fit: contain;
  flex-shrink: 0;
}
.friend-row-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.friend-row-name,
.friend-row-name-row {
  font-weight: 700;
  font-size: 13px;
  color: #5b3d20;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.friend-row-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.friend-row-stats {
  font-size: 11px;
  color: #7a5a35;
}
.friend-row-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
/* Full-width labeled action, its own line below the row — big enough to
   read at a glance and easy to hit, instead of a small tractor-emoji
   circle squeezed next to the remove button. */
.friend-visit-btn {
  width: 100%;
  padding: 7px 8px;
  border-radius: 8px;
  border: 2px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
  color: #5b3d20;
  font-family: inherit;
  font-weight: 700;
  font-size: 12px;
  transition: transform 0.1s ease;
}
.friend-visit-btn:hover {
  background: linear-gradient(180deg, #fff0cc 0%, #ffdf9e 100%);
}
.friend-visit-btn:active {
  transform: scale(0.97);
}
/* also reused by animalShop.js's "Comprar" button when unaffordable */
.friend-visit-btn:disabled {
  opacity: 0.5;
  pointer-events: none;
}
/* Remove stays a small circular "✕" — same red "discard/cancel" language
   as every close button in the app, not a trash-can metaphor. */
.friend-remove-btn {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 800;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.1s ease;
  border: 2px solid #8a2f2f;
  background: #f5a5a5;
  color: #5b3d20;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
}
.friend-remove-btn:active {
  transform: scale(0.92);
}
.friend-remove-btn:hover {
  background: #e57c7c;
}
/* Sits right at the row's — and the panel's — right edge, so the generic
   [data-tooltip]::after (centered under the element) would spill half its
   width past the panel border and get clipped by #friends-panel-body's
   overflow-x:hidden. Anchoring the tooltip's right edge to the button's
   right edge instead makes it grow leftward, into the panel, where there's
   actually room for the text. */
.friend-remove-btn[data-tooltip]::after {
  left: auto;
  right: 0;
  transform: translateY(-6px);
}
.friend-remove-btn[data-tooltip]:hover::after {
  transform: translateY(-10px);
}
.friend-remove-confirm {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: #5b3d20;
  font-weight: 700;
  white-space: nowrap;
}

/* gray = offline (default), green + glow = online. Used standalone next to
   a farm name in the friends list, the search results, and each chat
   window's title bar. */
.friend-status-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #b0b0b0;
  flex-shrink: 0;
}
.friend-status-dot.online {
  background: #5fd35f;
  box-shadow: 0 0 4px #5fd35f;
}

/* ---------- Amigos panel ---------- */
#friends-add-btn {
  width: 100%;
  padding: 10px;
  margin-bottom: 12px;
  border: 2px solid #6b4423;
  border-radius: 10px;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
  font-family: inherit;
  font-weight: 700;
  font-size: 14px;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.2);
}
#friends-list-main {
  display: flex;
  flex-direction: column;
}

/* ---------- Adicionar amigo (busca) — full-screen overlay + card, same
   family as #visit-farm-overlay ---------- */
#friend-search-modal {
  position: fixed;
  inset: 0;
  z-index: 22; /* above #friends-panel (20) so it always sits on top */
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
#friend-search-modal.hidden {
  display: none;
}
#friend-search-card {
  width: min(90vw, 440px);
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 4px solid #6b4423;
  border-radius: 20px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}
#friend-search-input-row {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 2px solid #6b4423;
  border-radius: 10px;
  background: #fffaf0;
  padding: 6px 10px;
  margin: 12px 16px 0;
}
#friend-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 14px;
  color: #5b3d20;
  outline: none;
}
#friend-search-input::placeholder {
  color: #b89968;
}
#friend-search-results {
  padding: 12px 16px 16px;
  overflow-y: auto;
  overflow-x: hidden; /* same reasoning as #friends-panel-body above */
}

/* ---------- Chat windows — floating, draggable, one per open
   conversation (see friends.js's openWindows Map) ---------- */
.chat-window {
  position: fixed;
  width: 300px;
  height: 380px;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 3px solid #6b4423;
  border-radius: 14px;
  box-shadow: 0 8px 0 rgba(0, 0, 0, 0.2), 0 16px 30px rgba(0, 0, 0, 0.3);
  overflow: hidden;
}
.chat-window-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.3);
  border-bottom: 2px solid #d9a45c;
  /* no cursor override — see .draggable-header's comment above */
  touch-action: none; /* setPointerCapture drag needs this — otherwise the
                          browser's own touch-scroll gesture competes with it */
  flex-shrink: 0;
}
.chat-window-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: 13px;
  color: #5b3d20;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Same ::before-circle split as .panel-close-btn above, same reasoning —
   the button itself stays a plain square hitbox (corners included), the
   circle is purely visual on ::before. */
.chat-window-close {
  position: relative;
  z-index: 0;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: #5b3d20;
  font-size: 12px;
  font-weight: 800;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-window-close::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid #8a2f2f;
  background: #f5a5a5;
  pointer-events: none;
}
.chat-window-messages {
  flex: 1;
  overflow-y: auto;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.chat-message {
  align-self: flex-start;
  max-width: 80%;
  padding: 6px 10px;
  border-radius: 12px;
  background: #fffaf0;
  border: 1px solid #d9a45c;
  color: #5b3d20;
  font-size: 12px;
  word-break: break-word;
}
.chat-message.own {
  align-self: flex-end;
  background: #ffe9b8;
  border-color: #6b4423;
}
.chat-window-input-row {
  display: flex;
  gap: 6px;
  padding: 8px;
  border-top: 2px solid #d9a45c;
  flex-shrink: 0;
}
.chat-window-input-row input {
  flex: 1;
  min-width: 0;
  border: 2px solid #6b4423;
  border-radius: 8px;
  background: #fffaf0;
  padding: 6px 8px;
  font-family: inherit;
  font-size: 12px;
  color: #5b3d20;
  outline: none;
}
.chat-window-input-row button {
  flex-shrink: 0;
  padding: 6px 12px;
  border: 2px solid #6b4423;
  border-radius: 8px;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
  font-family: inherit;
  font-weight: 700;
  font-size: 12px;
}

/* ---------- New-message notification toast (bottom-right, independent of
   any open panel/chat window — see friends.js's background inbox poll) ---------- */
#chat-notif-container {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 40; /* above chat windows (30+) so a notification never hides behind one */
  display: flex;
  flex-direction: column-reverse; /* newest toast lowest, stack grows upward */
  gap: 8px;
  pointer-events: none; /* lets clicks pass through the gaps between toasts */
}
.chat-notif-toast {
  display: flex;
  align-items: stretch;
  width: 260px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 3px solid #6b4423;
  border-radius: 12px;
  box-shadow: 0 6px 0 rgba(0, 0, 0, 0.2), 0 12px 24px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  pointer-events: auto;
  animation: chat-notif-in 0.2s ease;
}
@keyframes chat-notif-in {
  from {
    transform: translateY(12px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
.chat-notif-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px;
  border: none;
  background: none;
  text-align: left;
  font-family: inherit;
}
.chat-notif-body:hover {
  background: rgba(255, 255, 255, 0.35);
}
.chat-notif-name {
  font-weight: 800;
  font-size: 12px;
  color: #5b3d20;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-notif-text {
  font-size: 12px;
  color: #7a5a35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-notif-close {
  flex-shrink: 0;
  width: 28px;
  border: none;
  border-left: 2px solid #d9a45c;
  background: rgba(255, 255, 255, 0.25);
  color: #5b3d20;
  font-size: 11px;
  font-weight: 800;
}
.chat-notif-close:hover {
  background: #f5a5a5;
}

/* ---------- Visit farm overlay — the REAL pan/zoom scene pointed at
   another player's plots, not a static grid. Mirrors #farm-viewport/
   #farm-stage/#plot-layer's own rules under new IDs (both scenes coexist
   in the DOM, only one ever visible), and reuses .plot-slot/.plot-crop-icon
   /.water-mark/.plot-status-popup as-is — those are plain classes, not
   scoped to #plot-layer specifically. ---------- */
#visit-farm-overlay {
  position: fixed;
  inset: 0;
  z-index: 21; /* above the player's own farm-viewport (no explicit z-index, i.e. 0) */
}
#visit-farm-overlay.hidden {
  display: none;
}
#visit-viewport {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 45%, #cdeeff 0%, #a9dfff 70%, #8fd0f5 100%);
  overflow: hidden;
  touch-action: none;
}
#visit-stage {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(max(100vw, 100vh) * var(--zoom, 1.12));
  height: calc(max(100vw, 100vh) * var(--zoom, 1.12));
  transform: translate(-50%, -50%) translate(var(--pan-x, 0px), var(--pan-y, 0px));
  cursor: grab;
}
#visit-stage.panning {
  cursor: grabbing;
}
#visit-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}
#visit-plot-layer {
  position: absolute;
  inset: 0;
  cursor: url('../interface/seta_seta_cursor.png') 20 2, auto;
}

/* Dark-to-clear vignette around the screen edges while visiting — purely
   decorative, a quick "this isn't your own farm" visual cue. Sits above
   the scene but below the banner/toolbar/splash (which all need to stay
   fully legible), and never intercepts clicks. */
#visit-vignette {
  position: fixed;
  inset: 0;
  z-index: 22;
  pointer-events: none;
  /* clear through more of the middle (55%), then ramps up harder right at
     the border (0.82 vs the old 0.55) — "mais forte nas beiradas, só nas
     beiradas" */
  background: radial-gradient(
    ellipse at center,
    transparent 55%,
    rgba(0, 0, 0, 0.08) 78%,
    rgba(0, 0, 0, 0.82) 100%
  );
}

/* "Roubar" — floats above any plot the server flags stealable, and is the
   *only* way to trigger a steal (no armable tool for it, see visitFarm.js) */
.steal-label {
  position: absolute;
  left: 50%;
  bottom: 108%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(20, 40, 10, 0.75);
  border: 1px solid #8fdd6f;
  color: #b9ffa0;
  font-weight: 800;
  font-size: 11px;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
  pointer-events: none; /* the whole .plot-slot is already the click target */
  z-index: 2;
  animation: steal-label-pulse 1.4s ease-in-out infinite;
}
.steal-label.hidden {
  display: none;
}
@keyframes steal-label-pulse {
  0%, 100% {
    transform: translateX(-50%) scale(1);
  }
  50% {
    transform: translateX(-50%) scale(1.08);
  }
}

/* Shown only while the visitor's own level is below the raid minimum
   (see RAID_MIN_LEVEL in visitFarm.js) — sits right under the territory
   banner, same visual family as the rest of this UI. */
#level-warning-card {
  position: fixed;
  top: 76px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 23;
  max-width: min(90vw, 380px);
  text-align: center;
  padding: 8px 16px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 3px solid #6b4423;
  border-radius: 10px;
  color: #5b3d20;
  font-weight: 700;
  font-size: 12px;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2), 0 8px 16px rgba(0, 0, 0, 0.3);
}
#level-warning-card.hidden {
  display: none;
}

/* "TERRITÓRIO INIMIGO" — a fixed red stamp-style banner, visible for the
   whole visit (unlike the disguise splash below, which is one-time) */
#territory-banner {
  position: fixed;
  top: 18px;
  left: 50%;
  transform: translateX(-50%) rotate(-2deg);
  z-index: 23;
  background: linear-gradient(180deg, #c0392b 0%, #922019 100%);
  border: 3px solid #5c120c;
  border-radius: 10px;
  padding: 8px 22px;
  text-align: center;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3), 0 8px 20px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}
#territory-banner-main {
  font-weight: 800;
  font-size: 18px;
  color: #fff3d9;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.35);
}
#territory-banner-sub {
  font-weight: 600;
  font-size: 11px;
  color: #ffd9d3;
}

/* Disguise splash — the visitor's own balaclava avatar, shown ~1s on
   entering then faded out (see visitFarm.js's SPLASH_MS) */
#disguise-splash {
  position: fixed;
  inset: 0;
  z-index: 24;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  transition: opacity 0.4s ease;
}
#disguise-splash.hidden {
  opacity: 0;
  pointer-events: none;
}
#disguise-splash-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 24px 32px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 4px solid #6b4423;
  border-radius: 20px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.4);
}
#disguise-splash-img {
  width: 220px;
  height: 220px;
  object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.35));
}
#disguise-splash-text {
  font-weight: 700;
  font-size: 14px;
  color: #5b3d20;
  text-align: center;
}

/* Raid mini-toolbar — only exists while visiting (see visitFarm.js), a
   simple row rather than the paginated #toolbar the player's own farm uses */
#visit-toolbar {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 23;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 3px solid #6b4423;
  border-radius: 16px;
  box-shadow: 0 6px 0 rgba(0, 0, 0, 0.2), 0 12px 24px rgba(0, 0, 0, 0.3);
}
#visit-back-btn {
  padding: 10px 14px;
  border: 2px solid #6b4423;
  border-radius: 10px;
  background: #fff3d9;
  color: #5b3d20;
  font-family: inherit;
  font-weight: 700;
  font-size: 13px;
}
.visit-tool-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 14px;
  border: 2px solid #6b4423;
  border-radius: 10px;
  background: #fff3d9;
  font-family: inherit;
}
.visit-tool-btn.selected {
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: 0 0 0 3px #8fdd6f;
}
.visit-tool-btn.locked {
  cursor: not-allowed;
}
.visit-tool-icon-wrap {
  position: relative;
  display: inline-flex;
}
/* the icon itself always stays visible — only dimmed + crossed out when
   locked, never actually hidden (see visitFarm.js's comment on this).
   opacity:0.4 + full grayscale together washed the 🐛 emoji out to the
   point of being nearly invisible against the toolbar — the red ✕ badge
   below already carries the "locked" signal, so this only needs to read
   as a light dim, not make the icon disappear. */
.visit-tool-btn.locked .visit-tool-icon {
  opacity: 0.8;
  filter: grayscale(0.4);
}
.visit-tool-lock-badge {
  display: none;
  position: absolute;
  top: -4px;
  right: -6px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #d9483d;
  border: 2px solid #fff3d9;
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  line-height: 1;
  align-items: center;
  justify-content: center;
}
.visit-tool-btn.locked .visit-tool-lock-badge {
  display: flex;
}
.visit-tool-icon {
  font-size: 22px;
  line-height: 1;
}
.visit-tool-label {
  font-weight: 700;
  font-size: 11px;
  color: #5b3d20;
}

/* ---------- Armed seed indicator ---------- */
#armed-seed-indicator {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 8px;
  background: #fff8e8;
  border: 3px solid #3f6b1f;
  border-radius: 999px;
  padding: 6px 10px 6px 6px;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.25);
  font-size: 12px;
  font-weight: 700;
  color: #3a2716;
}
#armed-seed-indicator.hidden {
  display: none;
}
#armed-seed-indicator img,
#armed-animal-icon {
  width: 28px;
  height: 28px;
  object-fit: contain;
}
#armed-animal-icon {
  position: relative;
  flex-shrink: 0;
}
#armed-seed-indicator button {
  position: relative;
  /* z-index (any value, not just auto) is required here — without it this
     element doesn't establish its own stacking context, so the ::before's
     z-index:-1 escapes to the nearest ancestor that DOES have one and can
     render behind the whole surrounding card instead of just behind this
     button's own text, making the circle vanish entirely */
  z-index: 0;
  width: 20px;
  height: 20px;
  font-size: 11px;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
}
#armed-seed-indicator button::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #f5a5a5;
  pointer-events: none;
}

/* ---------- Event banner (evento.txt) ---------- */
/* Small, out-of-the-way reminder that a timed event is running — top-right
   corner is the one spot the HUD (top-left), armed-seed pill (top-center)
   and toolbar (bottom-center) all leave clear. Kept in the DOM at all
   times (display never toggles) so the opacity transition can actually
   animate — .hidden switches to display:none only once fully faded out. */
/* Plain colored text, no wood-panel card behind it — just a quick, eye-
   catching reminder rather than another piece of "interface" on screen. */
#event-banner {
  position: fixed;
  /* #settings-btn sits at top:16px/right:16px, 52px tall — this used to
     share that exact spot and get painted on top of it (later in the DOM),
     visually hiding the gear icon under the event text */
  top: 78px;
  right: 16px;
  z-index: 15;
  max-width: 280px;
  background: rgba(30, 20, 10, 0.55);
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 800;
  color: #7cf05a;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease;
}
#event-banner.visible {
  opacity: 1;
}
#event-banner.hidden {
  display: none;
}

/* ---------- Level-up modal ---------- */
/* interface/gui/levelup.webp (1176x1338) already has "Você alcançou um novo
   nível! / Confira suas recompensas:" painted in, plus an empty box below
   it reserved for future per-level reward icons (not built yet — just
   left blank). The only thing missing from the art is the level number
   itself, dropped into the gap between the PARABÉNS! ribbon and that text. */
#levelup-overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
}
#levelup-overlay.hidden {
  display: none;
}
#levelup-card {
  position: relative;
  width: min(60vw, 380px);
  aspect-ratio: 1176 / 1338;
}
#levelup-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
/* floats just outside the card rather than pinned to the art itself — the
   ribbon banner eats the top corners here, so there's no reliable painted
   spot to anchor an X to like the other panels' close buttons do */
#levelup-close {
  position: absolute;
  top: 8%;
  right: -2%;
  z-index: 2;
  width: 40px;
  height: 40px;
  font-size: 19px;
  font-weight: 800;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
#levelup-close::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a2f2f;
  background: #f5a5a5;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#levelup-level-text {
  position: absolute;
  left: 0;
  top: 29.9%;
  width: 100%;
  height: 11.2%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 32px;
  color: #6b4423;
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
  pointer-events: none;
}
#levelup-rewards {
  position: absolute;
  left: 11.05%;
  top: 50.67%;
  width: 77.73%;
  height: 35.43%;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  justify-content: center;
  gap: 12px;
  padding: 10px;
  overflow: hidden;
}
.levelup-reward-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
.levelup-reward-item img {
  width: 52px;
  height: 52px;
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.3));
}
.levelup-reward-item span {
  font-size: 13px;
  font-weight: 700;
  color: #5b3d20;
}
.levelup-reward-value {
  font-size: 10px !important;
  font-weight: 600 !important;
  color: #7a5a30 !important;
}

/* ---------- Toolbar ---------- */
/* the whole bar is the atalhos.png art (6 slots + prev/next arrows painted
   in); slot/arrow positions below are that image's own layout measured as
   percentages, so they stay aligned regardless of how big the bar renders */
#toolbar {
  position: fixed;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  z-index: 10;
  width: min(70vw, 620px);
  aspect-ratio: 1874 / 426;
}
#toolbar-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
#toolbar-slots {
  position: absolute;
  inset: 0;
}
.toolbar-arrow {
  position: absolute;
  top: 43.4%;
  width: 6.1%;
  height: 44.6%;
  transform: translate(-50%, -50%);
  background: transparent;
  border: none;
  padding: 0;
}
#toolbar-prev {
  left: 11.2%;
}
#toolbar-next {
  left: 88.6%;
}
.toolbar-arrow.disabled {
  opacity: 0.35;
  pointer-events: none;
}

.toolbar-slot {
  position: absolute;
  top: 43.4%;
  width: 10.46%;
  height: 44.6%;
  transform: translate(-50%, -50%);
  background: transparent;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* NOT padding:10% — percentage padding resolves against the containing
     block's (the whole bar's) width, not this slot's own — on a slot this
     narrow that ate the entire box. Shrink the icon itself instead. */
}
.toolbar-slot img,
.toolbar-slot .tool-emoji {
  max-width: 78%;
  max-height: 78%;
}
.toolbar-slot.selected img,
.toolbar-slot.selected .tool-emoji {
  filter: drop-shadow(0 0 7px #8fdd6f) drop-shadow(0 0 7px #8fdd6f);
  transform: scale(0.9);
}
/* "click here" nudge — toolbar.js's pulseSlot(), triggered when the player
   tries to plant without a seed selected / on unwatered soil. Glow lives on
   the icon itself (filter:drop-shadow, same recipe as .selected above), NOT
   a box-shadow on .toolbar-slot — that button has no border-radius, so a
   box-shadow there rendered as an ugly square glow poking out past the
   round slot art. One fast, bold pop (not a slow multi-cycle breathe) —
   quick enough that re-triggering it (clicking again right away) rarely
   catches it mid-animation, and the bigger scale/stronger glow is actually
   noticeable at a glance instead of needing to stare at the icon to see it. */
.toolbar-slot.pulse img,
.toolbar-slot.pulse .tool-emoji {
  animation: toolbar-slot-pulse-icon 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes toolbar-slot-pulse-icon {
  0% { filter: none; transform: scale(1); }
  35% { filter: drop-shadow(0 0 14px #8fdd6f) drop-shadow(0 0 14px #8fdd6f); transform: scale(1.4); }
  100% { filter: none; transform: scale(1); }
}
/* fertilizante_turbo (Loja de Diamantes premium item) active — continuous
   green pulse on the Fertilizante slot, independent of .selected (which
   only applies transiently while armed) and .pulse (one-shot nudge) above.
   Both only ever touch `filter`, so if turbo+selected+pulse overlap the
   animation simply wins each frame — reads fine since they're all green. */
.toolbar-slot.fertilizante-turbo img,
.toolbar-slot.fertilizante-turbo .tool-emoji {
  animation: toolbar-slot-turbo-pulse 1.6s ease-in-out infinite;
}
@keyframes toolbar-slot-turbo-pulse {
  0%, 100% { filter: drop-shadow(0 0 4px #35d15c) drop-shadow(0 0 4px #35d15c); }
  50% { filter: drop-shadow(0 0 13px #35d15c) drop-shadow(0 0 13px #35d15c); }
}
.toolbar-slot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  transition: width .08s ease, height .08s ease, filter .1s ease, transform .1s ease;
}
/* the watering-can render looks tiny next to the flat icons at 100% — bump
   it up just in this one spot, and ease off slightly while armed */
.toolbar-slot[data-action="water"] img {
  width: 155%;
  height: 155%;
}
.toolbar-slot.selected[data-action="water"] img {
  width: 136%;
  height: 136%;
}
.tool-emoji {
  font-size: 26px;
  line-height: 1;
  pointer-events: none;
  transition: filter .1s ease, transform .1s ease;
}
.toolbar-slot .tool-label {
  position: absolute;
  bottom: -20px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 10px;
  font-weight: 700;
  color: #5b3d20;
  background: #fff8e8;
  border: 2px solid #6b4423;
  border-radius: 6px;
  padding: 1px 5px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .1s ease;
}
.toolbar-slot:hover .tool-label {
  opacity: 1;
}
.tool-tooltip {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  background: #3a2716;
  color: #fff8e8;
  font-size: 11px;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 8px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.35);
  z-index: 20;
}
.toolbar-slot:hover .tool-tooltip {
  opacity: 1;
  transform: translateX(-50%) translateY(-10px);
}

/* ---------- Generic custom tooltip ----------
   Same visual recipe as .tool-tooltip above, but attribute-driven so any
   element can opt in with data-tooltip="..." instead of the browser's own
   native title="..." balloon (which shows up as a plain white/gray square
   with no styling — this replaces every spot that used to rely on that). */
[data-tooltip] {
  position: relative;
}
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  background: #3a2716;
  color: #fff8e8;
  font-size: 11px;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 8px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.35);
  z-index: 25;
}
[data-tooltip]:hover::after {
  opacity: 1;
  transform: translateX(-50%) translateY(-10px);
}
/* #settings-btn sits at the very top of the viewport (top:16px) with
   nothing above it — the generic tooltip above (bottom:100%) pushed it
   past the top edge, off-screen. Grows downward instead, same as any
   tooltip anchored to the top row would need to. */
#settings-btn[data-tooltip]::after {
  bottom: auto;
  top: 100%;
  transform: translateX(-50%) translateY(6px);
}
#settings-btn[data-tooltip]:hover::after {
  transform: translateX(-50%) translateY(10px);
}

/* ---------- Shop panel ("Lojinha") ---------- */
/* same idea as the shortcuts bar: loja.png supplies the frame, title and
   the 5x3 grid of slots, positions below are that art's own layout */
#shop-panel {
  position: fixed;
  left: 50%;
  bottom: 155px;
  transform: translateX(-50%);
  z-index: 16; /* above #armed-seed-indicator (15) so its close button always wins clicks */
  width: min(76vw, 680px);
  aspect-ratio: 1661 / 1135;
}
#shop-panel.hidden {
  display: none;
}
#shop-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
#shop-close {
  position: absolute;
  top: 3%;
  right: 3.5%;
  z-index: 2;
  width: 40px;
  height: 40px;
  font-size: 19px;
  font-weight: 800;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
#shop-close::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a2f2f;
  background: #f5a5a5;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#shop-balance-row {
  position: absolute;
  left: 50%;
  top: 19.9%;
  transform: translate(-50%, -50%);
  display: flex;
  justify-content: center;
  font-size: 16px;
  font-weight: 700;
  color: #fff8e8;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
  white-space: nowrap;
  pointer-events: none;
}
#shop-balance-row strong {
  font-size: 17px;
}
#shop-balance-row span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
#shop-slots {
  position: absolute;
  inset: 0;
}
.shop-seed-card {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 2px;
  /* no overflow:hidden here (used to have one) — the unaffordable/locked
     tint (.shop-seed-card::before) deliberately extends past this box on
     narrow columns to reach the full painted square, and hidden would clip
     that right back off. Content (icon/text) is sized to fit on its own —
     nothing actually relied on the clip. */
  font-family: inherit;
}
.shop-seed-card:hover {
  transform: translate(-50%, -50%) translateY(-3px);
}
.shop-seed-card.bought {
  animation: shop-card-bought 0.5s ease;
}
@keyframes shop-card-bought {
  0%, 100% { box-shadow: none; }
  30% { box-shadow: 0 0 0 3px #8fdd6f; }
}
/* fully inert, not just dimmed — no partial-gray card you can still poke at.
   The tint is a ::before sized by --tint-w (set per-column in JS) instead of
   a plain background on the card itself — the card's own box is narrower
   than the painted square for columns 0/4 (arrow clearance) and even the
   "full width" columns were a hair short of the actual art, so a background
   sized to the card left a strip of the painted square un-tinted on every
   column, worse on the outer two. ::before is independently sized/centered
   so it always covers the full square regardless of the card's own width,
   and z-index:-1 keeps it behind the icon/text content (same trick as the
   round-button ::before circles elsewhere in this file). A gray tint (not
   filter:grayscale on the whole card) so the red price below still reads as
   red instead of getting desaturated too. */
.shop-seed-card.unaffordable,
.shop-seed-card.locked {
  pointer-events: none;
}
.shop-seed-card.unaffordable::before,
.shop-seed-card.locked::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: var(--tint-w, 100%);
  height: 100%;
  border-radius: 8px;
  z-index: -1;
}
.shop-seed-card.unaffordable::before {
  background: rgba(110, 110, 110, 0.55);
}
.shop-seed-card.unaffordable .shop-seed-name {
  color: #6b6b6b;
}
/* only the cost row turns red — the reward row is unaffected by whether
   you can afford it right now, so it stays green/gold */
.shop-seed-card.unaffordable .price-cost-row {
  color: #c0392b;
}
/* level-gated — a different tint from .unaffordable (bluer, less red) so
   "can't afford this yet" and "too low level for this at all" read as
   different situations at a glance instead of both just looking "broke" */
.shop-seed-card.locked::before {
  background: rgba(70, 90, 120, 0.55);
}
.shop-seed-card.locked .shop-seed-name {
  color: #4a5a6b;
}
.shop-seed-card .shop-seed-lock {
  display: none;
}
.shop-seed-card.locked .shop-seed-price {
  display: none;
}
.shop-seed-card.locked .shop-seed-lock {
  display: block;
  font-size: 10px;
  font-weight: 800;
  color: #e8eef5;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

/* ---------- Buy fx: "-N" and a shrinking seed copy rise & fade off the card ---------- */
.buy-fx-coins {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translate(-50%, 0) scale(0.8);
  font-size: 15px;
  font-weight: 800;
  color: #ff5a5a;
  text-shadow: 0 2px 0 #5b3d20, 0 0 6px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  z-index: 5;
  animation: buy-coins-rise 0.9s ease-out forwards;
}
@keyframes buy-coins-rise {
  0% { transform: translate(-50%, 0) scale(0.8); opacity: 0; }
  15% { transform: translate(-50%, -10px) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -60px) scale(1); opacity: 0; }
}
.buy-fx-icon {
  position: absolute;
  left: 50%;
  top: 28%;
  width: 40px;
  height: 40px;
  object-fit: contain;
  transform: translate(-50%, 0) scale(0.9);
  pointer-events: none;
  z-index: 4;
  animation: buy-icon-rise 0.6s ease-in forwards;
}
@keyframes buy-icon-rise {
  0% { transform: translate(-50%, 0) scale(0.9); opacity: 1; }
  100% { transform: translate(-50%, -70px) scale(0.35); opacity: 0; }
}
.shop-seed-card img {
  width: 40%;
  height: 40%;
  object-fit: contain;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}
.shop-seed-name {
  font-size: 13px;
  font-weight: 700;
  color: #3a2716;
  line-height: 1.1;
  /* toolbar.js's fitLabelToCard shrinks this down to fit a long name (e.g.
     "Flor De Cristal") on one line — max-width/nowrap/ellipsis here is the
     fallback for the rare name that's still too long even at the smallest
     allowed size, so it never visually spills past the card either way. */
  max-width: 92%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Pulled up on top of the plant icon on purpose, SCOPED to the tiny
   toolbar.js shop/bag cards specifically (explicit ask: those cards are
   small/fixed-height enough that the name still read as cramped even with
   fitLabelToCard shrinking it — a slight overlap with the art buys the
   text more room without shrinking it further). Does NOT apply inside
   .diamond-shop-card — that card is a normal-flow, self-sized layout with
   no cramped-height problem to begin with, and the same overlap there just
   ate into the icon for no reason (see src/diamondShop.js's own cards). */
.shop-seed-card .shop-seed-name {
  margin-top: -8px;
  position: relative;
  z-index: 1;
}
/* Cost on its own row, coin payout below it — EXP used to be a 3rd row
   here too but that was overflowing the card's height, so it moved out to
   its own corner badge (.shop-seed-exp below), same spot the bag card
   already puts its EXP figure. */
.shop-seed-price {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.25;
  white-space: nowrap;
}
.shop-seed-price .price-cost-row {
  color: #5b3d20;
}
/* reward stays green (the "you gain this" color used everywhere in the app) */
.shop-seed-price .price-reward-row {
  color: #2f7d32;
}
/* floats in the card's corner instead of stacking with cost/reward —
   mirrors .bag-item-value's positioning exactly. No background plate
   behind it, same reasoning as the bag badge: it sits over the seed art. */
.shop-seed-exp {
  position: absolute;
  top: 2px;
  right: 3px;
  font-size: 8px;
  font-weight: 700;
  color: #5b3d20;
  background: linear-gradient(180deg, #ffe27a, #ffc94d);
  border: 1px solid #c98a1f;
  border-radius: 5px;
  padding: 1px 3px;
  pointer-events: none;
}
.shop-seed-card.locked .shop-seed-exp {
  display: none;
}
/* NOT a floating corner badge (that was the original design — see git
   history if reviving it) — at this card's actual size (~75px wide on
   desktop) there was no corner gap wide enough for a legible badge next
   to the centered icon without shrinking the icon down to the point it
   barely read as art anymore. A small pill in normal flow, right under
   the name, can't ever overlap anything since it just pushes the rest of
   the stack down a few px instead of floating over it. */
.shop-seed-time {
  font-size: 9px;
  font-weight: 700;
  line-height: 1.1;
  color: #234a6b;
  background: linear-gradient(180deg, #cfe8ff, #a9d4ff);
  border: 1px solid #5f96c9;
  border-radius: 5px;
  padding: 0px 4px;
  white-space: nowrap;
}
.shop-seed-card.locked .shop-seed-time {
  display: none;
}

/* ---------- Loja de Diamantes (src/diamondShop.js) ---------- */
/* Small, static (no pagination) catalog — a normal-flow CSS Grid instead
   of toolbar.js's absolute-positioned/JS-sized slot grid (.shop-seed-card
   has no width/height of its own; it only works inside that slot system).
   Reuses .shop-seed-name/.shop-seed-time/.shop-seed-price for consistent
   typography with the normal shop, same idea as .animal-shop-card reusing
   renderAnimalIcon. */
#diamond-shop-balance {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 0 4px;
  font-size: 16px;
  font-weight: 800;
  color: #234a6b;
}
#diamond-shop-balance img {
  width: 20px;
  height: 20px;
  object-fit: contain;
}
/* Prominent CTA above the plant/animal grid — deliberately louder than any
   other button in this panel (gold gradient + a slow pulsing glow) so it
   reads as "the special action" at a glance, same idea real cash-shop
   buttons use to stand out from the regular catalog around them. */
#diamond-buy-packages-btn {
  display: block;
  width: calc(100% - 32px);
  margin: 2px 16px 10px;
  padding: 10px 12px;
  border: 3px solid #8a5a1a;
  border-radius: 12px;
  background: linear-gradient(180deg, #fff2c4 0%, #ffd75e 45%, #f0a827 100%);
  color: #5a3a0a;
  font-family: inherit;
  font-weight: 800;
  font-size: 14px;
  text-align: center;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.2), 0 0 0 rgba(240, 168, 39, 0.6);
  cursor: pointer;
  animation: diamond-cta-pulse 1.8s ease-in-out infinite;
}
#diamond-buy-packages-btn:hover {
  background: linear-gradient(180deg, #fff8dc 0%, #ffe184 45%, #f5bb4c 100%);
}
#diamond-buy-packages-btn:active {
  transform: scale(0.97);
}
@keyframes diamond-cta-pulse {
  0%, 100% {
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.2), 0 0 0 rgba(240, 168, 39, 0.55);
  }
  50% {
    box-shadow: 0 3px 0 rgba(0, 0, 0, 0.2), 0 0 14px 4px rgba(240, 168, 39, 0.55);
  }
}

.diamond-shop-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.diamond-shop-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 8px 4px 6px;
  background: linear-gradient(180deg, #fff6e0 0%, #ffe6ad 100%);
  border: 3px solid #d9a760;
  border-radius: 12px;
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.6), 0 2px 3px rgba(0, 0, 0, 0.15);
}
.diamond-shop-card img {
  width: 44px;
  height: 44px;
  object-fit: contain;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}
.diamond-price-icon {
  width: 9px;
  height: 9px;
  object-fit: contain;
  vertical-align: -1px;
  margin-right: 2px;
}
.diamond-price-row {
  color: #1a6ba8 !important; /* overrides .price-cost-row's brown — diamonds read blue, not coin-brown */
}
.diamond-shop-buy-btn {
  margin-top: 4px;
  padding: 4px 10px;
  border: 2px solid #3a5a1f;
  border-radius: 8px;
  background: linear-gradient(180deg, #9be564, #6fbf3c);
  color: #24380f;
  font-family: inherit;
  font-weight: 800;
  font-size: 11px;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
  cursor: pointer;
}
.diamond-shop-buy-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
@media (max-width: 820px) {
  .diamond-shop-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
/* Diamond shop's own qty-picker (plants only — see src/diamondShop.js's
   own comment). A plain flat card, not the painted informacao.png frame
   the normal shop's #shop-buy-modal uses — this panel is a newer, CSS-only
   design throughout (same family as #ranking-panel/#friends-panel), and a
   flat card sizes itself correctly on any width without needing painted-
   art pixel positions measured per breakpoint. */
#diamond-buy-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 16px; /* matches the panel's own corner radius so it never peeks past it */
  z-index: 25;
}
#diamond-buy-backdrop.hidden {
  display: none;
}
#diamond-buy-modal {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 26;
  width: min(80vw, 260px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 18px 16px 16px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 4px solid #6b4423;
  border-radius: 16px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.35);
}
#diamond-buy-modal.hidden {
  display: none;
}
#diamond-buy-icon {
  width: 64px;
  height: 64px;
  object-fit: contain;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}
#diamond-buy-name {
  font-size: 15px;
  font-weight: 800;
  color: #3a2716;
  text-align: center;
}
#diamond-buy-qty-row {
  display: flex;
  align-items: center;
  gap: 14px;
}
#diamond-buy-qty-row button {
  width: 32px;
  height: 32px;
  border: 2px solid #6b4423;
  border-radius: 8px;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
  font-size: 16px;
  font-weight: 800;
  cursor: pointer;
}
#diamond-buy-qty-row button.disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}
#diamond-buy-qty-value {
  min-width: 28px;
  text-align: center;
  font-size: 16px;
  font-weight: 800;
  color: #3a2716;
}
#diamond-buy-total {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 16px;
  font-weight: 800;
  color: #1a6ba8;
}
#diamond-buy-total .diamond-price-icon {
  width: 16px;
  height: 16px;
}
#diamond-buy-actions {
  display: flex;
  gap: 8px;
  width: 100%;
  margin-top: 4px;
}
#diamond-buy-actions .diamond-shop-buy-btn {
  flex: 1;
  padding: 8px 0;
  font-size: 13px;
}
.diamond-buy-cancel-btn {
  background: #f5a5a5 !important;
  border-color: #8a2f2f !important;
  color: #5b1a1a !important;
}

/* "Comprar Diamantes" packages catalog — same flat-card/backdrop family as
   #diamond-buy-backdrop/#diamond-buy-modal above, just wider (8 packages in
   a grid instead of one qty picker) and with its own ✕ close button since
   there's no cancel/confirm step here, just browsing a catalog. */
#diamond-packages-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 16px;
  z-index: 25;
}
#diamond-packages-backdrop.hidden {
  display: none;
}
#diamond-packages-modal {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 26;
  width: min(90vw, 340px);
  max-height: 85%;
  overflow-y: auto;
  padding: 16px 14px 14px;
  background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
  border: 4px solid #6b4423;
  border-radius: 16px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.35);
}
#diamond-packages-modal.hidden {
  display: none;
}
#diamond-packages-header {
  font-size: 16px;
  font-weight: 800;
  color: #3a2716;
  text-align: center;
  margin-bottom: 10px;
}
#diamond-packages-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid #6b4423;
  border-radius: 50%;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  color: #5b3d20;
  font-weight: 800;
  font-size: 12px;
  cursor: pointer;
}
#diamond-packages-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
.diamond-package-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 10px 6px 8px;
  background: linear-gradient(180deg, #fff6e0 0%, #ffe6ad 100%);
  border: 3px solid #d9a760;
  border-radius: 12px;
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.6), 0 2px 3px rgba(0, 0, 0, 0.15);
}
.diamond-package-price {
  font-size: 14px;
  font-weight: 800;
  color: #3a2716;
}
.diamond-package-amount {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: 12px;
  font-weight: 700;
  color: #1a6ba8;
}
.diamond-package-amount img {
  width: 13px;
  height: 13px;
  object-fit: contain;
}
.diamond-package-buy-btn {
  width: 100%;
  margin-top: 4px;
  padding: 5px 4px;
  border: 2px solid #3a5a1f;
  border-radius: 8px;
  background: linear-gradient(180deg, #9be564, #6fbf3c);
  color: #24380f;
  font-family: inherit;
  font-weight: 800;
  font-size: 11px;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
  cursor: pointer;
}
.diamond-package-buy-btn:hover {
  background: linear-gradient(180deg, #b0f07c, #7ecf4c);
}
.diamond-package-buy-btn:active {
  transform: scale(0.96);
}
@media (max-width: 420px) {
  #diamond-packages-grid {
    grid-template-columns: 1fr 1fr;
    gap: 6px;
  }
  #diamond-packages-modal {
    padding: 14px 10px 10px;
  }
}

/* ---------- Shop buy-confirmation modal ---------- */
/* interface/gui/informacao.png centered over the shop's own grid (not a
   full-screen overlay like the level-up card) — click a card, this pops up
   in the middle of the loja.png window with a qty +/- picker and a live
   total, replacing the old inline "comprar? sim/não" row on the card. */
#shop-buy-backdrop {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: rgba(58, 39, 22, 0.45);
  border-radius: inherit;
}
#shop-buy-backdrop.hidden {
  display: none;
}
#shop-buy-modal {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  width: min(60%, 300px);
  aspect-ratio: 300 / 200;
}
#shop-buy-modal.hidden {
  display: none;
}
#shop-buy-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
#shop-buy-close {
  position: absolute;
  /* same corner offsets as #plant-info-close — same source art, same
     measured-opaque corner, so no floating-outside-the-frame redo needed */
  top: 10%;
  right: 1%;
  z-index: 2;
  background: none;
  border: none;
  padding: 0;
  width: 26px;
  height: 26px;
  font-size: 12px;
  font-weight: 800;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
#shop-buy-close::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a2f2f;
  background: #f5a5a5;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#shop-buy-title {
  /* informacao.png's own cream area only starts around y 20% — this used to
     sit at 8%, up in the border/transparent margin above the frame */
  position: absolute;
  left: 0;
  top: 21%;
  width: 100%;
  text-align: center;
  font-size: 13px;
  font-weight: 800;
  color: #3a2716;
  padding: 0 8%;
}
#shop-buy-icon {
  position: absolute;
  left: 6%;
  top: 36%;
  width: 28%;
  height: 40%;
  object-fit: contain;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.3));
}
#shop-buy-body {
  position: absolute;
  left: 37%;
  top: 36%;
  width: 57%;
  height: 40%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
}
#shop-buy-name {
  font-size: 14px;
  font-weight: 800;
  color: #3a2716;
  /* single unbreakable Portuguese word, same flex min-width:auto escape
     hatch as #plant-info-name (see that rule's own comment) — this is the
     defensive fix for it, not tuned to one specific measurement */
  overflow-wrap: break-word;
  word-break: break-word;
}
#shop-buy-qty-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
/* gets clicked repeatedly in quick succession (unlike a one-off close
   button), so it needs a genuinely full click target. The hit box (this
   element) is deliberately bigger than the visible circle (::before, inset
   4px in) — a tap landing in that 4px margin just outside the drawn circle
   still registers, so there's no razor-thin edge where a slightly-off
   click silently misses */
#shop-buy-qty-row button {
  position: relative;
  /* z-index (any value, not just auto) is required — without it this
     element doesn't establish its own stacking context, so the ::before's
     z-index:-1 escapes to the nearest ancestor that has one (the buy
     modal) and renders behind the whole card instead of just behind this
     button's own "−"/"+" text, making the circle vanish entirely */
  z-index: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #5b3d20;
  font-size: 16px;
  font-weight: 800;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
}
#shop-buy-qty-row button::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  border: 2px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  pointer-events: none;
}
#shop-buy-qty-row button.disabled {
  opacity: 0.4;
  pointer-events: none;
}
#shop-buy-qty-value {
  min-width: 20px;
  text-align: center;
  font-size: 15px;
  font-weight: 800;
  color: #3a2716;
}
#shop-buy-total {
  font-size: 13px;
  font-weight: 700;
  color: #5b3d20;
}
#shop-buy-confirm-btn {
  position: absolute;
  left: 8%;
  bottom: 6%;
  width: 84%;
  height: 15%;
  border-radius: 10px;
  border: 3px solid #3f6b1f;
  background: linear-gradient(180deg, #b8f0a0 0%, #8fdd6f 100%);
  color: #2d4a17;
  font-weight: 800;
  font-size: 14px;
}

/* ---------- Bag / inventory panel ("Inventário") ---------- */
/* inventario.png is a plain wooden board — no slots painted in like
   loja.png, so the 5x3 grid below is drawn entirely with CSS
   (.bag-item-card's own border/background), positioned against the art's
   measured-empty cream area. */
#bag-panel {
  position: fixed;
  left: 50%;
  bottom: 155px;
  transform: translateX(-50%);
  z-index: 16; /* above #armed-seed-indicator (15), same as #shop-panel */
  width: min(76vw, 680px);
  aspect-ratio: 1068 / 846;
}
#bag-panel.hidden {
  display: none;
}
#bag-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}
#bag-close {
  position: absolute;
  top: 14%;
  right: 4%;
  z-index: 2;
  background: none;
  border: none;
  padding: 0;
  width: 40px;
  height: 40px;
  font-size: 19px;
  font-weight: 800;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
#bag-close::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a2f2f;
  background: #f5a5a5;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#bag-balance-row {
  position: absolute;
  left: 9%;
  top: 29%;
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 17px;
  font-weight: 700;
  color: #5b3d20;
  pointer-events: none;
}
#bag-balance-row strong {
  font-size: 19px;
}
/* no arrows painted into inventario.png like loja.png has — these are
   plain CSS buttons, same round look as #bag-close/#shop-close, sitting in
   the wooden border on either side of the grid. Shared by the bag and the
   shop (loja.png's own arrows got removed from the art, same as
   inventario.png never had any) — position is per-panel via the #id rules
   below since the two boards aren't the same shape. */
.panel-arrow {
  /* z-index needed — #shop-slots/#bag-slots are appended after these in the
     DOM, so without it they'd stack on top and silently eat the arrows'
     clicks. Never noticed before because the arrows stayed disabled
     (pointer-events:none) until there were enough items for a real 2nd
     page — a real seed list finally surfaced it. */
  z-index: 2;
  position: absolute;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  color: #5b3d20;
  font-size: 18px;
  font-weight: 800;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
}
/* the actual circle is drawn here, not on .panel-arrow itself — a rounded
   element silently drops corner clicks in this browser (confirmed via
   elementFromPoint sampling: the bounding box's corners fall through to
   whatever's stacked underneath, e.g. #shop-slots/#bag-slots). Keeping
   .panel-arrow itself a plain square makes its whole box, corners
   included, an actual click target, while this ::before still paints as a
   full circle so it looks exactly like it always did. Every other round
   button in the app (close buttons, qty +/-) uses the same split. */
.panel-arrow::before {
  content: '';
  z-index: -1;
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #6b4423;
  background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
#bag-prev {
  top: 56.5%;
  left: 8%;
  width: 30px;
  height: 30px;
}
#bag-next {
  top: 56.5%;
  right: 8%;
  width: 30px;
  height: 30px;
}
/* same 30px size as the bag's, for consistency — loja.png's border is
   narrower than inventario.png's, so at this size these sit a little into
   the painted border trim on both sides instead of floating purely in the
   cream gap like the bag's do. Biased toward the edge (away from the
   cards) rather than centered in that gap — touching a card read as much
   worse than touching the border art. */
#shop-prev {
  top: 58%;
  left: 6.3%;
  width: 30px;
  height: 30px;
}
#shop-next {
  top: 58%;
  right: 6.3%;
  width: 30px;
  height: 30px;
}
.panel-arrow.disabled {
  opacity: 0.35;
  pointer-events: none;
}
#bag-slots {
  position: absolute;
  inset: 0;
}
/* Centered over the same area the 5x3 slot grid occupies (BAG_COL_X_PCT/
   BAG_ROW_Y_PCT in toolbar.js run roughly 20-92% x, 41-85% y) — shown
   instead of a silently empty grid when the player owns zero seeds. */
/* Near the top of the window, not centered over the slot grid (was
   top:63%, right in the middle of the cards — read as "buried inside the
   inventory" instead of a banner announcing it's empty). top:14% still
   collided with the "Inventário" title painted into inventario.png itself
   — this sits below that art and below #bag-balance-row's coin count
   (top:29%) instead, clear of both, with room above the grid (~38%). */
#bag-empty-msg {
  position: absolute;
  left: 50%;
  top: 30%;
  transform: translate(-50%, -50%);
  width: 74%;
  text-align: center;
  font-size: 14px;
  font-weight: 700;
  color: #8a6a3f;
  line-height: 1.4;
}
#bag-empty-msg.hidden {
  display: none;
}
.bag-item-card {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 3px;
  background: linear-gradient(180deg, #fff6e0 0%, #ffe6ad 100%);
  border: 3px solid #d9a760;
  border-radius: 10px;
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.6), 0 2px 3px rgba(0, 0, 0, 0.15);
  font-family: inherit;
}
.bag-item-card:hover {
  transform: translate(-50%, -50%) translateY(-3px);
}
.bag-item-card img {
  width: 52%;
  height: 52%;
  object-fit: contain;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}
/* "2x Pessego" — count and name share one row now instead of stacking on
   two, which is what freed up enough height for .bag-item-value (below) to
   move out from behind the icon into normal flow. */
.bag-item-name-row {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 3px;
  max-width: 100%;
}
.bag-item-name {
  font-size: 12px;
  font-weight: 700;
  color: #3a2716;
  line-height: 1.1;
  /* same fitLabelToCard treatment as .shop-seed-name — see that rule's
     comment. min-width:0 is the flexbox-specific part: a flex item's
     default min-width is auto (= its content size), which silently
     defeats max-width/ellipsis unless overridden. */
  min-width: 0;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bag-item-count {
  font-size: 11px;
  font-weight: 700;
  color: #6b4423;
  line-height: 1.1;
}
/* Normal flow now, centered below the name row — this used to float
   absolutely positioned in the card's top-right corner, right on top of
   the icon, which was unreadable for big sprites (a full peach tree fills
   the card corner to corner). Mirrors the shop card's price block. */
.bag-item-value {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 8px;
  font-weight: 700;
  line-height: 1.2;
  pointer-events: none;
}
/* green, matching the shop card's reward row — same "you gain this" color
   used app-wide, now that this shows 💰 instead of the old wheat icon */
.bag-item-value > span:first-child {
  color: #2f7d32;
}
/* small yellow banner, same as the shop card's .shop-seed-exp — needed a
   solid backing to stay legible against whatever's behind it */
.bag-item-exp {
  color: #5b3d20;
  background: linear-gradient(180deg, #ffe27a, #ffc94d);
  border: 1px solid #c98a1f;
  border-radius: 5px;
  padding: 1px 3px;
  margin-top: 1px;
}

/* ==========================================================================
   Responsividade — passo 1 (celular)
   Tudo aqui dentro só se aplica em telas estreitas; a versão desktop (fora
   desta media query) fica exatamente como estava. Ver também src/pannable.js
   (arrastar com o dedo, não só botão direito) e a meta viewport-fit=cover em
   index.html (necessária pra env(safe-area-inset-*) valer algo em telas com
   notch/gesture bar).
   ========================================================================== */
@media (max-width: 820px) {
  /* #auth-overlay's own comment explains the base (desktop) value — on a
     tall/narrow phone viewport the wallpaper is height-bound instead
     (background-size:cover matches the FULL image height to the
     viewport, no vertical crop), so the name sits noticeably lower as a
     fraction of viewport height there (~34-38%) than it does on a wide
     desktop window. Needs its own larger number, not just the desktop
     default. */
  #auth-overlay {
    padding-top: max(140px, 38vh);
  }
  /* Botões fixos nos cantos + a barra de baixo, respeitando notch/gesture
     bar do aparelho — max() com o valor antigo garante que em aparelhos sem
     notch (env() = 0) nada muda visualmente. */
  #hud {
    top: max(16px, env(safe-area-inset-top));
    /* pulled in from the standard 16px — there's real spare margin next to
       the HUD on a narrow phone once it's this size, no reason to hold it
       to the same edge gap as a desktop window */
    left: max(6px, env(safe-area-inset-left));
  }
  #settings-btn {
    top: max(16px, env(safe-area-inset-top));
    right: max(16px, env(safe-area-inset-right));
  }
  /* "colada" na borda de baixo do aparelho, mesmo sem notch — 10px é o
     valor certo pro desktop, mas fica curto demais como distância de
     respiro/zona de polegar num celular. */
  #toolbar {
    bottom: max(28px, calc(env(safe-area-inset-bottom) + 14px));
    width: min(88vw, 620px);
  }

  /* interface/gui/perfil.png (o card do HUD) mantém a MESMA proporção em
     qualquer tela — o problema não é o card, é que ele encolhia até 46vw,
     e o apelido do nível ("Rei do Galinheiro") e o número de nível são
     fonte fixa em px dentro de caixas em %: numa tela estreita essas caixas
     ficam pequenas demais pro texto e ele vaza pra fora ("Nível" saindo por
     cima, o número saindo por baixo). Alargar o card dá mais pixels reais
     pra essas mesmas caixas percentuais; o teto (--hud-mobile-max-w) deixa
     uma folga fixa pro ícone de engrenagem no canto direito, que não
     encolhe nem se move no mobile. */
  #hud {
    width: min(70vw, 340px);
  }
  /* Base desktop split (margin-top:16% + img height:62% + label height:22%
     = 100% of #hud-avatar's own box) was never rebalanced when the label
     grew to 40% here — 16+62+40 = 118%, which is what was actually pushing
     "Galinheiro" out past the box's own bottom edge no matter how small the
     font got. Shrinking the chicken portrait's own margin/height below
     frees that room back up (16+62 -> 4+50 = 54%, leaving 46% for the
     label, still under 100% with a little to spare) instead of just
     fighting it with font-size. */
  #hud-avatar-img {
    margin-top: 4%;
    height: 50%;
    /* small nudge down, requested directly — transform instead of adding
       to margin-top so the #hud-avatar-label budget below (see comment
       above) isn't eaten into and "Galinheiro" doesn't clip again */
    transform: translateY(3px);
  }
  #hud-avatar-label {
    height: 46%;
    font-size: 7.5px;
    line-height: 1.05;
    /* "Galinheiro" alone is one unbreakable word — the height bump above
       fixed 2 lines not fitting VERTICALLY, but on an even-wider-rendered
       font (see the plant-info-panel comment on the Fredoka/Comic-Sans-MS
       fallback issue) the word itself can be too wide for the box, and with
       nowhere to break it just overflows sideways instead of wrapping.
       Same fix as #plant-info-name. */
    overflow-wrap: break-word;
    word-break: break-word;
  }
  #hud-coins,
  #hud-level {
    font-size: 12px;
  }

  /* Ícones da coluna esquerda (loja de animais/amigos/ranking/eventos) são
     15% da largura do #hud — crescem sozinhos com o #hud acima, mas ainda
     dá uma folga extra aqui pro alvo de toque ficar mais confortável. */
  #hud-money-shop,
  #hud-ranking-btn,
  #hud-friends-btn,
  #hud-events-btn,
  #hud-animals-btn {
    width: 19%;
  }

  /* #farm-stage/#visit-stage: keeps the same LARGER-dimension basis as
     desktop (see that rule's own comment — reverted from a smaller-
     dimension mobile override that made the farm look small and distant
     instead of "close"), but with a bigger multiplier than desktop's 1.12.
     On a tall phone the WIDTH is the constrained axis, and 1.12x only
     overflows it by a little — barely any pixels of pan slack once
     PAN_LEFT_FRACTION/PAN_UP_FRACTION (src/main.js) eat into that, nowhere
     near enough to actually reach the leftmost/bottommost plots. More
     overflow here directly buys back that travel distance; it doesn't
     change the starting zoom level (still framed the same way on load),
     just how far there is left to drag once you start. */
  /* Bumped again from 1.5 — a bigger stage relative to the fixed viewport
     is a closer/more-zoomed-in camera (each screen pixel maps to less of
     base.png), which both brings the map closer as asked AND, since
     #farm-stage/#visit-stage stay centered on the viewport regardless of
     size, shrinks how much shows above the animal pen at rest (panY starts
     at 0, already the top of PAN_UP_FRACTION's allowed range — see
     main.js's own comment on that constant) without touching the pan-up
     clamp itself. Also grows maxX/maxY together, so left/right pan slack
     only gets better, never worse. */
  #farm-stage,
  #visit-stage {
    width: calc(max(100vw, 100vh) * 1.8);
    height: calc(max(100vw, 100vh) * 1.8);
  }

  /* #event-banner: the boxed version (dark background, top-right corner)
     read as sitting "no meio da tela" once it had to move below the now-
     taller mobile #hud (see git history on this rule) — a filled card
     floating alone mid-screen draws the eye a lot more than the same text
     does. Moved again: now a plain unboxed line of text riding just above
     the toolbar, same spot a lot of mobile games put a transient status
     line. Anchored with the exact same bottom expression #toolbar itself
     uses (below), plus the toolbar's own rendered height (aspect-ratio
     1874/426 off its own width) plus a gap — so it always sits right above
     the bar regardless of exactly how tall the bar renders at a given
     width. */
  #event-banner {
    top: auto;
    bottom: calc(max(28px, calc(env(safe-area-inset-bottom) + 14px)) + min(88vw, 620px) * 0.2273 + 10px);
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    max-width: 94vw;
    /* plain floating text, no card — the dark text-shadow (inherited from
       the base rule) is what keeps it legible over the farm art without a
       background plate */
    background: none;
    border-radius: 0;
    padding: 0 4px;
    font-size: 12px;
  }

  /* Same reasoning as the shop/bag mobile redesign above: informacao.png
     is a fixed 300x200 frame with a measured-empty interior, and every
     child here is positioned as a % of that fixed aspect ratio. Bumping
     #plant-info-body's own height (previous attempt) still couldn't win
     against a genuinely tall content stack — the fertilized status row
     ("Fertilizada — crescendo 15% mais rápido" + icon) pushed the total
     past 100% of even the enlarged box, and with overflow:hidden +
     justify-content:center that clipped evenly off both ends instead of
     just growing the panel. Dropping the painted art and laying this out
     as a normal top-to-bottom flex card — same recipe already used for
     #shop-panel/#bag-panel — lets it grow to fit whatever content a given
     plant/status combination needs instead of fighting a fixed frame. */
  #plant-info-panel,
  #animal-info-panel {
    top: 50%;
    bottom: auto;
    transform: translate(-50%, -50%);
    /* was min(90vw,380px) — read as a "giant screen" popping up over an
       accidental tap on a plant. Smaller footprint, still grows via
       overflow-y below if a status row ever needs more than this. */
    width: min(66vw, 280px);
    aspect-ratio: auto;
    max-height: 70vh;
    background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
    border: 4px solid #6b4423;
    border-radius: 18px;
    box-shadow: 0 8px 0 rgba(0, 0, 0, 0.25), 0 16px 32px rgba(0, 0, 0, 0.3);
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 30px 14px 14px;
    overflow-y: auto;
  }
  #plant-info-bg,
  #animal-info-bg {
    display: none;
  }
  #plant-info-close,
  #animal-info-close {
    top: 8px;
    right: 8px;
    width: 30px;
    height: 30px;
  }
  #plant-info-img,
  #animal-info-icon {
    position: static;
    width: 40%;
    height: auto;
    aspect-ratio: 1 / 1;
    margin-bottom: 6px;
    flex-shrink: 0;
  }
  #plant-info-body,
  #animal-info-body {
    position: static;
    width: 100%;
    height: auto;
    overflow: visible;
    align-items: center;
    text-align: center;
    gap: 5px;
  }
  /* Coin/EXP payout as a matched pair of small badges side by side, instead
     of two plain text lines stacked one under the other with no visual
     hierarchy — same "pill with a border" family as .shop-seed-exp/the
     shop's own price rows, so this reads as the same kind of figure
     everywhere in the app instead of a one-off plain-text treatment. */
  #plant-info-reward-row {
    display: flex;
    gap: 6px;
    justify-content: center;
  }
  #plant-info-value,
  #plant-info-exp {
    justify-content: center;
    background: linear-gradient(180deg, #ffe9b8 0%, #ffd98a 100%);
    border: 1px solid #c98a1f;
    border-radius: 999px;
    padding: 3px 9px;
    font-size: 10px;
  }
  #plant-info-exp {
    background: linear-gradient(180deg, #d9f0c8 0%, #b8e097 100%);
    border-color: #6fa040;
  }
  /* Animal card only has a name + this one button (no value/exp/time/status
     rows like the plant card), and "Retirar animal" is the entire point of
     opening it — burying it below the picture meant a full stack read
     (name, then scroll past the image, then the button) before reaching
     the actual action. #animal-info-body and #animal-info-icon are both
     direct children of #animal-info-panel's flex column, so swapping which
     one comes first is just an order flip, no DOM change needed — moves
     name+button as a unit above the picture. Plant card keeps its own
     order (icon first) since it has enough rows that "action right up top"
     isn't the same win there. */
  #animal-info-body {
    order: -1;
  }

  /* #armed-seed-indicator ("Fertilizante selecionado — clique numa
     plantação para fertilizar" + ✕) — on mobile this just floats a card
     over the top of the farm for something the toolbar slot itself already
     communicates on tap (its own short description). No mobile equivalent
     needed here since arming/unarming is already handled by tapping the
     same toolbar slot again — this card was purely informational, nothing
     here relied on its ✕ button being the only way to cancel. */
  #armed-seed-indicator {
    display: none;
  }
  /* ...except while an animal is armed — placing one has no toolbar slot
     to tap again, so this card's ✕ is the only way off that screen. Not
     just informational the way the tool/seed case above is: without this,
     the placement zone overlay just stayed up with no way to back out. */
  #armed-seed-indicator.armed-indicator-animal {
    display: flex;
  }

  /* Loja/Inventário no mobile: abandonam a arte pintada (loja.png/
     inventario.png) inteiramente, não só alargam em cima dela. Essa arte
     grava uma grade fixa de 5 colunas dentro da própria imagem — não tem
     como realinhar isso só com CSS, e mesmo esticando o painel ao máximo
     (tentativa anterior) os 15 cartões continuavam pequenos demais pra
     reconhecer a planta. Aqui os dois painéis passam a ser um cartão CSS
     simples (mesmo visual creme/madeira já usado em outros painéis do
     app, como a loja de animais) com uma grade CSS de verdade — cartões
     desenhados por CSS Grid (auto-fit, cresce/encolhe conforme a largura
     real disponível) em vez de posicionados em % sobre uma arte de
     tamanho fixo.
     grid-template-areas coloca saldo/setas/grade em linhas e colunas
     específicas sem precisar mudar a ordem dos elementos no DOM (o mesmo
     JS em toolbar.js constrói os dois painéis, desktop incluso — ele
     continua usando a arte pintada normalmente lá fora desta media query).
     !important nas posições de cada cartão é necessário porque
     renderShopPage()/fillBagCard() ainda setam left/top/width/height via
     style inline (só assim o desktop continua funcionando) — só uma regra
     CSS com !important ganha de um inline sem !important. */
  #shop-panel,
  #bag-panel {
    /* base rule anchors these to bottom:155px (measured to clear the
       desktop toolbar) — with the frame art gone this reads as a modal, not
       a HUD element riding just above the toolbar, so it should be
       centered on screen like one instead of pinned low. */
    top: 50%;
    bottom: auto;
    transform: translate(-50%, -50%);
    width: min(96vw, 460px);
    aspect-ratio: auto;
    max-height: 78vh;
    background: linear-gradient(180deg, #fff3d9 0%, #ffe4ad 100%);
    border: 4px solid #6b4423;
    border-radius: 20px;
    box-shadow: 0 10px 0 rgba(0, 0, 0, 0.25), 0 20px 40px rgba(0, 0, 0, 0.35);
    display: grid;
    grid-template-columns: 1fr auto auto;
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
      "balance prev next"
      "empty empty empty"
      "grid grid grid";
  }
  /* Grade sem paginação real no mobile — dá pra rolar #shop-slots/#bag-slots
     (overflow-y: auto abaixo) pra ver o resto, então as setas de página só
     ocupavam espaço e ainda tampavam conteúdo em telas estreitas. */
  #shop-prev,
  #bag-prev,
  #shop-next,
  #bag-next {
    display: none;
  }
  /* Belt-and-suspenders on top of toolbar.js now simply never giving these
     <img>s a src on mobile (the real fix — see that file's own comment):
     if display:none here somehow didn't win the cascade on some browser,
     an src-less <img> renders nothing regardless of its own CSS anyway. */
  #shop-bg,
  #bag-bg {
    display: none !important;
  }
  #shop-close,
  #bag-close {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 34px;
    height: 34px;
  }
  #shop-balance-row,
  #bag-balance-row {
    grid-area: balance;
    position: static;
    transform: none;
    justify-content: flex-start;
    padding: 14px 8px 10px 16px;
  }
  /* #shop-balance-row's base color (#fff8e8, near-white) was tuned for
     loja.png's own dark painted background — on the cream/tan mobile panel
     background above, that's almost illegible. #bag-balance-row already
     used a dark brown (inventario.png's own board was always light), so
     matching that color here instead of inventing a new one. */
  #shop-balance-row {
    color: #5b3d20;
    text-shadow: none;
  }
  #bag-empty-msg {
    grid-area: empty;
    position: static;
    transform: none;
    width: auto;
    padding: 0 16px 10px;
  }
  #shop-prev,
  #bag-prev,
  #shop-next,
  #bag-next {
    position: static;
    transform: none;
    width: 32px;
    height: 32px;
    align-self: center;
    margin: 0 4px;
  }
  #shop-next,
  #bag-next {
    margin-right: 48px; /* clears #shop-close/#bag-close in the corner */
  }
  #shop-slots,
  #bag-slots {
    grid-area: grid;
    position: static !important;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    gap: 10px;
    padding: 6px 14px 14px;
    overflow-y: auto;
    align-content: start;
  }
  /* .bag-item-card already draws its own card look on desktop too
     (inventario.png never had painted slots — see that rule's own
     comment), but .shop-seed-card never did: loja.png DID have painted
     squares, so the desktop card is just bare icon+text content sitting on
     top of that art. Hiding the art above without giving .shop-seed-card
     the same drawn-card treatment left every item floating directly on the
     shared panel background with no border between them — one big blob
     instead of 9 cards, exactly what showed up. Same visual recipe as
     .bag-item-card/.animal-shop-card so all three read as the same family
     of card. */
  .shop-seed-card {
    background: linear-gradient(180deg, #fff6e0 0%, #ffe6ad 100%);
    border: 3px solid #d9a760;
    border-radius: 10px;
    box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.6), 0 2px 3px rgba(0, 0, 0, 0.15);
  }
  .shop-seed-card,
  .bag-item-card {
    /* relative, not static — still pulled out of the old absolute-position-
       over-painted-art scheme (left/top/width/height below), but a card
       still needs to itself be a positioned element for its own children
       that use position:absolute (.shop-seed-exp/.bag-item-value's corner
       badge — those anchor to top/right:Npx expecting THIS card as their
       containing block, not the whole grid) */
    position: relative !important;
    left: auto !important;
    top: auto !important;
    width: auto !important;
    height: auto !important;
    transform: none !important;
    aspect-ratio: 1 / 1.2;
    padding: 6px 4px;
  }
  .shop-seed-card:hover,
  .bag-item-card:hover {
    transform: none !important;
  }
  .shop-seed-card img,
  .bag-item-card img {
    width: 62%;
    height: 62%;
  }
  /* .shop-seed-card.unaffordable/.locked's ::before tint (base rule above,
     width: var(--tint-w, 100%)) reads a per-column % that renderShopPage()
     computed for the OLD desktop layout, sized to overflow the card's own
     box on purpose there (painted squares were wider than the card content
     box on the outer columns). That inline custom property is still set on
     mobile, where the card box IS the real visual boundary — so the tint
     overflowed up to ~20% past each card's edges, wide enough for two
     touching locked/unaffordable cards' tints to bleed into the 10px gap
     between them and visually fuse into one big rounded blob spanning the
     row. Forcing it back to 100% here makes the tint match the card again. */
  .shop-seed-card.unaffordable::before,
  .shop-seed-card.locked::before {
    width: 100%;
  }
  /* #shop-buy-modal is itself a % of #shop-panel (min(60%,300px)) — grows
     some from the panel widening above already, but bumped further here
     too since 60% of even the wider mobile panel still left the qty
     picker/total/confirm button cramped enough to overlap (the "Deseja
     realmente comprar" / fruit name collision, "Comprar" button clipped). */
  #shop-buy-modal {
    width: min(88%, 340px);
  }
  /* Fixed 4 columns (see .animal-shop-grid's base rule) cut the 4th card
     off the edge of #animal-shop-panel's narrower mobile width — 3 fits
     cleanly instead. */
  .animal-shop-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
