:root {
    /* Shared HUD design language — every panel added in later milestones
       (lantern meter, night timer, objectives, action cards, upgrade
       cards) should reuse these tokens so the UI reads as one system. */
    --panel-bg: rgba(23, 17, 12, 0.82);
    --panel-border: rgba(255, 184, 77, 0.32);
    --panel-shadow: 0 2px 10px rgba(0, 0, 0, 0.45), 0 0 14px rgba(255, 150, 60, 0.06);
    --panel-radius: 14px;
    --text-main: #f4e6c8;
    --text-dim: rgba(244, 230, 200, 0.65);
    --amber: #ffb84d;
    --amber-bright: #ffcf7a;
    --accent-wood: #c9975a;
    --accent-stone: #b9b9b2;
    --accent-shard: #b47bff;
    --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    /* Top HUD-specific palette — direct spec for the pure-CSS bar rebuild
       (see #top-hud's own comment). Kept separate from the general
       --panel- and --amber- tokens above rather than repointing those,
       since this is deliberately a slightly different look (warmer,
       richer gold) just for this one bar, not a site-wide palette change. */
    --hud-border: #9a681d;
    --hud-gold: #d79a2b;
    --hud-text: #f4ead2;
    --hud-text-muted: #b9aa8f;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html, body {
    width: 100%;
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

body {
    /* Letterbox bars around the portrait stage on landscape-shaped
       (desktop) windows. On a real phone #game-stage fills this
       completely, so this color is never actually seen there. */
    background: #000;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* The portrait-locked game area. Sized in px by main.js on every resize —
   full viewport on a real portrait device, letterboxed to 9:16 when the
   window itself is landscape-shaped and can't be physically rotated
   (i.e. desktop). Every HUD element below is positioned relative to THIS,
   not the browser viewport, so it stays correctly placed either way. */
#game-stage {
    position: relative;
    overflow: hidden;
    background: #0a1018;
}

#game-stage.letterboxed {
    border-radius: 8px;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08), 0 20px 60px rgba(0, 0, 0, 0.6);
}

#game-canvas {
    display: block;
    width: 100%;
    height: 100%;
    touch-action: none;
}

/* --- Top HUD: one pure-CSS bar, no background image at all ---
   Third attempt at this bar. Attempt 1 (one wood-plank IMAGE spanning
   the top) distorted under its own stretched aspect ratio and overflowed
   at 320px. Attempt 2 backed out to three independent plain-bordered
   panels. This is a from-scratch CSS/HTML rebuild of the ONE-bar look
   (direct visual reference supplied) using only gradients/borders/
   box-shadow for the bar chrome — nothing raster, so nothing to distort
   or crop regardless of viewport width. #lantern-hud/#night-hud/
   #resource-hud keep their EXACT ids (main.js's DOM lookups are
   untouched) but are now flex/grid *sections inside* this bar rather
   than standalone positioned panels. clamp() throughout so text/icons
   scale with the viewport instead of overflowing their section at
   narrow widths — verified at 320/360/375px via getBoundingClientRect(),
   not just eyeballed. */
#top-hud {
    position: absolute;
    top: max(10px, env(safe-area-inset-top));
    left: 50%;
    transform: translateX(-50%);
    /* % (not vw) on purpose — #top-hud's containing block is #game-stage,
       which is letterboxed to a 9:16 box narrower than the real browser
       viewport on landscape-shaped (desktop) windows. vw is ALWAYS
       viewport-relative regardless of containing block, so it silently
       ignored the letterbox and overflowed past both edges of the actual
       stage — caught by measuring #top-hud's rect against #game-stage's
       own rect, not just eyeballing a phone-shaped preview. */
    width: min(94%, 640px);
    display: flex;
    align-items: stretch;
    /* A single flowing background image (assets/ui/woods-background.webp,
       1400x685) behind the bar, per direct request — different from
       Attempt 1's failed wood-plank image (see this block's own top
       comment) in the specific way that matters: `background-size: cover`
       always preserves the image's own aspect ratio (crops, never
       stretches non-uniformly) and a CSS background can never overflow
       its own element box the way a raw <img> could, so neither of
       Attempt 1's two failure modes applies here regardless of how
       narrow/wide this bar's own fluid width gets. The gradient stays
       layered on top (comma-separated background-image list — first
       layer paints over later ones) as a legibility scrim, just lighter
       than before (0.97/0.98 alpha -> 0.72/0.8) so the forest actually
       reads through softly instead of being fully hidden under it. */
    background-image:
        linear-gradient(180deg, rgba(29, 22, 13, 0.72), rgba(13, 10, 6, 0.8)),
        url('assets/ui/woods-background.webp');
    background-size: cover;
    background-position: center;
    border: 1px solid var(--hud-border);
    border-radius: 18px;
    box-shadow:
        inset 0 1px 0 rgba(255, 210, 110, 0.18),
        inset 0 -1px 0 rgba(0, 0, 0, 0.55),
        0 0 10px rgba(208, 142, 35, 0.16);
    color: var(--hud-text);
    padding: clamp(8px, 2.4vw, 14px) clamp(10px, 3vw, 18px);
    pointer-events: none;
    z-index: 10;
}

/* Thin vertical gold separators between the three sections. */
.hud-divider {
    align-self: stretch;
    width: 1px;
    margin: 0 clamp(8px, 2.4vw, 16px);
    background: linear-gradient(180deg, transparent, rgba(215, 154, 43, 0.5) 15%, rgba(215, 154, 43, 0.5) 85%, transparent);
    flex-shrink: 0;
}

#lantern-hud, #night-hud, #resource-hud {
    min-width: 0;
    transition: filter 0.4s ease;
}

#lantern-hud {
    flex: 0 1 30%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: clamp(3px, 1vw, 6px);
    pointer-events: none;
}

/* Fuel tier glow (see updateLanternHud() in main.js) — a warm-to-red
   filter over the whole section rather than a border/shadow swap, since
   this section no longer has its own border to recolor. */
#lantern-hud.tier-warning {
    filter: drop-shadow(0 0 6px rgba(255, 157, 77, 0.35));
}

#lantern-hud.tier-critical {
    filter: drop-shadow(0 0 8px rgba(224, 71, 58, 0.5));
    animation: pulse-bar 1s ease-in-out infinite;
}

.lantern-flame-icon {
    width: clamp(12px, 3vw, 16px);
    height: clamp(12px, 3vw, 16px);
}

.lantern-pct.tier-warning {
    color: #ff9d4d;
}

.lantern-pct.tier-critical {
    color: #ff7a5c;
}

#night-hud {
    flex: 0 1 40%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(3px, 1vw, 6px);
    text-align: center;
    pointer-events: none;
}

/* Same heartbeat the timer digits already get in the final stretch (see
   .night-timer-row.final-stretch below), now over the whole section. */
#night-hud.final-stretch {
    filter: drop-shadow(0 0 8px rgba(255, 90, 60, 0.45));
    animation: pulse-bar 0.8s ease-in-out infinite;
}

/* Clean 2-column grid, icon+count pairs only — no boxed subcards around
   each resource (direct spec). */
#resource-hud {
    flex: 0 1 30%;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    align-content: center;
    gap: clamp(3px, 1.2vw, 8px) clamp(8px, 2.4vw, 16px);
    pointer-events: none;
}

.res-card {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    transition: transform 0.15s ease;
}

.res-card.bump {
    transform: scale(1.16);
}

.res-card.hidden {
    display: none;
}

.res-icon {
    width: clamp(15px, 4.2vw, 22px);
    height: clamp(15px, 4.2vw, 22px);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.res-icon svg, .res-icon img {
    width: 100%;
    height: 100%;
    display: block;
    /* Real art (wood.webp) is a photo-lit object, not a flat icon glyph
       like the inline SVGs — object-fit:contain keeps it fully visible
       within the same square box those SVGs already filled, rather than
       cropping to fill it. */
    object-fit: contain;
}

.res-count {
    color: var(--hud-text);
    font-family: var(--font-ui);
    font-size: clamp(12px, 3.6vw, 16px);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Direct follow-up on Deep Pockets: "turn the 8 to a green number" —
   only #res-backpack-capacity gets this class (see main.js's
   refreshResourceHud()), and only once the upgrade has actually raised
   capacity above CONFIG.INVENTORY.CAPACITY's base value. Same green
   already used for "common"/positive indicators elsewhere (evidence
   toast rarity, etc.) rather than a new one-off color. */
#res-backpack-capacity.boosted {
    color: #8fae7a;
}

/* Second redesign pass, direct request — the previous beige/black pill
   (see BUILD_LOG Session 182) fixed the readability/wrapping complaints
   but read as "a generic web tooltip," not a game HUD element. Player
   supplied a full visual spec (ChatGPT-drafted) for a dark, cinematic
   "survival-tip card" instead — glass panel, icon badge, label + body
   copy, warm lantern-amber accents. Kept the same vertical anchor
   (just above #action-bar, not the spec's own suggested "top-middle")
   since that position was itself a deliberate earlier direct request
   (see the git history on this rule) and #top-hud already occupies the
   real top-middle of the screen — moving it there would sit it right on
   top of the light/night HUD instead. Structure is icon + label/body per
   the supplied markup (index.html's #full-flash now has real child
   elements — #full-flash-icon, #full-flash-label, #full-flash-text —
   instead of one bare text node; main.js's flashMessage() writes into
   #full-flash-text specifically now, see its own comment). */
#full-flash {
    position: absolute;
    bottom: calc(max(10px, env(safe-area-inset-bottom)) + 108px);
    left: 50%;
    display: flex;
    align-items: center;
    gap: 12px;
    width: min(420px, calc(100vw - 40px));
    padding: 16px 22px;
    background: rgba(12, 18, 22, 0.88);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: 16px;
    border: 1px solid rgba(245, 190, 90, 0.35);
    /* Drop shadow for depth off the dark background behind it, plus a
       thin warm line along the inner top edge (spec: "a very subtle warm
       inner glow along the top edge to tie it to the lantern theme") —
       an inset shadow reads as exactly that without a second visible
       border fighting the real one above. */
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(245, 190, 90, 0.18);
    pointer-events: none;
    opacity: 0;
    transform: translate(-50%, -8px) scale(0.97);
    transition: opacity 0.22s ease-out, transform 0.22s ease-out;
    z-index: 20;
}

#full-flash.show {
    opacity: 1;
    transform: translate(-50%, 0) scale(1);
}

/* Small circular badge on the left — spec: "38-42px... muted warm amber
   circle... icon should not overpower the text." One consistent
   exclamation glyph rather than a different icon per message (backpack/
   chest/compass/etc. per the spec's own suggestion) — flashMessage()
   is a single plain `text` function shared by ~15 unrelated call sites
   (backpack-full, refuel, brew shortfalls, etc.) with no notion of a
   "hint type" to key an icon off of; a generic "heads up" glyph reads
   fine across all of them without adding that plumbing for a purely
   visual pass. Glow is the chosen accent (spec: accent bar OR icon glow,
   not both) — no separate vertical bar. */
#full-flash-icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, #f6c876, #c98a32);
    box-shadow: 0 0 10px rgba(245, 190, 90, 0.4);
    display: flex;
    align-items: center;
    justify-content: center;
}

#full-flash-icon svg {
    width: 20px;
    height: 20px;
}

#full-flash-copy {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    text-align: left;
}

#full-flash-label {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: #e6b65c;
    text-transform: uppercase;
}

#full-flash-text {
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 500;
    line-height: 1.4;
    color: rgba(255, 255, 255, 0.92);
    /* text-wrap: balance carried over from the previous pass (see Session
       182) — still the right call here: evens out a 2-line wrap instead
       of stranding a short trailing line, now on a narrower left-aligned
       column instead of centered text. */
    text-wrap: balance;
}

/* --- Night/timer/evidence cluster: now plain flex sections living
   inside #top-hud (see its own comment) rather than a separately
   positioned/backgrounded panel. --- */
.night-title-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(3px, 1vw, 6px);
}

.night-icon {
    width: clamp(11px, 3vw, 15px);
    height: clamp(11px, 3vw, 15px);
    flex-shrink: 0;
}

.night-icon svg {
    width: 100%;
    height: 100%;
    display: block;
}

.night-icon-small {
    width: clamp(10px, 2.6vw, 13px);
    height: clamp(10px, 2.6vw, 13px);
}

.night-title {
    font-family: var(--font-ui);
    font-size: clamp(11px, 3vw, 15px);
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--hud-text);
}

/* Circular nodes connected by a thin line running behind them — the
   pip count itself stays fully dynamic (see main.js's CONFIG.NIGHT.
   TOTAL_NIGHTS loop that builds these; nothing here assumes a fixed
   number of nights). */
.night-pips {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(6px, 2vw, 12px);
}

.night-pips::before {
    content: '';
    position: absolute;
    left: 3px;
    right: 3px;
    top: 50%;
    height: 1px;
    background: rgba(215, 154, 43, 0.4);
    transform: translateY(-50%);
    z-index: 0;
}

.night-pips .pip {
    position: relative;
    z-index: 1;
    width: clamp(7px, 2vw, 11px);
    height: clamp(7px, 2vw, 11px);
    border-radius: 50%;
    background: #1b140c;
    border: 1.5px solid rgba(215, 154, 43, 0.5);
    box-sizing: border-box;
}

.night-pips .pip.done {
    background: var(--amber);
    border-color: var(--amber);
}

.night-pips .pip.current {
    background: var(--amber-bright);
    border-color: var(--amber-bright);
    box-shadow: 0 0 6px var(--amber-bright);
}

.night-timer-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(4px, 1.2vw, 6px);
}

.night-timer {
    font-family: var(--font-ui);
    font-size: clamp(12px, 3.4vw, 16px);
    font-weight: 700;
    color: var(--amber-bright);
    font-variant-numeric: tabular-nums;
}

.night-timer-row.final-stretch .night-timer {
    color: #ff8a70;
    animation: pulse-bar 0.8s ease-in-out infinite;
}

/* --- Evidence HUD row (secondary exploration system, see EvidenceSystem.js) ---
   Tappable in-game — opens the same BIGFOOT INVESTIGATION board the
   start menu uses (see main.js's openEvidenceBoard()), pausing
   gameplay while it's open. `pointer-events: auto` overrides
   #night-hud's own `pointer-events: none` (that parent is otherwise
   just a passive readout, not a tap target) — only this one row opts
   back in. */
.evidence-hud-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    margin-top: 1px;
    pointer-events: auto;
    cursor: pointer;
    padding: 2px 4px;
    border-radius: 6px;
    transition: background 0.15s ease;
}

.evidence-hud-row:active {
    background: rgba(255, 255, 255, 0.12);
}

/* Same-specificity tie against the plain `.evidence-hud-row { display:
   flex }` rule above was landing on whichever happened to come later in
   the file — the generic `.hidden` utility was silently losing that tie
   here specifically. This combined selector wins outright regardless of
   source order. Needed now that the tutorial hides this row for its
   whole duration (see main.js's startTutorial()/'BEGIN NIGHT 1' handler
   — "remove evidence during tutorial"). */
.evidence-hud-row.hidden {
    display: none;
}

.evidence-hud-text {
    font-family: var(--font-ui);
    font-size: clamp(9.5px, 2.6vw, 11px);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #cfe8ff;
    display: inline-block;
}

/* Fired once per actual find (see popEvidenceCounter() in main.js,
   called from evidenceSystem.onFound) -- not on every generic HUD
   refresh, just the moment the count actually goes up. */
@keyframes evidence-pop {
    0% { transform: scale(1); }
    40% { transform: scale(1.4); color: #fff2b0; text-shadow: 0 0 8px rgba(255, 220, 120, 0.85); }
    100% { transform: scale(1); }
}

.evidence-hud-text.found-pop {
    animation: evidence-pop 0.5s ease-out;
}

/* --- Evidence-found toast — fixed HUD popup, see index.html's own comment --- */
#evidence-toast {
    position: absolute;
    top: 18%;
    left: 50%;
    transform: translate(-50%, -12px);
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: var(--panel-radius);
    box-shadow: var(--panel-shadow);
    overflow: hidden;
    padding: 10px 22px;
    text-align: center;
    pointer-events: none;
    z-index: 25;
    opacity: 0;
    transition: opacity 0.3s ease, transform 0.3s ease;
    white-space: nowrap;
}

#evidence-toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
}

.evidence-toast-kicker {
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--text-dim);
}

.evidence-toast-name {
    font-family: var(--font-ui);
    font-size: 16px;
    font-weight: 700;
    color: var(--text-main);
    margin-top: 2px;
}

.evidence-toast-rarity {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    margin-top: 2px;
}

.evidence-toast-rarity.common { color: #8fae7a; }
.evidence-toast-rarity.uncommon { color: #7ab7d8; }
.evidence-toast-rarity.rare { color: #e8c14a; }

/* A rare find gets a bigger, more excited presentation — direct design
   intent ("rare evidence should feel exciting to discover"), not just a
   different rarity color on the same-sized card. */
#evidence-toast.rare {
    padding: 14px 28px;
    border-color: #e8c14a;
    box-shadow: 0 0 24px rgba(232, 193, 74, 0.45), var(--panel-shadow);
}

#evidence-toast.rare .evidence-toast-kicker {
    color: #e8c14a;
    font-size: 12px;
}

#evidence-toast.rare .evidence-toast-name {
    font-size: 19px;
}

/* One-shot diagonal shine sweep across a rare card — the "legendary
   drop" flourish, layered on top of the bigger card/glow above rather
   than replacing it. ::before (not a real element) so it needs no
   markup change; skewed + wide so it reads as a single clean streak of
   light rather than a moving rectangle. Runs once per .show (the
   keyframe itself, not `infinite`) — a rare find should feel like an
   event, not a card that keeps glinting at you the whole 4.2s it's up. */
#evidence-toast.rare::before {
    content: '';
    position: absolute;
    top: -20%;
    left: -60%;
    width: 40%;
    height: 140%;
    background: linear-gradient(100deg, rgba(255, 255, 255, 0) 0%, rgba(255, 244, 214, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
    transform: skewX(-20deg);
    pointer-events: none;
}

#evidence-toast.rare.show::before {
    animation: evidence-toast-shine 1.1s ease-out 0.15s 1;
}

@keyframes evidence-toast-shine {
    from { left: -60%; }
    to   { left: 130%; }
}

/* --- Light meter: now a plain flex section living inside #top-hud (see
   its own comment) rather than a separately positioned/backgrounded
   panel — the bar's own left padding clears the art's lantern graphic
   now, not a per-section one. --- */
.lantern-row {
    display: flex;
    align-items: center;
    gap: clamp(4px, 1.2vw, 7px);
}

.lantern-label {
    font-family: var(--font-ui);
    font-size: clamp(10px, 2.8vw, 12px);
    letter-spacing: 0.08em;
    color: var(--hud-text-muted);
    font-weight: 600;
}

/* Large dynamic percentage, its own row below the icon+label row rather
   than stacked tight against the label — direct spec ("large dynamic
   percentage directly below"). */
.lantern-pct {
    font-family: var(--font-ui);
    font-size: clamp(20px, 6.5vw, 32px);
    font-weight: 700;
    color: var(--amber-bright);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.lantern-bar-track {
    width: 100%;
    height: clamp(6px, 1.6vw, 9px);
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.4);
    overflow: hidden;
}

.lantern-bar-fill {
    height: 100%;
    width: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, #7ed957, #ffd166);
    transition: width 0.25s ease, background 0.25s ease;
}

.lantern-bar-fill.tier-warning {
    background: linear-gradient(90deg, #ffd166, #ff9d4d);
}

.lantern-bar-fill.tier-critical {
    background: linear-gradient(90deg, #ff7a4d, #e0473a);
    animation: pulse-bar 1s ease-in-out infinite;
}

@keyframes pulse-bar {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

.lantern-sub {
    font-family: var(--font-ui);
    font-size: clamp(8.5px, 2.4vw, 10.5px);
    font-weight: 600;
    line-height: 1.25;
    color: var(--hud-text-muted);
}

.lantern-sub.critical {
    color: #ff8a70;
    font-weight: 600;
}

/* --- Bottom action bar — DROP/REFUEL/BREW/HEAL/SLING live in one
   permanent horizontal row, never hidden — see index.html's own comment
   on #action-bar for the full history of why (a flex COLUMN of
   show/hide buttons let one button's screen position shift depending
   on which siblings happened to be hidden at that moment — direct user
   report). CHOP/BREAK are NOT here (see #chop-btn-anchor in
   index.html) — they float over their own tree/rock, unchanged. A
   horizontal `display: flex` row doesn't have the show/hide-shift
   problem the old column did: nothing in this bar is ever removed from
   layout via `display: none` (see .action-btn.disabled below — it
   dims, it doesn't disappear), so there's nothing here that CAN close a
   gap around a hidden sibling. --- */
/* Real bordered bar (dark gradient/gold border/glow) matching the top
   HUD's palette. Now truly centered (left:50%/translateX(-50%), same
   technique as #top-hud, % not vw for the same letterboxed-stage reason
   documented on #top-hud above) — the old asymmetric left:66px/right:0
   was only there to clear #evidence-bar-btn's old standalone corner
   circle; now that EVIDENCE lives inside this bar (direct request), that
   clearance requirement is gone, so centering it was safe to do (also
   direct request: "center it all"). gap/padding/circle sizes are all
   clamp()-based. Direct report with a screenshot: at a real narrow phone
   width, the last button (REFUEL) was visibly clipped past the bar's own
   right edge — this bar grew from 6 buttons (the configuration this was
   originally verified against) to the current 7 (STORAGE added later)
   without re-checking the narrowest supported width; the 7 buttons' own
   minimum sizes (SLING's own larger clamp included) summed to more than
   the bar's available content width at 320px. Circle minimums (base and
   SLING's own larger one below) and the bar's own width % were all
   nudged down/up respectively to comfortably fit 7 at 320px — verified
   live afterward; only affects the narrowest viewports, since these are
   just the clamp()'s floor, not the size actually used above ~340-360px. */
#action-bar {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: min(97%, 640px);
    bottom: max(10px, env(safe-area-inset-bottom));
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-evenly;
    gap: clamp(2px, 2vw, 16px);
    padding: clamp(6px, 2vw, 10px) clamp(4px, 2.4vw, 14px);
    background: linear-gradient(180deg, rgba(29, 22, 13, 0.97), rgba(13, 10, 6, 0.98));
    border: 1px solid var(--hud-border);
    border-radius: 999px;
    box-shadow:
        inset 0 1px 0 rgba(255, 210, 110, 0.18),
        inset 0 -1px 0 rgba(0, 0, 0, 0.55),
        0 0 10px rgba(208, 142, 35, 0.16);
    z-index: 12;
}

/* Fixed HUD compass — see index.html's own comment on #camp-compass for
   the full "why." Sits directly above #action-bar in the bottom-left
   corner (genre-familiar minimap/compass spot) rather than beside it —
   #action-bar's own 94%-width pill leaves no real margin free on a phone
   screen to fit a second badge next to it at the same height. The ring
   image is pure static framing (never rotates — it has N/E/S/W painted
   on it); #camp-compass-needle is the only part that turns, via a CSS
   transform main.js's updateCampCompass() sets every frame, same
   projection technique the old tutorial-only pointer arrow already used. */
#camp-compass {
    position: absolute;
    left: max(10px, env(safe-area-inset-left));
    bottom: calc(100px + max(10px, env(safe-area-inset-bottom)));
    width: clamp(56px, 15vw, 76px);
    height: clamp(56px, 15vw, 76px);
    z-index: 12;
}

#camp-compass.hidden {
    display: none;
}

#camp-compass-ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5));
}

/* Rotated via main.js — always centered on the ring regardless of angle. */
#camp-compass-needle {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transform-origin: center center;
}

#camp-compass-needle svg {
    width: 42%;
    height: 42%;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}

/* The button itself is a plain (non-circular) tap target sized to fit
   the circle + its caption — `.action-btn-circle` is what actually
   looks round. Keeping them separate means the caption sits in normal
   flow below the circle instead of needing absolute positioning that'd
   risk overlapping the next button down the stack. */
.action-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    background: none;
    border: none;
    padding: 0;
    font-family: var(--font-ui);
    color: var(--text-main);
    cursor: pointer;
    opacity: 1;
    transform: scale(1);
    transition: opacity 0.2s ease, transform 0.2s ease;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
}

/* The 5 buttons living in #action-bar (DROP/REFUEL/BREW/HEAL/SLING) are
   permanently visible (direct user request: "they should always live
   there... make them inactive if you don't have the resources for
   it") and never get this class — they toggle `.disabled` below
   instead. CHOP/BREAK are the one remaining `.action-btn.hidden` user:
   they're not in #action-bar (direct user correction — they stay
   floating over their own tree/rock, see #chop-btn-anchor in
   index.html), and genuinely disappear entirely until a targeted
   tree/rock is actually within reach, same as they always did. */
.action-btn.hidden {
    display: none;
}

.action-btn:active {
    transform: scale(0.92);
}

.action-btn.disabled {
    opacity: 0.5;
    /* Still tappable on purpose — refuel() itself reports *why* it can't
       act (not enough wood) via onRefuelFail, which needs the click to
       actually reach the handler. Only silently no-ops when already at
       a self-evident cap (full lantern). */
}

/* Per an outside playtest pass: whether an action button could actually
   be used wasn't obvious enough — the player had to infer it from raw
   resource counts, read mid-panic. A visible, non-disabled button now
   gets a warm, gently pulsing glow (louder than just "not dimmed"); a
   disabled one is desaturated on top of the existing opacity dip, so
   the two states read as clearly different at a glance, not just
   slightly different shades of the same button. Glow only (no
   border-color change) so each button's own accent border — SLING's
   reddish tint, etc. — stays intact underneath it. */
.action-btn:not(.disabled) .action-btn-circle {
    animation: action-btn-ready-pulse 2s ease-in-out infinite;
}

@keyframes action-btn-ready-pulse {
    0%, 100% { box-shadow: var(--panel-shadow), 0 0 6px rgba(242, 200, 121, 0.35); }
    50% { box-shadow: var(--panel-shadow), 0 0 14px rgba(242, 200, 121, 0.7); }
}

.action-btn.disabled .action-btn-circle {
    filter: grayscale(0.85);
}

/* Direct tester feedback (relayed): "the tutorial should have all of the
   controls available that would be in a normal game; if they are not
   needed for the tutorial, grey them out in a way that will
   differentiate them from just being unavailable, so the user knows the
   topic is not covered to prevent confusion." A real bug behind this:
   EVIDENCE used to be hidden entirely for the whole tutorial (only ever
   `.remove('hidden')`d once real Night 1 began) — a player who skipped
   out never saw it exist at all until suddenly it did, and any tester
   who noticed it "disappear/appear" mid-run read that as broken. Deliberately
   NOT the same look as `.disabled` above (that means "you don't meet a
   real, temporary requirement right now" — walking closer or gathering
   more would fix it) — this means "this concept isn't part of the
   tutorial yet," a different kind of unavailable that greying alone
   doesn't communicate. Diagonal hazard-stripe overlay (a genuinely
   different visual pattern, not just a darker shade of the same dimming)
   plus a small clock badge reading "later" at a glance. See main.js's
   startTutorial()/its evidence click handlers for where this gets
   applied and what tapping it now does instead of hiding the button. */
.action-btn.tutorial-locked .action-btn-circle {
    filter: grayscale(1) brightness(0.8);
    animation: none !important; /* overrides the ready-pulse glow — this is never "ready" during tutorial */
}

.action-btn.tutorial-locked .action-btn-circle::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: repeating-linear-gradient(
        135deg,
        rgba(255, 210, 110, 0.16) 0px, rgba(255, 210, 110, 0.16) 4px,
        transparent 4px, transparent 9px
    );
    pointer-events: none;
}

.action-btn.tutorial-locked .action-btn-circle::before {
    content: '⏳';
    position: absolute;
    bottom: -3px;
    right: -3px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: rgba(20, 15, 10, 0.92);
    border: 1px solid rgba(255, 210, 110, 0.5);
    font-size: 9px;
    line-height: 15px;
    text-align: center;
    z-index: 1;
}

.action-btn-circle {
    position: relative;
    width: clamp(32px, 10vw, 52px);
    height: clamp(32px, 10vw, 52px);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    box-shadow: var(--panel-shadow);
}

/* SLING: still the primary combat action and still slightly bigger than
   the 4 utility buttons (not dramatically — direct spec), but its accent
   now leans into the shared HUD gold rather than a one-off red, and
   there's a distinct stronger-gold state on top of the generic ready-
   pulse every other enabled button gets: .threat-near (toggled in
   updateThrowStoneButton() off bigfoot.proximityIntensity) for "Bigfoot
   is nearby," independent of whether SLING itself is enabled — a low
   ammo count during an active threat is exactly when this glow matters
   most. Disabled still falls through to the shared grayscale, so a
   muted state stays muted regardless of proximity. */
.action-btn-stone .action-btn-circle {
    width: clamp(36px, 11vw, 58px);
    height: clamp(36px, 11vw, 58px);
    border-color: rgba(215, 154, 43, 0.55);
}

.action-btn-stone.threat-near:not(.disabled) .action-btn-circle {
    border-color: var(--hud-gold);
    box-shadow: var(--panel-shadow), 0 0 10px rgba(215, 154, 43, 0.6);
}

/* Small punchy scale-up while drawing the sling back, on top of the
   shared :active press effect. */
.action-btn-stone.charging .action-btn-circle {
    transform: scale(1.08);
    transition: transform 0.1s ease;
}

/* Persistent "pressed in" look while the sling is out (see equipSling()/
   unequipSling() in main.js) — distinct from .charging's own transient
   pull-scale above, so the button still reads as "currently equipped,
   tap again to put it away" even at rest between shots. */
.action-btn-stone.equipped .action-btn-circle {
    transform: scale(0.94);
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5), var(--panel-shadow);
}

/* SLING no longer has its own dock — it's the last button in #action-bar
   (see index.html's comment on it), which naturally has open canvas to
   its right and above for the pull-back drag to expand into
   (SLING_MAX_PULL_PX, see main.js), same as its old standalone corner
   spot did. No aiming exists (the throw always auto-targets Bigfoot's
   live position — see fireSlingshot()), so the drag's actual DIRECTION
   is purely cosmetic; only its distance matters, so a drag that happens
   to go off toward the screen edge clipping the pullback overlay is a
   cosmetic edge case, not a functional one. */

/* --- Slingshot pull-back visual ---------------------------------------
   Fixed-position (viewport coordinates, matching getBoundingClientRect()
   directly — see main.js) 240x240 overlay, centered on #throw-stone-btn
   each time a drag starts. The fork (#sling-fork-img) is static, real
   art (assets/ui/sling_fork.webp, the user's own drawing) rather than
   drawn lines; the bands and rock track the drag in real time via
   main.js setting the rock <image>'s x/y and the two band lines' x2/y2
   every pointermove, clamped to SLING_MAX_PULL_PX. On release, main.js
   adds .snapping (enabling the transitions below) and resets x/y/x2/y2
   back to center, so the rock visibly snaps back through the fork
   before the overlay hides — the actual projectile launches from
   fireSlingshot()'s own throw() call, independent of this purely-cosmetic
   snap animation. pointer-events stays none at rest (nothing to grab
   before the sling is even out) but flips to auto once .active — direct
   playtester report: once equipped, players reached for the visible
   pouch itself to pull it back, not back down to the SLING button a
   second time; onSlingPointerDown/Move/Up in main.js are now attached to
   this element too; see equipSling() for why the button still stays the
   ONLY thing that can toggle equip/unequip. */
#sling-pullback {
    position: fixed;
    width: 180px;
    height: 180px;
    left: 0;
    top: 0;
    transform: translate(-50%, -50%);
    pointer-events: none;
    touch-action: none;
    opacity: 0;
    z-index: 13;
    /* left/top transitions cover the one-time jump to screen center when
       charging starts (see main.js's pointerdown handler on
       #throw-stone-btn) — safe to animate since left/top are only ever
       set once per charge session, at pointerdown; the live per-frame
       drag tracking during pointermove only ever touches the rock/band
       SVG attributes, never these properties, so this never adds lag to
       the actual drag. Slightly longer than the small outward-nudge
       this used to cover (SLING now jumps from wherever it sits in the
       bottom bar to true center, a much bigger distance) — 0.18s read
       as an instant teleport at that distance; 0.24s is long enough to
       actually read as a slide. */
    transition: opacity 0.15s ease, left 0.24s ease-out, top 0.24s ease-out;
}

#sling-pullback.active {
    opacity: 1;
    pointer-events: auto;
}

#sling-pullback svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* Positioned/sized so the source art's prong tips (measured at
   (245,18) and (938,37) of the 1173x1341 source — see the comment in
   index.html) land at the band anchor points below, (54,21)/(127.5,22.5)
   in the 180x180 viewBox (the original (72,28)/(170,30) measurement
   scaled by 0.75 for this session's shrink). */
#sling-fork-img {
    position: absolute;
    left: 27.7px;
    top: 18.75px;
    width: 124.5px;
    height: auto;
    pointer-events: none;
    user-select: none;
}

/* Sits above the ring (which spans roughly y=15-165 in this 180-tall
   box, r=75 centered at 90,90) so it reads clearly instead of
   fighting the rock/bands for the same visual space. Color is set
   per-frame in JS to match the ring's own amber-to-red shift, so the
   label and the ring always agree instead of being two disconnected
   readouts of the same value. */
#sling-power-label {
    position: absolute;
    left: 0;
    top: 0px;
    width: 100%;
    text-align: center;
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.85);
    pointer-events: none;
}

.sling-band {
    stroke: #4a3220;
    stroke-width: 2.25;
    stroke-linecap: round;
    opacity: 0.85;
}

/* Session 121: charge-power ring — empty (full stroke-dashoffset) at
   rest, fills clockwise from straight up as the player pulls back
   (main.js sets stroke-dashoffset/stroke every pointermove, from
   SLING_RING_CIRCUMFERENCE at 0% down to 0 at 100%). Color is also
   JS-driven (amber at a light pull, hot red-orange at full draw) — the
   `stroke` set here is just the resting/pre-drag fallback, immediately
   overwritten once a drag actually starts. */
#sling-power-ring {
    fill: none;
    stroke: var(--amber-bright, #f2c879);
    stroke-width: 5.25;
    stroke-linecap: round;
    opacity: 0.9;
    transform: rotate(-90deg);
    /* Was 120px 120px — the old 240x240 box's own center. Session 142
       shrunk the viewBox to 180x180 (center 90,90); left stale this
       would have rotated the ring's fill around the wrong point. */
    transform-origin: 90px 90px;
    transition: stroke-dashoffset 0.03s linear;
}

#sling-rock {
    filter: drop-shadow(0 0 3px rgba(255, 179, 71, 0.65));
}

/* Only turned on right at release, so the rock/bands track the finger
   with zero lag while actively dragging, then spring back with this
   "overshoot" ease for one snap. */
#sling-pullback.snapping #sling-rock,
#sling-pullback.snapping .sling-band {
    transition: x 0.22s cubic-bezier(0.36, 1.75, 0.6, 1), y 0.22s cubic-bezier(0.36, 1.75, 0.6, 1),
        x2 0.22s cubic-bezier(0.36, 1.75, 0.6, 1), y2 0.22s cubic-bezier(0.36, 1.75, 0.6, 1);
}

.action-btn-drop .action-btn-circle {
    border-color: rgba(160, 160, 150, 0.4);
}

/* --- DROP submenu — pick which resource type to drop ------------------------
   #drop-btn-wrap is a plain flex item in #action-bar now, same as every
   other button — just `position: relative` so #drop-submenu's own
   absolute positioning has a real containing block to anchor to
   (DROP's actual position in the row is whatever the flex layout gives
   it, not fixed here). Pops UPWARD, not sideways — with 7 buttons
   packed into one row now, popping left/right risked overlapping
   whichever button happens to be next to DROP; straight up into open
   canvas space never does, regardless of where in the row DROP sits. */
#drop-btn-wrap {
    position: relative;
}

#drop-submenu {
    position: absolute;
    bottom: calc(100% + 12px);
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: row;
    gap: 8px;
    z-index: 13;
}

#drop-submenu.hidden {
    display: none;
}

.drop-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    background: none;
    border: none;
    padding: 0;
    font-family: var(--font-ui);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    transition: transform 0.15s ease;
}

.drop-option.hidden {
    /* Only the types actually held are worth showing — see
       updateDropButton() in main.js. */
    display: none;
}

.drop-option:active {
    transform: scale(0.9);
}

.drop-option-circle {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    box-shadow: var(--panel-shadow);
}

.drop-option-circle svg, .drop-option-circle img {
    width: 20px;
    height: 20px;
    object-fit: contain;
}

.drop-option-count {
    font-size: 11px;
    color: var(--text-main);
}

/* --- STORAGE submenu — one-tap "store everything" + per-type retrieve -------
   Same containing-block/pop-upward trick as #drop-submenu above (see its
   own comment), just a column instead of a single row: STORE ALL sits on
   its own line on top, the per-type retrieve options form their own row
   underneath, so the two very different actions ("empty my whole pack"
   vs. "take some of this one thing back") don't visually blur together
   into one homogeneous button row. */
.action-btn-storage .action-btn-circle {
    border-color: rgba(180, 130, 70, 0.4);
}

/* No longer `position: relative` — see #storage-submenu's own comment
   below on why its containing block moved off this wrap entirely. */
#storage-btn-wrap {
}

/* Direct bug report: "the max take display is broken and goes off the
   left side of the screen on a standard phone size." Root cause: this
   used to be `position: absolute` inside #storage-btn-wrap (STORAGE's
   own button), centered on THAT button's position via `left: 50%;
   transform: translateX(-50%)` — but STORAGE is the 2nd of 7 buttons in
   #action-bar, sitting close to the left edge, so centering a fairly
   wide panel (a 5-icon row plus the amount stepper) on that off-center
   point pushed a large chunk of it past the screen's left edge entirely.
   #storage-btn-wrap no longer has its own `position`, so this absolute
   box now falls through to the next positioned ancestor up the tree —
   #action-bar itself, which is ALREADY horizontally centered on the
   game stage (see its own `left: 50%; transform: translateX(-50%)`) —
   so centering within IT centers on the screen/stage instead of on one
   off-center button. Reads slightly less like it's popping directly out
   of the STORAGE button now, but every part of it stays on-screen on
   any phone width, which the old behavior never guaranteed. */
/* Direct follow-up report: "just saying 'storage' and making the button
   available at camp is confusing and not natural user interface" — this
   used to be bare text/icons/buttons floating directly over open ground
   with nothing framing them, unlike every other menu in the game
   (.overlay-panel, the tutorial banner, etc.), which all sit inside a
   real dark card. Wrapped in the same panel language (var(--panel-bg)/
   var(--panel-border)/var(--panel-shadow), already used everywhere else)
   so it reads as an actual interface popping up, not loose HUD elements
   drifting over the 3D scene. STORE ALL and the amount stepper keep
   their own smaller pill backgrounds nested inside this — that's fine,
   the same "button-on-a-card" layering every other panel in this game
   already uses (e.g. .records-close inside .records-panel). */
#storage-submenu {
    position: absolute;
    bottom: calc(100% + 12px);
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    z-index: 13;
    max-width: min(92vw, 320px);
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 16px;
    box-shadow: var(--panel-shadow);
    padding: 14px 16px;
}

#storage-submenu.hidden {
    display: none;
}

.storage-store-all {
    display: flex;
    align-items: center;
    gap: 6px;
    background: var(--panel-bg);
    border: 1px solid rgba(180, 130, 70, 0.5);
    border-radius: 999px;
    box-shadow: var(--panel-shadow);
    padding: 8px 16px;
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--text-main);
    white-space: nowrap;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    transition: transform 0.15s ease;
}

.storage-store-all:active {
    transform: scale(0.94);
}

.storage-store-all.hidden {
    display: none;
}

.storage-store-all-icon {
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.storage-store-all-icon svg {
    width: 100%;
    height: 100%;
}

/* Direct follow-up request: "give the storage the ability to pick what
   you want to store and the amount" — #storage-store-row mirrors
   #storage-retrieve-row exactly (same layout rule shared below), just a
   second instance sourced from the backpack instead of camp storage. */
#storage-store-row, #storage-retrieve-row {
    display: flex;
    flex-direction: row;
    gap: 8px;
}

/* Small uppercase header above each row — see index.html's own comment
   on why these exist now that there are two near-identical icon rows
   stacked (STORE vs RETRIEVE) where there used to be only one. Same
   language as .lantern-label/other small HUD captions elsewhere. */
.storage-section-label {
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--text-dim);
    text-transform: uppercase;
}

/* Direct report: "store retrieve button doesnt look obvious that it is
   either/or" — see main.js's updateStorageButton() comment. Hides right
   alongside its own row now instead of always rendering even when empty. */
.storage-section-label.hidden {
    display: none;
}

/* Only added when STORE is genuinely showing directly above RETRIEVE
   (see updateStorageButton()) — a real visual break between the two
   either/or sections, not just relying on the row gap to imply it. */
.storage-section-label.with-divider {
    border-top: 1px solid rgba(255, 216, 145, 0.12);
    padding-top: 8px;
    margin-top: 2px;
}

/* Identical shape to .drop-option — same circle/count layout, a distinct
   class name only because these fire selectTransferType(), not
   dropOneOf(). Shared by both the STORE and RETRIEVE rows above (see
   data-direction on each button in index.html) — same visual language,
   main.js tells them apart by that attribute, not by a second class. */
.storage-transfer-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    background: none;
    border: none;
    padding: 0;
    font-family: var(--font-ui);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    transition: transform 0.15s ease;
}

.storage-transfer-option:active {
    transform: scale(0.9);
}

.storage-transfer-option.hidden {
    /* Only types actually held (in the backpack for STORE, in camp
       storage for RETRIEVE) are worth showing — see updateStorageButton()
       in main.js. */
    display: none;
}

/* Which type/direction #storage-amount-stepper below is currently
   picking an amount for — see main.js's selectTransferType(). Highlights
   the SAME circle .drop-option-circle already draws, just with a gold
   ring/glow on top, rather than a second visual language for "selected". */
.storage-transfer-option.selected .drop-option-circle {
    border-color: var(--hud-gold, #d09e2b);
    box-shadow: var(--panel-shadow), 0 0 8px rgba(215, 154, 43, 0.7);
}

/* Amount picker for whichever type is currently .selected above — see
   index.html's own comment on #storage-amount-stepper. Sits on its own
   row under the retrieve icons rather than inline with them, since a
   5-wide icon row plus a 5-button stepper side by side would never fit
   the width this submenu already keeps to. */
#storage-amount-stepper {
    display: flex;
    align-items: center;
    gap: 6px;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 999px;
    box-shadow: var(--panel-shadow);
    padding: 6px 10px;
}

#storage-amount-stepper.hidden {
    display: none;
}

#storage-stepper-minus, #storage-stepper-plus {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: rgba(0, 0, 0, 0.35);
    color: var(--text-main);
    font-size: 16px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    padding: 0;
    transition: transform 0.12s ease, opacity 0.12s ease;
}

#storage-stepper-minus:active, #storage-stepper-plus:active {
    transform: scale(0.9);
}

/* Set (not just visually dimmed) when the amount is already at its 1 or
   MAX bound — see main.js's changeStepperAmount() — so a spammed tap
   past the bound is a real no-op, not just a button that LOOKS disabled
   while still doing something. */
#storage-stepper-minus:disabled, #storage-stepper-plus:disabled {
    opacity: 0.35;
    cursor: default;
}

#storage-stepper-amount {
    min-width: 22px;
    text-align: center;
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 700;
    color: var(--amber-bright);
}

#storage-stepper-max, #storage-stepper-take {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--text-main);
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid var(--panel-border);
    border-radius: 999px;
    padding: 6px 10px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    white-space: nowrap;
}

/* TAKE is the one real confirm action here — given the shared gold
   button treatment every other confirm action in this game gets
   (CONTINUE, PLAY AGAIN, etc.) rather than the muted neutral MAX/−/+
   controls around it, so it's unambiguous which button actually commits. */
#storage-stepper-take {
    color: #1a1108;
    background: linear-gradient(180deg, var(--amber-bright), var(--amber));
    border: none;
}

#storage-stepper-max:active, #storage-stepper-take:active {
    transform: scale(0.94);
}

/* Session 118: .action-btn-pick/.action-btn-harvest removed along with
   their buttons — see index.html's comment above #pot-btn. */

/* Explicit 56px, not inherited from the shared .action-btn-circle
   default — CHOP/BREAK float independently over their own tree/rock
   (see #chop-btn-anchor in index.html), not in #action-bar, so they
   shouldn't be affected if that bar's own sizing ever changes again. */
.action-btn-chop .action-btn-circle {
    border-color: rgba(180, 130, 70, 0.4);
    width: 56px;
    height: 56px;
}

.action-btn-break .action-btn-circle {
    border-color: rgba(154, 154, 148, 0.45);
    width: 56px;
    height: 56px;
}

.action-btn-pot .action-btn-circle {
    border-color: rgba(226, 162, 60, 0.45);
}

.action-btn-heal .action-btn-circle {
    border-color: rgba(242, 200, 121, 0.5);
}

/* #evidence-bar-btn takes over the compass's old bottom-left circle
   position/sizing exactly (see the rule below) rather than reusing
   .action-btn-circle's own 56px default — kept distinct since this
   button now lives outside #action-bar entirely, not sized relative to
   its former neighbors there. */

/* Session 120: positioning shell for CHOP/BREAK's world-tracked
   buttons — main.js repositions this via CSS transform every frame
   (positionWorldButton(), same world-to-screen projection technique as
   FloatingTextManager), keyed to (0,0) by left/top here exactly like
   .floating-text/.floating-icon already do. `display: inline-block`
   sizes the shell to just its one button child (not the full viewport
   width a bare block-level div defaults to), so the transform's own
   `translate(-50%, -100%)` centers on the button itself. Deliberately a
   separate element from .action-btn — that button already owns a CSS
   `transform` for its own :active press-scale feedback, and a single
   element can't cleanly carry two independent transforms (one driven by
   JS every frame, one driven by :active) without one clobbering the
   other. */
.gather-target-anchor {
    position: absolute;
    left: 0;
    top: 0;
    display: inline-block;
    z-index: 14;
}

/* A dropped pile's own count, hovering over it in-world — see
   ResourceManager's dropNode()/main.js's updateResourcePileLabel(). Same
   visual language as .action-btn-badge (dark pill, amber number) just
   bigger and free-standing rather than pinned to a button's corner, since
   this has no button to attach to. */
#resource-pile-label {
    display: inline-block;
    min-width: 28px;
    padding: 4px 9px;
    border-radius: 999px;
    background: rgba(10, 8, 6, 0.82);
    border: 1px solid var(--panel-border);
    box-shadow: var(--panel-shadow);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 700;
    text-align: center;
    color: var(--amber-bright);
    white-space: nowrap;
}

/* Direct player report: a small amber-bordered pill visible in the
   top-left corner of the screen at all times, even on the start menu.
   Root cause — #resource-pile-anchor (appended straight to <body> by
   main.js, same as #bigfoot-health-anchor below, so positionWorldButton()
   can place it in plain viewport coordinates) toggles a `.hidden` class
   via JS (see updateResourcePileLabel()) whenever no drop-pile is
   currently active, but unlike #bigfoot-health-anchor just below, no
   `.hidden` rule for it ever existed here — the class was doing nothing,
   so the anchor (and its #resource-pile-label child) sat visible at its
   un-positioned default (left:0, top:0 from .gather-target-anchor above)
   from page load until the very first pile was ever created. */
#resource-pile-anchor.hidden {
    display: none;
}

/* --- Bigfoot health bar — see index.html's own comment ----------------------
   #bigfoot-health-anchor is the JS-repositioned shell (same split as
   .gather-target-anchor above); #bigfoot-health-bar/#bigfoot-health-
   segments hold the actual visual, untouched by the per-frame transform. */
#bigfoot-health-anchor.hidden {
    display: none;
}

#bigfoot-health-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3px;
    background: rgba(10, 8, 6, 0.55);
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    box-shadow: var(--panel-shadow);
}

#bigfoot-health-segments {
    display: flex;
    gap: 3px;
}

.bigfoot-health-segment {
    width: 20px;
    height: 7px;
    border-radius: 2px;
    background: rgba(255, 255, 255, 0.12);
    transition: background 0.15s ease;
}

/* Landed hits count DOWN from full — see updateBigfootHealthBar() in
   main.js — so a "full" (not-yet-hit) segment is the warm, ready-glow
   amber the rest of this HUD already uses for "available/healthy," and
   a spent one drops to the same dim gray every other emptied HUD state
   uses (.res-card.hidden's sibling states, etc.), not a jarring red —
   this is a threat's remaining composure, not the player's own health. */
.bigfoot-health-segment.full {
    background: var(--amber-bright, #f2c879);
    box-shadow: 0 0 4px rgba(242, 200, 121, 0.6);
}

/* --- Gather progress bars (CHOP/BREAK/BREW) --------------------------------
   Direct request: "a visual bar similar to the hit bar on bigfoot" for
   gathering progress, then a direct follow-up redesign once the first
   pass (discrete segments, hovering above like the button did) was
   live: continuous horizontal fill instead of segments, and positioned
   BELOW the tree/boulder/pot instead of above — see CHOP_PROGRESS_ANCHOR
   etc. in main.js for the low "under the object" world height each now
   uses (was the same height as the CHOP/BREAK button itself, which
   reads as above/over it). Each bar is its own anchor now (was nested
   inside #chop-btn-anchor/#break-btn-anchor sharing the button's own
   position) specifically so it can sit at a different world height than
   the button without fighting over one shared transform. */
.gather-progress-bar {
    width: 46px;
    height: 8px;
    border-radius: 4px;
    background: rgba(10, 8, 6, 0.55);
    border: 1px solid var(--panel-border);
    box-shadow: var(--panel-shadow);
    overflow: hidden;
}

.gather-progress-stack {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.gather-progress-fill {
    height: 100%;
    width: 0%;
    border-radius: 4px;
    background: var(--amber-bright, #f2c879);
    box-shadow: 0 0 4px rgba(242, 200, 121, 0.6);
    transition: width 0.15s ease;
}

/* BREW's two ingredients get their own tint per bar (matching the BREW
   button's own herb/resin badge colors) instead of the shared amber
   every other progress bar here uses, so which requirement is satisfied
   stays legible without needing to read a number. */
.gather-progress-fill.herb {
    background: #8faf74;
    box-shadow: 0 0 4px rgba(143, 175, 116, 0.6);
}

.gather-progress-fill.resin {
    background: #d99a4e;
    box-shadow: 0 0 4px rgba(217, 154, 78, 0.6);
}

/* Direct player report, with a screenshot: a small dark rectangle
   showing in a screen corner, unpositioned — the exact same class of
   bug Session 173 already found and fixed for #resource-pile-anchor
   (this codebase has no single global `.hidden { display: none }` —
   every hideable element needs its own scoped rule, and these three new
   anchors were missed when they were first added). Each needs this
   explicit rule; the shared `.gather-target-anchor` base class alone
   does nothing to hide them. */
#chop-progress-anchor.hidden,
#break-progress-anchor.hidden,
#brew-progress-anchor.hidden {
    display: none;
}

.action-btn-icon {
    width: 30px;
    height: 30px;
}

.action-btn-icon svg, .action-btn-icon img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
}

/* Small cost badge in the corner — wood/stone spend a resource on use. */
.action-btn-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 999px;
    background: rgba(10, 10, 10, 0.85);
    border: 1px solid var(--panel-border);
    font-size: 10.5px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    color: var(--amber-bright);
}

.action-btn-stone .action-btn-badge {
    color: #ffb3a0;
}

/* BREW's two-ingredient recipe (2 herb + 1 resin) — herb keeps the
   default top-right slot, resin gets its own bottom-right slot so both
   requirements stay legible at once instead of one string cramming
   both numbers together. Colors match each resource's own icon tint
   (see #res-herb/#res-resin's SVGs) so the badge reads as "which
   ingredient" even without reading the number. */
.action-btn-badge-herb {
    color: #8faf74;
}

.action-btn-badge-resin {
    top: auto;
    bottom: -4px;
    color: #d99a4e;
}

/* Caption under each circle so the three buttons stay distinguishable
   at a glance, not just by icon. */
.action-btn-label {
    font-size: clamp(7.5px, 2vw, 9px);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--hud-text-muted);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    white-space: nowrap;
}

/* --- Floating collect/refuel text --- */
.floating-text {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
    font-family: var(--font-ui);
    font-weight: 700;
    font-size: 18px;
    color: var(--amber-bright);
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);
    z-index: 15;
    will-change: transform, opacity;
}

/* A hit draining the light (see main.js's applyLightPenalty()) — red
   instead of the default amber, so "-25% LIGHT" reads as a setback at a
   glance rather than blending in with the amber refuel/collect text. */
.floating-text.damage {
    color: #ff6a52;
}

/* --- Floating icon burst — same world-to-screen tracking/rise/fade as
   .floating-text above (FloatingTextManager.spawnIcon() shares its update
   loop), just an <img> instead of a text label, popping in with a little
   overshoot rather than just fading in flatly. Generic, reusable — no
   current caller (its one use, the mushroom-eat "fun effect," was
   removed alongside the healing mechanic) but kept for any future
   reward-beat icon. --- */
.floating-icon {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
    z-index: 16;
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.5));
    will-change: transform, opacity;
    animation: floating-icon-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes floating-icon-pop {
    0% { scale: 0.2; }
    70% { scale: 1.12; }
    100% { scale: 1; }
}

/* --- Tension vignette: continuous, proximity-driven dark-purple border
   that intensifies the closer Bigfoot gets during an active STALK/CHASE
   threat (bigfoot.proximityIntensity, opacity set directly from JS each
   frame rather than via a class, since it's a continuous 0-1 value, not
   on/off). Distinct from #chase-overlay below — this ramps in during
   STALK too, well before an actual chase, and has no "RUN" text of its
   own. --- */
#tension-vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 24;
    opacity: 0;
    box-shadow: inset 0 0 120px 40px rgba(60, 10, 40, 0.55);
    transition: opacity 0.2s ease;
}

/* --- Chase overlay: pulsing red vignette + "RUN" cue while Bigfoot is
   actively pursuing (GameState.CHASE). Purely atmospheric — pointer-events
   stay off so it never interferes with tap-to-move input underneath. --- */
#chase-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 25;
    opacity: 0;
    transition: opacity 0.35s ease;
}

#chase-overlay.show {
    opacity: 1;
    animation: chase-pulse 1.1s ease-in-out infinite;
}

#chase-overlay::after {
    content: 'RUN';
    position: absolute;
    top: 16%;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.3em;
    color: #ff6a52;
    text-shadow: 0 0 12px rgba(255, 60, 30, 0.8);
}

@keyframes chase-pulse {
    0%, 100% { box-shadow: inset 0 0 60px 10px rgba(180, 20, 10, 0.35); }
    50% { box-shadow: inset 0 0 90px 25px rgba(180, 20, 10, 0.6); }
}

/* --- Heartbeat vignette: a red "lub-dub" pulse, distinct from the
   chase overlay's single smooth pulse — two quick beats then a rest,
   reading as a pulse rather than a steady throb. Held on continuously
   during the final seconds of a night — see main.js's
   updateHeartbeatVignette(). --- */
#heartbeat-vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 24;
    opacity: 0;
    transition: opacity 0.3s ease;
}

#heartbeat-vignette.active {
    opacity: 1;
    animation: heartbeat-pulse 1s ease-in-out infinite;
}

@keyframes heartbeat-pulse {
    0%   { box-shadow: inset 0 0 60px 10px rgba(200, 20, 20, 0.22); }
    14%  { box-shadow: inset 0 0 110px 34px rgba(200, 20, 20, 0.58); }
    28%  { box-shadow: inset 0 0 60px 10px rgba(200, 20, 20, 0.22); }
    42%  { box-shadow: inset 0 0 120px 38px rgba(200, 20, 20, 0.62); }
    60%, 100% { box-shadow: inset 0 0 60px 10px rgba(200, 20, 20, 0.22); }
}

/* --- Damage flash: one-shot full-screen red pulse the instant a hit
   actually lands — see main.js's triggerDamageFlash(), fired from
   applyLightPenalty(). A single punchy fade-out, not a loop, so it
   reads as "that just happened" rather than blending into the
   continuous vignettes above. --- */
#damage-flash {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 26;
    opacity: 0;
    background: radial-gradient(ellipse at center, rgba(200, 20, 20, 0) 35%, rgba(200, 20, 20, 0.6) 100%);
}

#damage-flash.show {
    animation: damage-flash-pulse 0.5s ease-out;
}

@keyframes damage-flash-pulse {
    0% { opacity: 1; }
    100% { opacity: 0; }
}

/* --- Start menu (lives inside #game-stage) -----------------------------------
   Its own full-bleed background art, not .overlay's dark scrim — this IS
   the screen, nothing is running underneath it (see main.js's startGame()
   comment). Sits above every other overlay (.overlay is z-index 30). */
#start-menu {
    position: absolute;
    inset: 0;
    z-index: 50;
    /* Solid fallback so this always reads as an opaque covering screen
       even if menu_background.webp is missing/still loading — a failed
       background-image has no fallback of its own and would otherwise
       leave the menu fully see-through with gameplay visible behind it. */
    background-color: #0a0806;
    background-image: url('assets/ui/menu_background.webp');
    background-size: cover;
    background-position: center;
    overflow: hidden;
    transition: opacity 0.4s ease;
    opacity: 1;
}

#start-menu.hidden {
    opacity: 0;
    pointer-events: none;
}

/* Dev-only test button (see its own comment in index.html/main.js for the
   ?debug=1 gating). Deliberately styled distinct from the real menu
   buttons (small, plain, top-right) so it reads as debug scaffolding
   rather than part of the actual UI. `display: none` is the REAL default
   now, not just a JS-applied one — direct report: on a slow-to-parse
   page (the submission build's single ~1MB inline script is the extreme
   case, but the live site isn't immune either) there's a real window
   between first paint and the point the script's own DEBUG-gating code
   actually runs, during which this button (and #debug-hud/#debug-
   controls) would show raw/unhidden. CSS applies at parse time, before
   any JS has to run at all, so this can never flash regardless of how
   long the script takes to reach that line. main.js's own `if (DEBUG)`
   branch now explicitly sets `display` back on for these three elements
   instead of only ever turning it off. */
#replay-tutorial-btn {
    display: none;
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 60;
    padding: 8px 14px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: #ffe9b8;
    background: rgba(20, 14, 8, 0.75);
    border: 1px solid rgba(255, 200, 120, 0.5);
    border-radius: 6px;
    cursor: pointer;
}
#replay-tutorial-btn:active {
    background: rgba(20, 14, 8, 0.95);
}

#start-menu-title-wrap {
    position: absolute;
    left: 50%;
    top: 34%;
    transform: translateX(-50%);
    width: 84%;
    max-width: 520px;
}

#start-menu-title {
    display: block;
    width: 100%;
    height: auto;
    /* `both`: holds the 0% keyframe's translateY(-120vh) for the instant
       before the animation engine kicks in, and holds the final settled
       translateY(0) forever after — without `forwards` it would snap
       back to its un-animated CSS position (no transform at all) the
       moment the animation completes. */
    animation: title-drop-bounce 1.7s both;
    animation-delay: 0.2s;
    filter: drop-shadow(0 10px 14px rgba(0, 0, 0, 0.5));
}

/* Falls in from off-screen, hits its resting spot (right above the
   firepit in the background art — #start-menu-title-wrap's own `top`
   places that rest point, this animation only ever moves relative to
   it), and settles through 3 decreasing bounces. Percentage translateY
   values here are relative to the image's OWN height (how CSS % works
   for transform: translateY on a non-percentage-sized box), so the
   bounce heights scale naturally with however big the title renders.
   Per-keyframe `animation-timing-function` alternates ease-in (falling,
   accelerating) / ease-out (rising into each bounce's peak, decelerating)
   so it reads as real gravity, not a mechanical back-and-forth. */
@keyframes title-drop-bounce {
    0%   { transform: translateY(-140vh); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }
    50%  { transform: translateY(0); animation-timing-function: cubic-bezier(0, 0.55, 0.45, 1); }
    62%  { transform: translateY(-9%); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }
    72%  { transform: translateY(0); animation-timing-function: cubic-bezier(0, 0.55, 0.45, 1); }
    82%  { transform: translateY(-4%); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }
    90%  { transform: translateY(0); animation-timing-function: cubic-bezier(0, 0.55, 0.45, 1); }
    96%  { transform: translateY(-1.3%); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45); }
    100% { transform: translateY(0); }
}

#start-menu-buttons {
    position: absolute;
    left: 50%;
    /* Same starting reference point as the title itself (its own
       `top: 34%`, both height-relative against the same containing
       block) — this part matches the title's REST position exactly,
       never guessed independently. */
    top: 34%;
    /* ...then pushed down by the title's own rendered height (plus a
       fixed 40px visible gap) via `margin-top`, NOT folded into the
       `top` percentage above. This is deliberate, not a stray
       leftover: per the CSS box model, a vertical MARGIN's percentage
       always resolves against the containing block's WIDTH (the same
       rule the classic "padding-top: %" aspect-ratio trick relies on),
       while `top`'s percentage resolves against its HEIGHT — so this
       is the one part of the box model that can express "a distance
       equal to X% of this element's own width" without needing to know
       the actual pixel width at all, which a `vw`-based guess can't do
       correctly on a desktop LETTERBOXED window (#game-stage narrower
       than the raw viewport — see layoutStage() in main.js). An
       earlier version used `min(84vw, 520px) * 0.444` here instead,
       which measured against the wrong box on a letterboxed window —
       overestimating the title's real rendered height there and
       pushing this column low enough to run the 4th button off the
       bottom edge (direct user report: "its still hitting the bottom").
       menu_title.webp is 1881x836 (height ≈44.4% of width); the title
       itself is `width: min(84%, 520px)` of this same container, so
       its rendered height as a fraction of THIS container's width is
       min(84% * 0.444, 520px * 0.444) = min(37.3%, 231px). */
    margin-top: calc(min(37.3%, 231px) + 40px);
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Tightened from 16px — direct user request to shrink the SPACE
       BETWEEN the 4 buttons themselves, not touch the title/its
       position at all: with a computed (not guessed) `top` above, the
       column can run past the visible bottom edge on a short screen
       before it ever gets anywhere near the title, so the fix for
       that is a shorter column, not more room from the title. */
    gap: 8px;
    /* Held invisible until the title's own fall+bounce (1.7s animation +
       0.2s delay above) has fully settled — appearing mid-fall would
       upstage the title before it's finished landing. `both` (not just
       `forwards`) so the 0%-keyframe's opacity:0 actually holds during
       this delay instead of the button flashing visible immediately. */
    animation: menu-buttons-in 0.5s ease-out both;
    animation-delay: 1.9s;
}

@keyframes menu-buttons-in {
    from { opacity: 0; transform: translateX(-50%) translateY(14px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

#start-menu-play-btn,
#start-menu-howto-btn,
#start-menu-records-btn,
#start-menu-investigation-btn {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    transition: transform 0.15s ease;
}

#start-menu-play-btn:active,
#start-menu-howto-btn:active,
#start-menu-records-btn:active,
#start-menu-investigation-btn:active {
    transform: scale(0.94);
}

#start-menu-play-btn img {
    width: min(64vw, 320px);
    height: auto;
    display: block;
}

/* evidence-collected.webp (direct user-supplied art) is a wide wood-sign
   BANNER, unlike HOW TO PLAY/VIEW RECORDS' own roughly-square art — same
   width budget as those two, but renders noticeably shorter at that
   width. All three of these (assets/ui/menu_howto_btn.webp, view-
   records.webp, evidence-collected.webp) had ~20% of their own canvas
   height as baked-in transparent padding above/below the actual sign
   art — that, not #start-menu-buttons' own `gap`, was the real source
   of the big visual space between these three buttons (direct report);
   fixed by cropping each file down to its alpha bounding box (plus a
   small margin) in place, same filenames, no markup/JS change needed. */
#start-menu-howto-btn img,
#start-menu-records-btn img,
#start-menu-investigation-btn img {
    width: min(48vw, 240px);
    height: auto;
    display: block;
}

/* --- How to Play poster -------------------------------------------------------
   Reuses .overlay's shared dark scrim (this DOES sit over the start
   menu, dimming it, unlike #start-menu itself) but skips .overlay-panel
   entirely — the poster art is already a complete, fully-framed piece
   on its own (its own wood-sign border, its own drawn CLOSE plate), so
   a second card border around it would just double up the framing. */
#howto-overlay {
    /* Opened FROM the start menu (#start-menu is z-index 50, above
       every other .overlay's default 30) — needs to render above it. */
    z-index: 55;
    padding: 20px;
}

/* Same "opened from the start menu" z-index situation as #howto-overlay
   above — see index.html's comment on #records-overlay. */
#records-overlay {
    z-index: 55;
}

/* Direct request: "change the Your Records page by using similar css to
   this" — a full modal stylesheet the user supplied (dark gradient card,
   amber hairline border, backdrop blur, a highlighted top stat, pill
   close button). Every rule below is scoped under #records-overlay so
   none of it touches the shared .overlay-panel/h1/p or button styling
   every OTHER overlay (How To Play, night-complete, game-over, victory,
   evidence board, etc.) still relies on — this reskins Records alone. */
#records-overlay .overlay-panel {
    width: min(88vw, 340px);
    max-width: min(88vw, 340px);
    padding: 28px 26px 24px;
    background: linear-gradient(180deg, rgba(25, 18, 12, 0.97), rgba(13, 10, 7, 0.985));
    border: 1px solid rgba(181, 121, 30, 0.72);
    border-radius: 20px;
    box-shadow:
        inset 0 1px 0 rgba(255, 215, 130, 0.09),
        inset 0 -1px 0 rgba(0, 0, 0, 0.7),
        0 14px 38px rgba(0, 0, 0, 0.46),
        0 0 22px rgba(194, 128, 28, 0.07);
    backdrop-filter: blur(7px);
    -webkit-backdrop-filter: blur(7px);
}

#records-overlay .overlay-panel h1 {
    margin: 0 0 7px;
    font-size: clamp(20px, 5vw, 24px);
    line-height: 1.1;
    font-weight: 800;
    letter-spacing: 0.035em;
    color: #ff806c;
    text-shadow: 0 2px 5px rgba(0, 0, 0, 0.75);
}

#records-overlay .overlay-panel p {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.4;
    color: #b9aa92;
}

/* Subtle divider under the subtitle — same spot/shape the supplied
   design's own .records-divider occupies, done as a pseudo-element
   instead of a new HTML node since there's nothing else it needs to do. */
#records-overlay .overlay-panel p::after {
    content: "";
    display: block;
    height: 1px;
    margin-top: 13px;
    background: linear-gradient(90deg, transparent, rgba(206, 144, 44, 0.32), transparent);
}

#records-overlay .records-list {
    display: grid;
    gap: 2px;
    margin-top: 13px;
    margin-bottom: 21px;
    text-align: left;
}

#records-overlay .records-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 16px;
    min-height: 30px;
    padding: 6px 4px;
    border-bottom: 1px solid rgba(255, 216, 145, 0.055);
    white-space: nowrap;
}

#records-overlay .records-row:last-child {
    border-bottom: 0;
}

#records-overlay .records-row span:first-child {
    min-width: 0;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 500;
    color: #b9aa94;
    white-space: normal;
}

#records-overlay .records-row span:last-child {
    text-align: right;
    font-family: var(--font-ui);
    font-size: 13.5px;
    font-weight: 800;
    color: #ffd16c;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.65);
}

/* The headline stat (Furthest Night Reached, always the first row) gets
   its own highlighted treatment — same "featured" idea as the supplied
   design's own best-record row, picked out via :first-child rather than
   a new HTML class since it's always the first entry in the list. */
#records-overlay .records-row:first-child {
    margin: 1px 0 7px;
    padding: 9px 10px;
    background: linear-gradient(90deg, rgba(151, 98, 21, 0.12), rgba(208, 146, 38, 0.06));
    border: 1px solid rgba(201, 139, 39, 0.18);
    border-radius: 10px;
}

#records-overlay .records-row:first-child span:first-child {
    color: #c8baa1;
}

#records-overlay .records-row:first-child span:last-child {
    color: #ffd46e;
    text-shadow: 0 0 10px rgba(255, 185, 62, 0.11);
}

/* Pill close button — overrides the shared #records-close-btn rule
   (id+id beats its plain single-id selector regardless of source order)
   without touching try-again/continue/play-again/etc., which all share
   that one rule with it. */
#records-overlay #records-close-btn {
    display: block;
    min-width: 105px;
    margin: 21px auto 0;
    padding: 11px 24px;
    border: 1px solid rgba(214, 144, 43, 0.95);
    border-radius: 999px;
    background: linear-gradient(180deg, #ffc96f 0%, #ffb64d 55%, #f3a43c 100%);
    color: #1a1209;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.035em;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.38),
        inset 0 -2px 0 rgba(145, 86, 14, 0.18),
        0 5px 14px rgba(0, 0, 0, 0.24);
    transition: transform 100ms ease, filter 120ms ease, box-shadow 120ms ease;
}

#records-overlay #records-close-btn:hover {
    filter: brightness(1.04);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42), 0 6px 17px rgba(0, 0, 0, 0.3);
}

@media (max-width: 360px) {
    #records-overlay .overlay-panel {
        width: min(91vw, 330px);
        padding: 24px 20px 21px;
    }
    #records-overlay .records-row span:first-child {
        font-size: 12px;
    }
    #records-overlay .records-row span:last-child {
        font-size: 13px;
    }
}

/* --- Bigfoot Investigation board (secondary exploration system) ------------- */
/* Same "opened from the start menu" z-index situation as #howto-overlay/#records-overlay above. */
#evidence-board-overlay {
    z-index: 55;
}

/* Redesigned to match the top/bottom HUD's own dark-gold visual family —
   direct request, structural reference was a screenshot mockup, explicitly
   "do not use a baked background image." flex-column + a capped max-height
   is what guarantees the header and CLOSE button can never clip or get
   scrolled away regardless of how many items the roster grows to — only
   #evidence-board-grid (flex:1, min-height:0) actually scrolls, same
   "header/scroll-area/footer never scrolls the footer away" shape as the
   tutorial banner's own layout elsewhere. */
.evidence-board-panel {
    width: min(92vw, 430px);
    max-width: 92%;
    max-height: min(82vh, 720px);
    box-sizing: border-box;
    padding: 22px 20px 18px;
    display: flex;
    flex-direction: column;
    background: linear-gradient(180deg, rgba(28, 21, 15, 0.98), rgba(14, 10, 7, 0.98));
    border: 1px solid #8e6427;
    box-shadow:
        inset 0 1px 0 rgba(255, 210, 120, 0.12),
        inset 0 -1px 0 rgba(0, 0, 0, 0.6),
        0 8px 30px rgba(0, 0, 0, 0.45),
        0 0 16px rgba(190, 125, 35, 0.1);
}

.evidence-board-header {
    flex-shrink: 0;
    padding-bottom: 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid rgba(211, 153, 55, 0.18);
}

#evidence-board-progress {
    color: var(--hud-text-muted);
    margin-bottom: 0;
}

/* Optional visual improvement from the spec — width is set in JS
   (populateEvidenceBoard()) from evidenceSystem.discoveredCount/
   totalCount every time the board (re)builds, never hardcoded. */
.collection-progress {
    height: 4px;
    margin-top: 10px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.07);
    overflow: hidden;
}

.collection-progress-fill {
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, #9a6b27, #e1b251);
    transition: width 0.3s ease;
}

/* The only scrolling region in this modal (see .evidence-board-panel's
   own comment) — flex:1 lets it fill whatever space the header/button
   leave, min-height:0 is required for a flex child to be allowed to
   shrink below its content's natural height and actually scroll instead
   of overflowing its parent. */
#evidence-board-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: clamp(8px, 2vw, 12px);
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 2px 4px 2px 2px;
    margin-bottom: 18px;
    scrollbar-width: thin;
    scrollbar-color: #725126 rgba(0, 0, 0, 0.25);
}

#evidence-board-grid::-webkit-scrollbar {
    width: 7px;
}

#evidence-board-grid::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.25);
    border-radius: 10px;
}

#evidence-board-grid::-webkit-scrollbar-thumb {
    background: #725126;
    border-radius: 10px;
}

#evidence-board-grid::-webkit-scrollbar-thumb:hover {
    background: #9a6d2e;
}

@media (max-width: 350px) {
    #evidence-board-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* Fixed aspect ratio (not content-driven height) so every card is the
   same size regardless of how long its name is — the icon area (flex:1
   within the card) absorbs the difference, not the card itself. */
.evidence-slot {
    min-width: 0;
    aspect-ratio: 0.82;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 3px;
    padding: 8px 4px 6px;
    border-radius: 12px;
}

/* Intentionally mysterious, not "broken/disabled" — no border glow, just
   the mystery-card art (see .evidence-slot-icon-unknown below). Padding
   zeroed out here (the base .evidence-slot rule above has some, for the
   name/rarity rows discovered cards show) so that art runs edge-to-edge
   against this border instead of sitting in a visible gap inside it —
   direct follow-up request. */
.evidence-slot.undiscovered {
    background: linear-gradient(180deg, rgba(16, 13, 10, 0.92), rgba(9, 8, 6, 0.96));
    border: 1px solid rgba(140, 104, 50, 0.16);
    padding: 0;
}

/* Nested radius = outer radius (12px, see .evidence-slot) minus this
   border's own width (1px) — the standard concentric-rounding formula,
   so the art's corners trace this slot's border exactly instead of
   leaving a small square peeking out past a mismatched inner curve. */
.evidence-slot.undiscovered .evidence-slot-icon {
    border-radius: 11px;
}

/* Per-rarity color still carries which tier this is, same information
   the old flat-color border already gave — just a plain border now,
   no box-shadow bloom (the per-rarity circle behind the icon, see
   .evidence-slot-icon-circle below, carries the color accent instead,
   direct request to de-glow these cards for a cleaner look). Common
   keeps its existing green rather than the sample swatch verbatim, to
   stay consistent with that same green used elsewhere (evidence toast,
   herb icon, etc.). */
.evidence-slot.discovered {
    cursor: pointer;
    background: linear-gradient(180deg, rgba(25, 31, 18, 0.75), rgba(12, 15, 9, 0.9));
}

.evidence-slot.discovered.common {
    border: 1px solid #8fae7a;
}

.evidence-slot.discovered.uncommon {
    border: 1px solid #7ab7d8;
    background: linear-gradient(180deg, rgba(18, 26, 31, 0.75), rgba(9, 13, 15, 0.9));
}

.evidence-slot.discovered.rare {
    border: 1px solid #e8c14a;
    background: linear-gradient(180deg, rgba(31, 27, 15, 0.75), rgba(15, 13, 9, 0.9));
}

/* flex:1 (not a fixed px size) is what keeps this area consistent across
   cards even when .evidence-slot-name below it varies from one to two
   lines — the icon area absorbs the difference, the card's own overall
   aspect-ratio never changes. */
.evidence-slot-icon {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: clamp(18px, 6vw, 26px);
    color: #9f927c;
    overflow: hidden;
}

.evidence-slot-icon img {
    width: 76%;
    height: 76%;
    object-fit: contain;
}

/* The mystery-card art (assets/evidence/unknown-item.webp) already has
   its own border baked in, unlike the real per-item icons above, and
   the name/rarity rows below it are skipped for undiscovered slots (see
   populateEvidenceBoard()) — so it fills the full icon box rather than
   sitting small and padded inside it. cover (not contain) so the art's
   own edges reach this box's border on every side flush, at the cost of
   a sliver of its outer edge being cropped off where its aspect ratio
   doesn't exactly match the box's — direct follow-up request to have
   the card fit the border rather than float inside it with a gap. */
.evidence-slot-icon-unknown {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Flat rectangular fill replaced with a per-rarity circle (aspect-ratio:1
   off its own width keeps it a true circle regardless of the icon area's
   actual box shape) — a plain clean backdrop for the icon that also
   still carries the rarity color, now that the card border no longer
   glows (see .evidence-slot.discovered above). */
.evidence-slot-icon-circle {
    width: 78%;
    aspect-ratio: 1;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.evidence-slot-icon-circle.common {
    background: radial-gradient(circle, rgba(143, 174, 122, 0.28), rgba(143, 174, 122, 0.06) 72%);
}

.evidence-slot-icon-circle.uncommon {
    background: radial-gradient(circle, rgba(122, 183, 216, 0.28), rgba(122, 183, 216, 0.06) 72%);
}

.evidence-slot-icon-circle.rare {
    background: radial-gradient(circle, rgba(232, 193, 74, 0.28), rgba(232, 193, 74, 0.06) 72%);
}

/* Names must never overflow or blow out the card's fixed aspect ratio —
   clamped to 2 lines, wrapping anywhere long/unbroken words need to. */
.evidence-slot-name {
    flex-shrink: 0;
    width: 100%;
    font-family: var(--font-ui);
    font-size: clamp(7.5px, 2.6vw, 9.5px);
    font-weight: 600;
    line-height: 1.08;
    text-align: center;
    color: var(--hud-text);
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.evidence-slot-rarity {
    flex-shrink: 0;
    font-family: var(--font-ui);
    font-size: clamp(6.5px, 2vw, 7.5px);
    font-weight: 700;
    letter-spacing: 0.04em;
}

.evidence-slot.discovered .evidence-slot-rarity.common { color: #8fae7a; }
.evidence-slot.discovered .evidence-slot-rarity.uncommon { color: #7ab7d8; }
.evidence-slot.discovered .evidence-slot-rarity.rare { color: #e8c14a; }

/* One z-index above #evidence-board-overlay (55) — opened from a tap
   INSIDE that board, so it needs to render on top of it rather than
   competing at the same layer. */
#evidence-lightbox {
    z-index: 56;
    padding: 20px;
}

#evidence-lightbox-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    max-width: 100%;
    max-height: 100%;
    cursor: pointer;
}

/* Direct request: "arrows and you can flip through the evidence and
   badges in order." Absolutely positioned against #evidence-lightbox
   itself (already `position:absolute; inset:0` via .overlay) rather
   than laid out as flex siblings of the panel — keeps the panel's own
   centering/sizing completely untouched (it's still the only thing
   that determines the overlay's visual center), the arrows just float
   at the screen edges on top of it. Sized/colored off the same
   .action-btn-circle tokens the bottom action bar's own circular
   buttons use, for a consistent look, without pulling in that class's
   own icon-column layout (this is a single glyph, centered both ways). */
.evidence-lightbox-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: clamp(36px, 9vw, 46px);
    height: clamp(36px, 9vw, 46px);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    box-shadow: var(--panel-shadow);
    color: var(--text-main);
    font-size: clamp(20px, 5vw, 26px);
    line-height: 1;
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    appearance: none;
    -webkit-appearance: none;
    outline: none;
    transition: transform 0.15s ease, opacity 0.2s ease;
    z-index: 1; /* above the panel's own background, below nothing else in here */
}

.evidence-lightbox-arrow:active {
    transform: translateY(-50%) scale(0.9);
}

.evidence-lightbox-arrow.left { left: 8px; }
.evidence-lightbox-arrow.right { right: 8px; }

/* Only one discovered entry to look at (or somehow none) — nothing to
   flip to, so the arrows have no job; hidden rather than shown-but-
   inert, same "don't show a dead control" rule disabled buttons
   elsewhere in this project don't quite follow but a fully pointless
   one should. */
.evidence-lightbox-arrow.hidden {
    display: none;
}

#evidence-lightbox-img {
    max-width: min(80vw, 360px);
    max-height: 55vh;
    object-fit: contain;
    border-radius: 12px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.6);
    background: rgba(0, 0, 0, 0.25);
}

#evidence-lightbox-name {
    font-family: var(--font-ui);
    font-size: 18px;
    font-weight: 700;
    color: var(--text-main);
    text-align: center;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

#evidence-lightbox-rarity {
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.04em;
}

#evidence-lightbox-rarity.common { color: #8fae7a; }
#evidence-lightbox-rarity.uncommon { color: #7ab7d8; }
#evidence-lightbox-rarity.rare { color: #e8c14a; }

/* Hidden for selfLabeled items (the night-survived badges, whose name/
   rarity is baked directly into the art) — see main.js's
   openEvidenceLightbox()/showNightBadgeReveal(). */
#evidence-lightbox-name.hidden,
#evidence-lightbox-rarity.hidden {
    display: none;
}

.evidence-slot.undiscovered .evidence-slot-name,
.evidence-slot.undiscovered .evidence-slot-rarity {
    color: var(--text-dim);
    opacity: 0.6;
}

#howto-poster {
    max-width: 100%;
    max-height: 100%;
    /* object-fit isn't needed — this is a plain <img>, not a filled
       box, so it just scales down to fit within the padded overlay
       while keeping its own aspect ratio (the two max-* rules above). */
    cursor: pointer;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.6);
    border-radius: 8px;
}

/* --- Game over overlay (lives inside #game-stage) --- */
.overlay {
    position: absolute;
    inset: 0;
    background: rgba(4, 4, 6, 0.82);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 30;
    transition: opacity 0.4s ease;
    opacity: 1;
}

.overlay.hidden {
    opacity: 0;
    pointer-events: none;
}

/* Shared branding background for the between-night notice PANELS
   specifically (SURVIVE THE NIGHT intro, night-complete, game-over,
   victory) -- direct request: "use this standard image... so the
   branding is similar," and a direct follow-up correction that this
   means the panel/card itself, not the full-screen backdrop behind it
   (which stays the plain dark scrim every other .overlay already uses).
   Deliberately a separate class from plain .overlay-panel (How To Play/
   Records/Evidence Board/rotate-prompt panels are unaffected). Second
   image swap — the first pick (the tutorial banner's own cauldron scene)
   read as too specific/busy for these generic notices; this one (a
   plain moonlit woods path, no strong off-center subject) is the direct
   replacement, "works better for basic stuff." No background-position
   shift needed this time — nothing here needs protecting from a crop the
   way the cauldron did, so plain centered cover is fine. The flat-color
   gradient layered UNDER the image in the same `background` shorthand
   (rather than a real scrim element) darkens it enough that the existing
   white/amber text stays readable over it without needing to touch every
   child text color. */
.overlay-night-notice .overlay-panel {
    background:
        linear-gradient(rgba(10, 8, 6, 0.55), rgba(10, 8, 6, 0.72)),
        url('assets/ui/woods-background.webp');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

.overlay-panel {
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 18px;
    box-shadow: var(--panel-shadow);
    padding: 30px 28px;
    max-width: 82%;
    text-align: center;
}

.overlay-panel h1 {
    font-family: var(--font-ui);
    font-size: 20px;
    color: #ff8a70;
    letter-spacing: 0.03em;
    margin-bottom: 8px;
}

.overlay-panel p {
    font-family: var(--font-ui);
    font-size: 13px;
    color: var(--text-dim);
    margin-bottom: 20px;
}

.overlay-stats {
    display: flex;
    gap: 18px;
    justify-content: center;
    margin-bottom: 24px;
}

.overlay-stat {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-ui);
    color: var(--text-main);
    font-weight: 600;
    font-size: 14px;
}

#night-complete-title {
    color: var(--amber-bright);
}

/* Distinct treatment for a night survived while Bigfoot was a genuine
   active threat (STALK/CHASE, or high proximity) right as the timer ran
   out — see main.js's nightCompleteWasCloseCall. A calm ending already
   reads fine as-is; this is specifically for "he was right there and you
   still made it," which deserves a bigger relief beat than the same flat
   transition every other survived night gets. */
/* Also applies to #victory-overlay — surviving the true final night while
   Bigfoot was genuinely bearing down is the single most dramatic possible
   finish this game has, and previously got zero special treatment on the
   one screen where it mattered most (showDawnSummary() returned into the
   victory branch before nightCompleteWasCloseCall was ever consulted). */
#night-complete-overlay.close-call .overlay-panel,
#victory-overlay.close-call .overlay-panel {
    border-color: #e8c14a;
    animation: close-call-relief-pulse 1.1s ease-out;
}

@keyframes close-call-relief-pulse {
    0% { box-shadow: 0 0 0 rgba(232, 193, 74, 0); }
    30% { box-shadow: 0 0 36px rgba(232, 193, 74, 0.55); }
    100% { box-shadow: 0 0 0 rgba(232, 193, 74, 0); }
}

/* Pre-existing bug, fixed here: `.overlay-panel h1` (one class + one
   element = higher specificity) was beating this plain class selector, so
   the victory title rendered in the same salmon-red as every other
   overlay's title (loss included) and this rule never actually applied.
   Matched to .overlay-panel h1's own specificity to win instead. */
.overlay-panel h1.victory-title {
    color: #b8e08a;
}

.overlay-fuel {
    font-family: var(--font-ui);
    font-size: 12.5px;
    color: var(--text-dim);
    margin-top: -14px;
    margin-bottom: 20px;
}

.dot-wood, .dot-stone {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    display: inline-block;
}

/* The user's two supplied reference images (lose-ui.webp, lights-out.webp)
   both turned out to be flat mockup screenshots — title/button/divider
   text baked directly into the picture, not a usable background layer —
   so this is built straight from CSS matching those mockups by eye
   instead of using either file as an image. A bolder, more solid amber
   border than the shared .overlay-panel default (var(--panel-border) is
   only 32% opaque, reads as a faint hairline against the reference
   images' clearly-visible gold outline). */
#game-over-overlay .overlay-panel {
    border: 2px solid rgba(255, 184, 77, 0.6);
}

/* --- Death jumpscare — Bigfoot-catch-specific cinematic before Game Over ---
   See index.html's own comment on #death-jumpscare and main.js's
   playDeathJumpscare()/DEATH_SEQUENCE constants for the full timeline.
   Hands only, no face — direct follow-up request after the first version
   (which slammed his face into view) — "just have hands reaching up,
   grabbing and coming down." Named CSS custom properties here (not
   hardcoded ms in each animation) so hand speed / impact / withdraw /
   hold duration can all be re-tuned from one place. Every value below
   uses vw/vh/clamp(), not fixed desktop px — this is a portrait-only
   mobile game (see #game-stage's own comments elsewhere). */
#death-jumpscare {
    --death-hands-duration: 400ms;
    --death-impact-duration: 160ms;
    --death-withdraw-duration: 350ms;
    --death-title-duration: 220ms;
    /* Direct bug report, with a screenshot: on a wide desktop window the
       hands flew out into the black letterbox bars instead of staying
       over the game. Root cause: `fixed` is ALWAYS relative to the real
       browser viewport, never a containing block — same reasoning
       #top-hud's own comment already documents for why vw/vh doesn't
       respect the letterbox either (see .death-hand below). `absolute`
       makes #game-stage (position:relative, sized in real px by
       layoutStage()) the containing block instead, so `inset:0` — and
       everything below anchored off it — is scoped to the letterboxed
       stage, not the full window. */
    position: absolute;
    inset: 0;
    z-index: 200;
    overflow: hidden;
    /* Deliberately NOT none, unlike its decorative children below — a
       full-screen click-blocker for the ~2s this plays, layered on top
       of (not instead of) game.state already being GAME_OVER by the time
       this shows (see triggerGameOver() in main.js) — see index.html's
       own comment on why both matter. */
    pointer-events: auto;
    background: transparent;
    transition: opacity var(--death-hands-duration) ease;
}

#death-jumpscare.hidden {
    opacity: 0;
    pointer-events: none;
}

/* Fades the world to black behind the hands — starts the instant the
   hands begin moving (see JS), reaches full black by the impact beat, and
   holds there right through the withdraw. */
.death-dim {
    position: absolute;
    inset: 0;
    background: #000;
    opacity: 0;
    pointer-events: none;
    transition: opacity calc(var(--death-hands-duration) + var(--death-impact-duration)) ease-in;
}

#death-jumpscare.dim .death-dim {
    opacity: 0.92;
}

.death-hand {
    position: absolute;
    /* Real bug: min(55vw, 480px) sized off the full browser viewport, not
       the letterboxed stage — see #death-jumpscare's own comment above.
       --stage-w is the stage's real px width (set by layoutStage() in
       main.js), so this now caps at 55% of the ACTUAL stage, same as
       before on a real portrait device (where stage width == viewport
       width) but correctly smaller on a letterboxed desktop window. */
    width: min(calc(var(--stage-w) * 0.55), 480px);
    object-fit: contain;
    opacity: 0;
    pointer-events: none;
    filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.6));
}

.death-hand-left {
    left: calc(var(--stage-w) * -0.2);
    bottom: calc(var(--stage-h) * -0.22);
    transform: translate(calc(var(--stage-w) * -0.06), calc(var(--stage-h) * 0.1)) rotate(-18deg) scale(0.7);
}

.death-hand-right {
    right: calc(var(--stage-w) * -0.2);
    bottom: calc(var(--stage-h) * -0.22);
    transform: translate(calc(var(--stage-w) * 0.06), calc(var(--stage-h) * 0.1)) rotate(18deg) scale(0.7);
}

/* Palms stay facing the player throughout — only position/rotation/scale
   animate, the art itself is never flipped or distorted. Strong ease-out
   (a steep expo curve) so the motion reads as sudden and aggressive, not
   a slow drift — direct spec. Distances in calc(var(--stage-w/h) * ...)
   rather than vw/vh — see #death-jumpscare's own comment on why a plain
   %-translate can't be substituted here either (it resolves against the
   TRANSLATED ELEMENT's own size, not the stage). */
@keyframes death-hand-grab-left {
    0% { transform: translate(calc(var(--stage-w) * -0.06), calc(var(--stage-h) * 0.1)) rotate(-18deg) scale(0.7); opacity: 0; }
    35% { opacity: 1; }
    100% { transform: translate(calc(var(--stage-w) * 0.17), calc(var(--stage-h) * -0.16)) rotate(-6deg) scale(1.1); opacity: 1; }
}

@keyframes death-hand-grab-right {
    0% { transform: translate(calc(var(--stage-w) * 0.06), calc(var(--stage-h) * 0.1)) rotate(18deg) scale(0.7); opacity: 0; }
    35% { opacity: 1; }
    100% { transform: translate(calc(var(--stage-w) * -0.17), calc(var(--stage-h) * -0.16)) rotate(6deg) scale(1.1); opacity: 1; }
}

.death-hand-left.grab {
    animation: death-hand-grab-left var(--death-hands-duration) cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.death-hand-right.grab {
    animation: death-hand-grab-right var(--death-hands-duration) cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* "...and coming down" — the withdraw beat added for the hands-only cut.
   Starts exactly where .grab's own 100% keyframe ends (see main.js:
   'grab' is removed and 'withdraw' added in the same synchronous tick,
   so there's no one-frame snap back to the resting position in between),
   drops further down/away than the original off-screen start, and fades
   out — reads as Bigfoot pulling his hands back down out of frame rather
   than just the grab reversing itself. */
@keyframes death-hand-withdraw-left {
    0% { transform: translate(calc(var(--stage-w) * 0.17), calc(var(--stage-h) * -0.16)) rotate(-6deg) scale(1.1); opacity: 1; }
    100% { transform: translate(calc(var(--stage-w) * -0.1), calc(var(--stage-h) * 0.26)) rotate(-24deg) scale(0.75); opacity: 0; }
}

@keyframes death-hand-withdraw-right {
    0% { transform: translate(calc(var(--stage-w) * -0.17), calc(var(--stage-h) * -0.16)) rotate(6deg) scale(1.1); opacity: 1; }
    100% { transform: translate(calc(var(--stage-w) * 0.1), calc(var(--stage-h) * 0.26)) rotate(24deg) scale(0.75); opacity: 0; }
}

.death-hand-left.withdraw {
    animation: death-hand-withdraw-left var(--death-withdraw-duration) cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

.death-hand-right.withdraw {
    animation: death-hand-withdraw-right var(--death-withdraw-duration) cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

/* Screen shake, applied to the whole overlay (hands + dim layer together)
   right as the hands land their grab — kept short (matches --death-
   impact-duration) so it reads as one sharp hit, not a messy wobble. */
@keyframes death-shake {
    0% { transform: translate(0, 0); }
    25% { transform: translate(calc(var(--stage-w) * -0.012), calc(var(--stage-h) * 0.008)); }
    50% { transform: translate(calc(var(--stage-w) * 0.016), calc(var(--stage-h) * -0.01)); }
    75% { transform: translate(calc(var(--stage-w) * -0.008), calc(var(--stage-h) * 0.006)); }
    100% { transform: translate(0, 0); }
}

#death-jumpscare.shake {
    animation: death-shake var(--death-impact-duration) linear;
}

/* A single bright flash at the moment of impact, settling to the same
   near-black the .death-dim layer is already fading toward — the two
   together are what sells "screen darkens briefly" right after the
   grab lands. */
.death-impact-overlay {
    position: absolute;
    inset: 0;
    background: rgba(255, 255, 255, 0);
    pointer-events: none;
}

.death-impact-overlay.show {
    animation: death-impact-flash var(--death-impact-duration) ease-out forwards;
}

@keyframes death-impact-flash {
    0% { background: rgba(255, 255, 255, 0); }
    30% { background: rgba(255, 255, 255, 0.35); }
    100% { background: rgba(0, 0, 0, 0.2); }
}

/* Now sits in the open central gap between the two gripping hands
   (rather than "over the Bigfoot image", which no longer exists) — same
   "LIGHTS OUT" name the earlier lose-ui.webp/lights-out.webp reference
   mockups already used for this screen (see the comment above on
   #game-over-overlay .overlay-panel). No dedicated horror webfont exists
   in this project yet (var(--font-ui) is a plain system stack) — bold
   weight, slight rotation, and a two-layer red glow approximate the
   distressed look without adding a new font dependency. */
.death-title {
    position: absolute;
    left: 50%;
    top: 44%;
    width: calc(var(--stage-w) * 0.92); /* was 92vw — see #death-jumpscare's own comment */
    transform: translate(-50%, -50%) translateY(14px) rotate(-2deg) scale(0.92);
    text-align: center;
    font-family: var(--font-ui);
    font-size: clamp(32px, calc(var(--stage-w) * 0.12), 64px); /* was 12vw */
    font-weight: 800;
    letter-spacing: 0.04em;
    color: #b82e22;
    text-shadow:
        0 3px 6px rgba(0, 0, 0, 0.9),
        0 0 14px rgba(160, 30, 20, 0.45);
    opacity: 0;
    pointer-events: none;
    white-space: nowrap;
}

@keyframes death-title-in {
    0% { transform: translate(-50%, -50%) translateY(14px) rotate(-2deg) scale(0.9); opacity: 0; }
    100% { transform: translate(-50%, -50%) translateY(0) rotate(-2deg) scale(1); opacity: 1; }
}

.death-title.show {
    animation: death-title-in var(--death-title-duration) ease-out forwards;
}

/* --- Abandoned-campsite jumpscare — a one-time forest landmark scare ------
   See index.html's own comment on #campsite-jumpscare and main.js's
   playCampsiteJumpscare()/CAMPSITE_JUMPSCARE_SEQUENCE for the full
   timeline. Leaner than #death-jumpscare above (one image, no title, no
   hands) — same vw/vh/clamp()-only convention (portrait-only mobile game). */
#campsite-jumpscare {
    --campsite-scare-punch-duration: 180ms;
    --campsite-scare-shake-duration: 220ms;
    --campsite-scare-fade-duration: 350ms;
    /* fixed -> absolute: same letterbox bug as #death-jumpscare above (see
       its own comment) — this one read as merely oversized rather than
       flying into the black bars, since the face is flex-centered with no
       vw/vh-based translate, but was still sized off the full window. */
    position: absolute;
    inset: 0;
    z-index: 195; /* below #death-jumpscare (200) — the two should never overlap in practice, but if they somehow did, an actual death always wins */
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: auto; /* full-screen click-blocker for the brief moment this plays, same reasoning as #death-jumpscare */
    background: transparent;
    transition: opacity var(--campsite-scare-fade-duration) ease;
}

#campsite-jumpscare.hidden {
    opacity: 0;
    pointer-events: none;
}

.campsite-jumpscare-dim {
    position: absolute;
    inset: 0;
    background: #000;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--campsite-scare-punch-duration) ease-in;
}

#campsite-jumpscare.dim .campsite-jumpscare-dim {
    opacity: 0.85;
}

#campsite-jumpscare-face {
    position: relative;
    width: min(calc(var(--stage-w) * 0.78), 520px); /* was 78vw */
    object-fit: contain;
    opacity: 0;
    pointer-events: none;
    filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.7));
    transform: scale(1.5);
}

/* Punches from slightly-oversized-and-invisible straight to full view —
   strong ease-out so it reads as a sudden lunge into frame, same "sharp,
   not a slow drift" intent as the death hands' own grab keyframes. */
@keyframes campsite-scare-punch {
    0% { transform: scale(1.5); opacity: 0; }
    40% { opacity: 1; }
    100% { transform: scale(1); opacity: 1; }
}

#campsite-jumpscare-face.punch {
    animation: campsite-scare-punch var(--campsite-scare-punch-duration) cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes campsite-scare-shake {
    0% { transform: translate(0, 0); }
    25% { transform: translate(calc(var(--stage-w) * -0.014), calc(var(--stage-h) * 0.009)); }
    50% { transform: translate(calc(var(--stage-w) * 0.018), calc(var(--stage-h) * -0.011)); }
    75% { transform: translate(calc(var(--stage-w) * -0.009), calc(var(--stage-h) * 0.007)); }
    100% { transform: translate(0, 0); }
}

#campsite-jumpscare.shake {
    animation: campsite-scare-shake var(--campsite-scare-shake-duration) linear;
}

/* Applied to #top-hud/#action-bar the instant the jumpscare starts (see
   main.js's playDeathJumpscare()) — a quick dim/fade rather than actually
   hiding them, so nothing pops or reflows once the jumpscare ends and
   they're restored for the Game Over screen underneath. */
.death-fade {
    transition: opacity 150ms ease;
    opacity: 0.15;
}

/* --- Victory cinematic — true-final-night-only lead-in ---------------------
   Same architecture as #death-jumpscare above (plain DOM overlay, driven by
   class toggles from main.js's playVictorySequence(), no 3D scene
   involvement), reusing .death-fade on #top-hud/#action-bar for the same
   dim treatment rather than inventing a second one. See index.html's own
   comment on #victory-cinematic for why this is a flat 2D flipbook instead
   of a real in-world cutscene. */
#victory-cinematic {
    position: fixed;
    inset: 0;
    z-index: 200;
    overflow: hidden;
    pointer-events: auto;
    background: transparent;
    transition: opacity 400ms ease;
}

#victory-cinematic.hidden {
    opacity: 0;
    pointer-events: none;
}

/* Set when probeVictoryPlayerArt() finds the new art isn't there yet
   (still being made) — suppresses beat 2 entirely rather than showing a
   broken image, leaving beat 1 + the dawn wash + title to carry the
   cinematic on their own until the art lands. */
#victory-player-frame.hidden {
    display: none;
}

/* Warm radial wash standing in for the dawn light breaking — separate from
   (and layered on top of) the real Three.js fog/ambient dawn tween
   (updateDawnTween() in main.js), which is subtle by design; this is the
   punchier version for the one moment that should feel unmistakably big. */
.victory-dawn-wash {
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse at 50% 62%, rgba(255, 214, 140, 0.85), rgba(150, 108, 58, 0.35) 55%, rgba(20, 16, 10, 0.1) 100%);
    opacity: 0;
    transition: opacity 1.8s ease;
}

#victory-cinematic.dawn .victory-dawn-wash {
    opacity: 1;
}

/* Sized/centered to carry the cinematic on its own — was tuned smaller and
   lower to leave room for a Bigfoot-retreat beat above it; that beat's
   gone now (see #victory-player-frame's own comment below), so this is
   free to be the clear visual focus instead of sharing the frame. Capped
   at 200px (was 380px) — direct request, read as too big at the larger
   size. Still uses min() with a vw fraction so it keeps shrinking
   proportionally on anything narrower than ~322px, rather than a flat
   200px overflowing a very small screen. */
.victory-cine-sprite {
    position: absolute;
    left: 50%;
    top: 52%;
    width: min(62vw, 200px);
    object-fit: contain;
    transform: translate(-50%, -50%);
    opacity: 0;
    filter: drop-shadow(0 12px 26px rgba(0, 0, 0, 0.55));
}

/* The camper rises into their own victory pose (see
   VICTORY_PLAYER_FRAMES/probeVictoryPlayerArt() in main.js). Holds fully
   visible at the end rather than fading back out, since this is the frame
   that should still be on screen when #victory-cine-title lands. Was
   preceded by a Bigfoot-retreat beat using his own walk-cycle frames — cut
   on direct request once the real art came in, so this is the whole
   cinematic now. */
#victory-player-frame.rise {
    animation: victory-player-rise 1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes victory-player-rise {
    0% { opacity: 0; transform: translate(-50%, -50%) translateY(24px) scale(0.88); }
    35% { opacity: 1; }
    100% { opacity: 1; transform: translate(-50%, -50%) translateY(0) scale(1); }
}

.victory-cine-title {
    position: absolute;
    left: 50%;
    top: 20%;
    width: 92vw;
    transform: translate(-50%, -50%) translateY(10px) scale(0.9);
    text-align: center;
    font-family: var(--font-ui);
    font-size: clamp(22px, 7.5vw, 36px);
    font-weight: 800;
    letter-spacing: 0.03em;
    color: #ffe9b8;
    text-shadow:
        0 3px 10px rgba(0, 0, 0, 0.85),
        0 0 22px rgba(255, 200, 110, 0.55);
    opacity: 0;
    pointer-events: none;
}

.victory-cine-title.show {
    animation: victory-title-in 500ms ease-out forwards;
}

@keyframes victory-title-in {
    0% { opacity: 0; transform: translate(-50%, -50%) translateY(10px) scale(0.9); }
    100% { opacity: 1; transform: translate(-50%, -50%) translateY(0) scale(1); }
}

/* Small callout under the subtitle, shown only the first time
   records.data.victories was 0 going into this run (see showDawnSummary())
   — a nice-to-notice extra, not load-bearing, so it stays understated. */
.victory-first-badge.hidden {
    display: none;
}

.victory-first-badge {
    display: inline-block;
    margin: -10px auto 16px;
    padding: 4px 12px;
    border: 1px solid rgba(255, 200, 110, 0.5);
    border-radius: 999px;
    background: rgba(255, 200, 110, 0.12);
    color: #ffe9b8;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
}

/* Plain, unlabeled divider right under the subtitle — the reference
   mockups have this ABOVE the labeled "WHAT YOU HAD LEFT" one below,
   just a centered diamond with no text on either side. */
.overlay-divider-plain {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 14px;
}

.overlay-divider-plain::before,
.overlay-divider-plain::after {
    content: '';
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--panel-border), transparent);
}

.overlay-divider-plain-diamond {
    width: 6px;
    height: 6px;
    background: var(--amber);
    transform: rotate(45deg);
    flex-shrink: 0;
}

/* "WHAT YOU HAD LEFT" divider above the wood/stone stat row — see
   index.html's own comment on .overlay-stats-label. Same dotted-line-
   with-diamond flourish as the reference mockups. */
.overlay-stats-label {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 16px;
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--text-dim);
}

.overlay-stats-label::before,
.overlay-stats-label::after {
    content: '';
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--panel-border), transparent);
}

/* Full 5-icon resource breakdown (game-over screen) — see index.html's
   own comment. Vertical divider lines between columns via box-shadow on
   each item except the last, rather than a border, so the divider's
   color/opacity matches --panel-border exactly without needing a
   separate pseudo-element per gap. */
.overlay-resource-stats {
    display: flex;
    justify-content: center;
    margin-bottom: 24px;
}

.overlay-resource-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 0 14px;
    box-shadow: 1px 0 0 var(--panel-border);
}

.overlay-resource-stat:first-child {
    padding-left: 0;
}

.overlay-resource-stat:last-child {
    padding-right: 0;
    box-shadow: none;
}

.overlay-resource-stat img {
    width: 44px;
    height: 44px;
    object-fit: contain;
}

.overlay-resource-stat span {
    font-family: var(--font-ui);
    font-size: 22px;
    font-weight: 700;
    color: var(--text-main);
}

/* Direct request: "the what you have left modal shows the inventory in
   camp - separate from the inventory on hand. the camp inventory can be a
   smaller summary." Deliberately a single small text line, not a second
   icon grid — visually secondary to .overlay-resource-stats above it, not
   competing with it. Shared across game-over/night-complete/victory (see
   each overlay's own #<prefix>-camp-storage span), populated by main.js's
   campStorageSummaryText(). */
.overlay-camp-storage {
    margin: -12px 0 20px;
    font-family: var(--font-ui);
    font-size: 12px;
    color: var(--text-dim);
}

.overlay-camp-storage-label {
    font-weight: 700;
    letter-spacing: 0.03em;
    margin-right: 4px;
}

/* Small campfire-flame divider (Night Survived screen only) — same
   line-flanked-by-a-small-centered-icon shape as .overlay-divider-plain
   above, just with a flame instead of a diamond. Forgotten sizing on
   the first pass rendered the raw SVG at its unconstrained default,
   filling most of the card. */
.overlay-divider-icon {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 20px 0;
}

.overlay-divider-icon::before,
.overlay-divider-icon::after {
    content: '';
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--panel-border), transparent);
}

.overlay-divider-icon svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.dot-wood { background: var(--accent-wood); }
.dot-stone { background: var(--accent-stone); }

/* --- Upgrade card picker (Milestone 6) ---------------------------------- */
.upgrade-panel {
    max-width: 90%;
    width: 320px;
}

#upgrade-cards {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.upgrade-card {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    text-align: left;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--panel-border);
    border-radius: 12px;
    padding: 14px 16px;
    cursor: pointer;
    font-family: var(--font-ui);
    appearance: none;
    -webkit-appearance: none;
    transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}

.upgrade-card:active {
    transform: scale(0.97);
    border-color: var(--amber-bright);
    background: rgba(255, 200, 120, 0.1);
}

/* Direct playtester follow-up: "Ensure the rewards system works. I dont
   really see a difference when selecting the reward system" — the pick
   itself always applied correctly (verified: every upgrade's effect
   lands on a CONFIG value every other system already reads fresh), but
   nothing on screen ever confirmed the choice took, so it read as
   "nothing happened." showUpgradeChoices() in main.js now holds on the
   clicked card with this state (title glows, a checkmark badge appears,
   the OTHER two cards fade via .upgrade-card-fade below) for a beat
   before actually moving on to the next night, instead of the whole
   panel just vanishing the instant a card is tapped. */
.upgrade-card.selected {
    border-color: var(--amber-bright);
    background: rgba(255, 200, 120, 0.14);
    cursor: default;
}

.upgrade-card.selected .upgrade-card-title::after {
    /* One extra space after the hex escape is consumed as the escape's
       own terminator (per the CSS spec), not rendered — two are needed
       here so "Applied" doesn't end up glued right against the check. */
    content: ' \2713  Applied';
    color: var(--amber-bright);
    font-weight: 700;
}

.upgrade-card-fade {
    opacity: 0.35;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

/* Direct user-supplied art, one per upgrade (assets/ui/<id>.webp) — see
   showUpgradeChoices() in main.js. Sits to the left of the title/desc
   text column via .upgrade-card's own flex row. */
.upgrade-card-icon {
    width: 48px;
    height: 48px;
    object-fit: contain;
    flex-shrink: 0;
}

.upgrade-card-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.upgrade-card-title {
    display: block;
    font-size: 15px;
    font-weight: 700;
    color: var(--amber-bright);
    margin-bottom: 3px;
}

.upgrade-card-desc {
    display: block;
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.4;
}

#try-again-btn, #continue-btn, #play-again-btn, #records-close-btn, #evidence-board-close-btn, #tutorial-gotit-btn, #night1-intro-begin-btn {
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #1a1108;
    background: linear-gradient(180deg, var(--amber-bright), var(--amber));
    border: none;
    border-radius: 999px;
    padding: 12px 30px;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
}

#try-again-btn:active, #continue-btn:active, #play-again-btn:active, #records-close-btn:active, #evidence-board-close-btn:active, #tutorial-gotit-btn:active, #night1-intro-begin-btn:active {
    transform: scale(0.96);
}

/* This one specifically lives inside a flex-column panel now (see
   .evidence-board-panel) — flex-shrink:0 keeps it from ever being
   squeezed by the scrolling grid above it, and align-self:center
   overrides the panel's default flex stretch so it stays pill-shaped
   instead of spanning the full width. min-height/min-width are a
   comfortable-touch-target floor, not a change to its normal size. */
#evidence-board-close-btn {
    flex-shrink: 0;
    align-self: center;
    min-height: 44px;
    min-width: 110px;
}

/* Shown once, right after the first-time tutorial ends (skipped or
   finished naturally), before Night 1 actually starts ticking — see
   index.html's own comment. Reuses .overlay/.overlay-panel exactly like
   every other full-screen beat (game-over, night-complete, upgrade), so
   it reads as part of the same visual language rather than a one-off. */
#night1-intro-title {
    color: var(--amber-bright);
}

/* --- First-time-user tutorial ------------------------------------------------
   Bottom-anchored, sitting just above #action-bar — direct user report:
   centered mid-screen (its old spot) sat directly over CHOP/BREAK, which
   float over their own tree/rock (see #chop-btn-anchor in index.html)
   and can land anywhere in that same middle band, making them
   untappable while the tutorial pointed at exactly that tree/rock.
   140px clears #action-bar's own height (~85px — see #debug-controls'
   own comment on that figure) with real breathing room to spare, and
   never competes with the top HUD panels either. Same dark-wood-panel
   language as every other HUD chrome here (var(--panel-bg)/
   var(--panel-border)/var(--panel-shadow)). */
/* Direct request: "use an image as a background instead of what is
   there and layer the text and button over it... one single basic
   background for most of the tutorial UI screens and then specialized
   backgrounds... for each step when necessary" — the image itself is set
   per step via main.js (background-image, see TUTORIAL_STEPS' own
   `background` field), cover/centered so it works whether the source art
   is landscape (the two purpose-made pieces) or portrait (the shared
   default). Widened well past the old text-card's 340px cap so the art
   actually reads as a scene, not a sliver. */
/* Full-screen darkening scrim behind the banner, shown/hidden in lockstep
   with it (see main.js's renderTutorialStep()/celebrateTutorialStep()) —
   direct redesign request: "darken the screen behind the banner so that
   the banner catches the eye first and primary... you are purposefully
   pausing the game action behind it." Sits one z-index below the banner
   itself; gameplay is genuinely paused (gameplayPausedForOverlay) for the
   whole time this is up, not just visually implied by the dark, so what's
   frozen underneath never needs to stay legible. */
#tutorial-scrim {
    position: fixed;
    inset: 0;
    background: rgba(6, 4, 2, 0.72);
    z-index: 14;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

#tutorial-scrim.show {
    opacity: 1;
}

/* Centered, not bottom-anchored — was deliberately moved to the bottom in
   an earlier session specifically because a centered banner sat directly
   over CHOP/BREAK (they float over their own tree/rock, anywhere in that
   same middle band) making them untappable while the tutorial pointed at
   exactly that target. That conflict can't happen anymore: gameplay is
   now fully paused whenever this banner is visible (see the scrim comment
   above), so there's nothing else on screen to tap OR obstruct while it's
   up — centering is safe again, and reads better as the deliberate,
   full-attention modal this redesign asks for: "center the instructive
   banners in the screen... giving the user control of the time to absorb
   the information." */
#tutorial-banner {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(86vw, 380px);
    min-height: 156px;
    background-color: var(--panel-bg);
    background-size: cover;
    background-position: center;
    border: 1px solid var(--panel-border);
    border-radius: var(--panel-radius);
    box-shadow: var(--panel-shadow);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    z-index: 15;
    overflow: hidden;
    transition: opacity 0.3s ease;
    /* Direct tester report: "by the fire, it becomes nearly impossible to
       move your character" — root cause, not collision: this only ever
       got pointer-events:none while .hidden (see below), so for the
       ~2s/cycle it's actually ON SCREEN it silently ate every tap over
       its own real estate (bottom 140-296px, up to 380px wide) — exactly
       where a player near camp naturally taps to walk. Nothing inside
       this banner needs a tap except #tutorial-gotit-btn, which restores
       pointer-events:auto for itself below. */
    pointer-events: none;
}

/* Direct bug report — a real, confirmed regression: this rule used to
   apply UNCONDITIONALLY, meaning #tutorial-gotit-btn stayed fully
   clickable even while #tutorial-banner sat at `opacity: 0` behind
   `.hidden` (see that class below) between/outside real tutorial steps.
   CSS pointer-events on a child is NOT blocked by a parent's
   pointer-events: none unless the child says so itself — this button
   saying `auto` unconditionally opted it back in at all times, sitting
   invisibly over the gameplay canvas at a fixed screen position (its own
   `top: 50%; left: 50%`) for the ENTIRE game, not just during the
   tutorial. Reproduced directly: a single ordinary tap at that exact
   screen spot during real, established gameplay (no debug tools, no
   tutorial actually active) silently landed on this button instead of
   the canvas beneath it and launched the real tutorial banner mid-game —
   matches the player's own report exactly ("it started playing again in
   the middle of the chase," "this has been a persistent problem").
   Scoped to only apply while the banner is actually shown; also see
   TutorialManager.advance()/skip() in TutorialManager.js for the
   matching defense-in-depth guard against this same class of stray call. */
#tutorial-banner:not(.hidden) #tutorial-gotit-btn {
    pointer-events: auto;
}

/* Darkens left-to-right so #tutorial-content stays readable over ANY
   background art, not just the two pieces deliberately painted with a
   built-in dark-right composition — a plain scene (the shared default)
   gets the same guaranteed text-safe zone. */
/* Direct follow-up report: text/badges were crowding — even overlapping
   — busy parts of some specialized art (a sign, a boulder, a cauldron —
   each piece's own subject sits at a different distance from the right
   edge). Darkens earlier and reaches full opacity well before
   #tutorial-content even starts, so there's a real buffer zone
   regardless of exactly how far right any given piece's subject extends,
   rather than tuning this per image. */
#tutorial-banner::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, rgba(10, 8, 6, 0) 16%, rgba(10, 8, 6, 0.72) 46%, rgba(10, 8, 6, 0.94) 100%);
    pointer-events: none;
}

#tutorial-content {
    position: relative;
    width: 46%;
    padding: 16px 18px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    text-align: left;
}

#tutorial-banner.hidden {
    opacity: 0;
    /* pointer-events: none is already the base #tutorial-banner rule now
       (see its own comment) — no longer needs repeating here. */
}

/* Every step "pops" in rather than silently swapping its text in place
   — direct user request ("things should pop up and off, not
   continuously be there"). The resting `transform: translateX(-50%)`
   (horizontal centering — bottom-anchored, so no vertical centering to
   preserve) has to be baked into every keyframe stop too, or the
   animation would visibly snap the banner off-center for its
   duration. */
@keyframes tutorial-pop-in {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(0.82); }
    65% { opacity: 1; transform: translate(-50%, -50%) scale(1.04); }
    100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

#tutorial-banner.pop-in {
    animation: tutorial-pop-in 0.32s ease-out;
}

@keyframes tutorial-pop-out {
    0% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(0.85); }
}

#tutorial-banner.pop-out {
    animation: tutorial-pop-out 0.25s ease-in forwards;
}

/* A lighter "in-place" flourish for a celebrate hold (see
   TutorialManager.update()'s onCelebrate / main.js's
   celebrateTutorialStep()) — the SAME banner acknowledges a win without
   doing the full pop-out/pop-in cycle above, which is reserved for an
   actual step change. A warm gold flash + quick bounce reads as "nice!"
   without restarting the whole banner — direct user request to
   "celebrate collecting... feeds the dopamine". */
@keyframes tutorial-celebrate-pulse {
    0% { transform: translate(-50%, -50%) scale(1); box-shadow: var(--panel-shadow); }
    30% { transform: translate(-50%, -50%) scale(1.06); box-shadow: 0 0 24px 4px rgba(255, 200, 90, 0.55); }
    100% { transform: translate(-50%, -50%) scale(1); box-shadow: var(--panel-shadow); }
}

#tutorial-banner.celebrate-pulse {
    animation: tutorial-celebrate-pulse 0.5s ease-out;
}

/* Icon badge + directional pointer side by side, not stacked — keeps the
   content column compact even with the extra art behind it. Left-aligned
   (not centered) to match the rest of #tutorial-content now that text
   sits in a fixed right-hand column rather than a centered card. */
/* Centered above the heading rather than flush left — direct follow-up
   request. Needs `align-self: stretch` (the parent, #tutorial-content, is
   `align-items: flex-start`, which would otherwise size this row to just
   its own two circles, leaving nothing for justify-content to center
   within) so it actually spans the column's full width first. */
#tutorial-badges-row {
    display: flex;
    align-self: stretch;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin-bottom: 6px;
}

/* Same circle-with-a-rotating-arrow shape/sizing the old #compass used
   (see main.js's TutorialManager wiring for the rotation math) — hidden
   entirely for steps with no real world target (getTarget() returns
   null), not just left pointing at nothing. */
#tutorial-pointer {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid var(--panel-border);
    display: flex;
    align-items: center;
    justify-content: center;
}

#tutorial-pointer.hidden {
    display: none;
}

#tutorial-pointer svg {
    width: 22px;
    height: 22px;
    transition: transform 0.15s linear;
}

/* Thematic badge (wood/stone/herb/cauldron/evidence-case — see
   TUTORIAL_STEPS' `icon` field) — same circle sizing as the pointer
   beside it for a matched pair, just filled with the real resource art
   instead of an arrow. */
#tutorial-icon-wrap {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid var(--panel-border);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px;
}

#tutorial-icon-wrap.hidden {
    display: none;
}

#tutorial-icon-wrap img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

#tutorial-message {
    font-family: var(--font-ui);
    font-size: 15px;
    font-weight: 700;
    color: var(--amber-bright);
    margin: 0 0 4px;
    /* Extra legibility insurance over the background art, on top of the
       ::before scrim — matters most right at the scrim's own left edge,
       where it's still fading in. */
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
}

#tutorial-sub {
    font-family: var(--font-ui);
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.4;
    margin: 0 0 14px;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
}

#tutorial-sub:empty {
    display: none;
    margin: 0;
}

#tutorial-gotit-btn.hidden {
    display: none;
}

/* Always available regardless of step — a deliberate escape hatch, not
   tucked inside the banner itself so it never disappears along with a
   step's own "GOT IT" button.
   Was originally small/quiet on purpose (a discreet exit, not something
   to draw attention toward) — direct follow-up request reversed that
   call: "put the skip right in front of them... I don't want the judges
   wandering off during a tutorial and having it break because of that."
   Now sized and colored to actually be noticed (gold border, bigger
   text, a slow attention-pulse — see .skip-pulse below), and the new
   "TUTORIAL" label makes it double as the persistent "you're in the
   tutorial right now" reminder the same request asks for, not purely an
   escape hatch. Still living as a child of #top-hud, positioned at
   top:100% (its parent's own bottom edge), so it tracks the bar's real
   (clamp()-driven) height automatically rather than a hardcoded px guess
   — #top-hud has no overflow:hidden, so this still renders outside its
   box exactly as intended despite being a child. */
#tutorial-skip-btn {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: clamp(8px, 2.4vw, 12px);
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-ui);
    font-size: clamp(13px, 3.6vw, 16px);
    font-weight: 700;
    color: var(--hud-text);
    background: linear-gradient(180deg, rgba(29, 22, 13, 0.97), rgba(13, 10, 6, 0.98));
    border: 1.5px solid var(--hud-gold);
    border-radius: 999px;
    padding: clamp(7px, 2vw, 10px) clamp(14px, 4vw, 18px);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    -webkit-tap-highlight-color: transparent;
    pointer-events: auto;
    z-index: 15;
    box-shadow: 0 0 0 0 rgba(255, 184, 77, 0.5);
    animation: skip-btn-pulse 2.6s ease-in-out infinite;
}

/* Slow, low-amplitude glow — meant to catch a wandering eye, not nag.
   Fast/large pulses read as urgent/alarming; this is closer to a candle
   flicker than a notification badge. */
@keyframes skip-btn-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(255, 184, 77, 0.45); }
    50% { box-shadow: 0 0 0 6px rgba(255, 184, 77, 0); }
}

#tutorial-skip-btn.hidden {
    display: none;
}

#tutorial-skip-btn:active {
    transform: scale(0.95);
}

.skip-label {
    color: var(--hud-gold);
    letter-spacing: 0.06em;
}

.skip-dot {
    color: var(--hud-text);
    opacity: 0.5;
}

.skip-chevron {
    color: var(--hud-gold);
    font-weight: 700;
}

/* --- Rotate-to-portrait lock: OUTSIDE #game-stage on purpose, so it
   covers the whole physical screen (not just a letterboxed area) when a
   real touch device is rotated to landscape. Never shown on desktop —
   that case gets letterboxed instead, see main.js. --- */
#rotate-overlay {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgba(4, 4, 6, 0.94);
}

.rotate-icon {
    width: 46px;
    height: 78px;
    border: 3px solid var(--amber-bright);
    border-radius: 8px;
    position: relative;
    margin: 0 auto 20px;
    animation: rotate-hint 1.8s ease-in-out infinite;
}

.rotate-icon::after {
    content: '';
    position: absolute;
    bottom: 6px;
    left: 50%;
    width: 8px;
    height: 8px;
    margin-left: -4px;
    border-radius: 50%;
    background: var(--amber-bright);
}

@keyframes rotate-hint {
    0%, 15% { transform: rotate(0deg); }
    50%, 80% { transform: rotate(90deg); }
    100% { transform: rotate(90deg); }
}

/* display: none is the real default now, not just JS-applied — see
   #replay-tutorial-btn's own comment on why (a real pre-JS flash window
   on a slow-to-parse page). main.js's `if (DEBUG)` branch sets this back
   to 'block' explicitly. */
#debug-hud {
    display: none;
    position: absolute;
    left: 10px;
    bottom: 10px;
    color: rgba(220, 230, 210, 0.65);
    font-family: "Courier New", monospace;
    font-size: 11px;
    line-height: 1.4;
    white-space: pre;
    pointer-events: none;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    z-index: 10;
}

/* Dev-only — see the comment at the markup site in index.html. */
/* bottom: 85px clears #action-bar's own height (SLING's 54px circle +
   label, its tallest button, plus the bar's own bottom inset) — used to
   just be bottom:10px, back when #action-buttons was a right-side
   column sitting well above this corner; now that SLING (the rightmost,
   biggest button) sits at the very bottom-right itself, the two would
   otherwise overlap directly. */
/* Real default is hidden now (main.js's `if (DEBUG)` branch switches this
   to `flex` explicitly) — see #debug-hud's own comment on why display:none
   moved here instead of staying JS-only. */
#debug-controls {
    display: none;
    position: absolute;
    right: 10px;
    bottom: 85px;
    flex-direction: column;
    gap: 6px;
    z-index: 20;
}

#debug-controls button {
    font-family: "Courier New", monospace;
    font-size: 10.5px;
    color: rgba(220, 230, 210, 0.85);
    background: rgba(20, 16, 12, 0.7);
    border: 1px solid rgba(220, 230, 210, 0.3);
    border-radius: 6px;
    padding: 6px 10px;
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
}

#debug-controls button:active {
    background: rgba(220, 230, 210, 0.2);
}

/* No narrow-phone override block needed anymore — every size in #top-hud
   and its sections above is already clamp()-based, so it scales down
   continuously with the viewport instead of needing a breakpoint snap.
   Verified at 320/360/375px below. */

/* The old fixed-px + breakpoint version of this bar clipped SLING off
   the right edge below ~416px (a real playtester report, not
   hypothetical). Rebuilt with clamp()-based gap/padding/circle sizes
   above instead (same approach as #top-hud) — scales continuously
   rather than snapping at two breakpoints, and re-verified clipping-free
   at 320/340/360/375/420px besides.*/

/* --- Short phones: #start-menu-buttons' `top` (see its own comment —
   computed from the title, always active, no breakpoint) already keeps
   the button column clear of the title at every height. All this
   breakpoint does is shrink the column's OWN footprint on short screens
   (tighter gap, smaller button art/text) so all 4 buttons still fit
   above the bottom edge instead of the last one running off — a fit
   concern, not a collision concern. --- */
@media (max-height: 850px) {
    #start-menu-buttons {
        gap: 5px;
    }
    #start-menu-play-btn img {
        width: min(48vw, 200px);
    }
    #start-menu-howto-btn img,
    #start-menu-records-btn img,
    #start-menu-investigation-btn img {
        width: min(34vw, 150px);
    }
}
