* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; overflow: hidden; height: 100%;
  background: #0b2540;
  /* A rounded, playful display font throughout — per direct request for
     something more colorful/cute/gamey than a plain system font. Falls back
     cleanly to the old system stack if the Google Fonts request is ever
     blocked/offline. */
  font-family: 'Baloo 2', 'Segoe UI', system-ui, sans-serif;
  font-size: 15px; /* was the browser default ~13-14px effective size across this UI's px-sized rules — bumped slightly per direct request for "slightly bigger" text throughout */
}

#game-canvas {
  display: block;
  position: fixed;
  inset: 0;
  cursor: crosshair;
}

/* One-shot title splash — used to play automatically on load; now gated
   behind the player clicking "Start" on the new start screen instead (per
   direct request). #splash-screen starts fully invisible (opacity:0, not
   just "not yet animating") so the word can never bleed through the start
   screen's blur — main.js's triggerSplash() adds a single .play class
   directly to #splash-screen once Start is actually clicked, which both
   reveals it AND (via the descendant selectors below) kicks off both layers
   of animation together: #splash-title itself fades/grows in, holds, then
   fades back out (splash-grow-fade, on the whole word so the letters' own
   bounce doesn't fight a separate opacity change); each .splash-letter span
   (built by main.js from the title's plain text) plays its own independent
   bounce (splash-letter-bounce), staggered by animation-delay so they hop in
   sequence rather than all at once — timed to start only once the grow-in
   has finished (see main.js's growInDuration). Pure decoration throughout:
   pointer-events none so it never blocks a click, and main.js removes the
   element entirely once #splash-title's own animation finishes (its
   'animationend' listener filters to e.target === title, since child letter
   animations also bubble the same event) rather than leaving a hidden node
   sitting on top forever. */
#splash-screen {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  z-index: 500;
  pointer-events: none;
  opacity: 0;
}
#splash-screen.play { opacity: 1; }
#splash-title {
  font-size: 64px; font-weight: 900;
  color: #fff6ea;
  text-shadow: 0 0 24px rgba(255, 111, 174, 0.8), 0 6px 0 #c25f8f, 0 10px 18px rgba(0, 0, 0, 0.35);
}
#splash-screen.play #splash-title {
  animation: splash-grow-fade 4.5s ease forwards;
}
@keyframes splash-grow-fade {
  0% { transform: scale(0.3); opacity: 0; }
  25% { transform: scale(1); opacity: 1; }
  75% { transform: scale(1); opacity: 1; }
  100% { transform: scale(1); opacity: 0; }
}
/* display:inline-block is required here unconditionally (not just once
   .play is active) — a plain inline <span> ignores `transform` entirely per
   the CSS spec, so without this the translateY bounce below would silently
   do nothing even while its animation was correctly running. (This exact
   line was lost in an earlier pass when the animation itself was split out
   into the gated rule below it — restored here as its own always-on base
   rule.) */
.splash-letter {
  display: inline-block;
}
#splash-screen.play .splash-letter {
  animation: splash-letter-bounce 0.6s ease both;
}
@keyframes splash-letter-bounce {
  0% { transform: translateY(0); }
  30% { transform: translateY(-22px); }
  50% { transform: translateY(4px); }
  70% { transform: translateY(-8px); }
  100% { transform: translateY(0); }
}

#hud {
  /* Always visible now, top-right — per direct request, the Shop/Tank
     Upgrades panels no longer carry their own duplicate HUD copy (see the
     removed #shop-hud/#tank-hud rule further down), so this is the one
     place money/caps/cleanliness/power live, and it never hides itself
     while either panel is open any more. Both toggle buttons moved down to
     #bottom-bar-row, so the old "clear the collapsed shop panel" right
     offset isn't needed either — plain 14px like every other corner pill. */
  /* right offset clears the right glass tank wall's own live width (main.js's
     renderTankWalls sets --glass-wall-right-width every frame) plus the usual
     14px corner margin, so the pill never visually overlaps the glass panel
     — per direct request. --glass-wall-right-width defaults to 22px (the
     glass's own guaranteed-minimum width, TANK_WALL_MIN_WIDTH) for the one
     frame before the canvas has rendered and actually set it. */
  position: fixed; top: 14px; right: calc(14px + var(--glass-wall-right-width, 22px));
  display: flex; gap: 14px; align-items: center;
  padding: 9px 18px;
  /* Poppy, non-flat pass, per direct request — a soft diagonal gradient plus
     a bright inner highlight along the top edge and a faint tinted inset
     shadow along the bottom reads as a raised, glossy pill instead of a flat
     tinted rectangle. Same recipe reused across #notification-latest/-log,
     #shop-panel/#tank-panel, and every button below. */
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  color: #6b4c6b;
  font-size: 15px;
  font-weight: 700;
  border-radius: 999px;
  border: 2px solid #ffffff;
  box-shadow: 0 6px 16px rgba(107, 76, 107, 0.25), inset 0 2px 0 rgba(255, 255, 255, 0.9), inset 0 -3px 6px rgba(255, 173, 209, 0.3);
  pointer-events: auto; /* needs to receive hover for its title tooltips */
  z-index: 10;
}

/* Electricity readout — only shown once Electric Eel is unlocked (see
   UI.js's updateHUD). #hud-power-wrap needs position:relative so the graph
   popup can anchor directly beneath the pill, same dropdown-under-pill
   pattern #notification-log already uses under #notification-latest below. */
/* #hud's other children are plain text siblings sharing ONE outer pill (see
   #hud's own background/padding) — this wrapper has no visual styling of its
   own (no background/border), just position:relative so #hud-power-graph
   can anchor beneath the button without looking like a second nested pill. */
#hud-power-wrap { position: relative; display: inline-flex; align-items: center; }
#hud-power {
  display: block;
  background: none;
  color: inherit;
  font: inherit;
  border: none;
  padding: 0;
  cursor: pointer;
  border-radius: 8px;
  transition: opacity 0.12s ease;
}
#hud-power:hover { opacity: 0.7; }
#hud-power.hidden { display: none; }
/* A small dropdown chevron, per direct request — flips to point up (a plain
   180deg rotate of the same down-arrow glyph, so there's only ever one
   character to keep in sync) while the graph popup is open. */
#hud-power-arrow {
  display: inline-block;
  margin-left: 3px;
  transition: transform 0.15s ease;
}
#hud-power-arrow.open { transform: rotate(180deg); }
/* Hidden until the Electricity Graph Tank Upgrade is bought — per direct
   request, the arrow shouldn't hint at a dropdown that doesn't work yet. */
#hud-power-arrow.hidden { display: none; }
#hud-power-graph {
  /* Shifted down and left from directly-below-the-pill, per direct report
     that it overlapped #hud itself. */
  position: absolute; top: calc(100% + 24px); right: 70px;
  background: linear-gradient(165deg, #fffaf3 0%, #fff3d9 100%);
  border: 2px solid #ffffff;
  border-radius: 16px;
  padding: 10px;
  box-shadow: 0 6px 16px rgba(107, 76, 107, 0.25), inset 0 2px 0 rgba(255, 255, 255, 0.85);
  z-index: 12;
}
#hud-power-graph.hidden { display: none; }
#hud-power-graph-canvas { display: block; }

/* The dedicated circular pause-menu toggle button (top-right, below #hud)
   is gone per direct request — Escape is the only way to open/close the
   pause menu now (see main.js's keydown handler and #hotkey-legend's
   dynamic Esc line below). */

/* Build/fish legend — now just "Click to purchase" (see index.html's own
   comment for why the old "(Esc) to cancel" half moved into #hotkey-legend
   instead) — and the tutorial-skip legend — "(Esc) to skip tutorial" — per
   direct request/report. Went through three placements: first just above the
   fixed bottom tool-bar (blocked the view of the tank), then top-middle
   (still in the way, per a direct follow-up), then tucked into the
   bottom-left corner right next to the Shop toggle button, as a child of
   #shop-anchor with `position: absolute` — which LOOKED right but had a
   real bug baked in: #shop-anchor lives inside #bottom-bar-row, and
   #bottom-bar-row (`position: fixed` + its own z-index + a `transform`) is
   a genuine CSS stacking context of its own, so anything painted inside it
   is trapped stacking at #bottom-bar-row's OWN z-index (15) relative to
   the rest of the page no matter what z-index a descendant sets — raising
   these two to 702 (see git history) visually did nothing, since they
   could never escape that cap in the first place. Per direct report ("I've
   already asked for this and it got skipped"), moved to plain top-level
   siblings of #tutorial-overlay in the DOM (index.html) instead, `position:
   fixed` with no static right/bottom — UI.js's positionBottomLeftLegends
   sets those live off the Shop button's own on-screen rect every frame
   they're shown (its position isn't static CSS, since #bottom-bar-row
   centers itself and its width depends on live content), replicating the
   exact same "left of, bottom-aligned with, the Shop button" visual spot
   this design already settled on. The two are mutually exclusive (UI.js's
   updateHUD never shows both at once — the purchase legend is suppressed
   entirely while a tutorial's active), so they can safely share the exact
   same sizing/position logic. */
#build-legend, #tutorial-skip-legend {
  position: fixed;
  padding: 9px 20px;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  color: #6b4c6b;
  font-size: 14px;
  font-weight: 700;
  border-radius: 999px;
  border: 2px solid #ffffff;
  box-shadow: 0 6px 16px rgba(107, 76, 107, 0.25), inset 0 2px 0 rgba(255, 255, 255, 0.9);
  z-index: 702; /* now actually applies — no longer trapped inside #bottom-bar-row's own stacking context, see the comment above */
  white-space: nowrap;
}
#build-legend { display: flex; gap: 14px; }
#build-legend.hidden, #tutorial-skip-legend.hidden { display: none; }

/* Persistent, always-on hotkey reminder — per direct request, small white
   text in the literal bottom-left corner of the viewport (distinct from
   #build-legend/#tutorial-skip-legend above, which sit further right,
   tracking the Shop button's own position, and only ever show
   conditionally). Text content itself is rewritten live by UI.js's
   updateHUD every frame — see main.js's KeyE/KeyQ handlers for what each
   line's hotkey actually does right now. */
#hotkey-legend {
  position: fixed;
  /* Real bug fix, per direct report ("the glass splits the legend and it
     looks unpolished") — this was left at a flat 14px while every other
     screen-edge element (#hud, #notification-ticker, #pause-toggle-btn) had
     already been updated to clear the glass tank walls' own live-measured
     width (--glass-wall-left/right-width, set every frame by main.js's
     renderTankWalls) when those were added. This one was simply missed. */
  left: calc(14px + var(--glass-wall-left-width, 22px));
  bottom: 14px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: rgba(255, 255, 255, 0.85);
  font-size: 12px;
  font-weight: 700;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.65);
  pointer-events: none;
}

/* Guided tutorial spotlight — see UI.js's TUTORIAL_FLOWS/
   updateTutorialOverlay. #tutorial-overlay covers the whole viewport and
   darkens everything except the current step's target circle, punched out
   with a REAL clip-path hole (not just a visual one) — clip-path restricts
   both rendering AND pointer hit-testing in modern browsers, so a click
   inside the circle passes straight through to whatever real element sits
   underneath (the Shop button, a species icon, the canvas...) while
   anywhere else in the darkened area swallows the click entirely. The exact
   clip-path polygon/path is computed and set every frame by JS (the circle
   moves — a DOM target's rect, or a world point via the live camera).
   Higher z-index than everything else in the game, #pause-overlay (650)
   included — a guided tutorial step takes priority over any other UI. */
#tutorial-overlay {
  position: fixed; inset: 0; z-index: 700;
  background: rgba(20, 12, 20, 0.72);
  cursor: pointer;
}
#tutorial-overlay.hidden { display: none; }

/* Instruction text for whichever tutorial step is active — a separate fixed
   pill (not inside #tutorial-overlay) so it stays fully opaque/legible
   regardless of the overlay's own clip-path hole, and so it can still show
   during a noSpotlight step (the "scroll" step) where #tutorial-overlay
   itself is hidden entirely. */
#tutorial-text {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 701;
  padding: 10px 22px;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  color: #6b4c6b;
  font-size: 16px;
  font-weight: 800;
  border-radius: 999px;
  border: 2px solid #ffffff;
  box-shadow: 0 6px 16px rgba(107, 76, 107, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.9);
  white-space: nowrap;
  pointer-events: none; /* purely informational — never blocks the click that's supposed to land on the spotlighted target */
}
#tutorial-text.hidden { display: none; }

/* Alien Invasion final-countdown banner — shown only during the last
   ALIEN_COUNTDOWN_START_MS (10s) before a wave, top-center per direct
   request ("a countdown timer... that shows up at the top of the screen").
   UI.js's updateAlienCountdown toggles .hidden and updates the seconds
   text live every frame. */
#alien-countdown {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  padding: 9px 22px;
  background: linear-gradient(160deg, #ffdede 0%, #ff8f8f 100%);
  color: #5a1414;
  font-size: 16px;
  font-weight: 800;
  border-radius: 999px;
  border: 2px solid #ffffff;
  box-shadow: 0 6px 16px rgba(90, 20, 20, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.85);
  z-index: 20;
  animation: alien-countdown-pulse 0.7s ease-in-out infinite;
}
#alien-countdown.hidden { display: none; }
@keyframes alien-countdown-pulse {
  0%, 100% { transform: translateX(-50%) scale(1); }
  50% { transform: translateX(-50%) scale(1.05); }
}

/* Mother Alien Fish's own top-middle health bar, per direct spec ("a
   universal boss health bar at the top middle of the screen instead of over
   the boss's head") — UI.js's updateBossHealthBar toggles .hidden and sets
   #boss-health-bar-fill's width every frame the boss is alive. */
#boss-health-bar-wrap {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 30;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  pointer-events: none;
}
#boss-health-bar-wrap.hidden { display: none; }
#boss-health-bar-name {
  font-weight: 800; font-size: 15px; color: #ffd6f5;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.7), 0 0 10px rgba(217, 78, 212, 0.6);
}
#boss-health-bar-track {
  width: 420px; height: 26px;
  background: rgba(20, 8, 8, 0.75);
  border: 2px solid #ffffff;
  border-radius: 13px;
  overflow: hidden;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
}
#boss-health-bar-fill {
  height: 100%;
  background: linear-gradient(180deg, #ff9a8a 0%, #e0392b 100%);
  transition: width 0.2s linear;
}

/* The end-game stats modal, per direct spec ("slowly fade in a game over
   modal, with stats about the game"). Opacity-only transition (no
   display:none toggle mid-fade — that has no transition at all) driven by
   UI.js's showGameOverModal: 'hidden' is removed immediately, then
   '.visible' is added a frame later via a forced-reflow retrigger so the
   browser genuinely animates from 0. */
#boss-victory-overlay {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  background: rgba(10, 4, 16, 0.78);
  opacity: 0;
  transition: opacity 2.5s ease;
  pointer-events: none;
}
#boss-victory-overlay.hidden { display: none; }
#boss-victory-overlay.visible { opacity: 1; pointer-events: auto; }
#boss-victory-modal {
  width: 380px; max-width: 90vw;
  padding: 28px;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  border: 2px solid #ffffff;
  border-radius: 26px;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), inset 0 2px 0 rgba(255, 255, 255, 0.9);
  text-align: center;
}
#boss-victory-modal h2 {
  margin: 0 0 18px; font-size: 20px; color: #6b4c6b;
}
#boss-victory-stats {
  display: flex; flex-direction: column; gap: 8px;
  margin-bottom: 20px; text-align: left;
}
.boss-victory-stat-row {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 14px; color: #6b4c6b;
}
.boss-victory-stat-row b { color: #4a2f4a; }

/* Scroll-down hint — per direct request, shown from 10s after game start
   until the player has panned down at least once (UI.js's updateScrollHint,
   checking state.camera.y > 0 as the "has scrolled" signal and toggling
   .hidden every frame). Sits just above #bottom-bar-row, spanning the width
   of the screen, pointer-events:none so it can never intercept a click. */
#scroll-hint {
  position: fixed;
  bottom: 100px; left: 0; right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  z-index: 14; /* just under #bottom-bar-row's 15, so the toolbar always stays the topmost thing down there */
}
#scroll-hint.hidden { display: none; }
/* "Scroll down" — per direct request, the arrows alone weren't explicit
   enough. Same pill styling every other short instructional readout in this
   game already uses (#tutorial-text, #alien-countdown). */
#scroll-hint-text {
  padding: 6px 18px;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  color: #6b4c6b;
  font-size: 15px;
  font-weight: 800;
  border-radius: 999px;
  border: 2px solid #ffffff;
  box-shadow: 0 4px 10px rgba(107, 76, 107, 0.3), inset 0 2px 0 rgba(255, 255, 255, 0.9);
}
#scroll-hint-arrows {
  display: flex;
  justify-content: center;
  gap: 44px;
}
.scroll-hint-arrow {
  font-size: 28px;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
  animation: scroll-hint-bounce 1s ease-in-out infinite;
}
/* A slight per-arrow stagger reads as a little wave rolling toward the
   toolbar rather than every arrow bouncing in flat unison. */
.scroll-hint-arrow:nth-child(1) { animation-delay: 0.2s; }
.scroll-hint-arrow:nth-child(2) { animation-delay: 0.1s; }
.scroll-hint-arrow:nth-child(3) { animation-delay: 0s; }
.scroll-hint-arrow:nth-child(4) { animation-delay: 0.1s; }
.scroll-hint-arrow:nth-child(5) { animation-delay: 0.2s; }
@keyframes scroll-hint-bounce {
  0%, 100% { transform: translateY(0); opacity: 0.6; }
  50% { transform: translateY(10px); opacity: 1; }
}

/* Rolling notification log — collapsed by default to just its latest line
   (Mound crack narration, tips, alerts once later phases write to it).
   Click the pill to expand a scrollback of recent messages. */
#notification-ticker {
  /* Same live glass-wall-clearing offset as #hud (see that rule's own
     comment) — mirrored to the left edge, per direct request that the chat
     shouldn't visually overlap the left glass panel either. */
  position: fixed; top: 14px; left: calc(14px + var(--glass-wall-left-width, 22px));
  max-width: 360px;
  z-index: 10;
}
#notification-latest {
  display: block;
  max-width: 100%;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  color: #6b4c6b;
  /* A distinct, more "chat-like" font from the rest of the game's Baloo 2
     UI chrome — per direct request ("more interesting font"), a casual
     handwritten-comic face reads well for this game's sarcastic notification
     voice. Bumped a size too ("slightly bigger"), with 700 weight for
     legibility at that style (Comic Neue's regular weight reads thin). */
  font-family: 'Comic Neue', 'Baloo 2', sans-serif;
  font-size: 15px;
  font-weight: 700;
  text-align: left;
  padding: 9px 16px;
  border: 2px solid #ffffff;
  border-radius: 999px;
  box-shadow: 0 6px 16px rgba(107, 76, 107, 0.25), inset 0 2px 0 rgba(255, 255, 255, 0.9), inset 0 -3px 6px rgba(255, 173, 209, 0.3);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: transform 0.12s ease;
}
#notification-latest:hover { background: linear-gradient(160deg, #fffdf8 0%, #ffdcee 100%); transform: scale(1.02); }
#notification-log {
  margin-top: 8px;
  max-height: 220px;
  overflow-y: auto;
  background: linear-gradient(165deg, #fffaf3 0%, #fff0f6 100%);
  border: 2px solid #ffffff;
  border-radius: 16px;
  padding: 10px 14px;
  box-shadow: 0 6px 16px rgba(107, 76, 107, 0.25), inset 0 2px 0 rgba(255, 255, 255, 0.85);
  /* Firefox's scrollbar styling — Chromium ignores these two and uses the
     ::-webkit-scrollbar rules below instead. */
  scrollbar-width: thin;
  scrollbar-color: #ffb6d5 #fff0f6;
}
#notification-log.hidden { display: none; }
#notification-log::-webkit-scrollbar { width: 8px; }
#notification-log::-webkit-scrollbar-track { background: #fff0f6; border-radius: 999px; }
#notification-log::-webkit-scrollbar-thumb { background: #ffb6d5; border-radius: 999px; }
#notification-log::-webkit-scrollbar-thumb:hover { background: #ff9ac4; }
.notification-line {
  font-family: 'Comic Neue', 'Baloo 2', sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: #6b4c6b;
  padding: 6px 0;
  border-bottom: 1px solid rgba(107, 76, 107, 0.1);
}
.notification-line:last-child { border-bottom: none; }

/* 50% bigger than the original 36px/18px (a later "50% bigger" pass from an
   earlier request), per direct request ("make the shop and tank upgrade
   icons 50% bigger" — these two toggle buttons). Now flex children of
   #bottom-bar-row (see the bottom-tool-bar section below) instead of a
   standalone top-right cluster — per direct request, Shop moved to the left
   of the bottom toolbar, Tank Upgrades to the right of it. */
#shop-collapse-btn, #tank-collapse-btn {
  flex: none;
  width: 54px; height: 54px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, #ffe4f2 0%, #ffc2e0 100%);
  border: 2px solid #ffffff; border-radius: 50%;
  cursor: pointer;
  font-size: 27px;
  line-height: 1;
  box-shadow: 0 3px 8px rgba(255, 150, 200, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  transition: transform 0.12s ease, background 0.12s ease;
}
#shop-collapse-btn:hover, #tank-collapse-btn:hover { background: linear-gradient(160deg, #ffdcee 0%, #ffb0d8 100%); transform: scale(1.08); }
/* Whichever panel is currently open needs to be obvious at a glance — both
   buttons stay visible and identical-looking regardless of panel state
   otherwise, since only one panel is ever expanded but both toggles remain
   on screen. A filled ring + a solid color swap (not just a subtle shadow)
   reads clearly even at this small a size. */
#shop-collapse-btn.panel-toggle-active, #tank-collapse-btn.panel-toggle-active {
  background: #ff6fae;
  box-shadow: 0 0 0 3px #ffffff, 0 0 0 6px #ff6fae, 0 2px 8px rgba(255, 111, 174, 0.55);
  transform: scale(1.1);
}
#shop-collapse-btn.panel-toggle-active:hover, #tank-collapse-btn.panel-toggle-active:hover { background: #ff5aa0; }

/* Shop and Tank Upgrades are fly-out modals anchored to their own toggle
   button, per direct request ("should look the same as they do now, but
   only half as tall" — later "show up right above their respective icons
   instead of on the far left and right of the screen"). #shop-anchor/
   #tank-anchor (position:relative wrappers around each toggle button, see
   #bottom-bar-row below) are what make that second request work: the panel
   is now a true positioned CHILD of the same wrapper the button lives in
   (position:absolute; bottom:100%+gap), so it tracks the button's REAL
   on-screen position automatically — including the button not actually
   sitting at the screen edge, since #bottom-bar-row itself is centered —
   rather than the old approach (position:fixed; left/right:14px) which
   just assumed the button was pinned to the screen edge, true before the
   toggle buttons moved into this centered row but wrong afterward. No JS
   needed for the positioning itself, same as before. */
#shop-anchor, #tank-anchor { position: relative; }

#shop-panel, #tank-panel {
  position: absolute;
  bottom: calc(100% + 25px); /* just above the button, with a visual buffer gap — widened from +10px per direct request ("have a visual buffer between the icons and their menus") */
  width: 286px;
  height: calc((100vh - 28px) / 2); /* half of the old full-height slot (100vh - 14px top - 14px bottom) */
  background: linear-gradient(165deg, #fff8f0 0%, #ffe4f2 100%);
  color: #6b4c6b;
  z-index: 10;
  border-radius: 26px;
  border: 2px solid #ffffff;
  box-shadow: 0 8px 26px rgba(107, 76, 107, 0.25), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  /* Centered on its own anchor button (left:50% + translateX(-50%), same
     centering pattern #bottom-bar-row itself already uses) — per direct
     request ("have the center of the shop line up with the center of the
     shop button"), replacing the old left:0/right:0 edge-alignment, which
     put the panel's center off to one side of the button instead since the
     panel (286px) is much wider than the button (54px). The translateX has
     to stay part of the SAME transform property the open/close scale
     animation below also drives (a single `transform` can't be split across
     two rules without one clobbering the other), so it's baked into both
     this base rule and the .collapsed rule beneath it. */
  left: 50%;
  transform: translateX(-50%);
  transform-origin: 50% 100%; /* grows from/shrinks into its icon, now centered under it */
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.18s ease;
}
/* Shop got an extra 10% on top of that half-height (then another +10% on
   both axes per a later direct request — combined into one *1.21 height
   multiplier and a 315px width, up from the shared 286px), #tank-panel stays
   at the plain half-height/width above. */
#shop-panel { height: calc((100vh - 28px) / 2 * 1.21); width: 315px; }
#shop-panel.collapsed, #tank-panel.collapsed {
  transform: translateX(-50%) scale(0.05);
  opacity: 0;
  pointer-events: none;
}

/* The actual scrolling now happens on an INNER wrapper, inset from the
   card's own edges, rather than on #shop-panel/#tank-panel directly — per
   direct report, a scrollbar that spans the full height of an element that
   ALSO has border-radius gets its top/bottom visually clipped by the
   curve (the browser clips overflow content, scrollbar included, to the
   rounded shape, and the corner radius eats into the scrollbar's own
   corners right where it's flush with the box edge). Insetting this inner
   box by the same 14px the old padding used keeps the exact same content
   layout as before, but now the scrollbar belongs to a plain, un-rounded
   rectangle safely inside the curve — both shorter (it stops 14px short of
   the card's real top/bottom) and never clipped. */
#shop-panel-scroll, #tank-panel-scroll {
  position: absolute; inset: 14px;
  overflow-y: auto;
  overflow-x: hidden;
  /* Custom scrollbar — matches #notification-log's pink/rounded style
     instead of the browser's plain default, per direct request that no
     scrollbar in the game should look ugly. */
  scrollbar-width: thin;
  scrollbar-color: #ffb6d5 #fff0f6;
}
#shop-panel-scroll::-webkit-scrollbar, #tank-panel-scroll::-webkit-scrollbar { width: 8px; }
#shop-panel-scroll::-webkit-scrollbar-track, #tank-panel-scroll::-webkit-scrollbar-track { background: #fff0f6; border-radius: 999px; }
#shop-panel-scroll::-webkit-scrollbar-thumb, #tank-panel-scroll::-webkit-scrollbar-thumb { background: #ffb6d5; border-radius: 999px; }
#shop-panel-scroll::-webkit-scrollbar-thumb:hover, #tank-panel-scroll::-webkit-scrollbar-thumb:hover { background: #ff9ac4; }

/* Per direct request, #shop-money/#tank-points-display sit inline with
   their panel's own title now — the only HUD-style readout either panel
   carries (see UI.js's updateHUD's own comment on #hud being the one
   always-visible copy of everything else) — pushed to the row's far end via
   space-between while the title itself stays left. */
/* The header row + preview card stay pinned to the top of the scrollable
   shop, per direct request ("make the shop window at the top sticky, so
   it's always showing and can't be scrolled off") — wrapping both in one
   sticky container (rather than making each sticky individually) avoids
   having to hand-compute a second `top` offset for the preview to stack
   correctly under the header row. Needs its own opaque background (matching
   the panel's own gradient) since the icon grid scrolls up underneath it,
   plus a bottom border as the visual break the same request asked for
   between the sticky window and the scrolling shop items below. Only
   #shop-panel has this wrapper — #tank-panel's simpler card list was never
   part of this request. */
#shop-sticky-header {
  position: sticky;
  top: 0;
  padding-bottom: 10px;
  background: linear-gradient(165deg, #fff8f0 0%, #ffe4f2 100%);
  border-bottom: 2px solid rgba(255, 182, 213, 0.6);
  z-index: 2;
}
#shop-header-row, #tank-header-row {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
#shop-header, #tank-header { font-size: 17px; font-weight: 800; color: #c25f8f; }

#shop-money, #tank-points-display {
  display: inline-block;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  border: 2px solid #ffffff;
  font-size: 13px; font-weight: 800;
  padding: 7px 12px;
  border-radius: 999px;
  box-shadow: 0 3px 9px rgba(107, 76, 107, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.85);
}
#hud-science-cap.hidden { display: none; } /* only shown once the Science Octopus is unlocked, same "hidden until relevant" precedent as #hud-power/electric Eel */
/* Real bug fix: these two toggled a .hidden class that had no matching CSS
   rule anywhere, so #hud-waves/#hud-gold-per-min stayed visibly on screen
   (showing a stale "Wave 0"/"$0/min") even while "hidden" — per direct
   report of the gold/min one specifically. #hud-wave-countdown is new and
   needs the same rule from the start. */
#hud-waves.hidden { display: none; }
#hud-gold-per-min.hidden { display: none; }
#hud-wave-countdown.hidden { display: none; }

/* A soft diagonal light sweep that plays across a UI element every so often
   — per direct request, applied broadly across the HUD/shop/panels (see
   UI.js's scheduleSheen/scheduleSheenAll, which pick a random 5-30s delay
   per element independently and re-trigger this on a loop). The sweep lives
   entirely in ::after's own background-position, not the element's actual
   position — border-radius: inherit means it automatically matches whatever
   shape the host element has (circle, pill, rounded-rect), so it never
   spills a rectangular streak outside a round button, and it needs no
   overflow:hidden on the host at all, which matters for elements like the
   species/building icon buttons whose price-tag badge intentionally sits
   partly outside their own box (overflow:hidden there would clip it). */
.sheen-target { position: relative; }
.sheen-target::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(115deg, transparent 35%, rgba(255, 255, 255, 0.7) 50%, transparent 65%);
  background-size: 250% 250%;
  background-position: 200% 0%;
  pointer-events: none;
  opacity: 0;
}
.sheen-target.sheen-play::after {
  animation: sheen-sweep 1.1s ease;
}
@keyframes sheen-sweep {
  0% { background-position: 200% 0%; opacity: 0; }
  15% { opacity: 1; }
  55% { opacity: 1; }
  100% { background-position: -100% 0%; opacity: 0; }
}

/* A small springy pop — per direct request that things "bounce a little bit
   when appropriate" throughout the menus/shop/chat/HUD. Reuses the same
   forced-reflow remove-then-re-add retrigger trick every other one-shot
   animation class in this file already uses (see playFlash/scheduleSheen in
   UI.js). Kept as one shared class rather than a bespoke animation per
   element, applied at the specific moments called out directly: the pause
   menu popping open, and the notification pill on a new message (paired
   there with .sheen-play for "bounce AND shimmer"). */
@keyframes bounce-in {
  0% { transform: scale(0.85); }
  50% { transform: scale(1.07); }
  75% { transform: scale(0.97); }
  100% { transform: scale(1); }
}
.bounce-play { animation: bounce-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); }

/* Generic "good change: green flash + bouncy pop" / "bad change: red flash +
   multi-axis shake" pair (translate AND rotate, not just side to side) —
   originally money-only, now shared by any HUD readout that needs the same
   feedback: #hud-money on every money change, #hud-coin-cap/#hud-science-cap
   when a production cycle is blocked by their cap or the count rises,
   #hud-cleanliness whenever cleanliness moves up or down. UI.js re-triggers
   these by removing then re-adding the class so rapid, repeated changes each
   restart the animation. */
@keyframes flash-pickup {
  0% { transform: scale(1) translateY(0); color: #6b4c6b; }
  25% { transform: scale(1.24) translateY(-6.4px); color: #2ecc71; }
  50% { transform: scale(0.94) translateY(1.6px); color: #2ecc71; }
  75% { transform: scale(1.06) translateY(-1.6px); color: #2ecc71; }
  100% { transform: scale(1) translateY(0); color: #6b4c6b; }
}
@keyframes flash-spend {
  0% { transform: translate(0, 0) rotate(0deg); color: #6b4c6b; }
  15% { transform: translate(-3.2px, 2.4px) rotate(-3.2deg); color: #ff4757; }
  30% { transform: translate(3.2px, -2.4px) rotate(3.2deg); color: #ff4757; }
  45% { transform: translate(-2.4px, -2.4px) rotate(-2.4deg); color: #ff4757; }
  60% { transform: translate(2.4px, 2.4px) rotate(2.4deg); color: #ff4757; }
  75% { transform: translate(-1.6px, 1.6px) rotate(-0.8deg); color: #ff4757; }
  90% { transform: translate(0.8px, -0.8px) rotate(0.8deg); }
  100% { transform: translate(0, 0) rotate(0deg); color: #6b4c6b; }
}
.flash-pickup { animation: flash-pickup 0.33s ease; }
.flash-spend { animation: flash-spend 0.33s ease; }

/* Coin/Science Cap warning — toggled by UI.js's updateHUD once the live
   count reaches CAP_WARNING_THRESHOLD_FRACTION (80%) of the active cap, on
   whichever of the HUD/Shop/Tank copies is currently visible. A continuous
   color pulse (not a one-shot animation like flash-pickup/-spend above) so
   it keeps reading as "still full," not just a blip the moment it crosses
   the threshold. */
@keyframes cap-warning-pulse {
  0%, 100% { color: #6b4c6b; }
  50% { color: #ff4757; }
}
.cap-warning { animation: cap-warning-pulse 1s ease-in-out infinite; }
/* Both classes can land on the same element at once (near the cap AND the
   count just rose) — listing both animations explicitly here (rather than
   relying on plain cascade order between the two single-animation rules
   above) is what lets the one-shot shake actually play instead of being
   silently overridden by the continuous pulse for its whole 0.33s run; once
   flash-spend's single iteration finishes, the still-infinite pulse
   naturally takes back over for the shared `color` property with no extra
   code needed. */
.cap-warning.flash-spend { animation: cap-warning-pulse 1s ease-in-out infinite, flash-spend 0.33s ease; }

/* "At the cap" bounce — per direct request, layered on top of the pulse
   above once the live count reaches the actual FULL 100% (not just the 80%
   warning threshold), so it reads like a real notification badge popping
   rather than just a color change. A pure scale "pop," deliberately never
   touching translate/rotate the way flash-spend's shake does below — per
   direct request to keep the two visually distinct, since a blocked
   production attempt (flash-spend) is a different signal from "the cap is
   full" itself (this). */
@keyframes cap-full-bounce {
  0%, 100% { transform: scale(1); }
  25% { transform: scale(1.18); }
  45% { transform: scale(0.95); }
  65% { transform: scale(1.06); }
  85% { transform: scale(0.99); }
}
.cap-warning.cap-full { animation: cap-warning-pulse 1s ease-in-out infinite, cap-full-bounce 1.6s ease-in-out infinite; }
/* All three can land on the same element at once (full, AND a blocked
   production attempt just shook it) — same "list every active animation
   explicitly" fix as the two-way compound rule above, so the one-shot
   shake still plays in full and the two continuous animations both resume
   afterward with no extra code needed. */
.cap-warning.cap-full.flash-spend { animation: cap-warning-pulse 1s ease-in-out infinite, cap-full-bounce 1.6s ease-in-out infinite, flash-spend 0.33s ease; }

/* Every building slot shares ONE 3-columns-per-row grid — per direct
   request ("3 max buildings/tools to a row"). Food/Demolish/Merge used to
   live in here too but moved out to the fixed #bottom-tool-bar below, per a
   later direct request that they "always be there" regardless of whether
   the shop is open — #build-tool-grid is the only thing left in here now,
   still using display:contents so its dynamically-built building buttons
   are direct grid items of #tool-bar (matters for the 3-per-row wrapping
   even with just one child group left). */
#tool-bar {
  display: grid;
  grid-template-columns: repeat(3, 56px);
  justify-content: center; /* was 'start' — left-aligning the grid tracks put the leftmost column flush against the panel edge, clipping its hover-grow scale (.tool-btn:hover) against that edge; centering leaves room on both sides */
  gap: 6px; /* tightened slightly (was 8px), same vertical-compacting request as #shop-species-grid above */
  margin-bottom: 6px;
}
.tool-btn {
  width: 56px; height: 56px; flex: none; /* was 40px — now matches the species icons' original size, per direct request */
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, #fffdf8 0%, #fff0dc 100%);
  border: 3px solid transparent;
  border-radius: 16px;
  cursor: pointer;
  padding: 0;
  font-size: 22px;
  /* Glossy "candy button" treatment, per the poppy make-over request — a
     bright top-edge highlight plus a soft bottom shadow reads as a raised
     button instead of a flat swatch. Kept as inset shadow layers (not a
     background gradient) on .tool-btn-build/.species-icon-btn below, since
     those two read their actual fill from a --tile-color/--species-color CSS
     var per instance and a gradient would fight that. */
  box-shadow: 0 3px 8px rgba(107, 76, 107, 0.2), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  transition: transform 0.1s ease, background 0.1s ease;
}
.tool-btn:hover:not(:disabled) { transform: scale(1.06); }
.tool-btn.selected { border-color: #ffb84d; background: linear-gradient(160deg, #fffaf0 0%, #ffe6b3 100%); }
/* Demolish (nothing built yet) / Merge (combining or splicing not unlocked
   yet), per direct request — grayed out and genuinely inert, same look
   every other disabled buy/confirm button in this game already uses. */
.tool-btn:disabled { opacity: 0.4; cursor: not-allowed; filter: grayscale(0.6); }
.tool-icon { display: block; border-radius: 50%; }
.tool-icon-food { width: 22px; height: 22px; background: #ffb238; box-shadow: 0 0 0 5px rgba(255, 178, 56, 0.3); } /* matches Config.js's FOOD_COLOR — was a green too close to Waste's own color to tell apart at a glance */
.tool-btn-emoji { font-size: 26px; } /* the Demolish button's 🔨 — was a plain colored circle, per direct request for an actual hammer/trashcan icon */

#build-tool-grid {
  display: contents;
}
.tool-btn-build {
  position: relative; /* anchors .building-icon-price and .tool-btn-family-dots */
  background: var(--tile-color, #ccc); opacity: 0.92;
  /* Glossy highlight layered on top of the per-instance --tile-color fill —
     see .tool-btn's comment above for why this is inset shadows, not a
     background gradient. */
  box-shadow: 0 3px 8px rgba(107, 76, 107, 0.2), inset 0 2px 0 rgba(255, 255, 255, 0.55), inset 0 -3px 6px rgba(0, 0, 0, 0.12);
}
.tool-btn-build.selected { opacity: 1; }
.building-icon-price {
  position: absolute; bottom: -8px; left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(160deg, #fffdf8 0%, #fff0dc 100%);
  color: #6b4c6b;
  font-size: 9px; font-weight: 800;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid #ffffff;
  box-shadow: 0 2px 5px rgba(107, 76, 107, 0.3);
  white-space: nowrap;
}
/* A family slot (the 3 Fan tiers stacked into one button — see Config.js's
   BUILDING_FAMILIES) shows one small dot per unlocked tier in that family,
   the current one filled in solid, so "there's more than one option here,
   click again to cycle" reads at a glance without needing a tooltip. */
.tool-btn-family-dots {
  position: absolute; top: -5px; left: 50%;
  transform: translateX(-50%);
  display: flex; gap: 2px;
}
.tool-btn-family-dots span {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
}
.tool-btn-family-dots span.current { background: #ffb84d; }

/* The fixed bottom tool-bar — Food/Demolish/Merge, always on screen
   regardless of shop/tank-panel/pause state, per direct request. Their
   descriptive text (cost, what each tool does) moved from the shop's old
   #tool-tooltip line onto each button's own native `title` attribute
   instead — a real hover tooltip, not a separate always-visible line. */
/* #bottom-bar-row is the fixed, centered, always-on-top row — the Shop
   toggle on the left, Tank Upgrades toggle on the right, flanking the
   food/demolish/merge pill in the middle, per direct request. #bottom-tool-bar
   itself is no longer independently fixed-positioned — it's just a plain
   flex child of the row now, keeping its own pill background/padding so it
   still reads as a distinct group from the two circular toggle buttons
   beside it. */
#bottom-bar-row {
  position: fixed;
  bottom: 14px; left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 14px;
  z-index: 15; /* always visible/clickable no matter what else is open */
}
#bottom-tool-bar {
  display: flex;
  gap: 10px;
  padding: 8px;
  background: linear-gradient(165deg, #fffaf3 0%, #ffe4f2 100%);
  border: 2px solid #ffffff;
  border-radius: 22px;
  box-shadow: 0 8px 22px rgba(107, 76, 107, 0.28), inset 0 2px 0 rgba(255, 255, 255, 0.7);
}

#shop-species-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center; /* was left-aligned by default (flex-start) — clipped the leftmost icon's hover-grow scale (.species-icon-btn:hover) against the panel edge, same fix as #tool-bar above */
  gap: 10px 10px; /* tightened slightly (was 14px 12px) per direct request to compact the shop vertically so less scrolling is needed below the sticky header */
  margin-top: 12px; /* now the gap below #shop-sticky-header, which owns the visual break itself */
  margin-bottom: 10px;
}
.species-icon-btn {
  position: relative;
  width: 64px; height: 64px; flex: none; /* was 56px — "slightly bigger" per direct request */
  border-radius: 50%;
  border: 3px solid transparent;
  background: var(--species-color, #ccc);
  cursor: pointer;
  padding: 0;
  /* Same glossy-highlight-over-a-dynamic-color-var trick as .tool-btn-build
     above — inset highlight/shadow layers instead of a background gradient. */
  box-shadow: 0 3px 8px rgba(107, 76, 107, 0.25), inset 0 3px 0 rgba(255, 255, 255, 0.55), inset 0 -4px 7px rgba(0, 0, 0, 0.12);
  transition: transform 0.12s ease, border-color 0.12s ease;
}
.species-icon-btn:hover { transform: scale(1.08); }
.species-icon-btn.selected { border-color: #ffb84d; }
.species-icon-price {
  position: absolute; bottom: -8px; left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(160deg, #fffdf8 0%, #fff0dc 100%);
  color: #6b4c6b;
  font-size: 10px; font-weight: 800;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid #ffffff;
  box-shadow: 0 2px 5px rgba(107, 76, 107, 0.3);
  white-space: nowrap;
}

/* A genuinely FIXED height, not min-height — per direct request ("I don't
   want it pushing the shop items up and down every time I click a different
   option"). An earlier pass made this min-height instead of a hard height so
   a tall stats block (the Processor family's 4 stat lines) wouldn't clip,
   but that meant the icon grid below still visibly shifted every time the
   box grew/shrank between selections, which is exactly what's being fixed
   here. Sized generously enough that every real building/species' content
   fits without scrolling in practice; #shop-preview-content's own
   overflow-y:auto (below) is a safety net for anything that somehow doesn't,
   scrolling INSIDE the fixed box instead of resizing it. */
#shop-preview {
  height: 232px;
  display: flex;
  flex-direction: column;
  background: linear-gradient(165deg, #fffaf3 0%, #fff0f6 100%);
  border-radius: 18px;
  border: 2px solid #ffffff;
  padding: 12px;
  margin-bottom: 0; /* #shop-sticky-header's own padding-bottom provides the gap now that this sits inside it */
  box-shadow: 0 4px 12px rgba(107, 76, 107, 0.16), inset 0 2px 0 rgba(255, 255, 255, 0.7);
}
#shop-preview-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 12px;
  opacity: 0.6;
}
#shop-preview-empty.hidden { display: none; }
#shop-preview-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto; /* safety net — see #shop-preview's own comment on why this box is a fixed height now */
  scrollbar-width: thin;
  scrollbar-color: #ffb6d5 #fff0f6;
}
#shop-preview-content.hidden { display: none; }
#shop-preview-content::-webkit-scrollbar { width: 6px; }
#shop-preview-content::-webkit-scrollbar-track { background: #fff0f6; border-radius: 999px; }
#shop-preview-content::-webkit-scrollbar-thumb { background: #ffb6d5; border-radius: 999px; }
#shop-preview-content::-webkit-scrollbar-thumb:hover { background: #ff9ac4; }
#shop-preview-top {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 10px;
}
#shop-preview-canvas {
  width: 60px; height: 60px; flex: none;
  border-radius: 50%;
  background: #ffe9f2;
  box-shadow: 0 2px 6px rgba(107, 76, 107, 0.2);
}
#shop-preview-text { flex: 1; min-width: 0; }
/* Per direct request ("there's too much small text in the shop... reduce
   the amount of text in everything and increase the size of the font") —
   every size in this preview block bumped up a notch, alongside actually
   removing text elsewhere (speciesStatsHtml dropping its Role/Cost lines —
   see UI.js). */
#shop-preview-name { font-size: 17px; font-weight: 800; color: #c25f8f; }
#shop-preview-desc {
  font-size: 13px; line-height: 1.4; opacity: 0.85; margin-top: 2px;
  overflow: hidden; /* clip rather than grow the fixed-height box, if a future longer description would otherwise overflow */
}
/* No Buy button any more — a fish is armed as the active click-tool exactly
   like a building is (see UI.js's selectSpeciesForPreview/main.js's click
   handler), so the preview is read-only info plus this one hint line. */
#shop-preview-hint {
  margin-top: auto; /* pins to the bottom of the fixed-height column no matter how tall the description above it is */
  text-align: center;
  font-size: 13px; font-weight: 700; opacity: 0.6;
}
/* Processor/Auto-Feeder/Fan stats block — shown instead of/alongside the
   prose description when one of those is previewed, per direct request
   ("show the stats associated with that building"). A small wrapped row of
   compact stat chips rather than a paragraph, so it reads at a glance. */
#shop-preview-stats {
  display: flex; flex-wrap: wrap; gap: 5px 10px;
  margin-top: 6px;
}
#shop-preview-stats.hidden { display: none; }
.building-stat {
  font-size: 13px;
  color: #6b4c6b;
  opacity: 0.85;
}
.building-stat b { font-weight: 800; opacity: 1; }

/* Tank Upgrades panel — a stack of cards (leveled ladders like Food Quality/
   Fish Movement/Coin Capacity, plus one-time unlocks like Electricity Graph/
   Gold-per-min/Wave Countdown), each with a level readout, a description of
   the current effect, and a Buy button. Same visual language as the shop's
   preview/buy button, just stacked as a list instead of one big preview. */
#tank-upgrade-list {
  display: flex; flex-direction: column; gap: 14px;
}
.tank-upgrade-card {
  background: linear-gradient(165deg, #fffaf3 0%, #fff0f6 100%);
  border-radius: 18px;
  border: 2px solid #ffffff;
  padding: 14px;
  box-shadow: 0 4px 12px rgba(107, 76, 107, 0.16), inset 0 2px 0 rgba(255, 255, 255, 0.7);
}
.tank-upgrade-card.locked { opacity: 0.6; }
.tank-upgrade-name { font-size: 14px; font-weight: 800; color: #c25f8f; margin-bottom: 2px; }
.tank-upgrade-level { font-size: 11px; font-weight: 700; opacity: 0.7; margin-bottom: 6px; }
.tank-upgrade-desc { font-size: 11px; line-height: 1.4; opacity: 0.85; margin-bottom: 10px; }
.tank-upgrade-desc .stat-current { color: #ff4757; font-weight: 800; } /* same red as the money-spend flash */
.tank-upgrade-desc .stat-next { color: #2ecc71; font-weight: 800; } /* same green as the money-pickup flash */
.tank-upgrade-buy {
  width: 100%;
  background: linear-gradient(160deg, #a8f0cb 0%, #7ad4a8 100%); color: #1f4a34; border: none; border-radius: 12px;
  padding: 9px; cursor: pointer; font-size: 13px; font-weight: 800;
  box-shadow: 0 3px 8px rgba(53, 145, 97, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5);
  transition: transform 0.1s ease, background 0.1s ease;
}
.tank-upgrade-buy:hover { background: linear-gradient(160deg, #97e8bd 0%, #68c896 100%); transform: scale(1.02); }
.tank-upgrade-buy:disabled {
  background: #e6dcd0; color: #a99; opacity: 0.7; cursor: not-allowed; box-shadow: none; transform: none;
}

#pause-overlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(107, 76, 107, 0.35);
  /* Above the shop panel (10) and debug overlay (20) — and, per direct
     request, above the start screen (600) too: opening Settings from the
     start screen shows this SAME overlay layered on top of #start-overlay
     rather than swapping it out (see UI.js's initStartScreen), so the
     latter's own blurred/dimmed backdrop keeps covering the game and the
     not-yet-triggered splash underneath both. */
  z-index: 650;
}
#pause-overlay.hidden { display: none; }

#pause-menu {
  width: 280px;
  background: linear-gradient(165deg, #fffaf3 0%, #ffe4f2 100%);
  color: #6b4c6b;
  border-radius: 28px;
  border: 3px solid #ffffff;
  padding: 28px 24px;
  box-shadow: 0 16px 40px rgba(107, 76, 107, 0.45), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  text-align: center;
}
#pause-main.hidden, #pause-settings.hidden { display: none; }
#pause-title { font-size: 22px; font-weight: 800; color: #c25f8f; margin-bottom: 20px; }

.pause-btn {
  display: block;
  width: 100%;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe0ee 100%);
  color: #6b4c6b;
  border: none; border-radius: 14px;
  padding: 12px;
  margin-bottom: 12px;
  font-size: 15px; font-weight: 800;
  cursor: pointer;
  box-shadow: 0 3px 8px rgba(107, 76, 107, 0.2), inset 0 2px 0 rgba(255, 255, 255, 0.75);
  transition: transform 0.1s ease, background 0.1s ease;
}
.pause-btn:last-child { margin-bottom: 0; }
.pause-btn:hover { transform: scale(1.03); background: linear-gradient(160deg, #fffaf3 0%, #ffd0e4 100%); }
/* Per direct request — Continue Game stays visible but reads as clearly
   unavailable (rather than disappearing outright) when there's no save. */
.pause-btn:disabled { opacity: 0.45; cursor: not-allowed; filter: grayscale(0.6); transform: none; }
.pause-btn:disabled:hover { transform: none; background: linear-gradient(160deg, #fffdf8 0%, #ffe0ee 100%); }
#pause-resume-btn { background: linear-gradient(160deg, #a8f0cb 0%, #7ad4a8 100%); color: #1f4a34; box-shadow: 0 3px 8px rgba(53, 145, 97, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5); }
#pause-resume-btn:hover { background: linear-gradient(160deg, #97e8bd 0%, #68c896 100%); }

/* Music/SFX volume sliders — replaces the old "more settings coming soon"
   placeholder now that there's something real to put here. */
.volume-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 2px;
  font-size: 13px; font-weight: 700;
  color: #6b4c6b;
}
.volume-row label { flex: none; width: 78px; text-align: left; }
.volume-row input[type="range"] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: 999px;
  background: linear-gradient(90deg, #ffb6d5, #ffe0ee);
  outline: none;
  cursor: pointer;
}
.volume-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #fff6ea;
  border: 2px solid #ff6fae;
  box-shadow: 0 1px 4px rgba(107, 76, 107, 0.3);
  cursor: pointer;
}
.volume-row input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #fff6ea;
  border: 2px solid #ff6fae;
  box-shadow: 0 1px 4px rgba(107, 76, 107, 0.3);
  cursor: pointer;
}

/* First-launch start screen — shown on page load, ahead of everything else
   (highest z-index in the game), with a blurred/dimmed view of the tank
   behind it via backdrop-filter (a compositor-level blur on this one static
   overlay, nothing like the per-frame ctx.filter cost this codebase
   otherwise avoids on the canvas itself). Settings opened from here reuses
   the EXACT SAME #pause-settings sub-view the real pause menu uses (see
   UI.js's initStartScreen/returnFromPauseSettings) rather than a second copy
   of the sliders, so "Settings with the same settings as the pause menu" is
   literally true, not just visually similar. */
#start-overlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(11, 37, 64, 0.45);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 600; /* above the splash (500) — the splash only ever starts animating once this closes */
}
#start-overlay.hidden { display: none; }
/* Same size and look as #pause-menu, per direct request — identical width/
   padding/border/radius/shadow recipe (just the outer overlay's own tint
   differs, to read as "before the game" rather than "game paused"), and
   flex-shrink:0/max-width so it can never be stretched to fill the
   flex-centered overlay even in an unusual viewport. */
#start-modal {
  width: 280px;
  max-width: 90vw;
  flex-shrink: 0;
  background: linear-gradient(165deg, #fffaf3 0%, #ffe4f2 100%);
  color: #6b4c6b;
  border-radius: 28px;
  border: 3px solid #ffffff;
  padding: 28px 24px;
  box-shadow: 0 16px 40px rgba(107, 76, 107, 0.45), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  text-align: center;
}
/* Deliberately no title or tagline text at all any more, per direct request
   for "a less is more type of visual" — just the three buttons. (Also means
   the game's name is never shown here — the splash animation, once Start is
   clicked, is the only place it appears, so it still reads as a reveal
   rather than being shown twice/spoiled.) */
#start-new-game-btn, #start-continue-btn { background: linear-gradient(160deg, #a8f0cb 0%, #7ad4a8 100%); color: #1f4a34; box-shadow: 0 3px 8px rgba(53, 145, 97, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5); }
#start-new-game-btn:hover, #start-continue-btn:hover { background: linear-gradient(160deg, #97e8bd 0%, #68c896 100%); }

/* How-To-Play tutorial page — a scrollable list of icon+text rows, opened
   from the start screen's Help button. Same centered-modal language as
   every other popup in this game. */
#start-help-overlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(11, 37, 64, 0.45);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 600;
}
#start-help-overlay.hidden { display: none; }
#start-help-modal {
  width: 520px;
  max-width: 92vw;
  max-height: 84vh;
  background: linear-gradient(165deg, #fffaf3 0%, #ffe4f2 100%);
  color: #6b4c6b;
  border-radius: 28px;
  border: 3px solid #ffffff;
  padding: 22px 24px;
  box-shadow: 0 20px 50px rgba(11, 37, 64, 0.5), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  display: flex;
  flex-direction: column;
}
#start-help-header-row { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
#start-help-title { font-size: 20px; font-weight: 800; color: #c25f8f; flex: 1; }
#start-help-back-btn { flex: none; width: auto; padding: 9px 16px; margin-bottom: 0; }
#start-help-list {
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: #ffb6d5 #fff0f6;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#start-help-list::-webkit-scrollbar { width: 8px; }
#start-help-list::-webkit-scrollbar-track { background: #fff0f6; border-radius: 999px; }
#start-help-list::-webkit-scrollbar-thumb { background: #ffb6d5; border-radius: 999px; }
.help-row {
  display: flex; align-items: center; gap: 14px;
  background: linear-gradient(160deg, #fffdf8 0%, #fff0dc 100%);
  border: 2px solid #ffffff;
  border-radius: 16px;
  padding: 10px 14px;
  box-shadow: 0 3px 8px rgba(107, 76, 107, 0.14), inset 0 2px 0 rgba(255, 255, 255, 0.7);
}
.help-icon { flex: none; font-size: 26px; width: 36px; text-align: center; }
.help-text { font-size: 12.5px; line-height: 1.45; }
.help-text b { color: #c25f8f; }

/* Mound "Throw money at it" confirm popup. No dimming backdrop — #mound-overlay
   stays fully transparent, it's only there as a full-viewport invisible
   click-catcher (so a click outside the card closes it instead of falling
   through to the canvas underneath, same as the pause menu's backdrop-click
   behavior, just without the visible tint). The card itself is positioned
   right next to the Mound rather than centered on screen: #mound-menu-anchor
   gets its left/top set every frame it's open (UI.js's updateMoundMenuPosition,
   called from updateHUD) to the Mound's live on-screen position, tracking the
   camera if the player pans while it's open; the static translate(-50%,-100%)
   anchors the anchor's bottom-center at that point. The fly-out/fly-in
   animation is a separate scale+opacity transition on #mound-menu itself
   (transform-origin pinned to that same bottom-center), so positioning and
   animation never fight each other in one transform. */
#mound-overlay {
  position: fixed; inset: 0;
  z-index: 190;
}
#mound-overlay.hidden { display: none; }

#mound-menu-anchor {
  position: fixed;
  transform: translate(-50%, -100%);
}

#mound-menu {
  width: 180px;
  background: #fff8f0;
  color: #6b4c6b;
  border-radius: 21px;
  padding: 21px 18px;
  box-shadow: 0 14px 36px rgba(107, 76, 107, 0.4);
  text-align: center;
  transform-origin: 50% 100%; /* grows from/shrinks back into its bottom edge, right where the Mound is */
  transform: scale(1);
  opacity: 1;
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.18s ease;
}
#mound-menu.mound-menu-closed {
  transform: scale(0.05);
  opacity: 0;
}
#mound-title { font-size: 15px; font-weight: 800; color: #c25f8f; margin-bottom: 15px; }

.mound-throw-btn {
  display: block;
  width: 100%;
  background: #8fe0b8; color: #1f4a34; border: none; border-radius: 11px;
  padding: 9px; margin-bottom: 9px;
  font-size: 14px; font-weight: 800;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(53, 145, 97, 0.3);
  transition: transform 0.1s ease, background 0.1s ease;
}
.mound-throw-btn:hover { background: #7ad4a8; transform: scale(1.03); }
.mound-throw-btn:disabled {
  background: #e6dcd0; color: #a99; opacity: 0.7; cursor: not-allowed; box-shadow: none; transform: none;
}
#mound-menu .pause-btn { padding: 9px; font-size: 12px; margin-bottom: 0; }

/* Manufacturer/Power Plant recipe pop-up — a small "miniature shop" menu,
   per direct request, opened by clicking either building on the canvas.
   Same fly-out-of-its-anchor mechanic as #mound-overlay/#mound-menu above
   (no dimming backdrop, a transparent full-viewport click-catcher so a click
   outside the card closes it), just wider to fit up to 3 recipe icons in a
   row side by side. UI.js's updateRecipeMenuPosition tracks the clicked
   tile's live screen position every frame it's open. */
#recipe-overlay {
  position: fixed; inset: 0;
  z-index: 190;
}
#recipe-overlay.hidden { display: none; }

#recipe-menu-anchor {
  position: fixed;
  transform: translate(-50%, -100%);
}

#recipe-menu {
  width: 320px;
  max-height: 74vh;
  display: flex;
  flex-direction: column;
  background: #fff8f0;
  color: #6b4c6b;
  border-radius: 21px;
  padding: 18px 16px;
  box-shadow: 0 14px 36px rgba(107, 76, 107, 0.4);
  text-align: center;
  transform-origin: 50% 100%;
  transform: scale(1);
  opacity: 1;
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.18s ease;
}
#recipe-menu.recipe-menu-closed {
  transform: scale(0.05);
  opacity: 0;
}
#recipe-menu-title { flex-shrink: 0; font-size: 15px; font-weight: 800; color: #c25f8f; margin-bottom: 12px; }
/* Per direct request — each recipe now lays out horizontally (icon left,
   name+description stacked to its right) and the whole list stacks
   vertically, one per line, since a Manufacturer now has 5 recipes to show
   at once (up from 3-4) — too many for a side-by-side row to fit
   comfortably. Scrolls independently of the sticky stats footer below it
   (#recipe-menu-stats) via its own overflow-y, capped by #recipe-menu's own
   max-height above. */
#recipe-menu-options {
  display: flex; flex-direction: column; gap: 8px;
  flex: 1 1 auto;
  min-height: 0; /* required for overflow-y to actually constrain a flex child instead of just growing past #recipe-menu's own max-height */
  overflow-y: auto;
  padding-right: 4px;
}
.recipe-option {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  background: #f3e9dd; border: 2px solid transparent; border-radius: 14px;
  padding: 8px 10px;
  text-align: left;
  cursor: pointer;
  transition: transform 0.1s ease, border-color 0.1s ease, background 0.1s ease;
}
.recipe-option:hover { transform: scale(1.02); }
.recipe-option.selected { border-color: var(--recipe-color, #8fe0b8); background: #eafaf1; }
.recipe-option.locked { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.recipe-option-icon { font-size: 26px; flex-shrink: 0; }
.recipe-option-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.recipe-option-name { font-size: 11px; font-weight: 800; }
.recipe-option-desc { font-size: 9.5px; color: #8a7a8a; line-height: 1.25; }
/* The Manufacturer/Power Plant's own general stats (per-ingredient timing,
   power draw) — per direct request ("add in just the stats from the
   manufacturer into its recipe picker"), reusing buildingStatsHtml's exact
   .building-stat chip output rather than a bespoke second copy of it.
   "Sticky at the bottom of the popup" per direct request — achieved here
   via plain flexbox rather than CSS position:sticky (which needs its OWN
   scrolling ancestor to track against; this element is a plain SIBLING of
   the scrolling #recipe-menu-options above, not a descendant of it, so
   sticky positioning would have nothing to stick to). #recipe-menu itself
   is the fixed-height flex column (max-height + overflow hidden by virtue
   of #recipe-menu-options being the only child that scrolls, via its own
   flex:1/min-height:0/overflow-y — see that rule's own comment); this
   footer just sits after it as a flex-shrink:0 item, which reads as
   "always visible at the bottom" for free with no scroll-tracking needed.
   A top border visually separates it from the scrolling list above. */
#recipe-menu-stats {
  display: flex; flex-wrap: wrap; gap: 4px 8px; justify-content: center;
  flex-shrink: 0;
  padding-top: 10px; margin-top: 10px;
  border-top: 1px solid rgba(107, 76, 107, 0.15);
}

/* Generic "what is this and what does it do" pop-up for any placed building
   — per direct request ("every building has a pop out modal kinda like the
   manufacturer's recipe picker, but with the info that would normally show
   up in the shop window"). Same fly-out-of-its-anchor mechanic as
   #recipe-menu (see that block's own comment), just showing a read-only
   icon/name/description/stats block instead of clickable recipe options. */
#building-info-overlay { position: fixed; inset: 0; z-index: 190; }
#building-info-overlay.hidden { display: none; }
#building-info-anchor { position: fixed; transform: translate(-50%, -100%); }
#building-info-menu {
  width: 260px;
  background: #fff8f0;
  color: #6b4c6b;
  border-radius: 21px;
  padding: 16px 18px;
  box-shadow: 0 14px 36px rgba(107, 76, 107, 0.4);
  text-align: left;
  transform-origin: 50% 100%;
  transform: scale(1);
  opacity: 1;
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.18s ease;
}
#building-info-menu.building-info-menu-closed { transform: scale(0.05); opacity: 0; }
#building-info-header-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
#building-info-icon { font-size: 24px; line-height: 1; }
#building-info-name { font-size: 15px; font-weight: 800; color: #c25f8f; }
#building-info-desc { font-size: 12px; line-height: 1.4; opacity: 0.85; margin-bottom: 8px; }
#building-info-stats { display: flex; flex-wrap: wrap; gap: 4px 8px; }

/* Science Lab: a real branching tech tree now, per direct request — a
   centered dimmed-backdrop modal (same pattern #pause-overlay already
   uses) instead of the small Mound-anchored flyout every other popup here
   uses, since a node-link tree needs real, consistent screen space
   regardless of where the camera happens to be panned. */
#lab-overlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(107, 76, 107, 0.35);
  z-index: 210; /* above the pause menu (200) — the Lab and pause menu are never open together, but this keeps the stacking order sensible if that ever changes */
}
#lab-overlay.hidden { display: none; }

#lab-modal {
  width: 680px;
  max-width: 92vw;
  max-height: 86vh;
  background: linear-gradient(165deg, #fffaf3 0%, #ffe4f2 100%);
  color: #6b4c6b;
  border: 3px solid #ffffff;
  border-radius: 28px;
  padding: 22px 24px;
  box-shadow: 0 16px 40px rgba(107, 76, 107, 0.45), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  display: flex;
  flex-direction: column;
  transform-origin: 50% 50%;
  transform: scale(1);
  opacity: 1;
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.18s ease;
}
#lab-modal.lab-modal-closed {
  transform: scale(0.85);
  opacity: 0;
}

#lab-header-row {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 16px;
}
#lab-title { font-size: 20px; font-weight: 800; color: #c25f8f; flex: none; }
#lab-science-readout {
  display: inline-block;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  border: 2px solid #ffffff;
  color: #6b4c6b;
  font-size: 13px; font-weight: 800;
  padding: 6px 14px;
  border-radius: 999px;
  box-shadow: 0 3px 9px rgba(107, 76, 107, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.85);
  flex: 1;
}
/* Zoom in/out, per direct request — mirrors the mouse-wheel zoom UI.js's
   initLabTreeDrag also wires up, for anyone without (or who doesn't think
   to try) a scroll wheel over the tree. */
#lab-zoom-controls { display: flex; gap: 6px; flex: none; }
#lab-zoom-controls .pause-btn {
  width: 32px; height: 32px; padding: 0; margin-bottom: 0;
  font-size: 18px; font-weight: 800; line-height: 1;
}
#lab-close-btn { flex: none; width: auto; padding: 9px 16px; margin-bottom: 0; }

/* Click-and-drag panning instead of scrolling, per direct request — the
   tree grew too large (the Gene-Splicing hybrid sub-tree) for scrollbars/
   wheel-scroll to stay a pleasant way to explore it. overflow:hidden hides
   any native scrollbar and blocks wheel-scroll, but scrollLeft/scrollTop
   are still fully readable/settable via JS (see UI.js's initLabTreeDrag) —
   panning just sets them directly from a mousedown-drag instead of the
   browser's own scroll handling. drawLabTreeConnectors already reads
   scrollLeft/scrollTop for its connector math, so panning this way needs no
   further change there. user-select:none stops a drag from also
   highlighting node text the way a click-drag normally would on a page.
   Per a direct follow-up report, the drag listener now lives on the whole
   #lab-modal (see UI.js's initLabTreeDrag), not just this wrap — the player
   had to click exactly the tree's own empty background to pan it; now any
   mousedown anywhere in the modal (the header row, the padding around the
   tree, a locked node) starts a pan, so #lab-modal itself carries the grab
   cursor/no-select treatment and .dragging class too, not just this inner
   wrap. */
#lab-tree-wrap {
  position: relative;
  flex: 1;
  min-height: 380px;
  overflow: hidden;
}
#lab-modal { cursor: grab; user-select: none; }
#lab-modal.dragging { cursor: grabbing; }

#lab-tree-canvas {
  position: absolute; top: 0; left: 0;
  pointer-events: none; /* the connector lines sit visually under the node buttons and never intercept clicks */
}
#lab-tree-columns {
  position: relative;
  display: flex;
  gap: calc(48px * var(--lab-zoom, 1));
  padding: calc(12px * var(--lab-zoom, 1)) calc(16px * var(--lab-zoom, 1));
  min-width: max-content;
}
.lab-tree-col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: calc(28px * var(--lab-zoom, 1));
  width: calc(170px * var(--lab-zoom, 1));
  flex: none;
}

/* Zoom, per direct request ("make it so you can zoom in and out of the
   science lab") — driven by a --lab-zoom custom property (UI.js's
   setLabZoom, mouse-wheel or the +/- buttons above) that scales every real
   box-model/font dimension in the tree by the same factor, rather than a
   CSS transform: scale(). A transform doesn't change an element's actual
   layout size, so #lab-tree-wrap's overflow/scrollWidth (what the existing
   drag-pan and drawLabTreeConnectors's getBoundingClientRect() math both
   depend on) would silently desync from what's visually drawn; scaling the
   real dimensions instead means every bit of existing geometry-reading code
   keeps working unmodified. */
.lab-node {
  width: 100%;
  background: linear-gradient(160deg, #fffdf8 0%, #fff0dc 100%);
  border: 2px solid #ffffff;
  border-radius: calc(16px * var(--lab-zoom, 1));
  padding: calc(10px * var(--lab-zoom, 1)) calc(14px * var(--lab-zoom, 1));
  text-align: left;
  cursor: pointer;
  box-shadow: 0 3px 8px rgba(107, 76, 107, 0.2), inset 0 2px 0 rgba(255, 255, 255, 0.7);
  transition: transform 0.1s ease, opacity 0.15s ease;
}
.lab-node:hover:not(:disabled) { transform: scale(1.04); }
.lab-node:disabled { cursor: not-allowed; }
.lab-node.locked { opacity: 0.55; }
/* Unaffordable but unlocked — still fully clickable (opens the purchase
   modal to read it), per direct request; just a lighter dim than .locked
   so the two read as distinct states rather than identically inert. */
.lab-node.unaffordable { opacity: 0.8; }
.lab-node.purchased {
  background: linear-gradient(160deg, #a8f0cb 0%, #7ad4a8 100%);
  box-shadow: 0 3px 8px rgba(53, 145, 97, 0.3), inset 0 2px 0 rgba(255, 255, 255, 0.5);
}
.lab-node-name { font-size: calc(13px * var(--lab-zoom, 1)); font-weight: 800; color: #6b4c6b; }
.lab-node.purchased .lab-node-name { color: #1f4a34; }
.lab-node-cost { font-size: calc(11px * var(--lab-zoom, 1)); font-weight: 700; opacity: 0.75; margin-top: 3px; }

/* Purchase confirmation modal — per direct request ("I want to have a
   purchase modal pop up when in the science lab, so you have a chance to
   read what something does and confirm the purchase, instead of just
   clicking it"). Clicking a lab node no longer spends anything directly; it
   opens this on top of #lab-overlay (a higher z-index, same dimmed-backdrop
   pattern) showing what the node grants — a fish or building's real
   description plus its stats (the same .building-stat chip look the shop
   preview already uses) — with Confirm actually doing the purchase UI.js's
   buyLabUpgrade always used to do immediately on click. */
#lab-purchase-overlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(107, 76, 107, 0.45);
  z-index: 230; /* above #lab-overlay (210) — this sits on top of the tree modal, not beside it */
}
#lab-purchase-overlay.hidden { display: none; }
#lab-purchase-modal {
  width: 340px;
  max-width: 90vw;
  background: linear-gradient(165deg, #fffaf3 0%, #ffe4f2 100%);
  color: #6b4c6b;
  border: 3px solid #ffffff;
  border-radius: 24px;
  padding: 20px 22px;
  box-shadow: 0 16px 40px rgba(107, 76, 107, 0.45), inset 0 2px 0 rgba(255, 255, 255, 0.7);
}
#lab-purchase-header-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
#lab-purchase-icon { font-size: 26px; line-height: 1; }
#lab-purchase-name { font-size: 17px; font-weight: 800; color: #c25f8f; }
#lab-purchase-desc { font-size: 12.5px; line-height: 1.45; opacity: 0.85; margin-bottom: 8px; }
#lab-purchase-stats {
  display: flex; flex-wrap: wrap; gap: 4px 8px;
  margin-bottom: 12px;
}
#lab-purchase-cost {
  font-size: 14px; font-weight: 800; color: #6b4c6b;
  text-align: center;
  background: linear-gradient(160deg, #fffdf8 0%, #ffe9f4 100%);
  border: 2px solid #ffffff;
  border-radius: 999px;
  padding: 7px 10px;
  margin-bottom: 14px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);
}
#lab-purchase-buttons { display: flex; gap: 10px; }
#lab-purchase-buttons .pause-btn { margin-bottom: 0; flex: 1; }
#lab-purchase-confirm-btn {
  background: linear-gradient(160deg, #a8f0cb 0%, #7ad4a8 100%);
  color: #1f4a34;
  box-shadow: 0 3px 8px rgba(53, 145, 97, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5);
}
/* Per direct request — the node itself is always clickable to read once
   unlocked, but Confirm is what's actually greyed out when it can't be
   afforded, same look every other disabled buy button in this game uses. */
#lab-purchase-confirm-btn:disabled {
  background: #e6dcd0; color: #a99; opacity: 0.7; cursor: not-allowed; box-shadow: none; transform: none;
}

#debug-overlay {
  position: fixed; bottom: 12px; left: 12px;
  background: rgba(0, 0, 0, 0.7);
  color: #7cfc7c;
  font-family: 'Consolas', monospace;
  font-size: 12px;
  padding: 10px 14px;
  border-radius: 6px;
  white-space: pre;
  z-index: 20;
  pointer-events: none;
}
#debug-overlay.hidden { display: none; }
