/* ---- The quest log ---------------------------------------------------------------------------
   An indexed register: one line per obligation accepted, the unfinished above the finished. */
.rpg-quests { display: flex; flex-direction: column; gap: 12px; }
.rpg-quest { padding: 9px 12px; border-radius: var(--r-2);
  background: rgba(7,9,26,.42); border: 1px solid rgba(154,134,232,.16); }
.rpg-quest-head { display: flex; align-items: center; gap: 8px; }
.rpg-quest-name { flex: 1 1 auto; font-family: var(--font-display); font-size: 15px;
  color: var(--ink); }
.rpg-quest-progress { font-family: var(--font-mono); font-size: 12px;
  font-variant-numeric: tabular-nums; color: var(--ink-mute); }
.rpg-quest-obj { margin: 5px 0 0 20px; font-size: 13px; color: var(--ink-soft); }
/* READY is the one state worth a colour: the objective is met and the player has to walk back to
   somebody, which no number on this row can say. */
.rpg-quest.is-ready { border-color: rgba(201,164,110,.5); }
.rpg-quest.is-ready .rpg-quest-progress { color: var(--gold-hi); }
/* Done stays on the page rather than vanishing — a record, not a task queue — but recedes. */
.rpg-quest.is-done { opacity: .5; }
.rpg-quests-empty { margin: 4px 2px; font-size: 13px; color: var(--ink-mute); font-style: italic; }
/* account button + its drop-down. `position: relative` on the wrapper so the menu hangs off the
   BUTTON rather than off the action bar, which would put it under the minimap. */
.rpg-account { position: relative; display: grid; place-items: center; }
.rpg-account-menu { position: absolute; top: 48px; right: 0; min-width: 168px; display: none;
  padding: 5px; border-radius: var(--r-2); background: var(--glass-deep);
  border: 1px solid var(--glass-stroke); backdrop-filter: blur(8px); z-index: 30; }
.rpg-account-menu.is-open { display: block; }
.rpg-account-item { width: 100%; padding: 9px 11px; text-align: left; border: 0; border-radius: var(--r-2);
  background: none; color: var(--ink-soft); font-size: 13px; cursor: pointer; white-space: nowrap; }
.rpg-account-item:hover { background: rgba(255,255,255,.08); color: var(--gold-hi); }
/* top-left status: name, level, gold, HP bar, XP bar */
/* 234px -> 306px. The medallion is 62px plus its gap, so at the old width the three bars would have
   lost a third of their length to make room for it — and a bar's LENGTH is what makes a fraction
   readable at a glance. The reference's own vitals cluster measures ~425px wide; 306 is the
   conservative step that keeps the bars at their current length rather than matching the plate. */
/* ── THE BUFF / DEBUFF STRIP ───────────────────────────────────────────────────────────────────
   TO THE RIGHT OF THE CHARACTER WINDOW (owner's call), pinned to the same top edge. That is where
   the previous version of this game put it (`.buff-strip`, beside the char-box) and it is right for
   the reason the whole left block is: this corner is what you ARE, and a status is a temporary
   amendment to it. Anything further away would be a second place to look during a fight.

   306px is `.rpg-status`'s width and 14px its left offset, so the strip starts one gutter past its
   right-hand edge. Stated as a sum rather than as 334 so the two cannot drift: widening the status
   block moves this without anybody having to notice.

   `nowrap` and `flex-shrink: 0` so the chips always sit side by side. The reference records
   wrapping into a stacked column as the failure this prevents, and a status strip that changes
   SHAPE as effects arrive is one the eye has to re-find every time. */
.hud-buffs {
  position: absolute; top: 14px; left: calc(14px + 306px + 10px);
  display: flex; flex-direction: row; align-items: flex-start;
  flex-wrap: nowrap; flex-shrink: 0; gap: 7px;
  z-index: 9; pointer-events: auto;
}
/* One chip: a 40px square with the mark in the middle, the timer ring around it and the seconds
   under it. `--accent` is written per chip from the effect's own colour (see `STATUS`). */
.hud-buff {
  position: relative; width: 40px; height: 48px;
  display: grid; place-items: center;
  cursor: help; color: var(--accent, var(--gold));
}
.hud-buff-ring { position: absolute; left: 0; top: 0; width: 40px; height: 40px; }
/* The track: always full, always dim. Without it the remaining arc has nothing to be a FRACTION of
   and reads as decoration rather than as a timer. */
.hud-buff-track { fill: rgba(10, 11, 26, .72); stroke: rgba(255, 255, 255, .16); stroke-width: 3; }
/* ...and the arc that empties. Rotated so it starts at twelve o'clock and runs clockwise, which is
   the direction every clock face in the world already taught the player. The transition is what
   makes a once-a-second update read as a sweep instead of a series of jumps. */
.hud-buff-arc {
  fill: none; stroke: var(--accent, var(--gold)); stroke-width: 3; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 20px 20px;
  transition: stroke-dashoffset 320ms linear;
  filter: drop-shadow(0 0 4px var(--accent, var(--gold)));
}
.hud-buff-icon {
  position: relative; z-index: 1; display: grid; place-items: center;
  width: 40px; height: 40px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .85));
}
/* The marks are drawn at 18px and inherit `currentColor`, which the chip sets to the accent — so a
   debuff's mark is the debuff's colour without a second rule per effect. */
.hud-buff-icon svg { width: 21px; height: 21px; }
/* The number under the chip. "Nearly over" and "two seconds" are different questions, and the ring
   only answers the first. Tabular so a countdown does not shimmer as its digits change width. */
.hud-buff-secs {
  position: absolute; left: 0; right: 0; bottom: 0; text-align: center;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  font-variant-numeric: tabular-nums; line-height: 12px;
  color: var(--gold-hi); text-shadow: 0 1px 3px rgba(0, 0, 0, .95);
}
/* GOOD AND BAD READ FROM THE PERIPHERY. A buff sits in a warm-rimmed well; a debuff's ring pulses,
   because something being DONE to you should not be as easy to ignore as something you did. */
.hud-buff--buff .hud-buff-track { stroke: rgba(255, 200, 97, .22); }
.hud-buff--debuff .hud-buff-arc { animation: hud-buff-pulse 1.6s ease-in-out infinite; }
@keyframes hud-buff-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .55; }
}
@media (prefers-reduced-motion: reduce) {
  .hud-buff-arc { animation: none; transition: none; }
}

.rpg-status { position: absolute; top: 14px; left: 14px; width: 306px; pointer-events: none;
  border: 2px solid transparent; border-radius: var(--r-2);
  background:
    var(--frame-corners-sm),
    /* A GRADIENT, not the bare colour. In the `background` shorthand a <color> is only legal in the
       FINAL layer, so `var(--chrome) padding-box,` in the middle invalidated the entire declaration
       and this panel painted NOTHING — it has been fully transparent since it was written, which
       nobody noticed because the corner brackets and the gold inner glow still drew and read as a
       frame. Over a green meadow the "fill" was the meadow, which is what made it olive.
       `linear-gradient(c, c)` is the standard way to spend a colour as an image layer. */
    linear-gradient(var(--chrome), var(--chrome)) padding-box,
    var(--frame-trim-soft) border-box;
  padding: var(--sp-2) var(--sp-3);
  /* NO backdrop-filter. It was here, and VIL forbids it: the interface is the Adventurer's Ledger,
     a panel is a PAGE, and a page is a surface you read — never an opening you look through. A
     blur-through is the single clearest way to make a panel read as glass, which is the thing the
     Ledger is not. Removed on VIL's first application to real code. */
  box-shadow: var(--frame-inner-glow), 0 6px 18px -6px rgba(0,0,0,.55);
  /* Two columns now: the medallion, then the name-and-bars column. */
  display: flex; gap: 10px; align-items: flex-start; }

/* THE PORTRAIT MEDALLION (owner's call, and it DRAWS this time — see `GameUI.mountPortrait`).
   Removed last round because what shipped was a placeholder gradient waiting for a render that
   never came; it now holds a still of the player's own dressed figure.
   `overflow: hidden` + the radius is what makes a square canvas read as a medallion, and the ring
   is a sibling ON TOP so the gold rim is never clipped by its own frame. */
.rpg-portrait { position: relative; width: 62px; height: 62px; flex: none;
  border-radius: 50%; overflow: hidden;
  background: radial-gradient(circle at 50% 34%, var(--plum-hi), var(--plum-lo)); }
/* The face fills the disc. `cover`, not `contain`: the render is a head on transparent ground and
   a letterboxed one would sit in a ring of empty medallion. */
.rpg-portrait-face { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; }
/* Gold as a RIM — never a fill (VIL · Colour). Outside the clip, so it stays a clean circle. */
.rpg-portrait-ring { position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  border: 2px solid var(--gold); box-shadow: 0 0 12px -2px var(--gold-glow); }
.rpg-statuscol { flex: 1 1 auto; min-width: 0; }

/* identity: the character's name, with level and gold beneath it. */
.rpg-ident { display: flex; flex-direction: column; gap: 3px; margin-bottom: 8px; }
.rpg-ident .name { font-family: var(--font-display); font-size: 20px; line-height: 1.05;
  color: var(--ink); letter-spacing: .01em;
  /* A name is player-typed and can be long; the block has a fixed width, so it must clip rather
     than push the gold count off the panel. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rpg-status-row { display: flex; align-items: baseline; justify-content: space-between; }
/* THE CLASS LINE. Still the chrome face and still uppercase — it sits directly under a display-face
   name and has to read as a label rather than as a second name. (It carried `LV n · MLV n` before
   the levels moved beside their bars; the note about digits and old-style serifs went with them,
   and applies now to `.rpg-bar-lv`.) */
.rpg-status .lvl { font-family: var(--font-mono); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; font-size: 12px; color: var(--gold-hi); }
/* HP row, and the SP row under it. Both server-owned and both polled by GameUI.frame(). */
/* Bar + tag. The bar flexes, the tag is a fixed gutter so all three tags align down the block. */
/* THE BAR IS NAMED, not positional — it was `> :first-child`, which only held while the bar WAS
   the first child. */
.rpg-bar-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.rpg-bar-row > .rpg-hprow,
.rpg-bar-row > .rpg-sprow,
.rpg-bar-row > .rpg-xpbar,
.rpg-bar-row > .rpg-wxpbar { flex: 1 1 auto; min-width: 0; }
/* THE LEVELS MOVED UNDER THE PORTRAIT (owner's call) and the per-row gutter went with them. It
   existed to keep three bars starting at one x; with nothing in front of them they already do —
   and now that x is the same one the NAME and the CLASS start at, which is what was asked for and
   what the gutter made impossible. */
.rpg-portrait-col { display: flex; flex-direction: column; align-items: center; gap: 4px;
  /* STRETCHES, so the level pair below can be pinned to the bottom of the block and line up with
     the bars in the next column. `.rpg-status` is `align-items: flex-start`, which left this column
     only as tall as its own content and made cross-column alignment impossible. */
  flex: none; align-self: stretch; }
/* RIGHT-ALIGNED to the medallion's right edge (owner's call). Centred, "LV 7" and "MLV 12" sat at
   two different left edges under a circle, which reads as two loose labels rather than as one
   block belonging to the portrait above them. */
/* LV SITS ON THE XP BAR, MLV ON THE MXP BAR (owner's call). Each label is given the SAME box as a
   `.rpg-bar-row` — 14px tall with a 6px gap under it — and the pair is pushed to the bottom of the
   stretched portrait column, so the last label's box lands exactly on the last bar's box and the
   one above it on the bar above. Derived from the bar geometry rather than nudged by eye: change
   `.rpg-bar-row`'s height or gap and these follow, because they are the same two numbers.
   `margin-bottom` cancels the last bar row's own trailing margin. Horizontal alignment is
   untouched — still flush to the medallion's right edge. */
.rpg-portrait-lvls { display: flex; flex-direction: column; align-items: flex-end;
  align-self: stretch; margin-top: auto; margin-bottom: 6px;
  font-family: var(--font-mono); font-weight: 600; font-variant-numeric: tabular-nums;
  font-size: 11px; letter-spacing: .04em;
  color: var(--gold-hi); text-shadow: 0 1px 2px #000; }
.rpg-portrait-lvls > span { height: 14px; display: flex; align-items: center; line-height: 1; }
.rpg-portrait-lvls > .lv-adv { margin-bottom: 6px; }
/* THE TAG RIDES THE SAME PIXEL AS THE NUMBER IN THE BAR.
   The figures inside the three bars are nudged up one pixel (see `padding-bottom` on `.hp-label`
   and the two `.xp-label`s) because these faces set digits low in the em and a mathematically
   centred number reads as sitting on the floor of the bar. The tag beside it was left exactly
   centred, so number and suffix ended up a pixel apart and the row stopped reading as one line.
   Same mechanism, so the two cannot drift: in a row that centres its items, a bottom pad of 2px
   raises the text by 1. */
.rpg-bar-tag { flex: 0 0 30px; font-family: var(--font-mono); font-weight: 500; font-variant-numeric: tabular-nums; font-size: 11px; letter-spacing: .04em;
  padding-bottom: 2px;
  color: var(--ink-soft); text-shadow: 0 1px 2px #000; }

.rpg-hprow { position: relative; height: 14px; border-radius: var(--r-pill); overflow: hidden;
  background: rgba(7,9,26,.6); border: 1px solid var(--glass-stroke); }
.rpg-hprow .fill { position: absolute; inset: 0; width: 100%; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--hp-deep), var(--hp)); transition: width .15s; }
.rpg-hprow .hp-label { position: absolute; inset: 0; display: grid; place-items: center; padding-bottom: 2px;
  font-family: var(--font-mono); font-weight: 500; font-variant-numeric: tabular-nums; font-size: 11px; letter-spacing: .02em; color: var(--ink);
  text-shadow: 0 1px 2px #000; }
.rpg-hprow.is-low { border-color: var(--ember); box-shadow: 0 0 10px rgba(255,122,82,.35); }

/* SP. The HP row's twin - same height, same trough, same pill, same label treatment - because it is
   the same KIND of thing (a live pool that empties and refills) and must read as its sibling rather
   than as a new invention. The ONE difference is the hue, for the reason the MXP bar differs from
   the XP bar above it: two identically-coloured bars stacked are two coloured lines, and colour is
   what tells a player at a glance which of them just moved. Blue against health's red, which is the
   convention this whole genre reads without being told. No `is-low`: an empty SP bar is a spend the
   player made on purpose, not damage arriving at them, so it does not want an alarm state. */
.rpg-sprow { position: relative; height: 14px; border-radius: var(--r-pill); overflow: hidden;
  background: rgba(7,9,26,.6); border: 1px solid var(--glass-stroke); }
.rpg-sprow .fill { position: absolute; inset: 0; width: 100%; border-radius: var(--r-pill);
  background: linear-gradient(90deg, #1d3f8f, #5b93ee); transition: width .15s; }
.rpg-sprow .sp-label { position: absolute; inset: 0; display: grid; place-items: center; padding-bottom: 2px;
  font-family: var(--font-mono); font-weight: 500; font-variant-numeric: tabular-nums; font-size: 11px; letter-spacing: .02em; color: var(--ink);
  text-shadow: 0 1px 2px #000; }

/* The XP bar is the ONE part of the status block that takes the pointer, because it swaps its
   label on hover. Scoped to the bar and not to `.rpg-status`, which stays pointer-events:none —
   the whole block is ~90px tall and would otherwise swallow world clicks in that corner. */
.rpg-xpbar { position: relative; height: 14px; border-radius: var(--r-pill); overflow: hidden;
  background: rgba(7,9,26,.6); border: 1px solid var(--glass-stroke); pointer-events: auto; }
.rpg-xpbar:hover { border-color: var(--glass-stroke-hi); }
.rpg-xpbar .fill { position: absolute; inset: 0; width: 0%; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--xp-deep), var(--xp)); transition: width .2s; }
.rpg-xpbar .xp-label { position: absolute; inset: 0; display: grid; place-items: center; padding-bottom: 2px;
  font-family: var(--font-mono); font-weight: 500; font-variant-numeric: tabular-nums; font-size: 11px; letter-spacing: .02em; color: var(--ink);
  text-shadow: 0 1px 2px #000; }
/* Percentage normally, exact numbers on hover. Exactly one of the pair is ever displayed, so the
   grid always has a single centred child. */
.rpg-xpbar .xp-num { display: none; }
.rpg-xpbar:hover .xp-pct { display: none; }
.rpg-xpbar:hover .xp-num { display: block; }

/* WXP — the gathering track. Same shape as the XP bar, deliberately a different HUE: the two fill
   from completely different actions and sit one above the other, so colour is what tells a player
   at a glance which of them just moved. Green/teal, matching the worker floats and the Trades panel. */
.rpg-wxpbar { position: relative; height: 14px; border-radius: var(--r-pill); overflow: hidden;
  background: rgba(7,9,26,.6); border: 1px solid var(--glass-stroke); pointer-events: auto; }
.rpg-wxpbar:hover { border-color: var(--glass-stroke-hi); }
.rpg-wxpbar .fill { position: absolute; inset: 0; width: 0%; border-radius: var(--r-pill);
  background: linear-gradient(90deg, #2f7d5c, #8ae0b0); transition: width .2s; }
.rpg-wxpbar .xp-label { position: absolute; inset: 0; display: grid; place-items: center; padding-bottom: 2px;
  font-family: var(--font-mono); font-weight: 500; font-variant-numeric: tabular-nums; font-size: 11px; letter-spacing: .02em; color: var(--ink);
  text-shadow: 0 1px 2px #000; }
.rpg-wxpbar .wxp-num { display: none; }
.rpg-wxpbar:hover .wxp-pct { display: none; }
.rpg-wxpbar:hover .wxp-num { display: block; }

/* panel window */
/* THE MODAL PAGE. Same fitting as .v-page — see the long note there for why the frame is a stack
   with a light catch rather than a line, and why the corners are curved scrollwork rather than
   L-brackets.
   The flourishes arrive as BACKGROUND LAYERS rather than as a child element, because these panels
   are built in six separate files and threading an extra <span> through all of them to carry four
   pseudo-elements is a worse trade than four background images. One SVG, mirrored per corner by an
   internal transform, so the curve is authored once. */
.rpg-panel { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  width: min(880px, 92vw); max-height: 86vh; overflow: auto; pointer-events: auto;
  border: 1px solid #0d0a20; border-radius: var(--r-4); padding: var(--sp-6);
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 34'><g transform='translate(0,0)'><g fill='none' stroke='%23c9a46e' stroke-linecap='round'><path d='M1 33 L1 13 C1 6.4 6.4 1 13 1 L33 1' stroke-width='1.4'/><path d='M5.5 33 L5.5 14.5 C5.5 9.5 9.5 5.5 14.5 5.5 L33 5.5' stroke-width='.85' opacity='.65'/><path d='M1 24 C6 23 9 20 10 15' stroke-width='.9' opacity='.9'/><path d='M24 1 C23 6 20 9 15 10' stroke-width='.9' opacity='.9'/></g><circle cx='10.6' cy='10.6' r='1.5' fill='%23efdcbb'/></g></svg>") left 5px top 5px / 34px 34px no-repeat,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 34'><g transform='translate(34,0) scale(-1,1)'><g fill='none' stroke='%23c9a46e' stroke-linecap='round'><path d='M1 33 L1 13 C1 6.4 6.4 1 13 1 L33 1' stroke-width='1.4'/><path d='M5.5 33 L5.5 14.5 C5.5 9.5 9.5 5.5 14.5 5.5 L33 5.5' stroke-width='.85' opacity='.65'/><path d='M1 24 C6 23 9 20 10 15' stroke-width='.9' opacity='.9'/><path d='M24 1 C23 6 20 9 15 10' stroke-width='.9' opacity='.9'/></g><circle cx='10.6' cy='10.6' r='1.5' fill='%23efdcbb'/></g></svg>") right 5px top 5px / 34px 34px no-repeat,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 34'><g transform='translate(0,34) scale(1,-1)'><g fill='none' stroke='%23c9a46e' stroke-linecap='round'><path d='M1 33 L1 13 C1 6.4 6.4 1 13 1 L33 1' stroke-width='1.4'/><path d='M5.5 33 L5.5 14.5 C5.5 9.5 9.5 5.5 14.5 5.5 L33 5.5' stroke-width='.85' opacity='.65'/><path d='M1 24 C6 23 9 20 10 15' stroke-width='.9' opacity='.9'/><path d='M24 1 C23 6 20 9 15 10' stroke-width='.9' opacity='.9'/></g><circle cx='10.6' cy='10.6' r='1.5' fill='%23efdcbb'/></g></svg>") left 5px bottom 5px / 34px 34px no-repeat,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 34'><g transform='translate(34,34) scale(-1,-1)'><g fill='none' stroke='%23c9a46e' stroke-linecap='round'><path d='M1 33 L1 13 C1 6.4 6.4 1 13 1 L33 1' stroke-width='1.4'/><path d='M5.5 33 L5.5 14.5 C5.5 9.5 9.5 5.5 14.5 5.5 L33 5.5' stroke-width='.85' opacity='.65'/><path d='M1 24 C6 23 9 20 10 15' stroke-width='.9' opacity='.9'/><path d='M24 1 C23 6 20 9 15 10' stroke-width='.9' opacity='.9'/></g><circle cx='10.6' cy='10.6' r='1.5' fill='%23efdcbb'/></g></svg>") right 5px bottom 5px / 34px 34px no-repeat,
    /* THE INNER RAIL, AS BACKGROUND LINES RATHER THAN A POSITIONED PSEUDO-ELEMENT.
       `.rpg-panel` is its own scroll container (`overflow: auto`), and an absolutely-positioned
       child of a scroll container travels with the CONTENT — so the rail slid up out of the window
       as soon as a long panel was scrolled, which is what the owner reported. A background does
       not: with the default `background-attachment: scroll` it is painted against the element's
       padding box and stays put however far the contents move.
       Four 1px lines rather than one stroked rounded rect, because a single SVG scaled to an
       arbitrary panel size distorts its own corner radius. The corners are already covered by the
       flourish SVGs above, which sit at 5px and hide the join. */
    linear-gradient(rgba(198,160,104,.30), rgba(198,160,104,.30)) 4px 4px / calc(100% - 8px) 1px no-repeat,
    linear-gradient(rgba(198,160,104,.30), rgba(198,160,104,.30)) 4px calc(100% - 5px) / calc(100% - 8px) 1px no-repeat,
    linear-gradient(rgba(198,160,104,.30), rgba(198,160,104,.30)) 4px 4px / 1px calc(100% - 8px) no-repeat,
    linear-gradient(rgba(198,160,104,.30), rgba(198,160,104,.30)) calc(100% - 5px) 4px / 1px calc(100% - 8px) no-repeat,
    radial-gradient(120% 90% at 50% 8%, rgba(145,165,183,.12), transparent 62%),
    linear-gradient(180deg, #30353d 0%, #20252d 42%, #151a21 100%);
  box-shadow:
    0 0 0 1px rgba(198,160,104,.85),
    0 0 0 2px rgba(13,10,32,.9),
    inset 0 1px 0 rgba(255,236,198,.22),
    inset 0 -1px 0 rgba(0,0,0,.55),
    inset 0 0 0 1px rgba(154,134,232,.10),
    inset 0 14px 28px -18px rgba(255,236,198,.16),
    inset 0 -22px 34px -26px rgba(0,0,0,.75),
    0 24px 80px rgba(0,0,0,.65);
  display: none; }
.rpg-panel.is-open { display: block; }
/* SETTINGS. Narrower than the ledgers: it is a short list of switches, and a switch stranded on the
   far side of 880px of parchment reads as unrelated to its own label. */
.rpg-panel--settings { width: min(560px, 92vw); }
.rpg-settings { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-4); }
/* The row is the LABEL, so the whole thing is a hit target — a 13px checkbox is not one. */
.rpg-setting { display: flex; gap: var(--sp-3); align-items: flex-start; cursor: pointer;
  padding: var(--sp-3); border-radius: var(--r-3); border: 1px solid transparent; }
.rpg-setting:hover { border-color: rgba(201,164,110,.35); background: rgba(255,240,200,.04); }
.rpg-setting-box { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--gold-hi, #ffd76a);
  cursor: pointer; flex: none; }
.rpg-setting-text { display: flex; flex-direction: column; gap: 2px; }
.rpg-setting-label { font: 600 15px var(--font-body); color: var(--ink); }
/* The hint is quieter than the label but NOT decorative — it is the only place the effect of the
   switch is written down, so it stays at a readable size rather than shrinking to a caption. */
.rpg-setting-hint { font: 13px var(--font-body); color: var(--ink-soft); line-height: 1.45; }
.rpg-settings-note { font: italic 13px var(--font-quill); color: var(--ink-quill, #b9aed2);
  margin: var(--sp-5) 0 0; }
/* A SPEED ROW has no checkbox, so its text starts where a toggle's text does — the labels line up
   in one column and the rows read as a list rather than as two interleaved ones. */
.rpg-setting--speed { padding-left: calc(var(--sp-3) + 16px + var(--sp-3)); cursor: default; }
.rpg-setting--speed:hover { border-color: transparent; background: none; }
/* Greyed, not hidden, when the control it belongs to is off: a row that vanishes makes the panel
   jump, and the player loses the explanation of what they just turned off. */
.rpg-setting.is-disabled { opacity: .45; }
.rpg-setting-slider { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
/* FIXED width, not `flex: 1`. Flexing made each slider as long as its own row allowed, and the
   rows differ because the hints differ — so two controls that do the same kind of thing were drawn
   at two lengths, which reads as them having different ranges. */
.rpg-setting-range { width: 240px; flex: none; accent-color: var(--gold-hi, #ffd76a); cursor: pointer; }
.rpg-setting.is-disabled .rpg-setting-range { cursor: not-allowed; }
/* Fixed width and tabular figures so the number does not shove the slider about as it changes. */
.rpg-setting-value { min-width: 62px; font: 600 13px var(--font-mono); font-variant-numeric: tabular-nums;
  color: var(--gold-hi, #ffd76a); }
/* A CHOICE ROW is a speed row with buttons instead of a slider: no checkbox, so it is indented to
   the same column, and the buttons sit under the hint like the slider does. */
.rpg-setting--choice { padding-left: calc(var(--sp-3) + 16px + var(--sp-3)); cursor: default; }
.rpg-setting--choice:hover { border-color: transparent; background: none; }
.rpg-setting-choices { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.rpg-setting-choice { font: 600 13px var(--font-body); color: var(--ink-soft);
  background: rgba(255,255,255,.05); border: 1px solid var(--rule, #6b5f4a); border-radius: 4px;
  padding: 4px 12px; cursor: pointer; }
.rpg-setting-choice:hover { color: var(--ink); border-color: var(--gold-hi, #ffd76a); }
/* The chosen one is filled, not merely outlined: an outline reads as a hover state, and a player
   who cannot tell which of three is active will click the one they already have. */
.rpg-setting-choice.is-on { color: #2a2112; background: var(--gold-hi, #ffd76a);
  border-color: var(--gold-hi, #ffd76a); }
.rpg-panel-title { font-family: var(--font-display); font-size: 30px; color: var(--gold-hi);
  margin: 0 0 var(--sp-2); letter-spacing: .02em; }
.rpg-panel-sub { font-family: var(--font-quill); font-style: italic; color: var(--ink-quill); margin: 0 0 var(--sp-5); }
/* ABSOLUTE, ALIGNED WITH THE TITLE — and this is a deliberate REVERSAL, so the reason is written
   down rather than left for someone to rediscover.
   It was briefly `position: sticky; float: right`, to stop it scrolling out of the top of a long
   window. That works only where the button is followed by a normal block: a float does not
   interact with a flex or grid container, and several panels (the bestiary's title row, the
   strongbox's columns) put one immediately after it — so on those the X landed somewhere other
   than the corner. The owner reported it on two and suspected more, which is exactly right.
   Alignment on every window beats pinning on some, so it goes back to absolute. The inner rail
   above stays background-drawn, because that fix does NOT depend on the flow and holds everywhere.
   Pinning the X properly needs each panel's header to become a real title ROW containing it, which
   is a markup change across a dozen files and a job of its own. */
.rpg-close { position: absolute; top: 14px; right: 16px; width: 30px; height: 30px; border-radius: 50%;
  background: var(--glass-hi); border: 1px solid var(--glass-stroke); color: var(--ink-soft); cursor: pointer;
  z-index: 3; }
.rpg-close:hover { color: var(--ember); border-color: var(--ember); }

/* inventory */
.rpg-inv { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: var(--sp-5);
  align-items: start; min-width: 0; }
/* PAPERDOLL: the character stands in the middle with its slots arranged around the edges — head
   above, the two columns flanking, tools underneath. Explicit grid areas rather than source order,
   so the arrangement is stated in one place and the markup stays a flat list of containers. */
.rpg-doll { display: grid; gap: var(--sp-2);
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "mount head   pet"
    "left  stage  right"
    "tools tools  tools"; }
/* THE TWO CALLED COMPANIONS, one in each top corner (owner, 2026-09-04). They used to sit on the
   tool rack; at seven cells that row overflowed and — because flex items shrink by default — every
   tool tile got narrower instead. These corners were empty cells doing nothing, and neither a
   saddle nor a pet is a tool: both are CALLED rather than carried. */
.rpg-doll-mount { grid-area: mount; }
.rpg-doll-pet   { grid-area: pet; }
.rpg-doll-mount, .rpg-doll-pet { display: flex; align-items: flex-start; justify-content: center; }
/* Helmet and face side by side over the figure — the face slot sits to the RIGHT of the head
   slot, not under it (owner, 2026-09-03). */
.rpg-doll-head  { grid-area: head; display: flex; justify-content: center; gap: var(--sp-2); }
.rpg-doll-left  { grid-area: left; }
.rpg-doll-right { grid-area: right; }
.rpg-doll-left, .rpg-doll-right { display: grid; gap: var(--sp-2); align-content: center; }
/* Tools sit CENTRED under the figure rather than spread across the full width — as three `1fr`
   columns the fixed-width slots left-aligned in their cells and the row read as unrelated to the
   character above it. */
.rpg-doll-tools { grid-area: tools; display: flex; justify-content: center; gap: var(--sp-2);
  margin-top: var(--sp-1); }
/* The slots are a fixed size so the figure — not the grid — takes the slack when the panel
   resizes; `1fr` on the columns instead made the slots stretch into rectangles. */
.rpg-doll .rpg-slot { width: 52px; flex: 0 0 auto; }
/* `flex: 0 0 auto` is the half that was missing, and it is why the tool tiles shrank the day a
   seventh cell was added: a fixed `width` is only a STARTING size for a flex item, and the default
   `flex-shrink: 1` lets the browser take it back as soon as the row does not fit. The width now
   means what it says, and an overflowing rack overflows visibly instead of quietly resizing every
   tile in it. */
/* The stage is the 3D canvas host. `min-height` rather than a fixed height so it grows with the
   flanking columns; the renderer resizes to whatever it ends up as. */
/* An inset alcove for the figure to stand in, so the canvas reads as part of the panel rather
   than a bare rectangle pasted onto it. */
.rpg-doll-stage { grid-area: stage; position: relative; min-height: 244px; min-width: 0;
  border-radius: var(--r-3); overflow: hidden;
  border: 1px solid var(--glass-stroke);
  background:
    radial-gradient(72% 54% at 50% 88%, rgba(126,138,255,.16), transparent 70%),
    linear-gradient(180deg, rgba(9,11,30,.55), rgba(9,11,30,.15));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05); }
.rpg-doll-figure { position: absolute; inset: 0; }
.rpg-doll-figure canvas { display: block; width: 100%; height: 100%; }
.rpg-group { display: grid; gap: var(--sp-2); min-width: 0; }
.rpg-group-title { margin: 0; font-family: var(--font-banner); font-size: 11px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-mute);
  display: flex; align-items: center; gap: var(--sp-2); }
/* Hairline rule trailing the group name — cheap way to read as a titled section rather than a
   floating word, and it absorbs the width difference between the two column headings. */
.rpg-group-title::after { content: ''; flex: 1; height: 1px; background: var(--glass-stroke); }
.rpg-group-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  letter-spacing: .02em; color: var(--ink-soft); }
/* THE LIGHT PLATE, everywhere an item can sit. #e6e4ed on every slot — bag cells, paperdoll and
   tool slots, and the item card's art box below — so the item ground is one colour across the
   whole window, occupied or not. Rarity is allowed to touch ONLY the outer border colour. */
.rpg-slot, .rpg-cell { background: radial-gradient(circle at center, #3F3466 0%, #2A214B 55%, #211A3D 100%); border: 2px solid var(--glass-stroke);
  /* The plate stops at the CONTENT box: the 2px padding leaves a dark channel between the border
     and the light plate, so a rarity border reads against dark on both of its sides instead of
     vanishing into the pale ground. */
  background-clip: content-box; padding: 2px;
  border-radius: var(--r-2); aspect-ratio: 1; position: relative; display: grid; place-items: center;
  cursor: pointer; user-select: none; min-width: 0;
  transition: border-color .13s, box-shadow .13s, transform .1s; }
.rpg-slot:hover, .rpg-cell:hover { border-color: var(--gold); transform: translateY(-1px); }
.rpg-slot { border-style: dashed; }
/* An OCCUPIED gear slot goes solid — dashed is the "nothing here yet" affordance, and a worn item
   framed in dashes read as still-empty. data-rarity is set exactly when the slot holds an item. */
.rpg-slot[data-rarity] { border-style: solid; }
.rpg-slot .rpg-slot-tag { position: absolute; bottom: 2px; left: 0; right: 0; text-align: center;
  font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: #A299BF; pointer-events: none; }
/* An OCCUPIED slot hides its label: the icon says what it is, and at these sizes the tag was
   overlapping the artwork. It comes back on hover for players still learning the layout. */
.rpg-slot[data-rarity] .rpg-slot-tag { opacity: 0; transition: opacity .13s; }
.rpg-slot[data-rarity]:hover .rpg-slot-tag { opacity: 1; }
/* `align-content: start` — without it the bag's rows STRETCH to fill whatever height the taller
   equipment column forces on the row, which is what spread the satchel into widely separated
   bands of icons with dead space between them. */
.rpg-bag { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: var(--sp-2);
  align-content: start; min-width: 0; }
/* THE PACK COLUMN. A ninth track that is a GUTTER and a tenth that holds the extra slots, so the
   pack reads as a separate compartment beside the bag rather than as five more of the same. The
   column only exists while a pack is worn — otherwise the bag is the eight columns it has always
   been, with no empty strip hanging off its right edge. Cells are placed explicitly by
   `InventoryPanel.renderBag`; auto-flow cannot say "to the right of" and would simply wrap. */
.rpg-bag.has-pack { grid-template-columns: repeat(8, minmax(0, 1fr)) var(--sp-3) minmax(0, 1fr); }
/* ...and the slots themselves are marked, because the player has to be able to SEE which five they
   lose by taking the pack off. That is the whole point of the unequip refusal: a rule about a
   distinction nobody can see is a rule nobody can act on. A warm gold edge and a faint gold ground
   — the panel's own accent, so it reads as "extra" rather than as an error state. */
.rpg-cell.is-pack { border-color: var(--gold-deep, var(--gold-mid));
  background: linear-gradient(180deg, rgba(201,164,110,.10), rgba(201,164,110,.03)); }
.rpg-cell.is-pack:hover { border-color: var(--gold); }
.rpg-cell.drop-hover { border-color: var(--violet); box-shadow: 0 0 10px var(--violet-glow); }

/* The split-stack prompt: a small floating box on the drop cell. */
.rpg-split { position: absolute; z-index: 5; width: 168px; padding: 10px 12px;
  border-radius: var(--r-2); border: 1px solid var(--gold);
  background: linear-gradient(180deg, #2b2360, #16113a);
  box-shadow: 0 10px 26px -8px rgba(0,0,0,.8); }
.rpg-split-title { font-family: var(--font-banner); font-size: 10px; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase; color: var(--gold-hi); margin-bottom: 7px; }
.rpg-split-row { display: flex; gap: 5px; align-items: center; margin-bottom: 8px; }
.rpg-split-step { width: 26px; height: 26px; padding: 0; border-radius: var(--r-1, 5px);
  border: 1px solid var(--glass-stroke); background: var(--glass-hi); color: var(--ink);
  font-size: 15px; line-height: 1; cursor: pointer; }
.rpg-split-step:hover { border-color: var(--gold); }
.rpg-split-n { flex: 1; min-width: 0; padding: 4px 6px; border-radius: var(--r-1, 5px);
  border: 1px solid var(--glass-stroke); background: rgba(7,9,26,.6); color: var(--ink);
  font: 600 14px var(--font-mono); text-align: center; outline: none; }
.rpg-split-n:focus { border-color: var(--gold); }
/* NO SPINNER. A `type="number"` draws the browser's own up/down arrows on hover and focus, and they
   are the one piece of OS chrome left in this window — grey, system-styled, and duplicating the
   plus and minus buttons either side of the field, which are the same control done in the game's
   voice. `-moz-appearance: textfield` is the Firefox spelling; the pseudo-element is the WebKit and
   Chromium one, and `margin: 0` is needed as well because the element reserves its gutter even when
   `appearance` is none. Typing and the arrow keys are untouched — this removes the widget, not the
   behaviour. */
.rpg-split-n { -moz-appearance: textfield; appearance: textfield; }
.rpg-split-n::-webkit-outer-spin-button,
.rpg-split-n::-webkit-inner-spin-button { -webkit-appearance: none; appearance: none; margin: 0; }

/* "ALL" — the third button in the row, and it had no rule at all, so it rendered as a raw browser
   button: system font, system chrome, beside two that speak the game's language. It is a SHORTCUT
   rather than the decision (that is OK), so it takes the quiet treatment `cancel` wears and sizes
   to its own text instead of claiming an equal third of the row — the row is 168px wide and three
   equal buttons left none of them enough for their label. */
.rpg-split-all { flex: none; padding: 5px 10px; border-radius: var(--r-1, 5px);
  border: 1px solid var(--glass-stroke); background: var(--glass-hi); color: var(--ink-soft);
  font-family: var(--font-banner); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; cursor: pointer; }
.rpg-split-all:hover { border-color: var(--gold); color: var(--gold-hi); }
.rpg-split-actions { display: flex; gap: 6px; }
.rpg-split-ok { flex: 1; padding: 5px 0; border-radius: var(--r-1, 5px);
  border: 1px solid var(--gold); background: rgba(201,164,110,.16); color: var(--gold-hi);
  font-family: var(--font-banner); font-size: 11px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; cursor: pointer; }
.rpg-split-ok:hover { background: rgba(201,164,110,.28); }
.rpg-split-cancel { flex: 1; padding: 5px 0; border-radius: var(--r-1, 5px);
  border: 1px solid var(--glass-stroke); background: transparent; color: var(--ink-soft);
  font-family: var(--font-banner); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; cursor: pointer; }
.rpg-split-cancel:hover { color: var(--ink); }

/* The bag search, above the category tabs. Same fitting as the craft window's search box. */
.rpg-bag-search { width: 100%; padding: 8px 11px; border-radius: var(--r-2); margin-bottom: 8px;
  border: 1px solid var(--glass-stroke); background: var(--glass-deep); color: var(--ink);
  font: 14px var(--font-body); outline: none; }
.rpg-bag-search:focus { border-color: var(--gold); }
.rpg-bag-search::placeholder { color: var(--ink-dim); }

