/* Medieval World — client chrome.
   NOTE: web/style.css pins .volume-slider to position:fixed;top:12px, so this
   game uses its own .mw-slider class rather than the shared one. */

:root {
  --mw-ink: #f0ece2;
  --mw-dim: #b6ab97;
  --mw-panel: rgba(28, 24, 20, .88);
  --mw-line: #55483a;
  --mw-gold: #d9a441;
  --mw-red: #9d3b32;
  /* Where the LEFT column starts, under the back link. Two tenants hang off it — the
     audio panel and the party frame — and they are never up together (see .mw-pty), so
     one number places both. On :root and not on #mwHud because .mw-audio is a child of
     <body> and would not inherit it from a sibling; the right-hand column's own
     numbers live on #mwHud for the opposite reason. */
  --mw-lcol-top: 58px;
}

/* An author `display:` rule beats the UA stylesheet's [hidden]{display:none},
   so .mw-menu{display:grid} silently kept the join screen on top of the world.
   Everything in this file toggles via the hidden attribute — make it win. */
[hidden] { display: none !important; }

html, body { height: 100%; margin: 0; overflow: hidden; background: #6f7780; }
body { font-family: "Georgia", ui-serif, serif; color: var(--mw-ink); }
#c { display: block; width: 100vw; height: 100vh; touch-action: none; }

.mw-back {
  position: fixed; left: 12px; top: 12px; z-index: 60;
  background: var(--mw-panel); border: 1px solid var(--mw-line); color: var(--mw-ink);
  padding: 6px 12px; border-radius: 18px; text-decoration: none; font-size: 13px;
  backdrop-filter: blur(6px);
}
.mw-back:hover { border-color: var(--mw-gold); }

/* ── join screen ─────────────────────────────────────────────────────── */
.mw-menu {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center;
  background: radial-gradient(circle at 50% 30%, #4b525c 0%, #22262c 75%);
  padding: 16px; overflow: auto;
}
.mw-card {
  width: min(620px, 100%); background: var(--mw-panel);
  border: 1px solid var(--mw-line); border-radius: 14px; padding: 22px 24px 20px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .5);
}
.mw-card h1 { margin: 0; font-size: 30px; letter-spacing: .01em; color: var(--mw-gold); }
.mw-sub { margin: 4px 0 18px; color: var(--mw-dim); font-size: 14px; }
.mw-row { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.mw-row span { width: 92px; color: var(--mw-dim); font-size: 13px; }
.mw-row input {
  flex: 1; background: #1b1814; border: 1px solid var(--mw-line); color: var(--mw-ink);
  border-radius: 8px; padding: 9px 11px; font: inherit; font-size: 15px;
}
.mw-row input:focus { outline: none; border-color: var(--mw-gold); }
.mw-chip {
  display: inline-block; margin-bottom: 12px; padding: 6px 12px; border-radius: 16px;
  background: #24201a; border: 1px solid var(--mw-line); font-size: 13px; color: var(--mw-dim);
}
.mw-chip b { color: var(--mw-gold); }
.mw-label { color: var(--mw-dim); font-size: 13px; margin: 6px 0 8px; }
.mw-chars { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 18px; }
.mw-chars button {
  background: #221e19; border: 1px solid var(--mw-line); color: var(--mw-ink);
  border-radius: 10px; padding: 10px 6px; font: inherit; font-size: 13px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.mw-chars button .glyph { font-size: 22px; line-height: 1; }
.mw-chars button:hover { border-color: var(--mw-gold); }
.mw-chars button.on { background: var(--mw-gold); color: #241c0c; border-color: var(--mw-gold); font-weight: 700; }
/* ── a character you have already played ──────────────────────────────────
   The picker used to be a glyph and a name, which is the right amount of screen for
   a first visit and the wrong amount for a returning player: the choice they are
   actually making is "carry on with the level 12 magician, or start a man-at-arms",
   and none of that was on the card. A saved card carries its level and, where the
   server sent them, its purse and the world it is standing in. */
.mw-chars button .mw-char-name { line-height: 1.15; }
.mw-chars button.saved { border-color: rgba(217, 164, 65, .55); }
.mw-char-lv {
  font-style: normal; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--mw-gold); background: rgba(217, 164, 65, .14);
  border: 1px solid rgba(217, 164, 65, .45); border-radius: 999px; padding: 1px 7px;
}
.mw-char-sub { font-style: normal; font-size: 10px; color: var(--mw-dim); line-height: 1.2; }
.mw-char-new {
  font-style: normal; font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--mw-dim);
}
/* On the selected card the gold pill would be gold-on-gold. */
.mw-chars button.on .mw-char-lv {
  color: #241c0c; background: rgba(36, 28, 12, .16); border-color: rgba(36, 28, 12, .4);
}
.mw-chars button.on .mw-char-sub, .mw-chars button.on .mw-char-new { color: #4a3a18; }
.mw-go {
  width: 100%; padding: 13px; border-radius: 10px; border: none; cursor: pointer;
  background: linear-gradient(#d9a441, #b07d24); color: #241c0c;
  font: inherit; font-size: 17px; font-weight: 700; letter-spacing: .02em;
}
.mw-go:disabled { opacity: .5; cursor: default; }
.mw-status { text-align: center; color: var(--mw-dim); font-size: 12.5px; margin-top: 10px; min-height: 16px; }
.mw-keys {
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--mw-line);
  color: var(--mw-dim); font-size: 12px; line-height: 1.7;
}
.mw-keys b { color: var(--mw-ink); }

/* ── HUD ─────────────────────────────────────────────────────────────── */
.mw-hud {
  position: fixed; inset: 0; z-index: 40; pointer-events: none;
  /* The right-hand column: minimap, skill nag, quest tracker. See the .mw-track
     rule for why these are variables and not three magic numbers. --mw-map-h is
     the minimap's canvas plus its footer strip, measured in a browser. */
  --mw-col-top: 62px; --mw-map-h: 217px; --mw-nag-h: 30px; --mw-col-gap: 8px;
}
.mw-top {
  position: absolute; right: 12px; top: 12px; display: flex; gap: 8px;
  flex-wrap: wrap; justify-content: flex-end; pointer-events: auto;
}
.mw-pill {
  background: var(--mw-panel); border: 1px solid var(--mw-line); color: var(--mw-ink);
  border-radius: 18px; padding: 6px 12px; font-size: 13px; backdrop-filter: blur(6px);
}
.mw-btn { cursor: pointer; font-family: inherit; }
.mw-btn:hover { border-color: var(--mw-gold); }
/* the Ice Palace's ULTRA toggle, lit while the authored renderer is on */
#elsaUltraBtn.on { border-color: var(--mw-gold); color: var(--mw-gold); }
.mw-toast {
  position: absolute; left: 50%; top: 64px; transform: translateX(-50%);
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 10px;
  padding: 8px 16px; font-size: 14px; opacity: 0; transition: opacity .25s;
}
.mw-toast.show { opacity: 1; }

/* ── gate hint ───────────────────────────────────────────────────────────
   A gate fires by being walked into, so this is the only warning the player
   gets. Sits above the emote bar rather than beside the toast, because a toast
   about somebody arriving must not push it off the screen at the moment it
   matters. Clear of the touch stick and the jump/punch buttons, same as the
   emote bar it sits over. */
.mw-gate {
  position: absolute; left: 50%; bottom: 126px; transform: translateX(-50%);
  background: var(--mw-panel); border: 1px solid var(--mw-gold); color: var(--mw-ink);
  border-radius: 14px; padding: 7px 16px; font-size: 14px; white-space: nowrap;
  backdrop-filter: blur(6px); pointer-events: none;
  box-shadow: 0 0 18px rgba(217, 164, 65, .25);
}
/* Two levels or more under the far country's floor. A warning, not a refusal —
   the road stays open, so this recolours the same pill rather than adding a
   second one competing for the same 126 px slot. */
.mw-gate-warn {
  border-color: #c8623a; color: #f0c3ad;
  box-shadow: 0 0 18px rgba(200, 98, 58, .3);
}
@media (hover: none) and (pointer: coarse) {
  .mw-gate { bottom: 236px; font-size: 13px; padding: 6px 12px; }
}

/* ── health ──────────────────────────────────────────────────────────────
   Bottom-left, above the touch stick on a phone. The fill is a width
   transition rather than a re-render so regeneration reads as a climb.

   IT WAS REPORTED MISSING and it was not missing. Measured in a real browser at
   six viewports from 1280x600 to 2560x1440 (tests/_probe_medieval_hud.py) the
   element is on screen, painted and correct every time. What it was not, was
   VISIBLE: 190 x 14 px of dark red at the extreme bottom-left corner, over snow
   or over pale stone, with a 13 px number beside it — about a centimetre and a
   half of low-contrast sliver on a 1440p screen, in the one corner of the HUD a
   player never looks at. "I do not see a health bar" is a true sentence about
   that, and no amount of z-index would have fixed it.

   So it is 268 x 22 now, framed in a light hairline that holds against BOTH a
   dark forest and a white mountain, carries a heart so it is identifiable
   without being read, and the number is 15 px. The empty part of the track is
   nearly black rather than translucent panel, because the thing a player has to
   read mid-fight is the BOUNDARY between full and empty. */
.mw-health {
  position: absolute; left: 16px; bottom: 52px;
  display: flex; align-items: center; gap: 8px; pointer-events: none;
}
.mw-health-glyph {
  font-size: 15px; line-height: 1; filter: drop-shadow(0 1px 2px #000);
}
.mw-health-bar {
  width: 268px; height: 22px; border-radius: 11px; overflow: hidden;
  background: rgba(8, 6, 5, .82); border: 1px solid rgba(240, 226, 200, .55);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .06);
  backdrop-filter: blur(6px);
}
.mw-health-bar > i {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, #b3352a, #e05a42 55%, #f0b055);
  box-shadow: inset 0 -6px 10px rgba(0, 0, 0, .28);
  transition: width .22s ease-out, background .3s linear;
}
/* Low health recolours the bar itself: on a busy screen a number is missed and a
   colour change is not. */
.mw-health.low .mw-health-bar > i { background: linear-gradient(90deg, #a71d1d, #e0402c); }
.mw-health.low .mw-health-num { color: #ff9b8a; }
.mw-health-num {
  font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px #000, 0 0 6px #000; min-width: 34px;
}
@media (hover: none) and (pointer: coarse) {
  .mw-health { bottom: 240px; }
  .mw-health-bar { width: 168px; height: 18px; border-radius: 9px; }
}

/* Damage vignette. Pointer-events off and opacity driven from JS, so it never
   intercepts a tap even at full strength. */
.mw-hurt {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  transition: opacity .35s ease-out;
  background: radial-gradient(ellipse at center,
              rgba(140, 12, 12, 0) 42%, rgba(140, 12, 12, .85) 100%);
}

.mw-dead {
  position: absolute; inset: 0; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; background: rgba(8, 6, 5, .62); backdrop-filter: blur(3px);
  text-shadow: 0 2px 6px #000;
}
.mw-dead-title { font-size: 30px; letter-spacing: .06em; color: #e8d6b0; }
.mw-dead-by { font-size: 15px; opacity: .85; }
.mw-dead-count { font-size: 13px; opacity: .7; }

/* Health bars over wounded bodies. Same DOM-overlay trick as the name tags, and
   only created for something that has actually been hurt. */
.mw-hpbar {
  position: absolute; transform: translate(-50%, -100%); pointer-events: none;
  width: 46px; height: 5px; border-radius: 3px; overflow: hidden;
  background: rgba(10, 8, 6, .8); border: 1px solid rgba(0, 0, 0, .6);
}
.mw-hpbar > i {
  display: block; height: 100%; background: #d8483a; transition: width .15s linear;
}

/* name tags are DOM, so they stay crisp at any distance */
.mw-tag {
  position: absolute; transform: translate(-50%, -100%); pointer-events: none;
  background: rgba(20, 18, 15, .72); border: 1px solid var(--mw-line);
  border-radius: 12px; padding: 2px 9px; font-size: 12px; white-space: nowrap;
  text-shadow: 0 1px 2px #000;
}

/* ── emote bar ───────────────────────────────────────────────────────────
   Centred along the bottom, clear of the touch stick (left) and the jump/punch
   buttons (right), so it does not overlap either on a phone. */
.mw-emotes {
  position: absolute; left: 50%; bottom: 72px; transform: translateX(-50%);
  display: flex; gap: 8px; pointer-events: none;
}
.mw-emote {
  pointer-events: auto; cursor: pointer; font-family: inherit;
  width: 44px; height: 44px; line-height: 1; font-size: 20px;
  display: flex; align-items: center; justify-content: center;
  background: var(--mw-panel); border: 1px solid var(--mw-line); color: var(--mw-ink);
  border-radius: 12px; backdrop-filter: blur(6px); position: relative;
  touch-action: manipulation;
}
.mw-emote:hover { border-color: var(--mw-gold); }
.mw-emote.on { border-color: var(--mw-gold); background: rgba(217, 164, 65, .28); }
/* the number key, small, in the corner — hidden on touch where there is no keyboard */
.mw-emote b {
  position: absolute; right: 3px; bottom: 1px; font-size: 9px; font-weight: 600;
  opacity: .55;
}
@media (hover: none) and (pointer: coarse) {
  .mw-emote b { display: none; }
  .mw-emotes { bottom: 182px; gap: 6px; }
  .mw-emote { width: 40px; height: 40px; font-size: 18px; }
}

/* ── touch controls ──────────────────────────────────────────────────── */
.mw-touch { position: absolute; inset: 0; pointer-events: none; }
.mw-stick {
  position: absolute; left: 22px; bottom: 26px; width: 132px; height: 132px;
  border-radius: 50%; background: rgba(28, 24, 20, .5);
  border: 2px solid var(--mw-line); pointer-events: auto; touch-action: none;
}
.mw-stick i {
  position: absolute; left: 50%; top: 50%; width: 54px; height: 54px; margin: -27px 0 0 -27px;
  border-radius: 50%; background: rgba(217, 164, 65, .8);
}
.mw-buttons {
  position: absolute; right: 22px; bottom: 30px; display: flex; gap: 12px;
  pointer-events: auto;
}
.mw-buttons button {
  width: 92px; height: 92px; border-radius: 50%; border: 2px solid var(--mw-line);
  background: rgba(28, 24, 20, .62); color: var(--mw-ink); font: inherit;
  font-size: 13px; font-weight: 700; letter-spacing: .05em;
}
.mw-buttons button:active { background: var(--mw-gold); color: #241c0c; }

/* ── audio panel ─────────────────────────────────────────────────────────
   TOP LEFT, under the back link, and not under the ♪ button that opens it. The
   right-hand column at `top: 58px` is the MINIMAP's (`.mw-map`, top 62), so a
   panel anchored there covered it completely — sliders on top of the map, the
   map's own zoom buttons unreachable underneath. The right column already has
   two tenants stacked down it (map, then `.mw-track` at 288) and the audio panel
   is the transient one, so it moves rather than lengthening that queue. */
.mw-audio {
  position: fixed; left: 12px; top: var(--mw-lcol-top); z-index: 55; width: 268px;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 12px;
  padding: 12px 14px; backdrop-filter: blur(8px); font-size: 13px;
}
.mw-audio-head {
  display: flex; justify-content: space-between; align-items: center;
  color: var(--mw-gold); margin-bottom: 10px; font-size: 14px;
}
.mw-audio-head button {
  background: none; border: none; color: var(--mw-dim); cursor: pointer; font-size: 15px;
}
.mw-audio-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.mw-audio-row > span { width: 54px; color: var(--mw-dim); }
.mw-toggle {
  width: 44px; padding: 4px 0; border-radius: 12px; border: 1px solid var(--mw-line);
  background: #1b1814; color: var(--mw-ink); cursor: pointer; font: inherit; font-size: 12px;
}
.mw-toggle.off { color: #7d7367; }
/* local slider — never rely on the shared .volume-slider, it is position:fixed */
.mw-slider {
  flex: 1; -webkit-appearance: none; appearance: none; height: 4px; border-radius: 2px;
  background: #453a2d; outline: none;
}
.mw-slider::-webkit-slider-thumb {
  -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%;
  background: var(--mw-gold); cursor: pointer;
}
.mw-slider::-moz-range-thumb {
  width: 15px; height: 15px; border: none; border-radius: 50%;
  background: var(--mw-gold); cursor: pointer;
}
/* NOT `.mw-track`, and the rename is the fix for half of "the sound bar overlaps
   the minimap". The quest TRACKER further down this file owns that name and is
   `position: absolute; right: 12px; top: 288px` — so the audio panel's skip row,
   which is a plain flex row inside a panel, was being torn out of the panel and
   parked over the map by a rule written for something else. Two unrelated widgets
   under one class name, and the later one won. */
.mw-audio-track { justify-content: space-between; }
.mw-audio-track button {
  background: #1b1814; border: 1px solid var(--mw-line); color: var(--mw-ink);
  border-radius: 8px; padding: 4px 10px; cursor: pointer; font-size: 13px;
}
.mw-audio-track span {
  flex: 1; text-align: center; color: var(--mw-dim); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── loading ─────────────────────────────────────────────────────────── */
.mw-loading {
  position: fixed; inset: 0; z-index: 70; display: grid; place-items: center;
  background: #1d2126; transition: opacity .4s;
}
.mw-loading.done { opacity: 0; pointer-events: none; }
.mw-loading-inner { text-align: center; width: min(420px, 82vw); }
.mw-loading-title { font-size: 26px; color: var(--mw-gold); margin-bottom: 6px; }
.mw-loading-what { color: var(--mw-dim); font-size: 13px; margin-bottom: 14px; }
.mw-bar { height: 4px; background: #33383f; border-radius: 2px; overflow: hidden; }
.mw-bar i { display: block; height: 100%; width: 0; background: var(--mw-gold); transition: width .2s; }

/* ══════════════════════════════════════════════════════════════════════════
   PROGRESSION — level, action bar, pack, loot, bosses.
   Every element below is created by rpg.js rather than written into
   index.html: the panels are data-driven (one row per bag entry, one button
   per learned ability) and would be empty markup at boot.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── level + experience ──────────────────────────────────────────────────
   Directly under the health bar, and deliberately thinner than it: health is
   the thing you read mid-fight, experience is the thing you read afterwards. */
.mw-xp {
  position: absolute; left: 16px; bottom: 16px;
  display: flex; align-items: center; gap: 8px; pointer-events: none;
}
.mw-lv {
  min-width: 22px; height: 22px; padding: 0 5px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(#d9a441, #a9741d); color: #241c0c;
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.mw-xp-bar {
  width: 190px; height: 6px; border-radius: 4px; overflow: hidden;
  background: rgba(20, 18, 15, .72); border: 1px solid var(--mw-line);
}
.mw-xp-bar > i {
  display: block; height: 100%; width: 0;
  background: linear-gradient(90deg, #6d8f3a, #b7d15e);
  transition: width .3s ease-out;
}
.mw-xp-num {
  font-size: 10.5px; color: var(--mw-dim); font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 2px #000;
}

/* ── coin ────────────────────────────────────────────────────────────────
   Sits on the end of the experience row rather than getting a corner of its
   own: level, progress and purse are the three numbers that describe the
   character, and reading them takes one glance if they are one line. */
.mw-coin {
  display: inline-flex; align-items: center; gap: 4px;
  color: #e8c65a; font-size: 12px; text-shadow: 0 1px 2px #000;
}
.mw-coin b {
  font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: .2px;
}
/* The purse in the pack header is a THIRD element in a `space-between` row, so
   it needs `margin-left:auto` to stay beside the close button instead of being
   spread to the middle of the bar. */
.mw-coin-pack { margin-left: auto; margin-right: 12px; font-size: 13px; }

/* ── action bar ──────────────────────────────────────────────────────────
   Bottom centre; the emote bar moved up above it. Abilities first, then
   anything drinkable; keys 1..8 (emotes are Alt+1..8 and kept their own bar).

   NOT `.mw-bar`, which is what this was called for an afternoon: that class
   already belonged to the LOADING PROGRESS BAR at the bottom of this file
   (`height: 4px; overflow: hidden`), so the action bar rendered as a 4-pixel
   letterbox with six 46-pixel buttons hanging out of it and straight through
   the emote row. It cost a browser measurement to find, because the buttons
   were all present and correct in the DOM and the probe that counted them
   was perfectly happy. */
.mw-actionbar {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
  display: flex; gap: 6px; pointer-events: none;
}
.mw-slot {
  pointer-events: auto; cursor: pointer; font-family: inherit; position: relative;
  width: 46px; height: 46px; border-radius: 10px; overflow: hidden;
  background: var(--mw-panel); border: 1px solid var(--mw-line); color: var(--mw-ink);
  backdrop-filter: blur(6px); padding: 0; touch-action: manipulation;
}
.mw-slot:hover { border-color: var(--mw-gold); }
.mw-slot.locked { opacity: .38; cursor: default; }
.mw-slot-icon { font-size: 21px; line-height: 46px; display: block; }
.mw-slot > b {
  position: absolute; left: 4px; top: 2px; font-size: 9px; font-weight: 600; opacity: .55;
}
.mw-slot > em {
  position: absolute; right: 4px; bottom: 2px; font-size: 10px; font-style: normal;
  color: var(--mw-gold);
}
/* The cooldown sweep. Height is written every frame from the server's own number —
   it is a picture of a value, never a second timer. */
.mw-slot-cd {
  position: absolute; left: 0; right: 0; bottom: 0; height: 0;
  background: rgba(10, 8, 6, .68); pointer-events: none;
  border-top: 1px solid rgba(217, 164, 65, .5);
}
@media (hover: none) and (pointer: coarse) {
  .mw-actionbar { bottom: 130px; gap: 5px; }
  .mw-slot { width: 42px; height: 42px; }
  .mw-slot-icon { font-size: 19px; line-height: 42px; }
  .mw-slot > b { display: none; }
}

/* ── boss bar ────────────────────────────────────────────────────────────
   Top centre and wide, because a boss is the only thing in this world that
   is worth taking the middle of the screen for. */
.mw-boss {
  position: absolute; left: 50%; top: 96px; transform: translateX(-50%);
  width: min(560px, 82vw); pointer-events: none; text-align: center;
  background: rgba(20, 14, 12, .74); border: 1px solid #6a3b30;
  border-radius: 12px; padding: 8px 14px 9px; backdrop-filter: blur(6px);
  box-shadow: 0 6px 26px rgba(0, 0, 0, .5);
}
.mw-boss-name { font-size: 15px; letter-spacing: .02em; color: #e8cfa4; }
.mw-boss-name span { color: var(--mw-dim); font-size: 12px; }
.mw-boss-bar {
  margin: 6px 0 4px; height: 12px; border-radius: 6px; overflow: hidden;
  background: rgba(8, 6, 5, .8); border: 1px solid #4c2a22;
}
.mw-boss-bar > i {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, #7d1d18, #c0392b 60%, #e07a3a);
  transition: width .18s linear;
}
.mw-boss-bar > i.rage { background: linear-gradient(90deg, #b3160f, #ff5a2b); }
.mw-boss-note { font-size: 11.5px; color: var(--mw-dim); min-height: 14px; }
.mw-boss-note.warded { color: #8fc6ea; }
.mw-boss-note.rage { color: #ff8f6a; }

/* ── loot ────────────────────────────────────────────────────────────────
   The prompt sits where the gate hint does but a little lower, because both
   can legitimately be on screen at once (a sack in a gateway). */
.mw-loot-prompt {
  position: absolute; left: 50%; bottom: 172px; transform: translateX(-50%);
  background: var(--mw-panel); border: 1px solid var(--mw-line); color: var(--mw-ink);
  border-radius: 12px; padding: 6px 14px; font-size: 13.5px; white-space: nowrap;
  backdrop-filter: blur(6px); pointer-events: none;
}
.mw-loot-prompt b {
  display: inline-block; min-width: 17px; margin-right: 6px; padding: 1px 5px;
  border-radius: 5px; background: var(--mw-gold); color: #241c0c;
  font-size: 11px; text-align: center;
}
.mw-dimtext { color: var(--mw-dim); }

/* The touch E. Shown only while the server has a prompt up; sits under the
   prompt line where a right thumb already rests near the jump/punch pair. */
.mw-act-btn {
  position: absolute; left: 50%; bottom: 118px; transform: translateX(-50%);
  background: var(--mw-gold); color: #241c0c; border: 0; border-radius: 12px;
  padding: 10px 22px; font-size: 14px; font-weight: 600;
  box-shadow: 0 2px 10px rgba(0,0,0,.45);
}
.mw-act-btn:active { filter: brightness(1.15); }
@media (hover: hover) and (pointer: fine) { .mw-act-btn { display: none; } }

/* ── aim ─────────────────────────────────────────────────────────────────
   The crosshair. Hidden until aim mode is on; `bloom` is the flinch beat. */
#mwCross {
  position: absolute; left: 50%; top: 50%; width: 14px; height: 14px;
  transform: translate(-50%, -50%); pointer-events: none; opacity: 0;
  transition: opacity .12s;
}
#mwCross::before, #mwCross::after {
  content: ''; position: absolute; background: rgba(255, 232, 180, .95);
  box-shadow: 0 0 3px rgba(0,0,0,.8);
}
#mwCross::before { left: 6px; top: 0; width: 2px; height: 14px; }
#mwCross::after { left: 0; top: 6px; width: 14px; height: 2px; }
#mwCross.on { opacity: 1; }
#mwCross.bloom { opacity: 1; transform: translate(-50%, -50%) scale(1.5); }
.mw-buttons button.on { background: var(--mw-gold); color: #241c0c; }

.mw-loot-feed {
  position: absolute; right: 16px; bottom: 90px; width: 250px;
  display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
  pointer-events: none;
}
.mw-loot-line {
  display: flex; align-items: center; gap: 7px; max-width: 100%;
  background: rgba(20, 18, 15, .8); border: 1px solid var(--mw-line);
  border-radius: 9px; padding: 4px 10px; font-size: 12.5px;
  transition: opacity .6s, transform .6s;
}
.mw-loot-line b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mw-loot-line em { font-style: normal; color: var(--mw-dim); }
.mw-loot-line.go { opacity: 0; transform: translateX(24px); }

/* ── floating numbers ───────────────────────────────────────────────────── */
.mw-floats {
  position: absolute; left: 50%; top: 42%; width: 0; height: 0;
  pointer-events: none;
}
.mw-float {
  position: absolute; left: 0; top: 0; transform: translateX(-50%);
  font-weight: 700; text-shadow: 0 2px 4px #000; white-space: nowrap;
}
.mw-levelup {
  position: absolute; left: 50%; top: 34%; transform: translate(-50%, 0) scale(.8);
  font-size: 40px; letter-spacing: .08em; color: var(--mw-gold);
  text-shadow: 0 0 22px rgba(217, 164, 65, .7), 0 3px 8px #000;
  opacity: 0; transition: opacity .5s, transform .9s cubic-bezier(.2, 1.4, .4, 1);
  pointer-events: none;
}
.mw-levelup.go { opacity: 1; transform: translate(-50%, -18px) scale(1); }

/* ── the pack / character sheet ─────────────────────────────────────────── */
.mw-pack {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 58; width: min(820px, 94vw); max-height: 86vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line);
  border-radius: 14px; backdrop-filter: blur(10px);
  box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-pack-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 11px 16px; border-bottom: 1px solid var(--mw-line);
  color: var(--mw-gold); font-size: 15px;
}
.mw-pack-head button {
  background: none; border: none; color: var(--mw-dim); cursor: pointer; font-size: 16px;
}
.mw-pack-body {
  display: grid; grid-template-columns: 262px 1fr; gap: 0; overflow: hidden; flex: 1;
}
.mw-sheet {
  padding: 14px 16px; border-right: 1px solid var(--mw-line); overflow: auto;
}
.mw-sheet-level { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.mw-sheet-level b { font-size: 30px; color: var(--mw-gold); line-height: 1; }
.mw-sheet-level span { color: var(--mw-dim); font-size: 12px; }
.mw-slots { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.mw-slot-row {
  display: flex; align-items: center; gap: 7px; font-size: 12.5px;
  background: #221e19; border: 1px solid var(--mw-line); border-radius: 8px;
  padding: 6px 8px;
}
.mw-slot-row.empty { opacity: .55; }
.mw-slot-tag { width: 52px; color: var(--mw-dim); font-size: 11px; flex: none; }
.mw-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mw-off {
  background: #1b1814; border: 1px solid var(--mw-line); color: var(--mw-dim);
  border-radius: 6px; font: inherit; font-size: 10.5px; padding: 2px 6px; cursor: pointer;
}
.mw-off:hover { border-color: var(--mw-gold); color: var(--mw-ink); }
.mw-statlist { display: flex; flex-direction: column; gap: 3px; }
.mw-stat {
  display: flex; justify-content: space-between; font-size: 12.5px;
  color: var(--mw-dim); padding: 2px 1px;
}
.mw-stat b { color: var(--mw-ink); font-variant-numeric: tabular-nums; }
.mw-effects { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 5px; }
.mw-fx {
  font-size: 11px; padding: 2px 8px; border-radius: 10px;
  background: rgba(90, 169, 230, .16); border: 1px solid #3d6a8c; color: #b8dcf5;
}
.mw-fx b { font-variant-numeric: tabular-nums; }

.mw-bagwrap { display: flex; flex-direction: column; overflow: hidden; }
.mw-bag-head {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 12px 16px 8px; color: var(--mw-dim); font-size: 12px;
}
.mw-bag-hint { font-size: 11px; opacity: .7; }
.mw-bag {
  overflow: auto; padding: 0 16px 16px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px;
  align-content: start;
}
.mw-cell {
  position: relative; display: flex; align-items: center; gap: 7px; text-align: left;
  background: #221e19; border: 1px solid var(--mw-line); border-radius: 8px;
  padding: 7px 8px; font: inherit; font-size: 12px; color: var(--mw-ink); cursor: pointer;
}
.mw-cell:hover { border-color: var(--mw-gold); }
.mw-cell.locked { opacity: .5; }
.mw-cell-icon { font-size: 17px; flex: none; }
.mw-cell-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mw-cell em { font-style: normal; color: var(--mw-gold); font-size: 11px; }
.mw-cell-req {
  position: absolute; right: 6px; bottom: 1px; font-style: normal;
  font-size: 9px; color: #c88; }

.mw-tip {
  position: fixed; z-index: 80; width: 230px; pointer-events: none;
  background: rgba(16, 14, 12, .96); border: 1px solid var(--mw-line);
  border-radius: 9px; padding: 9px 11px; font-size: 12px; line-height: 1.5;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; gap: 3px;
}
.mw-tip-sub { color: var(--mw-dim); font-size: 11px; }
.mw-tip-text { color: #ddd4c2; }

@media (max-width: 720px) {
  .mw-card { padding: 18px 16px; }
  .mw-card h1 { font-size: 24px; }
  .mw-chars { grid-template-columns: repeat(2, 1fr); }
  .mw-top { left: 12px; right: 12px; top: 50px; justify-content: center; }
  /* A phone is not wide enough for the panel AND the map side by side, so the map
     stands down while the panel is up rather than being half-covered by it. Same
     `:has()` trick as the quest panel and the act button below. */
  /* Below the pill row, which WRAPS to two lines on a phone (measured: 50..119 at
     412 px wide) — 96 put the panel's title behind the Leave button. One number for
     the whole left column: the party frame follows it. */
  :root { --mw-lcol-top: 128px; }
  .mw-audio { left: 12px; right: 12px; width: auto; }
  body:has(#audioPanel:not([hidden])) .mw-map { display: none; }
  .mw-pack-body { grid-template-columns: 1fr; }
  .mw-sheet { border-right: none; border-bottom: 1px solid var(--mw-line); }
  .mw-loot-feed { width: 190px; bottom: 200px; }
  .mw-boss { top: 84px; }
}
@media (hover: none) and (pointer: coarse) {
  .mw-xp { bottom: 210px; }
  .mw-xp-bar { width: 132px; }
  .mw-coin { font-size: 11px; }
  .mw-loot-prompt { bottom: 282px; }
}


/* ══ quests ══════════════════════════════════════════════════════════════
   The marker, the tracker, the conversation and the journal. Everything here
   is the same parchment-over-iron the pack uses, because they are two halves
   of the same character screen and a second visual language would read as a
   second game. */

/* The `!` over a head. A DOM element positioned by game.js updateTags, exactly
   like a name tag — see the note at the top of quests.js for why it is not a
   billboard in the scene. */
.mw-mk {
  position: absolute; transform: translate(-50%, -100%); pointer-events: none;
  font-weight: 800; font-size: 26px; line-height: 1;
  font-family: "Georgia", ui-serif, serif;
  text-shadow: 0 0 6px rgba(0, 0, 0, .95), 0 2px 3px #000;
  animation: mw-mk-bob 2.2s ease-in-out infinite;
}
.mw-mk.open { color: #ffd53d; }
.mw-mk.turn { color: #ffb02e; }
/* Grey: this character has something, but not for you yet. Dimmer AND smaller,
   so a town of future errands does not shout as loudly as one you can take. */
.mw-mk.lock { color: #8d8578; font-size: 20px; opacity: .8; animation: none; }
@keyframes mw-mk-bob {
  0%, 100% { margin-top: 0; }
  50% { margin-top: -5px; }
}

/* The same glyph INSIDE a sentence. It cannot be `.mw-mk`, which is
   position:absolute and animated — used inline it flew out of the paragraph and
   bobbed in the middle of the panel. */
.mw-mk-in { color: #ffd53d; font-weight: 800; }

/* The tracked errand, under the compass pills at the top right. */
.mw-track {
  position: absolute; right: 12px; top: 92px; width: 210px; pointer-events: none;
  background: rgba(20, 18, 15, .62); border: 1px solid var(--mw-line);
  border-left: 3px solid var(--mw-gold); border-radius: 8px;
  padding: 8px 11px; font-size: 12px; text-shadow: 0 1px 2px #000;
}
.mw-track b { color: var(--mw-gold); font-size: 13px; }
.mw-track ul { margin: 5px 0 0; padding: 0 0 0 14px; color: var(--mw-dim); }
.mw-track li.done { color: #8fce7e; text-decoration: line-through; }
/* THE PANEL IS `pointer-events: none` so it cannot eat a click meant for the world.
   The one thing in it that IS a control has to opt back in, or the button draws
   perfectly and never fires. 26 px, because a thumb has to be able to hit it. */
.mw-track .mw-track-drop {
  position: absolute; top: 5px; right: 5px; pointer-events: auto;
  width: 26px; height: 26px; padding: 0; line-height: 24px; text-align: center;
  background: rgba(20, 18, 15, .5); border: 1px solid var(--mw-line);
  border-radius: 6px; color: var(--mw-dim); font-size: 13px; cursor: pointer;
  touch-action: manipulation;
}
.mw-track .mw-track-drop:hover { color: #e0553f; border-color: #e0553f; }
/* ...and the title must not run under it. */
.mw-track b { display: block; padding-right: 26px; }

/* The conversation. Bottom centre rather than middle of the screen: you are
   standing in front of somebody, and covering them with the thing they are
   saying is the one place a dialogue box must not be. */
.mw-talk {
  /* LEFT, not bottom centre. The camera is behind the player and the person they
     are talking to is dead ahead in the middle of the screen; a centred panel
     covers the one thing the conversation is about. Down the left margin leaves
     the character, their marker and the world in view while they speak. */
  position: fixed; left: 20px; top: 50%; transform: translateY(-50%);
  z-index: 58; width: min(430px, 92vw); max-height: 74vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line);
  border-radius: 14px; backdrop-filter: blur(10px);
  box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-talk-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-bottom: 1px solid var(--mw-line);
}
.mw-talk-head b { color: var(--mw-gold); font-size: 15px; }
.mw-talk-head span { color: var(--mw-dim); font-size: 12px; }
.mw-talk-head button {
  margin-left: auto; background: none; border: none; color: var(--mw-dim);
  cursor: pointer; font-size: 16px;
}
.mw-talk-body { padding: 12px 14px; overflow: auto; }

/* The journal. Same frame as the pack, so I and J feel like one screen. */
.mw-journal {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 58; width: min(620px, 94vw); max-height: 84vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line);
  border-radius: 14px; backdrop-filter: blur(10px);
  box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-journal-body { padding: 12px 16px; overflow: auto; }

/* One errand, in either panel. */
.mw-q {
  border: 1px solid var(--mw-line); border-radius: 10px;
  background: rgba(255, 255, 255, .03);
  padding: 10px 12px; margin-bottom: 10px;
}
.mw-q.ready { border-color: var(--mw-gold); }
.mw-q-title {
  display: flex; align-items: baseline; gap: 8px;
  color: var(--mw-gold); font-size: 14px;
}
.mw-q-lv { margin-left: auto; color: var(--mw-dim); font-size: 11px; }
.mw-q-pitch {
  margin: 7px 0 0; font-size: 13px; line-height: 1.5; color: var(--mw-ink);
  font-style: italic; opacity: .92;
}
.mw-q-obj { margin: 8px 0 0; padding: 0 0 0 16px; font-size: 12.5px; }
.mw-q-obj li { margin: 2px 0; color: var(--mw-ink); }
.mw-q-obj li.done { color: #8fce7e; }
.mw-q-obj em { color: var(--mw-dim); font-style: normal; }
.mw-q-sec {
  color: var(--mw-dim); font-size: 11px; letter-spacing: .09em;
  text-transform: uppercase; margin: 4px 0 6px;
}
.mw-q-locked { color: var(--mw-dim); font-size: 12px; margin: 4px 0; opacity: .8; }
.mw-q-idle { color: var(--mw-dim); font-size: 13px; }
.mw-q-donelist {
  margin: 0; padding: 0 0 0 16px; color: var(--mw-dim); font-size: 12px;
}
.mw-q-btns { display: flex; gap: 8px; margin-top: 10px; }
.mw-q-btns button {
  font-family: inherit; cursor: pointer; font-size: 13px;
  padding: 5px 14px; border-radius: 8px;
  background: linear-gradient(#d9a441, #a9741d); color: #241c0c;
  border: 1px solid #6d4c14; font-weight: 700;
}
.mw-q-btns button.ghost {
  background: none; color: var(--mw-dim); border: 1px solid var(--mw-line);
  font-weight: 400;
}
.mw-q-btns button:hover { filter: brightness(1.08); }

/* The reward row. */
.mw-rw { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.mw-rw span {
  font-size: 11.5px; padding: 2px 8px; border-radius: 10px;
  border: 1px solid var(--mw-line); background: rgba(0, 0, 0, .25);
}
.mw-rw-xp { color: #cfe08a; }
.mw-rw-gold { color: #e8c65a; }
/* The four tiers, and these MUST equal rpg.js's RARITY table — a test parses both and
   fails if they drift. They already had: rare was #5aa9e6 here, #6fa8dc in shop.js and
   #5aa9e6 in rpg.js, so the same item was three colours in three panels. */
.mw-r-normal { color: #d8d2c4; }
.mw-r-rare { color: #f2c438; }
.mw-r-epic { color: #b46ee0; }
.mw-r-legendary { color: #c0762b; }
/* Retired rungs, aliased rather than dropped: a message written before the four tiers
   must not render as unstyled default text. */
.mw-r-common, .mw-r-uncommon { color: #d8d2c4; }

/* The completion flash. */
.mw-questdone {
  position: absolute; left: 50%; top: 26%; transform: translate(-50%, 8px);
  text-align: center; pointer-events: none; opacity: 0;
  transition: opacity .35s ease-out, transform .35s ease-out;
}
.mw-questdone.go { opacity: 1; transform: translate(-50%, 0); }
.mw-questdone b {
  display: block; color: var(--mw-gold); font-size: 26px;
  text-shadow: 0 2px 10px #000;
}
.mw-questdone span {
  color: var(--mw-dim); font-size: 12px; letter-spacing: .16em;
  text-transform: uppercase;
}

/* Mobile: a thumb has no J key. Hidden on a desktop, where it would be clutter
   beside a keybinding that already works. */
.mw-journal-btn {
  position: absolute; right: 12px; bottom: 210px; z-index: 20;
  width: 46px; height: 46px; border-radius: 50%; font-size: 21px; line-height: 1;
  background: var(--mw-panel); border: 1px solid var(--mw-line);
  color: var(--mw-ink); cursor: pointer; pointer-events: auto;
}
@media (hover: hover) and (pointer: fine) { .mw-journal-btn { display: none; } }

/* While a quest panel is up, the mobile E button is both redundant and in the way:
   the panel carries its own Accept / Hand in, and on a phone the panel's bottom edge
   lands right on top of it. `:has()` because the two live in different subtrees —
   the act button is in #mwHud and the panels are on <body>. */
body:has(.mw-talk:not([hidden])) .mw-act-btn,
body:has(.mw-journal:not([hidden])) .mw-act-btn { display: none; }
@media (hover: none) and (pointer: coarse) {
  /* On a phone there is no left margin to spare, so it goes back to the bottom -
     and above the touch controls, which own the lower third. */
  .mw-talk {
    left: 50%; top: auto; bottom: 132px; transform: translateX(-50%);
    width: 94vw; max-height: 44vh;
  }
  .mw-track { width: 160px; top: 84px; font-size: 11px; }
}


/* ══ the fly view (dev only) ═════════════════════════════════════════════
   Bottom left, out of the way of the map and the pack: this panel is read while
   the whole screen is the thing being looked at. */
.mw-fly {
  position: fixed; left: 12px; bottom: 12px; z-index: 56; width: 232px;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 12px;
  backdrop-filter: blur(8px); box-shadow: 0 12px 40px rgba(0, 0, 0, .5);
  padding: 10px 12px 11px; font-size: 12px; pointer-events: auto;
}
.mw-fly-head {
  display: flex; justify-content: space-between; align-items: center;
  color: var(--mw-gold); font-size: 13px; margin-bottom: 6px;
}
.mw-fly-head button {
  font: inherit; font-size: 11px; padding: 3px 8px; border-radius: 6px;
  cursor: pointer; background: #221e19; border: 1px solid var(--mw-line);
  color: var(--mw-dim);
}
.mw-fly-where {
  color: var(--mw-ink); font-variant-numeric: tabular-nums; font-size: 11.5px;
  margin-bottom: 8px;
}
.mw-fly-speeds, .mw-fly-maps { display: flex; flex-wrap: wrap; gap: 4px; }
.mw-fly-maps { margin-top: 6px; }
.mw-fly-speeds button, .mw-fly-maps button {
  font: inherit; font-size: 11px; padding: 4px 8px; border-radius: 6px;
  cursor: pointer; background: #221e19; border: 1px solid var(--mw-line);
  color: var(--mw-ink);
}
.mw-fly-speeds button em { font-style: normal; color: var(--mw-dim); font-size: 10px; }
.mw-fly-speeds button.on, .mw-fly-maps button.on {
  background: var(--mw-gold); color: #241c0c; border-color: var(--mw-gold);
}
.mw-fly-speeds button.on em { color: #241c0c; }
.mw-fly-keys { margin-top: 8px; color: var(--mw-dim); font-size: 10.5px; line-height: 1.5; }
/* The pill had NO position of its own, so it fell where a static child of #mwHud
   falls — 0,0 — and the back link (fixed, left 12 top 12, z-index 60) sat on top
   of it. The result was a button you could see the edge of and never read. It is
   chrome of the same kind as the back link, so it goes beside it: the link is 81
   px wide at the longest label this page has. */
.mw-fly-pill {
  position: absolute; left: 105px; top: 12px; z-index: 58; pointer-events: auto;
}
.mw-fly-pill.on { border-color: var(--mw-gold); color: var(--mw-gold); }

@media (hover: none) and (pointer: coarse) {
  /* No WASD on a phone, so the panel is the map hops and the readout only. */
  .mw-fly { width: 190px; bottom: 250px; }
  .mw-fly-keys { display: none; }
}


/* ══ the shop ════════════════════════════════════════════════════════════
   The pack's frame, two columns: what they sell and what you carry. A trade is a
   comparison, so the two lists have to be on screen at once. */
.mw-shop {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 59; width: min(760px, 94vw); max-height: 86vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 14px;
  backdrop-filter: blur(10px); box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-shop-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 11px 16px; border-bottom: 1px solid var(--mw-line);
  color: var(--mw-gold); font-size: 15px;
}
.mw-shop-head button {
  background: none; border: none; color: var(--mw-dim); cursor: pointer; font-size: 16px;
}
.mw-shop-greet {
  margin: 0; padding: 9px 16px 4px; color: var(--mw-dim);
  font-size: 12.5px; font-style: italic;
}
.mw-shop-body {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  overflow: auto; padding: 6px 16px 12px; flex: 1;
}
.mw-shop-col { min-width: 0; padding-right: 10px; }
.mw-shop-col + .mw-shop-col { padding: 0 0 0 10px; border-left: 1px solid var(--mw-line); }
.mw-shop-colhead {
  display: flex; justify-content: space-between; align-items: baseline;
  color: var(--mw-dim); font-size: 11px; letter-spacing: .05em;
  text-transform: uppercase; margin: 10px 0 6px;
}
.mw-shop-row {
  position: relative; display: flex; align-items: center; gap: 7px;
  background: #221e19; border: 1px solid var(--mw-line); border-radius: 8px;
  padding: 6px 8px; font-size: 12px; margin-bottom: 5px; cursor: pointer;
}
.mw-shop-row:hover { border-color: var(--mw-gold); }
.mw-shop-row.locked { opacity: .5; }
.mw-shop-row em { font-style: normal; color: var(--mw-gold); font-size: 11px; }
.mw-shop-price {
  margin-left: auto; color: var(--mw-gold); font-variant-numeric: tabular-nums;
  display: flex; align-items: center; gap: 3px; flex: none;
}
.mw-shop-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 16px; border-top: 1px solid var(--mw-line);
}
.mw-shop-hint { color: var(--mw-dim); font-size: 11px; flex: 1; }
.mw-qty-wrap { display: flex; gap: 4px; }
.mw-shop-foot button, .mw-qty {
  font: inherit; font-size: 12px; padding: 5px 10px; border-radius: 7px;
  cursor: pointer; background: #221e19; border: 1px solid var(--mw-line);
  color: var(--mw-ink);
}
.mw-qty.on { background: var(--mw-gold); color: #241c0c; border-color: var(--mw-gold); }
#mwShopRespec { border-color: var(--mw-red); color: #ffb3a6; display: flex; gap: 4px; }
.mw-q-shop button {
  font: inherit; font-size: 13px; padding: 7px 13px; border-radius: 8px;
  cursor: pointer; background: #221e19; border: 1px solid var(--mw-gold);
  color: var(--mw-gold);
}

/* ══ the skill tree ══════════════════════════════════════════════════════
   Three paths side by side, three tiers deep, and the connector between tiers is
   DRAWN. That line is the whole point: it says without words that the capstone at
   the bottom is paid for by what is above it. */
.mw-skills {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 59; width: min(940px, 96vw); max-height: 88vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 14px;
  backdrop-filter: blur(10px); box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-skill-pts {
  color: var(--mw-gold); font-size: 13px; font-variant-numeric: tabular-nums;
}
.mw-skill-note {
  margin: 0; padding: 8px 16px 0; color: var(--mw-dim);
  font-size: 12px; font-style: italic;
}
.mw-skill-note:empty { display: none; }
.mw-skill-body {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
  overflow: auto; padding: 12px 16px 16px;
}
.mw-path { display: flex; flex-direction: column; align-items: stretch; min-width: 0; }
.mw-path-head {
  display: flex; justify-content: space-between; align-items: baseline;
  color: var(--mw-gold); font-size: 13px; letter-spacing: .06em;
  padding-bottom: 8px; border-bottom: 1px solid var(--mw-line); margin-bottom: 10px;
}
.mw-path-head span { color: var(--mw-dim); font-size: 11px; }
/* The connector. A 2 px rule with a diamond on it, centred under the node above. */
.mw-path-link {
  height: 18px; position: relative; margin: 2px 0;
}
.mw-path-link::before {
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px;
  transform: translateX(-50%); background: var(--mw-line);
}
.mw-path-link::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: 7px; height: 7px;
  transform: translate(-50%, -50%) rotate(45deg); background: var(--mw-line);
}
.mw-tier-gate {
  text-align: center; font-size: 10.5px; color: var(--mw-dim);
  letter-spacing: .04em; margin-bottom: 6px;
}
.mw-tier-gate.met { color: #7fc98a; }
.mw-node {
  position: relative; background: #221e19; border: 1px solid var(--mw-line);
  border-radius: 10px; padding: 9px 10px 8px; cursor: pointer;
}
.mw-node.open { border-color: var(--mw-gold); box-shadow: 0 0 14px rgba(217,164,65,.18); }
.mw-node.learned { border-color: #7fc98a; }
.mw-node.maxed { border-color: #7fc98a; background: #1f2a1e; cursor: default; }
.mw-node.locked { opacity: .48; cursor: not-allowed; }
.mw-node-icon { font-size: 17px; }
.mw-node-name { font-size: 13px; color: var(--mw-ink); margin-left: 5px; }
.mw-node-pips { float: right; display: flex; gap: 3px; padding-top: 6px; }
.mw-node-pips i {
  width: 7px; height: 7px; border-radius: 50%; display: block;
  border: 1px solid var(--mw-line); background: #15120f;
}
.mw-node-pips i.on { background: var(--mw-gold); border-color: var(--mw-gold); }
.mw-node-text { margin: 5px 0 3px; font-size: 11.5px; color: var(--mw-dim); line-height: 1.45; }
.mw-node-gain { margin: 0; font-size: 11px; color: #cfc4a6; }
.mw-node-tag {
  position: absolute; right: 8px; bottom: 6px; font-style: normal; font-size: 9px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--mw-gold); opacity: .8;
}
.mw-node-why { display: block; margin-top: 5px; font-style: normal; font-size: 10.5px; color: #c88; }
.mw-skill-nag {
  position: absolute; right: 12px; z-index: 23; pointer-events: auto;
  font-size: 12px;
  top: calc(var(--mw-col-top) + var(--mw-map-h) + var(--mw-col-gap));
  height: var(--mw-nag-h); box-sizing: border-box;
}
.mw-skill-nag.urgent { border-color: var(--mw-gold); color: var(--mw-gold); }

@media (max-width: 900px) {
  .mw-shop-body { grid-template-columns: 1fr; }
  .mw-shop-col + .mw-shop-col { padding: 0; border-left: none; border-top: 1px solid var(--mw-line); }
  .mw-skill-body { grid-template-columns: 1fr; }
}
@media (hover: none) and (pointer: coarse) {
  .mw-shop, .mw-skills { width: 96vw; max-height: 80vh; }
  /* the column's one number on a phone; the nag and the tracker follow it */
  #mwHud { --mw-map-h: 196px; --mw-col-top: 58px; }
  /* The hint wraps to three words a line on a phone and the quantity steps are the
     thing the thumb needs. Tapping a row is discoverable without being told. */
  .mw-shop-hint { display: none; }
}


/* ══ dragging the pack ═══════════════════════════════════════════════════
   The ghost follows the pointer and must never be under it — `elementFromPoint`
   is how the drop target is found, and a ghost that answers it would mean every
   drag lands on itself. */
.mw-drag {
  position: fixed; z-index: 90; pointer-events: none;
  display: flex; align-items: center; gap: 7px;
  background: rgba(34, 30, 25, .96); border: 1px solid var(--mw-gold);
  border-radius: 8px; padding: 6px 9px; font-size: 12px; max-width: 210px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .6);
}
/* Outside the panel means "throw it on the ground", and that has to be visible
   BEFORE the finger lifts. */
.mw-drag.discard { border-color: var(--mw-red); opacity: .8; }
.mw-drag.discard::after { content: '✕'; color: var(--mw-red); font-size: 13px; }

body.mw-dragging { user-select: none; -webkit-user-select: none; }
body.mw-dragging .mw-cell, body.mw-dragging .mw-slot-row { cursor: grabbing; }
.mw-cell.mw-dragged, .mw-slot-row.mw-dragged { opacity: .38; }
.mw-cell.mw-drop, .mw-slot-row.mw-drop {
  border-color: var(--mw-gold); box-shadow: inset 0 0 0 1px var(--mw-gold);
}
.mw-cell.mw-drop-bad, .mw-slot-row.mw-drop-bad { border-color: var(--mw-red); }

@keyframes mw-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}
.mw-shake { animation: mw-shake .4s; }

/* Throwing away something rare asks first. Its own element, not window.confirm:
   that one blocks the render loop and a phone browser may suppress it. */
.mw-confirm {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 92; width: min(340px, 88vw); text-align: center;
  background: var(--mw-panel); border: 1px solid var(--mw-gold); border-radius: 12px;
  padding: 18px 20px 14px; box-shadow: 0 20px 60px rgba(0, 0, 0, .6);
  backdrop-filter: blur(10px);
}
.mw-confirm p { margin: 0 0 14px; font-size: 14px; }
.mw-confirm div { display: flex; gap: 8px; justify-content: center; }
.mw-confirm button {
  font: inherit; font-size: 13px; padding: 7px 13px; border-radius: 8px;
  cursor: pointer; background: #221e19; border: 1px solid var(--mw-line);
  color: var(--mw-ink);
}
.mw-confirm .mw-yes { border-color: var(--mw-red); color: #ffb3a6; }
.mw-confirm button:hover { border-color: var(--mw-gold); }

/* A cell is a drag handle on a phone, so the browser must not claim the gesture
   for a scroll or a text selection first. The BAG still scrolls — the drag only
   starts after a long press, and a finger that moves before then is let go. */
@media (hover: none) and (pointer: coarse) {
  .mw-cell, .mw-slot-row { touch-action: pan-y; }
  body.mw-dragging .mw-bag { touch-action: none; }
}


/* ══ minimap ═════════════════════════════════════════════════════════════
   Top right, under the pills. A square rather than a disc: the two blips that
   matter are CLAMPED TO THE RIM when they fall outside the window, and a rim
   you can print a name against is worth more here than a rounded corner. */
.mw-map {
  position: absolute; right: 12px; top: var(--mw-col-top);
  width: 186px; z-index: 22;
  border: 1px solid var(--mw-line); border-radius: 8px; overflow: hidden;
  background: rgba(20, 18, 15, .72); backdrop-filter: blur(4px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .45);
  pointer-events: auto;
}
/* Square canvas, chrome underneath. The panel's HEIGHT is the canvas plus the
   footer, which is why nothing here pins it: the rim of the canvas is where the
   off-window gates and errands print their names, and a title or a button that
   floats over it takes that rim away from them. */
.mw-map canvas { display: block; width: 100%; aspect-ratio: 1 / 1; }
.mw-map-foot {
  display: flex; align-items: center; gap: 4px; padding: 3px 3px 3px 7px;
  border-top: 1px solid var(--mw-line); background: rgba(20, 18, 15, .82);
}
.mw-map-title {
  flex: 1 1 auto; min-width: 0; text-align: left;
  font-size: 10.5px; color: var(--mw-gold); letter-spacing: .06em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  pointer-events: none; text-shadow: 0 1px 2px #000;
}
.mw-map-btns { display: flex; gap: 3px; flex: 0 0 auto; }
.mw-map-btns button {
  width: 22px; height: 22px; line-height: 1; font-size: 13px; cursor: pointer;
  font-family: inherit; color: var(--mw-dim); border-radius: 5px;
  background: rgba(20, 18, 15, .78); border: 1px solid var(--mw-line);
}
.mw-map-btns button:hover { color: var(--mw-ink); border-color: var(--mw-gold); }

/* The whole world, on M. Centred and large, because the reason to open it is to
   read the names on it. */
.mw-map.big {
  right: 50%; top: 50%; transform: translate(50%, -50%);
  width: min(74vh, 78vw); z-index: 57;
  border-color: var(--mw-gold);
}
.mw-map.big .mw-map-title { font-size: 15px; text-align: center; padding-left: 26px; }
.mw-map.big .mw-map-foot { padding: 5px; }

/* ── the right-hand column ────────────────────────────────────────────────
   Minimap, then the skill-point nag, then the quest tracker, and all three were
   positioned with independent magic numbers. Measured in a browser, they did not
   fit: on desktop the nag (272-302) overlapped BOTH the map (62-279) and the
   tracker (288-354); on a phone the nag at 212-242 sat entirely on top of a
   minimap that runs 58-254. Nothing failed — every element was exactly where its
   own rule put it, which is why three separate probes were happy.

   One number per breakpoint now, and the two below it are arithmetic. The nag's
   slot is held even when the nag is hidden: a tracker that jumps 38 px the moment
   you spend a skill point is worse than a gap. */
.mw-track { top: calc(var(--mw-col-top) + var(--mw-map-h)
                      + var(--mw-col-gap) + var(--mw-nag-h) + var(--mw-col-gap)); }

@media (hover: none) and (pointer: coarse) {
  /* A phone panel is 150 px wide and three buttons take half of it, so the name
     goes on its own line rather than being elided to "Raven…" — the map name is
     how a player knows which of five worlds they are looking at. */
  .mw-map { width: 150px; }
  .mw-map-foot { flex-wrap: wrap; justify-content: center; padding: 3px; }
  .mw-map-title { flex: 1 0 100%; text-align: center; font-size: 10px; }
  .mw-track { width: 150px; }
  .mw-map.big { width: 92vw; }
  .mw-map.big .mw-map-title { padding-left: 0; }
}


/* ══ speech ══════════════════════════════════════════════════════════════
   The caption along the bottom, and the bubble over a citizen's head.

   THE CLASS IS `.mw-say`, NOT `.mw-sub`. `.mw-sub` is TAKEN: it is the dim
   paragraph under the title on the join screen, forty lines up this file. This
   project has now paid for that mistake twice — `.mw-bar` rendered the action
   bar as a four-pixel letterbox with six buttons hanging out of it, and
   `.mw-track` put the sound bar through the quest tracker — and both times the
   DOM was perfectly correct and the probe was perfectly happy.

   WHERE IT SITS, and why 200 is not a round number. The bottom of this screen is
   a stack, and every tenant of it is measured elsewhere in this file:

     .mw-actionbar   bottom  16  height 46   ->  16 ..  62
     .mw-emotes      bottom  72  height 44   ->  72 .. 116
     .mw-act-btn     bottom 118  (touch only)
     .mw-gate        bottom 126
     .mw-loot-prompt bottom 172

   200 clears all of it with room for a second line to wrap into. On a coarse
   pointer the whole stack moves up (130, 182, 236) and so does this. */
.mw-say {
  position: absolute; left: 50%; bottom: 200px; transform: translateX(-50%);
  width: min(760px, 84vw); box-sizing: border-box;
  padding: 9px 18px 10px; border-radius: 12px;
  background: rgba(14, 12, 10, .78); border: 1px solid var(--mw-line);
  backdrop-filter: blur(7px); box-shadow: 0 6px 26px rgba(0, 0, 0, .45);
  color: var(--mw-ink); font-size: 15px; line-height: 1.45; text-align: center;
  text-shadow: 0 1px 2px #000; pointer-events: none; z-index: 40;
  opacity: 0; transform-origin: 50% 100%;
  transition: opacity .22s ease-out;
}
.mw-say.on { opacity: 1; }
.mw-say b {
  display: block; color: var(--mw-gold); font-size: 12.5px;
  letter-spacing: .06em; text-transform: uppercase; margin-bottom: 2px;
}
/* Overheard, not addressed to you. Dimmer and a shade smaller, so a player can tell
   at a glance whether the sentence on screen is one they are meant to answer. */
.mw-say.ambient {
  background: rgba(14, 12, 10, .62); font-size: 14px; width: min(620px, 80vw);
}
.mw-say.ambient b { color: #c8bda4; }

/* The bubble. Positioned by game.js updateTags, exactly like the `!` marker and the
   name tag — see the header of dialogue.js for why nothing here is parented into an
   avatar. It must not read as a name tag, so: no gold, a plate rather than bare
   text, and a hard width cap. */
.mw-bubble {
  position: absolute; transform: translate(-50%, -100%);
  max-width: 230px; padding: 5px 10px 6px; border-radius: 10px;
  background: rgba(18, 16, 13, .84); border: 1px solid rgba(160, 150, 130, .38);
  color: #ded6c6; font-size: 12px; line-height: 1.35; text-align: center;
  text-shadow: 0 1px 2px #000; pointer-events: none; white-space: normal;
  backdrop-filter: blur(4px);
}

@media (hover: none) and (pointer: coarse) {
  .mw-say { bottom: 272px; font-size: 14px; width: 92vw; padding: 8px 12px 9px; }
  .mw-say.ambient { width: 88vw; font-size: 13px; }
  .mw-bubble { max-width: 180px; font-size: 11px; }
}

/* On a phone the conversation panel is itself at `bottom: 132px` with `max-height:
   44vh`, which lands directly on top of the caption — and in that one case the panel
   is already printing the same words, larger and scrollable, so the caption is pure
   loss. `:has()` because the two live in different subtrees (the caption is on <body>
   and so is the panel, but the rule has to be conditional on the panel's state); the
   file already uses this idiom for `.mw-act-btn` twenty lines up. DESKTOP KEEPS IT:
   there the panel is down the left margin and the caption is nowhere near it. */
@media (hover: none) and (pointer: coarse) {
  body:has(.mw-talk:not([hidden])) .mw-say { display: none; }
}

/* On a DESKTOP the panel is down the LEFT margin — `left: 20px`, `min(430px, 92vw)`
   wide — and a 760-pixel caption centred in a 1280-pixel window runs from 260 to
   1020, so its first eighty pixels are behind the panel. Measured on the probe's own
   screenshot: the line read "…omeone who has never gutted a hare", with "You have the
   look of s" underneath the dialogue.

   So while a conversation is open the caption is centred in what is LEFT of the
   screen rather than in the window — which is also where the character being spoken
   to is standing, since the panel was put on the left to keep them in view. The width
   is clamped against the viewport as well as the panel, so a narrow laptop shrinks the
   bar instead of pushing it off the right edge. */
@media (hover: hover) and (pointer: fine) {
  body:has(.mw-talk:not([hidden])) .mw-say {
    left: calc(50% + 235px);
    width: min(620px, calc(100vw - 500px));
  }
}


/* ══ the ballad ══════════════════════════════════════════════════════════
   The lyric sheet, while one character sings. Down the RIGHT margin, opposite
   the conversation panel (`.mw-talk`, left) so that walking away from the bard
   and talking to somebody else does not stack the two — and clear of the map
   and the tracker, which own the top of that column, by starting below them. */
.mw-song {
  position: fixed; right: 16px; bottom: 84px; z-index: 56;
  width: min(320px, 88vw); max-height: 52vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line);
  border-right: 3px solid var(--mw-gold); border-radius: 12px;
  backdrop-filter: blur(10px); box-shadow: 0 14px 46px rgba(0, 0, 0, .55);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-song-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 9px 12px; border-bottom: 1px solid var(--mw-line);
}
.mw-song-head b { color: var(--mw-gold); font-size: 14px; }
.mw-song-head span { color: var(--mw-dim); font-size: 11.5px; }
.mw-song-head button {
  margin-left: auto; background: none; border: none; color: var(--mw-dim);
  cursor: pointer; font-size: 15px; line-height: 1;
}
.mw-song-note {
  padding: 6px 12px 0; color: var(--mw-dim); font-size: 11.5px; font-style: italic;
}
.mw-song-body { padding: 10px 14px 12px; overflow: auto; }
/* A stanza that is not being sung is still readable — the sheet is the content when
   the recording is missing or music is muted, so an unsung verse must not be greyed
   into decoration. The highlight is a lift, not the difference between legible
   and not. */
.mw-song-v {
  margin: 0 0 10px; font-size: 13px; line-height: 1.5; color: #cfc6b4;
  transition: color .3s linear;
}
.mw-song-v.on { color: var(--mw-ink); text-shadow: 0 0 12px rgba(217, 164, 65, .3); }
.mw-song-bar { height: 3px; background: rgba(255, 255, 255, .07); }
.mw-song-bar > i {
  display: block; height: 100%; width: 0;
  background: var(--mw-gold); transition: width .25s linear;
}
/* The title beside the "Sing me something" button in the conversation. */
.mw-q-song { color: var(--mw-dim); font-size: 12px; font-style: italic; }

@media (hover: none) and (pointer: coarse) {
  /* A phone has no right margin to spare and the conversation panel is already at
     the bottom — but the two are never open together (asking for the song closes
     the conversation), so the sheet takes the same slot. */
  .mw-song {
    right: 50%; transform: translateX(50%); bottom: 132px; width: 94vw;
    max-height: 44vh;
  }
}

/* ── controls panel (controls.js) ──────────────────────────────────────────
   A FRESH `.mw-ctl-*` prefix and `mwCtl*` ids, all verified free before use.
   This stylesheet has been bitten three times by a name that was already taken
   (`.mw-bar` letterboxed the action bar, `.mw-track` put the sound bar through the
   quest tracker, `.mw-sub` was the join screen's paragraph), and every time the DOM
   was correct and every probe was happy. z-index 59 is the peer of the skills sheet;
   the panel sits above the HUD and below the audio drawer and the death overlay. */
.mw-ctl {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 59; width: min(680px, 94vw); max-height: 86vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 14px;
  backdrop-filter: blur(10px); box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-ctl-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--mw-line);
}
.mw-ctl-head b { font-size: 15px; letter-spacing: .02em; }
.mw-ctl-hint { color: var(--mw-dim); font-size: 12px; font-style: italic; flex: 1; }
.mw-ctl-reset, .mw-ctl-x {
  background: rgba(255, 255, 255, .06); color: var(--mw-ink);
  border: 1px solid var(--mw-line); border-radius: 8px;
  padding: 4px 10px; font: inherit; font-size: 12px; cursor: pointer;
}
.mw-ctl-reset:hover, .mw-ctl-x:hover { background: rgba(255, 255, 255, .13); }
.mw-ctl-body { overflow-y: auto; padding: 4px 14px 14px; }
.mw-ctl-cat {
  color: var(--mw-gold); font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  margin: 14px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--mw-line);
}
.mw-ctl-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 5px 0; font-size: 13px;
}
.mw-ctl-row > span:first-child { color: var(--mw-ink); }
.mw-ctl-keys { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* One rule for the button and the static <i>, so a rebindable key and a fixed one
   read identically — the difference a player cares about is the cursor. */
.mw-ctl-key {
  display: inline-block; min-width: 30px; text-align: center;
  background: rgba(0, 0, 0, .34); border: 1px solid var(--mw-line); border-radius: 6px;
  padding: 3px 8px; font: inherit; font-size: 12px; font-style: normal;
  color: var(--mw-ink); box-shadow: inset 0 -2px 0 rgba(0, 0, 0, .35);
}
button.mw-ctl-key { cursor: pointer; }
button.mw-ctl-key:hover { border-color: var(--mw-gold); }
button.mw-ctl-key.on { border-color: var(--mw-gold); color: var(--mw-gold); }
.mw-ctl-add {
  background: none; border: 1px dashed var(--mw-line); border-radius: 6px;
  color: var(--mw-dim); font: inherit; font-size: 12px; padding: 3px 7px; cursor: pointer;
}
.mw-ctl-add:hover { color: var(--mw-gold); border-color: var(--mw-gold); }
.mw-ctl-fixed > span:first-child { color: var(--mw-dim); }
.mw-ctl-opt { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--mw-dim); }
.mw-ctl-opt input { accent-color: var(--mw-gold); }
.mw-ctl-note { color: var(--mw-dim); font-size: 12px; font-style: italic; padding: 2px 0 6px; }
.mw-ctl-clash {
  background: rgba(190, 60, 50, .22); border: 1px solid rgba(220, 90, 80, .5);
  border-radius: 8px; padding: 6px 10px; margin: 8px 0; font-size: 12px;
}
#mwCtlSens { width: 130px; accent-color: var(--mw-gold); }
@media (hover: none) and (pointer: coarse) {
  /* On a phone the keyboard rows are a reference, not a control surface — but the
     panel is still the only place the touch buttons are ever explained. */
  .mw-ctl { width: 94vw; max-height: 82vh; }
  .mw-ctl-row { font-size: 12px; }
}


/* ══ chat, the party frame and the trade window ═══════════════════════════
   Three panels and three FRESH prefixes — `.mw-chat-*`, `.mw-pty-*`/`.mw-emenu-*`
   and `.mw-trd-*` — every one of them grepped for before it was used. This
   stylesheet has been bitten three times by a name that was already taken
   (`.mw-bar` letterboxed the action bar, `.mw-track` put the sound row through
   the quest tracker, `.mw-sub` was the join screen's paragraph) and every time
   the DOM was perfectly correct and every probe was perfectly happy.

   NOT ONE CHANNEL COLOUR IS IN THIS FILE. Blue for the party, ink for global and
   dim for the server's own voice live in ONE table, `SCOPES` in chat.js, and are
   applied inline from there. Three palettes is how the rarity colours came to
   disagree on all five entries with every test green — see the note above
   `.mw-r-normal`. */

/* ── chat ────────────────────────────────────────────────────────────────
   Bottom left, above the bottom-left stack this file already measures:

     .mw-xp      bottom 16  height 22  ->  16 .. 38
     .mw-health  bottom 52  height  22 ->  52 .. 74

   82 clears both. The health bar grew from 14 px to 22 and moved up 8 on
   2026-08-28 (see its own block for why), so this moved with it: at 68 the
   scrollback's first line sat on top of the bar. On a coarse pointer that stack
   moves up (210, 240) and so does
   this. RESTING, the panel is the scrollback and nothing else: no border, no
   input row, and `pointer-events: none` so it can never steal a camera drag from
   the corner it sits in. The lines fade themselves out after fourteen seconds,
   in CSS rather than on a timer, and come back the moment the panel opens —
   what faded is the resting behaviour, not the line's history. */
.mw-chat {
  position: absolute; left: 12px; bottom: 82px; width: min(360px, 46vw);
  z-index: 25; pointer-events: none;
  display: flex; flex-direction: column; gap: 5px;
}
.mw-chat-log {
  display: flex; flex-direction: column; gap: 1px;
  max-height: min(132px, 18vh); overflow-y: auto; overflow-x: hidden;
  padding: 2px 6px; border-radius: 8px;
  font-size: 13px; line-height: 1.36; text-shadow: 0 1px 2px #000;
  scrollbar-width: thin;
}
.mw-chat-line { overflow-wrap: anywhere; animation: mw-chat-rest .9s linear 14s forwards; }
@keyframes mw-chat-rest { to { opacity: 0; } }
.mw-chat-tag { font-style: normal; font-size: 11px; opacity: .72; margin-right: 4px; }
.mw-chat-from { font-weight: 600; }
/* The input row exists only while the panel is open — a text box sitting on the
   HUD is a text box a click can fall into mid-fight. */
.mw-chat-row { display: none; align-items: center; gap: 6px; }
.mw-chat.open { pointer-events: auto; }
.mw-chat.open .mw-chat-row { display: flex; }
.mw-chat.open .mw-chat-log {
  /* vh as well as px: the panel grows UPWARD from the bottom-left corner and the
     party frame comes DOWN the same edge, so on a short window the two would meet. */
  max-height: min(214px, 26vh); background: rgba(14, 12, 10, .74);
  border: 1px solid var(--mw-line); backdrop-filter: blur(6px);
}
.mw-chat.open .mw-chat-line { animation: none; opacity: 1; }
.mw-chat-scope {
  flex: 0 0 auto; font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 7px; background: rgba(20, 18, 15, .8);
  border: 1px solid var(--mw-line);
}
.mw-chat-in {
  flex: 1; min-width: 0; font: inherit; font-size: 13px;
  background: rgba(14, 12, 10, .86); border: 1px solid var(--mw-line);
  border-radius: 8px; color: var(--mw-ink); padding: 6px 9px;
}
.mw-chat-in:focus { outline: none; border-color: var(--mw-gold); }
.mw-chat-x {
  flex: 0 0 auto; background: none; border: none; color: var(--mw-dim);
  font: inherit; font-size: 14px; cursor: pointer; padding: 2px 4px;
}
.mw-chat-x:hover { color: var(--mw-ink); }

/* ── the party frame ─────────────────────────────────────────────────────
   The left column, under the back link, at the one number `:root` holds for it.

   IT STANDS DOWN FOR THE AUDIO PANEL, which is pinned to the same corner. Two
   panels in one place is the failure `.mw-audio` was itself moved to avoid, and
   between a frame you read all session and a menu you opened two seconds ago,
   the one you opened wins the corner while it is up. */
.mw-pty {
  position: absolute; left: 12px; top: var(--mw-lcol-top); width: 208px;
  z-index: 23; pointer-events: auto;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 10px;
  padding: 7px 9px 9px; backdrop-filter: blur(6px);
  font-size: 12.5px; box-shadow: 0 6px 24px rgba(0, 0, 0, .4);
}
body:has(#audioPanel:not([hidden])) .mw-pty { display: none; }
.mw-pty-head { display: flex; align-items: baseline; gap: 6px; }
.mw-pty-head b { color: var(--mw-gold); font-size: 13px; letter-spacing: .03em; }
.mw-pty-near { flex: 1; color: var(--mw-dim); font-size: 10.5px; }
.mw-pty-go {
  font: inherit; font-size: 11px; cursor: pointer; padding: 2px 8px;
  border-radius: 8px; border: 1px solid var(--mw-line);
  background: rgba(20, 18, 15, .8); color: var(--mw-ink);
}
.mw-pty-go:hover { border-color: var(--mw-gold); }
.mw-pty-go.ghost { color: var(--mw-dim); }
/* The two numbers the player is being scored with. Dim, because they are read
   once and then believed — the row above them is the one being watched. */
.mw-pty-mult {
  color: var(--mw-dim); font-size: 10.5px; margin: 3px 0 7px;
  font-variant-numeric: tabular-nums;
}
.mw-pty-rows { display: flex; flex-direction: column; gap: 7px; }
.mw-pty-line { display: flex; align-items: baseline; gap: 5px; }
.mw-pty-name {
  color: var(--mw-ink); max-width: 108px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mw-pty-cls {
  flex: 1; min-width: 0; color: var(--mw-dim); font-size: 10px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mw-pty-kick {
  flex: 0 0 auto; background: none; border: none; color: var(--mw-dim);
  font: inherit; font-size: 11px; cursor: pointer; padding: 0 2px;
}
.mw-pty-kick:hover { color: #e0806f; }
.mw-pty-hp {
  height: 6px; margin-top: 3px; border-radius: 4px; overflow: hidden;
  background: rgba(20, 18, 15, .72); border: 1px solid var(--mw-line);
}
.mw-pty-hp > i {
  display: block; height: 100%;
  background: linear-gradient(90deg, #9d2f26, #d0533f 55%, #e2a04a);
  transition: width .22s ease-out;
}
.mw-pty-hp.low > i { background: linear-gradient(90deg, #a71d1d, #e0402c); }
/* OUT OF RANGE is a caption, not merely a dimmed row: it is the reason the two
   multipliers read the way they do, and 80 m away is not the same fact as being
   on another map. Gold rather than red — it is a distance, not a wound. */
.mw-pty-row.far .mw-pty-name { color: var(--mw-dim); }
.mw-pty-far {
  display: block; margin-top: 2px; font-size: 10px; letter-spacing: .04em;
  color: var(--mw-gold); opacity: .85;
}
.mw-pty-row.self .mw-pty-name { font-weight: 600; }

/* The invitation. It takes the party frame's own slot, because you can only be
   asked to join while you are in no party (GroupBook.invite refuses anybody who
   already has one) — the two are never up together, and the rule below is the
   insurance that says so rather than trusting it. */
.mw-pty-inv {
  position: absolute; left: 12px; top: var(--mw-lcol-top); width: 208px;
  z-index: 24; pointer-events: auto;
  background: var(--mw-panel); border: 1px solid var(--mw-gold); border-radius: 10px;
  padding: 9px 10px; backdrop-filter: blur(6px); font-size: 12.5px;
  box-shadow: 0 0 18px rgba(217, 164, 65, .25);
}
body:has(#mwPtyInv:not([hidden])) .mw-pty { display: none; }
.mw-pty-invwho { display: block; margin-bottom: 8px; line-height: 1.35; }
.mw-pty-invbtns { display: flex; gap: 6px; }

/* ── the menu E puts on a person ─────────────────────────────────────────
   It TAKES THE PROMPT LINE'S SLOT (`.mw-loot-prompt`, bottom 172) and hides it,
   because the line that told you to press E has nothing left to say once the
   menu that answers it is up. One tenant per slot — that is the whole reason
   the bottom-centre stack is written down beside `.mw-say`.

   ONE HORIZONTAL ROW, and the height is the reason. The free band here is 38 px:
   the gate hint runs 126..162 (7+7 padding, 14 px text) and the speech caption
   starts at 200. This row is 33 (3+3 padding, a 25 px button, 2 border), so at
   163 it runs 163..196 and fits between them with both up at once. A stacked
   menu would have gone straight through a citizen's line. */
.mw-emenu {
  position: absolute; left: 50%; bottom: 163px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 7px;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 12px;
  padding: 3px 7px 3px 12px; backdrop-filter: blur(6px);
  pointer-events: auto; z-index: 26; white-space: nowrap;
}
body:has(#mwEmenu:not([hidden])) .mw-loot-prompt { display: none; }
/* ...and the touch E with it: this menu's own buttons are the thumb affordance,
   and a floating "interact" over the top of them would fire the wrong verb. */
body:has(#mwEmenu:not([hidden])) .mw-act-btn { display: none; }
.mw-emenu-who {
  color: var(--mw-gold); font-size: 13px; max-width: 130px;
  overflow: hidden; text-overflow: ellipsis;
}
.mw-emenu-opts { display: flex; align-items: center; gap: 6px; }
/* The server can legitimately answer with nothing on offer — both parties full, say.
   Saying so is the point: a menu that opened empty would read as a broken menu. */
.mw-emenu-none { color: var(--mw-dim); font-size: 12px; font-style: italic; }
.mw-emenu-btn {
  font: inherit; font-size: 12.5px; cursor: pointer; padding: 3px 11px;
  border-radius: 9px; border: 1px solid var(--mw-line);
  background: rgba(20, 18, 15, .8); color: var(--mw-ink); touch-action: manipulation;
}
.mw-emenu-btn:hover { border-color: var(--mw-gold); }
.mw-emenu-btn:disabled { opacity: .45; cursor: default; }
/* E, pressed while the menu is already up. The key has nothing to send — the
   menu is the answer — so it says so by flashing rather than doing nothing. */
.mw-emenu-bump { animation: mw-emenu-flash .5s ease-out; }
@keyframes mw-emenu-flash {
  0% { border-color: var(--mw-gold); box-shadow: 0 0 16px rgba(217, 164, 65, .5); }
  100% { border-color: var(--mw-line); box-shadow: none; }
}

/* ── the trade window ────────────────────────────────────────────────────
   The shop's frame and for the same reason: a trade is a COMPARISON, so both
   offers have to be on screen at once. z-index 59 puts it level with the shop
   and the skill sheet — the three are alternatives, never a stack. */
.mw-trd {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 59; width: min(680px, 94vw); max-height: 88vh;
  background: var(--mw-panel); border: 1px solid var(--mw-line); border-radius: 14px;
  backdrop-filter: blur(10px); box-shadow: 0 20px 70px rgba(0, 0, 0, .6);
  display: flex; flex-direction: column; overflow: hidden;
}
.mw-trd-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 11px 16px; border-bottom: 1px solid var(--mw-line);
  color: var(--mw-gold); font-size: 15px;
}
.mw-trd-head button {
  background: none; border: none; color: var(--mw-dim); cursor: pointer; font-size: 16px;
}
.mw-trd-body { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.mw-trd-col { min-width: 0; padding: 10px 12px; display: flex; flex-direction: column; }
.mw-trd-col + .mw-trd-col { border-left: 1px solid var(--mw-line); }
.mw-trd-colhead {
  color: var(--mw-dim); font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; margin-bottom: 7px;
}
.mw-trd-list {
  min-height: 96px; max-height: 34vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 4px;
}
.mw-trd-bag {
  max-height: 22vh; overflow-y: auto; display: flex; flex-direction: column; gap: 4px;
}
.mw-trd-row {
  display: flex; align-items: center; gap: 7px; font-size: 12.5px;
  padding: 4px 8px; border-radius: 8px; cursor: pointer;
  background: rgba(20, 18, 15, .55); border: 1px solid var(--mw-line);
}
.mw-trd-row:hover { border-color: var(--mw-gold); }
.mw-trd-icon { flex: 0 0 auto; font-size: 15px; }
.mw-trd-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mw-trd-n { font-style: normal; color: var(--mw-dim); font-size: 11px; }
.mw-trd-back { font-style: normal; color: var(--mw-dim); font-size: 11px; }
/* An id neither the catalogue nor the message could explain. Loud on purpose —
   a blank row is a player agreeing to something they cannot read. */
.mw-trd-unknown { border-color: var(--mw-red); }
.mw-trd-unknown .mw-trd-name { color: #ffb3a6; }
.mw-trd-empty { margin: 4px 0; color: var(--mw-dim); font-size: 12px; font-style: italic; }
.mw-trd-gold {
  display: flex; align-items: center; gap: 6px; margin-top: 9px;
  color: #e8c65a; font-size: 13px;
}
.mw-trd-coin { font-size: 14px; }
.mw-trd-goldin {
  width: 92px; font: inherit; font-size: 13px; color: var(--mw-ink);
  background: rgba(14, 12, 10, .86); border: 1px solid var(--mw-line);
  border-radius: 8px; padding: 4px 7px;
}
.mw-trd-goldin:focus { outline: none; border-color: var(--mw-gold); }
.mw-trd-purse { color: var(--mw-dim); font-size: 11px; }
.mw-trd-theirgold { font-variant-numeric: tabular-nums; }
.mw-trd-ready {
  margin-top: 10px; text-align: center; font: inherit; font-size: 13px;
  padding: 7px 0; border-radius: 10px; border: 1px solid var(--mw-line);
  background: rgba(20, 18, 15, .8); color: var(--mw-ink); cursor: pointer;
}
.mw-trd-ready.on { background: var(--mw-gold); color: #241c0c; border-color: var(--mw-gold); }
/* Their side is a FLAG and not a button: nothing this player clicks can change
   it, and a disabled-looking button is an invitation to try. */
.mw-trd-flag { cursor: default; color: var(--mw-dim); }
.mw-trd-foot { padding: 10px 12px 12px; border-top: 1px solid var(--mw-line); }
.mw-trd-note { margin: 8px 0 0; color: var(--mw-dim); font-size: 11.5px; line-height: 1.45; }
.mw-trd-note.warn { color: #ffb3a6; }

@media (hover: none) and (pointer: coarse) {
  /* The bottom-left stack moves up on a phone (.mw-xp 210, .mw-health 240) and
     the chat panel goes with it, clear of the touch stick at 26..158. */
  .mw-chat { bottom: 260px; width: min(330px, 64vw); }
  .mw-chat-log { max-height: min(96px, 14vh); }
  .mw-chat.open .mw-chat-log { max-height: min(150px, 20vh); }
  /* ...and the bottom-centre stack with it: the prompt line this menu replaces
     is at 282 on a coarse pointer, so the menu takes that slot too. */
  .mw-emenu { bottom: 282px; }
  /* The phone stack is tighter than the desktop one and the caption (272..311) is
     the one tenant the prompt's slot already collides with. Stepping over it only
     while a line is actually being spoken keeps the menu where a thumb expects it
     the rest of the time. */
  body:has(.mw-say.on) .mw-emenu { bottom: 320px; }
  .mw-emenu-btn { padding: 7px 12px; }
  .mw-trd { width: 96vw; max-height: 84vh; }
  .mw-trd-body { grid-template-columns: 1fr; }
  .mw-trd-col + .mw-trd-col { border-left: none; border-top: 1px solid var(--mw-line); }
  .mw-trd-list { min-height: 60px; max-height: 22vh; }
}
