/* Scapelike Idle — OSRS-flavored UI. Palette: dark brown panels #3e3529/#494034,
   parchment #d8ccb4, gold #ff981f, hitsplat red #c02316. */

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

:root {
  /* Topbar (~68px since v3.50.0, when the nav gained icons above its labels)
   * + footer (~52px) + the layout's 14px padding top and bottom.
   * Only used to bound the centre column against the viewport. */
  --page-chrome: 150px;
  --panel: #3e3529;
  --panel2: #494034;
  --edge: #5a4e3c;
  --edge-dark: #2b251c;
  --parchment: #d8ccb4;
  --gold: #ff981f;
  --red: #c02316;
  --green: #35b839;
  --purple: #b44cff;
  --dim: #9a8f7a;
}

html, body { height: 100%; }
body {
  font-family: 'VT323', monospace;
  font-size: 19px;
  background: #262119;
  background-image: radial-gradient(1200px 500px at 50% -150px, #383024, #262119 70%);
  background-attachment: fixed;
  color: var(--parchment);
  letter-spacing: 0.3px;
}

img { max-width: 100%; image-rendering: pixelated; }
a { color: var(--gold); text-decoration: none; }
a:hover { text-decoration: underline; }
button {
  font-family: inherit; font-size: 17px; cursor: pointer;
  background: var(--panel2); color: var(--parchment);
  border: 2px solid var(--edge); padding: 4px 12px;
}
button:hover { border-color: var(--gold); color: var(--gold); }
/* A link that does a button's job (a download), dressed as one. */
a.button-link {
  display: inline-block; font-size: 17px; margin: 4px 0 8px;
  background: var(--panel2); color: var(--parchment);
  border: 2px solid var(--edge); padding: 4px 12px;
}
a.button-link:hover { border-color: var(--gold); color: var(--gold); text-decoration: none; }
#app-panel h3 { font-size: 19px; color: #f2e6cc; font-weight: normal; margin: 12px 0 4px; }
.app-steps { margin: 4px 0 8px; padding-left: 24px; }
.app-steps li { margin: 2px 0; }
input {
  font-family: inherit; font-size: 18px;
  background: #241f17; color: var(--parchment);
  border: 2px solid var(--edge); padding: 6px 8px; width: 100%;
}
input:focus { outline: none; border-color: var(--gold); }
input[type=color] { width: 44px; height: 32px; padding: 2px; vertical-align: middle; }

/* ------------------------------------------------------------- top bar --- */
.topbar {
  display: flex; align-items: center; gap: 24px;
  background: linear-gradient(#4a4034, #3e3529);
  border-bottom: 3px solid var(--edge-dark);
  box-shadow: 0 2px 0 var(--edge) inset;
  padding: 8px 18px;
}
.logo { font-size: 26px; color: var(--gold); text-shadow: 2px 2px 0 #000; }
.topbar nav { display: flex; gap: 18px; font-size: 20px; }
.topbar nav a.here { color: #fff; border-bottom: 2px solid var(--gold); }
/*
 * v3.50.0: the game's tab strip — every tab the same size, each with the
 * client's own icon above its name. `flex: 1 1 0` (a ZERO basis, not `auto`)
 * is what makes them equal: with `auto` each tab's base size would be its own
 * label, so "Game" would end up a third the width of "Collection Log" and the
 * strip would look hand-set. The zero basis throws the labels away and shares
 * the free space out in equal parts instead; `max-width` then stops nine tabs
 * from sprawling across an ultrawide monitor.
 *
 * `align-items: stretch` (the flex default, left alone deliberately) keeps the
 * heights equal too, so a label that wraps to two lines on a narrow window
 * does not leave its neighbours shorter than itself.
 *
 * Only the game's nav; the admin console's three-link bar keeps the plain row
 * above, which is why this is scoped to .tabs rather than to .topbar nav.
 */
.topbar nav.tabs { flex: 1 1 auto; min-width: 0; gap: 2px; }
.topbar nav.tabs a {
  flex: 1 1 0; min-width: 0; max-width: 112px;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 4px 3px 3px; text-align: center; line-height: 1.05;
  border-bottom: 2px solid transparent;
}
.topbar nav.tabs a:hover { color: #fff; text-decoration: none; }
.topbar nav.tabs a.here { border-bottom-color: var(--gold); }
/* A fixed box for icons the wiki hands over at nine different sizes: contain
 * scales each one down to fit without cropping or stretching it, and the
 * global `image-rendering: pixelated` keeps the sprites' edges hard. */
.topbar nav.tabs .tab-ico { width: 24px; height: 24px; object-fit: contain; flex: none; }
.topbar nav.tabs .tab-label { font-size: 16px; }
/* The Equipment tab's upgrade dot (markUpgrades) is appended as the anchor's
 * last child. In a column that would be a third ROW under the label, making
 * the whole strip taller the moment a better item is banked, so it is lifted
 * out of the flow and parked beside the icon — the same thing the phone's tab
 * bar does with it. */
.topbar nav.tabs a { position: relative; }
.topbar nav.tabs .up-dot { position: absolute; top: 1px; left: 50%; margin-left: 10px; }
/* Two steps down for narrow desktop windows. Nine tabs, a logo and the user
 * box stop fitting comfortably around 1150px; below 1000px the longest label
 * takes a second line, so the gap between tabs GROWS rather than shrinks —
 * without it the wrapped "Collection Log" sits shoulder to shoulder with its
 * neighbours and the strip reads as one run of words. */
@media (max-width: 1150px) {
  .topbar { gap: 12px; }
  .topbar nav.tabs .tab-ico { width: 20px; height: 20px; }
  .topbar nav.tabs .tab-label { font-size: 14px; }
}
@media (max-width: 1000px) {
  /* The tab strip grows a line here, so the centre column's allowance grows
   * with it (see --page-chrome, which is measured from the topbar). */
  :root { --page-chrome: 160px; }
  .topbar { gap: 10px; }
  .logo { font-size: 20px; }
  .topbar nav.tabs { gap: 7px; }
  .topbar nav.tabs .tab-label { font-size: 13px; }
}
.userbox { margin-left: auto; display: flex; gap: 12px; align-items: center; color: var(--dim); }
.userbox #who { color: var(--parchment); }
/* Shown only to an administrator (app.js), and only the console enforces it. */
#admin-link { color: var(--gold); }

/* -------------------------------------------------------------- layout --- */
.game-layout {
  display: grid;
  grid-template-columns: 290px 1fr 270px;
  gap: 14px;
  padding: 14px;
  max-width: 1400px;
  margin: 0 auto;
}
@media (max-width: 1000px) { .game-layout { grid-template-columns: 1fr; } }

.panel {
  background: var(--panel);
  border: 3px solid var(--edge-dark);
  box-shadow: 0 0 0 2px var(--edge) inset;
  padding: 12px;
}
.panel h2 {
  font-size: 21px; color: var(--gold); margin-bottom: 8px;
  text-shadow: 1px 1px 0 #000;
}

/* ----------------------------------------------------------- boss list --- */
/* padding-top lives on the heading instead of the panel: see below. */
#boss-list { max-height: calc(100vh - 220px); overflow-y: auto; padding-top: 0; }
/* The heading and the category labels ride the scroll. "Bosses" never leaves
 * the top, and each category label is sticky INSIDE its own group — so it
 * holds the line under the heading until the next group's label reaches that
 * point and shoves it off. Sticky inside one shared parent would only stack
 * the labels on top of each other; the per-group wrapper (app.js,
 * renderBossList) is what makes the old one scroll away. --boss-head-h is the
 * measured height of the heading, set once on load. */
#boss-list > h2 {
  position: sticky; top: 0; z-index: 3;
  background: var(--panel);
  /* Sideways into the panel's padding, so rows pass under it edge to edge.
   * The top inset is PADDING, and the panel gives up its own padding-top to
   * pay for it: sticky holds a box by its MARGIN edge, so a negative
   * margin-top here is clamped back to the scrollport and shoves the heading
   * down by that much — leaving a live strip of rows scrolling above it. */
  margin: 0 -12px; padding: 12px 12px 6px;
  /* Re-draw the panel's 2px inset edge, which this now sits on top of. */
  box-shadow: inset 0 2px 0 var(--edge), inset 2px 0 0 var(--edge), inset -2px 0 0 var(--edge);
}
.tier-label {
  position: sticky; top: var(--boss-head-h, 44px); z-index: 2;
  background: var(--panel);
  color: var(--dim); margin: 0 0 4px;
  /* The old 10px top MARGIN, moved inside the box: a sticky element is held
   * by its margin edge, so a margin here would park the label 10px low and
   * leave a transparent band for rows to scroll through. */
  padding: 10px 0 2px;
  border-bottom: 1px solid var(--edge); font-size: 17px;
}
.boss-row {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 6px; margin: 2px 0; cursor: pointer;
  border: 2px solid transparent;
}
.boss-row:hover { background: var(--panel2); border-color: var(--edge); }
.boss-row.active { background: #55461f; border-color: var(--gold); }
.boss-row img { width: 34px; height: 34px; object-fit: contain; flex: none; }
.boss-row .bname { flex: 1; line-height: 1.05; }
.boss-row .bkc { color: var(--gold); font-size: 16px; }
.boss-row.locked { cursor: not-allowed; opacity: 0.55; }
.boss-row.locked img { filter: grayscale(1) brightness(0.6); }
.boss-row.locked .block { font-size: 14px; color: var(--dim); max-width: 120px; text-align: right; line-height: 1; }

/* --------------------------------------------------------------- stage --- */
/* Anchoring the centre column to the viewport is what lets the chat below use
 * `flex: 1` at all. Without it the grid row's height is DERIVED from its
 * contents, so the chat growing simply makes room for itself — circular, and
 * the reason the panel used to run away down the page. The left panel already
 * anchors itself the same way (#boss-list, above). */
#stage {
  display: flex; flex-direction: column; gap: 10px;
  /* A DEFINITE height, not max-height. `flex: 1` on the chat below grows into
   * free space, and a max-height only caps — the grid row would still be sized
   * from its contents, leaving no free space and pinning the chat at its floor
   * on every screen. This is the line that makes the chat scale with the
   * window. */
  height: calc(100vh - var(--page-chrome));
  height: calc(100dvh - var(--page-chrome));
}
/*
 * Nothing above the chat may be squeezed to make room for it. A flex item's
 * default flex-shrink is 1, so once #stage is height-bounded the arena's
 * `height: 380px` becomes a mere suggestion — it was collapsing to 69px on a
 * 1080p screen as the chat filled. The :not() keeps this independent of source
 * order, rather than relying on a later rule to win.
 */
#stage > *:not(#chatbox) { flex: none; }
#arena {
  position: relative;
  height: 380px;
  background: radial-gradient(ellipse at 50% 65%, #51462f 0%, #3a3226 45%, #2b251c 100%);
  border: 3px solid var(--edge-dark);
  box-shadow: 0 0 0 2px var(--edge) inset, 0 0 60px rgba(0,0,0,.5) inset;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
#boss-wrap {
  position: relative; z-index: 2;
  animation: bob 2.2s ease-in-out infinite;
}
@keyframes bob {
  0%, 100% { transform: translateY(-6px); }
  50%      { transform: translateY(6px); }
}
#boss-img {
  max-height: 280px; max-width: 420px;
  filter: drop-shadow(0 6px 12px rgba(0,0,0,.6));
}
#boss-img.lunge { animation: lunge 0.15s ease-out; }
@keyframes lunge {
  0%   { transform: translate(0, 0); }
  35%  { transform: translate(-10px, 6px) scale(1.05); }
  70%  { transform: translate(4px, -2px); }
  100% { transform: translate(0, 0); }
}
#boss-shadow {
  position: absolute; bottom: 26px; left: 50%; z-index: 1;
  width: 180px; height: 26px; margin-left: -90px;
  background: radial-gradient(ellipse, rgba(0,0,0,.55), transparent 70%);
  animation: shadowpulse 2.2s ease-in-out infinite;
}
@keyframes shadowpulse {
  0%, 100% { transform: scaleX(0.85); opacity: .7; }
  50%      { transform: scaleX(1.05); opacity: 1; }
}
#loot-beam {
  position: absolute; bottom: 10%; left: 50%; z-index: 0;
  width: 110px; height: 92%; margin-left: -55px;
  background: linear-gradient(to top, rgba(180,76,255,.6), rgba(180,76,255,.25) 55%, transparent);
  filter: blur(3px);
  opacity: 0; transition: opacity .4s;
  pointer-events: none;
}
#loot-beam.show { opacity: 1; animation: beamflicker 0.9s ease-in-out infinite; }
@keyframes beamflicker { 0%,100% { filter: blur(3px) brightness(1); } 50% { filter: blur(4px) brightness(1.35); } }

#hitsplats { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
.hitsplat {
  position: absolute;
  width: 44px; height: 44px;
  background: var(--red);
  border-radius: 8px;
  box-shadow: 0 0 0 2px #7e150c inset, 2px 2px 4px rgba(0,0,0,.5);
  color: #fff; font-size: 26px;
  display: flex; align-items: center; justify-content: center;
  animation: splat 0.8s ease-out forwards;
}
@keyframes splat {
  0%   { transform: scale(0.3); opacity: 0; }
  12%  { transform: scale(1.2); opacity: 1; }
  28%  { transform: scale(1); }
  75%  { opacity: 1; }
  100% { transform: translateY(-26px) scale(0.9); opacity: 0; }
}

#boss-title { text-align: center; font-size: 30px; color: #fff; text-shadow: 2px 2px 0 #000; }
#boss-title .style-tag { font-size: 17px; color: var(--dim); }

.killbar {
  height: 24px;
  background: #241f17;
  border: 2px solid var(--edge);
  box-shadow: 0 0 0 1px #000 inset;
  position: relative;
}
#killbar-fill {
  height: 100%; width: 0%;
  background: linear-gradient(#ffb051, var(--gold) 45%, #c96e00);
}
.killbar .kb-label {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; color: #fff; text-shadow: 1px 1px 0 #000;
}

/* --------------------------------------------------------------- stats --- */
#stats .stat-line { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px dotted var(--edge); }
#stats .stat-line b { color: var(--gold); font-weight: normal; }
.powergrid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; margin: 8px 0; text-align: center; }
.powergrid .pcell { background: var(--panel2); border: 2px solid var(--edge); padding: 5px 2px; }
.powergrid .pcell .pv { font-size: 24px; color: var(--gold); }
.powergrid .pcell .pl { font-size: 14px; color: var(--dim); }
#next-unlock { margin-top: 10px; color: var(--dim); font-size: 17px; line-height: 1.3; }
#next-unlock b { color: var(--parchment); }

/* ------------------------------------------------------------- chatbox --- */
/* The chat and drop feed live in the CENTRE column under the KC bar, in a
 * fixed window of --chat-lines lines with the history scrollable above it.
 *
 * It used to be `flex: 1` against a parent with no height of its own, so there
 * was nothing to fill and nothing to overflow: the panel simply grew downwards
 * for every message and the scrollbar never appeared. Sizing the viewport in
 * LINES rather than pixels keeps it to the same number of messages after the
 * font size changes on small screens. */
#chatbox {
  display: flex; flex-direction: column;
  /* DESKTOP: take whatever height the arena leaves, with a floor so a short
   * laptop window cannot squeeze the chat to nothing. This works only because
   * #stage is bounded above; on its own it is the runaway-growth bug.
   *
   * The basis is 0, NOT auto. With `auto` a flex item's base size is its
   * CONTENT, so every message made the box want to be bigger, and the overflow
   * that created got taken out of the arena above. Basis 0 means the height
   * comes from the space available and nothing else — message count cannot
   * influence the layout at all. */
  flex: 1 1 0;
  min-height: 210px;
  /*
   * ...and a ceiling, so a tall monitor gets a generous chat rather than an
   * absurd one. 16 lines of #chat-inner's 18px/1.3 type (23.4px each), plus its
   * 8px padding top and bottom, plus 53px for the input row and borders — that
   * last figure measured by `npm run layout`, which also asserts the resulting
   * line count. `em` is deliberately not used here: on #chatbox it would
   * resolve against this element's inherited font-size, not the 18px set on
   * #chat-inner below, and would silently give the wrong number of lines.
   */
  --chat-max-lines: 16;
  max-height: calc(var(--chat-max-lines) * 23.4px + 16px + 53px);
  background: #2f2a20;
  border: 3px solid var(--edge-dark);
  box-shadow: 0 0 0 2px var(--edge) inset;
}
#chat-inner {
  --chat-pad: 8px;
  flex: 1 1 auto;
  /* Required, not decorative: a flex item's automatic minimum is its content,
   * so without this it refuses to shrink and never scrolls. */
  min-height: 0;
  /* `scroll` rather than `auto` keeps the bar present, so the text never
   * reflows sideways the moment the panel first overflows. */
  overflow-y: scroll;
  padding: var(--chat-pad) 12px; font-size: 18px; line-height: 1.3;
  /* Long words and pasted URLs wrap instead of forcing a sideways scroll. */
  overflow-wrap: anywhere;
  /* Firefox; the WebKit equivalent is below. */
  scrollbar-width: thin;
  scrollbar-color: var(--edge) #241f18;
}
#chat-inner::-webkit-scrollbar { width: 12px; }
#chat-inner::-webkit-scrollbar-track { background: #241f18; }
#chat-inner::-webkit-scrollbar-thumb {
  background: var(--edge);
  border: 2px solid #241f18;
}
#chat-inner::-webkit-scrollbar-thumb:hover { background: var(--gold); }

/* SINGLE COLUMN (phones and narrow tablets). Deliberately the opposite rule:
 * here the page is one long scroll, so "fill the viewport" means nothing — and
 * a tall scrollable panel sitting mid-page captures the touch drags that were
 * meant to scroll the page, which is how a widget becomes a wall. Fixed, and
 * shorter than desktop: something you scroll past rather than get stuck in.
 * Sized in LINES so the smaller type at 700px shows the same six messages
 * rather than a different number. */
@media (max-width: 1000px) {
  #stage { height: auto; max-height: none; }
  #chatbox { --chat-lines: 6; flex: none; min-height: 0; }
  #chat-inner {
    flex: none;
    /* 1.3em == the line-height, and the padding is added back because
     * box-sizing is border-box globally — without that term it eats a line. */
    height: calc(var(--chat-lines) * 1.3em + var(--chat-pad) * 2);
  }
}
#chat-inner .line { color: var(--parchment); }
#chat-inner .line.drop { color: var(--gold); }
#chat-inner .line.rare { color: var(--purple); }
#chat-inner .line.pet { color: var(--green); }
#chat-inner .line.sys { color: var(--dim); }

/* -------------------------------------------------------------- toasts --- */
#toasts {
  position: fixed; right: 16px; bottom: 16px; z-index: 60;
  display: flex; flex-direction: column; gap: 8px;
}
.toast {
  display: flex; flex-direction: column; gap: 6px;
  min-width: 260px; max-width: 340px;
  background: var(--panel);
  border: 2px solid var(--gold);
  box-shadow: 0 4px 14px rgba(0,0,0,.6);
  padding: 8px 12px;
  animation: toastin .3s ease-out;
}
.toast { position: relative; padding-right: 30px; }
.toast.out { opacity: 0; transform: translateX(30px); transition: all .4s; }
@keyframes toastin { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; } }
/* A card holds one or more rows, newest on top; later rows slide in too. */
.toast-row { display: flex; gap: 12px; align-items: center; animation: toastrow .25s ease-out; }
.toast-row + .toast-row { border-top: 1px solid rgba(255,255,255,.08); padding-top: 6px; }
@keyframes toastrow { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; } }
.toast-more { font-size: 14px; color: var(--dim); text-align: right; }
.toast img { width: 40px; height: 40px; object-fit: contain; flex: none; }
.toast .tname { color: #fff; }
.toast .trate { font-size: 15px; color: var(--dim); }
.toast.rare { border-color: var(--purple); box-shadow: 0 0 16px rgba(180,76,255,.55); }
.toast-row.rare .tname { color: var(--purple); }
.toast.pet { border-color: var(--green); box-shadow: 0 0 16px rgba(53,184,57,.5); }
.toast-row.pet .tname { color: var(--green); }
/* Purchases keep the default gold border — it's already the money colour —
   and only tint the name, so a buy reads as a quieter cousin of a drop. */
.toast.buy { box-shadow: 0 0 16px rgba(255,152,31,.4); }
.toast-row.buy .tname { color: var(--gold); }

/* --------------------------------------------------------------- shake --- */
body.shake { animation: shake 0.4s; }
@keyframes shake {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-5px, 3px); }
  40% { transform: translate(5px, -3px); }
  60% { transform: translate(-4px, -2px); }
  80% { transform: translate(3px, 2px); }
}

/* --------------------------------------------------------------- modal --- */
.modal {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0,0,0,.72);
  display: flex; align-items: center; justify-content: center;
}
.modal.hidden { display: none; }
.modal-box {
  background: var(--panel);
  border: 3px solid var(--gold);
  box-shadow: 0 0 40px rgba(0,0,0,.8);
  max-width: 480px; width: 92%;
  padding: 20px;
  max-height: 80vh; overflow-y: auto;
}
.modal-box h2 { color: var(--gold); font-size: 26px; margin-bottom: 10px; text-shadow: 2px 2px 0 #000; }
/* Account → Per-monster progress: monster name opens its KC hiscores */
.kc-link { background: none; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; text-align: left; }
.kc-link:hover { color: var(--gold); }
#kc-body table.board { margin-bottom: 8px; }
#kc-body table.board th { cursor: default; }
#kc-body tr.kc-me td { color: var(--gold); background: rgba(255,152,31,.12); }
#kc-body tr.kc-gap td { text-align: center; color: var(--dim); padding: 2px; }
#kc-close { margin-top: 10px; }
.away-drops { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; }
.away-drop { display: flex; align-items: center; gap: 10px; background: var(--panel2); border: 1px solid var(--edge); padding: 4px 8px; }
.away-drop img { width: 30px; height: 30px; object-fit: contain; }
.away-drop .qty { margin-left: auto; color: var(--gold); }
.away-drop.new { border-color: var(--gold); }
.away-drop .away-new { font-size: .8em; color: var(--gold); border: 1px solid var(--gold); padding: 0 4px; white-space: nowrap; }

/* --------------------------------------------------------------- login --- */
.login-wrap { max-width: 760px; margin: 60px auto; padding: 0 16px; }
.login-title { text-align: center; font-size: 44px; color: var(--gold); text-shadow: 3px 3px 0 #000; margin-bottom: 6px; }
.login-sub { text-align: center; color: var(--dim); margin-bottom: 28px; }
.login-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 700px) { .login-cols { grid-template-columns: 1fr; } }
.login-cols form { display: flex; flex-direction: column; gap: 10px; }
.err { color: #ff6b5e; min-height: 22px; }

/* ------------------------------------------------------ collection log --- */
.cl-wrap { max-width: 1100px; margin: 0 auto; padding: 14px; display: flex; flex-direction: column; gap: 14px; }
.cl-boss { }
.cl-head {
  display: flex; align-items: center; gap: 12px;
  background: var(--panel2);
  border-bottom: 2px solid var(--edge);
  padding: 6px 10px; margin-bottom: 8px;
}
.cl-head img { width: 34px; height: 34px; object-fit: contain; }
.cl-head .cl-title { font-size: 22px; color: var(--gold); }
/* The name is the handle for the non-log drops (seeds, saplings, clues). */
/* v3.49.0: hovering it still explains, but clicking it now opens the
   monster's own panel — so it is a pointer rather than a question mark. */
.cl-head .cl-title[data-extra] { cursor: pointer; border-bottom: 1px dotted currentColor; }
.cl-head .cl-count { margin-left: auto; color: var(--dim); }
.cl-boss.complete .cl-title { color: var(--green); }
.cl-boss.complete .cl-head { border-color: var(--green); }
.cl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); gap: 8px; }
.cl-cell {
  position: relative;
  background: #2c261d;
  border: 2px solid var(--edge);
  aspect-ratio: 1; padding: 8px;
  display: flex; align-items: center; justify-content: center;
}
.cl-cell img { max-height: 44px; filter: grayscale(1) brightness(0.45); }
.cl-cell.owned { border-color: var(--gold); }
.cl-cell.owned img { filter: none; }
.cl-cell .qty {
  position: absolute; top: 1px; right: 3px;
  font-size: 14px; color: #ffff00; text-shadow: 1px 1px 0 #000;
}
/* v3.38.1: a slot behind a blocker — the item is on the table, but something
   else has to happen first (today: beating TzTok-Jad for TzHaar-Ket's
   obsidian arsenal). The padlock sits over the art so the grid says which
   slots are even in play; the tooltip says what the condition is. */
.cl-cell.locked { border-style: dashed; }
.cl-cell.locked img { opacity: .4; }
.cl-lock {
  position: absolute; inset: 0; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  color: var(--dim);
}
.cl-lock .ic { width: 20px; height: 20px; filter: drop-shadow(1px 1px 0 #000); }

/* --------------------------------------------------------- leaderboard --- */
.board-wrap { max-width: 900px; margin: 0 auto; padding: 14px; }
table.board { width: 100%; border-collapse: collapse; }
table.board th, table.board td { padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--edge); }
table.board th { background: var(--panel2); color: var(--gold); cursor: pointer; user-select: none; }
table.board th:hover { color: #fff; }
table.board th .arrow { font-size: 14px; }
table.board tr:hover td { background: rgba(255,152,31,.06); }
table.board td.num { color: var(--gold); }
.board-note { color: var(--dim); margin-top: 8px; font-size: 16px; }

/* --------------------------------------------------------------- pick --- */
.nothing { text-align: center; color: var(--dim); padding: 60px 0; font-size: 24px; }

/* -------------------------------------------------------- quests & notes --- */
.toast.quest { border-color: #e8c04a; box-shadow: 0 0 16px rgba(232,192,74,.55); }
.toast-row.quest .tname { color: #e8c04a; }
#chat-inner .line.quest { color: #e8c04a; }
.quests-wrap, .notes-wrap { max-width: 900px; margin: 0 auto; padding: 14px; }
.quests-note { color: var(--dim); margin-bottom: 12px; }
.q-card {
  background: var(--panel2); border: 2px solid var(--edge);
  padding: 10px 12px; margin-bottom: 12px;
}
.q-card.ready { border-color: #e8c04a; box-shadow: 0 0 12px rgba(232,192,74,.35); }
.q-card.completed { opacity: .75; border-color: var(--green); }
.q-card.locked { opacity: .6; }
.q-head { display: flex; justify-content: space-between; align-items: baseline; }
.q-name { font-size: 22px; color: var(--gold); }
.q-state { color: var(--dim); font-size: 16px; }
.q-card.ready .q-state { color: #e8c04a; }
.q-card.completed .q-state { color: var(--green); }
.q-flavor { color: var(--parchment); font-style: italic; margin: 4px 0 8px; }
.q-lvl { color: #ff6b5e; font-size: 16px; margin-bottom: 6px; }
.q-lvl.done { color: var(--green); }
.q-req { margin: 6px 0; }
.q-req-label { font-size: 16px; }
.q-req.done .q-req-label { color: var(--green); }
.q-bar { height: 10px; background: #241f17; border: 1px solid var(--edge); }
.q-bar div { height: 100%; background: linear-gradient(#ffb051, var(--gold)); }
.q-req.done .q-bar div { background: linear-gradient(#4fd151, #2f9e33); }
.q-rewards { color: var(--dim); font-size: 16px; margin-top: 6px; }
.q-btn { margin-top: 8px; }
.q-btn.ready { border-color: #e8c04a; color: #e8c04a; animation: fightpulse 1.6s ease-in-out infinite; }
.q-btn.warn { border-color: var(--red); color: #ff8b7e; }
.clue-btns { display: flex; flex-wrap: wrap; gap: 8px; }

/* -------------------------------------------------------------- farming --- */
.farm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-top: 10px; }
.farm-patch {
  background: var(--panel2); border: 2px solid var(--edge);
  padding: 10px 12px; text-align: center;
}
.farm-patch.empty { opacity: .7; border-style: dashed; }
/* Ghost plot: visibly present but plainly not yours yet — fainter than an
   empty plot, and with no button, so it reads as "coming later" rather than
   "something you failed to use". */
.farm-patch.locked {
  opacity: .38; border-style: dashed; background: transparent;
  padding-bottom: 12px;
}
.farm-patch.locked .fp-body { margin-bottom: 0; font-style: italic; }
.farm-patch.ready { border-color: #e8c04a; box-shadow: 0 0 12px rgba(232,192,74,.35); }
.fp-head { color: var(--dim); font-size: 16px; margin-bottom: 4px; }
.fp-body { margin-bottom: 8px; }
.fp-body.ready { color: #e8c04a; }
.fp-btn { width: 100%; }
.fp-btn.ready { border-color: #e8c04a; color: #e8c04a; animation: fightpulse 1.6s ease-in-out infinite; }
.fp-seed { display: block; width: 100%; text-align: left; margin-bottom: 6px; }
/* v3.20.0: what planting each seed takes. */
.fp-seed-tag { font-size: 14px; margin-left: 4px; }
.fp-seed-tag.have { color: #e8c04a; }
.fp-seed-tag.buy { color: var(--green); }
.fp-seed-tag.none { color: var(--dim); }
.fp-seed.no { opacity: .55; cursor: not-allowed; }
.fp-seed-note { margin-top: 4px; }
.anima-head { margin: 18px 0 4px; }
.farm-patch.anima.active { border-color: var(--green); }
.fp-desc { color: var(--dim); font-size: 14px; margin-bottom: 8px; }
#seed-list { max-height: 60vh; overflow-y: auto; margin-bottom: 8px; }
/* The Tithe Farm's sapling shop reuses the seed picker's rows. */
.tf-points { margin: 8px 0; }
#tf-shop-list { margin-top: 8px; }
.note { margin-bottom: 16px; }
.note-head { font-size: 24px; color: var(--gold); border-bottom: 1px solid var(--edge); margin-bottom: 6px; }
.note-date { color: var(--dim); font-size: 16px; margin-left: 8px; }
.note ul { margin-left: 22px; line-height: 1.45; }

/* --------------------------------------------------------- board & misc --- */
.board-tabs { display: flex; gap: 8px; margin-bottom: 10px; }
.board-tabs button.on { border-color: var(--gold); color: var(--gold); background: #55461f; }
.cl-master { color: var(--green); font-size: 15px; margin-left: 10px; }
/* v3.56.0: a finished MINIGAME page. Gold rather than green, because it is
   the other kind of star — a trophy, with no mastery behind it. */
.cl-master.mg { color: var(--gold); }
.site-footer {
  text-align: center; color: var(--dim); font-size: 15px;
  padding: 14px 0 18px; opacity: .8;
}
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* --------------------------------------------------- v1.2: notifications --- */
#notif-stack {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 150; display: flex; flex-direction: column; gap: 8px;
  width: min(420px, calc(100vw - 20px));
  pointer-events: none;
}
.notif-banner {
  display: flex; align-items: center; gap: 12px;
  background: linear-gradient(#4a4034, #322b20);
  border: 2px solid #e8c04a;
  box-shadow: 0 6px 24px rgba(0,0,0,.7), 0 0 18px rgba(232,192,74,.35);
  padding: 10px 14px;
  animation: nbin .35s ease-out;
}
.notif-banner.out { opacity: 0; transform: translateY(-16px); transition: all .45s; }
@keyframes nbin { from { opacity: 0; transform: translateY(-22px) scale(.96); } to { opacity: 1; } }
.notif-banner img { width: 40px; height: 40px; object-fit: contain; }
.nb-title { color: #e8c04a; font-size: 20px; text-shadow: 1px 1px 0 #000; }
.nb-body { color: var(--parchment); font-size: 16px; }
.notif-banner { position: relative; padding-right: 32px; }

/* × on toasts and banners: dismiss before the auto-close timer runs out.
   It opts back into clicks — #notif-stack is pointer-events: none. */
.popup-x {
  position: absolute; top: 2px; right: 2px;
  width: 26px; height: 26px; padding: 0;
  background: none; border: none;
  color: var(--dim); font-size: 22px; line-height: 1;
  pointer-events: auto;
}
.popup-x:hover { color: var(--gold); }

/* ------------------------------------------------------- v1.2: economy --- */
.qmark {
  width: 22px; height: 22px; padding: 0; margin-left: 4px;
  border-radius: 50%; font-size: 14px; line-height: 1;
  color: var(--dim); border-color: var(--edge);
  vertical-align: middle;
}
.store-head { margin-top: 16px; }
#gp-line { font-size: 20px; margin: 4px 0; }
#gp-line b { color: #ffd700; }
.boost-line { color: var(--parchment); padding: 2px 0; }
.boost-timer { color: var(--gold); margin-left: 6px; }
/* Every shop shelf flows into as many columns as the centre panel affords —
 * the same auto-fill language as the bank grid. Before this the potion and
 * upgrade shelves were pinned at `1fr 1fr`, and the supplies and token
 * shelves were not grids at all: twenty full-width cards, one per row. */
.shop-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(146px, 1fr));
  gap: 8px; margin-top: 6px;
}
.pg-sub { color: var(--dim); font-size: 15px; margin: 8px 0 0; text-transform: uppercase; letter-spacing: 0.04em; }
.upg-pips { display: flex; justify-content: center; gap: 4px; margin: 3px 0; }
.upg-pip { width: 9px; height: 9px; border: 1px solid var(--edge); border-radius: 2px; }
.upg-pip.on { background: var(--gold); border-color: var(--gold); }
.upg-now { color: var(--parchment); font-size: 13px; margin-bottom: 4px; }
/* Column, so the buy button can be pushed to the floor of every card — a row
 * of cards with one- and two-line effect text still lines its buttons up. */
.pot-card {
  background: #2c261d; border: 2px solid var(--edge); padding: 7px 6px; text-align: center;
  display: flex; flex-direction: column;
}
.pot-icon { width: 30px; height: 30px; object-fit: contain; display: block; margin: 0 auto 3px; }
/* The class was always emitted for gear you have not earned; packed shelves
 * are where it started to matter. */
.pot-card.locked { border-style: dashed; }
.pot-card.locked .pot-icon { filter: grayscale(1) brightness(.6); }
.pot-card.locked .pot-name { color: var(--dim); }
.tok-icon { width: 16px; height: 16px; object-fit: contain; vertical-align: middle; }
/* The token balance rides on the Platinum Shop heading rather than taking a
 * line of its own. */
#token-line { color: var(--parchment); margin-left: 6px; }
#token-line b { color: var(--gold); }
.pot-name { color: var(--gold); line-height: 1.05; font-size: 16px; }
.pot-fx { font-size: 13px; line-height: 1.15; color: var(--dim); margin: 3px 0 5px; }
.pot-buy { width: 100%; padding: 6px 2px; }
/* margin-top:auto — see .pot-card. Applies to the disabled buttons too, which
 * are plain <button>s rather than .pot-buy. */
.pot-card > button { width: 100%; margin-top: auto; padding: 6px 2px; font-size: 16px; }
.bank-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; margin-bottom: 10px;
  border-bottom: 2px solid var(--edge); padding-bottom: 8px;
}
.bank-tabs { display: flex; gap: 8px; }
.bank-tabs button { padding: 8px 16px; font-size: 18px; }
.bank-tabs button.on { border-color: var(--gold); color: var(--gold); background: #55461f; }
#gp-line { font-size: 22px; }
#gp-line b { color: #ffd700; }
#bank-hint { margin-bottom: 10px; }
.inv-item { grid-template-rows: auto auto auto auto; }
.ii-sell { font-size: 14px; color: #ffd700; grid-column: 2; }
.dupes-tag { color: #ffd700; }
.ii-sellbtn {
  grid-column: 1 / -1;
  margin-top: 6px; padding: 8px 4px; font-size: 15px;
  border-color: #8a7430; color: #ffd700; background: #3a3220;
}
.ii-sellbtn:hover { border-color: #ffd700; }
#sell-mode.on { border-color: #ffd700; color: #ffd700; background: #55461f; }
.inv-item.tagged { border-color: #ffd700; box-shadow: 0 0 10px rgba(255,215,0,.35); }
.inv-item.tagged-last { border-color: var(--red); box-shadow: 0 0 10px rgba(192,35,22,.45); }
.tag-badge {
  grid-column: 1 / -1;
  font-size: 13px; letter-spacing: 1px;
  color: #ffd700; text-shadow: 1px 1px 0 #000;
}
.inv-item.tagged-last .tag-badge { color: #ff8b7e; }
#sell-bar {
  position: fixed; left: 12px; right: 12px; bottom: 14px; z-index: 85;
  display: flex; align-items: center; gap: 12px;
  background: linear-gradient(#4a4034, #322b20);
  border: 2px solid #ffd700;
  box-shadow: 0 6px 24px rgba(0,0,0,.7);
  padding: 10px 16px;
  max-width: 640px; margin: 0 auto;
}
#sell-bar-info { flex: 1; font-size: 18px; }
#sell-bar-info b { color: #ffd700; }
#sell-bar-go { border-color: #ffd700; color: #ffd700; padding: 8px 22px; font-size: 18px; }
#sell-bar-go:disabled { opacity: .5; cursor: not-allowed; }
@media (max-width: 700px) {
  #sell-bar { bottom: calc(74px + env(safe-area-inset-bottom)); }
}
#bank-toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
#bank-toolbar select {
  font-family: inherit; font-size: 16px; padding: 8px;
  background: #241f17; color: var(--parchment); border: 2px solid var(--edge);
  flex: 1; min-width: 120px;
}
#bank-toolbar button { flex: 1; min-width: 130px; }
.inv-item { min-height: 52px; }
.inv-item.ghost { opacity: .45; }
.inv-item.ghost img { filter: grayscale(1) brightness(.6); }
.sheet-head { display: flex; gap: 12px; align-items: center; margin-bottom: 10px; }
.sheet-head img { width: 52px; height: 52px; object-fit: contain; }
.sheet-name { font-size: 24px; color: var(--gold); }
.sheet-rows { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin-bottom: 12px; }
.sheet-rows div:nth-child(odd) { color: var(--dim); }
.sheet-btn { display: block; width: 100%; margin-top: 8px; padding: 11px 0; font-size: 18px; }
.sheet-btn.warn { border-color: var(--red); color: #ff8b7e; }
/* Exact-quantity sell control: produce and forge materials only. */
.sell-qty { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; color: var(--dim); }
.sell-qty input { width: 90px; text-align: right; }

/* ------------------------------------------------------- v1.2: account --- */
.account-wrap { max-width: 900px; margin: 0 auto; padding: 14px; display: flex; flex-direction: column; gap: 14px; }
.account-wrap .stat-line { display: flex; justify-content: space-between; gap: 12px; padding: 3px 0; border-bottom: 1px dotted var(--edge); }
.account-wrap .stat-line b { color: var(--gold); font-weight: normal; text-align: right; }
/* v3.20.0: the Skilling page's level/axe lines had no layout at all, so the
 * label ran straight into its value ("AxeBronze axe"). */
.skilling-wrap .stat-line { display: flex; justify-content: space-between; gap: 12px; padding: 3px 0; }
.skilling-wrap .stat-line b { color: var(--gold); font-weight: normal; text-align: right; }
.nf-row { display: block; padding: 6px 0; font-size: 18px; }
.nf-row input[type=checkbox] { width: 18px; height: 18px; }
#nf-save { margin-top: 8px; margin-right: 10px; }
#pw-form { display: flex; flex-direction: column; gap: 10px; max-width: 380px; }

/* --------------------------------------------------------- v1.2: guide --- */
.guide h3 { color: var(--gold); margin: 14px 0 4px; font-size: 21px; }
.guide p { line-height: 1.5; margin-bottom: 6px; }
.guide .g-code {
  background: #241f17; border: 1px solid var(--edge);
  padding: 8px 12px; font-size: 16px; color: #b6aaff;
  overflow-x: auto;
}
.guide h2 { font-size: 25px; margin: 26px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--edge); scroll-margin-top: 60px; }
.guide h2:first-child { margin-top: 0; }
.guide h3 { scroll-margin-top: 60px; }
.guide ul, .guide ol { margin: 0 0 8px 24px; line-height: 1.5; }
.guide li { margin-bottom: 3px; }
.guide .g-tip, .guide .g-toc {
  background: #241f17; border: 1px solid var(--edge);
  padding: 10px 14px; margin: 12px 0;
}
.guide .g-tip { border-left: 4px solid var(--gold); }
.guide .g-tip > b, .guide .g-toc > b { display: block; color: var(--gold); margin-bottom: 4px; }
.guide .g-tip ol, .guide .g-toc ol { margin-bottom: 0; }
.guide .g-facts {
  display: grid; grid-template-columns: max-content 1fr; gap: 2px 14px;
  background: #241f17; border: 1px solid var(--edge);
  padding: 8px 12px; margin: 4px 0 8px;
}
.guide .g-facts dt { color: var(--dim); }
.guide .g-glossary { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; line-height: 1.4; }
.guide .g-glossary dt { color: var(--gold); }
.site-footer a { color: var(--dim); text-decoration: underline; }
.site-footer a:hover { color: var(--gold); }

/* -------------------------------------------------------------- mobile --- */
a, button, .boss-row, .cl-cell, .inv-item, .slot-cell { touch-action: manipulation; }
#mobile-tabs { display: none; }
#touch-tip {
  display: none; position: fixed; z-index: 200;
  transform: translateY(-100%);
  background: #241f17; color: var(--parchment);
  border: 2px solid var(--gold);
  padding: 6px 10px; font-size: 16px; line-height: 1.25;
  box-shadow: 0 4px 14px rgba(0,0,0,.6);
  pointer-events: none;
}
/* Bank hover: a banked item vs what it would replace. */
#touch-tip.rich { font-size: 15px; padding: 8px 10px; }
#touch-tip .cmp-head { margin-bottom: 6px; line-height: 1.2; }
#touch-tip .cmp-vs { color: var(--dim); font-size: 14px; }
#touch-tip .cmp-table { border-collapse: collapse; width: 100%; }
#touch-tip .cmp-table td { padding: 1px 4px; white-space: nowrap; }
#touch-tip .cmp-table td:first-child { padding-left: 0; text-transform: capitalize; }
#touch-tip .cmp-table td:not(:first-child) { text-align: right; }
#touch-tip .cmp-table svg { width: 14px; height: 14px; vertical-align: -2px; }
#touch-tip .cmp-up { color: #7fd36b; }
#touch-tip .cmp-down { color: #e36b5b; }
#touch-tip .cmp-eq { color: var(--dim); }
#touch-tip .cmp-note { margin-top: 6px; font-size: 14px; color: var(--gold); }
#touch-tip .cmp-note.cmp-warn { color: #e36b5b; }
/* What a minion drops that no log slot can hold. Written on the bare classes
   rather than under #touch-tip since v3.49.0: the same block is drawn in the
   hover tip, in the monster's own log panel (MobLog) and in the pop-out
   window, and it should read identically in all three. */
.cl-extra-head { margin-bottom: 6px; line-height: 1.2; color: var(--gold); }
.cl-extra-tier { color: var(--dim); font-size: 14px; }
.cl-extra-row { margin-top: 6px; }
.cl-extra-label { line-height: 1.2; }
.cl-extra-rate { color: var(--dim); }
.cl-extra-items { display: flex; flex-wrap: wrap; gap: 3px 6px; margin-top: 3px; }
.cl-extra-item {
  display: inline-flex; align-items: center; gap: 3px;
  background: #2c261d; border: 1px solid var(--edge);
  padding: 1px 4px; font-size: 14px; white-space: nowrap;
}
.cl-extra-item img { width: 18px; height: 18px; object-fit: contain; }
.cl-extra-note { color: var(--dim); }

@media (max-width: 700px) {
  /* Compact sticky header; nav moves to the bottom tab bar. */
  .topbar { position: sticky; top: 0; z-index: 60; gap: 10px; padding: 7px 12px; }
  .topbar nav { display: none; }
  .logo { font-size: 20px; white-space: nowrap; }
  .userbox { gap: 8px; font-size: 15px; }
  .userbox #who { max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .userbox button { padding: 6px 10px; font-size: 15px; }

  body { padding-bottom: calc(66px + env(safe-area-inset-bottom)); }
  #mobile-tabs {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
    background: linear-gradient(#4a4034, #322b20);
    border-top: 3px solid var(--edge-dark);
    box-shadow: 0 1px 0 var(--edge) inset;
    padding-bottom: env(safe-area-inset-bottom);
  }
  #mobile-tabs a {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 8px 0 7px; color: var(--dim); text-decoration: none;
  }
  #mobile-tabs a.here { color: var(--gold); background: rgba(255, 152, 31, 0.09); }
  #mobile-tabs a:active { background: rgba(255, 152, 31, 0.16); }
  .mt-ico { font-size: 19px; line-height: 1; }
  .mt-label { font-size: 12px; letter-spacing: 0.5px; }

  /* Game screen: arena first, stats second, monster list last. */
  .game-layout { display: flex; flex-direction: column; gap: 10px; padding: 10px; }
  #stage { order: 0; }
  #stats { order: 1; }
  #boss-list { order: 2; max-height: 340px; }
  #arena { height: 280px; }
  #boss-img { max-height: 185px; max-width: 82vw; }
  #boss-shadow { width: 140px; margin-left: -70px; bottom: 20px; }
  #hpbar { width: min(240px, 68vw); }
  #boss-title { font-size: 24px; }
  #fight-btn { font-size: 23px; padding: 10px 20px; }
  #fight-info { font-size: 15px; }
  #flee-btn { padding: 6px 12px; }
  .hitsplat { width: 38px; height: 38px; font-size: 22px; }
  .xp-float { font-size: 19px; }
  .boss-row { padding: 9px 8px; }
  .boss-row .block { max-width: 105px; font-size: 13px; }
  #train-styles button { padding: 9px 2px; }
  /* Phones: the feed sits under the KC bar in the single-column stack. The
   * height is inherited from the line-count rule, so shrinking the type here
   * makes the panel shorter rather than showing more messages than desktop. */
  #chat-inner { font-size: 16px; }

  /* Toasts: full-width strip floating above the tab bar. */
  #toasts {
    left: 10px; right: 10px;
    bottom: calc(74px + env(safe-area-inset-bottom));
    align-items: stretch;
  }
  .toast { min-width: 0; max-width: none; }

  /* Other pages */
  .equip-layout, .quests-wrap, .notes-wrap, .board-wrap { padding: 10px; gap: 10px; }
  .cl-wrap { padding: 10px; gap: 10px; }
  .cl-grid { grid-template-columns: repeat(auto-fill, minmax(54px, 1fr)); gap: 6px; }
  .cl-lock .ic { width: 16px; height: 16px; }
  .cl-head .cl-master { display: block; margin-left: 0; }
  #slots-grid { gap: 6px; }
  .inv-items { grid-template-columns: repeat(auto-fill, minmax(136px, 1fr)); }
  .q-btn { width: 100%; padding: 11px 0; font-size: 19px; }
  table.board { min-width: 540px; }
  table.board th, table.board td { padding: 8px 8px; font-size: 17px; }
  .board-tabs button { flex: 1; padding: 9px 4px; }
  .login-wrap { margin: 24px auto; }
  .modal-box { max-height: 76vh; }
}
.hidden { display: none !important; }
.dim-note { color: var(--dim); font-size: 16px; }
/* v3.51.0: the rally line in a skill's status — green, like the party panel's. */
.rally-note { color: var(--green); }
/* v3.66.0: the expedition button spends most of a day counting down its 24h
   call cooldown. Dimmed rather than hidden — the countdown IS the answer. */
#exp-call:disabled { opacity: .5; cursor: not-allowed; }
#exp-call:disabled:hover { border-color: var(--edge); color: var(--parchment); }

/* -------------------------------------------------------------- combat --- */
#boss-img.dying {
  animation: die 0.42s ease-in forwards;
}
@keyframes die {
  0%   { transform: none; filter: none; opacity: 1; }
  40%  { transform: rotate(7deg) translateY(6px); filter: grayscale(.6) brightness(1.6); }
  100% { transform: rotate(14deg) translateY(22px) scale(0.92); filter: grayscale(1) brightness(0.4); opacity: 0.05; }
}
#boss-img.spawn { animation: spawnin 0.35s ease-out; }
@keyframes spawnin {
  0%   { transform: scale(0.75); opacity: 0; filter: brightness(2); }
  100% { transform: scale(1); opacity: 1; filter: none; }
}
/* v3.65.0: the same fight changing form rather than a new monster arriving
 * (Grotesque Guardians at half health) — a white-hot flare and a rear-up in
 * place, so it reads as a transformation and not as a respawn. Below .lunge
 * in the file on purpose: while it runs it is the animation that shows. */
#boss-img.phase-shift { animation: phaseshift 0.5s ease-out; }
@keyframes phaseshift {
  0%   { transform: scale(1.12); opacity: 0.2; filter: brightness(2.4); }
  45%  { transform: scale(1.05); opacity: 1; filter: brightness(1.5); }
  100% { transform: scale(1); opacity: 1; filter: none; }
}
#arena.under-attack::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  box-shadow: 0 0 60px 18px rgba(192, 35, 22, 0.55) inset;
  animation: vignette 0.45s ease-out forwards;
}
@keyframes vignette { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }
#arena.under-attack #boss-wrap { animation: bob 2.2s ease-in-out infinite, jab 0.25s ease-out; }
@keyframes jab { 40% { margin-top: 10px; } }

/* v3.24.1 Firemaking: the OSRS fire (the wiki's animated Fire.gif) stands in
 * for the log sprite, and each log you burn is tossed onto it (.log-toss,
 * from JS) so you can still see which log it is. */
#fire { display: none; }
#arena.burning #boss-wrap { animation: none; align-self: flex-end; margin-bottom: 14px; }
#arena.burning #boss-img { display: none; }
#arena.burning #fire {
  display: block; width: 190px;
  image-rendering: auto; /* a smooth render, not pixel art */
}
.log-toss {
  position: absolute; left: 50%; bottom: 44px; z-index: 3;
  width: 54px; margin-left: -27px;
  pointer-events: none;
  animation: logtoss 0.6s ease-in forwards;
}
@keyframes logtoss {
  0%   { opacity: 0; transform: translate(-70px, -110px) rotate(-40deg); }
  20%  { opacity: 1; }
  75%  { opacity: 1; transform: translate(0, 0) rotate(0deg); }
  100% { opacity: 0; transform: translate(0, 6px) rotate(0deg); }
}
@media (max-width: 700px) {
  #arena.burning #fire { width: 150px; }
  .log-toss { bottom: 34px; width: 44px; margin-left: -22px; }
}

#hpbar {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
  width: 260px; height: 20px; z-index: 5;
  background: #7e150c;
  border: 2px solid #000;
  box-shadow: 0 2px 6px rgba(0,0,0,.6);
}
#hp-fill {
  height: 100%; width: 100%;
  background: linear-gradient(#4fd151, #2f9e33);
}
/* Same OSRS health-bar look as #hpbar — red "damage taken" showing through
 * a shrinking green fill — just inline in the sidebar instead of floating
 * over the arena, and with no centered label (the number lives next to it
 * in its own stat-line already). */
.mini-hpbar {
  width: 100%; height: 12px; margin: 2px 0 8px;
  background: #7e150c;
  border: 2px solid #000;
}
.mini-hpbar > div {
  height: 100%; width: 100%;
  background: linear-gradient(#4fd151, #2f9e33);
  transition: width 0.4s ease-out;
}
.mini-hpbar.warn > div { background: linear-gradient(#ffcf4d, #d69b1f); }
.mini-hpbar.danger > div { background: linear-gradient(#ff6b5e, #c02316); }
#hpbar span {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; color: #fff; text-shadow: 1px 1px 0 #000;
}

#fight-overlay {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  padding-bottom: 28px; gap: 10px;
  background: linear-gradient(rgba(20,16,10,0), rgba(20,16,10,.55) 75%);
}
#fight-btn {
  font-size: 28px; padding: 10px 30px;
  background: linear-gradient(#5d4f2e, #443a22);
  border: 3px solid var(--gold);
  color: var(--gold);
  text-shadow: 2px 2px 0 #000;
  box-shadow: 0 4px 16px rgba(0,0,0,.6), 0 0 18px rgba(255,152,31,.25);
  animation: fightpulse 1.6s ease-in-out infinite;
}
#fight-btn:hover:not(:disabled) { color: #fff; border-color: #fff; }
#fight-btn:disabled { animation: none; opacity: .65; cursor: not-allowed; border-color: var(--edge); color: var(--dim); }
@keyframes fightpulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }
#fight-info { font-size: 17px; color: var(--parchment); text-shadow: 1px 1px 0 #000; text-align: center; padding: 0 12px; }

#flee-btn {
  position: absolute; top: 12px; right: 12px; z-index: 6;
  font-size: 15px; padding: 3px 10px;
  opacity: .75;
}
#flee-btn:hover { opacity: 1; }

/* The open Slayer task's count, bottom-right of the arena (renderArenaSlayer).
 * Deliberately quiet — a plaque on the wall of the room, not a HUD element
 * competing with the hitsplats — and click-through, because the companion's
 * drop, the mimic and the impling all spawn at random points in here and a
 * badge that ate one of those clicks would cost a player an actual drop. */
#arena-slayer {
  position: absolute; right: 10px; bottom: 10px; z-index: 5;
  pointer-events: none;
  display: flex; align-items: baseline; gap: 7px;
  padding: 3px 9px;
  background: rgba(0,0,0,.5);
  border: 2px solid var(--edge-dark);
  box-shadow: 0 2px 6px rgba(0,0,0,.45);
  color: var(--gold);
  font-size: 16px;
  text-shadow: 1px 1px 0 #000;
  white-space: nowrap;
}
#arena-slayer .as-label {
  font-size: 11px; letter-spacing: .5px; text-transform: uppercase;
  color: #b9ad8c;
}
/* v3.74.1: the task just finished, and no new one has been taken. Green and
 * breathing, like the farming cell with a patch ready (.farm-ready-glow) —
 * the corner spent the whole task as a quiet gold number, so the one moment
 * it has something to say is the one moment it is allowed to move. It holds
 * until a master hands out the next task rather than timing out: every kill
 * landing in front of it until then pays no Slayer xp, which is exactly the
 * thing worth still saying five minutes later. */
#arena-slayer.done {
  color: #8fe88f;
  border-color: #3f7f43;
  animation: as-done-pulse 1.8s ease-in-out infinite;
}
#arena-slayer.done .as-label { color: #6fcf7a; }
/* The finished notice is the one exception to click-through: it links to the
 * Slayer tab, where the next task is taken. A drop landing under it for the
 * few kills it is up costs less than hunting for the tab. */
#arena-slayer.done { pointer-events: auto; cursor: pointer; }
#arena-slayer .as-link {
  display: flex; align-items: baseline; gap: 7px;
  color: inherit; text-decoration: none;
}
#arena-slayer .as-link:hover .as-count { text-decoration: underline; }
@keyframes as-done-pulse {
  0%, 100% { background: rgba(0,0,0,.5);     box-shadow: 0 2px 6px rgba(0,0,0,.45); }
  50%      { background: rgba(22,68,28,.62); box-shadow: 0 0 12px 2px rgba(72, 214, 96, .7), 0 2px 6px rgba(0,0,0,.45); }
}
@media (prefers-reduced-motion: reduce) {
  #arena-slayer.done {
    animation: none;
    background: rgba(22,68,28,.62);
    box-shadow: 0 0 9px 1px rgba(72, 214, 96, .55), 0 2px 6px rgba(0,0,0,.45);
  }
}
@media (max-width: 700px) {
  #arena-slayer { right: 6px; bottom: 6px; font-size: 14px; padding: 2px 7px; }
}

#pet-img {
  position: absolute; bottom: 30px; left: 26px; z-index: 2;
  width: 52px; height: 52px; object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(0,0,0,.6));
  animation: bob 1.8s ease-in-out infinite;
}
/* The Platinum Shop's Golden chicken is token-exclusive, and it is the same
 * bird as the free starter companion, so beside the arena it has to read as
 * the bought one at 52px. The gold is in its own artwork, which keeps every
 * facet of the bird its own shade; this only adds a glow on top. Never a
 * colour filter here — that is what flattened the old chick into a blob. */
#pet-img.pet-gold {
  filter: drop-shadow(0 4px 6px rgba(0,0,0,.6)) drop-shadow(0 0 7px rgba(255, 176, 58, .8));
}
/* The companion's drop — click it within its window or it's gone. The
 * pulse plus a visible squeeze on the last couple seconds is the only
 * "hurry up" signal; there is no numeric countdown to keep it readable
 * at a glance. */
#pet-drop {
  position: absolute; z-index: 3;
  width: 44px; height: 44px; padding: 4px;
  background: none; border: none;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.7)) drop-shadow(0 0 10px rgba(255, 176, 58, .85));
  animation: dropPulse 0.9s ease-in-out infinite;
  transition: left 0.4s ease-out, bottom 0.4s ease-out;
}
#pet-drop img { width: 100%; height: 100%; object-fit: contain; image-rendering: pixelated; }
#pet-drop:hover { transform: scale(1.1); }
@keyframes dropPulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 14px rgba(255, 176, 58, .8); }
  50% { transform: scale(1.15); box-shadow: 0 0 22px rgba(255, 176, 58, 1); }
}

/* v3.4.0 the mimic: shakes and hops elsewhere on every hit, and wears its
 * own little health bar so you can see how many more it takes. */
#mimic {
  position: absolute; z-index: 4;
  width: 76px; height: 84px; padding: 0;
  background: none; border: none; cursor: pointer;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.7)) drop-shadow(0 0 10px rgba(220, 60, 60, .8));
  transition: left 0.2s ease-out, bottom 0.2s ease-out;
}
#mimic.mimic-still { transition: none; }
#mimic img { width: 76px; height: 76px; object-fit: contain; }
#mimic .mimic-hp {
  display: block; width: 60px; height: 6px; margin: 2px auto 0;
  background: #5a1010; border: 1px solid #000;
}
#mimic-hp-fill { display: block; height: 100%; background: #3fbf3f; transition: width 0.1s linear; }
#mimic.mimic-hit img { animation: mimicShake 0.18s linear; }
@keyframes mimicShake {
  0%, 100% { transform: translateX(0) rotate(0); }
  25% { transform: translateX(-4px) rotate(-6deg); }
  75% { transform: translateX(4px) rotate(6deg); }
}
/* An impling in flight — moved every frame by flyImp() in app.js. */
#impling {
  position: absolute; z-index: 4;
  width: 46px; height: 46px; padding: 0;
  background: none; border: none; cursor: pointer;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.7)) drop-shadow(0 0 8px rgba(160, 255, 160, .75));
}
#impling img { width: 100%; height: 100%; object-fit: contain; animation: impFlutter 0.35s ease-in-out infinite; }
#impling.flip img { transform: scaleX(-1); animation-name: impFlutterFlip; }
@keyframes impFlutter { 50% { transform: translateY(-4px); } }
@keyframes impFlutterFlip { 0%, 100% { transform: scaleX(-1); } 50% { transform: scaleX(-1) translateY(-4px); } }
.boost-queued { color: var(--dim); padding-left: 22px; font-size: 0.92em; }

.boss-row.fighting .bname { color: var(--gold); }
.boss-row .swords { font-size: 14px; }
.pg-head { margin-top: 10px; color: var(--dim); font-size: 16px; border-bottom: 1px solid var(--edge); }

/* -------------------------------------------------------------- skills --- */
.hitsplat.miss { background: #3c58b0; box-shadow: 0 0 0 2px #24356e inset, 2px 2px 4px rgba(0,0,0,.5); }
.xpbar {
  position: relative; height: 14px; margin-bottom: 6px;
  background: #241f17; border: 1px solid var(--edge);
}
.xpbar div { height: 100%; width: 0%; background: linear-gradient(#8a7bff, #5b48d6); }
.xpbar span {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; color: #fff; text-shadow: 1px 1px 0 #000; letter-spacing: 0;
}
/* v3.45.0: the skills tab. Three across, like the game's own: the icon on the
 * left, the two levels stacked diagonally (top = right now, bottom = real),
 * and a progress bar to the next level along the bottom edge. The whole
 * composition is anchored to the cell's centre rather than its edges so it
 * stays put when the sidebar goes full-width on a phone. */
#skill-tab {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 3px; margin: 6px auto 4px;
  /* Never binds in the sidebar (~238px of content); it keeps the tab looking
   * like a tab instead of stretching edge to edge when the panel goes
   * full-width on a phone. */
  max-width: 270px;
}
.sk-cell {
  position: relative; height: 48px; overflow: hidden;
  background: linear-gradient(#5b4f3c, #453c2e);
  border: 1px solid var(--edge-dark);
  box-shadow: 0 0 0 1px #6d5e47 inset;
  color: var(--parchment); text-decoration: none;
}
a.sk-cell:hover { box-shadow: 0 0 0 1px var(--gold) inset; text-decoration: none; }
.sk-cell .sk-ic { position: absolute; left: 50%; margin-left: -34px; top: 12px; width: 23px; height: 23px; object-fit: contain; }
.sk-cell b {
  position: absolute; font-weight: normal; font-size: 21px; line-height: 20px;
  color: #ffff00; text-shadow: 1px 1px 0 #000;
}
.sk-cell .sk-cur { left: 50%; margin-left: -9px; top: 4px; }
.sk-cell .sk-base { left: 50%; margin-left: 12px; top: 23px; }
/* v3.54.9: the game's own diagonal between the two numbers — the live level
 * sits above the slash, the real one below it, so a boosted cell reads as one
 * number over another instead of two digits crowding each other. Anchored to
 * the cell's centre like the numbers are, so it lands the same way whatever
 * width the sidebar is; the numbers paint over it (z-index below) rather than
 * the line cutting through a digit. */
.sk-cell::after {
  content: ""; position: absolute; left: 50%; top: 23px;
  margin-left: -12px; width: 50px; height: 2px;
  background: linear-gradient(#2b251c 0 1px, rgba(109, 94, 71, 0.55) 1px 2px);
  transform: rotate(-31deg);
  pointer-events: none;
}
.sk-cell b { z-index: 1; }
/* A potion is up: the live number goes green, the real one stays yellow. */
.sk-cell.boosted .sk-cur { color: #6cff4a; }
/* v3.52.0: a rally and nothing else is lifting it — blue, so a bonus that
 * depends on other people being here reads differently from one you bought.
 * A potion on the same skill takes the green back (see renderSkill). */
.sk-cell.rallied .sk-cur { color: #59b7ff; }
.sk-prog {
  position: absolute; left: 0; right: 0; bottom: 0; height: 5px;
  background: #241f17; border-top: 1px solid var(--edge-dark);
}
/* Fill colour is set per skill in app.js: red just after a level, green as the
 * next one comes into reach. */
.sk-prog div { height: 100%; width: 0%; background: var(--red); }
.sk-total {
  grid-column: 1 / -1; text-align: center; padding: 1px 0 2px;
  background: #453c2e; border: 1px solid var(--edge-dark);
  box-shadow: 0 0 0 1px #6d5e47 inset;
  color: var(--dim); font-size: 16px;
}
.sk-total b { color: #ffff00; font-weight: normal; font-size: 20px; text-shadow: 1px 1px 0 #000; }
/* v3.45.0: patches waiting. The farming cell breathes a gold light instead of
 * wearing a badge — a 48px cell has no room for one, and the glow catches the
 * eye from across the sidebar. z-index lifts it over the cells that paint
 * after it, so the halo is not clipped by its neighbours. */
.sk-cell.farm-ready-glow { z-index: 1; border-color: var(--gold); animation: farm-pulse 1.8s ease-in-out infinite; }
@keyframes farm-pulse {
  0%, 100% { box-shadow: 0 0 0 1px #6d5e47 inset, 0 0 2px 0 rgba(255, 198, 64, 0.25); }
  50%      { box-shadow: 0 0 0 1px var(--gold) inset, 0 0 9px 2px rgba(255, 198, 64, 0.8); }
}
@media (prefers-reduced-motion: reduce) {
  .sk-cell.farm-ready-glow {
    animation: none;
    box-shadow: 0 0 0 1px var(--gold) inset, 0 0 7px 1px rgba(255, 198, 64, 0.6);
  }
}

#train-styles { display: flex; gap: 6px; margin: 6px 0; }
#train-styles button { flex: 1; font-size: 15px; padding: 4px 2px; }
#train-styles button.on { border-color: var(--gold); color: var(--gold); background: #55461f; }
.xp-float {
  position: absolute; left: 50%; top: 34%; z-index: 7;
  transform: translateX(-50%);
  color: #b6aaff; font-size: 22px; text-shadow: 2px 2px 0 #000;
  pointer-events: none;
  animation: xpfloat 1.4s ease-out forwards;
}
@keyframes xpfloat {
  0%   { opacity: 0; margin-top: 0; }
  15%  { opacity: 1; }
  100% { opacity: 0; margin-top: -70px; }
}

/* ----------------------------------------------------------- equipment --- */
.equip-layout {
  display: grid;
  grid-template-columns: 380px 1fr;
  gap: 14px;
  padding: 14px;
  max-width: 1400px;
  margin: 0 auto;
  align-items: start;
}
@media (max-width: 900px) { .equip-layout { grid-template-columns: 1fr; } }
#slots-grid {
  display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px;
}
.slot-cell {
  background: #2c261d;
  border: 2px solid var(--edge);
  min-height: 96px;
  padding: 6px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 2px; text-align: center;
}
.slot-cell.filled { border-color: var(--gold); cursor: pointer; }
.slot-cell.filled:hover { border-color: #fff; }
.slot-cell .slot-name { font-size: 13px; color: var(--dim); text-transform: uppercase; letter-spacing: 1px; }
.slot-cell img { width: 34px; height: 34px; object-fit: contain; }
.slot-cell .slot-item { font-size: 14px; line-height: 1.05; color: var(--parchment); }
.slot-cell .slot-pow { font-size: 13px; color: var(--gold); }
.slot-cell .slot-empty { color: #57503f; margin-top: 16px; }
#equip-summary { margin-top: 12px; line-height: 1.45; border-top: 1px solid var(--edge); padding-top: 10px; }
#equip-summary b { color: var(--gold); font-weight: normal; }
.bank-note { color: var(--dim); font-size: 16px; margin-bottom: 10px; }
.inv-group { margin-bottom: 14px; }
.inv-head { color: var(--gold); border-bottom: 1px solid var(--edge); margin-bottom: 6px; text-transform: uppercase; font-size: 15px; letter-spacing: 1px; }
.inv-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.inv-item {
  display: grid; grid-template-columns: 38px 1fr; grid-template-rows: auto auto; column-gap: 8px;
  align-items: center;
  background: #2c261d; border: 2px solid var(--edge);
  padding: 6px 8px; cursor: pointer;
}
.inv-item img { grid-row: span 2; width: 34px; height: 34px; object-fit: contain; }
.inv-item:hover { border-color: var(--gold); }
.inv-item.equipped { border-color: var(--green); cursor: default; }
.inv-item .ii-name { font-size: 15px; line-height: 1.05; }
.inv-item .ii-pow { font-size: 13px; color: var(--dim); }
.inv-item .eqtag { color: var(--green); }

/* ------------------------------------------------- v1.2.4: clarity & UX --- */
/* Skeleton: never show fake level-1 zeros before the first payload lands. */
body.loading #stats .stat-line b,
body.loading .sk-cell b,
body.loading .sk-total b { color: var(--dim); opacity: .5; }
body.loading #boss-rows { opacity: .35; }
.bank-nudge { color: #ffd700; font-size: 15px; }
.style-proj { display: block; font-size: 12px; color: var(--dim); letter-spacing: 0; }
#train-styles button.on .style-proj { color: var(--gold); }
.away-upgrades {
  margin-top: 10px; padding: 8px 10px;
  background: #55461f; border: 2px solid var(--gold);
  line-height: 1.4;
}
.away-gear-link { font-size: 17px; }
.ui-dialog .modal-box { max-width: 420px; }
.ui-dialog-body { line-height: 1.45; margin-bottom: 14px; }
.ui-dialog-btns { display: flex; gap: 10px; }
.ui-dialog-btns button { flex: 1; padding: 10px 0; font-size: 18px; }
.ui-dialog-btns .ui-ok { border-color: var(--gold); color: var(--gold); }
.ui-dialog-btns .ui-ok.warn { border-color: var(--red); color: #ff8b7e; }
.up-dot { color: #ffd700; font-size: 12px; vertical-align: super; margin-left: 3px; animation: fightpulse 2s ease-in-out infinite; }
#mobile-tabs .up-dot { position: absolute; margin-left: 16px; margin-top: -2px; }
#mobile-tabs a { position: relative; }

/* Staging test-server banner — impossible to mistake for live. */
#staging-badge {
  position: fixed; top: 0; left: 50%; transform: translateX(-50%);
  z-index: 300; pointer-events: none;
  background: #7e150c; color: #ffd0c9; border: 2px solid #ff6b5e; border-top: none;
  padding: 2px 14px 3px; font-size: 15px; letter-spacing: 1px;
}

/* ---------------------------------------------- v1.3.0: attack types & forge */
.weak-tag { margin-left: 6px; font-size: 14px; letter-spacing: 1px; }
#train-styles { display: flex; gap: 6px; flex-wrap: wrap; }
#train-styles button { min-width: 30%; }
.lanes span { margin-right: 8px; white-space: nowrap; }
.lane-weak { color: #7fd47f; }
.lane-normal { color: var(--dim); }
.lane-stiff { color: #e07070; }

#forge-slots { display: flex; gap: 10px; margin: 10px 0; }
.forge-slot {
  flex: 1; min-height: 84px; border: 2px dashed var(--dim); border-radius: 6px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 6px; color: var(--dim); font-size: 15px;
}
.forge-slot.filled { border-style: solid; border-color: var(--gold); color: var(--parchment); cursor: pointer; }
.forge-slot img { width: 40px; height: 40px; object-fit: contain; }
.fs-name { font-size: 14px; text-align: center; }
#forge-actions { display: flex; gap: 8px; margin-bottom: 8px; }
#forge-go { background: #55461f; border-color: var(--gold); color: var(--gold); }
#forge-go:disabled { opacity: 0.5; }
#forge-result { padding: 8px 10px; border-radius: 6px; margin-bottom: 8px; background: var(--panel2); }
#forge-result.forge-ok { border-left: 3px solid #7fd47f; }
#forge-result.forge-hint { border-left: 3px solid var(--gold); }
#forge-result.forge-cold { border-left: 3px solid var(--dim); color: var(--dim); }
#forge-picker { display: flex; flex-wrap: wrap; gap: 4px; max-height: 180px; overflow-y: auto; }
.forge-pick { position: relative; width: 44px; height: 44px; padding: 2px; }
.forge-pick img { width: 100%; height: 100%; object-fit: contain; }
.forge-pick.on { border-color: var(--gold); background: #55461f; }
.forge-pick.mat { border-color: #8a6ad4; }
.fp-qty {
  position: absolute; right: 1px; bottom: 0; font-size: 12px; color: var(--gold);
  text-shadow: 1px 1px 0 #000;
}
.recipe-card {
  display: flex; align-items: center; gap: 10px; background: var(--panel2);
  border-radius: 6px; padding: 8px; margin-top: 8px;
}
.recipe-card img { width: 40px; height: 40px; object-fit: contain; }
.rc-body { flex: 1; }
.rc-name { font-size: 17px; }
.rc-parts { font-size: 14px; }
.rc-parts .ok { color: #7fd47f; }
.rc-parts .missing { color: #e07070; }
.rumor-card { background: var(--panel2); border-radius: 6px; padding: 8px 10px; margin-top: 8px; color: var(--dim); }

/* ------------------------------------------------- v1.3.1: bank/forge cleanup */
.inv-count { color: var(--dim); font-size: 13px; letter-spacing: 0; text-transform: none; }
#forge-search {
  width: 100%; margin-bottom: 8px; padding: 6px 10px; font-size: 16px;
  background: var(--panel2); border: 1px solid var(--edge); border-radius: 6px;
}
.forge-pick {
  width: 72px; height: 72px; padding: 3px 2px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.forge-pick img { width: 32px; height: 32px; object-fit: contain; flex: none; }
.fp-name {
  font-size: 11px; line-height: 1.1; color: var(--dim); max-width: 100%;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  text-align: center; letter-spacing: 0;
}
.forge-pick.on .fp-name { color: var(--gold); }

/* --------------------------------------------- v1.3.1: compact bank rework */
.bank-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.bank-card {
  position: relative; width: 104px; padding: 8px 4px 6px;
  background: var(--panel2); border: 1px solid var(--edge); border-radius: 6px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  cursor: pointer; text-align: center;
}
.bank-card:hover { border-color: var(--gold); }
.bank-card img { width: 36px; height: 36px; object-fit: contain; }
.bank-card.equipped { border-color: var(--gold); background: #4a3f22; }
.bank-card.ghost { opacity: .45; }
.bank-card.ghost img { filter: grayscale(1) brightness(.6); }
.bank-card.tagged { border-color: #c25b5b; }
.bank-card.tagged-last { border-color: #e03d3d; background: #4a2424; }
.bc-name {
  font-size: 13px; line-height: 1.15; max-width: 100%; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.bc-sub { font-size: 12px; color: var(--dim); max-width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.bc-qty {
  position: absolute; top: 2px; right: 4px; font-size: 12px; color: var(--gold);
  text-shadow: 1px 1px 0 #000;
}
.bc-worn { position: absolute; top: 2px; left: 4px; font-size: 13px; color: var(--gold); }
.bank-card .tag-badge {
  position: absolute; top: 22px; left: 0; right: 0; font-size: 11px;
  background: #e03d3dcc; color: #fff; padding: 1px 0; letter-spacing: 1px;
}
#item-sheet .modal-box { max-width: 440px; width: min(440px, 92vw); }
#item-sheet .sheet-head img { width: 64px; height: 64px; }
.sheet-worn { color: var(--gold); font-size: 14px; }

/* -------------------------------------- v1.3.1: docked item panel (no popup) */
.equip-layout { grid-template-columns: 340px 1fr 330px; }
@media (max-width: 1200px) { .equip-layout { grid-template-columns: 280px 1fr 300px; } }
#item-panel { position: sticky; top: 14px; max-height: calc(100vh - 28px); overflow-y: auto; }
.ip-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.ip-head h2 { margin-bottom: 0; }
#ip-close { padding: 2px 10px; }
.bank-card.selected { border-color: #fff; box-shadow: 0 0 0 1px #fff inset; }
#item-panel .sheet-btn { display: block; width: 100%; margin-top: 6px; }
@media (max-width: 900px) {
  /* HOTFIX v2.0.1: this appended block comes LATER in the cascade than the
   * original 900px single-column rule, so the 1200px three-column rule was
   * winning on phones and forcing ~580px of fixed columns onto a 375px
   * screen — the whole gear tab overflowed sideways. Single column must be
   * re-declared HERE, after the 1200px override. */
  .equip-layout { grid-template-columns: 1fr; }
  /* Phones: the panel is a bottom drawer that slides up on selection. */
  #item-panel {
    display: none; position: fixed; left: 8px; right: 8px;
    bottom: calc(66px + env(safe-area-inset-bottom)); top: auto;
    max-height: 60vh; overflow-y: auto; z-index: 60; margin: 0;
    box-shadow: 0 -6px 24px #000c; border: 1px solid var(--edge);
  }
  #item-panel.open { display: block; }
  /* Finger-sized close target for the drawer. */
  #ip-close { padding: 8px 16px; font-size: 18px; }
}

/* --------------------------------------------- v1.3.1: two-part forge rework */
#forge-slots { align-items: stretch; }
.forge-op { align-self: center; font-size: 26px; color: var(--dim); flex: none; padding: 0 2px; }
.forge-slot.result { border-style: solid; border-color: var(--edge); font-size: 30px; color: var(--dim); }
.forge-slot.result.known { border-color: var(--gold); color: var(--parchment); font-size: 15px; }
.forge-progress { display: flex; align-items: center; gap: 10px; margin: 4px 0 8px; }
.fp-bar { flex: 1; height: 10px; background: #221d15; border-radius: 5px; overflow: hidden; border: 1px solid var(--edge); }
.fp-bar div { height: 100%; background: var(--gold); }
.forge-progress span { color: var(--dim); font-size: 14px; white-space: nowrap; }
.rumor-card { display: flex; align-items: center; gap: 12px; }
.rum-pair { display: flex; align-items: center; gap: 6px; flex: none; }
.rum-pair img { width: 30px; height: 30px; object-fit: contain; }
.rum-partner { margin-top: 4px; font-size: 0.92em; }
.rum-q {
  width: 30px; height: 30px; border: 1px dashed var(--dim); border-radius: 4px;
  display: inline-flex; align-items: center; justify-content: center; font-size: 18px;
}
.forge-undisc { margin-top: 10px; }

/* v1.3.1: per-type accuracy strip on the item card */
.att-strip span { margin-right: 8px; white-space: nowrap; color: var(--dim); }
.att-strip .as-best { color: var(--gold); }
.att-strip .as-zero { opacity: .4; }

/* ---------------------------------------------- v1.3.1: themed SVG icons */
.ic { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; fill: currentColor; }
/* v3.45.0: the game's own skill icons (public/img/skills). They are 16-25px
 * sprites drawn to be read at that size, so they are sized in em to sit on the
 * line of whatever names them, and the global img rule keeps them unsmoothed.
 * object-fit preserves the shapes that are not square (Strength is 16x20). */
.sk-img {
  width: 1.15em; height: 1.15em; object-fit: contain;
  display: inline-block; vertical-align: -0.22em;
}
.sk-img-lg { width: 0.95em; height: 0.95em; vertical-align: -0.1em; }
.st-row .sk-img { flex: none; }
/* Hiscores: the icon IS the column header, so the cell only needs to be wide
 * enough for it and the level under it. */
table.board th.sk-col { text-align: center; padding-left: 4px; padding-right: 4px; }
.ic-dim { color: var(--dim); }
.bank-tabs button .ic, #forge-go .ic { margin-right: 4px; }
#mobile-tabs a .ic { width: 20px; height: 20px; display: block; margin: 0 auto 2px; }
/* The same tab sprites the header uses, sized for a phone's tab. They sit
 * inside .mt-ico, which is sized by FONT for the one emoji still in the map —
 * an image in there has no line box to sit on, so the wrapper is made a
 * centring flex box with a floor tall enough for the sprite. */
#mobile-tabs .mt-ico { display: flex; align-items: center; justify-content: center; min-height: 22px; }
#mobile-tabs .tab-ico { width: 22px; height: 22px; object-fit: contain; }
.att-strip .ic, .style-proj + .ic { vertical-align: -0.1em; }
#train-styles button .ic { margin-right: 2px; }
.weak-tag .ic { width: 0.85em; height: 0.85em; margin-left: 2px; color: #7fd47f; }
.bc-worn .ic, .sheet-worn .ic { color: var(--gold); }
.logo .ic-logo { color: var(--gold); width: 22px; height: 22px; vertical-align: -0.2em; }
.inv-head .ic { width: 0.9em; height: 0.9em; }

/* ----------------------------------------------------------- v2.0.2 hotfix */
/* Any content wider than the screen used to widen iOS Safari's layout
 * viewport, un-anchoring position:fixed bars (mobile tab bar drifting on the
 * Shop pane et al). Clip the axis at the root so no pane can ever do that. */
html, body { overflow-x: hidden; overflow-x: clip; }
.bank-tabs { flex-wrap: wrap; }
.pot-card, .forge-slot { min-width: 0; }
.fs-name, .rc-parts, .pot-name { overflow-wrap: anywhere; }
@media (max-width: 700px) {
  .bank-tabs button { padding: 8px 10px; font-size: 17px; }
}

/* ------------------------------------------------------- v2.2.0: chat --- */
/* The type-here strip: part of the same frame, never scrolls away. */
#chat-form {
  display: flex; gap: 6px; flex: none;
  padding: 6px; border-top: 2px solid var(--edge);
  background: #272219;
}
#chat-input {
  flex: 1; font-size: 17px; padding: 5px 9px;
  background: #1d1913; border-width: 1px;
}
#chat-send { flex: none; padding: 5px 14px; font-size: 16px; }
/* Emoji: VT323 has no emoji glyphs, so name the colour fonts explicitly
 * rather than trusting the monospace fallback to find one. */
#chat-inner, #chat-input, #emoji-picker, #chat-emoji {
  font-family: 'VT323', 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', monospace;
}
#chat-form { position: relative; }
#chat-emoji { flex: none; padding: 3px 8px; font-size: 18px; line-height: 1; }
#emoji-picker {
  position: absolute; right: 6px; bottom: calc(100% + 4px); z-index: 30;
  width: min(340px, calc(100vw - 32px)); max-height: 260px; overflow-y: auto;
  padding: 6px 8px; background: #1d1913; border: 2px solid var(--edge);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .5);
}
#emoji-picker.hidden { display: none; }
/* v3.20.0: chat filters — same frame as the emoji picker. */
#chat-filter { flex: none; padding: 3px 8px; line-height: 1; }
#chat-filter.on { color: var(--gold); border-color: var(--gold); }
#chat-filter-pop {
  position: absolute; right: 6px; bottom: calc(100% + 4px); z-index: 30;
  /* Sized to the widest filter label rather than a fixed 220px: the two
     columns are equal, so a label that wrapped (Collection log) made its
     button taller than the rest. max-content lets the popup grow instead. */
  width: min(max-content, calc(100vw - 32px));
  min-width: min(220px, calc(100vw - 32px));
  padding: 6px 10px; background: #1d1913; border: 2px solid var(--edge);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .5); font-size: 17px;
}
#chat-filter-pop label { display: block; padding: 2px 0; cursor: pointer; }
#chat-filter-pop .dim-note { font-size: 14px; margin-top: 4px; }
/* v3.29.0: the filters are toggle buttons. Lit = that category shows; dark =
   hidden. Two columns so seven categories stay compact in the popup. The
   show/hide-all button is the eighth cell, so all eight buttons are one
   size: equal columns, equal rows, no wrapped label stretching a row. */
#chat-filter-pop .filter-grid {
  display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: 1fr;
  gap: 4px; margin: 4px 0;
}
#chat-filter-pop .filter-grid button {
  width: 100%; padding: 3px 6px; font-size: 15px; text-align: center;
  white-space: nowrap;
  color: var(--dim); border-color: var(--edge-dark);
}
#chat-filter-pop .filter-grid button.on { color: var(--gold); border-color: var(--gold); }
/* Same size as the toggles, but a lit frame: it does something rather than
   reporting a state. */
#chat-filter-pop .filter-all { color: var(--parchment); border-color: var(--edge); }
#chat-inner.hide-players .line[data-cat="players"],
#chat-inner.hide-broadcasts .line[data-cat="broadcasts"],
#chat-inner.hide-drops .line[data-cat="drops"],
#chat-inner.hide-log .line[data-cat="log"],
#chat-inner.hide-levels .line[data-cat="levels"],
#chat-inner.hide-combat .line[data-cat="combat"],
#chat-inner.hide-hunter .line[data-cat="hunter"] { display: none; }
#emoji-picker .emoji-head { color: var(--dim); font-size: 15px; margin: 4px 0 2px; }
#emoji-picker .emoji-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(34px, 1fr)); }
#emoji-picker .emoji-grid button {
  font-size: 20px; line-height: 1; padding: 5px 0; border: none; background: none; cursor: pointer;
}
#emoji-picker .emoji-grid button:hover,
#emoji-picker .emoji-grid button:focus-visible { background: rgba(255, 152, 31, .18); }
/* Server announcements: the rarest thing in the game, so they read like an
 * event rather than a line of talk. No name, no crown, nothing clickable. */
#chat-inner .line.announce {
  color: var(--gold);
  background: rgba(255, 152, 31, .09);
  border-left: 3px solid var(--gold);
  padding: 2px 6px;
  margin: 3px 0;
}
/* v3.29.0: a finished collection log reads green rather than gold, matching
   the green-log language the rest of the game uses for the same idea. */
#chat-inner .line.announce.announce-greenlog {
  color: var(--green);
  background: rgba(53, 184, 57, .10);
  border-left-color: var(--green);
}
.chat-time { color: var(--dim); font-size: 14px; margin-right: 6px; }
#chat-inner .line.said { color: var(--parchment); }
#chat-inner .line.said .who { color: #7fd0ff; }
#chat-inner .line.said.mine .who { color: var(--gold); }

/* --------------------------------------------- v2.3.0: staff crowns --- */
/* A crown's colour is its meaning, so these two opt out of currentColor. */
/* v3.33.5: the administrator's crown, and only that one — a moderator wears
   no crown. An 11x11 pixel-art sprite drawn at its native size and never
   smoothed, so the pixels stay square. Half the old 22px, so it sits inside
   the cap height of a name instead of towering over it — hence the smaller
   baseline drop as well. */
.crown { display: inline-flex; align-items: center; margin-right: 4px; vertical-align: -0.05em; }
.crown img {
  width: 11px; height: 11px;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .55));
}

/* ------------------------------------------- v2.15.0: equipment page polish */
/* Clearer hierarchy on the gear page: titled panels with a one-line purpose,
 * a paper doll, search + category chips on the bank, and a details panel whose
 * actions say what they do. Scoped to this page so the rest of the site keeps
 * its look. */
body[data-page="equip"] {
  --card: #2f291f;
  --card-hi: #3a3226;
  --radius: 8px;
  --gold-soft: rgba(255, 152, 31, .12);
  --sell: #e8c04a;
}
.equip-layout { gap: 16px; padding: 16px; }
.equip-layout .panel {
  border-radius: 10px; padding: 14px 16px;
  background: linear-gradient(#433a2d, #3a3226 160px);
  box-shadow: 0 0 0 1px var(--edge) inset, 0 4px 16px rgba(0,0,0,.35);
}
.equip-layout button { border-radius: 6px; }
.equip-layout .qmark { border-radius: 50%; }
.equip-layout select, .equip-layout input { border-radius: 6px; }

/* Panel headers: icon, title, one line saying what the panel is for. */
.panel-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.ph-ico .ic { width: 30px; height: 30px; color: var(--gold); display: block; }
.ph-text h2 { margin-bottom: 0; font-size: 25px; line-height: 1; color: #f2e6cc; }
.ph-sub { color: var(--dim); font-size: 16px; line-height: 1.2; margin-top: 2px; }
.pane-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin: 2px 0 10px;
}
.pane-head .ph-text { min-width: 0; }

/* Tabs + gp: the page's top-level switch, sized like one. */
.bank-head { border-bottom: 1px solid var(--edge); padding-bottom: 12px; margin-bottom: 12px; }
.bank-tabs button {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 16px; font-size: 20px; justify-content: center;
  background: var(--card); border-color: var(--edge);
}
.bank-tabs button.on {
  background: linear-gradient(#6a5424, #4f3f1d);
  border-color: var(--gold); color: #ffcf6a;
  box-shadow: 0 0 0 1px rgba(255,152,31,.35), 0 0 12px rgba(255,152,31,.18);
}
#gp-line {
  display: inline-flex; align-items: center; gap: 8px; margin: 0;
  font-size: 24px; padding: 4px 14px; border-radius: 999px;
  background: var(--card); border: 1px solid var(--edge);
}
#gp-line .ic { color: #ffd700; width: 22px; height: 22px; }

/* Search */
.search-box {
  position: relative; display: flex; align-items: center;
  flex: 0 1 280px; min-width: 180px;
}
.search-box .sb-ico { position: absolute; left: 10px; color: var(--dim); pointer-events: none; display: flex; }
.search-box input { padding: 7px 10px 7px 34px; font-size: 18px; background: #221d15; border-width: 1px; }
.search-box input:focus { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold-soft); }

/* Quick tips: first visit only, one tap to dismiss for good. */
#tips {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px 14px;
  background: var(--card); border: 1px solid var(--edge); border-left: 3px solid var(--gold);
  border-radius: var(--radius); padding: 8px 12px; margin-bottom: 12px;
}
#tips.hidden { display: none !important; }
.tips-title { color: #ffcf6a; font-size: 19px; white-space: nowrap; }
.tips-title .ic { color: #ffd700; }
#tips ol {
  grid-column: 1 / -1; grid-row: 2; list-style: none; counter-reset: tip;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  gap: 6px 16px; font-size: 15px; line-height: 1.2;
}
#tips li { counter-increment: tip; display: flex; gap: 8px; align-items: center; }
#tips li::before {
  content: counter(tip); flex: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: #e8b53a; color: #2b251c;
  display: inline-flex; align-items: center; justify-content: center; font-size: 16px;
}
#tips li b { color: var(--parchment); font-weight: normal; text-decoration: underline dotted; }
#tips-close { padding: 3px 14px; font-size: 16px; }

/* Category chips */
#f-cats { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.cat-chip {
  padding: 5px 14px; font-size: 18px; background: var(--card);
  border: 1px solid var(--edge); border-radius: 999px !important;
}
.cat-chip .cc-n { color: var(--dim); font-size: 15px; margin-left: 2px; }
.cat-chip.on { background: #e8b53a; border-color: #e8b53a; color: #2b251c; }
.cat-chip.on .cc-n { color: #5a4516; }
.cat-chip:disabled { opacity: .4; cursor: default; }
.cat-chip:disabled:hover { border-color: var(--edge); color: var(--parchment); }

/* Secondary filters + bulk actions share one quiet row. */
#bank-toolbar { align-items: center; gap: 6px; margin-bottom: 14px; }
#bank-toolbar select { flex: 1 1 130px; min-width: 0; max-width: 190px; font-size: 16px; padding: 6px 8px; border-width: 1px; }
#bank-toolbar button { flex: 0 0 auto; min-width: 0; padding: 6px 12px; font-size: 16px; }
#bank-toolbar .qmark { padding: 0; }
.tb-split { flex: 1; }
#sell-dupes-all:disabled { opacity: .5; cursor: default; }
#sell-dupes-all:disabled:hover { border-color: var(--edge); color: var(--parchment); }

/* Bank grid: uniform, roomier cards; the art leads, the name labels it. */
.inv-head {
  display: flex; align-items: center; gap: 6px;
  color: #ffcf6a; border-bottom: 1px solid var(--edge);
  margin: 4px 0 8px; padding-bottom: 3px; font-size: 16px;
}
.inv-count {
  font-size: 13px; background: var(--card); border: 1px solid var(--edge);
  border-radius: 999px; padding: 0 7px; color: var(--dim);
}
.bank-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 8px; margin-bottom: 16px;
}
.bank-card {
  width: auto; min-height: 104px; padding: 10px 6px 8px; gap: 3px;
  background: var(--card); border: 1px solid var(--edge); border-radius: var(--radius);
  justify-content: center;
  transition: border-color .12s, background .12s, transform .12s;
}
.bank-card:hover { background: var(--card-hi); border-color: #8a7550; transform: translateY(-1px); }
.bank-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.bank-card img { width: 44px; height: 44px; }
.bc-name { font-size: 16px; line-height: 1.05; }
.bc-sub { font-size: 13px; }
.bc-qty { top: 4px; right: 7px; font-size: 15px; color: #f2e6cc; }
.bc-worn { top: 4px; left: 7px; font-size: 15px; }
.bc-lock { position: absolute; top: 4px; left: 7px; font-size: 15px; color: var(--parchment); }
.bc-worn + .bc-lock { left: 26px; }
/* v3.58.0: the level gate, read at a glance. The badge sits along the bottom
 * where bc-qty and bc-worn are not, and the card itself goes quiet — still
 * fully readable, because an item you are working towards is worth looking at.
 */
.bc-req {
  position: absolute; bottom: 4px; right: 5px;
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 12px; line-height: 1; padding: 2px 5px; border-radius: 999px;
  background: rgba(30, 26, 19, .82); border: 1px solid #7a4a42; color: #ff8b7e;
}
.bank-card.req-locked img { opacity: .55; }
.bank-card.req-locked .bc-name { color: var(--dim); }
.bank-card.equipped { background: #3d3420; border-color: #7a6228; }
.bank-card.selected {
  border-color: #ffcf6a; background: #4a3d1e;
  box-shadow: 0 0 0 1px #ffcf6a inset, 0 0 14px rgba(255,207,106,.3);
}
.bank-card .tag-badge { top: auto; bottom: 0; border-radius: 0 0 var(--radius) var(--radius); }
.bank-empty { color: var(--dim); text-align: center; padding: 36px 0; font-size: 18px; }
#bank-count { text-align: center; padding-top: 8px; border-top: 1px solid var(--edge); }
#bank-count:empty { display: none; }

/* Paper doll: the OSRS equipment-tab shape. */
#slots-grid { display: block; }
/* v2.31.0: the real OSRS equipment-tab arrangement — helm on top, cape and
 * amulet below it, weapon/body/shield across the middle, legs beneath, then
 * gloves/boots/ring along the bottom. (OSRS puts ammo beside the amulet; this
 * game has no ammo slot, so that cell stays empty.) */
#slots-grid .doll {
  display: grid; gap: 8px;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas:
    ".      helm     ."
    "cape   necklace ."
    "weapon body     shield"
    ".      legs     ."
    "gloves boots    ring";
}
.slot-weapon { grid-area: weapon; } .slot-helm { grid-area: helm; } .slot-body { grid-area: body; }
.slot-legs { grid-area: legs; } .slot-shield { grid-area: shield; } .slot-gloves { grid-area: gloves; }
.slot-boots { grid-area: boots; } .slot-ring { grid-area: ring; } .slot-necklace { grid-area: necklace; }
.slot-cape { grid-area: cape; }
.doll-sub { color: var(--dim); font-size: 15px; margin: 12px 0 6px; border-bottom: 1px solid var(--edge); }
.doll-trinkets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.slot-cell {
  position: relative; min-height: 92px; padding: 5px 4px 6px;
  background: var(--card); border: 1px solid var(--edge); border-radius: var(--radius);
  justify-content: center;
}
.slot-cell .slot-name { font-size: 12px; }
.slot-cell img { width: 38px; height: 38px; }
.slot-cell .slot-item { font-size: 15px; }
.slot-cell .slot-pow { font-size: 14px; }
.slot-cell .slot-sil { width: 30px; height: 30px; color: #4d4436; margin: 4px 0 2px; }
.slot-cell .slot-empty { color: #6b614f; margin: 0; font-size: 14px; }
.slot-cell.filled { border-color: #7a6228; background: linear-gradient(#3d3420, var(--card)); }
/* v3.62.0: the shield square while a two-handed weapon is worn — shut, not
 * empty, so it reads as "nothing goes here" rather than "put something here". */
.slot-cell.barred { border-style: dashed; opacity: .55; }
.slot-cell.barred .slot-sil { color: #3c3529; }
.slot-barred {
  position: absolute; bottom: 4px; left: 0; right: 0; text-align: center;
  font-size: 12px; letter-spacing: 1px; color: #6b614f; text-transform: uppercase;
}
.slot-cell.filled:hover, .slot-cell.filled:focus-visible { border-color: #e07070; outline: none; }
.slot-off {
  position: absolute; top: 2px; right: 5px; font-size: 13px; color: #e07070;
  opacity: 0; transition: opacity .12s;
}
.slot-cell.filled:hover .slot-off, .slot-cell.filled:focus-visible .slot-off { opacity: 1; }
@media (hover: none) { .slot-off { opacity: .6; } }

/* Your stats */
#equip-summary {
  margin-top: 14px; padding: 12px; line-height: 1.2;
  background: var(--card); border: 1px solid var(--edge); border-radius: var(--radius);
}
#equip-summary h3 { font-size: 21px; color: #f2e6cc; font-weight: normal; margin-bottom: 6px; }
.st-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; }
.st-list { display: grid; gap: 3px; }
.st-row { display: flex; align-items: center; gap: 7px; font-size: 18px; min-width: 0; }
.st-row .ic { flex: none; color: var(--dim); }
.st-label { color: var(--parchment); white-space: nowrap; }
.st-row b { color: #ffcf6a; font-weight: normal; margin-left: auto; }
.st-list .st-row b { margin-left: 6px; }
.st-sep { height: 1px; background: var(--edge); margin: 8px 0; }
.st-vs { color: var(--dim); font-size: 16px; margin: 8px 0 3px; }
/* Armour sets on the gear sheet */
.set-row .st-label { white-space: normal; }
.set-state { margin-left: auto; font-size: 15px; color: var(--dim); text-align: right; }
.set-row.active .set-state, .sheet-set.active .set-state { color: var(--green); }
.set-row.active .st-row .ic { color: var(--green); }
.set-missing { font-size: 15px; margin: 0 0 4px 23px; line-height: 1.1; }
.st-power { margin-top: 8px; }
.st-power b { color: var(--parchment); font-weight: normal; }

/* Item details */
#item-panel .ip-head { border-bottom: 1px solid var(--edge); padding-bottom: 8px; margin-bottom: 12px; }
#item-panel .ip-head h2 { font-size: 25px; color: #f2e6cc; }
#ip-close { width: 36px; height: 36px; padding: 0; font-size: 18px; }
.sheet-placeholder {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  text-align: center; color: var(--dim); padding: 40px 12px;
}
.sheet-placeholder .ic { width: 40px; height: 40px; color: #5a4e3c; }
#item-panel .sheet-head {
  gap: 14px; padding: 10px; margin-bottom: 12px;
  background: var(--card); border: 1px solid var(--edge); border-radius: var(--radius);
}
.sheet-art {
  flex: none; width: 76px; height: 76px; border-radius: var(--radius);
  background: radial-gradient(circle at 50% 45%, #4a4034, #2b251c);
  display: flex; align-items: center; justify-content: center;
}
#item-panel .sheet-head img { width: 60px; height: 60px; }
#item-panel .sheet-name { font-size: 25px; line-height: 1; color: #f2e6cc; }
.sheet-worn, .sheet-owned {
  display: inline-flex; align-items: center; gap: 5px; margin-top: 5px;
  font-size: 15px; padding: 1px 10px; border-radius: 999px;
}
.sheet-worn { color: #ffcf6a; border: 1px solid #b58a2a; background: rgba(232,181,58,.12); }
.sheet-owned { color: var(--parchment); border: 1px solid var(--edge); }
.sheet-locked { color: var(--dim); font-size: 14px; margin-top: 4px; }
/* The "Requires" row: every level a piece asks for, the unmet ones marked.
 * Met requirements stay visible rather than disappearing, so the row reads
 * the same before and after the levels arrive. */
.sheet-reqs { display: flex; flex-wrap: wrap; gap: 4px 12px; }
.sheet-reqs .req { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.sheet-reqs .req.met { color: var(--green); }
.sheet-reqs .req.short { color: #ff8b7e; }
.sheet-reqs .req-have { color: var(--dim); font-size: 14px; }
#item-panel .sheet-rows { gap: 6px 14px; font-size: 18px; padding: 0 2px; }
#item-panel .sheet-rows > div:nth-child(odd) { white-space: nowrap; }
.att-strip { display: flex; flex-wrap: wrap; gap: 3px 12px; }
.att-strip span { margin: 0; }
.sheet-actions {
  display: flex; flex-direction: column; gap: 8px;
  border-top: 1px solid var(--edge); padding-top: 12px;
}
#item-panel .sheet-btn {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  margin: 0; padding: 9px 10px; font-size: 20px; line-height: 1.1;
  background: var(--card); border: 1px solid #6a5d48; border-radius: var(--radius);
}
.sb-main { display: inline-flex; align-items: center; gap: 7px; }
.sb-sub { font-size: 14px; color: var(--dim); }
#item-panel .sheet-btn:hover { border-color: var(--gold); color: #ffcf6a; }
#item-panel .sheet-btn.primary {
  background: linear-gradient(#f0c24e, #d9a431); border-color: #f5d27a; color: #2b251c;
}
#item-panel .sheet-btn.primary .sb-sub { color: #5a4516; }
#item-panel .sheet-btn.primary:hover { background: linear-gradient(#f7d066, #e4b03c); color: #1e1a13; }
#item-panel .sheet-btn.gold { border-color: #8a7430; color: #ffd700; }
#item-panel .sheet-btn.warn { background: #4a2622; border-color: #9c3a31; color: #ff9d90; }
#item-panel .sheet-btn.warn .sb-sub { color: #d99a92; }
#item-panel .sheet-btn.warn:hover { background: #5a2c27; border-color: #e0584b; color: #ffb3a8; }
/* v3.58.0: a piece the player has not got the levels for. Deliberately not
 * disabled — it still goes to the server, whose refusal names the exact
 * shortfall — but it reads as a lock rather than an invitation. */
#item-panel .sheet-btn.locked {
  background: #332c22; border-color: #6a5d48; color: var(--dim); border-style: dashed;
}
#item-panel .sheet-btn.locked:hover { border-color: #8a7a5e; color: var(--parchment); }
.sell-qty { margin-top: 0; }

/* Shop & forge share the page's card language. */
#bank .pg-head { margin-top: 16px; margin-bottom: 8px; color: #ffcf6a; }
/* The shop stacks five or six shelves under one another — full bank spacing
 * between them is most of why the pane used to scroll. */
#shop-pane .pg-head { margin-top: 12px; margin-bottom: 4px; }
#shop-pane .pane-head { margin-bottom: 6px; }
.pot-card { border-radius: var(--radius); border-width: 1px; background: var(--card); }
.recipe-card, .rumor-card, #forge-result { border: 1px solid var(--edge); }

@media (max-width: 1400px) {
  .bank-tabs button { padding: 6px 11px; font-size: 18px; }
  #gp-line { font-size: 20px; padding: 3px 12px; }
  .equip-layout .panel { padding: 12px; }
}
@media (max-width: 900px) {
  /* `1fr` alone is minmax(auto, 1fr): the column would size to its widest
   * min-content (the scrolling chip row, the tip grid) and run off a phone. */
  .equip-layout { grid-template-columns: minmax(0, 1fr); }
  .equip-layout > .panel { min-width: 0; }
  #item-panel.open { border-radius: 12px 12px 8px 8px; }
  .search-box { flex: 1 1 100%; }
}
@media (max-width: 700px) {
  .equip-layout { padding: 10px; gap: 10px; }
  .equip-layout .panel { padding: 12px; }
  .bank-tabs { flex: 1; }
  .bank-tabs button { flex: 1; padding: 8px 6px; font-size: 18px; }
  #gp-line { font-size: 20px; }
  .bank-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 6px; }
  /* Two across on a phone rather than one: the cards are text, not artwork. */
  .shop-grid { grid-template-columns: repeat(auto-fill, minmax(116px, 1fr)); gap: 6px; }
  .bank-card { min-height: 92px; padding: 8px 4px 6px; }
  .bank-card img { width: 38px; height: 38px; }
  .bc-name { font-size: 15px; }
  #f-cats { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; scrollbar-width: none; }
  .cat-chip { flex: none; }
  #bank-toolbar select { flex: 1 1 30%; max-width: none; }
  .tb-split { display: none; }
  #bank-toolbar button:not(.qmark) { flex: 1 1 40%; }
  .slot-cell { min-height: 84px; }
  .slot-cell .slot-item { font-size: 14px; }
}

/* --------------------------------------------- v2.20.0: gear advice --- */
#gear-advice { margin-top: 12px; }
.adv-line { margin: 4px 0; line-height: 1.4; }
.adv-line b { color: var(--gold); font-weight: normal; text-transform: capitalize; }
.adv-list { display: flex; flex-direction: column; gap: 3px; margin: 6px 0; }
.adv-row { display: flex; align-items: center; gap: 8px; }
.adv-slot { color: var(--dim); min-width: 74px; text-transform: capitalize; }
.adv-item { display: inline-flex; align-items: center; gap: 5px; color: var(--parchment); }
.adv-item img { width: 20px; height: 20px; object-fit: contain; image-rendering: pixelated; }
.adv-equip { display: block; width: 100%; margin-top: 8px; padding: 9px 0; border-color: var(--gold); color: var(--gold); }
/* v3.43.0: melee / magic / ranged under the main button — one row, three
   equal columns, the same total width as the button above them. */
.adv-equip-row { display: flex; gap: 6px; margin-top: 6px; }
.adv-equip-row .adv-equip-style {
  flex: 1 1 0; width: auto; min-width: 0; margin-top: 0;
  padding: 8px 2px; font-size: 15px;
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
}
.adv-equip-row .adv-equip-style span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adv-equip-row .adv-equip-style .ic { flex: none; }
.adv-equip-row .adv-equip-style[disabled] { opacity: .45; cursor: not-allowed; border-color: var(--edge); color: var(--dim); }

/* ------------------------------------------ v2.22.0: who is out there --- */
.boss-row .bhere {
  display: inline-flex; align-items: center; gap: 2px;
  color: var(--green); font-size: 15px; flex: none;
}
.boss-row .bhere svg { width: 13px; height: 13px; }
/* Someone is actually at the keyboard there (inside the rally window); a
 * dimmer badge means they are away but still banking kills. */
.boss-row .bhere { color: var(--dim); }
.boss-row .bhere.live { color: var(--green); }
.boss-row.locked .bhere { color: var(--dim); }

/* ----------------------------------------- v2.24.0: lane glyph tooltips --- */
/* The glyphs are explanations on demand, so they advertise themselves as
 * hoverable/tappable rather than looking like plain text. */
.weak-tag, .lane-cell { cursor: help; }
.lane-cell { white-space: nowrap; }
@media (hover: none) { .weak-tag, .lane-cell { cursor: pointer; } }

/* Shop cards for gear you have not earned yet (skill capes). */
.pot-card.locked { opacity: .72; }
.pot-card.locked .pot-name { color: var(--dim); }

/* ------------------------------------ v2.31.1: a tighter worn-gear doll --- */
/* Icon-only squares, sized to the art rather than to three lines of text —
 * the name and stats are in the tooltip (see renderDoll). */
#slots-grid .doll,
.doll-trinkets {
  grid-template-columns: repeat(3, 62px);
  justify-content: center;
  gap: 6px;
}
.slot-cell {
  min-height: 0; height: 62px; width: 62px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  cursor: help;
}
.slot-cell.filled { cursor: pointer; }
.slot-cell img { width: 42px; height: 42px; }
.slot-cell .slot-sil { width: 26px; height: 26px; margin: 0; }
.doll-sub { margin: 10px 0 6px; text-align: center; }
.slot-off { top: 0; right: 3px; font-size: 12px; }
@media (max-width: 700px) {
  #slots-grid .doll, .doll-trinkets { grid-template-columns: repeat(3, 56px); gap: 5px; }
  .slot-cell { height: 56px; width: 56px; min-height: 0; }
  .slot-cell img { width: 38px; height: 38px; }
}

/* ------------------------------------------------------- fight caves --- */
/* v3.1.0. The one screen in the game that has to be readable at a glance
 * while you are clicking: tells are big, coloured by style, and the bar
 * inside them is the time you have left. */
.caves-layout { max-width: 900px; margin: 0 auto; padding: 14px; }
.caves-blurb { color: var(--parchment); margin: 0 0 12px; }
.caves-rules { display: grid; gap: 8px; margin-bottom: 14px; }
.caves-rules > div { background: var(--panel2); border: 1px solid var(--edge); padding: 8px 10px; }
.caves-rules b { color: var(--gold); }
.caves-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin-bottom: 12px; }
.cv-stat { background: var(--panel2); border: 1px solid var(--edge); padding: 6px 10px; display: flex; justify-content: space-between; }
.cv-stat span { color: var(--dim); }
.cv-stat b { color: var(--gold); }
.caves-msg { min-height: 24px; color: var(--parchment); }
.caves-msg.bad { color: var(--red); }
.caves-msg.good { color: var(--gold); }
.caves-altar { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; margin-top: 14px; }
.caves-altar > div { background: var(--panel2); border: 1px solid var(--edge); padding: 8px 10px; }
.caves-altar button { margin: 4px 0; max-width: 100%; }
.caves-altar button:disabled { opacity: .5; cursor: not-allowed; }
.big-btn {
  font-family: inherit; font-size: 22px; padding: 10px 18px; cursor: pointer;
  background: var(--panel2); color: var(--gold); border: 1px solid var(--gold);
}
.big-btn:hover { background: #55461f; }
.big-btn:disabled { opacity: .5; cursor: not-allowed; }
.link-btn { font-family: inherit; font-size: 17px; background: none; border: none; color: var(--dim); cursor: pointer; text-decoration: underline; }
.link-btn:hover { color: var(--parchment); }

.caves-top { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 10px; }
.caves-wave { font-size: 24px; color: var(--gold); }
.caves-vitals { flex: 1; min-width: 240px; display: grid; gap: 4px; }
.cv-bar-wrap { display: flex; align-items: center; gap: 8px; }
.cv-label { color: var(--dim); width: 56px; }
.cv-bar { flex: 1; height: 14px; background: var(--edge-dark); border: 1px solid var(--edge); }
.cv-bar > div { height: 100%; transition: width .1s linear; }
.cv-bar.hp > div { background: var(--green); }
.cv-bar.pray > div { background: #4c8fd6; }
body.cv-low-hp .cv-bar.hp > div { background: var(--red); }
body.cv-low-prayer .cv-bar.pray > div { background: var(--gold); }
.caves-streak { color: var(--dim); }
.caves-streak span { color: var(--gold); font-size: 22px; }

.cv-arena { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; min-height: 260px; align-items: flex-end; padding: 10px 0; }
.cv-mob {
  position: relative; width: 150px; padding: 8px; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--edge); text-align: center;
}
.cv-mob:hover { border-color: var(--parchment); }
.cv-mob.targeted { border-color: var(--gold); background: #55461f; }
.cv-mob img { width: 90px; height: 90px; object-fit: contain; }
.cv-mob-name { color: var(--parchment); font-size: 17px; }
.cv-mob-hp { height: 8px; background: var(--edge-dark); border: 1px solid var(--edge); margin-top: 4px; }
.cv-mob-hp > div { height: 100%; background: var(--red); }

/* The tell: what it is about to do, and how long you have to answer it. */
.cv-tell { height: 44px; margin: -4px 0 2px; }
.cv-tell.show {
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  border: 2px solid; padding: 2px 4px; font-size: 20px;
}
.cv-tell.show > i { display: block; height: 5px; background: currentColor; transition: width .05s linear; }
.cv-tell.melee { color: #ff6b4a; border-color: #ff6b4a; }
.cv-tell.ranged { color: #6fd36f; border-color: #6fd36f; }
.cv-tell.magic { color: #6fa8ff; border-color: #6fa8ff; }
.cv-tell.urgent { animation: cv-flash .25s steps(2, end) infinite; }
@keyframes cv-flash { to { opacity: .45; } }

.cv-protect { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin: 10px 0 6px; }
.cv-pray-btn {
  font-family: inherit; font-size: 22px; padding: 10px 16px; min-width: 120px; cursor: pointer;
  background: var(--panel2); color: var(--parchment); border: 2px solid var(--edge);
}
.cv-pray-btn[data-style="melee"].armed { color: #ff6b4a; border-color: #ff6b4a; background: #4a2a22; }
.cv-pray-btn[data-style="ranged"].armed { color: #6fd36f; border-color: #6fd36f; background: #26401f; }
.cv-pray-btn[data-style="magic"].armed { color: #6fa8ff; border-color: #6fa8ff; background: #1f2f4a; }
.cv-pray-btn .cv-key { color: var(--dim); font-size: 16px; margin-right: 6px; }
.cv-hint { color: var(--dim); font-size: 16px; text-align: center; margin-bottom: 8px; }

.cv-res-line { font-size: 21px; color: var(--parchment); margin-bottom: 4px; }
.cv-res-line.good { color: var(--gold); }
.cv-res-item { display: flex; align-items: center; gap: 8px; color: var(--gold); font-size: 22px; margin-top: 8px; }
.cv-res-item img { width: 40px; height: 40px; object-fit: contain; }
/* v3.56.0: every minigame's result screen ends with a pair of buttons —
   straight back in, or out to the lobby. They wrap on a phone. */
.cv-res-btns { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }

@media (max-width: 700px) {
  .cv-mob { width: 110px; }
  .cv-mob img { width: 64px; height: 64px; }
  .cv-pray-btn { min-width: 0; flex: 1; font-size: 19px; padding: 12px 6px; }
  .cv-arena { min-height: 200px; }
}

/* v3.5.0 Puro-Puro. The field is the maze itself; implings are positioned
 * by percentage so the round scales with the page, and the ring that
 * follows the cursor is exactly the net's reach. */
.pp-count { font-size: 22px; color: var(--parchment); }
.pp-count b { color: var(--gold); }
.pp-field {
  position: relative; width: 100%; aspect-ratio: 2 / 1; overflow: hidden;
  background: #3b3214 url('/img/ui/puro-puro.jpg') center / cover no-repeat;
  border: 1px solid var(--edge); cursor: crosshair; user-select: none; touch-action: none;
}
.pp-imp {
  position: absolute; width: 7%; aspect-ratio: 1; object-fit: contain;
  transform: translate(-50%, -50%); pointer-events: none;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,.7)) drop-shadow(0 0 6px rgba(160, 255, 160, .6));
}
.pp-net {
  position: absolute; border: 2px solid rgba(255, 240, 180, .85); border-radius: 50%;
  transform: translate(-50%, -50%); pointer-events: none; left: -100px; top: -100px;
  box-shadow: 0 0 6px rgba(0,0,0,.6);
}
.pp-net.cooling { border-color: rgba(160, 160, 160, .5); }
.pp-pop {
  position: absolute; transform: translate(-50%, -100%); pointer-events: none;
  font-size: 20px; text-shadow: 1px 1px 0 #000; animation: ppPop 0.9s ease-out forwards;
}
.pp-pop.good { color: #7dff7d; }
.pp-pop.bad { color: #ff9a9a; }
@keyframes ppPop { to { transform: translate(-50%, -180%); opacity: 0; } }

/* v3.5.1 the Minigames hub: one card per minigame, the whole card a link. */
.mg-grid { display: grid; gap: 12px; }
.mg-card {
  display: flex; gap: 14px; align-items: center; padding: 12px;
  background: var(--panel2); border: 1px solid var(--edge); color: var(--parchment); text-decoration: none;
}
.mg-card:hover { border-color: var(--gold); }
.mg-card img { width: 84px; height: 84px; object-fit: contain; flex: none; }
.mg-name { font-size: 24px; color: var(--gold); }
.mg-blurb { margin: 2px 0 6px; }
.mg-stats { color: var(--dim); }
@media (max-width: 520px) { .mg-card img { width: 56px; height: 56px; } }

/* v3.6.0 Tithe Farm. Plots, the barrel and the farmer are placed by
 * percentage of the field, like Puro-Puro's implings. A plot's look is its
 * engine status; the bar under it is the time left in the current stage
 * (or before a ripe plant rots). */
.tf-field {
  position: relative; width: 100%; aspect-ratio: 3 / 2; overflow: hidden;
  background:
    repeating-linear-gradient(90deg, rgba(0,0,0,.05) 0 2px, transparent 2px 22px),
    radial-gradient(ellipse at 50% 40%, #5f7a2e, #3e5220 75%);
  border: 1px solid var(--edge); user-select: none; touch-action: none;
}
.tf-plot {
  position: absolute; width: 16%; aspect-ratio: 1; transform: translate(-50%, -50%);
  background: #4a3120; border: 2px solid #2c1c10; border-radius: 4px; cursor: pointer;
  box-shadow: inset 0 0 0 3px rgba(0,0,0,.15), 0 2px 4px rgba(0,0,0,.5);
  background-image: repeating-linear-gradient(0deg, rgba(0,0,0,.18) 0 3px, transparent 3px 9px);
}
.tf-plot:hover { border-color: var(--gold); }
.tf-plot.growing.dry { background-color: #7a5a3a; }
.tf-plot.growing.wet { background-color: #33200f; }
.tf-plot.blighted { background-color: #4b4740; filter: grayscale(.7); }
.tf-crop {
  position: absolute; inset: 14%; background: center / contain no-repeat;
  pointer-events: none; transform-origin: 50% 90%;
}
/* The seedling grows a little each stage; a ripe plant shows its fruit. */
.tf-plot.growing .tf-crop, .tf-plot.blighted .tf-crop {
  background-image: none !important;
}
.tf-plot.growing .tf-crop::before, .tf-plot.blighted .tf-crop::before {
  content: ''; position: absolute; left: 50%; bottom: 8%; width: 14%; height: 70%;
  background: #6fbf3a; border-radius: 3px; transform: translateX(-50%);
  box-shadow: -9px -8px 0 -1px #7fd24a, 9px -14px 0 -1px #7fd24a;
}
.tf-plot.stage0 .tf-crop { transform: scale(.4); }
.tf-plot.stage1 .tf-crop { transform: scale(.65); }
.tf-plot.stage2 .tf-crop { transform: scale(.9); }
.tf-plot.blighted .tf-crop::before { background: #6b5f4a; box-shadow: -9px -4px 0 -1px #5a5040, 9px -8px 0 -1px #5a5040; transform: translateX(-50%) rotate(18deg); }
.tf-plot.grown .tf-crop { filter: drop-shadow(0 0 5px rgba(255, 220, 90, .8)); }
/* A plant that needs water now wears a drop; one about to be lost pulses. */
.tf-plot.growing.dry::after {
  content: ''; position: absolute; right: 6%; top: 6%; width: 18%; aspect-ratio: 1;
  background: #5fb6ff; border-radius: 0 50% 50% 50%; transform: rotate(45deg);
  box-shadow: 0 0 4px rgba(0,0,0,.6);
}
.tf-plot.urgent { animation: tfPulse .6s ease-in-out infinite alternate; }
@keyframes tfPulse { to { box-shadow: 0 0 0 3px #ff5a3a, 0 0 12px #ff5a3a; } }
.tf-bar { position: absolute; left: 6%; right: 6%; bottom: 5%; height: 6%; background: rgba(0,0,0,.45); pointer-events: none; }
.tf-bar i { display: block; height: 100%; width: 0; background: #7fd24a; }
.tf-plot.dry .tf-bar i, .tf-plot.urgent .tf-bar i { background: #ff9a3a; }
.tf-plot.grown .tf-bar i { background: var(--gold); }
.tf-q {
  position: absolute; left: -6px; top: -8px; min-width: 16px; padding: 0 3px;
  background: var(--gold); color: #1a1208; font-size: 16px; line-height: 16px; text-align: center;
  border-radius: 8px; pointer-events: none;
}
.tf-q:empty { display: none; }
.tf-barrel {
  position: absolute; width: 10%; aspect-ratio: 3 / 4; transform: translate(-50%, -50%); cursor: pointer;
}
.tf-barrel img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; filter: drop-shadow(0 2px 3px rgba(0,0,0,.7)); }
.tf-barrel:hover img { filter: drop-shadow(0 0 4px var(--gold)); }
.tf-farmer {
  position: absolute; width: 7%; aspect-ratio: 1; transform: translate(-50%, -85%);
  pointer-events: none; z-index: 2;
}
.tf-farmer img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 2px 2px rgba(0,0,0,.8)); }
.tf-can-ic { width: 22px; height: 22px; vertical-align: -4px; margin-right: 4px; }

/* v3.7.0 Barbarian Assault. Three lanes run from the cave mouth (right) to
 * the gate (left); the Penance are placed by percentage along their lane.
 * The two calls sit above the field and flash when they change. */
.ba-hp { display: flex; align-items: center; gap: 8px; font-size: 20px; }
.ba-hp-bar { width: 140px; height: 12px; background: #3a1010; border: 1px solid var(--edge); }
.ba-hp-bar i { display: block; height: 100%; width: 100%; background: #c8322a; transition: width .2s; }
.ba-calls { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 8px 0; }
.ba-call {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  background: var(--panel2); border: 1px solid var(--edge); padding: 6px 10px; font-size: 20px;
}
.ba-call b { font-size: 26px; color: var(--gold); }
.ba-call small { color: var(--dim); margin-left: auto; font-size: 16px; }
.ba-call[data-v="melee"] b { color: #ff7a6a; }
.ba-call[data-v="ranged"] b { color: #8fe36a; }
.ba-call[data-v="magic"] b { color: #7ab4ff; }
.ba-call.flash { animation: baFlash 1s ease-out; }
@keyframes baFlash { from { background: rgba(255, 200, 80, .55); } }
.ba-field {
  position: relative; width: 100%; aspect-ratio: 5 / 2; overflow: hidden; user-select: none; touch-action: none;
  background:
    repeating-linear-gradient(180deg, transparent 0 calc(33.333% - 2px), rgba(0,0,0,.35) calc(33.333% - 2px) 33.333%),
    linear-gradient(90deg, #6b5a3a, #7d6a45 40%, #4a3b28);
  border: 1px solid var(--edge);
}
.ba-gate {
  position: absolute; left: 0; top: 0; bottom: 0; width: 3%;
  background: repeating-linear-gradient(180deg, #5a3d1f 0 10px, #3b2712 10px 12px);
  box-shadow: 2px 0 6px rgba(0,0,0,.6);
}
.ba-mob {
  position: absolute; width: 9%; aspect-ratio: 1; transform: translate(-50%, -50%); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
}
.ba-mob img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; filter: drop-shadow(0 2px 3px rgba(0,0,0,.7)); }
.ba-mob:hover img { filter: drop-shadow(0 0 5px var(--gold)); }
.ba-runner { width: 7%; }
.ba-queen { width: 15%; z-index: 1; }
.ba-pips { position: absolute; bottom: -4px; font-size: 14px; line-height: 1; color: #ff6a5a; text-shadow: 1px 1px 0 #000; pointer-events: none; white-space: nowrap; }
.ba-queen .ba-pips { font-size: 18px; color: var(--gold); }
.ba-mob.shrug img { animation: baShrug .3s; }
@keyframes baShrug { 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
.ba-loadout { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
.ba-picks { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.ba-pick {
  display: flex; align-items: center; justify-content: center; gap: 6px; padding: 6px 4px; font-size: 18px;
  background: var(--panel2); border: 2px solid var(--edge); color: var(--parchment); cursor: pointer;
}
.ba-pick img { width: 22px; height: 22px; object-fit: contain; }
.ba-pick kbd { font-size: 14px; color: var(--dim); }
.ba-pick.on { border-color: var(--gold); background: #3b3220; color: var(--gold); }
.ba-shop-h { margin: 14px 0 6px; }
.ba-shop { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; }
.ba-shop-item { display: flex; align-items: center; gap: 10px; background: var(--panel2); border: 1px solid var(--edge); padding: 6px 10px; }
.ba-shop-item img { width: 36px; height: 36px; object-fit: contain; }
.ba-shop-item button { margin-left: auto; }
@media (max-width: 520px) {
  .ba-calls, .ba-loadout { grid-template-columns: 1fr; }
  .ba-pick kbd { display: none; }
  .ba-hp-bar { width: 80px; }
}

/* v3.8.0 Mage Training Arena. The lobby's four room cards and the shop's
 * cost chips share one colour per room; each room then draws its own stage
 * under a thin bar showing how long the current action has left. */
.mta-c-telekinetic { --room: #7ab4ff; }
.mta-c-alchemist { --room: #ffd24a; }
.mta-c-enchanting { --room: #c58bff; }
.mta-c-graveyard { --room: #8fe36a; }
.mta-rooms { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px; margin: 10px 0; }
.mta-room {
  display: flex; flex-direction: column; gap: 6px; padding: 8px 10px;
  background: var(--panel2); border: 1px solid var(--edge); border-top: 3px solid var(--room);
}
.mta-room-h { font-size: 22px; color: var(--room); }
.mta-room-pts { margin-top: auto; }
.mta-room button { align-self: flex-start; }
.mta-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.mta-chip { font-size: 15px; padding: 0 4px; border: 1px solid var(--room); color: var(--room); white-space: nowrap; }
.mta-shop .ba-shop-item { min-height: 58px; }
.mta-note { margin: 6px 0 0; }
.mta-room-name { font-size: 22px; color: var(--gold); }
.mta-busy { height: 4px; margin: 6px 0; background: rgba(0,0,0,.35); }
.mta-busy i { display: block; height: 100%; width: 0; background: var(--gold); }
.mta-stage { user-select: none; touch-action: manipulation; }
.mta-stage button { cursor: pointer; }
.mta-stage kbd { font-size: 13px; color: var(--dim); margin-left: 4px; }

/* Telekinetic Theatre: the maze is a square grid; the statue sits on top
 * and slides from cell to cell. */
.mta-tk-info { display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap; font-size: 20px; margin-bottom: 6px; }
.mta-tk-wrap { position: relative; width: min(100%, 420px); aspect-ratio: 1; margin: 0 auto; }
.mta-tk-grid {
  display: grid; grid-template-columns: repeat(var(--n), 1fr); width: 100%; height: 100%;
  background: #3c3a4a; border: 2px solid var(--edge);
}
.mta-tk-grid > div { border: 1px solid rgba(0,0,0,.25); }
.mta-tk-grid .pillar { background: linear-gradient(135deg, #8a8494, #4d4856); box-shadow: inset 0 0 0 2px rgba(0,0,0,.4); }
.mta-tk-grid .goal { background: radial-gradient(circle, #ffd24a 0 30%, rgba(255,210,74,.25) 32% 60%, transparent 62%); }
.mta-tk-statue {
  position: absolute; aspect-ratio: 1; transition: left .3s ease-out, top .3s ease-out;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
}
.mta-tk-statue img { width: 80%; height: 80%; object-fit: contain; image-rendering: pixelated; filter: drop-shadow(0 2px 2px rgba(0,0,0,.8)); }
.mta-tk-pad {
  display: grid; grid-template-columns: repeat(3, 72px); grid-template-areas: ". up ." "left mid right" ". down .";
  gap: 4px; justify-content: center; margin-top: 8px;
}
.mta-tk-pad button { font-size: 20px; padding: 6px 0; background: var(--panel2); border: 2px solid var(--edge); color: var(--parchment); }
.mta-tk-pad .up { grid-area: up; } .mta-tk-pad .down { grid-area: down; }
.mta-tk-pad .left { grid-area: left; } .mta-tk-pad .right { grid-area: right; }
.mta-tk-pad .mid { grid-area: mid; font-size: 16px; }

/* Alchemists' Playground: the price board, a row of cupboards (the one you
 * stand at is lit), and your hands. */
.mta-al-board { background: var(--panel2); border: 1px solid var(--edge); padding: 6px 10px; }
.mta-al-board-h { display: flex; justify-content: space-between; font-size: 18px; }
.mta-al-prices { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin-top: 4px; }
.mta-al-price { display: flex; flex-direction: column; align-items: center; font-size: 15px; text-align: center; }
.mta-al-price img { width: 30px; height: 30px; object-fit: contain; }
.mta-al-price b { font-size: 24px; }
.mta-al-price.top b { color: var(--gold); }
.mta-al-cupboards { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; margin: 10px 0; }
.mta-al-cup {
  position: relative; aspect-ratio: 3 / 4; background: linear-gradient(180deg, #6b4a2a, #4a3119);
  border: 2px solid #2b1c0d; display: flex; align-items: center; justify-content: center; padding: 0;
}
.mta-al-cup.here { border-color: var(--gold); }
.mta-al-cup kbd { position: absolute; bottom: 2px; right: 3px; }
.mta-al-door { width: 70%; aspect-ratio: 1; display: flex; align-items: center; justify-content: center; }
.mta-al-door img { max-width: 100%; max-height: 100%; }
.mta-al-hand { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 18px; }
.mta-al-held { display: flex; gap: 4px; }
.mta-al-held button, .mta-al-slot {
  width: 52px; height: 52px; display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
  background: var(--panel2); border: 2px solid var(--edge); color: var(--parchment); padding: 0;
}
.mta-al-held button:hover { border-color: var(--gold); }
.mta-al-held img { width: 28px; height: 28px; object-fit: contain; }
.mta-al-held b { font-size: 16px; }
.mta-al-coins { margin-left: auto; display: flex; align-items: center; gap: 4px; font-size: 22px; }
.mta-al-coins img { width: 28px; }

/* Enchanting Chamber: the bonus call on top, the four piles, and now and
 * then a dragonstone on the floor. */
.mta-en-bonus {
  display: flex; align-items: center; gap: 8px; background: var(--panel2); border: 1px solid var(--edge);
  padding: 6px 10px; font-size: 20px;
}
.mta-en-bonus b { display: flex; align-items: center; gap: 6px; color: #c58bff; font-size: 26px; }
.mta-en-bonus b img { width: 28px; height: 28px; object-fit: contain; }
.mta-en-bonus small { margin-left: auto; font-size: 16px; }
.mta-en-bonus.flash { animation: baFlash 1s ease-out; }
.mta-en-floor {
  position: relative; margin: 8px 0; aspect-ratio: 5 / 2;
  background: radial-gradient(circle at 50% 50%, #3a2d4d, #1f1829 75%); border: 1px solid var(--edge);
}
.mta-en-piles { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 8% 10px; }
.mta-en-pile {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  background: rgba(0,0,0,.3); border: 2px solid var(--edge); color: var(--parchment); font-size: 16px;
}
.mta-en-pile img { width: 40%; max-width: 40px; aspect-ratio: 1; object-fit: contain; }
.mta-en-pile.bonus { border-color: #c58bff; box-shadow: 0 0 10px rgba(197,139,255,.6); }
.mta-en-pile.empty { opacity: .45; }
.mta-en-stock { font-style: normal; color: #c58bff; letter-spacing: 2px; }
.mta-en-pile kbd { position: absolute; top: 2px; right: 4px; }
.mta-en-stone {
  position: absolute; z-index: 2; width: 44px; height: 44px; transform: translate(-50%, -50%); padding: 0;
  background: rgba(255,255,255,.08); border: 2px solid #c8322a; border-radius: 50%; animation: mtaPulse .6s infinite alternate;
}
.mta-en-stone img { width: 70%; height: 70%; object-fit: contain; }
@keyframes mtaPulse { to { box-shadow: 0 0 14px #ff6a5a; } }
.mta-en-orbs { font-size: 18px; }

/* Creature Graveyard: the four bone piles (each shows how many bones its
 * next grab gives), what you carry, and the spells. Falling bones drop in
 * from the top. */
.mta-gy-hp { margin-bottom: 6px; }
.mta-gy-floor {
  position: relative; overflow: hidden; aspect-ratio: 5 / 2;
  background: linear-gradient(180deg, #2a2e26, #3b4232); border: 1px solid var(--edge);
}
.mta-gy-floor.full .mta-gy-pile { opacity: .5; }
.mta-gy-piles { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 10% 10px; }
.mta-gy-pile {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: rgba(0,0,0,.3); border: 2px solid var(--edge); color: var(--parchment);
}
.mta-gy-pile img { width: 45%; max-width: 44px; object-fit: contain; }
.mta-gy-pile b { font-size: 24px; color: var(--gold); }
.mta-gy-pile kbd { position: absolute; top: 2px; right: 4px; }
.mta-gy-fall { position: absolute; top: -20%; z-index: 2; pointer-events: none; animation: mtaFall 1s ease-in forwards; }
.mta-gy-fall img { width: 32px; }
.mta-gy-fall b { color: #ff6a5a; font-size: 20px; text-shadow: 1px 1px 0 #000; }
@keyframes mtaFall { 60% { top: 45%; opacity: 1; } to { top: 45%; opacity: 0; } }
.mta-gy-inv { display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap; margin: 8px 0; font-size: 18px; }
.mta-gy-inv img.ic { width: 20px; height: 20px; object-fit: contain; vertical-align: -3px; }
.mta-gy-acts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.mta-gy-acts button { font-size: 18px; padding: 8px 4px; background: var(--panel2); border: 2px solid var(--edge); color: var(--parchment); }
.mta-gy-acts button:hover, .mta-tk-pad button:hover { border-color: var(--gold); }

@media (max-width: 520px) {
  .mta-stage kbd { display: none; }
  .mta-al-cupboards { grid-template-columns: repeat(4, 1fr); }
  .mta-al-cup { aspect-ratio: 1; }
  .mta-al-prices { gap: 2px; }
  .mta-al-price span { display: none; }
  .mta-en-floor, .mta-gy-floor { aspect-ratio: 4 / 3; }
  .mta-en-piles, .mta-gy-piles { grid-template-columns: repeat(2, 1fr); padding: 10px; }
  .mta-gy-acts { grid-template-columns: 1fr; }
}

/* Armour set panel in the item sheet */
.sheet-set { margin-bottom: 12px; padding: 8px 10px; border: 1px solid var(--edge); border-radius: var(--radius); background: var(--card); }
.sheet-set.active { border-color: var(--green); }
.sheet-set .ss-head { display: flex; gap: 8px; align-items: baseline; color: #f2e6cc; font-size: 20px; }
.sheet-set .ss-bonus { color: var(--dim); font-size: 16px; margin: 2px 0 6px; line-height: 1.15; }
.sheet-set .ss-bonus b { color: #ffcf6a; font-weight: normal; }
.ss-pieces { display: grid; gap: 3px; }
.ss-piece { display: flex; justify-content: space-between; gap: 8px; width: 100%; text-align: left; padding: 3px 8px; font-size: 16px; }
.ss-piece.here { border-color: var(--gold); cursor: default; }
.ss-where { flex: none; }
.ss-where.worn { color: var(--green); }
.ss-where.owned { color: var(--parchment); }
.ss-where.missing { color: var(--dim); }

/* ------------------------------------ v3.14.0: starred collection log --- */
.cl-star {
  /* Top right is free: .qty only shows on owned slots, which have no star. */
  position: absolute; top: 1px; right: 1px;
  width: 18px; height: 18px; padding: 3px;
  background: none; border: none; line-height: 0;
  color: var(--dim); opacity: .55;
}
.cl-star .ic { width: 11px; height: 11px; }
.cl-star:hover { opacity: 1; color: var(--gold); }
.cl-star.on { opacity: 1; color: #ffd84a; filter: drop-shadow(1px 1px 0 #000); }
.cl-cell.starred { border-color: #ffd84a; border-style: dashed; }
/* ------------------------- v3.49.0: one monster's log, popped out --- */
/* The monster's NAME under the arena is the control that pops it out. Styled
   as the text it replaced rather than as a button, because that line is a
   title first and a control second. */
#boss-title .bt-name {
  background: none; border: none; padding: 0;
  font: inherit; color: inherit; line-height: inherit;
  border-bottom: 1px dotted currentColor; cursor: pointer;
}
#boss-title .bt-name:hover { color: #fff; }
#boss-title .bt-name:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

/* The card itself: the clue tracker's shape, on the other side of the screen
   by default so the two do not open on top of each other. Anything shared
   between them lives in FloatCard (public/app.js). */
#mob-log-card {
  position: fixed; right: 16px; top: 96px; z-index: 45;
  width: min(330px, calc(100vw - 32px));
  background: var(--panel);
  border: 3px solid var(--edge-dark);
  box-shadow: 0 0 0 2px var(--edge) inset, 0 6px 22px rgba(0,0,0,.55);
}
#mob-log-card.hidden { display: none; }
/* No text selection or touch-scrolling while a drag is in flight. */
#mob-log-card .ml-head {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 30px 4px 8px; position: relative;
  background: var(--panel2); border-bottom: 2px solid var(--edge);
  cursor: grab; touch-action: none; user-select: none;
}
#mob-log-card.dragging .ml-head { cursor: grabbing; }
#mob-log-card.dragging { box-shadow: 0 0 0 2px var(--gold) inset, 0 10px 28px rgba(0,0,0,.65); }
#mob-log-card .ml-grip { color: var(--dim); font-size: 18px; line-height: 1; }
#mob-log-card .ml-title {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  color: var(--gold); font-size: 18px; white-space: nowrap;
}
#mob-log-card .ml-ico { width: 20px; height: 20px; object-fit: contain; flex: none; }
#mob-log-card .ml-count { color: var(--dim); font-size: 15px; }
#mob-log-card .ml-body { padding: 8px; max-height: 60vh; overflow-y: auto; }
#mob-log-card .cl-boss { border: none; padding: 0; background: none; box-shadow: none; }
#mob-log-card .cl-grid { grid-template-columns: repeat(auto-fill, minmax(54px, 1fr)); gap: 6px; }
#mob-log-card .cl-cell img { max-height: 34px; }
#mob-log-card .cl-master { color: var(--green); font-size: 15px; margin-bottom: 6px; }
#mob-log-card .ml-link { display: block; margin-top: 8px; font-size: 15px; }
@media (max-width: 640px) {
  /* The music bar owns the bottom of a phone screen; keep clear of it. */
  #mob-log-card .ml-body { max-height: 40vh; }
}

/* Extras, wherever they are drawn inline rather than in the hover tip. */
.cl-extras { margin-top: 12px; border-top: 1px solid var(--edge); padding-top: 8px; }

.star-alert .modal-box { border-color: #ffd84a; box-shadow: 0 0 28px rgba(255,216,74,.45); }
.star-alert h2 .ic { width: 22px; height: 22px; color: #ffd84a; vertical-align: -2px; }
.sa-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.sa-row img { width: 48px; height: 48px; object-fit: contain; }
.sa-name { color: var(--gold); font-size: 22px; }

/* v3.17.0: the Skilling page (Woodcutting / Thieving / Farming tabs). */
.skill-tabs { margin-bottom: 12px; }
.skill-tabs button { flex: 1; }
/* v3.69.0: on the Slayer tab, the line that replaces the Fight button once
   you are already fighting the task monster. Not a button — there is nothing
   left to press — so it is styled as the confirmation it is. */
.slay-on { color: var(--green); }
/* The Slayer tab's masters and store: always three across rather than the
   farm grid's auto-fill, dropping to one column on a phone. */
.farm-grid.slay-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 700px) { .farm-grid.slay-grid { grid-template-columns: 1fr; } }
/* v3.84.0: the Slayer tab, laid out to be scanned. Three summary tiles, the
   rules as a short list, the task as one card with the monster as its
   heading, and master cards whose numbers line up across the row. */
.slay-sum { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-bottom: 10px; }
.slay-tile { background: var(--panel2); border: 2px solid var(--edge); padding: 8px 10px; }
.slay-tile-label { color: var(--dim); font-size: 14px; text-transform: uppercase; letter-spacing: .5px; }
.slay-tile-val { color: var(--gold); font-size: 26px; line-height: 1.2; }
.slay-tile-sub { color: var(--dim); font-size: 14px; min-height: 14px; }
.slay-tile .xpbar { margin: 4px 0 0; }
.slay-rules { margin: 0 0 6px; padding-left: 20px; color: var(--dim); }
.slay-rules li { margin: 2px 0; }
.slay-rules b { color: var(--parchment); font-weight: normal; }
.slay-task-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.slay-task-head .ct-popout { margin: 0; }
.slay-card { background: var(--panel2); border: 2px solid var(--edge); padding: 10px 12px; margin-top: 6px; }
.slay-card.on { border-color: var(--green); }
.slay-mob { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.slay-mob b { color: var(--gold); font-size: 22px; font-weight: normal; }
.xpbar.slay-kills { height: 20px; margin-bottom: 8px; }
.xpbar.slay-kills div { background: linear-gradient(#d4453a, #8f1e15); }
.xpbar.slay-kills span { font-size: 13px; }
.slay-card .stat-line .dim-note { font-size: 13px; }
.slay-status { margin: 6px 0 8px; }
.slay-status.slay-off { color: var(--dim); }
.slay-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.slay-master { text-align: left; display: flex; flex-direction: column; }
.slay-master-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  border-bottom: 1px solid var(--edge); padding-bottom: 4px; margin-bottom: 4px; }
.slay-master-head b { color: var(--parchment); font-size: 18px; font-weight: normal; }
.slay-roster { font-size: 14px; margin: 6px 0; }
.slay-master .fp-desc { font-size: 13px; font-style: italic; flex: 1; }
.slay-master .fp-btn { margin-top: auto; }
@media (max-width: 700px) {
  .slay-sum { grid-template-columns: 1fr 1fr; }
  .slay-sum .slay-tile:first-child { grid-column: 1 / -1; }
}
.skill-status { margin: 10px 0; color: var(--parchment); }
.skill-status .skill-stop { margin-left: 8px; padding: 2px 12px; }
.skill-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 8px; }
.skill-card {
  display: flex; align-items: center; gap: 10px;
  padding: 8px; border: 1px solid var(--edge); background: var(--panel2);
}
.skill-card.doing { border-color: var(--gold); background: #55461f; }
.skill-card.locked { opacity: 0.55; }
.skill-card.locked img { filter: grayscale(1) brightness(0.6); }
.skill-card img { width: 48px; height: 48px; object-fit: contain; flex: none; }
.skill-card .sc-body { flex: 1; min-width: 0; }
.skill-card .sc-name { font-size: 19px; line-height: 1.05; }
.skill-card .sc-detail { font-size: 15px; color: var(--dim); }
.skill-card.doing .sc-detail { color: var(--parchment); }
.skill-card .sc-bar { margin: 4px 0 0; height: 8px; border-width: 1px; background: #241f17; }
.skill-card .sc-bar > div { transition: none; width: 0; background: linear-gradient(#ffb051, var(--gold) 45%, #c96e00); }
.skill-card .sc-bar > div.stunned,
#killbar-fill.stunned { background: linear-gradient(#e0574b, #b3352b 45%, #7a1f18); }
.skill-card button { flex: none; min-width: 96px; }
.boss-row.skill-now .bname { color: var(--gold); }
.xp-float.xp-float-miss { color: #ff6b5e; }
@media (max-width: 700px) {
  .skill-grid { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------- v3.28: music --- */
/* A thin bar pinned bottom-left. It sits above the footer rather than in it,
   because it has to survive on every page including the minigames, which
   replace the whole main column. */
#music-bar {
  position: fixed; left: 10px; bottom: 10px; z-index: 40;
  display: flex; align-items: center; gap: 6px;
  background: var(--panel); border: 2px solid var(--edge);
  padding: 4px 8px; max-width: min(320px, calc(100vw - 20px));
  box-shadow: 0 2px 0 var(--edge-dark);
}
#music-bar button {
  padding: 2px 8px; font-size: 15px; line-height: 1.2; min-width: 30px;
}
/* Autoplay was refused and we are waiting on a click — hint that the button
   is live rather than broken. */
#music-bar button.armed { border-color: var(--gold); color: var(--gold); }
#mus-title {
  flex: 1; min-width: 0; font-size: 15px; color: var(--parchment);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#mus-title:hover { color: var(--gold); }
#mus-vol { width: 70px; padding: 0; }
/* Phones put a fixed tab bar across the foot of the screen at a higher
   z-index than this one, so a player pinned to bottom: 6px was painted
   underneath it: invisible, untappable, and — because a fresh session only
   resumes music that was already playing — silent for the whole visit. Ride
   just above the tabs instead, the same offset #sell-bar uses, and reserve
   the extra room on <body> so the bar never sits on top of the last line of
   the page. The breakpoint is the tab bar's 700px, not the 640px this block
   started at, or the same burial happens on a wide phone. */
@media (max-width: 700px) {
  #music-bar {
    left: 6px; right: 6px; max-width: none;
    bottom: calc(74px + env(safe-area-inset-bottom));
  }
  body.has-music { padding-bottom: calc(116px + env(safe-area-inset-bottom)); }
  #mus-vol { width: 56px; }
}
#music-bar button.on { border-color: var(--gold); color: var(--gold); }

/* The account page's track list folds away behind this header row, so the
   panel is normally just the controls and a one-line count. */
.mu-lib-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0 6px; }
/* The way back out of the settings panel, for players who got here from the
   player bar's title link rather than the nav. */
#mu-back { margin-bottom: 10px; font-size: 16px; }
#mu-lib-toggle { padding: 2px 10px; font-size: 15px; line-height: 1.3; }
.mu-lib-head #mu-status { margin: 0; }

/* --------------------------------------------------- v3.29: the net hire --- */
/* Puro-Puro's lobby: buying the net working hours. Priced buttons rather than
   a dropdown, so the three windows can be compared at a glance. */
.net-hire {
  border: 2px solid var(--edge); background: var(--panel2);
  padding: 8px 10px; margin: 8px 0;
}
.net-hire .nh-head { color: var(--gold); font-size: 17px; margin-bottom: 2px; }
.net-hire .nh-live { color: var(--green); font-size: 15px; }
.net-hire .nh-note { color: var(--dim); font-size: 15px; margin-bottom: 6px; }
.net-hire .nh-opts { display: flex; gap: 6px; flex-wrap: wrap; }
.net-hire .nh-opts button {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 4px 12px; min-width: 76px; font-size: 17px; line-height: 1.15;
}
.net-hire .nh-opts button small { color: var(--dim); font-size: 14px; }
.net-hire .nh-opts button:disabled { opacity: .45; cursor: not-allowed; }
.net-hire .nh-opts button:disabled:hover { border-color: var(--edge); color: var(--parchment); }

/* ------------------------------------------------ v3.30: skill progress --- */
/* Sits directly under the nav and sticks to the top once the header scrolls
   away, so what you are training is always on screen.

   v3.69.0: a ROW of bars rather than one — a swing can pay three skills at
   once, and a Slayer task adds a fourth on top (app.js SkillBar.activeSkills).
   The strip is the flex row; each skill is an .sb-item inside it. */
#skill-bar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; flex-wrap: wrap; gap: 3px 18px;
  background: linear-gradient(#3e3529, #352d23);
  border-bottom: 2px solid var(--edge-dark);
  box-shadow: 0 1px 0 var(--edge) inset;
  padding: 5px 18px; font-size: 17px;
}
#skill-bar.hidden { display: none; }
/* A zero basis would let a long label (Woodcutting, 12,345 xp to 75) set one
   cell's width and starve its neighbours, so the basis is the width the cell
   WANTS: with room for them all they share the row equally, and without it
   they wrap onto a second line rather than squeezing the tracks away. */
#skill-bar .sb-item {
  flex: 1 1 290px; min-width: 0;
  display: flex; align-items: center; gap: 12px;
}
/* Which part gives way first when a cell is squeezed: the track, down to a
   readable minimum, then the two labels — which clip rather than wrap, so a
   tight cell stays one line tall and the strip keeps its height. The level is
   three characters and never gives way at all. */
#skill-bar .sb-skill,
#skill-bar .sb-xp { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#skill-bar .sb-skill { color: var(--gold); white-space: nowrap; }
#skill-bar .sb-lvl { flex: none; color: var(--parchment); white-space: nowrap; }
#skill-bar .sb-track {
  flex: 1 1 60px; min-width: 44px; height: 12px;
  background: #241f17; border: 2px solid var(--edge); overflow: hidden;
}
#skill-bar .sb-fill {
  /* Colour is set per update in app.js (progressColor): red the moment you
     level, green as the next one comes into reach — the same ramp the
     sidebar skills tab uses. The overlay is a fixed sheen on top of it. */
  height: 100%; background-color: var(--green);
  background-image: linear-gradient(rgba(255,255,255,.28), rgba(0,0,0,.18));
  transition: width .35s ease, background-color .35s ease;
}
#skill-bar .sb-xp { color: var(--dim); white-space: nowrap; }
/* Two or three bars spend on separators what one bar could spend on gaps. */
#skill-bar.sb-multi { gap: 2px 14px; font-size: 16px; }
#skill-bar.sb-multi .sb-item { flex-basis: 230px; gap: 9px; }
/* Below this a controlled stance's three cells stop holding a legible track
   and a full "12,345 xp to 75" at the same time. The number to the next level
   is the part the sidebar skills tab already prints, so it is the part that
   goes; the bar itself is what this strip is for. */
@media (max-width: 1050px) {
  #skill-bar.sb-multi .sb-xp { display: none; }
}
@media (max-width: 640px) {
  #skill-bar { gap: 3px 8px; padding: 4px 10px; font-size: 15px; }
  /* One bar to a line on a phone, and the "xp to" tail goes: at this width
     the track is the only part of a cell with anything to say. */
  #skill-bar .sb-item { flex-basis: 100%; gap: 8px; }
  #skill-bar .sb-xp { display: none; }
  #skill-bar.sb-multi { font-size: 15px; }
  #skill-bar.sb-multi .sb-item { flex-basis: 100%; }
}

/* ------------------------------------------- v3.40: the clue tracker --- */
/* A pop-out clue card that follows you across pages and can be dragged
   anywhere. It sits above the music bar but below the banner stack, so a
   ready-clue banner is never hidden behind the window announcing it. */
#clue-tracker {
  position: fixed; left: 16px; top: 96px; z-index: 45;
  width: min(300px, calc(100vw - 32px));
  background: var(--panel);
  border: 3px solid var(--edge-dark);
  box-shadow: 0 0 0 2px var(--edge) inset, 0 6px 22px rgba(0,0,0,.55);
}
#clue-tracker.hidden { display: none; }
/* No text selection or touch-scrolling while a drag is in flight. */
#clue-tracker .ct-head {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 30px 4px 8px; position: relative;
  background: var(--panel2); border-bottom: 2px solid var(--edge);
  cursor: grab; touch-action: none; user-select: none;
}
#clue-tracker.dragging .ct-head { cursor: grabbing; }
#clue-tracker.dragging { box-shadow: 0 0 0 2px var(--gold) inset, 0 10px 28px rgba(0,0,0,.65); }
#clue-tracker .ct-grip { color: var(--dim); font-size: 18px; line-height: 1; }
#clue-tracker .ct-title { color: var(--gold); font-size: 18px; }
#clue-tracker .ct-body { padding: 8px; max-height: 60vh; overflow-y: auto; }
#clue-tracker .ct-clue + .ct-clue { margin-top: 10px; border-top: 1px dotted var(--edge); padding-top: 8px; }
#clue-tracker .ct-clue-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 17px; }
#clue-tracker .ct-state { color: var(--dim); font-size: 15px; }
#clue-tracker .ct-clue.ready .ct-state { color: #e8c04a; }
#clue-tracker .q-req-label { font-size: 15px; }
/* How many steps the scroll is still holding back (v3.54.0). */
#clue-tracker .ct-more { color: var(--dim); font-size: 14px; margin-top: 4px; font-style: italic; }
#clue-tracker .ct-link { display: block; margin-top: 8px; font-size: 15px; }
@media (max-width: 640px) {
  /* The music bar owns the bottom of a phone screen; keep clear of it. */
  #clue-tracker .ct-body { max-height: 40vh; }
}

/* The ready-clue banner never times out, so unlike its siblings in the stack
   it has to stay clickable — the whole point is the link inside it. */
.notif-banner.sticky { pointer-events: auto; border-color: #e8c04a; }
.notif-banner.sticky .nb-body a { text-decoration: underline; }

/* The Clues page's own pop-out control, next to the active clue cards — and
   since v3.71.0 the Slayer tab's, which offers the same thing. */
.ct-popout { margin-bottom: 10px; font-size: 16px; }

/* ----------------------------------------- v3.71.0: the slayer tracker --- */
/* The open Slayer task on a card that follows you across pages, popped out of
   the Slayer tab. Same furniture as the clue tracker above and the collection
   log pop-out: fixed, draggable, above the music bar and below the banner
   stack. It parks on the RIGHT so that a player running both trackers at once
   gets them side by side rather than stacked on top of each other. */
#slayer-tracker {
  position: fixed; right: 16px; top: 96px; z-index: 45;
  width: min(300px, calc(100vw - 32px));
  background: var(--panel);
  border: 3px solid var(--edge-dark);
  box-shadow: 0 0 0 2px var(--edge) inset, 0 6px 22px rgba(0,0,0,.55);
}
#slayer-tracker.hidden { display: none; }
/* No text selection or touch-scrolling while a drag is in flight. */
#slayer-tracker .sl-head {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 30px 4px 8px; position: relative;
  background: var(--panel2); border-bottom: 2px solid var(--edge);
  cursor: grab; touch-action: none; user-select: none;
}
#slayer-tracker.dragging .sl-head { cursor: grabbing; }
#slayer-tracker.dragging { box-shadow: 0 0 0 2px var(--gold) inset, 0 10px 28px rgba(0,0,0,.65); }
#slayer-tracker .sl-grip { color: var(--dim); font-size: 18px; line-height: 1; }
#slayer-tracker .sl-title { color: var(--gold); font-size: 18px; }
#slayer-tracker .sl-body { padding: 8px; max-height: 60vh; overflow-y: auto; }
#slayer-tracker .sl-from { color: var(--dim); font-size: 15px; }
#slayer-tracker .sl-mob { font-size: 17px; margin-bottom: 4px; }
#slayer-tracker .sl-count { font-size: 15px; margin-top: 2px; }
#slayer-tracker .sl-xp { color: var(--dim); font-size: 14px; }
/* The two answers to "are these kills counting". The warning is the reason the
   card exists, so it is the one line on it that is allowed to shout. */
#slayer-tracker .sl-on { color: var(--green); font-size: 15px; margin-top: 6px; }
#slayer-tracker .sl-warn {
  color: #e8c04a; font-size: 15px; margin-top: 6px;
  border-left: 3px solid #e8c04a; padding-left: 6px;
}
#slayer-tracker .sl-link { display: block; margin-top: 8px; font-size: 15px; }
@media (max-width: 640px) {
  /* The music bar owns the bottom of a phone screen; keep clear of it. */
  #slayer-tracker .sl-body { max-height: 40vh; }
}

/* --------------------------------------------- v3.43.0: 99 confetti --- */
/* A screenful of falling paper when a skill hits 99. One layer per
   celebration, removed by the script once the last piece has landed; it
   covers everything and catches nothing, so a click mid-fall still lands
   on whatever is underneath. */
.confetti { position: fixed; inset: 0; z-index: 9000; pointer-events: none; overflow: hidden; }
.confetti i {
  position: absolute; top: -28px; display: block;
  animation-name: confetti-fall; animation-timing-function: linear; animation-fill-mode: forwards;
}
@keyframes confetti-fall {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translate3d(var(--dx, 0px), 104vh, 0) rotate(var(--spin, 720deg)); opacity: 0; }
}
/* Decoration only — someone who has asked for less motion gets none of it. */
@media (prefers-reduced-motion: reduce) { .confetti { display: none; } }

/* ------------------------------------------ v3.53.0: beta watermark --- */
/* The build stamp in the top-left corner of every page — the login screen
   and the admin console included. It is furniture rather than UI: it catches
   no clicks, so the topbar underneath it still works, and it sits above the
   sticky header (z 60) and the phone tab bar (z 90) but below a modal
   (z 100), which owns the screen for as long as it is open.

   Desktop clears the logo without help: the topbar centres a 26px logo in a
   ~68px bar, so the text starts around 21px down and this band ends above
   it. The phone header is half that height, so the rule below buys the band
   its room there by pushing the header's own padding down. */
#beta-mark {
  position: fixed; top: 0; left: 0; z-index: 95; pointer-events: none;
  display: flex; align-items: baseline; gap: 5px;
  padding: 0 7px 1px; line-height: 1.2;
  font-size: 12px; letter-spacing: 1px;
  color: var(--dim);
  background: rgba(20, 17, 12, 0.72);
  border-right: 1px solid var(--edge);
  border-bottom: 1px solid var(--edge);
}
#beta-mark .bm-beta { color: var(--gold); }
/* The admin console kept the old single-row nav, so its header is about 46px
   tall rather than 68px and its logo starts where this band ends. It buys the
   band the same room the phone header does below. */
body[data-page="admin"] .topbar { padding-top: 20px; }
@media (max-width: 700px) {
  #beta-mark { font-size: 11px; padding: 0 6px 1px; gap: 4px; }
  .topbar { padding-top: 19px; }
}

/* ------------------------------------------ v3.55.0: the Rogues' Den --- */
/* The maze is a grid of tiles the engine's own size (--cols by --rows, set
   on the element by the page), and everything that moves — the guards and
   the player — floats above it, placed by percentage, so the whole thing
   scales with the panel and nothing has to agree on a pixel size.

   A trap wears its state as a class: nothing (idle), `warn` for the tell
   before it fires, `on` while it would catch you. Those three are the only
   thing the page draws about a trap, and they come straight from the engine
   both halves of the game run. */
.rd-count { font-size: 22px; color: var(--parchment); }
.rd-count b { color: var(--gold); }
.rd-maze {
  position: relative; width: 100%; overflow: hidden;
  border: 1px solid var(--edge); background: #1b1813;
  user-select: none; touch-action: none;
}
.rd-tiles {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--rows), 1fr);
}
.rd-t { position: relative; }
.rd-wall { background: #191612; box-shadow: inset 0 0 0 1px #0d0b09; }
.rd-floor { background: #514735; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .3); }
/* A doorway is safe ground and the tile you are sent back to. */
.rd-door { background: #665637; box-shadow: inset 0 0 0 2px rgba(255, 152, 31, .45); }
.rd-chest {
  background: radial-gradient(circle, #7c5c16, #4a4234 72%);
  box-shadow: inset 0 0 0 2px var(--gold);
  animation: rdChest 1.3s ease-in-out infinite alternate;
}
@keyframes rdChest { to { box-shadow: inset 0 0 0 2px var(--gold), 0 0 10px rgba(255, 152, 31, .65); } }
/* The chest itself, sat on the glowing tile. Shut for the whole run; the
   lid goes up the moment the run is cleared, which is also when the player
   fades back so there is something to look at. */
.rd-chest img {
  position: absolute; inset: 10%;
  object-fit: contain;
  transform: scale(.8);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .8));
  transition: opacity .25s ease;
}
.rd-chest img.open { opacity: 0; }
.rd-chest.cleared img.shut { opacity: 0; }
.rd-chest.cleared img.open { opacity: 1; }

.rd-haz::before { content: ''; position: absolute; inset: 16%; }
/* Flame vent: a dark grate, embers as the tell, then the flame. */
.rd-vent::before { border-radius: 50%; background: radial-gradient(circle, #2c251a 42%, #6b5c3e 43%); }
.rd-vent.warn::before { background: radial-gradient(circle, #7a3f12 45%, #6b5c3e 46%); }
.rd-vent.on::before { inset: 3%; border-radius: 50%; background: radial-gradient(circle, #ffe08a, #ff5a12 68%); }
/* Swinging blade: a resting edge, then a blur that fills the square. */
.rd-blade::before { background: linear-gradient(45deg, transparent 44%, #8b9098 44% 56%, transparent 56%); }
.rd-blade.warn::before { background: linear-gradient(45deg, transparent 40%, #d6a24a 40% 60%, transparent 60%); }
.rd-blade.on::before {
  inset: 2%; border-radius: 50%;
  background: conic-gradient(#e2e7ee, #5f646c, #e2e7ee, #5f646c, #e2e7ee);
}
/* Spike floor: holes, then the spikes out of them. */
.rd-spike::before {
  background:
    radial-gradient(circle at 30% 30%, #262018 26%, transparent 27%),
    radial-gradient(circle at 70% 30%, #262018 26%, transparent 27%),
    radial-gradient(circle at 30% 70%, #262018 26%, transparent 27%),
    radial-gradient(circle at 70% 70%, #262018 26%, transparent 27%);
}
.rd-spike.warn::before {
  background:
    radial-gradient(circle at 30% 30%, #9a6a22 26%, transparent 27%),
    radial-gradient(circle at 70% 30%, #9a6a22 26%, transparent 27%),
    radial-gradient(circle at 30% 70%, #9a6a22 26%, transparent 27%),
    radial-gradient(circle at 70% 70%, #9a6a22 26%, transparent 27%);
}
.rd-spike.on::before {
  inset: 6%;
  background: repeating-linear-gradient(90deg, transparent 0 8%, #cfd6df 8% 16%, transparent 16% 25%);
  clip-path: polygon(0 100%, 8% 0, 16% 100%, 25% 0, 33% 100%, 41% 0, 50% 100%, 58% 0, 66% 100%, 75% 0, 83% 100%, 91% 0, 100% 100%);
}

.rd-actors { position: absolute; inset: 0; pointer-events: none; }
.rd-guard, .rd-player {
  position: absolute; transform: translate(-50%, -50%);
  width: calc(100% / var(--cols)); aspect-ratio: 1; left: -100%;
}
.rd-guard { object-fit: contain; filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .8)); }
.rd-player {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 152, 31, .35), transparent 68%);
  transform: translate(-50%, -50%) scale(.84);
}
.rd-player img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 0 3px rgba(0, 0, 0, .9)); }
.rd-player.stunned { animation: rdStun .35s steps(2) infinite; }
.rd-player.done { opacity: .3; transition: opacity .25s ease; }
/* The rank and time keep their width; the name takes what is left and
   ellipsizes, so a long name can never push the row past the panel. */
.rd-board { margin-top: 14px; width: 100%; max-width: 340px; min-width: 0; box-sizing: border-box; }
.rd-board h3 { margin: 0 0 6px; font-size: 20px; color: var(--gold); }
.rd-board-row {
  display: grid; grid-template-columns: 2em minmax(0, 1fr) auto; gap: 8px;
  font-size: 20px; color: var(--parchment); padding: 2px 0;
}
.rd-board-row.you { color: var(--gold); }
.rd-board-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rd-board-ms { text-align: right; font-variant-numeric: tabular-nums; }
@keyframes rdStun { to { opacity: .35; } }

/* The pad is there for touch, where there is no key to hold down. */
.rd-pad {
  display: grid; gap: 4px; margin: 10px auto 4px; width: 168px;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas: '. n .' 'w . e' '. s .';
}
.rd-pad button {
  font-family: inherit; font-size: 22px; line-height: 1;
  padding: 10px 0; color: var(--parchment);
  background: var(--panel2); border: 1px solid var(--edge); cursor: pointer;
}
.rd-pad button:active { background: #55461f; }
.rd-pad button:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.rd-pad button[data-dir="0"] { grid-area: n; }
.rd-pad button[data-dir="1"] { grid-area: e; }
.rd-pad button[data-dir="2"] { grid-area: s; }
.rd-pad button[data-dir="3"] { grid-area: w; }

/* The outfit tracker in the lobby: five slots, the ones you have lit. */
.rd-outfit { margin: 4px 0 12px; }
.rd-outfit-head { color: var(--gold); margin-bottom: 6px; }
.rd-have-lockpick { color: var(--parchment); }
.rd-pieces { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 8px; }
.rd-piece {
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 4px;
  background: var(--panel2); border: 1px solid var(--edge); color: var(--dim);
  text-align: center; opacity: .45;
}
.rd-piece img { width: 40px; height: 40px; object-fit: contain; }
.rd-piece.have { opacity: 1; color: var(--parchment); border-color: var(--gold); }
@media (prefers-reduced-motion: reduce) {
  .rd-chest, .rd-player.stunned { animation: none; }
  .rd-chest img, .rd-player.done { transition: none; }
}
