/* ============================================================================
 * Project MiG — Stylesheet (CLEAN MODERN DARK redesign, 2026-07-12)
 * ----------------------------------------------------------------------------
 * A flat, modern dark "game dashboard" look: solid surfaces, soft rounded
 * corners, subtle borders, crisp spacing, system fonts. No external fonts or
 * CDNs so the game works fully offline from file://.
 *
 * This look is the PERMANENT baseline (owner-approved 2026-07-13; the old
 * medieval-look backup was deleted at their request).
 *
 * THEMING: everything is CSS-variable driven. A theme (picked in Settings)
 * retints the NEUTRAL surfaces (bg / panels / borders) via
 * :root[data-theme="…"]. The gold accent, semantic green/red, and rarity
 * colours stay constant on every theme so buttons, highlights and item
 * colours keep working everywhere. "default" = Amber (the warm graphite
 * :root values below); "dark" also neutralises the text inks.
 * ==========================================================================*/

:root {
  /* Surfaces (Amber default: warm dark graphite) */
  --bg:        #121110;
  --bg2:       #171512;
  --panel:     #1c1a16;
  --panel2:    #221f1a;
  --panel3:    #2d2922;
  --border:    #363129;
  --border2:   #4a4336;

  /* Text inks */
  --ink:       #ece9e1;
  --ink-dim:   #aca69a;
  --ink-faint: #7b7568;

  /* Accent + semantic colours (constant across themes) */
  --gold:      #eab543;
  --gold-2:    #f7cf74;
  --green:     #58c96b;
  --red:       #e25c4d;

  /* Attack-style colours (mob chips / rows / icons) */
  --st-melee:  #e0604a;
  --st-ranged: #4fc06a;
  --st-magic:  #5aa7f2;

  /* Quality colors */
  --q-common:    #cdd1d9;
  --q-uncommon:  #55c95f;
  --q-rare:      #4f9cf5;
  --q-epic:      #b26af0;
  --q-legendary: #e8524d;
  --q-quest:     #f25bce; /* Quest — pink/magenta (special, outside the rarity ladder) */

  /* Shape + depth + type */
  --radius:    12px;
  --radius-sm: 8px;
  /* One vertical gap between a popup's stacked blocks (meta / note / drop list / buttons),
     so every popup breathes the same way. */
  --popup-gap: 16px;
  /* One square-slot size shared by the Inventory tab's bag, the Equipment panel and the
     Tools panel, so the three read as the same grid. Change it once, all three follow. */
  --slot-size: 72px;
  --shadow:    0 1px 2px rgba(0,0,0,.25), 0 6px 18px rgba(0,0,0,.22);
  --ui-font:   "Segoe UI", system-ui, -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;
  --head-font: "Segoe UI", system-ui, -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* ============================================================================
 * THEMES — the user picks one in Settings (saved to state.settings.theme; the
 * UI sets data-theme on <html>). Each retints ONLY the neutral surfaces.
 * ==========================================================================*/
:root[data-theme="crimson"] {
  --bg: #141010; --bg2: #181313; --panel: #1e1716; --panel2: #251c1a; --panel3: #312420;
  --border: #3f2e2a; --border2: #573e37;
}
:root[data-theme="verdant"] {
  --bg: #0f120f; --bg2: #131713; --panel: #171d18; --panel2: #1c231d; --panel3: #252f27;
  --border: #2e3c31; --border2: #405344;
}
:root[data-theme="azure"] {
  --bg: #0e1116; --bg2: #12161d; --panel: #161b23; --panel2: #1a212b; --panel3: #232c39;
  --border: #2c3746; --border2: #3d4c62;
}
/* Neutral charcoal dark mode — true greys; the ONE theme that also neutralises
   the text inks (the others keep the warm cream) so it reads as a proper
   neutral dark. */
:root[data-theme="dark"] {
  --bg: #0e0f12; --bg2: #131418; --panel: #17191d; --panel2: #1c1e24; --panel3: #262932;
  --border: #2f333d; --border2: #424856;
  --ink: #e9ebef; --ink-dim: #a9aeb8; --ink-faint: #757b87;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui-font);
  font-size: 15px;
}

/* ---- App skeleton -------------------------------------------------------- */
/* ⚠️ THE APP HEIGHT IS `dvh`, NOT `vh` (v0.20). On a phone browser `100vh` is the
   height with the URL bar HIDDEN, so the app is always taller than what you can
   actually see and the bottom tab bar sits underneath the browser's own bar until
   you scroll. `dvh` is the *dynamic* viewport height, which shrinks and grows with
   the browser chrome, so the tab bar stays put. The plain `vh` line stays first as
   a fallback for browsers without dvh. Do not "simplify" it back to one line. */
/* ⚠️ AND THE COLUMN IS `minmax(0, 1fr)`, NOT the implicit `auto` (v0.21). An auto
   grid column is sized by its widest child, so the TOP BAR — whose stat chips do
   not wrap — could stretch the whole app past the viewport and the entire page
   scrolled sideways, tab bar and all. This is the same trap `#body`/`#main` carry
   `min-width: 0` for, one level up. Every row below (topbar, body, tab bar) needs
   its own `min-width: 0` to actually take advantage of it. */
#app {
  display: grid; grid-template-rows: auto 1fr auto; grid-template-columns: minmax(0, 1fr);
  height: 100vh; height: 100dvh;
}

#topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 22px; min-width: 0;
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
}
.brand {
  font-family: var(--head-font); font-size: 20px; font-weight: 800; letter-spacing: .2px;
  color: var(--ink); display: flex; align-items: center; gap: 10px; min-width: 0;
}
.brand-logo { height: 30px; width: auto; display: block; flex-shrink: 0; }
/* ⚠️ THE WORDMARK IS DESKTOP-ONLY (v0.28.1). It reads in full here and is hidden
   outright at the 820px breakpoint below - v0.28 removed it from BOTH, which went
   further than the owner asked.
   `min-width: 0` + the ellipsis are kept from v0.21: they stop the name pushing
   the page wide on an in-between width before the breakpoint takes over.
   ⚠️ `.brand-idle` IS GONE. It wrapped the " Idle" half so the phone could hide
   just that (v0.22 #7); the phone hides the whole wordmark now, so the span had
   no job left. Do not reintroduce it to "shorten" the name on a narrow screen -
   that is the arrangement this release replaced. */
.brand-name { line-height: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brand .accent { color: var(--gold); }
.brand-sub {
  font-family: var(--ui-font); font-size: 11px; font-weight: 600; color: var(--ink-faint);
  letter-spacing: 2px; text-transform: uppercase;
}
.top-stats { display: flex; gap: 8px; }
.stat {
  background: var(--panel); border: 1px solid var(--border);
  padding: 6px 14px; border-radius: 999px; font-weight: 600; font-size: 13px; color: var(--ink-dim);
}
.stat.gold { color: var(--gold-2); }
/* Discord button in the top bar (an <a>, so reset link styling + centre the icon). */
.discord-btn {
  display: inline-flex; align-items: center; justify-content: center;
  color: #b9c0ff; text-decoration: none; padding: 6px 12px; cursor: pointer;
  transition: color .12s, border-color .12s;
}
.discord-btn:hover { color: #fff; border-color: #5865F2; background: #5865F2; }
.discord-ico { display: block; }

/* ⚠️ `min-width: 0` HERE IS THE ONE THAT MATTERS (v0.20). #body is the grid ITEM
   inside #app, and a grid item defaults to `min-width: auto`, which refuses to
   shrink below its content. So a page wider than the phone (the two Database
   pages) pushed #body past its grid column and the whole document scrolled
   sideways, taking the top bar and the bottom tab bar with it. #main carries the
   same guard for the same reason one level down. `min-height: 0` was already
   here doing the vertical equivalent — this is its horizontal twin. */
#body { display: flex; min-height: 0; min-width: 0; }

/* ---- Sidebar ------------------------------------------------------------- */
/* ⚠️ THE LEFT COLUMN IS `#sideCol`, NOT `#sidebar` (v0.26). The search box has to
   live OUTSIDE the nav, because `UI.renderNav` rewrites `#sidebar`'s innerHTML on
   every action and every 10-second sync and would eat what is being typed. So the
   column owns the width, the background and the border, the search box is pinned
   at its top, and `#sidebar` scrolls underneath it. */
#sideCol {
  width: 240px; flex-shrink: 0; min-width: 0;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--bg2);
  border-right: 1px solid var(--border);
}
#sidebar {
  flex: 1 1 auto; min-height: 0;
  /* 8px on top, not 12: the SEARCH BOX above owns the column's top inset now, and
     8px is the gap every row in this list keeps from the next one. */
  padding: 8px 10px 12px; overflow-y: auto;
}

/* ---- Global search (v0.26) ----------------------------------------------- */
/* Items and players, always in their own labelled sections so a result list can
   never read "Player X / Iron Bar / Player Y". */
/* ⚠️ IT HAS TO LINE UP WITH THE ROWS BELOW IT, and that is fiddlier than it looks
   (v0.26 fix). `#sidebar` SCROLLS, so the browser takes a gutter out of its right
   edge and every nav row and skill card ends short by that much. The search box
   lives OUTSIDE the scroller (it must — see UI.renderSearch), so it ran the full
   width and stuck out past everything under it by the width of the scrollbar.
   `--side-sb` is MEASURED at runtime and set on #sideCol, because the reserved
   width is not knowable from CSS: this stylesheet asks for 8px and the browser
   here reserves 10, and Firefox reserves a different amount again. */
.side-search {
  flex: 0 0 auto; position: relative;
  padding: 12px calc(10px + var(--side-sb, 0px)) 0 10px;
}
.gs-wrap { position: relative; }
/* Sized and coloured to sit in the same family as `.side-char` and `.nav-item`:
   the same 8px radius, the same 1px `--border`, the same panel surface, the same
   14px label, and the same 42px row height. The placeholder starts at 45px so it
   lines up with the nav LABELS (12px padding + a 22px icon + an 11px gap). */
.gs-input {
  width: 100%; box-sizing: border-box;
  padding: 9px 34px 9px 45px; line-height: 22px;
  background: var(--panel2); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--ui-font); font-size: 14px; font-weight: 600;
}
.gs-input:focus { outline: none; border-color: var(--gold); }
.gs-input::placeholder { color: var(--ink-faint); font-weight: 600; }
.gs-ico, .gs-clear {
  position: absolute; top: 50%; transform: translateY(-50%);
  color: var(--ink-faint); pointer-events: none;
}
/* Same box the nav icons occupy, so the magnifier sits on their column. */
.gs-ico { left: 12px; width: 22px; text-align: center; font-size: 15px; }
.gs-clear {
  right: 6px; pointer-events: auto; cursor: pointer;
  background: none; border: 0; padding: 4px 7px; line-height: 1;
  font-family: var(--ui-font); font-size: 14px; border-radius: var(--radius-sm);
}
.gs-clear:hover { color: var(--ink); background: var(--panel3); }
/* The results float OVER the nav rather than pushing it down, so the sidebar
   never jumps about while someone types. */
.gs-results {
  /* Flush with the INPUT's own edges (it is positioned inside `.gs-wrap`), so
     the dropdown reads as part of the box rather than a floating panel. */
  position: absolute; left: 0; right: 0; top: 100%; z-index: 60;
  margin-top: 6px; max-height: 58vh; overflow-y: auto;
  background: var(--panel2); border: 1px solid var(--border2);
  border-radius: var(--radius); box-shadow: 0 18px 40px rgba(0,0,0,.5);
  padding: 6px;
}
.gs-sec {
  padding: 8px 8px 4px; font-size: 10.5px; letter-spacing: 1.4px;
  font-weight: 700; color: var(--ink-faint); text-transform: uppercase;
}
.gs-sec + .gs-sec, .gs-row + .gs-sec { border-top: 1px solid var(--border); margin-top: 4px; }
.gs-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 8px; border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--ink); cursor: pointer; text-align: left;
  font-family: var(--ui-font); font-size: 13px;
}
.gs-row:hover, .gs-row.sel { background: var(--panel3); }
.gs-row .item-art, .gs-row .item-ico { width: 22px; height: 22px; font-size: 16px; flex: 0 0 auto; }
.gs-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-sub { flex: 0 0 auto; font-size: 11px; color: var(--ink-faint); }
.gs-none { padding: 10px; color: var(--ink-faint); font-size: 12.5px; text-align: center; }
/* On a phone the box is MOVED into the Menu page (see UI.renderSearch), where it
   is an ordinary block rather than a pinned column header. */
.mmenu-search { position: relative; margin: 0 0 12px; }
.mmenu-search .side-search { padding: 0; }
.gs-hint { padding: 6px 8px 4px; font-size: 11px; color: var(--ink-faint); text-align: center; }
.side-nav { display: flex; flex-direction: column; gap: 2px; margin-bottom: 6px; }
/* Bottom nav group (Item Database / Logbook / Settings) — divided from the skill list above. */
.side-nav-bottom { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border); margin-bottom: 0; }
/* Build number under the sidebar (players quote it in bug reports). */
.side-version { margin: 10px 4px 2px; font-size: 11px; color: var(--ink-faint); text-align: center; letter-spacing: .4px; }
.side-head {
  margin: 16px 8px 8px; font-size: 11px; letter-spacing: 1.6px; font-weight: 700;
  text-transform: uppercase; color: var(--ink-faint);
}
.nav-item {
  display: flex; align-items: center; gap: 11px; width: 100%;
  background: transparent; border: 1px solid transparent; color: var(--ink-dim);
  font-family: var(--ui-font); font-size: 14px; font-weight: 600; text-align: left;
  padding: 9px 12px; border-radius: var(--radius-sm); cursor: pointer; transition: all .12s;
}
.nav-item:hover { background: var(--panel2); color: var(--ink); }
.nav-item.active {
  background: var(--panel3);
  color: var(--gold-2);
  box-shadow: inset 3px 0 0 -1px var(--gold);
}
.nav-ico { font-size: 17px; width: 22px; text-align: center; }
/* A nav row that LEAVES the game (the Wiki, v0.18). It is an <a target="_blank">
   rather than a view button, so it never takes the .active state — you are never
   "on" it. The trailing arrow says so before it is clicked. */
.nav-item.nav-ext { text-decoration: none; }
.nav-ext-ico { margin-left: auto; color: var(--ink-faint); font-size: 12px; }
.nav-item.nav-ext:hover .nav-ext-ico { color: var(--gold-2); }
/* Reset game — moved out of the sidebar into Settings' "Danger zone" (2026-07-17). */
.danger-title { color: var(--red); }
.reset-btn {
  background: transparent; border: 1px dashed var(--border); color: var(--ink-dim);
  align-self: flex-start; width: auto; padding: 9px 18px;
}
.reset-btn:hover { color: var(--red); border-color: var(--red); border-style: solid; background: rgba(200,73,60,.08); }

/* Skill cards in the sidebar (clickable + drag-to-reorder) */
.sk-card {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 11px 11px; margin-bottom: 6px; transition: border-color .12s, transform .08s, background .12s;
}
.sk-card:hover { border-color: var(--border2); background: var(--panel3); transform: translateX(2px); }
.sk-card.sel { border-color: var(--gold); box-shadow: 0 0 0 1px rgba(234,181,67,.35); }
.sk-card.locked { opacity: .5; }
.sk-card.training { box-shadow: 0 0 0 1px rgba(88,201,107,.45); }
/* Drag-to-reorder: grab cursor, dim the dragged card, gold line shows where it'll drop. */
.sk-card[draggable="true"] { cursor: grab; }
.sk-card.dragging { opacity: .45; }
.sk-card.drop-before { box-shadow: inset 0 4px 0 -1px var(--gold-2); }
.sk-card.drop-after  { box-shadow: inset 0 -4px 0 -1px var(--gold-2); }
.sk-row { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.sk-ico { font-size: 16px; width: 20px; text-align: center; flex-shrink: 0; }
.sk-name {
  flex: 1; min-width: 0; color: var(--ink); font-size: 13.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sk-lvl { color: var(--ink-dim); font-size: 12px; font-weight: 700; flex-shrink: 0; }
.sk-card.sel .sk-lvl { color: var(--gold-2); }
.sk-train { color: var(--green); font-size: 10px; animation: pulse 1.2s infinite; flex-shrink: 0; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

/* ---- The merged Melee/Ranged/Magic card (rebuilt v0.17) -------------------
   ONE card the size of a normal skill card, holding three compact cells side by
   side. Hovering a cell hides its two siblings and expands it to a full row with
   name + XP bar; leaving restores all three. Entirely CSS: each cell carries both
   forms as children, so `:hover` swaps them and `:not(:hover)` collapses the
   others. See styleTrioCard() in ui.js before changing the markup shape. */
.sk-card.sk-styles { padding: 5px; cursor: default; }
.sk-card.sk-styles:hover { transform: none; background: var(--panel2); border-color: var(--border2); }
/* ⚠️ THE ROW RESERVES ITS EXPANDED HEIGHT (v0.20, owner's call). Hovering a style
   swaps that cell to the taller "full" form (name + XP bar), which used to grow the
   whole card and shove every skill below it down the sidebar, then snap back on
   mouse-out. The row now always occupies the taller height, so hovering only
   changes what is drawn inside it and nothing else moves.
   ⚠️ If you change what `.sk-cell-full` renders, re-check this number. */
.sk-styles-row { display: flex; gap: 4px; align-items: stretch; min-height: 52px; }
.sk-style-cell {
  flex: 1 1 0; min-width: 0; cursor: pointer; text-align: center;
  background: transparent; border: 1px solid transparent; border-radius: 7px;
  padding: 6px 4px; color: var(--ink); font-family: var(--ui-font);
  transition: background .12s, border-color .12s;
}
.sk-style-cell:hover { background: var(--panel3); border-color: var(--border2); }
/* Compact form: icon over/next to the level, no name (there is no room for three).
   ⚠️ IT MUST FIT INSIDE THE CELL. A third of a sidebar card is about 72px, and the
   `training` outline is drawn ON the cell — so the moment the content is wider than
   the cell, the level text spills straight through that green border and looks
   broken (v0.21, owner's item #11). The training dot is therefore NOT rendered in
   the compact form (the outline already says which style is training) and the level
   ellipsises rather than overflowing. */
.sk-cell-compact { display: flex; align-items: center; justify-content: center; gap: 5px; min-width: 0; overflow: hidden; }
.sk-cell-compact .sk-lvl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sk-cell-compact .sk-ico { width: auto; }
.sk-cell-full { display: none; }
/* HOVER: the other two cells collapse away and this one takes the whole card. */
.sk-styles-row:hover .sk-style-cell:not(:hover) { display: none; }
.sk-styles-row:hover .sk-style-cell:hover { flex-basis: 100%; text-align: left; }
.sk-style-cell:hover .sk-cell-compact { display: none; }
.sk-style-cell:hover .sk-cell-full { display: block; }
.sk-cell-full .sk-row { margin-bottom: 7px; }
.sk-style-cell.training { box-shadow: inset 0 0 0 1px rgba(88,201,107,.45); border-radius: 7px; }

/* ---- The progress bar (custom-image capable) ----------------------------- */
.skbar {
  position: relative; height: 6px; background: rgba(255,255,255,.07);
  border: 0; border-radius: 999px; overflow: hidden;
}
.skbar.big { height: 14px; border-radius: 999px; }
.skbar-fill {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, #3fae57, #6cd684);
  transition: clip-path .3s ease;
}
/* When a skill supplies a full-bar image, it spans the whole track and is
   simply revealed left-to-right by the clip-path. */
.skbar-fill.img { background-size: 100% 100%; background-repeat: no-repeat; image-rendering: pixelated; box-shadow: none; }

/* ---- Main panel ---------------------------------------------------------- */
/* ⚠️ `min-width: 0` IS LOAD-BEARING (v0.20) — do not delete it as redundant.
   #main is a grid item, and a grid item's default `min-width: auto` refuses to
   shrink below its content's intrinsic minimum. The Item and Mob Database pages
   contain `.db-list { flex-basis: 440px }`, so on a 375px phone the whole column
   was forced to 471px and the ENTIRE app scrolled sideways: the page, the top bar
   and the tab bar with it. `min-width: 0` lets the column shrink to the viewport
   so the database panels wrap instead. Any future wide panel (a table, a chart)
   would have done the same thing. */
#main { flex: 1; min-width: 0; overflow-y: auto; padding: 26px 30px 36px; }
#main h1 {
  font-family: var(--head-font); font-size: 26px; font-weight: 800; letter-spacing: -.01em;
  margin: 0 0 4px; color: var(--ink);
}
#main h2 { font-family: var(--head-font); font-weight: 700; color: var(--ink); }
.panel-intro { color: var(--ink-dim); margin: 0 0 20px; max-width: 720px; line-height: 1.55; }

/* Skill page header */
.skill-header {
  display: flex; align-items: center; gap: 18px; margin-bottom: 16px;
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 20px;
}
.sh-ico { font-size: 40px; }
.sh-body { flex: 1; min-width: 0; }
.sh-top { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 10px; }
.sh-top h1 { margin: 0; }
.sh-lvl { font-family: var(--head-font); font-size: 18px; font-weight: 700; color: var(--ink); }
.sh-lvl span { color: var(--ink-faint); font-size: 13px; font-weight: 600; }

/* Locked skill page */
.locked-page { text-align: center; padding: 60px 20px; color: var(--ink-dim); }
.lp-ico { font-size: 64px; opacity: .8; }
.locked-page h1 { margin: 12px 0 8px; }
.lp-badge {
  display: inline-block; background: var(--panel2); border: 1px solid var(--border);
  color: var(--ink-faint); padding: 6px 16px; border-radius: 999px; margin-bottom: 14px; font-weight: 600;
}
.locked-page .panel-intro { margin: 0 auto; max-width: 460px; }

/* ---- Skill page tabs (segmented control — still clearly tabs) ------------- */
.tab-bar {
  display: inline-flex; gap: 4px; margin: 4px 0 18px; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 10px; padding: 4px;
}
.tab {
  background: transparent; border: 0; color: var(--ink-dim);
  font-family: var(--ui-font); font-size: 14px; font-weight: 600; cursor: pointer;
  padding: 7px 16px; border-radius: 7px; transition: all .1s;
}
.tab:hover { color: var(--ink); background: var(--panel2); }
.tab.active {
  background: var(--panel3);
  color: var(--gold-2);
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
}

/* Gear/tool stat line on action & no card overflow */
.ac-stats { font-size: 12px; color: var(--ink); margin-top: 4px; font-weight: 600; }

/* Sub-tab chips (e.g. pick a tool type in Tool Smithing) */
.subtab-bar { display: flex; gap: 6px; margin: 0 0 16px; flex-wrap: wrap; }
.subtab {
  background: var(--panel); border: 1px solid var(--border); color: var(--ink-dim);
  font-family: var(--ui-font); font-size: 13px; font-weight: 600; cursor: pointer;
  padding: 6px 14px; border-radius: 999px; transition: all .1s;
}
.subtab:hover { color: var(--ink); border-color: var(--border2); }
.subtab.active { background: rgba(234,181,67,.14); color: var(--gold-2); border-color: var(--gold); }

/* Craft-quantity modal */
.craft-recipe { font-size: 13px; color: var(--ink-dim); text-align: center; margin-bottom: 14px; }
.craft-qrow { display: flex; align-items: center; gap: 12px; }
/* ⚠️ THE HIT AREA IS DELIBERATELY TALLER THAN THE BAR (v0.23). A range input with
 * no height is ~16px in Chrome, which is a small thing to grab when it sits under
 * a text label — miss it by three pixels upward and you are dragging the label's
 * text instead, which does nothing. `height` on the input enlarges the box the
 * browser hit-tests without changing how the track is drawn. Applies to every
 * slider in the game (craft quantity, buy/sell amount, battle count, food). */
.craft-slider { flex: 1; accent-color: var(--gold); height: 24px; cursor: pointer; }
.qbtn-step {
  width: 34px; height: 34px; border-radius: var(--radius-sm); font-size: 20px; font-weight: 700; cursor: pointer;
  background: var(--panel3); color: var(--gold-2); border: 1px solid var(--border2);
}
.qbtn-step:hover { filter: brightness(1.12); }
.craft-count { text-align: center; color: var(--ink-dim); font-size: 14px; margin: 10px 0 16px; }
.craft-count b { color: var(--gold-2); font-size: 18px; }
.craft-note { text-align: center; color: var(--ink-faint); font-size: 12px; margin: -8px 0 14px; }

/* Equipped totals (below the Equipment panel) */
.equip-totals {
  margin-top: 12px; background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px;
  display: flex; flex-direction: column; gap: 6px;
}
.equip-totals div { display: flex; justify-content: space-between; font-size: 14px; }
.equip-totals span { color: var(--ink-dim); }
.equip-totals b { color: var(--ink); }
/* Inventory tab's equipped-stat totals: two columns so the panel stays compact. */
.equip-totals.stat-totals {
  /* auto-fit, NOT a fixed 2 columns: in the Character page's 300px column two
     "label  value" pairs do not fit and used to spill outside the panel. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 6px 22px;
  background: var(--panel);
}
.equip-totals.stat-totals div { gap: 10px; min-width: 0; }
.equip-totals.stat-totals span {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.equip-totals.stat-totals b { white-space: nowrap; }

/* Region block banner (a production skill you can't do in the current zone) */
.zone-block-banner {
  background: rgba(226,92,77,.10); border: 1px solid rgba(226,92,77,.55); border-radius: var(--radius-sm);
  padding: 13px 18px; margin: 0 0 18px; max-width: 720px; line-height: 1.5;
  color: var(--red); font-weight: 600;
}

/* Empty tab (e.g. Cooking → Meat before combat) */
.empty-tab {
  background: var(--panel); border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 28px 22px; color: var(--ink-faint); max-width: 620px; line-height: 1.5;
}

/* ---- Card grids ---------------------------------------------------------- */
.card-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }

/* Action cards */
.action-card {
  position: relative;
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 14px 16px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  transition: transform .1s, border-color .12s, box-shadow .12s;
}
.action-card:hover { transform: translateY(-2px); border-color: var(--border2); box-shadow: var(--shadow); }
.action-card.active { outline: 2px solid var(--gold); outline-offset: -2px; }
.action-card.locked { opacity: .55; }
.action-card.locked:hover { transform: none; }
.ac-q { font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; font-weight: 700; margin-bottom: 4px; }
.item-art {
  width: 72px; height: 72px; object-fit: contain;
  image-rendering: pixelated; image-rendering: crisp-edges;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.45));
}
.img-missing {
  background: repeating-linear-gradient(45deg, var(--panel3), var(--panel3) 6px, var(--panel2) 6px, var(--panel2) 12px);
  border-radius: 6px;
}
.ac-name { font-family: var(--head-font); font-weight: 700; font-size: 15px; margin-top: 8px; }
.ac-meta { font-size: 12px; color: var(--gold); margin-top: 3px; font-weight: 600; }
.ac-recipe { font-size: 12px; color: var(--ink-dim); margin-top: 7px; line-height: 1.5; }
.ac-have { color: var(--ink-faint); }
.ac-lock { font-size: 12px; color: var(--ink-faint); margin-top: 10px; }
/* The recipe state shown BESIDE a level lock (v0.23, owner's item #7). It sits on
   its own line under the level requirement so the card reads as two facts rather
   than one run-on sentence, and "learned" is green because it is the good news. */
.ac-recipe-state { display: block; margin-top: 3px; font-size: 11px; font-weight: 600; }
.ac-recipe-state.known   { color: var(--green); }
.ac-recipe-state.missing { color: var(--ink-faint); }
/* v0.27: the "N crafts left" line on a SINGLE-USE recipe's card. It sits on its
   own row above the button, so it reads as a stock count rather than a lock. */
.ac-oneuse { margin-top: 4px; }

/* ---- Buttons ------------------------------------------------------------- */
.btn {
  margin-top: 10px; padding: 8px 18px; cursor: pointer;
  font-family: var(--ui-font); font-size: 14px; font-weight: 700; color: #221808;
  background: var(--gold);
  border: 1px solid transparent; border-radius: var(--radius-sm); transition: filter .1s, transform .06s;
}
.btn:hover { filter: brightness(1.08); }
.btn:active { transform: translateY(1px); }
.btn:disabled { background: var(--panel3); color: var(--ink-faint); border-color: var(--border); cursor: default; transform: none; }
.btn.btn-full:disabled { color: var(--red); border-color: rgba(226,92,77,.55); background: rgba(226,92,77,.10); font-weight: 700; }
.btn.stop { background: var(--red); border-color: transparent; color: #fff; }

/* ---- Inventory & Equipment (rehauled 2026-07-13: framed sections) --------- */
.inv-wrap { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
/* Each section (Inventory / Equipment / Tools) is its own framed panel. */
.inv-sec {
  background: var(--panel2); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 18px; box-shadow: var(--shadow);
}
.inv-sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
/* The bag panel hugs its fixed 6-wide grid instead of stretching wider than the slots
   (which left dead space to the right of the grid). It will grow naturally when more
   inventory columns/rows are added later. Fixed 2026-07-17. */
.inv-sec-bag { flex: 0 1 auto; min-width: 0; }
.inv-sec-equip, .inv-sec-tools { flex: 0 0 auto; }
.inv-title { margin: 0 0 4px; font-size: 17px; color: var(--ink); }
.inv-count { color: var(--ink-faint); font-size: 13px; }
.inv-hint { color: var(--ink-faint); font-size: 12px; margin: 0 0 12px; }

/* The bag grid fills its panel's width (slots stay square). The Inventory tab's bag is a
   FIXED 6 × 4 = 24 (--inv-cols, set from MIG.INV_COLS by ui.js so the two can't drift), and its
   slots are exactly --slot-size, matching the Equipment and Tools panels beside it.
   Other .inv-grid users (the bank) keep the responsive auto-fill below. */
.inv-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(var(--slot-size), 1fr)); }
.inv-grid.inv-bag { grid-template-columns: repeat(var(--inv-cols, 6), var(--slot-size)); justify-content: start; }
.inv-slot {
  position: relative; aspect-ratio: 1 / 1; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
}
.inv-slot.empty { border: 1px dashed var(--border); background: rgba(255,255,255,.02); }
.inv-slot.filled {
  background: var(--panel2);
  border: 1px solid var(--border); cursor: pointer; transition: transform .08s, filter .1s;
}
.inv-slot.filled:hover { transform: translateY(-2px); filter: brightness(1.1); }
.inv-slot .item-art { width: 70%; height: 70%; }
/* ⚠️⚠️ THE `z-index` ON THESE TWO BADGES IS LOAD-BEARING (v0.34, a player report:
   "item art is in FRONT of the text of the item, same thing with the +x text").
   BOTH badges are `position: absolute` with `z-index: auto`, and by the plain reading
   of the painting order that puts them ABOVE a static `<img>` sibling. It did not,
   and the reason is one line 70 lines above this one:

       .item-art { filter: drop-shadow(0 3px 5px rgba(0,0,0,.45)); }

   **A `filter` other than `none` creates a STACKING CONTEXT.** That makes the image
   paint as an atomic unit in the same layer as positioned siblings, and since the
   markup is `badge, badge, img` the image comes LAST in tree order and wins. So the
   quantity count and the "+2" were painted UNDER the artwork - worst on a busy or
   pale picture, which is why it reached us as "some users" rather than everyone.
   ⚠️ Fixing it on the IMG (dropping the shadow, or `z-index: -1`) would either lose
   the art's shadow everywhere or push the picture behind its own slot background.
   The badges are the things that must be on top, so they say so.
   ⚠️ THE SAME TRAP APPLIES TO ANY FUTURE BADGE over `.item-art`, and to any element
   that gains a `filter`, `transform`, `opacity < 1`, `will-change` or `mix-blend-mode`
   - every one of those silently promotes it into the positioned layer. */
/* ⚠️⚠️ AND BEING ON TOP IS NOT THE SAME AS BEING READABLE (2026-09-05, the
   owner's screenshot). The art is 70% of the slot, so the badge ALWAYS overlaps its
   top-right corner - and once the z-index above put the digits in front, they landed
   on the picture, where gold-on-grey-rock loses the bottom half of every glyph. A
   text-shadow cannot fix that: it darkens what is behind the strokes, which is exactly
   what a busy texture already is.
   ⚠️ SO THE BADGE CARRIES ITS OWN GROUND. A dark pill is the game's own language
   for a number over something else - the top bar's HP, gold and gem chips are all
   pills - and it makes the count legible over any artwork we ever draw.
   ⚠️ The padding stays inside the tile: 30px of digits plus 10px of padding against
   a 71px slot on the narrowest phone. */
.inv-qty {
  position: absolute; top: 3px; right: 4px; font-weight: 700; font-size: 12px;
  color: var(--gold-2); text-shadow: 0 1px 2px #000;
  background: rgba(10, 8, 5, .82); border-radius: 7px; padding: 0 5px;
  line-height: 16px; pointer-events: none;
  z-index: 2;
}
/* GEAR UPGRADE badge (v0.27) — the "+2" on an upgraded piece, bottom LEFT so it
   can never sit under the quantity badge in the top right. Both a bag tile and
   an equipment slot use it, and both are `position: relative` already. */
.inv-upg {
  position: absolute; bottom: 3px; left: 4px; font-weight: 800; font-size: 12px;
  color: var(--gold); text-shadow: 0 1px 2px #000; pointer-events: none;
  /* The same pill as `.inv-qty`, for the same reason - it sits over the artwork too. */
  background: rgba(10, 8, 5, .82); border-radius: 7px; padding: 0 5px; line-height: 16px;
  z-index: 2; /* see the note on .inv-qty - `.item-art`'s drop-shadow filter makes the picture a stacking context */
}
/* The before/after rows in the gear upgrade popup. */
.gu-arrow { color: var(--ink-faint); margin: 0 2px; }
.gu-nogain { color: var(--ink-faint); font-weight: 600; }
.gu-stone { width: 20px; height: 20px; vertical-align: -4px; margin-right: 4px; }

/* Equipment panel */
.equip-panel {
  background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px;
}
.equip-row { display: flex; justify-content: center; gap: 10px; margin-bottom: 10px; }
.equip-row:last-child { margin-bottom: 0; }
.equip-cell { width: var(--slot-size); }
.equip-cell.spacer { visibility: hidden; }
.equip-slot {
  width: var(--slot-size); height: var(--slot-size); border-radius: 10px;
  background: rgba(255,255,255,.03); border: 1px solid var(--border);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.equip-slot.filled { position: relative; background: var(--panel2); }
.equip-slot.filled .inv-qty { font-size: 11px; } /* equipped-ammo stack count */
.equip-slot .item-art { width: 78%; height: 78%; }
.eq-ico { font-size: 22px; opacity: .5; }
.eq-label { font-size: 9px; letter-spacing: .5px; text-transform: uppercase; color: var(--ink-faint); }
/* Bespoke empty-slot icons (assets/Art/Equipment/*.png). Sized like the emoji
   they replace; `img.slot-img` wins over the font-size rules above. Kept dimmer
   than equipped art so an empty slot still reads as empty. */
/* `object-fit: contain` keeps each icon's own aspect ratio (the art is portrait,
   not square), so nothing is stretched. Sized to sit between the tiny emoji they
   replaced and the 78% an EQUIPPED item's art fills, and kept dimmed so an empty
   slot still reads as empty at a glance. The Inventory slot also carries a text
   label underneath, so its icon is a little smaller than the Character page's. */
img.slot-img { display: block; object-fit: contain; opacity: .6; }
.eq-ico.slot-img { width: 38px; height: 38px; }
.slot-ico.slot-img { width: 46px; height: 46px; }
.equip-slot.pet-slot img.slot-img { opacity: .45; }
/* The reserved PET slot (Beast Mastery Phase 2) — dashed until pets exist. */
.equip-slot.pet-slot { border-style: dashed; }
.equip-slot.pet-slot .eq-ico { opacity: .35; }
.equip-slot.pet-slot .eq-label { opacity: .75; }

/* ---- Vendor popup -------------------------------------------------------- */
.popup-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(6,7,9,.72); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center;
}
.popup {
  position: relative; width: 320px; max-width: 90vw;
  background: var(--panel2);
  border: 1px solid var(--border2); border-radius: 16px; padding: 20px;
  box-shadow: 0 24px 64px rgba(0,0,0,.5);
}
/* ⚠️ The ✕ is CENTRED BY FLEX (v0.20), not by luck. Without this the glyph is laid
   out on a text baseline inside a 26px box, so it sat high and slightly left — and
   how far off depended on the font, which is why it looked fine on desktop and
   wrong on phones. `line-height: 1` and zero padding stop the button's own text
   metrics from moving it again. Every close button in the game uses this class. */
.popup-x {
  position: absolute; top: 10px; right: 10px; width: 26px; height: 26px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid var(--border); color: var(--ink-dim); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; line-height: 1; font-size: 13px; font-family: var(--ui-font);
}
.popup-x:hover { color: var(--red); border-color: var(--red); }
.popup-top { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.popup-art { width: 60px; height: 60px; object-fit: contain; image-rendering: pixelated; }
.popup-name { font-family: var(--head-font); font-weight: 700; font-size: 19px; }
.popup-q { font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; font-weight: 700; }
.popup-meta {
  display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--popup-gap);
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); padding: 12px 0;
}
.popup-meta div { display: flex; justify-content: space-between; font-size: 14px; }
/* An item's FLAVOUR TEXT (v0.22, `item.desc`). It is a sentence, not a label and
   a value, so it overrides the space-between row layout it sits among. Reads like
   a mob's description on purpose — same italic, same dimmed ink. */
.popup-meta .item-desc {
  display: block; font-style: italic; color: var(--ink-dim); font-size: 13px; line-height: 1.45;
}
/* Stat comparison against the item worn in the same slot (v0.20 item #9).
   Green up = better, red down = worse, yellow = identical. The arrow's `title`
   names the equipped item and its value, so the exact difference is one hover
   away without cluttering the row. */
/* "You have no quiver" notice in the Inventory's ammo column (v0.23). Amber
   rather than red: firing from the slot is a valid way to play, just a limited
   and lossy one, and the panel exists to explain the trade rather than scold. */
/* The character BIO (v0.25, owner's item #8) — the player's own words on their
   public page. Rendered with esc() like every other player string. */
.ch-bio {
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 18px; margin: 0 0 16px;
}
.ch-bio-head {
  display: flex; align-items: center; gap: 10px;
  font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; font-weight: 700;
  color: var(--ink-faint); margin-bottom: 8px;
}
.ch-bio-btn { margin-top: 0; margin-left: auto; padding: 5px 12px; font-size: 12px; }
.ch-bio-count { margin-left: auto; font-size: 11px; letter-spacing: 0; text-transform: none; }
.ch-bio-count.over { color: var(--red); }
.ch-bio-text { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--ink-dim); white-space: pre-wrap; }
.ch-bio-text.dim { color: var(--ink-faint); font-style: italic; }
.ch-bio-input {
  width: 100%; background: rgba(255,255,255,.04); border: 1px solid var(--border2);
  border-radius: var(--radius-sm); padding: 9px 12px; color: var(--ink);
  font-family: var(--ui-font); font-size: 14px; line-height: 1.5; resize: vertical;
}
.ch-bio-input:focus { outline: none; border-color: var(--gold); }
.ch-bio-msg { margin-top: 8px; font-size: 12.5px; color: var(--red); }
.ch-bio-actions { display: flex; gap: 8px; margin-top: 10px; }
.ch-bio-actions .btn { margin-top: 0; }
/* Admin panel: the bio and its Clear button. */
.adm-bio { display: flex; align-items: flex-start; gap: 12px; }
.adm-bio-text { flex: 1; margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-dim); white-space: pre-wrap; }
.adm-bio-text.dim { color: var(--ink-faint); font-style: italic; }
.adm-bio .btn { margin-top: 0; flex-shrink: 0; }

/* The Character page's current-activity panel (v0.25, owner's item #7). Replaced
   a one-line "⏳ Mining Coal" with the item's art, a live bar and the countdown. */
.ch-act-panel {
  display: flex; align-items: center; gap: 16px;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 18px; margin: 0 0 16px; box-shadow: var(--shadow);
}
.ch-act-panel.idle { opacity: .72; }
.ch-act-art-wrap {
  width: 56px; height: 56px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.04); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.ch-act-art { width: 44px; height: 44px; object-fit: contain; image-rendering: pixelated; }
.item-ico.ch-act-art, .ch-act-ico { font-size: 30px; line-height: 1; }
.ch-act-body { flex: 1; min-width: 0; }
.ch-act-label {
  font-size: 17px; font-weight: 700; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ch-act-sub { font-size: 12.5px; color: var(--ink-faint); margin: 2px 0 9px; }
.ch-act-sub span { color: var(--gold-2); font-weight: 700; }
.ch-act-bar { height: 10px; }
@media (max-width: 620px) {
  .ch-act-panel { padding: 12px 13px; gap: 12px; }
  .ch-act-art-wrap { width: 46px; height: 46px; }
  .ch-act-art { width: 36px; height: 36px; }
  .ch-act-label { font-size: 15px; }
}

.quiver-warn {
  background: #241d10; border: 1px solid #5a4a24; border-radius: var(--radius-sm);
  padding: 11px 13px; margin-top: 10px; font-size: 12.5px; line-height: 1.5; color: var(--gold-2);
}
.quiver-warn b { color: var(--gold-2); }

.stat-cmp { font-size: 11px; margin-left: 5px; font-weight: 700; }
.cmp-up   { color: var(--green); }
.cmp-down { color: var(--red); }
.cmp-same { color: var(--gold); }
.popup-meta span { color: var(--ink-faint); }
.popup-meta b { color: var(--ink); }
/* Popup buttons always sit one consistent gap below whatever precedes them. Without this
   they touched the block above (a note, a drop list) in some popups but not others,
   because only .popup-meta reserved space beneath itself. */
/* ⚠️ THIS ROW WRAPS, AND ITS BUTTONS MAY SHRINK. Both halves are load-bearing (v0.26).
   The popup is only 320px wide, so THREE buttons never fitted on one line — a recipe
   the player already knows shows "Already known" + "Vendor… (+20g)" + "🏪 Market…".
   With `flex: 1` and no wrap they could not shrink either, because a flex item's
   default `min-width: auto` refuses to go below its own min-content width, so the
   last button hung ~23px OUTSIDE the popup's rounded border. `flex-wrap` puts two on
   a line and the third on its own; `min-width: 0` is the backstop for any single
   label long enough to overflow on its own. The phone block used to carry these two
   rules for itself — they belong here, at every width. */
.popup-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: var(--popup-gap); }
/* .popup-meta already contributes the same gap below itself — don't double it. */
.popup-meta + .popup-actions { margin-top: 0; }
.popup-actions .btn { flex: 1 1 44%; min-width: 0; margin-top: 0; }

/* ---- THE ALTAR (v0.26) ---------------------------------------------------- */
/* The pill that sits in the Combat tab's POI bar, and the shrine popup it opens.
   The owner's brief: NOT the world boss's red, a whitish bar instead, and text
   with something in it rather than an empty label. So the altar reads as candle
   light - a warm white wash and a pale bar - and every state says what it is,
   what it gives, and what today's tithes are buying for tomorrow. */
/* ⚠️ THE BEACON'S COLOUR *IS* THE TIER (v0.26, the owner's spec), so it can be
   read from across the Combat tab without opening anything:
       unlit  candle white   ·  T1 green  ·  T2 blue  ·  T3 the mythic orange
   One variable, `--beacon`, drives the border, the halo and the progress bar, so
   a tier cannot end up half-recoloured. T3 deliberately borrows the MYTHIC ladder
   colour the rarest items already use — it is the brightest thing in the game's
   palette and the owner asked for the altar's best state to feel like one. */
.altar-pill, .altar-item {
  --beacon: 238, 233, 220;          /* dark: the candle white it has always had */
}
.beacon-t1 { --beacon:  85, 201,  95; }   /* green  — matches --q-uncommon */
.beacon-t2 { --beacon:  79, 156, 245; }   /* blue   — matches --q-rare */
.beacon-t3 { --beacon: 255, 122,  44; }   /* orange — the mythic ladder colour */

.altar-pill {
  border-color: rgba(var(--beacon), .30) !important;
  background: linear-gradient(180deg, rgba(var(--beacon),.09), rgba(var(--beacon),.03)) !important;
  gap: 4px;
}
.altar-pill:hover { border-color: rgba(var(--beacon), .55) !important; }
/* Lit: brighter edge and a halo in the beacon's own colour, so a burning altar
   is visible at a glance without shouting like a boss fight does. */
.altar-pill.lit {
  border-color: rgba(var(--beacon), .60) !important;
  box-shadow: 0 0 0 1px rgba(var(--beacon),.12) inset, 0 0 18px -6px rgba(var(--beacon),.55);
}
/* T3 is the one that gets to move, the way a mythic item does. */
.altar-pill.beacon-t3.lit {
  box-shadow: 0 0 0 1px rgba(var(--beacon),.16) inset, 0 0 22px -6px rgba(var(--beacon),.75);
  animation: beaconpulse 3s ease-in-out infinite;
}
@keyframes beaconpulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(var(--beacon),.16) inset, 0 0 18px -7px rgba(var(--beacon),.60); }
  50%      { box-shadow: 0 0 0 1px rgba(var(--beacon),.22) inset, 0 0 26px -5px rgba(var(--beacon),.95); }
}
.altar-tier-chip {
  display: inline-block; margin-left: 6px; padding: 1px 7px;
  border-radius: 999px; font-size: 10.5px; font-weight: 700; letter-spacing: .4px;
  background: rgba(var(--beacon, 238,233,220),.16); color: rgb(var(--beacon, 238,233,220));
  border: 1px solid rgba(var(--beacon, 238,233,220),.35);
}
/* The progress bar. WHITISH on purpose - see the note above. */
.altar-bar {
  display: block; width: 100%; height: 5px; margin-top: 6px;
  border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.10);
}
/* ⚠️ INSIDE THE POI CARD the bar is the card's BOTTOM EDGE, not a row of its own —
   that is what lets the altar match a two-line POI card exactly. The pill clips it
   to its own corners. */
.altar-pill { position: relative; overflow: hidden; }
/* A guarantee rather than a hope: even if the wording grows, the altar's sub line
   can never wrap and push the card taller than the POI cards beside it. */
.altar-pill .poi-pill-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.altar-pill .altar-bar {
  position: absolute; left: 0; right: 0; bottom: 0; width: auto;
  height: 4px; margin: 0; border-radius: 0;
}
.altar-bar.wide { height: 9px; margin: 6px 0; }
/* The fill is the beacon's colour too, so the bar and the frame can never
   disagree about what tier is burning. */
.altar-bar-fill {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, rgba(var(--beacon, 238,233,220),.55), rgba(var(--beacon, 238,233,220),.95));
}
.altar-bar-note {
  display: block; margin-top: 4px;
  font-size: 10.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums;
}

/* ---- The shrine popup ---- */
.altar-popup { width: 420px; }
.altar-progress {
  margin: var(--popup-gap) 0 0; padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.altar-progress-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 12.5px; }
.altar-progress-head span { color: var(--ink-dim); }
.altar-progress-head b { color: var(--ink); font-variant-numeric: tabular-nums; }
.altar-progress-sub { font-size: 11.5px; color: var(--ink-faint); }
.altar-rows { display: flex; flex-direction: column; gap: 8px; margin-top: var(--popup-gap); }
.altar-row {
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--panel); border: 1px solid var(--border);
  opacity: .55;                       /* an unlit tier is visible but plainly off */
}
.altar-row.on {
  opacity: 1;
  border-color: rgba(238,233,220,.42);
  background: linear-gradient(180deg, rgba(238,233,220,.07), transparent), var(--panel);
}
.altar-row-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.altar-row-name { font-weight: 700; }
.altar-row-cost { font-size: 11.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.altar-boosts { margin: 6px 0 8px; padding-left: 18px; }
.altar-boosts li { font-size: 12.5px; color: var(--ink-dim); }
.altar-row.on .altar-boosts li { color: var(--ink); }
.altar-pray-btn { width: 100%; margin-top: 0; background: #eee9dc; color: #241f16; }
/* Inside the popup each tier's row carries its own beacon colour, so the three
   are told apart at a glance and match the pill that opened them. */
.altar-row { --beacon: 238, 233, 220; }
.altar-row.tier-1 { --beacon:  85, 201,  95; }
.altar-row.tier-2 { --beacon:  79, 156, 245; }
.altar-row.tier-3 { --beacon: 255, 122,  44; }
.altar-row.on { border-color: rgba(var(--beacon),.45); }
.altar-row.on .altar-row-name { color: rgb(var(--beacon)); }
.altar-row.on .altar-pray-btn { background: rgb(var(--beacon)); color: #1b1710; }

/* The Admin tab's threshold editor. */
.adm-altar-now { font-size: 12.5px; color: var(--ink-dim); margin: 4px 0 10px; }
.adm-altar-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 10px; }
.adm-altar-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ink-dim); }

/* The read-only altar row at the foot of the World Map's zone panel. */
.altar-item {
  border-color: rgba(var(--beacon),.28) !important;
  background: linear-gradient(180deg, rgba(var(--beacon),.07), transparent) !important;
}
.altar-item.lit { border-color: rgba(var(--beacon),.55) !important; }

/* ---- The indulgence box (v0.26), inside a priest's conversation ------------ */
.indulge-box {
  margin: 12px 0; padding: 12px 14px;
  background: var(--panel); border: 1px solid rgba(238,233,220,.28);
  border-radius: var(--radius-sm);
}
.indulge-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-weight: 700; }
.indulge-purse { font-weight: 400; font-size: 12.5px; color: var(--ink-dim); }
.indulge-note { margin: 6px 0 10px; font-size: 12.5px; color: var(--ink-dim); }
.indulge-row { display: flex; gap: 8px; align-items: stretch; flex-wrap: wrap; }
.indulge-input {
  flex: 1 1 140px; min-width: 0; padding: 8px 10px;
  background: var(--panel2); color: var(--ink);
  border: 1px solid var(--border2); border-radius: var(--radius-sm);
  font-family: var(--ui-font); font-size: 14px;
}
.indulge-input:focus { outline: none; border-color: var(--gold); }
.indulge-go { margin-top: 0; flex: 0 0 auto; background: #eee9dc; color: #241f16; }
.indulge-msg { margin: 8px 0 0; font-size: 12.5px; }
.indulge-msg.bad { color: var(--red); }
.indulge-msg.ok { color: var(--green); }

/* ---- The Class / Profession Change chooser (v0.26) ------------------------ */
.writ-popup { width: 440px; }
.writ-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 8px; margin-top: var(--popup-gap);
}
.writ-card {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 12px 10px; cursor: pointer; text-align: center;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--ink); font-family: var(--ui-font);
  transition: border-color .12s, background .12s;
}
.writ-card:hover { border-color: var(--gold); background: var(--panel3); }
.writ-card.sel { border-color: var(--gold); background: rgba(234,181,67,.10); }
.writ-ico { font-size: 26px; line-height: 1; }
.writ-name { font-weight: 700; font-size: 14px; }
.writ-perk { font-size: 11.5px; color: var(--ink-faint); }
.writ-check { position: absolute; top: 6px; right: 8px; color: var(--gold); font-weight: 700; }

/* ---- Logbook ------------------------------------------------------------- */
.log-list { display: flex; flex-direction: column; gap: 3px; max-width: 820px; }
.log-row {
  display: flex; gap: 14px; padding: 8px 12px; border-radius: var(--radius-sm);
  background: var(--panel); border-left: 3px solid var(--border2);
}
.log-row:nth-child(even) { background: var(--bg2); }
.log-time { color: var(--ink-faint); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.log-msg { color: var(--ink-dim); }
.log-row.level  { border-left-color: var(--gold);  } .log-row.level .log-msg { color: var(--gold-2); }
.log-row.start  { border-left-color: var(--green); }
.log-row.stop   { border-left-color: var(--red);   }
.log-row.warn   { border-left-color: var(--red);   } .log-row.warn .log-msg { color: var(--red); }

/* ---- Active task bar ----------------------------------------------------- */
#activeBar {
  border-top: 1px solid var(--border);
  background: var(--bg2);
  padding: 12px 22px;
  min-height: 76px; display: flex; align-items: center;
}
.ab-idle { color: var(--ink-faint); font-size: 15px; width: 100%; text-align: center; }
#activeBar.running { display: grid; grid-template-columns: 1fr 1.4fr auto; gap: 24px; align-items: center; }
.ab-left { display: flex; align-items: center; gap: 14px; }
.ab-art {
  width: 52px; height: 52px; object-fit: contain;
  image-rendering: pixelated; filter: drop-shadow(0 2px 4px rgba(0,0,0,.45));
}
.ab-title { font-family: var(--head-font); font-weight: 700; font-size: 17px; }
.ab-sub { font-size: 12px; color: var(--ink-dim); margin-top: 2px; }
.ab-mid { width: 100%; }
.ab-prog {
  position: relative; height: 18px; background: rgba(255,255,255,.07);
  border: 0; border-radius: 999px; overflow: hidden;
}
.ab-prog-fill {
  height: 100%; width: 0; background: linear-gradient(90deg, #3fae57, #6cd684);
  /* No width transition on purpose: ui.js drives this fill every animation frame
     (requestAnimationFrame) from the task's real timestamps, so it's already
     smooth. A CSS glide would trail behind the countdown and replay stale
     movement after alt-tabbing back to the game. */
}
.ab-prog-label {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600; color: var(--ink); text-shadow: 0 1px 2px #000;
}
.ab-right { display: flex; align-items: center; gap: 16px; }
/* Square active-bar buttons: 🔄 refresh + the red ✕ stop, same size side by side */
.ab-sq {
  width: 42px; height: 42px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 17px; line-height: 1;
}
.ab-right .ab-sq + .ab-sq { margin-left: -6px; }
.ab-countdown {
  font-family: var(--ui-font); font-size: 26px; font-weight: 800; letter-spacing: .5px;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
#activeBar .btn { margin-top: 0; }

/* Active stat effects (Inventory → Equipment panel button + breakdown popup) */
.fx-btn { margin-top: 10px; width: 100%; }
.fx-popup { max-height: 82vh; overflow-y: auto; }
.fx-hint { font-size: 12px; color: var(--ink-dim); margin: 8px 0 4px; line-height: 1.5; }
.fx-rows div span { color: var(--ink-dim); }
.fx-empty { color: var(--ink-faint); font-style: italic; padding: 12px 0; }

/* Battle panel: your stats and the enemy's, side by side (stacks when narrow) */
/* Your stats vs the enemy's. Named rows keep the two sides ALIGNED: the enemy
   has a description line the player does not, and as two independent columns
   that pushed every enemy stat row out of step with yours (#2, 2026-07-25).
   Each row now shares one height across both columns. Stacked on a phone. */
.battle-stats-duo {
  display: grid; gap: 0 20px; align-items: start;
  grid-template-columns: 1fr;
  grid-template-areas: "yhead" "ydesc" "ystats" "ehead" "edesc" "estats";
}
@media (min-width: 821px) {
  .battle-stats-duo {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "yhead ehead" "ydesc edesc" "ystats estats";
  }
}
.bsd-yhead { grid-area: yhead; } .bsd-ehead { grid-area: ehead; }
.bsd-ydesc { grid-area: ydesc; } .bsd-edesc { grid-area: edesc; }
.bsd-ystats { grid-area: ystats; } .bsd-estats { grid-area: estats; }

/* Sidebar character card (skin + username, top of the sidebar) */
.side-char {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 12px; margin-bottom: 8px;
  background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.side-char-ico { font-size: 22px; line-height: 1; }
.side-char-name {
  font-weight: 800; font-size: 14px; color: var(--gold-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Name card + mailbox chip share one row; the card takes the slack. */
.side-char-wrap { display: flex; align-items: stretch; gap: 6px; margin-bottom: 8px; }
.side-char-wrap .side-char { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
/* The same pairing for a NAV ROW plus a square chip (#6, v0.16): the World Map
   chip rides the Inventory button exactly the way the mailbox rides the
   character card above. Any future row + chip pair should reuse this. */
.side-row-wrap { display: flex; align-items: stretch; gap: 6px; margin-bottom: 4px; }
/* The Inventory button is FRAMED like the character card and the two chips
   beside it (#5, v0.17), so the row reads as one unit instead of a bare label
   sitting next to two boxes. Only the nav item inside a row-wrap gets this —
   the plain nav rows below are unchanged. */
.side-row-wrap .nav-item {
  flex: 1 1 auto; min-width: 0;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.side-row-wrap .nav-item:hover { border-color: var(--gold); }
.side-row-wrap .nav-item.active { border-color: var(--gold); background: var(--panel3, var(--panel2)); }
.side-map .side-mail-ico { opacity: 1; }
/* The MAILBOX chip: a square button at the right of the name card, badged with
   the unread count (2026-07-24). */
.side-mail {
  position: relative; flex: 0 0 auto; width: 40px;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); cursor: pointer; color: inherit;
}
.side-mail:hover { border-color: var(--gold); }
.side-mail.active { border-color: var(--gold); background: var(--panel3, var(--panel2)); }
.side-mail-ico { font-size: 17px; line-height: 1; opacity: .8; }
.side-mail.has-mail .side-mail-ico { opacity: 1; }
.side-mail-badge {
  position: absolute; top: -6px; right: -6px; min-width: 17px; height: 17px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  background: var(--red, #c0392b); color: #fff;
  font-size: 10px; font-weight: 800; line-height: 1; border-radius: 999px;
  border: 1px solid var(--bg);
}

/* Mailbox tab */
.mail-list { display: flex; flex-direction: column; gap: 8px; }
.mail-item {
  display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
}
.mail-item.unread { border-left: 3px solid var(--gold); background: var(--panel2); }
.mail-ico { font-size: 20px; line-height: 1; flex: 0 0 auto; }
.mail-body { flex: 1 1 auto; min-width: 0; }
.mail-top { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.mail-top b { font-size: 14px; }
.mail-top span { font-size: 11.5px; color: var(--ink-faint); white-space: nowrap; }
.mail-text { margin-top: 3px; font-size: 13px; color: var(--ink-dim); line-height: 1.5; }
.mail-x {
  flex: 0 0 auto; background: none; border: none; color: var(--ink-faint);
  cursor: pointer; font-size: 14px; padding: 2px 4px; border-radius: 6px;
}
.mail-x:hover { color: var(--red, #e06c6c); background: rgba(255,255,255,.06); }

/* Metrics tab — grouped stat cards */
.metric-sec { margin: 18px 0 8px; font-size: 13px; font-weight: 700; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .4px; }
.metric-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px; }
.metric {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  background: var(--panel2); border: 1px solid var(--border2);
  border-radius: var(--radius-sm); padding: 10px 14px; font-size: 13px;
}
.metric span { color: var(--ink-dim); }
.metric b { font-weight: 800; color: var(--ink); text-align: right; }

/* ============================================================================
 * CHARACTER CREATION — full-screen overlay shown until state.character exists
 * ==========================================================================*/
#charCreate {
  position: fixed; inset: 0; z-index: 200;
  background: var(--bg);
  display: flex; align-items: flex-start; justify-content: center;
  overflow-y: auto; padding: 28px 18px;
}
/* Full-screen "you are banned" notice (js/net.js showBanned). Sits above
   everything, including the character-creation overlay. */
#banOverlay {
  position: fixed; inset: 0; z-index: 500;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  overflow-y: auto; padding: 28px 18px;
}
#banOverlay .ban-card {
  width: min(520px, 100%); text-align: center;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 34px 30px;
}
#banOverlay .ban-emoji { font-size: 52px; margin-bottom: 8px; }
#banOverlay h1 { font-size: 22px; font-weight: 800; margin: 0 0 12px; }
#banOverlay p { color: var(--ink-dim); margin: 8px 0; }
#banOverlay .ban-reason { color: var(--ink); }
#banOverlay a { color: var(--gold); }
#banOverlay .ban-signout { margin-top: 18px; }

/* Admin tab (moderation panel). */
.adm-box {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px; margin: 14px 0; max-width: 640px;
}
.adm-label, .adm-sub { font-weight: 700; font-size: 13px; margin: 4px 0 6px; color: var(--ink-dim); }
.adm-sub { margin-top: 16px; color: var(--ink); }
.adm-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.adm-input {
  flex: 1 1 180px; min-width: 140px; padding: 9px 11px; font-size: 14px; color: var(--ink);
  background: var(--panel2); border: 1px solid var(--border2); border-radius: var(--radius-sm);
  font-family: var(--ui-font);
}
.adm-input:focus { outline: none; border-color: var(--gold); }
.adm-select { flex: 0 0 auto; min-width: 120px; }
.adm-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.adm-danger { background: var(--danger, #b3402f); border-color: var(--danger, #b3402f); color: #fff; }
.adm-player { font-size: 18px; font-weight: 800; margin-bottom: 4px; }
.adm-status { font-size: 14px; margin-bottom: 6px; }
.adm-tag {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  background: var(--gold); color: #201a0a; padding: 2px 7px; border-radius: 999px; vertical-align: middle;
}
.adm-msg { max-width: 640px; font-size: 14px; }
.adm-ok { color: var(--success, #6fbf73); }
.adm-bad { color: var(--danger, #e06c6c); }
.cc-card {
  width: min(720px, 100%);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 26px 30px 30px;
}
.cc-title { font-size: 24px; font-weight: 800; margin-bottom: 6px; }
.cc-intro { color: var(--ink-dim); font-size: 14px; margin-bottom: 14px; }
.cc-label { display: block; font-weight: 700; font-size: 14px; margin: 18px 0 8px; }
.cc-hint { color: var(--ink-faint); font-weight: 400; font-size: 12px; margin-left: 6px; }
.cc-name {
  width: 100%; padding: 10px 12px; font-size: 15px; color: var(--ink);
  background: var(--panel2); border: 1px solid var(--border2); border-radius: var(--radius-sm);
  font-family: var(--ui-font);
}
.cc-name:focus { outline: none; border-color: var(--gold); }
.cc-name-msg { color: var(--danger, #e06c6c); font-size: 12px; min-height: 16px; margin: 4px 2px 0; }
.cc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(126px, 1fr)); gap: 8px; }
.cc-skins { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.cc-chip {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px;
  padding: 12px 8px; background: var(--panel2); border: 1px solid var(--border2);
  border-radius: var(--radius-sm); cursor: pointer; color: var(--ink);
  font-family: var(--ui-font);
  transition: border-color .12s, background .12s, transform .08s;
}
.cc-chip:hover { transform: translateY(-1px); border-color: var(--border); }
.cc-chip.sel { border-color: var(--gold); background: var(--panel3); box-shadow: 0 0 0 1px var(--gold) inset; }
.cc-chip .cc-ico { font-size: 26px; line-height: 1.1; }
.cc-chip .cc-nm { font-weight: 700; font-size: 13px; }
.cc-chip .cc-buff { font-size: 11.5px; font-weight: 600; color: var(--gold-2); }
.cc-chip .cc-blurb { font-size: 11px; color: var(--ink-faint); }
.cc-start { width: 100%; margin-top: 22px; font-size: 16px; padding: 12px; }
.cc-start:disabled { opacity: .45; cursor: not-allowed; }
/* The way OUT of the creation screen (v0.27). Drawn only when the other slot
 * already holds a character, so it never offers a door that leads nowhere. It is
 * quieter than the Begin button on purpose: leaving is the secondary action. */
.cc-back {
  width: 100%; margin-top: 10px; font-size: 14px; padding: 10px;
  background: var(--panel2); color: var(--ink-dim);
}
.cc-back-note { font-size: 12px; color: var(--ink-faint); text-align: center; margin: 8px 0 0; }

/* Settings -> Characters (v0.27). One row per account slot. */
.cs-list { display: flex; flex-direction: column; gap: 8px; max-width: 480px; margin-bottom: 18px; }
.cs-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--panel2); border: 1px solid var(--border2);
  border-radius: var(--radius-sm); padding: 10px 14px;
}
.cs-row.cs-active { border-color: var(--gold); }
.cs-skin { font-size: 28px; line-height: 1; }
.cs-skin.cs-empty { color: var(--ink-faint); font-weight: 700; }
.cs-info { flex: 1 1 auto; min-width: 0; }
.cs-name { font-weight: 700; font-size: 14.5px; }
.cs-sub { font-size: 12px; color: var(--ink-dim); margin-top: 2px; }
.cs-here { font-size: 12px; color: var(--gold-2); font-weight: 700; white-space: nowrap; }
.cs-btn { font-size: 13px; padding: 7px 12px; white-space: nowrap; }

/* Character summary card (Settings tab) */
.cc-summary {
  display: flex; align-items: center; gap: 14px; max-width: 480px;
  background: var(--panel2); border: 1px solid var(--border2);
  border-radius: var(--radius-sm); padding: 12px 16px; margin-bottom: 18px;
}
.cc-sum-skin { font-size: 34px; }
.cc-sum-name { font-weight: 800; font-size: 16px; }
.cc-sum-line { font-size: 12.5px; color: var(--ink-dim); margin-top: 2px; }

/* ============================================================================
 * QUALITY (rarity) colors — text (q-*) and card borders/glow (qb-*)
 * ==========================================================================*/
.q-common    { color: var(--q-common); }
.q-uncommon  { color: var(--q-uncommon); }
.q-rare      { color: var(--q-rare); }
.q-epic      { color: var(--q-epic); }
.q-quest     { color: var(--q-quest); font-weight: 700; }
.q-gold      { color: var(--gold-2); font-weight: 700; }
.q-legendary {
  font-weight: 700;
  /* Red-on-red gradient (deep red → bright red → deep red) — not red/white. */
  background: linear-gradient(90deg, #a8120c, #ff3d30, #a8120c);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.q-mythic {
  font-weight: 700;
  background: linear-gradient(90deg, #ffb33b, #ff7a2c, #ff3b2e, #ff7a2c, #ffb33b);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  animation: mythicflow 4s linear infinite;
}
@keyframes mythicflow { 0% { background-position: 0% 0; } 100% { background-position: 200% 0; } }

.qb-common    { border-color: #6f7480; }
.qb-uncommon  { border-color: #3f9a42; box-shadow: 0 0 8px rgba(85,201,95,.14); }
.qb-rare      { border-color: #3a6fc4; box-shadow: 0 0 8px rgba(79,156,245,.16); }
.qb-epic      { border-color: #8e44c9; box-shadow: 0 0 9px rgba(178,106,240,.18); }
.qb-quest     { border-color: #d23ba8; box-shadow: 0 0 10px rgba(242,91,206,.22); }
.qb-legendary { border-color: #c43d39; box-shadow: 0 0 10px rgba(232,82,77,.20); }
.qb-mythic {
  border-color: #ff7a2c;
  box-shadow: 0 0 14px rgba(255,110,44,.35), inset 0 0 10px rgba(255,90,44,.10);
  animation: mythicglow 3s ease-in-out infinite;
}
@keyframes mythicglow {
  0%,100% { box-shadow: 0 0 11px rgba(255,110,44,.32), inset 0 0 8px rgba(255,90,44,.08); }
  50%     { box-shadow: 0 0 20px rgba(255,140,44,.50), inset 0 0 12px rgba(255,90,44,.15); }
}

/* Rarity-tinted slot backgrounds in the Inventory tab (bag + Tools + Equipment).
   Background = a dark shade of the rarity hue; border = the rarity colour (the qb-* glow stays).
   Higher specificity (.x.filled.qb-y) so it beats the plain .inv-slot.filled border/background. */
.inv-slot.filled.qb-common,    .equip-slot.filled.qb-common    { background: #2b2d33; border-color: #6f7480; }
.inv-slot.filled.qb-uncommon,  .equip-slot.filled.qb-uncommon  { background: #16351b; border-color: #3f9a42; }
.inv-slot.filled.qb-rare,      .equip-slot.filled.qb-rare      { background: #152a47; border-color: #3a6fc4; }
.inv-slot.filled.qb-epic,      .equip-slot.filled.qb-epic      { background: #2a163d; border-color: #8e44c9; }
.inv-slot.filled.qb-legendary, .equip-slot.filled.qb-legendary { background: #3c1815; border-color: #c43d39; }
.inv-slot.filled.qb-quest,     .equip-slot.filled.qb-quest     { background: #37152d; border-color: #d23ba8; }
.inv-slot.filled.qb-mythic,    .equip-slot.filled.qb-mythic    { background: #3c2409; border-color: #ff7a2c; }

/* ---- Emoji icon fallback (items with no art, e.g. tools) ----------------- */
.item-ico { display: inline-flex; align-items: center; justify-content: center; line-height: 1; }
.action-card .item-ico.item-art { width: 72px; height: 72px; font-size: 46px; }
.inv-slot .item-ico.item-art { width: 70%; height: 70%; font-size: 30px; }
.item-ico.popup-art { width: 60px; height: 60px; font-size: 42px; }
.item-ico.at-art { width: 30px; height: 30px; font-size: 22px; }
.item-ico.toast-art { width: 34px; height: 34px; font-size: 24px; }

/* ---- Top-bar active action indicator ------------------------------------- */
.top-right { display: flex; align-items: center; gap: 14px; }
.active-top {
  display: flex; align-items: center; gap: 9px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 999px; padding: 5px 15px 5px 8px;
}
.active-top.hidden { display: none; }
.at-art { width: 30px; height: 30px; object-fit: contain; image-rendering: pixelated; }
.at-info { line-height: 1.15; }
.at-name { font-size: 13px; color: var(--ink); font-weight: 600; }
.at-xph { font-size: 11px; color: var(--green); font-weight: 700; }

/* ---- Gather/craft toasts (bottom-right) ---------------------------------- */
#toasts {
  position: fixed; right: 18px; bottom: 96px; z-index: 90;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end; pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 10px;
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: 10px; padding: 8px 14px 8px 10px;
  box-shadow: var(--shadow); animation: toastIn .3s ease;
}
.toast-art { width: 34px; height: 34px; object-fit: contain; image-rendering: pixelated; }
.toast-txt { font-size: 14px; color: var(--ink); font-weight: 600; }
.toast.out { animation: toastOut .4s ease forwards; }
.toast-mob.mob-melee  { border-color: var(--st-melee); }
.toast-mob.mob-ranged { border-color: var(--st-ranged); }
.toast-mob.mob-magic  { border-color: var(--st-magic); }
@keyframes toastIn  { from { transform: translateX(40px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes toastOut { to { transform: translateX(40px); opacity: 0; } }

/* ---- Action card rate + skill-page tool line + popup note ---------------- */
.ac-rate { font-size: 11px; color: var(--green); margin-top: 2px; font-weight: 600; }
.sh-tool { margin-top: 9px; font-size: 13px; color: var(--ink-dim); }
.sh-tool.warn { color: var(--red); }
.sk-card.headline {
  background: linear-gradient(170deg, rgba(234,181,67,.10), rgba(234,181,67,.02)), var(--panel2);
  border-color: var(--border2);
}
.sk-card.headline .sk-name { color: var(--gold-2); }
/* `margin-top` added in v0.16 (#14): in the Beast Battle report this note sat
   flush against the last loot row, so the two read as one block. */
.popup-note {
  margin-top: 16px;
  background: rgba(255,255,255,.03); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 12px; font-size: 13px; color: var(--ink-dim); text-align: center;
}

/* ---- Quality-colored start buttons --------------------------------------- */
.btn.qbtn-common    { background: #cfd3da; border-color: transparent; color: #23262b; }
.btn.qbtn-uncommon  { background: #47b654; border-color: transparent; color: #0c2410; }
.btn.qbtn-rare      { background: #4a8ee0; border-color: transparent; color: #fff; }
.btn.qbtn-epic      { background: #a563e0; border-color: transparent; color: #fff; }
.btn.qbtn-legendary { background: #d4524b; border-color: transparent; color: #fff; }
/* ⚠️ QUEST QUALITY NEEDS ITS OWN BUTTON COLOUR (v0.22, owner's item #2). Quest is
   deliberately NOT in the MIG.QUALITIES ladder, so it was the one quality with no
   `qbtn-` rule: the bank's Deposit/Withdraw button for the Special Water Pouch
   stayed default grey while every other item's tinted itself. `--q-quest` is the
   same pink the item name uses. */
.btn.qbtn-quest { background: var(--q-quest); border-color: transparent; color: #2a0616; }
.btn.qbtn-mythic {
  background: linear-gradient(90deg, #ffb33b, #ff7a2c, #ff3b2e, #ff7a2c, #ffb33b);
  background-size: 200% 100%; border-color: transparent; color: #2a0f06;
  animation: mythicflow 4s linear infinite;
}

/* ---- Tools panel (Inventory tab) ----------------------------------------- */
.tools-panel {
  background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px;
  display: inline-block;
}
.tools-panel .equip-cell { width: 72px; }
.equip-slot .item-ico { width: 78%; height: 78%; font-size: 34px; }
.eq-undername {
  text-align: center; font-size: 9px; letter-spacing: .5px; text-transform: uppercase;
  color: var(--ink-faint); margin-top: 3px;
}

/* ---- World Map ----------------------------------------------------------- */
.map-status { color: var(--ink-dim); margin: 0 0 14px; font-size: 15px; }
.map-status b { color: var(--gold-2); }
.map-wrap { display: flex; gap: 22px; align-items: flex-start; flex-wrap: wrap; }
.worldmap {
  position: relative; flex: 1 1 460px; max-width: 720px;
  border: 1px solid var(--border); border-radius: var(--radius); overflow: visible;
  background: var(--panel); box-shadow: var(--shadow);
}
.worldmap-img { display: block; width: 100%; height: auto; image-rendering: pixelated; border-radius: var(--radius); }
.worldmap-img.img-missing { width: 100%; aspect-ratio: 16 / 10; }
/* Zone markers — sized and haloed to stand out on the busy pixel-art terrain:
   a WHITE ring + dark outer halo reads on light and dark ground alike. */
.map-pin {
  position: absolute; transform: translate(-50%, -50%); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: transparent; border: 0; padding: 0;
}
.map-pin-dot {
  width: 24px; height: 24px; border-radius: 50%; box-sizing: border-box;
  background: var(--gold); border: 3px solid #fff;
  box-shadow: 0 0 0 2px rgba(0,0,0,.6), 0 3px 8px rgba(0,0,0,.7), 0 0 14px rgba(234,181,67,.55);
  display: flex; align-items: center; justify-content: center; font-size: 12px; color: #101113;
  transition: transform .1s;
}
.map-pin-label {
  font-size: 12px; font-weight: 700; color: #fff; white-space: nowrap; letter-spacing: .2px;
  background: rgba(5,6,8,.88); padding: 3px 10px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.3); text-shadow: 0 1px 2px #000;
  box-shadow: 0 2px 6px rgba(0,0,0,.5);
  display: inline-flex; align-items: center; gap: 6px;
}
/* The zone's Combat-Level requirement, on the pin next to its name. */
.map-pin-lvl {
  font-size: 10px; font-weight: 800; letter-spacing: .3px; color: var(--gold-2);
  background: rgba(234,181,67,.16); border: 1px solid rgba(234,181,67,.45);
  padding: 0 5px; border-radius: 999px; text-shadow: none;
}
/* A zone your Combat Level can't reach yet: its requirement reads RED, not grey. */
.map-pin.locked .map-pin-lvl { color: #ff8a80; background: rgba(200,73,60,.35); border-color: var(--red); }
.map-pin.current .map-pin-lvl { color: var(--green); background: rgba(88,201,107,.16); border-color: rgba(88,201,107,.5); }
.map-pin.low { flex-direction: column-reverse; } /* near-bottom pins: label above the dot */
.map-pin.locked .map-pin-dot {
  background: #6a6e78; border-color: #cfd2d8;
  box-shadow: 0 0 0 2px rgba(0,0,0,.6), 0 3px 8px rgba(0,0,0,.7);
}
.map-pin.locked .map-pin-label { color: #c3c7cf; }
.map-pin.current .map-pin-dot {
  background: var(--green); animation: pinpulse 2s ease-in-out infinite;
}
@keyframes pinpulse {
  0%,100% { box-shadow: 0 0 0 2px rgba(0,0,0,.6), 0 3px 8px rgba(0,0,0,.7), 0 0 10px rgba(88,201,107,.7); }
  50%     { box-shadow: 0 0 0 2px rgba(0,0,0,.6), 0 3px 8px rgba(0,0,0,.7), 0 0 22px rgba(88,201,107,1); }
}
.map-pin:hover { z-index: 3; }
.map-pin:hover .map-pin-dot { transform: scale(1.12); }
.map-pin:hover .map-pin-label, .map-pin.sel .map-pin-label { color: var(--gold-2); border-color: var(--gold); }
.map-pin.sel .map-pin-dot { box-shadow: 0 0 0 2px var(--gold-2), 0 3px 8px rgba(0,0,0,.7), 0 0 16px rgba(234,181,67,.9); }

.zone-panel {
  flex: 0 0 240px; background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; box-shadow: var(--shadow);
}
.zone-name { font-family: var(--head-font); font-weight: 800; font-size: 20px; color: var(--ink); margin-bottom: 4px; }
.zone-req { color: var(--ink-dim); font-size: 13px; margin-bottom: 14px; }
.zone-here { color: var(--green); font-weight: 600; }
.zone-traveltime { color: var(--ink-dim); font-size: 13px; margin-bottom: 10px; }
.zone-locked { color: var(--red); font-size: 13px; margin-bottom: 10px; }
.zone-panel .btn { margin-top: 0; width: 100%; }
.ab-travel-ico { font-size: 38px; line-height: 1; filter: drop-shadow(0 2px 4px rgba(0,0,0,.45)); }

/* Points of interest (within-zone, instant) — in the zone detail panel */
.poi-list { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
.poi-head { font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; font-weight: 700; color: var(--ink-faint); margin-bottom: 8px; }
.poi-subhead { letter-spacing: .3px; text-transform: none; color: var(--green); font-weight: 600; }
.poi-item {
  display: block; width: 100%; text-align: left; margin-bottom: 7px; cursor: default;
  background: var(--panel);
  border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px;
}
button.poi-item { cursor: pointer; transition: border-color .12s, transform .08s, filter .1s; }
button.poi-item:hover { border-color: var(--border2); transform: translateX(2px); filter: brightness(1.06); }
.poi-item.current { border-color: var(--green); box-shadow: inset 0 0 0 1px rgba(88,201,107,.3); }
.poi-top { display: flex; align-items: center; gap: 8px; }
.poi-ico { font-size: 16px; flex-shrink: 0; }
.poi-name { flex: 1; min-width: 0; font-weight: 700; color: var(--ink); font-size: 14px; }
.poi-tag {
  flex-shrink: 0; font-size: 10px; letter-spacing: .5px; text-transform: uppercase; font-weight: 700;
  padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border2); color: var(--ink-dim);
}
.poi-tag-aggressive { color: var(--red); border-color: var(--red); }
.poi-tag-friendly   { color: var(--q-uncommon); border-color: #3f9a42; }
.poi-tag-main       { color: var(--gold-2); border-color: var(--border2); }
.poi-here { flex-shrink: 0; font-size: 11px; font-weight: 700; color: var(--green); }
/* Mob roster chips inside a POI (name · level · style). No wrapping inside a
   chip — a long name gets an ellipsis instead of growing into a tall box. */
.poi-mobs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 7px; }
.mob-chip {
  display: inline-block; white-space: nowrap; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; vertical-align: middle;
  font-size: 11px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; color: var(--ink);
  background: rgba(255,255,255,.04); border: 1px solid var(--border);
}
.mob-chip em { font-style: normal; color: var(--ink-faint); font-size: 10px; }
.mob-chip.mob-melee  { border-color: var(--st-melee); }
.mob-chip.mob-ranged { border-color: var(--st-ranged); }
.mob-chip.mob-magic  { border-color: var(--st-magic); }

/* Zone panel extras: gatherable resources + the region's World Boss */
.zone-sec-head { margin: 14px 0 6px; font-size: 12px; font-weight: 700; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .4px; }
.zone-res { display: flex; flex-wrap: wrap; gap: 5px; }
/* Both the resource chips and the boss are BUTTONS now — they open an inspect popup. */
.res-chip {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 600;
  padding: 3px 9px 3px 5px; border-radius: 999px; color: var(--ink);
  background: rgba(255,255,255,.04); border: 1px solid var(--border); white-space: nowrap;
  cursor: pointer; font-family: inherit; transition: background .1s, border-color .1s, transform .08s;
}
.res-chip:hover { background: rgba(255,255,255,.09); border-color: var(--gold); transform: translateY(-1px); }
.res-art { width: 18px; height: 18px; object-fit: contain; image-rendering: pixelated; }
span.item-ico.res-art { font-size: 13px; width: auto; height: auto; }
/* A zone's World Boss buttons. FLEX, not inline-block, so a long boss name and
   its "Lv N" tag stay on one line and the tag can never be clipped or orphaned
   on a second row (#7, 2026-07-25). Each button is full width with a gap between
   them, so two bosses in a zone no longer sit with their red borders touching. */
.zone-wboss {
  display: flex; align-items: center; gap: 8px; width: 100%;
  font-size: 13px; font-weight: 700; color: var(--ink);
  padding: 8px 11px; margin-bottom: 8px;
  border: 1px solid var(--red); border-radius: var(--radius-sm);
  background: rgba(200,73,60,.08);
  cursor: pointer; font-family: inherit; text-align: left; transition: background .1s, transform .08s;
}
.zone-wboss:last-of-type { margin-bottom: 0; }
.zone-wboss:hover { background: rgba(200,73,60,.18); transform: translateY(-1px); }
/* The name takes the slack and ellipsises; the level tag never shrinks. */
.zone-wboss .zw-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zone-wboss em {
  flex: 0 0 auto; font-style: normal; color: var(--ink-faint);
  font-weight: 600; font-size: 11px; white-space: nowrap;
}

/* ---- Region tab — one cohesive "stage" (banner + POIs + tracking) -------- */
.region-desc { color: var(--ink-dim); font-style: italic; margin: 0 0 16px; max-width: 760px; line-height: 1.55; }

.region-stage {
  border: 1px solid var(--border); border-radius: 14px; overflow: hidden;
  background: var(--panel2); box-shadow: var(--shadow);
}
/* Image banner across the top — the zone PNG CONTAINED over a blurred copy of itself. */
.region-banner { position: relative; height: 180px; border-bottom: 1px solid var(--border); background: var(--bg2); }
.region-img-blur {
  position: absolute; inset: 0; background-size: cover; background-position: center; background-repeat: no-repeat;
  filter: blur(26px) brightness(.5); transform: scale(1.25);
}
.region-img {
  position: absolute; inset: 0; z-index: 1; background-size: contain; background-position: center; background-repeat: no-repeat;
  image-rendering: pixelated;
}
/* POI switcher bar (rehaul 2026-07-12) — horizontal pills under the banner. */
/* ⚠️ EVERY CARD IN THIS ROW IS THE SAME HEIGHT (the owner's call, v0.26), which
   means the ALTAR has to FIT a POI's two lines rather than the row stretching to
   the altar's four. So the altar's tithe note moved into its popup and its beacon
   BAR is painted along the card's bottom edge instead of taking a row of its own
   (see `.altar-pill .altar-bar`). Stretch is the default and is what makes them
   equal — do not put `align-items: flex-start` back, and do not add a third line
   to the altar card without taking one away. */
.poi-bar {
  display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px;
  background: rgba(255,255,255,.02); border-bottom: 1px solid var(--border);
}
.poi-pill {
  display: flex; flex-direction: column; gap: 2px; text-align: left;
  padding: 8px 18px; border-radius: 10px; min-width: 140px;
  background: var(--panel); border: 1px solid var(--border);
  color: var(--ink); font-family: var(--ui-font);
}
button.poi-pill { cursor: pointer; transition: border-color .12s, transform .08s, background .12s; }
button.poi-pill:hover { border-color: var(--gold); background: var(--panel3); transform: translateY(-1px); }
.poi-pill-name { font-weight: 700; font-size: 14px; }
.poi-pill-sub { font-size: 11px; color: var(--ink-faint); }
.poi-pill.current { border-color: var(--green); background: rgba(88,201,107,.08); }
.poi-pill.current .poi-pill-sub { color: var(--green); font-weight: 600; }
.poi-pill.locked { opacity: .6; }
.poi-pill.locked .poi-pill-sub { color: var(--red); }

/* Tracking / battle panel — BLENDS into the stage (no separate card border). */
.region-main { padding: 16px 18px; }
.region-main .track-panel, .region-main .battle-panel { background: transparent; border: 0; box-shadow: none; padding: 0; border-radius: 0; }
.track-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.track-head h2 { margin: 0; font-size: 18px; color: var(--ink); }
.track-head .btn { margin-top: 0; }
/* The level chip and the Start/Stop button are ONE group at the right end (v0.21,
   owner's item #14). They used to be three separate `space-between` children, so
   the chip drifted into the middle of the panel, miles from the button it belongs
   to. Keep them in this wrapper. */
.track-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.track-rate { font-size: 12px; color: var(--ink-faint); margin: 6px 0 14px; line-height: 1.5; }
.track-rate i { color: var(--gold-2); font-style: italic; }
/* Mob CARDS (rehaul 2026-07-12) — a grid that uses the panel's full width. Each
   card carries its attack-style colour on the left edge and opens the mob popup. */
/* Capped to --combat-col so the LAST mob card's right edge lines up with the
   World Boss panel above it (#4, v0.17) instead of running past it. */
/* ⚠️ auto-FIT, not auto-FILL (#4, v0.17). auto-fill keeps empty tracks, so four
   mob cards on a wide screen sat squeezed at the left with dead space out to the
   boss panel's edge. auto-fit collapses the empty tracks and the real cards
   stretch, so the LAST card's right edge lines up with the World Boss panel. */
.mob-cards { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); max-width: var(--combat-col); }
.region-main .track-panel, .region-main .battle-panel { max-width: var(--combat-col); }
.track-panel .track-rate, .track-panel .track-stackline { max-width: var(--combat-col); }

/* ---- Combat tab: TWO COLUMNS (v0.22, the owner's redesign — item #18) -------
   Left: the World Boss strips and the tracking panel. Right: the collected-stack
   note and the recent-drops feed. Everything used to stack full width, which
   stretched the mob cards thin and pushed the drops feed below the fold.
   ⚠️ `min-width: 0` on the columns is required — they hold grids and tables whose
   intrinsic width would otherwise refuse to shrink and push the page sideways,
   which is exactly the bug fixed in v0.20. */
.region-cols {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 22px; align-items: start;
}
.region-cols .region-col { min-width: 0; }
/* Inside the columns the panels are already framed by .region-main's own rules,
   so the width caps that kept them readable full-width would now fight the grid. */
.region-cols .track-panel, .region-cols .battle-panel,
.region-cols .mob-cards, .region-cols .track-rate { max-width: none; }
/* Two mob cards per row, as in the owner's design, rather than as many as fit. */
.region-cols .mob-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.region-cols .track-stack {
  margin-top: 0; padding-top: 0; border-top: 0; margin-bottom: 14px;
}
.region-cols .drops-feed { margin-top: 0; }
/* The Dungeoneering tab reuses the same two columns (v0.22, item #19): dungeon
   cards left, the recent-drops feed right.
   ⚠️ THE DUNGEON GRID IS EXACTLY TWO COLUMNS (v0.21, owner's item #7). It used to
   auto-fit, and a zone's four dungeons then landed 3 + 1 at any width where only
   three fitted, which reads as a mistake. Two columns means four dungeons are
   always a tidy 2 x 2 and a lone Misc dungeon (Pendrake) still takes half a row.
   Do NOT put `auto-fit` back — parity is the whole point, not density. */
.dungeon-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 560px) { .dungeon-grid { grid-template-columns: minmax(0, 1fr); } }
/* One column when there is no room for two. 1100px, not the phone breakpoint:
   two columns of mob cards stop being readable well before a phone. */
@media (max-width: 1100px) {
  .region-cols { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  .region-cols .mob-cards { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
}
.mob-card {
  background: rgba(255,255,255,.03); border: 1px solid var(--border); border-left-width: 3px;
  border-radius: 10px; padding: 11px 13px; cursor: pointer;
  transition: background .12s, transform .08s;
}
.mob-card:hover { background: rgba(255,255,255,.055); transform: translateY(-1px); }
.mob-card.locked { opacity: .55; }
.mob-card.mob-melee  { border-left-color: var(--st-melee); }
.mob-card.mob-ranged { border-left-color: var(--st-ranged); }
.mob-card.mob-magic  { border-left-color: var(--st-magic); }
.mob-card-top { display: flex; align-items: center; gap: 8px; }
.mob-card-top .mob-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mob-card-top .mob-chance { margin-left: auto; flex-shrink: 0; }
.mob-card-meta { font-size: 11px; color: var(--ink-dim); margin: 3px 0 10px 30px; }
.mob-card-ctrls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.mob-style-ico { font-size: 17px; flex-shrink: 0; width: 22px; text-align: center; }
.mob-name { font-weight: 700; color: var(--ink); font-size: 14px; }
.mob-chance { font-size: 11px; font-weight: 700; color: var(--gold-2); }
.mob-chance.focused { color: var(--green); }
.mob-have { color: var(--gold-2); }
.focus-btn {
  cursor: pointer; font-size: 12px; font-weight: 700; padding: 5px 13px; border-radius: 999px;
  background: var(--panel3); color: var(--ink-dim); border: 1px solid var(--border2); transition: all .1s;
}
.focus-btn:hover { color: var(--gold-2); border-color: var(--gold); }
.focus-btn.on { background: var(--gold); color: #221808; border-color: transparent; }
.focus-na { font-size: 10px; color: var(--ink-faint); font-style: italic; }
.mob-locktag { font-size: 11px; color: var(--red); font-weight: 600; }
/* Start-Tracking / Stop colours. */
.track-start-btn { background: var(--green); border: 1px solid transparent; color: #0d2a12; }
.track-start-btn:hover { filter: brightness(1.08); }
.track-stop-btn { background: var(--red); border: 1px solid transparent; color: #fff; }
.track-stop-btn:hover { filter: brightness(1.08); }
.track-stack {
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--ink-dim);
}
.track-stack b { color: var(--ink); }
.track-cta { margin: 0 0 18px; }
.track-cta .btn { margin-top: 0; }

/* Clickable mob chips (World Map POI preview) → detail popup */
.mob-chip { cursor: pointer; font-family: inherit; }
.mob-chip:hover { filter: brightness(1.12); border-color: var(--gold); }

/* Mob detail popup (description · combat stats · drop table) */
.mob-popup { width: 384px; }
.mob-pop-ico {
  font-size: 34px; width: 56px; height: 56px; flex-shrink: 0; border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.04); border: 1px solid var(--border);
}
.mob-pop-ico.mob-melee  { border-color: var(--st-melee); }
.mob-pop-ico.mob-ranged { border-color: var(--st-ranged); }
.mob-pop-ico.mob-magic  { border-color: var(--st-magic); }
.mob-pop-sub { font-size: 13px; color: var(--ink-dim); margin-top: 3px; }
.mob-pop-desc { font-size: 13px; color: var(--ink-dim); font-style: italic; line-height: 1.5; margin-bottom: 14px; }
.mob-pop-section { font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; font-weight: 700; color: var(--ink-faint); margin: 8px 0 8px; }
.mob-pop-hint { letter-spacing: .3px; text-transform: none; color: var(--ink-faint); font-weight: 400; }
.mob-pop-stats { margin-bottom: 4px; }
.drop-list { display: flex; flex-direction: column; gap: 7px; }
.drop-row { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.drop-item { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; font-weight: 600; }
.drop-ico { width: 26px; height: 26px; object-fit: contain; image-rendering: pixelated; flex-shrink: 0; }
.item-ico.drop-ico { font-size: 18px; display: inline-flex; align-items: center; justify-content: center; }
.drop-q { font-size: 10px; letter-spacing: .5px; text-transform: uppercase; font-weight: 700; flex-shrink: 0; }
.drop-q.dim { color: var(--ink-faint); }
.drop-chance { flex-shrink: 0; font-weight: 700; color: var(--gold-2); font-variant-numeric: tabular-nums; min-width: 50px; text-align: right; }
.drop-tag { flex-shrink: 0; font-size: 10px; font-weight: 700; letter-spacing: .03em; padding: 1px 7px; border-radius: 999px; text-transform: uppercase; }
.drop-tag.drop-learned { color: var(--green); background: rgba(88,201,107,.14); }
.drop-tag.drop-unlearned { color: var(--gold-2); background: rgba(247,207,116,.13); }
.drop-row.is-learned .drop-item > .q-common,
.drop-row.is-learned .drop-item > [class^="q-"] { opacity: .6; }
/* The guild zone's halved-loot disclaimer (v0.27). It sits INSIDE the drop list
   on purpose: a rate and the rule that bends it must not be separated. */
.hollow-note { margin: 0 0 10px; padding: 8px 12px; border-radius: var(--radius-sm);
  background: var(--panel2); border: 1px solid var(--border); font-size: 12px; font-weight: 600;
  line-height: 1.5; color: var(--ink-dim); }
.drop-row.drop-note { display: block; margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 12px; font-weight: 600; line-height: 1.45; color: var(--gold-2); }

/* ---- Combat: top-bar HP, battle buttons, setup modal & in-battle panel --- */
.stat.hp { color: #f0a0a0; }
.stat.hp.low { color: var(--red); animation: pulse 1.1s infinite; }
/* Any top-bar stat carrying the `hidden` class is hidden (the frost / fire timers
   and the pets counter are all shown only when relevant). One shared rule so a new
   toggled stat can't slip through with its own scoped rule missing. */
.stat.hidden { display: none; }
.stat.frost { color: #aee3ff; font-variant-numeric: tabular-nums; }
.stat.frost.low { color: var(--red); animation: pulse 1.1s infinite; }
.stat.fire { color: #ffb27a; font-variant-numeric: tabular-nums; }
.stat.fire.low { color: var(--red); animation: pulse 1.1s infinite; }

/* Per-mob Battle button (in the tracking panel's mob cards) */
.battle-btn {
  cursor: pointer; font-size: 12px; font-weight: 700; padding: 5px 14px; border-radius: 999px; white-space: nowrap;
  background: var(--red); color: #fff; border: 1px solid transparent;
  transition: filter .1s, transform .08s;
}
.battle-btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
.battle-btn:disabled { background: var(--panel3); color: var(--ink-faint); border-color: var(--border); cursor: default; font-weight: 600; }
/* ⚠️ A DISABLED BATTLE BUTTON CARRIES ITS REFUSAL AS ITS LABEL, so its text is
   however long the reason is - and `white-space: nowrap` with the default
   `min-width: auto` means it simply refuses to shrink and pokes out past its own
   card (the v0.26 `.popup-actions` lesson, on a different button). It wraps when
   it is a sentence rather than a verb. The ENABLED button keeps `nowrap`: its
   label is always short ("⚔ Battle (5)") and a pill reads better on one line. */
.battle-btn:disabled { white-space: normal; min-width: 0; text-align: center; line-height: 1.35; }

/* Battle-setup modal */
.popup.battle-setup { width: 440px; max-height: 88vh; overflow-y: auto; }
.battle-note { border-radius: var(--radius-sm); padding: 9px 12px; font-size: 13px; margin-bottom: 12px; }
.battle-note.ok  { background: rgba(88,201,107,.10); border: 1px solid rgba(88,201,107,.5); color: var(--ink); }
.battle-note.bad { background: rgba(226,92,77,.10); border: 1px solid rgba(226,92,77,.55); color: var(--red); font-weight: 600; }
.battle-field { margin-bottom: 14px; }
.battle-field > label { display: block; font-size: 13px; color: var(--ink-dim); margin-bottom: 7px; }
.battle-field > label b { color: var(--gold-2); }
.battle-field .craft-slider { width: 100%; }
/* Mirrors .battle-foodrow so the two sliders in the battle popup are laid out the
 * same way (v0.23) — see the note in renderBattleModal. */
.battle-sliderrow { display: flex; align-items: center; gap: 10px; }
.battle-sliderrow .craft-slider { flex: 1; }
.food-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.food-chip {
  cursor: pointer; font-size: 12px; font-weight: 600; padding: 5px 12px; border-radius: 999px;
  background: var(--panel3); color: var(--ink-dim); border: 1px solid var(--border2); transition: all .1s;
}
.food-chip em { font-style: normal; color: var(--ink-faint); font-size: 10px; }
.food-chip:hover { color: var(--ink); border-color: var(--gold); }
.food-chip.sel { background: var(--gold); color: #221808; border-color: transparent; }
.food-chip.sel em { color: #5a3d10; }
.battle-foodrow { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.battle-foodrow span { font-size: 12px; color: var(--ink-faint); }
.battle-foodrow .craft-slider { flex: 1; }
.battle-foodrow b { color: var(--gold-2); min-width: 28px; text-align: right; }

/* Shared key/value stat grid (battle projection + in-battle tabs). Flows into
   multiple columns where there is room (in-battle panel); stays a single column
   in the narrow battle-setup modal. */
.battle-grid {
  background: rgba(255,255,255,.03); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 14px; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 5px 32px; margin-bottom: 10px;
}
.battle-grid div { display: flex; justify-content: space-between; font-size: 13px; }
.battle-grid span { color: var(--ink-dim); }
.battle-grid b { color: var(--ink); }
.battle-warn {
  font-size: 12px; font-weight: 600; color: var(--gold-2); margin: -2px 0 10px;
  background: rgba(234,181,67,.09); border: 1px solid rgba(234,181,67,.4); border-radius: var(--radius-sm); padding: 7px 10px;
}
.battle-warn + .battle-warn { margin-top: 0; }
.battle-warn.xp-warn { color: #f3a64d; border-color: rgba(196,122,42,.6); background: rgba(243,166,77,.11); }

/* End-of-battle results popup */
.popup.combat-result { width: 400px; max-height: 88vh; overflow-y: auto; }
.result-head { text-align: center; font-family: var(--head-font); font-size: 21px; font-weight: 800; margin: 2px 0 14px; }
.result-head.win  { color: var(--gold-2); }
.result-head.died, .result-head.lose { color: var(--red); }
.combat-result .drop-row.dim { color: var(--ink-faint); font-style: italic; }

/* World Boss STRIP (Combat / Tracking view; rehaul 2026-07-12) — one horizontal
   row: icon + name | win chance + HP | Fight button. Drops below as chips. */
/* ⚠️ ONE WIDTH FOR THE WHOLE COMBAT COLUMN (v0.17). The World Boss strip and the
   tracking panel below it share `--combat-col`, so the last mob card's right edge
   lines up with the red boss panel's instead of running past it. Change the width
   HERE and both follow. */
/* FULL WIDTH (v0.17). A 980px cap made four mob cards wrap onto two rows on a
   wide monitor, which is worse than the sprawl it was meant to fix. The sprawl
   is handled instead by PACKING the boss row's facts left (see .wboss-row), so
   the panel can be as wide as the page. This stays a single knob: set it to a
   pixel value and the boss strip, tracking panel and mob grid all cap together. */
:root { --combat-col: 100%; }
.wboss-panel { background: rgba(226,92,77,.08); border: 1px solid rgba(226,92,77,.35); border-radius: var(--radius); padding: 12px 16px; margin-bottom: 12px; max-width: var(--combat-col); }
.wboss-panel.awake { border-color: rgba(232,110,100,.7); box-shadow: 0 0 0 1px rgba(232,110,100,.35), 0 0 16px rgba(205,80,80,.2); }
/* Name, win chance and HP sit TOGETHER on the left. Only the action (Fight) and
   the status note are pushed to the right edge, which is where an action belongs.
   `.wboss-title` was `flex: 1 1 220px`, i.e. it ate all the slack (#13). */
.wboss-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
/* The boss icon is a button — it opens the boss inspect popup. */
/* ⚠️ THE BUTTON AND THE PICTURE ARE NOW TWO ELEMENTS (v0.28, when the art
   landed). `.wboss-ico` used to be the <button> itself and carried both the press
   behaviour and the emoji's font size. An <img> cannot be sized by `font-size`,
   so the button became `.wboss-ico-btn` and the thing inside it - emoji span OR
   image - is `.wboss-ico`. Both still hover together, because the transform is on
   the button. */
.wboss-ico-btn {
  flex-shrink: 0; background: transparent; border: 0; padding: 0; cursor: pointer;
  line-height: 0; transition: transform .08s, filter .1s;
}
.wboss-ico-btn:hover { transform: scale(1.12); filter: brightness(1.15); }
.wboss-ico { font-size: 34px; line-height: 1; display: inline-block; }
.wboss-title { flex: 0 1 auto; min-width: 0; margin-right: 4px; }
.wboss-name { font-family: var(--head-font); font-weight: 800; font-size: 17px; }
.wboss-tag { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: #f0a5a5; background: rgba(226,92,77,.18); padding: 2px 8px; border-radius: 999px; vertical-align: middle; margin-left: 6px; }
.wboss-sub { color: var(--ink-faint); font-size: 12px; margin-top: 2px; }
.wboss-stats { display: flex; gap: 18px; flex-shrink: 0; }
.wboss-stats > div { display: flex; flex-direction: column; gap: 2px; }
.wboss-stats span { color: var(--ink-faint); font-size: 11px; }
.wboss-stats b { font-size: 18px; font-variant-numeric: tabular-nums; }
.wboss-win { color: var(--green); }
/* THE DEATH RISK beside the win chance (v0.33). Amber while it is a gamble,
   red once a defeat is certain - the number a player must read before eating. */
.wboss-death { color: var(--gold-2); }
.wboss-death-bad { color: var(--red); }
/* The status note sits WITH the stats, not pushed to the far edge: it is another
   fact about the boss, and only the ACTION belongs on the right (#4, v0.17). */
.wboss-note { color: var(--ink-faint); font-size: 13px; flex-shrink: 0; max-width: 280px; }
.wboss-fighting { font-weight: 700; color: var(--gold-2); flex-shrink: 0; }
.wboss-fight-btn { margin-top: 0; flex-shrink: 0; margin-left: auto; background: var(--red); color: #fff; padding: 10px 24px; }
.wboss-drops { margin-top: 10px; padding-top: 9px; }
.wboss-drops { margin-top: 12px; border-top: 1px solid rgba(255,255,255,.06); padding-top: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wboss-drops-label { color: var(--ink-faint); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; font-weight: 700; }
.wboss-drop-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 11px; border-radius: 999px;
  background: rgba(255,255,255,.04); border: 1px solid var(--border); font-size: 12px; font-weight: 600;
  color: inherit; font-family: inherit; cursor: pointer; /* now a button (#11) */
}
.wboss-drop-chip:hover { border-color: var(--gold); background: rgba(255,255,255,.07); }
.wboss-drop-chip em { font-style: normal; color: var(--gold-2); font-weight: 700; }
.wboss-drop-chip .drop-ico { width: 18px; height: 18px; }
/* Inspectable drop rows in the mob / world-boss popups (#11). */
.drop-clickable { cursor: pointer; border-radius: 6px; margin: -2px -6px; padding: 2px 6px; }
.drop-clickable:hover { background: rgba(255,255,255,.06); }
.result-lost { margin-top: 8px; font-size: 12px; color: var(--red); font-weight: 600; }
.result-revive {
  margin-top: 14px; padding: 11px 13px; font-size: 13px; line-height: 1.55; color: var(--ink-dim);
  background: rgba(255,255,255,.03); border: 1px solid var(--border); border-left: 3px solid var(--red); border-radius: var(--radius-sm);
}
.result-revive b { color: var(--ink); }
/* Active bar: the "priest is at work" note shown while being revived */
.ab-revive-note { font-size: 12px; color: var(--ink-faint); font-style: italic; }

/* Slayer skill page: current-task box + token readout */
.slayer-task-box {
  margin: 6px 0 14px; padding: 13px 16px; border-radius: var(--radius);
  background: var(--panel2);
  border: 1px solid var(--border); border-left: 3px solid var(--gold);
}
.slayer-task-box.done { border-left-color: var(--green); }
.slayer-task-title { font-family: var(--head-font); font-size: 16px; font-weight: 700; color: var(--gold-2); margin-bottom: 6px; }
.slayer-task-box.done .slayer-task-title { color: var(--green); }
.slayer-task-body { font-size: 15px; margin-bottom: 4px; }
.slayer-task-note { font-size: 13px; color: var(--ink-dim); }
.slayer-daily { font-size: 14px; color: var(--ink-dim); margin: 8px 0 2px; }
.slayer-daily-note { font-size: 12px; color: var(--ink-faint); }
.slayer-locked { color: var(--red); font-weight: 600; }
.slayer-token-row { font-size: 15px; margin: 8px 0 4px; }

/* Dungeoneering: dungeon cards on the skill page */
.dungeon-card { text-align: center; }
.dungeon-ico { font-size: 40px; margin: 4px 0 2px; }
.dungeon-stats { font-size: 13px; color: var(--ink-dim); margin-top: 6px; }
.dungeon-stats b { color: var(--ink); }
/* The XP a dungeon run pays (v0.30, the owner's item). Sits directly under the
   stat requirement, above the Magic Find line, and is deliberately quieter than
   the gold accent those two use: it is a fact about the dungeon, not a bonus. */
.dungeon-xp { font-size: 13px; color: var(--ink-dim); margin: 2px 0 4px; }
.dungeon-mf { font-size: 13px; color: var(--gold-2); margin: 4px 0 8px; }
/* The surplus half of the Magic Find line (v0.30). Dimmer than the total so the
   headline number still reads first, and it wraps rather than widening the card. */
.dungeon-mf-sur { color: var(--ink-dim); font-size: 12px; }
/* The "plant something else instead" way out of a level-locked crop (v0.30).
   Reads as a link rather than a primary button: it is a suggestion, not the
   action the player came here to take. */
.farm-alt { display: block; width: 100%; margin-top: 6px; padding: 7px 10px;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--gold-2); font-size: 12.5px; font-weight: 600; text-align: left; cursor: pointer; }
.farm-alt:hover { background: var(--panel3); }
/* "Fighting wounded costs you N% win chance" on a world boss strip (v0.30, reworded v0.32). */
/* THE GUILD LOGBOOK (v0.30). Deliberately built from the CHARACTER logbook's
   vocabulary rather than a new one: same row rhythm, same dim timestamp on the
   right, so the two read as the same feature pointed at different subjects.
   The popup is capped and scrolls INSIDE itself - 300 lines must never make the
   page scroll (`.popup` is already in the themed-scrollbar selector list). */
.guild-log-btn { width: 100%; }
.guild-log-popup { max-width: 620px; width: 100%; }
.glog-list { max-height: 60vh; overflow-y: auto; margin-top: 8px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--panel2); }
.glog-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; gap: 8px;
  align-items: baseline; padding: 8px 10px; border-bottom: 1px solid var(--border); font-size: 13px; }
.glog-row:last-child { border-bottom: none; }
.glog-ico { font-size: 15px; line-height: 1; }
.glog-text { min-width: 0; color: var(--ink); overflow-wrap: anywhere; }
.glog-when { color: var(--ink-faint); font-size: 11.5px; white-space: nowrap; }
@media (max-width: 820px) {
  /* On a phone the timestamp drops under the line rather than squeezing it. */
  .glog-row { grid-template-columns: 22px minmax(0, 1fr); }
  .glog-when { grid-column: 2; }
}
.wboss-hpwarn { margin-top: 6px; padding: 6px 9px; border-radius: var(--radius-sm);
  background: rgba(220,120,60,.12); border: 1px solid rgba(220,120,60,.35);
  color: var(--ink-dim); font-size: 12.5px; }

/* Alchemy: a stat boosted by an active potion (shown in the Inventory equip totals) */
.equip-totals b.stat-boosted { color: var(--green); }

/* Inventory: Spell Codex + Rune Pouch column (magic) — framed like the other
   Inventory-tab sections. */
.magic-col {
  flex: 0 0 auto; width: 276px; max-width: 100%;
  background: var(--panel2); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 18px; box-shadow: var(--shadow);
}
.magic-col .inv-title { margin-top: 0; }
.spell-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 16px; }
.spell-cell {
  position: relative; aspect-ratio: 1/1; border-radius: 10px; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--border2);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; transition: transform .08s, filter .1s;
  color: var(--ink); font: inherit;
}
.spell-cell:hover { transform: translateY(-2px); filter: brightness(1.1); border-color: var(--gold); }
.spell-cell.sel { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold-2), 0 0 10px rgba(234,181,67,.3); }
.spell-cell.locked { opacity: .5; }
.spell-cell.util { border-color: #7a6cc4; }
.spell-ico { font-size: 26px; line-height: 1; }
.spell-cap { font-size: 8.5px; line-height: 1.05; text-align: center; color: var(--ink-dim); padding: 0 2px; }
.spell-badge { position: absolute; top: 3px; right: 4px; color: var(--gold-2); font-size: 12px; font-weight: 700; }
.spell-lock { position: absolute; top: 3px; left: 4px; font-size: 11px; }
.pouch-list { display: flex; flex-direction: column; gap: 6px; }
.pouch-rune {
  display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: var(--radius-sm);
  background: rgba(255,255,255,.03); border: 1px solid var(--border);
}
.pouch-rune.dim { opacity: .82; }
.pouch-ico { font-size: 18px; width: 22px; text-align: center; flex-shrink: 0; }
.pouch-name { flex: 1; min-width: 0; font-size: 12px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pouch-rune b { color: var(--gold-2); font-size: 12px; }
.pouch-btn { margin-top: 0; padding: 3px 10px; font-size: 12px; }
.pouch-empty { font-size: 12px; color: var(--ink-faint); font-style: italic; padding: 4px 0; }
/* ⚠️ WHY A ROW IS REFUSED, WRITTEN OUT (v0.29). It used to be a `title`, and a
   phone has no hover - worse, v0.28's tap-tips do not fire on a DISABLED button,
   which is the one element that most needs to explain itself. The line sits
   directly under its row and is indented to the name column so it reads as that
   row's own note rather than as the next item. */
/* The short "why you cannot accept" line, directly under the trade buttons
   (v0.29). The full sentences stay in the banner at the top of the trade - this
   is the copy that has to survive scrolling past a bag-sized item picker. */
.tr-blocked {
  margin-top: 10px; padding: 9px 12px; border-radius: var(--radius-sm);
  background: rgba(226, 92, 77, .12); border: 1px solid var(--red);
  color: #f0c0bb; font-size: 12.5px; line-height: 1.5;
}
.tr-block-list div { display: block; padding: 5px 0; line-height: 1.5; }
.pouch-why {
  font-size: 11.5px; line-height: 1.45; color: var(--ink-dim);
  padding: 4px 9px 7px 39px; margin-top: -3px;
}
.pouch-sub { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; font-weight: 700; color: var(--ink-faint); margin-top: 8px; }

/* In-battle panel (Region tab) */
.battle-panel { border-color: var(--red); }
.battle-panel .track-head h2 { color: #f0937a; }
/* A battle number that means "you are going to die" (v0.21, item #8). */
.battle-grid b.kv-danger { color: var(--red); }
.battle-warn.hp-warn { border-color: rgba(214,90,74,.5); }
.battle-progress { font-size: 13px; color: var(--ink-dim); margin: 6px 0 12px; }
.battle-progress b { color: var(--gold-2); }
.tab-bar.battle-tabs { margin: 0 0 12px; }
.tab-bar.battle-tabs .tab { padding: 6px 14px; font-size: 13px; }

/* ---- Item Database ------------------------------------------------------- */
.db-wrap { display: flex; gap: 22px; align-items: flex-start; flex-wrap: wrap; }
.db-list {
  flex: 1 1 440px; min-width: 0; overflow: hidden;
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
}
.db-list-head {
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  color: var(--ink-faint); font-size: 12px; letter-spacing: 1px; text-transform: uppercase; font-weight: 700;
}
.db-list-inner { max-height: 72vh; overflow-y: auto; }
.db-row {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; cursor: pointer;
  padding: 9px 16px; background: transparent; border: 0; border-bottom: 1px solid rgba(255,255,255,.05);
  transition: background .1s; color: var(--ink); font-family: var(--ui-font);
}
.db-row:hover { background: rgba(255,255,255,.045); }
.db-row-art { width: 40px; height: 40px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.db-art { width: 40px; height: 40px; object-fit: contain; image-rendering: pixelated; }
.item-ico.db-art { font-size: 28px; }
.db-row-name { flex: 1; min-width: 0; font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.db-row-sub { flex-shrink: 0; font-size: 12px; color: var(--ink-dim); }
.db-row-type { flex-shrink: 0; font-size: 12px; color: var(--ink-faint); min-width: 54px; text-align: right; }
/* Mob Database row icon. Mobs with drawn art (MIG.MOB_ART) show the picture;
   the rest still stand in with their attack-style emoji. */
.mob-db-ico {
  width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  font-size: 24px; border: 1px solid var(--border); border-radius: 10px; background: rgba(255,255,255,.04);
}
.mob-db-ico.mob-melee  { border-color: var(--st-melee); }
.mob-db-ico.mob-ranged { border-color: var(--st-ranged); }
.mob-db-ico.mob-magic  { border-color: var(--st-magic); }
.db-empty { padding: 34px 16px; color: var(--ink-faint); font-style: italic; text-align: center; }

.db-filters { flex: 0 0 300px; max-width: 100%; display: flex; flex-direction: column; gap: 16px; }
.db-sec {
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px;
}
.db-sec-head { font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; font-weight: 700; color: var(--ink-faint); margin-bottom: 9px; }
.db-input {
  width: 100%; background: rgba(255,255,255,.04); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 11px; color: var(--ink); font-family: var(--ui-font); font-size: 14px; margin-bottom: 8px;
}
.db-input::placeholder { color: var(--ink-faint); }
.db-input:focus { outline: none; border-color: var(--gold); }
.db-opt-list { max-height: 230px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; padding-right: 7px; }
.db-opt {
  display: block; width: 100%; text-align: left; cursor: pointer; font-family: var(--ui-font); font-size: 14px;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm); padding: 7px 10px;
  color: var(--ink-dim); transition: background .1s, border-color .1s;
}
.db-opt:hover { background: var(--panel3); }
.db-opt.sel { background: var(--panel3); border-color: var(--gold); font-weight: 700; color: var(--gold-2); }

/* Source list inside the inspect popup */
.src-list { display: flex; flex-direction: column; gap: 7px; }
.src-row {
  font-size: 13px; color: var(--ink-dim); line-height: 1.5;
  background: rgba(255,255,255,.03); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 11px;
}
.src-row b { color: var(--ink); }
.src-row.dim { color: var(--ink-faint); font-style: italic; }

/* ---- Scrollbars (shared across the app + the Item Database panes) --------- */
#main::-webkit-scrollbar, #sidebar::-webkit-scrollbar,
.db-list-inner::-webkit-scrollbar, .db-opt-list::-webkit-scrollbar,
.popup::-webkit-scrollbar, .fx-popup::-webkit-scrollbar,
.pin-body::-webkit-scrollbar { width: 8px; height: 8px; }
#main::-webkit-scrollbar-thumb, #sidebar::-webkit-scrollbar-thumb,
.db-list-inner::-webkit-scrollbar-thumb, .db-opt-list::-webkit-scrollbar-thumb,
.popup::-webkit-scrollbar-thumb, .fx-popup::-webkit-scrollbar-thumb,
.pin-body::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 999px; }
#main::-webkit-scrollbar-track, #sidebar::-webkit-scrollbar-track,
.db-list-inner::-webkit-scrollbar-track, .db-opt-list::-webkit-scrollbar-track,
.popup::-webkit-scrollbar-track, .fx-popup::-webkit-scrollbar-track,
.pin-body::-webkit-scrollbar-track { background: transparent; }
/* Firefox has no ::-webkit- pseudo-elements — this is the equivalent there, and
   it is what makes a scrolling popup match the rest of the game. */
#main, #sidebar, .db-list-inner, .db-opt-list, .popup, .fx-popup, .pin-body {
  scrollbar-width: thin; scrollbar-color: var(--border2) transparent;
}

/* ---- People tab: NPC portraits, dialogue, shops & bank ------------------- */
.npc-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
/* "Go to <POI>" buttons in the People tab (POI-gated NPCs like Camelot). */
.npc-goto-row { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 18px; }
.npc-goto { margin-top: 0; }
.npc-card {
  position: relative; cursor: pointer; text-align: center;
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 14px 14px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  transition: transform .1s, border-color .12s, box-shadow .12s;
  font-family: var(--ui-font); color: var(--ink);
}
.npc-card:hover { transform: translateY(-2px); border-color: var(--border2); box-shadow: var(--shadow); }
.npc-portrait {
  position: relative; width: 76px; height: 76px; border-radius: 50%; font-size: 40px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.04); border: 2px solid var(--border2); margin-bottom: 4px;
}
.npc-portrait.big { width: 60px; height: 60px; font-size: 34px; margin-bottom: 0; }
.npc-role-banker   .npc-portrait, .npc-portrait.npc-role-banker   { border-color: var(--gold); }
.npc-role-merchant .npc-portrait, .npc-portrait.npc-role-merchant { border-color: #3f9a42; }
.npc-role-slayer   .npc-portrait, .npc-portrait.npc-role-slayer   { border-color: var(--st-melee); }
.npc-role-priest   .npc-portrait, .npc-portrait.npc-role-priest   { border-color: #c9a24a; }
.npc-badge {
  position: absolute; top: -2px; right: -2px; min-width: 20px; height: 20px; padding: 0 4px;
  border-radius: 999px; background: var(--gold); color: #221808; font-size: 13px; font-weight: 800;
  display: flex; align-items: center; justify-content: center; border: 2px solid var(--bg);
}
.npc-badge.new { background: var(--red); color: #fff; }
/* The same "!" on the NAV row and the phone Menu row (v0.29, the owner's item
   #1). It is INLINE rather than absolutely positioned like `.npc-badge`, because
   a nav row is a flex line with no corner to hang off - and it must not push the
   label around, so it takes no vertical space of its own. */
.nav-alert {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; margin-left: 7px; padding: 0 4px;
  border-radius: 999px; background: var(--red); color: #fff;
  font-size: 11px; font-weight: 800; line-height: 1; vertical-align: middle;
}
.npc-name { font-family: var(--head-font); font-weight: 700; font-size: 16px; color: var(--ink); }
.npc-role { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--gold-2); font-weight: 700; }
.npc-blurb { font-size: 12px; color: var(--ink-faint); line-height: 1.4; margin-top: 3px; }

.npc-talk { max-width: 640px; }
.npc-leave { margin-top: 0; margin-bottom: 16px; }
.npc-talk-head { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
.npc-talk-name { font-family: var(--head-font); font-weight: 800; font-size: 21px; color: var(--ink); }
.npc-talk-role { font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: var(--ink-dim); font-weight: 700; }
.npc-bubbles { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.npc-bubble {
  background: var(--panel3);
  border: 1px solid var(--border2); border-radius: 4px 14px 14px 14px;
  padding: 13px 17px; font-size: 15px; color: var(--ink); line-height: 1.55; font-style: italic;
  position: relative; max-width: 560px;
}
.npc-options { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; max-width: 560px; }
.npc-opt {
  cursor: pointer; width: 100%; text-align: left; font-family: var(--ui-font); font-size: 14px; font-weight: 600;
  background: var(--panel); border: 1px solid var(--border); color: var(--ink-dim);
  padding: 11px 16px; border-radius: 10px; transition: all .1s;
}
.npc-opt:hover { background: var(--panel3); border-color: var(--gold); color: var(--gold-2); transform: translateX(2px); }

/* Shop + bank shared header */
.shop-head { display: flex; align-items: center; gap: 16px; margin-bottom: 6px; flex-wrap: wrap; }
.shop-head h2 { margin: 0; font-size: 21px; color: var(--ink); flex: 1; min-width: 0; }
.shop-head .npc-leave { margin: 0; }
.shop-gold { font-weight: 700; color: var(--gold-2); background: var(--panel); border: 1px solid var(--border); border-radius: 999px; padding: 6px 14px; }
.shop-price { font-size: 14px; font-weight: 700; color: var(--gold-2); margin-top: 7px; }

/* Bank */
.bank-cols { display: flex; gap: 26px; align-items: flex-start; flex-wrap: wrap; margin-top: 8px; }
.bank-col { flex: 1 1 320px; min-width: 0; }
.bank-col-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 10px; }
.bank-col-head h3 { margin: 0; font-family: var(--head-font); font-weight: 700; font-size: 17px; color: var(--ink); }
.bank-slotcount { font-size: 12px; color: var(--ink-faint); }
/* The bank's two grids use the SAME slot size as the Inventory tab's bag (they used to be
   a cramped fixed 58px). Inherits .inv-grid's minmax(70px, 1fr) + the square .inv-slot. */
.inv-grid.bank-grid { max-width: none; }
.bank-buyslot { margin-top: 14px; width: 100%; }
/* Shown in the Buy-a-slot button's place once the vault hits BANK_SLOT_MAX.
 * A sentence rather than a disabled button: a disabled button reads as 'you
 * cannot afford this yet' and there is no yet. */
.bank-slotcap { margin-top: 14px; text-align: center; font-size: 13px;
  color: var(--ink-faint); padding: 9px 12px; border: 1px dashed var(--border);
  border-radius: var(--radius-sm); }
.bank-empty-note { color: var(--ink-faint); font-style: italic; font-size: 13px; padding: 8px 0; }

/* Quests tab */
.quest-card {
  background: var(--panel2);
  border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius);
  padding: 14px 18px; margin-bottom: 12px; max-width: 720px;
}
.quest-card.active { border-left-color: var(--gold); }
.quest-card.avail  { border-left-color: var(--green); }
.quest-card.done   { border-left-color: var(--border2); opacity: .82; }
.quest-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.quest-name { font-family: var(--head-font); font-weight: 700; font-size: 18px; color: var(--ink); }
.quest-status { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; font-weight: 700; flex-shrink: 0; }
.quest-status.active { color: var(--gold-2); } .quest-status.avail { color: var(--green); } .quest-status.done { color: var(--ink-faint); }
.quest-giver { font-size: 12px; color: var(--ink-dim); margin: 4px 0 8px; }
.quest-desc { font-size: 14px; color: var(--ink-dim); line-height: 1.5; margin-bottom: 8px; }
/* A quest whose giver you haven't met yet: the details stay hidden. */
.quest-desc.locked { color: var(--ink-faint); font-style: italic; }
.quest-step { font-size: 14px; color: var(--gold-2); font-weight: 600; line-height: 1.5; }

/* Settings placeholder */
.settings-placeholder {
  display: flex; align-items: center; gap: 18px; max-width: 520px;
  background: var(--panel); border: 1px dashed var(--border); border-radius: var(--radius); padding: 22px 24px;
}
.settings-ico { font-size: 42px; opacity: .8; }
.settings-placeholder > div { display: flex; flex-direction: column; gap: 3px; }
.settings-placeholder b { color: var(--ink); font-size: 16px; }
.settings-placeholder span { color: var(--ink-faint); font-size: 13px; }

/* Theme picker (Settings → Theme) */
.theme-grid { display: flex; flex-wrap: wrap; gap: 14px; max-width: 640px; }
.theme-card {
  position: relative; cursor: pointer; width: 144px; color: var(--ink); font-family: var(--ui-font);
  background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 12px;
  display: flex; flex-direction: column; gap: 9px; transition: border-color .12s, transform .08s, box-shadow .12s;
}
.theme-card:hover { border-color: var(--border2); transform: translateY(-2px); box-shadow: var(--shadow); }
.theme-card.sel { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold-2); }
.theme-swatch { height: 54px; border-radius: var(--radius-sm); border: 1px solid rgba(0,0,0,.45); }
/* Each swatch previews its OWN theme (dark tint + the constant gold accent strip),
   regardless of the theme currently active. */
.theme-swatch[data-theme="default"] { background: linear-gradient(120deg, #121110, #2d2922 68%, #eab543 68%); }
.theme-swatch[data-theme="dark"]    { background: linear-gradient(120deg, #0e0f12, #262932 68%, #eab543 68%); }
.theme-swatch[data-theme="crimson"] { background: linear-gradient(120deg, #141010, #312420 68%, #eab543 68%); }
.theme-swatch[data-theme="verdant"] { background: linear-gradient(120deg, #0f120f, #252f27 68%, #eab543 68%); }
.theme-swatch[data-theme="azure"]   { background: linear-gradient(120deg, #0e1116, #232c39 68%, #eab543 68%); }
.theme-name { font-weight: 700; font-size: 14px; text-align: center; }
.theme-name em { color: var(--ink-faint); font-style: normal; font-weight: 400; font-size: 12px; }
.theme-check { position: absolute; top: 8px; right: 11px; color: var(--gold-2); font-weight: 800; font-size: 15px; }

/* ============================================================================
 * BEAST MASTERY — pet slots, beast cards, HP bars, and the battle popups.
 * ==========================================================================*/
.beast-statusbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 14px; }
.beast-statusbar .stat b { color: var(--ink); margin-left: 4px; }
.beast-statusnote { font-size: 12px; color: var(--ink-faint); margin-left: 4px; }

.beast-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); margin-bottom: 22px; }
.beast-card {
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px 14px; cursor: pointer; display: flex; flex-direction: column; gap: 8px;
  transition: transform .08s, box-shadow .12s, filter .12s;
}
.beast-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); filter: brightness(1.06); }
.beast-card.beast-egg, .beast-card.beast-empty { cursor: default; }
.beast-card.beast-egg:hover, .beast-card.beast-empty:hover { transform: none; box-shadow: none; filter: none; }
/* flex-wrap + a min-width floor on the id column: a LONG status pill (e.g. "⏳ Waiting in
   The Wraithwoods", "⚔ Battling at <poi>") drops onto its own line instead of squeezing the
   name to one ellipsised letter and stacking the meta's "·" separators. Fixed 2026-07-17. */
.beast-card-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; row-gap: 6px; }
.beast-ico { font-size: 30px; flex-shrink: 0; }
.beast-id { flex: 1 1 auto; min-width: 110px; display: flex; flex-direction: column; }
.beast-name { font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.beast-meta { font-size: 11px; color: var(--ink-dim); }
.beast-state {
  flex-shrink: 0; font-size: 10.5px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--ink-dim); white-space: nowrap;
}
.beast-state.bst-equipped { color: var(--green); border-color: rgba(88,201,107,.5); background: rgba(88,201,107,.08); }
.beast-state.bst-battling { color: var(--gold-2); border-color: rgba(234,181,67,.5); background: rgba(234,181,67,.08); animation: pulse 1.4s infinite; }
.beast-state.bst-down { color: var(--red); border-color: rgba(226,92,77,.5); background: rgba(226,92,77,.08); }
/* Wraps so a battling beast can stack its countdown and its incoming-XP line. */
.beast-ctrls { display: flex; gap: 8px; min-height: 4px; flex-wrap: wrap; align-items: center; }
/* ⚠️ RECALL ALWAYS TAKES ITS OWN FULL-WIDTH LINE (v0.23, owner's item #3).
   `.beast-ctrls` wraps and `.beast-btn` is `flex: 1`, so the button had two very
   different shapes: squeezed onto the end of the countdown row when everything
   happened to fit, and full width on its own line when it did not. Which one you
   got changed as the card's text changed, so the button moved under the player's
   cursor while they were reading. Forcing the wrap makes the card's shape fixed
   for the whole battle. */
.beast-ctrls .beast-recall { flex: 1 0 100%; }
/* Tabular figures so a ticking countdown never changes width either. */
.beast-timer b { font-variant-numeric: tabular-nums; }
.beast-xp-due { flex-basis: 100%; font-size: 11.5px; color: var(--ink-faint); }
.beast-btn { margin-top: 0; padding: 6px 12px; font-size: 12.5px; flex: 1; }
.beast-timer { font-size: 12.5px; color: var(--ink-dim); }
.beast-timer b { color: var(--gold-2); }
.beast-hatch { font-size: 13px; color: var(--ink-dim); padding: 6px 0 2px; }
.beast-hatch b { color: var(--gold-2); }
.beast-card.beast-empty {
  border-style: dashed; background: rgba(255,255,255,.02);
  align-items: center; justify-content: center; text-align: center; gap: 3px; min-height: 118px;
}
.beast-empty-ico { font-size: 26px; opacity: .35; }
.beast-empty-txt { font-weight: 700; color: var(--ink-faint); font-size: 13px; }
.beast-empty-sub { font-size: 11px; color: var(--ink-faint); }

/* Beast HP bar (red, labelled) */
.bhp {
  position: relative; height: 16px; border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.07);
}
.bhp-fill { position: absolute; inset: 0; width: 0; background: linear-gradient(90deg, #c8493c, #e57a63); transition: width .3s ease; }
.bhp-label {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 700; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.7);
}
/* The beast XP bar (v0.23). Deliberately the SAME shape as the HP bar above it,
   because the two sit together and a bare bar next to a labelled one reads as
   whatever the player guesses. See beastXpBar in ui.js. */
.bxp {
  position: relative; height: 16px; border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.07); margin-top: 5px;
}
.bxp-fill { position: absolute; inset: 0; width: 0; background: linear-gradient(90deg, #3f9e52, #64c97a); transition: width .3s ease; }
.bxp-label {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 700; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.7);
}
/* A readable quest item's words, shown as the thing itself rather than as prose. */
.read-note {
  margin: 12px 0 4px; padding: 14px 16px; border-radius: var(--radius-sm);
  background: rgba(255,255,255,.05); border: 1px solid var(--border2);
  font-size: 14px; line-height: 1.55; font-style: italic; color: var(--ink);
}
.read-note p { margin: 0 0 8px; }
.read-note p:last-child { margin-bottom: 0; text-align: right; font-style: normal; color: var(--ink-dim); }

/* Beast popup extras */
.beast-pop-meta { margin-top: 10px; }
.beast-release-btn { color: var(--red); border-color: rgba(226,92,77,.45); background: transparent; }
.beast-release-btn:hover { background: rgba(226,92,77,.10); }

/* Send-to-battle modal: the mobs the beast will face */
.beast-mobchips { display: flex; flex-wrap: wrap; gap: 6px; }
.beast-mobchip {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--panel2); border: 1px solid var(--border); border-radius: 999px;
  padding: 4px 11px; font-size: 12px; color: var(--ink-dim);
}
.beast-mobchip em { font-style: normal; color: var(--ink-faint); font-size: 10.5px; }
.beast-mobchip.mob-melee  { border-left: 3px solid var(--st-melee); }
.beast-mobchip.mob-ranged { border-left: 3px solid var(--st-ranged); }
.beast-mobchip.mob-magic  { border-left: 3px solid var(--st-magic); }

/* Beast Battle report popup */
.beast-result { margin-bottom: 4px; }
.beast-result-gap { height: 14px; border-bottom: 1px solid var(--border); margin-bottom: 14px; }
.beast-killline { font-size: 12.5px; color: var(--ink-dim); margin: 8px 0 2px; }

/* ============================================================================
 * COMPANIONS — the Companion tab's header card (icon · identity · skill bars)
 * ==========================================================================*/
.cp-head {
  display: flex; align-items: center; gap: 18px; margin-bottom: 16px; flex-wrap: wrap;
  background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 20px;
}
.cp-ico { font-size: 44px; line-height: 1; }
.cp-id { flex: 1 1 220px; min-width: 0; }
.cp-name { font-family: var(--head-font); font-weight: 800; font-size: 21px; color: var(--ink); }
.cp-sub { font-size: 12px; color: var(--gold-2); font-weight: 700; margin-top: 2px; }
.cp-blurb { font-size: 13px; color: var(--ink-dim); margin-top: 4px; line-height: 1.45; }
.cp-skills { flex: 0 1 280px; min-width: 220px; display: flex; flex-direction: column; gap: 7px; }
.cp-skillrow { display: flex; justify-content: space-between; align-items: baseline; }
.cp-skillname { font-weight: 700; font-size: 13.5px; color: var(--ink); }

/* ---- Narrow screens ------------------------------------------------------ */
@media (max-width: 760px) {
  #sideCol { width: 188px; }
  #activeBar.running { grid-template-columns: 1fr; gap: 10px; }
}

/* ============================================================================
 * 2026-07-18 batch: quest tracker · clickable craft inputs · offline summary ·
 * rift/transmute pickers · beast recall button
 * ==========================================================================*/

/* Pinned-quest tracker: a small floating box above the active bar, bottom-right. */
.quest-tracker {
  position: fixed; right: 16px; bottom: 86px; z-index: 40;
  max-width: 300px; display: flex; align-items: flex-start;
  background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--shadow);
}
.quest-tracker .qt-body {
  border: 0; background: transparent; text-align: left; cursor: pointer;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 4px; min-width: 0;
  color: var(--ink); font: inherit;
}
.quest-tracker .qt-name { font-weight: 700; font-size: 13px; color: var(--gold-2); }
.quest-tracker .qt-step { font-size: 12px; color: var(--ink-dim); line-height: 1.4; }
.quest-tracker .qt-x {
  order: 2; border: 0; background: transparent; color: var(--ink-faint); cursor: pointer;
  font-size: 12px; padding: 8px 10px 0 0; flex-shrink: 0;
}
.quest-tracker .qt-x:hover { color: var(--red); }

/* Craft-card input links: click an ingredient to open its Database entry. */
.ac-inplink { cursor: pointer; border-bottom: 1px dotted var(--ink-faint); }
.ac-inplink:hover { border-bottom-color: var(--gold-2); filter: brightness(1.2); }

/* Offline "while you were away" summary. */
.offsum-items { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.offsum-item {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--panel3); border: 1px solid var(--border); border-radius: 8px;
  padding: 4px 8px; font-size: 12.5px;
}
.offsum-item .item-art { width: 22px; height: 22px; font-size: 18px; }
.offsum-more { color: var(--ink-faint); font-size: 12.5px; align-self: center; }

/* Rift zone picker + Transmute item picker: stacked full-width buttons. */
.rift-zones { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.rift-zones .btn { justify-content: flex-start; text-align: left; }
.transmute-row .tm-art { width: 22px; height: 22px; font-size: 18px; }

/* Beast recall: small red-tinted button on a battling beast's card. */
.beast-btn.beast-recall { border-color: rgba(226,92,77,.5); color: var(--red); }
.beast-btn.beast-recall:hover { background: rgba(226,92,77,.10); }

/* Quest-card pin button (sits between the name and the status chip). */
.quest-pin-btn { font-size: 11.5px; padding: 3px 10px; margin-left: auto; flex-shrink: 0; }
.quest-pin-btn.pinned { color: var(--gold-2); border-color: rgba(234,181,67,.5); }
.quest-pin-btn + .quest-status { margin-left: 8px; }

/* ===========================================================================
   LEADERBOARDS (online only) — filter row + ranking table.
   =========================================================================== */
.lb-filters {
  display: flex; flex-wrap: wrap; gap: 12px; margin: 14px 0 6px;
}
.lb-filter { display: flex; flex-direction: column; gap: 5px; min-width: 160px; flex: 1 1 160px; }
.lb-filter > span {
  font-size: 11px; font-weight: 700; letter-spacing: .8px; text-transform: uppercase;
  color: var(--ink-faint);
}
.lb-filter select {
  background: var(--panel2); color: var(--ink); font: inherit; font-size: 13.5px;
  border: 1px solid var(--border2); border-radius: var(--radius-sm);
  padding: 8px 10px; outline: none; cursor: pointer;
}
.lb-filter select:focus { border-color: var(--gold); }

.lb-meta { margin: 12px 0 8px; color: var(--ink-dim); font-size: 13px; }
.lb-meta b { color: var(--gold-2); }

.lb-table {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; background: var(--panel);
}
.lb-row {
  display: grid; grid-template-columns: 64px 1.6fr 1fr 1fr 1fr; gap: 10px;
  align-items: center; padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px;
}
/* Skill boards add an EXP column. */
.lb-table .lb-row:has(> span:nth-child(6)) { grid-template-columns: 64px 1.5fr .9fr .9fr .7fr .9fr; }
.lb-row:last-child { border-bottom: 0; }
.lb-head {
  background: var(--panel2); color: var(--ink-faint);
  font-size: 11px; font-weight: 700; letter-spacing: .8px; text-transform: uppercase;
}
.lb-row > span:first-child { font-variant-numeric: tabular-nums; color: var(--ink-dim); font-weight: 700; }
.lb-row > span:last-child { font-variant-numeric: tabular-nums; }
.lb-you { background: rgba(234,181,67,.10); }
.lb-you > span { color: var(--ink); }
/* Clickable player name → their character page (#6, 2026-07-24). */
.lb-name {
  background: none; border: none; padding: 0; font: inherit; color: var(--gold);
  cursor: pointer; text-align: left;
}
.lb-name:hover { text-decoration: underline; }
.lb-tag {
  font-size: 10px; text-transform: uppercase; letter-spacing: .6px;
  color: var(--bg); background: var(--gold); padding: 1px 6px; border-radius: 999px;
}
.lb-medal-1 { color: #f7cf74 !important; }
.lb-medal-2 { color: #cfd3d8 !important; }
.lb-medal-3 { color: #d9a066 !important; }

@media (max-width: 720px) {
  .lb-row { grid-template-columns: 44px 1.4fr 1fr; }
  .lb-row > span:nth-child(4), .lb-row > span:nth-child(5) { display: none; }
}

/* ===========================================================================
   CHARACTER PAGE + TRADING (M3)
   =========================================================================== */
/* The sidebar name card is now a button (the Character tab). */
button.side-char { width: 100%; text-align: left; cursor: pointer; font: inherit; }
button.side-char.active { border-color: var(--gold); }

/* ⚠️ THE BUTTONS DROP THEIR TOP MARGIN HERE (v0.20). The global `.btn` rule sets
   `margin-top: 10px`, which is right for a button sitting at the foot of a card
   but wrong inside a search row: it pushed Search 10px below the input AND
   inflated the row to 46px, which the input then stretched to fill. So the input
   was 46 tall, the button 36, and neither lined up. With the margin gone the row
   is the button's 40px min-height and both match exactly. Same fix serves the
   Trades page, which reuses this class. */
.ch-lookup { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; align-items: stretch; }
.ch-lookup .btn { margin-top: 0; }
.ch-lookup input {
  flex: 1 1 220px; background: var(--panel2); color: var(--ink); font: inherit;
  border: 1px solid var(--border2); border-radius: var(--radius-sm); padding: 9px 12px; outline: none;
}
.ch-lookup input:focus { border-color: var(--gold); }
/* ⚠️ `.ch-lookup` IS THE TRADES TAB'S BOX NOW, not the character page's.
   The character page's own player lookup (and its `.ch-search-wrap` /
   `.ch-suggest` dropdown, both deleted in v0.27) was removed once the GLOBAL
   search box in the left column could look a player up from anywhere. Trades
   still has its own "start a trade with this player" box and still uses these
   rules, so do not delete them as unused. */
.ch-msg { background: var(--panel2); border-left: 3px solid var(--gold); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; padding: 10px 14px; margin-bottom: 12px; }

.ch-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.ch-avatar {
  font-size: 44px; line-height: 1; width: 76px; height: 76px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel2); border: 1px solid var(--border2); border-radius: var(--radius);
}
.ch-id { flex: 0 1 auto; min-width: 200px; }
.ch-id h1 { margin: 0 0 2px; }
.ch-sub { color: var(--ink-dim); font-size: 13.5px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ch-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.ch-dot.on { background: var(--green); box-shadow: 0 0 6px var(--green); }
.ch-dot.off { background: var(--ink-faint); }
/* ⚠️ `.ch-activity { margin-top: 6px; font-size: 13.5px; color: ink-dim }` USED TO
   LIVE HERE and is gone (v0.25). It dated from when the activity figures were a
   line in the page HEADER; since v0.22 the only element with that class is the
   Idle Activity PANEL in the left column, where the 6px landed on top of the
   column's own 14px flex gap (the second half of the owner's alignment report,
   with the 14px margin further down being the first) and the dim colour made its
   heading darker than every other panel heading beside it. Every row inside the
   panel sets its own size and colour, so nothing was relying on it. */
.ch-activity b { color: var(--gold-2); font-variant-numeric: tabular-nums; }
.ch-headline { display: flex; gap: 10px; }
.ch-stat-big {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px 18px; text-align: center; min-width: 96px;
}
.ch-stat-big span { display: block; font-size: 26px; font-weight: 800; color: var(--gold-2); line-height: 1.1; }
.ch-stat-big label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .8px; color: var(--ink-faint); }

.ch-styles { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.ch-style {
  background: var(--panel); border: 1px solid var(--border); border-radius: 999px;
  padding: 6px 14px; font-size: 13px; color: var(--ink-dim);
}
.ch-style b { color: var(--ink); margin-left: 4px; }

.ch-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0; }

/* The ADMIN badge beside a name on the Character page (v0.21). Gold, so it reads
   as a mark of office rather than the neutral grey "you" tag next to it. */
.ch-admin-tag {
  display: inline-block; vertical-align: middle; margin-left: 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase;
  color: #1a1611; background: var(--gold); border-radius: 999px; padding: 2px 9px;
}
/* Character-page gear and tool slots are BUTTONS into the item inspector (v0.21).
   They keep the .equip-slot look and only gain a pointer + a hover lift, so the
   page still reads as a display rather than a place you can change things. */
/* The account behind a searched character, in the Admin panel (v0.22). */
.adm-account {
  background: var(--panel2); border: 1px solid var(--border2); border-radius: var(--radius-sm);
  padding: 10px 12px; margin: 10px 0 12px;
}
.adm-acct-row { display: flex; justify-content: space-between; gap: 14px; font-size: 13px; padding: 3px 0; }
.adm-acct-row > span { color: var(--ink-faint); flex: 0 0 auto; }
.adm-acct-row > b { text-align: right; min-width: 0; word-break: break-word; }
.adm-char { display: inline-block; margin-left: 8px; }
.adm-char.here { color: var(--gold-2); }
.adm-char em { font-style: normal; color: var(--ink-faint); font-weight: 600; }
.adm-acct-note { margin-top: 6px; font-size: 11.5px; color: var(--ink-faint); }

/* ---- THE CHARACTER SWITCHER (v0.22) --------------------------------------
   A top-bar chip between Total Level and gold, showing the skin of the character
   it would take you to. Its own class rather than a plain `.stat` because it is a
   BUTTON: it needs a pointer, a hover and a reset of the browser's button styling. */
.switch-btn {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font: inherit; font-weight: 600; font-size: 13px; color: var(--ink-dim);
  transition: color .12s, border-color .12s, background .12s;
}
.switch-btn:hover { color: var(--ink); border-color: var(--gold); background: var(--panel2); }
.switch-skin { font-size: 16px; line-height: 1; }
@media (max-width: 820px) { .switch-label { display: none; } }

/* Character creation: the Ironman option and the "why is this narrowed" note. */
.cc-chip.cc-iron { border-color: var(--border2); }
.cc-chip.cc-iron.sel { border-color: var(--gold); }
.cc-iron-list { display: block; margin-top: 6px; }
.cc-iron-list em {
  display: block; font-style: normal; font-size: 11.5px; color: var(--red); line-height: 1.5;
}
.cc-note {
  background: var(--panel2); border: 1px solid var(--border2); border-radius: var(--radius-sm);
  padding: 10px 12px; margin-bottom: 10px; font-size: 13px; color: var(--ink-dim); line-height: 1.5;
}

/* The IRONMAN badge beside a name (v0.22). Cold steel next to the Admin gold. */
.ch-iron-tag {
  display: inline-block; vertical-align: middle; margin-left: 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .5px;
  color: var(--ink); background: var(--panel3); border: 1px solid var(--border2);
  border-radius: 999px; padding: 2px 9px;
}
button.equip-slot.inspectable { cursor: pointer; font: inherit; transition: transform .08s, border-color .12s; }
button.equip-slot.inspectable:hover { transform: translateY(-1px); border-color: var(--gold); }

.ch-panel {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; margin-bottom: 14px;
}
.ch-panel h2 { margin: 0 0 12px; font-size: 15px; letter-spacing: .3px; }
/* ⚠️ NARROW ON PURPOSE (#12, v0.16). These rows are `space-between`, so at panel
   width the label sat at the far left and its value at the far right with a lake
   between them. A label and its value are one fact and belong within a glance of
   each other. The panel keeps its full width for the heading and the Metrics
   button; only the rows are capped. */
.ch-panel-narrow { max-width: 420px; }
/* min-width so the shrink-to-fit row (v0.21) cannot squeeze the panel down to the
   width of its longest value; max-width so the rows stay scannable on a monitor. */
.ch-info { display: flex; flex-direction: column; gap: 2px; min-width: 240px; max-width: 360px; }

/* THE ACTIVITY TRACKER (v0.22). Three labelled meters under the character info.
   The meter reuses `.skbar`, so it matches every other progress bar in the game.
   ⚠️ NO `margin-top` (fixed v0.25, owner's item #3). It used to carry 14px, which
   landed ON TOP of `.ch-col-main`'s own 14px flex gap: the panel sat 28px below
   Character while every other panel on the page was 14px from its neighbour, and
   the left column visibly failed to line up with the right one. A flex parent
   with a `gap` already owns the spacing — do not put a margin back. */
.ch-act-row { display: flex; align-items: center; gap: 10px; padding: 5px 0; font-size: 13.5px; }
.ch-act-name { flex: 0 0 88px; color: var(--ink-faint); }
.ch-act-meter { flex: 1 1 auto; min-width: 0; height: 8px; }
.ch-act-pct { flex: 0 0 52px; text-align: right; color: var(--gold-2); font-weight: 700; }
.ch-act-pct.dim { color: var(--ink-faint); font-weight: 600; }
.ch-act-note { margin-top: 8px; font-size: 11.5px; color: var(--ink-faint); line-height: 1.45; }
.ch-info-row { display: flex; justify-content: space-between; gap: 10px; border-bottom: 1px solid var(--border); padding: 5px 0; font-size: 13.5px; }
.ch-info-row span { color: var(--ink-faint); }

/* Columns must match the slot size (--slot-size, 72px), or the 72px slots spill
   out of narrower tracks and overlap each other (#4, 2026-07-24). */
.ch-equip { display: grid; grid-template-columns: repeat(3, var(--slot-size)); gap: 8px; justify-content: center; }
.ch-eq-blank { width: var(--slot-size); height: var(--slot-size); }
.ch-pet { font-size: 24px; position: relative; }
/* The equipped pet's portrait on the Character page (v0.32). Sized off
   --slot-size like `.equip-slot .item-art`, so it matches the Inventory tab's
   identical slot. The inline-flex centres the EMOJI fallback, which
   `beastIcoHtml` returns as a span with this same class. */
.ch-pet-art {
  width: calc(var(--slot-size) * .78); height: calc(var(--slot-size) * .78);
  display: inline-flex; align-items: center; justify-content: center;
  object-fit: contain; image-rendering: pixelated; image-rendering: crisp-edges;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.45));
}
.ch-pet b { position: absolute; right: -6px; bottom: -6px; font-size: 10px; background: var(--gold); color: #1a1611; border-radius: 999px; padding: 0 4px; }

/* The 13 non-combat skills: large icons, FULL WIDTH under the character row
   (v0.21). `auto-fit` means all 13 sit on one line on a wide monitor and the grid
   steps down by itself as the window narrows — no per-width column counts to keep
   in step. 76px is the narrowest a cell can be and still read. */
.ch-skills { display: grid; grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); gap: 8px; }
.ch-skills-panel { max-width: 1400px; }
.ch-skill {
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 4px 8px; text-align: center;
}
.ch-skill-ico { display: block; font-size: 30px; line-height: 1; }
.ch-skill b { display: block; margin-top: 6px; font-size: 15px; color: var(--gold-2); }
@media (max-width: 720px) { .ch-skills { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* --- Trading --- */
.tr-sides { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 680px) { .tr-sides { grid-template-columns: 1fr; } }
.tr-side { background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; }
.tr-side h3 { margin: 0 0 8px; font-size: 13px; text-transform: uppercase; letter-spacing: .6px; color: var(--ink-faint); }
.tr-items { display: flex; flex-wrap: wrap; gap: 6px; min-height: 34px; }
.tr-item { display: inline-flex; align-items: center; gap: 5px; background: var(--panel); border: 1px solid var(--border2); border-radius: 999px; padding: 3px 10px 3px 4px; font-size: 12.5px; }
.tr-item img { width: 20px; height: 20px; }
.tr-empty { color: var(--ink-faint); font-size: 13px; font-style: italic; }
.tr-tax { margin-top: 8px; font-size: 12.5px; color: var(--ink-dim); }
.tr-tax b { color: var(--gold-2); }
/* Can't-afford-the-tax highlight (#2, 2026-07-24). */
.tr-tax-bad { color: var(--red, #e06c6c); }
.tr-tax-bad b { color: var(--red, #e06c6c); }
.tr-tax-note { margin-top: 4px; font-weight: 600; }
.tr-h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .6px; color: var(--ink-faint); margin: 16px 0 8px; }
.tr-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 8px; }
.tr-pick { display: flex; align-items: center; gap: 8px; background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px 8px; }
.tr-pick img { width: 26px; height: 26px; }
.tr-pick-name { flex: 1; font-size: 12.5px; line-height: 1.25; }
.tr-pick-name small { display: block; color: var(--ink-faint); font-size: 11px; }
.tr-pick input, .tr-gold input {
  width: 74px; background: var(--panel); color: var(--ink); font: inherit; font-size: 13px;
  border: 1px solid var(--border2); border-radius: 6px; padding: 5px 6px; text-align: right;
}
.tr-gold { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 13.5px; }
.tr-gold small { color: var(--ink-faint); }
.tr-row {
  display: grid; grid-template-columns: 1fr 1.6fr auto; gap: 10px; align-items: center; width: 100%;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 14px; margin-bottom: 8px; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.tr-row:hover { border-color: var(--gold); }
.tr-row-mid { color: var(--ink-dim); font-size: 12.5px; }
.tr-hist { border-bottom: 1px solid var(--border); padding: 9px 0; font-size: 13px; }
.tr-hist:last-child { border-bottom: 0; }
.tr-hist-top { display: flex; justify-content: space-between; gap: 10px; }
.tr-hist-top span { color: var(--ink-faint); font-size: 11.5px; }
.tr-hist-line { color: var(--ink-dim); margin-top: 3px; font-size: 12.5px; }
.tr-hist-cancel { opacity: .6; }

/* ============================================================================
 * THE PLAYER MARKET (M3). The browse list reuses the Item Database's .db-*
 * shell, so this file only adds the market-specific bits: the price column, the
 * item page's listing table, the buy/sell forms, and the 30-day history charts.
 * ==========================================================================*/
.mk-wrap .db-list { max-width: 1100px; }
.mk-row {
  display: grid;
  grid-template-columns: 40px minmax(90px, 1.7fr) minmax(90px, 1.5fr) minmax(56px, .9fr) minmax(56px, .8fr);
  gap: 14px; align-items: center;
}
.mk-browse-head {
  cursor: default; background: var(--panel); color: var(--ink-faint);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase; font-weight: 700;
  padding-top: 7px; padding-bottom: 7px;
}
.mk-browse-head:hover { background: var(--panel); }
.mk-c-name { font-weight: 600; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mk-c-stock { font-size: 13px; color: var(--ink-dim); white-space: nowrap; }
.mk-c-stock small { color: var(--ink-faint); font-size: 11.5px; }
.mk-c-type { font-size: 12px; color: var(--ink-faint); white-space: nowrap; }
.mk-c-price { font-size: 14px; text-align: right; white-space: nowrap; }
.mk-browse-head .mk-c-price, .mk-browse-head span:last-child { text-align: right; }
/* My Listings reuses the Browse row (v0.21, owner's item #17) and differs only in
   having a sixth column for the Cancel button. Everything else — art size, row
   height, hover, header — comes from .db-row / .mk-row, which is the point. */
.mk-mine-row {
  grid-template-columns: 40px minmax(90px, 1.7fr) minmax(70px, 1fr) minmax(64px, .9fr) minmax(64px, .9fr) auto;
}
/* The item name in My Listings opens that item's market page (v0.34), so the cell
   is a <button>. It re-declares only what a <button> would otherwise bring with
   it - the browser's own font, padding, border and background.
   ⚠️⚠️ IT MUST NOT TOUCH `color` OR `background-image`, AND BOTH TRAPS ARE REAL.
   The element also carries its rarity class, and `button.mk-name-btn` (a type +
   a class) is MORE SPECIFIC than `.q-mythic` (a class), so anything declared here
   beats the rarity:
     - a `color` rule would grey out every rarity in the list;
     - `background: none` (the shorthand) wipes `background-image`, and the
       LEGENDARY and MYTHIC rules paint their text with a gradient through
       `background-clip: text` + `-webkit-text-fill-color: transparent`. Killing
       the gradient while the transparent fill survives leaves the name INVISIBLE.
       That happened, and it was caught by reading the computed style rather than
       by looking - "no gradient" and "no text" look identical on a dark panel.
   So: `background-color` only, and no `color` at all. */
button.mk-name-btn {
  background-color: transparent; border: 0; padding: 0; margin: 0;
  font: inherit; font-weight: 600; font-size: 15px;
  text-align: left; cursor: pointer; width: 100%; min-width: 0;
}
button.mk-name-btn:hover { text-decoration: underline; }
.mk-c-act { text-align: right; }
.mk-c-act .btn { margin-top: 0; }
@media (max-width: 720px) {
  .mk-row { grid-template-columns: 40px 1fr auto; }
  /* The listings row keeps its Cancel button on a phone — losing it would leave
     no way to take something off the market. "Left" and "Worth" fold away, and
     the unit price stays, since that is the number a seller checks.
     ⚠️ nth-CHILD, not nth-of-type: every cell is a <span>, so nth-of-type counts
     all six of them and `.mk-c-price:nth-of-type(2)` matched nothing at all,
     leaving six cells in a four-column grid and a squashed 8px button. */
  .mk-mine-row { grid-template-columns: 40px 1fr auto auto; }
  .mk-mine-row > span:nth-child(3),
  .mk-mine-row > span:nth-child(5) { display: none; }
  .mk-c-type { display: none; }
  .mk-browse-head { display: none; }
}

/* --- The item page header --- */
.mk-head {
  display: flex; align-items: center; gap: 14px; margin: 12px 0 14px; padding: 14px 16px;
  background: var(--panel); border: 1px solid var(--border); border-left-width: 4px; border-radius: var(--radius);
}
.mk-head-art { width: 52px; height: 52px; font-size: 40px; line-height: 1; }
.mk-head-name { font-size: 19px; font-weight: 700; }
.mk-head-sub { color: var(--ink-dim); font-size: 12.5px; margin-top: 2px; }

/* --- Quick buy (sweeps the cheapest listings) --- */
.mk-buyrow { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
.mk-buyrow label { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
/* Match the themed inputs elsewhere — the number box was rendering as a bare
   white rectangle (#5, 2026-07-24). */
.mk-buyrow input {
  width: 90px; background: var(--panel); color: var(--ink); font: inherit; font-size: 13px;
  border: 1px solid var(--border2); border-radius: 6px; padding: 6px 8px; text-align: right;
}
.mk-buyrow input:focus { outline: none; border-color: var(--gold); }
.mk-buytotal { font-size: 13.5px; color: var(--ink-dim); }
.mk-buytotal b { color: var(--gold-2); font-size: 15px; }

/* --- The listing table (cheapest first, oldest first on a tie) --- */
.mk-table { border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.mk-lrow {
  display: grid; grid-template-columns: 1.6fr .8fr .9fr .9fr auto; gap: 10px;
  align-items: center; padding: 8px 12px; font-size: 13px; border-bottom: 1px solid var(--border);
}
.mk-lrow:last-child { border-bottom: 0; }
.mk-lrow img { width: 22px; height: 22px; vertical-align: -5px; }
.mk-lrow small { color: var(--ink-faint); }
.mk-lhead {
  background: var(--panel2); color: var(--ink-faint); font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .6px;
}
.mk-mine { background: rgba(234, 181, 67, .07); }
.mk-seller {
  background: none; border: 0; padding: 0; font: inherit; color: var(--gold-2);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.mk-seller:hover { color: var(--gold); }
.mk-mini { padding: 4px 10px; font-size: 12px; }
@media (max-width: 720px) {
  .mk-lrow { grid-template-columns: 1fr auto; row-gap: 4px; }
  .mk-lhead { display: none; }
}

/* --- Sell / buy forms (popups) --- */
.mk-sell-form { display: grid; gap: 10px; margin-bottom: var(--popup-gap); }
.mk-sell-form label { display: flex; align-items: center; gap: 10px; font-size: 13.5px; flex-wrap: wrap; }
.mk-sell-form input {
  width: 110px; background: var(--panel); color: var(--ink); font: inherit; font-size: 13px;
  border: 1px solid var(--border2); border-radius: 6px; padding: 6px 8px; text-align: right;
}
.mk-sell-form small { color: var(--ink-faint); font-size: 11.5px; }

/* --- Picking something out of the bag to sell --- */
.mk-bag { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.mk-bag-item {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 7px 10px; color: var(--ink); font: inherit; font-size: 12.5px;
}
.mk-bag-item:hover { border-color: var(--gold); }
.mk-bag-item img { width: 26px; height: 26px; }
.mk-bag-name { flex: 1; line-height: 1.2; }
.mk-bag-qty { color: var(--gold-2); font-weight: 600; }

/* --- The 30-day history charts (pure SVG, hover a day for its numbers) --- */
.mk-chart-title {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .6px;
  color: var(--ink-faint); margin: 12px 0 4px;
}
.mk-chart-box { width: 100%; overflow: hidden; }
.mk-chart { display: block; max-width: 100%; }
.mk-grid { stroke: var(--border); stroke-width: 1; }
.mk-axis { fill: var(--ink-faint); font-size: 10px; font-family: var(--ui-font); }
.mk-bar { fill: var(--gold); opacity: .75; }
.mk-line { fill: none; stroke: var(--green); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.mk-dot { fill: var(--green); }
.mk-hit { fill: transparent; cursor: crosshair; }
.mk-hit:hover { fill: rgba(255, 255, 255, .06); }

/* Character page: the info panel, Equipment and Tools all on ONE shrink-to-fit
   row (v0.21, owner's item #6).
   ⚠️ IT IS FLEX, NOT A GRID WITH A `1fr` COLUMN, and that is the whole fix. The
   old `minmax(0, 1fr) auto` grid gave the left column every spare pixel while the
   info panel inside it was capped at 420px, so ~350px of empty page sat between
   the panel and Equipment. Flex lets the panel take only the width it asks for and
   Equipment follows immediately. Do not put a `1fr` track back. */
.ch-cols {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-start;
}
.ch-cols > .ch-panel { margin-bottom: 0; }
/* The RIGHT column of the character page: Equipment and Tools side by side
   (#2, v0.16). Both are narrow slot grids, so they pair well; below 1180px the
   whole page is already stacking, so they drop under each other too. */
/* ⚠️ THE GEAR ROW GROWS INTO ITS LINE (`flex: 1 1 auto`, v0.23). It was
   shrink-to-fit, which was right when it held two fixed grids of squares. With
   the Stats panel added as a third it left that panel about 190px, NARROWER than
   the 270px column it used to live in inside Equipment, and often bumped it onto
   a line of its own. Growing means Stats gets the real leftover width, and on a
   screen wide enough for the info panel to sit beside the gear it still only
   takes what is left rather than forcing a wrap. */
.ch-gear-cols { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
.ch-gear-cols > .ch-panel { margin-bottom: 0; flex: 0 0 auto; }
/* The Stats panel (v0.23) sits third, after Equipment and Tools, and is the one
   in that row allowed to GROW: the other two are grids of fixed squares, and the
   numbers were the thing being squeezed. Its own grid gets a comfortable minimum
   so a two-word label and a six-figure value never share a cramped column.
   ⚠️ Character page only — the Inventory tab keeps its totals where they were. */
/* Stats is the one panel in the row allowed to take the slack: the other two are
   grids of fixed squares, and the numbers were the thing being squeezed. It still
   wraps below them on a narrow screen, which is correct there. */
/* ⚠️ THE GROWING SLOT MOVED UP ONE LEVEL (v0.25, owner's item #3). Stats and
   Beasts are stacked inside `.ch-stats-col`, so the column is now the flex item
   that takes the slack and the Stats panel simply fills it. Adding Beasts as a
   THIRD sibling of .ch-gear-cols would have starved Stats all over again — that
   row may only ever have one growing member (see the v0.23 note above). */
.ch-stats-col {
  display: flex; flex-direction: column; gap: 14px;
  flex: 1 1 240px; min-width: 200px; max-width: 460px;
}
.ch-stats-col > .ch-panel { margin-bottom: 0; }
.ch-gear-cols > .ch-panel-stats { flex: 1 1 auto; min-width: 0; }
.ch-panel-stats .equip-totals { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
.ch-panel-stats .fx-btn { margin-top: 12px; width: 100%; }
.ch-tools { display: grid; grid-template-columns: repeat(2, var(--slot-size)); gap: 8px; justify-content: start; }
.ch-tool-cell { width: var(--slot-size); }
/* Left column: character info (+ More Metrics) stacked over the skills grid, so
   they fill the height beside the narrow equipment panel instead of leaving a
   tall gap (owner's call, 2026-07-22). */
.ch-col-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; flex: 0 1 auto; }
.ch-col-main > .ch-panel { margin-bottom: 0; }
/* ⚠️ THE GEAR ROW STRETCHES NOW (v0.23). It used to be `flex: 0 1 auto` — "may
   drop to its own line, but never fills leftovers" — which was right for two
   fixed grids of squares. With the Stats panel added as a third it starved that
   panel of width (about 190px, NARROWER than the 270px column it used to sit in
   inside Equipment) and often pushed it onto a line of its own, which is the
   opposite of "move it to the right of Tools". Growing gives Stats the real
   leftover width, and on a screen wide enough for the info panel to sit alongside
   the gear it still takes only what remains rather than forcing a wrap. */
.ch-cols > .ch-gear-cols { flex: 1 1 auto; }

/* The Metrics popup reuses .fx-popup's scrolling shell but needs more room for
   its label/value grids than a standard narrow popup. */
.fx-popup .metric-sec:first-of-type { margin-top: 4px; }

/* ============================================================================
 * GOLD COIN ICON (2026-07-22). Gold is currency, not an item, so it has no
 * MIG.items entry and can't go through artTag — `goldIco(cls)` builds it.
 * The default sits inline with text; the modifiers match the art sizes used by
 * drop rows and toasts.
 * ==========================================================================*/
.gold-ico {
  width: 1.05em; height: 1.05em; object-fit: contain; vertical-align: -0.16em;
  image-rendering: pixelated; image-rendering: crisp-edges; flex-shrink: 0;
}
.gold-ico.drop-ico { width: 18px; height: 18px; vertical-align: -4px; }
.gold-ico.toast-art { width: 34px; height: 34px; vertical-align: middle; }

/* ============================================================================
 * v0.15 ADDITIONS
 * ==========================================================================*/

/* ---- Beast PORTRAITS ------------------------------------------------------
   Most beasts are still an emoji. A beast def may carry `img` (the three
   SKILLING pets do, as placeholder art), which renders as an <img> wearing the
   same class — so both need to size sensibly. The card portrait is bigger since
   v0.15, which is the whole reason the reward numbers moved behind a toggle. */
.beast-ico { font-size: 34px; flex-shrink: 0; width: 46px; height: 46px; display: flex; align-items: center; justify-content: center; }
img.beast-ico, img.mob-pop-ico {
  object-fit: contain; image-rendering: pixelated; image-rendering: crisp-edges;
  padding: 3px; filter: drop-shadow(0 2px 4px rgba(0,0,0,.45));
}
img.mob-pop-ico { display: block; }

/* ---- MOB ART (v0.21) -----------------------------------------------------
   Every mob portrait goes through ui.js's `mobIcoHtml`, which emits an <img> for
   a drawn mob and the old emoji <span> for the rest. The emoji rules above size
   TEXT, so each place an image can land needs an image rule as well or the PNG
   renders at its natural size and blows the layout apart. `img.mob-art` is the
   one hook: it just makes the picture fill whatever box its sibling class set. */
/* ⚠️ EACH SIZE CARRIES ITS OWN INSET, RIGHT NEXT TO ITS WIDTH (v0.23, owner's
   item #2). The art used to draw almost edge to edge inside its frame, so a mob
   looked wedged into its own box. The inset is roughly 8% of the box, written out
   per size rather than shared, so the number can never be wrong for one of them.
   ⚠️ DO NOT "SIMPLIFY" THIS TO A PERCENTAGE. That was the first attempt and it is
   a trap: percentage padding resolves against the CONTAINING BLOCK's width, not
   the element's own. On the Combat tab the 64px portrait sits in a ~209px flex
   card, so `padding: 8%` came out as 16.7px and shrank a 60px picture to 29px.
   The 2px below is only a floor for any size that has no rule of its own. */
img.mob-art {
  object-fit: contain; image-rendering: pixelated; image-rendering: crisp-edges;
  padding: 2px; filter: drop-shadow(0 2px 4px rgba(0,0,0,.45));
}
img.mob-db-ico { width: 40px; height: 40px; padding: 3px; }
/* ---- WORLD BOSS ART (v0.28, the owner's 18 files) -------------------------
   `img.wboss-art` is the same one hook `img.mob-art` is: it makes the picture
   fill whatever box its sibling class already set, and each SIZE carries its own
   inset right beside its width. ⚠️ Do NOT use a percentage inset here either -
   see the warning above `img.mob-art`; it resolves against the containing block,
   not the element, and that really did shrink a 60px portrait to 29px once.
   ⚠️ These are BIG pictures on purpose. The boss is the most important thing on
   the strip and in its popup, so it leads at 64px rather than at the 34px the
   emoji was sized for. */
img.wboss-art {
  object-fit: contain; image-rendering: pixelated; image-rendering: crisp-edges;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .5));
}
img.wboss-ico { width: 64px; height: 64px; padding: 4px; }
img.zw-ico    { width: 40px; height: 40px; padding: 3px; }
/* The popup and the after-fight screen share `.mob-pop-ico`'s 56px frame, so the
   boss sits in exactly the box a mob does. */
img.mob-pop-ico.wboss-art { width: 56px; height: 56px; padding: 4px; }
img.mob-pop-ico { width: 56px; height: 56px; padding: 4px; }
img.mob-style-ico { width: 22px; height: 22px; padding: 2px; }
img.ab-travel-ico { width: 38px; height: 38px; padding: 3px; }
img.at-art { width: 30px; height: 30px; padding: 2px; }
img.toast-art { width: 34px; height: 34px; padding: 3px; }
/* ⚠️ THE COMBAT TAB'S PORTRAIT IS THE BIG ONE (v0.22, owner's item #6). It shipped
   at 34px in v0.21, which was sized for an emoji and far too small to actually see
   a drawn mob. The card's top row now leads with a proper 64px framed portrait, so
   the artwork is the first thing you look at. It is still only rendered for a mob
   that HAS art, so undrawn mobs keep the old compact card exactly. */
.mob-card-ico { width: 64px; height: 64px; flex-shrink: 0; }
img.mob-card-ico {
  width: 64px; height: 64px; border-radius: 10px; padding: 5px;
  background: rgba(255,255,255,.04); border: 1px solid var(--border);
}
.mob-card.mob-melee  img.mob-card-ico { border-color: var(--st-melee); }
.mob-card.mob-ranged img.mob-card-ico { border-color: var(--st-ranged); }
.mob-card.mob-magic  img.mob-card-ico { border-color: var(--st-magic); }
/* With a 64px portrait the top row is a portrait beside a stacked name/meta block
   rather than one line of chips, so the card becomes a two-column layout. */
.mob-card.has-art { display: flex; gap: 12px; align-items: flex-start; }
.mob-card.has-art .mob-card-body { flex: 1 1 auto; min-width: 0; }
.mob-card.has-art .mob-card-meta { margin-left: 0; }

/* The mob popup and the Mob Database rows get a lift too — same reasoning, less
   room to play with. */
img.mob-db-ico { width: 48px; height: 48px; padding: 4px; }
.mob-db-ico, .db-row-art { width: 48px; height: 48px; }
img.mob-pop-ico { width: 84px; height: 84px; padding: 6px; }
.mob-pop-ico { width: 84px; height: 84px; font-size: 44px; }

/* Collapsed reward details on a battling beast's card (v0.15). */
.beast-xp-toggle {
  background: none; border: 0; padding: 2px 4px; cursor: pointer; font: inherit;
  font-size: 12px; font-weight: 700; color: var(--gold-2); letter-spacing: .02em;
}
.beast-xp-toggle:hover { text-decoration: underline; }

/* Mail "go there" button (v0.15). Built client-side from the message KIND —
   the server never sends markup. */
.mail-link {
  margin-top: 8px; background: var(--panel3); border: 1px solid var(--border2);
  color: var(--ink); border-radius: 999px; padding: 4px 12px; font: inherit;
  font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.mail-link:hover { border-color: var(--gold); color: var(--gold-2); }
.mail-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.mail-actions .mail-link { margin-top: 8px; }
.mail-report { border-color: var(--gold); color: var(--gold-2); }

/* ---- THE PINNED PANEL (v0.15) --------------------------------------------
   One Inventory panel, floating above every page and draggable by its bar.
   DESKTOP ONLY: the phone query at the end of this file hides it outright, and
   ui.js never renders it under the breakpoint (a floating box would cover most
   of a phone and its drag would fight touch scrolling). */
.inv-pin {
  background: none; border: 0; cursor: pointer; font-size: 14px; line-height: 1;
  padding: 2px 4px; border-radius: 6px; opacity: .45; transition: opacity .1s, background .1s;
}
.inv-pin:hover { opacity: 1; background: rgba(255,255,255,.06); }
.inv-pin.on { opacity: 1; }

/* ⚠️ WIDE ENOUGH FOR THE WHOLE BAG (#1, v0.16). The bag grid is a FIXED
   `repeat(var(--inv-cols), var(--slot-size))` = 6 x 72px plus gaps, so a 420px
   cap forced a horizontal scrollbar and cut the last column off. The pinned
   panel exists so you can see your bag at a glance, and half a bag is useless.
   `width: max-content` lets it size to its content and the cap only steps in on
   a genuinely small window. If --inv-cols or --slot-size ever grows, re-check
   this number. */
.pin-panel {
  position: fixed; z-index: 60; width: max-content; max-width: min(94vw, 620px);
  max-height: 78vh; display: flex; flex-direction: column;
  background: var(--panel2); border: 1px solid var(--border2);
  border-radius: 14px; box-shadow: 0 18px 44px rgba(0,0,0,.55);
}
.pin-panel.dragging { opacity: .9; box-shadow: 0 22px 54px rgba(0,0,0,.7); }
.pin-bar {
  display: flex; align-items: center; gap: 8px; padding: 7px 8px 7px 10px;
  border-bottom: 1px solid var(--border); cursor: grab; user-select: none;
  border-radius: 13px 13px 0 0; background: var(--panel3);
}
.pin-panel.dragging .pin-bar { cursor: grabbing; }
.pin-grip { color: var(--ink-faint); font-size: 13px; letter-spacing: -1px; }
.pin-title { flex: 1 1 auto; font-size: 13px; font-weight: 700; color: var(--ink); white-space: nowrap; }
.pin-x {
  background: none; border: 0; color: var(--ink-faint); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 3px 5px; border-radius: 6px;
}
.pin-x:hover { color: var(--red, #e06c6c); background: rgba(255,255,255,.06); }
/* The panel body scrolls on its own so a tall bag never runs off the screen.
   The panels inside drop their own frame: the floating box already is one. */
.pin-body { overflow: auto; padding: 12px 14px 14px; }
.pin-body .inv-sec, .pin-body .magic-col {
  background: none; border: 0; box-shadow: none; padding: 0; width: auto; max-width: none;
}
.pin-body .inv-sec-head { display: none; }   /* the pin bar is already the title */
.pin-body .inv-hint { margin-bottom: 8px; }

/* ============================================================================
 * PHONE LAYOUT (2026-07-25, v0.13)
 * ----------------------------------------------------------------------------
 * Below 820px the sidebar is replaced by a five-tab bar pinned to the bottom of
 * the screen, where thumbs actually reach. Order of the fixed furniture, top to
 * bottom: compact top bar, the scrolling page, the active-task strip, tab bar.
 *
 * The breakpoint is duplicated in ui.js (UI.MOBILE_BREAKPOINT). Keep both in
 * step, or the JS will render a phone page inside the desktop frame.
 * iOS SAFE AREAS: the home-indicator strip overlaps anything pinned to the
 * bottom, so the tab bar pads itself with env(safe-area-inset-bottom).
 * ========================================================================== */

/* Hidden by default and only revealed in the phone query below, so the desktop
   layout is completely unaffected by any of this. */
#mobileNav { display: none; }

@media (max-width: 820px) {
  /* ---- Frame ------------------------------------------------------------- */
  #app { grid-template-rows: auto 1fr auto auto; }
  #sideCol { display: none; }               /* the tab bar replaces it */
  /* The pinned panel is DESKTOP ONLY. ui.js also refuses to render it under the
     breakpoint, but belt and braces: a stale node must never cover a phone. */
  .pin-panel, .inv-pin { display: none !important; }
  #main { padding: 14px 14px 18px; -webkit-overflow-scrolling: touch; }
  /* ---- The two DATABASE pages stack on a phone --------------------------- */
  /* `.db-list` carries `flex-basis: 440px` and `.db-filters` a fixed 300px, which
     is wider than a 375px screen, so the pages scrolled sideways. Both go full
     width and sit one above the other instead. The list comes FIRST in the DOM,
     but the filters are what you reach for on a phone, so `order` puts them on
     top. (The frame itself is held still by `min-width: 0` on #main — that is the
     fix that stops the top bar and tab bar sliding away with the page.) */
  .db-wrap { flex-direction: column; gap: 14px; }
  .db-list, .db-filters { flex: 1 1 auto; width: 100%; max-width: 100%; }
  .db-filters { order: -1; }
  .db-list-inner { max-height: none; }
  .db-opt-list { max-height: 190px; }
  #main h1 { font-size: 21px; }
  .panel-intro { margin-bottom: 14px; font-size: 13.5px; }

  /* ---- Top bar: the brand shrinks, the numbers that matter stay ----------- */
  #topbar { padding: 8px 12px; gap: 8px; }
  .brand-sub { display: none; }              /* the "Medieval Idle MMO" tagline */
  /* ⚠️ THE WHOLE WORDMARK GOES ON A PHONE (v0.28.1, the owner's actual brief).
     It was shortened to "Norwyn" in v0.22 and shrunk again below 380px, and it was
     STILL crowding the stat chips - which is what the removal was for. The logo
     says the same thing in a fifth of the space. Desktop keeps it in full. */
  .brand-name { display: none; }
  .brand-logo { height: 26px; }
  .top-right { min-width: 0; }
  .top-stats { gap: 6px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .top-stats::-webkit-scrollbar { display: none; }
  .top-stats .stat { font-size: 12px; padding: 4px 8px; white-space: nowrap; }
  #totalLevel { display: none; }             /* it has its own spot on Character */
  .active-top { display: none; }             /* the active STRIP covers this */

  /* ---- Active-task strip: slim, and always above the tab bar -------------- */
  /* TWO ROWS on a phone. The desktop's three side-by-side columns leave the
     middle (1fr) column with no room at 375px, which collapsed the progress bar
     to ZERO width — the one thing an idle player most needs to see. Details and
     buttons share the top row; the progress bar spans its own row underneath. */
  #activeBar { padding: 7px 12px; min-height: 0; }
  #activeBar.running {
    grid-template-columns: 1fr auto;
    grid-template-areas: "info btns" "prog prog";
    gap: 6px 10px; align-items: center;
  }
  #activeBar .ab-left { grid-area: info; min-width: 0; gap: 9px; }
  #activeBar .ab-right { grid-area: btns; gap: 6px; }
  #activeBar .ab-mid { grid-area: prog; min-width: 0; }
  .ab-idle { font-size: 12.5px; }
  .ab-art { width: 28px; height: 28px; }
  .ab-info { min-width: 0; }
  .ab-title { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* The "produced this session" line is nice to have, not vital: keep it to a
     single line so the strip cannot grow tall enough to eat the page. */
  .ab-sub { font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .ab-prog { height: 16px; }
  .ab-prog-label { font-size: 10.5px; }
  .ab-countdown { font-size: 11.5px; }
  #activeBar .btn { padding: 6px 9px; font-size: 12px; min-height: 34px; }
  #activeBar .btn.ab-sq { width: 34px; height: 34px; padding: 0; }

  /* ---- The five-tab bar --------------------------------------------------- */
  #mobileNav {
    display: grid; grid-template-columns: repeat(5, 1fr);
    background: var(--bg2); border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .mnav-item {
    position: relative; background: none; border: 0; cursor: pointer;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; padding: 7px 2px 6px; min-height: 54px;   /* comfortable thumb target */
    color: var(--ink-faint); font: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .mnav-ico { position: relative; font-size: 19px; line-height: 1; }
  .mnav-label { font-size: 10.5px; font-weight: 700; letter-spacing: .2px; }
  .mnav-item.active { color: var(--gold-2); }
  /* A gold rule along the top edge marks the current tab. */
  .mnav-item.active::before {
    content: ""; position: absolute; top: 0; left: 22%; right: 22%;
    height: 2px; background: var(--gold); border-radius: 0 0 2px 2px;
  }
  .mnav-badge {
    position: absolute; top: -5px; right: -9px; min-width: 15px; height: 15px; padding: 0 3px;
    display: flex; align-items: center; justify-content: center;
    background: var(--red, #c0392b); color: #fff;
    font-size: 9px; font-weight: 800; line-height: 1; border-radius: 999px;
    border: 1px solid var(--bg2);
  }

  /* ---- Skills page (the Skills tab, and Combat's Skills switch) ----------- */
  .msk-head { font-size: 13px; text-transform: uppercase; letter-spacing: .5px;
    color: var(--ink-dim); margin: 16px 0 8px; }
  .msk-head:first-of-type { margin-top: 4px; }
  .msk-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }
  .msk-card {
    display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
    background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 11px 13px; cursor: pointer; color: var(--ink); font: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .msk-card:active { background: var(--panel2); }
  .msk-card.locked { opacity: .5; }
  .msk-card.training { border-color: var(--gold); }
  .msk-ico { font-size: 25px; line-height: 1; flex: 0 0 auto; }
  .msk-body { flex: 1 1 auto; min-width: 0; display: block; }
  .msk-top { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
  .msk-top b { font-size: 14.5px; }
  .msk-lvl { margin-left: auto; font-size: 12px; color: var(--ink-dim); font-weight: 700; }
  .msk-train { color: var(--gold); font-size: 10px; }

  /* ---- Menu page ---------------------------------------------------------- */
  .mmenu-head { font-size: 13px; text-transform: uppercase; letter-spacing: .5px;
    color: var(--ink-dim); margin: 18px 0 8px; }
  .mmenu-head:first-of-type { margin-top: 4px; }
  .mmenu-group {
    background: var(--panel); border: 1px solid var(--border);
    border-radius: var(--radius); overflow: hidden;
  }
  .mmenu-row {
    display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
    background: none; border: 0; border-bottom: 1px solid var(--border);
    padding: 13px 14px; cursor: pointer; color: var(--ink); font: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .mmenu-row:last-child { border-bottom: 0; }
  /* The Character page's Mailbox button, tinted while mail is waiting so it
     reads at a glance the way the sidebar chip's badge does on desktop. */
  .btn.btn-unread { background: var(--gold-2); box-shadow: 0 0 0 2px rgba(234,181,67,.28); }
  /* The Wiki row is an <a>, not a <button> — kill the underline so it reads as
     the same kind of row as its neighbours. */
  a.mmenu-row { text-decoration: none; }
  .mmenu-row:active { background: var(--panel2); }
  .mmenu-ico { font-size: 20px; line-height: 1; flex: 0 0 auto; width: 26px; text-align: center; }
  .mmenu-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .mmenu-text b { font-size: 14.5px; }
  .mmenu-text small { font-size: 11.5px; color: var(--ink-faint); }
  .mmenu-chev { color: var(--ink-faint); font-size: 20px; line-height: 1; flex: 0 0 auto; }
  .mmenu-version { text-align: center; color: var(--ink-faint); font-size: 11.5px; margin: 18px 0 4px; }
  .mob-subtabs { margin-bottom: 12px; }

  /* ---- Popups are CENTRED on phones too (v0.24, owner's item #9) ---------- */
  /* ⚠️ THEY USED TO BE BOTTOM SHEETS. The reasoning was sound — a phone dialog
     should not overflow and its ✕ should be near the thumb — but a player
     reported a popup "at the bottom of the screen" as a bug, and the owner's call
     is to centre them, matching desktop. What must be KEPT from the sheet rules
     is everything that stopped a tall popup breaking the layout: the height cap,
     scrolling inside itself, and the safe-area padding so the home indicator
     never sits on the buttons. Centre it, do not un-cap it. */
  .popup-overlay { align-items: center; padding: 12px; }
  .popup, .fx-popup {
    width: 100%; max-width: none; max-height: 86vh; overflow-y: auto;
    border-radius: var(--radius);
    padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .popup-x { top: 10px; right: 12px; font-size: 20px; padding: 6px 10px; }
  /* (The wrap + min-width rules that used to live here are now on .popup-actions
     itself — the overflow they fixed happened at desktop widths too.) */

  /* ---- Wide content that must not force the whole page sideways ---------- */
  .lb-table, .db-list, .subtab-bar, .mk-chart-box {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .subtab-bar { scrollbar-width: none; }
  .subtab-bar::-webkit-scrollbar { display: none; }
  .card-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }
  .inv-grid.inv-bag { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); }
  /* ⚠️ THE EQUIPMENT AND TOOLS PANELS ARE `flex: 0 0 auto` ON DESKTOP, i.e. sized
     to their widest child. Their equipped-stat grid grows with the NUMBERS in it,
     so a late-game character (six-figure stats, nine-figure gold) made the panel
     wider than the phone and `#main` scrolled sideways — invisible on a fresh save,
     which is why it survived the v0.20 pass. On a phone every panel is simply one
     per line and may never exceed the column. */
  /* `.ch-stats-col` (v0.25) is a COLUMN of panels rather than a panel, so it needs
     naming here too or the "one per line" rule skips the one flex item that is
     allowed to grow on desktop. */
  .inv-wrap > .inv-sec, .ch-gear-cols > .ch-panel, .ch-gear-cols > .ch-stats-col,
  .ch-cols > .ch-col-main {
    flex: 1 1 100%; min-width: 0; max-width: 100%;
  }
  .equip-panel, .equip-totals, .ch-equip, .ch-tools { max-width: 100%; }
  /* The character row is flex now, so the phone rule is simply "one per line". */
  .ch-cols > .ch-col-main, .ch-cols > .ch-gear-cols { flex: 1 1 100%; }
  .ch-skills { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .equip-totals.stat-totals { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .metric-grid { grid-template-columns: 1fr; }
  .theme-grid { grid-template-columns: 1fr 1fr; }
  .tr-sides { grid-template-columns: 1fr; }

  /* The floating quest tracker would sit on top of the tab bar. */
  .quest-tracker { display: none; }

  /* ---- World map pins ----------------------------------------------------- */
  /* Pins are centred on their coordinates, so a zone near the right edge pushed
     its (nowrap) label off screen. Smaller labels, capped width with an ellipsis,
     and the map clips whatever still reaches past its frame. The dot is the tap
     target and the full zone name appears in the panel below once tapped. */
  .worldmap { overflow: hidden; }
  /* Long zone names (Primora Sanctum, Twinspire Riverlands) were being cut off
     by the ellipsis. They WRAP to a second line instead now, so the whole name
     is always readable; nudgeMapLabels() still keeps them inside the frame
     (#1, 2026-07-25). */
  .map-pin-label {
    font-size: 10px; padding: 2px 7px; gap: 3px;
    max-width: 40vw; white-space: normal; text-align: center;
    line-height: 1.25; border-radius: 9px;
    display: inline-flex; flex-wrap: wrap; justify-content: center;
  }
  .map-pin-dot { width: 20px; height: 20px; border-width: 2px; font-size: 10px; }
  .map-pin-lvl { font-size: 9px; padding: 0 4px; }

  /* Touch targets: never smaller than a fingertip. */
  .btn { min-height: 40px; }
  .subtab { min-height: 38px; }
}

/* Roomier phones and small tablets get two skill columns. */
@media (min-width: 520px) and (max-width: 820px) {
  .msk-grid { grid-template-columns: 1fr 1fr; }
}

/* Very narrow phones (iPhone SE and similar). */
@media (max-width: 380px) {
  .mnav-label { font-size: 9.5px; }
  .mnav-ico { font-size: 18px; }
  /* The wordmark is already hidden by the 820px block above, so there is nothing
     left to shrink at this width. `.top-stats` still scrolls in its own row. */
  /* ⚠️ THE SLOT GRIDS ARE FIXED-WIDTH (3 x --slot-size for Equipment, 2 for Tools,
     6 for the bag), so at 320px the Equipment panel alone was 334px inside a 316px
     column and the whole page scrolled sideways. Shrinking the slot is the only
     thing that helps — the grids cannot reflow. */
  :root { --slot-size: 56px; }
  .top-stats .stat { font-size: 11.5px; padding: 3px 7px; }
  /* One stat column: two label/value pairs do not fit 320px. */
  .equip-totals.stat-totals { grid-template-columns: minmax(0, 1fr); }
  /* The 8-hour session countdown is the least urgent number in the strip. */
  .ab-countdown { display: none; }
}

/* ============================================================================
 * THE RECENT DROPS FEED (v0.14) — server-wide World Boss / Dungeon drops.
 * Sits under the World Boss strips (Combat tab) and under the dungeon list.
 * ========================================================================== */
.drops-feed {
  margin-top: 14px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
}
.drops-head {
  font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase;
  font-weight: 700; color: var(--ink-faint); margin-bottom: 10px;
}
.drops-empty { color: var(--ink-faint); font-size: 12.5px; font-style: italic; }
/* ⚠️ AN ALIGNED TABLE, NOT FLEX ROWS (v0.17). The feed is a three-column grid so
   Drop / Player / When line up all the way down the list, under a header row
   naming each column. `.drops-feed` used to be flex rows, which meant every row
   found its own column positions and nothing lined up; before that the item name
   was `flex: 1 1 auto` and ate the whole width, leaving a canyon (#13).
   A long item name ELLIPSISES inside its own column instead of pushing the other
   two around. Keep the grid on the container: per-row flex breaks the alignment.
   (This also replaces a rule that had a stray NUL byte in its `content`, which
   is what rendered as "?b7" beside the timestamp.) */
.drops-feed { max-width: 640px; }
.drops-table {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, auto) auto;
  align-items: center;
  column-gap: 16px;
}
.drops-col-head {
  font-size: 10px; letter-spacing: .9px; text-transform: uppercase; font-weight: 700;
  color: var(--ink-faint); padding-bottom: 6px; border-bottom: 1px solid var(--border2);
}
.drops-cell {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-size: 12.5px; padding: 5px 0; border-bottom: 1px solid var(--border);
}
/* The last three cells of the grid are the last row — drop its rule. */
.drops-table > .drops-cell:nth-last-child(-n+3) { border-bottom: 0; }
.drops-ico { width: 20px; height: 20px; object-fit: contain; flex: 0 0 auto; image-rendering: pixelated; }
.drops-item { font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.drops-who {
  background: none; border: 0; padding: 0; font: inherit; min-width: 0;
  color: var(--gold-2); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.drops-who:hover { text-decoration: underline; }
.drops-when { color: var(--ink-faint); font-size: 11px; white-space: nowrap; }

@media (max-width: 820px) {
  .drops-feed { padding: 10px 12px; }
  /* Phone: drop the Player column's own track and let the name sit under the
     item, so three columns do not fight for 320px. */
  .drops-table { grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; }
  .drops-col-head:nth-child(2) { display: none; }
  .drops-cell:nth-child(3n+2) { grid-column: 1; padding-top: 0; border-bottom: 0; padding-left: 28px; }
  .drops-cell:nth-child(3n+3) { align-self: start; }
}


/* ---- Tracking's level, shown in the Combat tab (v0.17) --------------------
   The skill left the sidebar, so its level and progress live in the panel where
   it is actually trained. Sized like a chip so it sits next to the heading
   without competing with the Start Tracking button. */
.track-lvl {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 700; color: var(--gold-2);
  background: rgba(255,255,255,.04); border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 11px; white-space: nowrap;
}
.track-lvl-bar {
  display: inline-block; width: 54px; height: 5px; border-radius: 999px;
  background: var(--panel3); overflow: hidden;
}
.track-lvl-bar > span { display: block; height: 100%; background: var(--green); border-radius: 999px; }

/* ---- The World Bosses skill, shown in the Combat tab (v0.28) ---------------
   It left the sidebar exactly as Tracking did, so its level lives where it is
   trained. ONE line above the boss strips, never one per boss - two bosses share
   a POI in two places and a chip on each would say the same thing twice.
   ⚠️ It borrows `.track-lvl` above rather than inventing a second chip, so the
   two skills that live in this tab read as the same kind of thing. Sized to the
   same column cap as the boss panels so the left edges line up. */
.wboss-skill {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  max-width: var(--combat-col); margin-bottom: 8px;
  padding: 7px 14px; border-radius: var(--radius);
  background: var(--panel2); border: 1px solid var(--border);
}
.wboss-skill-name { font-size: 13px; font-weight: 600; color: var(--ink); }
/* Pushed to the right edge so the level and the bonus do not read as one figure. */
.wboss-skill-mf { margin-left: auto; font-size: 12px; color: var(--gold-2); white-space: nowrap; }
.wboss-skill-mf.muted { color: var(--ink-faint); }

/* ---- ADMIN: the evidence dossier (v0.17) ----------------------------------
   Read-only moderation evidence: logbook, account facts, market/trade history.
   Sits under the ban controls in the Admin tab, loaded on demand. */
.adm-meta { margin-bottom: 12px; }
.adm-log {
  max-height: 460px; overflow-y: auto; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: rgba(0,0,0,.18);
}
.adm-log-row {
  display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 12px;
  padding: 5px 10px; font-size: 12.5px; border-bottom: 1px solid var(--border);
}
.adm-log-row:last-child { border-bottom: 0; }
.adm-log-when { color: var(--ink-faint); font-size: 11px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.adm-log-msg { min-width: 0; word-break: break-word; }
.adm-log-row.log-level .adm-log-msg { color: var(--gold-2); }
.adm-log-row.log-warn .adm-log-msg { color: var(--red); }
.adm-econ {
  display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto auto;
  column-gap: 14px; margin-bottom: 14px; font-size: 12.5px;
}
.adm-econ-head {
  font-size: 10px; letter-spacing: .9px; text-transform: uppercase; font-weight: 700;
  color: var(--ink-faint); padding-bottom: 5px; border-bottom: 1px solid var(--border2);
}
.adm-econ-cell {
  padding: 4px 0; border-bottom: 1px solid var(--border);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The dossier's own scrollbox joins the game's scrollbar family. */
.adm-log::-webkit-scrollbar { width: 8px; }
.adm-log::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 999px; }
.adm-log::-webkit-scrollbar-track { background: transparent; }
.adm-log { scrollbar-width: thin; scrollbar-color: var(--border2) transparent; }

/* ===========================================================================
   FARMING (v0.23). One plot, drawn as a numbered list of stages so there is
   always exactly one obvious next thing to do. Every stage has an art slot the
   owner's pictures drop into later — see farmArtHtml in ui.js.
   =========================================================================== */
.farm-busy {
  background: rgba(88,201,107,.10); border: 1px solid rgba(88,201,107,.45);
  border-radius: var(--radius-sm); padding: 10px 14px; margin-bottom: 14px; font-size: 13px;
}
.farm-busy b { color: var(--gold-2); font-variant-numeric: tabular-nums; }
.farm-plot {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 18px 20px;
}
.farm-head h2 { margin: 0 0 4px; font-size: 18px; }
.farm-empty { text-align: center; padding: 26px 10px; }
.farm-empty h2 { margin: 10px 0 4px; font-size: 18px; }
.farm-empty p { color: var(--ink-dim); font-size: 13px; margin: 0 0 14px; }
/* One row per stage. The number turns into a tick once the stage is behind you,
   so the plot reads as progress rather than as a wall of buttons. */
.farm-step {
  display: grid; grid-template-columns: 26px 1fr; gap: 10px 12px;
  padding: 12px 0; border-top: 1px solid var(--border);
}
.farm-step:first-of-type { border-top: 0; }
.farm-step-n {
  width: 26px; height: 26px; border-radius: 999px; display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; background: var(--panel3); color: var(--ink-faint); border: 1px solid var(--border2);
}
.farm-step-label { align-self: center; font-size: 14px; font-weight: 600; color: var(--ink); }
.farm-step-body { grid-column: 2; }
.farm-step.done .farm-step-n { background: rgba(88,201,107,.18); color: var(--green); border-color: rgba(88,201,107,.5); }
.farm-step.done .farm-step-label { color: var(--ink-dim); font-weight: 500; }
.farm-step.active .farm-step-n { background: var(--gold); color: #221808; border-color: transparent; }
.farm-step.todo .farm-step-label, .farm-step.skip .farm-step-label { color: var(--ink-faint); font-weight: 500; }
.farm-step.dead .farm-step-n { background: rgba(226,92,77,.18); color: var(--red); border-color: rgba(226,92,77,.5); }
.farm-note { font-size: 12px; color: var(--ink-dim); margin: 6px 0; }
.farm-lock { font-size: 12px; color: var(--ink-faint); margin: 6px 0; }
.farm-dead { color: var(--red); }
/* Art slots. Sized here so a picture that arrives later needs no code change. */
.farm-art { width: 64px; height: 64px; object-fit: contain; image-rendering: pixelated; flex-shrink: 0; }
.farm-art-sm { width: 22px; height: 22px; object-fit: contain; vertical-align: -5px; margin-right: 4px; }
span.farm-art { font-size: 46px; display: inline-flex; align-items: center; justify-content: center; }
span.farm-art-sm { font-size: 17px; }
.farm-art-empty { opacity: .45; }
/* ⚠️ NO grayscale filter here. It existed while the dead plot was drawn with a
   living crop's picture; the real "Dead Crop" art (v0.23) is already withered, and
   greying it out again made it unreadable. The EMOJI fallback keeps the tint. */
span.farm-art-dead { filter: grayscale(1) brightness(.7); }
.farm-seeds { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 10px; }
.farm-seed {
  display: flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 7px 12px; border-radius: 999px; font-size: 12px; font-weight: 600;
  background: var(--panel3); color: var(--ink-dim); border: 1px solid var(--border2); transition: all .1s;
}
.farm-seed:hover { border-color: var(--gold); color: var(--ink); }
.farm-seed.sel { border-color: var(--gold); background: rgba(214,164,72,.14); color: var(--ink); }
.farm-seed.dim { opacity: .5; }
.farm-seed em { font-style: normal; color: var(--ink-faint); font-size: 10.5px; }
.farm-sowbox { max-width: 460px; }
.farm-sowbox > label { display: block; font-size: 13px; color: var(--ink-dim); margin-bottom: 6px; }
.farm-sowbox > label b { color: var(--gold-2); }
.farm-cures { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.farm-cures .btn { margin-top: 0; flex: 0 1 auto; }
.farm-cures .btn em { font-style: normal; color: var(--gold-2); }
.farm-grow { display: flex; align-items: center; gap: 14px; margin: 4px 0 10px; }
.farm-grow-time { font-size: 15px; font-weight: 700; }
.farm-grow-time b { color: var(--gold-2); font-variant-numeric: tabular-nums; }
.farm-abandon { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.farm-abandon .btn { margin-top: 0; }

/* ---- THE SKILL PAGE'S XP LINE (v0.34) -------------------------------------
   The one number a player checks constantly, and it used to live in a `title`
   that a phone cannot show at all. Sits directly under the big progress bar. */
.sh-xp {
  margin-top: 5px; font-size: 12.5px; color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.sh-xp-togo { color: var(--ink-faint); }
.sh-xp-max { color: var(--gold-2); font-weight: 700; }

/* ---- QUEST AND SLAYER MARKERS ON A MOB CARD (v0.34) ------------------------
   ⚠️ `flex-wrap` and `min-width: 0`: a card can carry two markers at once (a
   goblin that is also a Slayer task), and a flex item defaults to `auto` and
   refuses to shrink below its own text, which is what pushes a card out of a
   375px phone. */
.mob-markers { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
.mob-marker {
  min-width: 0; padding: 2px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .2px;
  font-variant-numeric: tabular-nums; border: 1px solid transparent;
}
.mob-marker-slayer { background: rgba(226,92,77,.16); color: #ff8d80; border-color: rgba(226,92,77,.45); }
.mob-marker-quest  { background: rgba(214,164,72,.16); color: var(--gold-2); border-color: rgba(214,164,72,.45); }

/* ---- THE BULK CHEST-OPEN SLIDER (v0.34) ------------------------------------
   ⚠️ The slider's `input` handler patches #chestNLabel and #chestNBtn only, and
   neither is an ancestor of the range input. Rewriting a parent of a range input
   destroys the element mid-drag and the browser loses pointer capture (v0.24). */
.chest-openbox { margin-top: 10px; }
.chest-openbox > label { display: block; font-size: 13px; color: var(--ink-dim); margin-bottom: 6px; }
.chest-openbox > label b { color: var(--gold-2); font-variant-numeric: tabular-nums; }

/* ---- THE OVER-LEVEL XP RULE, EXPLAINED IN PLACE (v0.38) --------------------
   A player asked why one creature paid 20 attack style XP with a bow and 60 with
   a staff. Both numbers were right - each XP type is judged by its own level -
   but nothing on screen said so. These two are the explanation. */
.xp-warn-why {
  margin-top: 6px; font-size: 11.5px; line-height: 1.45;
  color: var(--ink-dim); font-weight: 400;
}
.kv-sub {
  display: block; font-size: 10.5px; color: var(--ink-faint);
  font-weight: 400; letter-spacing: .1px;
}


/* ---- THE FARMING PLANNER (v0.33) -----------------------------------------
   One screen decides the whole plot: the crop chips above, then the seed
   slider and the two option lists, then one total and one button.
   ⚠️ `#farmPlanSummary` is the ONLY part the slider's input handler rewrites,
   and it is a SIBLING of the slider, never an ancestor of it. Rewriting a
   parent of a range input destroys the element mid-drag and the browser loses
   pointer capture — the v0.24 slider bug, hit twice in this codebase already. */
.farm-planner { max-width: 640px; }
.farm-planner > h2 { margin-bottom: 2px; }
.farm-opts { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.farm-opt {
  display: flex; align-items: center; gap: 10px; width: 100%; cursor: pointer; text-align: left;
  padding: 9px 12px; border-radius: var(--radius-sm); font-size: 13px;
  background: var(--panel3); color: var(--ink-dim); border: 1px solid var(--border2); transition: all .1s;
}
.farm-opt:hover { border-color: var(--gold); color: var(--ink); }
.farm-opt.sel { border-color: var(--gold); background: rgba(214,164,72,.14); color: var(--ink); }
.farm-opt.dim { opacity: .55; }
/* ⚠️ min-width: 0 — a flex item defaults to `auto` and refuses to shrink below
   its own text, which is what pushes a row out of a 320px phone. */
.farm-opt-name { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.farm-opt-name small { display: block; font-weight: 400; font-size: 11px; color: var(--ink-faint); }
.farm-opt em { font-style: normal; font-size: 11px; color: var(--gold-2); flex-shrink: 0; }
/* The line that says what the plot was laid out AS, above the three stages. */
.farm-recipe {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  margin: 4px 0 12px; padding: 8px 12px; border-radius: var(--radius-sm);
  background: var(--panel3); border: 1px solid var(--border2); font-size: 13px;
}
.farm-recipe .farm-note { margin: 0; }

/* ---- THE DEATH POPUP (v0.33) ---------------------------------------------
   Three doors out of the resurrection queue. The two paid ones sit side by
   side and collapse to one column on a phone; "wait" is the plain action
   button underneath, because it is the free default and never the emphasis. */
.death-popup { max-width: 560px; }
.death-choices { display: flex; gap: 12px; margin-top: 8px; flex-wrap: wrap; }
/* ⚠️ min-width: 0 on both, or a long price line stops the pair from ever
   wrapping and the popup scrolls sideways inside its own rounded border. */
.death-choice {
  flex: 1 1 220px; min-width: 0; padding: 12px; border-radius: var(--radius-sm);
  background: var(--panel3); border: 1px solid var(--border2);
}
.death-choice-head { font-weight: 700; font-size: 14px; margin-bottom: 4px; }
.death-choice .btn { width: 100%; }
.death-choice .farm-lock { margin-bottom: 0; }

/* The player's own line under the Combat tab's region blurb (v0.24, #11). */
/* (.region-hero is gone — v0.26 removed the hero line from under the zone heading;
   the battle panel keeps its own player blurb, which is where it belongs.) */
/* A drop in the Recent Drops feed is a button into the item inspector (v0.24, #15).
   Mirrors .drops-who so the two clickable columns read the same. */
.drops-drop {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-align: left; width: 100%;
}
.drops-drop:hover .drops-item { text-decoration: underline; }

/* Mailbox tools + the notification toggle (v0.24, #5). */
.mail-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; }
.mail-tool { font-size: 12.5px; padding: 6px 12px; }
.mail-tool.danger { color: var(--red); border-color: rgba(226,92,77,.45); }
.mail-tool[disabled] { opacity: .45; cursor: default; }
.mail-wipe-confirm { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-dim); }
.set-toggle { display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 14px; margin: 4px 0 2px; }
.set-toggle input { width: 18px; height: 18px; accent-color: var(--gold); cursor: pointer; }

/* --- WYN GEMS + the Gemstore (v0.24, #4) --------------------------------- */
.stat.gems { color: var(--gems, #7fd1ff); font-variant-numeric: tabular-nums; }
.q-gems { color: var(--gems, #7fd1ff); font-weight: 700; }
.gem-balance { margin: 0 0 12px; font-size: 15px; color: var(--ink-dim); }
.gem-balance b { color: var(--gems, #7fd1ff); }
.gem-card { text-align: center; }
.gem-card-ico { font-size: 34px; line-height: 1; margin-bottom: 6px; }
.gem-price { margin: 6px 0 10px; font-size: 15px; color: var(--gems, #7fd1ff); }
.gem-soon {
  border: 1px dashed var(--border2); border-radius: var(--radius);
  padding: 22px 18px; text-align: center; color: var(--ink-dim);
}
.gem-soon-title { font-size: 17px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.gem-soon-text { font-size: 13.5px; margin: 0 auto 12px; max-width: 460px; }
.gem-soon-tag {
  display: inline-block; padding: 4px 12px; border-radius: 999px;
  background: var(--panel3); font-size: 12px; color: var(--ink-faint);
}

/* --- The Character page's BEASTS panel (v0.24, #8) ----------------------- */
/* ⚠️ The tiles reserve a real 72px square for art that has not been drawn yet,
   so nothing about this layout changes when the owner's beast pictures land. */
/* ⚠️ NO `margin-top` ANY MORE (v0.25). The panel used to hang off the bottom of
   the page on its own; it now lives inside `.ch-stats-col`, whose flex GAP is
   the spacing. A margin on top of that gap is exactly the double-spacing the
   owner spotted on Idle Activity. */
.ch-beasts-count { color: var(--ink-faint); font-weight: 600; font-size: 14px; }
/* Four tiles across at the panel's usual width, stepping down as it narrows. */
.ch-beasts { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 10px; }
.ch-beasts-all { margin-top: 12px; width: 100%; }
/* The Show All popup can afford wider tiles than the panel it opened from. */
.ch-beasts-full { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.ch-beast {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 10px 6px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--panel3); cursor: pointer; font: inherit; min-width: 0;
}
.ch-beast:hover { border-color: var(--gold); }
.ch-beast-art { width: 72px; height: 72px; display: flex; align-items: center; justify-content: center; }
.ch-beast-ico { font-size: 40px; width: 72px; height: 72px; object-fit: contain; display: flex; align-items: center; justify-content: center; }
.ch-beast-name { font-size: 12.5px; font-weight: 700; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-beast-lvl { font-size: 11px; color: var(--ink-faint); }

/* A refusal from the server inside the sell popup (v0.25). It has to read as an
   ERROR, not as another line of explanatory small print — the whole bug was that
   a failed listing looked like nothing had happened at all. */
.mk-sell-err {
  border: 1px solid var(--red); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--red) 12%, transparent);
  color: var(--ink); padding: 9px 11px;
}

/* --- ACHIEVEMENTS (v0.25) ------------------------------------------------- */
/* The Character page panel: up to three pinned badges, then a button into the
   full list. It shares `.ch-stats-col` with Stats and Beasts, so it needs no
   margin of its own — the column's flex gap owns the spacing. */
.ch-achs { display: flex; flex-direction: column; gap: 8px; }
.ch-ach {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--panel3); min-width: 0;
}
.ch-ach-ico { font-size: 24px; line-height: 1; flex: 0 0 auto; }
.ch-ach-body { display: flex; flex-direction: column; min-width: 0; }
.ch-ach-name { font-size: 13.5px; color: var(--gold-2); }
.ch-ach-desc { font-size: 11.5px; color: var(--ink-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-ach-all { margin-top: 12px; width: 100%; }

/* The full list, inside the popup. */
.ach-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.ach-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--panel3); min-width: 0;
}
/* Earned rows are lifted rather than the unearned ones being dimmed: a long list
   of greyed-out text reads as broken, and most of the list is unearned early on. */
.ach-row.done { border-color: var(--gold); background: var(--panel2); }
.ach-ico { font-size: 22px; line-height: 1; flex: 0 0 auto; width: 28px; text-align: center; }
.ach-main { display: flex; flex-direction: column; gap: 3px; flex: 1 1 auto; min-width: 0; }
.ach-name { font-size: 13.5px; }
.ach-row.done .ach-name { color: var(--gold-2); }
.ach-tick { color: var(--green); }
.ach-desc { font-size: 11.5px; color: var(--ink-faint); }
.ach-bar { height: 6px; }
.ach-prog { font-size: 11px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.ach-pin { flex: 0 0 auto; font-size: 12px; padding: 5px 9px; }
.ach-pin.on { border-color: var(--gold); color: var(--gold-2); }

/* --- The Wyn Gem icon, drawn like the gold coin (v0.24, #3) --------------- */
.gem-ico { width: 16px; height: 16px; object-fit: contain; vertical-align: -3px; image-rendering: pixelated; }
.gem-ico-lg { width: 26px; height: 26px; vertical-align: -5px; }
.gem-ico-nav { width: 18px; height: 18px; vertical-align: -4px; }

/* --- ⚠️ ART IS NEVER STRETCHED (v0.24, owner's item #2) -------------------
   Not every picture is square. Staffs are ~21-40px wide by 128 tall, bows ~36-40
   by 128, and the spell codices are 128 by 94. Several call sites pass their own
   class to `artTag` (`.mk-head-art` was the one the owner caught), and those
   classes set a square width and height with no `object-fit` — so the browser's
   default `fill` squashed a tall thin staff into a square blob.
   `artTag` ALWAYS emits `data-ico` on its <img>, so this one selector covers
   every art image in the game, including any class added later. Do not rely on
   remembering to put `object-fit` on each new class. */
img[data-ico] { object-fit: contain; }

/* TAP-TIPS (v0.28) - a `title` made readable on a touch screen. Hold any titled
   element (every XP bar carries its numbers in one) and this bubble appears.
   ⚠️ `pointer-events: none` is not decoration: the bubble is positioned under the
   finger that summoned it, and without this it would swallow the touchend that is
   meant to dismiss it. */
.tap-tip {
  position: fixed; z-index: 200; pointer-events: none;
  max-width: 70vw; padding: 7px 11px; border-radius: var(--radius-sm);
  background: var(--panel2); border: 1px solid var(--border2); color: var(--ink);
  font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
}

/* A scaling weapon's "what am I right now" note (v0.24, #1).
   ⚠️⚠️ `display: block` IS THE LOAD-BEARING LINE, and it was missing until v0.28.
   `.popup-meta div` is `display: flex; justify-content: space-between` and that
   selector catches EVERY div inside the popup, so this note's `<b>` tags became
   flex items and the text around them became separate ones - the owner sent a
   screenshot of Excalibur's note laid out as three ragged two-word columns.
   ⚠️ AND IT NEEDS TWO CLASSES TO WIN. `.popup-meta div` is (0,1,1) and a bare
   `.scale-note` is only (0,1,0), so the first attempt at this fix changed nothing
   at all. `.item-desc` above is written as `.popup-meta .item-desc` for exactly
   that reason. **Any new block of prose inside `.popup-meta` needs the same.**
   (The `.scale-now` / `.scale-rule` rules below are already two classes, so those
   win on their own.) */
.scale-note, .popup-meta .scale-note {
  display: block; margin-top: 8px; padding: 9px 11px; border-radius: var(--radius-sm);
  background: var(--panel3); color: var(--ink-dim); font-size: 12.5px; line-height: 1.5;
}
/* The headline (what the weapon IS) reads first and brighter. The rules under it
   are reference, so they stay quiet and small. */
.scale-note .scale-now { display: block; color: var(--ink); font-size: 13px; }
.scale-note .scale-rule { display: block; margin-top: 5px; }
.scale-note b { font-weight: 600; }

/* ===========================================================================
   PREVIEW OTHER DUNGEONS (v0.27) — a compact picker under the drops feed.
   It reuses `.drops-feed` for its surface and heading so the two panels in that
   column read as one thing. Everything here is deliberately SMALL: rows, not
   cards, and no art (the owner asked for "not huge, no eye-sore").
   ========================================================================== */
.dpv-panel { margin-top: 14px; }
.region-cols .dpv-panel { margin-top: 14px; }
.dpv-select {
  width: 100%; margin-bottom: 10px; padding: 7px 9px;
  background: var(--panel2); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font: inherit; font-size: 13px;
}
.dpv-list { display: flex; flex-direction: column; gap: 4px; }
.dpv-row {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 9px; text-align: left; cursor: pointer;
  background: var(--panel2); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.dpv-row:hover { border-color: var(--border2); }
.dpv-ico { font-size: 16px; flex: 0 0 auto; }
/* min-width: 0 or a long dungeon name refuses to shrink and pushes the stat
   requirement out of the row (the same family as #body / #main). */
.dpv-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dpv-name {
  font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dpv-meta { font-size: 11.5px; color: var(--ink-faint); }
.dpv-meta .gold-ico.dpv-coin { width: 11px; height: 11px; vertical-align: -1px; }
.dpv-req {
  flex: 0 0 auto; font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--ink-faint);
}
.dpv-req.ok { color: var(--green); }
.dpv-note { margin-top: 9px; font-size: 11.5px; color: var(--ink-faint); line-height: 1.45; }

/* --- THE GUILD TAB (v0.27) ------------------------------------------------
   Deliberately built from panels and rows the rest of the game already uses
   (.panel, .popup-actions, .ab-prog, .ch-lookup) rather than a second styling
   language. Only the roster grid and the rank chips are new.
   ⚠️ .guild-bio-text is PLAIN TEXT on purpose. It holds player-written Discord
   links and must never become an anchor - see the note in ui.js. `word-break`
   is what stops a long pasted URL widening the panel and scrolling the app
   sideways (the min-width: 0 family). */
/* ===== THE GUILD TAB (redesigned v0.27, the owner's brief) =================
 * ⚠️ THE OLD LAYOUT WAS ONE COLUMN OF FULL-WIDTH PANELS, and on a wide monitor
 * every one of them stretched the whole way across. A four-word task sat on a
 * 1,600px row with its progress bar reaching the far wall. The owner's words:
 * "almost every UI element is stretched for some reason."
 * The fix is TWO CAPPED COLUMNS. Nothing on this tab wants the full width, so
 * nothing gets it - `.guild-cols` caps the whole thing and each column caps
 * itself, which is what stops a 4K monitor from stretching a roster row.
 * ⚠️ DO NOT put a panel back outside `.guild-cols`. That is what "full width"
 * means here, and it is the bug. */
/* WARNING - IT IS A GRID, NOT TWO STACKED COLUMNS, and that is what makes paired
 * panels LINE UP. Two independent flex columns let each panel sit directly under
 * the one above it, so Guild Resources and Today's tasks started at different
 * heights purely because the identity panel and About are different lengths. A
 * grid row is as tall as its tallest cell, so the pairs share a top edge.
 * Each panel pins its own COLUMN, so a missing one (no invite rights, say) can
 * never shunt the rest into the wrong side. */
/* WARNING - COLUMN A IS 360px BECAUSE `.ch-info` IS (v0.27.2). It was 400, so the
 * identity panel's rows stopped 40px short of the Roster's rows underneath them
 * and the two blocks visibly did not share a right edge - the owner's report.
 * `.ch-info` is shared with the Character page and caps itself at 360, so the
 * COLUMN moved to meet it rather than the panel being widened. Everything in
 * column A (identity, roster, resources, the settings button) now ends on the
 * same line, and the 40px went into the gutter, which the owner also asked to
 * widen. ⚠️ Change this and `.ch-info`'s max-width together or the two blocks
 * drift apart again. */
.guild-cols { display: grid; gap: 14px 26px; align-items: start;
  grid-template-columns: minmax(0, 360px) minmax(0, 560px); max-width: 1000px; }
.gg-a { grid-column: 1; min-width: 0; }
.gg-b { grid-column: 2; min-width: 0; }
.guild-cols .guild-panel { margin-bottom: 0; }
@media (max-width: 900px) {
  .guild-cols { grid-template-columns: minmax(0, 1fr); }
  .gg-a, .gg-b { grid-column: 1; }
}

/* ⚠️ ONE SELECT STYLE FOR THE WHOLE GAME. The guild dropdowns and the raid time
 * box were browser-default WHITE against a dark page, because select styling had
 * been written per-component (`.lb-filter select`, `.dpv-select`) and nobody had
 * written one for guilds. A shared class is the fix rather than a third copy.
 * ⚠️ `color-scheme: dark` is what makes the native TIME picker (and a select's
 * own dropdown list) render dark - colours alone do not reach either. */
.ui-select, .ui-time {
  background: var(--panel2); color: var(--ink); font: inherit; font-size: 13px;
  border: 1px solid var(--border2); border-radius: var(--radius-sm);
  padding: 8px 10px; outline: none; cursor: pointer; color-scheme: dark;
  min-width: 0; max-width: 100%; box-sizing: border-box;
}
.ui-select:focus, .ui-time:focus { border-color: var(--gold); }
.ui-time { cursor: text; font-variant-numeric: tabular-nums; }

/* The guild identity block, mirroring the Character page's info panel so the two
   read as the same kind of thing. */
.guild-id-name { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 10px; }
.guild-id-name h2 { margin: 0; font-size: 17px; }
/* WARNING - THE XP BAR ENDS WHERE THE NUMBERS ABOVE IT DO. It ran the full width
 * of the panel while the rows above stopped at `.ch-info`'s 360px, so the bar
 * visibly overshot the block it belongs to. Same cap, and the figure sits INSIDE
 * the bar (`.ab-prog-label`, the same treatment the active-task bar uses) rather
 * than on a line of its own underneath. */
.guild-xp { margin-top: 12px; max-width: 360px; }

/* ⚠️ THE THREE GATHERING SKILLS STACK, they do not sit in a row (owner's brief).
   Three side-by-side columns each held one short line and wasted the width they
   were given. */
.guild-res-cols { display: flex; flex-direction: column; gap: 8px; }

/* A roster row is a table, and a table on a 1,600px page puts a name at the far
   left and a timestamp at the far right with a lake between them. Capped. */
/* The settings button stands alone in its cell - no panel, no heading, no blurb.
   Its label already says what it opens (the owner's brief). */
.guild-settings-btn { width: 100%; justify-content: center; }

/* The settings popup replaced the old always-visible "Danger zone" panel, which
   put two irreversible buttons on the page at all times. */
.guild-set-row { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.guild-set-row:last-of-type { margin-bottom: 0; }
.guild-set-label { font-weight: 600; }

/* ⚠️ THE GUILD PANELS HAD NO SURFACE AT ALL UNTIL v0.28, and the owner asked why
   the guild info block was "black". They carry a `panel` class in the markup and
   **there is no `.panel` rule in this stylesheet** - it has never done anything.
   So every guild panel was transparent, and what showed through was the page
   background. They take the Character page's card treatment now, which is what
   the identity panel was drawn to echo in the first place.
   ⚠️ The values are copied from `.ch-panel` deliberately rather than shared: the
   two are different components that happen to agree, and a guild panel that
   silently changed shape because the Character page was retuned would be worse
   than a little duplication. Change them together if you change either. */
.guild-panel {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; margin-bottom: 14px;
}
.guild-panel h2 { margin: 0 0 12px; font-size: 15px; letter-spacing: .3px; }

/* ---- Viewing a guild you are NOT in (v0.28) --------------------------------
   It reuses `.guild-cols` and every panel the members' page uses, so the two
   read as the same place. The only additions are the way back and the apply
   row. */
/* The forging upgrades a scaling weapon loses when it grows a tier (v0.28). Red,
   because it IS a loss, sitting in a popup that is otherwise good news. */
.wup-lost { color: var(--red); font-weight: 600; }
/* ---- A raid’s loot, and the raid about to be scheduled ---------------------
   ⚠️ REWRITTEN IN v0.29 (the owner's item #8, with a screenshot). v0.28 drew the
   drops as inline CHIPS on one wrapping line, on the reasoning that a guild can
   have two raids up and nine to choose from and the panel must not become a wall
   of prices. In practice the chips wrapped INSIDE a narrow flex cell next to the
   toggle, so the raid's own title was crushed to "The Br..." and the loot read as
   a run-on sentence. It uses the DUNGEON's `.drop-row` now - the same list, for
   the same question - and the whole block sits on its own full-width line.
   The list is still behind a toggle, so the panel stays quiet until asked. */
/* Back and Inspect share the market item page's top line (v0.29). */
.mk-head-acts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* A one-line explanation under an Admin sub-heading (v0.29). */
.adm-note { font-size: 12px; color: var(--ink-faint); margin: 0 0 8px; line-height: 1.5; }
.guild-raid-lootwrap { margin-top: 8px; }
.guild-raid-loot { margin-top: 8px; }
/* ⚠️ The rows are `.drop-row`, which is styled with the dungeon list. Only the
   things specific to being INSIDE a guild panel belong here. */
.guild-raid-loot .drop-row { padding: 5px 8px; border-radius: var(--radius-sm); }
.guild-raid-loot .drop-row:hover { background: rgba(255, 255, 255, .04); }
.guild-raid-loot-note { margin: 6px 0 0; opacity: .8; }
.guild-raid-loot-btn { padding-left: 8px; padding-right: 8px; }
.guild-raid-loot-btn.on { border-color: var(--gold); }
/* The raid the picker is currently on. Same row shape as a facility, because it
   is the same kind of thing: an icon, what it is, and one control. */
/* ⚠️ TWO COLUMNS, NOT THREE (v0.29). The third held the Loot button, and that
   `auto` track took its width from the OPEN LIST inside it - which is what
   starved the title. The button moved out to its own line below. */
.guild-raid-pick {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
  gap: 10px; padding: 9px; border-radius: var(--radius-sm);
  background: var(--panel2); margin: 10px 0 4px; min-width: 0;
}
.guild-raid-pick-ico { font-size: 22px; line-height: 1; }
.guild-raid-pick-body { min-width: 0; }
.guild-view-top { margin-bottom: 12px; }
/* The button and its explanation sit on one line while there is room and wrap on
   a phone, rather than the sentence being squeezed to two words. */
.guild-apply-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }

/* ---- Applications (v0.28) --------------------------------------------------
   The same three-part shape as a roster row - who, then what you can do about
   them - so the two panels scan the same way. The buttons are `auto` here rather
   than a fixed track because there are two of them and their labels are fixed. */
.guild-app-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: 8px; padding: 7px 8px; border-radius: var(--radius-sm);
  background: var(--panel2); margin-bottom: 4px; min-width: 0;
}
.guild-app-acts { display: flex; gap: 6px; }

/* ---- Guild facilities (v0.28) ----------------------------------------------
   Deliberately the same row shape as a guild RESOURCE, because they are the same
   kind of thing: something the guild spends tokens on and then loses to time.
   ⚠️ A built one is marked by a green left edge rather than by a colour change,
   so the four rows stay the same height whether or not anything is standing -
   the panel must not jump about as buildings come and go. */
.guild-fac {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: 10px; padding: 8px; border-radius: var(--radius-sm);
  background: var(--panel2); margin-bottom: 4px; min-width: 0;
  border-left: 3px solid transparent;
}
.guild-fac-live { border-left-color: var(--green); }
.guild-fac-ico { font-size: 20px; line-height: 1; }
.guild-fac-body { min-width: 0; }
.guild-fac-acts { display: flex; justify-content: flex-end; }
.guild-fac-acts .btn { white-space: nowrap; }
.guild-head { display: flex; flex-direction: column; gap: 10px; }
.guild-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.guild-title h2 { margin: 0; }
.guild-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px 16px; }
.guild-stats > div { display: flex; justify-content: space-between; gap: 8px; }
.guild-rank { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
  border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap;
  background: var(--panel2); border: 1px solid var(--border); }
.guild-rank-t4 { color: var(--gold); border-color: var(--gold); }
.guild-rank-t3 { color: var(--st-melee); }
.guild-rank-t2 { color: var(--ink); }
.guild-rank-t1 { color: var(--ink-dim); }
.guild-roster { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* WARNING - FIXED TRACKS, NOT `auto`. Every `.guild-row` is its own grid, so an
   `auto` track is sized by THAT row's content and a long rank chip knocked its
   own row out of line with the others - which is exactly what the owner spotted.
   Fixed widths are what make separate grids agree.
   Three columns because the roster lives in the 400px column now: name (with
   total level and last seen beneath it), rank, and one Manage button. */
.guild-row { display: grid; grid-template-columns: minmax(0, 1fr) 104px 78px;
  align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius-sm);
  background: var(--panel2); min-width: 0; }
.guild-row-head { background: none; color: var(--ink-dim); font-size: 12px; font-weight: 600; padding-bottom: 0; }
.guild-row-you { outline: 1px solid var(--gold); }
.guild-mem-name { background: none; border: none; padding: 0; color: var(--gold);
  font: inherit; font-weight: 600; cursor: pointer; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guild-mem-lvl, .guild-mem-seen { color: var(--ink-dim); font-size: 12px; }
.guild-mem-cell { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.guild-mem-meta { color: var(--ink-faint); font-size: 11px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
/* WARNING - THE PADDING IS CUT, AND THAT IS THE WHOLE FIX (v0.27.2). `.btn`
   carries `padding: 8px 18px`, which leaves 42px of content box inside a 78px
   track - narrower than the word "Manage". The label therefore overflowed and
   `text-align: center` had nothing left to centre it in, so it sat visibly off
   to one side. `justify-content` was already here and did nothing, because a
   `.btn` is a BLOCK: it needs `display: flex` to take effect. */
.guild-row-acts .btn { width: 100%; display: flex; align-items: center;
  justify-content: center; padding-left: 8px; padding-right: 8px; }
.guild-row-head .guild-mem-cell { flex-direction: row; }
.guild-row-acts { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
/* The Manage popup replaced a row of promote / demote / remove buttons that made
   every roster line a different width. One button, one popup, one column. */
.guild-manage-who { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.guild-manage-sec { margin-top: 14px; }
.guild-manage-sec h4 { margin: 0 0 6px; font-size: 12px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); font-weight: 700; }
.guild-manage-ranks { display: flex; flex-direction: column; gap: 6px; }
.guild-manage-ranks .btn { justify-content: flex-start; text-align: left; }
.guild-bio-text { white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
  background: var(--panel2); border-radius: var(--radius-sm); padding: 10px; margin-bottom: 8px; }
.guild-bio-box { width: 100%; min-height: 110px; resize: vertical; box-sizing: border-box; }
.guild-invite { background: var(--panel2); border-radius: var(--radius-sm); padding: 10px; margin-bottom: 8px; }
.guild-ok { background: var(--panel2); border-left: 3px solid var(--green);
  border-radius: var(--radius-sm); padding: 8px 12px; margin-bottom: 12px; }
/* The roster is five columns of small text; below the phone breakpoint it becomes
   two rows so nothing is squeezed to nothing (the v0.13 active-bar lesson). */
@media (max-width: 820px) {
  .guild-row { grid-template-columns: minmax(0, 1fr) auto; grid-auto-rows: auto; }
  .guild-row-head { display: none; }
  .guild-mem-lvl, .guild-mem-seen { grid-column: 1 / -1; }
  .guild-row-acts { grid-column: 1 / -1; justify-content: flex-start; }
}

/* --- The Guild tab's task + resource panels (v0.27) -----------------------
   Rows, not cards: at guild level 100 there are ten tasks and ten cards is a
   wall. One line of text, one slim bar, one number, one button. */
.guild-sub { color: var(--ink-dim); font-size: 12px; margin-bottom: 10px; }
.guild-task { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center; gap: 10px; padding: 7px 8px; border-radius: var(--radius-sm);
  background: var(--panel2); margin-bottom: 4px; min-width: 0; }
.guild-task-done { opacity: 0.55; }
.guild-task-ico { font-size: 16px; }
.guild-task-body { min-width: 0; }
.guild-task-line { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guild-task-bar { height: 4px; margin-top: 5px; }
.guild-task-tick { color: var(--green); font-size: 11px; font-weight: 600; }
.guild-task-num { color: var(--ink-dim); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.guild-task-acts { display: flex; gap: 4px; }
/* (superseded by the stacked rule above - kept out of the way) */
.guild-res-col { background: var(--panel2); border-radius: var(--radius-sm); padding: 8px; min-width: 0; }
.guild-res-head { font-size: 12px; font-weight: 600; margin-bottom: 6px; color: var(--ink-dim); }
/* WARNING - `.res-ico` HAD NO RULE AT ALL, so a 128px source PNG rendered at 128px
 * and one ore filled the panel. Every other art helper in the game sizes its own
 * icon; this one was simply missed. `object-fit: contain` because not every source
 * is square (the v0.24 `img[data-ico]` lesson). */
.res-ico { width: 26px; height: 26px; flex-shrink: 0; object-fit: contain;
  image-rendering: pixelated; }
.guild-res { display: flex; align-items: center; gap: 9px; padding: 5px 0; min-width: 0; }
.guild-res-body { min-width: 0; overflow: hidden; }
.guild-res-empty { padding: 4px 0; }
/* WARNING - THE WHOLE RAID BLOCK HAD NO CSS. Its name, time, odds and Sign up
 * button were four inline children of a bare div, so they sat shoulder to
 * shoulder with the bar and the summary line jammed underneath. The owner's
 * words: "way too close and looks very awkward".
 * The row is a grid now - name takes the slack, the meta sits right, the button
 * is pinned to the end at a fixed width so it lines up down the list however long
 * a raid's name is. */
.guild-raid { background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 12px 14px; margin-bottom: 10px; }
.guild-raid:last-of-type { margin-bottom: 14px; }
.guild-raid-top { display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 10px; margin-bottom: 4px; }
.guild-raid-name { min-width: 0; }
.guild-raid-title { font-weight: 700; font-size: 14px; display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guild-raid-when { color: var(--ink-dim); font-size: 12px; }
/* A raid that is being fought right now: neither Sign up nor Withdraw applies,
   so the button's slot carries a status word instead (v0.31). */
.guild-raid-live { font-size: 12px; font-weight: 600; color: var(--st-melee); white-space: nowrap; }
/* "N signed up" opens the muster roll (v0.32). It has to read as pressable
   inside a line of plain summary text, so it is underlined rather than boxed. */
.guild-muster-btn {
  background: none; border: 0; padding: 0; font: inherit; color: var(--gold);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.guild-muster-btn:hover, .guild-muster-btn:focus-visible { color: var(--ink); }
/* ⚠️ `width`, NOT just `max-width`. `.popup` sets a FIXED 320px, so raising
   only the maximum leaves it at 320 and the name column collapses to nothing. */
.muster-popup { width: 440px; max-width: 92vw; }
.muster-head, .muster-row {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto 64px;
  gap: 8px; align-items: center; padding: 5px 6px;
}
.muster-head { font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--ink-faint); }
.muster-list { max-height: 320px; overflow-y: auto; }
.muster-row { border-top: 1px solid var(--border); font-size: 13px; }
.muster-row.you { background: rgba(255,255,255,.04); border-radius: 6px; }
.muster-rank { color: var(--ink-faint); font-size: 11px; text-align: right; }
.muster-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.muster-you { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; color: var(--gold); }
.muster-style { font-size: 12px; color: var(--ink-faint); white-space: nowrap; }
.muster-contrib { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
/* The Beast Compendium (v0.32): every beast as a picture grid, and one beast
   inspected with its level 1 and level 100 stat block side by side. */
.cmp-popup { width: 560px; max-width: 92vw; }
.cmp-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px; max-height: 46vh; overflow-y: auto; padding: 2px;
}
.cmp-cell {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 4px; border-radius: 10px; cursor: pointer;
  background: var(--panel3); border: 1px solid var(--border);
}
.cmp-cell:hover, .cmp-cell:focus-visible { border-color: var(--gold); transform: translateY(-1px); }
.cmp-ico { width: 56px; height: 56px; object-fit: contain; font-size: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  image-rendering: pixelated; image-rendering: crisp-edges; }
.cmp-name { font-size: 11px; text-align: center; line-height: 1.2; }
/* A beast the player already owns. A quiet tick, not a loud badge - the grid is
   a reference first and a collection tracker second. */
.cmp-have { box-shadow: inset 0 0 0 1px var(--gold); }
/* ⚠️ THE CHECKMARK NEEDS ITS OWN FILL COLOUR (v0.34). `.cmp-name` now carries a
   `q-*` class, and the legendary and mythic ones paint text with a clipped gradient
   plus `-webkit-text-fill-color: transparent` - which a ::after inherits, so the tick
   on an owned mythic beast went invisible. Set the fill, not just the colour. */
.cmp-have .cmp-name::after {
  content: " \2713"; color: var(--gold); -webkit-text-fill-color: var(--gold);
}
.cmp-back { margin-bottom: 8px; }
.cmp-detail-top { display: flex; gap: 12px; align-items: center; margin-bottom: 10px; }
.cmp-detail-ico { width: 84px; height: 84px; object-fit: contain; font-size: 52px;
  display: inline-flex; align-items: center; justify-content: center;
  image-rendering: pixelated; image-rendering: crisp-edges; flex: 0 0 auto; }
.cmp-detail-name { font-size: 18px; font-weight: 700; }
.cmp-detail-sub { font-size: 12px; color: var(--ink-faint); }
.cmp-owned { color: var(--gold); }
.cmp-stats { border-top: 1px solid var(--border); }
.cmp-srow {
  display: grid; grid-template-columns: minmax(0, 1fr) 78px 78px; gap: 8px;
  padding: 5px 4px; border-bottom: 1px solid var(--border); font-size: 13px;
}
.cmp-srow span:nth-child(n+2) { text-align: right; font-variant-numeric: tabular-nums; }
.cmp-shead { font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--ink-faint); }
.cmp-srow span:nth-child(3) { color: var(--gold); font-weight: 600; }
.cmp-hp span:nth-child(2), .cmp-hp span:nth-child(3) { font-weight: 700; }
.beast-compendium-btn { margin-left: auto; }

/* The character switcher warns when the OTHER character's idle has finished
   (v0.32). A small badge on the corner of the button, in the game's warning
   colour rather than the gold accent, so it reads as "look at this" and not as
   another thing to press. */
.switch-btn { position: relative; }
.switch-warn {
  position: absolute; top: -4px; right: -4px;
  min-width: 15px; height: 15px; padding: 0 3px; border-radius: 999px;
  background: var(--gold); color: #1a1611;
  font-size: 10px; font-weight: 800; line-height: 15px; text-align: center;
  box-shadow: 0 0 0 2px var(--panel2);
}
.switch-btn.switch-idle { border-color: var(--gold); }

/* The Guild Leaderboard (v0.32). Deliberately shaped like `.lb-table` on the
   player board - same medals, same "you" tint - so one teaches the other. */
/* ⚠️ Same rule as `.muster-popup`: `.popup` is a fixed 320px wide. */
.glb-popup { width: 560px; max-width: 92vw; }
.glb-meta { font-size: 12px; color: var(--ink-faint); margin: 4px 0 8px; }
.glb-table { max-height: 46vh; overflow-y: auto; }
.glb-head, .glb-row {
  display: grid; grid-template-columns: 40px minmax(0, 1fr) 62px 92px 72px;
  gap: 8px; align-items: center; padding: 6px 6px;
}
.glb-head { font-size: 10px; letter-spacing: .5px; text-transform: uppercase; color: var(--ink-faint); }
.glb-row { border-top: 1px solid var(--border); font-size: 13px; }
.glb-row.glb-you { background: rgba(255,255,255,.05); border-radius: 6px; }
.glb-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.glb-row span:nth-child(n+3), .glb-head span:nth-child(n+3) {
  text-align: right; font-variant-numeric: tabular-nums;
}
/* The column being ranked by. The filter changes what MATTERS, not what shows. */
.glb-on { color: var(--gold); font-weight: 700; }
.glb-head .glb-on { color: var(--gold); }
@media (max-width: 560px) {
  /* Tokens is the first thing to go on a phone: it is the least compared of the
     three and the row must not scroll sideways. */
  .glb-head, .glb-row { grid-template-columns: 32px minmax(0, 1fr) 52px 76px; }
  .glb-head span:nth-child(5), .glb-row span:nth-child(5) { display: none; }
}
/* A beast battle report is a RECEIPT: the loot was banked when the battle ended,
   which may have been hours ago and on another device (v0.31). */
.beast-result-when { font-size: 12px; color: var(--ink-faint); font-style: italic; margin: 6px 0 2px; }
.guild-raid-right { display: flex; align-items: center; gap: 10px; }
.guild-raid-odds { font-weight: 700; color: var(--gold-2); font-variant-numeric: tabular-nums;
  min-width: 42px; text-align: right; }
.guild-raid .guild-raid-btn { flex: 0 0 auto; min-width: 84px; }
.guild-raid .guild-task-bar { margin: 10px 0 8px; }
.guild-raid-meta { color: var(--ink-dim); font-size: 12px; line-height: 1.6; }
.guild-raid-lvl { font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--ink-faint); border: 1px solid var(--border2); border-radius: 999px;
  padding: 1px 7px; margin-left: 6px; white-space: nowrap; }

/* The scheduling / spawning row. Given real breathing room above it, because it
   is a different kind of thing from the list it follows. */
.guild-spawn { display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  padding-top: 12px; border-top: 1px solid var(--border); margin-top: 4px; }
.guild-spawn { flex-wrap: wrap; }
.guild-spawn .ui-select { flex: 1 1 100%; }
.guild-spawn .ui-time { flex: 0 0 auto; }
/* WARNING - `.btn` CARRIES `margin-top: 10px`, AND A FLEX LINE HONOURS IT. That
   is why Schedule sat visibly below the time box it belongs beside: `align-items:
   center` centres the MARGIN box, so half the 10px still showed as an offset.
   The heights are pinned too - a select, a time input and a button each compute a
   slightly different intrinsic height from the same font, and three controls in a
   row want one line, not three near-misses. */
.guild-spawn .btn { margin: 0; }
.guild-spawn .ui-select, .guild-spawn .ui-time, .guild-spawn .btn { height: 36px; }
/* The replace disclaimer. Loud on purpose: it is the one place a guild can waste
   tokens by pressing a button they meant well by. */
.guild-warn { background: var(--panel2); border-left: 3px solid var(--red);
  border-radius: var(--radius-sm); padding: 8px 12px; margin: 10px 0 0; font-size: 13px; }
@media (max-width: 820px) {
  .guild-task { grid-template-columns: auto minmax(0, 1fr) auto; }
  .guild-task-acts { grid-column: 1 / -1; justify-content: flex-end; }
}


/* ---- THE ORDERS POPUP (v0.35) --------------------------------------------
   Opened from the People tab. The cards are their own grid so four Orders read
   as four choices rather than one long list.
   NOTE the popup is capped and scrolls INSIDE itself, like every other tall
   popup - the height cap and the safe-area padding are what stop a tall popup
   breaking the phone layout, and they must not be removed to "fit more in". */
/* ⚠️⚠️ THE POPUP NEEDS ITS OWN WIDTH OR THE CARDS CANNOT SIT SIDE BY SIDE.
   `.popup` is a fixed 320px, so a `minmax(260px, 1fr)` grid inside it can only ever
   fit ONE column and all four Orders stack into a column taller than a desktop
   screen. The two other wide popups (`.battle-setup`, `.combat-result`) set their
   own width the same way.
   ⚠️ It scrolls INSIDE itself at 88vh, like those two, and `max-width: 94vw` plus
   the auto-fit grid collapse it to one column on a phone. */
.popup.popup-orders { width: 1120px; max-width: 96vw; max-height: 88vh; overflow-y: auto; padding: 16px; }
/* ⚠️ FOUR EXPLICIT COLUMNS, NOT `auto-fit`. With auto-fit the fourth Order wrapped
   onto a second row the moment the window was anything less than very wide, which is
   the exact complaint - four Orders read as four choices only when they are one row.
   `minmax(0, 1fr)` rather than `1fr` so a long perk name cannot widen its own column
   past its share (the `min-width: auto` family). It steps down at real breakpoints
   below, so a phone still gets one readable column. */
.ord-grid { display: grid; gap: 10px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1120px) { .ord-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .ord-grid { grid-template-columns: minmax(0, 1fr); } }
.ord-card { background: var(--panel2); border: 1px solid var(--border); border-radius: var(--radius);
            padding: 10px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.ord-head { display: flex; align-items: center; gap: 10px; }
.ord-ico { font-size: 26px; line-height: 1; }
.ord-name { font-weight: 700; }
.ord-rank { font-size: 12px; color: var(--ink-dim); }
.ord-favor { color: var(--ink-faint); }
.ord-bar { height: 6px; }
.ord-perks { display: flex; flex-direction: column; gap: 6px; }
.ord-perk { display: grid; grid-template-columns: 34px 1fr; gap: 2px 8px; align-items: baseline;
            font-size: 12px; color: var(--ink-faint); }
.ord-perk-at { font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.ord-perk-name { font-weight: 600; color: var(--ink-dim); }
.ord-perk-desc { grid-column: 2; }
.ord-perk-got { grid-column: 2; color: var(--green); font-weight: 600; }
.ord-perk-on .ord-perk-at, .ord-perk-on .ord-perk-name { color: var(--green); }
.ord-tasks { display: flex; flex-direction: column; gap: 8px; }
.ord-task { display: flex; gap: 8px; align-items: flex-start; justify-content: space-between;
            border-top: 1px solid var(--border); padding-top: 8px; }
/* min-width: 0 so a long task name cannot push the button out of the card. */
.ord-task-main { min-width: 0; }
.ord-task-name { font-weight: 600; font-size: 13px; }
.ord-task-pay { color: var(--green); font-weight: 700; }
.ord-task-desc, .ord-task-cost { font-size: 11px; color: var(--ink-faint); }
.ord-task-cost { margin-top: 2px; }
.ord-cost { white-space: nowrap; }
.ord-short { color: var(--red); }
.ord-task-act { flex: 0 0 auto; text-align: right; }
.ord-task-act .btn { margin-top: 0; }
/* The refusal is VISIBLE text, never a title: a phone has no hover and tap-tips
   do not fire on a disabled button (the v0.29 quiver lesson). */
.ord-why { font-size: 11px; color: var(--red); max-width: 130px; margin-top: 2px; }

/* ---- AN NPC'S ORDER, AS A THIN OUTLINE (v0.35) ----------------------------
   The owner's call: a marker on every NPC who belongs to an Order. It is the
   CARD's own border rather than an extra chip, so it costs no layout and cannot
   push a name out of line.
   NOTE the colours are the owner's for three of the four (blue wizard, green
   ranger, red knight) and GOLD for the Merchant, which they did not name - gold
   is the game's own accent and the one colour a merchant reads as. */
.npc-card.npc-order-wizard   { border-color: #4f8ff7; }
.npc-card.npc-order-ranger   { border-color: #58c96b; }
.npc-card.npc-order-knight   { border-color: #e25c4d; }
.npc-card.npc-order-merchant { border-color: #eab543; }
.npc-card[class*="npc-order-"]:hover { border-width: 1px; }
.npc-order-tag { font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.npc-order-wizard   .npc-order-tag { color: #4f8ff7; }
.npc-order-ranger   .npc-order-tag { color: #58c96b; }
.npc-order-knight   .npc-order-tag { color: #e25c4d; }
.npc-order-merchant .npc-order-tag { color: #eab543; }
.ord-card-barred { opacity: .72; }
.ord-card-barred .ord-why { max-width: none; color: var(--ink-faint); }
