/* ═══════════════════════════════════════════════════════════════
   Design tokens - single source of truth for the GridForge HUD.
   Sweep literals onto these so theming + UI scaling are free.
   --gf-ui-scale drives global HUD sizing (set from prefs at runtime).
   ═══════════════════════════════════════════════════════════════ */
:root {
    /* Native form controls (select popups, scrollbars, datalists) render in
       the browser's dark scheme instead of a jarring light one. */
    color-scheme: dark;

    /* ═══ FORGED PLATE - the core scale ═══
       VISUAL_IDENTITY.md's thesis in DOM form: solid matter below, light in the
       seams between. Three ideas carry the whole chrome:
         CHAMFER  every plate is an octagon, never a rounded rect. There is no
                  --gf-radius any more; a right angle now MEANS something (see
                  .gf-armed-frame), so nothing else may have one.
         PLATE    matte, bevelled, opaque-ish. No backdrop blur: frosted glass is
                  the SaaS look and it contradicts "matte world, no wet sheen".
         SEAM     the accent is not a border around a panel, it is a lit gap
                  WHERE TWO PLATES MEET - titlebar against content, slot against
                  slot. That is the one thing no UI kit can sell you. */

    /* Matter */
    --gf-void:          #0E0D0C;
    --gf-plate:         #14120F;
    --gf-plate-raised:  #1C1915;
    --gf-plate-sunk:    #0F0E0C;

    /* Trim + the seam light (ash-gold, VISUAL_IDENTITY's "UI trim" role) */
    --gf-trim:          #B89A4A;
    --gf-trim-bright:   #D9B65E;
    --gf-trim-dim:      rgba(184,154,74,0.34);
    --gf-seam:          rgba(184,154,74,0.55);
    --gf-seam-glow:     rgba(184,154,74,0.22);
    --gf-trim-rgb:      184, 154, 74;
    --gf-bone-rgb:      216, 203, 182;

    /* The control face. Every button in the game reads these two, so the whole
       set retunes from here - which is the point: converging 12 bespoke button
       families onto one look by repeating a gradient literal 12 times would
       just have rebuilt the original problem in a tidier shape. (The literal
       ratchet in tests/ui_primitives_test.mjs caught exactly that.) */
    --gf-face:       linear-gradient(180deg, rgba(var(--gf-trim-rgb), 0.22), rgba(var(--gf-trim-rgb), 0.10));
    --gf-face-hover: linear-gradient(180deg, rgba(var(--gf-trim-rgb), 0.34), rgba(var(--gf-trim-rgb), 0.16));

    /* Geometry of the cut */
    --gf-chamfer:       9px;    /* windows, panels, sheets */
    --gf-chamfer-sm:    5px;    /* buttons, slots, chips, inputs */
    --gf-trim-w:        1px;
    --gf-seam-w:        1px;

    /* The cut, pre-baked as a drop-in `clip-path` value so converting a rule is
       a one-line swap: `border-radius: X` becomes `clip-path: var(--gf-cut*)`.
       px inside a `zoom`ed element scale with the zoom, so the chamfer stays
       proportional across the whole --gf-ui-scale range. */
    --gf-cut: polygon(
        9px 0, calc(100% - 9px) 0,
        100% 9px, 100% calc(100% - 9px),
        calc(100% - 9px) 100%, 9px 100%,
        0 calc(100% - 9px), 0 9px);
    --gf-cut-sm: polygon(
        5px 0, calc(100% - 5px) 0,
        100% 5px, 100% calc(100% - 5px),
        calc(100% - 5px) 100%, 5px 100%,
        0 calc(100% - 5px), 0 5px);
    /* Top-cut only - for a titlebar or a tab that sits against a plate below it,
       where cutting the bottom corners would open a gap in the seam. */
    --gf-cut-top: polygon(
        9px 0, calc(100% - 9px) 0,
        100% 9px, 100% 100%, 0 100%, 0 9px);

    /* The bevel: one lit edge, one shaded edge. Light top-left, per the world's
       own key light. These compose INSIDE a plate, never as an outer glow. */
    --gf-bevel-lit:     inset 1px 1px 0 rgba(216,203,182,0.10);
    --gf-bevel-shade:   inset -1px -1px 0 rgba(0,0,0,0.45);
    --gf-bevel:         var(--gf-bevel-lit), var(--gf-bevel-shade);

    /* Type. The game client had no font tokens at all - Consolas and Cinzel were
       spelled out in 8 different ways across the sheet. */
    --gf-font-display:  'Fraunces', 'Marcellus', Georgia, serif;
    --gf-font-ui:       'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
    --gf-font-mono:     'IBM Plex Mono', ui-monospace, 'SF Mono', monospace;

    /* Accent / brand */
    /* Legacy accent names, now aliases. They were #f0c060 - a brighter, more
       saturated gold than anything in VISUAL_IDENTITY.md's palette, so the HUD
       and the marketing site were two different golds. */
    --gf-accent:        var(--gf-trim);
    --gf-accent-hot:    var(--gf-trim-bright);
    --gf-accent-deep:   #8A6F2E;
    --gf-accent-glow:   rgba(184,154,74,0.45);
    /* Muted parchment for a guild's granted title (main.css .gf-guild-rowtitle).
       It was used once, through a var() fallback, and never defined - which is
       what kept tests/test_client_css_tokens.py red. */
    --gf-accent-2:      #a99a7c;

    /* Ash-gold, VISUAL_IDENTITY.md's UI trim. 23 declarations referenced
       --gf-gold and NONE of them defined it - an undefined custom property makes
       the whole declaration invalid at computed-value time, so every one of them
       silently fell back to inherit. The literal rgba(184,154,74,…) sitting
       beside several of them is what they were reaching for. */
    --gf-gold:          #B89A4A;
    --gf-gold-bright:   #D9B65E;
    --gf-ash-gold:      #B89A4A;

    /* Martial ember - VISUAL_IDENTITY.md's Ember role, and the ONE color of
       "a weapon special is primed". It is a state shown on four surfaces, so it
       needs one definition or they drift (it was a bare literal in the HUD css
       and again in specials_book.js before it became a token).

       This WAS #e0864f, a lighter orange chosen deliberately so "primed" could
       not be read as a second war badge: --gf-danger (#e0524a) and the war
       badge (now --gf-danger) already mean danger and at-war, and arming a special
       turns war mode ON, so both light together. That reasoning was sound but
       the value drifted off the binding palette - VISUAL_IDENTITY.md:30 says
       Ember is #E2622A, and the website used that, so one token name meant two
       colors.

       Realigned, and the separation it existed to protect is MEASURED rather
       than assumed (CIE76 dE against each red):
           vs --gf-danger #e0524a   old 28.6  ->  new 21.1
           vs war badge  (now --gf-danger, which this move also fixed)
       Both stay well clear of the ~10 threshold where two colors read as one.
       The old argument is also weaker now: primed is carried by SILHOUETTE -
       .gf-armed-frame is the one square in a chamfered UI - so the color is
       reinforcement, not the signal. And the two never share a surface: the
       war badge is text in the HUD readout, primed is a frame around a slot. */
    --gf-ember:         #E2622A;
    --gf-ember-hot:     #FF7A3C;
    --gf-ember-deep:    #8A3A16;
    --gf-ember-glow:    rgba(226,98,42,0.55);
    --gf-ember-rgb:     226, 98, 42;

    /* Semantic */
    --gf-danger:        #e0524a;
    --gf-danger-deep:   #8a2a25;
    --gf-success:       #6fc874;
    --gf-info:          #5aa6e0;
    --gf-mana:          #4f86d8;
    --gf-stamina:       #d9b13e;

    /* Text */
    --gf-text-1: #f4ecd8;   /* primary */
    --gf-text-2: #c9c2b4;   /* secondary */
    --gf-text-3: #948d80;   /* muted */
    --gf-text-4: #5e584d;   /* faint */

    /* Bone, VISUAL_IDENTITY.md's text/pale-stone role, plus two older spellings
       that were only ever reached through their var() fallbacks. Defined at
       exactly the values those fallbacks carried, so nothing moves - but editing
       them now actually does something, which is the entire point of a token. */
    --gf-bone: #D8CBB6;
    --gf-text: var(--gf-bone);   /* legacy alias, .ss-* panels */
    --gf-dim:  #9a8f7d;          /* legacy muted, .ss-* panels */

    /* How much the Grid is doing where you stand, 0..1 (the grid_conditions
       `glow`), driving the mana bar's shimmer speed. NOTHING writes this yet -
       no JS ever set it, so the shimmer has run at its floor rate since it
       shipped. Declared at the inert default so the rule is valid and the gap is
       visible; wiring it is a separate change. */
    --gf-grid: 0;

    /* Panels / surfaces. Cool-grey glass is gone: these were rgba(18,18,26)
       over a backdrop blur, i.e. blue-tinted frost. The palette has no cool
       neutral - Void is warm charcoal - so the plates are warm and opaque. */
    --gf-panel-bg:        rgba(20,18,15,0.94);
    --gf-panel-bg-strong: rgba(14,13,12,0.97);
    --gf-panel-bg-soft:   rgba(28,25,21,0.88);
    --gf-slot-bg:         rgba(216,203,182,0.045);
    --gf-slot-bg-hover:   rgba(216,203,182,0.10);

    /* Borders. Warm-shifted off pure white so a hairline reads as lit ash
       rather than as a grey UI rule. */
    --gf-border:        rgba(216,203,182,0.13);
    --gf-border-soft:   rgba(216,203,182,0.07);
    --gf-border-accent: var(--gf-trim-dim);

    /* The Grid. Mirrors website/css/tokens.css, which game.html does NOT load -
       every in-game `var(--gf-grid-cyan, #66e0ff)` had been resolving to its
       fallback. tests/test_client_css_tokens.py now fails on an undefined token. */
    --gf-grid-cyan:     #66E0FF;
    --gf-grid-cyan-rgb: 102, 224, 255;

    /* Staff chrome - VISUAL_IDENTITY.md's one UI exception to "Grid cyan is the
       Grid's alone". Cyan here means PLAYERS CANNOT SEE THIS, so it is dark
       cool-tinted glass + a cyan trim and never a saturated fill (a cyan field
       would compete with the real Grid in the world behind it). Role identity
       rides --ribbon-role as a 2px stripe, never as the border or the glow.
       --ribbon-accent is the INVARIANT cyan and never varies by rank; three
       readings of these two had drifted apart (studio.css spells the contract). */
    --gf-staff-bg:       rgba(12,26,32,0.90);
    --gf-staff-bg-soft:  rgba(102,224,255,0.07);
    --gf-staff-bg-hover: rgba(102,224,255,0.16);
    --gf-staff-border:   rgba(102,224,255,0.42);
    --gf-staff-glow:     rgba(102,224,255,0.14);
    --gf-staff-text:     #cdeef8;
    --gf-staff-label:    #66E0FF;

    /* Depth. These are drop-shadows, NOT box-shadows, at every chamfered
       surface: a box-shadow is clipped away by clip-path, a filter follows the
       cut silhouette. --gf-radius* are deliberately GONE; the sweep converts
       every border-radius to a chamfer, and a disc keeps a literal 50%. */
    --gf-shadow:        0 6px 22px rgba(0,0,0,0.55);
    --gf-shadow-strong: 0 10px 38px rgba(0,0,0,0.70);
    --gf-inset:         var(--gf-bevel);
    /* The same depth as a FILTER, for any chamfered surface: clip-path cuts a
       box-shadow off entirely, while a filter follows the cut silhouette. Use
       --gf-drop wherever the element carries a clip-path. */
    --gf-drop:          drop-shadow(0 6px 14px rgba(0,0,0,0.55));
    --gf-drop-strong:   drop-shadow(0 10px 22px rgba(0,0,0,0.70));

    /* --gf-ui-scale is the GLOBAL HUD scale (set on :root from the `uiScale`
       account pref, default 0.85). Panels read it via `zoom` (see the HUD-scale
       block near the end of this file). --gf-bar-scale is the per-hotbar-slot
       scale hotbar.js sets on each bar; slots compose both so a hotbar honors
       the global scale AND its own per-bar sizing. */
    --gf-ui-scale: 1;
    --gf-bar-scale: 1;
    --gf-slot:   calc(46px * var(--gf-ui-scale) * var(--gf-bar-scale));
    --gf-slot-gap: calc(5px * var(--gf-ui-scale) * var(--gf-bar-scale));

    --gf-mono: var(--gf-font-mono);   /* legacy alias */

    /* ── Stacking bands - the ONE ordering, mirrored by js/ui/z_layers.js ──
       Every z-index that means something GLOBALLY reads one of these. Ordering
       inside a single panel stays a small local number at the component: those
       live in their own stacking context and mean nothing here.

       This exists because the bands were only notionally ordered. #escape-menu
       shipped at 200 against a GameWindow counter that starts at 1000, so the
       pause menu rendered behind the minimap and behind any stat bar - and on
       touch those are precisely the two windows that stay floating instead of
       becoming full-screen sheets. Nothing threw; the menu just looked broken.

       Two orderings here are load-bearing and easy to get backwards:
         modal > sheet   - or tapping Menu with an inventory sheet open puts the
                           menu behind the sheet, i.e. the same bug one step on.
         dialog > modal  - gameConfirm is opened BY the escape menu (Disconnect
                           Discord); below it, the confirm renders behind its own
                           opener. Same for the portrait gate, which is blocking. */
    --gf-z-world:            0;   /* the 3D canvas */
    --gf-z-hud:             10;   /* passive readouts */
    --gf-z-hud-bar:         40;   /* .gf-bar */
    --gf-z-hud-readout:     50;   /* static corner readouts - the Grid read, pet slots.
                                     Deliberately BELOW touch: they are passive chrome,
                                     and the pet-slots readout is pointer-events:auto,
                                     so above the controls it would eat taps. */

    --gf-z-touch:          100;   /* .mobile-controls-overlay ROOT - above .gf-bar */
    --gf-z-touch-zone:      10;   /*   ^ hit areas, inside the overlay */
    --gf-z-touch-marker:    20;   /*   ^ pointer-events:none decoration */
    --gf-z-touch-cluster:  120;   /*   ^ draggable button clusters */
    --gf-z-touch-drag:     140;   /*   ^ the cluster under the finger */

    /* Action FEEDBACK - the readouts that answer "is my action working?". Either
       world-pinned (the gather bar follows the block, so it lands anywhere on screen)
       or transient (a castbar, a Track readout). Both must outrank `touch`: a control
       you can re-press is cheaper to cover than the only thing telling you an action is
       progressing. The overlay root is a stacking context, and moving it from 4 to 100
       put the whole mobile control layer over this family - and the joystick and
       quick-loot pill both carry backdrop-filter, so they did not merely cover the
       gather bar, they blurred it into the backdrop. All of it is pointer-events:none,
       so nothing here can steal a tap from what it paints over. */
    --gf-z-world-pin:      200;   /* gather bar + off-screen arrow, castbar, tracking
                                     HUD, targeting prompt */
    --gf-z-world-pin-float:220;   /* damage numbers / impact flourish - top of the family */

    --gf-z-window:        1000;   /* nextZIndex() FLOOR */
    --gf-z-window-max:    8999;   /* nextZIndex() CEILING - enforced in JS */

    --gf-z-sheet:         9000;   /* .gf-mobile-sheet */
    --gf-z-sheet-chrome:  9500;   /* drag ghost / marquee: raised INSIDE a sheet */

    --gf-z-popover:      20000;   /* ctx menu, assign pop */
    --gf-z-modal:        30000;   /* #escape-menu */
    --gf-z-dialog:       40000;   /* gameConfirm, portrait gate */
    --gf-z-system:      100000;   /* blocking staff / beta gates */
    --gf-z-cursor:     1000000;   /* #gf-custom-cursor */
}

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

/* ═══════════════════════════════════════════════════════════════
   Global chrome - sleek, on-theme scrollbars + selection + focus.
   One source of truth: WebKit pseudo-elements are document-global and
   Firefox scrollbar-* is inherited, so this styles every scrollable
   element in the game (HUD, studio, login) regardless of where it's
   defined. Subtle translucent gold thumb, transparent track.
   ═══════════════════════════════════════════════════════════════ */
::-webkit-scrollbar        { width: 10px; height: 10px; }
::-webkit-scrollbar-track  { background: transparent; }
::-webkit-scrollbar-thumb  {
    background: rgba(var(--gf-trim-rgb), 0.28);
    border: 2px solid transparent;       /* inset look via padding-box clip */
    background-clip: padding-box;
    clip-path: var(--gf-cut);
}
::-webkit-scrollbar-thumb:hover  { background: rgba(var(--gf-trim-rgb), 0.5); }
::-webkit-scrollbar-thumb:active { background: var(--gf-accent-hot); }
::-webkit-scrollbar-corner       { background: transparent; }

/* Firefox - gated to Firefox so a non-auto scrollbar-color doesn't disable
   the richer WebKit styling in Chromium 121+ (-moz-appearance resolves only
   in Firefox). */
@supports (-moz-appearance: none) {
    * { scrollbar-width: thin; scrollbar-color: rgba(var(--gf-trim-rgb), 0.32) transparent; }
}

::selection { background: rgba(var(--gf-trim-rgb), 0.30); color: var(--gf-text-1); }

:focus-visible { outline: 2px solid var(--gf-border-accent); outline-offset: 2px; }

/* ── Fantasy icons (game-icons.net, CC BY 3.0) ──
   Masked SVG tinted to currentColor (gold by default). In-game reuse of the
   marketing-site pipeline. Usage:
     <span class="gf-ic" style="--ic:url('/assets/icons/sword.svg')"></span>
   or via client/js/ui/game_icons.js. Attribution: client/assets/CREDITS.md. */
.gf-ic {
    display: inline-block;
    vertical-align: middle;
    width: 1em;
    height: 1em;
    flex: 0 0 auto;
    color: var(--gf-accent);
    background-color: currentColor;
    -webkit-mask: var(--ic, none) center / contain no-repeat;
    mask: var(--ic, none) center / contain no-repeat;
    /* The SVGs are opaque single-path shapes (fill=currentColor → black when
       used as an external mask image). Force ALPHA masking so the shape shows;
       the default `match-source` uses luminance and would hide black shapes. */
    -webkit-mask-mode: alpha;
    mask-mode: alpha;
}

/* ── HUD icon placements (size/tint per surface) ── */
.cs-cat-ic   { color: var(--gf-accent);      vertical-align: -2px; margin: 0 4px 0 1px; opacity: .95; }
.cs-skill-ic { color: var(--gf-accent-deep); width: 12px; height: 12px; vertical-align: -2px; margin-right: 6px; opacity: .8; }
.sb-tab-ic   { vertical-align: -2px; }
.craft-station-ic { color: var(--gf-accent);      vertical-align: -2px; margin-right: 5px; }
.craft-row-ic     { color: var(--gf-accent-deep); width: 14px; height: 14px; margin-right: 7px; vertical-align: -2px; opacity: .85; }
.pt-name-ic       { color: var(--gf-accent);      width: 15px; height: 15px; vertical-align: -2px; margin-right: 5px; }
.pt-row-ic        { color: var(--gf-accent-deep); width: 12px; height: 12px; vertical-align: -2px; margin-right: 6px; opacity: .8; }
/* Hotbar/action-slot icon: cream on the colored backing plate for legibility. */
.gf-slot-icon .gf-slot-svg {
    width: 74%; height: 74%;
    color: var(--gf-text-1);
    filter: drop-shadow(0 1px 1px rgba(0,0,0,0.65));
}
/* Inventory slots: faint equip-slot placeholder + filled-item category icon. */
.inv-slot { position: relative; }
.inv-slot-ph {
    position: absolute; inset: 22%; width: auto; height: auto;
    color: var(--gf-accent); opacity: 0.22; pointer-events: none;
}
.inv-item-ic {
    position: absolute; inset: 20%; width: auto; height: auto;
    color: var(--gf-text-1); filter: drop-shadow(0 1px 1px rgba(0,0,0,0.6)); pointer-events: none;
}
/* Third-party attribution footer in the Quick Help panel (CC BY 3.0). */
.qh-credit {
    margin-top: 10px; padding-top: 8px;
    border-top: 1px solid var(--gf-border-soft);
    font-size: 10px; color: var(--gf-text-3);
    text-align: center; line-height: 1.5;
}
.qh-credit a { color: var(--gf-accent-deep); text-decoration: none; }
.qh-credit a:hover { color: var(--gf-accent); text-decoration: underline; }

body {
    background: #000;
    overflow: hidden;
    font-family: var(--gf-font-ui);
    color: #fff;
    user-select: none;
}

#canvas-container {
    position: fixed;
    inset: 0;
}

.gf-custom-cursor-active,
.gf-custom-cursor-active * {
    cursor: none !important;
}

#canvas-container canvas {
    display: block;
    width: 100% !important;
    height: 100% !important;
}

/* ── Custom Gridforge Cursor ── */
#gf-custom-cursor {
    --gf-cursor-primary: var(--gf-trim);
    --gf-cursor-bright: var(--gf-trim-bright);
    --gf-cursor-detail: #5aa6e0;
    --gf-cursor-soft: rgba(var(--gf-trim-rgb), 0.3);
    --gf-cursor-fill: rgba(var(--gf-trim-rgb), 0.9);
    position: fixed;
    top: 0;
    left: 0;
    width: 32px;
    height: 32px;
    pointer-events: none;
    /* Always the topmost thing on screen - it is the pointer. At its old 10000
       it went UNDER the escape menu the moment that became a real modal. */
    z-index: var(--gf-z-cursor);
    /* Top-left anchored to the mouse; each visual offsets its own hotspot
       (e.g. the arrow tip) onto this point. */
    transition: opacity 0.15s ease;
    will-change: left, top;
}

#gf-custom-cursor.gf-cursor-war {
    --gf-cursor-primary: #ff5a3c;
    --gf-cursor-bright: #ff9a80;
    --gf-cursor-detail: #d82f2f;
    --gf-cursor-soft: rgba(255, 70, 48, 0.34);
    --gf-cursor-fill: rgba(226, 55, 38, 0.94);
}

.gf-cursor-pointer-shell { fill: #0f0f15; stroke: var(--gf-cursor-primary); }
.gf-cursor-pointer-core { fill: var(--gf-cursor-bright); }
.gf-cursor-pointer-detail { fill: var(--gf-cursor-detail); }
.gf-cursor-arrow-ring { fill: none; stroke: var(--gf-cursor-soft); }
.gf-cursor-arrow-core { fill: var(--gf-cursor-fill); stroke: var(--gf-cursor-bright); }
.gf-cursor-arrow-detail { fill: var(--gf-cursor-bright); }

.gf-cursor-visual {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.25);
    transform-origin: center center;
}

/* Targeting reticle: the dashed ring rotates slowly like a locking sensor. */
.gf-cursor-target-visual .gf-reticle-spin {
    animation: gf-reticle-spin 5s linear infinite;
}
@keyframes gf-reticle-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* Crosshair */
#crosshair {
    position: fixed;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 20px; height: 20px;
    pointer-events: none;
}
#crosshair::before, #crosshair::after {
    content: '';
    position: absolute;
    background: rgba(255,255,255,0.85);
}
#crosshair::before { width: 2px; height: 100%; left: 50%; transform: translateX(-50%); }
#crosshair::after  { width: 100%; height: 2px; top: 50%; transform: translateY(-50%); }

/* Minimap - canvas fills its GameWindow content area */
.minimap-canvas-host {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: #111;
    position: relative;
}
.minimap-canvas-host canvas {
    width: 100%;
    height: 100%;
    display: block;
    /* auto (not pixelated): the hillshaded background upscales smoothly. Per-pixel
       live terrain is written 1:1 via putImageData, so it stays crisp regardless. */
    image-rendering: auto;
}
/* Minimap window is compact - less titlebar padding so the canvas dominates */
.game-window[data-window-id="minimap"] .game-window-content {
    overflow: hidden;
    padding: 0;
}

/* ── Map shell: header (expanded only) above the canvas host ── */
.gf-map-shell {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
}
.gf-map-shell .minimap-canvas-host {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

.gf-map-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 5px 9px;
    flex-shrink: 0;
    border-bottom: 1px solid var(--gf-border-soft);
    background: linear-gradient(180deg, rgba(56,52,40,0.35), rgba(30,28,22,0.35));
}
.gf-map-header-title {
    font-family: var(--gf-mono);
    font-size: 11px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--gf-text-2);
}
.gf-map-switcher {
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-1);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    font-family: var(--gf-mono);
    font-size: 11px;
    padding: 2px 6px;
    cursor: pointer;
}
.gf-map-switcher:disabled { opacity: 0.7; cursor: default; }

/* ── Controls overlay (zoom / recenter / expand) ── */
.gf-map-controls {
    position: absolute;
    top: 6px;
    right: 6px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    z-index: 2;
}
.gf-map-btn {
    width: 24px;
    height: 24px;
    padding: 0;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    font-size: 14px;
    cursor: pointer;
    transition: border-color 0.12s, color 0.12s, box-shadow 0.12s;
}
.gf-map-btn:hover { border-color: var(--gf-border-accent); color: #fff; }
.gf-map-btn.is-on {
    border-color: var(--gf-border-accent);
    color: var(--gf-accent);
    box-shadow: 0 0 0 1px var(--gf-accent-glow);
}

/* "Only you can see this" - shown on the map FACE whenever a staff-only layer is
   actually drawing (spawners, editor objects, the fauna heatmap, faction
   territories, the quest graph, or Fog of War lifted). Top-left, clear of the
   controls cluster on the right and the coords/legend along the bottom.
   `pointer-events: auto` only so the tooltip listing the live layers works. */
.gf-map-staff-badge {
    position: absolute;
    left: 6px;
    top: 6px;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 2px 7px 2px 6px;
    border: 1px solid var(--gf-staff-border);
    border-left: 2px solid var(--ribbon-role, var(--gf-grid-cyan));
    clip-path: var(--gf-cut-sm);
    background: var(--gf-staff-bg);
    box-shadow: 0 0 10px var(--gf-staff-glow);
    font: 9px/1.5 var(--gf-mono);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gf-staff-label);
    cursor: help;
}
.gf-map-staff-count {
    padding: 0 4px;
    clip-path: var(--gf-cut);
    background: var(--gf-staff-bg-hover);
    letter-spacing: 0;
}

/* ── Coords + legend readouts ── */
.gf-map-coords {
    position: absolute;
    left: 6px;
    bottom: 6px;
    z-index: 2;
    pointer-events: none;
    font-family: var(--gf-mono);
    font-size: 10px;
    letter-spacing: 0.5px;
    color: var(--gf-text-2);
    background: rgba(0,0,0,0.45);
    padding: 2px 6px;
    clip-path: var(--gf-cut-sm);
}
.gf-map-legend {
    position: absolute;
    right: 6px;
    bottom: 6px;
    z-index: 2;
    pointer-events: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 4px 8px;
    max-width: 65%;
    background: rgba(0,0,0,0.45);
    padding: 3px 6px;
    clip-path: var(--gf-cut-sm);
}
/* The legend is too busy for the tiny compact map - show it only when expanded
   (the inline display from the toggle still wins to hide it entirely). */
.game-window[data-window-id="minimap"]:not(.gf-map-expanded) .gf-map-legend {
    display: none;
}
.gf-leg-item {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-family: var(--gf-mono);
    font-size: 9px;
    color: var(--gf-text-2);
}
.gf-leg-item i {
    width: 8px;
    height: 8px;
    clip-path: var(--gf-cut-sm);
    display: inline-block;
}

/* ── Transient "map not available" toast ── */
.gf-map-toast {
    position: fixed;
    left: 50%;
    bottom: 80px;
    transform: translateX(-50%);
    z-index: 99999;
    opacity: 0;
    pointer-events: none;
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-1);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    padding: 8px 14px;
    font-size: 12px;
    box-shadow: var(--gf-shadow);
    transition: opacity 0.2s;
}
.gf-map-toast.show { opacity: 1; }

#terrain-loading-indicator {
    position: fixed;
    left: 50%;
    bottom: 74px;
    z-index: 99998;
    transform: translateX(-50%) translateY(4px);
    opacity: 0;
    pointer-events: none;
    padding: 7px 13px;
    border: 1px solid rgba(102, 224, 255, 0.5);
    clip-path: var(--gf-cut-sm);
    background: rgba(14, 13, 12, 0.88);
    color: var(--gf-grid-cyan, var(--gf-grid-cyan));
    box-shadow: 0 0 18px rgba(102, 224, 255, 0.12);
    font: 10px/1.2 var(--gf-mono);
    letter-spacing: 0.16em;
    transition: opacity 0.16s, transform 0.16s;
}
#terrain-loading-indicator.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* ── Expanded Split Layout & Tabs ── */
.gf-map-split-container {
    display: flex;
    flex-direction: row;
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden;
}
.gf-map-shell .minimap-canvas-host {
    flex: 1;
    min-width: 0;
    position: relative;
}
/* The spawner-populator sidebar and the tab bar above it are STAFF-ONLY, and
   they live inside the very map window players also use - the sharpest place in
   the game for "which half of this can they see?". So both take the staff chrome
   (cool-tinted glass + cyan trim + the role stripe) rather than the HUD's gold. */
.gf-map-sidebar {
    width: 320px;
    flex-shrink: 0;
    border-left: 1px solid var(--gf-staff-border);
    background: var(--gf-staff-bg);
    display: flex;
    flex-direction: column;
    height: 100%;
    transition: width 0.2s ease, margin-right 0.2s ease;
    z-index: 10;
}
.gf-map-sidebar.collapsed {
    width: 0;
    overflow: hidden;
    border-left: none;
}
.gf-map-sidebar-header {
    padding: 8px 12px;
    background: var(--gf-staff-bg-soft);
    border-bottom: 1px solid var(--gf-staff-border);
    border-top: 2px solid var(--ribbon-role, var(--gf-grid-cyan));
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.gf-map-sidebar-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--gf-mono);
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--gf-staff-label);
}

/* Tab controls in header */
.gf-map-tabs {
    display: flex;
    gap: 4px;
    align-items: center;
}
.gf-map-tab-btn {
    display: flex;
    align-items: center;
    gap: 5px;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid var(--gf-staff-border);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-staff-text);
    font-family: var(--gf-mono);
    font-size: 10px;
    padding: 3px 8px;
    cursor: pointer;
    transition: all 0.15s;
}
.gf-map-tab-btn:hover {
    color: #fff;
    background: var(--gf-staff-bg-soft);
}
.gf-map-tab-btn.active {
    color: var(--gf-staff-label);
    border-color: var(--gf-grid-cyan);
    background: var(--gf-staff-bg-hover);
    text-shadow: 0 0 4px var(--gf-staff-glow);
}
.gf-map-tab-btn .gf-ic { color: var(--gf-staff-label); }

/* Sidebar lists and filters */
.gf-map-sidebar-body {
    flex: 1;
    overflow-y: auto;
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-height: 0;
}
.gf-entity-filter-bar {
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: rgba(0,0,0,0.2);
    padding: 8px;
    clip-path: var(--gf-cut);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.03);
}
.gf-entity-search {
    width: 100%;
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-1);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    padding: 4px 8px;
    font-size: 11px;
}
.gf-entity-search:focus {
    border-color: var(--gf-gold);
    outline: none;
}
.gf-entity-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.gf-filter-tag {
    font-size: 9px;
    font-family: var(--gf-mono);
    padding: 2px 6px;
    background: rgba(var(--gf-bone-rgb), 0.05);
    border: 1px solid transparent;
    clip-path: var(--gf-cut);
    color: var(--gf-text-2);
    cursor: pointer;
    user-select: none;
    transition: all 0.12s;
}
.gf-filter-tag:hover {
    color: var(--gf-text-1);
    background: rgba(var(--gf-bone-rgb), 0.1);
}
.gf-filter-tag.active {
    background: rgba(var(--gf-trim-rgb), 0.15);
    border-color: rgba(var(--gf-trim-rgb), 0.4);
    color: var(--gf-gold);
}

.gf-entity-sort-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 10px;
    color: var(--gf-text-3);
    font-family: var(--gf-mono);
}
.gf-entity-sort-select {
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-2);
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    font-size: 10px;
    padding: 1px 4px;
    cursor: pointer;
}

/* Scroller Entity Table */
.gf-entity-table-wrap {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}
.gf-entity-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
}
.gf-entity-row {
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.02);
    cursor: pointer;
    transition: background 0.1s;
}
.gf-entity-row:hover {
    background: rgba(var(--gf-bone-rgb), 0.03);
}
.gf-entity-row.selected {
    background: rgba(var(--gf-trim-rgb), 0.08);
}
.gf-entity-cell {
    padding: 6px 4px;
    vertical-align: middle;
}
.gf-entity-icon {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    display: inline-block;
    box-shadow: 0 0 4px rgba(var(--gf-bone-rgb), 0.1);
}
.gf-entity-name {
    font-weight: bold;
    color: var(--gf-text-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 130px;
}
.gf-entity-sub {
    font-size: 9px;
    color: var(--gf-text-3);
    font-family: var(--gf-mono);
}
.gf-entity-dist {
    font-family: var(--gf-mono);
    color: var(--gf-text-2);
    font-size: 10px;
    text-align: right;
}
.gf-entity-status {
    font-size: 9px;
    font-family: var(--gf-mono);
    text-align: right;
}
.gf-status-active { color: #55ee77; }
.gf-status-exhausted { color: #ff5555; animation: gf-pulse-exhausted 1.5s infinite; }
.gf-status-disabled { color: #888888; }
.gf-status-hp { color: #a8a8a8; }

@keyframes gf-pulse-exhausted {
    0% { opacity: 0.6; }
    50% { opacity: 1; text-shadow: 0 0 4px rgba(255,85,85,0.6); }
    100% { opacity: 0.6; }
}

/* Populator Presets Panel */
.gf-presets-panel {
    background: rgba(0,0,0,0.25);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.03);
    clip-path: var(--gf-cut);
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.gf-presets-title {
    font-family: var(--gf-mono);
    font-size: 10px;
    color: var(--gf-gold);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.05);
    padding-bottom: 4px;
}
.gf-preset-select {
    width: 100%;
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-1);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    padding: 4px;
    font-size: 11px;
}
.gf-preset-btnrow {
    display: flex;
    gap: 4px;
}
.gf-preset-btn {
    flex: 1;
    font-size: 10px;
    font-family: var(--gf-mono);
    padding: 4px 6px;
    cursor: pointer;
    background: var(--gf-face);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-accent-hot);
    transition: all 0.15s;
}
.gf-preset-btn:hover {
    color: var(--gf-text-1);
    border-color: var(--gf-gold);
}
.gf-preset-btn.active {
    background: rgba(var(--gf-trim-rgb), 0.15);
    border-color: var(--gf-gold);
    color: var(--gf-gold);
    box-shadow: 0 0 5px rgba(var(--gf-trim-rgb), 0.2);
}

/* Brush controls */
.gf-brush-controls {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 6px;
    background: rgba(var(--gf-bone-rgb), 0.02);
    clip-path: var(--gf-cut-sm);
    font-size: 10px;
    font-family: var(--gf-mono);
}
.gf-brush-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.gf-brush-slider {
    flex: 1;
    margin-left: 8px;
    accent-color: var(--gf-gold);
}

/* Edit / Details Form Overlay */
.gf-map-editor-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.55);
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.gf-map-editor-card {
    background: rgba(18, 16, 14, 0.95);
    border: 1px solid var(--gf-gold);
    clip-path: var(--gf-cut);
    width: 290px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.8);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.gf-map-editor-head {
    padding: 8px 12px;
    background: rgba(var(--gf-trim-rgb), 0.1);
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.2);
    font-family: var(--gf-mono);
    font-size: 11px;
    text-transform: uppercase;
    color: var(--gf-gold);
}
.gf-map-editor-body {
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 380px;
    overflow-y: auto;
}
.gf-map-editor-row {
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-size: 10px;
    color: var(--gf-text-2);
    font-family: var(--gf-mono);
}
.gf-map-editor-row label {
    font-weight: bold;
    color: var(--gf-text-3);
}
.gf-map-editor-row input[type="text"],
.gf-map-editor-row input[type="number"],
.gf-map-editor-row select {
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-1);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    padding: 4px 6px;
    font-size: 11px;
}
.gf-map-editor-row input:focus,
.gf-map-editor-row select:focus {
    border-color: var(--gf-gold);
    outline: none;
}
.gf-map-editor-row-inline {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 10px;
    color: var(--gf-text-2);
    font-family: var(--gf-mono);
}
.gf-map-editor-row-inline label {
    font-weight: bold;
    color: var(--gf-text-3);
}
.gf-map-editor-row-inline input[type="checkbox"] {
    accent-color: var(--gf-gold);
    cursor: pointer;
}
.gf-map-editor-footer {
    padding: 8px 12px;
    border-top: 1px solid var(--gf-border-soft);
    background: rgba(0,0,0,0.2);
    display: flex;
    justify-content: flex-end;
    gap: 6px;
}
.gf-editor-btn {
    font-size: 10px;
    font-family: var(--gf-mono);
    padding: 4px 10px;
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
    border: 1px solid var(--gf-border-accent);
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    transition: all 0.12s;
}
.gf-editor-btn:hover {
    color: var(--gf-text-1);
    border-color: var(--gf-border-accent);
}
.gf-editor-btn.primary {
    border-color: var(--gf-gold);
    color: var(--gf-gold);
    background: rgba(var(--gf-trim-rgb), 0.1);
}
.gf-editor-btn.danger {
    border-color: #ff4444;
    color: #ff4444;
    background: rgba(255, 68, 68, 0.05);
}
.gf-editor-btn.danger:hover {
    background: rgba(255, 68, 68, 0.15);
}

/* Map overlay HUD banner for placement/brush mode */
.gf-map-placement-banner {
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(18, 16, 14, 0.88);
    border: 1px solid var(--gf-gold);
    clip-path: var(--gf-cut);
    padding: 5px 15px;
    color: var(--gf-text-1);
    font-family: var(--gf-mono);
    font-size: 11px;
    z-index: 99;
    box-shadow: 0 4px 15px rgba(0,0,0,0.6);
    display: flex;
    align-items: center;
    gap: 12px;
    pointer-events: auto;
}
.gf-banner-text {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.gf-banner-pulse {
    width: 6px;
    height: 6px;
    background: #ffaa00;
    border-radius: 50%;
    animation: gf-banner-pulse-anim 1.0s infinite alternate;
}
@keyframes gf-banner-pulse-anim {
    from { opacity: 0.3; transform: scale(0.8); }
    to { opacity: 1; transform: scale(1.3); box-shadow: 0 0 6px #ffaa00; }
}
.gf-banner-btn {
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    padding: 2px 10px;
    font-size: 10px;
    cursor: pointer;
    transition: all 0.12s;
}
.gf-banner-btn:hover {
    background: var(--gf-gold);
    color: #000;
}

/* HUD info panel */
#hud-info {
    position: fixed;
    top: 12px; left: 12px;
    font-size: 13px;
    color: #fff;
    background: rgba(0,0,0,0.5);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.1);
    clip-path: var(--gf-cut);
    padding: 8px 12px;
    pointer-events: all;
    line-height: 1.6;
    min-width: 140px;
    z-index: 10;
}

#hud-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

#hud-class {
    font-weight: bold;
    font-size: 15px;
    color: var(--gf-trim);
}

#hud-toggle {
    background: none;
    border: 1px solid rgba(var(--gf-bone-rgb), 0.2);
    color: var(--gf-text-3);
    width: 20px;
    height: 20px;
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
#hud-toggle:hover { color: #fff; border-color: rgba(255,255,255,0.4); }

#hud-details {
    margin-top: 4px;
}

#hud-location {
    color: #e6d7b0;
    font-weight: 600;
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#hud-weight { color: #c9bfa6; font-variant-numeric: tabular-nums; }
#hud-weight.over { color: #ff7a5a; }
#hud-fps { color: #8b8; font-variant-numeric: tabular-nums; }
#hud-time { color: #aac; }
/* Degraded-link pill (Info panel only; hidden on a healthy link). Same warning
   tone as an over-weight readout - no new color. */
#hud-link {
    display: inline-block;
    margin-top: 3px;
    padding: 1px 7px;
    border: 1px solid rgba(255, 122, 90, 0.45);
    clip-path: var(--gf-cut-sm);
    color: #ff7a5a;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
}
#hud-link[hidden] { display: none; }

/* Always-visible gold readout (the realm currency). */
#hud-gold {
    margin-top: 4px;
    font-size: 14px;
    font-weight: 600;
    color: #f0d060;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

/* Unread letters. A notification, not a launcher - it appears only when there
   is something to read, and clicking it opens the mailbox. Gold like the coin
   line beside it; cyan is the Grid's, never ordinary UI's. */
#hud-mail {
    margin-top: 4px;
    font-size: 13px;
    font-weight: 600;
    color: #f0d060;
    cursor: pointer;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
#hud-mail[hidden] { display: none; }
#hud-mail:hover { color: #ffe9a0; }

/* War/Peace stance badge (toggled with Tab). */
#hud-war-mode {
    margin-top: 4px;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
#hud-war-mode.peace { color: #8b9a8b; }
#hud-war-mode.war {
    /* --gf-danger, not the orphan #ff5a3c it used to be. This badge STACKS with
       #hud-armed-special in the same readout and both light at once (arming a
       special turns war mode on), so the two need real separation. Measured
       CIE76 dE against --gf-ember-hot var(--gf-ember-hot): the orphan gave 16.2, which
       reads as the same colour at 11px; --gf-danger gives 25.8. It is also
       palette-legal, which #ff5a3c never was. */
    color: var(--gf-danger);
    text-shadow: 0 0 8px rgba(255, 60, 30, 0.7), 0 1px 2px rgba(0, 0, 0, 0.7);
}

/* ═══════════════════════════════════════════════════════════════
   ARMED WEAPON SPECIAL - one visual language, four surfaces.

   The state is carried by SILHOUETTE, not color alone: an armed thing wears a
   SQUARE ember frame over a filled plate - square because every other surface
   in the chrome is a chamfered octagon - and an idle one wears nothing. Color
   only reinforces it, because color alone fails at 14px, fails for color-vision
   deficiency, and - the specific trap here - collides with the war badge, which
   is red and is ALWAYS lit at the same time (arming a special turns war mode on).

   The frame is a wrapper, never a second icon file: doubling 36 glyphs into
   armed/idle pairs would be 36 more chances for the two to drift.
   ═══════════════════════════════════════════════════════════════ */

/* The square plate. Applied to a hotbar slot, a book row and the HUD badge
   alike, so "primed" is the same shape everywhere it appears. */
.gf-armed-frame {
    position: relative;
    border: 1px solid var(--gf-ember) !important;
    background:
        linear-gradient(180deg, rgba(var(--gf-ember-rgb), 0.22), rgba(var(--gf-ember-rgb), 0.06));
    box-shadow:
        0 0 10px var(--gf-ember-glow),
        inset 0 0 0 1px rgba(var(--gf-ember-rgb), 0.35);
    /* THE SQUARE. Primed is still a silhouette - it is now the INVERSE one.
       Every other plate in the game is a chamfered octagon (--gf-cut*), so a
       hard right angle is the loudest outline available and costs nothing to
       read at 14px with the color stripped. This used to be a two-corner notch,
       which worked when everything around it was a rounded rect; once the whole
       chrome became chamfered, "notched" stopped being distinctive. Do not
       "restore" the notch - that is how this state goes quiet again. */
    clip-path: none;
}

/* HUD badge: "◈ Move Name" under the war badge. */
#hud-armed-special {
    margin-top: 3px;
    padding: 1px 7px;
    display: inline-block;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--gf-ember-hot);
    text-shadow: 0 0 7px var(--gf-ember-glow), 0 1px 2px rgba(0, 0, 0, 0.7);
}
#hud-armed-special[hidden] { display: none; }

/* Reticle: a chevron pair closing on the crosshair. Present/absent IS the
   signal - there is no idle state drawn at all, so it costs nothing to read. */
#hud-armed-reticle {
    position: fixed;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 46px; height: 18px;
    pointer-events: none;
    z-index: 5;
}
#hud-armed-reticle[hidden] { display: none; }
#hud-armed-reticle::before,
#hud-armed-reticle::after {
    content: '';
    position: absolute;
    top: 50%;
    width: 7px; height: 7px;
    border-top: 2px solid var(--gf-ember);
    border-right: 2px solid var(--gf-ember);
    filter: drop-shadow(0 0 3px var(--gf-ember-glow));
}
/* Left chevron points right (inward), right chevron points left. */
#hud-armed-reticle::before { left: 0;  transform: translateY(-50%) rotate(45deg); }
#hud-armed-reticle::after  { right: 0; transform: translateY(-50%) rotate(-135deg); }

/* ═══════════════════════════════════════════════════════════════
   Combat Book (weapon special moves).

   The panel set `class="specials-panel"` and NO SUCH RULE EXISTED - every
   color, border and size was an inline style.cssText literal, which is how its
   ember drifted from the HUD badge's and why it was the one HUD surface that
   ignored --gf-ui-scale. Tokens only here.
   ═══════════════════════════════════════════════════════════════ */
.specials-panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    color: var(--gf-text-1);
    background: var(--gf-panel-bg-strong);
}
.specials-head {
    padding: 8px 10px;
    border-bottom: 1px solid var(--gf-border-soft);
    font-size: 11px;
    line-height: 1.45;
    color: var(--gf-text-3);
}
.specials-accent { color: var(--gf-ember); }
.specials-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.specials-section {
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gf-text-3);
    margin: 8px 2px 2px;
}
.specials-note { font-size: 11px; color: var(--gf-text-4); padding: 0 2px 4px; }

.specials-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 6px 8px;
    clip-path: var(--gf-cut-sm);
    border: 1px solid var(--gf-border-soft);
    background: var(--gf-slot-bg);
    user-select: none;
    cursor: pointer;
}
.specials-row:hover { background: var(--gf-slot-bg-hover); }
.specials-row-locked { opacity: 0.45; cursor: default; }
.specials-row-locked:hover { background: var(--gf-slot-bg); }
/* Reference-only rows (the All-moves tab) and un-slottable pool rows: they are
   readable at full strength but do nothing on click, so they must not offer a
   pointer or a hover lift. */
.specials-row-inert { cursor: default; }
.specials-row-inert:hover { background: var(--gf-slot-bg); }
.specials-row-picked {
    border-color: var(--gf-ember);
    background: linear-gradient(180deg, rgba(var(--gf-ember-rgb), 0.16), rgba(var(--gf-ember-rgb), 0.04));
}

.specials-icon {
    width: 18px; height: 18px;
    flex: 0 0 auto;
    color: var(--gf-ember);
    filter: drop-shadow(0 0 4px var(--gf-ember-glow));
}
.specials-row-locked .specials-icon { filter: grayscale(0.6); opacity: 0.6; }

.specials-mid { flex: 1; min-width: 0; }
.specials-title { font-size: 13px; color: var(--gf-text-1); }
.specials-row-locked .specials-title { color: var(--gf-text-3); }
.specials-desc { font-size: 11px; color: var(--gf-text-3); margin-top: 2px; }

.specials-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    flex: 0 0 auto;
}
.specials-tag { font-size: 10px; white-space: nowrap; color: var(--gf-text-3); }
.specials-tag-slot  { color: var(--gf-text-3); }
.specials-tag-lock  { color: var(--gf-text-3); }
.specials-tag-cost  { color: var(--gf-stamina); }
.specials-tag-cd    { color: var(--gf-text-3); }
.specials-tag-armed { color: var(--gf-ember-hot); font-weight: 700; letter-spacing: 0.06em; }

/* Chat panel */
#chat-area {
    position: fixed;
    bottom: 60px; left: 12px;
    width: 360px;
    background: rgba(0,0,0, var(--chat-opacity, 0.6));
    border: 1px solid rgba(var(--gf-bone-rgb), 0.1);
    clip-path: var(--gf-cut);
    pointer-events: all;
    z-index: 10;
}

#chat-resize {
    position: absolute;
    bottom: -3px; right: -3px;
    width: 14px; height: 14px;
    cursor: se-resize;
    z-index: 2;
}
#chat-resize::after {
    content: '';
    position: absolute;
    bottom: 3px; right: 3px;
    width: 6px; height: 6px;
    border-right: 2px solid rgba(var(--gf-bone-rgb), 0.25);
    border-bottom: 2px solid rgba(var(--gf-bone-rgb), 0.25);
}

#chat-log {
    /* Real height (not max-height) so the panel holds whatever size the
       player gave it even when the log is empty - resizing works regardless
       of how much text is in it. */
    height: 160px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    padding: 6px 8px;
}
/* Pin sparse content to the bottom of the fixed-height log (chat reads up). */
#chat-log > :first-child { margin-top: auto; }

.chat-line {
    font-size: 13px;
    padding: 2px 0;
    word-break: break-word;
    line-height: 1.4;
}

.chat-line .chat-name {
    font-weight: 600;
    color: #7ad;
}

.chat-line.chat-system,
.chat-line[data-category="system"] {
    color: #8fd28f;
    font-style: italic;
    font-size: 12px;
}

.chat-line[data-category="player"] { color: #e0e0e0; }
.chat-line[data-category="skill"]  { color: #7dd3fc; font-size: 12px; }
.chat-line[data-category="stat"]   { color: #a5f3a3; font-size: 12px; }

/* Global channel (A5) - server-wide, a distinct amethyst so it reads apart
   from local say. The [Global] prefix comes from the channel tag, not the DOM. */
.chat-line[data-category="global"] { color: #c9a3ff; }
.chat-line[data-category="global"] .chat-name { color: #b98cff; }
/* Party (/p) - the same bright green the party name-plate and frames use, so
   the channel and the people in it read as one thing. */
.chat-line[data-category="party"] { color: #8fe6ac; }
.chat-line[data-category="party"] .chat-name { color: #66ff99; }

/* Channel tab row (A5) */
.chat-tabs {
    display: flex;
    gap: 2px;
    padding: 3px 4px 0 4px;
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.08);
}
.chat-tab {
    appearance: none;
    background: transparent;
    border: 1px solid transparent;
    border-bottom: none;
    clip-path: var(--gf-cut-top);
    color: var(--gf-text-2, #9aa3b5);
    font-size: 11px;
    padding: 2px 9px;
    cursor: pointer;
    line-height: 1.5;
}
.chat-tab:hover { color: var(--gf-text-1, #e0e0e0); background: rgba(var(--gf-bone-rgb), 0.05); }
.chat-tab.active {
    color: var(--gf-accent-hot, var(--gf-ember));
    background: rgba(226,98,42,0.12);
    border-color: rgba(226,98,42,0.35);
}
/* "+" new-tab button - slimmer, quieter than a real tab. */
.chat-tab-add {
    padding: 2px 7px;
    font-weight: 700;
    color: var(--gf-text-2, #9aa3b5);
    opacity: 0.7;
}
.chat-tab-add:hover { opacity: 1; }

/* Custom-tab editor popup (A5) - a small floating panel over the chat. */
.chat-tab-editor {
    position: fixed;
    z-index: 320;
    min-width: 168px;
    background: var(--gf-panel-bg, rgba(14,13,12,0.96));
    border: 1px solid var(--gf-border-accent, rgba(226,98,42,0.4));
    clip-path: var(--gf-cut);
    box-shadow: var(--gf-shadow, 0 8px 24px rgba(0,0,0,0.5));
    padding: 8px;
    pointer-events: all;
    font-size: 12px;
}
.chat-tab-editor-title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--gf-text-2, #9aa3b5);
    margin-bottom: 6px;
}
.chat-tab-editor-name {
    width: 100%;
    box-sizing: border-box;
    background: rgba(0,0,0,0.4);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.18);
    color: #fff;
    padding: 4px 7px;
    font-size: 12px;
    clip-path: var(--gf-cut-sm);
    outline: none;
    margin-bottom: 6px;
}
.chat-tab-editor-chan {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--gf-text-1, #e0e0e0);
    padding: 2px 0;
    cursor: pointer;
}
.chat-tab-editor-chan input { cursor: pointer; }
.chat-tab-editor-btns {
    display: flex;
    gap: 6px;
    margin-top: 8px;
}
.chat-tab-editor-save,
.chat-tab-editor-cancel {
    flex: 1;
    appearance: none;
    clip-path: var(--gf-cut-sm);
    padding: 4px 8px;
    font-size: 12px;
    cursor: pointer;
    border: 1px solid transparent;
}
.chat-tab-editor-save {
    background: rgba(226,98,42,0.18);
    border-color: rgba(226,98,42,0.45);
    color: var(--gf-accent-hot, var(--gf-ember));
}
.chat-tab-editor-save:hover { background: rgba(226,98,42,0.28); }
.chat-tab-editor-cancel {
    background: rgba(var(--gf-bone-rgb), 0.05);
    border-color: rgba(var(--gf-bone-rgb), 0.14);
    color: var(--gf-text-2, #9aa3b5);
}
.chat-tab-editor-cancel:hover { color: var(--gf-text-1, #e0e0e0); }

/* Torn-off chat window (A5) - lives in a GameWindow, so it inherits the gold
   frame; the body is a filtered chat log over a compact send input. */
.chat-tearoff {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}
.chat-tearoff-log {
    /* #chat-log styling is id-scoped, so restate the log layout here (fill the
       window body, scroll, bottom-pin sparse content). */
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    padding: 6px 8px;
    background: rgba(0,0,0,0.28);
}
.chat-tearoff-log > :first-child { margin-top: auto; }
.chat-tearoff-input { padding: 5px 6px 2px; }
.chat-tearoff-input input {
    width: 100%;
    box-sizing: border-box;
    background: rgba(0,0,0,0.5);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.18);
    color: #fff;
    padding: 5px 8px;
    font-size: 12px;
    clip-path: var(--gf-cut-sm);
    outline: none;
}

/* Staff-only channel (/s) - distinct gold so staff can pick it out at a glance. */
.chat-line.chat-staff,
.chat-line[data-category="staff"] { color: #ffd479; }
.chat-line.chat-staff .chat-name { color: #ffb938; }

#chat-box {
    display: none;
    padding: 4px 8px 6px;
    pointer-events: all;
}

#chat-input {
    width: 100%;
    background: rgba(0,0,0,0.5);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.2);
    color: #fff;
    padding: 6px 10px;
    font-size: 13px;
    clip-path: var(--gf-cut-sm);
    outline: none;
}

/* ── Pinned chat (desktop) ───────────────────────────────────────────────
   Chat dissolves into the scene: bare text on the canvas, no frame, no
   background, no tabs, no grip, no input row. Recent lines sit at full
   strength and settle back to a subdued floor, so a quiet screen reads as
   world rather than as UI.

   `pointer-events: none` is the load-bearing line, not a cosmetic one: the
   whole point of pinning is that the text costs you NOTHING of the view, so a
   click aimed at a creature standing behind the log must reach the creature.
   That also means `:hover` never fires here - a pointer-events:none element
   receives no pointer events at all - which is why the lift-on-approach is a
   cached-rect test in the frame loop (HUD.chatProximityTest) writing
   `.chat-near`, and NOT a :hover rule. Do not "simplify" it into one.

   Scoped `:not(.mobile-controls-active)` and free of !important so the mobile
   block below - which is the same idea, already shipping, and carries
   !important throughout - always wins when both are on. Mobile is untouched by
   everything here. Torn-off chat panes are GameWindows and are deliberately
   NOT pinnable: pinning is a property of the docked panel. */
body.chat-pinned:not(.mobile-controls-active) #chat-area {
    background: none;
    border: 0;
    box-shadow: none;
    pointer-events: none;
}
body.chat-pinned:not(.mobile-controls-active) #chat-log {
    overflow: hidden;
    /* Fade the top edge into the world instead of ending on a hard line. */
    mask-image: linear-gradient(to bottom, transparent 0, #000 22%, #000 100%);
}
body.chat-pinned:not(.mobile-controls-active) #chat-resize,
body.chat-pinned:not(.mobile-controls-active) #chat-drag,
body.chat-pinned:not(.mobile-controls-active) #chat-box,
body.chat-pinned:not(.mobile-controls-active) .chat-tabs {
    display: none;
}

/* Legibility over open sky, snow and lit stone. The overhead speech sprites
   solve the same problem with a heavy dark outline on the canvas; this is the
   DOM twin. Without it, pale text over bright terrain is unreadable exactly
   when the player has chosen to let the world show through. */
body.chat-pinned:not(.mobile-controls-active) #chat-log .chat-line {
    text-shadow:
        0 0 3px rgba(0,0,0,0.95), 0 1px 2px rgba(0,0,0,0.9),
        -1px 0 1px rgba(0,0,0,0.75), 1px 0 1px rgba(0,0,0,0.75);
    opacity: var(--chat-rest-opacity, 0.34);
    transition: opacity 420ms ease;
}
/* A line is `chat-fresh` from arrival until its timer strips the class. The
   REMOVAL is the fade - there is no animation to drive and no per-frame work. */
body.chat-pinned:not(.mobile-controls-active) #chat-log .chat-line.chat-fresh { opacity: 1; }

/* Cursor near the log, or the composer open: the whole backlog comes back. */
body.chat-pinned:not(.mobile-controls-active) #chat-log.chat-near .chat-line,
body.chat-pinned.chat-composing:not(.mobile-controls-active) #chat-log .chat-line { opacity: 1; }

/* Composing while pinned: the frame returns, and with it the ability to
   scroll, select and copy. At rest the log is deliberately inert - Enter is
   the way back to it. Restoring pointer events at rest would trade the one
   thing pinning is for. */
body.chat-pinned.chat-composing:not(.mobile-controls-active) #chat-area {
    background: rgba(0,0,0, var(--chat-opacity, 0.6));
    border: 1px solid rgba(var(--gf-bone-rgb), 0.1);
    pointer-events: all;
}
body.chat-pinned.chat-composing:not(.mobile-controls-active) #chat-log {
    overflow-y: auto;
    mask-image: none;
}
body.chat-pinned.chat-composing:not(.mobile-controls-active) #chat-box,
body.chat-pinned.chat-composing:not(.mobile-controls-active) .chat-tabs { display: flex; }
body.chat-pinned.chat-composing:not(.mobile-controls-active) #chat-resize,
body.chat-pinned.chat-composing:not(.mobile-controls-active) #chat-drag { display: block; }

/* Slash-command completion dropdown. Sits ABOVE the chat input (chat lives at
   the bottom of the screen), anchored to #chat-box which is position:relative
   for exactly this. Same gold-on-glass palette as the /commands palette. */
#chat-box { position: relative; }
.cmd-completer {
    position: absolute;
    left: 8px;
    right: 8px;
    bottom: calc(100% - 4px);
    z-index: 260;
    max-height: 240px;
    overflow-y: auto;
    background: var(--gf-panel-bg-strong);
    border: 1px solid #4a3a26;
    clip-path: var(--gf-cut);
    box-shadow: 0 -4px 16px rgba(0,0,0,0.5);
    font-size: 12px;
    pointer-events: all;
}
.cmd-cp-row {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 5px 10px;
    cursor: pointer;
    white-space: nowrap;
}
.cmd-cp-row:hover { background: rgba(192,160,112,0.12); }
.cmd-cp-row.sel { background: rgba(192,160,112,0.22); }
.cmd-cp-row.cmd-cp-hint { cursor: default; }
.cmd-cp-row.cmd-cp-hint:hover { background: none; }
.cmd-cp-slash { color: var(--gf-bone); font-weight: 600; }
.cmd-cp-args { color: #c0a070; }
.cmd-cp-desc {
    color: var(--gf-accent-2);
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
}
.cmd-cp-role {
    color: var(--gf-staff-label);
    text-transform: capitalize;
    font-size: 10px;
    letter-spacing: 0.05em;
}
/* A suggestion you can only run because you're staff. `.cmd-cp-role` was
   already rendered on exactly these rows - it just looked like every other
   gold thing in the HUD. */
.cmd-cp-row.cmd-cp-staff { background: var(--gf-staff-bg-soft); }
.cmd-cp-row.cmd-cp-staff:hover,
.cmd-cp-row.cmd-cp-staff.sel { background: var(--gf-staff-bg-hover); }
.cmd-cp-row.cmd-cp-staff .cmd-cp-slash { color: var(--gf-staff-text); }
.cmd-cp-cube { color: var(--gf-staff-label); align-self: center; }

/* Context menu */
.ctx-menu {
    position: fixed;
    z-index: var(--gf-z-popover);
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut);
    padding: 4px 0;
    min-width: 180px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.5);
    /* Never let a long menu overflow the screen: cap height and scroll. */
    max-height: min(80dvh, 560px);
    overflow-y: auto;
    overflow-x: hidden;
}

.ctx-item {
    padding: 8px 14px;
    font-size: 13px;
    color: var(--gf-text-2);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
}
.ctx-item:hover { background: rgba(var(--gf-bone-rgb), 0.08); color: #fff; }

.ctx-label { flex: 1; }

/* Submenu rows: trailing ▸ and a lit state while their flyout is open. */
.ctx-item.has-sub::after { content: '▸'; opacity: 0.55; font-size: 11px; padding-left: 10px; }
.ctx-item.has-sub.open { background: rgba(var(--gf-bone-rgb), 0.08); color: #fff; }

.ctx-slider {
    width: 80px;
    accent-color: var(--gf-trim);
    cursor: pointer;
}

.ctx-val {
    font-family: var(--gf-font-mono);
    font-size: 11px;
    color: var(--gf-text-3);
    min-width: 28px;
    text-align: right;
}

.ctx-sep {
    height: 1px;
    background: rgba(var(--gf-bone-rgb), 0.08);
    margin: 4px 8px;
}

/* ── Staff chrome ──────────────────────────────────────────────────────────
   Grid cyan means PLAYERS CANNOT SEE THIS - VISUAL_IDENTITY.md's one UI
   exception to "cyan is the Grid's alone". Dark cool-tinted glass with a cyan
   trim, never a saturated fill; the role color rides a 2px stripe only, so the
   cyan keeps one meaning across all five tiers. Set by the `staff` flag on a
   context_menu.js item descriptor - never by a call site. */
.ctx-menu-staff {
    background: var(--gf-staff-bg);
    border-color: var(--gf-staff-border);
    border-top: 2px solid var(--ribbon-role, var(--gf-grid-cyan));
    box-shadow: 0 4px 16px rgba(0,0,0,0.55), 0 0 20px var(--gf-staff-glow);
}

/* Opens a contiguous run of staff rows: cube + the viewer's rank. Mono/uppercase
   to match #terrain-loading-indicator, the shipped "Grid chrome" precedent. */
.ctx-staff-hdr {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 4px 0 2px;
    padding: 4px 12px 4px 10px;
    border-left: 2px solid var(--ribbon-role, var(--gf-grid-cyan));
    background: var(--gf-staff-bg-soft);
    font: 9px/1.4 var(--gf-mono);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gf-staff-label);
    pointer-events: none;
}
.ctx-menu-staff .ctx-staff-hdr { margin-top: 0; }
.ctx-staff-hdr-label { flex: 1; }

.ctx-staff-cube { color: var(--gf-staff-label); vertical-align: -1px; }
.ctx-item.ctx-staff .ctx-staff-cube { margin-right: 7px; opacity: 0.9; }

.ctx-item.ctx-staff {
    background: var(--gf-staff-bg-soft);
    color: var(--gf-staff-text);
}
.ctx-item.ctx-staff:hover,
.ctx-item.ctx-staff.has-sub.open {
    background: var(--gf-staff-bg-hover);
    color: #fff;
}
/* Inside an all-staff menu the body is already tinted; don't double it. */
.ctx-menu-staff .ctx-item.ctx-staff { background: transparent; }
.ctx-menu-staff .ctx-item:hover,
.ctx-menu-staff .ctx-item.has-sub.open { background: var(--gf-staff-bg-hover); color: #fff; }
.ctx-menu-staff .ctx-item { color: var(--gf-staff-text); }
.ctx-menu-staff .ctx-sep { background: rgba(102,224,255,0.14); }
.ctx-menu-staff .ctx-slider { accent-color: var(--gf-grid-cyan); }

/* A destructive entry mid-arm (`confirm: true` in command_registry.py). */
.ctx-item.ctx-staff-armed,
.ctx-menu-staff .ctx-item.ctx-staff-armed {
    background: rgba(224,82,74,0.18);
    color: var(--gf-danger);
}
/* A permanently destructive entry (no confirm step of its own). */
.ctx-item.ctx-staff-danger,
.ctx-menu-staff .ctx-item.ctx-staff-danger { color: var(--gf-danger); }
.ctx-item.ctx-staff-danger:hover,
.ctx-menu-staff .ctx-item.ctx-staff-danger:hover {
    background: rgba(224,82,74,0.22);
    color: #ffe2de;
}

/* ── A whole GameWindow only staff can open ────────────────────────────────
   Same law as the menus: cool-tinted plate, cyan trim, role stripe, no fill.
   Applied via staff_menu.markStaffWindow(), never by hand.

   The whole variant is two token swaps, because the frame is CSS now. It used
   to opt OUT of the window frame entirely (the gold 9-slice was a fixed-color
   bitmap, so a staff window wearing it read as a player window whatever its
   contents said) - a recolorable frame makes the opt-out unnecessary. Per
   VISUAL_IDENTITY.md the cyan is a TRIM, never a saturated fill - a cyan field
   would compete with the real Grid in the world behind the panel. Role identity
   stays a 2px stripe in --ribbon-role, never the border and never the glow. */
.game-window.gf-window-staff {
    background: var(--gf-staff-border);
    filter: var(--gf-drop) drop-shadow(0 0 10px var(--gf-staff-glow));
}
.game-window.gf-window-staff::before {
    background: var(--gf-staff-bg);
    border-top: 2px solid var(--ribbon-role, var(--gf-grid-cyan));
}
.game-window.gf-window-staff .game-window-titlebar {
    background: linear-gradient(180deg, rgba(102,224,255,0.14), rgba(12,26,32,0.62));
    border-bottom: 1px solid var(--gf-staff-border);
}
.game-window.gf-window-staff .game-window-title {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--gf-staff-label);
}
.game-window.gf-window-staff.gf-window-focused {
    border-color: var(--gf-grid-cyan);
    box-shadow: var(--gf-shadow-strong), 0 0 0 1px var(--gf-staff-glow), var(--gf-inset);
}

/* `⬒ WARDEN · not visible to players` on a staff-only surface that is not a
   menu. Built by staff_menu.staffNote() so the wording and the cube size are
   identical wherever it appears. */
.gf-staff-note {
    display: flex; align-items: center; gap: 6px;
    padding: 3px 0 5px;
    border-bottom: 1px solid var(--gf-staff-border);
    font: 9px/1.4 var(--gf-mono);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gf-staff-label);
    flex: 0 0 auto;
}

/* The Blocks palette had NO stylesheet rule at all - it was built entirely from
   inline `style.cssText`, which is why it was the one staff panel with no way to
   restyle it. The inline rules still own its layout; this owns its skin. */
.block-palette {
    padding: 8px;
    color: var(--gf-staff-text);
}

/* Character select overlay */
#overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.92);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
    pointer-events: all;
}

#overlay-inner {
    width: 520px;
    text-align: center;
    pointer-events: all;
}

#overlay input {
    user-select: text;
    pointer-events: all;
}

#overlay button {
    pointer-events: all;
    cursor: pointer;
}

#overlay h1 {
    font-size: 42px;
    letter-spacing: 4px;
    color: var(--gf-trim);
    text-shadow: 0 0 20px rgba(var(--gf-trim-rgb), 0.5);
    margin-bottom: 6px;
}

#overlay .subtitle {
    color: var(--gf-text-3);
    font-size: 14px;
    margin-bottom: 32px;
    letter-spacing: 2px;
    text-transform: uppercase;
}

#input-name,
#input-password {
    display: block;
    width: 100%;
    padding: 12px 16px;
    background: rgba(var(--gf-bone-rgb), 0.08);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.2);
    color: #fff;
    font-family: var(--gf-font-ui);
    font-size: 16px;
    clip-path: var(--gf-cut);
    outline: none;
    margin-bottom: 12px;
    text-align: center;
    letter-spacing: 1px;
}
#input-password {
    /* Match the visual height/font of the username field */
    font-family: var(--gf-font-ui);
    letter-spacing: 4px;
}
#input-name:focus,
#input-password:focus {
    border-color: rgba(var(--gf-trim-rgb), 0.5);
    box-shadow: 0 0 8px rgba(var(--gf-trim-rgb), 0.15);
}

#auth-buttons {
    display: flex;
    gap: 12px;
    margin-top: 4px;
    margin-bottom: 8px;
}

.auth-btn {
    flex: 1;
    display: block;
    padding: 14px 16px;
    background: var(--gf-trim);
    color: #1a1000;
    font-family: var(--gf-font-ui);
    font-size: 16px;
    font-weight: bold;
    border: 2px solid var(--gf-trim);
    clip-path: var(--gf-cut);
    cursor: pointer;
    letter-spacing: 2px;
    text-transform: uppercase;
    transition: all 0.15s;
    text-align: center;
    pointer-events: all;
    text-decoration: none;
}
.auth-btn:hover {
    background: #ffd070;
    border-color: #ffd070;
}

.auth-btn.auth-btn-secondary {
    background: rgba(var(--gf-bone-rgb), 0.06);
    color: var(--gf-text-2);
    border: 2px solid rgba(var(--gf-bone-rgb), 0.2);
}
.auth-btn.auth-btn-secondary:hover {
    background: rgba(var(--gf-bone-rgb), 0.12);
    border-color: rgba(var(--gf-bone-rgb), 0.35);
    color: #fff;
}

.auth-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: none;
}

#char-select-links {
    margin-top: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    font-size: 13px;
}
.overlay-link {
    color: var(--gf-text-3);
    text-decoration: none;
    letter-spacing: 1px;
    transition: color 0.15s;
}
.overlay-link:hover {
    color: var(--gf-trim);
}
.overlay-link-sep {
    color: var(--gf-text-4);
}

.race-detail {
    background: rgba(var(--gf-bone-rgb), 0.04);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.1);
    clip-path: var(--gf-cut);
    padding: 12px 14px;
    margin-bottom: 16px;
    font-size: 12px;
    line-height: 1.55;
    color: var(--gf-text-2);
    text-align: left;
    min-height: 60px;
}

/* (btn-start removed - replaced by auth buttons) */

/* Character select / create */
#input-char-name {
    display: block;
    width: 100%;
    padding: 12px 16px;
    background: rgba(var(--gf-bone-rgb), 0.08);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.2);
    color: #fff;
    font-family: var(--gf-font-ui);
    font-size: 16px;
    clip-path: var(--gf-cut);
    outline: none;
    margin-bottom: 12px;
    text-align: center;
    letter-spacing: 1px;
}
#input-char-name:focus {
    border-color: rgba(var(--gf-trim-rgb), 0.5);
    box-shadow: 0 0 8px rgba(var(--gf-trim-rgb), 0.15);
}

#char-slots {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
    margin-bottom: 8px;
}

.char-slot {
    padding: 16px;
    background: rgba(var(--gf-bone-rgb), 0.05);
    border: 2px solid rgba(var(--gf-bone-rgb), 0.15);
    clip-path: var(--gf-cut);
    cursor: pointer;
    text-align: left;
    transition: all 0.15s;
    color: var(--gf-text-2);
}
.char-slot:hover {
    border-color: rgba(var(--gf-trim-rgb), 0.5);
    background: rgba(var(--gf-trim-rgb), 0.08);
}
.char-slot.empty {
    border-style: dashed;
    color: var(--gf-text-3);
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 110px;
    font-size: 14px;
    letter-spacing: 1px;
}

.char-slot .char-name {
    font-size: 18px;
    font-weight: bold;
    color: var(--gf-trim);
    display: block;
    margin-bottom: 4px;
}
.char-slot .char-class {
    text-transform: uppercase;
    font-size: 11px;
    letter-spacing: 1px;
    color: var(--gf-text-3);
    display: block;
    margin-bottom: 8px;
}
.char-slot .char-stats {
    font-size: 12px;
    color: var(--gf-text-3);
    margin-bottom: 8px;
}

.char-slot[data-race="veilborn"] .char-name { color: var(--gf-trim); }

.char-slot-actions {
    display: flex;
    gap: 6px;
    margin-top: 6px;
}

.char-slot-btn {
    flex: 1;
    padding: 6px 10px;
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    font-size: 12px;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 1px;
}
.char-slot-btn:hover { background: rgba(var(--gf-trim-rgb), 0.3); }
.char-slot-btn.danger {
    background: rgba(218,54,51,0.15);
    color: #f85149;
    border-color: rgba(218,54,51,0.4);
}
.char-slot-btn.danger:hover { background: rgba(218,54,51,0.3); }

/* Escape menu. A MODAL: it outranks the whole GameWindow band, which is what it
   never did - at 200 against a window counter starting at 1000, the minimap and
   every stat bar floated on top of it, and on touch those are exactly the two
   windows that stay floating instead of becoming full-screen sheets. */
#escape-menu {
    position: fixed;
    inset: 0;
    z-index: var(--gf-z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
}

.esc-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.7);
}

.esc-container {
    position: relative;
    z-index: 1;
    width: 380px;
    transition: width 0.2s ease;
}
.esc-container:has(.esc-panel-settings[style*="flex"]) {
    width: 540px;
}

.esc-panel {
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.12);
    clip-path: var(--gf-cut);
    padding: 32px 28px;
}

.esc-panel h2 {
    font-size: 22px;
    letter-spacing: 3px;
    color: var(--gf-trim);
    text-align: center;
    margin-bottom: 12px;
}

.esc-btn {
    width: 100%;
    padding: 12px;
    background: rgba(var(--gf-bone-rgb), 0.08);
    border: 1px solid var(--gf-border);
    color: var(--gf-text-1);
    font-size: 15px;
    clip-path: var(--gf-cut);
    cursor: pointer;
    transition: all 0.15s;
    letter-spacing: 1px;
}
.esc-btn:hover { background: rgba(var(--gf-bone-rgb), 0.15); color: #fff; }

.esc-btn-danger { color: #f85149; border-color: rgba(248,81,73,0.3); }
.esc-btn-danger:hover { background: rgba(248,81,73,0.15); }

.esc-btn-secondary { color: #8b949e; font-size: 13px; padding: 8px; margin-top: 8px; }

/* Settings tabs */
.esc-tabs {
    display: flex;
    gap: 6px;
    margin-bottom: 8px;
}

.esc-tab {
    flex: 1;
    padding: 8px;
    background: transparent;
    border: 1px solid rgba(var(--gf-bone-rgb), 0.1);
    color: #8b949e;
    font-size: 13px;
    clip-path: var(--gf-cut);
    cursor: pointer;
}
.esc-tab.active { background: rgba(var(--gf-trim-rgb), 0.15); color: var(--gf-trim); border-color: rgba(var(--gf-trim-rgb), 0.3); }

/* Settings panel - futuristic scanline overlay */
.esc-panel-settings {
    position: relative;
    max-width: 540px;
}
.esc-panel-settings::after {
    content: '';
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(
        0deg, transparent, transparent 2px,
        rgba(var(--gf-trim-rgb), 0.03) 2px, rgba(var(--gf-trim-rgb), 0.03) 4px
    );
    pointer-events: none;
    clip-path: var(--gf-cut);
}

.esc-btn-small {
    padding: 8px 16px;
    font-size: 12px;
    width: auto;
}

/* Keybind list */
.keybinds-list {
    max-height: 380px;
    overflow-y: auto;
}

/* ── Unified Options window (sidebar shell: Game | Sound | Keybinds | Macros | Help) ──
   Lives inside a resizable GameWindow, so panes flex to fill rather than
   capping at a fixed height like the old escape-modal layout did. The left
   rail holds ONE global search box that filters whichever pane is active,
   plus the section nav (gold left-rail marks the active section; badges show
   per-section match counts while a query is live). */
.gf-options-menu {
    display: flex;
    flex-direction: column;
    height: 100%;
    color: var(--gf-text-1);
    font-size: 13px;
}
.gf-options-shell {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-columns: 168px 1fr;
}
.gf-options-side {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 0;
    padding: 10px 8px 10px 10px;
    border-right: 1px solid var(--gf-border-soft);
}
.gf-options-search {
    width: 100%;
    padding: 7px 10px;
    background: rgba(0,0,0,0.35);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut);
    color: var(--gf-text-1);
    font-size: 12px;
    outline: none;
    transition: border-color 0.2s;
}
.gf-options-search:focus {
    border-color: rgba(var(--gf-trim-rgb), 0.5);
    box-shadow: 0 0 8px rgba(var(--gf-trim-rgb), 0.15);
}
.gf-options-nav {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.gf-options-nav-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 10px;
    border-left: 2px solid transparent;
    clip-path: var(--gf-cut-sm);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--gf-text-2);
    cursor: pointer;
    user-select: none;
    transition: background 0.12s, color 0.12s;
}
.gf-options-nav-item:hover { background: rgba(var(--gf-bone-rgb), 0.04); color: var(--gf-text-1); }
.gf-options-nav-item.gf-active {
    border-left-color: var(--gf-accent);
    background: rgba(var(--gf-trim-rgb), 0.08);
    color: var(--gf-accent-hot);
}
.gf-options-nav-badge {
    margin-left: auto;
    min-width: 16px;
    padding: 0 5px;
    line-height: 15px;
    clip-path: var(--gf-cut);
    text-align: center;
    font-size: 10px;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
    color: var(--gf-accent);
    background: rgba(var(--gf-trim-rgb), 0.12);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.3);
}
.gf-options-nav-badge.gf-zero { opacity: 0.45; }
.gf-options-main { display: flex; min-width: 0; min-height: 0; }
.gf-options-pane {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 10px 12px;
}
/* Global-search row states: class-hidden misses + a gold flash on the hits
   when jumping sections with a live query. */
.gf-opt-filtered { display: none !important; }
.gf-opt-hit { animation: gf-opt-hit 0.9s ease-out; }
@keyframes gf-opt-hit {
    0%   { background: rgba(var(--gf-trim-rgb), 0.26); box-shadow: 0 0 10px rgba(var(--gf-trim-rgb), 0.35); }
    100% { background: transparent; box-shadow: none; }
}
.gf-options-pane.macro-pane { padding: 0; }
.gf-options-pane.macro-pane > .gf-macro-editor { flex: 1; min-height: 0; }
.gf-options-menu .keybinds-list { max-height: none; flex: 1; min-height: 0; }
.gf-options-menu .help-list { flex: 1; min-height: 0; overflow-y: auto; }
.gf-options-menu .keybind-footer { align-items: center; gap: 8px; }
.gf-options-menu .gf-reset-confirm-msg {
    margin-right: auto;
    color: var(--gf-text-2);
    font-size: 12px;
}

/* ── Game Options pane ── */
.gf-options-pane.game-options-pane { overflow-y: auto; }
.gf-go-intro { color: var(--gf-text-3); font-size: 12px; line-height: 1.4; margin-bottom: 6px; }
.gf-go-group {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--gf-accent); margin: 14px 0 2px; padding-bottom: 4px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-go-row {
    display: flex; align-items: center; gap: 12px;
    padding: 7px 4px; cursor: pointer; clip-path: var(--gf-cut-sm);
}
.gf-go-row:hover { background: rgba(var(--gf-bone-rgb), 0.04); }
.gf-go-label { flex: 1; font-size: 13px; color: var(--gf-text-1); }
.gf-go-select { flex: 0 0 130px; }
.gf-go-check { width: 16px; height: 16px; accent-color: var(--gf-accent); cursor: pointer; }
.gf-go-color { flex: 0 0 42px; width: 42px; height: 24px; padding: 0; border: 1px solid var(--gf-border); clip-path: var(--gf-cut-sm); background: none; cursor: pointer; }
/* A row whose governing toggle is off: still readable, visibly inert. */
.gf-go-row-off { opacity: 0.45; }
.gf-go-row-off, .gf-go-row-off input { cursor: default; }
/* A sentence under a control (what a route choice means). */
.gf-go-note { padding: 0 4px 8px; font-size: 11.5px; line-height: 1.4; color: var(--gf-text-2, #a9a39a); }

/* In-place reconnect strip: the frozen world stays on screen beneath it while
   the socket is re-opened and the same session resumed (main.js
   beginInPlaceReconnect). Calm by design - gold trim over glass, no full
   overlay - because the old opaque CONNECTION LOST screen was the alarming
   half of a two-second blip. */
#link-strip {
    position: fixed;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 99999;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 14px;
    border: 1px solid rgba(217, 182, 94, 0.45);
    clip-path: var(--gf-cut-sm);
    background: rgba(14, 13, 12, 0.9);
    color: var(--gf-gold-bright, var(--gf-trim-bright));
    box-shadow: 0 0 18px rgba(0, 0, 0, 0.35);
    font: 11px/1.2 var(--gf-mono);
    letter-spacing: 0.14em;
    pointer-events: none;
}
#link-strip[hidden] { display: none; }
#link-strip-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--gf-gold-bright, var(--gf-trim-bright));
    animation: gf-link-pulse 1.1s ease-in-out infinite;
}
@keyframes gf-link-pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }

/* Restart notice tone: the same strip, a different voice. Gold with a 1.1s pulse
   reads as "your connection is in trouble", and a scheduled restart is not a
   fault - it must not borrow that alarm. But it is still the one non-blocking
   top-of-screen band, so it stays the SAME element (main.js _restartPaint is its
   only writer here) rather than a second banner fighting for the same 12px. No
   new z-index: #link-strip already sits at 99999, and z_layers.js stays the
   canonical table. */
#link-strip.gf-restart {
    border-color: rgba(214, 138, 74, 0.55);
    color: #E2A45C;
}
#link-strip.gf-restart #link-strip-dot {
    background: #E2A45C;
    animation-duration: 2.2s;
}
#link-strip.gf-restart-now {
    border-color: rgba(206, 92, 74, 0.6);
    color: #E08A6E;
}
#link-strip.gf-restart-now #link-strip-dot {
    background: #E08A6E;
    animation-duration: 0.7s;
}

/* Sound options pane (reuses the gf-go-* row/label/group look) */
.gf-options-pane.sound-options-pane { overflow-y: auto; }
.gf-so-slider { flex: 0 0 180px; display: flex; align-items: center; gap: 10px; }
.gf-so-slider input[type="range"] {
    flex: 1; height: 4px; cursor: pointer; accent-color: var(--gf-accent);
    background: transparent;
}
.gf-so-val {
    flex: 0 0 38px; text-align: right; font-size: 12px;
    font-variant-numeric: tabular-nums; color: var(--gf-text-3);
}
.gf-so-np { flex: 1; font-size: 13px; color: var(--gf-accent); font-style: italic; }

.keybind-footer {
    display: flex;
    justify-content: flex-end;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid rgba(var(--gf-bone-rgb), 0.06);
}

/* Category headers */
.keybind-category-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 4px 6px;
    cursor: pointer;
    user-select: none;
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.15);
    margin-top: 4px;
}
.keybind-cat-arrow {
    color: var(--gf-text-4);
    font-size: 12px;
    font-family: var(--gf-font-mono);
    width: 12px;
}
.keybind-cat-label {
    color: var(--gf-trim);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 2px;
    text-transform: uppercase;
    flex: 1;
}
.keybind-cat-count {
    color: var(--gf-text-4);
    font-size: 11px;
    font-family: var(--gf-font-mono);
}
.keybind-category-header:hover .keybind-cat-label { color: #ffd070; }

/* Keybind rows */
.keybind-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 7px 4px 7px 20px;
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.04);
    border-left: 3px solid transparent;
    transition: opacity 0.2s, max-height 0.3s, padding 0.2s, border-color 0.2s;
    max-height: 50px;
    overflow: hidden;
}
.keybind-row:hover { background: rgba(var(--gf-bone-rgb), 0.03); }
.keybind-row.keybind-filtered {
    opacity: 0;
    max-height: 0;
    padding: 0 4px 0 20px;
    border-bottom: none;
}
.keybind-row.keybind-conflict {
    border-left-color: #f85149;
    animation: conflictPulse 2s ease infinite;
}
.keybind-row.keybind-locked { opacity: 0.5; }

.keybind-category-header.keybind-filtered {
    display: none;
}

/* Help tab (Settings → Help) */
.help-list {
    max-height: 380px;
    overflow-y: auto;
}
.help-blurb {
    color: var(--gf-text-2);
    font-size: 12px;
    line-height: 1.55;
    padding: 0 4px 10px;
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.06);
    margin-bottom: 8px;
}
.help-blurb p { margin: 0 0 6px; }
.help-blurb p:last-child { margin-bottom: 0; }
.help-blurb kbd {
    display: inline-block;
    background: rgba(0,0,0,0.5);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.3);
    color: var(--gf-trim);
    padding: 1px 6px;
    clip-path: var(--gf-cut-sm);
    font-size: 10px;
    font-family: var(--gf-font-mono);
    vertical-align: baseline;
}
.help-section {
    margin-bottom: 10px;
}
.help-section.help-filtered {
    display: none;
}
.help-section-title {
    color: var(--gf-trim);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 2px;
    text-transform: uppercase;
    margin: 8px 0 4px;
    padding: 6px 4px 4px;
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.15);
}
.help-rows {
    display: flex;
    flex-direction: column;
}
.help-row {
    display: grid;
    grid-template-columns: 150px 120px 1fr;
    gap: 10px;
    padding: 6px 4px 6px 20px;
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.04);
    align-items: baseline;
    transition: opacity 0.2s, max-height 0.3s, padding 0.2s;
    max-height: 60px;
    overflow: hidden;
}
.help-row:hover { background: rgba(var(--gf-bone-rgb), 0.03); }
.help-row.help-filtered {
    opacity: 0;
    max-height: 0;
    padding: 0 4px 0 20px;
    border-bottom: none;
}
.help-row-label {
    color: #e0e0e0;
    font-size: 12px;
}
.help-row-bind {
    color: #7dd3fc;
    font-size: 11px;
    font-family: var(--gf-font-mono);
}
.help-row-desc {
    color: var(--gf-text-3);
    font-size: 11px;
    line-height: 1.4;
}

/* Quick Help overlay (reuses .help-blurb / .help-row* above) */
.quick-help-panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow-y: auto;
    padding: 10px 12px 12px;
    background: var(--gf-panel-bg-strong);
}
.help-row-bind.qh-unbound {
    color: var(--gf-text-3);
    opacity: 0.7;
    font-style: italic;
}

/* Floating "?" quick-help button */
#gf-help-btn {
    position: fixed;
    right: 14px;
    bottom: 14px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--gf-panel-bg);
    border: 1px solid var(--gf-border-accent);
    color: var(--gf-accent);
    font-family: var(--gf-mono);
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    z-index: 50;
    box-shadow: var(--gf-shadow);
    transition: transform 0.12s, box-shadow 0.12s, color 0.12s;
}
#gf-help-btn:hover {
    color: var(--gf-accent-hot);
    transform: scale(1.12);
    box-shadow: var(--gf-shadow), 0 0 10px var(--gf-accent-glow);
}

/* Staff bug/idea submission button - sits just left of the "?" button */
#gf-submit-btn {
    position: fixed;
    right: 50px;
    bottom: 14px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--gf-panel-bg);
    border: 1px solid var(--gf-border-accent);
    color: var(--gf-accent);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    z-index: 50;
    box-shadow: var(--gf-shadow);
    transition: transform 0.12s, box-shadow 0.12s;
}
#gf-submit-btn:hover {
    transform: scale(1.12);
    box-shadow: var(--gf-shadow), 0 0 10px var(--gf-accent-glow);
}

/* My Pets roster button - sits just left of the 📝 button */
#gf-pets-btn {
    position: fixed;
    right: 86px;
    bottom: 14px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--gf-panel-bg);
    border: 1px solid var(--gf-border-accent);
    color: var(--gf-accent);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    z-index: 50;
    box-shadow: var(--gf-shadow);
    transition: transform 0.12s, box-shadow 0.12s;
}
#gf-pets-btn:hover {
    transform: scale(1.12);
    box-shadow: var(--gf-shadow), 0 0 10px var(--gf-accent-glow);
}

/* Report / Vote panel (in-game submissions) */
.ss-panel {
    display: flex;
    flex-direction: column;
    font-size: 13px;
    color: var(--gf-text, var(--gf-bone));
    height: 100%;
}
.ss-hidden { display: none !important; }
.ss-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--gf-border, rgba(var(--gf-trim-rgb), 0.25)); }
.ss-tab {
    flex: 1; padding: 8px 6px; background: transparent; border: none;
    color: var(--gf-dim, var(--gf-dim)); cursor: pointer; font: inherit; font-size: 12px;
    border-bottom: 2px solid transparent;
}
.ss-tab-active { color: var(--gf-accent, var(--gf-ember)); border-bottom-color: var(--gf-accent, var(--gf-ember)); }
.ss-tabpage { padding: 12px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 10px; }
.ss-typecards { display: flex; gap: 8px; }
.ss-typecard {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 9px 4px; clip-path: var(--gf-cut); cursor: pointer; font: inherit; font-size: 12px;
    background: rgba(0,0,0,0.3); border: 1px solid rgba(var(--gf-bone-rgb), 0.12); color: inherit;
}
.ss-typecard span { font-size: 20px; }
.ss-typecard-active { border-color: var(--gf-accent, var(--gf-ember)); background: rgba(226,98,42,0.15); }
.ss-row { display: flex; flex-direction: column; gap: 4px; }
.ss-row > label { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--gf-dim, var(--gf-dim)); }
.ss-panel select, .ss-panel input[type="text"], .ss-panel textarea {
    background: rgba(0,0,0,0.35); border: 1px solid var(--gf-border); clip-path: var(--gf-cut-sm);
    color: inherit; padding: 7px 8px; font: inherit; width: 100%; box-sizing: border-box;
}
.ss-panel textarea { resize: vertical; min-height: 44px; }
.ss-inline { flex-direction: row; gap: 12px; }
.ss-inline > div { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.ss-step { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.ss-step-n { color: var(--gf-dim, var(--gf-dim)); font-size: 12px; min-width: 16px; }
.ss-step input { flex: 1; }
.ss-step-x, .ss-add-step {
    background: rgba(0,0,0,0.3); border: 1px solid var(--gf-border); clip-path: var(--gf-cut-sm);
    color: var(--gf-dim, var(--gf-dim)); cursor: pointer; font: inherit; padding: 4px 8px;
}
.ss-add-step { align-self: flex-start; font-size: 12px; }
.ss-sev { width: 100%; }
.ss-shot-head { display: flex; align-items: center; justify-content: space-between; }
.ss-recapture {
    background: rgba(0,0,0,0.3); border: 1px solid var(--gf-border-accent, rgba(226,98,42,0.4));
    clip-path: var(--gf-cut-sm); color: inherit; cursor: pointer; font: inherit; font-size: 11px; padding: 4px 8px;
}
.ss-shots { display: flex; flex-wrap: wrap; gap: 8px; }
.ss-shot { position: relative; }
.ss-shot-canvas { max-width: 160px; border: 1px solid var(--gf-border); clip-path: var(--gf-cut-sm); cursor: crosshair; touch-action: none; }
.ss-shot-del {
    position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; border-radius: 50%;
    background: rgba(0,0,0,0.7); border: none; color: #fff; cursor: pointer; font-size: 11px; line-height: 1;
}
.ss-markhint { font-size: 11px; color: var(--gf-dim, var(--gf-dim)); }
.ss-diag { font-size: 12px; }
.ss-diag summary { cursor: pointer; color: var(--gf-dim, var(--gf-dim)); }
.ss-diag-body { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 8px 0; }
.ss-diag-row { display: contents; }
.ss-diag-row span:first-child { color: var(--gf-dim, var(--gf-dim)); }
.ss-diag-row span:last-child { color: var(--gf-text, var(--gf-bone)); word-break: break-word; }
.ss-diag-note { font-size: 11px; color: var(--gf-dim, var(--gf-dim)); opacity: 0.85; }
.ss-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 2px; }
.ss-actions button {
    padding: 7px 16px; clip-path: var(--gf-cut-sm); border: 1px solid var(--gf-border-accent, rgba(226,98,42,0.4));
    background: rgba(0,0,0,0.3); color: inherit; cursor: pointer; font: inherit;
}
.ss-submit { background: var(--gf-accent, var(--gf-ember)); color: var(--gf-plate); font-weight: 600; border-color: transparent; }
.ss-actions button:hover { filter: brightness(1.12); }
.ss-status { font-size: 12px; color: var(--gf-accent, var(--gf-ember)); min-height: 14px; }
.ss-empty { color: var(--gf-dim, var(--gf-dim)); text-align: center; padding: 24px 8px; font-size: 12px; }
/* My Reports + Vote lists */
.ss-mine, .ss-vote {
    display: flex; align-items: center; gap: 8px; padding: 8px; clip-path: var(--gf-cut);
    background: rgba(0,0,0,0.25); border: 1px solid rgba(var(--gf-bone-rgb), 0.08); margin-bottom: 6px;
}
.ss-mine { cursor: pointer; }
.ss-mine:hover { border-color: var(--gf-border-accent, rgba(226,98,42,0.4)); }
.ss-mine-title, .ss-vote-title { flex: 1; font-size: 12px; }
.ss-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--gf-accent, var(--gf-ember)); }
.ss-badge {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.4px; padding: 2px 7px;
    clip-path: var(--gf-cut-sm); border: 1px solid rgba(var(--gf-trim-rgb), 0.4); color: var(--gf-ash-gold, var(--gf-trim)); white-space: nowrap;
}
.ss-badge-done, .ss-badge-shipped { color: #7fd18a; border-color: rgba(127,209,138,0.4); }
.ss-badge-in_progress, .ss-badge-planned { color: var(--gf-accent, var(--gf-ember)); border-color: rgba(226,98,42,0.45); }
.ss-badge-wontfix, .ss-badge-duplicate { color: var(--gf-dim); }
.ss-vote-btn {
    background: var(--gf-face);
    border: 1px solid var(--gf-border-accent); clip-path: var(--gf-cut-sm);
    color: var(--gf-accent-hot); cursor: pointer; font: inherit; font-size: 12px; padding: 4px 8px; white-space: nowrap;
}
.ss-vote-btn.ss-voted { background: var(--gf-accent, var(--gf-ember)); color: var(--gf-plate); border-color: transparent; }

/* Chat config tab (Settings → Chat) */
.chat-config-list {
    display: flex;
    flex-direction: column;
    padding: 8px 0;
}
.chat-config-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 16px;
    border-bottom: 1px solid rgba(var(--gf-bone-rgb), 0.04);
}
.chat-config-row:hover { background: rgba(var(--gf-bone-rgb), 0.03); }
.chat-config-toggle {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    color: var(--gf-text-2);
    font-size: 13px;
}
.chat-config-toggle input[type="checkbox"] {
    width: 14px;
    height: 14px;
    accent-color: var(--gf-trim);
    cursor: pointer;
}
.chat-config-row input[type="color"] {
    width: 32px;
    height: 22px;
    padding: 0;
    border: 1px solid rgba(var(--gf-bone-rgb), 0.2);
    background: transparent;
    cursor: pointer;
}
.chat-config-size {
    width: 44px;
    background: rgba(0,0,0,0.4);
    border: 1px solid var(--gf-border);
    color: var(--gf-text-1);
    padding: 2px 4px;
    font-size: 12px;
    text-align: right;
    font-family: var(--gf-font-mono);
}
.chat-config-size-suffix {
    color: var(--gf-text-4);
    font-size: 11px;
}

@keyframes conflictPulse {
    0%, 100% { border-left-color: #f85149; }
    50% { border-left-color: rgba(248,81,73,0.3); }
}

.keybind-label {
    font-size: 13px;
    color: var(--gf-text-2);
    flex: 1;
}
.keybind-note {
    display: block;
    font-size: 10px;
    color: var(--gf-text-4);
    font-style: italic;
}

.keybind-key-wrap {
    display: flex;
    align-items: center;
    gap: 6px;
}

.keybind-warn {
    color: #f85149;
    font-size: 12px;
    font-weight: bold;
    width: 18px;
    height: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid rgba(248,81,73,0.4);
    clip-path: var(--gf-cut-sm);
    cursor: help;
}
.keybind-warn-browser {
    color: #d29922;
    border-color: rgba(210,153,34,0.4);
}

.keybind-key {
    min-width: 90px;
    padding: 6px 14px;
    background: linear-gradient(180deg, rgba(var(--gf-bone-rgb), 0.08), rgba(var(--gf-bone-rgb), 0.03));
    border: 1px solid var(--gf-border);
    color: var(--gf-trim);
    font-size: 13px;
    font-family: var(--gf-font-mono);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
    text-align: center;
    transition: all 0.15s;
    white-space: nowrap;
}
.keybind-key:hover {
    border-color: var(--gf-trim);
    box-shadow: 0 0 8px rgba(var(--gf-trim-rgb), 0.3);
}
.keybind-key-locked {
    cursor: default;
    opacity: 0.6;
}
.keybind-key-locked:hover {
    border-color: rgba(var(--gf-bone-rgb), 0.15);
    box-shadow: none;
}
.keybind-key.rebinding {
    background: rgba(var(--gf-trim-rgb), 0.15);
    border-color: var(--gf-trim);
    color: #fff;
    animation: scanPulse 1.5s ease infinite;
}

@keyframes scanPulse {
    0%, 100% { box-shadow: 0 0 4px rgba(var(--gf-trim-rgb), 0.2); }
    50% { box-shadow: 0 0 16px rgba(var(--gf-trim-rgb), 0.5); }
}

/* ═══════════════════════════════════════════
   Stat bars
   ═══════════════════════════════════════════ */

#stat-bars {
    position: fixed;
    bottom: 12px; left: 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    z-index: 10;
    pointer-events: none;
}

.stat-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    height: 16px;
}

.stat-bar-label {
    font-size: 10px;
    font-weight: 600;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-3);
    width: 28px;
    text-align: right;
    letter-spacing: 1px;
}

.stat-bar-track {
    width: 180px;
    height: 10px;
    background: rgba(0,0,0,0.6);
    border: 1px solid var(--gf-border-accent);
    box-shadow: 0 0 0 1px rgba(0,0,0,0.45);
    clip-path: var(--gf-cut-sm);
    overflow: hidden;
}

.stat-bar-fill {
    height: 100%;
    clip-path: var(--gf-cut-sm);
    transition: width 0.3s ease;
}

.stat-bar[data-stat="hp"] .stat-bar-fill { background: linear-gradient(90deg, #a03030, #c04040); }
.stat-bar[data-stat="stamina"] .stat-bar-fill { background: linear-gradient(90deg, #907020, #c0a040); }
.stat-bar[data-stat="mana"] .stat-bar-fill { background: linear-gradient(90deg, #304080, #4060c0); }

.stat-bar-value {
    font-size: 10px;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-3);
    min-width: 50px;
}

.stat-bar.stat-bar-low .stat-bar-fill {
    animation: lowHpPulse 1s ease infinite;
}

@keyframes lowHpPulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* ── Poisoned HP bars ──
 * The venom-green fill color itself is set in JS (from POISON_HP_COLOR in
 * ui/hp_color.js), because every one of these surfaces writes its fill color
 * INLINE and a stylesheet rule could never win against that. What lives here is
 * the second channel: a slow pulse, so the state does not rest on telling two
 * greens apart - green already means "healthy" on half these surfaces, and a
 * poisoned bar is the opposite of healthy.
 *
 * Slower and shallower than lowHpPulse on purpose: low HP is urgent, poison is
 * a condition you are carrying. They can be on at once and must not read as one
 * blinking thing.
 *
 * The 3D overhead plate is deliberately absent - it is a per-entity
 * CanvasTexture, where a pulse would mean re-uploading a texture per body per
 * frame. It takes the hue flip alone (see world.js `_drawMiniBar`).
 */
@keyframes gf-poison-pulse {
    0%, 100% { filter: none; }
    50%      { filter: brightness(1.35) saturate(1.25); }
}

.gf-poisoned .stat-bar-fill,
.gf-poisoned .companion-fill,
.gf-poisoned .gf-pf-fill,
.gf-poisoned .proster-hp-fill,
.gf-oh-bar.gf-poisoned > i,
.mobile-threat-bar.gf-poisoned,
#gf-target-frame.gf-poisoned .gf-tf-fill {
    animation: gf-poison-pulse 2.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .gf-poisoned .stat-bar-fill,
    .gf-poisoned .companion-fill,
    .gf-poisoned .gf-pf-fill,
    .gf-poisoned .proster-hp-fill,
    .gf-oh-bar.gf-poisoned > i,
    .mobile-threat-bar.gf-poisoned,
    #gf-target-frame.gf-poisoned .gf-tf-fill { animation: none; }
}

/* ── MobileStatBar (draggable per-mobile stat window) ── */
/* Stat-bar windows are always small; override the generic 100px min-height
 * so content doesn't trigger scrollbars when a bar shows HP only. */
.game-window[data-window-id^="statbar:"] {
    min-height: 0;
    min-width: 0;
}
.game-window[data-window-id^="statbar:"] .game-window-content {
    overflow: visible;
}
.stat-bar-panel {
    padding: 6px 8px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 180px;
}
.stat-bar-row {
    display: flex;
    align-items: center;
    gap: 6px;
    height: 14px;
}
.stat-bar-row .stat-bar-label {
    width: 24px;
}
.stat-bar-row .stat-bar-track {
    flex: 1 1 auto;
    width: auto;
    height: 10px;
}
.stat-bar-row .stat-bar-fill {
    width: 0%;
    height: 100%;
    background: #cc3a3a;
    transition: width 0.2s ease;
}
.stat-bar-val {
    font-size: 10px;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-2);
    min-width: 56px;
    text-align: right;
}

/* UO-style out-of-range stat bar: the mobile left AoI, updates stopped, the
   window stays parked + draggable, grayed until the mobile re-streams in. */
.game-window.gf-stale .stat-bar-panel {
    filter: grayscale(0.85);
    opacity: 0.55;
}
.game-window.gf-stale .game-window-title {
    opacity: 0.6;
}

/* Targeting cursor hovering a resolvable mobile → pointer affordance. */
body.gf-mobile-hover #canvas-container {
    cursor: pointer;
}

/* ── Stat-bar cursors ──
   A stat bar has exactly two kinds of surface and they must not share a cursor:
   the TITLEBAR is the drag handle (only the titlebar starts a window drag) and
   the BODY is an action surface - click to resolve a targeting cursor onto this
   mobile, double-click while at war to engage it, right-click for its menu. The
   body had no cursor at all, so a fully interactive panel read as inert text.
   These are the fallbacks: on the world/UI surfaces that opt into the custom
   Gridforge cursor, `.gf-custom-cursor-active *` hides the native one outright
   and the SVG cursor carries the state instead. */
.game-window[data-window-id^="statbar:"] .game-window-content {
    cursor: pointer;
}
/* The expanded self panel is a readout with its own interactive chips, not a
   target surface - it never resolves a cursor (the dblclick handler skips it). */
.game-window[data-window-id^="statbar:"] .stat-bar-detail {
    cursor: default;
}

/* ENGAGED: the mobile the auto-attack loop is swinging at. Selecting a target
   and fighting it are two different facts, so the bar you are attacking has to
   be distinguishable from the four beside it that you are merely watching.
   Ember trim + a leading swords glyph - a SILHOUETTE cue, not color alone, and
   never Grid cyan (that is staff chrome). */
.game-window[data-window-id^="statbar:"].gf-bar-engaged {
    border-color: var(--gf-ember);
    box-shadow: var(--gf-shadow-strong), 0 0 0 1px rgba(226, 98, 42, 0.45),
                0 0 10px rgba(226, 98, 42, 0.35);
}
.game-window[data-window-id^="statbar:"].gf-bar-engaged .game-window-titlebar {
    background: linear-gradient(180deg, rgba(226, 98, 42, 0.26), rgba(40, 22, 14, 0.62));
    border-bottom-color: rgba(226, 98, 42, 0.55);
}
.game-window[data-window-id^="statbar:"].gf-bar-engaged .game-window-title::before {
    content: '⚔';
    margin-right: 5px;
    color: var(--gf-ember-hot);
    text-shadow: 0 0 6px rgba(226, 98, 42, 0.6);
}

/* ── UO-style expanded status panel (self bar) ── */
.stat-bar-detail {
    margin-top: 5px;
    padding-top: 5px;
    border-top: 1px solid rgba(var(--gf-trim-rgb), 0.35);
    display: flex;
    flex-direction: column;
    gap: 1px;
    font-family: var(--gf-font-mono);
    font-size: 10.5px;
}
.stat-bar-detail .sbd-sec {
    color: var(--gf-trim);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    font-size: 9px;
    margin: 5px 0 1px;
    opacity: 0.85;
}
.stat-bar-detail .sbd-sec:first-child { margin-top: 0; }
.stat-bar-detail .sbd-row {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 0 2px;
    line-height: 15px;
}
.stat-bar-detail .sbd-label { color: var(--gf-dim); }
/* The self bar's width is FIXED (stat_bar.js SELF_BAR_WIDTH*) so a long value
   (weapon names) must ellipsize instead of widening the panel. */
.stat-bar-detail .sbd-val {
    color: #e6dcc4; font-weight: 600; white-space: nowrap;
    max-width: 150px; overflow: hidden; text-overflow: ellipsis;
}

/* Plain row sections pack two-up (the expanded panel is wide); toggle-bearing
   sections (Attributes) stay full-width so the chip groups have room. */
.stat-bar-detail .sbd-rows {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 14px;
    row-gap: 1px;
}
.stat-bar-detail .sbd-rows-full { grid-template-columns: 1fr; }
.stat-bar-detail .sbd-rows .sbd-row { min-width: 0; }

/* Regeneration strip: one mini-card per pool, accent-edged in the pool color,
   big total /s over a base + added line (full breakdown in the tooltip). */
.stat-bar-detail .sbd-regen {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
    margin-bottom: 2px;
}
.stat-bar-detail .sbd-regen-card {
    display: flex; flex-direction: column; gap: 1px;
    padding: 5px 7px;
    min-width: 0;
    background: var(--gf-panel-bg-soft);
    border: 1px solid var(--gf-border-soft);
    border-left: 2px solid var(--gf-text-3);
    clip-path: var(--gf-cut-sm);
}
.stat-bar-detail .sbd-rc-hp { border-left-color: #cc3a3a; }
.stat-bar-detail .sbd-rc-stamina { border-left-color: var(--gf-stamina); }
.stat-bar-detail .sbd-rc-mana { border-left-color: var(--gf-mana); }
.stat-bar-detail .sbd-rc-name {
    font-size: 8.5px; text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--gf-text-3);
}
.stat-bar-detail .sbd-rc-rate {
    font-size: 13px; font-weight: 700; line-height: 1.15;
    color: var(--gf-text-1);
}
.stat-bar-detail .sbd-rc-rate em {
    font-style: normal; font-size: 9px; font-weight: 600; margin-left: 1px;
    color: var(--gf-text-3);
}
.stat-bar-detail .sbd-rc-sub {
    font-size: 8.5px; color: var(--gf-text-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Resist chips: all seven axes always render so the layout is stable - active
   resists take the Grid-cyan accent, vulnerabilities read as danger, zeros dim. */
.stat-bar-detail .sbd-resists {
    display: flex; flex-wrap: wrap; gap: 4px;
    margin-bottom: 3px;
}
.stat-bar-detail .sbd-res-chip {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 2px 6px 2px 4px;
    background: var(--gf-panel-bg-soft);
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-2);
}
.stat-bar-detail .sbd-res-chip .gf-ic { color: inherit; }
.stat-bar-detail .sbd-res-chip.is-zero { opacity: 0.45; }
.stat-bar-detail .sbd-res-chip.is-on {
    border-color: rgba(102, 224, 255, 0.45);
    color: #9ce8ff;
}
.stat-bar-detail .sbd-res-chip.is-neg {
    border-color: rgba(224, 82, 74, 0.5);
    color: var(--gf-danger);
}
.stat-bar-detail .sbd-res-val { font-size: 9.5px; font-weight: 700; }

/* Armor footer under the resist chips - armor is its own mitigation stage. */
.stat-bar-detail .sbd-armor {
    display: flex; align-items: center; gap: 5px;
    padding: 1px 2px 0;
    font-size: 9.5px;
    color: var(--gf-text-2);
}
.stat-bar-detail .sbd-armor .gf-ic { color: var(--gf-accent); }
.stat-bar-detail .sbd-armor-mit { margin-left: auto; color: var(--gf-text-3); }

/* Discreet expand/collapse toggle in the self bar's titlebar. */
.stat-bar-expand {
    background: transparent;
    border: none;
    color: rgba(var(--gf-bone-rgb), 0.65);
    font-size: 11px;
    line-height: 1;
    padding: 0 5px;
    cursor: pointer;
}
.stat-bar-expand:hover { color: #e2a24a; }

/* ── Companion (pet) bars - compact rows under the self bar ── */
.companion-bars {
    display: flex;
    flex-direction: column;
    gap: 3px;
    margin-top: 5px;
    padding-top: 5px;
    border-top: 1px solid rgba(var(--gf-bone-rgb), 0.12);
}
.companion-bars:empty { display: none; }
.companion-pet {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 1px 3px;
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.companion-pet:hover { background: rgba(var(--gf-bone-rgb), 0.07); }
.companion-pet.companion-hidden { opacity: 0.4; }
.companion-name {
    font-size: 10px;
    font-weight: 600;
    font-family: var(--gf-font-mono);
    color: #9fd0a0;
    letter-spacing: 0.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 172px;
}
.companion-row {
    display: flex;
    align-items: center;
    gap: 5px;
    height: 9px;
}
.companion-row .companion-label {
    font-size: 8px;
    width: 18px;
    text-align: right;
    color: var(--gf-text-3);
    font-family: var(--gf-font-mono);
}
.companion-row .companion-track {
    flex: 1 1 auto;
    height: 7px;
    background: rgba(0,0,0,0.6);
    border: 1px solid var(--gf-border-accent, rgba(var(--gf-bone-rgb), 0.15));
    clip-path: var(--gf-cut-sm);
    overflow: hidden;
}
.companion-row .companion-fill {
    height: 100%;
    width: 0%;
    clip-path: var(--gf-cut-sm);
    transition: width 0.2s ease;
}
.companion-row .companion-val {
    font-size: 8px;
    min-width: 44px;
    text-align: right;
    color: var(--gf-text-3);
    font-family: var(--gf-font-mono);
}

/* Targeting cursor prompt */
#targeting-prompt.targeting-mobile {
    border: 1px solid #60c080;
    color: #c0f0c0;
}
body.targeting-active {
    cursor: crosshair;
}
/* A ground cursor whose current pick would be REFUSED - no ground within reach,
   or a patrol leg that is too long/too short. The world-space ring turns Ember at
   the same moment (main.js GROUND_CURSOR_BAD); this is the same verdict on the
   prompt, so the reason and the refusal never disagree. Grid cyan is reserved for
   the Grid - a "you may click here" cursor is the ring, never this caption. */
body.gf-cursor-invalid {
    cursor: not-allowed;
}
body.gf-cursor-invalid #targeting-prompt {
    border: 1px solid var(--gf-ember);
    color: var(--gf-ember-hot);
}

/* UO-style: a targeting cursor can resolve on UI surfaces, not just the 3D
   world. A mobile cursor → click a health/stat bar; an item cursor → click an
   occupied hotbar/pack slot. Light those surfaces up so the player sees them
   as clickable targets. */
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"],
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"] .game-window-titlebar,
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"] .game-window-content,
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"] .stat-bar-detail,
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"] .companion-pet {
    cursor: crosshair;
}
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"] {
    outline: 1px solid rgba(96, 192, 128, 0.6);
    outline-offset: 1px;
}
body.gf-targeting-mobile .game-window[data-window-id^="statbar:"]:hover {
    outline-color: #66ff88;
    box-shadow: 0 0 9px rgba(102, 255, 136, 0.5);
}
body.gf-targeting-item .gf-bar-builder .gf-slot:not(.gf-slot-empty),
body.gf-targeting-item .gf-slot-itemref,
body.gf-targeting-item .inv-slot.inv-slot-filled {
    cursor: crosshair;
}
body.gf-targeting-item .gf-bar-builder .gf-slot:not(.gf-slot-empty):hover,
body.gf-targeting-item .gf-slot-itemref:hover,
body.gf-targeting-item .inv-slot.inv-slot-filled:hover {
    outline: 1px solid #66ff88;
    box-shadow: 0 0 9px rgba(102, 255, 136, 0.5);
}

/* ═══════════════════════════════════════════
   Hotbar
   ═══════════════════════════════════════════ */

#hotbar {
    position: fixed;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 4px;
    padding: 4px;
    background: rgba(0,0,0,0.55);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.1);
    clip-path: var(--gf-cut-sm);
    z-index: 5;
    pointer-events: none;
}
.hotbar-slot {
    width: 44px;
    height: 44px;
    background: rgba(var(--gf-bone-rgb), 0.04);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.12);
    clip-path: var(--gf-cut-sm);
    position: relative;
    transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
    pointer-events: auto;
}
.hotbar-slot.hotbar-slot-active {
    border-color: var(--gf-trim);
    background: rgba(var(--gf-trim-rgb), 0.15);
    box-shadow: 0 0 8px rgba(var(--gf-trim-rgb), 0.4);
}
.hotbar-slot-key {
    position: absolute;
    top: 2px;
    left: 4px;
    font-size: 10px;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-3);
    pointer-events: none;
}
.hotbar-slot-active .hotbar-slot-key {
    color: var(--gf-trim);
}

/* ═══════════════════════════════════════════
   Character sheet
   ═══════════════════════════════════════════ */

/* The old standalone #character-sheet panel (and its .cs-header/.cs-title/
   .cs-close chrome) is retired - its content lives in the paperdoll + the
   Skills/Titles windows now. The cs-* stat/skill/section visual classes below
   live on, reused by those panels. .cs-body is just an inner padding wrapper:
   it must NOT scroll - the GameWindow content area is the single scroll region
   (the nested-overflow double scrollbar was the bug this revamp fixes). */
.cs-body {
    padding: 14px 18px;
}

/* Stats section */
.cs-section-header {
    font-size: 11px;
    font-weight: 600;
    color: var(--gf-trim);
    letter-spacing: 2px;
    text-transform: uppercase;
    padding: 8px 0 6px;
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.1);
    margin-bottom: 8px;
    cursor: pointer;
}
.cs-section-header:hover { color: #ffd070; }

.cs-stat-row {
    display: flex;
    align-items: center;
    padding: 6px 0;
    gap: 10px;
}
.cs-stat-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--gf-text-2);
    width: 40px;
}
.cs-stat-value {
    font-size: 14px;
    font-family: var(--gf-font-mono);
    color: #fff;
    width: 36px;
    text-align: right;
}
.cs-stat-toggle {
    width: 24px;
    height: 24px;
    clip-path: var(--gf-cut-sm);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.2);
    background: rgba(var(--gf-bone-rgb), 0.05);
    color: var(--gf-text-3);
    font-size: 12px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
}
.cs-stat-toggle:hover { border-color: rgba(255,255,255,0.4); }
.cs-stat-toggle[data-toggle="up"] { color: #4caf50; border-color: rgba(76,175,80,0.4); }
.cs-stat-toggle[data-toggle="down"] { color: #f85149; border-color: rgba(248,81,73,0.4); }
.cs-stat-toggle[data-toggle="locked"] { color: var(--gf-text-3); border-color: rgba(var(--gf-bone-rgb), 0.15); }

.cs-cap-display {
    font-size: 11px;
    color: var(--gf-text-4);
    font-family: var(--gf-font-mono);
    text-align: right;
    padding: 4px 0 12px;
}

/* Skills section */
.cs-skill-row {
    display: flex;
    align-items: center;
    padding: 4px 0;
    gap: 8px;
}
.cs-skill-name {
    font-size: 12px;
    color: var(--gf-text-2);
    width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cs-skill-bar-track {
    flex: 1;
    height: 6px;
    background: rgba(var(--gf-bone-rgb), 0.06);
    clip-path: var(--gf-cut-sm);
    overflow: hidden;
}
.cs-skill-bar-fill {
    height: 100%;
    background: var(--gf-trim);
    clip-path: var(--gf-cut-sm);
    transition: width 0.3s ease;
}
.cs-skill-value {
    font-size: 11px;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-3);
    width: 40px;
    text-align: right;
}
.cs-skill-toggle {
    width: 20px;
    height: 20px;
    clip-path: var(--gf-cut-sm);
    border: 1px solid var(--gf-border);
    background: rgba(var(--gf-bone-rgb), 0.04);
    color: var(--gf-text-3);
    font-size: 10px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
}
.cs-skill-toggle[data-toggle="up"] { color: #4caf50; border-color: rgba(76,175,80,0.3); }
.cs-skill-toggle[data-toggle="down"] { color: #f85149; border-color: rgba(248,81,73,0.3); }
.cs-skill-toggle[data-toggle="locked"] { color: var(--gf-text-4); }

/* ═══════════════════════════════════════════
   Ghost overlay (shown while is_ghost = true)
   ═══════════════════════════════════════════ */

#ghost-overlay {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 24px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s;
    z-index: 180;
}
#ghost-overlay.active {
    opacity: 1;
}

.ghost-card {
    position: relative;
    min-width: 340px;
    max-width: 420px;
    padding: 20px 28px;
    background: var(--gf-panel-bg);
    border: 1px solid rgba(160, 200, 255, 0.35);
    clip-path: var(--gf-cut);
    box-shadow: 0 0 36px rgba(80, 140, 220, 0.25);
    color: #cfd8e4;
    text-align: center;
    font-family: sans-serif;
}

.ghost-title {
    font-size: 22px;
    font-weight: 600;
    letter-spacing: 2px;
    color: #a9c7ff;
    text-shadow: 0 0 12px rgba(120, 170, 240, 0.6);
    margin-bottom: 14px;
}

.ghost-body {
    font-size: 13px;
    line-height: 1.6;
    color: #9aa6b8;
}

.ghost-controls {
    margin: 14px 0 10px;
    padding: 10px 12px;
    background: rgba(var(--gf-bone-rgb), 0.04);
    clip-path: var(--gf-cut);
    text-align: left;
    display: inline-block;
}

.ghost-controls-title {
    font-size: 11px;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #6f7c91;
    margin-bottom: 6px;
}

.ghost-key {
    display: inline-block;
    min-width: 46px;
    padding: 1px 6px;
    margin-right: 8px;
    background: rgba(160, 200, 255, 0.1);
    border: 1px solid rgba(160, 200, 255, 0.3);
    clip-path: var(--gf-cut-sm);
    font-family: monospace;
    font-size: 11px;
    text-align: center;
    color: #cfd8e4;
}

.ghost-footnote {
    margin-top: 10px;
    font-size: 12px;
    color: #6f7c91;
    font-style: italic;
}

/* Close button is the only interactive part of the overlay — the rest of the
   card stays pointer-events:none so clicks pass through to the world. */
.ghost-close {
    position: absolute;
    top: 8px;
    right: 10px;
    width: 24px;
    height: 24px;
    padding: 0;
    background: transparent;
    border: none;
    color: #6f7c91;
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
    transition: color 0.15s;
}
.ghost-close:hover {
    color: #cfd8e4;
}

/* Desaturate the world while the local player is ghosted. Applied to the
   canvas only so the overlay and HUD keep their colors. */
body.is-ghost canvas {
    filter: saturate(0.45) brightness(0.85);
    transition: filter 0.4s;
}

/* ═══════════════════════════════════════════
   Inventory panel
   ═══════════════════════════════════════════ */

/* ── Inventory (client/js/ui/inventory.js) ──────────────────────────────────
   The backpack is a FIXED 8×5 grid of --inv-cell sockets: an item never changes
   size with the window, and the window is sized to the grid (main.js registers
   it non-resizable), so nothing here can ever need a scrollbar. The panel only
   lives inside a GameWindow - no fixed positioning or chrome of its own - and
   the sockets wear the same cut-in treatment as the paperdoll's chips
   (.pd-slot.inv-slot) so bag and doll read as one kit. Tokens only. */
#inventory-panel {
    --inv-cell: 44px;
    --inv-gap: 4px;
    --inv-inset: 14px;
    display: none;
    flex-direction: column;
    width: 100%;
    color: var(--gf-text-1);
}
#inventory-panel.open { display: flex; }

/* Legacy in-panel header: the GameWindow's titlebar carries the name. */
.inv-header { display: none; }

.inv-body {
    display: flex;
    flex: 0 0 auto;
    padding: 8px var(--inv-inset) 10px;
}

.inv-section-title {
    display: none;
}

/* QoL automation toolbar (Phase C): one row of quiet ghost buttons above the
   bag, its bottom rule the only line in the window. */
.inv-qol-bar {
    display: flex; align-items: center; gap: 5px;
    margin-bottom: 8px; padding-bottom: 8px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.inv-qol-btn {
    flex: 1 1 0; min-width: 0;
    padding: 4px 6px;
    font: inherit; font-size: 10.5px; letter-spacing: 0.04em;
    color: var(--gf-accent-hot);
    background: var(--gf-face);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.inv-qol-btn:hover {
    color: var(--gf-accent-hot);
    background: rgba(var(--gf-trim-rgb), 0.12);
    border-color: var(--gf-border-accent);
}
.inv-qol-btn:active { transform: translateY(1px); }

/* Bag column (equipment moved to the paperdoll - paperdoll.js) */
.inv-bag-col { flex: 0 0 auto; }
.inv-bag-grid {
    display: grid;
    grid-template-columns: repeat(8, var(--inv-cell));
    grid-auto-rows: var(--inv-cell);
    gap: var(--inv-gap);
    justify-content: center;
}
/* Only the bag pins its sockets: other .inv-slot grids (staff inspector,
   portable supply) size their own cells. */
.inv-bag-grid > .inv-slot { width: var(--inv-cell); height: var(--inv-cell); aspect-ratio: auto; }

/* Inventory socket (shared by the bag and the paperdoll's equip chips). A
   flat hairline over a flat fill reads as a form; the inner shadow is what
   makes a cell read as a socket cut into the panel. */
.inv-slot {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    box-sizing: border-box;
    clip-path: var(--gf-cut-sm);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.22);
    background: linear-gradient(180deg, var(--gf-plate-raised), var(--gf-plate-sunk));
    box-shadow: inset 0 1px 0 rgba(var(--gf-bone-rgb), 0.05),
                inset 0 -3px 8px rgba(0, 0, 0, 0.45);
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s, background-color 0.15s;
}
.inv-slot:hover {
    border-color: var(--gf-accent);
    box-shadow: 0 0 10px var(--gf-accent-glow),
                inset 0 0 6px rgba(var(--gf-trim-rgb), 0.18);
    background-color: rgba(var(--gf-trim-rgb), 0.05);
}
.inv-slot.inv-slot-filled {
    border-color: rgba(var(--gf-trim-rgb), 0.55);
    background: linear-gradient(180deg, rgba(32, 30, 36, 0.78), rgba(14, 13, 18, 0.86));
    box-shadow: inset 0 1px 0 rgba(var(--gf-bone-rgb), 0.07),
                inset 0 -3px 9px rgba(0, 0, 0, 0.5);
}
.inv-slot.inv-slot-filled:hover {
    border-color: var(--gf-accent-hot);
    box-shadow: 0 0 12px var(--gf-accent-glow),
                inset 0 0 7px rgba(var(--gf-trim-rgb), 0.22);
}

.inv-slot-label {
    position: absolute;
    bottom: 2px; left: 3px;
    font-size: 7px;
    color: var(--gf-text-4);
    pointer-events: none;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.inv-slot-swatch {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 60%;
    height: 60%;
    clip-path: var(--gf-cut-sm);
    opacity: 0;
    transition: opacity 0.1s;
}
/* A real rendered model earns more of the cell than the flat category chip,
   and needs no rounded plate behind it. */
.inv-slot-swatch.has-model {
    width: 88%;
    height: 88%;
    /* square */
    background-color: transparent !important;
}

/* Shared item visual for rows that are not slot grids (vendor, trade). */
.gf-item-swatch {
    position: relative;
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    clip-path: var(--gf-cut-sm);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35);
}
.gf-item-swatch.has-model {
    /* square */
    box-shadow: none;
    background-color: transparent !important;
}
.gf-item-swatch-ic {
    position: absolute; inset: 18%; width: auto; height: auto;
    color: var(--gf-text-1);
    filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.6));
    pointer-events: none;
}

.inv-slot-qty {
    position: absolute;
    bottom: 2px; right: 3px;
    font-size: 9px;
    font-family: var(--gf-mono);
    font-variant-numeric: tabular-nums;
    color: var(--gf-text-1);
    pointer-events: none;
    text-shadow: 0 0 3px rgba(0,0,0,0.8);
}

/* Weight bar: a thin gold track under the bag. Tone is set by CLASS on the row
   (is-heavy past three quarters, is-over at the cap) so the JS spells no color. */
.inv-weight-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 var(--inv-inset) 12px;
}
.inv-weight-label {
    flex: 0 0 auto;
    font-size: 9.5px;
    color: var(--gf-text-3);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}
.inv-weight-track {
    flex: 1 1 auto;
    height: 5px;
    background: var(--gf-slot-bg);
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    overflow: hidden;
}
.inv-weight-fill {
    height: 100%;
    width: 0%;
    background: var(--gf-gold);
    clip-path: var(--gf-cut-sm);
    transition: width 0.2s, background 0.2s;
}
.inv-weight-value {
    flex: 0 0 auto;
    min-width: 64px;
    font-size: 10.5px;
    font-family: var(--gf-mono);
    font-variant-numeric: tabular-nums;
    color: var(--gf-text-2);
    text-align: right;
}
.inv-weight-row.is-heavy .inv-weight-fill { background: var(--gf-stamina); }
.inv-weight-row.is-over .inv-weight-fill { background: var(--gf-danger); }
.inv-weight-row.is-over .inv-weight-value { color: var(--gf-danger); }

/* Drag ghost */
.inv-drag-ghost {
    position: fixed;
    width: 36px; height: 36px;
    clip-path: var(--gf-cut-sm);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.3);
    z-index: var(--gf-z-sheet-chrome);
    pointer-events: none;
    opacity: 0.85;
    font-size: 10px;
    font-family: var(--gf-font-mono);
    color: #fff;
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    padding: 2px;
    text-shadow: 0 0 3px rgba(0,0,0,0.8);
}

/* Stackable quantity picker (client/js/ui/qty_picker.js) - follows the cursor
   during a drag / carry / transfer prompt. Body-appended so it sits outside the
   --gf-ui-scale zoom layer and positions by raw clientX/clientY. */
.gf-qty-picker {
    position: fixed;
    z-index: 9600;
    pointer-events: auto;
    background: rgba(14, 13, 12, 0.96);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.55);   /* ash-gold trim */
    clip-path: var(--gf-cut);
    box-shadow: 0 6px 20px rgba(0,0,0,0.6), 0 0 10px rgba(226, 98, 42, 0.15);
    padding: 6px 8px;
    font-family: var(--gf-font-mono);
    color: var(--gf-bone);                                 /* bone */
    user-select: none;
}
.gf-qty-picker .gf-qty-row {
    display: flex;
    align-items: center;
    gap: 6px;
}
.gf-qty-picker .gf-qty-slider {
    width: 96px;
    accent-color: var(--gf-ember);                          /* ember */
}
.gf-qty-picker .gf-qty-num {
    width: 56px;
    background: rgba(0,0,0,0.5);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.4);
    clip-path: var(--gf-cut-sm);
    color: #F0E6D2;
    font-family: inherit;
    font-size: 13px;
    padding: 2px 4px;
    text-align: right;
}
.gf-qty-picker .gf-qty-ok,
.gf-qty-picker .gf-qty-cancel {
    display: none;
    width: 22px; height: 22px;
    line-height: 20px;
    text-align: center;
    padding: 0;
    background: rgba(0,0,0,0.4);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.4);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-bone);
    cursor: pointer;
    font-size: 12px;
}
.gf-qty-picker .gf-qty-ok:hover { border-color: var(--gf-ember); color: #fff; }
.gf-qty-picker .gf-qty-cancel:hover { border-color: #c04040; color: #fff; }
.gf-qty-picker .gf-qty-hint {
    margin-top: 4px;
    font-size: 9px;
    opacity: 0.55;
    text-align: center;
    letter-spacing: 0.02em;
}
/* Passive (drag readout): slider disabled from pointer, no buttons, no hint. */
.gf-qty-picker:not(.gf-qty-armed) .gf-qty-slider,
.gf-qty-picker:not(.gf-qty-armed) .gf-qty-num { pointer-events: none; opacity: 0.9; }
.gf-qty-picker:not(.gf-qty-armed) .gf-qty-hint { visibility: hidden; }
/* Interactive carry shows ✕; prompt mode (gf-qty-hasok) also shows ✓. */
.gf-qty-picker.gf-qty-armed .gf-qty-cancel { display: inline-block; }
.gf-qty-picker.gf-qty-armed.gf-qty-hasok .gf-qty-ok { display: inline-block; }

/* ═══════════════════════════════════════════
   Tooltip system
   ═══════════════════════════════════════════ */

.gf-tooltip {
    position: fixed;
    z-index: 9999;
    pointer-events: none;
    background: rgba(12, 12, 18, 0.95);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.2);
    clip-path: var(--gf-cut-sm);
    padding: 8px 12px;
    font-size: 12px;
    color: var(--gf-text-1);
    max-width: 260px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.7);
    opacity: 0;
    transition: opacity 0.1s ease;
}
.gf-tooltip.visible { opacity: 1; }

.gf-tt-name {
    font-weight: 600;
    font-size: 13px;
    color: #fff;
    margin-bottom: 2px;
}
.gf-tt-qty {
    font-size: 11px;
    color: var(--gf-trim);
    font-weight: normal;
}
.gf-tt-type {
    font-size: 10px;
    color: var(--gf-text-3);
    margin-bottom: 4px;
}
.gf-tt-sep {
    border-top: 1px solid rgba(var(--gf-bone-rgb), 0.08);
    margin: 5px 0;
}
/* The affiliation line under a mobile's name ("gridbound · Leader"). Upright and
   dim: it is a fact about who this person stands with, not a worn title, so it
   must not read as one - hence its own class rather than reusing .gf-tt-desc. */
.gf-tt-guild {
    color: var(--gf-accent-2);
    font-size: 11px;
    line-height: 1.3;
    margin-top: 1px;
}
.gf-tt-prop {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1px 0;
    font-size: 11px;
}
.gf-tt-prop-label {
    color: var(--gf-text-3);
}
.gf-tt-prop-value {
    color: var(--gf-text-1);
    font-family: var(--gf-font-mono);
}
.gf-tt-desc {
    color: var(--gf-text-3);
    font-style: italic;
    font-size: 11px;
    line-height: 1.3;
}
/* One line of resolved skill mechanics under the description. Upright rather than
   italic so it reads as fact next to the flavor, and dimmer so it never competes. */
.gf-tt-mech {
    color: #7d8894;
    font-size: 10px;
    line-height: 1.35;
    margin-top: 4px;
}
/* Universal item-effect lines (hit-spells, stat/resist bonuses, leech, …). */
.gf-tt-effect {
    color: var(--gf-grid-cyan);
    font-size: 11px;
    line-height: 1.35;
}

/* Hotbar item rendering (inside existing hotbar-slot) */
.hotbar-item-swatch {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 28px; height: 28px;
    clip-path: var(--gf-cut-sm);
    opacity: 0;
    pointer-events: none;
}
.hotbar-item-qty {
    position: absolute;
    bottom: 2px; right: 3px;
    font-size: 9px;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-1);
    pointer-events: none;
    text-shadow: 0 0 3px rgba(0,0,0,0.8);
}

/* ── Game Window System (UO-style draggable windows) ── */

/* ── THE PLATE ──
   A window is two stacked chamfered layers, not a bordered box:

     .game-window          the TRIM - a solid ash-gold octagon
     .game-window::before  the PLATE - the same octagon inset by --gf-trim-w

   It is built this way because `clip-path` cuts a real `border` away on the
   four diagonals, so a bordered box loses its outline exactly at the corners
   that define the shape. Two filled layers keep the trim continuous all the way
   round. For the same reason the depth is a `filter: drop-shadow()` and not a
   `box-shadow` - a box-shadow is clipped off entirely by the cut, while a
   filter follows the silhouette.

   The old chrome was a Kenney 9-slice PNG over a backdrop blur. Both are gone:
   the frame is now CSS (so it recolors from a token - see .gf-window-staff) and
   the blur is gone because frosted glass contradicts the matte-world rule. */
.game-window {
    background: var(--gf-trim-dim);
    clip-path: var(--gf-cut);
    filter: var(--gf-drop);
    display: flex;
    flex-direction: column;
    min-width: 150px;
    min-height: 100px;
    overflow: hidden;
    transition: filter 0.15s, background 0.15s;
}
.game-window::before {
    content: '';
    position: absolute;
    inset: var(--gf-trim-w);
    clip-path: var(--gf-cut);
    background: var(--gf-panel-bg);
    box-shadow: var(--gf-bevel);
    pointer-events: none;
}
/* Real content rides above the plate. No negative z-index anywhere: the window
   carries an inline z-index from bringSurfaceToFront(), which makes it a
   stacking context, so a z-index:-1 pseudo would be trapped behind its own
   parent and vanish. */
.game-window > * { position: relative; z-index: 1; }
/* Always-on micro HUD (per-mobile stat bars) keeps a light gold hairline - no
   ornate frame - so combat with several bars open stays uncluttered. */
.game-window[data-window-id^="statbar"] {
    clip-path: var(--gf-cut-sm);
    filter: none;
}
.game-window[data-window-id^="statbar"]::before { clip-path: var(--gf-cut-sm); }

/* ── Buff / debuff bar ────────────────────────────────────────────────────── */
/* A strip of square effect icons living in the self stat-bar's titlebar —
   buffs left, debuffs right. Each icon is a kind-tinted square with a radial
   cooldown sweep (+ an optional shield-absorb badge); hover shows the full
   name/type/description/time tooltip. */
.eff-strip {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.eff-side {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
}
.eff-side.empty { display: none; }
/* Keep debuffs pinned right even when the buffs side is hidden. */
.eff-debuffs { margin-left: auto; justify-content: flex-end; }

.eff-ic {
    position: relative;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--eff-color, #3a4152);
    clip-path: var(--gf-cut-sm);
    background: rgba(10,12,18,0.82);
    box-shadow: inset 0 0 8px rgba(0,0,0,0.45), 0 0 0 1px rgba(0,0,0,0.35);
}
.eff-ic .eff-ic-glyph { width: 17px; height: 17px; }

/* Radial "cooldown" sweep: darkens clockwise as --sweep goes 0 (full) → 1 (expired). */
.eff-cd {
    position: absolute;
    inset: 0;
    /* shape inherited from the clipped parent */
    pointer-events: none;
    background: conic-gradient(from 0deg, rgba(6,8,14,0.62) calc(var(--sweep, 0) * 360deg), transparent 0);
}

/* Shield absorb readout, bottom-right corner. */
.eff-absorb {
    position: absolute;
    right: -3px;
    bottom: -3px;
    display: none;
    padding: 0 2px;
    font: 700 8px/1.35 var(--gf-mono, monospace);
    color: #d8ecff;
    background: rgba(8,12,20,0.92);
    border: 1px solid var(--eff-color, #3a4152);
    clip-path: var(--gf-cut-sm);
    pointer-events: none;
}
.eff-absorb.on { display: block; }

/* Subtle wear-off cue in the effect's final stretch. */
@keyframes eff-expiring-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
.eff-ic.eff-expiring { animation: eff-expiring-pulse 0.9s ease-in-out infinite; }

/* Titlebar in "effects" mode (the self stat bar): hosts the icon strip, with a
   stable min-height so the bar doesn't jump as effects come and go. */
.game-window-titlebar.has-eff-strip {
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 8px;
    min-height: 30px;
}
/* Show the player's name when idle; hand the space to the icons once any effect
   is active (name + icons would otherwise fight for the same row). */
.game-window-titlebar.has-eff-strip.has-effects .game-window-title { display: none; }
/* Focused (most recently raised) window/chat surface gets an accent lift. */
.game-window.gf-window-focused { background: var(--gf-trim); filter: var(--gf-drop-strong); }
#chat-area.gf-window-focused {
    border-color: var(--gf-trim);
    box-shadow: var(--gf-shadow-strong), var(--gf-inset);
}

/* The titlebar is a raised plate, and the line under it is THE SEAM - not a
   border but a lit gap where two plates meet. This is the one move the whole
   identity rests on: the accent never outlines a panel, it glows in the join. */
.game-window-titlebar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 5px 9px;
    background: linear-gradient(180deg, var(--gf-plate-raised), var(--gf-plate));
    border-bottom: var(--gf-seam-w) solid var(--gf-seam);
    box-shadow: 0 1px 6px -1px var(--gf-seam-glow);
    cursor: grab;
    user-select: none;
    flex-shrink: 0;
}

.game-window-titlebar:active {
    cursor: grabbing;
}

.game-window-title {
    font-size: 11px;
    font-family: var(--gf-mono);
    color: var(--gf-accent);
    text-shadow: 0 1px 2px rgba(0,0,0,0.6);
    text-transform: uppercase;
    letter-spacing: 1.2px;
}

.game-window-close {
    background: none;
    border: none;
    color: var(--gf-text-3);
    font-size: 16px;
    cursor: pointer;
    padding: 0 4px;
    line-height: 1;
    transition: color 0.12s, transform 0.12s;
}
.game-window-close:hover {
    color: var(--gf-danger);
    transform: scale(1.15);
}

.game-window-content {
    flex: 1;
    overflow: auto;
    min-height: 0;
}

/* ── Chromeless windows ──────────────────────────────────────────────────────
   No titlebar: the CONTENT supplies its own header, nominated as the drag handle
   through GameWindow.setDragHandle(). The titlebar is still BUILT (four call
   sites dereference it) and only hidden here, which is what lets a mobile sheet
   hand it straight back below. */
.game-window.gf-window-chromeless > .game-window-titlebar { display: none; }

/* The close button + any addTitleWidget control, floated over the top-right
   corner. z-index clears the content beneath it. */
.game-window-chrome {
    position: absolute;
    top: 0; right: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 6px;
}

.gf-window-handle { cursor: grab; user-select: none; }
.gf-window-handle:active { cursor: grabbing; }

/* When inventory panel is inside a game window, override its fixed positioning */
/* The inventory window is sized to its fixed grid (see #inventory-panel), so
   its content area never scrolls - hide the channel outright so a rounding
   pixel can't summon a bar. Same for the paperdoll, whose min size is its
   content floor (main.js), and the remote doll. */
[data-window-id="inventory"] > .game-window-content,
[data-window-id="paperdoll"] > .game-window-content,
[data-window-id="remote-paperdoll"] > .game-window-content { overflow: hidden; }

/* ── Paperdoll / Skills / Titles windows (the character window trio) ── */
/* Each panel sits inside a GameWindow content area, which is the single scroll
   region - the panels carry no positioning or overflow of their own (the nested
   double-scrollbar was the old bug). Visibility is gated by .open, kept in sync
   with the window's show/hide by main.js. */
.game-window-content #paperdoll,
.game-window-content #remote-paperdoll { width: 100%; display: none; }
.game-window-content #paperdoll.open,
.game-window-content #remote-paperdoll.open { display: flex; }
.game-window-content #skills-panel,
.game-window-content #titles-panel { width: 100%; display: none; }
.game-window-content #skills-panel.open,
.game-window-content #titles-panel.open { display: block; }

#paperdoll,
#remote-paperdoll {
    --pd-slot: 38px;
    --pd-col-gap: 4px;
    /* The content floor: the taller column (7 worn chips) at its pitch. The
       body never shrinks below it, and the window's minHeight (main.js) is
       set above it plus header, rail and padding - so no column ever needs to
       scroll and the content area never grows a bar. */
    --pd-col-h: calc(7 * var(--pd-slot) + 6 * var(--pd-col-gap));
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 12px 12px;
    height: 100%;
    box-sizing: border-box;
}

/* Remote paperdoll: a read-only "gear + identity" view of another player/NPC.
   Reuses every .pd-* rule; adds only a notoriety caption + a read-only tint. */
.pd-noto { text-align: center; font-family: var(--gf-mono); font-size: 10px;
    letter-spacing: 1px; text-transform: uppercase; margin-top: -2px; }
#remote-paperdoll .pd-slot.inv-slot { cursor: default; }
/* Managed (hireling) paperdoll: chips are live, and a backpack sits under the doll.
   Player chrome - gold trim over dark glass; never cyan (that is staff-only). */
#remote-paperdoll.rpd-editable .pd-slot.inv-slot.inv-slot-filled { cursor: grab; }
#remote-paperdoll .rpd-bag-wrap { display: none; flex: 0 0 auto; }
#remote-paperdoll.rpd-editable .rpd-bag-wrap { display: block; }
.rpd-bag-title {
    font-family: var(--gf-mono); font-size: 10px; letter-spacing: 1px;
    text-transform: uppercase; color: var(--gf-gold); margin: 0 0 4px;
    border-bottom: 1px solid var(--gf-border-soft); padding-bottom: 2px;
}
.rpd-bag {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 3px;
}
.rpd-bag-slot {
    position: relative;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    background: var(--gf-slot-bg);
    box-sizing: border-box;
}
.rpd-bag-slot.is-filled { border-color: var(--gf-border-accent); cursor: grab; }
.rpd-bag-slot:hover { background: var(--gf-slot-bg-hover); }
.rpd-bag-swatch {
    position: absolute; inset: 3px; clip-path: var(--gf-cut-sm);
    background-size: contain; background-repeat: no-repeat; background-position: center;
    pointer-events: none;
}
.rpd-bag-swatch.has-model { background-color: transparent !important; }
.rpd-bag-qty {
    position: absolute; bottom: 1px; right: 2px; font-size: 9px;
    font-family: var(--gf-mono); color: var(--gf-text-1); pointer-events: none;
    text-shadow: 0 0 3px rgba(0,0,0,0.8);
}

/* Identity header. Doubles as the window's DRAG HANDLE (both dolls are hosted
   chromeless, so there is no titlebar), which is why it carries the padding and
   the divider that used to sit on .pd-name itself. The right padding keeps the
   centered name clear of the floating close button in the corner tray. */
.pd-header {
    flex: 0 0 auto;
    padding: 2px 26px 6px;
    border-bottom: 1px solid var(--gf-border);
}
.pd-header .pd-name,
.pd-header .pd-titles { border-bottom: none; padding-bottom: 0; }

/* Identity caption above the doll: the reputation title's lead-in, the name,
   the race. Cinzel is safe here only because every reputation word is pinned to
   Title Case (tests/test_titles_lock.py) - a lowercase-bearing line ("the Bold")
   never goes in this face. Balanced wrapping keeps a long "The Grid-Blessed
   Lady Name" from stranding the name alone on a second line. */
.pd-name {
    font-family: var(--gf-font-display);
    font-size: 14px;
    font-weight: 600;
    color: var(--gf-accent);
    letter-spacing: 0.6px;
    text-align: center;
    text-wrap: balance;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--gf-border);
    text-shadow: 0 1px 3px rgba(0,0,0,0.75);
}
.pd-race { color: var(--gf-text-3); font-size: 10px; text-transform: uppercase; letter-spacing: 1px; margin-left: 6px; }
/* The reputation title's tone - also the Titles window's reputation card and the
   Public Stats subline. Tokens only, and never Grid cyan: a reputation is a
   player surface. */
.pd-rep[data-tone="virtue"] { color: var(--gf-gold-bright); }
.pd-rep[data-tone="neutral"] { color: var(--gf-text-2); }
.pd-rep[data-tone="dread"] { color: var(--gf-ember); }

/* Under the name (title_header.headerHtml): the skill title, every worn
   overhead title in plate order with the guild line last, and - on your own
   doll - the standing line. A boss's epithet ("the Last Sovereign") shows here
   on click too. */
.pd-identity { min-width: 0; }
.pd-titles {
    text-align: center;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--gf-border);
}
/* The skill title is a rank read off the character's best skill, not a thing
   worn: a quieter small-caps label over the worn rows. */
.pd-skill-title {
    font-family: var(--gf-mono);
    font-size: 10px;
    letter-spacing: 1px;
    text-transform: uppercase;
    line-height: 1.35;
    color: var(--gf-text-2);
}
.pd-title {
    font-family: var(--gf-mono);
    font-size: 11px;
    letter-spacing: 0.6px;
    line-height: 1.25;
}
/* Reputation standing. DERIVED, not worn - it is in no `titles` payload and
   cannot be taken off - so it is deliberately quieter and set apart from the
   rows above it, which a player chose or earned. */
.pd-standing {
    margin-top: 3px;
    font-family: var(--gf-mono);
    font-size: 9px;
    letter-spacing: 0.8px;
    color: var(--gf-text-3);
    opacity: 0.9;
}

/* Body: flanking equip-slot columns · tall portrait preview · flanking column.
   The slot columns spread their five slots along the doll's full height. */
.pd-body {
    display: grid;
    grid-template-columns: var(--pd-slot) 1fr var(--pd-slot);
    gap: 8px;
    align-items: stretch;
    flex: 1 1 auto;
    /* Never shorter than its columns: the window is what yields (its minimum
       is above this floor), never the chips. */
    min-height: var(--pd-col-h);
}
/* Top-aligned with a fixed gap, NOT space-between. The columns hold different
   counts now (6 worn slots against 2 hands + 2 jewels), and space-between
   divides each column's own slack by its own count - so the same 48px chips
   would sit at two different pitches either side of the doll and read as a
   layout bug. A fixed gap keeps one rhythm across both. */
.pd-col {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    gap: var(--pd-col-gap);
    /* No overflow of its own: a column of chips that scrolls is a layout
       failure, not a feature. The body's min-height guarantees the room. */
    overflow: visible;
}

/* Equip slots reuse .inv-slot; this pins them to a square cell and gives them
   the paired outer-glow + inner-glow the hotbar's .gf-slot / .bm-tool already
   use. A flat 1px hairline over a flat fill is what made the doll read as a
   form; the inset is what makes a chip read as a socket cut into the panel. */
.pd-slot.inv-slot {
    width: var(--pd-slot);
    height: var(--pd-slot);
    aspect-ratio: auto;
    flex: 0 0 auto;
    clip-path: var(--gf-cut-sm);
    transition: border-color 0.15s, box-shadow 0.15s, background-color 0.15s;
    border: 1px solid rgba(var(--gf-trim-rgb), 0.25);
    background: linear-gradient(180deg, var(--gf-plate-raised), var(--gf-plate-sunk));
    box-shadow: inset 0 1px 0 rgba(var(--gf-bone-rgb), 0.05),
                inset 0 -3px 8px rgba(0, 0, 0, 0.45);
}
.pd-slot.inv-slot:hover {
    border-color: var(--gf-accent);
    box-shadow: 0 0 10px var(--gf-accent-glow),
                inset 0 0 6px rgba(var(--gf-trim-rgb), 0.18);
    background-color: rgba(var(--gf-trim-rgb), 0.05);
}
/* A filled chip is the one holding real gear - it earns the warmer rim. */
.pd-slot.inv-slot.inv-slot-filled {
    border-color: rgba(var(--gf-trim-rgb), 0.6);
    background: linear-gradient(180deg, rgba(32, 30, 36, 0.78), rgba(14, 13, 18, 0.86));
    box-shadow: inset 0 1px 0 rgba(var(--gf-bone-rgb), 0.07),
                inset 0 -3px 9px rgba(0, 0, 0, 0.5);
}
.pd-slot.inv-slot.inv-slot-filled:hover {
    border-color: var(--gf-accent-hot);
    box-shadow: 0 0 12px var(--gf-accent-glow),
                inset 0 0 7px rgba(var(--gf-trim-rgb), 0.22);
}

.pd-preview {
    position: relative;
    min-height: 200px;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    overflow: hidden;
    background:
        radial-gradient(120% 80% at 50% 18%, rgba(var(--gf-trim-rgb), 0.10), transparent 60%),
        linear-gradient(180deg, var(--gf-plate-raised), var(--gf-plate-sunk));
    box-shadow: inset 0 0 26px rgba(0,0,0,0.55);
    flex: 1 1 auto;
}
/* A faint pedestal under the model, lifted from .race-model-frame::after in
   auth.css - the character-creation frame this one is descended from. Costs one
   gradient and is most of the difference between "a canvas" and "a stage". */
.pd-preview::after {
    content: '';
    position: absolute;
    left: 50%; bottom: 12px;
    width: 62%; height: 16px;
    transform: translateX(-50%);
    background: radial-gradient(ellipse at center, rgba(var(--gf-trim-rgb), 0.22), transparent 70%);
    filter: blur(2px);
    pointer-events: none;
}
/* An item drag is in flight and the pointer is over the doll: releasing here
   equips it. The whole portrait is the target, not just the model. */
.pd-preview.pd-droptarget {
    border-color: var(--gf-accent-hot);
    box-shadow: inset 0 0 26px rgba(0, 0, 0, 0.55),
                inset 0 0 0 1px var(--gf-accent-glow),
                0 0 14px var(--gf-accent-glow);
}
.pd-canvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
.pd-poster {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--gf-accent-deep); opacity: 0.45; font-size: 30px;
    pointer-events: none;
}
.pd-preview.has-model .pd-poster { display: none; }

/* Camera Reset Button */
.pd-preview-reset-cam {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(14, 13, 12, 0.6);
    border: 1px solid var(--gf-border-accent);
    border-radius: 50%;
    color: var(--gf-accent);
    font-size: 14px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    z-index: 5;
}
.pd-preview-reset-cam:hover {
    background: rgba(var(--gf-trim-rgb), 0.2);
    color: var(--gf-accent-hot);
    border-color: var(--gf-accent);
}

/* Bag / scroll buttons tucked at the doll's feet, one each side. The row itself
   is click-through so a drag on the canvas still rotates the doll; the buttons
   re-enable pointer events. */
.pd-feet {
    position: absolute;
    left: 8px; right: 8px; bottom: 8px;
    display: flex; justify-content: space-between; align-items: flex-end;
    pointer-events: none;
}
.pd-feet-btn {
    pointer-events: auto;
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    background: linear-gradient(180deg, rgba(84,66,30,0.6), rgba(28,22,12,0.72));
    color: var(--gf-accent);
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0,0,0,0.5);
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.12s;
}
.pd-feet-btn.pd-feet-droptarget {
    border-color: var(--gf-accent-hot);
    box-shadow: 0 0 10px var(--gf-accent-glow);
}
.pd-feet-btn:hover {
    background: rgba(var(--gf-trim-rgb), 0.16);
    border-color: var(--gf-accent);
    color: var(--gf-accent-hot);
    transform: translateY(-1px);
}
.pd-feet-ic { width: 17px; height: 17px; }

/* Attribute value + raise/lower/lock controls grouped at the right of a row */
.stat-bar-detail .sbd-ctl { display: inline-flex; align-items: center; gap: 6px; }
.stat-bar-detail .sbd-toggle-group { display: inline-flex; gap: 2px; }
/* Compact toggle chips for the status panel - all three shown; inactive ones
   are dimmed, the active state is highlighted by its up/down/locked color. */
.stat-bar-detail .sbd-toggle {
    width: 16px; height: 16px;
    clip-path: var(--gf-cut-sm);
    font-size: 9px;
    line-height: 1;
    flex: 0 0 auto;
    opacity: 0.4;
    color: var(--gf-dim);
    border-color: rgba(var(--gf-bone-rgb), 0.12);
}
.stat-bar-detail .sbd-toggle:hover { opacity: 0.8; }
.stat-bar-detail .sbd-toggle.is-active { opacity: 1; }
.stat-bar-detail .sbd-toggle.is-active[data-toggle="up"] { color: #4caf50; border-color: rgba(76,175,80,0.5); background: rgba(76,175,80,0.12); }
.stat-bar-detail .sbd-toggle.is-active[data-toggle="down"] { color: #f85149; border-color: rgba(248,81,73,0.5); background: rgba(248,81,73,0.12); }
.stat-bar-detail .sbd-toggle.is-active[data-toggle="locked"] { color: #cbb884; border-color: rgba(203,184,132,0.45); background: rgba(203,184,132,0.12); }

/* ── Action rail ─────────────────────────────────────────────────────────────
   One always-visible row of small icon buttons, replacing a collapsible 4-wide
   grid of nine TEXT buttons under a two-tab strip. It wraps rather than
   scrolling: the window is resizable and a clipped row would hide a control with
   nothing to say it was there. */
.pd-rail {
    flex: 0 0 auto;
    /* A GRID, not a wrapping flex row: at this width free wrapping gives a
       ragged last row, which reads as a mistake. The column count is chosen to
       keep it to TWO rows - a third would push the doll below the window's
       height floor. 15 entries today (Vendor search made it odd), so eight
       columns give 8 + 7; re-pick it if an entry is added or removed.
       8*24 + 7*3 gap + 12 padding = 225px, inside the window's 262 minWidth. */
    display: grid;
    grid-template-columns: repeat(8, 24px);
    justify-content: center;
    gap: 3px;
    padding: 5px 6px 3px;
    border-top: 1px solid var(--gf-border-soft);
}
.pd-rail-btn {
    width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    flex: 0 0 auto;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    background: linear-gradient(180deg, rgba(84,66,30,0.5), rgba(28,22,12,0.62));
    color: var(--gf-accent);
    cursor: pointer;
    padding: 0;
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.06s;
}
.pd-rail-btn:hover {
    background: rgba(var(--gf-trim-rgb), 0.18);
    border-color: var(--gf-accent);
    color: var(--gf-accent-hot);
}
.pd-rail-btn:active { transform: translateY(1px); }
/* The one rail entry that opens IN the window - mark it while its drawer is up. */
.pd-rail-btn.is-open {
    border-color: var(--gf-accent);
    background: rgba(var(--gf-trim-rgb), 0.22);
    color: var(--gf-accent-hot);
    box-shadow: 0 0 8px var(--gf-accent-glow), inset 0 0 6px rgba(var(--gf-trim-rgb), 0.18);
}
.pd-rail-ic { width: 15px; height: 15px; }

/* ── Titles window (titles_panel.js) ─────────────────────────────────────────
   Five sections in one body: plate preview, overhead picks, guild title, skill
   title, reputation chart. The GameWindow content area is the one scroll
   region. Tokens only and never Grid cyan - a staff rank shown here is drawn in
   its own role hue, not in staff chrome. */
.tw-body { display: flex; flex-direction: column; gap: 14px; }
.tw-section { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.tw-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    font-family: var(--gf-mono);
    font-size: 10px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--gf-accent);
    padding-bottom: 3px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.tw-head-label { min-width: 0; }
.tw-head-meta { flex: 0 0 auto; color: var(--gf-text-3); letter-spacing: 0.6px; }
.tw-note { font-size: 11px; line-height: 1.35; color: var(--gf-text-3); }
.tw-preview .tw-note,
.tw-rep .tw-note { text-align: center; }

/* The plate preview mimics the world plate: monospace, stacked, outlined. */
.tw-plate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding: 10px 8px;
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut);
    background: var(--gf-panel-bg-strong);
    font-family: var(--gf-mono);
    text-align: center;
    text-shadow: 0 1px 2px rgba(0,0,0,0.95), 0 0 3px rgba(0,0,0,0.9);
}
.tw-plate-name { font-weight: 700; font-size: 15px; color: var(--gf-text-1); overflow-wrap: anywhere; }
.tw-plate-title { font-weight: 600; font-size: 11px; line-height: 1.3; overflow-wrap: anywhere; }

.tw-group {
    font-family: var(--gf-mono);
    font-size: 9px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--gf-text-3);
    margin-top: 6px;
}
.tw-list { display: flex; flex-direction: column; gap: 1px; }
.tw-picks .tw-worn { gap: 3px; margin-bottom: 2px; }
.tw-row,
.tw-choice {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 6px;
    border: 1px solid transparent;
    clip-path: var(--gf-cut-sm);
    font-size: 12px;
    cursor: pointer;
}
.tw-row:hover,
.tw-choice:hover { background: var(--gf-slot-bg); }
.tw-row.is-worn {
    background: var(--gf-slot-bg);
    border-color: var(--gf-border-soft);
    cursor: default;
}
.tw-row.is-capped { cursor: default; opacity: 0.6; }
.tw-guild .tw-row { align-self: stretch; }
.tw-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.tw-check,
.tw-radio { flex: 0 0 auto; margin: 0; cursor: pointer; accent-color: var(--gf-accent); }
.tw-check:disabled { cursor: default; opacity: 0.4; }
.tw-slot {
    flex: 0 0 auto;
    min-width: 12px;
    font-family: var(--gf-mono);
    font-size: 10px;
    color: var(--gf-text-3);
    text-align: center;
}
.tw-move {
    flex: 0 0 auto;
    width: 20px; height: 20px;
    padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    background: transparent;
    color: var(--gf-accent);
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
}
.tw-move:hover:not(:disabled) { background: var(--gf-slot-bg-hover); color: var(--gf-accent-hot); }
.tw-move:disabled { cursor: default; opacity: 0.25; }

/* Staff: the rank is an office, not a pick. */
.tw-office {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 8px;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    background: var(--gf-slot-bg);
}
.tw-office-name { font-family: var(--gf-mono); font-size: 13px; font-weight: 600; }
.tw-office-note {
    font-family: var(--gf-mono);
    font-size: 9px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--gf-text-3);
}

/* Skill title: automatic or one of the highest-rank skills, then progress. */
.tw-skill .tw-list { gap: 0; }
.tw-choice-text { flex: 1; min-width: 0; color: var(--gf-text-1); overflow-wrap: anywhere; }
.tw-choice-meta { flex: 0 0 auto; font-family: var(--gf-mono); font-size: 10px; color: var(--gf-text-3); }
.tw-progress { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }
.tw-progress-text { font-family: var(--gf-mono); font-size: 10px; color: var(--gf-text-2); }
.tw-progress-bar {
    height: 4px;
    clip-path: var(--gf-cut-sm);
    background: var(--gf-slot-bg-hover);
    overflow: hidden;
}
/* Scaled rather than sized, so the fraction is the only number JS writes. */
.tw-progress-fill {
    display: block;
    width: 100%; height: 100%;
    background: var(--gf-accent);
    transform-origin: left center;
    transform: scaleX(var(--tw-fill, 0));
}

/* Reputation: the title, the numbers, and the chart it is read from. The title
   reads in the name line's face - every word it carries is Title Case. */
.tw-rep-title {
    font-family: var(--gf-font-display);
    font-size: 14px;
    font-weight: 600;
    text-align: center;
    text-wrap: balance;
    padding-top: 2px;
}
.tw-rep-stats {
    font-family: var(--gf-mono);
    font-size: 10px;
    letter-spacing: 0.6px;
    color: var(--gf-text-3);
    text-align: center;
}
.tw-matrix-wrap { overflow-x: auto; margin-top: 4px; }
.tw-matrix {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
    font-size: 9px;
}
.tw-matrix th,
.tw-matrix td {
    padding: 3px 2px;
    border: 1px solid var(--gf-border-soft);
    text-align: center;
    line-height: 1.2;
    overflow-wrap: anywhere;
}
.tw-mx-corner {
    width: 17%;
    font-family: var(--gf-mono);
    font-size: 8px;
    font-weight: normal;
    color: var(--gf-text-3);
}
.tw-mx-col,
.tw-mx-row {
    font-family: var(--gf-mono);
    font-weight: 600;
    color: var(--gf-text-3);
    background: var(--gf-slot-bg);
}
.tw-mx-col.is-axis,
.tw-mx-row.is-axis { color: var(--gf-accent); }
.tw-mx-cell { color: var(--gf-text-3); }
.tw-matrix tr[data-tone="virtue"] .tw-mx-cell { color: var(--gf-gold); }
.tw-matrix tr[data-tone="neutral"] .tw-mx-cell { color: var(--gf-text-2); }
.tw-matrix tr[data-tone="dread"] .tw-mx-cell { color: var(--gf-ember-deep); }
.tw-matrix .tw-mx-cell.is-current {
    color: var(--gf-accent-hot);
    font-weight: 700;
    background: var(--gf-slot-bg-hover);
    box-shadow: inset 0 0 0 1px var(--gf-accent);
}

/* Text button, still used by the dress panel's own controls (Save / Wear /
   Edit / Delete) - those name actions rather than windows, so they keep words. */
.pd-btn {
    padding: 6px 4px;
    background: var(--gf-face);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-accent-hot);
    font-family: var(--gf-mono);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pd-btn:hover { background: rgba(var(--gf-trim-rgb), 0.12); border-color: var(--gf-accent); color: var(--gf-accent-hot); }

/* ── Dress sets (a drawer under the action rail) ────────────────────────────
   THE [hidden] OVERRIDE IS LOAD-BEARING. There is no global `[hidden]` rule in
   this stylesheet, and an explicit `display:` beats the UA agent's
   `[hidden]{display:none}` on specificity - so `el.hidden = true` on a flex or
   grid container does NOTHING and the drawer renders permanently open. Every
   other toggled element here carries the same override for the same reason. */
.pd-dress[hidden] { display: none !important; }
.pd-dress {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 8px;
    /* A drawer at the bottom of a column flex: it may shrink but never grow past
       its share, and min-height:0 is what lets a long set list SCROLL rather
       than push the doll off the top of the window. */
    flex: 0 1 auto;
    min-height: 0;
    max-height: 45%;
    overflow-y: auto;
    scrollbar-gutter: stable;
    background: rgba(14, 13, 12, 0.25);
    border-top: 1px solid var(--gf-border-soft);
}
.pd-dress-bar { display: flex; gap: 6px; align-items: center; }
.pd-dress-bar .pd-btn { flex: 0 0 auto; padding: 5px 10px; }
.pd-dress-empty, .pd-dress-warn {
    font-size: 11px;
    color: var(--gf-text-3);
    line-height: 1.45;
}
.pd-dress-warn { color: var(--gf-accent); margin-top: 4px; grid-column: 1 / -1; }
.pd-dress-row {
    border: 1px solid rgba(var(--gf-trim-rgb), 0.18);
    clip-path: var(--gf-cut-sm);
    background: rgba(20, 18, 15, 0.45);
}
.pd-dress-head { display: flex; align-items: center; gap: 5px; padding: 5px 6px; }
/* Reads as a LABEL until you touch it - the row is mostly for WEARING, and a
   full text field made renaming look like the primary action.
   The `.pd-dress-head input` qualifier is load-bearing: `.gf-input` is a single
   class defined LATER in this file, so a bare `.pd-dress-name` ties on
   specificity and loses on source order - which is exactly how this shipped as
   a big bright box. */
.pd-dress-head input.pd-dress-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 11px;
    line-height: 15px;
    padding: 2px 5px;
    background: transparent;
    border: 1px solid transparent;
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-1);
}
.pd-dress-head input.pd-dress-name:hover {
    background: rgba(0, 0, 0, 0.25);
    border-color: var(--gf-border-soft);
}
.pd-dress-head input.pd-dress-name:focus {
    background: rgba(0, 0, 0, 0.35);
    border-color: var(--gf-border-accent);
}
.pd-dress-meta {
    font-family: var(--gf-mono);
    font-size: 9px;
    color: var(--gf-text-3);
    white-space: nowrap;
}
.pd-dress-head .pd-btn { flex: 0 0 auto; padding: 4px 7px; font-size: 8px; }
.pd-dress-edit {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 3px;
    padding: 0 6px 6px;
}
/* The three states a set slot can hold. Color only REINFORCES - each cell also
   spells its state out, so the meaning never rides on hue alone. */
.pd-dress-cell {
    border: 1px solid rgba(var(--gf-trim-rgb), 0.15);
    border-left: 3px solid transparent;
    clip-path: var(--gf-cut-sm);
    padding: 2px 5px 3px;
    cursor: pointer;
    background: rgba(14, 13, 12, 0.5);
    transition: border-color 0.12s, background 0.12s, opacity 0.12s;
}
.pd-dress-cell:hover { background: rgba(var(--gf-trim-rgb), 0.07); }
.pd-dress-cell.state-wear { border-left-color: var(--gf-accent); }
.pd-dress-cell.state-strip { border-left-color: #8a6f5f; }
.pd-dress-cell.state-ignore { opacity: 0.45; }
.pd-dress-cell-slot {
    font-family: var(--gf-mono);
    font-size: 8px;
    line-height: 11px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--gf-accent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pd-dress-cell-val {
    font-size: 9px;
    line-height: 13px;
    color: var(--gf-text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.game-window-resize {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 14px;
    height: 14px;
    cursor: nwse-resize;
    background: linear-gradient(135deg, transparent 50%, rgba(100, 120, 160, 0.4) 50%);
}

/* ── Corpse loot window ── */
.corpse-window-content {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 6px 10px 10px 10px;
    color: #d8d4cc;
    font-family: inherit;
}
.corpse-header {
    padding: 2px 0 6px 0;
    border-bottom: 1px solid rgba(120, 140, 170, 0.3);
    margin-bottom: 6px;
}
.corpse-subtitle {
    font-weight: bold;
    color: #e8dcaa;
    font-size: 14px;
}
/* Container-style slot grid showing item model thumbnails (mirrors
   .container-grid; the corpse-loot list is retired). */
.corpse-grid {
    flex: 1;
    min-height: 80px;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
    gap: 4px;
    padding: 4px;
    align-content: start;
}
.corpse-slot {
    position: relative;
    min-height: 52px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3px;
    border: 1px solid rgba(160, 120, 60, 0.25);
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.25);
}
.corpse-slot.is-filled {
    border-color: rgba(160, 120, 60, 0.5);
    background: rgba(40, 30, 18, 0.6);
    cursor: pointer;
}
.corpse-slot.is-filled:hover {
    background: rgba(70, 52, 28, 0.75);
    border-color: rgba(200, 155, 90, 0.7);
}
.corpse-slot-swatch {
    width: 40px;
    height: 40px;
    clip-path: var(--gf-cut-sm);
    background: rgba(150, 120, 70, 0.6);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
.corpse-slot-qty {
    position: absolute;
    right: 3px;
    bottom: 2px;
    font-size: 11px;
    font-weight: bold;
    color: var(--gf-trim-bright);
    text-shadow: 0 0 3px #000, 0 0 3px #000;
    pointer-events: none;
}
.corpse-protected-banner {
    margin-top: 5px;
    padding: 4px 6px;
    font-size: 11px;
    line-height: 1.25;
    color: #bfe0ff;
    background: rgba(40, 80, 130, 0.35);
    border: 1px solid rgba(102, 176, 255, 0.55);
    clip-path: var(--gf-cut-sm);
}

/* Public Stats panel — reputation / murder counts (public_stats.js) */
.public-stats {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 8px 10px 10px;
    color: #d8d4cc;
    font-family: inherit;
    gap: 4px;
}
.ps-header {
    padding-bottom: 6px;
    border-bottom: 1px solid rgba(120, 140, 170, 0.3);
    margin-bottom: 4px;
}
.ps-name { font-weight: bold; color: #e8dcaa; font-size: 15px; display: block; }
/* The reputation title under the name; its hue is the shared .pd-rep tone. */
.ps-rep { display: block; font-size: 12px; letter-spacing: 0.3px; margin-top: 1px; }
.ps-meta { display: flex; gap: 8px; font-size: 12px; margin-top: 2px; }
.ps-noto { font-weight: bold; }
.ps-guild { color: #9fb4d6; }
.ps-section-title {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--gf-accent-2); margin: 4px 0 2px;
}
.ps-rows { display: flex; flex-direction: column; gap: 2px; }
.ps-row {
    display: flex; justify-content: space-between;
    padding: 4px 6px; background: rgba(40, 30, 18, 0.4);
    border: 1px solid rgba(160, 120, 60, 0.25); clip-path: var(--gf-cut-sm);
    font-size: 13px;
}
.ps-row-label { color: #cabfa6; }
.ps-row-val { color: #e8dcaa; font-weight: bold; }
.ps-row-val.ps-danger { color: #e05656; }
.ps-note {
    margin-top: auto; padding-top: 8px; font-size: 11px; font-style: italic;
    color: #7d7566; text-align: center;
}

/* Player Profile — moderated bio (editable for self, read-only for others). */
.player-profile {
    display: flex; flex-direction: column; height: 100%;
    padding: 8px 10px 10px; color: #d8d4cc; font-family: inherit; gap: 6px;
}
.pp-header {
    padding-bottom: 6px; border-bottom: 1px solid rgba(120, 140, 170, 0.3);
}
.pp-name { font-weight: bold; color: #e8dcaa; font-size: 15px; }
.pp-body { display: flex; flex-direction: column; flex: 1; gap: 6px; min-height: 0; }
.pp-textarea {
    flex: 1; min-height: 120px; resize: none; width: 100%;
    background: rgba(20, 16, 10, 0.6); color: #e8e2d4;
    border: 1px solid rgba(160, 120, 60, 0.35); clip-path: var(--gf-cut-sm);
    padding: 6px 8px; font-family: inherit; font-size: 13px; line-height: 1.4;
}
.pp-textarea:focus { outline: none; border-color: rgba(200, 160, 90, 0.6); }
.pp-foot { display: flex; align-items: center; gap: 8px; }
.pp-counter { font-size: 11px; color: var(--gf-accent-2); }
.pp-error { font-size: 11px; color: #e05656; flex: 1; }
.pp-error.pp-ok { color: #7fc47f; }
.pp-save {
    margin-left: auto; padding: 4px 14px; cursor: pointer;
    background: rgba(90, 70, 30, 0.7); color: #f0e6c8;
    border: 1px solid rgba(200, 160, 90, 0.5); clip-path: var(--gf-cut-sm); font-size: 13px;
}
.pp-save:hover { background: rgba(120, 92, 40, 0.85); }
.pp-view {
    flex: 1; overflow-y: auto; white-space: pre-wrap; word-break: break-word;
    font-size: 13px; line-height: 1.5; color: #d8d2c4;
    background: rgba(40, 30, 18, 0.35); border: 1px solid rgba(160, 120, 60, 0.2);
    clip-path: var(--gf-cut-sm); padding: 8px 10px;
}
.pp-view.pp-empty { color: #7d7566; font-style: italic; }
.corpse-empty {
    text-align: center;
    color: var(--gf-text-3);
    padding: 16px 0;
    font-style: italic;
}
.corpse-footer {
    padding-top: 6px;
    border-top: 1px solid rgba(120, 140, 170, 0.3);
    margin-top: 6px;
}
.corpse-loot-all {
    width: 100%;
    padding: 6px;
    background: rgba(80, 60, 40, 0.7);
    border: 1px solid rgba(180, 140, 80, 0.6);
    color: #e8dcaa;
    font-family: inherit;
    font-size: 13px;
    cursor: pointer;
    clip-path: var(--gf-cut-sm);
}
.corpse-loot-all:hover:not(:disabled) {
    background: rgba(120, 85, 50, 0.8);
}
.corpse-loot-all:disabled {
    opacity: 0.4;
    cursor: default;
}
.corpse-skin {
    width: 100%;
    padding: 6px;
    margin-bottom: 6px;
    background: rgba(60, 78, 46, 0.7);
    border: 1px solid rgba(140, 180, 90, 0.6);
    color: #d8e8aa;
    font-family: inherit;
    font-size: 13px;
    cursor: pointer;
    clip-path: var(--gf-cut-sm);
}
.corpse-skin:hover {
    background: rgba(85, 110, 60, 0.85);
}
.corpse-loading {
    color: var(--gf-accent-2);
    padding: 10px;
    text-align: center;
}

/* ── Crafting panel ── */
.crafting-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.craft-subheader {
    padding: 6px 12px;
    border-bottom: 1px solid #4a3a26;
    background: rgba(0,0,0,0.3);
}
.craft-station-label {
    font-size: 13px;
    color: #c0a070;
    letter-spacing: 0.05em;
}
.craft-body {
    display: flex;
    flex: 1;
    min-height: 0;
}
.craft-list {
    flex: 1 1 55%;
    overflow-y: auto;
    border-right: 1px solid #4a3a26;
    padding: 4px 0;
}
.craft-detail {
    flex: 1 1 45%;
    padding: 12px;
    overflow-y: auto;
}
.craft-row {
    display: flex;
    justify-content: space-between;
    padding: 6px 12px;
    cursor: pointer;
    border-left: 3px solid transparent;
    font-size: 12px;
}
.craft-row:hover {
    background: rgba(160, 120, 60, 0.15);
}
.craft-row.selected {
    background: rgba(160, 120, 60, 0.25);
    border-left-color: #c0a070;
}
.craft-row.no-skill { color: #786858; }
.craft-row.no-mats  { color: var(--gf-accent-2); }
.craft-row.craftable .craft-name { color: #f0d090; }
.craft-row .craft-skill {
    font-size: 10px;
    color: #807060;
    text-transform: capitalize;
}
.craft-row.exceptional {
    animation: craft-flash-exc 1.2s ease-out;
}
.craft-row.flash {
    animation: craft-flash 0.8s ease-out;
}
@keyframes craft-flash {
    0%   { background: rgba(120,200,120,0.5); }
    100% { background: transparent; }
}
@keyframes craft-flash-exc {
    0%   { background: rgba(240,200,80,0.7); }
    100% { background: transparent; }
}
.craft-detail h3 {
    margin: 0 0 8px 0;
    font-size: 16px;
    color: #f0d090;
}
.craft-desc {
    font-size: 11px;
    color: var(--gf-accent-2);
    margin: 0 0 10px 0;
    font-style: italic;
}
.craft-skill-block {
    margin-bottom: 10px;
    font-size: 11px;
    text-transform: capitalize;
}
.craft-skill-block .ok { color: #80c080; }
.craft-skill-block .fail { color: #c08070; }
.craft-section-hdr {
    margin: 8px 0 4px 0;
    font-size: 11px;
    color: #c0a070;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.craft-ingredients, .craft-output {
    font-size: 12px;
    margin-bottom: 6px;
}
.craft-ing, .craft-output {
    padding: 2px 0;
    text-transform: capitalize;
}
.craft-btn {
    margin-top: 14px;
    padding: 8px 16px;
    width: 100%;
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    border: 1px solid var(--gf-border-accent);
    cursor: pointer;
    font-family: inherit;
    font-size: 13px;
    letter-spacing: 0.05em;
    clip-path: var(--gf-cut-sm);
}
.craft-btn:hover:not(.disabled) {
    background: var(--gf-face-hover);
}
.craft-btn.disabled {
    opacity: 0.45;
    cursor: not-allowed;
    background: #2a2018;
}
.craft-empty {
    color: #786858;
    text-align: center;
    margin-top: 40px;
    font-style: italic;
}

/* ═══════════════════════════════════════════════════════════════
   Town NPC dialog (vendor shop / skill trainer / veterinarian)
   ═══════════════════════════════════════════════════════════════ */
.npc-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.npc-header {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 8px 12px;
    border-bottom: 1px solid #4a3a26;
    background: rgba(0, 0, 0, 0.3);
}
.npc-name {
    font-size: 14px;
    color: #f0d090;
    letter-spacing: 0.05em;
}
.npc-subtitle {
    font-size: 10.5px;
    color: var(--gf-accent-2);
    font-style: italic;
}
.npc-gold {
    margin-left: auto;
    font-size: 12px;
    color: var(--gf-trim-bright);
    white-space: nowrap;
}
.npc-tabs {
    display: flex;
    gap: 4px;
    padding: 6px 10px 0 10px;
}
.npc-tab {
    padding: 5px 16px;
    background: #2a2018;
    color: var(--gf-accent-2);
    border: 1px solid #4a3a26;
    border-bottom: none;
    cursor: pointer;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.04em;
}
.npc-tab.active {
    background: rgba(160, 120, 60, 0.25);
    color: #f0d090;
}
.npc-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 0;
}
.npc-list {
    display: flex;
    flex-direction: column;
}
.npc-cat {
    padding: 8px 12px 3px 12px;
    font-size: 10px;
    color: #c0a070;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.npc-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border-left: 3px solid transparent;
    font-size: 12px;
}
.npc-row:hover {
    background: rgba(160, 120, 60, 0.12);
}
.npc-row-name {
    flex: 1 1 auto;
    color: var(--gf-bone);
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}
/* One compact line of real mechanics, so two bows can be compared without
   hovering each in turn. */
.npc-row-stats {
    font-size: 10px;
    color: var(--gf-dim);
    letter-spacing: 0.03em;
}
.npc-row-price {
    flex: 0 0 auto;
    font-size: 11px;
    color: #c0a070;
    white-space: nowrap;
}
/* The running total when you can't afford it. Color is the only signal that
   needs to reach you before the disabled Buy button does. */
.npc-row-price.short { color: #c86a5a; }

/* Items the shop refuses (enchanted/exceptional work - it prices per item
   DEFINITION and cannot value an instance). Shown grayed rather than hidden, so
   a missing best sword is never a mystery. */
.npc-row-refused {
    opacity: 0.45;
    font-style: italic;
}

/* ── Shop toolbar: search + category pills ─────────────────────────────── */
.npc-toolbar {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 6px 12px 4px 12px;
    border-bottom: 1px solid rgba(74, 58, 38, 0.6);
}
.npc-search {
    width: 100%;
    box-sizing: border-box;
    padding: 4px 8px;
    background: var(--gf-plate);
    color: var(--gf-bone);
    border: 1px solid #4a3a26;
    font-family: inherit;
    font-size: 12px;
}
.npc-search::placeholder { color: #7a6a52; }
.npc-search:focus {
    outline: none;
    border-color: var(--gf-border-accent, rgba(var(--gf-trim-rgb), 0.4));
}
.npc-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.npc-pill {
    padding: 2px 9px;
    background: #241b14;
    color: #a89670;
    border: 1px solid #4a3a26;
    clip-path: var(--gf-cut);
    cursor: pointer;
    font-family: inherit;
    font-size: 10.5px;
    letter-spacing: 0.03em;
}
.npc-pill:hover { color: var(--gf-bone); }
.npc-pill.active {
    background: rgba(160, 120, 60, 0.3);
    color: #f0d090;
    border-color: #8a6840;
}

/* ── Vendor Search (vendor_search.js) ─────────────────────────────────────
   One PAGE of a server query, so the list grows by "Load more" and never
   virtualizes. Rows are `.npc-row` on purpose: the delegated item tooltip in
   main.js reads that class, and a search result is described exactly as the
   shop it leads to describes it. Tokens and ash-gold only - never Grid cyan.
   The panel lives in a GameWindow, so it is NOT in the --gf-ui-scale zoom list. */
.vs-panel {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 0;
    height: 100%;
    padding: 6px 0 4px;
    box-sizing: border-box;
}
.vs-toolbar { display: flex; gap: 6px; padding: 0 12px; }
.vs-search { flex: 1 1 auto; min-width: 0; }
.vs-sort { flex: 0 0 auto; max-width: 44%; }
.vs-pills { padding: 0 12px; }
.vs-filters { margin: 0 12px; flex: 0 0 auto; max-height: 46%; overflow-y: auto; }
.vs-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    gap: 6px 8px;
}
.vs-grid-head {
    grid-column: 1 / -1;
    margin-top: 4px;
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gf-gold, var(--gf-trim));
    border-bottom: 1px solid var(--gf-border-soft, rgba(74, 58, 38, 0.6));
}
.vs-grid .gf-input, .vs-grid .gf-select { width: 100%; box-sizing: border-box; }
.vs-checks { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 8px 0 6px; }
.vs-check { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: #cfc2a6; cursor: pointer; }
.vs-status { padding: 0 12px; font-size: 11px; color: var(--gf-dim); }
.vs-list {
    flex: 1 1 auto;
    min-height: 120px;
    overflow-y: auto;
    border-top: 1px solid rgba(74, 58, 38, 0.6);
    border-bottom: 1px solid rgba(74, 58, 38, 0.6);
}
.vs-origin {
    padding: 5px 12px;
    font-size: 11px;
    color: #d8b46a;
    background: rgba(160, 120, 60, 0.12);
}
.vs-row { align-items: flex-start; }
.vs-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.vs-name { color: var(--gf-bone); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vs-stats, .vs-where {
    font-size: 10px;
    color: var(--gf-dim);
    letter-spacing: 0.03em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.vs-where { color: var(--gf-accent-2); }
.vs-price { flex: 0 0 auto; text-align: right; white-space: nowrap; }
.vs-unit { font-size: 12px; color: var(--gf-accent, var(--gf-trim)); }
.vs-each { font-size: 10px; color: var(--gf-dim); }
.vs-travel { flex: 0 0 auto; align-self: center; }
.vs-travel:disabled { opacity: 0.45; cursor: not-allowed; }
.vs-more { align-self: center; }
.vs-note { padding: 0 12px; font-size: 10.5px; color: #7a6a52; }
.vendor-listing { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.vendor-listing.unlisted { color: #d8b46a; }

/* The way home, while a free Return is open. A HUD chip, not a window. */
.vs-return-chip {
    position: fixed;
    left: 50%;
    top: calc(var(--mobile-safe-top, 8px) + 44px);
    transform: translateX(-50%);
    z-index: var(--gf-z-window, 1000);
    padding: 6px 14px;
    font-family: inherit;
    font-size: 12px;
    color: var(--gf-accent, var(--gf-trim));
    background: var(--gf-panel-bg, rgba(20, 15, 10, 0.92));
    border: 1px solid var(--gf-border-accent, rgba(var(--gf-trim-rgb), 0.4));
    clip-path: var(--gf-cut);
    cursor: pointer;
    box-shadow: var(--gf-shadow, 0 2px 10px rgba(0, 0, 0, 0.5));
}
.vs-return-chip:hover { border-color: var(--gf-accent, var(--gf-trim)); }
.vs-return-chip.dim { opacity: 0.55; cursor: not-allowed; }
body.mobile-controls-active .vs-travel,
body.mobile-controls-active .vs-more,
body.mobile-controls-active .vs-return-chip { min-height: 42px; }
body.mobile-controls-active .vs-toolbar { flex-wrap: wrap; }
body.mobile-controls-active .vs-sort { max-width: none; flex: 1 1 100%; }
body.mobile-controls-active .vs-panel { padding-bottom: var(--gf-keyboard-offset, 0px); }

/* ── Quantity stepper: − [n] + Max ─────────────────────────────────────── */
.npc-step {
    display: inline-flex;
    align-items: stretch;
    flex: 0 0 auto;
}
.npc-step .npc-qty {
    width: 46px;
    text-align: center;
    border-left: none;
    border-right: none;
    /* Spinners fight the −/+ buttons for the same job in half the width. */
    -moz-appearance: textfield;
    appearance: textfield;
}
.npc-step .npc-qty::-webkit-outer-spin-button,
.npc-step .npc-qty::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.npc-step-btn {
    padding: 3px 7px;
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    border: 1px solid var(--gf-border-accent);
    cursor: pointer;
    font-family: inherit;
    font-size: 12px;
    line-height: 1;
    clip-path: var(--gf-cut-sm);
}
.npc-step-btn:hover {
    background: rgba(160, 120, 60, 0.25);
    color: #f0d090;
}
.npc-step-max {
    font-size: 10px;
    letter-spacing: 0.04em;
}
.npc-qty {
    width: 52px;
    padding: 3px 4px;
    background: var(--gf-plate);
    color: #f0d090;
    border: 1px solid #4a3a26;
    font-family: inherit;
    font-size: 12px;
}
.npc-btn {
    padding: 4px 12px;
    background: var(--gf-face);
    color: var(--gf-accent-hot);
    border: 1px solid var(--gf-border-accent);
    cursor: pointer;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.04em;
    clip-path: var(--gf-cut-sm);
}
.npc-btn:hover:not(:disabled) {
    background: var(--gf-face-hover);
}
.npc-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    background: #2a2018;
}
.npc-btn.sell {
    background: linear-gradient(180deg, #4a5a30 0%, #313e1c 100%);
    border-color: #6a8048;
}
.npc-btn.sell:hover:not(:disabled) {
    background: linear-gradient(180deg, #5a6a38 0%, #4a5a30 100%);
}
.npc-done {
    flex: 0 0 auto;
    font-size: 11px;
    color: #80a080;
    font-style: italic;
    padding-right: 6px;
}
.npc-empty {
    color: #786858;
    text-align: center;
    margin-top: 36px;
    font-style: italic;
    font-size: 12px;
}

.npc-qty-wide { width: 100%; box-sizing: border-box; text-align: center; }

/* ═══════════════════════════════════════════════════════════════
   Player-vendor management window (Phase 12 P4, owner side)
   ═══════════════════════════════════════════════════════════════ */
.vendor-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.vendor-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-bottom: 1px solid #4a3a26;
    background: rgba(0, 0, 0, 0.3);
}
.vendor-count { font-size: 12px; color: #f0d090; letter-spacing: 0.04em; }
.vendor-price-set {
    margin-left: auto;
    font-size: 11px;
    color: #c0a070;
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}
.vendor-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 0;
}
.vendor-hint {
    padding: 6px 12px 8px 12px;
    font-size: 10.5px;
    color: var(--gf-accent-2);
    font-style: italic;
}
.vendor-list { display: flex; flex-direction: column; }
.vendor-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    font-size: 12px;
}
.vendor-row:hover { background: rgba(160, 120, 60, 0.12); }
.vendor-row-name { flex: 1 1 auto; color: var(--gf-bone); }
.vendor-row-price { flex: 0 0 auto; font-size: 11px; color: #c0a070; }
.vendor-qty {
    width: 72px;
    padding: 3px 4px;
    background: var(--gf-plate);
    color: #f0d090;
    border: 1px solid #4a3a26;
    font-family: inherit;
    font-size: 12px;
}
.vendor-btn {
    padding: 4px 12px;
    background: linear-gradient(180deg, #6a4a20 0%, #4a3018 100%);
    color: #f0d090;
    border: 1px solid #8a6840;
    cursor: pointer;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.04em;
}
.vendor-btn:hover:not(:disabled) {
    background: var(--gf-face-hover);
}
.vendor-btn.danger {
    background: linear-gradient(180deg, #6a2a20 0%, #4a1c16 100%);
    border-color: #8a4838;
    width: 100%;
    text-align: center;
}
.vendor-btn.danger:hover { background: linear-gradient(180deg, #8a3830 0%, #6a2a20 100%); }
.vendor-btn.active {
    background: linear-gradient(180deg, #a07830 0%, #7a5420 100%);
    border-color: #c0a060;
    color: #fff0c0;
}

/* ── Homestead window (housing_panel.js) ─────────────────────────────────────
   Head (the house's identity and chips) / a tab strip for a manager / one
   scrolling body, with the same 12 px inset everywhere. Dark glass and ash-gold
   trim on the --gf-* tokens only. The sign's board color rides --gf-home-board
   (set from the dye catalog in JS; natural wood is the default below), and the
   one Grid-cyan surface is the staff record, which wears the staff tokens. */
.gf-home {
    --gf-home-board: color-mix(in srgb, var(--gf-ember-deep) 62%, var(--gf-gold));
    --gf-home-swatch: var(--gf-text-4);
    container: gf-home / inline-size;
    display: flex; flex-direction: column; height: 100%; min-height: 0;
    color: var(--gf-text-1); font-size: 12px;
}
.gf-home-head {
    flex: 0 0 auto; padding: 12px 12px 10px;
    border-bottom: 1px solid var(--gf-border-soft);
    background:
        linear-gradient(180deg, color-mix(in srgb, var(--gf-gold) 10%, transparent), transparent 80%),
        var(--gf-panel-bg-soft);
}
.gf-home-head:empty { display: none; }
.gf-home-id { display: flex; align-items: center; gap: 10px; min-width: 0; }
.gf-home-medal {
    flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
    display: grid; place-items: center;
    border: 1px solid var(--gf-border-accent);
    background: radial-gradient(circle at 50% 30%, color-mix(in srgb, var(--gf-gold) 26%, transparent), var(--gf-panel-bg-strong) 75%);
    box-shadow: var(--gf-inset), 0 0 0 3px color-mix(in srgb, var(--gf-gold) 9%, transparent);
}
.gf-home-medal .gf-ic { color: var(--gf-gold-bright); }
.gf-home-titles { flex: 1 1 auto; min-width: 0; }
.gf-home-name {
    font-family: var(--gf-font-display); font-size: 16px; font-weight: 700;
    line-height: 1.25; letter-spacing: 0.02em; color: var(--gf-accent);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-home-owner {
    margin-top: 1px; font-size: 11px; color: var(--gf-text-3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-home-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.gf-home-chip {
    display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut-sm);
    background: var(--gf-slot-bg); color: var(--gf-text-2);
    font-size: 10.5px; line-height: 15px; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.gf-home-chip-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--gf-success); }
.gf-home-chip.is-worn { color: var(--gf-stamina); border-color: color-mix(in srgb, var(--gf-stamina) 35%, transparent); }
.gf-home-chip.is-worn .gf-home-chip-dot { background: var(--gf-stamina); }
.gf-home-chip.is-idoc {
    color: var(--gf-danger); border-color: color-mix(in srgb, var(--gf-danger) 45%, transparent);
    background: color-mix(in srgb, var(--gf-danger) 10%, transparent);
}
.gf-home-chip.is-idoc .gf-home-chip-dot { background: var(--gf-danger); }
.gf-home-chip.is-here { color: var(--gf-gold-bright); border-color: var(--gf-border-accent); }
.gf-home-chip.is-away { color: var(--gf-text-3); }

.gf-home > .gf-home-tabs { padding: 8px 12px 0; }
.gf-home-body {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-gutter: stable;
    display: flex; flex-direction: column; gap: 12px;
    padding: 12px 12px 14px;
}
.gf-home-pane { display: flex; flex-direction: column; gap: 12px; }
.gf-home-body > *, .gf-home-pane > * { flex-shrink: 0; }
/* A narrow window is usually a short one. A tighter head gives the body its
   height back, and the size chip gives way (a manager has the size under
   Details) so the rest hold one row. */
@container gf-home (max-width: 430px) {
    .gf-home-head { padding: 9px 12px 8px; }
    .gf-home-id { gap: 9px; }
    .gf-home-medal { width: 32px; height: 32px; }
    .gf-home-name { font-size: 15px; }
    .gf-home-chips { gap: 4px; margin-top: 7px; }
    .gf-home-chip { padding: 1px 7px; font-size: 10px; }
    .gf-home-chip.is-size { display: none; }
    .gf-home > .gf-home-tabs { padding-top: 5px; }
}

.gf-home-section { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.gf-home-section-head {
    display: flex; align-items: center; gap: 8px; min-height: 22px;
    padding-bottom: 5px; border-bottom: 1px solid var(--gf-border-soft);
}
.gf-home-section-title {
    flex: 1 1 auto; min-width: 0; font-size: 10px; font-weight: 600;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--gf-gold);
}
.gf-home-section-count {
    min-width: 18px; padding: 0 6px; clip-path: var(--gf-cut-sm); text-align: center;
    font-size: 10px; line-height: 16px; font-variant-numeric: tabular-nums;
    color: var(--gf-text-2); background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft);
}
.gf-home-section-head .gf-btn-sm { padding: 2px 8px; }
.gf-home-note { font-size: 11px; line-height: 1.5; color: var(--gf-text-3); }
.gf-home-lede { font-size: 11.5px; line-height: 1.5; color: var(--gf-text-2); }
.gf-home-empty-row {
    padding: 10px 12px; border: 1px dashed var(--gf-border); clip-path: var(--gf-cut);
    font-size: 11.5px; font-style: italic; line-height: 1.5; text-align: center; color: var(--gf-text-3);
}

/* Buttons: .gf-btn is the house control; primary adds weight, ghost and danger
   are the kit's own. An icon rides before the label without becoming its text. */
.gf-home-btn-ic { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.gf-home-btn-ic > .gf-ic { order: -1; color: currentColor; }
.gf-btn.gf-home-primary {
    font-weight: 600; color: var(--gf-accent-hot); border-color: var(--gf-accent-deep);
    background: linear-gradient(180deg, color-mix(in srgb, var(--gf-accent) 36%, transparent), color-mix(in srgb, var(--gf-accent) 16%, transparent));
    box-shadow: var(--gf-inset);
}
.gf-btn.gf-home-primary:hover {
    background: linear-gradient(180deg, color-mix(in srgb, var(--gf-accent) 50%, transparent), color-mix(in srgb, var(--gf-accent) 24%, transparent));
}

/* ── The sign, as a visitor reads it: a framed board in its own color ── */
.gf-home-plaque {
    position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px;
    min-width: 0; padding: 18px 20px 13px; text-align: center;
    border: 1px solid color-mix(in srgb, var(--gf-home-board) 50%, var(--gf-gold));
    clip-path: var(--gf-cut);
    background:
        linear-gradient(180deg, color-mix(in srgb, var(--gf-home-board) 36%, transparent), color-mix(in srgb, var(--gf-home-board) 14%, transparent)),
        var(--gf-panel-bg-strong);
    box-shadow:
        inset 0 0 0 3px color-mix(in srgb, var(--gf-panel-bg-strong) 55%, transparent),
        inset 0 0 0 4px color-mix(in srgb, var(--gf-home-board) 45%, transparent),
        var(--gf-inset), var(--gf-shadow);
}
/* The four nails that hold the board up. */
.gf-home-plaque::before {
    content: ''; position: absolute; inset: 8px; pointer-events: none; opacity: 0.65;
    background:
        radial-gradient(circle, var(--gf-gold) 0 1.6px, transparent 2.2px) left top / 6px 6px no-repeat,
        radial-gradient(circle, var(--gf-gold) 0 1.6px, transparent 2.2px) right top / 6px 6px no-repeat,
        radial-gradient(circle, var(--gf-gold) 0 1.6px, transparent 2.2px) left bottom / 6px 6px no-repeat,
        radial-gradient(circle, var(--gf-gold) 0 1.6px, transparent 2.2px) right bottom / 6px 6px no-repeat;
}
.gf-home-plaque-name {
    max-width: 100%; font-family: var(--gf-font-display); font-size: 18px; font-weight: 700;
    line-height: 1.25; letter-spacing: 0.03em; color: var(--gf-gold-bright);
    text-shadow: 0 1px 2px var(--gf-panel-bg-strong); overflow-wrap: anywhere;
}
.gf-home-plaque-motto {
    max-width: 100%; font-size: 12px; font-style: italic; color: var(--gf-text-2); overflow-wrap: anywhere;
}
.gf-home-plaque-owner {
    margin-top: 3px; font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--gf-gold);
}
.gf-home-plaque-rule {
    width: 62%; height: 1px; margin: 9px 0 6px;
    background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--gf-gold) 60%, transparent), transparent);
}
.gf-home-plaque-welcome {
    max-width: 100%; font-size: 12px; line-height: 1.55; color: var(--gf-text-1);
    white-space: pre-wrap; overflow-wrap: anywhere; text-wrap: pretty;
}
.gf-home-plaque-access {
    align-self: stretch; margin-top: 9px; padding-top: 7px;
    border-top: 1px solid color-mix(in srgb, var(--gf-home-board) 30%, var(--gf-border-soft));
    font-size: 10.5px; color: var(--gf-text-3);
}
.gf-home-callout {
    display: flex; align-items: flex-start; gap: 9px; padding: 9px 11px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut);
    background: var(--gf-slot-bg); color: var(--gf-text-2); font-size: 12px; line-height: 1.5;
}
.gf-home-callout > .gf-ic { margin-top: 2px; color: var(--gf-gold); }
.gf-home-callout.is-ok { border-color: var(--gf-border-accent); color: var(--gf-text-1); }

/* ── Condition: a segmented meter, one pip per stage left before collapse ── */
.gf-home-cond {
    display: flex; flex-direction: column; gap: 9px; padding: 10px 12px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut); background: var(--gf-slot-bg);
}
.gf-home-cond-top { display: flex; align-items: center; gap: 10px; }
.gf-home-cond-words { flex: 1 1 auto; min-width: 0; }
.gf-home-cond-label { font-size: 13px; font-weight: 600; }
.gf-home-cond-sub { margin-top: 1px; font-size: 11px; color: var(--gf-text-3); }
.gf-home-cond.is-good .gf-home-cond-label { color: var(--gf-success); }
.gf-home-cond.is-worn .gf-home-cond-label { color: var(--gf-stamina); }
.gf-home-cond.is-idoc { border-color: color-mix(in srgb, var(--gf-danger) 40%, transparent); }
.gf-home-cond.is-idoc .gf-home-cond-label { color: var(--gf-danger); }
.gf-home-meter { display: flex; gap: 4px; }
.gf-home-pip {
    flex: 1 1 0; height: 6px; clip-path: var(--gf-cut-sm);
    background: color-mix(in srgb, var(--gf-text-4) 40%, transparent);
}
.gf-home-cond.is-good .gf-home-pip.is-on { background: var(--gf-success); }
.gf-home-cond.is-worn .gf-home-pip.is-on { background: var(--gf-stamina); }
.gf-home-cond.is-idoc .gf-home-pip.is-on { background: var(--gf-danger); }

.gf-home-facts {
    display: flex; flex-direction: column; overflow: hidden;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut); background: var(--gf-slot-bg);
}
.gf-home-fact { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 6px 10px; }
.gf-home-fact + .gf-home-fact { border-top: 1px solid var(--gf-border-soft); }
.gf-home-fact-label { flex: 0 0 auto; color: var(--gf-text-3); }
.gf-home-fact-value {
    min-width: 0; text-align: right; color: var(--gf-text-1);
    overflow-wrap: anywhere; font-variant-numeric: tabular-nums;
}
.gf-home-danger {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; padding: 10px 12px;
    border: 1px solid color-mix(in srgb, var(--gf-danger) 28%, transparent); clip-path: var(--gf-cut);
    background: color-mix(in srgb, var(--gf-danger) 6%, transparent);
}
.gf-home-danger-words { flex: 1 1 180px; min-width: 0; }
.gf-home-danger-title { margin-bottom: 1px; font-size: 12px; font-weight: 600; color: var(--gf-danger); }

/* ── Sign tab: form | live preview, one column when the window is narrow ── */
.gf-home-signpane { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }
.gf-home-preview-col { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.gf-home-preview-cap {
    display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 18px;
    font-size: 10.5px; color: var(--gf-text-3);
}
.gf-home-preview-cap .gf-home-glyph { width: 16px; height: 16px; }
.gf-home-signform { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
@container gf-home (min-width: 464px) {
    .gf-home-signpane { grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr); }
    .gf-home-preview-col { position: sticky; top: 0; }
}
.gf-home-field, .gf-home-group { display: flex; flex-direction: column; gap: 5px; }
.gf-home-field-head, .gf-home-group-head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.gf-home-field-label, .gf-home-group-label { font-size: 11px; color: var(--gf-text-2); }
.gf-home-field.is-changed .gf-home-field-label::after { content: ' •'; color: var(--gf-gold-bright); }
.gf-home-group-value { font-size: 11px; color: var(--gf-gold-bright); }
.gf-home-count { font-size: 10px; color: var(--gf-text-4); font-variant-numeric: tabular-nums; }
.gf-home-count.is-near { color: var(--gf-stamina); }
.gf-home-count.is-full { color: var(--gf-ember); }
.gf-input.gf-home-input {
    flex: 0 0 auto; width: 100%; padding: 7px 9px; font-size: 12.5px; line-height: 1.4;
    background: var(--gf-panel-bg-strong); user-select: text;
    box-shadow: inset 0 1px 3px color-mix(in srgb, var(--gf-panel-bg-strong) 85%, transparent);
}
textarea.gf-input.gf-home-input { min-height: 78px; resize: vertical; }
.gf-input.gf-home-input:focus {
    border-color: var(--gf-border-accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--gf-gold) 22%, transparent);
}
.gf-home-styles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
.gf-home-style {
    display: flex; align-items: center; gap: 6px; min-width: 0; padding: 6px 7px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut-sm);
    background: var(--gf-slot-bg); color: var(--gf-text-2);
    font: inherit; font-size: 11px; line-height: 1.25; text-align: left; cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.gf-home-style:hover { background: var(--gf-slot-bg-hover); color: var(--gf-text-1); }
.gf-home-style.is-chosen {
    border-color: var(--gf-border-accent); color: var(--gf-accent-hot);
    background: linear-gradient(180deg, color-mix(in srgb, var(--gf-gold) 18%, transparent), color-mix(in srgb, var(--gf-gold) 6%, transparent));
    box-shadow: var(--gf-inset);
}
/* A style's glyph: flat vector, its board in the chosen color. */
.gf-home-glyph { order: -1; flex: 0 0 auto; display: inline-block; width: 20px; height: 20px; }
.gf-home-glyph svg { display: block; width: 100%; height: 100%; }
.gf-home-glyph .gb { fill: var(--gf-home-board); }
.gf-home-glyph .gp { fill: var(--gf-text-3); }
.gf-home-glyph .gs { fill: var(--gf-text-4); }
.gf-home-style.is-chosen .gf-home-glyph .gp { fill: var(--gf-gold); }
.gf-home-swatches { display: grid; grid-template-columns: repeat(8, minmax(0, 26px)); gap: 6px; }
.gf-home-swatch {
    width: 100%; aspect-ratio: 1; padding: 0; border-radius: 50%; cursor: pointer;
    border: 1px solid color-mix(in srgb, var(--gf-panel-bg-strong) 75%, transparent);
    background: var(--gf-home-swatch);
    box-shadow:
        inset 0 1px 0 color-mix(in srgb, var(--gf-text-1) 22%, transparent),
        inset 0 -3px 4px color-mix(in srgb, var(--gf-panel-bg-strong) 40%, transparent);
    transition: transform 0.1s, box-shadow 0.12s;
}
.gf-home-swatch.is-natural {
    background: linear-gradient(135deg, color-mix(in srgb, var(--gf-gold) 60%, var(--gf-ember-deep)), color-mix(in srgb, var(--gf-ember-deep) 75%, var(--gf-panel-bg-strong)));
}
.gf-home-swatch:hover { transform: scale(1.1); }
.gf-home-swatch.is-chosen { box-shadow: 0 0 0 2px var(--gf-panel-bg-strong), 0 0 0 3.5px var(--gf-gold-bright); }
/* The sign's status closes the form. With unsaved changes it becomes a bar
   pinned to the bottom of the scrolling body, so Save and Revert stay in reach
   from the first box to the last swatch; a clean sign pins nothing. */
.gf-home-signfoot {
    grid-column: 1 / -1; min-height: 30px;
    display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px;
}
.gf-home-signpane.is-dirty .gf-home-signfoot {
    position: sticky; bottom: -14px; z-index: 1;
    margin: 0 -12px -14px; padding: 9px 12px 10px;
    border-top: 1px solid var(--gf-border-accent);
    /* The strong glass twice over, so the swatches scrolling beneath do not
       show through the bar. */
    background: linear-gradient(var(--gf-panel-bg-strong), var(--gf-panel-bg-strong)), var(--gf-panel-bg-strong);
}
.gf-home-signstatus { flex: 1 1 auto; min-width: 0; font-size: 11px; color: var(--gf-text-3); }
.gf-home-signstatus.is-dirty { color: var(--gf-gold-bright); }
.gf-home-signstatus.is-dirty::before {
    content: ''; display: inline-block; width: 6px; height: 6px; margin-right: 6px;
    border-radius: 50%; background: currentColor; vertical-align: 1px;
}
.gf-home-signstatus.is-invalid { color: var(--gf-danger); }

/* ── Access: who may build, as one radio group; then the guests ── */
.gf-home-seg {
    display: flex; flex-direction: column; overflow: hidden;
    border: 1px solid var(--gf-border); clip-path: var(--gf-cut);
    background: var(--gf-panel-bg-strong);
    box-shadow: inset 0 1px 3px color-mix(in srgb, var(--gf-panel-bg-strong) 80%, transparent);
}
.gf-home-seg-opt {
    display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 12px;
    border: 0; background: transparent; color: var(--gf-text-2);
    font: inherit; text-align: left; cursor: pointer;
    transition: background 0.12s, color 0.12s, box-shadow 0.12s;
}
.gf-home-seg-opt + .gf-home-seg-opt { border-top: 1px solid var(--gf-border-soft); }
.gf-home-seg-opt:hover { background: var(--gf-slot-bg-hover); color: var(--gf-text-1); }
.gf-home-seg-opt.is-chosen {
    color: var(--gf-text-1);
    background: linear-gradient(90deg, color-mix(in srgb, var(--gf-gold) 20%, transparent), color-mix(in srgb, var(--gf-gold) 5%, transparent));
    box-shadow: inset 3px 0 0 var(--gf-gold-bright);
}
.gf-home-seg-ic { color: var(--gf-text-3); }
.gf-home-seg-opt.is-chosen .gf-home-seg-ic { color: var(--gf-gold-bright); }
.gf-home-seg-words { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gf-home-seg-label { font-size: 12.5px; font-weight: 600; }
.gf-home-seg-desc {
    font-size: 11px; color: var(--gf-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-home-seg-pip {
    flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%;
    border: 1px solid var(--gf-border); background: var(--gf-slot-bg);
}
.gf-home-seg-opt.is-chosen .gf-home-seg-pip {
    border-color: var(--gf-gold-bright);
    background: radial-gradient(circle, var(--gf-gold-bright) 0 3px, transparent 3.5px), var(--gf-slot-bg);
}
.gf-home-guests {
    display: flex; flex-direction: column;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut); background: var(--gf-slot-bg);
}
.gf-home-guest { display: flex; align-items: center; gap: 10px; padding: 7px 10px; }
.gf-home-guest + .gf-home-guest { border-top: 1px solid var(--gf-border-soft); }
.gf-home-avatar {
    flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
    border: 1px solid var(--gf-border-accent); background: var(--gf-panel-bg-strong);
}
.gf-home-avatar .gf-ic { color: var(--gf-gold); }
.gf-home-guest-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gf-home-guest-name {
    font-size: 12.5px; color: var(--gf-text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-home-guest-since { font-size: 10.5px; color: var(--gf-text-3); }
.gf-home-add { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; }
.gf-home-hint { font-size: 11px; color: var(--gf-text-3); }

/* ── Homes: every home held, one click from its own window ── */
.gf-home-capacity { display: flex; align-items: center; gap: 10px; color: var(--gf-text-2); }
.gf-home-capacity-pips { display: flex; gap: 3px; flex: 0 1 140px; }
.gf-home-capacity-pips .gf-home-pip { height: 5px; }
.gf-home-capacity-pips .gf-home-pip.is-on { background: var(--gf-gold); }
.gf-home-capacity-value { margin-left: auto; color: var(--gf-text-1); font-variant-numeric: tabular-nums; }
.gf-home-cards { display: flex; flex-direction: column; gap: 6px; }
.gf-home-card {
    display: flex; align-items: center; gap: 10px; width: 100%; min-width: 0; padding: 8px 10px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut);
    background: var(--gf-slot-bg); color: var(--gf-text-1);
    font: inherit; text-align: left; cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.gf-home-card:hover { background: var(--gf-slot-bg-hover); border-color: var(--gf-border-accent); }
.gf-home-card.is-viewing {
    border-color: var(--gf-border-accent);
    background: linear-gradient(90deg, color-mix(in srgb, var(--gf-gold) 14%, transparent), var(--gf-slot-bg));
}
.gf-home-card-ic {
    flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
    border: 1px solid var(--gf-border); background: var(--gf-panel-bg-strong);
}
.gf-home-card.is-viewing .gf-home-card-ic { border-color: var(--gf-border-accent); }
.gf-home-card-ic .gf-ic { color: var(--gf-gold); }
.gf-home-card-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.gf-home-card-name {
    font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-home-card-meta { display: flex; flex-wrap: wrap; gap: 0 6px; font-size: 10.5px; color: var(--gf-text-3); }
.gf-home-card-meta > span + span::before { content: '·'; margin-right: 6px; color: var(--gf-text-4); }
.gf-home-card-cond.is-good { color: var(--gf-success); }
.gf-home-card-cond.is-worn { color: var(--gf-stamina); }
.gf-home-card-cond.is-idoc { color: var(--gf-danger); }
.gf-home-card-where { font-style: italic; color: var(--gf-text-2); }
.gf-home-badge {
    flex: 0 0 auto; padding: 1px 7px; clip-path: var(--gf-cut-sm);
    border: 1px solid var(--gf-border-accent); background: color-mix(in srgb, var(--gf-gold) 12%, transparent);
    font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gf-gold-bright);
}
.gf-home-card.is-compact { padding: 6px 9px; }
.gf-home-card.is-compact .gf-home-card-ic { width: 24px; height: 24px; }
/* ── No home yet: a title under the house medal, one line, then the two steps
   that get one. Solid glass, not a dashed box - an empty state is a page,
   not a placeholder. ── */
.gf-home-empty {
    display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 14px 14px 12px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut);
    background:
        radial-gradient(90% 60% at 50% 0%, color-mix(in srgb, var(--gf-gold) 9%, transparent), transparent 70%),
        var(--gf-slot-bg);
    text-align: center;
}
.gf-home-empty .gf-home-empty-ic { color: var(--gf-gold); opacity: 0.85; margin-bottom: 2px; }
.gf-home-empty-title {
    font-family: var(--gf-font-display); font-size: 14px; font-weight: 700;
    letter-spacing: 0.02em; color: var(--gf-accent);
}
.gf-home-empty-text { max-width: 34em; font-size: 11.5px; line-height: 1.55; color: var(--gf-text-3); }
.gf-home-steps {
    list-style: none; margin: 9px 0 0; padding: 0; width: 100%;
    display: flex; flex-direction: column; gap: 5px; text-align: left;
}
.gf-home-step {
    display: flex; align-items: center; gap: 10px; padding: 7px 10px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut);
    background: var(--gf-panel-bg-soft);
}
.gf-home-step-num {
    flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
    display: grid; place-items: center;
    font-family: var(--gf-font-display); font-size: 10px; font-weight: 700; line-height: 1;
    color: var(--gf-gold-bright); border: 1px solid var(--gf-border-accent);
    background: color-mix(in srgb, var(--gf-gold) 12%, transparent);
}
.gf-home-step-ic { flex: 0 0 auto; display: grid; place-items: center; color: var(--gf-gold); }
.gf-home-step-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gf-home-step-title { font-size: 12px; font-weight: 600; color: var(--gf-text-1); }
.gf-home-step-detail { font-size: 11px; line-height: 1.45; color: var(--gf-text-3); }

/* ── Guild charter plaque: the guild under its medal, then three figures ── */
.gf-home-plaque-card {
    display: flex; flex-direction: column; gap: 9px; padding: 9px 12px 10px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut);
    background:
        linear-gradient(180deg, color-mix(in srgb, var(--gf-gold) 7%, transparent), transparent 70%),
        var(--gf-slot-bg);
}
.gf-home-plaque-id { display: flex; align-items: center; gap: 9px; min-width: 0; }
.gf-home-plaque-medal {
    flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
    border: 1px solid var(--gf-border-accent); background: var(--gf-panel-bg-strong);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--gf-gold) 8%, transparent);
}
.gf-home-plaque-medal .gf-ic { color: var(--gf-gold-bright); }
.gf-home-plaque-titles { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gf-home-plaque-name {
    font-family: var(--gf-font-display); font-size: 13px; font-weight: 700;
    color: var(--gf-accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-home-plaque-sub { font-size: 10.5px; color: var(--gf-text-3); }
.gf-home-stats {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px;
}
.gf-home-stat {
    display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 7px 6px 6px;
    border: 1px solid var(--gf-border-soft); clip-path: var(--gf-cut-sm);
    background: var(--gf-panel-bg-soft); min-width: 0;
}
.gf-home-stat-value {
    font-size: 15px; font-weight: 600; line-height: 1.15; color: var(--gf-text-1);
    font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.gf-home-stat-label { font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--gf-text-3); }
.gf-home-stat-pips { display: flex; gap: 3px; width: 100%; max-width: 64px; margin-top: 4px; }
.gf-home-stat-pips .gf-home-pip { height: 4px; }
.gf-home-stat-pips .gf-home-pip.is-on { background: var(--gf-gold); }

/* A flex row of controls. The mail compose form borrows it (mail_panel.js). */
.housing-row { display: flex; align-items: center; gap: 6px; }

/* ── Staff record: players never see it, so it wears the staff tokens and spells
   no color of its own (VISUAL_IDENTITY: dark cool glass, cyan trim) ── */
.housing-staff {
    display: flex; flex-direction: column; gap: 1px; padding: 8px 11px 10px;
    border: 1px solid var(--gf-staff-border); clip-path: var(--gf-cut);
    background: var(--gf-staff-bg); color: var(--gf-staff-text);
    box-shadow: 0 0 14px var(--gf-staff-glow), var(--gf-inset);
}
.housing-staff > .gf-staff-note { margin-bottom: 5px; }
.housing-staff-row { display: flex; gap: 10px; padding: 2px 0; font-size: 11.5px; line-height: 1.45; }
.housing-staff-key {
    flex: 0 0 76px; padding-top: 1px; font-size: 9.5px; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--gf-staff-label);
}
.housing-staff-val { flex: 1 1 auto; min-width: 0; color: var(--gf-staff-text); overflow-wrap: anywhere; }
.housing-staff-actions {
    display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; padding-top: 8px;
    border-top: 1px solid var(--gf-staff-border);
}
.housing-staff-btn {
    padding: 4px 9px; border: 1px solid var(--gf-staff-border); clip-path: var(--gf-cut-sm);
    background: var(--gf-staff-bg-soft); color: var(--gf-staff-text);
    font: inherit; font-size: 11px; cursor: pointer; transition: background 0.12s;
}
.housing-staff-btn:hover { background: var(--gf-staff-bg-hover); }

/* ── Mail panel (Phase 12 P6) - reuses the vendor-* skeleton ── */
.mail-list { display: flex; flex-direction: column; gap: 2px; }
.mail-letter { border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.12); }
.mail-letter.unread .vendor-row-name { color: #f0d090; font-weight: 600; }
.mail-row { display: flex; align-items: center; justify-content: space-between; cursor: pointer; gap: 8px; }
.mail-row:hover { background: rgba(var(--gf-trim-rgb), 0.08); }
.mail-date { color: #786858; font-size: 11px; white-space: nowrap; }
.mail-detail { display: flex; flex-direction: column; gap: 6px; padding: 6px 8px 10px; }
.mail-body { color: var(--gf-bone); font-size: 12px; white-space: pre-wrap; line-height: 1.4;
             max-height: 160px; overflow-y: auto; }
.mail-attach { color: #f0d090 !important; }
.mail-compose { display: flex; flex-direction: column; gap: 8px; margin: 8px 0; }
.mail-field { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.mail-textarea {
    width: 100%; box-sizing: border-box; resize: vertical;
    background: rgba(14, 13, 12, 0.6); color: var(--gf-bone);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.35); clip-path: var(--gf-cut-sm);
    padding: 6px; font-family: inherit; font-size: 12px;
}
.mail-flash { color: var(--gf-ember) !important; }
.vendor-empty {
    color: #786858;
    text-align: center;
    margin-top: 30px;
    font-style: italic;
    font-size: 12px;
}
.vendor-footer {
    padding: 8px 12px;
    border-top: 1px solid #4a3a26;
    background: rgba(0, 0, 0, 0.25);
}

/* ═══════════════════════════════════════════════════════════════
   Command Palette (/commands window) - role-filtered clickable commands
   ═══════════════════════════════════════════════════════════════ */
.cmd-palette-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.cmd-subheader {
    padding: 6px 12px;
    border-bottom: 1px solid #4a3a26;
    background: rgba(0,0,0,0.3);
    font-size: 11px;
    color: var(--gf-accent-2);
}
.cmd-subheader .cmd-role {
    color: #c0a070;
    text-transform: capitalize;
    letter-spacing: 0.05em;
}
.cmd-search {
    margin: 8px 10px 4px 10px;
    padding: 6px 10px;
    background: rgba(0,0,0,0.4);
    border: 1px solid #4a3a26;
    clip-path: var(--gf-cut-sm);
    color: var(--gf-bone);
    font-family: inherit;
    font-size: 13px;
    outline: none;
}
.cmd-search:focus { border-color: #c0a070; }
.cmd-list {
    flex: 1;
    overflow-y: auto;
    padding: 4px 0 10px 0;
    min-height: 0;
}
.cmd-cat-hdr {
    margin: 10px 0 4px 0;
    padding: 0 12px;
    font-size: 11px;
    color: #c0a070;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.cmd-cat-hdr-pinned { color: #e0b860; }

/* ── The palette's staff half ──────────────────────────────────────────────
   One `⬒ WARDEN · not visible to players` bar, then every staff category under
   it. Categories are grouped by an ordered map now rather than by comparing
   each row against the previous one, which emitted `GENERAL` twice: the
   registry has General rows both before and after the Party block. */
.cmd-staff-divider {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 14px 0 2px;
    padding: 5px 12px;
    border-top: 1px solid var(--gf-staff-border);
    border-bottom: 1px solid var(--gf-staff-border);
    border-left: 2px solid var(--ribbon-role, var(--gf-grid-cyan));
    background: var(--gf-staff-bg-soft);
    font: 9px/1.5 var(--gf-mono);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gf-staff-label);
}
.cmd-cat-hdr-staff { color: var(--gf-staff-label); }
.cmd-row.cmd-row-staff:hover {
    background: var(--gf-staff-bg-soft);
    border-left-color: var(--gf-grid-cyan);
}
.cmd-row.cmd-row-staff .cmd-label { color: var(--gf-staff-text); }
.cmd-row.cmd-row-staff .cmd-badge {
    color: var(--gf-staff-label);
    background: var(--gf-staff-bg-soft);
    border-color: var(--gf-staff-border);
}
/* The row a right-click menu sent you to, to finish filling in. */
.cmd-row.cmd-row-prefilled {
    background: var(--gf-staff-bg-hover);
    border-left-color: var(--gf-grid-cyan);
}

.cmd-fav-btn {
    background: none;
    border: none;
    color: #6a5a3c;
    cursor: pointer;
    font-size: 13px;
    line-height: 1;
    padding: 0 2px;
    margin-right: 2px;
    transition: color 0.12s ease;
}
.cmd-fav-btn:hover { color: #e0b860; }
.cmd-fav-btn.on { color: #f0c860; }
.cmd-row {
    padding: 6px 12px 8px 12px;
    border-left: 3px solid transparent;
}
.cmd-row:hover {
    background: rgba(160, 120, 60, 0.10);
    border-left-color: #6a5436;
}
.cmd-row-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 4px;
}
.cmd-label {
    font-size: 13px;
    color: #f0d090;
}
.cmd-slash {
    font-size: 10px;
    color: #807060;
    font-family: 'Courier New', monospace;
}
.cmd-badge {
    margin-left: auto;
    font-size: 9px;
    color: var(--gf-accent-2);
    background: rgba(160, 120, 60, 0.18);
    border: 1px solid #6a5436;
    clip-path: var(--gf-cut-sm);
    padding: 1px 5px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.cmd-args {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.cmd-arg {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}
.cmd-arg-input {
    background: rgba(0,0,0,0.35);
    border: 1px solid #4a3a26;
    color: var(--gf-bone);
    font-family: inherit;
    font-size: 12px;
    padding: 3px 6px;
    clip-path: var(--gf-cut-sm);
    width: 86px;
    box-sizing: border-box;
}
.cmd-arg-input:focus {
    outline: none;
    border-color: #c0a070;
}
.cmd-arg-input.invalid {
    border-color: #c06050;
    background: rgba(160, 60, 50, 0.18);
}
select.cmd-arg-input {
    width: auto;
    min-width: 86px;
}
.cmd-arg-player { width: 110px; }
.cmd-pick-btn {
    background: rgba(160, 120, 60, 0.15);
    border: 1px solid #6a5436;
    color: #f0d090;
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
    padding: 3px 6px;
    clip-path: var(--gf-cut-sm);
}
.cmd-pick-btn:hover { background: rgba(160, 120, 60, 0.35); }
.cmd-run-btn {
    margin-left: auto;
    padding: 4px 14px;
    background: linear-gradient(180deg, #6a4a20 0%, #4a3018 100%);
    color: #f0d090;
    border: 1px solid #8a6840;
    cursor: pointer;
    font-family: inherit;
    font-size: 12px;
    letter-spacing: 0.05em;
    clip-path: var(--gf-cut-sm);
}
.cmd-run-btn:hover {
    background: var(--gf-face-hover);
}
.cmd-run-btn.confirm {
    background: linear-gradient(180deg, #8a3020 0%, #5a1c12 100%);
    border-color: #c06050;
    color: #f0c0b0;
}
.cmd-empty {
    color: #786858;
    text-align: center;
    margin-top: 40px;
    font-style: italic;
}

/* ═══════════════════════════════════════════════════════════════
   AAA Action Bars / Hotbars (UO-style hotkeys)
   Multi-bar, floating single-icon buttons, drag-to-assign,
   cooldown sweeps, keybind overlays, slot-state coding.
   ═══════════════════════════════════════════════════════════════ */

.gf-bar {
    position: fixed;
    display: flex;
    gap: var(--gf-slot-gap);
    padding: 6px;
    background: var(--gf-panel-bg);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    box-shadow: var(--gf-shadow), var(--gf-inset);
    z-index: var(--gf-z-hud-bar);
    pointer-events: auto;
}
.gf-bar.gf-bar-vertical { flex-direction: column; }
/* Floating single-icon buttons are chromeless until hovered/editing. */
.gf-bar.gf-bar-floating {
    padding: 3px;
    background: transparent;
    border-color: transparent;
    box-shadow: none;
}
.gf-bar.gf-bar-floating:hover,
body.gf-hud-edit .gf-bar.gf-bar-floating {
    background: var(--gf-panel-bg-soft);
    border-color: var(--gf-border-soft);
}
/* The bar that owns the number keys right now. */
.gf-bar.gf-bar-keyactive {
    border-color: var(--gf-border-accent);
    box-shadow: var(--gf-shadow), 0 0 0 1px var(--gf-accent-glow), var(--gf-inset);
}

/* Drag handle (shown in HUD edit mode) */
.gf-bar-grip {
    display: none;
    align-self: stretch;
    width: 10px;
    margin-right: 2px;
    clip-path: var(--gf-cut-sm);
    background: repeating-linear-gradient(90deg, var(--gf-accent-glow) 0 2px, transparent 2px 4px);
    cursor: grab;
    flex-shrink: 0;
}
.gf-bar.gf-bar-vertical .gf-bar-grip {
    width: auto; height: 10px; margin: 0 0 2px 0;
}
body.gf-hud-edit .gf-bar-grip { display: block; }
body.gf-hud-edit .gf-bar { outline: 1px dashed var(--gf-border-accent); }

/* ── Slots ── */
.gf-slot {
    position: relative;
    width: var(--gf-slot);
    height: var(--gf-slot);
    background: var(--gf-slot-bg);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.18);
    clip-path: var(--gf-cut-sm);
    overflow: hidden;
    flex-shrink: 0;
    transition: border-color 0.12s, background 0.12s, box-shadow 0.12s, transform 0.06s;
    cursor: pointer;
}
.gf-slot:hover {
    background: var(--gf-slot-bg-hover);
    border-color: var(--gf-border-accent);
}
.gf-slot:active { transform: translateY(1px); }
.gf-slot.gf-slot-empty {
    border-style: dashed;
    border-color: var(--gf-border-soft);
    cursor: default;
}
.gf-slot.gf-slot-empty:hover { background: var(--gf-slot-bg); }
/* The SELECTED slot (armed for block placement, or the active builder slot).
   Gold, and about selection - see .gf-slot-armed below, which is a different
   state that used to share this class. */
.gf-slot.gf-slot-active {
    border-color: var(--gf-accent);
    box-shadow: 0 0 10px var(--gf-accent-glow), inset 0 0 0 1px var(--gf-accent-glow);
}
/* A slot holding the PRIMED weapon special. Its own class and its own shape:
   it reused .gf-slot-active for its whole life, which made "this move is armed
   and your next blow lands it" pixel-identical to "this is the block you are
   about to place". Ember + a notched outline, so the two never read alike. */
.gf-slot.gf-slot-armed {
    border-color: var(--gf-ember);
    background: linear-gradient(180deg, rgba(var(--gf-ember-rgb), 0.20), rgba(var(--gf-ember-rgb), 0.05));
    box-shadow: 0 0 12px var(--gf-ember-glow), inset 0 0 0 1px rgba(var(--gf-ember-rgb), 0.40);
}
/* The corner notch, drawn as a filled ember wedge rather than a clip so the
   slot's own cooldown ring and icon are untouched. */
.gf-slot.gf-slot-armed::before {
    content: '';
    position: absolute;
    top: 0; right: 0;
    width: 0; height: 0;
    border-top: 9px solid var(--gf-ember);
    border-left: 9px solid transparent;
    pointer-events: none;
    z-index: 3;
}
.gf-slot.gf-slot-armed .gf-slot-icon { filter: drop-shadow(0 0 5px var(--gf-ember-glow)); }
/* Per-surface opt-out (Options -> Gameplay -> Combat indicators). Scoped to the
   body so one toggle covers every bar without the hotbar knowing about prefs. */
body.gf-no-armed-hotbar .gf-slot.gf-slot-armed {
    border-color: var(--gf-border);
    background: var(--gf-slot-bg);
    box-shadow: none;
}
body.gf-no-armed-hotbar .gf-slot.gf-slot-armed::before { display: none; }
body.gf-no-armed-hotbar .gf-slot.gf-slot-armed .gf-slot-icon { filter: none; }
body.gf-no-armed-hud #hud-armed-special { display: none !important; }
body.gf-no-armed-reticle #hud-armed-reticle { display: none !important; }
/* Drop target highlight while dragging an assignable thing over it. */
.gf-slot.gf-slot-droptarget {
    border-color: var(--gf-success);
    box-shadow: 0 0 10px rgba(111,200,116,0.5);
}

/* Icon layer */
.gf-slot-icon {
    position: absolute;
    inset: 3px;
    clip-path: var(--gf-cut-sm);
    background-size: cover;
    background-position: center;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: calc(20px * var(--gf-ui-scale) * var(--gf-bar-scale));
    font-weight: 700;
    color: rgba(0,0,0,0.55);
    text-shadow: 0 1px 0 rgba(var(--gf-bone-rgb), 0.25);
}
/* Target-mode colored ring (UO Enhanced Client convention). */
.gf-slot[data-target="self"]    .gf-slot-icon { box-shadow: inset 0 0 0 2px rgba(90,166,224,0.8); }
.gf-slot[data-target="cursor"]  .gf-slot-icon { box-shadow: inset 0 0 0 2px rgba(111,200,116,0.8); }
.gf-slot[data-target="last"]    .gf-slot-icon { box-shadow: inset 0 0 0 2px rgba(150,150,150,0.8); }
.gf-slot[data-target="current"] .gf-slot-icon { box-shadow: inset 0 0 0 2px rgba(224,82,74,0.85); }

/* Keybind overlay (top-left) */
.gf-slot-key {
    position: absolute;
    top: 1px; left: 3px;
    font-size: calc(10px * var(--gf-ui-scale) * var(--gf-bar-scale));
    font-family: var(--gf-mono);
    color: var(--gf-text-2);
    text-shadow: 0 1px 2px #000, 0 0 2px #000;
    pointer-events: none;
    z-index: 3;
}
.gf-slot-active .gf-slot-key { color: var(--gf-accent-hot); }

/* Quantity / charges (bottom-right) */
.gf-slot-qty {
    position: absolute;
    bottom: 1px; right: 3px;
    font-size: calc(11px * var(--gf-ui-scale) * var(--gf-bar-scale));
    font-family: var(--gf-mono);
    color: var(--gf-text-1);
    text-shadow: 0 1px 2px #000, 0 0 2px #000;
    pointer-events: none;
    z-index: 3;
}

/* On-slot state coding */
.gf-slot.gf-slot-unusable .gf-slot-icon { filter: grayscale(0.85) brightness(0.6); }
.gf-slot.gf-slot-nomana::after,
.gf-slot.gf-slot-nostam::after,
.gf-slot.gf-slot-oorange::after {
    content: '';
    position: absolute; inset: 0;
    pointer-events: none;
    z-index: 2;
}
.gf-slot.gf-slot-nomana::after { background: rgba(64,96,200,0.30); }
/* Weapon specials burn stamina, not mana - a warm wash so the two read apart. */
.gf-slot.gf-slot-nostam::after { background: rgba(200,140,64,0.28); }
.gf-slot.gf-slot-oorange::after { background: rgba(0,0,0,0.35); }
/* Ready/proc glow */
.gf-slot.gf-slot-proc {
    animation: gfProcGlow 1.1s ease-in-out infinite;
}
@keyframes gfProcGlow {
    0%,100% { box-shadow: 0 0 6px var(--gf-accent-glow); }
    50%     { box-shadow: 0 0 16px var(--gf-accent-hot); }
}

/* Cooldown radial sweep (GPU-composited conic mask) */
.gf-slot-cooldown {
    position: absolute;
    inset: 0;
    z-index: 4;
    pointer-events: none;
    background: conic-gradient(rgba(0,0,0,0.66) var(--gf-cd, 0deg), transparent 0deg);
    display: none;
}
.gf-slot.gf-slot-cooling .gf-slot-cooldown { display: block; }
.gf-slot-cd-text {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: calc(14px * var(--gf-ui-scale) * var(--gf-bar-scale));
    font-weight: 700;
    font-family: var(--gf-mono);
    color: #fff;
    text-shadow: 0 1px 3px #000;
    pointer-events: none;
}
.gf-slot.gf-slot-cooling .gf-slot-cd-text { display: flex; }
/* "bling" pop when a cooldown finishes */
.gf-slot.gf-slot-ready-pop { animation: gfReadyPop 0.32s ease-out; }
@keyframes gfReadyPop {
    0%   { transform: scale(1); box-shadow: 0 0 0 var(--gf-accent-glow); }
    40%  { transform: scale(1.12); box-shadow: 0 0 18px var(--gf-accent-hot); }
    100% { transform: scale(1); box-shadow: none; }
}

/* ── Macro / hotbar context menu hint chip ── */
.gf-bar-addbtn {
    align-self: center;
    display: none;
    margin-left: 4px;
    padding: 0 8px;
    height: calc(var(--gf-slot) * 0.5);
    font-size: 18px;
    line-height: 1;
    color: var(--gf-accent);
    background: var(--gf-slot-bg);
    border: 1px dashed var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
body.gf-hud-edit .gf-bar-addbtn { display: inline-flex; align-items: center; }

/* ═══════════════════════════════════════════════════════════════
   Macro Editor + Ability Picker (inside a GameWindow)
   ═══════════════════════════════════════════════════════════════ */
.gf-macro-editor { display: flex; flex-direction: column; height: 100%; color: var(--gf-text-1); font-size: 13px; }
.gf-macro-cols { display: flex; flex: 1; min-height: 0; }
.gf-macro-list {
    width: 190px;
    border-right: 1px solid var(--gf-border-soft);
    overflow-y: auto;
    flex-shrink: 0;
}
.gf-macro-list-item {
    display: flex; align-items: center; gap: 8px;
    padding: 7px 10px;
    cursor: pointer;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-macro-list-item:hover { background: var(--gf-slot-bg-hover); }
.gf-macro-list-item.gf-active { background: rgba(var(--gf-trim-rgb), 0.14); box-shadow: inset 3px 0 0 var(--gf-accent); }
.gf-macro-list-icon {
    width: 26px; height: 26px; clip-path: var(--gf-cut-sm); flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; color: rgba(0,0,0,0.6); font-size: 13px;
}
.gf-macro-list-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gf-macro-list-key { font-family: var(--gf-mono); font-size: 10px; color: var(--gf-text-3); }

.gf-macro-detail { flex: 1; padding: 12px 14px; overflow-y: auto; min-width: 0; }
.gf-macro-detail h4 { color: var(--gf-accent); font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin: 14px 0 6px; }
.gf-macro-detail h4:first-child { margin-top: 0; }
.gf-field { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.gf-field label { width: 78px; color: var(--gf-text-2); font-size: 12px; flex-shrink: 0; }
.gf-input, .gf-select {
    flex: 1;
    background: rgba(0,0,0,0.35);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-1);
    padding: 5px 7px;
    font-family: inherit;
    font-size: 12px;
    min-width: 0;
}
.gf-input:focus, .gf-select:focus { outline: none; border-color: var(--gf-border-accent); }
.gf-keycap {
    flex: 1; text-align: center;
    background: rgba(0,0,0,0.35);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-1); padding: 5px 7px; cursor: pointer; font-family: var(--gf-mono); font-size: 12px;
}
.gf-keycap.gf-rebinding { border-color: var(--gf-accent); color: var(--gf-accent-hot); }

/* ── Shared widget kit (gf_widgets.js) ──
   Collapsible section: native <details> with a gold uppercase summary row
   (the .gf-go-group look) + rotating arrow + optional count badge. */
.gf-cs { margin: 14px 0 2px; }
.gf-cs:first-child { margin-top: 0; }
.gf-cs-head {
    list-style: none;
    display: flex; align-items: center; gap: 6px;
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--gf-accent);
    padding-bottom: 4px;
    border-bottom: 1px solid var(--gf-border-soft);
    cursor: pointer;
    user-select: none;
}
.gf-cs-head::-webkit-details-marker { display: none; }
.gf-cs-head::before { content: '▸'; font-size: 9px; color: var(--gf-text-3); transition: transform 0.12s; }
.gf-cs[open] > .gf-cs-head::before { transform: rotate(90deg); }
.gf-cs-head:hover { color: var(--gf-accent-hot); }
.gf-cs-count {
    margin-left: auto;
    font-size: 10px; color: var(--gf-text-3);
    background: var(--gf-slot-bg);
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut);
    padding: 0 6px; line-height: 14px;
    text-transform: none; letter-spacing: 0;
}
.gf-cs-body { padding-top: 2px; }

/* Combobox: themed searchable select. The flyout is body-appended (so it
   escapes window overflow clipping) and carries the global HUD zoom itself -
   gf_widgets.js divides its viewport coords by the measured scale. */
.gf-combo { position: relative; flex: 1; min-width: 0; display: flex; }
.gf-combo .gf-input { width: 100%; }
.gf-combo-fly {
    position: fixed;
    list-style: none; margin: 0; padding: 3px 0;
    overflow-y: auto;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow);
    zoom: var(--gf-ui-scale, 1);
}
.gf-combo-opt {
    display: flex; align-items: center; gap: 6px;
    padding: 4px 9px; cursor: pointer;
    font-size: 12px; color: var(--gf-text-1);
    white-space: nowrap; overflow: hidden;
}
.gf-combo-opt.gf-active { background: rgba(var(--gf-trim-rgb), 0.14); }
.gf-combo-opt .gf-combo-label { overflow: hidden; text-overflow: ellipsis; }
.gf-combo-opt .gf-combo-ic { flex-shrink: 0; display: inline-flex; }
.gf-combo-note { margin-left: auto; padding-left: 10px; font-size: 10px; color: var(--gf-text-3); flex-shrink: 0; }
.gf-combo-opt.gf-disabled { color: var(--gf-text-4); cursor: default; }
.gf-combo-opt.gf-disabled.gf-active { background: none; }
.gf-combo-opt.gf-disabled .gf-combo-note { color: var(--gf-text-4); }
.gf-combo-group {
    position: sticky; top: 0; z-index: 1;
    padding: 4px 9px 2px;
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--gf-accent);
    background: var(--gf-panel-bg-strong);
    border-bottom: 1px solid var(--gf-border-soft);
    cursor: default;
}
.gf-combo-empty { padding: 5px 9px; font-size: 11px; color: var(--gf-text-3); font-style: italic; }

.gf-step {
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    padding: 9px 10px;
    margin-bottom: 8px;
    background: rgba(0,0,0,0.18);
}
.gf-step-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; }
.gf-step-num { font-size: 11px; color: var(--gf-text-3); font-family: var(--gf-mono); }
.gf-step-del { background: none; border: none; color: var(--gf-text-3); cursor: pointer; font-size: 14px; }
.gf-step-del:hover { color: var(--gf-danger); }
.gf-cond-row { display: flex; gap: 6px; align-items: center; }
.gf-cond-row .gf-select { flex: 1 1 0; }
.gf-cond-row .gf-input { flex: 0 0 60px; }

/* Macro DSL (Phase B): control-flow steps + nested block bodies. */
.gf-step-ctl { border-color: var(--gf-accent, var(--gf-trim)); background: rgba(var(--gf-trim-rgb), 0.08); }
.gf-macro-body { border-left: 2px solid var(--gf-border-soft); margin: 4px 0 4px 6px; padding-left: 8px; }
.gf-macro-blocklabel { display: flex; align-items: center; justify-content: space-between; font-size: 11px; color: var(--gf-text-3); font-family: var(--gf-mono); margin: 6px 0 3px; }
.gf-macro-branch { border-top: 1px dashed var(--gf-border-soft); padding-top: 4px; margin-top: 4px; }
.gf-macro-validate { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.gf-macro-valstatus { font-size: 12px; font-family: var(--gf-mono); }
.gf-macro-valstatus.ok { color: var(--gf-success, #6fbf73); }
.gf-macro-valstatus.err { color: var(--gf-danger, #d9534f); }

.gf-btn {
    background: var(--gf-face);
    border: 1px solid var(--gf-border-accent);
    color: var(--gf-accent-hot);
    clip-path: var(--gf-cut-sm);
    padding: 6px 12px;
    cursor: pointer;
    font-size: 12px;
    font-family: inherit;
    transition: background 0.12s, transform 0.06s;
}
.gf-btn:hover { background: var(--gf-face-hover); }
.gf-btn:active { transform: translateY(1px); }
.gf-btn-sm { padding: 4px 9px; font-size: 11px; }
.gf-btn-danger { color: var(--gf-danger); border-color: var(--gf-danger-deep); background: rgba(224,82,74,0.12); }
.gf-btn-ghost { background: rgba(var(--gf-bone-rgb), 0.05); border-color: var(--gf-border); color: var(--gf-text-2); }
/* The filled affirmative. gameConfirm/gamePrompt used to hand-roll this as an
   inline ash-gold fill; it is a house variant now. */
.gf-btn-primary {
    background: var(--gf-trim);
    border-color: var(--gf-trim-bright);
    color: var(--gf-void);
    font-weight: 600;
}
.gf-btn-primary:hover { background: var(--gf-trim-bright); }
.gf-macro-toolbar { display: flex; gap: 7px; padding: 9px; border-top: 1px solid var(--gf-border-soft); flex-shrink: 0; flex-wrap: wrap; }
.gf-macro-empty { color: var(--gf-text-3); text-align: center; margin-top: 48px; font-style: italic; padding: 0 20px; line-height: 1.5; }

/* ── Macro editor v2 (categories / search / repeat / icon picker / share) ── */
/* Left column: the search box above the scrolling, category-grouped list. */
.gf-macro-listcol {
    display: flex; flex-direction: column;
    width: 190px; flex-shrink: 0; min-height: 0;
    border-right: 1px solid var(--gf-border-soft);
}
.gf-macro-listcol > .gf-macro-list { width: auto; border-right: none; flex: 1; min-height: 0; }
/* flex:0 0 auto (not just flex-shrink:0) overrides the inherited
   `.gf-input { flex: 1 }` — otherwise flex-grow:1 stretches the search field
   down the column, splitting the height with the list below it. */
.gf-macro-search { margin: 8px 8px 6px; flex: 0 0 auto; }
.gf-macro-cat { margin: 0; }
.gf-macro-cat > .gf-cs-head { padding: 6px 8px 4px; border-bottom: none; }
.gf-macro-cat > .gf-cs-body { padding-top: 0; }
.gf-macro-list-rep {
    flex-shrink: 0;
    font-family: var(--gf-mono); font-size: 10px;
    color: var(--gf-accent);
    background: rgba(var(--gf-trim-rgb), 0.14);
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut);
    padding: 0 5px; line-height: 14px;
}
.gf-macro-list-icon .gf-ic { width: 15px; height: 15px; color: inherit; }
/* A spell/effect combobox squeezed into a condition row. */
.gf-cond-row .gf-combo { flex: 1 1 90px; min-width: 0; }
.gf-cond-row .gf-combo .gf-input { flex: 1; width: 100%; }
.gf-macro-valstatus.pend { color: var(--gf-text-3); }
.gf-macro-dlg-note { font-size: 11px; color: var(--gf-text-3); line-height: 1.4; margin-bottom: 8px; }

/* Repeat ×N stepper */
.gf-rep-step { display: flex; align-items: center; gap: 6px; }
.gf-rep-step .gf-btn { width: 24px; padding: 4px 0; text-align: center; }
.gf-rep-val { min-width: 34px; text-align: center; font-family: var(--gf-mono); font-size: 12px; }

/* Icon picker popover (body-appended, carries the HUD zoom itself) */
.gf-iconpick-pop {
    position: fixed;
    width: 252px;
    padding: 10px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow);
    zoom: var(--gf-ui-scale, 1);
}
.gf-iconpick-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin: 8px 0 4px; }
.gf-iconpick-cell {
    height: 30px; clip-path: var(--gf-cut-sm);
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    color: rgba(0,0,0,0.65); font-size: 15px;
    border: 1px solid transparent;
}
.gf-iconpick-cell:hover { border-color: var(--gf-border-accent); }
.gf-iconpick-cell.gf-active { outline: 2px solid var(--gf-accent); }
.gf-iconpick-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.gf-iconpick-row label { width: 60px; flex-shrink: 0; color: var(--gf-text-2); font-size: 11px; }

/* Share ▾ menu (small anchored popover over the toolbar) */
.gf-share-menu {
    position: fixed;
    min-width: 172px;
    padding: 4px 0;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow);
    zoom: var(--gf-ui-scale, 1);
}
.gf-share-menu-item { padding: 6px 12px; font-size: 12px; color: var(--gf-text-1); cursor: pointer; white-space: nowrap; }
.gf-share-menu-item:hover { background: rgba(var(--gf-trim-rgb), 0.14); color: var(--gf-accent-hot); }

/* ── Assign picker (hotbar slot "Assign…" - assign_picker.js) ──
   Anchored pick-anything panel: search + collapsible Spells / Active skills /
   Macros / Commands. Body-appended and carries the HUD zoom itself (the
   popover trick); becomes a bottom sheet under mobile controls (see the
   mobile block) where tap-assign is the ONE assignment path. */
.gf-assign-pop {
    position: fixed;
    z-index: var(--gf-z-popover);
    width: 300px;
    max-height: min(440px, 72vh);
    display: flex; flex-direction: column;
    padding: 8px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow);
    color: var(--gf-text-1);
    zoom: var(--gf-ui-scale, 1);
}
.gf-assign-search { flex: 0 0 auto; margin-bottom: 6px; }
.gf-assign-body { flex: 1; min-height: 0; overflow-y: auto; }
.gf-assign-body .gf-cs { margin: 8px 0 2px; }
.gf-assign-body .gf-cs:first-child { margin-top: 0; }
/* School sub-headers inside the Spells section (inline color = school hue). */
.gf-assign-sub {
    display: flex; align-items: center; gap: 5px;
    margin: 6px 0 2px;
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--gf-text-3);
}
.gf-assign-row {
    display: flex; align-items: center; gap: 7px;
    padding: 4px 6px;
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
    font-size: 12px; color: var(--gf-text-1);
}
.gf-assign-row:hover { background: var(--gf-slot-bg-hover); }
.gf-assign-row.gf-disabled { opacity: 0.45; cursor: default; }
.gf-assign-row.gf-disabled:hover { background: none; }
.gf-assign-ic {
    width: 18px; height: 18px; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 700;
    color: var(--gf-accent);
}
.gf-assign-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gf-assign-note { flex: 0 0 auto; padding-left: 8px; font-size: 10px; color: var(--gf-text-3); }
/* Search filter class-hides rows / empty sub-headers / empty sections. */
.gf-assign-pop .gf-hidden { display: none; }


/* ── Modal dialogs - gameConfirm / gamePrompt / gameAlert / choicePopup ──────
   ONE family for every blocking dialog in the game. Two things were wrong here:

   confirm_dialog.js was 100% inline style.cssText with its own color literals,
   so the most-seen modal in the game could not be restyled by touching CSS.

   choicePopup's .gf-choice-* lived only in studio.css - so housing, map_view
   and gate_picker each opened a dialog whose appearance depended on the Map
   Studio stylesheet happening to be loaded. It is here now, as an alias.

   The buttons are the house .gf-btn. A dialog that spells its own button style
   is how the twelve parallel button families started. */
.gf-dialog-overlay,
.gf-choice-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(6,4,2,0.72);
}
/* Same two-layer plate as .game-window, and for the same reason: a real border
   is cut away on the four diagonals by clip-path, which leaves the corners that
   define the shape with no outline at all. */
.gf-dialog,
.gf-choice-panel {
    position: relative;
    width: min(92vw, 360px);
    max-height: 70vh;
    overflow-y: auto;
    background: var(--gf-trim-dim);
    clip-path: var(--gf-cut);
    padding: 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    color: var(--gf-text-2);
    font-size: 12px;
    filter: var(--gf-drop-strong);
}
.gf-dialog::before,
.gf-choice-panel::before {
    content: '';
    position: absolute;
    inset: var(--gf-trim-w);
    clip-path: var(--gf-cut);
    background: var(--gf-panel-bg-strong);
    box-shadow: var(--gf-bevel);
    pointer-events: none;
}
.gf-dialog > *,
.gf-choice-panel > * { position: relative; z-index: 1; }
.gf-dialog-wide,
.gf-choice-panel { width: min(92vw, 400px); }
.gf-dialog-title,
.gf-choice-title {
    font-family: var(--gf-font-display);
    font-weight: 600;
    font-size: 14px;
    color: var(--gf-trim-bright);
    letter-spacing: 0.02em;
}
.gf-dialog-body,
.gf-choice-body { line-height: 1.5; color: var(--gf-text-2); }
.gf-dialog-input { width: 100%; font-size: 13px; }
.gf-dialog-err { color: var(--gf-ember); min-height: 1em; font-size: 11px; }
/* The seam again: the button row sits on its own plate below a lit joint. */
.gf-dialog-row {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 2px;
    padding-top: 10px;
    border-top: var(--gf-seam-w) solid var(--gf-seam);
    box-shadow: 0 -1px 6px -1px var(--gf-seam-glow);
}
/* choicePopup's rows are buttons, stacked and left-aligned. */
.gf-choice-btn {
    text-align: left;
    padding: 7px 10px;
    clip-path: var(--gf-cut-sm);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    border: 1px solid var(--gf-trim-dim);
    background: var(--gf-plate-raised);
    color: var(--gf-text-2);
    transition: background 0.12s, border-color 0.12s;
}
.gf-choice-btn:hover:not(:disabled) { background: rgba(var(--gf-trim-rgb), 0.22); border-color: var(--gf-trim); }
.gf-choice-btn:disabled { opacity: 0.45; cursor: default; }
.gf-choice-cancel {
    align-self: flex-end;
    padding: 5px 12px;
    clip-path: var(--gf-cut-sm);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    border: 1px solid var(--gf-border);
    background: var(--gf-plate-raised);
    color: var(--gf-text-3);
}

/* Modal dialogs (template gallery / transfer / import preview / share code) */
.gf-mdialog-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.55);
    display: flex; align-items: center; justify-content: center;
}
.gf-mdialog {
    width: min(460px, calc(100vw - 40px));
    max-height: min(580px, calc(100vh - 60px));
    display: flex; flex-direction: column;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    box-shadow: var(--gf-shadow);
    color: var(--gf-text-1);
    zoom: var(--gf-ui-scale, 1);
}
.gf-mdialog-head {
    display: flex; align-items: center;
    padding: 10px 14px;
    border-bottom: 1px solid var(--gf-border-soft);
    color: var(--gf-accent);
    font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
}
.gf-mdialog-close { margin-left: auto; background: none; border: none; color: var(--gf-text-3); cursor: pointer; font-size: 14px; }
.gf-mdialog-close:hover { color: var(--gf-danger); }
.gf-mdialog-body { padding: 12px 14px; overflow-y: auto; min-height: 0; font-size: 12px; }
.gf-mdialog-foot { display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; border-top: 1px solid var(--gf-border-soft); }

/* Template gallery cards */
.gf-tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; }
.gf-tpl-card {
    display: flex; gap: 9px; align-items: flex-start;
    padding: 9px;
    background: var(--gf-slot-bg);
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.gf-tpl-card:hover { border-color: var(--gf-border-accent); background: var(--gf-slot-bg-hover); }
.gf-tpl-glyph {
    width: 28px; height: 28px; clip-path: var(--gf-cut-sm); flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    color: rgba(0,0,0,0.65); font-size: 15px;
}
.gf-tpl-name { font-weight: 600; font-size: 12px; }
.gf-tpl-desc { font-size: 11px; color: var(--gf-text-3); line-height: 1.35; margin-top: 2px; }

/* Cross-character transfer dialog */
.gf-xfer-dir { display: flex; gap: 6px; margin-bottom: 10px; }
.gf-xfer-dir .gf-btn.gf-active { border-color: var(--gf-accent); color: var(--gf-accent-hot); }
.gf-xfer-char {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 9px; margin-bottom: 5px;
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.gf-xfer-char:hover { background: var(--gf-slot-bg-hover); }
.gf-xfer-char.gf-active { border-color: var(--gf-accent); background: rgba(var(--gf-trim-rgb), 0.10); }
.gf-xfer-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--gf-text-4); flex-shrink: 0; }
.gf-xfer-dot.gf-online { background: var(--gf-success); }
.gf-xfer-meta { margin-left: auto; padding-left: 10px; font-size: 10px; color: var(--gf-text-3); flex-shrink: 0; }
.gf-xfer-all { display: flex; align-items: center; gap: 6px; font-size: 12px; margin-top: 8px; }
.gf-xfer-picklist {
    max-height: 150px; overflow-y: auto;
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    padding: 4px 8px; margin-top: 6px;
}
.gf-xfer-picklist label { display: flex; gap: 6px; align-items: center; font-size: 12px; padding: 2px 0; cursor: pointer; }

/* Transient toast (share/transfer/import results) */
.gf-macro-toast {
    position: fixed; left: 50%; bottom: 90px;
    transform: translateX(-50%);
    max-width: 70vw;
    padding: 8px 14px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow);
    color: var(--gf-text-1); font-size: 12px;
    pointer-events: none;
    zoom: var(--gf-ui-scale, 1);
    animation: gfMacroToast 3.8s ease forwards;
}
.gf-macro-toast.gf-err { border-color: var(--gf-danger); }
@keyframes gfMacroToast {
    0% { opacity: 0; margin-bottom: -6px; }
    8% { opacity: 1; margin-bottom: 0; }
    85% { opacity: 1; }
    100% { opacity: 0; }
}

/* ── Ability picker grid (drag spells/skills/items onto a bar) ── */
.gf-picker { display: flex; flex-direction: column; height: 100%; }
.gf-picker-tabs { display: flex; gap: 2px; padding: 6px 8px 0; flex-shrink: 0; border-bottom: 1px solid var(--gf-border-soft); }
.gf-picker-tab {
    padding: 6px 12px; cursor: pointer; font-size: 12px; color: var(--gf-text-3);
    border: 1px solid transparent; border-bottom: none; clip-path: var(--gf-cut-top);
}
.gf-picker-tab.gf-active { color: var(--gf-accent); background: var(--gf-slot-bg); border-color: var(--gf-border-soft); }
/* When the macro editor is embedded in the Options window, its own
   Macros/Abilities sub-tab bar is replaced by the Options top-level tabs. */
.gf-macro-editor.gf-embedded > .gf-picker-tabs { display: none; }
/* flex:0 0 auto overrides the inherited `.gf-input { flex:1 }` so the search
   field stays one line tall instead of stretching down the column flex. */
.gf-picker-search { margin: 8px; flex: 0 0 auto; }
.gf-picker-grid { flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(120px,1fr)); gap: 6px; padding: 0 8px 8px; align-content: start; }
.gf-picker-cell {
    display: flex; align-items: center; gap: 7px;
    padding: 6px 8px;
    border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm);
    cursor: grab; background: rgba(0,0,0,0.18);
}
.gf-picker-cell:hover { border-color: var(--gf-border-accent); background: var(--gf-slot-bg-hover); }
.gf-picker-cell-icon { width: 24px; height: 24px; clip-path: var(--gf-cut-sm); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; color: rgba(0,0,0,0.6); }
.gf-picker-cell-name { flex: 1; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gf-picker-cell-meta { font-size: 10px; color: var(--gf-text-3); }

/* Floating drag-ghost used by hotbar/picker assignment. */
.gf-drag-ghost {
    position: fixed; z-index: var(--gf-z-sheet-chrome); pointer-events: none;
    width: 40px; height: 40px; clip-path: var(--gf-cut-sm);
    border: 1px solid var(--gf-accent); background: var(--gf-panel-bg-strong);
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; color: var(--gf-accent-hot); font-size: 16px;
    box-shadow: var(--gf-shadow);
}

/* ═══════════════════════════════════════════════════════════════
   Enriched resource bars (HP / Stamina / Mana)
   Damage-trail sub-fill + regen shimmer + icon/shape coding.
   ═══════════════════════════════════════════════════════════════ */
.stat-bar-row .stat-bar-track { position: relative; }
/* Delayed "damage trail" - a second fill that lags behind on loss. */
.stat-bar-trail {
    position: absolute;
    left: 0; top: 0; height: 100%;
    width: 0%;
    /* shape inherited from the clipped parent */
    background: rgba(var(--gf-bone-rgb), 0.28);
    transition: width 0.7s cubic-bezier(.4,0,.2,1) 0.12s;
    z-index: 1;
    pointer-events: none;
}
.stat-bar-row .stat-bar-fill { position: relative; z-index: 2; }
/* Subtle moving shimmer to read as "alive / regenerating". */
.stat-bar-row .stat-bar-fill::after {
    content: '';
    position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(var(--gf-bone-rgb), 0.22), transparent);
    background-size: 200% 100%;
    animation: gfShimmer 2.6s linear infinite;
    opacity: 0.0;
    transition: opacity 0.3s;
}
.stat-bar-row.gf-regen .stat-bar-fill::after { opacity: 1; }
@keyframes gfShimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
/* Mana shimmer quickens with how much the Grid is doing here (--gf-grid 0..1). */
.stat-bar-row.gf-mana .stat-bar-fill::after { animation-duration: calc(3.2s - var(--gf-grid, 0) * 1.8s); }
/* Pools are identified by fill COLOR alone - no text label or glyph. */
.stat-bar-row.gf-low .stat-bar-fill { animation: lowHpPulse 1s ease infinite; }

/* ═══════════════════════════════════════════════════════════════
   HUD Edit Mode
   ═══════════════════════════════════════════════════════════════ */
/* The HUD edit banner outranks every surface it arms - including a full-screen
   mobile sheet - but stays under a confirm dialog, because Reset Layout asks. */
#gf-hud-edit-banner {
    position: fixed;
    top: 14px; left: 50%;
    transform: translateX(-50%);
    z-index: calc(var(--gf-z-modal) - 1);
    display: none;
    align-items: center;
    gap: 12px;
    padding: 8px 16px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    box-shadow: var(--gf-shadow-strong);
    color: var(--gf-text-1);
    font-size: 13px;
}
body.gf-hud-edit #gf-hud-edit-banner { display: flex; }
#gf-hud-edit-banner .gf-eb-title { color: var(--gf-accent); font-weight: 700; }

/* ── Pet Training window ── */
.pet-training-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
    overflow: hidden;
}
.pt-body {
    flex: 1;
    overflow-y: auto;
    padding: 8px 12px;
}
.pt-empty { padding: 20px; text-align: center; opacity: 0.7; }
.pt-header {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    border-bottom: 1px solid #4a3a26;
    padding-bottom: 6px;
    margin-bottom: 8px;
}
.pt-name { font-size: 16px; font-weight: 700; }
.pt-level { opacity: 0.85; font-size: 13px; }
.pt-bond-badge {
    background: #2a6aa0; color: #cfe8ff;
    clip-path: var(--gf-cut-sm); padding: 1px 6px; font-size: 11px;
}
.pt-xp { margin-bottom: 8px; }
.pt-xp-bar {
    height: 8px; background: rgba(0,0,0,0.4);
    border: 1px solid #4a3a26; clip-path: var(--gf-cut-sm); overflow: hidden;
}
.pt-xp-fill { height: 100%; background: linear-gradient(90deg, #6aa84f, #b7e892); }
.pt-xp-label { font-size: 11px; opacity: 0.7; margin-top: 2px; }
.pt-pending { margin-bottom: 8px; }
.pt-pend-bar {
    height: 6px; background: rgba(0,0,0,0.4);
    border: 1px solid #6a5526; clip-path: var(--gf-cut-sm); overflow: hidden;
}
.pt-pend-fill {
    height: 100%;
    background: repeating-linear-gradient(45deg, #b8893a, #b8893a 6px, #8a6628 6px, #8a6628 12px);
}
.pt-pend-label { font-size: 11px; color: var(--gf-trim-bright); opacity: 0.85; margin-top: 2px; }
.pt-points { font-weight: 700; color: #f0d890; margin: 6px 0; }
.pt-section {
    margin: 10px 0 4px; font-size: 12px; text-transform: uppercase;
    letter-spacing: 0.06em; color: #b89a64; border-bottom: 1px solid #3a2e1e;
}
.pt-row {
    display: flex; align-items: center; gap: 8px; padding: 3px 0;
}
.pt-row-label { flex: 1; }
.pt-row-val { min-width: 64px; text-align: right; opacity: 0.9; }
.pt-overcap { color: #f0c040; font-weight: 700; }
.pt-btn {
    background: #3a2e1e; color: var(--gf-bone); border: 1px solid #5a4632;
    clip-path: var(--gf-cut-sm); padding: 2px 8px; cursor: pointer; font-family: inherit;
    min-width: 36px;
}
.pt-btn:hover:not(:disabled) { background: #5a4632; }
.pt-btn:disabled { opacity: 0.35; cursor: default; }
/* up/down/locked toggle button on a pet's skills + stats. */
.pt-toggle { min-width: 26px; font-size: 11px; padding: 2px 5px; }
.pt-toggle-up { color: #7fe084; border-color: #3d7a40; }
.pt-toggle-down { color: #e08a7f; border-color: #7a4036; }
.pt-toggle-locked { color: #c0b070; border-color: #6a5e32; }
/* ── Hireling Sheet (reuses the .pt-* pet-training chrome, no point spend) ── */
.hs-order { margin-left: auto; font-size: 11px; opacity: 0.7; text-transform: capitalize; }
.hs-tabs { margin-top: 6px; }
.hs-pools { display: flex; flex-direction: column; gap: 3px; margin-bottom: 4px; }
.hs-pool { display: flex; align-items: center; gap: 8px; font-size: 11px; }
.hs-pool-label { width: 52px; opacity: 0.8; }
.hs-pool-track {
    flex: 1; height: 7px; background: rgba(0,0,0,0.4);
    border: 1px solid #4a3a26; clip-path: var(--gf-cut-sm); overflow: hidden;
}
.hs-pool-fill { display: block; height: 100%; }
.hs-hp .hs-pool-fill { background: linear-gradient(90deg, #8a2e2a, #d0605a); }
.hs-sta .hs-pool-fill { background: linear-gradient(90deg, #8a6a28, var(--gf-trim-bright)); }
.hs-mana .hs-pool-fill { background: linear-gradient(90deg, #2a4a8a, #6a90d8); }
.hs-pool-val { min-width: 72px; text-align: right; font-family: var(--gf-mono); }
.hs-rep-label { color: var(--gf-trim-bright); font-size: 12px; }
.pt-bond { font-size: 13px; }
.pt-bond-ok { color: #8fd0ff; }
.pt-hint { font-size: 11px; opacity: 0.65; margin-top: 3px; }
.pt-reset { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
.pt-reset-confirm { color: #ffb0a0; border-color: #7a4036; background: #4a2620; }
.pt-reset-confirm:hover:not(:disabled) { background: #6a342a; }
.pt-note {
    margin-top: 10px; font-size: 11px; font-style: italic;
    opacity: 0.55; border-top: 1px solid #3a2e1e; padding-top: 6px;
}
/* Species subtitle under the header - the creature "Muninn" actually is. */
.pt-species {
    font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--gf-trim); margin: -4px 0 6px 23px;
}
/* Total skill-cap bar (pet_training.skill_cap_total budget). */
.pt-capbar { margin: 2px 0 6px; }
.pt-capbar-track {
    height: 7px; background: rgba(0,0,0,0.4);
    border: 1px solid #4a3a26; clip-path: var(--gf-cut-sm); overflow: hidden;
}
.pt-capbar-fill {
    height: 100%; background: linear-gradient(90deg, #8a6628, var(--gf-trim-bright));
    transition: width 0.4s ease;
}
.pt-capbar-full .pt-capbar-fill { background: linear-gradient(90deg, #8a4628, #e0906a); }
.pt-capbar-label { font-size: 11px; color: var(--gf-trim-bright); opacity: 0.85; margin-top: 2px; }
/* Skill filter chips: Learned · Training · All. */
.pt-chips { display: flex; gap: 5px; margin: 4px 0 6px; }
.pt-chip {
    background: rgba(0,0,0,0.35); color: var(--gf-accent-2); border: 1px solid #4a3a26;
    clip-path: var(--gf-cut); padding: 1px 9px; font-size: 10px; cursor: pointer;
    font-family: inherit; letter-spacing: 0.04em;
}
.pt-chip:hover { border-color: #6a5632; color: var(--gf-bone); }
.pt-chip.active { background: #3a2e1e; color: #f0d890; border-color: #8a6e3a; }
/* Collapsible skill-category group headers. */
.pt-cat-head {
    display: flex; align-items: center; gap: 6px; cursor: pointer;
    margin-top: 5px; padding: 2px 0; font-size: 11px; color: #c8ae78;
    user-select: none;
}
.pt-cat-head:hover { color: #f0d890; }
.pt-cat-caret { width: 10px; font-size: 9px; opacity: 0.7; }
.pt-cat-name { letter-spacing: 0.05em; text-transform: uppercase; }
.pt-cat-count { opacity: 0.55; font-size: 10px; }
/* Per-skill/stat progress bars. */
.pt-skill-row .pt-row-label { flex: 0 1 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pt-skill-bar-track {
    flex: 1; height: 6px; min-width: 40px; background: rgba(0,0,0,0.4);
    border: 1px solid #4a3a26; clip-path: var(--gf-cut-sm); overflow: hidden;
    display: inline-block;
}
.pt-skill-bar-fill {
    display: block; height: 100%;
    background: linear-gradient(90deg, #8a6628, var(--gf-trim-bright));
    transition: width 0.4s ease;
}
.pt-skill-bar-overcap { background: linear-gradient(90deg, #b8893a, #f0c040); }
.pt-stat-bar { flex: 0 1 60px; }
.pt-skill-row .pt-row-val { min-width: 76px; font-size: 12px; }
/* Resist breakdown: species base / +trained / +genetic chips → effective %. */
.pt-res-row { flex-wrap: nowrap; gap: 6px; }
.pt-res-row .pt-row-label { flex: 0 0 65px; }
.pt-res-chips { display: flex; gap: 4px; align-items: center; flex: 1; justify-content: flex-end; }
.pt-res-chip {
    font-size: 10px; clip-path: var(--gf-cut-sm); padding: 0 5px;
    border: 1px solid #4a3a26; background: rgba(0,0,0,0.3); color: var(--gf-accent-2);
}
.pt-res-base { color: #c8bda0; }
.pt-res-train { color: #f0d890; border-color: #8a6e3a; }
.pt-res-gen { color: #9fe0a4; border-color: #3d7a40; }
.pt-res-weak { color: #e08a7f !important; border-color: #7a4036; }
.pt-res-eff { min-width: 36px; text-align: right; font-weight: 700; color: var(--gf-bone); }
.pt-res-row .pt-btn, .pt-regen-row .pt-btn { min-width: 32px; padding: 2px 4px; font-size: 11px; }

/* Regeneration row & rate badge. */
.pt-regen-row .pt-stat-bar { flex: 0 1 50px; }
.pt-regen-rate { font-size: 11px; color: #8fd0ff; min-width: 44px; text-align: right; opacity: 0.9; }
/* Specials as cards: name + tier pips + surfaced description. */
.pt-ability-card {
    border: 1px solid #3a2e1e; clip-path: var(--gf-cut-sm);
    background: rgba(0,0,0,0.25); padding: 5px 8px; margin: 4px 0;
}
.pt-ability-learned { border-color: #6a5632; background: rgba(58,46,30,0.35); }
.pt-ability-head { display: flex; align-items: center; gap: 8px; }
.pt-ability-name { flex: 1; font-weight: 700; font-size: 13px; }
.pt-ability-desc { font-size: 11px; opacity: 0.7; margin-top: 3px; line-height: 1.35; }
.pt-pips { display: flex; gap: 3px; align-items: center; }
.pt-pip {
    width: 7px; height: 7px; border-radius: 50%;
    border: 1px solid #6a5632; background: rgba(0,0,0,0.4);
}
.pt-pip.filled { background: var(--gf-trim-bright); border-color: var(--gf-trim-bright); box-shadow: 0 0 3px rgba(217,182,94,0.6); }

/* ═══════════════════════════════════════════════════════════════
   Staff Welcome / Code of Conduct (client/js/ui/staff_welcome.js)
   Full-screen modal. --ribbon-ROLE is set per-role on the card; --ribbon-accent
   stays the invariant cyan, because it is what MEANS "staff-only".
   ═══════════════════════════════════════════════════════════════ */
.staff-welcome-overlay {
    position: fixed; inset: 0;
    z-index: var(--gf-z-system);
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    background: rgba(6, 6, 10, 0.78);
}
.staff-welcome-card {
    --ribbon-accent: var(--gf-grid-cyan);
    --ribbon-role:   var(--gf-accent);
    display: flex; flex-direction: column;
    width: min(720px, 100%);
    max-height: min(86vh, 900px);
    background: var(--gf-panel-bg-strong);
    border: 1px solid color-mix(in srgb, var(--ribbon-accent) 55%, var(--gf-border));
    border-top: 3px solid var(--ribbon-accent);
    clip-path: var(--gf-cut);
    box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6),
                0 0 26px color-mix(in srgb, var(--ribbon-accent) 22%, transparent);
    outline: none;
    overflow: hidden;
}
.staff-welcome-header {
    padding: 20px 26px 14px;
    border-bottom: 1px solid var(--gf-border);
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--ribbon-accent) 12%, transparent), transparent);
}
.staff-welcome-title {
    margin: 0; font-size: 22px; font-weight: 700;
    color: var(--gf-text-1);
    font-family: var(--gf-font-display);
}
.staff-welcome-subtitle {
    margin-top: 6px; font-size: 13px; color: var(--gf-text-2);
}
.staff-welcome-subtitle strong { color: var(--ribbon-accent); }
.staff-welcome-body {
    flex: 1; overflow-y: auto;
    padding: 18px 26px;
}
.staff-welcome-section { margin-top: 6px; }
.staff-welcome-section h2 {
    margin: 0 0 6px; font-size: 13px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ribbon-accent);
}
.staff-welcome-brief { margin: 0 0 14px; }
.staff-welcome-brief h3 {
    margin: 0 0 2px; font-size: 15px; color: var(--gf-text-1);
}
.staff-welcome-coc {
    margin-top: 16px; padding-top: 14px;
    border-top: 1px solid var(--gf-border);
}
.staff-welcome-prose { font-size: 13.5px; line-height: 1.55; color: var(--gf-text-1); }
.staff-welcome-prose p { margin: 0 0 8px; }
.staff-welcome-prose ol {
    margin: 4px 0 14px;
    padding-left: 2.3em;
}
.staff-welcome-prose li {
    padding-left: 0.25em;
}
.staff-welcome-prose li + li {
    margin-top: 9px;
}
.staff-welcome-prose strong { color: var(--gf-accent-hot, var(--gf-accent)); }
.staff-welcome-prose code {
    font-family: var(--gf-font-mono); font-size: 0.9em;
    padding: 1px 5px; clip-path: var(--gf-cut-sm);
    background: rgba(var(--gf-bone-rgb), 0.08); color: var(--gf-text-1);
}
.staff-welcome-footer {
    padding: 14px 26px 18px;
    border-top: 1px solid var(--gf-border);
    background: var(--gf-panel-bg);
    display: flex; flex-direction: column; gap: 12px;
}
.staff-welcome-agree {
    display: flex; align-items: center; gap: 10px;
    font-size: 13.5px; color: var(--gf-text-1); cursor: pointer; user-select: none;
}
.staff-welcome-agree input { width: 17px; height: 17px; cursor: pointer; accent-color: var(--ribbon-accent); }
.staff-welcome-buttons { display: flex; justify-content: flex-end; gap: 10px; }
.staff-welcome-btn {
    padding: 9px 20px; clip-path: var(--gf-cut); cursor: pointer;
    font-size: 13.5px; font-weight: 600; border: 1px solid var(--gf-border);
    background: rgba(var(--gf-bone-rgb), 0.06); color: var(--gf-text-1);
    transition: background 0.12s, border-color 0.12s, opacity 0.12s;
}
.staff-welcome-btn-ghost:hover { background: rgba(var(--gf-bone-rgb), 0.12); }
.staff-welcome-btn-primary {
    border-color: var(--ribbon-accent);
    background: color-mix(in srgb, var(--ribbon-accent) 26%, transparent);
    color: #fff;
}
.staff-welcome-btn-primary:hover:not(:disabled) {
    background: color-mix(in srgb, var(--ribbon-accent) 42%, transparent);
}
.staff-welcome-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ── Build Mode (in-world staff editor) - gold trim over glass ──────────────
   The .bm-* namespace styles the Build Mode window (icon toolbar + contextual
   panel + layers), the bottom coach bar, and the CSS-only delayed tooltips.
   Icons are game-icons.net masks via .gf-ic (see game_icons.js).

   NOTE: .bm-* is the SHARED AUTHORING KIT, not Build-Mode-private. Map Studio
   (.ms-* below) reuses .bm-tool / .bm-key / .bm-section / .bm-gensec / .bm-btn
   / .bm-chip so the two staff surfaces look and behave identically. Changing a
   rule here changes both - which is the point. */

.bm-body { display: flex; flex-direction: column; gap: 10px; padding: 10px; font-size: 12px; color: var(--gf-text-1); }
.bm-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.bm-count { color: var(--gf-text-3); font-size: 11px; }

/* Icon tool tiles - one per tool, hotkey badge in the corner. */
.bm-toolbar { display: grid; grid-template-columns: repeat(auto-fill, 44px); gap: 6px; }
.bm-tool {
    position: relative; width: 44px; height: 44px; clip-path: var(--gf-cut); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    background: rgba(var(--gf-bone-rgb), 0.045);
    border: 1px solid var(--gf-border-accent);
    color: var(--gf-accent);
    transition: background .12s, border-color .12s, box-shadow .12s, transform .08s;
}
.bm-tool:hover:not(:disabled) { background: rgba(var(--gf-trim-rgb), 0.14); transform: translateY(-1px); }
.bm-tool.active {
    background: rgba(var(--gf-trim-rgb), 0.22);
    border-color: var(--gf-trim);
    box-shadow: 0 0 10px rgba(var(--gf-trim-rgb), 0.35), inset 0 0 6px rgba(var(--gf-trim-rgb), 0.18);
    color: #ffe9b8;
}
.bm-tool:disabled { opacity: .35; cursor: default; }
.bm-tool .gf-ic { width: 22px; height: 22px; }
.bm-key {
    position: absolute; right: 2px; bottom: 1px; font-size: 9px; line-height: 1;
    color: var(--gf-text-3); font-family: var(--gf-mono);
    background: rgba(0,0,0,0.55); clip-path: var(--gf-cut-sm); padding: 1px 3px; pointer-events: none;
}
.bm-tool.active .bm-key { color: #ffe9b8; }

/* Small labeled action buttons (Undo/Redo/Free Cam/Snap + form actions). */
.bm-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.bm-btn {
    background: rgba(20,16,12,0.8); border: 1px solid rgba(var(--gf-trim-rgb), 0.45);
    color: var(--gf-text-1); clip-path: var(--gf-cut-sm); padding: 4px 9px; font-size: 11px; cursor: pointer;
    transition: background .12s, border-color .12s, color .12s;
}
.bm-btn:hover:not(:disabled) { background: rgba(var(--gf-trim-rgb), 0.14); }
.bm-btn.active { background: rgba(var(--gf-trim-rgb), 0.35); border-color: rgba(226,180,90,0.9); color: #fff0d0; }
.bm-btn.danger { border-color: rgba(255,90,90,0.6); color: #ff8a8a; }
.bm-btn.danger:hover:not(:disabled) { background: rgba(255,90,90,0.12); }
.bm-btn:disabled { opacity: .4; cursor: default; }
.bm-badge {
    display: inline-block; min-width: 14px; margin-left: 5px; padding: 0 4px; clip-path: var(--gf-cut);
    background: rgba(var(--gf-trim-rgb), 0.25); color: #ffe9b8; font-size: 9px; font-family: var(--gf-mono);
}

.bm-section {
    font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
    font-size: 10px; color: var(--gf-text-3);
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.3); padding-bottom: 3px;
    display: flex; align-items: center; gap: 6px;
}
.bm-section .gf-ic { width: 11px; height: 11px; }

/* Onboarding / hint card. */
.bm-card {
    border: 1px solid var(--gf-border-accent); clip-path: var(--gf-cut); padding: 9px 10px;
    background: rgba(var(--gf-trim-rgb), 0.07); line-height: 1.55; font-size: 11.5px;
}
.bm-card b { color: #ffe9b8; }

.bm-form { display: flex; flex-direction: column; gap: 4px; }

/* Terrain op tiles - colored dot telegraphs the op's preview tint. */
.bm-ops { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; }
.bm-op { position: relative; padding-left: 16px; text-align: left; }
.bm-op::before {
    content: ''; position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--op-color, var(--gf-accent));
    box-shadow: 0 0 5px var(--op-color, var(--gf-accent));
}

/* Slider rows (brush radius / strength). */
.bm-slider-row { display: flex; align-items: center; gap: 8px; }
.bm-slider-row label { flex: 0 0 62px; color: var(--gf-text-3); font-size: 11px; }
.bm-slider-row input[type=range] { flex: 1; accent-color: var(--gf-trim); height: 14px; cursor: pointer; min-width: 0; }
.bm-slider-val { flex: 0 0 26px; text-align: right; font-family: var(--gf-mono); color: #ffe9b8; font-size: 11px; }

/* World Builder pinned action bar - every world verb visible without scrolling. */
.bm-worldbar {
    position: sticky; top: 0; z-index: 4;
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    padding: 7px 0 6px;
    background: rgba(16,12,9,0.97);
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.3);
}

/* World Builder two-pane layout: a STICKY preview/dashboard aside that stays
   put while the form column scrolls - so you always see the map while tuning.
   `.two` (side-by-side) is toggled by JS when the window is wide enough;
   otherwise it collapses to one column with the preview pinned as a header. */
.bm-world-layout { display: grid; grid-template-columns: 1fr; gap: 10px; align-items: start; }
.bm-world-layout.two { grid-template-columns: minmax(220px, 42%) 1fr; }
.bm-world-aside {
    position: sticky; top: 0; z-index: 3; align-self: start;
    display: flex; flex-direction: column; gap: 5px;
    background: rgba(16,12,9,0.97); padding-bottom: 6px;
}
.bm-world-layout:not(.two) .bm-world-aside {
    border-bottom: 1px solid rgba(var(--gf-trim-rgb), 0.28); margin-bottom: 2px;
}
/* Keep the pinned preview compact in single-column mode so it doesn't eat the
   whole scroll area. */
.bm-world-layout:not(.two) .bm-preview-host { max-width: 320px; margin: 0 auto; width: 100%; }
.bm-world-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* The action bar lives inside the already-sticky aside now - drop its own
   sticky/background so it doesn't double up. */
.bm-world-aside .bm-worldbar {
    position: static; background: transparent; border-bottom: none;
    padding-top: 2px; z-index: auto;
}

/* World Builder gen-param form (grouped sliders / chips / preset cards). */
.bm-genrow { display: flex; align-items: center; gap: 7px; }
.bm-genrow > .bm-genlabel {
    flex: 0 0 118px; color: var(--gf-text-3); font-size: 11px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bm-genrow input[type=range] { flex: 1; accent-color: var(--gf-trim); height: 14px; cursor: pointer; min-width: 0; }
.bm-genrow input[type=number] {
    flex: 0 0 52px; width: 52px; background: rgba(10,8,6,0.85);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.4); color: #e8dcc2; clip-path: var(--gf-cut-sm);
    padding: 1px 4px; font-size: 11px; font-family: var(--gf-mono);
}
.bm-help {
    display: inline-block; margin-left: 4px; opacity: 0.45; cursor: help;
    font-size: 9px; border: 1px solid currentColor; border-radius: 50%;
    width: 11px; height: 11px; line-height: 11px; text-align: center; flex: 0 0 auto;
}
.bm-gensec { margin: 1px 0; border-left: 2px solid rgba(var(--gf-trim-rgb), 0.18); padding-left: 7px; }
.bm-gensec > summary {
    cursor: pointer; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    font-size: 10px; color: var(--gf-text-3); padding: 3px 0; user-select: none;
}
.bm-gensec[open] > summary { color: #ffe9b8; }
.bm-gensec > .bm-gensec-body { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 5px; }
.bm-chiprow { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.bm-chip {
    background: rgba(20,16,12,0.8); border: 1px solid rgba(var(--gf-trim-rgb), 0.35);
    color: var(--gf-text-2); clip-path: var(--gf-cut); padding: 2px 9px; font-size: 10.5px;
    cursor: pointer; transition: background .12s, border-color .12s, color .12s;
}
.bm-chip:hover { background: rgba(var(--gf-trim-rgb), 0.14); }
.bm-chip.active { background: rgba(var(--gf-trim-rgb), 0.35); border-color: rgba(226,180,90,0.9); color: #fff0d0; }
.bm-preset-bar { display: flex; gap: 6px; overflow-x: auto; padding: 2px 0 5px; }
.bm-preset-card {
    flex: 0 0 auto; width: 76px; cursor: pointer; text-align: center;
    border: 1px solid rgba(var(--gf-trim-rgb), 0.3); clip-path: var(--gf-cut); padding: 4px;
    background: rgba(14,11,8,0.65); transition: border-color .12s, background .12s;
}
.bm-preset-card:hover { background: rgba(var(--gf-trim-rgb), 0.1); }
.bm-preset-card.active { border-color: rgba(226,180,90,0.95); background: rgba(var(--gf-trim-rgb), 0.22); }
.bm-preset-card .bm-preset-name {
    display: block; margin-top: 3px; font-size: 9.5px; color: var(--gf-text-2);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bm-hint {
    display: inline-block; margin: 2px 4px 0 0; padding: 2px 8px; clip-path: var(--gf-cut);
    background: rgba(226,180,90,0.14); border: 1px solid rgba(226,180,90,0.45);
    color: #ffd98a; font-size: 10px; line-height: 1.4;
}
.bm-sketch {
    width: 192px; height: 192px; cursor: crosshair; touch-action: none;
    border: 1px solid rgba(var(--gf-trim-rgb), 0.45); clip-path: var(--gf-cut-sm);
    image-rendering: pixelated; align-self: center;
}

/* World Builder terrain preview overlays (WorldPreview component). */
.bm-pv-shimmer {
    position: absolute; left: 0; right: 0; top: 0; height: 3px;
    overflow: hidden; pointer-events: none; clip-path: var(--gf-cut-top);
}
.bm-pv-shimmer::after {
    content: ''; position: absolute; inset: 0; width: 45%;
    background: linear-gradient(90deg, transparent,
        rgba(var(--gf-trim-rgb), 0.85), transparent);
    animation: bm-pv-sweep 1.1s linear infinite;
}
@keyframes bm-pv-sweep {
    from { transform: translateX(-110%); }
    to   { transform: translateX(320%); }
}
.bm-pv-badge {
    position: absolute; right: 5px; bottom: 5px; pointer-events: none;
    padding: 0 6px; clip-path: var(--gf-cut); font-size: 8.5px; letter-spacing: .08em;
    text-transform: uppercase; color: #ffd98a; background: rgba(14,11,8,0.75);
    border: 1px solid rgba(226,180,90,0.4);
}
.bm-pv-scale {
    position: absolute; left: 7px; bottom: 6px; pointer-events: none;
    font-size: 9px; color: #f0e6d0; text-shadow: 0 1px 2px #000;
    border-bottom: 2px solid rgba(240,230,208,0.9); padding-bottom: 1px;
    border-left: 1px solid rgba(240,230,208,0.9);
    border-right: 1px solid rgba(240,230,208,0.9);
    text-align: center; line-height: 1.2; min-width: 24px;
}
.bm-pv-toolbar {
    position: absolute; top: 4px; right: 4px; display: flex; gap: 4px;
}
.bm-pv-toolbtn {
    width: 22px; height: 22px; padding: 0; cursor: pointer;
    background: rgba(16,12,9,0.82); border: 1px solid rgba(var(--gf-trim-rgb), 0.45);
    color: #e8dcc2; clip-path: var(--gf-cut-sm); font-size: 12px; line-height: 20px;
    transition: background .12s, border-color .12s;
}
.bm-pv-toolbtn:hover { background: rgba(var(--gf-trim-rgb), 0.18); border-color: rgba(226,180,90,0.9); }

/* Candidate gallery (World Builder "Explore"): 12 tiles, pin + adopt. */
.bm-gallery { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; }
.bm-gallery-tile {
    position: relative; cursor: pointer; clip-path: var(--gf-cut-sm); padding: 2px;
    border: 1px solid rgba(var(--gf-trim-rgb), 0.25);
    transition: border-color .12s, box-shadow .12s;
}
.bm-gallery-tile:hover { border-color: rgba(226,180,90,0.75); }
.bm-gallery-tile.pinned {
    border-color: rgba(255,209,102,0.95);
    box-shadow: 0 0 7px rgba(255,209,102,0.35);
}
.bm-gallery-tile.adopted { border-color: rgba(57,217,138,0.9); }
.bm-tile-tools {
    position: absolute; top: 3px; right: 3px; display: none; gap: 3px; z-index: 3;
}
.bm-gallery-tile:hover .bm-tile-tools,
.bm-gallery-tile.pinned .bm-tile-tools { display: flex; }
.bm-tile-tools button {
    width: 20px; height: 20px; padding: 0; cursor: pointer; font-size: 10px;
    background: rgba(16,12,9,0.85); border: 1px solid rgba(var(--gf-trim-rgb), 0.5);
    color: #e8dcc2; clip-path: var(--gf-cut-sm); line-height: 18px;
}
.bm-tile-tools button:hover { background: rgba(var(--gf-trim-rgb), 0.2); }
.bm-tile-cap {
    margin-top: 1px; font-size: 9px; opacity: 0.55; text-align: center;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Terrain material swatch grid. */
.bm-swatches { display: grid; grid-template-columns: repeat(auto-fill, 30px); gap: 5px; }
.bm-swatch {
    width: 30px; height: 30px; clip-path: var(--gf-cut-sm); cursor: pointer;
    border: 1px solid rgba(var(--gf-trim-rgb), 0.4);
    transition: box-shadow .1s, transform .08s, border-color .1s;
}
.bm-swatch:hover { transform: translateY(-1px); }
.bm-swatch.active { border-color: #ffe9b8; box-shadow: 0 0 8px rgba(var(--gf-trim-rgb), 0.5); }

/* Segmented toggle (brush shape). */
.bm-seg { display: flex; border: 1px solid rgba(var(--gf-trim-rgb), 0.45); clip-path: var(--gf-cut-sm); overflow: hidden; width: max-content; }
.bm-seg .bm-btn { border: none; /* square */ }
.bm-seg .bm-btn + .bm-btn { border-left: 1px solid rgba(var(--gf-trim-rgb), 0.35); }

/* Layer visibility rows. */
.bm-layer { display: flex; align-items: center; gap: 7px; font-size: 11.5px; cursor: pointer; color: var(--gf-text-1); }
.bm-layer input { accent-color: var(--gf-trim); cursor: pointer; }

/* Coach bar - the persistent "what will my next click do" pill. Shared by any
   armed in-world pick (Build Mode's own modes; the gridgate link picker), so an
   armed cursor always announces itself the same way. */
#bm-coach, #gate-pick-coach {
    position: fixed; top: 54px; left: 50%; transform: translateX(-50%);
    z-index: 64; pointer-events: none; white-space: nowrap;
    padding: 6px 16px; clip-path: var(--gf-cut);
    background: rgba(10,8,6,0.78);
    border: 1px solid var(--gf-border-accent);
    box-shadow: 0 2px 10px rgba(0,0,0,0.5), inset 0 0 12px rgba(var(--gf-trim-rgb), 0.06);
    color: var(--gf-trim); font-size: 12px; font-family: var(--gf-mono);
}
#bm-coach .bm-coach-key, #gate-pick-coach .bm-coach-key {
    display: inline-block; padding: 0 5px; margin: 0 2px; clip-path: var(--gf-cut-sm);
    background: rgba(var(--gf-trim-rgb), 0.18); border: 1px solid rgba(var(--gf-trim-rgb), 0.35);
    color: #ffe9b8; font-size: 10px;
}
#bm-coach.warn { color: #ff8a8a; border-color: rgba(255,90,90,0.5); }

/* Build Mode control tooltips are rendered through the body-level .gf-tooltip
   (see BuildMode._wireTips). A CSS ::after tip is clipped by the window's
   overflow:auto content box - the body-appended element floats free and stays
   legible. */

/* ── Map Studio (full-screen world authoring) ───────────────────────────────
   The .ms-* namespace covers only what is STRUCTURALLY new here - the mode tab
   strip, the tool palette row, the panel sections and the one contextual
   inspector. Everything else reuses the shared authoring kit above (.bm-tool,
   .bm-key, .bm-section, .bm-gensec, .bm-btn, .bm-chip) and the house tab strip
   (.gf-picker-tab), so Map Studio and Build Mode cannot drift.

   Palette note: gold is the active/focus color throughout. Grid cyan is the
   Grid alone (VISUAL_IDENTITY.md) - never use it for a selected tool. */

.gf-map-studio {
    background: var(--gf-panel-bg-strong);
    color: var(--gf-text-1);
    font: 12px/1.4 system-ui, sans-serif;
}
.gf-map-studio input[type=text],
.gf-map-studio input[type=number],
.gf-map-studio select { font-family: inherit; }

/* Mode tabs - each mode reveals only its own tools, layers and sections. */
.ms-tabs { padding: 5px 12px 0; gap: 3px; flex: none; }
.ms-tab {
    display: flex; align-items: center; gap: 6px;
    font-size: 12px; letter-spacing: .02em;
}
.ms-tab .gf-ic { width: 13px; height: 13px; }
.ms-tab:hover { color: var(--gf-accent-hot); }

/* Tool palette: labeled 44px tiles, only this mode's tools. */
.ms-palette {
    display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px;
    flex: none;
}
.ms-tool {
    width: auto; min-width: 44px; height: 44px; padding: 0 8px;
    flex-direction: column; gap: 1px;
}
.ms-tool .gf-ic { width: 17px; height: 17px; }
.ms-tool-glyph { font-size: 15px; line-height: 1; }
.ms-tool-cap {
    font-size: 9px; letter-spacing: .04em; text-transform: uppercase;
    color: var(--gf-text-3); line-height: 1;
}
.ms-tool.active .ms-tool-cap { color: #ffe9b8; }

/* Panel sections (native <details>, so collapse is free). */
.ms-sec { margin: 0; }
.ms-sec-head { cursor: pointer; list-style: none; }
.ms-sec-head::-webkit-details-marker { display: none; }
.ms-sec-head::before {
    content: '▸'; font-size: 9px; color: var(--gf-text-4);
    transition: transform .12s; display: inline-block;
}
.ms-sec[open] > .ms-sec-head::before { transform: rotate(90deg); }
.ms-sec-body { padding-top: 6px; }

/* The ONE contextual inspector - every per-tool editor renders in here. */
.ms-ctx {
    display: none; gap: 6px; flex-wrap: wrap; align-items: center;
    font-size: 10px; padding: 5px 6px; clip-path: var(--gf-cut-sm);
    border: 1px solid var(--gf-border-soft);
    background: rgba(var(--gf-bone-rgb), 0.03);
}
.ms-ctx-names { border-color: rgba(255,226,150,0.45); background: rgba(255,226,150,0.06); }
.ms-ctx-opportunity { border-color: rgba(255,189,104,0.55); background: rgba(255,189,104,0.07); }
.ms-ctx-director { border-color: rgba(90,214,255,0.4); background: rgba(90,214,255,0.06); }
.ms-ctx-quest { border-color: rgba(var(--gf-trim-rgb), 0.5); background: rgba(var(--gf-trim-rgb), 0.08); }
.ms-hint { color: var(--gf-text-3); font-size: 11px; font-style: italic; }

/* Layer checkboxes + brush fields. */
.ms-check {
    display: flex; align-items: center; gap: 6px; cursor: pointer;
    font-size: 11.5px; padding: 1px 0; color: var(--gf-text-2);
}
.ms-check:hover { color: var(--gf-text-1); }
.ms-field { display: flex; align-items: center; gap: 6px; font-size: 11px; }
.ms-field > input[type=range] { flex: 1; min-width: 0; }
.ms-field-val {
    width: 40px; text-align: right; font-family: var(--gf-mono);
    font-size: 10px; color: var(--gf-text-3);
}

/* Legend: an opt-in overlay on the map, collapsed to a pill by default. It is
   INTERACTIVE (pointer-events auto) - it used to be pointer-events:none with
   overflow-y:auto, so once it clipped, the tail you needed was unreachable. */
.ms-legend {
    position: absolute; right: 6px; bottom: 6px; max-width: 172px;
    max-height: 62%; overflow-y: auto; pointer-events: auto;
    padding: 5px 7px; clip-path: var(--gf-cut-sm); font-size: 9px; line-height: 1.5;
    background: rgba(10,8,6,0.88); border: 1px solid var(--gf-border-accent);
    color: var(--gf-text-2);
}
.ms-legend-head {
    cursor: pointer; list-style: none; color: var(--gf-accent);
    text-transform: uppercase; letter-spacing: .08em; font-size: 9px;
}
.ms-legend-head::-webkit-details-marker { display: none; }

/* ── Map Browser (.msb-*) - the "find a map among hundreds" overlay ─────────
   A child of the Studio root at inset:0, so it inherits .gf-map-studio's
   tokens and needs no z-index of its own. Gold stays the active color. */
.msb {
    position: absolute; inset: 0; z-index: 5;
    display: flex; flex-direction: column;
    background: var(--gf-panel-bg-strong);
}
.msb-bar {
    display: flex; align-items: center; gap: 6px; flex: none;
    padding: 8px 10px; border-bottom: 1px solid var(--gf-border-accent);
}
.msb-search {
    flex: 1; min-width: 120px; max-width: 420px;
    background: rgba(0,0,0,0.4); border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm); color: var(--gf-accent-hot); padding: 5px 9px;
    font-size: 12px;
}
.msb-sort {
    background: rgba(0,0,0,0.4); border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm); color: var(--gf-accent-hot); padding: 4px 6px;
    font-size: 11px;
}
.msb-bar button {
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm); color: var(--gf-text-1); cursor: pointer;
    padding: 4px 9px; font-size: 11px; font-family: inherit;
}
.msb-bar button:hover { background: var(--gf-slot-bg-hover); }
.msb-bar button.on { background: rgba(var(--gf-trim-rgb), 0.22); color: var(--gf-accent-hot); }
.msb-count { margin-left: auto; color: var(--gf-text-3); font-size: 11px; }
.msb-close { font-size: 13px !important; }

/* The one-shot "click a map" banner (Set parent…). */
.msb-arm {
    flex: none; padding: 5px 10px; font-size: 11px;
    background: rgba(var(--gf-trim-rgb), 0.16);
    border-bottom: 1px solid var(--gf-border-accent);
    color: var(--gf-accent-hot);
}
.msb.is-arming .msb-row { cursor: crosshair; }

.msb-body { flex: 1; display: flex; min-height: 0; }

/* Facet rail */
.msb-rail {
    flex: none; width: 210px; overflow-y: auto; padding: 8px;
    border-right: 1px solid var(--gf-border-soft);
}
.msb-fgroup { margin-bottom: 10px; }
.msb-flabel {
    color: var(--gf-accent); font-size: 9px; letter-spacing: .08em;
    text-transform: uppercase; margin-bottom: 3px;
}
.msb-facet {
    display: flex; width: 100%; align-items: center; gap: 6px;
    background: none; border: 1px solid transparent; clip-path: var(--gf-cut-sm);
    color: var(--gf-text-2); cursor: pointer; padding: 3px 6px;
    font-size: 11px; font-family: inherit; text-align: left;
}
.msb-facet:hover { background: var(--gf-slot-bg); }
.msb-facet.on {
    background: rgba(var(--gf-trim-rgb), 0.20); border-color: var(--gf-border-accent);
    color: var(--gf-accent-hot);
}
.msb-fname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msb-fn { color: var(--gf-text-3); font-variant-numeric: tabular-nums; }
.msb-facet.on .msb-fn { color: var(--gf-accent-hot); }
.msb-clear {
    width: 100%; margin-top: 4px; padding: 4px; clip-path: var(--gf-cut-sm);
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft);
    color: var(--gf-text-2); cursor: pointer; font-size: 11px; font-family: inherit;
}
.msb-clear:disabled { opacity: .35; cursor: default; }

/* List */
.msb-listwrap { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.msb-head, .msb-row {
    display: grid; align-items: center; gap: 6px;
    grid-template-columns: minmax(180px,1fr) 68px 90px 92px 62px 52px 52px 56px 84px;
    padding: 0 10px;
}
.msb-head {
    flex: none; height: 26px; border-bottom: 1px solid var(--gf-border-soft);
    color: var(--gf-text-3); font-size: 10px; text-transform: uppercase;
    letter-spacing: .05em;
}
.msb-h {
    background: none; border: 0; padding: 0; text-align: left; color: inherit;
    font: inherit; text-transform: inherit; letter-spacing: inherit;
}
button.msb-h { cursor: pointer; }
button.msb-h:hover { color: var(--gf-accent); }
.msb-arrow { color: var(--gf-accent); margin-left: 3px; }
.msb-list { flex: 1; overflow-y: auto; outline: none; }
.msb-group {
    padding: 8px 10px 3px; color: var(--gf-accent); font-size: 9px;
    letter-spacing: .08em; text-transform: uppercase;
}
.msb-row {
    height: 24px; cursor: pointer; border-left: 2px solid transparent;
    color: var(--gf-text-2); font-size: 11px;
}
.msb-row:hover { background: var(--gf-slot-bg); }
.msb-row.is-sel { background: var(--gf-slot-bg-hover); color: var(--gf-text-1); }
.msb-row.is-target {
    border-left-color: var(--gf-accent);
    background: rgba(var(--gf-trim-rgb), 0.14); color: var(--gf-accent-hot);
}
.msb-c-name { display: flex; align-items: center; gap: 4px; min-width: 0; }
.msb-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msb-twisty {
    flex: none; width: 14px; background: none; border: 0; padding: 0;
    color: var(--gf-text-3); cursor: pointer; font: inherit;
}
.msb-twisty-none { cursor: default; }
.msb-pin { flex: none; color: var(--gf-text-4); cursor: pointer; }
.msb-pin:hover { color: var(--gf-accent); }
.msb-pin.on { color: var(--gf-accent); }
.msb-status { flex: none; width: 10px; color: var(--gf-accent); }
.msb-kids {
    flex: none; padding: 0 4px; clip-path: var(--gf-cut); font-size: 9px;
    background: var(--gf-slot-bg); color: var(--gf-text-3);
}
/* The dungeon-chain rail. Monospace and pre so the box-drawing columns line up
   between rows - a proportional font staggers them and the "chain" reads as
   noise. Indentation is the rail's own width; there is no padding-left. */
.msb-guide {
    /* --gf-mono, not --gf-font-mono: the latter is defined nowhere, so this
       declaration was invalid at computed-value time and the rail fell back to
       the inherited proportional font - the exact staggering the comment above
       says the monospace is here to prevent. */
    flex: none; white-space: pre; font-family: var(--gf-mono, monospace);
    color: var(--gf-text-4); letter-spacing: 0;
}
.msb-chainhead { color: var(--gf-text-3); }
.msb-floor {
    flex: none; padding: 0 4px; clip-path: var(--gf-cut-sm); font-size: 9px;
    background: rgba(201,154,255,0.16); color: #c99aff;
    font-variant-numeric: tabular-nums;
}
.msb-warn { flex: none; color: var(--gf-danger); font-size: 10px; }
.msb-orphan {
    margin: 6px 0; padding: 5px 7px; clip-path: var(--gf-cut-sm); font-size: 10px;
    background: rgba(224,82,74,0.10); border: 1px solid rgba(224,82,74,0.35);
    color: var(--gf-text-2);
}
.msb-c-num, .msb-c-date { font-variant-numeric: tabular-nums; color: var(--gf-text-3); }
.msb-c-theme, .msb-c-shape, .msb-c-kind {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--gf-text-3);
}
.msb-empty, .msb-hint { padding: 24px 14px; color: var(--gf-text-3); font-size: 12px; }

/* Detail pane */
.msb-detail {
    flex: none; width: 280px; overflow-y: auto; padding: 12px;
    border-left: 1px solid var(--gf-border-soft);
}
.msb-dtitle { color: var(--gf-accent-hot); font-size: 14px; margin-bottom: 2px; }
.msb-dsub { color: var(--gf-text-3); font-size: 11px; margin-bottom: 8px; }
.msb-thumb {
    width: 100%; aspect-ratio: 1; margin-bottom: 8px; clip-path: var(--gf-cut-sm);
    background: rgba(0,0,0,0.35); border: 1px solid var(--gf-border-soft);
    overflow: hidden; display: flex; align-items: center; justify-content: center;
}
.msb-thumb canvas, .msb-thumb img { width: 100%; height: 100%; object-fit: cover; }
.msb-chain {
    display: flex; flex-wrap: wrap; align-items: center; gap: 3px;
    margin-bottom: 8px; padding: 5px; clip-path: var(--gf-cut-sm);
    background: rgba(0,0,0,0.25); border: 1px solid var(--gf-border-soft);
}
.msb-crumb {
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm); color: var(--gf-text-2); cursor: pointer;
    padding: 2px 5px; font-size: 10px; font-family: inherit;
}
.msb-crumb.on {
    background: rgba(var(--gf-trim-rgb), 0.24); border-color: var(--gf-border-accent);
    color: var(--gf-accent-hot);
}
.msb-carrow { color: var(--gf-text-4); font-size: 10px; }
.msb-facts {
    display: grid; grid-template-columns: auto 1fr; gap: 2px 10px;
    margin: 0 0 8px; font-size: 11px;
}
.msb-facts dt { color: var(--gf-text-3); }
.msb-facts dd { margin: 0; color: var(--gf-text-1); font-variant-numeric: tabular-nums; }
.msb-id {
    color: var(--gf-text-4); font-size: 9px; margin-bottom: 8px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.msb-actions { display: flex; flex-direction: column; gap: 4px; }
.msb-act {
    padding: 6px; clip-path: var(--gf-cut-sm); cursor: pointer; font-size: 11px;
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft);
    color: var(--gf-text-1); font-family: inherit;
}
.msb-act:hover:not(:disabled) { background: var(--gf-slot-bg-hover); }
.msb-act:disabled { opacity: .5; cursor: default; }
.msb-act-primary {
    background: rgba(var(--gf-trim-rgb), 0.20); border-color: var(--gf-border-accent);
    color: var(--gf-accent-hot);
}
.msb-act-danger { color: var(--gf-danger); border-color: rgba(224,82,74,0.35); }

/* Narrow viewports: the detail pane is the first thing to go, then three
   columns. The track count MUST equal the cells that survive - name + 8
   columns is 9, minus the 3 hidden here is 6. It read 6 while hiding only ONE
   real class (`.msb-c-towns`/`.msb-c-spawners` matched nothing - all three
   count columns shared `msb-c-num`), so eight cells landed in a six-track grid
   and the last two wrapped onto a second row.
   `tests/map_studio_world_picker_test.mjs` cross-checks both halves. */
@media (max-width: 1100px) {
    .msb-detail { display: none; }
    .msb-head, .msb-row { grid-template-columns: minmax(160px,1fr) 62px 84px 58px 48px 78px; }
    .msb-c-shape, .msb-c-towns, .msb-c-spawners { display: none; }
}

/* ── Mobile & Touch Controls Overlay ── */
/* The touch control layer. This is a stacking context, so every z-index inside
   it is ordered only against its own siblings and the ONE number that matters
   globally is this root.
   It used to be 4 - under the action bar (40/41), which in portrait is parked on
   top of the Chat/Bag/Menu row by a <620px rule, so the bar won every contested
   tap and those buttons were simply dead. It stays BELOW the window band on
   purpose: raising it would put the 42vw joystick hit zone over any stat bar
   dragged into the lower-left. The minimap-over-buttons collision is fixed by
   geometry instead (see the portrait block near the end of this file). */
.mobile-controls-overlay {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: var(--gf-z-touch);
    display: none;
}

body.mobile-controls-active .mobile-controls-overlay {
    display: block;
}

/* Hide desktop floating help/submit buttons when mobile controls overlay is active */
body.mobile-controls-active #gf-help-btn,
body.mobile-controls-active #gf-submit-btn,
body.mobile-controls-active #gf-pets-btn {
    display: none !important;
}

.mobile-move-zone {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 45vw;
    z-index: var(--gf-z-touch-zone);
    pointer-events: auto;
    touch-action: none;
}

.mobile-swipe-zone {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    width: 55vw;
    z-index: var(--gf-z-touch-zone);
    pointer-events: auto;
    touch-action: none;
}

.mobile-joystick-container {
    position: absolute;
    bottom: 50px;
    left: 50px;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.45);
    border: 2px solid rgba(var(--gf-bone-rgb), 0.3);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
    z-index: var(--gf-z-touch-marker);
    touch-action: none;
    pointer-events: none;
    opacity: 0.5;
    transform: scale(0.95);
    transition: opacity 0.22s cubic-bezier(0.4, 0, 0.2, 1),
                transform 0.22s cubic-bezier(0.4, 0, 0.2, 1),
                border-color 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}

.mobile-joystick-container.active {
    opacity: 0.95;
    border-color: rgba(255, 255, 255, 0.7);
    transform: scale(1.05);
}

.mobile-joystick-thumb {
    position: absolute;
    left: 35px;
    top: 35px;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(200, 200, 200, 0.9) 100%);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5), inset 0 2px 4px rgba(255, 255, 255, 0.6);
    pointer-events: none;
}

/* Arc Layout for Action Buttons in the bottom-right corner */
.mobile-action-buttons {
    position: absolute;
    bottom: 20px;
    right: 20px;
    width: 280px;
    height: 280px;
    z-index: var(--gf-z-touch-cluster);
    pointer-events: none;
}

.mobile-btn {
    position: absolute;
    pointer-events: auto;
    border-radius: 50%;
    background: rgba(18, 16, 14, 0.7);
    border: 2px solid rgba(200, 170, 120, 0.5);
    color: #ffd99e;
    font-family: var(--gf-mono), monospace;
    font-weight: bold;
    text-transform: uppercase;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.6), inset 0 2px 5px rgba(var(--gf-bone-rgb), 0.1);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
    transition: transform 0.08s, background-color 0.08s, border-color 0.08s;
}

/* THE [hidden] OVERRIDE IS LOAD-BEARING - the same trap main.css already
   documents for .pd-dress. There is no global `[hidden]` rule in this sheet, and
   the author `display: flex` above beats the UA `[hidden]{display:none}` on
   specificity. So `diveButton.hidden = true` did NOTHING: the Dive button, whose
   own comment says it is "shown ONLY while swimming, so the dock is unchanged in
   ordinary play", has been permanently visible in the action dock - crowding the
   one part of a portrait screen with the least room to spare. */
.mobile-btn[hidden] { display: none !important; }

.mobile-btn:active {
    transform: scale(0.9);
    background-color: rgba(200, 170, 120, 0.25);
    border-color: rgba(255, 217, 158, 0.85);
    color: #fff;
}

/* Individual button sizes, positions, and styling forming an arc */
.mobile-btn-jump {
    bottom: 10px;
    right: 10px;
    width: 68px;
    height: 68px;
    font-size: 11px;
    background: rgba(90, 50, 160, 0.7);
    border-color: rgba(150, 110, 220, 0.65);
    color: #e2d2ff;
}
.mobile-btn-jump:active {
    background-color: rgba(120, 80, 200, 0.85);
    border-color: rgba(180, 140, 255, 0.95);
}

.mobile-btn-war {
    bottom: 90px;
    right: 90px;
    width: 52px;
    height: 52px;
    font-size: 9px;
    background: rgba(160, 40, 40, 0.7);
    border-color: rgba(220, 90, 90, 0.65);
    color: #ffd2d2;
}
.mobile-btn-war:active {
    background-color: rgba(200, 60, 60, 0.85);
    border-color: rgba(255, 120, 120, 0.95);
}

.mobile-btn-target {
    bottom: 155px;
    right: 85px;
    width: 50px;
    height: 50px;
    font-size: 9px;
}


.mobile-btn-sprint {
    bottom: 160px;
    right: 160px;
    width: 48px;
    height: 48px;
    font-size: 8px;
    background: rgba(25, 95, 50, 0.75);
    border-color: rgba(90, 220, 130, 0.55);
    color: #c5ffc5;
}
.mobile-btn-sprint.active {
    background: rgba(0, 150, 65, 0.85);
    border-color: rgba(0, 255, 120, 0.85);
    color: #fff;
    box-shadow: 0 0 14px rgba(0, 255, 120, 0.55), inset 0 2px 5px rgba(var(--gf-bone-rgb), 0.25);
}

.mobile-btn-bag {
    bottom: 220px;
    right: 20px;
    width: 46px;
    height: 46px;
    font-size: 8px;
}

.mobile-btn-menu {
    bottom: 20px;
    right: 220px;
    width: 46px;
    height: 46px;
    font-size: 8px;
}

/* Make HUD panels scale better on mobile devices */
body.mobile-controls-active #hud-info {
    transform: scale(0.9);
    transform-origin: top right;
}
body.mobile-controls-active #chat-area {
    max-width: 90vw;
    width: 300px !important;
    height: 140px !important;
    font-size: 11px;
}

/* Responsive Scaling for Draggable Game Windows on Mobile */
body.mobile-controls-active .game-window {
    transform: scale(0.8);
    transform-origin: top left;
    max-width: 90vw;
}

/* Touch-friendly larger Close Buttons on mobile */
body.mobile-controls-active .game-window-close {
    font-size: 22px;
    padding: 4px 10px;
    margin-right: -4px;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
}

/* ── Animal Lore window (lore_window.js) ── */
.lore-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.lore-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 0 10px;
}
.lore-section {
    padding: 4px 0;
}
.lore-section-title {
    padding: 6px 12px 2px;
    font-size: 11px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: #c8a45c;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
    margin-bottom: 2px;
}
.lore-section-title:empty {
    display: none;
}
.lore-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 12px;
    font-size: 12px;
}
.lore-label {
    flex: 0 0 44%;
    color: #b09468;
}
.lore-value {
    flex: 1 1 auto;
    color: var(--gf-bone);
}
.lore-locked {
    color: #7d6a4d;
    font-style: italic;
}
.lore-traits {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/* ── Trade window (Phase 12 P3) - two offer columns + gold + confirm ── */
.trade-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.trade-cols {
    flex: 1;
    min-height: 0;
    display: flex;
    gap: 1px;
    background: rgba(160, 120, 60, 0.25);
}
.trade-col {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: rgba(20, 16, 12, 0.95);
}
.trade-col-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 6px;
    padding: 6px 10px;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
}
.trade-col-name {
    color: #c8a45c;
    font-size: 12px;
    letter-spacing: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.trade-col-check { font-size: 10px; color: #8a7a58; }
.trade-col-check.is-ok { color: #7ad67f; }
.trade-items {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 0;
}
.trade-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    font-size: 12px;
}
.trade-item-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.trade-item-qty { flex: 0 0 auto; color: #b09468; font-size: 11px; }
.trade-item-remove {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: #c8785c;
    cursor: pointer;
    font-size: 12px;
    padding: 0 2px;
}
.trade-item-remove:hover { color: #ff8a6a; }
.trade-empty {
    padding: 8px 12px;
    color: #7d6a4d;
    font-style: italic;
    font-size: 11px;
}
.trade-gold {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border-top: 1px solid rgba(160, 120, 60, 0.25);
    font-size: 12px;
    color: var(--gf-trim-bright);
}
.trade-gold-input {
    width: 90px;
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(160, 120, 60, 0.4);
    color: var(--gf-trim-bright);
    font-family: inherit;
    font-size: 12px;
    padding: 2px 6px;
    clip-path: var(--gf-cut-sm);
}
.trade-gold-input:focus { outline: none; border-color: #c8a45c; }
.trade-gold-val { color: var(--gf-trim-bright); }
.trade-hint {
    padding: 5px 12px;
    font-size: 10px;
    font-style: italic;
    color: #7d6a4d;
    text-align: center;
}
.trade-foot {
    display: flex;
    gap: 8px;
    padding: 8px 10px;
    border-top: 1px solid rgba(160, 120, 60, 0.25);
}
.trade-btn {
    flex: 1;
    background: rgba(120, 90, 40, 0.35);
    border: 1px solid rgba(160, 120, 60, 0.5);
    color: var(--gf-bone);
    font-family: inherit;
    font-size: 12px;
    padding: 5px 9px;
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.trade-btn:hover { background: rgba(160, 120, 60, 0.5); }
.trade-confirm.is-on {
    background: rgba(90, 150, 80, 0.5);
    border-color: rgba(120, 200, 110, 0.6);
    color: #b6f0ad;
}
.trade-cancel { color: #e6b09a; }

/* Secure storage chest window (Phase 12 P5b) - a slot grid of the chest's
   contents; double-click a slot to withdraw, double-click a bag item to store. */
.container-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.container-grid {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
    gap: 4px;
    padding: 8px;
    align-content: start;
}
.container-slot {
    position: relative;
    min-height: 58px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 4px;
    border: 1px solid rgba(160, 120, 60, 0.25);
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.25);
}
.container-slot.is-filled {
    border-color: rgba(160, 120, 60, 0.5);
    background: rgba(40, 30, 18, 0.6);
    cursor: pointer;
}
.container-slot.is-filled:hover { background: rgba(70, 52, 28, 0.7); }
.container-slot-swatch {
    width: 28px;
    height: 28px;
    clip-path: var(--gf-cut-sm);
    background: rgba(150, 120, 70, 0.6);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
.container-slot-swatch.has-model {
    /* square */
    box-shadow: none;
    background-color: transparent !important;
}
.container-slot-name {
    font-size: 10px;
    line-height: 1.1;
    text-align: center;
    color: #d8c69a;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.container-slot-qty {
    position: absolute;
    right: 3px;
    bottom: 2px;
    font-size: 10px;
    color: var(--gf-trim-bright);
    text-shadow: 0 0 3px #000;
}
.container-hint {
    padding: 5px 12px;
    font-size: 10px;
    font-style: italic;
    color: #7d6a4d;
    text-align: center;
    border-top: 1px solid rgba(160, 120, 60, 0.25);
}
/* Chest name + any security still in force. Empty for a chest with neither, so
   an ordinary bag of loot gains no chrome it did not have before. */
.container-banner {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 12px 5px;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
}
.container-banner:empty { display: none; }
.container-banner-name {
    font-size: 12px;
    letter-spacing: 0.04em;
    color: var(--gf-trim-bright);
}
.container-banner-warn {
    font-size: 10px;
    font-style: italic;
    color: #d2724e;
}

/* Wearable portable-supply window: deliberately one large slot, not a second
   backpack. Gold/brown means mortal craft; Sealed remains cyan only in tooltip
   copy where it names a real Grid property. */
.portable-supply-panel {
    width: 100%; height: 100%; display: flex; flex-direction: column;
    align-items: stretch; justify-content: space-between;
    color: var(--gf-bone); font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.portable-supply-summary {
    padding: 12px 14px 8px; text-align: center;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
}
.portable-supply-count { color: var(--gf-trim-bright); font-size: 19px; letter-spacing: .04em; }
.portable-supply-nameplate {
    color: #d8c69a; font-size: 12px; letter-spacing: .08em;
    text-transform: uppercase; margin-bottom: 2px;
}
.portable-supply-details { margin-top: 4px; color: #9b8560; font-size: 10px; }
.portable-supply-slot-host {
    flex: 1; min-height: 0; display: flex; align-items: center;
    justify-content: center; padding: 14px;
}
.portable-supply-slot.inv-slot {
    width: 104px; height: 104px; padding: 8px; box-sizing: border-box;
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 5px; cursor: default;
    border-color: rgba(160, 120, 60, 0.45);
    background: rgba(0, 0, 0, 0.28);
}
.portable-supply-slot.is-filled { cursor: grab; background: rgba(40, 30, 18, 0.62); }
.portable-supply-slot.is-filled:hover { border-color: rgba(230, 200, 106, 0.75); }
.portable-supply-swatch.inv-slot-swatch { width: 46px; height: 46px; opacity: 1; }
.portable-supply-name { color: #d8c69a; font-size: 11px; text-align: center; }
.portable-supply-empty { color: #6f6049; font-size: 11px; letter-spacing: .14em; }
.portable-supply-qty.inv-slot-qty { right: 6px; bottom: 4px; color: var(--gf-trim-bright); }
.portable-supply-hint {
    padding: 7px 12px; min-height: 14px; text-align: center;
    border-top: 1px solid rgba(160, 120, 60, 0.25);
    color: #7d6a4d; font-size: 10px; font-style: italic;
}

/* Runebook window - a list, not a slot grid: a rune's identity is its NAME, so
   rows read as an index of places rather than a bag of things. Same glass-and-
   gold shell as the container window. */
.runebook-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.runebook-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 8px;
}
.runebook-section {
    margin: 8px 2px 4px;
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #9b8560;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
    padding-bottom: 3px;
}
.runebook-section:first-child { margin-top: 2px; }
.runebook-empty {
    padding: 10px 4px;
    font-size: 11px;
    font-style: italic;
    color: #7d6a4d;
}
.runebook-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    flex-wrap: wrap;
    padding: 6px;
    margin-bottom: 4px;
    border: 1px solid rgba(160, 120, 60, 0.25);
    clip-path: var(--gf-cut-sm);
    background: rgba(40, 30, 18, 0.5);
}
.runebook-info { min-width: 0; flex: 1 1 140px; }
.runebook-name {
    font-size: 12px;
    color: var(--gf-bone);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.runebook-coords {
    font-size: 10px;
    color: #7d6a4d;
}
.runebook-actions { display: flex; gap: 3px; flex-wrap: wrap; }
.runebook-btn {
    font-family: inherit;
    font-size: 10px;
    padding: 3px 7px;
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
    border: 1px solid rgba(160, 120, 60, 0.45);
    background: rgba(60, 45, 24, 0.7);
    color: var(--gf-trim-bright);
}
.runebook-btn:hover { background: rgba(90, 68, 34, 0.85); }
/* The three rune-destination spells read as the Grid, not as gold. */
.runebook-btn.runebook-cast {
    border-color: rgba(102, 224, 255, 0.35);
    color: var(--gf-grid-cyan);
}
.runebook-btn.runebook-cast:hover { background: rgba(30, 70, 86, 0.8); }
.runebook-btn.runebook-minor { color: #b09a70; }
.runebook-hint {
    padding: 5px 12px;
    font-size: 10px;
    font-style: italic;
    color: #7d6a4d;
    text-align: center;
    border-top: 1px solid rgba(160, 120, 60, 0.25);
}

/* Pack animal window (pet-system P3) - the chest window's saddlebag cousin;
   reuses the container slot grid, adds a weight readout bar. */
.petpack-weight {
    position: relative;
    height: 16px;
    margin: 4px 10px 0;
    border: 1px solid rgba(160, 120, 60, 0.35);
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.35);
    overflow: hidden;
}
.petpack-weight-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0;
    background: linear-gradient(180deg, rgba(var(--gf-trim-rgb), 0.75), rgba(140, 110, 46, 0.6));
    transition: width 0.25s ease;
}
.petpack-weight-fill.is-heavy { background: linear-gradient(180deg, #b0563a, #7d3524); }
.petpack-weight-text {
    position: relative;
    text-align: center;
    font-size: 10px;
    line-height: 16px;
    color: var(--gf-bone);
    text-shadow: 0 0 3px #000;
}

/* Stable window (pet-system P1) - stablemaster boarding. */
.stable-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.stable-fee {
    padding: 6px 12px;
    font-size: 11px;
    color: var(--gf-trim-bright);
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
}
.stable-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 10px 10px;
}
.stable-section { margin-top: 8px; }
.stable-section-title {
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gf-trim);
    border-bottom: 1px solid rgba(160, 120, 60, 0.3);
    padding-bottom: 2px;
    margin-bottom: 4px;
}
.stable-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 2px;
}
.stable-name { flex: 0 1 auto; font-size: 12px; color: var(--gf-bone); }
.stable-meta { flex: 1 1 auto; font-size: 10px; color: #8d7a5d; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stable-btn {
    flex: 0 0 auto;
    padding: 2px 10px;
    font-size: 10px;
    font-family: inherit;
    color: var(--gf-accent-hot);
    background: var(--gf-face);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.stable-btn:hover:not(:disabled) { background: rgba(100, 74, 38, 0.8); }
.stable-btn:disabled { opacity: 0.4; cursor: default; }
.stable-empty { font-size: 11px; font-style: italic; color: #7d6a4d; padding: 3px 2px; }

/* My Pets roster window (pet-system P2). */
.proster-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
}
.proster-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 12px;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
}
.proster-slots { font-size: 11px; color: #7fc8ff; }
.proster-btn {
    padding: 2px 10px;
    font-size: 10px;
    font-family: inherit;
    color: var(--gf-bone);
    background: rgba(70, 52, 28, 0.7);
    border: 1px solid rgba(160, 120, 60, 0.5);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.proster-btn:hover:not(:disabled) { background: rgba(100, 74, 38, 0.8); }
.proster-btn:disabled { opacity: 0.4; cursor: default; }
.proster-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 4px 10px 10px;
}
.proster-section { margin-top: 8px; }
.proster-section-title {
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gf-trim);
    border-bottom: 1px solid rgba(160, 120, 60, 0.3);
    padding-bottom: 2px;
    margin-bottom: 4px;
}
.proster-empty { font-size: 11px; font-style: italic; color: #7d6a4d; padding: 3px 2px; }
.proster-row {
    padding: 4px 2px 5px;
    border-bottom: 1px solid rgba(160, 120, 60, 0.12);
}
.proster-row.is-ghost { opacity: 0.65; }
.proster-row-top { display: flex; align-items: baseline; gap: 8px; }
.proster-name { flex: 0 1 auto; font-size: 12px; color: var(--gf-bone); }
.proster-meta { flex: 1 1 auto; font-size: 10px; color: #8d7a5d; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.proster-hp {
    position: relative;
    height: 6px;
    margin: 3px 0 2px;
    border: 1px solid rgba(160, 120, 60, 0.3);
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.4);
    overflow: hidden;
}
.proster-hp-fill {
    position: absolute;
    inset: 0 auto 0 0;
    background: #50a060;
    transition: width 0.25s ease;
}
.proster-hp-fill.is-low { background: #c04040; }
.proster-row-foot { display: flex; gap: 10px; font-size: 10px; }
.proster-posture { color: #a9c4d8; }
.proster-region { color: #8d7a5d; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.proster-breed { color: #d8a0c0; }
.proster-row-stabled { display: flex; align-items: baseline; gap: 8px; }

/* Fallen-pet prompt banners (pet-system P2) - persistent, dismissible, stacked
   under the top-center of the screen until the pet is revived or truly dies. */
#gf-pet-ghost-banners {
    position: fixed;
    top: 64px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9400;
    display: flex;
    flex-direction: column;
    gap: 6px;
    align-items: center;
    pointer-events: none;
}
.gf-pet-ghost-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: min(560px, 90vw);
    padding: 7px 12px;
    clip-path: var(--gf-cut);
    background: linear-gradient(180deg, rgba(14, 14, 20, 0.9), rgba(14, 14, 20, 0.72));
    border: 1px solid rgba(127, 200, 255, 0.4);
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55), inset 0 0 16px rgba(127, 200, 255, 0.06);
    color: #cfe4f2;
    font: 500 12px/1.35 system-ui, sans-serif;
    pointer-events: auto;
}
.gf-pgb-icon { font-size: 15px; }
.gf-pgb-close {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: #8fb4cc;
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    padding: 2px 4px;
}
.gf-pgb-close:hover { color: #ffffff; }

/* Animal Lore ♂/♀ chip beside the life stage (P0 follow-up). */
.lore-sex-chip {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    clip-path: var(--gf-cut);
    font-size: 11px;
    line-height: 1.4;
    background: rgba(0, 0, 0, 0.3);
}
.lore-sex-chip.is-male { color: #6a9fd8; border: 1px solid rgba(106, 159, 216, 0.5); }
.lore-sex-chip.is-female { color: #e08aa8; border: 1px solid rgba(224, 138, 168, 0.5); }

/* Guild crafting station - the Cauldron window (GUILD_PROGRESSION Pass 4b). */
.gstation-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px;
    overflow-y: auto;
    color: var(--gf-bone);
    font-family: var(--gf-font-display);
    background: rgba(20, 16, 12, 0.95);
    box-sizing: border-box;
}
.gstation-recipe-row { display: flex; }
.gstation-recipe-select {
    flex: 1;
    padding: 5px 8px;
    background: rgba(0, 0, 0, 0.35);
    color: var(--gf-bone);
    border: 1px solid rgba(160, 120, 60, 0.4);
    clip-path: var(--gf-cut-sm);
    font-family: inherit;
    font-size: 12px;
}
.gstation-recipe-name { font-size: 14px; color: var(--gf-trim-bright); }
.gstation-ingredients { font-size: 10px; color: #9e8a68; line-height: 1.3; }
.gstation-status { font-size: 11px; font-style: italic; color: #b8a680; }
.gstation-status.status-producing { color: #8fd08f; }
.gstation-status.status-no_upkeep,
.gstation-status.status-output_full { color: #d0925a; }
.gstation-status.status-needs_reagents { color: #c8b46a; }
.gstation-progress {
    height: 8px;
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.4);
    border: 1px solid rgba(160, 120, 60, 0.3);
    overflow: hidden;
}
.gstation-progress-fill {
    height: 100%;
    background: linear-gradient(90deg, #7a4a1f, var(--gf-trim-bright));
    transition: width 0.4s ease;
}
.gstation-section-label {
    margin-top: 4px;
    font-size: 11px;
    color: #d8c69a;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
    padding-bottom: 2px;
}
.gstation-list { display: flex; flex-direction: column; gap: 2px; }
.gstation-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 12px;
    color: #d8c69a;
    padding: 2px 4px;
}
.gstation-item-out { background: rgba(40, 30, 18, 0.5); clip-path: var(--gf-cut-sm); }
.gstation-empty { font-size: 10px; font-style: italic; color: #7d6a4d; padding: 2px 4px; }
.gstation-btn {
    padding: 3px 10px;
    background: rgba(70, 52, 28, 0.7);
    color: var(--gf-bone);
    border: 1px solid rgba(160, 120, 60, 0.5);
    clip-path: var(--gf-cut-sm);
    font-family: inherit;
    font-size: 11px;
    cursor: pointer;
}
.gstation-btn:hover { background: rgba(100, 74, 40, 0.8); }
.gstation-toggle { margin-top: 6px; align-self: flex-start; }

/* Genetic trait rarity chips (Animal Lore + Pet Training) - tier derives from
   the trait's rarity_weight server-side (breeding.trait_display). */
.gf-trait-chip {
    display: inline-block;
    padding: 1px 7px;
    margin: 1px 2px 1px 0;
    clip-path: var(--gf-cut);
    border: 1px solid rgba(176, 148, 104, 0.45);
    background: rgba(30, 26, 20, 0.6);
    color: var(--gf-bone);
    font-size: 11px;
    line-height: 16px;
    white-space: nowrap;
}
.gf-trait-chip.tier-rare {
    border-color: rgba(106, 163, 224, 0.75);
    color: #a8ccf2;
}
.gf-trait-chip.tier-super_rare {
    border-color: rgba(176, 96, 255, 0.8);
    color: #d0a8ff;
}
.gf-trait-chip.tier-ultra_rare {
    border-color: rgba(245, 197, 66, 0.9);
    color: #f5c542;
    background: rgba(60, 44, 12, 0.65);
    box-shadow: 0 0 6px rgba(245, 197, 66, 0.35);
}
.gf-map-canvas:focus-visible { outline: 2px solid var(--gf-grid-cyan, var(--gf-grid-cyan)); outline-offset: -2px; }

.gf-map-search {
    position: absolute; top: 7px; left: 7px; z-index: 5; width: min(330px, calc(100% - 52px));
    padding: 6px; border: 1px solid var(--gf-border-accent); clip-path: var(--gf-cut-sm);
    background: rgba(14,13,12,.95); box-shadow: var(--gf-shadow);
}
.gf-map-search[hidden], .gf-map-inspect[hidden],
.gf-map-compass-ribbon[hidden] { display: none !important; }
.gf-map-search input { width: 100%; box-sizing: border-box; color: var(--gf-text-1); background: #0c0e12;
    border: 1px solid var(--gf-border); padding: 6px 8px; font: 11px var(--gf-mono); }
.gf-map-search-results { max-height: 260px; overflow: auto; margin-top: 4px; }
.gf-map-search-results button { display: block; width: 100%; border: 0; border-top: 1px solid rgba(var(--gf-bone-rgb), .06);
    padding: 6px; text-align: left; color: var(--gf-text-2); background: transparent; font: 10px var(--gf-mono); cursor: pointer; }
.gf-map-search-results button:hover, .gf-map-search-results button:focus { color: #fff; background: rgba(102,224,255,.12); }
.gf-map-search-empty { padding: 8px; color: var(--gf-text-3); font: 10px var(--gf-mono); }

.gf-map-inspect { position: absolute; z-index: 4; max-width: min(300px, 70%);
    color: var(--gf-text-2); background: rgba(14,13,12,.92); border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm); box-shadow: var(--gf-shadow); font: 10px/1.45 var(--gf-mono); }
.gf-map-inspect { left: 7px; top: 88px; padding: 8px 26px 8px 9px; }
.gf-map-inspect b { display: block; color: var(--gf-text-1); font-size: 12px; margin-bottom: 3px; }
.gf-map-inspect span { white-space: pre-line; }
.gf-map-panel-close { position: absolute; right: 3px; top: 2px; border: 0; background: none; color: var(--gf-text-2); cursor: pointer; }
.gf-map-inspect-actions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 7px; }
.gf-map-inspect-actions button { background: rgba(var(--gf-bone-rgb), .06); color: var(--gf-text-2); border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm); padding: 3px 5px; font: 9px var(--gf-mono); cursor: pointer; }
.gf-map-scale { position: absolute; left: 8px; bottom: 29px; z-index: 2; height: 5px; box-sizing: border-box;
    border: solid rgba(255,255,255,.9); border-width: 0 1px 1px; color: #fff; text-align: center;
    text-shadow: 0 1px 2px #000; font: 9px/1 var(--gf-mono); padding-bottom: 12px; pointer-events: none; }
.gf-map-compass-ribbon { position: absolute; top: 7px; left: 50%; transform: translateX(-50%); z-index: 2;
    min-width: 70px; padding: 3px 9px; text-align: center; color: #fff; background: rgba(0,0,0,.58);
    border: 1px solid rgba(var(--gf-bone-rgb), .16); clip-path: var(--gf-cut); font: 10px var(--gf-mono); pointer-events: none; }
.gf-map-coordination-panel { position:absolute; right:38px; top:7px; z-index:6; width:230px; padding:9px;
    color:var(--gf-text-2); background:rgba(14,13,12,.96); border:1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm); box-shadow:var(--gf-shadow); font:10px var(--gf-mono); }
.gf-map-coordination-panel[hidden] { display:none; }
.gf-map-coordination-panel > b { display:block; color:var(--gf-text-1); margin-bottom:7px; }
.gf-map-coordination-panel label { display:block; margin:6px 0; }
.gf-map-coordination-panel input[type="text"] { width:100%; box-sizing:border-box; margin-top:3px; padding:4px;
    color:var(--gf-text-1); background:#0c0e12; border:1px solid var(--gf-border); font:10px var(--gf-mono); }
.gf-map-coord-actions { display:flex; gap:5px; }
.gf-map-coord-actions button { padding:4px 6px; color:var(--gf-text-2); background:rgba(var(--gf-bone-rgb), .06);
    border:1px solid var(--gf-border); font:9px var(--gf-mono); cursor:pointer; }
.gf-map-party-list { margin-top:7px; padding-top:6px; border-top:1px solid rgba(var(--gf-bone-rgb), .08); }
/* Route chip - the ONE surface a route speaks through, parked in the Map window
   TITLEBAR (GameWindow.addTitleWidget) and never on the canvas: the docked
   minimap is ~200 px square and the old on-canvas route summary covered a
   quarter of it. Two faces, set by data-mode:
     preview - route glyph, distance/ETA, ▶ Go, ✕ clear
     walking - walking mark (pulsing), remaining distance, ✕ stop
   When the map window is minimized the chip re-parents INTO the floating map
   icon, and when the window is closed outright into .gf-walk-dock, so the stop
   button is reachable for as long as the walk is running. */
.gf-walk-chip {
    display: flex; align-items: center; gap: 5px;
    margin: 0 6px; padding: 1px 3px 1px 6px;
    border: 1px solid var(--gf-grid-cyan, var(--gf-grid-cyan)); clip-path: var(--gf-cut);
    background: rgba(102, 224, 255, .16);
    box-shadow: 0 0 6px rgba(102, 224, 255, .22) inset;
    color: var(--gf-grid-cyan, var(--gf-grid-cyan));
    font: 9px/1.5 var(--gf-mono); white-space: nowrap; cursor: default;
}
.gf-walk-chip[hidden] { display: none !important; }
/* A preview is a proposal, not a live state: it keeps the Grid accent but drops
   the filled, glowing treatment that says "this is happening right now". */
.gf-walk-chip[data-mode="preview"] {
    background: rgba(102, 224, 255, .07);
    box-shadow: none;
    border-color: var(--gf-border-accent, rgba(102, 224, 255, .45));
}
.gf-walk-ico { display: flex; }
.gf-walk-chip[data-mode="walking"] .gf-walk-ico { animation: gf-walk-pulse 1.05s ease-in-out infinite; }
@keyframes gf-walk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
@media (prefers-reduced-motion: reduce) { .gf-walk-ico { animation: none !important; } }
.gf-walk-dist { min-width: 30px; text-align: right; color: var(--gf-text-1, #fff); }
.gf-walk-go, .gf-walk-stop {
    border: 0; background: none; padding: 0 3px; cursor: pointer;
    color: var(--gf-grid-cyan, var(--gf-grid-cyan)); font: 11px/1 var(--gf-mono);
}
.gf-walk-go { font-size: 9px; }
.gf-walk-go[hidden] { display: none !important; }
.gf-walk-go:hover, .gf-walk-go:focus-visible,
.gf-walk-stop:hover, .gf-walk-stop:focus-visible { color: #fff; }
/* Last-resort home: the map window is closed outright, so there is neither a
   titlebar nor a minimized icon to live in. */
.gf-walk-dock {
    position: fixed; z-index: 30; display: flex; align-items: center; height: 38px;
    padding: 0 2px; box-sizing: border-box;
    background: rgba(14, 13, 12, .82);
    border: 1px solid var(--gf-gold, var(--gf-trim)); clip-path: var(--gf-cut);
    box-shadow: 0 3px 10px rgba(0, 0, 0, .5);
}
.gf-walk-dock[hidden] { display: none !important; }

.game-window[data-window-id="minimap"]:not(.gf-map-expanded) .gf-map-search,
.game-window[data-window-id="minimap"]:not(.gf-map-expanded) .gf-map-inspect { max-width: 75%; }

/* ── Universal Ship Controls ────────────────────────────────────────────────
   A compact captain's console used by every vessel. The GameWindow titlebar
   supplies drag/focus behavior; the body owns helm, telegraph, integrity, and
   optional gunnery controls. Per-character position/scale/collapse persistence
   is handled by BoatManager. */
.ship-controls-window {
    min-width: 290px;
    min-height: 0;
    overflow: hidden;
}
.ship-controls-window .game-window-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ship-controls-window .game-window-content {
    max-height: min(82vh, 780px);
    overflow-x: hidden;
    overflow-y: auto;
}
.ship-controls-window.ship-controls-collapsed {
    min-height: 0 !important;
    height: auto !important;
}
.ship-title-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 1px;
    margin-left: 8px;
}
.ship-title-actions [data-panel-scale-label] {
    min-width: 38px;
    font: 9px/1 var(--gf-mono, monospace);
}
.gf-ship-controls {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 9px;
    color: var(--gf-text-2, var(--gf-bone));
    font: 11px/1.25 system-ui, sans-serif;
    user-select: none;
    background:
        radial-gradient(circle at 50% -20%, rgba(var(--gf-trim-rgb), .13), transparent 55%),
        rgba(11, 12, 15, .70);
}
.ship-status-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 5px;
}
.ship-status-row > div {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    min-width: 0;
    padding: 5px 3px;
    border: 1px solid rgba(var(--gf-trim-rgb), .25);
    clip-path: var(--gf-cut-sm);
    background: rgba(var(--gf-bone-rgb), .025);
}
.ship-status-row strong {
    max-width: 100%;
    overflow: hidden;
    color: var(--gf-text-1, var(--gf-text-1));
    font: 700 11px/1.1 var(--gf-mono, monospace);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ship-status-label {
    color: var(--gf-text-3, var(--gf-text-3));
    font: 8px/1 var(--gf-mono, monospace);
    letter-spacing: .08em;
    text-transform: uppercase;
}
.ship-state-line {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    padding: 0 2px;
    color: var(--gf-text-3, var(--gf-text-3));
    font-size: 10px;
}
.ship-state-line [data-ship-state] { color: var(--gf-accent, var(--gf-trim-bright)); }
.ship-control-section {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 9px 7px 7px;
    border: 1px solid rgba(var(--gf-trim-rgb), .28);
    clip-path: var(--gf-cut);
    background: rgba(7,8,11,.38);
}
.ship-section-title {
    position: absolute;
    top: -6px;
    left: 8px;
    padding: 0 5px;
    color: rgba(var(--gf-bone-rgb), .72);
    background: #111217;
    font: 8px/1.2 var(--gf-mono, monospace);
    letter-spacing: .12em;
    text-transform: uppercase;
}
.ship-fine-controls,
.ship-course-controls {
    display: grid;
    grid-template-columns: 1fr .72fr 1fr;
    gap: 4px;
}
.ship-telegraph {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 3px;
    margin-top: 1px;
}
.ship-control-btn {
    min-width: 0;
    min-height: 34px;
    padding: 4px 5px;
    border: 1px solid rgba(var(--gf-trim-rgb), .42);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-2, var(--gf-bone));
    background: linear-gradient(180deg, rgba(var(--gf-bone-rgb), .055), rgba(var(--gf-bone-rgb), .018));
    box-shadow: inset 0 1px 0 rgba(var(--gf-bone-rgb), .035);
    font: 600 10px/1.2 system-ui, sans-serif;
    cursor: pointer;
    touch-action: none;
    transition: border-color .12s, background .12s, color .12s, transform .08s;
}
.ship-control-btn:hover:not(:disabled) {
    border-color: rgba(226,184,91,.78);
    color: var(--gf-text-1, var(--gf-text-1));
    background: rgba(var(--gf-trim-rgb), .12);
}
.ship-control-btn:active:not(:disabled),
.ship-control-btn.active {
    transform: translateY(1px);
    border-color: var(--gf-accent, var(--gf-trim-bright));
    color: #fff4d6;
    background: rgba(var(--gf-trim-rgb), .24);
    box-shadow: inset 0 0 9px rgba(226,184,91,.13);
}
.ship-control-btn:disabled { opacity: .38; cursor: not-allowed; }
.ship-steady { color: #b9c8d9; }
.ship-turnaround { color: #e7c879; }
.ship-stop {
    color: #f1c8bd;
    border-color: rgba(193,88,65,.55);
    background: rgba(128,43,29,.14);
}
.ship-integrity {
    display: flex;
    align-items: center;
    gap: 8px;
}
.ship-integrity-item {
    flex: 1 1 0;
    min-width: 0;
    color: var(--gf-text-3, var(--gf-text-3));
    font-size: 9px;
    text-align: center;
}
.ship-integrity-track {
    height: 5px;
    margin-top: 3px;
    overflow: hidden;
    clip-path: var(--gf-cut-sm);
    background: rgba(var(--gf-bone-rgb), .10);
}
.ship-integrity-track > div { height: 100%; transition: width .2s; }
.ship-ward { flex: 0 0 38px; color: var(--gf-grid-cyan); }
.ship-ward strong { display: block; margin-top: 1px; }
.ship-handling { color: var(--gf-text-3, var(--gf-text-3)); font-size: 8px; text-align: center; }
.ship-ammo,
.ship-fire {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 4px;
}
.ship-fire { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ship-ammo .ship-control-btn { min-height: 30px; }
.ship-ammo .ship-control-btn small {
    display: block;
    margin-top: 1px;
    color: var(--gf-text-3, var(--gf-text-3));
    font-size: 8px;
}
.ship-target-row,
.ship-magazine-actions {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px;
    align-items: stretch;
}
.ship-target-select,
.ship-armory select,
.ship-magazine-actions input {
    min-width: 0;
    padding: 4px 5px;
    border: 1px solid rgba(var(--gf-trim-rgb), .35);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-2, var(--gf-bone));
    background: #17181d;
    font: 10px/1.2 system-ui, sans-serif;
}
.ship-side-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 5px;
}
.ship-side-actions > div {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 3px;
    padding: 5px;
    border: 1px solid rgba(var(--gf-bone-rgb), .08);
    clip-path: var(--gf-cut-sm);
}
.ship-side-actions strong,
.ship-side-actions span { grid-column: 1 / -1; text-transform: capitalize; }
.ship-side-actions span { color: var(--gf-text-3, var(--gf-text-3)); font-size: 9px; }
.ship-cannon-list { display: flex; flex-direction: column; gap: 6px; }
.ship-cannon-side-title {
    margin: 2px 0;
    color: var(--gf-accent, var(--gf-trim-bright));
    font: 8px/1 var(--gf-mono, monospace);
    letter-spacing: .12em;
    text-transform: uppercase;
}
.ship-cannon-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 5px;
    align-items: center;
    padding: 5px;
    border-left: 2px solid rgba(var(--gf-trim-rgb), .55);
    background: rgba(var(--gf-bone-rgb), .025);
}
.ship-cannon-row.empty { border-left-color: rgba(var(--gf-bone-rgb), .18); }
.ship-cannon-row.wrecked { border-left-color: #a2473e; }
.ship-cannon-row.empty em { color: var(--gf-text-3, var(--gf-text-3)); font-size: 9px; }
.ship-cannon-meta { display: flex; min-width: 0; flex-direction: column; gap: 1px; }
.ship-cannon-meta strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ship-cannon-meta span { color: var(--gf-text-3, var(--gf-text-3)); font-size: 8px; }
.ship-cannon-buttons { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 2px; max-width: 155px; }
.ship-cannon-mini {
    min-height: 23px;
    padding: 2px 5px;
    border: 1px solid rgba(var(--gf-trim-rgb), .35);
    clip-path: var(--gf-cut-sm);
    color: var(--gf-text-2, var(--gf-bone));
    background: rgba(var(--gf-bone-rgb), .035);
    font-size: 8px;
    cursor: pointer;
}
.ship-cannon-mini:disabled { opacity: .35; cursor: not-allowed; }
.ship-magazine-panel,
.ship-armory-panel {
    padding: 3px 5px 5px;
    border: 1px solid rgba(var(--gf-trim-rgb), .20);
    clip-path: var(--gf-cut-sm);
}
.ship-magazine-panel summary,
.ship-armory-panel summary { cursor: pointer; color: var(--gf-text-2, var(--gf-bone)); }
.ship-magazine-actions { grid-template-columns: minmax(70px, 1fr) auto auto; margin-top: 5px; }
.ship-magazine-actions label { display: flex; align-items: center; gap: 4px; font-size: 9px; }
.ship-magazine-actions input { width: 50px; }
.ship-armory { display: flex; flex-direction: column; gap: 3px; margin-top: 5px; }
.ship-armory label { display: flex; gap: 5px; align-items: center; }
.ship-armory select { flex: 1; }
.ship-armory small,
.ship-roster small { color: var(--gf-text-3, var(--gf-text-3)); font-size: 8px; }
.ship-roster { margin-top: 6px; }
.ship-roster > div { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-top: 3px; }
.ship-roster-title { color: var(--gf-accent, var(--gf-trim-bright)); font-size: 9px; }
.ship-control-help {
    padding: 0 4px 1px;
    color: var(--gf-text-3, var(--gf-text-3));
    font-size: 9px;
    line-height: 1.35;
    text-align: center;
}
@media (pointer: coarse) {
    .ship-control-btn { min-height: 40px; }
    .ship-title-actions .game-window-titlebar-btn { min-width: 28px; min-height: 26px; }
}

/* ── Global HUD scale (A1) ─────────────────────────────────────────────
   One knob (`--gf-ui-scale`, from the `uiScale` account pref) shrinks/grows
   the whole HUD as if the browser were zoomed, but only over the UI layer —
   the 3D canvas stays full-resolution. `zoom` (not transform:scale) keeps text
   crisp and hit-areas correct. Only top-level, body-appended HUD roots are
   listed so nothing double-scales. The hotbar is intentionally excluded - it
   scales through the --gf-slot token cascade instead (see :root above). */
.game-window,
#hud-info {
    zoom: var(--gf-ui-scale, 1);
}
/* `#paperdoll` used to be listed above and was scaled TWICE: GameWindow's
   constructor reparents it inside a `.game-window`, so it inherited that zoom
   and then applied its own. At a non-default UI scale the panel rendered at
   scale squared, which is most of why the window read as oversized. Its sibling
   `#remote-paperdoll` was never in the list, so the two dolls also disagreed. */

/* ── "You have entered <Town>" banner (Building Content B6) ──────────────────
   A center-top plate the HUD raises when a player crosses into a named
   settlement (server `settlement_entered`), then fades out. pointer-events:none
   so it never eats clicks; below modal dialogs but above the ambient HUD. */
.gf-settlement-banner {
    position: fixed;
    top: 15%;
    left: 50%;
    transform: translate(-50%, -8px);
    z-index: 9500;
    pointer-events: none;
    text-align: center;
    padding: 10px 32px;
    clip-path: var(--gf-cut);
    background: linear-gradient(180deg, rgba(14,14,20,0.86), rgba(14,14,20,0.64));
    border: 1px solid var(--gf-border-accent);
    box-shadow: 0 6px 26px rgba(0,0,0,0.55), inset 0 0 22px rgba(var(--gf-trim-rgb), 0.06);
    opacity: 0;
    transition: opacity 0.5s ease, transform 0.5s ease;
}
.gf-settlement-banner.show {
    opacity: 1;
    transform: translate(-50%, 0);
}
.gf-settlement-banner .gf-sb-kicker {
    font: 500 11px/1.2 system-ui, sans-serif;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--gf-text-1);
    opacity: 0.6;
    margin-bottom: 3px;
}
.gf-settlement-banner .gf-sb-name {
    font: 600 27px/1.15 Georgia, 'Times New Roman', serif;
    color: var(--gf-accent);
    text-shadow: 0 1px 2px rgba(0,0,0,0.7), 0 0 16px rgba(var(--gf-trim-rgb), 0.22);
}
/* Every settlement kind reads in town gold by default, so a size added to the
   ladder needs no rule here. These two are deliberate exceptions. */
/* A metropolis is the top of the size ladder, so its plate carries more weight. */
.gf-settlement-banner[data-kind="metropolis"] { border-color: rgba(var(--gf-trim-rgb), 0.55); }
.gf-settlement-banner[data-kind="metropolis"] .gf-sb-name {
    font-size: 33px;
    text-shadow: 0 1px 2px rgba(0,0,0,0.7), 0 0 22px rgba(var(--gf-trim-rgb), 0.34);
}
/* A landmark reads in the cool Grid accent instead of town gold (matches the map). */
.gf-settlement-banner[data-kind="landmark"] { border-color: rgba(102,224,255,0.42); }
.gf-settlement-banner[data-kind="landmark"] .gf-sb-name {
    color: #8fe3ff;
    text-shadow: 0 1px 2px rgba(0,0,0,0.7), 0 0 16px rgba(102,224,255,0.26);
}

/* A protected REGION announces itself through the same banner (a region is a
   rule, and may also be a place). Protection azure - the color its rectangle
   is drawn in on the map and the minimap - so the banner and the map agree
   about what kind of thing just named itself. */
.gf-settlement-banner[data-kind="region"] { border-color: rgba(109,180,255,0.45); }
.gf-settlement-banner[data-kind="region"] .gf-sb-name {
    color: #b3d8ff;
    text-shadow: 0 1px 2px rgba(0,0,0,0.7), 0 0 16px rgba(109,180,255,0.26);
}

/* ── Zoom indicator (transient camera-zoom readout) ──
   Flashes while the third-person zoom changes and fades out. Shows the current
   orbit distance, a Grid-cyan fill across the usable range, and a gold tick at
   the player's default zoom (set in Options → Camera) so they can dial back to
   it. pointer-events:none so it never eats input; scales with the global HUD. */
.gf-zoom-indicator {
    position: fixed;
    left: 50%;
    bottom: 20%;
    transform: translateX(-50%) scale(var(--gf-ui-scale, 1));
    transform-origin: center bottom;
    display: flex;
    flex-direction: column;
    gap: 6px;
    align-items: stretch;
    width: 158px;
    padding: 8px 12px 10px;
    background: rgba(14, 13, 12, 0.82);
    border: 1px solid rgba(var(--gf-trim-rgb), 0.34);
    clip-path: var(--gf-cut);
    box-shadow: 0 2px 14px rgba(0, 0, 0, 0.5);
    color: var(--gf-bone, var(--gf-bone));
    pointer-events: none;
    z-index: 60;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.18s ease, visibility 0.18s ease;
}
.gf-zoom-indicator.gf-visible { opacity: 1; visibility: visible; }
.gf-zoom-label {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 14px;
    font: 600 12px/1 'Segoe UI', system-ui, sans-serif;
}
.gf-zoom-title {
    color: #8a8578;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.14em;
}
.gf-zoom-val {
    color: var(--gf-grid-cyan);
    font-variant-numeric: tabular-nums;
}
.gf-zoom-at-default .gf-zoom-val { color: #d9bd66; }
.gf-zoom-track {
    position: relative;
    height: 5px;
    clip-path: var(--gf-cut-sm);
    background: rgba(var(--gf-bone-rgb), 0.14);
}
.gf-zoom-fill {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    clip-path: var(--gf-cut-sm);
    background: linear-gradient(90deg, rgba(102, 224, 255, 0.5), var(--gf-grid-cyan));
    transition: width 0.12s ease;
}
.gf-zoom-default {
    position: absolute;
    top: -3px;
    width: 2px;
    height: 11px;
    margin-left: -1px;
    background: var(--gf-trim);
    /* square */
    transition: left 0.12s ease;
}

/* ── Drag-box multi-select (dragSelectHealthBars) ──
   The rubber-band rectangle drawn while dragging a selection box over the world.
   Grid-cyan (the one cool accent) over a faint fill; pointer-events:none so it
   never eats the drag. Created/positioned/removed imperatively in main.js. */
.gf-marquee {
    position: fixed;
    z-index: var(--gf-z-sheet-chrome);
    pointer-events: none;
    border: 1px solid rgba(102,224,255,0.9);
    background: rgba(102,224,255,0.12);
    box-shadow: 0 0 10px rgba(102,224,255,0.25), inset 0 0 12px rgba(102,224,255,0.08);
    clip-path: var(--gf-cut-sm);
}

/* ── Object handles (UO Ctrl+Shift): clickable overhead pills ── */
.gf-obj-handles {
    position: fixed;
    inset: 0;
    z-index: 900;   /* above the 3D canvas, below UI windows/menus (nextZIndex ≥1000) */
    pointer-events: none;   /* only the pills capture the mouse */
    overflow: hidden;
}
.gf-obj-handle {
    position: absolute;
    transform: translate(-50%, -100%);
    pointer-events: auto;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 2px 7px 3px;
    min-width: 26px;
    max-width: 180px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow), 0 0 0 1px rgba(0,0,0,0.4);
    user-select: none;
    white-space: nowrap;
}
.gf-obj-handle:hover { border-color: var(--gf-border-accent); }
.gf-obj-handle.gf-oh-corpse { opacity: 0.85; }
.gf-oh-label {
    font: 600 11px/1.1 var(--gf-mono);
    text-shadow: 0 1px 2px rgba(0,0,0,0.9);
    max-width: 168px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.gf-oh-bar {
    width: 100%;
    height: 3px;
    background: rgba(0,0,0,0.6);
    clip-path: var(--gf-cut-sm);
    overflow: hidden;
}
.gf-oh-bar > i {
    display: block;
    height: 100%;
    width: 100%;
    background: #3ec46d;
}

/* The while-held category picker. */
.gf-oh-menu {
    position: fixed;
    left: 14px;
    top: 96px;
    z-index: 950;   /* above the pills, below UI windows/menus */
    pointer-events: auto;
    padding: 8px 10px 9px;
    background: var(--gf-panel-bg-strong);
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut);
    box-shadow: var(--gf-shadow-strong);
    user-select: none;
}
.gf-oh-menu-title {
    font: 700 11px/1 var(--gf-mono);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--gf-gold);
    margin-bottom: 6px;
}
.gf-oh-menu-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 3px 2px;
    font: 500 12px/1.1 var(--gf-mono);
    color: var(--gf-text-3);
    cursor: pointer;
}
.gf-oh-menu-row.gf-on { color: var(--gf-text-1); }
.gf-oh-check {
    flex: 0 0 auto;
    width: 13px;
    height: 13px;
    border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm);
    background: rgba(0,0,0,0.4);
    position: relative;
}
.gf-oh-menu-row.gf-on .gf-oh-check {
    border-color: var(--gf-gold);
    background: rgba(var(--gf-trim-rgb), 0.22);
}
.gf-oh-menu-row.gf-on .gf-oh-check::after {
    content: '';
    position: absolute;
    left: 3px;
    top: 0px;
    width: 4px;
    height: 8px;
    border: solid var(--gf-gold);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* ═══════════════════════════════════════════════════════════════
   Mobile adaptation layer
   Desktop remains the canonical HUD. Every override is gated by the existing
   runtime class so desktop geometry and saved layouts are never rewritten.
   ═══════════════════════════════════════════════════════════════ */
body.mobile-controls-active .mobile-controls-overlay {
    --mobile-control-scale: 1;
    --mobile-control-opacity: .9;
    --mobile-safe-top: max(8px, env(safe-area-inset-top));
    --mobile-safe-right: max(8px, env(safe-area-inset-right));
    --mobile-safe-bottom: max(8px, env(safe-area-inset-bottom));
    --mobile-safe-left: max(8px, env(safe-area-inset-left));
}
body.mobile-controls-active {
    overscroll-behavior: none;
    touch-action: none;
}
/* #gf-hud-edit-banner is deliberately NOT in this list any more. It carries the
   only Done button HUD edit mode has, so hiding it on touch turned "Edit HUD
   Layout" into a one-way trip: the mode disabled hotbar activation, showed
   nothing, and the keybind that could have turned it off had no default key. */
body.mobile-controls-active #gf-help-btn,
body.mobile-controls-active #gf-submit-btn,
body.mobile-controls-active #gf-pets-btn {
    display: none !important;
}

/* Camera gestures can begin across the world. Movement claims only the lower
   left thumb region, so objects in the upper-left remain directly tappable. */
body.mobile-controls-active .mobile-swipe-zone {
    inset: 0;
    width: auto;
    z-index: 0;
}
body.mobile-controls-active .mobile-move-zone {
    top: auto;
    left: 0;
    bottom: 0;
    width: 42vw;
    height: min(72dvh, 340px);
    z-index: 1;
}
body.mobile-controls-active .mobile-joystick-container {
    bottom: calc(var(--mobile-safe-bottom) + 24px);
    left: calc(var(--mobile-safe-left) + 24px);
    opacity: calc(var(--mobile-control-opacity) * .55);
    transform: scale(calc(var(--mobile-control-scale) * .92));
}
body.mobile-controls-active .mobile-joystick-container.active {
    opacity: var(--mobile-control-opacity);
    transform: scale(var(--mobile-control-scale));
}
.mobile-center-reticle {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 18px;
    height: 18px;
    transform: translate(-50%, -50%);
    border: 1px solid rgba(var(--gf-bone-rgb), .52);
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(14,13,12,.6), inset 0 0 6px rgba(14,13,12,.55);
    pointer-events: none;
    opacity: .7;
}
.mobile-center-reticle::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 3px;
    height: 3px;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: var(--gf-accent);
}
.gf-mobile-target-arrow {
    position: fixed;
    width: 0;
    height: 0;
    z-index: var(--gf-z-touch-marker);
    pointer-events: none;
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
    border-bottom: 18px solid var(--gf-accent);
    filter: drop-shadow(0 0 4px rgba(14,13,12,.9));
    opacity: 0;
    transition: opacity .12s ease;
    transform-origin: 50% 50%;
}

/* Compact combat dock: one dominant contextual action, jump, and secondary
   toggles. This replaces the original 280px arc without changing desktop UI. */
body.mobile-controls-active .mobile-action-buttons {
    bottom: calc(var(--mobile-safe-bottom) + 12px);
    right: calc(var(--mobile-safe-right) + 8px);
    width: 216px;
    height: 156px;
    transform: scale(var(--mobile-control-scale));
    transform-origin: bottom right;
    opacity: var(--mobile-control-opacity);
}
.mobile-btn .gf-ic {
    width: 23px;
    height: 23px;
    color: currentColor;
}
.mobile-btn-label {
    margin-top: 3px;
    font-size: 8px;
    line-height: 1;
    letter-spacing: .06em;
}
.mobile-btn-primary {
    bottom: 4px;
    right: 4px;
    width: 74px;
    height: 74px;
    color: var(--gf-text-1);
    background: rgba(122, 68, 28, .86);
    border-color: rgba(217, 182, 94, .85);
    box-shadow: 0 7px 18px rgba(0,0,0,.68), 0 0 0 1px rgba(226,98,42,.18) inset;
}
.mobile-btn-primary[data-mode="attack"] {
    background: rgba(148, 42, 30, .88);
    border-color: rgba(255, 122, 60, .9);
}
.mobile-btn-primary[data-mode="gather"] {
    background: rgba(118, 82, 28, .88);
    border-color: rgba(217, 182, 94, .9);
}
.mobile-btn-primary[data-mode="target"] {
    background: rgba(54, 48, 38, .88);
}
body.mobile-controls-active .mobile-btn-jump {
    bottom: 82px;
    right: 8px;
    width: 58px;
    height: 58px;
    font-size: 10px;
    background: rgba(78, 52, 32, .84);
    border-color: rgba(var(--gf-trim-rgb), .72);
    color: #f1dfbd;
}
body.mobile-controls-active .mobile-btn-jump:active {
    background-color: rgba(133, 82, 36, .9);
    border-color: rgba(217,182,94,.95);
}
body.mobile-controls-active .mobile-btn-war {
    bottom: 94px;
    right: 78px;
    width: 48px;
    height: 48px;
}
body.mobile-controls-active .mobile-btn-target {
    bottom: 46px;
    right: 84px;
    width: 48px;
    height: 48px;
}
body.mobile-controls-active .mobile-btn-sprint {
    bottom: 36px;
    right: 146px;
    width: 48px;
    height: 48px;
}
.mobile-btn.active {
    color: #fff;
    border-color: rgba(255,122,60,.9);
    background: rgba(132,54,24,.88);
    box-shadow: 0 0 12px rgba(226,98,42,.42), inset 0 1px 0 rgba(var(--gf-bone-rgb), .12);
}

/* Utility row lives at the top-right, away from both thumb zones. */
.mobile-utility-buttons {
    position: absolute;
    top: var(--mobile-safe-top);
    right: var(--mobile-safe-right);
    z-index: var(--gf-z-touch-cluster);
    display: flex;
    gap: 7px;
    pointer-events: none;
    opacity: var(--mobile-control-opacity);
    transform: scale(var(--mobile-control-scale));
    transform-origin: top right;
}
.mobile-utility-buttons .mobile-btn {
    position: relative;
    inset: auto;
    width: 44px;
    height: 44px;
    font-size: 7px;
}
.mobile-utility-buttons .mobile-btn .gf-ic {
    width: 18px;
    height: 18px;
}

/* Per-device left-handed mirror. The HUD stays top-left; the utility row stays
   top-right, while the two high-frequency thumb clusters trade sides. */
body.mobile-controls-active.mobile-left-handed .mobile-move-zone {
    left: auto;
    right: 0;
}
body.mobile-controls-active.mobile-left-handed .mobile-joystick-container {
    left: auto;
    right: calc(var(--mobile-safe-right) + 24px);
}
body.mobile-controls-active.mobile-left-handed .mobile-action-buttons {
    right: auto;
    left: calc(var(--mobile-safe-left) + 8px);
    transform-origin: bottom left;
}
body.mobile-controls-active.mobile-left-handed #chat-area {
    left: auto !important;
    right: max(10px, env(safe-area-inset-right)) !important;
}

/* Compact mobile HUD: race, stance, and currency stay; diagnostics disappear. */
body.mobile-controls-active #hud-info {
    top: max(8px, env(safe-area-inset-top));
    left: max(8px, env(safe-area-inset-left));
    min-width: 0;
    padding: 6px 9px;
    line-height: 1.25;
    background: rgba(14,13,12,.68);
    border-color: rgba(var(--gf-trim-rgb), .28);
    transform: none;
    zoom: 1;
}
body.mobile-controls-active #hud-header { display: block; }
body.mobile-controls-active #hud-class {
    font-size: 11px;
    letter-spacing: .05em;
}
body.mobile-controls-active #hud-toggle,
body.mobile-controls-active #hud-details {
    display: none !important;
}
body.mobile-controls-active #hud-war-mode,
body.mobile-controls-active #hud-armed-special,
body.mobile-controls-active #hud-gold {
    display: inline-block;
    margin: 3px 7px 0 0;
    font-size: 10px;
}
body.mobile-controls-active #targeting-prompt {
    bottom: calc(max(8px, env(safe-area-inset-bottom)) + 168px) !important;
    max-width: min(64vw, 420px);
}
body.mobile-controls-active #gf-target-frame,
body.mobile-controls-active #gf-target-frame.gf-tf-moved {
    top: calc(max(8px, env(safe-area-inset-top)) + 48px) !important;
    left: 50% !important;
    max-width: min(44vw, 280px);
    transform: translateX(-50%) translateY(-6px) !important;
}
body.mobile-controls-active #gf-target-frame.gf-tf-on,
body.mobile-controls-active #gf-target-frame.gf-tf-moved.gf-tf-on {
    transform: translateX(-50%) translateY(0) !important;
}

/* Chat is a passive toast during play and expands into a keyboard-aware
   composer only when the mobile Chat utility is pressed. */
body.mobile-controls-active #chat-area {
    left: max(10px, env(safe-area-inset-left)) !important;
    bottom: calc(max(10px, env(safe-area-inset-bottom)) + 150px) !important;
    width: min(44vw, 340px) !important;
    height: auto !important;
    max-width: none;
    font-size: 11px;
    background: linear-gradient(180deg, transparent, rgba(14,13,12,.62));
    border: 0;
    pointer-events: none;
    box-shadow: none;
}
body.mobile-controls-active #chat-log {
    height: 58px !important;
    padding: 5px 7px;
    overflow: hidden;
    mask-image: linear-gradient(to bottom, transparent 0, #000 30%, #000 100%);
}
body.mobile-controls-active #chat-log .chat-line {
    font-size: 11px;
    line-height: 1.25;
}
body.mobile-controls-active #chat-resize,
body.mobile-controls-active #chat-drag,
body.mobile-controls-active #chat-box,
body.mobile-controls-active .chat-tabs {
    display: none !important;
}
body.mobile-controls-active.mobile-chat-expanded #chat-area {
    left: max(8px, env(safe-area-inset-left)) !important;
    right: max(8px, env(safe-area-inset-right)) !important;
    bottom: calc(max(8px, env(safe-area-inset-bottom)) + var(--gf-keyboard-offset, 0px)) !important;
    width: auto !important;
    padding-top: 3px;
    background: rgba(14,13,12,.94);
    border: 1px solid rgba(var(--gf-trim-rgb), .45);
    pointer-events: all;
    z-index: var(--gf-z-sheet-chrome);
}
body.mobile-controls-active.mobile-chat-expanded #chat-log {
    height: min(32dvh, 180px) !important;
    overflow-y: auto;
    mask-image: none;
}
body.mobile-controls-active.mobile-chat-expanded #chat-box {
    display: flex !important;
}
body.mobile-controls-active.mobile-chat-expanded .chat-tabs {
    display: flex !important;
    overflow-x: auto;
}
body.mobile-controls-active.mobile-chat-expanded #chat-input {
    min-height: 42px;
    font-size: 16px;
}
body.mobile-controls-active.mobile-chat-expanded .mobile-action-buttons,
body.mobile-controls-active.mobile-chat-expanded .mobile-move-zone,
body.mobile-controls-active.mobile-chat-expanded .mobile-joystick-container,
body.mobile-controls-active.mobile-chat-expanded .gf-bar {
    visibility: hidden !important;
}
body.mobile-controls-active.mobile-chat-expanded .mobile-center-reticle,
body.mobile-controls-active.mobile-sheet-open .mobile-center-reticle {
    display: none;
}

/* Major panels become fixed sheets. Tiny stat bars and the minimap retain
   their normal floating forms. */
body.mobile-controls-active .game-window.gf-mobile-sheet {
    position: fixed !important;
    top: max(8px, env(safe-area-inset-top)) !important;
    right: max(6px, env(safe-area-inset-right)) !important;
    bottom: max(6px, env(safe-area-inset-bottom)) !important;
    left: max(6px, env(safe-area-inset-left)) !important;
    width: auto !important;
    height: auto !important;
    min-width: 0 !important;
    min-height: 0 !important;
    max-width: none !important;
    max-height: none !important;
    transform: none !important;
    zoom: 1 !important;
    clip-path: var(--gf-cut-top);
    z-index: var(--gf-z-sheet) !important;
}
/* A mobile sheet is a different form factor, so a CHROMELESS window gets its
   titlebar back: a full-width 48px grab strip plus the window's name. The corner
   tray simply overlays its right end, and .game-window-close is already sized to
   42px there by the shared rule, so one tray serves both forms. */
body.mobile-controls-active .game-window.gf-mobile-sheet.gf-window-chromeless
    > .game-window-titlebar {
    display: flex;
}
body.mobile-controls-active .game-window.gf-mobile-sheet .game-window-titlebar {
    min-height: 48px;
    padding: 8px 12px;
    cursor: default;
}
body.mobile-controls-active .game-window.gf-mobile-sheet .game-window-title {
    font-size: 12px;
}
body.mobile-controls-active .game-window.gf-mobile-sheet .game-window-resize {
    display: none !important;
}
body.mobile-controls-active .game-window[data-window-id^="statbar:"] {
    transform: none;
    max-width: min(62vw, 260px);
}
/* Options sheet: the sidebar shell stacks vertically - the global search goes
   full-width on top and the section nav becomes a horizontal scroll strip
   with 38px+ touch targets (active mark moves to the bottom edge). */
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-shell {
    display: flex;
    flex-direction: column;
    min-height: 0;
}
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-side {
    flex: 0 0 auto;
    padding: 8px 8px 0;
    border-right: none;
    border-bottom: 1px solid var(--gf-border-soft);
}
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-search {
    min-height: 40px;
    font-size: 16px;   /* ≥16px so iOS doesn't zoom the sheet on focus */
}
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-nav {
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    padding-bottom: 4px;
}
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-nav::-webkit-scrollbar {
    display: none;
}
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-nav-item {
    flex: 0 0 auto;
    min-height: 38px;
    padding: 8px 12px;
    border-left: none;
    border-bottom: 2px solid transparent;
    clip-path: var(--gf-cut-top);
}
body.mobile-controls-active .game-window.gf-mobile-sheet .gf-options-nav-item.gf-active {
    border-bottom-color: var(--gf-accent);
}
body.mobile-controls-active.mobile-sheet-open .mobile-action-buttons,
body.mobile-controls-active.mobile-sheet-open .mobile-move-zone,
body.mobile-controls-active.mobile-sheet-open .mobile-joystick-container,
body.mobile-controls-active.mobile-sheet-open .gf-bar {
    visibility: hidden !important;
}
body.mobile-controls-active .game-window-close {
    width: 42px;
    height: 42px;
    font-size: 26px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* A sheet is full-screen, so the bag fills the width with as many fixed
   sockets as fit - the cell stays one size, the column count moves. */
body.mobile-controls-active #inventory-panel { --inv-cell: 56px; --inv-gap: 5px; }
body.mobile-controls-active .inv-body { padding: 10px; }
body.mobile-controls-active .inv-bag-col { flex: 1 1 auto; min-width: 0; }
body.mobile-controls-active .inv-bag-grid {
    grid-template-columns: repeat(auto-fill, var(--inv-cell));
    justify-content: start;
}
body.mobile-controls-active .inv-qol-btn {
    min-height: 38px;
    padding: 5px 11px;
}
body.mobile-controls-active .inv-weight-row { padding: 10px 12px; }
body.mobile-controls-active .ctx-menu {
    left: max(8px, env(safe-area-inset-left)) !important;
    right: max(8px, env(safe-area-inset-right)) !important;
    top: auto !important;
    bottom: max(8px, env(safe-area-inset-bottom)) !important;
    width: auto;
    max-height: min(72dvh, 420px);
    overflow-y: auto;
    padding: 8px;
    clip-path: var(--gf-cut);
    z-index: var(--gf-z-popover) !important;
}
body.mobile-controls-active .ctx-item {
    min-height: 46px;
    display: flex;
    align-items: center;
    padding: 10px 14px;
    font-size: 14px;
}

/* Assign picker becomes a bottom sheet on touch layouts (assign_picker.js
   skips its inline anchor coords when mobile controls are active, so these
   win without !important). Tap-assign via the slot menu is the mobile path. */
body.mobile-controls-active .gf-assign-pop {
    left: 0; right: 0; top: auto; bottom: 0;
    width: auto;
    max-height: 60vh;
    clip-path: var(--gf-cut-top);
    padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
}
body.mobile-controls-active .gf-assign-row { min-height: 42px; font-size: 13px; }

/* One pageable six-slot action bar. Saved desktop geometry stays intact. */
body.mobile-controls-active .gf-bar {
    display: none !important;
}
body.mobile-controls-active .gf-bar.gf-bar-keyactive {
    display: flex !important;
    position: fixed !important;
    left: 50% !important;
    top: auto !important;
    bottom: calc(max(8px, env(safe-area-inset-bottom)) + 8px) !important;
    transform: translateX(-50%) !important;
    max-width: calc(100vw - 440px);
    padding: 5px;
    gap: 4px;
    z-index: calc(var(--gf-z-hud-bar) + 1);
    --gf-slot: 48px;
    --gf-slot-gap: 4px;
}
body.mobile-controls-active .gf-bar.gf-bar-keyactive .gf-slot-key {
    display: none;
}
.gf-mobile-page-btn {
    display: none;
    width: 30px;
    min-width: 30px;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    background: rgba(14,13,12,.82);
    color: var(--gf-accent);
    font: 700 18px/1 var(--gf-mono);
    cursor: pointer;
}
body.mobile-controls-active .gf-bar.gf-bar-keyactive .gf-mobile-page-btn {
    display: block;
}
body.mobile-controls-active .gf-slot.gf-mobile-page-hidden {
    display: none !important;
}
.gf-mobile-page-indicator {
    display: none;
    position: absolute;
    left: 50%;
    bottom: -8px;
    transform: translateX(-50%);
    font: 700 8px/1 var(--gf-mono);
    color: var(--gf-text-2);
}
body.mobile-controls-active .gf-mobile-page-indicator {
    display: block;
}

/* First-use controls card and a dismissible portrait recommendation. */
/* Blocking gates, so they outrank the pause menu rather than sitting just above
   the window band the way their old 10100 did. */
.mobile-tutorial,
.mobile-orientation-hint {
    position: fixed;
    inset: 0;
    z-index: var(--gf-z-dialog);
    display: none;
    align-items: center;
    justify-content: center;
    padding: max(18px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right))
             max(18px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left));
    background: rgba(10,9,8,0.52);
    pointer-events: all;
}
body.mobile-controls-active .mobile-tutorial.open {
    display: flex;
}
.mobile-tutorial-card,
.mobile-orientation-card {
    width: min(520px, 92vw);
    padding: 20px;
    border: 1px solid rgba(var(--gf-trim-rgb), .55);
    clip-path: var(--gf-cut);
    background: linear-gradient(180deg, rgba(35,28,20,.98), rgba(14,13,12,.98));
    box-shadow: var(--gf-shadow-strong);
    color: var(--gf-text-1);
}
.mobile-tutorial-title,
.mobile-orientation-title {
    color: var(--gf-accent);
    font: 600 18px/1.2 var(--gf-font-display);
    margin-bottom: 12px;
}
.mobile-tutorial-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0,1fr));
    gap: 9px 16px;
    color: var(--gf-text-2);
    font-size: 12px;
    line-height: 1.35;
}
.mobile-tutorial-grid strong { color: var(--gf-text-1); }
.mobile-tutorial-actions,
.mobile-orientation-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 16px;
}
.mobile-tutorial-actions button,
.mobile-orientation-actions button {
    min-height: 40px;
    padding: 7px 16px;
    touch-action: manipulation;
    cursor: pointer;
}

@media (orientation: portrait) and (max-width: 700px) {
    body.mobile-controls-active:not(.mobile-orientation-dismissed) .mobile-orientation-hint {
        display: flex;
    }
    body.mobile-controls-active #inventory-panel { --inv-cell: 52px; }
    body.mobile-controls-active .inv-bag-grid {
        grid-template-columns: repeat(auto-fill, var(--inv-cell));
        justify-content: center;
    }
}
@media (max-width: 760px), (max-height: 420px) {
    body.mobile-controls-active .gf-bar.gf-bar-keyactive {
        max-width: calc(100vw - 330px);
        --gf-slot: 44px;
    }
    body.mobile-controls-active .mobile-action-buttons {
        transform: scale(calc(var(--mobile-control-scale) * .9));
    }
}
/* On a narrow screen the bottom belongs to the two thumb clusters (joystick,
   action dock), so the action bar moves to the top strip - which it shares with
   the Chat/Bag/Menu row. It used to stay CENTERED there at max-width:100vw-180px:
   on a 390px phone that is x 90-300 against a utility row at x 236-382, and
   because the bar is z-index 41 against the touch overlay's old 4, the bar won
   every contested tap and those buttons were simply dead.

   Anchor it LEFT and cap it so the two never meet - a geometric guarantee, not a
   stacking one. The cap tracks --mobile-control-scale because the utility row is
   `transform: scale(...)` off `transform-origin: top right`: its 146px grows to
   183px at the 1.25 setting, and a flat cap would re-collide there.

     390px @1.00 -> bar 8-220, row 236-382 (16px clear)
     360px @1.00 -> bar 8-198, row 206-352  (8px clear)
     360px @1.25 -> bar 8-156, row 170-352 (14px clear) */
@media (max-width: 620px) {
    body.mobile-controls-active .gf-bar.gf-bar-keyactive {
        top: max(8px, env(safe-area-inset-top)) !important;
        bottom: auto !important;
        left: max(8px, env(safe-area-inset-left)) !important;
        transform: none !important;
        max-width: calc(100vw - 170px * var(--mobile-control-scale, 1));
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE V2: 4-PHASE COMBAT, SURVIVAL & CAMERA ENHANCEMENTS
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Phase 3: Emergency Survival Belt ── */
.mobile-survival-belt {
    position: fixed;
    bottom: calc(max(18px, env(safe-area-inset-bottom)) + 74px);
    right: calc(max(18px, env(safe-area-inset-right)) + 190px);
    display: flex;
    gap: 12px;
    z-index: var(--gf-z-touch-cluster);
    pointer-events: all;
    user-select: none;
    -webkit-user-select: none;
}
.mobile-left-handed .mobile-survival-belt {
    right: auto;
    left: calc(max(18px, env(safe-area-inset-left)) + 190px);
}

/* The `+190px` above is a LANDSCAPE constant, and there was no portrait override
   at all. On a 390px phone it puts the two 54px slots at x 62-182 - directly on
   the joystick's resting disc at x 32-152 - and being `pointer-events: all`
   above the move zone, the Heal and Cure buttons ATE the joystick's touches. On
   a 360px phone the two rects coincide exactly. The left-handed mirror has the
   identical bug on the other side, because the joystick mirrors too.

   Stack the belt on the same edge as the action dock, immediately above it, so
   it clears the joystick on BOTH axes rather than by luck. The dock is
   `transform: scale(...)` off `transform-origin: bottom right`, so its 156px
   height grows with the size setting and this offset has to track it.

     390px @1.00 -> belt x 262-382, y(from bottom) 190-244
                    joystick x 32-152, y 37-147

   This block must stay AFTER the two rules above: same specificity, so the
   later one wins, and placed earlier the override would silently lose. */
@media (orientation: portrait) and (max-width: 700px) {
    .mobile-survival-belt {
        right: calc(max(18px, env(safe-area-inset-right)) + 8px);
        left: auto;
        bottom: calc(max(18px, env(safe-area-inset-bottom))
                     + 172px * var(--mobile-control-scale, 1));
    }
    .mobile-left-handed .mobile-survival-belt {
        left: calc(max(18px, env(safe-area-inset-left)) + 8px);
        right: auto;
    }
}
.mobile-survival-slot {
    position: relative;
    width: 54px;
    height: 54px;
    clip-path: var(--gf-cut);
    background: linear-gradient(145deg, rgba(32, 28, 22, 0.88), rgba(16, 14, 12, 0.94));
    border: 1.5px solid rgba(212, 175, 55, 0.45);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.6), inset 0 1px 1px rgba(var(--gf-bone-rgb), 0.15);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    touch-action: manipulation;
    transition: transform 0.1s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.mobile-survival-slot:active {
    transform: scale(0.92);
}
.mobile-survival-slot.slot-empty {
    opacity: 0.45;
    border-color: rgba(120, 110, 95, 0.3);
    filter: grayscale(0.6);
}
.mobile-survival-icon {
    font-size: 20px;
    line-height: 1;
    margin-bottom: 2px;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8));
}
.mobile-survival-label {
    font-size: 9px;
    font-weight: 700;
    font-family: var(--gf-font-display);
    letter-spacing: 0.5px;
    color: var(--gf-text-2, var(--gf-text-2));
    text-transform: uppercase;
    text-shadow: 0 1px 2px #000;
}
.mobile-survival-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    clip-path: var(--gf-cut);
    background: linear-gradient(135deg, #e67e22, #d35400);
    border: 1px solid rgba(var(--gf-bone-rgb), 0.35);
    color: #fff;
    font-size: 10px;
    font-weight: 800;
    font-family: var(--gf-font-mono);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 6px rgba(0,0,0,0.7);
}
.mobile-survival-slot-cure .mobile-survival-badge {
    background: linear-gradient(135deg, #27ae60, #1e8449);
}

/* Crisis Pulse Animations */
@keyframes mobile-crisis-red {
    0% {
        box-shadow: 0 0 8px rgba(231, 76, 60, 0.6), inset 0 0 6px rgba(231, 76, 60, 0.4);
        border-color: rgba(231, 76, 60, 0.8);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 22px rgba(231, 76, 60, 0.95), 0 0 36px rgba(192, 57, 43, 0.7), inset 0 0 12px rgba(231, 76, 60, 0.8);
        border-color: #ff4d4d;
        transform: scale(1.1);
    }
    100% {
        box-shadow: 0 0 8px rgba(231, 76, 60, 0.6), inset 0 0 6px rgba(231, 76, 60, 0.4);
        border-color: rgba(231, 76, 60, 0.8);
        transform: scale(1);
    }
}
@keyframes mobile-crisis-poison {
    0% {
        box-shadow: 0 0 8px rgba(46, 204, 113, 0.6), inset 0 0 6px rgba(46, 204, 113, 0.4);
        border-color: rgba(46, 204, 113, 0.8);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 22px rgba(46, 204, 113, 0.95), 0 0 36px rgba(39, 174, 96, 0.7), inset 0 0 12px rgba(46, 204, 113, 0.8);
        border-color: #2ecc71;
        transform: scale(1.1);
    }
    100% {
        box-shadow: 0 0 8px rgba(46, 204, 113, 0.6), inset 0 0 6px rgba(46, 204, 113, 0.4);
        border-color: rgba(46, 204, 113, 0.8);
        transform: scale(1);
    }
}
.mobile-survival-slot.mobile-crisis-hp {
    animation: mobile-crisis-red 0.75s infinite ease-in-out;
}
.mobile-survival-slot.mobile-crisis-poison {
    animation: mobile-crisis-poison 0.75s infinite ease-in-out;
}

/* ── Phase 2: Nearby Threat Carousel ── */
.mobile-threat-carousel {
    position: fixed;
    top: calc(max(14px, env(safe-area-inset-top)) + 52px);
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    z-index: var(--gf-z-touch-cluster);
    pointer-events: all;
    user-select: none;
    -webkit-user-select: none;
    max-width: 96vw;
    overflow-x: auto;
    padding: 4px 6px;
}
.mobile-threat-card {
    min-width: 116px;
    max-width: 130px;
    padding: 6px 9px;
    clip-path: var(--gf-cut);
    background: linear-gradient(160deg, rgba(28, 24, 20, 0.92), rgba(14, 12, 10, 0.96));
    border: 1.5px solid rgba(160, 140, 110, 0.3);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
    cursor: pointer;
    touch-action: manipulation;
    transition: transform 0.12s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.mobile-threat-card:active {
    transform: scale(0.95);
}
.mobile-threat-card.active-target {
    border-color: var(--gf-accent, var(--gf-trim));
    box-shadow: 0 0 14px rgba(var(--gf-trim-rgb), 0.6), inset 0 0 6px rgba(var(--gf-trim-rgb), 0.3);
    transform: translateY(-2px);
}
.mobile-threat-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 4px;
}
.mobile-threat-name {
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 82px;
    text-shadow: 0 1px 2px #000;
}
.mobile-threat-dist {
    font-size: 9.5px;
    font-family: var(--gf-font-mono);
    color: var(--gf-text-3, var(--gf-text-3));
}
.mobile-threat-hp-bar {
    width: 100%;
    height: 5px;
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.6);
    border: 0.5px solid rgba(var(--gf-bone-rgb), 0.1);
    overflow: hidden;
}
.mobile-threat-hp-fill {
    height: 100%;
    width: 100%;
    clip-path: var(--gf-cut-sm);
    background: linear-gradient(90deg, #2ecc71, #27ae60);
    transition: width 0.2s ease, background 0.2s ease;
}
.mobile-threat-hp-fill.hp-mid {
    background: linear-gradient(90deg, #f1c40f, #e67e22);
}
.mobile-threat-hp-fill.hp-low {
    background: linear-gradient(90deg, #e74c3c, #c0392b);
}

/* ── Phase 4: Proximity Quick-Loot Pill ── */
.mobile-quick-loot-pill {
    position: fixed;
    bottom: calc(max(18px, env(safe-area-inset-bottom)) + 144px);
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 18px;
    clip-path: var(--gf-cut);
    background: linear-gradient(135deg, rgba(45, 36, 22, 0.95), rgba(20, 16, 12, 0.98));
    border: 1.5px solid rgba(var(--gf-trim-rgb), 0.7);
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.7), 0 0 14px rgba(var(--gf-trim-rgb), 0.35);
    color: var(--gf-accent, var(--gf-trim));
    font-family: var(--gf-font-display);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.5px;
    z-index: var(--gf-z-touch-cluster);
    cursor: pointer;
    touch-action: manipulation;
    pointer-events: all;
    user-select: none;
    -webkit-user-select: none;
    animation: mobile-loot-glow 2s infinite ease-in-out;
}
.mobile-quick-loot-pill:active {
    transform: translateX(-50%) scale(0.94);
}
@keyframes mobile-loot-glow {
    0%, 100% { box-shadow: 0 4px 18px rgba(0, 0, 0, 0.7), 0 0 10px rgba(var(--gf-trim-rgb), 0.3); }
    50% { box-shadow: 0 4px 22px rgba(0, 0, 0, 0.8), 0 0 20px rgba(var(--gf-trim-rgb), 0.65); }
}
.mobile-loot-icon {
    font-size: 16px;
    filter: drop-shadow(0 1px 3px rgba(0,0,0,0.8));
}
.mobile-loot-text {
    white-space: nowrap;
    text-shadow: 0 1px 3px #000;
}

/* ── Phase 4: Off-Screen Target Direction Tether ── */
.mobile-target-tether {
    position: fixed;
    z-index: var(--gf-z-touch-marker);
    pointer-events: none;
    user-select: none;
    -webkit-user-select: none;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    transition: opacity 0.15s ease;
}
.mobile-tether-arrow {
    width: 0;
    height: 0;
    border-left: 11px solid transparent;
    border-right: 11px solid transparent;
    border-bottom: 22px solid #e74c3c;
    filter: drop-shadow(0 0 8px rgba(231, 76, 60, 0.8));
}

/* Swipe Badge Indicator on Target Button */
.mobile-swipe-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    font-size: 9px;
    color: rgba(var(--gf-trim-rgb), 0.8);
    font-family: var(--gf-font-mono);
    pointer-events: none;
}

/* ── Options Menu: Mobile Pane ── */
.mobile-options-pane {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 16px 20px;
    overflow-y: auto;
}

/* ═══════════════════════════════════════════════════════════════
   Guild + Quests panels, and the opt-in quest tracker.

   All three surfaces used to be hand-rolled inline `cssText` with hardcoded
   hex - the guild panel borrowed the literal-colored `.friends-*` block and
   the quest surfaces carried no CSS at all. Everything below is tokens only,
   ash-gold, per VISUAL_IDENTITY.md: Grid cyan is the Grid's alone and means
   "players cannot see this", so it never appears on a player-facing panel.
   (Friends was the last holdout and has since been rebuilt here too, so the
   `.friends-*` block those styles were borrowed from is gone.)
   ═══════════════════════════════════════════════════════════════ */

/* ── Shared bits ── */
.gf-panel-empty {
    color: var(--gf-text-3); font-size: 12px; font-style: italic;
    padding: 10px 2px; line-height: 1.5;
}
/* A tab count pip (contracts waiting, wars declared on you). */
.gf-tab-badge {
    display: inline-block; margin-left: 5px; min-width: 15px; padding: 0 4px;
    clip-path: var(--gf-cut); background: var(--gf-accent-deep); color: #1a1408;
    font-size: 9px; font-weight: 700; line-height: 14px; text-align: center;
    vertical-align: 1px;
}
/* An action the viewer's rank does not reach. Shown rather than hidden, so a
   member can see what an officer gets - the tooltip carries the reason. */
.gf-btn-locked, .gf-btn:disabled {
    opacity: 0.4; cursor: not-allowed; filter: saturate(0.4);
}
.gf-btn-locked:hover, .gf-btn:disabled:hover {
    background: var(--gf-face);
    transform: none;
}

/* ── Guild panel ───────────────────────────────────────────────── */
.gf-guild { display: flex; flex-direction: column; height: 100%; min-height: 0; }

/* Identity + tier + vault, above the tabs and visible on every one of them:
   the Prestige tier gates the hall, the Cauldrons, the vendors and the war
   radius, so it is the spine of the panel rather than a tab of its own. */
.gf-guild-head {
    flex: 0 0 auto; padding: 9px 11px 8px;
    border-bottom: 1px solid var(--gf-border-soft);
    background: var(--gf-panel-bg-soft);
}
.gf-guild-head:empty { display: none; }
.gf-guild-id { display: flex; align-items: baseline; gap: 7px; }
.gf-guild-name {
    font-size: 15px; font-weight: 700; color: var(--gf-accent);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-guild-tag { font-size: 12px; color: var(--gf-gold); letter-spacing: 0.5px; }
.gf-guild-tier {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: 8px; margin-top: 4px;
}
.gf-guild-tier-name {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px;
    color: var(--gf-gold-bright);
}
.gf-guild-tier-meta { font-size: 10px; color: var(--gf-text-3); }
.gf-guild-bar {
    height: 4px; margin-top: 4px; clip-path: var(--gf-cut-sm);
    background: rgba(0,0,0,0.45); overflow: hidden;
}
.gf-guild-bar-fill {
    height: 100%; clip-path: var(--gf-cut-sm);
    background: linear-gradient(90deg, var(--gf-accent-deep), var(--gf-accent));
}
.gf-guild-headmeta {
    display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px;
    font-size: 10px; color: var(--gf-text-3);
}
.gf-guild-you { color: var(--gf-text-2); }
.gf-guild-purse { color: var(--gf-gold); margin-left: auto; }

.gf-guild-body, .gf-quests-body {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px 10px 12px;
}

.gf-guild-section { margin-bottom: 12px; }
.gf-guild-section-title {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.7px;
    color: var(--gf-gold); padding-bottom: 3px; margin-bottom: 5px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-guild-note {
    font-size: 11px; color: var(--gf-text-3); line-height: 1.5; margin: 5px 0 2px;
}
.gf-guild-form { display: flex; gap: 6px; align-items: center; margin: 5px 0; }
.gf-guild-form .gf-input { flex: 1 1 auto; min-width: 0; }
.gf-guild-taginput { flex: 0 0 66px !important; max-width: 66px; }

.gf-guild-row {
    display: flex; align-items: center; gap: 7px; padding: 5px 4px;
    border-bottom: 1px solid var(--gf-border-soft); font-size: 12px;
}
.gf-guild-row:last-child { border-bottom: none; }
.gf-guild-rowname {
    flex: 1 1 auto; min-width: 0; color: var(--gf-text-1);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-guild-rowmeta { flex: 0 0 auto; font-size: 10px; color: var(--gf-text-3); }
/* The leader-granted title. Italic parchment, never a rank chip: a rank is a
   permission (and wears the gf-rank-* colors), a title is what the guild calls
   this person. Shrinks before the name does - the name is what you scan for. */
.gf-guild-rowtitle {
    flex: 0 1 auto; min-width: 0; font-size: 11px; font-style: italic;
    color: var(--gf-accent-2, var(--gf-accent-2));
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-guild-rowacts { display: flex; gap: 4px; flex: 0 0 auto; align-items: center; }
.gf-guild-titleinput { flex: 0 1 150px !important; max-width: 150px; min-width: 0; }
.gf-guild-pending { opacity: 0.85; }

.gf-guild-dot {
    width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
}
.gf-guild-dot.is-online { background: var(--gf-success); }
.gf-guild-dot.is-offline { background: var(--gf-text-4); }

/* Rank is carried by weight and warmth, never by hue alone - the roster has to
   read for a color-blind player too. */
.gf-rank-leader  { color: var(--gf-accent); font-weight: 700; }
.gf-rank-officer { color: var(--gf-gold); }
.gf-rank-member  { color: var(--gf-text-3); }
.gf-war-ledger { color: var(--gf-danger); font-variant-numeric: tabular-nums; }

.gf-guild-stat {
    display: flex; justify-content: space-between; gap: 10px;
    padding: 3px 2px; font-size: 12px;
}
.gf-guild-stat-label { color: var(--gf-text-3); }
.gf-guild-stat-value { color: var(--gf-text-1); text-align: right; }
.gf-guild-hallname {
    font-size: 13px; color: var(--gf-accent); font-weight: 600; margin-bottom: 4px;
}
.gf-guild-charter.is-locked { opacity: 0.55; }
.gf-guild-charter-state { flex: 0 0 auto; font-size: 10px; }
.gf-guild-charter-state.is-locked { color: var(--gf-text-4); }
.gf-guild-charter-state.is-short  { color: var(--gf-danger); }
.gf-guild-charter-state.is-ok     { color: var(--gf-success); }
.gf-station-producing     { color: var(--gf-success); }
.gf-station-paused,
.gf-station-idle          { color: var(--gf-text-3); }
.gf-station-needs_reagents,
.gf-station-output_full   { color: var(--gf-stamina); }

/* ── Friends window ────────────────────────────────────────────────
   Head (counts + the two ways to add) / tab strip / filter / scrolling body.
   Tokens only, ash-gold. Status reads by WORD as well as by color, the same
   rule the guild roster follows: "Offline" and "3d ago" are text, and the dot
   only reinforces them. */
.gf-friends { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.gf-friends-head {
    flex: 0 0 auto; padding: 8px 10px 6px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-friends-count {
    font-size: 10px; letter-spacing: .7px; text-transform: uppercase;
    color: var(--gf-text-3); margin-bottom: 6px;
    font-variant-numeric: tabular-nums;
}
.gf-friends-add { display: flex; gap: 5px; align-items: center; }
.gf-friends-add .gf-input { flex: 1 1 auto; min-width: 0; }

.gf-friends-body {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px 10px 12px;
}
.gf-friends-section { margin-bottom: 10px; }
.gf-friends-section-title {
    font-size: 10px; letter-spacing: .7px; text-transform: uppercase;
    color: var(--gf-gold); padding-bottom: 3px; margin-bottom: 4px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-friends-note {
    font-size: 11px; color: var(--gf-text-3); line-height: 1.5; padding: 4px 2px;
}

.gf-friends-row {
    display: flex; align-items: center; gap: 7px;
    padding: 5px 2px; font-size: 12px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-friends-row:last-child { border-bottom: none; }
.gf-friends-row-pending { opacity: 0.9; }
.gf-friends-rowmain { flex: 1 1 auto; min-width: 0; }
.gf-friends-name {
    display: block; color: var(--gf-text-1);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The player's own private label for someone. Quiet on purpose - it is an aid
   to recognition, not a second name. */
.gf-friends-rownote {
    display: block; font-size: 10px; color: var(--gf-text-3);
    font-style: italic; margin-top: 1px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-friends-meta {
    flex: 0 0 auto; font-size: 11px; color: var(--gf-text-3); white-space: nowrap;
}
.gf-friends-rowacts { display: flex; gap: 4px; flex: 0 0 auto; align-items: center; }

.gf-friends-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.gf-friends-dot.is-online  { background: var(--gf-success); }
.gf-friends-dot.is-offline { background: var(--gf-text-4); }
/* A request is neither: hollow, so "waiting" never reads as "online". */
.gf-friends-dot.is-pending {
    background: transparent; border: 1px solid var(--gf-gold);
    width: 6px; height: 6px;
}

.gf-friends-noteedit {
    display: flex; gap: 5px; align-items: center;
    padding: 6px 2px; border-top: 1px solid var(--gf-border-soft);
}
.gf-friends-noteedit .gf-input { flex: 1 1 auto; min-width: 0; }

/* ── Party window (party_panel.js) ─────────────────────────────── */
.gf-party { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.gf-party-head {
    flex: 0 0 auto; padding: 8px 10px 6px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-party-count {
    font-size: 10px; letter-spacing: .7px; text-transform: uppercase;
    color: var(--gf-text-3); margin-bottom: 6px;
    font-variant-numeric: tabular-nums;
}
.gf-party-add { display: flex; gap: 5px; align-items: center; }
.gf-party-add .gf-input { flex: 1 1 auto; min-width: 0; }
.gf-party-err {
    font-size: 11px; color: var(--gf-danger); margin-top: 5px; line-height: 1.4;
}

.gf-party-body {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px 10px 12px;
}

.gf-party-row {
    position: relative; overflow: hidden;
    display: flex; align-items: center; gap: 7px;
    padding: 6px 5px; font-size: 12px; clip-path: var(--gf-cut-sm);
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-party-row:last-child { border-bottom: none; }
.gf-party-row.is-self .gf-party-name { font-weight: 600; }
/* The HP fill sits BEHIND the line, like the floating frames draw it, so the
   row stays one readable object instead of a label beside a gauge. */
.gf-party-fill {
    position: absolute; inset: 0; width: 100%; transform-origin: left center;
    opacity: .22; pointer-events: none;
    transition: transform .18s ease, background-color .18s ease;
}
.gf-party-line {
    position: relative; flex: 1 1 auto; min-width: 0;
    display: flex; align-items: baseline; gap: 6px;
}
.gf-party-star { flex: 0 0 auto; width: 9px; color: var(--gf-gold); font-size: 11px; }
.gf-party-name {
    flex: 1 1 auto; min-width: 0; color: var(--gf-text-1);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gf-party-hp {
    flex: 0 0 auto; font-size: 11px; color: var(--gf-text-3);
    font-variant-numeric: tabular-nums;
}
/* Offline / hidden / ghost / away. A word, not only a color - the roster has
   to work for a color-blind player too. */
.gf-party-chip {
    flex: 0 0 auto; font-size: 10px; letter-spacing: .4px; text-transform: uppercase;
    color: var(--gf-text-3); border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm); padding: 0 4px;
}
.gf-party-rowacts {
    position: relative; display: flex; gap: 4px; flex: 0 0 auto; align-items: center;
}

.gf-party-foot {
    flex: 0 0 auto; padding: 7px 10px 9px;
    border-top: 1px solid var(--gf-border-soft);
    display: flex; flex-direction: column; gap: 6px;
}
.gf-party-say { display: flex; gap: 5px; align-items: center; }
.gf-party-say .gf-input { flex: 1 1 auto; min-width: 0; }
.gf-party-leave { align-self: flex-start; }

/* ── Contract board (both windows draw it) ─────────────────────── */
.gf-contract-row {
    display: flex; align-items: flex-start; gap: 9px; padding: 8px 4px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-contract-row:last-child { border-bottom: none; }
.gf-contract-main { flex: 1 1 auto; min-width: 0; }
.gf-contract-name { font-size: 12px; color: var(--gf-text-1); font-weight: 600; }
.gf-contract-summary {
    font-size: 11px; color: var(--gf-text-3); line-height: 1.45; margin-top: 2px;
}
.gf-contract-reward { font-size: 11px; color: var(--gf-gold); margin-top: 3px; }
.gf-contract-side { flex: 0 0 auto; display: flex; align-items: center; }
.gf-contract-progress { font-size: 10px; color: var(--gf-text-3); }

/* ── Quests window ─────────────────────────────────────────────── */
.gf-quests { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.gf-quests-options {
    display: flex; flex-wrap: wrap; gap: 12px; padding: 2px 2px 7px;
    border-bottom: 1px solid var(--gf-border-soft); margin-bottom: 7px;
}
.gf-quests-opt {
    display: flex; align-items: center; gap: 5px; font-size: 11px;
    color: var(--gf-text-3); cursor: pointer; user-select: none;
}
.gf-quests-opt input { accent-color: var(--gf-accent-deep); cursor: pointer; margin: 0; }
.gf-quests-sec {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.7px;
    color: var(--gf-gold); margin-top: 6px; padding-bottom: 2px;
    border-bottom: 1px solid var(--gf-border-soft);
}

.gf-quest-card {
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft);
    clip-path: var(--gf-cut-sm); padding: 8px 10px; margin-bottom: 8px;
}
/* A tracked quest is the one on screen; say so where it is pinned FROM. */
.gf-quest-card.is-pinned { border-color: var(--gf-border-accent); }
.gf-quest-card.is-chapter { border-color: rgba(var(--gf-trim-rgb), 0.45); }
.gf-quest-card.is-resting { opacity: 0.7; border-style: dashed; }
.gf-quest-head { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.gf-quest-name {
    flex: 1 1 auto; min-width: 0; font-size: 13px; font-weight: 700;
    color: var(--gf-accent);
}
.gf-quest-badge {
    flex: 0 0 auto; font-size: 9px; text-transform: uppercase;
    letter-spacing: 0.5px; color: var(--gf-text-3);
}
.gf-status-active, .gf-status-offered { color: var(--gf-gold-bright); }
.gf-status-complete, .gf-status-turned_in { color: var(--gf-success); }
.gf-status-failed { color: var(--gf-danger); }
.gf-status-paused { color: var(--gf-text-3); }
.gf-quest-pin {
    flex: 0 0 auto; background: none; border: none; cursor: pointer;
    color: var(--gf-text-4); font-size: 14px; padding: 0 2px; line-height: 1;
}
.gf-quest-pin:hover { color: var(--gf-accent); }
.gf-quest-pin.is-on { color: var(--gf-accent); }
.gf-quest-summary {
    font-size: 11px; color: var(--gf-text-3); line-height: 1.45; margin-bottom: 5px;
}
.gf-quest-reward { font-size: 11px; color: var(--gf-success); margin-bottom: 5px; }
.gf-quest-obj {
    display: flex; align-items: center; gap: 6px; font-size: 11px;
    color: var(--gf-text-2); margin: 3px 0;
}
.gf-quest-obj.is-done { color: var(--gf-success); }
.gf-quest-objbar {
    flex: 0 0 44px; height: 3px; clip-path: var(--gf-cut-sm);
    background: rgba(0,0,0,0.45); overflow: hidden;
}
.gf-quest-objbar-fill { height: 100%; background: var(--gf-accent-deep); }
.gf-quest-where { flex: 0 0 auto; font-size: 10px; color: var(--gf-text-4); }
.gf-quest-prompt {
    font-size: 11px; color: var(--gf-text-2); font-style: italic;
    line-height: 1.4; margin: 6px 0 3px;
}
.gf-quest-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.gf-quest-subhead {
    margin: 10px 0 4px; color: var(--gf-gold); font-size: 10px;
    text-transform: uppercase; letter-spacing: 0.6px;
}
.gf-quest-donerow {
    display: flex; align-items: center; gap: 8px; padding: 5px 4px;
    border-bottom: 1px solid var(--gf-border-soft);
}
.gf-quest-donerow:last-child { border-bottom: none; }
.gf-quest-donerow .gf-quest-name { font-size: 12px; color: var(--gf-text-2); }
.gf-quest-offers { padding: 10px; overflow-y: auto; height: 100%; box-sizing: border-box; }

/* ── The tracker HUD ───────────────────────────────────────────── */
/* Opt-in: `display:none` until the player pins something. Left edge under the
   awareness/tracking HUDs' left twin, matching their 120px top. */
.gf-quest-tracker {
    position: fixed; top: 120px; left: 12px; z-index: 58;
    min-width: 172px; max-width: 268px;
    background: var(--gf-panel-bg);    border: 1px solid var(--gf-border-accent); clip-path: var(--gf-cut-sm);
    box-shadow: var(--gf-shadow); padding: 5px 8px 6px;
    font-size: 11px; color: var(--gf-text-2);
}
.gf-quest-tracker-head {
    display: flex; align-items: center; gap: 5px; margin-bottom: 3px;
}
.gf-quest-tracker-title {
    flex: 1 1 auto; font-size: 9px; text-transform: uppercase;
    letter-spacing: 0.7px; color: var(--gf-gold);
}
.gf-quest-tracker-btn, .gf-quest-tracker-unpin {
    background: none; border: none; cursor: pointer; padding: 0 3px;
    color: var(--gf-text-4); font-size: 11px; line-height: 1; font-family: inherit;
}
.gf-quest-tracker-btn:hover, .gf-quest-tracker-unpin:hover { color: var(--gf-accent); }
.gf-quest-tracker-quest + .gf-quest-tracker-quest {
    margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--gf-border-soft);
}
.gf-quest-tracker-name {
    display: flex; align-items: center; gap: 5px;
    font-size: 11px; color: var(--gf-accent); margin-bottom: 1px;
}
.gf-quest-tracker-name > :first-child { flex: 1 1 auto; }
.gf-quest-tracker-obj {
    display: flex; align-items: center; gap: 5px;
    margin-left: 7px; font-size: 10px; color: var(--gf-text-2);
}
.gf-quest-tracker-obj.is-done { color: var(--gf-success); }
.gf-quest-tracker-dir { white-space: nowrap; margin-left: auto; }
.gf-quest-tracker-arrow {
    display: inline-block; width: 10px; text-align: center; color: var(--gf-accent);
}
.gf-quest-tracker-meta { color: var(--gf-text-4); font-size: 9px; }
.gf-quest-tracker-choices {
    display: flex; flex-wrap: wrap; gap: 4px; margin: 3px 0 2px 7px;
}

/* ── House Placement Tool: the folio of plans ───────────────────────────────
   Gold-trim-over-glass like every other player window - deliberately NOT the
   cyan staff chrome, because a placement tool is an ordinary player good. */
.gf-house-catalog { display: flex; flex-direction: column; height: 100%;
    gap: 8px; padding: 10px 12px; color: var(--gf-text-1); }
.gf-hc-head { display: flex; align-items: center; justify-content: space-between;
    gap: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--gf-border-soft); }
.gf-hc-purse { font-weight: 600; color: var(--gf-stamina); }
.gf-hc-style { font-size: 11px; color: var(--gf-text-3);
    text-transform: uppercase; letter-spacing: .06em; }
.gf-hc-style-sel { margin-left: 6px; background: var(--gf-panel-bg-soft);
    color: var(--gf-text-1); border: 1px solid var(--gf-border);
    clip-path: var(--gf-cut-sm); padding: 3px 6px; font: inherit; text-transform: none; }
.gf-hc-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column;
    gap: 2px; }
.gf-hc-tier { margin: 8px 0 2px; font-size: 10px; letter-spacing: .1em;
    text-transform: uppercase; color: var(--gf-text-4); }
.gf-hc-row { display: grid; grid-template-columns: 1fr auto auto; gap: 10px;
    align-items: center; width: 100%; text-align: left; padding: 7px 9px;
    background: var(--gf-slot-bg); border: 1px solid transparent;
    clip-path: var(--gf-cut-sm); color: var(--gf-text-1); font: inherit; cursor: pointer; }
.gf-hc-row:hover:not(:disabled) { background: var(--gf-slot-bg-hover);
    border-color: var(--gf-border-accent); }
.gf-hc-row.gf-hc-on { border-color: var(--gf-border-accent);
    background: var(--gf-slot-bg-hover); }
/* Dimmed as a HINT, never inert: the row stays clickable and the server names
   the price and the purse. `not-allowed` here would advertise the dead button
   this class used to sit on. */
.gf-hc-row.gf-hc-poor { opacity: .55; }
.gf-hc-name { font-weight: 600; }
.gf-hc-size { color: var(--gf-text-3); font-size: 12px; }
.gf-hc-price { color: var(--gf-stamina); font-variant-numeric: tabular-nums; }
.gf-hc-foot { font-size: 11px; color: var(--gf-text-3); line-height: 1.4;
    padding-top: 6px; border-top: 1px solid var(--gf-border-soft); }
.gf-hc-empty { color: var(--gf-text-3); padding: 16px; text-align: center; }

/* ══════════════════════════════════════════════════════════════════════════
   HUD LAYOUT EDIT - per-surface placement and hiding
   See js/ui/hud_layout.js. Armed by the escape menu's "Edit HUD Layout" (and
   the hudEdit keybind); nothing here is reachable during ordinary play.
   ══════════════════════════════════════════════════════════════════════════ */

/* Placed surfaces are reparented HERE, out of their authored containers.
   The layer carries NO transform, deliberately: the action dock is
   `transform: scale(var(--mobile-control-scale))`, and a transformed ancestor
   makes a `position: fixed` descendant resolve against that box instead of the
   viewport - so a dragged button would land offset by however far the size
   slider is from 1, with every number in the drag maths correct and nothing
   thrown. */
.mobile-hud-layer {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: var(--gf-z-touch-drag);
}
/* Precise, not a blanket `.gf-hud-placed` rule: the joystick container is
   decoration with `pointer-events: none`, and granting it pointer events would
   put an invisible 120px disc over the movement zone it sits on. */
.mobile-hud-layer button { pointer-events: auto; }

/* A placed surface no longer wants whatever transform positioned it inside its
   old container (the action bar is centered with translateX(-50%)). */
.gf-hud-placed { transform: none !important; }
/* ...except a touch button, which inherited a SCALE from the cluster it just
   left. Neutralising that too would pin every placed button to size 1.0 however
   the size slider is set, and nothing would say so. Higher specificity than the
   rule above, so it wins despite both being !important. */
.mobile-hud-layer .mobile-btn.gf-hud-placed,
.mobile-hud-layer .mobile-survival-slot.gf-hud-placed {
    transform: scale(var(--mobile-control-scale, 1)) !important;
}
.mobile-hud-layer .mobile-btn.gf-hud-placed:active,
.mobile-hud-layer .mobile-survival-slot.gf-hud-placed:active {
    transform: scale(calc(var(--mobile-control-scale, 1) * .9)) !important;
}

.gf-hud-hidden { display: none !important; }

/* "+ Action Bar" is desktop-only: on touch every bar but the key-active one is
   display:none, so adding another one produces nothing visible. */
body.mobile-controls-active #gf-hud-edit-banner .gf-eb-addbar { display: none; }
/* The help line loses to the buttons and the Hidden list on a narrow screen. */
@media (max-width: 620px) {
    #gf-hud-edit-banner .gf-eb-help { display: none; }
}

/* Edit mode: everything placeable says so, and the two surfaces that are
   normally conditional have to be on screen or they cannot be placed at all -
   the loot pill only exists next to a corpse and the threat carousel only
   during a fight. */
body.gf-hud-edit .gf-hud-movable {
    outline: 1px dashed var(--gf-accent);
    outline-offset: 2px;
}
body.gf-hud-edit .mobile-quick-loot-pill,
body.gf-hud-edit .mobile-threat-carousel,
body.gf-hud-edit .mobile-survival-belt {
    display: flex !important;
}
/* The joystick rests at 0.5 opacity and takes no pointer events, so in edit
   mode it is both invisible and un-grabbable without this. */
body.gf-hud-edit .mobile-joystick-container {
    pointer-events: auto;
    opacity: var(--mobile-control-opacity, .9);
}

.gf-hud-hide {
    position: absolute;
    top: -7px; right: -7px;
    width: 20px; height: 20px;
    min-width: 20px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--gf-border-accent);
    background: var(--gf-danger, #da3633);
    color: #fff;
    font: 700 11px/1 var(--gf-mono), monospace;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    pointer-events: auto;
    touch-action: manipulation;
    z-index: 2;
}

/* On touch the banner IS the mode: it carries Done, Reset and the list of
   hidden surfaces, and it is the only way back out. Full-width at the bottom,
   clear of the top strip the action bar and utility row share. */
@media (max-width: 620px) {
    #gf-hud-edit-banner {
        top: auto;
        bottom: calc(max(8px, env(safe-area-inset-bottom)) + 8px);
        left: max(8px, env(safe-area-inset-left));
        right: max(8px, env(safe-area-inset-right));
        transform: none;
        flex-wrap: wrap;
        justify-content: center;
        gap: 8px;
    }
    #gf-hud-edit-banner button { min-height: 40px; }
}

.gf-eb-hidden-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
.gf-eb-restore {
    padding: 3px 9px;
    min-height: 32px;
    border: 1px solid var(--gf-border-accent);
    clip-path: var(--gf-cut-sm);
    background: rgba(14,13,12,.82);
    color: var(--gf-text-2);
    font: 600 11px/1 var(--gf-mono), monospace;
    cursor: pointer;
    touch-action: manipulation;
}
.gf-eb-restore:hover { color: var(--gf-accent); border-color: var(--gf-accent); }

/* ── Support desk ─────────────────────────────────────────────────────────
   Player Help window (ash-gold player chrome) + the 🛎 HUD button, and the
   staff Pages queue (staff chrome via markStaffWindow). server/game/support.py */
#gf-page-btn {
    position: fixed;
    right: 122px;
    bottom: 14px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--gf-panel-bg);
    border: 1px solid var(--gf-border-accent);
    color: var(--gf-accent);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    z-index: 50;
    box-shadow: var(--gf-shadow);
    transition: transform 0.12s, box-shadow 0.12s;
}
#gf-page-btn:hover {
    transform: scale(1.12);
    box-shadow: var(--gf-shadow), 0 0 10px var(--gf-accent-glow);
}
#gf-page-btn.has-unread::after {
    content: attr(data-unread);
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    clip-path: var(--gf-cut);
    background: var(--gf-ember);
    color: #1a0f08;
    font-size: 10px;
    font-weight: 700;
    line-height: 15px;
    text-align: center;
}
body.mobile-controls-active #gf-page-btn { display: none !important; }

.gf-help-desk { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--gf-text-2); }
.gf-help-tabs { display: flex; gap: 4px; padding: 6px 8px 0; border-bottom: 1px solid var(--gf-border-soft); }
.gf-help-tab {
    background: none; border: 1px solid transparent; border-bottom: none;
    clip-path: var(--gf-cut-top);
    color: var(--gf-text-3); padding: 6px 12px; cursor: pointer; font-size: 13px;
}
.gf-help-tab.on { color: var(--gf-ash-gold); border-color: var(--gf-border); background: var(--gf-slot-bg); }
.gf-help-body { flex: 1; overflow-y: auto; padding: 10px 12px; min-height: 0; }
.gf-help-status { padding: 6px 12px; font-size: 12px; color: var(--gf-text-3); min-height: 1.2em; border-top: 1px solid var(--gf-border-soft); }
.gf-help-status.bad { color: var(--gf-danger); }
.gf-help-lead { margin: 0 0 10px; color: var(--gf-text-1); font-size: 13px; }
.gf-help-note { margin: 4px 0 8px; font-size: 12px; color: var(--gf-text-3); }
.gf-help-fine { margin: 6px 0; font-size: 11px; color: var(--gf-text-4); }
.gf-help-group { margin: 10px 0 6px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--gf-ash-gold); }
.gf-help-cats { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 6px; }
.gf-help-cat {
    display: flex; gap: 8px; align-items: flex-start; text-align: left;
    padding: 8px; clip-path: var(--gf-cut); cursor: pointer;
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft); color: var(--gf-text-2);
}
.gf-help-cat:hover { background: var(--gf-slot-bg-hover); border-color: var(--gf-border-accent); }
.gf-help-cat-icon { font-size: 16px; line-height: 1.2; }
.gf-help-cat-text { display: flex; flex-direction: column; gap: 2px; }
.gf-help-cat-label { color: var(--gf-text-1); font-size: 13px; }
.gf-help-cat-blurb { font-size: 11px; color: var(--gf-text-3); }
.gf-help-form-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; }
.gf-help-form-title { color: var(--gf-text-1); font-size: 14px; flex: 1; }
.gf-help-label { display: block; margin: 8px 0 4px; font-size: 12px; color: var(--gf-text-2); }
.gf-help-input, .gf-help-text {
    width: 100%; box-sizing: border-box; padding: 6px 8px;
    background: rgba(0,0,0,0.3); border: 1px solid var(--gf-border); clip-path: var(--gf-cut-sm);
    color: var(--gf-text-1); font: inherit; font-size: 13px; resize: vertical;
}
.gf-help-input:focus, .gf-help-text:focus { outline: none; border-color: var(--gf-border-accent); }
.gf-help-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 10px; }
.gf-help-btn {
    padding: 6px 14px; clip-path: var(--gf-cut-sm); cursor: pointer; font-size: 13px;
    background: var(--gf-accent-deep); border: 1px solid var(--gf-accent); color: #1a1208;
}
.gf-help-btn:hover { background: var(--gf-accent); }
.gf-help-btn.ghost { background: none; border-color: var(--gf-border); color: var(--gf-text-2); }
.gf-help-link { background: none; border: none; color: var(--gf-ash-gold); cursor: pointer; padding: 4px 0; font-size: 12px; }
.gf-help-list { display: flex; flex-direction: column; gap: 5px; }
.gf-help-row {
    text-align: left; padding: 7px 9px; clip-path: var(--gf-cut-sm); cursor: pointer;
    background: var(--gf-slot-bg); border: 1px solid var(--gf-border-soft); color: var(--gf-text-2);
}
.gf-help-row:hover { background: var(--gf-slot-bg-hover); }
.gf-help-row.active { border-left: 2px solid var(--gf-ash-gold); }
.gf-help-row-top, .gf-help-row-bot { display: flex; align-items: center; gap: 8px; }
.gf-help-row-bot { margin-top: 3px; font-size: 11px; }
.gf-help-row-seq { color: var(--gf-text-3); font-variant-numeric: tabular-nums; }
.gf-help-row-cat { color: var(--gf-text-1); flex: 1; }
.gf-help-row-when { color: var(--gf-text-4); margin-left: auto; }
.gf-help-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--gf-ember); }
.gf-help-chip { padding: 1px 7px; clip-path: var(--gf-cut-sm); font-size: 11px; border: 1px solid var(--gf-border); color: var(--gf-text-2); }
.gf-help-chip.st-waiting_player { border-color: var(--gf-ember); color: var(--gf-ember-hot); }
.gf-help-chip.st-claimed { border-color: var(--gf-border-accent); color: var(--gf-accent); }
.gf-help-chip.st-resolved { border-color: rgba(111,200,116,0.5); color: var(--gf-success); }
.gf-help-thread { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
.gf-help-msg { padding: 6px 9px; clip-path: var(--gf-cut-sm); background: var(--gf-slot-bg); max-width: 92%; }
.gf-help-msg.mine { align-self: flex-end; }
.gf-help-msg.staff { align-self: flex-start; border-left: 2px solid var(--gf-ash-gold); }
.gf-help-msg-meta { font-size: 11px; color: var(--gf-text-3); margin-bottom: 2px; }
.gf-help-msg-body { color: var(--gf-text-1); font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
.gf-help-rate { margin-top: 10px; }
.gf-help-stars { display: flex; gap: 2px; }
.gf-help-star { background: none; border: none; font-size: 22px; color: var(--gf-text-4); cursor: pointer; padding: 0 2px; }
.gf-help-star.lit, .gf-help-star:hover { color: var(--gf-gold-bright); }

.gf-pages { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--gf-staff-text); font-size: 12px; }
.gf-pages-toolbar { display: flex; gap: 4px; padding: 6px 8px; flex-wrap: wrap; align-items: center; border-bottom: 1px solid var(--gf-staff-border); }
.gf-pages-btn {
    padding: 3px 9px; clip-path: var(--gf-cut-sm); cursor: pointer; font-size: 12px;
    background: var(--gf-staff-bg-soft); border: 1px solid var(--gf-staff-border); color: var(--gf-staff-text);
}
.gf-pages-btn:hover { background: var(--gf-staff-bg-hover); }
.gf-pages-btn:disabled { opacity: 0.4; cursor: default; }
.gf-pages-btn.on, .gf-pages-btn.primary { background: rgba(var(--gf-grid-cyan-rgb), 0.28); border-color: var(--gf-grid-cyan); color: #fff; }
.gf-pages-btn.good { border-color: rgba(111,200,116,0.7); color: #c9f3cb; }
.gf-pages-btn.ghost { background: none; }
.gf-pages-select, .gf-pages-search {
    background: rgba(0,0,0,0.35); border: 1px solid var(--gf-staff-border); color: var(--gf-staff-text);
    clip-path: var(--gf-cut-sm); padding: 3px 6px; font-size: 12px;
}
.gf-pages-search { flex: 1; min-width: 90px; }
.gf-pages-body { flex: 1; overflow-y: auto; padding: 8px; min-height: 0; }
.gf-pages-foot { padding: 5px 8px; border-top: 1px solid var(--gf-staff-border); color: var(--gf-text-3); font-variant-numeric: tabular-nums; }
.gf-pages-foot.bad { color: #ffb4ad; }
.gf-pages-empty { color: var(--gf-text-3); text-align: center; margin: 24px 0; }
.gf-pages-list { display: flex; flex-direction: column; gap: 5px; }
.gf-pages-row {
    position: relative; padding: 6px 8px 6px 10px; clip-path: var(--gf-cut-sm); cursor: pointer;
    background: var(--gf-staff-bg-soft); border: 1px solid transparent; border-left: 3px solid var(--gf-staff-border);
}
.gf-pages-row:hover { background: var(--gf-staff-bg-hover); }
.gf-pages-row.kind-report { border-left-color: rgba(224,82,74,0.75); }
.gf-pages-row.warn { border-color: rgba(217,182,94,0.55); }
.gf-pages-row.alarm { border-color: rgba(224,82,74,0.8); }
.gf-pages-row-a, .gf-pages-row-b { display: flex; align-items: center; gap: 8px; padding-right: 64px; }
.gf-pages-row-b { margin-top: 2px; color: var(--gf-text-2); }
.gf-pages-seq { color: var(--gf-text-3); font-variant-numeric: tabular-nums; }
.gf-pages-kind { font-size: 10px; padding: 0 5px; clip-path: var(--gf-cut-sm); border: 1px solid var(--gf-staff-border); }
.gf-pages-kind.kind-report { border-color: rgba(224,82,74,0.7); color: #ffb4ad; }
.gf-pages-cat { color: #fff; }
.gf-pages-prio { font-size: 10px; text-transform: uppercase; color: var(--gf-gold-bright); }
.gf-pages-prio.pr-urgent { color: #ff8c84; }
.gf-pages-age { margin-left: auto; color: var(--gf-text-3); font-variant-numeric: tabular-nums; }
.gf-pages-row.warn .gf-pages-age { color: var(--gf-gold-bright); }
.gf-pages-row.alarm .gf-pages-age { color: #ff8c84; font-weight: 700; }
.gf-pages-who { flex: 1; }
.gf-pages-state { color: var(--gf-text-3); }
.gf-pages-snip { margin-top: 3px; color: var(--gf-text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 64px; }
.gf-pages-row-claim { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); }
.gf-pages-detail-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.gf-pages-detail-title { flex: 1; color: #fff; font-size: 13px; }
.gf-pages-status { padding: 1px 8px; clip-path: var(--gf-cut-sm); border: 1px solid var(--gf-staff-border); }
.gf-pages-status.st-open { border-color: var(--gf-gold-bright); color: var(--gf-gold-bright); }
.gf-pages-meta { display: flex; flex-direction: column; gap: 3px; margin-bottom: 8px; }
.gf-pages-meta-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.gf-pages-meta-k { width: 58px; color: var(--gf-staff-label); font-size: 11px; }
.gf-pages-meta-v { color: #fff; }
.gf-pages-go { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.gf-pages-body-text { padding: 8px; background: rgba(0,0,0,0.3); clip-path: var(--gf-cut-sm); white-space: pre-wrap; overflow-wrap: anywhere; color: #fff; font-size: 13px; }
.gf-pages-ctx { margin-top: 6px; }
.gf-pages-ctx summary { cursor: pointer; color: var(--gf-staff-label); }
.gf-pages-ctx-line { padding: 1px 0 1px 12px; color: var(--gf-text-2); overflow-wrap: anywhere; }
.gf-pages-link { cursor: pointer; }
.gf-pages-link:hover { color: #fff; }
.gf-pages-thread { display: flex; flex-direction: column; gap: 5px; margin-top: 8px; }
.gf-pages-msg { padding: 5px 8px; clip-path: var(--gf-cut-sm); background: var(--gf-staff-bg-soft); }
.gf-pages-msg.player { border-left: 2px solid var(--gf-ash-gold); }
.gf-pages-msg.staff { border-left: 2px solid var(--gf-grid-cyan); }
.gf-pages-msg.internal { border-left: 2px dashed var(--gf-text-3); background: rgba(0,0,0,0.25); font-style: italic; }
.gf-pages-msg-meta { font-size: 11px; color: var(--gf-text-3); }
.gf-pages-msg-body { color: #fff; white-space: pre-wrap; overflow-wrap: anywhere; }
.gf-pages-actions { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.gf-pages-text {
    width: 100%; box-sizing: border-box; padding: 6px; resize: vertical; font: inherit; font-size: 12px;
    background: rgba(0,0,0,0.35); border: 1px solid var(--gf-staff-border); clip-path: var(--gf-cut-sm); color: #fff;
}
.gf-pages-btnrow { display: flex; gap: 5px; flex-wrap: wrap; }
.gf-pages-label { color: var(--gf-staff-label); font-size: 11px; }
.gf-pages-roster { display: flex; flex-direction: column; gap: 4px; }

/* ── Bank box ── the chest panel (`.container-*`) plus a gold header and the
   two capacity meters. Banked gold is a balance, never a stack in the grid. */
.bank-gold {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 2px 10px;
    padding: 8px 12px 7px;
    border-bottom: 1px solid rgba(160, 120, 60, 0.25);
}
.bank-gold-balance { font-size: 14px; letter-spacing: 0.04em; color: var(--gf-trim-bright); }
.bank-gold-carried { font-size: 11px; color: #a8966c; text-align: right; }
.bank-gold-row { grid-column: 1 / -1; display: flex; gap: 4px; margin-top: 4px; }
.bank-gold-amount {
    flex: 1;
    min-width: 0;
    padding: 4px 6px;
    font: inherit;
    font-size: 12px;
    color: var(--gf-bone);
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(160, 120, 60, 0.4);
    clip-path: var(--gf-cut-sm);
}
.bank-gold-btn {
    padding: 4px 9px;
    font: inherit;
    font-size: 11px;
    color: var(--gf-bone);
    background: rgba(70, 52, 28, 0.7);
    border: 1px solid rgba(160, 120, 60, 0.5);
    clip-path: var(--gf-cut-sm);
    cursor: pointer;
}
.bank-gold-btn:hover:not(:disabled) { background: rgba(100, 74, 38, 0.85); }
.bank-gold-btn:disabled { opacity: 0.4; cursor: default; }
.bank-meters { display: flex; gap: 12px; padding: 6px 12px 2px; }
.bank-meter { flex: 1; min-width: 0; }
.bank-meter-text { font-size: 10px; color: #a8966c; margin-bottom: 2px; }
.bank-meter-bar {
    height: 4px;
    clip-path: var(--gf-cut-sm);
    background: rgba(0, 0, 0, 0.4);
    overflow: hidden;
}
.bank-meter-fill { height: 100%; background: #b8934a; }
.bank-meter-fill.is-near-full { background: #d2724e; }
/* The HUD's banked-gold readout: muted beside the carried figure. */
.hud-gold-banked { margin-left: 6px; font-size: 0.85em; opacity: 0.65; }
