/* ============================================================================
   MOBILE SHELL — viewport-first (owner's design).
   The whole screen is the viewport; the desktop sidebar rides in a pull-out
   drawer on the right, chat in a sheet at the bottom. Palette is the desktop's
   own: warm carved stone on near-black ground, ember accents, gold for value.
   Desktop is untouched: every rule here requires body.msh-on.
   ============================================================================ */

body.msh-on {
    --msh-ground:   #0a0806;
    --msh-panel:    linear-gradient(180deg, #382a22, #2a2118);
    --msh-panel-hi: linear-gradient(180deg, #453427, #2f251a);
    --msh-edge:     #000;
    --msh-edge-warm:#6a4a26;
    --msh-carve-hi: inset 0 1px 0 rgba(255, 200, 120, .12);
    --msh-carve-lo: inset 0 -1px 0 rgba(0, 0, 0, .55);
    --msh-txt:      #f5f0e0;
    --msh-dim:      #b39a78;
    --msh-ember:    #ff981f;
    --msh-gold:     #ffd58a;
    --msh-top:      calc(48px + env(safe-area-inset-top, 0px));
    --msh-drawer-w: min(430px, calc(100vw - 26px));
    /* the chat sheet's height: the sheet, the handle that rides its edge and the
       scrim that stops at it all read this one number. The home-indicator inset is
       part of it, so the sheet reaches the screen edge and its content still clears
       the indicator (the chatbox pads itself by the same amount). */
    --msh-sheet-h:  calc(min(46vh, 380px) + env(safe-area-inset-bottom, 0px));
    background: var(--msh-ground);
    overflow: hidden;
}

/* ── compact topbar ── */
body.msh-on .topbar {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 30;
    height: var(--msh-top);
    padding: env(safe-area-inset-top, 0px) 10px 0 10px;
    display: flex; align-items: center;
    gap: 6px;
}
body.msh-on .topbar-left { flex: 0 0 auto; min-width: 0; }
body.msh-on .topbar-title { font-size: 0; gap: 0; padding: 0; }
body.msh-on .topbar-title .topbar-logo { width: 30px; height: 30px; margin: 0; }
body.msh-on .tcl-btn { display: none; }
/* ── item search ──
   The bar has no room for a standing field: the coin, orb and book chips already
   need more than the screen is wide. So the search sits as a magnifier, and the
   field slides out of the stone over the whole bar when it is tapped. */
body.msh-on .global-search-wrap { flex: 0 0 auto; min-width: 0; max-width: none; }
body.msh-on .global-search-wrap > .global-search-toggle { display: inline-flex; }
body.msh-on .global-search-wrap > .global-search-input { display: none; }

body.msh-on .global-search-wrap.gs-open {
    position: absolute;
    top: env(safe-area-inset-top, 0px); bottom: 0; left: 10px; right: 10px;
    z-index: 2;
    display: flex; align-items: center; gap: 6px;
    background: var(--msh-panel);
    animation: msh-search-open .18s ease-out;
}
body.msh-on .global-search-wrap.gs-open > .global-search-toggle { display: none; }
body.msh-on .global-search-wrap.gs-open > .global-search-close { display: inline-flex; }
body.msh-on .global-search-wrap.gs-open > .global-search-input {
    display: block;
    flex: 1 1 auto; min-width: 0;
    background: #12100c;
    border-color: var(--msh-edge);
    border-radius: 7px;
}
body.msh-on .global-search-wrap.gs-open > .global-search-input:focus {
    border-color: var(--msh-edge-warm);
    box-shadow: inset 0 1px 3px rgba(0,0,0,.5), 0 0 8px rgba(255,152,31,.22);
}
@keyframes msh-search-open {
    from { opacity: 0; transform: translateX(14px); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    body.msh-on .global-search-wrap.gs-open { animation: none; }
}

body.msh-on .global-search-dropdown {
    position: fixed;
    top: var(--msh-top); left: 8px; right: 8px;
    width: auto;
    max-height: min(60vh, 420px);
}
body.msh-on .topbar-stats {
    flex: 1 1 auto; min-width: 0;
    display: flex; align-items: center; justify-content: flex-end;
    gap: 5px;
}
body.msh-on .topbar-stat.tb-chip { padding: 3px 8px; font-size: 12px; }
body.msh-on .topbar-char-btn .stat-val { display: none; }
body.msh-on .topbar-char-btn { padding: 4px 6px; }

/* ============================================================================
   LAYOUT — the world owns the screen
   ============================================================================ */
body.msh-on .game { display: block; }
body.msh-on .game-body {
    display: block;
    position: fixed;
    top: var(--msh-top); left: 0; right: 0; bottom: 0;
    height: auto;
}
body.msh-on .left-col {
    width: 100% !important;
    height: 100% !important;
    max-width: none !important;
    max-height: none !important;
    min-height: 0 !important;
    display: flex; flex-direction: column;
}
body.msh-on .left-col .viewport {
    flex: 1 1 auto !important;
    height: auto !important;
    max-height: none !important;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
body.msh-on .chatbox-splitter,
body.msh-on .timer-hub,
body.msh-on .rl-bar { display: none !important; }

/* ============================================================================
   THE DRAWER — the desktop sidebar, pulled out from the right.
   Pure CSS over the existing node: nothing moves in the DOM, so every id,
   listener and refresh path keeps working, and growing past the breakpoint
   simply drops these rules.
   ============================================================================ */
body.msh-on .sidebar {
    /* the design-system's entrance animation fill-holds transform at identity,
       which silently overrides the drawer's translateX — animations outrank
       transitions, so the drawer could never close */
    animation: none;
    position: fixed;
    top: var(--msh-top); right: 0; bottom: 0;
    width: var(--msh-drawer-w);
    max-width: none;
    z-index: 40;
    transform: translateX(100%);
    transition: transform .24s cubic-bezier(.3, .8, .3, 1);
    background: var(--msh-ground);
    border-left: 1px solid var(--msh-edge-warm);
    box-shadow: -14px 0 34px rgba(0, 0, 0, .55);
    display: flex;
}
body.msh-on.msh-drawer-open .sidebar { transform: none; }

/* THE closed-drawer rule. translateX(100%) only moves the pixels off-screen — the element
   stays a live ~10,000px column that every style recalc and every layout pass on the page
   still walks, and the transform transition keeps it rasterized on the GPU permanently.
   content-visibility:hidden tells the engine to skip the subtree for style, layout, paint
   and raster while keeping its state (DOM, listeners, scroll position). The class goes on
   AFTER the slide-out finishes and comes off BEFORE the slide-in starts (mobile-shell.ts),
   so the drawer never visibly empties mid-slide. */
body.msh-on.msh-parked .sidebar { content-visibility: hidden; }
/* Belt to that suspender: looping animations inside the closed drawer stay frozen even in
   the moment between close and park, and in browsers without content-visibility. */
body.msh-on:not(.msh-drawer-open) .sidebar,
body.msh-on:not(.msh-drawer-open) .sidebar *,
body.msh-on:not(.msh-drawer-open) .sidebar *::before,
body.msh-on:not(.msh-drawer-open) .sidebar *::after { animation-play-state: paused !important; }

/* ── one scroll surface (owner's call) ──
   Desktop nests scrollers: the profile hub area, the mini skill grid (480px
   crop) and the content area each scroll on their own. On the phone the whole
   drawer is a single column that scrolls as one — every inner region grows to
   its content and only .sidebar-inner scrolls. */
body.msh-on .sidebar-inner {
    width: 100%; height: 100%;
    display: block;
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}
/* the desktop's quick-access strip duplicates the More grid tile-for-tile —
   in a one-column drawer the grid is right there, so the strip is noise */
body.msh-on .sidebar-top-tabs { display: none !important; }

body.msh-on .sidebar-upper {
    flex: none;
    min-height: 0;
    display: block;
}
body.msh-on .sidebar-body,
body.msh-on .sidebar-skills {
    flex: none;
    height: auto !important;
    max-height: none !important;
    min-height: 0;
    overflow: visible !important;
}
/* the mini skill grid loses its 480px crop while open; the collapse toggle
   still animates through max-height, so only the open state is uncapped */
body.msh-on .ph-skillwrap:not(.collapsed) .ph-skills { max-height: none; }

/* hover tooltips have no hover to end them on touch — a tap opens one and it
   sticks over the drawer until something else is tapped */
@media (pointer: coarse) {
    body.msh-on .sidebar-top-tab::after,
    body.msh-on .sidebar-tab::after,
    body.msh-on [data-tooltip]::after { display: none !important; }
    /* hover-born tooltips: a tap synthesizes mouseenter but never mouseleave,
       so they stick. Long-press tooltips carry .mobile-active and still show —
       that is the deliberate gesture on touch. */
    body.msh-on .game-tooltip:not(.mobile-active) { display: none !important; }
}

/* the desktop's draggable splitter bar has no business on a touch drawer */
body.msh-on .rl-bar-grab, body.msh-on .sidebar-splitter { display: none !important; }

/* methods/members open the left slide-in panel — on a phone it takes the
   drawer's footprint on the opposite side */
body.msh-on .rl-panel {
    width: var(--msh-drawer-w);
    max-width: none;
    top: var(--msh-top);
    z-index: 41;
}

/* ── the pocket handle ── */
.msh-handle {
    position: fixed;
    right: 0; top: 50%;
    transform: translateY(-50%);
    z-index: 42;
    width: 26px; height: 92px;
    border: 1px solid var(--msh-edge);
    border-right: none;
    border-radius: 12px 0 0 12px;
    background: var(--msh-panel);
    box-shadow: var(--msh-carve-hi), var(--msh-carve-lo), -4px 0 12px rgba(0, 0, 0, .45);
    display: grid; place-items: center;
    padding: 0;
    touch-action: none;
    /* transform, never `right`: animating a layout property made the 240ms slide force a
       full-document layout — the drawer's whole column included — on every frame */
    transition: transform .24s cubic-bezier(.3, .8, .3, 1);
}
body.msh-on.msh-drawer-open .msh-handle { transform: translateY(-50%) translateX(calc(-1 * var(--msh-drawer-w))); }
.msh-handle-grip {
    width: 4px; height: 44px;
    border-radius: 2px;
    background: linear-gradient(180deg, rgba(255, 152, 31, .85), rgba(201, 146, 14, .55));
    box-shadow: 0 0 8px rgba(255, 152, 31, .35);
}
.msh-handle:active .msh-handle-grip { box-shadow: 0 0 12px rgba(255, 152, 31, .6); }

/* ── scrim: the world dims behind an open panel; tap it to come back ── */
.msh-scrim {
    position: fixed;
    inset: 0;
    top: var(--msh-top);
    z-index: 38;
    background: rgba(0, 0, 0, .45);
    opacity: 0;
    pointer-events: none;
    transition: opacity .24s ease;
}
body.msh-on.msh-drawer-open .msh-scrim,
body.msh-on.msh-chat-open .msh-scrim { opacity: 1; pointer-events: auto; }
/* .left-col is `position: relative; z-index: 1`, which makes it a stacking context — so the
   chat sheet's own z-index only ranks it against its siblings inside the world column, and
   the whole column (sheet included) still paints under the scrim's z-index 38. The sheet
   ended up dimmed AND untouchable: every tap inside it hit the scrim, which closes it. Stop
   the scrim at the top of the sheet instead. With the drawer open the sheet is shut, so the
   scrim covers the screen as before. */
body.msh-on.msh-chat-open:not(.msh-drawer-open) .msh-scrim { bottom: var(--msh-sheet-h); }

/* ============================================================================
   CHAT — the chatbox as a bottom sheet
   ============================================================================ */
body.msh-on .left-col-bottom {
    display: block;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    height: var(--msh-sheet-h);
    z-index: 40;
    transform: translateY(105%);
    transition: transform .24s cubic-bezier(.3, .8, .3, 1);
    border-top: 1px solid var(--msh-edge-warm);
    box-shadow: 0 -12px 30px rgba(0, 0, 0, .55);
    background: var(--msh-ground);
}
body.msh-on.msh-chat-open .left-col-bottom { transform: none; }
body.msh-on .left-col-bottom .chatbox {
    display: flex !important;
    height: 100%;
    box-sizing: border-box;
    padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* the log is the part that gives way — without min-height:0 a flex child refuses to
   shrink under its content and pushes the tab row off the bottom of the screen */
body.msh-on .left-col-bottom .chatbox-inner { flex: 1 1 auto; min-height: 0; }
body.msh-on .left-col-bottom .chat-input-row,
body.msh-on .left-col-bottom .chatbox-tabs { flex: 0 0 auto; }
body.msh-on .chat-input-row { display: flex !important; }

.msh-chat-handle {
    position: fixed;
    left: 50%; bottom: 0;
    transform: translateX(-50%);
    z-index: 42;
    width: 92px; height: 22px;
    border: 1px solid var(--msh-edge);
    border-bottom: none;
    border-radius: 12px 12px 0 0;
    background: var(--msh-panel);
    box-shadow: var(--msh-carve-hi), 0 -4px 12px rgba(0, 0, 0, .45);
    display: grid; place-items: center;
    padding: 0;
    /* transform for the same reason as the drawer handle */
    transition: transform .24s cubic-bezier(.3, .8, .3, 1);
}
body.msh-on.msh-chat-open .msh-chat-handle { transform: translateX(-50%) translateY(calc(-1 * var(--msh-sheet-h))); }
.msh-handle-grip-h {
    width: 44px; height: 4px;
    border-radius: 2px;
    background: linear-gradient(90deg, rgba(255, 152, 31, .85), rgba(201, 146, 14, .55));
    box-shadow: 0 0 8px rgba(255, 152, 31, .35);
}

/* ============================================================================
   VITALS — health, faith and food on the viewport, combat only.
   The profile hub that carries these on desktop is behind the drawer now.
   ============================================================================ */
.msh-vitals {
    flex: 0 0 auto;
    display: none;
    gap: 8px;
    margin: 6px 8px;
    padding: 7px 10px;
    border: 1px solid var(--msh-edge);
    border-radius: 11px;
    background: var(--msh-panel);
    box-shadow: var(--msh-carve-hi), var(--msh-carve-lo), 0 4px 14px rgba(0, 0, 0, .45);
}
body.msh-on.msh-fighting .msh-vitals { display: flex; }
.msh-vital { flex: 1 1 0; display: flex; align-items: center; gap: 7px; min-width: 0; }
.msh-vital-ic { width: 20px; height: 20px; flex: 0 0 auto; object-fit: contain; }
.msh-vital-track {
    flex: 1 1 auto; min-width: 0;
    height: 12px;
    border-radius: 6px;
    background: rgba(0, 0, 0, .55);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .7), inset 0 0 0 1px rgba(255, 200, 120, .07);
    overflow: hidden;
}
.msh-vital-fill {
    display: block; height: 100%; width: 0;
    border-radius: 6px;
    transition: width .25s ease-out;
}
.msh-vital-hp .msh-vital-fill { background: linear-gradient(180deg, #7fe06a, #3f9e33); }
.msh-vital-faith .msh-vital-fill { background: linear-gradient(180deg, #7fc4ff, #2f7fd0); }
.msh-vital.low .msh-vital-fill { background: linear-gradient(180deg, #ff8a6a, #c02f1f); }
.msh-vital.low .msh-vital-num { color: #ff9a86; }
.msh-vital-num {
    flex: 0 0 auto;
    font-size: 11.5px; font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--msh-txt);
    text-shadow: 0 1px 0 #000;
}
.msh-vital-food {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 5px;
    padding-left: 8px;
    border-left: 1px solid rgba(255, 200, 120, .12);
}
.msh-vital-food.low .msh-vital-num { color: #ff9a86; }

/* ============================================================================
   READABILITY — space, contrast, competition, then size.
   Applies to the drawer's lists and the game's floating windows alike.
   Nothing here edits an icon file or override — display treatment only.
   ============================================================================ */
body.msh-on {
    --iw-icon-tile: 52px;
    --iw-icon-row:  40px;
    --iw-icon-chip: 26px;
}

/* low-res wiki sprites upscale crisply; the 256px base-set art keeps smooth */
body.msh-on img[src*="/wiki/"] {
    image-rendering: pixelated;
    image-rendering: -moz-crisp-edges;
    image-rendering: crisp-edges;
}

/* a lit socket behind list art so silhouettes separate from the dark panel */
/* Flat sockets on the phone: the desktop's radial-gradient-plus-three-shadows treatment,
   multiplied by a hundred list rows, is raster work the phone GPU re-pays on every frame
   of a scroll. A solid plate with a hairline border reads the same at 52px. */
body.msh-on .meth-icon,
body.msh-on .mc-icon {
    position: relative;
    width: 52px; height: 52px;
    border-radius: 9px;
    background: #17130e;
    border: 1px solid rgba(0, 0, 0, .55);
    box-sizing: border-box;
    display: grid; place-items: center;
    flex-shrink: 0;
}
body.msh-on .meth-icon .icon-method,
body.msh-on .mc-icon img {
    width: 38px; height: 38px;
    max-width: 38px; max-height: 38px;
    object-fit: contain;
}

/* rows: one name and one number get the weight */
body.msh-on .method-row,
body.msh-on .method-card {
    gap: 12px;
    padding: 10px 12px;
    min-height: 68px;
}
body.msh-on .meth-name,
body.msh-on .mc-name {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: .1px;
    line-height: 1.25;
}
body.msh-on .mc-xp { font-size: 14px; }
body.msh-on .mc-lvl { font-size: 11px; }
body.msh-on .mc-time,
body.msh-on .mc-bank-qty { font-size: 11px; opacity: .55; }
body.msh-on .mc-play,
body.msh-on .mc-info,
body.msh-on .meth-play {
    min-width: 44px;
    min-height: 44px;
}

/* the skill hub's running-method row wraps so the name owns a line */
body.msh-on .forge-top { flex-wrap: wrap; row-gap: 6px; }
body.msh-on .forge-info { flex: 1 1 100%; min-width: 0; }
body.msh-on .forge-item-name {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}
body.msh-on .sh-v2 .forge-stats { margin-left: 0; }

/* the bank window: five columns so items own a real cell, count on its plate */
body.msh-on .bw-grid {
    grid-template-columns: repeat(5, 1fr);
    /* auto rows ignore the cells' aspect-ratio height inside this scroll container
       (rows collapse to a fraction of the sheet and the cells fan out over each
       other); max-content rows size from the square cell */
    grid-auto-rows: max-content;
    gap: 6px;
    padding: 10px;
}
body.msh-on .bw-grid .bank-item,
body.msh-on .bw-grid .bw-gap {
    aspect-ratio: 1;
    border-radius: 9px;
    background: #17130e;
    border: 1px solid rgba(0, 0, 0, .5);
    box-sizing: border-box;
    display: grid; place-items: center;
    padding: 6px;
}
body.msh-on .bw-grid .bi-qty {
    top: 0; right: 0; left: 0;
    padding: 2px 4px 3px;
    font-size: 11px;
    text-align: right;
    border-radius: 9px 9px 0 0;
    background: linear-gradient(180deg, rgba(8, 6, 4, .92), rgba(8, 6, 4, 0));
    text-shadow: 0 1px 2px #000;
}
body.msh-on .eq-slot { min-width: 60px; min-height: 60px; }

/* the sidebar's 1fr skill grid: minmax(0,1fr) kills the min-content floor the
   non-wrapping total-level row was inflating past the container */
body.msh-on .skill-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 7px;
}
body.msh-on .skill-grid > * { min-width: 0; }
body.msh-on .total-level-cell { white-space: normal; }
body.msh-on .skill-cell { min-height: 66px; }
body.msh-on .skill-cell .icon-skill { width: 40px; height: 40px; }

/* ============================================================================
   TOUCH TARGETS — 44px for actions, 36px for dense filter chips
   ============================================================================ */
body.msh-on .rl-tab,
body.msh-on .rc-panel-tab,
body.msh-on .bw-tab,
body.msh-on .bw-shelf,
body.msh-on .bw-plate-gaps,
body.msh-on .osrs-btn,
body.msh-on .slayer-btn,
body.msh-on .marketplace-browse-toggle-btn,
body.msh-on .method-detail-btn {
    min-height: 44px;
    padding-top: 6px;
    padding-bottom: 6px;
}
body.msh-on input[type="text"],
body.msh-on input[type="search"],
body.msh-on input:not([type]),
body.msh-on select {
    min-height: 44px;
    font-size: 16px;   /* iOS Safari zooms the page for anything under 16px */
    max-width: 100%;
    box-sizing: border-box;
}
body.msh-on .marketplace-category-pill,
body.msh-on .clog-filter-btn,
body.msh-on .quest-filter-btn,
body.msh-on .rl-pill,
body.msh-on .chatbox-tab {
    min-height: 36px;
    padding: 0 12px;
    display: inline-flex;
    align-items: center;
}
body.msh-on .chatbox-tab-add { min-width: 36px; justify-content: center; }
/* the tab strip is a fixed 32px on desktop; touch tabs are 36px and were being clipped
   against the bottom edge of the screen */
body.msh-on .chatbox-tabs { height: auto; min-height: 40px; align-items: center; }
body.msh-on .bw-close,
body.msh-on .chat-foot-btn,
body.msh-on .equip-prayer-btn {
    min-width: 36px;
    min-height: 36px;
}
/* The bar's two chrome buttons are stacked, so they take 30px rather than the 36px a
   lone button gets — two 36px circles would stand taller than the slots beside them. */
body.msh-on .qbar-tool {
    min-width: 30px;
    min-height: 30px;
}
body.msh-on .sidebar-top-tab,
body.msh-on .nav-tile { min-height: 44px; }

/* chip rows scroll sideways instead of spilling past the screen edge */
body.msh-on .chatbox-tabs,
body.msh-on .rl-pills,
body.msh-on .marketplace-category-row {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    gap: 6px;
}
body.msh-on .chatbox-tabs::-webkit-scrollbar,
body.msh-on .rl-pills::-webkit-scrollbar,
body.msh-on .marketplace-category-row::-webkit-scrollbar { display: none; }
body.msh-on .chatbox-tabs > *,
body.msh-on .rl-pills > * { flex: 0 0 auto; }
body.msh-on .chatbox-tabs .chat-foot-actions,
body.msh-on .chatbox-tabs #pause-btn {
    position: sticky;
    right: 0;
    z-index: 1;
    background: linear-gradient(90deg, transparent, #14100a 22%);
    padding-left: 12px;
}
body.msh-on .chatbox-tabs #pause-btn { right: 62px; }
body.msh-on .chat-foot-btn { min-width: 36px; min-height: 36px; }

/* ============================================================================
   THE STAGE — the hero graphic earns the screen it now owns permanently.
   `body.msh-on` prefixed deliberately: desktop rules are `body.rebrand .x`, a
   class-only selector loses the specificity race.
   ============================================================================ */
body.msh-on .vp-center {
    margin: auto 0;
    gap: 14px;
    width: 100%;
    padding: 0 14px;
}
body.msh-on .action-icon-wrap,
body.msh-on .action-icon-wrap:has(.action-icon.monster) {
    width: min(58vw, 250px);
    height: min(58vw, 250px);
    border-width: 3px;
}
body.msh-on .action-icon .icon-action {
    width: min(36vw, 155px);
    height: min(36vw, 155px);
}
body.msh-on .action-icon.monster .icon-action {
    width: min(50vw, 215px);
    height: min(50vw, 215px);
}
body.msh-on .action-skill-name { font-size: 14px; letter-spacing: 2.5px; }
body.msh-on .action-method-name { font-size: 17px; }
body.msh-on .action-xp,
body.msh-on .action-cost { font-size: 12px; }
body.msh-on .monster-hp-wrap { max-width: 300px; }
body.msh-on .monster-hp-label { font-size: 15px; }
body.msh-on .monster-hp-bar { height: 14px; }

/* the quick bar spans the screen instead of overflowing a centred pill */
body.msh-on .qbar {
    left: 8px; right: 8px;
    bottom: calc(26px + env(safe-area-inset-bottom, 0px)); /* clear of the chat handle */
    transform: none;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 7px 8px;
}

/* ============================================================================
   CHARACTER SELECT — a ten-slot list is taller than a short screen; margin:auto
   centres identically when it fits but overflows downward, so scrolling can
   reach every slot (align-items:center + overflow:visible could not).
   ============================================================================ */
body.msh-on .charselect-overlay {
    align-items: flex-start;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 10px;
}
body.msh-on .charselect-overlay > * {
    margin: auto;
    max-height: none;
}

/* ============================================================================
   SHORT VIEWPORTS (phone in landscape, ~390px tall)
   ============================================================================ */
@media (max-height: 520px) {
    body.msh-on { --msh-top: calc(40px + env(safe-area-inset-top, 0px)); }
    .msh-vitals { padding: 5px 10px; margin: 4px 8px; }
    .msh-vital-track { height: 9px; }
    body.msh-on .action-icon-wrap,
    body.msh-on .action-icon-wrap:has(.action-icon.monster) {
        width: min(34vh, 190px); height: min(34vh, 190px);
    }
    body.msh-on .action-icon .icon-action { width: min(22vh, 120px); height: min(22vh, 120px); }
    body.msh-on .action-icon.monster .icon-action { width: min(30vh, 165px); height: min(30vh, 165px); }
    /* margin:auto centering hides overflow above the scroll start — on a short
       screen the monster nameplate was clipped off the top with no way to
       reach it. Top-align instead; the bottom overflow stays scrollable. */
    body.msh-on .vp-center { gap: 8px; margin: 14px 0 auto; }
    body.msh-on { --msh-sheet-h: calc(min(70vh, 300px) + env(safe-area-inset-bottom, 0px)); }
}

/* ── reduced motion ── */
@media (prefers-reduced-motion: reduce) {
    body.msh-on .sidebar,
    body.msh-on .left-col-bottom,
    .msh-handle, .msh-chat-handle, .msh-scrim { transition: none; }
    .msh-vital-fill { transition: none; }
}
