/* ============================================================================
   MOBILE PROTOTYPE — portrait phone layout (v2.5x)
   ----------------------------------------------------------------------------
   First-pass layout for testing on a phone. Reflows the EXISTING desktop panels
   (no new render code) into three zones:

     FROZEN TOP     play area (#dice-panel) + a one-line battle log
     SCROLLABLE     players -> board & decks -> identity -> full battle log
     FROZEN BOTTOM  your inventory (#hand-panel)

   Everything here is scoped to `html.mobile` (set by mobile.js on a phone) AND a
   portrait, coarse-pointer media query, so it can never touch the desktop UI.
   ========================================================================== */

@media (max-width: 820px) and (pointer: coarse) {
  html.mobile, html.mobile body { height: 100%; margin: 0; overflow: hidden; }
  /* Disable the browser's double-tap-to-zoom gesture (iOS Safari ignores
     user-scalable=no, but honours touch-action for the double-tap zoom). This
     stops an accidental double-tap on a card/button from zooming the whole page.
     Pinch-to-zoom is unaffected. */
  html.mobile, html.mobile body,
  html.mobile button, html.mobile a, html.mobile .hand-card,
  html.mobile .card, html.mobile .mobile-card-link, html.mobile img {
    touch-action: manipulation;
  }

  /* The menu/room screens stay as normal flow; only the in-game screen is
     restructured. When the game screen is active we take over the viewport with a
     fixed-height flex column. On the MENU we must NOT lock the height, or a menu
     taller than the screen (e.g. with the High scores button) gets cut off with
     no way to scroll to the bottom. So the fixed height applies only in-game. */
  html.mobile.in-game #app { height: 100dvh; display: flex; flex-direction: column; }
  /* Menu: allow the page to grow and scroll so every button is reachable. The
     base stylesheet pins html/body to height:100% + overflow:hidden (needed
     in-game); off the game screen we release that so the document scrolls.
     v5.0.75: added overscroll-behavior: contain, matching the same fix already
     applied to the in-game scroll pane (.play-area-scroll, below) for the same
     reason - a fast flick was reported to produce a choppy "recoil" once the
     scroll reached its end. Without this, iOS Safari's rubber-band overscroll
     at the top/bottom edge can chain into the fixed elements sitting behind
     <html> (the background art, the header), which is the documented cause of
     exactly this kind of stutter/snap-back on a fast scroll. contain stops the
     bounce from propagating past this scroller. NOTE: this couldn't be
     reproduced in this environment's headless touch emulation (it has no real
     momentum/rubber-band physics at all - see the scroll trajectory sampled in
     testing, a flat jump with no bounce), so this fix targets the
     best-documented, already-precedented cause rather than a live repro here;
     please confirm on a real phone. */
  html.mobile:not(.in-game) { height: auto; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
  /* Both axes must be visible: `overflow-y: visible` with `overflow-x: hidden`
     (inherited from the in-game rule above) computes to `auto` per the CSS spec,
     which turns <body> into a phantom scroll box and breaks position:sticky heads
     (e.g. the compendium / rarity "Back to Main Menu" bar). Full `visible` releases
     the scroll to the page so those sticky heads pin to the viewport top. */
  html.mobile:not(.in-game) body { height: auto; min-height: 100%; overflow: visible; }
  html.mobile:not(.in-game) #app {
    height: auto;
    min-height: 100dvh;
    display: block;
    padding-bottom: max(env(safe-area-inset-bottom, 0px), 24px);
  }
  html.mobile:not(.in-game) #lobby-screen,
  html.mobile:not(.in-game) #room-screen {
    justify-content: flex-start;   /* don't vertically-center a too-tall menu (that hides the ends) */
    overflow-y: visible;           /* the page scrolls, not just this box */
  }

  /* ---- Compact main menu: tighten vertical spacing so the whole menu (both
     cards + How to play + High scores + any "room not found" error) fits on a
     phone without scrolling. Uses clamp()/dvh-aware sizing so it also fits short
     desktop windows. ---- */
  /* Direct request: the intro eyes art (#menu-intro) sat flush against this
     4px padding-top with no notch/Dynamic-Island clearance, so on a real
     phone with one, the safe-area cutout physically obscured a horizontal
     slice across the top of the artwork - a hard "cutoff" line, not a CSS
     clipping bug (the viewport meta already has viewport-fit=cover, so
     content can render fully UNDER that cutout unless it's explicitly given
     room). max() keeps the existing tight 4px on any device that reports no
     inset (env() resolves to 0 there, so max(4px, 0px) is still just 4px -
     no change on a non-notched phone), and only grows past that on a device
     that actually has one. */
  html.mobile:not(.in-game) #app { padding-top: max(4px, env(safe-area-inset-top, 0px)); }
  html.mobile:not(.in-game) header { padding: 4px 12px !important; }
  html.mobile:not(.in-game) .menu-art { max-height: 10dvh; width: auto; margin: 0 auto; }
  html.mobile:not(.in-game) .tagline {
    font-size: 0.8rem; margin: 3px 10px 0; line-height: 1.2;
  }
  html.mobile:not(.in-game) #lobby-screen { gap: 6px; padding: 6px 12px; }
  html.mobile:not(.in-game) #lobby-screen .card { padding: 8px 12px; margin: 0; }
  html.mobile:not(.in-game) #lobby-screen .card h2 { margin: 0 0 4px; font-size: 1rem; }
  html.mobile:not(.in-game) #lobby-screen input { padding: 8px 10px; margin: 3px 0; }
  html.mobile:not(.in-game) #lobby-screen .card button { padding: 8px 12px; }
  html.mobile:not(.in-game) #lobby-screen .tutorial-open-btn {
    padding: 8px 12px; margin-top: 5px;
  }
  html.mobile:not(.in-game) #lobby-error { margin: 3px 0; }
  /* "Submit an Issue" button/overlay: same padding bump as the other 4 menu
     buttons above, plus a narrower/shorter overlay card so it comfortably
     fits a phone viewport without needing to scroll for most screen sizes. */
  html.mobile:not(.in-game) .menu-secondary-row { margin-top: 10px; }
  /* Direct request: stack all 6 main-menu buttons vertically on mobile
     instead of the desktop flex-wrap rows, which pack them 2-per-row on a
     phone-width screen (cramped, uneven tap targets). Column + full-width
     items turns both rows into one consistent single-file list; the two
     rows stay as separate elements (unchanged markup/margin-top spacing
     between them) so this is layout-only.
     Direct follow-up request: line these buttons up width-wise with the
     Create a Game / Join a Game cards above (#lobby-screen .card), which are
     capped at var(--lobby-card-w) (clamp(240px, 15vw, 400px) - effectively a
     flat 240px at any phone width, since 15vw stays well under that floor).
     The first pass stretched each button to the FULL row width instead
     (align-items: stretch, no cap), which is #lobby-screen's whole content
     width minus its own padding - visibly wider than the capped cards
     sitting right above them. align-items: center (instead of stretch) +
     the SAME max-width cap on each button reproduces exactly how the cards
     themselves end up centered at that width (#lobby-screen's own
     align-items: center chain), so both button rows now line up edge-to-edge
     with the cards.
     Direct follow-up-follow-up request (previewed both options live first,
     user picked this one): group the 4 "explore" buttons and the 2 "other"
     buttons into their own bordered/background blocks, same treatment as the
     Create/Join .card panels above them, each with its own h2.menu-group-title
     ("Explore the Dungeon" / "Other" - see index.html). The row itself is now
     the "card": width 100%, capped at var(--lobby-card-w) (reusing the exact
     same variable the real .card elements use, not a re-derived value, per
     this project's own convention), with the SAME mobile padding
     (8px 12px) and background/border/radius #lobby-screen .card gets. Buttons
     inside now fill 100% of that inner content box - no max-width of their
     own needed, since the OUTER row is what's capped at var(--lobby-card-w)
     - which makes every button's rendered width identical to Create Room/
     Join Room's, "including create room and join room" per the request,
     without hardcoding a second width value anywhere. */
  html.mobile:not(.in-game) .menu-actions-row,
  html.mobile:not(.in-game) .menu-secondary-row {
    flex-direction: column;
    align-items: center;
    width: 100%;
    max-width: var(--lobby-card-w);
    margin-left: auto;
    margin-right: auto;
    background: var(--panel);
    border: 1px solid #33303e;
    border-radius: 10px;
    padding: 8px 12px;
    gap: 8px;
  }
  html.mobile:not(.in-game) .menu-group-title {
    display: block;
    width: 100%;
    text-align: center;
    margin: 0 0 4px;
    font-size: 1rem;
  }
  html.mobile:not(.in-game) .menu-actions-row button,
  html.mobile:not(.in-game) .menu-secondary-row button {
    width: 100%;
    max-width: none;
    flex: 0 0 auto;
  }
  html.mobile:not(.in-game) .report-issue-btn { padding: 8px 12px; }
  html.mobile .issue-report-card { max-width: 92vw; padding: 14px 16px; }

  /* The "pick your own emoji" <select> (see .emoji-picker in the base stylesheet)
     is roughly half as wide on mobile - the desktop 57px was left with a lot of
     dead space around the single emoji glyph on a phone-sized seat row. Applies
     everywhere the picker can appear on mobile (the room-screen seat list AND,
     once a game starts, the in-game player-info area), not just the lobby.
     Padding is trimmed along with the width (not just the width alone) so the
     glyph and the custom arrow both still have real room - the base rule's 5px
     left / 16px right padding was sized for the full 57px box; halving the box
     without also trimming padding would clip the glyph, not just shrink the box.
     NOTE: this dev environment's headless Chromium has no color-emoji font (see
     project testing notes) - it falls back to smaller monochrome glyphs, which
     understates how much room a real color-emoji font (Apple/Segoe UI/Noto)
     actually needs. Kept deliberately more generous than the bare minimum this
     environment's fallback glyphs would need, for that reason. */
  html.mobile .emoji-picker {
    width: 34px;
    padding: 6px 12px 6px 3px;
  }

  /* ---- In-game: a fixed play area on top, a fixed inventory on the bottom, and
     everything else physically moved (by mobile.js) into #mobile-scroll in the
     right order. No display:contents / flex order — Safari-safe. ---- */
  /* Hide the app title header while in a game so the frozen play area owns the
     very top of the screen (mobile.js toggles html.in-game; avoids :has() for
     older iOS). */
  html.mobile.in-game > body header,
  html.mobile.in-game #app > header { display: none; }
  html.mobile.in-game #app > .menu-intro { display: none; }
  html.mobile #game-screen:not(.hidden) {
    display: block;
    height: 100dvh; position: relative;
    overflow: hidden; /* the scroll happens inside #mobile-scroll */
  }
  /* Neutralise the desktop grid wrappers without display:contents (which Safari
     mishandles in flex ordering). They just become plain blocks; the panels get
     moved out of them into #mobile-scroll anyway. */
  html.mobile #game-screen .game-grid,
  html.mobile #game-screen .game-left-col,
  html.mobile #game-screen .game-right-col,
  html.mobile #game-screen .game-top-row,
  html.mobile #game-screen .game-mid-row {
    display: block; margin: 0; padding: 0; gap: 0; min-height: 0; flex: none;
  }

  /* ===== FROZEN TOP: the play area ===== */
  html.mobile #game-screen .play-area {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 40;
    margin: 0;
    /* Clear the status bar / notch robustly: offset the whole box down by the
       safe-area inset AND pad its content, with a max() floor so there's still
       clearance even if a browser reports a 0 inset. The background fills up
       behind the inset so the bar area matches the panel. */
    padding-top: max(env(safe-area-inset-top, 0px), 12px);
    /* Hold a STABLE height so the panel (and everything below it, which is offset by
       --mtop = this panel's measured height) stops jumping between prompt types. A
       44dvh floor stops tight text-only prompts ("It's X's turn", a health-loss
       line, a face up/down note) from collapsing the panel, while the 52dvh cap plus
       the per-prompt size limits below keep the big prompts (dice roll, event/action
       card art) from ballooning much past it - so the height only ever drifts a few
       dvh instead of leaping. */
    min-height: calc(42dvh + env(safe-area-inset-top, 0px));
    max-height: calc(48dvh + env(safe-area-inset-top, 0px));
    /* This box is now a STATIC frame — it never scrolls itself (see
       .play-area-scroll below). It only clips its scrolling child to the
       rounded corners; it does NOT also carry overflow-y:auto, because a
       box-shadow border (including the "Your Turn" gold border in style.css)
       painted on the SAME element that scrolls is where iOS Safari loses/tears
       the border at the top or bottom edge once you scroll. Keeping the border
       on this never-scrolling box means it always paints correctly. */
    overflow: hidden;
    border-radius: 0 0 12px 12px;
    border-bottom: 2px solid #000;
    box-shadow: 0 6px 14px rgba(0,0,0,0.5), 0 0 0 1px rgba(176,69,62,0.4);
    background: var(--panel);
  }
  /* The actual scrolling pane, one level inside the static frame above. Owns the
     overflow, the touch-scroll momentum, and the horizontal/bottom padding that
     used to live on .play-area itself. mobile.js (ensurePlayAreaScroll) moves
     the play area's real content in here once, on page load. */
  html.mobile #game-screen .play-area-scroll {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* iOS Safari will NOT touch-scroll a position:fixed overflow box without momentum
       scrolling turned on — the "arrow says there's more but it won't scroll" bug an
       iPhone 13 hit on the extended turn controls. The sibling #mobile-scroll already
       carries this. overscroll-behavior:contain stops the drag from being swallowed
       by the height-locked body behind it. */
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding-left: 10px; padding-right: 10px; padding-bottom: 10px;
  }

  /* ===== Game chat (mobile): a small bubble next to the sound-settings gear, at
     the same top-right corner (per direct request - it used to sit absolute
     against .play-area's own right edge, vertically centered, which put it
     directly over the turn-choice buttons and cost it taps - see below).
     Tapping it opens the same #chat-panel markup as PC, confined to the Play
     Area's own box exactly like the PC version - see the
     positionMobileChatPanel() JS call below for how that's kept in sync.

     position:fixed, anchored to the viewport like the gear, and reparented
     onto document.body by layoutMobile() (mobile.js) - the SAME pattern
     already used for .audio-controls/#chat-panel/#rotate-portrait, and for
     the SAME reason: left nested inside #dice-panel, this bubble would only
     ever compete for stacking within #dice-panel's own stacking context
     (#dice-panel is position:fixed + z-index:40, so it establishes one), and
     that is exactly what silently ate every tap on a real phone. #turn-controls
     (position:relative, inside #play-area-scroll, itself a later-appended
     sibling of the bubble under #dice-panel) participates in that SAME
     stacking context and comes later in tree order, so on the "choose only
     one" turn screen its own box - invisible at that pixel, just container
     padding, which is why the bubble still visually rendered on top - won
     every hit-test over the bubble underneath it. Confirmed live: a real
     touchscreen tap landed on #turn-controls, not the bubble, in exactly that
     state; a plain mouse click (no real touch->click suppression) "worked"
     regardless, which is why this never showed up against a desktop-style
     click test. Reparenting onto document.body removes the bubble from
     #dice-panel's stacking context entirely, matching the gear it now sits
     beside. */
  html.mobile #game-screen #chat-bar { display: none; }
  /* The Dungeon Rooms panel's "Rules" button (style.css .rules-quick-btn, PC)
     is redundant on mobile - #mobile-rules-btn (below) already covers it in
     the old chat-bubble spot, and the panel-title copy doesn't have the room
     for two floated buttons at mobile widths. Direct request: PC only. */
  html.mobile .rules-quick-btn { display: none; }
  /* .in-game, not "#game-screen .chat-bubble": layoutMobile() reparents this
     bubble onto document.body (see mobile.js), so it is NOT a descendant of
     #game-screen any more and a descendant selector would silently stop
     matching - which is exactly what happened the first time this was tried,
     falling back to style.css's base ".chat-bubble { display: none; }" and
     hiding the bubble outright (caught live: getBoundingClientRect() came
     back a literal 0x0, computed position "static"). .in-game is a class on
     <html> itself (mobile.js toggles it), so it matches regardless of where
     in the DOM the element actually lives - the same pattern .audio-controls
     already used correctly, just below. */
  html.mobile.in-game .chat-bubble {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    /* v6.0.24 (direct request): moved off the top-right corner (now
       #mobile-rules-btn's spot, right below) to sit just above the player's
       own HP/dice status strip instead - easier to reach with a thumb when
       holding the phone. `top` here is only a FALLBACK for before that strip
       has a real rect to measure (e.g. the instant the game screen first
       mounts, or while spectating, when #inv-status stays empty) - the real
       position is computed live every layoutMobile() tick by
       positionMobileChatBubble() in mobile.js, which overwrites this via
       inline style once #inv-status is actually laid out. Offset well below
       the rules button's own 8px so the two can never overlap during that
       fallback window. */
    top: calc(env(safe-area-inset-top, 0px) + 56px);
    right: calc(env(safe-area-inset-right, 0px) + 8px);
    z-index: 55;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.2);
    font-size: 1.1rem;
    line-height: 1;
    padding: 0;
    /* #chat-bubble is a <button> itself (unlike the gear, whose OWN button is
       reset via ".audio-controls button { all: unset; }" inside a plain div
       wrapper that carries the actual position), so it inherits the generic
       `button { margin-right: 8px; margin-top: 4px; }` reset from style.css.
       For a position:fixed box, a nonzero margin insets the border box from
       the specified top/right offset - caught live via getBoundingClientRect
       vs getComputedStyle disagreeing by exactly this margin (rect 4px lower
       and 8px further left than the CSS top/right values said it should be),
       throwing off both the vertical alignment with the gear and the
       horizontal gap the gear's own left-offset math assumes. Zeroed out. */
    margin: 0;
  }
  /* "Rules" quick-access (v6.0.24) - takes over the chat bubble's OLD
     top-right corner spot verbatim (same size/shape/chrome), just a book
     instead of a speech bubble and no unread-glow behavior to mirror. */
  html.mobile.in-game .mobile-rules-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 8px);
    right: calc(env(safe-area-inset-right, 0px) + 8px);
    z-index: 55;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.2);
    font-size: 1.1rem;
    line-height: 1;
    padding: 0;
    margin: 0;
  }
  /* Persistent unread glow - by request this does NOT self-clear like the PC
     bar's one-shot flash; only actually opening the chat (openChatPanel in
     app.js) clears the .unread class. */
  html.mobile.in-game .chat-bubble.unread {
    background: rgba(217, 164, 65, 0.9);
    border-color: rgba(217, 164, 65, 0.9);
    box-shadow: 0 0 10px rgba(217, 164, 65, 0.7);
  }
  /* Confined to the Play Area's own box on mobile too, matching PC - no inset:0
     here; top/left/width/height are all set live by positionMobileChatPanel()
     in mobile.js to track #dice-panel's own rect every layout tick, since
     #chat-panel is reparented onto document.body (see layoutMobile() in
     mobile.js) to escape #dice-panel's stacking context - #dice-panel and
     #hand-panel are both position:fixed with the same z-index:40, and without
     the reparent #hand-panel (a later DOM sibling) would paint over the panel
     even at a higher z-index, because #dice-panel confines any nested
     descendant's z-index to compete only within its own stacking context.
     .in-game, not "#game-screen .chat-panel": same reparent-breaks-descendant-
     selector defect as the chat bubble above - #chat-panel is no longer inside
     #game-screen once reparented, so this must key off <html>'s own .in-game
     class instead. This one was silently wrong rather than silently invisible:
     the selector's mismatch fell back to style.css's desktop rule (position:
     absolute; z-index: 70; border-radius: 10px uniform), and since JS still set
     top/left/width/height inline the panel LOOKED right at a glance (those four
     properties fully constrain the box regardless of absolute vs fixed with no
     page scroll involved) - only the corner radius was actually off. */
  html.mobile.in-game .chat-panel {
    position: fixed;
    z-index: 250;
    border-radius: 0 0 12px 12px;
  }
  /* The ESC/T hotkey reminder is PC-only (see setupChatUI in app.js - neither
     key is wired on mobile), so hide the note entirely here rather than show
     instructions for keys that don't do anything on a phone. Its mobile
     counterpart (direct request: "a similar tip text... but changed for
     mobile") keeps the Enter-closes/Send-keeps-open half of the same
     message, just without the hotkey sentence - see .chat-panel-hint-mobile
     in index.html/style.css. */
  html.mobile .chat-panel-hint { display: none; }
  html.mobile .chat-panel-hint-mobile { display: block; }

  /* On mobile the Play-Area panel (#dice-panel) is the SINGLE scroll container; the turn
     controls just grow inside it. The desktop base rule caps #turn-controls at 50% height
     with its own overflow-y:auto, which on a phone made a nested scroll box that Android
     couldn't reliably pan — so an open Pickpocket/Destroy form (target/card selects + the
     Confirm button) couldn't be scrolled into view. Let it grow; the pane does the scrolling
     (updateTurnScrollCue already puts the "more below" cue on #dice-panel on mobile). */
  html.mobile #game-screen #turn-controls {
    max-height: none;
    overflow-y: visible;
  }
  /* An OBVIOUS "there's more below - scroll" cue for the mobile Play Area: when the
     player's options overflow the capped panel, a bouncing down-chevron sticks to the
     bottom edge, and it hides once scrolled to the bottom. updateTurnScrollCue toggles
     is-scrollable / at-scroll-bottom on the scrolling pane (#play-area-scroll) on mobile.
     NOT overlaid on top of the last strip of content (that used a negative top margin to
     add zero scroll height, but left a sliver of whatever was underneath - a button's own
     colour - visibly peeking past the chevron's gradient fade, since the fade only
     partially covers what it sits on top of). Takes its own real space instead, so it's
     genuinely the last thing in the scroll flow with nothing behind or past it.
     v5.0.75: was a SINGLE ::after carrying both the solid background block AND the "⌄"
     glyph, with the bounce animation on that one element - so the whole background block
     visibly bounced along with the arrow, per request. Split into two pseudo-elements
     sharing the same sticky position: ::before is now the static background (no
     animation) and ::after is just the arrow glyph, which keeps the bounce. Height also
     cut from 38px to 32px - "only thick enough to contain the full range of the down
     arrow movement" - still keeps the same 10px reach into the scroller's own bottom
     padding (bottom: -10px unchanged, see below) that fixed the peek-through bug above,
     just with less unused headroom above that reach for the (larger, 1.4rem) mobile
     glyph's own line-height plus its 2px bounce. */
  html.mobile #game-screen .play-area-scroll.is-scrollable::before {
    content: "";
    position: sticky;
    bottom: -10px;   /* reaches down into the scroller's own 10px bottom padding (see
                        .play-area-scroll's padding-bottom) so its solid background covers
                        that zone too - otherwise the padding was a gap the LAST real button
                        could still peek a sliver through, below the chevron but still inside
                        the rounded, clipped play-area box. */
    align-self: stretch;
    flex: 0 0 auto;
    display: block;
    height: 32px;
    pointer-events: none;
    background: var(--panel);
    opacity: 1;
    transition: opacity 0.2s ease;
    z-index: 2;
  }
  html.mobile #game-screen .play-area-scroll.is-scrollable::after {
    content: "⌄";
    position: sticky;
    bottom: -10px;
    align-self: stretch;
    flex: 0 0 auto;
    height: 32px;
    line-height: 26px;
    text-align: center;
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--accent2);
    pointer-events: none;
    opacity: 1;
    transition: opacity 0.2s ease;
    animation: turn-scroll-nudge 1.4s ease-in-out infinite;
    z-index: 2;
  }
  html.mobile #game-screen .play-area-scroll.at-scroll-bottom::before,
  html.mobile #game-screen .play-area-scroll.at-scroll-bottom::after { opacity: 0; }
  html.mobile #game-screen .play-area-scroll.at-scroll-bottom::after { animation: none; }
  /* Keep the "Play Area" label + YOUR TURN badge at the top of the panel. It is
     NOT sticky — a sticky header made the stage message scroll up underneath it
     and get clipped. The play area just scrolls as one piece. */
  html.mobile #game-screen .play-area .play-area-head {
    /* relative (not sticky) so the centred YOUR TURN badge positions against the
       head; a sticky header previously caused the stage message to clip. */
    position: relative;
    background: var(--panel);
    padding-bottom: 8px;
  }
  /* Now that the panel holds a stable 44dvh floor, CENTRE the stage content in it so
     short text-only prompts sit in the middle of the panel (expanded, not crammed at
     the top). overflow stays visible and the panel itself scrolls (overflow-y:auto),
     so a rare tall message still can't be clipped the way the desktop
     overflow:hidden box clipped it. */
  html.mobile #game-screen .play-area #play-stage {
    justify-content: center;
    overflow: visible;
    min-height: 0;
    padding-top: 12px;
  }
  html.mobile #game-screen .play-area .stage-message {
    overflow-wrap: anywhere;
    overflow: visible;
    padding-top: 6px;
    line-height: 1.25;
  }
  /* The weapon card art (dice-weapon-art, max-height 33vh) is taller than its
     flex container's shrunk height on a phone, so — centred in that short box — it
     overflowed UPWARD and covered the attack prompt + "N damage incoming" caption.
     Let the dice row take its natural height (so the art can't spill over the text),
     stack from the top, and cap the art so message + caption + art all fit. The
     play area scrolls if a long message ever makes it taller than the pane. */
  html.mobile #game-screen .play-area .dice-roll-area {
    flex: 0 0 auto;
    justify-content: flex-start;
  }
  html.mobile #game-screen .play-area .dice-and-weapon { max-height: none; }
  /* Cap the weapon card art in the attack-roll prompt so the roll fits inside the
     stable panel height instead of pushing it toward the ceiling. */
  html.mobile #game-screen .play-area .dice-weapon-art { max-height: 18vh; min-height: 0; }
  /* The rolled dice are the tallest single element in the roll; keep them modest on
     the narrow panel (the base is also lowered in setDiceScale). */
  html.mobile #game-screen .play-area .die-roll { max-width: 64px; max-height: 64px; }
  /* Health-loss flavour ("6 -> 4" with a die glyph) has no mobile override on desktop
     (2rem text + a 56px die), which made the health-loss prompt taller than a plain
     line. Bring it down so it sits in the same band as the other text prompts. */
  html.mobile #game-screen .play-area .hp-flavor { font-size: 1.4rem; margin-top: 6px; gap: 8px; }
  html.mobile #game-screen .play-area .hp-flavor .hp-die-glyph { width: 38px; height: 38px; }

  /* Once the player's turn buttons are up after an attack roll, the roll's card art +
     dice are just taking space and pushing the (clipped) narration off the top of the
     capped play area. Drop them the moment #turn-controls shows a button — the roll has
     finished (buttons only appear after the dice settle), and the caption text + the
     turn prompt remain. The dice still animate during the roll itself, when there are no
     buttons yet. :has() is a modern enhancement; without it the roll just stays visible
     (the pre-existing behaviour). */
  html.mobile #game-screen .play-area:has(#turn-controls button) .dice-and-weapon {
    display: none;
  }

  /* ===== SCROLLABLE MIDDLE: the flat container mobile.js fills in order ===== */
  html.mobile #mobile-scroll {
    position: absolute;
    /* Start BELOW the fixed play area (so the scrollbar doesn't run behind the tall top
       panel — the original "shorten the scrollbar" ask), but extend all the way DOWN to
       the screen bottom, i.e. BEHIND the fixed inventory. That's load-bearing for the
       transparent inventory: the game content has to scroll UNDER it for the see-through
       to show anything — otherwise the transparent panel just sits over the flat app
       background and reads as a solid fill (the "transparency stopped working" bug). The
       padding-bottom keeps the last content reachable ABOVE the inventory even though the
       scroll layer runs under it. */
    top: calc(var(--mtop, 46dvh) + 8px);
    left: 0; right: 0;
    bottom: 0;
    padding-bottom: calc(var(--mbot, 120px) + 16px);
    overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  html.mobile #mobile-scroll > .panel,
  html.mobile #mobile-scroll > .players-panel,
  html.mobile #mobile-scroll > .board-panel,
  html.mobile #mobile-scroll > .identity-panel,
  html.mobile #mobile-scroll > .log-panel {
    position: static;
    width: auto;
    margin: 8px 8px 0;
    max-height: none;
    height: auto;
    overflow: visible;
    display: block;
  }

  /* Sound settings: a single gear pinned to the bottom-right corner on the MENUS.
     The settings panel opens above it on tap. Fixed to the viewport so it never
     scrolls over the inventory cards. It sits above the frozen inventory's right
     edge, where there are no cards (inventory cards start from the left). */
  html.mobile .audio-controls {
    position: fixed;
    right: calc(env(safe-area-inset-right, 0px) + 10px);
    bottom: calc(var(--mbot, 120px) + 10px);
    left: auto; top: auto;
    z-index: 50;
    width: max-content;
    margin: 0;
    background: rgba(20, 18, 26, 0.9);
    flex-direction: row-reverse; /* volume panel appears IN LINE with (left of) the gear */
    align-items: center;
  }
  /* Originally a BUG fix (reported live) for overflow caused by the volume slider,
     which used to sit in this panel alongside the mute toggles: content-driven
     width (max-content above, no cap) and right-anchored meant the panel grew
     LEFT with no limit, and the gear + toggles + slider + (in-game) the relocated
     End Game button all on one row could add up to 300-370px, well past a
     320-390px phone's left edge. Direct request removed the slider from mobile
     entirely (buildControls() in audio.js only adds it when !isPhone; a real
     phone already has hardware volume controls, so fine-grained on-screen volume
     wasn't pulling its weight) - mute toggles + a real device's own volume
     buttons/silent switch cover it instead. That single change shrinks the
     panel's worst case (gear + 2 toggle buttons, or + the End Game button
     in-game) to well under half of what the slider alone used to need, so
     overflow is no longer a realistic risk - but the cap costs nothing to keep
     as a safety net (same right-offset each context anchors with: 10px menu,
     56px in-game, see below), and .audio-panel's flex-wrap just below still
     gives the End Game button somewhere to go if it ever needed to. */
  html.mobile .audio-controls.audio-open {
    max-width: calc(100vw - env(safe-area-inset-right, 0px) - 10px - 8px);
  }
  html.mobile.in-game .audio-controls.audio-open {
    max-width: calc(100vw - env(safe-area-inset-right, 0px) - 56px - 8px);
  }
  /* IN A GAME, the gear moves to the TOP-RIGHT corner of the play area instead of
     the bottom-right. The play area (#dice-panel) is frozen at the top of the
     screen on mobile, so a viewport top-right pin lands in its top-right corner —
     out of the way of the centred "YOUR TURN" badge and the left-aligned title,
     and clear of the frozen inventory at the bottom. Menus keep the bottom-right
     placement above (this override only applies while html.in-game is set — see
     layoutMobile in mobile.js). row-reverse already opens the panel leftward, so
     at the top-right it grows down-and-left, staying on-screen.
     Bumped further left (direct request) to make room for the chat bubble,
     which now sits at the plain top-right corner just to the gear's right -
     same top offset AND same explicit 40x40 size (see :not(.audio-open)
     below) as the bubble, so the two align and match exactly. The 56px
     right-offset is (bubble's own 8px right-offset + its 40px width) + an
     8px gap - independent of the gear's own width, so it stays correct
     regardless of how wide the collapsed gear pill is. */
  html.mobile.in-game .audio-controls {
    top: calc(env(safe-area-inset-top, 0px) + 8px);
    right: calc(env(safe-area-inset-right, 0px) + 56px);
    bottom: auto;
  }
  /* Force the collapsed gear pill to the EXACT same 40x40 circle as the chat
     bubble beside it (direct request: "exact same size, perfectly aligned").
     Left alone, this pill's size is content-driven - the 28px gear button
     plus its 6px/8px padding plus its 1px border naturally comes out to
     ~46x42, visibly different from the bubble's fixed 38px-then-40px circle.
     Reducing the padding to a uniform 6px on all sides make a 28px button
     fit an exact 40x40 box (28 + 6 + 6 = 40) with no leftover space to
     misalign, so no justify-content override is needed. Scoped to
     :not(.audio-open) so the expanded state (gear + volume slider + End
     Game button) is completely unaffected and keeps growing to fit its
     content exactly as before. */
  html.mobile.in-game .audio-controls:not(.audio-open) {
    width: 40px;
    height: 40px;
    padding: 6px;
  }
  html.mobile .audio-controls .audio-panel {
    /* The sound controls stay on one row; the relocated End Game button wraps onto
       its own line UNDER them (see below), so the settings window reads:
       [🎵 🔊 volume] / [End game]. */
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-end;
    margin-bottom: 0;
    margin-right: 6px;
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
    row-gap: 8px;
  }

  /* Host's End Game button now lives inside the gear settings panel (mobile.js moves
     it there). Any copy still in the board panel's title is hidden so it only shows
     in the settings window. */
  html.mobile #mobile-scroll > .board-panel .host-end-btn { display: none !important; }
  /* Give the button back a normal look (the .audio-controls button reset makes every
     button a round 28px icon). It sits IN LINE with the music/sound controls, over
     to the LEFT, and is kept small so it fits easily and can't be fat-fingered by
     accident: the settings window reads [End game] ...... [🎵 🔊 volume]. */
  html.mobile .audio-panel .host-end-btn.mobile-endgame-in-settings {
    all: revert;
    display: inline-block;
    flex: 0 0 auto;
    order: -1;             /* to the LEFT of the sound controls */
    width: auto;
    margin: 0 auto 0 0;    /* margin-right:auto pushes it to the far left of the row */
    padding: 4px 10px;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    text-align: center;
    color: #ffd9d5;
    background: #5c2320;
    border: 1px solid var(--accent);
    border-radius: 7px;
    cursor: pointer;
  }
  html.mobile .audio-panel .host-end-btn.mobile-endgame-in-settings:hover { background: var(--accent); color: #fff; }

  /* Identity pane: on desktop it sizes to its flex parent via container queries,
     which collapses to 0 height in the mobile block-flow scroll area. Give it an
     explicit auto height and a fixed art size. On mobile the health + attack dice
     are removed from this pane ENTIRELY (they're already mirrored in the inventory
     status strip over the cards) - only the identity card shows, centred. */
  html.mobile #mobile-scroll > .identity-panel {
    --id-card-h: 120px;
  }
  html.mobile #mobile-scroll > .identity-panel .identity-body {
    container-type: normal;
    flex: 0 0 auto;
    min-height: 0;
    height: auto;
    flex-wrap: wrap;
    padding: 4px 0 8px;
    justify-content: center; /* centre the lone identity card now the dice are gone */
  }
  html.mobile #mobile-scroll > .identity-panel .identity-art {
    height: 120px;
  }
  /* Drop the health die + attack die from the Identity pane on mobile (kept on desktop). */
  html.mobile .identity-panel .identity-stats { display: none; }

  /* #5: make the dungeon board spaces as large as the space beside the loot/event
     decks allows. The board keeps its row layout (decks on the side, board filling
     the rest); give it a generous height on mobile so the SVG scales up instead of
     staying at the 200px desktop minimum. */
  html.mobile #mobile-scroll > .board-panel .board-body {
    align-items: flex-start;
  }
  html.mobile #mobile-scroll > .board-panel .board-square-wrap {
    min-height: 260px;
    height: 56vw;            /* scale with screen width, capped below */
    max-height: 340px;
  }
  html.mobile #mobile-scroll > .board-panel .board-square-wrap .board-svg {
    width: 100%;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
  }
  /* Keep the deck column compact so the board gets the width. */
  html.mobile #mobile-scroll > .board-panel .board-side-col {
    flex-basis: 92px;
  }
  html.mobile #mobile-scroll > .board-panel .deck-back { height: 78px; }

  /* Prompts and popups show the real card art at a small, phone-fit size (not a
     title-only chip). The art stays tappable for a full-size preview. This covers
     the event / card-use banner in the play-area stage and the card images inside
     overlay prompts. */
  html.mobile #dice-panel #stage-event .hoverable-card-art,
  html.mobile #dice-panel #stage-event .card-art {
    display: block;
    /* Scale the art to the screen (min of a height and a width fraction, so it grows on
       big phones and shrinks on small ones) while still leaving room for the prompt's
       text. min-height:0 lets it shrink; the play area keeps its own 48dvh cap, so the
       art can never push the panel past its max space (it scrolls instead). */
    max-height: min(26dvh, 52vw);
    min-height: 0;
    width: auto;
    max-width: 72%;
    margin: 5px auto;
    border-radius: 10px;
    cursor: pointer;
  }
  /* Event reveal art: the name/effect lines are gone, so the card art fills more of the play area
     on mobile too (scoped to .event-art-fill so the card-use banner art keeps its smaller size). */
  html.mobile #dice-panel #stage-event img.event-art-fill {
    max-height: min(40dvh, 82vw);
    max-width: 92%;
  }
  /* Full-screen overlay prompts (reaction / obtain-a-card / choice): the card art
     scales with the screen too, capped so the art + the controls below it still fit.
     Covers both the obtain card (inside a .hand-card) and the reaction panel's art.
     direct request fix: this rendered much smaller than its own max-height allowed
     (e.g. a weapon-attack reaction icon measured ~65px tall against a 213px cap) -
     the shared #overlay-panels .card .reaction-art base rule (style.css) sets
     `flex: 0 1 auto` on the SAME element, and that property isn't overridden here
     (this rule only ever set max-height/width/margin/etc, never `flex` itself), so
     the shrink-enabled flex-basis:auto negotiation was still in effect and, for a
     replaced element like an <img>, that computes a much larger hypothetical main
     size than the real cap and then aggressively shrinks it back down - even with
     plenty of vertical room actually available. Explicit `flex: 0 0 auto` disables
     that shrink entirely, so the image sizes purely off max-height/max-width/aspect
     ratio like a normal block image. `.card`'s own overflow-y:auto (style.css) is
     the existing last-resort safety net if a genuinely cramped case can't fit. */
  html.mobile #overlay-panels .hand-card .card-art,
  html.mobile #overlay-panels .hand-card .card-art-back,
  html.mobile #overlay-panels .reaction-panel .card-art,
  html.mobile #overlay-panels .reaction-panel .hoverable-card-art {
    display: block;
    flex: 0 0 auto;
    max-height: min(32dvh, 60vw);
    min-height: 0;
    width: auto;
    max-width: 80%;
    margin: 0 auto 4px;
    border-radius: 8px;
    cursor: pointer;
  }

  /* #2: play-area prompt card rows (e.g. "Arrange your starting inventory") — lay
     the cards out so their Flip buttons stay inside the prompt box instead of
     spilling through it. Each card becomes a compact vertical cell: title chip +
     button, wrapping across the row. */
  html.mobile #dice-panel .card-option-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: center;
    align-items: stretch;      /* all cards equal height so the row bottom is uniform */
  }
  html.mobile #dice-panel .card-option-row .hand-card {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    max-width: 32%;        /* three across on one row, no wrap for the usual 3 cards */
    margin: 0;
    padding: 5px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    overflow: visible;
  }
  html.mobile #dice-panel .card-option-row .hand-card strong {
    font-size: 0.72rem;
    text-align: center;
    overflow-wrap: anywhere;
  }
  html.mobile #dice-panel .card-option-row .rearrange-btn {
    width: 100%;
    padding: 6px 4px;
    font-size: 0.75rem;
    white-space: nowrap;
  }
  /* The arrange/confirm panel stacks vertically so the "Ready" button always
     flows below the card row, even if the cards wrap to two lines. These override
     the high-specificity desktop #starting-orientation-panel rules. */
  html.mobile #overlay-panels #starting-orientation-panel {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }
  html.mobile #overlay-panels #starting-orientation-panel .card-option-row {
    flex: 0 0 auto;
    flex-wrap: wrap;
    align-items: stretch;
    width: 100%;
  }
  html.mobile #overlay-panels #starting-orientation-panel .card-option-row .hand-card {
    flex: 1 1 0;
    min-width: 0;
    max-width: 33%;
    justify-content: flex-start;
  }
  html.mobile #overlay-panels #starting-orientation-panel .card-option-row .hand-card .card-art,
  html.mobile #overlay-panels #starting-orientation-panel .card-option-row .hand-card .card-art-back {
    flex: 0 0 auto;
    height: auto;
    max-height: 20dvh;
    width: auto;
    max-width: 100%;
    margin: 0 auto 4px;
    cursor: pointer;
  }
  html.mobile #overlay-panels #starting-orientation-panel .card-option-row .hand-card .rearrange-btn {
    margin-top: 5px;
    width: 100%;
    padding: 6px 4px;
    font-size: 0.72rem;
    white-space: nowrap;
  }
  html.mobile #overlay-panels #starting-orientation-panel > button {
    display: block;
    width: 100%;
    flex: 0 0 auto;
    margin: 14px auto 0;
    padding: 12px;
    font-size: 0.95rem;
    white-space: nowrap;
    position: static;
  }
  html.mobile #overlay-panels .reaction-panel > button,
  html.mobile #overlay-panels .card > button {
    display: block;
    width: 100%;
    clear: both;
    margin: 14px auto 0;
    padding: 12px;
    font-size: 0.95rem;
    white-space: nowrap;
    position: static;
  }

  /* Player rows: give them room to breathe with tappable card thumbnails. */
  html.mobile .players-panel .player-row { padding: 8px 4px; font-size: 0.95rem; }
  html.mobile .players-panel { container-type: normal; }

  /* Board: let the SVG size to the phone width. */
  html.mobile .board-panel .board-square-wrap { max-width: 100%; }

  /* ===== FROZEN BOTTOM: your inventory ===== */
  /* NB: selector is intentionally parent-independent (`#hand-panel` on its own, not
     `#game-screen #hand-panel`). The panel is normally reparented under #game-screen,
     but if it ever lands elsewhere the old descendant selector silently dropped ALL of
     this — the panel then fell back to the desktop `.panel` look: OPAQUE var(--panel)
     fill, a full 4-side border, all-corners radius, in normal flow. That is exactly the
     "Your Inventory is no longer transparent" regression. Keying off `html.mobile` +
     `#hand-panel` alone makes the transparent, frozen-bottom treatment stick no matter
     where the panel sits. (#hand-panel is hidden off the game screen, so this is safe.) */
  html.mobile #hand-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* FULL SCREEN WIDTH, not shrink-to-content - the panel used to be only as wide
       as the current hand needed (width: fit-content; max-width: 92vw), which made
       the status strip below the cards (.inv-status - see its own comment) resize
       and shift as cards were gained/lost, since ITS box was anchored to the
       panel's own hand-size-dependent width. Requested explicitly: the HP/dice
       strip should be full width and never move regardless of hand size. The card
       row itself (.hand-row) keeps rendering at its own natural size inside this
       now-wider box - it isn't stretched, and the extra space around it stays
       pointer-events:none (see the scroll-through re-enable list below), so a
       touch there still passes through to the board underneath, same as before. */
    width: auto;
    z-index: 40;
    margin: 0;
    /* Allow the inventory to grow enough to show a tall dice-assignment card
       (die badge + taller buttons + dropdown + title). If it still can't fit on a
       short phone, it scrolls vertically rather than clipping the card's bottom. */
    max-height: 48dvh;
    overflow-x: auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: 12px 12px 0 0;
    /* No frame at all: clear the desktop `.panel` box border AND the old hairline top edge +
       top shadow, so the cards simply float over the scrolling content with nothing framing them. */
    border: 0;
    box-shadow: none;
    /* Transparent panel so the scrollable game content shows through the gaps
       around the cards (each card keeps its own solid background, so cards stay
       readable — only the empty panel area becomes see-through). `!important` because
       something at runtime was filling it back to the grey `.panel` colour right after
       entering a game ("appeared transparent, then immediately went to filled grey") —
       this pins the transparency so no inline style, state class, or later rule can
       override it. */
    background: transparent !important;
    /* Sides: 10px inset for the card row (see .hand-row's own padding, which now
       carries this instead - .inv-status cancels it back out with a negative margin
       so ITS box still reaches the true screen edges). No bottom padding here any
       more either - .inv-status (now the panel's true bottom-most, full-width edge)
       owns the safe-area inset itself, same reasoning as the old "flush against the
       true bottom" note that used to live on this rule. */
    padding: 8px 0 0;
  }
  /* NOT a left-handed override any more (there used to be one here setting
     left:auto/right:0 to flip which edge the shrink-to-fit panel hugged) - #hand-panel
     is unconditionally left:0;right:0 now (see the base rule above), full width
     regardless of handedness, and that override was actively HARMFUL to keep: for a
     `position:fixed` box, setting only ONE of left/right non-auto (with no explicit
     width) makes the browser shrink-wrap the box to its content instead of stretching
     it edge to edge - so `left:auto; right:0` alone silently undid the full-width fix
     in left-handed mode specifically. Caught live (a left-handed screenshot showed the
     status strip back to a narrow, content-sized box on the right, not spanning the
     screen) before this ever shipped - removed outright rather than "fixed" by adding
     `left: 0` back in, since a rule that (once corrected) does nothing different from
     the base rule is just dead weight. .hand-row's own left/right-handed card-alignment
     rules are unaffected - they key off `html.left-handed` elsewhere, not this. */
  /* Spectating is a different state entirely (the player is dead - no hand to manage), and
     wants to read as a normal, comfortably wide panel regardless of how narrow the hand-row/
     inv-status happen to be sized - without this, the shrink-to-fit width above would size the
     WHOLE panel down to whatever's currently visible, squeezing the spectating card's text. */
  html.mobile .spectating-pane { width: min(94vw, 420px); }
  /* SCROLL-THROUGH: the fixed inventory panel is full-width and transparent, so its empty gaps
     (to the left/right of the cards - whichever side, so this is handedness-agnostic - AND now
     the gaps between/around the cards themselves again, since .hand-row went back to having no
     background of its own) used to EAT touch-drags and the game content behind couldn't be
     scrolled from there. Make the transparent panel background click/scroll-through
     (pointer-events:none) and re-enable it only on the actual interactive pieces - the cards and
     the status strip - so a drag in a gap scrolls the content behind while taps on cards still
     work. .hand-row itself is NOT in the re-enable list on purpose: it's transparent again, so its
     own area (the gaps between/around cards) should scroll through too, same as it always did
     before it briefly had a solid background. */
  html.mobile #hand-panel { pointer-events: none; }
  html.mobile #hand-panel .hand-card,
  html.mobile #hand-panel .inv-status,
  html.mobile #hand-panel .spectating-pane { pointer-events: auto; }
  /* The Spectating card's collapse (✕) button is mobile-only (see style.css,
     which hides it by default) — this is the frozen, always-visible inventory
     strip's precious real estate on mobile, so being able to shrink the card
     down to just Leave Room matters here; desktop has no such constraint. */
  html.mobile .spectating-pane .spectating-close { display: flex; }
  /* Inventory order on mobile: the "Your Inventory" title is hidden entirely; the cards float on
     top, with the status strip (health mirror + dice xN) BELOW them. #hand-panel is a flex column. */
  html.mobile #hand-panel .hand-panel-head { display: none; }
  html.mobile #hand-panel .hand-row { order: 1; }
  html.mobile #hand-panel .inv-status { order: 2; }
  /* The status strip sits below the cards on its own dark (opaque), self-contained panel — a
     full border and rounded corners on all sides now that the card row above it is transparent
     again (no longer a merged single panel with a shared edge to avoid double-bordering). It
     still sits flush against the row above with zero gap (margin:0, and the row's own
     padding-bottom is 0), so the cards' bottom edges stay attached to this box even though the
     row itself has no background of its own anymore. The HP bar and dice-count indicator, INSIDE
     the strip, keep their fixed sizing regardless of hand size: .inv-hp is a fixed width (not
     flex:1 1 auto), so it never stretches to fill a wider strip, and the dice indicator sits right
     after it.
     BOX ITSELF: #hand-panel is `display:flex; flex-direction:column` (style.css), and its
     default `align-items:stretch` cross-stretches every child to the panel's width unless the
     child opts out. An earlier fix opted this box OUT (`align-self: flex-start`) so it wouldn't
     silently stretch to #hand-panel's then-hand-size-dependent width (a wide hand -> a wider
     #hand-panel -> a wider, emptier box - reported live as "the health bar bounding box is
     flexible"). Now #hand-panel itself is a fixed full-screen width (see its own comment), so
     the fix is the opposite: this box explicitly STAYS stretched (width: 100%) so it always
     reaches both edges, and #hand-panel's own left/right padding was moved onto .hand-row (see
     that rule) so THIS box - not the padding - is what actually touches the true screen edges.
     Confirmed live via a 1-card vs. 6-card hand: identical box width and position both times. */
  html.mobile #hand-panel .inv-status {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0; /* no gap - sits flush against the card row above */
    width: 100%;
    /* #hand-panel no longer contributes a bottom safe-area inset (see its own comment) since
       this box, not the panel's padding, is now the true bottom-most, edge-to-edge element -
       so it carries that inset itself, same clearance as before on devices with a home
       indicator (env(...) resolves to 0 everywhere else, same as always). */
    padding: 6px 9px calc(6px + env(safe-area-inset-bottom, 0px));
    background: #16151b;
    border: 1px solid #33303e;
    border-radius: 8px 8px 0 0; /* only the top corners show anything at the true screen edge */
  }
  html.mobile #hand-panel .inv-status .inv-hp {
    /* STRETCHES to fill the strip (flex: 1 1 auto), with the fixed-size .inv-dice
       indicator (flex: 0 0 auto, see below) trailing after it — so the HP bar
       itself reaches the full screen width, not just the .inv-status box around
       it. This used to be intentionally FIXED width (min(180px, 65vw), not
       flex:1) specifically so the label+bar's rendered size stayed constant
       regardless of strip width — but the user explicitly asked for the bar
       itself (not just its containing strip) to stretch full width and stay
       static there, i.e. "static" means independent of hand size (which it
       already is, since .inv-status's own width has been hand-size-independent
       since an earlier round), not independent of screen width. Confirmed live
       via a 1-card vs. 6-card hand: identical bar width both times, and both
       equal to the full available strip width. */
    flex: 1 1 auto;
    min-width: 0; /* a flex item's default min-width:auto would otherwise refuse to
      shrink below its content's natural width, which can clip the bar/label on
      very narrow screens - 0 lets it size purely from flex-basis/flex-grow. */
  }
  html.mobile #hand-panel .inv-status .inv-hp-label {
    display: block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
    line-height: 1.15;
  }
  html.mobile #hand-panel .inv-status .player-hp-bar { height: 7px; margin: 3px 0 0; }
  html.mobile #hand-panel .inv-status .inv-dice {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 3px;
  }
  html.mobile #hand-panel .inv-status .inv-dice .stat-die {
    width: 22px;
    height: 22px;
    display: block;
  }
  html.mobile #hand-panel .inv-status .inv-dice-mult {
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--accent2);
  }
  html.mobile #game-screen .hand-row {
    display: flex; flex-wrap: nowrap; gap: 6px; overflow-x: auto;
    /* BOTTOM-aligned, not top: this is what actually makes every card's art land on
       one shared line flush against the HP strip below. Every card's art is now the
       LAST thing in the card (see .hand-card-top-slot / slotWrap in app.js - all
       optional content, buttons and titles and hints alike, lives ABOVE the art, so
       nothing is ever inserted below it), so a card's own bottom edge IS its art's
       bottom edge; flex-end lining up every card's bottom edge on this row's shared
       baseline is therefore all that's needed to line up every art bottom too, for
       any mix of optional content any one card happens to carry - confirmed live
       across the two originally-reported cases (a bare weapon vs. a Heal card with
       its Use button on top; a face-up vs. a face-down card in a discard prompt) plus
       a 6-card stress case mixing both. */
    align-items: flex-end;
    /* Fully transparent — no background/border of its own. Each card already carries its own
       background (see .hand-card / .card-art), so the game board shows through in the gaps
       between and around the cards, same as it always did before the row briefly had a shared
       panel background. Only the status strip below (.inv-status) keeps a solid dark background
       now. padding-bottom is 0 so a card's own bottom edge sits flush against that strip, with
       no transparent sliver in between. Left/right is 18px, not the old 8px - #hand-panel used
       to also contribute 10px of its own side padding on top of this row's, but that padding
       moved off #hand-panel entirely so .inv-status (below) can reach the true screen edges
       (see both of their comments); this row makes up the difference itself so the cards keep
       the exact same 18px inset from the screen edge they always had. */
    padding: 8px 18px 0;
  }
  /* LEFT-HANDED: cards cluster against the RIGHT edge instead of the left (mirroring the
     right-handed default above). This used to fall out for free from #hand-panel itself being
     a narrow, shrink-to-fit box pinned to the right edge (see the removed left-handed override,
     and the "Your-Inventory strip side" comment below) - cards stayed in left-to-right order
     WITHIN that box, but the box's own position made the whole cluster hug the screen's right
     edge. Now that #hand-panel is unconditionally full width (see its own comment), that
     mechanism is gone, so this row needs to do the mirroring itself: justify-content: flex-end
     bunches the (still left-to-right ordered) cards against the row's right edge when they don't
     fill the row. A hand long enough to need the row's own horizontal scroll is unaffected either
     way - overflow takes over regardless of justify-content. Confirmed live: a 1-2 card
     left-handed hand renders flush against the right edge, matching the pre-full-width look. */
  html.mobile.left-handed #game-screen .hand-row { justify-content: flex-end; }
  /* An OBVIOUS "there's more - scroll" cue for the mobile hand strip, mirroring the Play
     Area's down-chevron (see .play-area-scroll.is-scrollable::after above) but sideways: a
     forced discard can briefly hold 6 cards (the normal cap is 5 - see presentGame's hand
     render), and on a narrow phone the 6th ran past #hand-panel's 92vw cap with nothing to
     show it was still there one swipe away - reported live as the hand "being cut off by the
     boundary box". updateHandScrollCue (app.js) toggles is-h-scrollable / at-h-scroll-end on
     #hand-row itself, the real horizontal scroller. Genuinely the row's own last flex item
     (not overlaid via negative margins, for the same reason the vertical chevron isn't - see
     that comment), so it never blocks the last card's own tap target; `position: sticky`
     then keeps it pinned to the visible right edge while scrolling. pointer-events:none so a
     tap that lands on it while it's sitting over a card still reaches that card underneath.
     z-index:4, not 2: a forced discard is exactly when EVERY pickable card carries
     .needs-selection, which is `position:relative; z-index:3` (its own glow ring, see
     style.css) - built and screenshotted at z-index:2 first and the chevron vanished
     completely, painted under the last glowing card despite being later in the DOM. 4 clears
     that (and any ordinary card's implicit auto stacking) with a little headroom. */
  html.mobile #game-screen .hand-row.is-h-scrollable::after {
    content: "›";
    position: sticky;
    right: 0;
    align-self: center;
    flex: 0 0 auto;
    width: 26px;
    height: 44px;
    line-height: 44px;
    text-align: center;
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--accent2);
    pointer-events: none;
    background: linear-gradient(to right, transparent, #0f0e14 55%);
    opacity: 1;
    transition: opacity 0.2s ease;
    animation: turn-scroll-nudge-h 1.4s ease-in-out infinite;
    z-index: 4;
  }
  html.mobile #game-screen .hand-row.at-h-scroll-end::after { opacity: 0; animation: none; }
  @keyframes turn-scroll-nudge-h {
    0%, 100% { transform: translateX(0); }
    50% { transform: translateX(3px); }
  }
  /* Your-Inventory strip side (v5.0.11): the strip sits on the OPPOSITE side to the handedness like
     the other card elements — handled at the PANEL level now (see html.mobile #hand-panel /
     html.mobile.left-handed #hand-panel above: the whole panel, shrunk to its content's width,
     pins to the left or right edge directly), not per-card margins. */
  html.mobile #game-screen .hand-row .hand-card:first-child { margin-left: 0; }
  /* Each inventory card is a compact fixed-width cell sized so 5-6 fit across the
     strip (with horizontal scroll as backup). Art fills the width; title and the
     Flip/Swap button sit below without wrapping. */
  html.mobile #game-screen .hand-row .hand-card {
    flex: 0 0 auto;
    width: 57px;
    min-width: 57px;
    margin: 0;
    padding: 3px;
    font-size: 0.56rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    overflow: visible;
  }
  html.mobile #game-screen .hand-row .hand-card .card-art {
    width: 100%;
    max-width: none;
    height: auto;
    border-radius: 5px;
    margin: 0;
  }
  html.mobile #game-screen .hand-row .hand-card .hand-card-title,
  html.mobile #game-screen .hand-row .hand-card > strong {
    font-size: 0.55rem !important;
    line-height: 1.1;
    text-align: center;
    overflow-wrap: anywhere;
    max-width: 100%;
  }
  /* The Flip / Swap button must never wrap "Flip" into "Fli / p". Let the card
     grow a touch and keep the label on one line.
     margin: 0 is required here (not just cosmetic): slotWrap nests this button
     inside .hand-card-top-slot now, which is a flex column with
     align-items:center - so it's no longer a DIRECT child of .hand-card and the
     desktop rule `.hand-row .hand-card > .rearrange-btn, .heal-btn { margin: 0
     0 4px }` (a `>` child combinator) no longer matches on mobile. Without an
     explicit reset here, the generic `button { margin-right: 8px; margin-top:
     4px }` rule was still winning the cascade - an ASYMMETRIC margin, which
     flexbox centering applied to the button's full margin box, not just its
     border box, visibly shifting the button ~4px left of the card art below
     it. Caught live via getBoundingClientRect comparison against the art
     (button and art should share the same left/right edges) before shipping. */
  html.mobile #game-screen .hand-row .hand-card .rearrange-btn {
    width: 100%;
    margin: 0;
    padding: 5px 2px;
    font-size: 0.62rem;
    white-space: nowrap;
    line-height: 1;
    min-height: 0;
  }
  html.mobile #game-screen .hand-row .hand-card .heal-btn { width: 100%; margin: 0; white-space: nowrap; font-size: 0.62rem; padding: 5px 2px; line-height: 1; min-height: 0; }
  /* Keep the "NEW" over-hand-limit badge at its original small size on mobile - the
     desktop bump in style.css (request: bigger on PC) would otherwise crowd this
     compact card, which has far less spare width than the desktop one. */
  html.mobile #game-screen .hand-row .hand-card .new-card-banner {
    margin: 0 auto 3px;
    padding: 1px 7px;
    font-size: 0.62rem;
  }
  /* face-down back should still show inside the compact card */
  html.mobile #game-screen .hand-row .hand-card .card-art-back { display: block !important; }
  /* TOP SLOT (see slotWrap's comment in app.js, where this is wrapped in): holds
     EVERYTHING optional a card can carry above its art - a Flip/Use button, a
     face-down title, a select-hint pill - stacked in that order. There's no bottom
     slot any more: the art is always the LAST thing in the card, so with the row's
     own align-items:flex-end (every card's bottom edge on one shared line, flush
     against the HP strip below), every card's ART BOTTOM lands on that same line
     automatically, for any combination of optional content - the top slot is simply
     free to grow taller upward to fit whatever it's carrying; it never pushes the art
     away from the bottom, because nothing is ever placed below the art. (An earlier
     version reserved a FIXED-height slot on each side of the art and matched heights
     across cards instead - that kept arts aligned with each other, but left a dead
     reserved gap below the art on a plain card with no title/hint, so the art didn't
     actually touch the strip. This is simpler and stronger: true zero-gap contact.) */
  html.mobile #game-screen .hand-row .hand-card .hand-card-top-slot {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }
  html.mobile #game-screen .hand-row .hand-card .hand-card-top-slot .hand-card-title { margin: 0; }

  /* Mobile dice assignment: the player taps the ART to assign the first die. */
  /* A subtle hint that the weapon art is tappable to start assigning dice. */
  html.mobile #game-screen .hand-row .hand-card.has-assign:not(.has-dice) .card-art {
    border: 1.5px solid rgba(217,164,65,0.5);
    border-radius: 6px;
    cursor: pointer;
  }

  /* Once a die is on the weapon it becomes a COMPACT controls tile with NO art,
     so it can never balloon and swallow the scroll strip. It widens just enough
     to fit the target dropdown and the Add/Clear buttons, then scrolls. */
  /* Keep the assigning tile the SAME 57px width as a normal inventory card, so the
     strip's column width never changes when dice go on/off a weapon. Everything
     stacks vertically (the tile just gets taller); the target name in the dropdown
     is squished to fit but keeps as many letters as the width allows (the native
     picker still shows full names when tapped). */
  html.mobile #game-screen .hand-row .hand-card.has-dice {
    width: 57px;
    min-width: 57px;
    padding: 3px;
  }
  /* The controls stack vertically (the tile grows taller; the WIDTH stays a normal
     card's 57px). Everything is full width and centred: the global button
     `margin: 4px 8px …` is zeroed so the controls don't shrink off-centre, and
     comfortable tap sizes are kept (height may grow). The card-preview-on-title is
     desktop-only, so on mobile the title is a plain label (no pointer/underline). */
  html.mobile #game-screen .hand-card.has-dice .weapon-assign-title {
    order: 2;                 /* on mobile the title drops to the BOTTOM of the stack */
    min-height: 0;            /* undo the desktop flip-button-aligned band */
    visibility: visible;      /* mobile SHOWS the title (the art is hidden here, so it's the only name) */
    margin-top: 2px;
    cursor: default;
    text-decoration: none;
    text-shadow: none;
  }
  /* The art+controls slot leads on mobile (the title sits below it via order:2).
     With the backdrop art hidden it just wraps the stacked controls. */
  html.mobile #game-screen .hand-card.has-dice .weapon-assign-slot {
    order: 1;
    display: flex;
    flex-direction: column;
    width: 100%;
  }
  /* No backdrop art on the narrow mobile tile, and no scrim gradient - the controls
     just stack on the plain card. */
  html.mobile #game-screen .hand-card.has-dice .weapon-assign-bg { display: none; }
  html.mobile #game-screen .hand-card.has-dice .weapon-assign-compact {
    position: static;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    width: 100%;
    height: auto;
    padding: 0;
    background: none;
  }
  /* PC moved the dice pip to the BOTTOM of the control stack (markup order); on mobile
     keep it at the TOP as before. */
  html.mobile #game-screen .hand-card.has-dice .weapon-assign-compact .weapon-dice-row { order: -1; }
  html.mobile #game-screen .hand-card.has-dice .weapon-assign-btns {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: 100%;
  }
  /* Full width, tiny font, margins zeroed, and the native dropdown ARROW removed
     (appearance:none) so the target name gets the whole width and keeps the most
     letters before it ellipsises. The <select> is still fully tappable - tapping it
     opens the native option list with every player's full name. */
  html.mobile #game-screen .hand-card.has-dice .weapon-target-select {
    width: 100%;
    max-width: 100%;
    margin: 0;
    font-size: 0.6rem;
    padding: 8px 3px;
    min-height: 32px;          /* taller, easier-to-tap dropdown */
    box-sizing: border-box;
    text-align: center;
    text-align-last: center;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background-image: none; /* belt-and-suspenders: no custom arrow graphic either */
  }
  /* Some engines still reserve arrow space via a pseudo — collapse it on mobile. */
  html.mobile #game-screen .hand-card.has-dice .weapon-target-select::-ms-expand { display: none; }
  /* Mobile hides the weapon art behind the controls, so there's nothing for the
     "lift off the art" drop shadow to sit on - remove it from the dice pip, the
     multiplier, both buttons, and the dropdown. */
  html.mobile #game-screen .hand-card.has-dice .weapon-dice-badge,
  html.mobile #game-screen .hand-card.has-dice .weapon-add-die,
  html.mobile #game-screen .hand-card.has-dice .weapon-clear-dice,
  html.mobile #game-screen .hand-card.has-dice .weapon-target-select { box-shadow: none; }
  html.mobile #game-screen .hand-card.has-dice .weapon-dice-mult { text-shadow: none; }
  html.mobile #game-screen .hand-card.has-dice .weapon-add-die,
  html.mobile #game-screen .hand-card.has-dice .weapon-clear-dice {
    width: 100%;
    box-sizing: border-box;
    margin: 0;
    min-height: 34px;          /* a comfortable, deliberate tap target */
    padding: 9px 3px;
    white-space: nowrap;
    line-height: 1;
  }
  html.mobile #game-screen .hand-card.has-dice .weapon-add-die { font-size: 0.68rem; }
  html.mobile #game-screen .hand-card.has-dice .weapon-clear-dice { font-size: 0.56rem; }
  /* Drop the die glyph inside "Add die" on the narrow tile — the label alone fits. */
  html.mobile #game-screen .hand-card.has-dice .weapon-add-die .wadb-die { display: none; }
  html.mobile #game-screen .hand-card.has-dice .weapon-dice-badge {
    width: 30px; height: 30px;
    font-size: 0.95rem;
  }
  /* The weapon title WRAPS across as many centred lines as it needs to show the
     WHOLE name (the base .hand-card-title white-space:nowrap + ellipsis is undone).
     overflow-wrap:anywhere breaks a word only if it can't fit on a line on its own. */
  html.mobile #game-screen .hand-card.has-dice .hand-card-title {
    display: block;
    font-size: 0.52rem !important;
    line-height: 1.1;
    text-align: center;
    color: var(--accent2);
    margin-top: 2px;
    max-width: 100%;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: anywhere;
    word-break: break-word;
  }

  /* The Attack-screen dice POOL (one token per die you own) is smaller on mobile than
     on desktop, so the whole attack menu (prompt + pool + Use Power + Attack + Clear /
     Cancel) fits the capped play-area height without the player having to scroll. */
  html.mobile #game-screen .attack-dice-pool .pool-die { width: 52px; height: 52px; }
  html.mobile #game-screen .attack-dice-pool { gap: 5px; margin: 6px 0 8px; }

  /* Overlay prompts (reaction windows, card claims, "You obtained a card!") are
     rendered as the LAST child of the play area, so they get pushed below the
     stage/dice content into whatever room is left at the bottom — cramped, and the
     card gets squished. On mobile, when a prompt is present, float #overlay-panels
     as a centered overlay filling the play area, with its own backdrop, so the
     prompt is prominent and never squished. :has() is fine here (this is a modern
     enhancement; without it the prompt still shows, just inline as before). */
  html.mobile #overlay-panels { position: relative; z-index: 60; }

  /* Obtained-card prompt: on mobile show only the card's name (no art / no preview
  /* The obtained-card prompt shows the real card art (small, phone-fit) with the
     card's name below it; tap the art for a full-size preview. */
  html.mobile .obtain-card {
    padding: 8px 14px;
  }
  html.mobile .obtain-card strong {
    font-size: 1.05rem;
    color: var(--accent2);
    text-align: center;
  }
  /* A card name that doubles as a tap-to-preview control (used inside prompts
     where the render already emits a <strong> name next to the art). Style it so
     it reads as tappable, with the same magnifier affordance as the chips. */
  /* Inline card names inside prompts (arrange inventory, obtained card, event/
     card-use banners) are PLAIN TEXT that doubles as a tap-to-preview target. They
     get only a subtle dotted underline + small magnifier — never a button's
     background/border/padding. Because this is its own class (not .mobile-card-link),
     there's no chip styling to fight, so titles always look like titles. */
  html.mobile .mobile-card-preview {
    cursor: pointer;
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 3px;
  }
  html.mobile .mobile-card-preview::after {
    content: " 🔍";
    font-size: 0.8em;
    opacity: 0.7;
  }

  /* The shared countdown bars (#play-countdowns) sit between the title and the
     YOUR TURN badge. On a phone the row is tight, so cap the bar width and shrink
     the label so all three (title · bar · YOUR TURN) fit on one line. */
  html.mobile #play-countdowns { max-width: 45%; }
  html.mobile #play-countdowns .play-countdown { max-width: 160px; }
  html.mobile #play-countdowns .play-countdown-text { font-size: 0.62rem; }
  html.mobile #game-screen .play-area .play-area-head { gap: 6px; }
  html.mobile #game-screen .play-area .play-area-head h3 {
    flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  html.mobile #game-screen .play-area .play-area-head #turn-badge { flex: 0 0 auto; }
  /* When a blocking prompt is present, float #overlay-panels as a centered modal
     over the FULL VIEWPORT — not confined to the frozen play area (which is only
     ~52dvh tall and was clipping taller prompts like "You obtained a card!"). The
     prompt sizes to its content and scrolls internally only if it's taller than
     the screen, so the whole thing (title, explanation, buttons) is always
     visible. :has() is a progressive enhancement; the JS-set class is primary. */
  html.mobile.overlay-prompt-active #overlay-panels,
  html.mobile #dice-panel.has-overlay-prompt #overlay-panels,
  html.mobile #dice-panel:has(#overlay-panels > .card:not(.inventory-select-prompt)) #overlay-panels {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: max(env(safe-area-inset-top, 0px), 16px) 14px
             max(env(safe-area-inset-bottom, 0px), 16px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background: rgba(12, 10, 16, 0.82);
    border-radius: 0;
  }
  html.mobile.overlay-prompt-active #overlay-panels > .card,
  html.mobile #dice-panel.has-overlay-prompt #overlay-panels > .card,
  html.mobile #dice-panel:has(#overlay-panels > .card:not(.inventory-select-prompt)) #overlay-panels > .card {
    width: 100%;
    max-width: 400px;
    max-height: calc(100dvh - 32px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    margin: auto;
  }
  /* A reaction (Shield/Mirror/take-the-hit) is still a full blocking modal like any
     other prompt above — EXCEPT the fixed "Your Inventory" strip stays reachable
     underneath it, because Heal is playable at any time and its only control is the
     "Use Heal" button on the inventory card itself (see flagOverlayPrompt in
     mobile.js). Lift the inventory above the modal's backdrop, and reserve room for
     it in the modal's own flex box so the reaction card is never covered by (or
     covering) it. */
  html.mobile.reaction-prompt-active #overlay-panels {
    /* Reserve exactly #hand-panel's real current height (+16px), set live as
       --reaction-inv-reserve by flagOverlayPrompt() in mobile.js - see its own
       comment. Falls back to the old worst-case (#hand-panel's own max-height)
       only if that JS hasn't run yet for some reason. */
    padding-bottom: var(--reaction-inv-reserve, calc(48dvh + 16px));
  }
  html.mobile.reaction-prompt-active #hand-panel {
    z-index: 210; /* above the reaction backdrop's z-index: 200 */
  }
  /* Inventory-selection prompts ("Discard 1 card — tap a glowing card below") must
     NOT be a full-screen modal: the player needs to tap the glowing cards in their
     inventory, which sits below. Render it as a compact, non-blocking banner at the
     top of the play area with no backdrop, so taps pass through to the inventory. */
  html.mobile #dice-panel.has-inventory-select-prompt #overlay-panels {
    position: static;
    inset: auto;
    z-index: auto;
    display: block;
    padding: 0;
    background: none;
    overflow: visible;
  }
  html.mobile #dice-panel.has-inventory-select-prompt #overlay-panels > .card.inventory-select-prompt {
    width: auto;
    max-width: 100%;
    margin: 0 auto 6px;
    padding: 8px 12px;
    text-align: center;
    background: rgba(30, 26, 38, 0.96);
    border: 1px solid var(--accent2);
    border-radius: 10px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  }
  html.mobile #dice-panel.has-inventory-select-prompt #overlay-panels > .card.inventory-select-prompt .prompt-command {
    font-size: 1.05rem;
    margin: 0 0 2px;
  }
  html.mobile #dice-panel.has-inventory-select-prompt #overlay-panels > .card.inventory-select-prompt .prompt-hint {
    font-size: 0.85rem;
    margin: 0;
  }
  /* The obtained-card prompt's card sits at a comfortable, un-squished size. */
  html.mobile #card-orientation-panel .hand-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
  }
  html.mobile #card-orientation-panel .hand-card .card-art,
  html.mobile #card-orientation-panel .hand-card .card-art-back {
    display: block;
    max-height: 22dvh;
    width: auto;
    max-width: 100%;
    margin: 0 auto 4px;
    cursor: pointer;
  }

  /* Hide desktop-only chrome that doesn't fit the phone. */
  html.mobile #game-screen .log-panel h3 { font-size: 0.8rem; }

  /* #5: battle log starts collapsed on mobile (a few lines); tapping the heading
     expands it. The heading text is reworded by mobile.js. */
  html.mobile #mobile-scroll > .log-panel .log-panel h3,
  html.mobile #mobile-scroll > .log-panel h3 {
    cursor: pointer;
    color: var(--accent2);
    text-decoration: underline;
    text-underline-offset: 2px;
  }
  html.mobile #mobile-scroll > .log-panel.mobile-log-collapsed #log-list {
    max-height: 4.6em;      /* ~3 lines */
    overflow: hidden;
  }
  /* Expanded: open to a FIXED depth and DON'T scroll. The log renders newest-first, so the top
     (newest) stays pinned in view and older entries simply flow off the bottom edge (clipped by
     overflow:hidden) rather than the panel scrolling. Depth is 92vh — twice the previous 46vh — so
     an expand shows about double the log. It lives in the page's scroll flow, so the page (not the
     log) scrolls if the panel is taller than the screen. */
  html.mobile #mobile-scroll > .log-panel:not(.mobile-log-collapsed) #log-list {
    max-height: 92vh;
    overflow: hidden;
  }

  /* ===== Mobile tutorial (light pass): the chapter nav is a horizontal
     scroller (desktop v5.0.68 uses a wrapping row instead - mobile keeps its
     own no-wrap scrolling version since it lives above a much narrower
     page), and enlarge tap targets. ===== */
  html.mobile #tutorial-screen .tutorial-main { flex-direction: column !important; gap: 0; }
  html.mobile #tutorial-screen .tutorial-nav {
    flex: 0 0 auto !important;
    width: 100% !important;
    display: flex; flex-direction: row; flex-wrap: nowrap;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    gap: 6px;
    border-right: none;
    border-bottom: 1px solid #2f2b3a;
    padding: 8px;
  }
  html.mobile #tutorial-screen .tut-nav-item {
    flex: 0 0 auto;
    width: auto;
    margin-bottom: 0;
    padding: 10px 14px;         /* bigger tap target */
    font-size: 0.9rem;
    white-space: nowrap;
  }
  /* The desktop small-screen rule hides the chapter title to shrink the sidebar;
     on mobile we WANT the titles back since the nav is now a full-width row. */
  html.mobile #tutorial-screen .tut-nav-title { display: inline !important; }
  /* #1: make the tutorial pages scrollable on a phone. The desktop height chain
     relies on :has() to hide the header (unsupported on older iOS) and on flex
     heights that don't resolve in the restructured mobile layout. Pin the screen
     to the viewport and give the body a bounded, scrollable height. */
  html.mobile #tutorial-screen:not(.hidden) {
    position: fixed;
    inset: 0;
    z-index: 100;                 /* cover the header/menu without needing :has() */
    height: 100dvh !important;
    max-height: 100dvh;
    overflow: hidden;
    padding-top: env(safe-area-inset-top, 0px);
    background: var(--bg);
  }
  html.mobile #tutorial-screen .tutorial-main { flex: 1 1 auto; min-height: 0; }
  /* Lay the page area out as a positioning context so the two nav arrows can pin
     to the bottom corners (prev on the left, next on the right, across from each
     other) with the lesson text scrolling above them. The old `display:block`
     here left the arrows in normal flow, which pushed the "next" arrow off the
     bottom of the screen so it looked like it didn't exist. */
  html.mobile #tutorial-screen .tutorial-page-area {
    flex: 1 1 auto;
    min-height: 0;
    position: relative;
    display: block;
  }
  html.mobile #tutorial-screen .tutorial-body {
    height: 100%;
    max-height: 100%;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* leave room at the bottom for the fixed arrow row so text never hides behind it */
    padding: 16px 16px calc(64px + env(safe-area-inset-bottom, 0px));
    font-size: 1rem;
    line-height: 1.5;
  }
  /* Both arrows: fixed to the bottom, one on each side, always visible. */
  html.mobile #tutorial-screen .tut-side-btn {
    position: absolute;
    bottom: max(env(safe-area-inset-bottom, 0px), 10px);
    width: 64px;
    height: 48px;
    margin: 0;
    z-index: 5;
    font-size: 1.6rem;
    background: #14121a;
    border: 1px solid #45414f;
  }
  html.mobile #tutorial-screen #tutorial-side-prev { left: 12px; }
  html.mobile #tutorial-screen #tutorial-side-next { right: 12px; }
  html.mobile #tutorial-screen .tutorial-topbar { padding: 10px 12px; }
  /* Direct request: on mobile there was no easy way to exit the Rules
     overlay without scrolling all the way through it. This is a second,
     always-visible exit (the footer sits in normal flex flow below
     .tutorial-main, never inside the scrolling .tutorial-body - see
     #tutorial-screen's flex-column layout in style.css - so it's on screen
     the whole time, unlike the chapter content above it). Make it a real
     full-width tap target. */
  html.mobile #tutorial-screen .tutorial-footer {
    flex-direction: column;
    gap: 8px;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
  }
  /* Direct follow-up request: the PC-only "Esc to leave, <- -> to navigate"
     hint (.tutorial-hint) makes no sense on mobile - there's no keyboard to
     press Esc or the arrow keys on. It had been showing anyway (no prior
     mobile override existed for it) in both the main-menu "How To Play" and
     the in-game "Rules" overlay, since both share this same #tutorial-screen
     markup. Removed entirely on mobile, not replaced - the bottom exit
     button right below already covers the "how do I leave" need. */
  html.mobile #tutorial-screen .tutorial-hint { display: none; }
  html.mobile #tutorial-exit-bottom {
    display: block;
    width: 100%;
    padding: 12px;
    font-size: 0.95rem;
  }
  /* v5.0.68: the exit button is now .leave-room-btn (shared with the
     Compendium/Rarity Guide back buttons), which needs no mobile-specific
     override - it already renders fine at its default size there. */
  /* v5.0.70: Stats for Nerds (#rarity-screen) is the exception - its head
     bar also holds the Rarity Guide / Weapon Analysis tab pair
     (#rarity-main-tabs), and the two together (full-size tabs + a
     full-size "← Back to Main Menu" button) run wider than a phone's
     content area, so the button wrapped onto its own row below the tabs
     instead of sitting alongside them. Shrinking the button toward the
     tabs' own compact pill scale (0.8rem / 6px 12px, see .comp-tab) gets
     both back onto one row on real phone widths; flex-wrap stays as a
     safety net (not switched to nowrap) for anything narrower still. */
  html.mobile #rarity-screen .compendium-head { gap: 8px; }
  html.mobile #rarity-screen .compendium-head .leave-room-btn {
    flex: 0 1 auto;
    min-width: 0;
    padding: 6px 10px;
    font-size: 0.72rem;
    white-space: nowrap;
  }
  html.mobile #rarity-screen #rarity-main-tabs { flex: 0 1 auto; min-width: 0; }
  /* ===== Text-fit safety: keep labels inside their boxes on a phone. Buttons
     wrap rather than clip; long single words shrink to fit; the turn-grid keeps
     its buttons readable. `:not(.spectating-close)` - this rule's own #game-screen
     ID selector otherwise beats the small circular close button's plain-class
     `.spectating-close` rule on `height` (id specificity wins regardless of that
     rule's own explicit height:22px), and this rule's `height:auto` + `min-height:
     40px` stretched a 22px-WIDE button to 40px TALL - a visible oval, confirmed
     live on a real iPhone. This treatment is for multi-word text buttons (Use
     Shield, Take the hit, turn-grid actions); a tiny icon-only circular control
     was never meant to be pulled in by the plain element selector. */
  html.mobile #game-screen button:not(.spectating-close),
  html.mobile #tutorial-screen button {
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-word;
    line-height: 1.2;
    min-height: 40px;      /* comfortable tap target */
    height: auto;
  }
  /* The collapsed Spectating bar's Leave Room button asks for a SHORT, compact
     look (see its own .spectating-pane.collapsed .btn-grey rule in style.css),
     but that rule is a plain class selector and loses the min-height:40px above
     to specificity (that rule's #game-screen ID beats any class-only rule) -
     confirmed live: the button kept rendering 40px tall regardless of its own
     reduced padding. #hand-panel is #hand-row's real parent, giving this
     override an ID of its own to actually win. */
  html.mobile #hand-panel .spectating-pane.collapsed .btn-grey {
    min-height: 0;
    height: auto;
  }
  html.mobile #game-screen .turn-grid { display: flex; flex-wrap: wrap; gap: 6px; }
  html.mobile #game-screen .turn-grid button { flex: 1 1 auto; min-width: 30%; font-size: 0.85rem; padding: 8px 6px; }
  html.mobile #game-screen .end-turn-btn { width: 100%; }
  /* Player names / stat lines shouldn't run off the row. */
  html.mobile #game-screen .player-row { overflow: hidden; }
  html.mobile #game-screen .player-row .player-name,
  html.mobile #game-screen .player-name { overflow-wrap: anywhere; }
  /* PC puts a revealed Maker's Maker-Only cards and the inventory thumbnails on
     one row (perm left / thumbs right). A phone row is far too narrow for both,
     so keep them stacked here: Maker-Only cards on their own full-width wrapping
     row above the thumbnails. */
  /* One row, mirrored from the PC layout: the player's normal inventory sits on
     the LEFT and a revealed Maker's Maker-Only cards sit to the RIGHT of it, on
     the same line and at the same size. The markup puts the Maker-Only cards
     first (PC wants them left), so flip the two with `order` rather than moving
     them in the DOM. Both sides wrap, so all three Maker-Only cards still fit. */
  html.mobile .player-row-bottom {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: 6px;
    flex-wrap: wrap;
  }
  /* Player-row cards (v5.0.11.x): the inventory THUMBNAILS and the Maker-Only permanent cards keep
     their original sides — in the Right-handed (default) layout the thumbnails hug the RIGHT (under
     the left-aligned name) and a revealed Maker's Maker-Only cards sit to their LEFT. (Only the
     floating "Your Inventory" strip flips between the two settings; see the hand-row rule above.) */
  html.mobile .player-row-bottom .player-card-thumbs {
    order: 2;
    width: auto;
    flex: 1 1 auto;
    justify-content: flex-end; /* a player's inventory thumbnails align to the RIGHT */
    flex-wrap: wrap;
  }
  /* Player NAME + HP text align OPPOSITE the handedness (cards stay on the handedness side): for the
     right-handed layout the cards are on the right, so the name/HP sit on the LEFT. The turn arrow
     follows the name to the opposite side (see style.css) so it still points across at the player. */
  html.mobile .player-row-name { text-align: left; }
  html.mobile .player-row-meta { justify-content: flex-start; }
  html.mobile .player-row-bottom .player-perm-cards {
    order: 1;
    width: auto;
    flex: 0 0 auto;
    justify-content: flex-start; /* Maker-Only permanent cards align to the LEFT (opposite the thumbnails) */
    flex-wrap: wrap;
  }

  /* LEFT-HANDED orientation (html.left-handed, chosen in the lobby): the MIRROR of the default
     right-handed pane above — name/HP flip to the RIGHT, inventory thumbnails to the LEFT, a revealed
     Maker's Maker-Only cards to the RIGHT, and the floating "Your Inventory" strip to the RIGHT. The
     turn arrow flips too (in style.css). Higher specificity (.mobile.left-handed) lets these win over
     the right-handed defaults without touching them. */
  html.mobile.left-handed .player-row-name { text-align: right; }
  html.mobile.left-handed .player-row-meta { justify-content: flex-end; }
  /* v5.1 (direct request): within that right-flush HP/badges group, the HP text itself
     should be the one flush against the right edge, with the status chips (poisoned/
     knocked out/etc.) sitting to its LEFT - i.e. the badges and the HP text swap places,
     not just the whole group moving as a unit (which the justify-content: flex-end above
     already did). The markup always puts the HP span before the badges span, so pulling
     the badges to the FRONT of source order (order: -1, everything else defaults to
     order: 0 and keeps its place) puts them left of the HP text once flex-end packs the
     pair against the right edge. Scoped to this handedness only - right-handed keeps HP
     first/badges-after, unchanged. */
  html.mobile.left-handed .player-row-meta .status-badges { order: -1; margin-left: 0; margin-right: 8px; }
  html.mobile.left-handed .player-row-bottom .player-card-thumbs { order: 1; justify-content: flex-start; } /* thumbnails LEFT */
  html.mobile.left-handed .player-row-bottom .player-perm-cards { order: 2; justify-content: flex-end; }    /* Maker-Only RIGHT */
  /* Only the floating "Your Inventory" strip flips: left-handed puts it on the RIGHT — handled at
     the PANEL level (html.mobile.left-handed #hand-panel above), not here; the per-card
     margin-left:auto trick this used to do is gone now that the whole panel shrinks to its content's
     width and pins to an edge directly (an auto margin on one card inside an already-shrunk row had
     no extra space to push into anyway). */
  /* A revealed player's identity card ("YOU ARE THE MAKER" / Hero card) mirrors too: right-handed
     puts it on the LEFT (source order), so left-handed flips it to the RIGHT — beside the
     right-aligned name/HP — with the row-main taking the left. This also clears the ▸ turn arrow,
     which sits at the LEFT in left-handed, so the card and arrow can no longer overlap. */
  html.mobile.left-handed .player-row-top { flex-direction: row; }
  html.mobile.left-handed .player-row-top .player-row-main { order: 1; }
  html.mobile.left-handed .player-row-top .player-identity-card { order: 2; }
  /* The mobile card-title chips in the play area wrap instead of clipping. */
  /* The play-area card-title chips already handle their own single-line ellipsis
     (see the .mobile-card-link rule above) — don't force wrapping here. */

  /* Awards / match-stats screen: fit the phone width (no left-right scroll). One
     award per row; the "This game" figures go to 3 columns so their labels aren't
     forced wider than the cell (which used to push the whole card past the screen
     edge). Everything is width-capped so only VERTICAL scrolling is ever needed. */
  html.mobile .victory-card.stats-card {
    width: 94vw;
    max-width: 94vw;
    box-sizing: border-box;
    overflow-x: hidden;
  }
  html.mobile .stats-grid {
    grid-template-columns: 1fr;
    gap: 8px;
  }
  html.mobile .summary-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* Row gap leaves headroom for the rarity banner that pokes above each cell
       (top:-9px); the top margin does the same for the first row. */
    gap: 20px 6px;
    margin-top: 14px;
  }
  /* overflow must stay VISIBLE or the rarity banner (top:-9px) is clipped off the top
     of the cell on mobile. Long values still wrap via overflow-wrap, so nothing spills. */
  html.mobile .summary-cell { padding: 8px 4px; min-width: 0; overflow: visible; }
  html.mobile .summary-value { font-size: 1.05rem; overflow-wrap: anywhere; }
  html.mobile .summary-label { overflow-wrap: anywhere; }
  html.mobile .stats-groups,
  html.mobile .stat-group,
  html.mobile .stat-award,
  html.mobile .game-summary,
  html.mobile .summary-grid {
    max-width: 100%;
    box-sizing: border-box;
    overflow-wrap: anywhere;
  }
}

/* Portrait lock: if the phone is rotated to landscape, ask them to rotate back
   (we intentionally do NOT support landscape in this prototype). */
@media (max-width: 960px) and (orientation: landscape) and (pointer: coarse) {
  html.mobile #rotate-portrait {
    position: fixed; inset: 0; z-index: 200;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center; gap: 10px; padding: 24px;
    background: var(--bg); color: var(--text);
  }
  html.mobile #rotate-portrait .rp-icon {
    width: 54px; height: 90px; border: 4px solid var(--accent2); border-radius: 12px;
    transform: rotate(90deg);
  }
}
#rotate-portrait { display: none; }
