/* ---------------- Top status bar ---------------- */
#status-bar {
  position: absolute;
  top: 0; left: 0; right: 0;
  padding: calc(env(safe-area-inset-top, 0px) + clamp(10px, calc(2.6 * var(--u)), 26px)) clamp(14px, calc(3.6 * var(--u)), 36px) clamp(10px, calc(2.6 * var(--u)), 26px);
  background: linear-gradient(#11160fe6, #11160f99 70%, #11160f00);
  pointer-events: none;
  z-index: 20;
}
.status-line {
  display: flex; align-items: center; gap: clamp(9px, calc(2.4 * var(--u)), 22px); margin-bottom: clamp(6px, calc(1.6 * var(--u)), 14px);
  font-size: clamp(16px, calc(4.6 * var(--u)), 38px); flex-wrap: wrap;
}
.s-name { font-weight: 700; font-size: clamp(20px, calc(6 * var(--u)), 50px); color: var(--accent); }
/* Boost pill is always shown and tappable (the status bar itself ignores pointer
   events, so re-enable them here). Dimmed when no boost is active ("empty"). */
#s-boost { pointer-events: auto; cursor: pointer; }
.pill.boost.empty { background: #1b2a1fcc; color: var(--muted); border-color: #314733; font-weight: 600; }
/* Top line as a 4-column grid so the boost pill sits dead-centre between the name
   (left) and the online count (right). Columns are pinned explicitly so hiding the
   boost doesn't shift the name/online out of place. Column 3 holds #event-badges (the
   generic timed-event badge row — event-badges.js) and is empty (zero-width) whenever
   no event is running; it flexes internally to however many badges are active at once,
   so this stays a 4-column grid no matter how many timed events exist. */
.top-line { display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto; align-items: center; flex-wrap: nowrap; }
.top-line .name-lv { grid-column: 1; justify-self: start; display: flex; align-items: center; gap: clamp(8px, calc(2 * var(--u)), 18px); min-width: 0; }
.name-lv .s-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.name-lv .pill { flex: none; }
/* Only wide screens have room to pin the boost dead-centre between name+Lv and the online
   badge; anywhere narrower it would overlap the Lv pill, so the default above keeps the
   boost right after the name cluster instead. */
@media (min-width: 768px) {
  .top-line { grid-template-columns: 1fr auto 1fr; }
}
/* Below that, compact the boost/Lv pills so name+Lv+boost+online fit one line;
   the name ellipsizes as the last resort (player names can be 20 chars). */
@media (max-width: 767px) {
  .top-line #s-boost { font-size: 14px; padding: 4px 10px; }
  .name-lv .pill { font-size: 13px; padding: 3px 9px; }
}
/* Narrow phones: the name itself comes down a notch too, and the row packs tighter. */
@media (max-width: 520px) {
  .name-lv .s-name { font-size: 16px; }
  .top-line { column-gap: 6px; }
  .name-lv { gap: 6px; }
  /* Keep the distance chip beside the HP bar (at full size the pair just overflows the row). */
  .hp-line .s-dist { font-size: 13px; }
}
.top-line #s-boost { grid-column: 2; justify-self: center; }
.top-line #event-badges { grid-column: 3; justify-self: end; }
.top-line .s-online { grid-column: 4; justify-self: end; margin-left: 0; }

/* Timed-event badge row: only in the DOM while at least one random-start event is running
   (event-badges.js adds/removes the container and its child pills), sitting immediately left
   of the online pill. Tapping any pill opens #event-modal, populated per-event by JS. */
#event-badges { display: flex; align-items: center; gap: 6px; }
.evt-badge {
  pointer-events: auto; cursor: pointer; -webkit-tap-highlight-color: transparent;
  line-height: 1; user-select: none;
  font-size: clamp(15px, calc(3.6 * var(--u)), 30px);
  filter: drop-shadow(0 0 6px #ffe9a655);
  animation: uni-bob 2.4s ease-in-out infinite;
}
.evt-badge:active { opacity: 0.7; }
@keyframes uni-bob { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-2px) } }
@media (prefers-reduced-motion: reduce) { .evt-badge { animation: none } }
.s-online { margin-left: auto; color: var(--muted); display: flex; align-items: center; gap: 6px; }
#s-online-badge { pointer-events: auto; cursor: pointer; -webkit-tap-highlight-color: transparent; }
#s-online-badge:active { opacity: 0.7; }
.s-online .dot { width: clamp(11px, calc(2.4 * var(--u)), 20px); height: clamp(11px, calc(2.4 * var(--u)), 20px); border-radius: 50%; background: var(--accent-2); box-shadow: 0 0 6px var(--accent-2); }
.s-version { margin-left: 8px; color: var(--muted); opacity: 0.45; font-size: clamp(9px, calc(2 * var(--u)), 13px); letter-spacing: 0.3px; font-weight: 600; }
.pill {
  background: #1b2a1fcc; border: 1px solid #314733; border-radius: 999px;
  padding: clamp(6px, calc(1.6 * var(--u)), 14px) clamp(15px, calc(3.8 * var(--u)), 34px); font-size: clamp(16px, calc(4.6 * var(--u)), 38px);
}
/* HP bar sitting under the player name (replaces the old HP pill): the fill width is
   driven by ui.js setHpDisplay; low/healing keep the pill's colour language. */
.hp-bar {
  position: relative; width: clamp(150px, calc(38 * var(--u)), 300px);
  height: clamp(22px, calc(5.6 * var(--u)), 44px);
  flex: 1 1 auto; min-width: 90px; max-width: clamp(150px, calc(38 * var(--u)), 300px);
  background: #26150fd9; border: 1px solid #5a2c25; border-radius: 999px; overflow: hidden;
  transition: border-color .25s;
}
.hp-fill { position: absolute; inset: 0; width: 100%; background: linear-gradient(#5aa14e, #2f6b33); transition: width .35s ease; }
.hp-text {
  position: relative; display: flex; align-items: center; justify-content: center; height: 100%;
  font-size: clamp(13px, calc(3.6 * var(--u)), 28px); font-weight: 700; color: #f2efe6;
  text-shadow: 0 1px 2px rgba(0,0,0,.65); font-variant-numeric: tabular-nums;
}
/* Low-HP warning: HP at or below 30% — red fill, brighter, and a gentle pulse to prompt a heal/retreat. */
#hp-bar.low { border-color: #d24a4a; animation: hp-low-pulse 1.1s ease-in-out infinite; }
#hp-bar.low .hp-fill { background: linear-gradient(#d24a4a, #8a2424); }
/* Healing cue: resting and recovering (and not low) — a calm green glow. */
#hp-bar.healing { border-color: #4a8a4a; animation: hp-heal-pulse 1.6s ease-in-out infinite; }
@keyframes hp-heal-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(90,200,90,0); } 50% { box-shadow: 0 0 9px 1px rgba(90,200,90,.5); } }
@keyframes hp-low-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(210,74,74,.0); } 50% { box-shadow: 0 0 10px 2px rgba(210,74,74,.6); } }
/* Combat level-up: a brief gold flash on the Lv pill when the headline level rises (live play). */
.pill.cl-up { animation: cl-up-flash 1.5s ease-out 1; }
@keyframes cl-up-flash {
  0% { box-shadow: 0 0 0 0 rgba(255,210,60,0); color: #ffe89a; }
  18% { box-shadow: 0 0 14px 3px rgba(255,210,60,.8); color: #fff4c2; }
  100% { box-shadow: 0 0 0 0 rgba(255,210,60,0); }
}
/* Critical-HP (≤20%) red edge vignette — a subtle, pulsing "you're in danger" wash. Never
   blocks input (pointer-events: none) and sits over the map but under the HUD/panels. */
.lowhp-vignette {
  position: fixed; inset: 0; pointer-events: none; z-index: 15;
  background: radial-gradient(ellipse 78% 78% at center, rgba(150,12,12,0) 58%, rgba(150,12,12,.45) 100%);
  animation: lowhp-pulse 1.5s ease-in-out infinite;
}
@keyframes lowhp-pulse { 0%, 100% { opacity: .5; } 50% { opacity: .95; } }
.lowhp-vignette.hidden { display: none; }

/* DREAD — shown only while a legendary world encounter (The Wandering Relic) is on screen.
   Deliberately NOT a jump-scare: it's a cold violet dark closing in from the edges, breathing
   slowly, with a faint drifting interference over the top. `--dread` (0…1) is set every frame
   by monster-layer.js from the creature's real distance, so the world literally darkens as it
   comes toward you and eases off when it drifts away. Never blocks input. */
.dread-vignette {
  position: fixed; inset: 0; pointer-events: none; z-index: 16;
  opacity: var(--dread, 0);
  transition: opacity 700ms ease-out;
  background:
    radial-gradient(ellipse 62% 62% at center, rgba(8,4,18,0) 30%, rgba(10,4,24,.62) 72%, rgba(6,2,16,.92) 100%);
  animation: dread-breathe 5.5s ease-in-out infinite;
}
/* Faint interference drifting across the frame — the sense that something is wrong with the
   picture itself, rather than with the world. Cheap: one repeating gradient, translated. */
.dread-vignette::after {
  content: ''; position: absolute; inset: -50%;
  background: repeating-linear-gradient(
    112deg,
    rgba(190,170,255,.045) 0px, rgba(190,170,255,.045) 1px,
    rgba(0,0,0,0) 1px, rgba(0,0,0,0) 4px);
  animation: dread-drift 9s linear infinite;
  mix-blend-mode: screen;
}
@keyframes dread-breathe { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.35); } }
@keyframes dread-drift { 0% { transform: translate3d(0,0,0); } 100% { transform: translate3d(6%, 9%, 0); } }
.dread-vignette.hidden { display: none; }

/* Accessibility: honour the OS "reduce motion" setting — swap the continuous HUD throbs for
   calm static states (the red/green/vignette cues stay fully visible, just without the pulse). */
@media (prefers-reduced-motion: reduce) {
  #hp-bar.low, #hp-bar.healing, .pill.cl-up { animation: none !important; }
  #hp-bar.low { box-shadow: 0 0 8px 1px rgba(210,74,74,.5); }
  #hp-bar.healing { box-shadow: 0 0 8px 1px rgba(90,200,90,.45); }
  .lowhp-vignette { animation: none !important; opacity: .72; }
  /* Dread stays fully visible for players who asked for less motion — it just stops breathing
     and drifting. The darkness is the point; the movement is the garnish. */
  .dread-vignette { animation: none !important; }
  .dread-vignette::after { animation: none !important; opacity: .5; }
  .mm-here::after { display: none; } /* drop the expanding ping ring; the solid dot still marks you */
}
/* App-update prompts: the in-game modal's "Later" (a calm, full-width secondary button
   under the gold primary) and the sign-in gate's message shown inside the auth card. */
#appupdate-modal .appupdate-later {
  display: block; width: 100%; margin-top: 10px; padding: 11px;
  background: #1b2a1f; color: var(--muted); border: 1px solid #314733; border-radius: 10px;
  font-size: 15px; font-weight: 600; cursor: pointer;
}
#appupdate-modal .appupdate-later:active { background: #24352a; }
.auth-update-msg { color: var(--ink); line-height: 1.45; margin: 12px 0 14px; }
/* Full-screen out-of-date block (old app shells, shown instead of the game). */
#appblock-screen { display: flex; align-items: center; justify-content: center; padding: 20px; }
#appblock-screen.hidden { display: none; }
/* While the update gate is up, no path into a new session — including the Google button,
   which reveals itself async after the gate has run. */
.auth-card.auth-blocked #auth-form, .auth-card.auth-blocked .tabs,
.auth-card.auth-blocked #google-auth { display: none !important; }

/* Settings cog at the right end of the area/gold status line. The status bar ignores
   pointer events, so re-enable them here (like the boost pill). */
.settings-cog {
  pointer-events: auto; cursor: pointer; flex: none;
  width: clamp(34px, calc(8.4 * var(--u)), 64px); height: clamp(34px, calc(8.4 * var(--u)), 64px);
  background: none; border: none; padding: 0; line-height: 0;
}
.settings-cog svg { width: 100%; height: 100%; display: block; filter: drop-shadow(0 1px 1px rgba(0,0,0,.5)); }
.settings-cog:active { opacity: .65; }
/* Gold/coin marker. Drawn as an inline SVG rather than the 🪙 emoji (U+1FA99),
   which is missing from the emoji fonts on many Windows PCs and renders as tofu. */
.pill.coins::before {
  content: "";
  display: inline-block; vertical-align: -0.14em; margin-right: .35em;
  width: 1em; height: 1em;
  background: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'%20fill='%23e8c45a'%20stroke='%237a5a1c'%20stroke-width='1.5'/%3E%3Ccircle%20cx='12'%20cy='12'%20r='7'%20fill='none'%20stroke='%23a9761f'%20stroke-width='1'/%3E%3Cpath%20d='M12%208l1.25%202.55%202.8.4-2.02%201.95.48%202.8L12%2016.6l-2.5%201.3.48-2.8-2.02-1.95%202.8-.4z'%20fill='%23fff3c4'/%3E%3C/svg%3E") center/contain no-repeat;
}
/* Reusable inline coin glyph for shop/inventory/combat text, same art as the
   HUD pill above. Use in place of the 🪙 emoji which is tofu on many Windows PCs. */
.coin-ico {
  display: inline-block; vertical-align: -0.14em; width: 1em; height: 1em;
  background: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'%20fill='%23e8c45a'%20stroke='%237a5a1c'%20stroke-width='1.5'/%3E%3Ccircle%20cx='12'%20cy='12'%20r='7'%20fill='none'%20stroke='%23a9761f'%20stroke-width='1'/%3E%3Cpath%20d='M12%208l1.25%202.55%202.8.4-2.02%201.95.48%202.8L12%2016.6l-2.5%201.3.48-2.8-2.02-1.95%202.8-.4z'%20fill='%23fff3c4'/%3E%3C/svg%3E") center/contain no-repeat;
}
.pill.boost { color: #1a1306; background: linear-gradient(#e8c45a, #c8922f); border-color: #7a5a1c; font-weight: 700; }
.pill.boost.hidden { display: none; }
.s-region { font-weight: 600; color: var(--accent-2); }
/* Third status line: area name (+Safe badge) left, then gold and the settings cog on the right. */
.region-line .pill.coins { margin-left: auto; }

/* Dungeon items — the Ocarina-of-Time key/map/compass row (DUNGEON-03). Sits above the
   region line and only appears inside a dungeon that declares itemKeys. Unheld items stay
   visible but dimmed: seeing the boss key you have not found yet is the point. */
.dgn-items { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.dgn-items .dgn-item {
  font-size: 15px; line-height: 1; padding: 2px 3px; border-radius: 4px;
  opacity: 0.28; filter: grayscale(1);
  transition: opacity .18s ease, filter .18s ease, box-shadow .18s ease;
}
.dgn-items .dgn-item.held {
  opacity: 1; filter: none;
  background: rgba(255, 214, 120, 0.14);
  box-shadow: 0 0 0 1px rgba(255, 214, 120, 0.35) inset;
}
.dgn-items .dgn-room {
  margin-left: auto; font-size: 11px; letter-spacing: .02em;
  color: rgba(235, 226, 208, 0.72); text-transform: uppercase;
}
.s-coords {
  font-variant-numeric: tabular-nums; color: var(--muted); font-size: clamp(13px, calc(3.4 * var(--u)), 28px);
  background: #1b2a1fcc; border: 1px solid #314733; border-radius: 999px; padding: clamp(5px, calc(1.3 * var(--u)), 12px) clamp(13px, calc(3.2 * var(--u)), 28px);
}
.s-coords::before { content: "⌖ "; color: var(--accent); }
/* Distance-from-Rest readout: a subtle pill (like the other HUD chips) so it stays legible
   over any terrain — plain muted text used to wash out against bright/busy backgrounds. */
/* Sits directly beside the HP bar (margin-left:auto would push it to the right edge).
   The row never wraps — the HP bar shrinks instead (min 90px) so these always share a line. */
.hp-line { flex-wrap: nowrap; }
/* ── Timed effects, directly under the health bar ────────────────────────────
   One chip per queued effect, oldest first. The ACTIVE chip is lit and its clock runs; the
   ones behind it are dimmed with an hourglass and the duration they will start on, so "waiting
   its turn" is readable without a legend. Debuffs (a negative in the label) take the warning
   colour. The strip is hidden entirely when the queue is empty, so it costs no vertical space
   in the common case. */
.buff-strip { gap: 6px; margin-top: -2px; flex-wrap: wrap; pointer-events: none; }
.buff-chip {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  font-size: clamp(12px, calc(3 * var(--u)), 22px); line-height: 1;
  padding: 2px clamp(7px, calc(1.8 * var(--u)), 11px);
  border-radius: 999px; border: 1px solid #2a3d2c; background: #11160fcc;
  font-variant-numeric: tabular-nums;
}
.buff-chip.active { border-color: var(--accent-2); color: var(--ink); box-shadow: 0 0 6px #7bd88f33; }
.buff-chip.queued { opacity: 0.62; color: var(--muted); }
.buff-chip.debuff.active { border-color: #c8563c; box-shadow: 0 0 6px #c8563c33; }
.buff-chip .buff-icon { font-size: 1.05em; }
.buff-chip .buff-time { font-weight: 700; color: var(--accent); }
.buff-chip.queued .buff-time { color: var(--muted); font-weight: 600; }
@media (max-width: 520px) {
  /* Phones: keep the strip to one line — the label is the first thing to go, the clock stays. */
  .buff-chip .buff-label { max-width: 42vw; overflow: hidden; text-overflow: ellipsis; }
}
.s-dist { margin-left: 0; flex: none; white-space: nowrap; color: var(--muted); background: #11160fcc; border: 1px solid #2a3d2c;
  border-radius: 999px; padding: 2px clamp(9px, calc(2.2 * var(--u)), 14px); font-variant-numeric: tabular-nums; }
.s-dist #s-dist { color: var(--ink); font-weight: 600; }
/* Bottom-left HUD stack: anchors the safe-zone badge + beginner quest tracker to the same
   spot the mode controls use, but as a column so the badge sits ABOVE the tracker instead
   of covering it. Non-interactive; only the tracker's × opts back into pointer events. */
.ql-stack {
  position: absolute; z-index: 19;
  left: clamp(10px, calc(2.6 * var(--u)), 28px);
  bottom: calc(clamp(104px, calc(26 * var(--u)), 200px) + var(--safe-pad));
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  pointer-events: none;
}
/* Safe-zone badge — only shown (JS toggles .hidden) while standing in a sanctuary. */
.s-safe {
  font-weight: 700; color: #7fd6a0; background: #14241acc; border: 1px solid #3f6e4f; border-radius: 999px; padding: 1px 9px; font-size: 0.86em;
}
.s-safe.hidden { display: none; }
/* Region-entry banner: a brief centred title that fades in/out when you cross into a new
   region. Non-interactive so it never blocks taps on the map underneath. */
.region-banner {
  position: absolute; top: 22%; left: 50%; transform: translate(-50%, -8px);
  pointer-events: none; text-align: center; z-index: 40;
  opacity: 0; transition: opacity .5s ease, transform .5s ease;
}
.region-banner.hidden { display: none; }
.region-banner.show { opacity: 1; transform: translate(-50%, 0); }
.region-banner .rb-name {
  display: block; font-weight: 800; color: var(--accent);
  font-size: clamp(26px, calc(7 * var(--u)), 56px);
  text-shadow: 0 2px 10px #000a, 0 0 22px rgba(0,0,0,.5); letter-spacing: .5px;
}

/* ---------------- Beginner quest tracker ---------------- */
.quest-tracker {
  /* Positioned by its parent .ql-stack (bottom-left, below the safe badge). */
  max-width: min(58vw, 300px);
  padding: clamp(7px, calc(1.7 * var(--u)), 14px) clamp(10px, calc(2.4 * var(--u)), 18px);
  background: linear-gradient(#171d12f2, #12160fdd);
  border: 1px solid #3c4a2e; border-left: 3px solid var(--accent);
  border-radius: 10px; box-shadow: 0 3px 14px #0007;
  /* Tappable: opens the Quests panel. This deliberately swallows taps in its own footprint
     (it used to be pointer-events:none so tap-to-move worked through it), because players
     could not reach the quest text any other way than ☰ → Quests — suggestion #1001593.
     It is a small bottom-left chip, so the lost walk-target area is minor. */
  pointer-events: auto; cursor: grab; touch-action: none;
  user-select: none; -webkit-user-select: none;
  animation: qt-in .35s ease-out;
}
.quest-tracker:hover { border-color: #55683c; }
.quest-tracker:active { filter: brightness(1.08); }
.quest-tracker.dragging { cursor: grabbing; animation: none; }
.quest-tracker.hidden { display: none; }
/* Opacity only: transform belongs to the drag position. Animating transform here used to win
   over the inline drag offset, making the tracker appear to move by just one pressed-state pixel. */
@keyframes qt-in { from { opacity: 0; } to { opacity: 1; } }
.quest-tracker .qt-head { display: flex; align-items: baseline; gap: 6px; }
.quest-tracker .qt-scroll { font-size: clamp(12px, calc(3 * var(--u)), 16px); }
.quest-tracker .qt-title {
  font-weight: 800; color: var(--accent); letter-spacing: .3px;
  font-size: clamp(12px, calc(3 * var(--u)), 16px);
}
.quest-tracker .qt-step { margin-left: auto; color: var(--muted); font-size: clamp(10px, calc(2.4 * var(--u)), 13px); font-weight: 600; }
.quest-tracker .qt-objective {
  color: var(--ink); margin-top: 3px; line-height: 1.25;
  font-size: clamp(12px, calc(2.9 * var(--u)), 15px);
}
.quest-tracker .qt-guide {
  color: var(--muted); margin-top: 2px; line-height: 1.2;
  font-size: clamp(10px, calc(2.4 * var(--u)), 13px);
}
/* The panel ignores pointer events (so it never eats map taps); only the × is interactive.
   Styled as a small round chip in the tracker's own woodland palette so it reads as a
   button rather than a stray glyph, and lit with the gold accent on hover/press. */
.quest-tracker .qt-hide {
  pointer-events: auto; cursor: pointer; margin-left: 8px; align-self: center; flex: 0 0 auto;
  position: relative; display: flex; align-items: center; justify-content: center;
  width: clamp(21px, calc(5.2 * var(--u)), 28px); height: clamp(21px, calc(5.2 * var(--u)), 28px);
  border-radius: 50%; border: 1px solid #3c4a2e;
  background: linear-gradient(#1e2617, #141a0f); color: var(--muted);
  font-size: clamp(14px, calc(3.4 * var(--u)), 18px); line-height: 1; padding: 0;
  box-shadow: inset 0 1px 0 rgba(233, 226, 190, .06), 0 1px 3px #0006;
  transition: color .12s ease, background .12s ease, border-color .12s ease, transform .05s ease;
}
/* The chip stays visually small next to the quest title, but the tap target reaches a
   comfortable thumb size. Its click handler stopPropagation()s so dismissing the tracker
   never also opens the Quests panel behind it. */
.quest-tracker .qt-hide::after { content: ''; position: absolute; inset: -8px; border-radius: 50%; }
.quest-tracker .qt-hide:hover {
  color: var(--ink); background: linear-gradient(#2a3420, #1c2414); border-color: var(--accent);
}
.quest-tracker .qt-hide:active { transform: translateY(1px); box-shadow: inset 0 1px 3px #0008; }
.quest-tracker .qt-hide:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Quests modal (☰ → Quests) */
#quest-modal .qm-title { font-weight: 800; color: var(--accent); font-size: 16px; }
#quest-modal .qm-step { color: var(--muted); font-size: 12px; margin-top: 2px; }
#quest-modal .qm-obj { color: var(--ink); margin-top: 8px; line-height: 1.3; }
#quest-modal .qm-guide { color: var(--muted); margin-top: 4px; line-height: 1.25; font-size: 13px; }
#quest-modal .qm-empty { color: var(--muted); line-height: 1.35; }
#quest-modal #quest-toggle { margin-top: 14px; }
#quest-modal #quest-close { margin-top: 8px; }

/* ---------------- Map controls ---------------- */
#map-controls {
  position: absolute; right: clamp(12px, calc(3 * var(--u)), 28px);
  bottom: calc(clamp(104px, calc(26 * var(--u)), 200px) + var(--safe-pad)); z-index: 20;
  display: flex; flex-direction: column; gap: clamp(12px, calc(3 * var(--u)), 24px); align-items: flex-end;
}
.map-btn {
  /* position: relative is LOAD-BEARING. The badges inside these buttons (.chat-badge on 💬 and
     🤝, .menu-dot on ☰) are position:absolute, so they anchor to the nearest positioned
     ancestor. Without this that ancestor is #map-controls — the whole stack — and every badge
     piles into the stack's top-right corner instead of sitting on its own button. In the Field
     Kit HUD that corner is the movement dial, which is how an unread private message came to
     put a "1" on the Explore button. */
  position: relative;
  width: clamp(68px, calc(17 * var(--u)), 130px); height: clamp(68px, calc(17 * var(--u)), 130px); border-radius: clamp(16px, calc(4 * var(--u)), 28px);
  background: #1b2a1fe6; border: 1px solid #314733; color: var(--ink);
  font-size: clamp(30px, calc(7.6 * var(--u)), 58px); cursor: pointer;
}
.map-btn.wide { width: auto; padding: 0 clamp(20px, calc(4.5 * var(--u)), 40px); font-size: clamp(20px, calc(5 * var(--u)), 40px); height: clamp(58px, calc(14 * var(--u)), 100px); }
.map-btn.wide#btn-mode {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: clamp(112px, calc(28 * var(--u)), 220px); line-height: 1.05;
}
#btn-mode .mode-live { display: block; font-weight: 800; }
#btn-mode small {
  display: block; margin-top: 2px; color: var(--muted); font-weight: 700;
  font-size: clamp(11px, calc(2.8 * var(--u)), 22px); white-space: nowrap;
}
.map-btn:active { background: var(--panel-2); }
/* Hidden while the (transparent) minimap is open so the buttons don't bleed through it. */
#map-controls.mm-hide { display: none; }
#btn-install { background: linear-gradient(#d8b25a, #b8862f); color: #1a1306; border-color: #7a5a1c; font-weight: 700; }
#btn-install:active { background: #c8922f; }

/* ---------------- Music controls popover ---------------- */
.music-pop {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 92px; z-index: 35;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  background: #1b2a1ff2; border: 1px solid #314733; border-radius: 12px; padding: 12px 14px;
  box-shadow: 0 8px 22px rgba(0,0,0,.5); width: 220px;
}
.music-pop.hidden { display: none; }
/* Sound settings popover anchored under the top-left cog rather than centred. */
.settings-pop {
  left: clamp(14px, calc(3.6 * var(--u)), 36px); transform: none;
  top: calc(env(safe-area-inset-top, 0px) + clamp(96px, calc(24 * var(--u)), 200px)); bottom: auto;
}

/* ---------------- Menu popover (☰: News, Hall of Wanderers, World Map) ---------------- */
.menu-pop {
  position: absolute; left: 50%; transform: translateX(-50%); z-index: 36;
  bottom: calc(var(--safe-pad) + clamp(120px, calc(30 * var(--u)), 220px));
  display: flex; flex-direction: column; gap: 8px;
  background: #1b2a1ff7; border: 1px solid #314733; border-radius: 14px; padding: 10px;
  box-shadow: 0 10px 26px rgba(0,0,0,.55); min-width: clamp(220px, calc(56 * var(--u)), 360px);
}
.menu-pop.hidden { display: none; }
.menu-item {
  display: flex; align-items: center; gap: 8px; text-align: left; white-space: nowrap;
  padding: clamp(12px, calc(3 * var(--u)), 20px) clamp(14px, calc(3.5 * var(--u)), 22px); border-radius: 10px;
  background: #0e130d; border: 1px solid #314733; color: var(--ink); cursor: pointer;
  font-size: clamp(16px, calc(4.2 * var(--u)), 28px);
}
.menu-item:active { background: var(--panel-2); }
/* Unread-news dot: a small red pip on the ☰ button and beside the News item. */
.menu-dot {
  position: absolute; top: 8px; right: 8px; width: 13px; height: 13px; border-radius: 50%;
  background: #e8503a; border: 2px solid #1b2a1f; box-shadow: 0 0 6px #e8503a;
}
.menu-dot.inline { position: static; width: 11px; height: 11px; border-width: 0; margin-left: 4px; }
.menu-dot.hidden { display: none; }
#btn-menu { position: relative; }

/* News modal content */
.news-card { text-align: left; }
/* Keep Close on screen however long the news gets. .modal-card scrolls its own content, so
   a button at the end of that content scrolls away with it: on an 874pt iPhone the News
   Close button rendered at y≈1093, ~220pt below the fold, and nothing about the panel says
   "scroll for the exit". Sticking it to the bottom of the scrollport costs nothing on a
   desktop (where it never overflows) and makes the panel dismissible in one tap on a phone.
   The negative margins bleed it to the card's edges, cancelling .modal-card's 20px padding;
   the gradient stops list items showing through as they scroll underneath. */
.news-actions {
  position: sticky; bottom: -20px;
  margin: 14px -20px -20px; padding: 12px 20px 20px;
  background: linear-gradient(180deg, rgba(27, 42, 31, 0) 0%, rgba(27, 42, 31, .96) 45%);
}
.news-actions #news-close { width: 100%; }
.news-item { border: 1px solid #314733; border-left-width: 4px; border-radius: 10px; background: #0e130d; padding: 11px 13px; margin-bottom: 10px; }
.news-item.featured { border-left-color: #d98fd5; background: #170f17; }
.news-item h3 { margin: 0 0 4px; font-size: clamp(16px, calc(4.4 * var(--u)), 28px); color: var(--accent); }
.news-item.featured h3 { color: #f0a8ec; }
.news-item p { margin: 0; font-size: clamp(13px, calc(3.6 * var(--u)), 24px); color: var(--ink); line-height: 1.4; }
.news-item .news-date { color: var(--muted); font-size: clamp(11px, calc(3 * var(--u)), 20px); }
.news-item a { color: #f0a8ec; text-decoration: underline; word-break: break-all; }
.guide-intro { margin: 0 0 12px; color: var(--muted); font-size: clamp(12px, calc(3.4 * var(--u)), 22px); }
/* Guides modal: an index of skills, each opening a full page. */
.guide-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.guide-item { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 14px; border: 1px solid #314733; border-radius: 10px; background: #0e130d;
  color: var(--ink); cursor: pointer; }
.guide-item:hover, .guide-item:active { border-color: var(--accent); background: #131a10; }
.guide-item-icon { font-size: clamp(22px, calc(6 * var(--u)), 40px); line-height: 1; }
.guide-item-text { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.guide-item-text strong { color: var(--accent); font-size: clamp(16px, calc(4.4 * var(--u)), 28px); }
.guide-item-text small { color: var(--muted); font-size: clamp(12px, calc(3.2 * var(--u)), 22px); }
.guide-item-arrow { color: var(--muted); font-size: clamp(20px, calc(5 * var(--u)), 34px); }
.guide-back { background: none; border: none; color: var(--accent); cursor: pointer;
  padding: 0 0 8px; font-size: clamp(14px, calc(3.6 * var(--u)), 24px); }
.guide-page h4 { margin: 16px 0 6px; color: var(--accent); font-size: clamp(15px, calc(4 * var(--u)), 26px); }
.guide-page p { margin: 0 0 10px; line-height: 1.45; font-size: clamp(13px, calc(3.6 * var(--u)), 24px); color: var(--ink); }
.guide-page ul { margin: 0 0 10px; padding-left: 20px; }
.guide-page li { margin: 0 0 5px; line-height: 1.4; font-size: clamp(13px, calc(3.6 * var(--u)), 24px); color: var(--ink); }
.guide-page strong { color: #e8d9a0; }
.music-pop .mp-title { font-weight: 700; color: var(--accent); font-size: 13px; }
.music-pop .mp-mute {
  width: 100%; padding: 7px; border-radius: 8px; border: 1px solid #314733;
  background: var(--panel-2); color: var(--ink); cursor: pointer; font-size: 13px; font-weight: 600;
}
.music-pop .mp-mute.active { border-color: var(--accent-2); color: var(--accent-2); }
.music-pop .mp-logout {
  width: 100%; padding: 7px; border-radius: 8px; border: 1px solid #6a3030;
  background: var(--panel-2); color: #e08070; cursor: pointer; font-size: 13px; font-weight: 600;
}
.music-pop .mp-logout:disabled { opacity: .5; cursor: default; }
.music-pop .mp-account { display: flex; flex-direction: column; align-items: center; gap: 6px; color-scheme: light; }
.music-pop .mp-account #link-google-host { display: flex; justify-content: center; min-height: 0; }
.music-pop .mp-linked { font-size: 12px; color: var(--accent-2, #7fd07f); font-weight: 600; }
.music-pop .mp-account-err { font-size: 11px; color: #e08070; text-align: center; }

/* Google link conflict: compare two characters side by side. */
.link-conflict-card { max-width: 460px; }
.link-compare { display: flex; gap: 10px; margin: 12px 0; }
.link-compare .lc-col { flex: 1 1 0; min-width: 0; background: var(--panel-2); border: 1px solid #ffffff14;
  border-radius: 10px; padding: 10px; }
.link-compare .lc-current { border-color: var(--accent-2, #7fd07f); }
.lc-head { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.lc-badge { background: #ffffff1a; border-radius: 6px; padding: 1px 5px; font-size: 10px; letter-spacing: 0; }
.lc-name { font-size: 16px; font-weight: 700; color: var(--accent); margin: 2px 0 8px; overflow-wrap: anywhere; }
.lc-grid { display: grid; grid-template-columns: auto 1fr; gap: 3px 8px; font-size: 12px; }
.lc-grid .lc-k { color: var(--muted); }
.lc-grid .lc-v { text-align: right; font-variant-numeric: tabular-nums; }
.lc-warn { font-size: 12px; color: #e8b54a; background: #e8b54a18; border-radius: 8px; padding: 8px 10px; }
.lc-actions { display: flex; gap: 10px; margin-top: 12px; }
.lc-actions button { flex: 1 1 0; }
.music-pop .mp-links { display: flex; justify-content: center; gap: 14px; font-size: 11px; }
.music-pop .mp-links a { color: var(--muted); text-decoration: none; }
.music-pop .mp-links a:hover { color: var(--accent); text-decoration: underline; }

/* ---------------- Tap-a-player inspect popup ---------------- */
.inspect-pop {
  position: absolute; z-index: 27; width: 168px;
  background: #1b2a1ff5; border: 1px solid #314733; border-radius: 10px;
  padding: 9px 11px; box-shadow: 0 8px 22px rgba(0,0,0,.5);
  color: var(--ink); text-align: center;
}
.inspect-pop.hidden { display: none; }
.inspect-pop .ip-name { font-weight: 700; font-size: 14px; }
.inspect-pop .ip-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }
/* Foe combat-level threat colour vs your own level (set in game.js _showInspect). */
.ip-lvl { font-weight: 700; }
.ip-lvl.threat-easy { color: #8fd07a; }   /* well below you — easy */
.ip-lvl.threat-even { color: #f0d68a; }   /* near your level — fair fight */
.ip-lvl.threat-hard { color: #f08a7a; }   /* well above you — dangerous */
.ip-attack {
  margin-top: 8px; width: 100%; height: 34px; border: none; border-radius: 8px;
  background: linear-gradient(#b6432e, #8d2f20); color: #fff; font-weight: 700;
  font-size: 14px; cursor: pointer;
}
.ip-attack:active { filter: brightness(1.15); }
.ip-attack.hidden { display: none; }

/* ---------------- Chat ---------------- */
#btn-chat { position: relative; }
.chat-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 999px; background: var(--accent); color: #11160f;
  font-size: 11px; font-weight: 700; line-height: 16px; text-align: center;
}
.chat-badge.hidden { display: none; }
.chat {
  position: absolute; left: 8px; right: 8px; bottom: calc(clamp(80px, calc(19 * var(--u)), 160px) + var(--safe-pad));
  /* 30, well above #quick-slots (26): the slots share this exact `bottom` offset, so they sit
     inside the chat window's lower edge. At an equal z-index the slots won on DOM order alone
     (they come later in index.html) and covered the newest messages. #bottom-nav is also 30 and
     still wins the tie the same way, which is right — chat stops above the nav. */
  z-index: 30; height: 44%; max-height: 360px; display: flex; flex-direction: column;
  background: #11160ff5; border: 1px solid #314733; border-radius: 12px 12px 0 0;
  box-shadow: 0 -8px 24px rgba(0,0,0,.5); overflow: hidden;
}
.chat.hidden { display: none; }
.chat-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 10px; border-bottom: 1px solid #314733; font-weight: 700; color: var(--accent);
  font-size: clamp(18px, calc(4.8 * var(--u)), 32px);
}
/* Channel tabs (General / Bug Reports / Suggestions). Sized well under the head's title
   scale; scrolls horizontally on the narrowest phones rather than wrapping. */
.chat-tabs { display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; min-width: 0; }
.chat-tabs::-webkit-scrollbar { display: none; }
.chat-tab {
  position: relative; flex: 0 0 auto; padding: 6px 12px; border-radius: 8px;
  background: transparent; border: 1px solid transparent; color: var(--muted);
  font: inherit; font-weight: 700; font-size: clamp(14px, calc(3.6 * var(--u)), 22px);
  cursor: pointer; white-space: nowrap;
}
.chat-tab.active { background: #1c2618; border-color: #314733; color: var(--accent); }
.chat-tab-dot {
  position: absolute; top: 3px; right: 3px; width: 8px; height: 8px; border-radius: 50%;
  background: #e8503a; box-shadow: 0 0 4px #e8503a;
}
.chat-tab-dot.hidden { display: none; }
.chat-log { flex: 1; overflow-y: auto; padding: 8px 12px; display: flex; flex-direction: column; gap: 5px; }
.chat-msg { font-size: clamp(15px, calc(4.2 * var(--u)), 28px); line-height: 1.35; word-break: break-word; }
.chat-msg b { color: var(--accent-2); margin-right: 4px; }
.chat-msg.mine b { color: var(--accent); }
.chat-name { cursor: pointer; }
.chat-name:active { text-decoration: underline; }
/* Bridged from Discord: blurple name, no profile tap. */
.chat-msg b.chat-discord { color: #8b9dff; cursor: default; }
.chat-msg b.chat-discord:active { text-decoration: none; }
.chat-time { color: var(--muted); font-size: 0.74em; opacity: .65; margin-right: 5px; font-variant-numeric: tabular-nums; }
/* Moderator warning: red, bordered, stands apart from player chatter. */
.chat-msg.chat-mod { color: #ff6b5e; background: #2a14129a; border-left: 3px solid #e8503a; border-radius: 6px; padding: 4px 8px; margin: 3px 0; font-weight: 600; }
.chat-msg.chat-mod b { color: #ff8a7e; }
/* Soulfire celebration line — gold, warm, and clearly not a warning. Same ephemeral system
   shape as .chat-mod above, deliberately opposite in colour: one is "you broke a rule", the
   other is "someone just paid for everyone's boost". */
.chat-msg.chat-celebrate { color: #ffd980; background: #2a24129a; border-left: 3px solid #d8a94a; border-radius: 6px; padding: 4px 8px; margin: 3px 0; font-weight: 600; }
.chat-msg.chat-celebrate b { color: #fff3d2; }
.chat-lv { color: var(--muted); font-size: 0.82em; opacity: .85; margin-right: 4px; font-weight: 700; }
/* A line a moderator has taken the words off. Deliberately QUIET rather than alarming: the
   tombstone is a gap in the conversation, not an accusation, and the eye should slide past it.
   The speaker's name keeps its normal colour so the thread still reads. */
.chat-msg.chat-deleted { opacity: .72; }
.chat-msg.chat-deleted .chat-gone { color: var(--muted); font-style: italic; opacity: .8; }
/* Moderator-only delete control, one per line. Sits at the end of the row, near-invisible until
   the row is hovered so it never competes with the words; always fully visible on touch, where
   there is no hover to reveal it. Sized to the 24px minimum tap target. */
.chat-del { border: 0; background: none; color: var(--muted); cursor: pointer; padding: 0 4px;
  font-size: 0.85em; line-height: 1; opacity: .28; transition: opacity .12s, color .12s;
  min-width: 24px; min-height: 24px; vertical-align: middle; }
.chat-msg:hover .chat-del { opacity: .7; }
.chat-del:hover, .chat-del:active { opacity: 1; color: #ff6b5e; }
@media (hover: none) { .chat-del { opacity: .55; } }
/* "auto translated" tag on machine-translated lines: quiet, italic, set apart from the words. */
.chat-trtag { color: var(--muted); font-size: 0.72em; font-style: italic; opacity: .6; white-space: nowrap; }
.chat-trtag::before { content: "· "; }
/* Flag glyph in the Language settings button/title. */
.lang-flag { font-size: 1.05em; }
/* Level-band tint (echoes the map's monster threat palette: green→tan→orange→red). */
.chat-lv.lv-new { color: #9ad06a; }
.chat-lv.lv-mid { color: #e6b6a6; }
.chat-lv.lv-vet { color: #f0a24a; }
.chat-lv.lv-elite { color: #ff8a3c; }
.chat-typing {
  padding: 0 12px 4px; min-height: 1.1em; font-style: italic; color: #9bb08a;
  font-size: clamp(12px, calc(3.2 * var(--u)), 19px); opacity: 0.85;
}
.chat-typing.hidden { display: none; }
/* "New messages" chip: appears when a line arrives while the reader is scrolled up; tap to jump. */
.chat-jump {
  align-self: center; margin: 0 0 4px; padding: 5px 14px; border-radius: 999px; cursor: pointer;
  border: 1px solid #6a4f1c; background: linear-gradient(#d8b25a, #b8862f); color: #1a1306; font-weight: 700;
  font-size: clamp(12px, calc(3.2 * var(--u)), 20px); box-shadow: 0 3px 10px rgba(0,0,0,.4);
}
.chat-jump.hidden { display: none; }
.chat-form { display: flex; gap: 6px; padding: 8px; border-top: 1px solid #314733; }
.chat-form input {
  flex: 1; min-width: 0; height: clamp(42px, calc(11 * var(--u)), 64px); padding: 0 14px; border-radius: 8px;
  border: 1px solid #314733; background: #0e130d; color: var(--ink); font-size: clamp(16px, calc(4.2 * var(--u)), 28px);
}
.chat-send {
  height: clamp(42px, calc(11 * var(--u)), 64px); padding: 0 clamp(16px, calc(4.4 * var(--u)), 30px); border: none; border-radius: 8px;
  background: var(--accent); color: #11160f; font-weight: 700; cursor: pointer; font-size: clamp(16px, calc(4.2 * var(--u)), 28px);
}

/* ---------------- Notifications ---------------- */
/* Toasts hang below the status bar. Its height is fluid — it grows with --u, wraps on
   narrow screens, and gains a line when the boost/safe pills show — so a fixed offset put
   the "you defeated X" toast straight on top of the HP pill. notifications.js measures the
   real header and publishes it as --hud-h; the fallback only applies before that runs.
   --hud-h already includes the bar's safe-area padding, so no env() here. */
#notifications {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(var(--hud-h, calc(env(safe-area-inset-top, 0px) + 78px)) + 8px);
  /* UNDER the menus (Dave, 2026-08-19). 25 sits above the world and the HUD (20-23) but below
     every surface that opens over it — quick slots (26), the inspect popup (27), panels and the
     minimap (28), chat and the nav (30), modals (40), the item-found moment (70) and the boot
     splash (90). Toasts used to be 50, i.e. on top of everything short of the treasure moment,
     which meant a run of loot lines painted across whatever screen the player had open.
     Still a child of <body> rather than #game-screen; see the comment on it in index.html. */
  width: min(94%, 620px); z-index: 25;
  /* align-items rather than a full-width column: each toast hugs its own text, so a short
     "+12 gold" is a small chip instead of a 620px bar across the top of the world. */
  display: flex; flex-direction: column; align-items: center; gap: clamp(3px, calc(.9 * var(--u)), 6px);
  pointer-events: none;
}
.toast {
  max-width: 100%;
  /* Half-transparent (Dave, 2026-08-19): the world reads through the chip rather than being
     blocked out by it. The blur below keeps the text legible over busy ground. */
  background: #141d1780; border: 1px solid #2b3a2d; border-left: 3px solid var(--accent);
  border-radius: 7px; padding: clamp(6px, calc(1.6 * var(--u)), 10px) clamp(10px, calc(2.6 * var(--u)), 17px);
  font-size: clamp(13px, calc(3.4 * var(--u)), 21px); line-height: 1.25; letter-spacing: .2px;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  box-shadow: 0 2px 10px rgba(0,0,0,.35), inset 0 1px 0 #ffffff10;
  animation: toast-in .25s ease, toast-out .4s ease 4.6s forwards;
}
.toast.good { border-left-color: var(--accent-2); }
.toast.warn { border-left-color: var(--danger); }
/* Loot toasts emphasised by rarity (colours match the inventory rarity palette). Common
   tiers (worn/standard/refined) keep the plain 'good' look; Rare+ get colour, Epic/Relic glow. */
.toast.loot-rare  { border-left-color: #5a86c0; }
.toast.loot-epic  { border-left-color: #b46fd0; border-color: #4a335a; box-shadow: 0 2px 10px rgba(0,0,0,.35), 0 0 10px rgba(180,111,208,.35), inset 0 1px 0 #ffffff10; }
.toast.loot-relic { border-left-color: #d8a24a; border-color: #6a5326; box-shadow: 0 2px 10px rgba(0,0,0,.35), 0 0 12px rgba(216,162,74,.45), inset 0 1px 0 #ffffff10; }
/* Discovery toasts: map-blue for a personal first visit; gold + glow for a global-first find. */
.toast.discover { border-left-color: #5f9bd8; }
.toast.first { border-left-color: #d8a24a; border-color: #6a5326; box-shadow: 0 2px 10px rgba(0,0,0,.35), 0 0 12px rgba(216,162,74,.45), inset 0 1px 0 #ffffff10; font-weight: 600; }
@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(-6px); } }

/* Server-wide announcements (boss kills, Relic finds). Centered, bolder, held ~2.5x longer than a
   normal toast (the out-animation is delayed to ~12.5s to match notifications.js ANNOUNCE_LIFE_MS),
   with a soft pulsing glow and a few drifting sparks. Variant colour is carried by --glow. */
.toast.announce {
  /* the one toast that is allowed to be loud — but it still rides the slimmer base metrics,
     buying its emphasis with weight, colour and glow rather than bulk. */
  text-align: center; font-weight: 700; letter-spacing: .3px; position: relative; overflow: visible;
  font-size: clamp(14px, calc(3.7 * var(--u)), 23px);
  padding: clamp(7px, calc(1.9 * var(--u)), 12px) clamp(14px, calc(3.4 * var(--u)), 22px);
  border-left-width: 1px; background: #12161de8;
  animation: toast-in .3s ease, toast-out .5s ease 12.4s forwards;
}
.toast.announce::before {
  content: ''; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none;
  box-shadow: 0 0 22px 2px var(--glow, #fff); opacity: .3;
  animation: announce-pulse 2.2s ease-in-out infinite;
}
.toast.announce-boss  { color: #c3e6ff; border-color: #2f4a63; --glow: rgba(120,190,255,.6);  text-shadow: 0 0 9px rgba(120,190,255,.55); }
.toast.announce-relic { color: #ffe6a3; border-color: #6a5326; --glow: rgba(255,208,110,.65); text-shadow: 0 0 9px rgba(255,208,110,.6); }
.toast.announce .spark {
  position: absolute; bottom: 6px; width: 3px; height: 3px; border-radius: 50%;
  background: var(--glow, #fff); box-shadow: 0 0 7px 1px var(--glow, #fff);
  opacity: 0; pointer-events: none; animation: spark-float 2.4s ease-in infinite;
}
@keyframes announce-pulse { 0%, 100% { opacity: .22; } 50% { opacity: .6; } }
@keyframes spark-float {
  0%   { opacity: 0; transform: translateY(0) scale(.5); }
  18%  { opacity: .95; }
  100% { opacity: 0; transform: translateY(-40px) scale(1); }
}

/* ---------------- Consumable quick slots (2, right above the bottom nav) ---------------- */
/* 26 keeps the slots under every window that opens over the bottom of the screen — .panel and
   .minimap (28), the inspect popup (27) and .chat (30) — since they share the panels' `bottom`
   offset and would otherwise float on top of an open Character/Skills/Shop window. */
#quick-slots {
  position: absolute; left: 0; right: 0; z-index: 26;
  bottom: calc(clamp(80px, calc(19 * var(--u)), 160px) + var(--safe-pad));
  display: flex; justify-content: center; gap: clamp(10px, calc(2.6 * var(--u)), 18px);
  pointer-events: none; /* the bar itself is a layout box; only its buttons are tappable */
}
.qs-slot { position: relative; pointer-events: none; width: clamp(46px, calc(12 * var(--u)), 64px); height: clamp(46px, calc(12 * var(--u)), 64px); }
.qs-main {
  width: 100%; height: 100%; border-radius: 10px; pointer-events: auto;
  background: #161d14; border: 1px solid #243024; color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-size: clamp(22px, calc(6 * var(--u)), 32px); padding: 3px; cursor: pointer;
}
.qs-main.assigned { border-color: #314733; }
.qs-main img { width: 100%; height: 100%; object-fit: contain; image-rendering: pixelated; }
/* "Any food" has no item art — it is a rule, not an item — so it renders as a glyph sized to
   fill the same box the icon would. */
.qs-main .qs-any { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 1.05em; line-height: 1; }
.qs-qty {
  position: absolute; top: -10px; left: 50%; transform: translateX(-50%); pointer-events: none;
  font-size: 11px; font-weight: 800; color: var(--fg); background: #11160fef;
  padding: 1px 6px; border-radius: 999px; white-space: nowrap; border: 1px solid #314733;
}
.qs-x, .qs-toggle, .qs-settings {
  position: absolute; width: 19px; height: 19px; border-radius: 50%; padding: 0;
  pointer-events: auto; cursor: pointer; font-size: 10px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.qs-x { top: -6px; right: -6px; background: var(--danger); color: #fff; border: none; }
/* Auto-use toggle and its settings cog are the two the thumb actually aims for, so they get a
   noticeably larger target than the little remove "x" above them. */
.qs-toggle, .qs-settings {
  width: clamp(26px, calc(6.6 * var(--u)), 36px); height: clamp(26px, calc(6.6 * var(--u)), 36px);
  font-size: clamp(14px, calc(3.6 * var(--u)), 19px);
}
.qs-toggle { bottom: -8px; left: -8px; background: #11160f; border: 1px solid #314733; color: var(--muted); }
.qs-toggle.on { background: var(--accent); border-color: var(--accent); color: #11160f; }
.qs-settings { bottom: -8px; right: -8px; background: #11160f; border: 1px solid #314733; color: var(--muted); }

/* Auto-use settings popover content (reuses the shared .modal/.modal-card chrome). */
.qs-settings-group { margin-bottom: 14px; }
.qs-settings-label { font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.qs-settings-row { display: flex; gap: 8px; flex-wrap: wrap; }
.qs-opt {
  flex: 1; min-width: 64px; padding: 8px 10px; border-radius: 8px;
  background: #161d14; border: 1px solid #243024; color: var(--muted); cursor: pointer; font-size: 13px;
}
.qs-opt.active { background: #1c2618; border-color: var(--accent); color: var(--accent); }

/* ---------------- Bottom navigation ---------------- */
#bottom-nav {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex; background: #11160ff2; border-top: 1px solid #314733;
  padding-bottom: var(--safe-pad);
}
.nav-btn {
  flex: 1; position: relative; background: transparent; border: none; color: var(--muted);
  padding: clamp(14px, calc(3.4 * var(--u)), 28px) 0 clamp(13px, calc(3.2 * var(--u)), 26px); font-size: clamp(33px, calc(8.4 * var(--u)), 64px); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: clamp(4px, calc(1 * var(--u)), 9px);
}
.nav-btn span { font-size: clamp(16px, calc(4 * var(--u)), 30px); }
.nav-btn.active { color: var(--accent); }
/* "Pack full" warning dot on the Inventory nav button (drops auto-sell when full). */
.nav-badge {
  position: absolute; top: clamp(6px, calc(1.6 * var(--u)), 14px); right: 28%;
  min-width: clamp(16px, calc(4 * var(--u)), 26px); height: clamp(16px, calc(4 * var(--u)), 26px);
  border-radius: 999px; background: var(--danger); color: #fff; font-weight: 800;
  font-size: clamp(11px, calc(2.8 * var(--u)), 18px); line-height: clamp(16px, calc(4 * var(--u)), 26px);
  text-align: center; box-shadow: 0 0 6px rgba(210,74,74,.7);
}
.nav-badge.hidden { display: none; }

/* ---------------- Slide-up panel ---------------- */
/* WIDTH. This is a phone-first bottom sheet, and left:0/right:0 let it span whatever screen it
   found: on a tablet or desktop the Inventory grid ran fifteen slots across, and Shop rows put
   their name against the far left edge and their buy button against the far right with a lake of
   empty panel in between. It now stops growing past a comfortable reading width and centres
   instead — phones are unaffected (min() picks 100%). The slide-in transform carries the centring
   too, since both states share the transform property. */
.panel {
  position: absolute; left: 50%; right: auto;
  width: min(100%, 760px);
  bottom: calc(clamp(80px, calc(19 * var(--u)), 160px) + var(--safe-pad)); z-index: 28;
  max-height: 60%; background: var(--panel);
  border-top: 1px solid #314733; border-radius: 16px 16px 0 0;
  box-shadow: 0 -10px 30px rgba(0,0,0,.5);
  transform: translate(-50%, 100%); transition: transform .25s ease;
  display: flex; flex-direction: column;
}
.panel:not(.hidden) { transform: translate(-50%, 0); }
/* Once it stops touching the screen edges it reads as a card, so round the bottom corners too. */
@media (min-width: 800px) { .panel { border-radius: 16px; border: 1px solid #314733; } }
.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 18px; border-bottom: 1px solid #2a3d2c; font-weight: 700; font-size: clamp(20px, calc(5.4 * var(--u)), 40px);
}
/* Pack fullness beside the "Inventory" title — lighter than the title, red once full. */
.panel-title-cap { margin-left: 10px; color: var(--muted); font-weight: 400; font-size: clamp(15px, calc(4 * var(--u)), 28px); }
.panel-title-cap.full { color: #f87171; font-weight: 700; }
.panel-close { background: none; border: none; color: var(--muted); font-size: clamp(28px, calc(7 * var(--u)), 50px); cursor: pointer; }
/* Inside a dialog card, the ✕ (hand-written or injected by ui.js ensureDialogX) sits in the
   top-right corner in normal flow — floated so it never overlaps a title and needs no
   per-dialog layout. Negative margins bite into the card's 20px padding to keep it tight. */
.modal-card > .panel-close { float: right; margin: -12px -12px 0 10px; padding: 2px 8px; line-height: 1; }
.modal-card.parchment > .panel-close { color: #6b5a38; } /* var(--muted) is unreadable on paper */
/* overflow-anchor:none stops the browser nudging the scroll position when the list is rebuilt
   under the viewport (a live update riding in with each toast) — otherwise it creeps down a bit. */
.panel-body { padding: 16px 18px 24px; overflow-y: auto; overflow-anchor: none; font-size: clamp(18px, calc(4.8 * var(--u)), 34px); }

/* Thin progress bar + small status chips — retained for the compact Skills list. */
.ubar { height: clamp(6px, calc(1.5 * var(--u)), 9px); background: #0c110b; border-radius: 5px; overflow: hidden; box-shadow: inset 0 0 0 1px #223528; }
.ubar > i { display: block; height: 100%; background: linear-gradient(90deg, var(--accent-2), var(--accent)); border-radius: 5px; }
.wchip { display: inline-block; padding: 1px clamp(6px,calc(1.7*var(--u)),9px); border-radius: 999px; font-size: clamp(10px, calc(2.9 * var(--u)), 16px); font-weight: 800; letter-spacing: .02em; vertical-align: middle; white-space: nowrap; }
.wchip.warn { background: #3a1c1c; color: #ffb1a3; border: 1px solid #5a2c25; }
.wchip.ok   { background: #14241a; color: #8fd6a3; border: 1px solid #3f6e4f; }
.wchip.info { background: #1b2536; color: #9db8e8; border: 1px solid #33456a; }
.wchip.gold { background: #2c2410; color: #f0c878; border: 1px solid #6a4f1c; }

/* Character/Shop text is sized off the SAME scale as the Skills and pack rows (.sk-name /
   .sk-xp), so every panel in the game reads at one size. Body text clamp(14,3.9u,24),
   secondary clamp(11,3u,17), section headings clamp(12,3.2u,20). Change them together. */
.stat-row { display: flex; justify-content: space-between; padding: clamp(6px,calc(1.7 * var(--u)),10px) 0;
  border-bottom: 1px solid #223528; font-size: clamp(14px, calc(3.9 * var(--u)), 24px); }
.stat-row span:last-child { color: var(--accent); font-weight: 600; }
.my-profile-btn {
  display: block; width: 100%; margin: 10px 0 4px; padding: clamp(8px, calc(2.1 * var(--u)), 13px);
  border-radius: 10px; border: 1px solid #4a5a3a; background: var(--panel-2); color: var(--accent-2);
  cursor: pointer; font-weight: 700; font-size: clamp(13px, calc(3.5 * var(--u)), 20px);
}
.my-profile-btn:active { background: #2a3a22; }
.logout-btn {
  display: block; width: 100%; margin-top: 20px; padding: clamp(14px, calc(3.6 * var(--u)), 26px);
  border: 1px solid #5a2a2a; border-radius: 10px; background: #2a1a1a;
  color: #e7b3b3; font-weight: 600; font-size: clamp(17px, calc(4.6 * var(--u)), 32px); cursor: pointer;
}
.logout-btn:hover { background: #3a2020; }
.logout-btn:disabled { opacity: 0.6; cursor: default; }
/* Gold-fed variant of the shared thin progress bar (the combat-focus skill). */
.ubar.gold { box-shadow: inset 0 0 0 1px #6a4f1c, 0 0 6px 0 rgba(232,196,90,.35); }
.ubar.gold > i { background: linear-gradient(90deg, #e8c45a, #f0d27a); }
.placeholder { color: var(--muted); text-align: center; padding: 24px 8px; }

/* ---------------- Skills list (compact rows) ---------------- */
.sk-group { color: var(--muted); font-size: clamp(11px, calc(3 * var(--u)), 17px); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700; margin: 10px 0 7px; }
.sk-group:first-child { margin-top: 0; }
/* Group header that carries the Gathering & Crafting sort control on its right. */
.sk-group-sortable { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sk-sort { display: inline-flex; align-items: center; gap: 6px; text-transform: none; letter-spacing: 0;
  font-weight: 600; color: var(--muted); font-size: clamp(10px, calc(2.7 * var(--u)), 15px); }
.sk-sort select { background: #1a2212; color: #e8d9a0; border: 1px solid #4a5a3a; border-radius: 6px;
  padding: clamp(3px,calc(1 * var(--u)),7px) clamp(6px,calc(1.6 * var(--u)),12px);
  font-size: clamp(11px, calc(2.9 * var(--u)), 16px); font-family: inherit; cursor: pointer; }
.sk-row { display: flex; align-items: center; gap: 10px; padding: clamp(7px,calc(1.9*var(--u)),11px) 0; border-bottom: 1px solid #1e2c20; }
.sk-badge { flex-shrink: 0; width: clamp(30px,calc(8.2*var(--u)),44px); height: clamp(30px,calc(8.2*var(--u)),44px);
  display: flex; align-items: center; justify-content: center; border-radius: 9px; font-weight: 900;
  font-size: clamp(15px,calc(4*var(--u)),24px); color: #11160f; background: var(--sc);
  box-shadow: inset 0 1px 1px rgba(255,255,255,.3), inset 0 -2px 4px rgba(0,0,0,.28); }
.sk-mid { flex: 1; min-width: 0; }
.sk-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 5px; }
.sk-name { font-weight: 700; font-size: clamp(14px, calc(3.9 * var(--u)), 24px); }
.sk-focus { margin-left: 6px; }
.sk-lvl { color: var(--muted); font-size: clamp(12px, calc(3.2 * var(--u)), 20px); white-space: nowrap; }
.sk-lvl b { color: var(--accent); font-weight: 800; font-size: 1.15em; }
.sk-xp { color: var(--muted); font-size: clamp(11px, calc(3 * var(--u)), 17px); margin-top: 4px; }

/* ---- Minimap overlay (a 1:10 live-world Phaser camera renders into the body) ---- */
.minimap {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 95%; z-index: 28;
  /* Transparent: the Phaser minimap camera renders into the single game canvas BEHIND this
     box, so the box (and its body) must be see-through for the terrain to show. Only the
     head carries a fill. */
  background: transparent; border: 1px solid #314733; border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0,0,0,.55); overflow: hidden;
}
.minimap.hidden { display: none; }
.minimap-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px; border-bottom: 1px solid #2a3d2c; font-weight: 700;
  font-size: clamp(18px, calc(4.8 * var(--u)), 34px);
  background: var(--panel);
}
/* Transparent: the Phaser minimap camera draws the terrain behind this rectangle. */
.minimap-body {
  position: relative; height: clamp(150px, 72vh, 1000px); background: transparent;
}
/* "You are here" — the camera centres on the player, so the marker sits dead centre. */
.mm-here {
  position: absolute; top: 50%; left: 50%; width: 10px; height: 10px;
  transform: translate(-50%, -50%); border-radius: 50%;
  background: #fff; box-shadow: 0 0 0 2px #11160f, 0 0 8px 3px rgba(255,255,255,.6);
  pointer-events: none;
}
.mm-here::after {
  content: ''; position: absolute; top: 50%; left: 50%; width: 22px; height: 22px;
  transform: translate(-50%, -50%); border-radius: 50%;
  border: 1px solid rgba(255,255,255,.5); animation: mm-ping 1.8s ease-out infinite;
}
@keyframes mm-ping { 0% { transform: translate(-50%,-50%) scale(.6); opacity: .9; } 100% { transform: translate(-50%,-50%) scale(1.6); opacity: 0; } }
.mm-info {
  position: absolute; left: 8px; bottom: 8px; pointer-events: none;
  padding: 3px 8px; border-radius: 6px; background: rgba(10,13,8,.7); color: #e8d9a0;
  font-size: clamp(12px, calc(3.2 * var(--u)), 22px);
}
/* Manual-mode walk-to marker (placed by tapping the minimap). */
.mm-x {
  position: absolute; transform: translate(-50%, -50%); pointer-events: none;
  color: #ff3b3b; font-weight: 900; line-height: 1;
  font-size: clamp(15px, calc(4 * var(--u)), 26px);
  text-shadow: 0 0 2px #000, 0 0 4px #000;
}
.mm-x.hidden { display: none; }

/* Static compass on the minimap (always north-up). Subtle edge letters that orient the
   east-west corridor without cluttering the terrain view. */
.mm-compass { position: absolute; inset: 0; pointer-events: none; }
.mm-c {
  position: absolute; color: #e8d9a0; opacity: .72; font-weight: 800;
  font-size: clamp(11px, calc(3 * var(--u)), 18px); line-height: 1;
  text-shadow: 0 0 2px #000, 0 0 4px #000;
}
.mm-c-n { top: 4px; left: 50%; transform: translateX(-50%); }
.mm-c-s { bottom: 4px; left: 50%; transform: translateX(-50%); }
.mm-c-w { left: 5px; top: 50%; transform: translateY(-50%); }
.mm-c-e { right: 5px; top: 50%; transform: translateY(-50%); color: #f0a24a; } /* east = tougher */

/* ---------------- Modals ---------------- */
.modal {
  position: absolute; inset: 0; z-index: 40;
  background: #000000aa; display: flex; align-items: center; justify-content: center;
  /* Keep the card (and its top-right ✕ close) clear of the phone status bar / notch and the
     home-indicator — a tall dialog is centred but can reach the screen edges, and the ✕ used
     to sit under the status bar and be hard to tap. */
  padding: calc(20px + env(safe-area-inset-top, 0px)) 20px calc(20px + env(safe-area-inset-bottom, 0px));
}
/* The confirm dialog alone has to outrank the BOOT SPLASH (.splash, z-index 90 — it deliberately
   outlives the reveal of #game-screen and covers the HUD until the world is drawn). Every other
   modal is opened by the player, who cannot tap anything while the splash is up; a confirm is the
   one that the SERVER can raise mid-boot, and the "Opened somewhere else" eviction notice is
   exactly that. At .modal's z-index 40 it opened UNDERNEATH the splash — invisible and untappable
   — so an evicted window read as an ordinary slow load and then behaved like a live one, taking
   taps and changing nothing (Killerworm, 2026-08-25). */
#confirm-modal { z-index: 95; }
.modal-card {
  width: 100%; max-width: 380px;
  border: 1px solid #314733; border-radius: 16px; padding: 20px;
  /* Never taller than the (padded) viewport — scroll inside instead, so the
     bottom controls (e.g. Done) stay reachable on short landscape phones. */
  max-height: 100%; overflow-y: auto;
  /* Scroll cue: a soft shadow/fade shows at the top and/or bottom edge whenever
     there's more content to scroll to, and auto-hides once you reach an end. Pure
     CSS (Lea Verou's scroll-shadow trick): the two solid "cover" layers scroll
     WITH the content (background-attachment: local) and mask the fixed shadow
     layers except where content actually overflows. Makes it obvious the dialog
     scrolls on PC and unusual window sizes. rgba(27,42,31,*) == var(--panel). */
  background:
    linear-gradient(var(--panel) 30%, rgba(27, 42, 31, 0)) top / 100% 30px no-repeat,
    linear-gradient(rgba(27, 42, 31, 0), var(--panel) 70%) bottom / 100% 30px no-repeat,
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)) top / 100% 14px no-repeat,
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)) bottom / 100% 14px no-repeat;
  background-color: var(--panel);
  background-attachment: local, local, scroll, scroll;
}

/* Interaction-spot readables (#interact-modal): parchment, deliberately unlike the
   brown menus — pale paper, dark ink, serif body — so notes/plaques/guestbooks feel
   like objects, not another game window. */
.modal-card.parchment {
  background: linear-gradient(160deg, #efe6cd, #e6d9b8 60%, #ddcfa9);
  border: 1px solid #b9a67c; border-radius: 8px;
  box-shadow: 0 8px 30px #000c, inset 0 0 60px #c9b88e55;
  color: #3a2f1c;
}
.modal-card.parchment h2 {
  color: #4a3a20; margin: 0 0 10px; font-size: 19px;
  cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none;
}
.modal-card.parchment.dragging h2 { cursor: grabbing; }
.parchment-text {
  margin: 0; white-space: pre-wrap; line-height: 1.45; font-size: 14px;
  font-family: Georgia, 'Times New Roman', serif; color: #3a2f1c;
}
.parchment-reward { margin: 12px 0 0; font-weight: 600; color: #6a5115; font-size: 13px; }
.parchment-close {
  display: block; margin: 16px auto 0; min-width: 130px; padding: 10px 20px;
  border: 1px solid #a08a58; border-radius: 8px; cursor: pointer;
  background: linear-gradient(180deg, #d8c69a, #c4af7e); color: #3a2f1c; font-weight: 600;
}
.parchment-close:active { transform: translateY(1px); }

/* House cabinet storage (#storage-modal): a wider two-column dialog — cabinet | pack. */
#storage-modal .storage-card { max-width: 640px; }
.storage-sub { color: var(--muted); margin: 0 0 12px; font-size: 13px; }
.storage-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.storage-col .inv-list { max-height: 46vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
@media (max-width: 520px) { .storage-cols { grid-template-columns: 1fr; } .storage-col .inv-list { max-height: 30vh; } }
/* Stylized Close: a warm-wood pill that echoes the cabinet, with a subtle sheen + press feel. */
#storage-modal .build-actions { margin-top: 14px; }
#storage-close {
  min-width: 150px; padding: 11px 22px;
  border: 1px solid #6a5734; border-radius: 10px;
  background: linear-gradient(#3d2e1c, #2a2015);
  color: #f2dfad; font-weight: 800; letter-spacing: .4px; cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 236, 190, 0.08), 0 2px 6px rgba(0, 0, 0, 0.35);
  transition: background .12s ease, border-color .12s ease, transform .05s ease;
}
#storage-close:hover { background: linear-gradient(#4c3a24, #362817); border-color: #8a7038; color: #ffe9b8; }
#storage-close:active { transform: translateY(1px); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4); }

/* Generic confirm dialog (#confirm-modal): message + Cancel/Confirm row. */
.confirm-msg { margin: 6px 0 16px; line-height: 1.4; }
.confirm-actions { display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap; }
.confirm-actions button { padding: 8px 16px; }

/* Account deletion (#delete-account-modal): a typed confirmation before an irreversible
   action, so it gets a real label + input rather than the plain confirm dialog. The delete
   button is red — this is the one destructive action in Settings. */
.del-acct-label { display: block; margin: 0 0 6px; font-size: .9em; opacity: .9; }
#del-acct-input { width: 100%; box-sizing: border-box; padding: 9px 10px; margin: 0 0 8px;
  border: 1px solid #6b5330; border-radius: 8px; background: #201a12; color: #f0e2c4;
  font: inherit; }
#del-acct-input:focus { outline: none; border-color: #a98544; box-shadow: 0 0 0 2px rgba(169, 133, 68, .3); }
#del-acct-err:empty { display: none; }
#del-acct-err { margin: 0 0 10px; font-size: .9em; color: #e08070; }
#del-acct-go { background: linear-gradient(#8c2f26, #6a201a); border-color: #b4574a; color: #ffe4de; }
#del-acct-go:hover:not(:disabled) { background: linear-gradient(#a3382d, #7d271f); }
#del-acct-go:disabled { opacity: .6; cursor: default; }

/* ☰ → Discord hub: Join / Link-account options + the persistent link-code panel. */
.discord-card p { margin: 0 0 12px; line-height: 1.4; }
.discord-card > button { display: block; width: 100%; margin: 0 0 10px; }
.discord-card #discord-close { background: none; border: 1px solid #314733; border-radius: 10px;
  color: var(--muted); font-weight: 600; padding: 10px; cursor: pointer; margin-top: 4px; }
.discord-link-panel { margin: 2px 0 10px; padding: 12px; border: 1px solid #314733; border-radius: 10px; background: #0e130d; }
.discord-link-panel.hidden { display: none; }
.discord-link-panel p { font-size: 14px; margin: 0 0 8px; }
.discord-muted { color: var(--muted); font-size: 13px; }
.discord-code { display: flex; align-items: center; gap: 10px; margin: 6px 0 10px; }
.discord-code span { flex: 1; font-family: ui-monospace, monospace; font-size: clamp(22px, 7vw, 30px);
  font-weight: 800; letter-spacing: .22em; color: var(--accent); background: #11160f;
  border: 1px solid #314733; border-radius: 8px; padding: 8px 12px; text-align: center; user-select: all; }
.discord-code button { border: 1px solid #314733; border-radius: 8px; background: var(--panel-2);
  color: var(--ink); font-weight: 700; padding: 8px 14px; cursor: pointer; white-space: nowrap; }
.discord-link-panel .danger { border: none; border-radius: 8px; background: #7a2a22; color: #ffecec;
  font-weight: 700; padding: 10px 16px; cursor: pointer; }

/* Visible, styled scrollbars on every scroll area, so it's obvious there's more
   to see on desktop (where the OS otherwise hides overlay scrollbars). */
.modal-card, .panel-body, .board-body, .lb-list, .tp-list, .profile-body, .chat-log {
  scrollbar-width: thin;                       /* Firefox */
  scrollbar-color: #4a6b46 #0e130d;            /* thumb, track (Firefox) */
}
.modal-card::-webkit-scrollbar, .panel-body::-webkit-scrollbar,
.board-body::-webkit-scrollbar, .lb-list::-webkit-scrollbar,
.tp-list::-webkit-scrollbar, .profile-body::-webkit-scrollbar,
.chat-log::-webkit-scrollbar { width: 12px; height: 12px; }
.modal-card::-webkit-scrollbar-track, .panel-body::-webkit-scrollbar-track,
.board-body::-webkit-scrollbar-track, .lb-list::-webkit-scrollbar-track,
.tp-list::-webkit-scrollbar-track, .profile-body::-webkit-scrollbar-track,
.chat-log::-webkit-scrollbar-track { background: #0e130d; border-radius: 8px; }
.modal-card::-webkit-scrollbar-thumb, .panel-body::-webkit-scrollbar-thumb,
.board-body::-webkit-scrollbar-thumb, .lb-list::-webkit-scrollbar-thumb,
.tp-list::-webkit-scrollbar-thumb, .profile-body::-webkit-scrollbar-thumb,
.chat-log::-webkit-scrollbar-thumb { background: #4a6b46; border-radius: 8px; border: 3px solid #0e130d; }
.modal-card::-webkit-scrollbar-thumb:hover, .panel-body::-webkit-scrollbar-thumb:hover,
.board-body::-webkit-scrollbar-thumb:hover, .lb-list::-webkit-scrollbar-thumb:hover,
.tp-list::-webkit-scrollbar-thumb:hover, .profile-body::-webkit-scrollbar-thumb:hover,
.chat-log::-webkit-scrollbar-thumb:hover { background: var(--accent-2); }
.modal-card h2 { margin: 0 0 10px; color: var(--accent); }
/* World overview map: a wide card with a horizontally-scrollable banner (the east-west corridor). */
.worldmap-card { max-width: min(96vw, 1100px); width: 96vw; }
.worldmap-where { margin: 0 0 8px; color: var(--accent-2); font-size: clamp(13px, calc(3.6 * var(--u)), 24px); }
/* Journey-progress bar: west (Rest) → east (frontier), with a marker at the player's position. */
.wm-journey { margin: 0 0 12px; }
.wm-journey.hidden { display: none; }
.wmj-track { position: relative; height: 10px; border-radius: 999px; border: 1px solid #314733;
  background: linear-gradient(90deg, #1c3a24 0%, #3a3414 55%, #4a1c18 100%); overflow: visible; }
.wmj-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 999px;
  background: linear-gradient(90deg, #5fd35f55, #f0d68a55); }
.wmj-marker { position: absolute; top: 50%; width: 14px; height: 14px; margin-left: -7px; z-index: 2;
  transform: translateY(-50%) rotate(45deg); background: #ffd23c; border: 1.5px solid #3a2a06;
  box-shadow: 0 0 8px rgba(255,210,60,.6); transition: left .3s ease; }
/* Town/region tick-marks along the journey track; the current town is brightened. */
.wmj-tick { position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px; z-index: 1;
  background: #0c110b; opacity: .5; }
.wmj-tick.cur { width: 3px; opacity: 1; background: #fff0b0; box-shadow: 0 0 5px rgba(255,210,60,.7); }
.wmj-ends { display: flex; justify-content: space-between; margin-top: 5px;
  font-size: clamp(11px, calc(3 * var(--u)), 20px); color: var(--muted); }
.wmj-ends #wmj-pct { color: var(--accent); font-weight: 700; }
.worldmap-scroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; border: 1px solid #314733; border-radius: 8px; background: #0e130d; }
.worldmap-img { display: block; height: clamp(200px, 44vh, 460px); width: auto; max-width: none; image-rendering: pixelated; }
/* The map ships as vertical slices laid side by side (a single 41752px-wide image decodes
   to ~190MB and was knocking over low-RAM phones). Slices carry width/height attributes so
   layout reserves their space before load, and lazy-loading only decodes what's scrolled to. */
.worldmap-strip { display: flex; width: max-content; }
.worldmap-strip img { display: block; flex: 0 0 auto; height: clamp(200px, 44vh, 460px); width: auto; max-width: none; image-rendering: pixelated; }
.worldmap-hint { margin: 8px 0 14px; font-size: clamp(12px, calc(3.2 * var(--u)), 22px); }
.worldmap-status { margin: 0 0 8px; font-size: clamp(13px, calc(3.6 * var(--u)), 24px); }

/* World-map settlements list: town name + map coordinates + level band. Two responsive columns
   on wide screens so a long corridor of towns stays scannable. */
.wm-towns { margin: 0 0 12px; }
.wm-towns.hidden { display: none; }
.wm-towns-h { margin: 0 0 8px; font-size: clamp(13px, calc(3.5 * var(--u)), 22px); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 700; }
.wm-town { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 10px;
  padding: clamp(6px, calc(1.6 * var(--u)), 10px) 2px; border-bottom: 1px solid #1e2c20; }
.wm-town.cur { background: #17251b; border-radius: 8px; padding-left: 8px; padding-right: 8px; border-bottom-color: transparent; }
.wm-town-name { color: var(--ink); font-weight: 600; font-size: clamp(13px, calc(3.6 * var(--u)), 22px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wm-town.cur .wm-town-name { color: var(--accent); }
.wm-town-coord { color: var(--accent-2); font-variant-numeric: tabular-nums; font-size: clamp(12px, calc(3.2 * var(--u)), 20px); white-space: nowrap; }
@media (min-width: 640px) { .wm-towns { display: grid; grid-template-columns: 1fr 1fr; gap: 0 22px; }
  .wm-towns-h { grid-column: 1 / -1; } }
.modal-card h3 { margin: 16px 0 8px; font-size: 14px; color: var(--muted); }

/* Death / respawn celebration */
#death-modal { background: #06080af2; } /* near-opaque: hides the respawn teleport behind it */
.death-card { text-align: center; max-width: 600px; border-color: #6b5a2a; padding: clamp(24px, calc(6 * var(--u)), 48px); }
.death-trophy { font-size: clamp(48px, calc(13 * var(--u)), 96px); line-height: 1; margin-bottom: 4px; }
.death-card h2 { color: #f2d23a; font-size: clamp(28px, calc(7.4 * var(--u)), 52px); }
.death-body { margin: 14px 0 4px; }
.death-line { font-size: clamp(18px, calc(5 * var(--u)), 34px); padding: 8px 0; }
.death-line b { color: #f2d23a; font-size: clamp(22px, calc(6 * var(--u)), 42px); }
/* New personal-best distance: a warm gold celebration with a soft glow + pop-in. */
.death-record { color: #f2d23a; font-weight: 700; text-shadow: 0 0 14px rgba(242,210,58,.45); animation: death-record-pop .5s ease-out; }
.death-record b { color: #fff0b0; }
@keyframes death-record-pop { 0% { transform: scale(.7); opacity: 0; } 60% { transform: scale(1.08); opacity: 1; } 100% { transform: scale(1); } }
.death-count { margin-top: 16px; font-size: clamp(14px, calc(3.8 * var(--u)), 26px); color: var(--muted); }
.death-count span { color: var(--accent); font-weight: 700; }
/* Reassurance line: death keeps your gear/coins — a calm, non-punishing note. */
.death-reassure { margin-top: 10px; font-size: clamp(14px, calc(3.8 * var(--u)), 26px); color: #7fd6a0; }
.summary-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #223528; font-size: 14px; }
.summary-row span:last-child { color: var(--accent-2); font-weight: 600; }
/* "Best find" banner in the offline summary — left border tinted by rarity (via .rar-*). */
.summary-bestitem { border-left: 4px solid #4ade80; background: #14241acc; border-radius: 8px; padding: 8px 12px; margin: 0 0 8px; font-weight: 700; color: #e8d9a0; }
#offline-modal .summary-bestitem { font-size: clamp(18px, calc(4.8 * var(--u)), 34px); padding: clamp(10px, calc(2.4 * var(--u)), 18px) clamp(12px, calc(3 * var(--u)), 22px); }
/* New furthest-east record reached while away — a warm gold celebration (matches the death screen). */
/* The offline setback line: a death while away sent the player back to a town. Deliberately
   NOT styled like the gold "new record" banner it sits beside — this is news the player needs,
   not a celebration, so it reads as a ruled note in the danger red the death screen uses. */
.summary-setback { border-left: 4px solid #c2452f; background: #2c141199; border-radius: 8px; padding: 8px 12px; margin: 0 0 8px; font-weight: 700; color: #f0a99b; line-height: 1.45; }
.summary-setback b { color: #ffd9d0; font-weight: 800; }
#offline-modal .summary-setback { font-size: clamp(13px, calc(3.1 * var(--u)), 20px); padding: clamp(9px, calc(2.1 * var(--u)), 16px) clamp(11px, calc(2.6 * var(--u)), 18px); }
.summary-record { border-left: 4px solid #f2d23a; background: #2c2410cc; border-radius: 8px; padding: 8px 12px; margin: 0 0 8px; font-weight: 800; color: #f2d23a; text-shadow: 0 0 12px rgba(242,210,58,.4); }
#offline-modal .summary-record { font-size: clamp(18px, calc(4.8 * var(--u)), 34px); padding: clamp(10px, calc(2.4 * var(--u)), 18px) clamp(12px, calc(3 * var(--u)), 22px); }

/* Offline summary headline: the streak badge and the time-away line lead the screen, above
   the level-ups. The ember colour is the same 🔥 language as the welcome-back toast. */
.summary-streak {
  text-align: center; font-weight: 800; color: #ffb454; letter-spacing: .01em;
  text-shadow: 0 0 14px rgba(255,140,40,.35); margin: 0 0 2px;
  font-size: clamp(17px, calc(4.6 * var(--u)), 32px);
}
.summary-away { text-align: center; color: #cdd6e4; margin: 0 0 10px; font-size: clamp(14px, calc(3.8 * var(--u)), 26px); }
.summary-away b { color: var(--accent-2); }

/* "Show full breakdown": the whole ledger is still built, just collapsed by default. Styled as
   a quiet text control rather than a button — it is a disclosure, not an action. */
.summary-more {
  display: block; width: 100%; margin: 10px 0 0; padding: 8px 0;
  background: none; border: none; border-top: 1px solid #223528;
  color: var(--accent); font: inherit; font-weight: 600; text-align: center; cursor: pointer;
  font-size: clamp(13px, calc(3.4 * var(--u)), 22px);
}
.summary-more:hover { color: var(--accent-2); }
.summary-full { margin-top: 4px; }

/* Offline level-ups: a celebratory banner above the summary rows. */
.summary-levelup {
  text-align: center; font-weight: 800; color: #1a1306;
  background: linear-gradient(#ffe08a, #e8b94a); border: 1px solid #b8862f; border-radius: 8px;
  padding: 8px 12px; margin: 0 0 8px;
}
.summary-levelup.sub { background: none; border: none; color: var(--accent); font-weight: 700; padding: 2px 0; margin: 0 0 4px; }
#offline-modal .summary-levelup { font-size: clamp(20px, calc(5.6 * var(--u)), 40px); padding: clamp(10px, calc(2.4 * var(--u)), 18px); }
#offline-modal .summary-levelup.sub { font-size: clamp(17px, calc(4.6 * var(--u)), 32px); }

/* Offline cap nudge: shown under the summary rows when travel hit the player's cap. */
.summary-note { border-left: 4px solid var(--accent); background: #1a1f2acc; border-radius: 8px; padding: 8px 12px; margin: 8px 0 0; color: #cdd6e4; }
.summary-note b { color: var(--accent); }
#offline-modal .summary-note { font-size: clamp(15px, calc(4 * var(--u)), 28px); padding: clamp(10px, calc(2.4 * var(--u)), 18px); }

/* "While you were away" — roughly double-size, scales with screen width */
#offline-modal .modal-card { max-width: 640px; padding: clamp(28px, calc(7 * var(--u)), 56px); }
#offline-modal .modal-card h2 { font-size: clamp(30px, calc(8 * var(--u)), 56px); margin: 0 0 18px; }
#offline-modal .summary-row { padding: clamp(11px, calc(2.6 * var(--u)), 20px) 0; font-size: clamp(20px, calc(5.4 * var(--u)), 38px); }
#offline-modal #offline-close { font-size: clamp(20px, calc(5.4 * var(--u)), 38px); padding: clamp(16px, calc(3.6 * var(--u)), 28px); margin-top: 24px; }
/* Admin teleport menu */
/* Live coordinate readout (moved off the main HUD into the admin panel). */
.tp-here { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; color: var(--muted); font-size: clamp(14px, calc(3.6 * var(--u)), 24px); }
.tp-list { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; max-height: 46vh; overflow-y: auto; margin-bottom: 10px; }
.tp-btn {
  padding: 12px 11px; border-radius: 8px; border: 1px solid #314733; background: #0e130d;
  color: var(--ink); font-size: clamp(15px, calc(4 * var(--u)), 26px); cursor: pointer; text-align: left;
}
.tp-btn:active { background: var(--panel-2); }
.tp-btn small { color: var(--muted); font-size: clamp(13px, calc(3.4 * var(--u)), 22px); }
/* Destination blurb under the name (teleport-scroll picker) — quieter than the label,
   and allowed to wrap, since the list is a two-column grid on narrow phones. */
.tp-btn small.tp-desc { display: block; margin-top: 3px; line-height: 1.3; font-style: italic; }
.tp-xy { display: flex; gap: 6px; align-items: center; margin-bottom: 10px; }
.tp-xy input { width: clamp(70px, calc(20 * var(--u)), 120px); height: clamp(42px, calc(11 * var(--u)), 62px); padding: 0 10px; border-radius: 8px; border: 1px solid #314733; background: #0e130d; color: var(--ink); font-size: clamp(15px, calc(4 * var(--u)), 26px); }
.tp-xy button { height: clamp(42px, calc(11 * var(--u)), 62px); padding: 0 clamp(16px, calc(4.2 * var(--u)), 30px); border-radius: 8px; border: none; background: var(--accent-2); color: #11160f; font-weight: 700; cursor: pointer; font-size: clamp(15px, calc(4 * var(--u)), 26px); }
.tp-player { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
.tp-player label { color: var(--muted, #9bb09b); font-size: clamp(14px, calc(3.6 * var(--u)), 22px); width: 100%; }
.tp-player select { flex: 1 1 auto; min-width: 0; height: clamp(42px, calc(11 * var(--u)), 62px); padding: 0 8px; border-radius: 8px; border: 1px solid #314733; background: #0e130d; color: var(--ink); font-size: clamp(14px, calc(3.6 * var(--u)), 22px); }
.tp-player button { height: clamp(42px, calc(11 * var(--u)), 62px); padding: 0 clamp(16px, calc(4.2 * var(--u)), 30px); border-radius: 8px; border: none; background: var(--accent-2); color: #11160f; font-weight: 700; cursor: pointer; font-size: clamp(15px, calc(4 * var(--u)), 26px); }

/* Who's online roster (tap the green HUD badge) */
.online-count { color: var(--accent-2); font-weight: 700; }
.online-list { display: flex; flex-direction: column; gap: 5px; max-height: 56vh; overflow-y: auto; margin-bottom: 12px; }
.online-empty { color: var(--muted); text-align: center; padding: 18px 0; font-size: clamp(14px, calc(3.6 * var(--u)), 22px); }
.online-row {
  display: flex; align-items: center; gap: 8px; padding: 10px 11px; border-radius: 8px;
  border: 1px solid #314733; background: #0e130d; font-size: clamp(15px, calc(4 * var(--u)), 26px);
}
.online-row.is-self { border-color: var(--accent-2); background: #12190e; }
.online-lvl { flex: 0 0 auto; min-width: 3.2em; color: var(--muted); font-size: clamp(12px, calc(3.2 * var(--u)), 20px); }
.online-name {
  flex: 1 1 auto; min-width: 0; color: var(--ink); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: left; background: none; border: none; padding: 0; margin: 0;
  font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.online-name:hover { color: var(--accent-2); text-decoration: underline; }
.online-name:active { opacity: 0.7; }
.online-you { color: var(--accent-2); font-weight: 700; }
.online-region { flex: 0 0 auto; color: var(--muted); font-size: clamp(12px, calc(3.2 * var(--u)), 20px); text-align: right; }
.tp-debug { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; padding-top: 10px; border-top: 1px solid #243024; }
.tp-debug label { color: var(--muted, #9bb09b); font-size: clamp(14px, calc(3.6 * var(--u)), 22px); }
.tp-debug select { height: clamp(38px, calc(10 * var(--u)), 56px); padding: 0 8px; border-radius: 8px; border: 1px solid #314733; background: #0e130d; color: var(--ink); font-size: clamp(14px, calc(3.6 * var(--u)), 22px); }
.tp-debug .dbg-toggle { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.tp-debug .dbg-toggle input { width: 18px; height: 18px; }

/* Leaderboards ("Hall of Wanderers") */
#board-modal .modal-card { max-width: 640px; padding: clamp(24px, calc(6 * var(--u)), 48px); display: flex; flex-direction: column; }
#board-modal .modal-card h2 { font-size: clamp(26px, calc(7 * var(--u)), 50px); margin: 0 0 16px; }
#board-modal #board-close { font-size: clamp(18px, calc(4.8 * var(--u)), 32px); padding: clamp(14px, calc(3.4 * var(--u)), 24px); margin-top: 18px; width: 100%; }
/* Category picker: ONE dropdown holding the whole catalogue. It was a horizontally-scrolling row
   of chips, which stopped scaling once the ten professions took the list to 21 — most of the
   categories sat off the right edge of a phone with nothing indicating they existed. */
.board-tabs { display: block; margin-bottom: 10px; }
.lb-board-select {
  width: 100%; padding: 11px 12px; border-radius: 8px; border: 1px solid #314733;
  background: #0e130d; color: var(--ink); cursor: pointer;
  font-size: clamp(15px, calc(4 * var(--u)), 26px);
}
.lb-board-select:focus { outline: none; border-color: var(--accent); }
.lb-board-select optgroup { color: var(--muted); font-style: normal; font-weight: 700; }
.lb-board-select option { color: var(--ink); background: #0e130d; font-weight: 400; }
/* The list is the only scroll region: header (title + tabs) and the Close button
   stay pinned so the modal fits any height, incl. short landscape phones. */
.board-body { min-height: 0; flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; }
#board-modal .board-tabs, #board-modal #board-close { flex: 0 0 auto; }

.lb-tip { color: var(--muted); font-size: clamp(12px, calc(3.1 * var(--u)), 20px); line-height: 1.35; margin: 0 2px 10px; }
.lb-periods { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; }
.lb-chip {
  padding: 6px 12px; border-radius: 999px; border: 1px solid #314733;
  background: #0e130d; color: var(--muted); font-size: clamp(13px, calc(3.4 * var(--u)), 22px); cursor: pointer;
}
.lb-chip.active { border-color: var(--accent-2); background: var(--panel-2); color: var(--ink); }
.lb-reset { color: var(--muted); font-size: clamp(11px, calc(2.9 * var(--u)), 18px); margin-left: auto; white-space: nowrap; }
.lb-search { margin-bottom: 8px; }
.lb-search input {
  width: 100%; box-sizing: border-box; padding: 10px 12px; border-radius: 10px;
  border: 1px solid #314733; background: #0e130d; color: var(--ink);
  font-size: clamp(14px, calc(3.7 * var(--u)), 24px);
}
.lb-list { min-height: 120px; max-height: 42vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }

.lb-spinner { display: flex; justify-content: center; padding: 28px 0; }
.lb-spinner span {
  width: 28px; height: 28px; border-radius: 50%;
  border: 3px solid #314733; border-top-color: var(--accent); animation: lb-spin 0.8s linear infinite;
}
@keyframes lb-spin { to { transform: rotate(360deg); } }

/* Podium for the top three */
.lb-podium { display: grid; grid-template-columns: 1fr 1.15fr 1fr; gap: 6px; align-items: end; margin: 4px 0 12px; }
.lb-pod {
  display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer;
  background: var(--panel-2); border: 1px solid #314733; border-radius: 12px; padding: 12px 6px 10px;
}
.lb-pod.p1 { border-color: var(--accent); background: #2c2716; order: 2; padding-top: 18px; }
.lb-pod.p2 { order: 1; }
.lb-pod.p3 { order: 3; }
.lb-pod.me { box-shadow: inset 0 0 0 2px var(--accent-2); }
.lb-pod-medal { font-size: clamp(22px, calc(6 * var(--u)), 40px); line-height: 1; }
.lb-pod-name { font-weight: 700; font-size: clamp(13px, calc(3.4 * var(--u)), 22px); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-pod-val { color: var(--accent-2); font-weight: 700; font-size: clamp(13px, calc(3.4 * var(--u)), 22px); text-align: center; }
.lb-pod-val small { color: var(--muted); font-weight: 400; }
.lb-pod .br-av { width: clamp(38px, calc(9.5 * var(--u)), 64px); height: clamp(38px, calc(9.5 * var(--u)), 64px); font-size: clamp(18px, calc(4.8 * var(--u)), 32px); }

/* List rows */
.lb-row, .lb-you {
  display: flex; align-items: center; gap: 10px; padding: 10px 6px;
  border-bottom: 1px solid #223528; font-size: clamp(15px, calc(4.2 * var(--u)), 28px); cursor: pointer;
}
.lb-row.me { background: #2a3a2a; border-radius: 8px; }
.br-av {
  width: clamp(30px, calc(7.5 * var(--u)), 50px); height: clamp(30px, calc(7.5 * var(--u)), 50px);
  border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  color: #fff; font-weight: 700; font-size: clamp(14px, calc(3.8 * var(--u)), 24px); text-shadow: 0 1px 2px #0008;
}
.br-rank { min-width: clamp(34px, calc(8.5 * var(--u)), 56px); text-align: center; font-weight: 700; color: var(--accent); flex: 0 0 auto; }
.br-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; flex-direction: column; }
.br-name small { color: var(--muted); font-weight: 400; font-size: 0.72em; }
.br-val { color: var(--accent-2); font-weight: 700; white-space: nowrap; flex: 0 0 auto; }
.br-val small { color: var(--muted); font-weight: 400; }
.br-go { color: var(--muted); font-size: 1.2em; flex: 0 0 auto; }

.lb-more {
  width: 100%; margin-top: 10px; padding: 10px; border-radius: 10px; cursor: pointer;
  border: 1px solid #314733; background: #0e130d; color: var(--ink); font-size: clamp(14px, calc(3.7 * var(--u)), 24px);
}
.lb-you {
  margin-top: 8px; border: 1px solid var(--accent); border-radius: 10px; background: #2a3a2a; cursor: default;
}
.lb-you-label { color: var(--accent); font-weight: 700; font-size: 0.8em; flex: 0 0 auto; }

/* Player profile */
#profile-modal .modal-card { max-width: 560px; padding: clamp(20px, calc(5 * var(--u)), 40px); }
.profile-body { max-height: 64vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.profile-body h3 { margin: 16px 0 4px; font-size: clamp(13px, calc(3.4 * var(--u)), 22px); color: var(--accent); border-bottom: 1px solid #243024; padding-bottom: 4px; }
.profile-body .summary-row { font-size: clamp(14px, calc(3.7 * var(--u)), 24px); }
.profile-body .summary-row small { color: var(--muted); }
.pf-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.pf-head .br-av { width: clamp(48px, calc(12 * var(--u)), 80px); height: clamp(48px, calc(12 * var(--u)), 80px); font-size: clamp(22px, calc(6 * var(--u)), 40px); }
.pf-name { font-weight: 700; font-size: clamp(20px, calc(5.4 * var(--u)), 38px); color: var(--accent); }
.pf-sub { color: var(--muted); font-size: clamp(12px, calc(3.1 * var(--u)), 20px); }
.pf-badges, .pf-titles { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.pf-badge, .pf-title {
  font-size: clamp(12px, calc(3.1 * var(--u)), 20px); padding: 4px 10px; border-radius: 999px;
  border: 1px solid #314733; background: #0e130d; color: var(--ink);
}
.pf-badge.gold { border-color: var(--accent); background: #2c2716; color: #f2d23a; }
.pf-badge.silver { border-color: #9aa6b0; color: #d8dde2; }
.pf-badge.bronze { border-color: #b07a45; color: #e3b489; }
.pf-title { border-color: var(--accent-2); color: var(--accent-2); }
.pf-privacy {
  display: flex; align-items: center; gap: 8px; margin-top: 16px; padding-top: 12px;
  border-top: 1px solid #243024; color: var(--muted); font-size: clamp(13px, calc(3.4 * var(--u)), 22px); cursor: pointer;
}
.pf-privacy input { width: 18px; height: 18px; }
#profile-modal #profile-close { width: 100%; margin-top: 16px; font-size: clamp(16px, calc(4.2 * var(--u)), 28px); padding: clamp(12px, calc(3 * var(--u)), 22px); }

.mode-list { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.mode-opt {
  padding: 12px 10px; border-radius: 10px; border: 1px solid #314733;
  background: #0e130d; color: var(--ink); cursor: pointer; text-align: left;
}
.mode-opt.mode-span-2 { grid-column: span 2; }
.mode-opt small { display: block; color: var(--muted); font-size: 14px; margin-top: 3px; }
#mode-modal .mode-opt small { font-size: 12px; line-height: 1.2; }
/* The "resources needed" line under a recipe in the mode pickers — a warm ingredient tint so it
   reads apart from the grey level/XP line above it. */
.mode-opt small.picker-res { color: #c9a06a; margin-top: 1px; }
.mode-opt.active { border-color: var(--accent); background: var(--panel-2); }
.mode-opt.locked, .mode-opt:disabled {
  opacity: .55; cursor: not-allowed; border-color: #2b322b; background: #0b0e0b;
}
.match-offline {
  display: flex; align-items: center; gap: 8px; margin-top: 14px;
  font-size: 14px; color: var(--ink); cursor: pointer;
}
.match-offline input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
#mode-done, #offline-close, #picker-back { margin-top: 14px; width: 100%; }

/* ---------------- Skills extras ---------------- */
.skill-sub { font-size: clamp(14px, calc(3.8 * var(--u)), 26px); color: var(--muted); margin: 3px 0 10px; }
.focus-h { margin: 6px 0 8px; font-size: clamp(16px, calc(4.2 * var(--u)), 28px); color: var(--muted); }
.focus-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.focus-btn {
  padding: 14px; border-radius: 10px; border: 1px solid #314733;
  background: #0e130d; color: var(--ink); cursor: pointer; font-size: clamp(16px, calc(4.4 * var(--u)), 30px);
}
.focus-btn.active { border-color: var(--accent); background: var(--panel-2); color: var(--accent); }
.focus-desc { margin: 10px 0 2px; font-size: clamp(14px, calc(3.8 * var(--u)), 26px); color: var(--muted); line-height: 1.35; }

/* Battle panel removed — combat is now choreographed on the map (see combat-fx.js);
   the result is surfaced via toast notifications. */

/* ---------------- Inventory ---------------- */
/* Sell All + sort dropdown + ⚙ settings + view switch share the head row. The auto-sell and
   auto-equip chips moved behind the ⚙ (see #invsettings-modal), which is what gave the row
   back to the sort dropdown. Pack count lives in the title. */
/* ── THE TWO BAGS ──────────────────────────────────────────────────────────
   The pack is a Pack tab and a Food tab, each with its own capacity and its own
   upgrade ladder. The strip sits above the head row in BOTH skins, so the grid
   and the classic list switch bags the same way. Rules live here rather than in
   grid-satchel.css for exactly that reason — that file is scoped to the grid. */
.inv-tabs { display: flex; gap: 6px; margin: 0 0 8px; }
.inv-tab {
  flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: center;
  gap: 6px; flex-wrap: wrap;
  padding: clamp(7px, calc(1.8 * var(--u)), 13px) clamp(6px, calc(1.6 * var(--u)), 12px);
  border-radius: 9px; cursor: pointer;
  border: 1px solid #3a424c; background: #1a1e24; color: #9aa8b6;
  font-size: clamp(14px, calc(3.6 * var(--u)), 23px); font-weight: 700;
  -webkit-tap-highlight-color: transparent;
}
.inv-tab.on { background: linear-gradient(#2c333d, #1d222a); border-color: #c9a227; color: #f2e6c8; }
.inv-tab .it-ico { font-size: 1.15em; line-height: 1; }
.inv-tab .it-count { font-weight: 600; font-size: 0.82em; opacity: 0.8; font-variant-numeric: tabular-nums; }
.inv-tab.full .it-count { color: #f0a08a; opacity: 1; }

/* The upgrade offer as a classic-list row (the grid gets the golden + instead). */
.inv-expand-list { margin-top: 10px; }
.inv-item.inv-expand { border-color: #6d5518; background: linear-gradient(#2a2415, #1b1810); }
.inv-item.inv-expand .ii-name { color: #f0cd7c; }
.inv-item.inv-expand .ic-icon { color: #e8c65a; }
.inv-item.inv-expand.maxed { opacity: 0.6; }

.inv-head { display: flex; align-items: stretch; gap: 8px; margin-bottom: 10px; }
/* `1 1 auto`, not `1 1 0`: sharing the free space on TOP of each label's natural width keeps
   both labels whole, where equal halves clipped them on a phone. */
.inv-head .inv-sellall, .inv-head #inv-sort { flex: 1 1 auto; min-width: 0; text-align: center; }
.inv-head #inv-sort {
  border-radius: 8px; border: 1px solid #314733; background: #0e130d; color: var(--ink);
  font-size: clamp(14px, calc(3.6 * var(--u)), 24px); padding: 0 8px;
}
/* The ⚙ chip: same standing-order colouring as the chips it hides — muted at rest, lit green
   while EITHER auto-sell or auto-equip is quietly working, so the state stays glanceable. */
.inv-settings-btn {
  flex: 0 0 auto; padding: 10px 12px; border-radius: 8px; border: 1px solid #314733;
  background: #0e130d; color: #9fb79f; cursor: pointer;
  font-size: clamp(16px, calc(4.2 * var(--u)), 30px); line-height: 1;
}
.inv-settings-btn:active { background: #16200f; }
.inv-settings-btn.on { border-color: #4a7a46; background: #16240f; color: #cfe8bf; }
/* Rows inside the ⚙ modal: each standing-order chip full-width with its explainer beneath. */
.invset-row { margin: 0 0 16px; }
.invset-row .inv-autosell { display: block; width: 100%; text-align: center; }
.invset-hint { margin: 6px 0 0; color: var(--muted); line-height: 1.35;
  font-size: clamp(13px, calc(3.4 * var(--u)), 22px); }
.inv-full-hint { color: #f0c0b6; background: #3a1c1ccc; border: 1px solid #5a2c25; border-radius: 8px;
  padding: clamp(7px, calc(1.8 * var(--u)), 14px) clamp(10px, calc(2.6 * var(--u)), 18px); margin: 0 0 10px;
  font-size: clamp(14px, calc(3.7 * var(--u)), 24px); }
.inv-sellall {
  font-size: clamp(15px, calc(4 * var(--u)), 28px); padding: 10px 14px; border-radius: 8px;
  border: 1px solid #5a3a3a; background: #2a1717; color: #ffb9a8; cursor: pointer; white-space: nowrap;
}
.inv-sellall:active { background: #3a1f1f; }
.inv-sellall:disabled { opacity: 0.45; cursor: default; }
/* Auto-sell is a standing ORDER, not an action, so it reads as a settings chip rather than
   another red sell button: muted while off, and lit green while it is quietly selling for you. */
.inv-autosell {
  font-size: clamp(13px, calc(3.2 * var(--u)), 25px); padding: 10px 8px; border-radius: 8px;
  border: 1px solid #314733; background: #0e130d; color: #9fb79f; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.inv-autosell:active { background: #16200f; }
.inv-autosell.on { border-color: #4a7a46; background: #16240f; color: #cfe8bf; }
/* DURABILITY BAR. A thin strip under a gear row's detail line. Three bands, not a gradient: a
   player must be able to see "about to fall off" without reading the number. */
.ii-dur { position: relative; height: 14px; margin-top: 4px; border-radius: 7px; overflow: hidden;
  background: #1a2118; border: 1px solid #2c3a2b; }
.ii-dur-fill { position: absolute; inset: 0 auto 0 0; border-radius: 7px 0 0 7px; }
.ii-dur-text { position: relative; display: block; text-align: center; line-height: 12px;
  font-size: clamp(9px, calc(2.4 * var(--u)), 15px); color: #dfe9d9; text-shadow: 0 1px 1px #0008; }
.ii-dur.dur-ok .ii-dur-fill { background: linear-gradient(90deg, #3f6b39, #5c9a52); }
.ii-dur.dur-low .ii-dur-fill { background: linear-gradient(90deg, #7a5a1e, #c99a2e); }
.ii-dur.dur-broken { border-color: #6a2a24; }
.ii-dur.dur-broken .ii-dur-fill { background: #6a2a24; }
.ii-dur.dur-broken .ii-dur-text { color: #ffb9a8; font-weight: 700; }
/* In an equip slot the bar shares a narrow column with the name and stat line, so it runs
   slimmer than in the pack and never pushes the row taller than its icon. */
.equip-slot .ii-dur { height: 12px; margin-top: 3px; max-width: 220px; }
.equip-slot .ii-dur-text { line-height: 10px; font-size: clamp(8px, calc(2.1 * var(--u)), 13px); }
/* The grid tile's hairline of condition — bottom edge, no text, same three bands. */
.gsl-dur { position: absolute; left: 3px; right: 3px; bottom: 3px; height: 3px; border-radius: 2px;
  background: #00000066; overflow: hidden; pointer-events: none; }
.gsl-dur i { display: block; height: 100%; border-radius: 2px; }
.gsl-dur.dur-ok i { background: #5c9a52; }
.gsl-dur.dur-low i { background: #c99a2e; }
.gsl-dur.dur-broken i { background: #c4483a; width: 100% !important; }
/* The same three bands as plain text, for the bench's damaged-gear list. */
.dur-ok { color: #8fc47f; } .dur-low { color: #e0b455; } .dur-broken { color: #ff9b88; font-weight: 700; }
/* A worn-out piece is STILL WORN, at half effect — so the slot reads as diminished rather than
   as empty or lost. A warm edge, not the red of an error. */
.equip-slot.es-wornout { border-color: #7a4a30; box-shadow: inset 0 0 0 1px #7a4a3055; }

/* REPAIR BUTTON. Shown wherever a damaged piece is — pack row, detail card, and the character
   screen's equip slot (the only place gear actually wears). It carries a PRICE, so it is styled
   as a purchase: warm gold ink on a dark well, distinct from the neutral Equip/Remove buttons
   beside it and from the red of Sell. Never the accent green — this spends money. */
.rp-btn { font-size: clamp(11px, calc(2.9 * var(--u)), 16px);
  padding: clamp(3px,calc(1 * var(--u)),6px) clamp(7px,calc(1.9 * var(--u)),12px);
  min-height: clamp(22px, calc(6 * var(--u)), 30px); border-radius: 6px; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  border: 1px solid #6d5320; background: linear-gradient(180deg, #3a2f16, #2a2211); color: #f0c878; }
.rp-btn:hover { border-color: #9a7a32; background: linear-gradient(180deg, #4a3c1d, #342a15); }
.rp-btn:active { transform: translateY(1px); }
.rp-btn .rp-gold { display: inline-flex; align-items: center; gap: 2px; font-weight: 700; color: #ffd98a; }
.rp-btn .coin-ico { transform: scale(0.85); }
/* Away from a bench the price carries a +50% premium — a dashed edge marks it as the dearer
   option without shouting, and the tooltip says why. */
.rp-btn.rp-away { border-style: dashed; }
/* In an equip slot the two actions stack on the right, Repair above Remove — repairing is what
   you actually want on a damaged piece, so it takes the top (and the thumb-nearest) position. */
.es-acts { display: flex; flex-direction: column; align-items: stretch; justify-content: center;
  gap: 4px; margin-left: auto; }
.es-acts > .rp-btn, .es-acts > .es-remove { justify-content: center; text-align: center; width: 100%; }
/* The shortcut out of repairing by hand, on the bench's Repair tab. Quiet — it is an offer, not
   the action the player came here for. */
.repair-autolink { display: flex; justify-content: flex-end; margin: -2px 0 8px; }
.repair-autolink button { background: none; border: 1px solid #35452f; color: var(--muted);
  border-radius: 6px; padding: 3px 9px; font-size: clamp(11px, calc(2.9 * var(--u)), 16px); cursor: pointer; }
.repair-autolink button:hover { color: var(--ink); border-color: #4c6444; }
.equip-slot.es-wornout .rp-btn { border-color: #9a6a32; box-shadow: 0 0 0 1px #9a6a3244; }

/* THE BENCH'S TWO JOBS. Temper and Repair share one card, so the tabs must read as a switch
   rather than as two more buttons among the many this screen already has. */
.temper-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 0 0 10px; }
.temper-tabs button { padding: 10px 12px; border-radius: 8px; cursor: pointer;
  border: 1px solid #314733; background: #0e130d; color: #9fb79f;
  font-size: clamp(14px, calc(3.7 * var(--u)), 24px); }
.temper-tabs button.on { border-color: #4a7a46; background: #16240f; color: #cfe8bf; font-weight: 700; }
/* The two ways to pay, side by side and equally weighted — neither is the "real" one. */
.repair-ways { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 8px 0; }
.repair-ways .temper-go { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.repair-ways .temper-go small { font-weight: 400; opacity: 0.8; font-size: 0.8em; line-height: 1.2; }
/* Pay-with-gold-alone. A different KIND of payment from the two material options, so it spans
   the full width under them rather than becoming a lopsided third column in the 2-col grid. */
.repair-ways .repair-gold-way { grid-column: 1 / -1; }
.temper-item.ti-sel { outline: 2px solid #4a7a46; }

/* AUTO-SELL PICKER. Two even columns that stay even: `1fr` alone is `minmax(auto, 1fr)`, so the
   longest label in a column could still stretch it. minmax(0, 1fr) plus min-width:0 on the cell
   pins both columns to exactly half the card whatever the tier is called. Each option is the same
   two-line shape (name over meta), so rows line up as well as columns do. */
#autosell-list, .as-keep-list { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
#autosell-list .as-opt, .as-keep-list .as-opt {
  min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  position: relative; text-align: left;
}
#autosell-list .as-name, .as-keep-list .as-name { display: block; width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#autosell-list .as-meta, .as-keep-list .as-meta { display: block; width: 100%; opacity: 0.75; line-height: 1.25; }
/* The current choice is a tick in the corner, NOT a trailing "(current)" word — text of a
   different length in one cell is exactly what unbalanced the columns. */
#autosell-list .as-current, .as-keep-list .as-current { border-color: #4a7a46; background: #16240f; opacity: 1; }
#autosell-list .as-current::after, .as-keep-list .as-current::after {
  content: '✓'; position: absolute; top: 6px; right: 8px; color: #cfe8bf; font-size: 0.9em; line-height: 1;
}
/* The keep floor is a SUB-SECTION of the same card, so it gets a rule above it rather than the
   look of a second dialog. The tier list scrolls; this stays a short, five-option row. */
.as-keep-title {
  margin: 14px 0 4px; padding-top: 12px; border-top: 1px solid #2a3a28;
  font-size: clamp(15px, calc(4 * var(--u)), 26px); color: var(--ink);
}
/* Two scrolling lists in one card fight each other for the screen, so the long tier ladder gives
   up some height and the five-option keep row never scrolls at all. */
#autosell-list { max-height: 32vh; }
.as-keep-list { max-height: none; overflow: visible; }
/* PACK DENSITY. A pack row is built to occupy the same vertical space as a Skills row
   (.sk-row): one line of name + actions, one line of detail, an icon the size of the skill
   badge. Every size below is deliberately the same clamp() the Skills screen uses, so the two
   lists read as one family — change them together. */
.inv-list { display: flex; flex-direction: column; gap: 4px; }
.inv-item {
  border: 1px solid #314733; border-left-width: 4px; border-radius: 9px;
  background: #0e130d; padding: clamp(5px,calc(1.4 * var(--u)),9px) clamp(7px,calc(1.9 * var(--u)),12px);
  display: flex; flex-direction: row; align-items: center; gap: 9px;
}
/* Standalone item artwork, to the left of the name/stats/buttons. Pixel art, so
   keep it crisp at any size. Sits on a faint inset tile so it reads as an item.
   Sized to .sk-badge — the skill monogram it sits level with. */
.ii-icon {
  flex-shrink: 0; width: clamp(30px, calc(8.2 * var(--u)), 44px); height: clamp(30px, calc(8.2 * var(--u)), 44px);
  image-rendering: pixelated; border-radius: 8px;
  background: #161d14; border: 1px solid #243024; padding: 2px;
}
.ii-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* Locked or too-low-to-equip: dim the icon + text as a cue, but NEVER the action buttons —
   Lock/Sell stay fully solid and usable. (A whole-row opacity would dim the buttons too,
   and child opacity can't undo a faded parent.) */
.inv-item.locked .ii-icon, .inv-item.locked .ii-main { opacity: 0.85; }
.inv-item.under-req .ii-icon, .inv-item.under-req .ii-main { opacity: 0.7; }
.ii-sub .req-unmet { color: #fbbf24; }
/* Name line: name (and stack count) on the left, the action buttons pushed hard right —
   mirrors .sk-top, which does the same with the skill name and its level. */
.ii-main { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* The name still wraps rather than truncating, so a long name is never hidden; at this size
   almost everything fits one line, and the buttons hold their width either way. */
.ii-name { flex: 1; min-width: 0; font-weight: 700;
  font-size: clamp(14px, calc(3.9 * var(--u)), 24px); white-space: normal; overflow-wrap: anywhere; }
.ii-sub { font-size: clamp(11px, calc(3 * var(--u)), 17px); color: var(--muted); margin-top: 0; }
/* Perishable countdown (raw fish). Muted while there's time, amber in the last hour. */
.ii-spoil { color: #9aa88f; }
.ii-spoil.soon { color: #e0a86a; font-weight: 600; }
.ii-sub .stat-better { color: #4ade80; }
.ii-sub .stat-worse { color: #f87171; }
.ii-up { display: inline-block; margin-left: 5px; padding: 0 5px; border-radius: 5px; vertical-align: middle;
  font-size: clamp(9px, calc(2.4 * var(--u)), 14px); font-weight: 700; color: #0e2a16;
  background: #4ade80; letter-spacing: 0.02em; white-space: nowrap; }
/* Compact inline actions. They hug their labels (no flex:1 stretching them across the row)
   and never shrink below the text, so Equip/Lock/Sell stay readable and tappable while taking
   a fraction of the room the old stacked button bar did. */
.ii-actions { display: flex; gap: 4px; flex-shrink: 0; }
.ii-actions button {
  flex: 0 0 auto;
  font-size: clamp(11px, calc(2.9 * var(--u)), 16px);
  padding: clamp(3px,calc(1 * var(--u)),6px) clamp(6px,calc(1.7 * var(--u)),11px);
  min-height: clamp(22px, calc(6 * var(--u)), 30px);
  border-radius: 6px; white-space: nowrap; line-height: 1.15;
  border: 1px solid #314733; background: var(--panel-2); color: var(--ink); cursor: pointer;
}
.ii-actions button:active { background: #2c4233; }
.ii-actions button[disabled] { opacity: .45; }

/* Consumable stacks (Teleport Scroll). A distinct gold accent; the icon box centres the glyph,
   and the quantity badge mirrors the in-game "x37" stack count. */
.inv-consumables { margin-bottom: 8px; }
.inv-item.inv-consumable { border-left-color: #d9b24a; }
.inv-item.inv-consumable .ii-name { color: #f0d68a; }
.ic-icon { display: flex; align-items: center; justify-content: center; font-size: clamp(20px, calc(5.6 * var(--u)), 32px);
  flex-shrink: 0; width: clamp(30px, calc(8.2 * var(--u)), 44px); height: clamp(30px, calc(8.2 * var(--u)), 44px); }
.ic-qty { display: inline-block; margin-left: 4px; padding: 0 6px; border-radius: 5px; vertical-align: middle;
  font-size: clamp(11px, calc(2.9 * var(--u)), 17px); font-weight: 700; color: #2a230a; background: #d9b24a; }

/* Teleport picker: subtitle + the "you are here" current-location row. */
.tp-sub { color: var(--muted); margin: 0 0 8px; font-size: clamp(14px, calc(3.8 * var(--u)), 26px); }
.tp-btn.tp-here { opacity: 0.6; }

/* rarity accents (left border + name colour) */
.rar-worn { border-left-color: #7e8a78; } .rar-worn .ii-name, .es-item.rar-worn { color: #b7c0b0; }
.rar-standard { border-left-color: #c9c9c9; } .rar-standard .ii-name, .es-item.rar-standard { color: #e6e6e6; }
.rar-refined { border-left-color: #6fae6a; } .rar-refined .ii-name, .es-item.rar-refined { color: #93d68c; }
.rar-rare { border-left-color: #5a86c0; } .rar-rare .ii-name, .es-item.rar-rare { color: #8db4ec; }
.rar-epic { border-left-color: #b46fd0; } .rar-epic .ii-name, .es-item.rar-epic { color: #d29cef; }
.rar-relic { border-left-color: #d8a24a; } .rar-relic .ii-name, .es-item.rar-relic { color: #f0c878; }

/* ---------------- Equipment (character panel) ---------------- */
.eq-h { margin: 11px 0 5px; font-size: clamp(12px, calc(3.2 * var(--u)), 20px); color: var(--muted); }
.equip-grid { display: flex; flex-direction: column; gap: 2px; }
.equip-slot { display: flex; align-items: center; gap: 8px; padding: clamp(6px,calc(1.7 * var(--u)),10px) 0; border-bottom: 1px solid #223528; }
.es-icon { flex-shrink: 0; width: clamp(30px, calc(8.2 * var(--u)), 44px); height: clamp(30px, calc(8.2 * var(--u)), 44px); image-rendering: pixelated; border-radius: 6px; background: #161d14; border: 1px solid #243024; }
.es-icon-empty { background: #11160f; border-style: dashed; }
/* Equipped-gear list on another player's profile card. */
.pf-equip { display: flex; flex-direction: column; gap: 4px; margin-bottom: 6px; }
.pf-eq-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid #223528; }
.pf-eq-main { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.pf-eq-name { font-weight: 600; color: #e6e6e6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: clamp(14px, calc(3.9 * var(--u)), 24px); }
.pf-eq-slot { color: var(--muted); font-size: clamp(11px, calc(3 * var(--u)), 17px); }
.pf-eq-name.rar-worn { color: #b7c0b0; } .pf-eq-name.rar-standard { color: #e6e6e6; }
.pf-eq-name.rar-refined { color: #93d68c; } .pf-eq-name.rar-rare { color: #8db4ec; }
.pf-eq-name.rar-epic { color: #d29cef; } .pf-eq-name.rar-relic { color: #f0c878; }
.es-label { width: clamp(62px, calc(16 * var(--u)), 104px); color: var(--muted); font-size: clamp(11px, calc(3 * var(--u)), 17px); flex-shrink: 0; }
.es-item { flex: 1; font-size: clamp(14px, calc(3.9 * var(--u)), 24px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.es-item.empty { color: #5c6a54; }
/* Name + per-item stats stacked in an equipped slot (so you can see what each worn piece gives). */
.es-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.es-main .es-item { flex: 0 0 auto; }
.es-stats { font-size: clamp(11px, calc(3 * var(--u)), 17px); color: var(--muted); line-height: 1.3; }
.es-remove { font-size: clamp(11px, calc(2.9 * var(--u)), 16px);
  padding: clamp(3px,calc(1 * var(--u)),6px) clamp(6px,calc(1.7 * var(--u)),11px);
  min-height: clamp(22px, calc(6 * var(--u)), 30px); border-radius: 6px; white-space: nowrap; border: 1px solid #314733; background: var(--panel-2); color: var(--ink); cursor: pointer; }
.set-bonuses { margin-top: 8px; font-size: clamp(15px, calc(4 * var(--u)), 26px); color: var(--accent); }
.set-bonuses > div { padding: 2px 0; }
.set-prog { display: flex; justify-content: space-between; gap: 10px; padding: 4px 0; font-size: clamp(14px, calc(3.8 * var(--u)), 24px); color: var(--muted); }
.set-prog span:last-child { color: var(--accent-2); white-space: nowrap; }
.set-prog small { opacity: .7; }

/* ---------------- Shop ---------------- */
/* Three-tab shop header (Rest / Crownwatch Market / Premium). Segmented rather than a
   dropdown: with only 2-3 options a picker costs an extra tap for no gain, and the Premium
   tab disappears entirely inside the iOS app. */
.shop-tabs { display: flex; gap: 6px; margin-bottom: 8px; }
.shop-tab { flex: 1 1 0; min-width: 0; padding: 7px 4px; border-radius: 8px; cursor: pointer;
  background: #1d1a12; border: 1px solid #4a412c; color: #c9bb96; font-weight: 600;
  font-size: clamp(10px, calc(2.7 * var(--u)), 15px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.shop-tab.on { background: #33291456; border-color: #b9903f; color: #ffd980; }
.cw-actions { margin: 8px 0; }
/* Soul Shard glyph — the premium currency's counterpart to .coin-ico. */
.shard-ico { display: inline-block; width: 1em; height: 1em; vertical-align: -0.12em;
  background: radial-gradient(circle at 35% 30%, #eabfff, #8b3fd8 60%, #4a1d78);
  clip-path: polygon(50% 0%, 100% 38%, 78% 100%, 22% 100%, 0% 38%); }
.sf-intro { color: #cfc2a0; font-size: clamp(11px, calc(2.9 * var(--u)), 15px); margin: 4px 0 8px; line-height: 1.4; }
.sf-boons { display: flex; flex-direction: column; gap: 6px; }
.sf-boon { display: flex; align-items: center; gap: 10px; text-align: left; width: 100%; cursor: pointer;
  background: #1c1a13; border: 1px solid #4a412c; border-radius: 10px; padding: 9px 11px; color: #e6dcc0; }
.sf-boon.sf-sel { border-color: #b9903f; background: #2b2413; }
.sf-ico { font-size: 22px; line-height: 1; flex: 0 0 auto; }
.sf-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.sf-name { font-weight: 700; color: #ffd980; }
.sf-blurb { font-size: clamp(10px, calc(2.7 * var(--u)), 14px); color: #cfc2a0; }
.sf-off { font-size: clamp(9px, calc(2.4 * var(--u)), 12px); color: #9fd08a; }
.sf-off.sf-live { color: #d8a94a; }
.sf-live-tag { flex: 0 0 auto; font-size: clamp(9px, calc(2.4 * var(--u)), 12px); color: #9fd08a; font-weight: 700; }
.sf-pick { margin-top: 10px; border-top: 1px solid #4a412c; padding-top: 9px; }
.sf-pick-h { color: #c9bb96; font-size: clamp(11px, calc(2.9 * var(--u)), 15px); margin-bottom: 6px; }
.sf-rungs { display: flex; flex-wrap: wrap; gap: 6px; }
.sf-rung { display: flex; flex-direction: column; align-items: center; gap: 2px; cursor: pointer;
  background: #1d1a12; border: 1px solid #4a412c; border-radius: 8px; padding: 7px 11px; color: #e6dcc0; }
.sf-rung:not([disabled]):hover { border-color: #b9903f; }
.sf-rung[disabled] { opacity: .45; cursor: default; }
.sf-dur { font-weight: 700; color: #ffd980; }
.sf-cost { font-size: clamp(9px, calc(2.4 * var(--u)), 12px); color: #cfc2a0; }
.sf-anon { display: flex; align-items: center; gap: 6px; margin-top: 9px; color: #c9bb96;
  font-size: clamp(10px, calc(2.7 * var(--u)), 13px); }
.sf-fineprint { color: #8d8367; font-size: clamp(9px, calc(2.4 * var(--u)), 12px); margin-top: 8px; line-height: 1.4; }
.pk-list { display: flex; flex-direction: column; gap: 6px; }
.pk-row { display: flex; align-items: center; gap: 10px; width: 100%; cursor: pointer;
  background: #1c1a13; border: 1px solid #4a412c; border-radius: 10px; padding: 10px 12px; color: #e6dcc0; }
.pk-row:hover { border-color: #b9903f; }
/* A pack the Play storefront didn't return a localized price for — shown, but not buyable,
   because guessing at the price is worse than admitting we don't have it. */
.pk-row.off { opacity: .45; cursor: not-allowed; }
.pk-row.off:hover { border-color: #4a412c; }
.pk-amt { font-weight: 700; color: #ffd980; flex: 1 1 auto; text-align: left; }
.pk-bonus { flex: 0 0 auto; font-size: clamp(9px, calc(2.4 * var(--u)), 12px); font-weight: 700;
  color: #0f1a0c; background: #9fd08a; border-radius: 999px; padding: 2px 7px; }
.pk-price { flex: 0 0 auto; font-weight: 700; color: #e6dcc0; }
.shop-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.shop-name { font-weight: 700; color: var(--accent); }
.shop-name { font-size: clamp(14px, calc(3.9 * var(--u)), 24px); }
.shop-coins { color: #f0c878; font-size: clamp(12px, calc(3.2 * var(--u)), 20px); }
.shop-hint { background: #2a210f; border: 1px solid #5a4a2a; color: #e6d4a0; border-radius: 8px; padding: 8px 11px; font-size: clamp(11px, calc(3 * var(--u)), 17px); margin-bottom: 6px; }
.shop-h { margin: 10px 0 5px; font-size: clamp(12px, calc(3.2 * var(--u)), 20px); color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.shop-note { color: var(--muted); border: 1px dashed #3a4a2c; border-radius: 8px; padding: 7px 10px; margin: 4px 0 6px;
  font-size: clamp(11px, calc(3 * var(--u)), 17px); line-height: 1.35; }
.shop-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  border: 1px solid #314733; border-left-width: 4px; border-radius: 10px;
  background: #0e130d; padding: clamp(5px,calc(1.4 * var(--u)),9px) clamp(7px,calc(1.9 * var(--u)),12px); margin-bottom: 4px;
}
.shop-item.service { border-left-color: #6fae6a; }
.si-icon { flex-shrink: 0; width: clamp(30px, calc(8.2 * var(--u)), 44px); height: clamp(30px, calc(8.2 * var(--u)), 44px); image-rendering: pixelated; border-radius: 7px; background: #161d14; border: 1px solid #243024; padding: 2px; }
.si-main { flex: 1; min-width: 0; }
/* The shop name no longer truncates at this size — most fit, and a long one wraps rather than
   ending in an ellipsis the player can't expand. */
.si-name { font-weight: 700; font-size: clamp(14px, calc(3.9 * var(--u)), 24px); white-space: normal; overflow-wrap: anywhere; }
.si-sub { font-size: clamp(11px, calc(3 * var(--u)), 17px); color: var(--muted); margin-top: 1px; }
.si-req { margin-top: 2px; color: #f87171; font-size: clamp(10px, calc(2.7 * var(--u)), 15px); font-weight: 600; }
.si-sub .stat-better { color: #4ade80; }
.si-sub .stat-worse { color: #f87171; }
.si-up { display: inline-block; padding: 0 5px; border-radius: 6px; font-weight: 700; color: #0e2a16; background: #4ade80; }
.buy-btn, .sell-btn {
  flex-shrink: 0; font-size: clamp(12px, calc(3.2 * var(--u)), 18px);
  padding: clamp(5px,calc(1.4 * var(--u)),9px) clamp(9px,calc(2.4 * var(--u)),15px);
  min-height: clamp(26px, calc(7 * var(--u)), 34px); border-radius: 7px; cursor: pointer; white-space: nowrap;
  border: 1px solid #6a4f1c; background: linear-gradient(#d8b25a, #b8862f); color: #1a1306; font-weight: 700;
}
.sell-btn { border-color: #314733; background: var(--panel-2); color: var(--ink); font-weight: 600; }
.buy-btn:disabled, .sell-btn:disabled { opacity: 0.4; cursor: not-allowed; filter: grayscale(0.5); }
/* Buy button + its (touch-visible) coin-shortfall note, stacked as one non-shrinking cell. */
.buy-wrap { flex-shrink: 0; display: flex; flex-direction: column; align-items: stretch; gap: 3px; }
.buy-need { font-size: clamp(9px, calc(2.5 * var(--u)), 14px); color: #e0a86a; text-align: center; line-height: 1.1; }

/* ---------------- Random-event modal ---------------- */
/* …and the timed-event explainer, which shared this styling back when it (wrongly) shared
   the id too — see the comment on #timed-event-modal in index.html. */
#event-modal .modal-card h2,
#timed-event-modal .modal-card h2 { font-size: clamp(26px, calc(7 * var(--u)), 50px); }
.event-badge {
  display: inline-block; font-size: clamp(13px, calc(3.6 * var(--u)), 24px); text-transform: uppercase; letter-spacing: 0.5px;
  padding: 5px 12px; border-radius: 999px; margin-bottom: 10px;
  border: 1px solid #314733; background: #0e130d; color: var(--muted);
}
.event-badge.rar-common { color: #c9c9c9; }
.event-badge.rar-uncommon { color: #93d68c; border-color: #3f6e3a; }
.event-badge.rar-rare { color: #8db4ec; border-color: #3a5680; }
.event-badge.rar-epic { color: #d29cef; border-color: #6a3a80; }
.event-badge.rar-legendary { color: #f0c878; border-color: #7a5a1c; box-shadow: 0 0 10px #7a5a1c66; }
#event-text { margin: 4px 0 16px; line-height: 1.4; font-size: clamp(18px, calc(4.8 * var(--u)), 34px); }
.event-options { display: flex; flex-direction: column; gap: 8px; }
.event-opt {
  padding: 15px 16px; border-radius: 10px; border: 1px solid #314733;
  background: #0e130d; color: var(--ink); font-size: clamp(17px, calc(4.6 * var(--u)), 32px); cursor: pointer; text-align: left;
}
.event-opt:active { background: var(--panel-2); }
/* Choice-event countdown: a depleting bar showing the auto-resolve timer (server picks the safe
   default at expiry). The fill width is transitioned from JS over the server-sent timeoutMs. */
.event-timer { margin-top: 12px; height: 6px; border-radius: 3px; background: #0e130d; border: 1px solid #314733; overflow: hidden; }
.event-timer.hidden, .event-timer-note.hidden { display: none; }
.event-timer-fill { height: 100%; width: 100%; background: linear-gradient(90deg, #e8c45a, #c8922f); }
.event-timer-note { margin: 6px 2px 0; color: var(--muted); font-size: clamp(12px, calc(3.2 * var(--u)), 22px); }

/* ---------------- Tutorial overlay ---------------- */
.tut-card { max-width: 340px; }
.tut-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.tut-counter { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.tut-skip { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 12px; padding: 2px 0; }
.tut-skip:hover { color: var(--ink); }
.tut-title { font-size: 18px; color: var(--accent); margin: 0 0 10px; }
.tut-text { color: #c8b87a; margin: 0 0 16px; line-height: 1.6; font-size: 14px; }
.tut-text strong { color: var(--ink); }
.tut-text em { color: var(--accent-2); font-style: normal; }
.tut-dots { display: flex; gap: 6px; justify-content: center; margin-bottom: 16px; }
.tut-dot { width: 7px; height: 7px; border-radius: 50%; background: #2a3a1a; transition: background 0.2s; }
.tut-dot.active { background: var(--accent-2); }
.tut-actions { display: flex; gap: 8px; justify-content: space-between; align-items: center; }
.tut-prev { background: none; border: 1px solid #314733; border-radius: 8px; color: var(--muted); cursor: pointer; font-size: 13px; padding: 8px 14px; }
.tut-prev:hover { color: var(--ink); border-color: #4a6a3a; }
.tut-next-btn { flex: 1; }

/* ---------------- Contextual tips ---------------- */
.tut-tip {
  position: fixed; left: 50%; transform: translateX(-50%) translateY(8px);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 92px);
  /* 39, one BELOW .modal — a coach mark must never paint over a dialog. At the same
     z-index as .modal it won by being appended to <body> later, and on a phone the
     "Heading out?" tip landed squarely on the offline summary's Continue button: four
     taps in a row did nothing because they were all hitting the tip. tutorial.js also
     holds tips back while any modal is open; this is the backstop for a tip that was
     already on screen when one opened. */
  width: min(92%, 420px); z-index: 39; pointer-events: none;
  opacity: 0; transition: opacity .25s ease, transform .25s ease;
}
.tut-tip.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
.tut-tip.hidden { display: none; }
.tut-tip-card {
  background: #16210ff2; border: 1px solid #314733; border-left: 4px solid var(--accent-2);
  border-radius: 12px; padding: 14px 16px; box-shadow: 0 10px 28px rgba(0,0,0,.5);
}
.tut-tip-title { color: var(--accent); font-size: 15px; font-weight: 600; margin-bottom: 6px; }
.tut-tip-text { color: #c8b87a; font-size: 14px; line-height: 1.55; margin-bottom: 12px; }
.tut-tip-text strong { color: var(--ink); }
.tut-tip-text em { color: var(--accent-2); font-style: normal; }
.tut-tip-ok { width: 100%; }

/* ── Item-found discovery popup ("treasure moment") ──────────────────────────────────
   Wrapper covers the screen but is click-through (pointer-events:none) so the popup never
   traps the player; only the card catches taps to dismiss early. Centred a little above the
   middle so it clears the player avatar. Rarity sets --rar / --rarGlow for border + glow. */
.if-wrap {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center;
  padding-bottom: 16vh;                 /* nudge the card up, off the avatar/controls */
  pointer-events: none;
}
.if-wrap.hidden { display: none; }

.if-card {
  --rar: #b8c4b0; --rarGlow: rgba(184,196,176,.4); --rarInk: #d8e0d0;
  pointer-events: auto; cursor: pointer;
  position: relative; width: min(90vw, 360px); max-width: 360px;
  padding: 18px 22px 14px; text-align: center;
  background: linear-gradient(180deg, #20271b 0%, #131810 100%);
  border: 2px solid var(--rar);
  border-radius: 16px;
  box-shadow: 0 12px 44px rgba(0,0,0,.6), 0 0 30px var(--rarGlow), inset 0 0 26px rgba(0,0,0,.45);
  overflow: hidden;
  will-change: transform, opacity;
}
.if-card.rar-worn     { --rar:#8b9088; --rarGlow:rgba(139,144,136,.35); --rarInk:#c4c8c0; }
.if-card.rar-standard { --rar:#b8c4b0; --rarGlow:rgba(184,196,176,.4);  --rarInk:#d8e0d0; }
.if-card.rar-refined  { --rar:#7fc9a0; --rarGlow:rgba(127,201,160,.45); --rarInk:#a8e6c4; }
.if-card.rar-rare     { --rar:#5a86c0; --rarGlow:rgba(90,134,192,.55);  --rarInk:#8db4ec; }
.if-card.rar-epic     { --rar:#b46fd0; --rarGlow:rgba(180,111,208,.55); --rarInk:#d29cef; }
.if-card.rar-relic    { --rar:#d8a24a; --rarGlow:rgba(216,162,74,.65);  --rarInk:#f0c878; }

/* entrance: scale + fade in (~0.3s) with a soft overshoot; exit: gentle scale/fade out */
@keyframes if-in  { 0%{opacity:0;transform:scale(.7)} 65%{opacity:1;transform:scale(1.05)} 100%{transform:scale(1)} }
@keyframes if-out { 0%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.92) translateY(8px)} }
.if-card.if-in  { animation: if-in .32s cubic-bezier(.2,1.2,.4,1) both; }
.if-card.if-out { animation: if-out .4s ease-in both; }

/* faint rotating radiance behind the whole card */
.if-rays {
  position: absolute; left: 50%; top: 38%; width: 460px; height: 460px;
  transform: translate(-50%,-50%); pointer-events: none; opacity: .5;
  background: conic-gradient(from 0deg, transparent 0 14deg, var(--rarGlow) 16deg 18deg, transparent 20deg 44deg);
  -webkit-mask: radial-gradient(circle, #000 0 30%, transparent 62%);
          mask: radial-gradient(circle, #000 0 30%, transparent 62%);
  animation: if-spin 14s linear infinite;
}
@keyframes if-spin { to { transform: translate(-50%,-50%) rotate(360deg); } }

.if-heading {
  position: relative; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  font-size: 13px; color: var(--rarInk); margin-bottom: 8px; text-shadow: 0 1px 3px #000;
}

/* artwork — the visual focus, with a glowing halo + slow magical ring */
.if-art { position: relative; width: 120px; height: 120px; margin: 2px auto 10px; }
.if-glow {
  position: absolute; inset: -8px; border-radius: 50%;
  background: radial-gradient(circle, var(--rarGlow) 0%, transparent 68%);
  animation: if-pulse 2.4s ease-in-out infinite;
}
@keyframes if-pulse { 0%,100%{opacity:.6;transform:scale(.94)} 50%{opacity:1;transform:scale(1.06)} }
.if-ring {
  position: absolute; inset: 4px; border-radius: 50%;
  border: 2px dashed var(--rar); opacity: .55;
  animation: if-spin2 9s linear infinite;
}
@keyframes if-spin2 { to { transform: rotate(360deg); } }
.if-img {
  position: absolute; inset: 0; margin: auto; width: 92px; height: 92px;
  image-rendering: pixelated; object-fit: contain;
  filter: drop-shadow(0 4px 8px rgba(0,0,0,.6)) drop-shadow(0 0 10px var(--rarGlow));
  animation: if-bob 3s ease-in-out infinite;
}
@keyframes if-bob { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-4px)} }

/* one-shot shine sweep across the card on entrance */
.if-shine {
  position: absolute; top: 0; left: -60%; width: 50%; height: 100%; pointer-events: none;
  background: linear-gradient(105deg, transparent 0%, rgba(255,255,255,.18) 50%, transparent 100%);
  transform: skewX(-18deg);
}
.if-card.if-in .if-shine { animation: if-sweep 1s ease-out .15s 1 both; }
@keyframes if-sweep { 0%{left:-60%} 100%{left:130%} }

.if-name {
  position: relative; font-weight: 800; color: var(--ink); line-height: 1.15;
  font-size: clamp(19px, 5.4vw, 24px); text-shadow: 0 1px 4px #000;
  word-break: break-word;
}
.if-rarity {
  position: relative; display: inline-block; margin: 6px 0 2px;
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--rarInk); padding: 2px 10px; border: 1px solid var(--rar); border-radius: 999px;
}
.if-desc {
  position: relative; color: #c8c2a8; font-size: 13.5px; line-height: 1.45;
  margin: 8px 4px 10px; font-style: italic;
}
.if-meta {
  position: relative; display: flex; flex-wrap: wrap; gap: 5px 7px; justify-content: center;
  margin-bottom: 8px;
}
.if-meta > span {
  font-size: 12px; font-weight: 600; color: #d8d2bc;
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1);
  padding: 2px 8px; border-radius: 6px; display: inline-flex; align-items: center; gap: 3px;
}
.if-meta .ifm-type { color: var(--rarInk); border-color: var(--rar); }
.if-meta .ifm-stat { color: #8fd07f; }
.if-meta .ifm-val { color: var(--accent); }
.if-more { position: relative; font-size: 12px; color: var(--accent-2); font-weight: 700; margin-bottom: 4px; }
.if-more.hidden { display: none; }
.if-hint { position: relative; font-size: 11px; color: #7d8478; letter-spacing: .03em; }

@media (max-width: 480px) {
  .if-wrap { padding-bottom: 18vh; }
  .if-card { width: min(86vw, 330px); padding: 16px 18px 12px; }
  .if-art { width: 104px; height: 104px; }
  .if-img { width: 80px; height: 80px; }
}
@media (prefers-reduced-motion: reduce) {
  .if-rays, .if-ring, .if-img, .if-glow, .if-card.if-in .if-shine { animation: none; }
  .if-card.if-in { animation: if-in .2s ease-out both; }
}

.building-card {
  max-width: 360px;
}
.building-card p {
  margin: 8px 0 14px;
  color: #ded7bd;
  line-height: 1.45;
}
.build-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}
.build-actions button {
  min-width: 112px;
}
.smith-card { max-width: 620px; }
.smith-lines { color: #ded7bd; line-height: 1.45; margin-bottom: 12px; }
.smith-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.smith-actions button, .smith-recipe button {
  border: 1px solid #6a5734;
  background: #2b2116;
  color: #f2dfad;
  border-radius: 6px;
  padding: 9px 12px;
  font-weight: 800;
}
.smith-recipe {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 10px;
  border-top: 1px solid rgba(242, 223, 173, 0.16);
  padding: 10px 0;
}
.smith-recipe span { color: #bdb394; font-size: 13px; }
.smith-btns { grid-row: 1 / span 3; grid-column: 2; align-self: center; display: flex; flex-direction: column; gap: 6px; }
.smith-btns button { width: 100%; }
.smith-recipe.locked { opacity: 0.55; }
.smith-btns button[disabled] { opacity: 0.5; cursor: not-allowed; }
/* Between forge ticks: the anvil/furnace makes one thing per tick and the server enforces it,
   so the "make one" buttons rest for the remainder. Separate from [disabled], which carries
   the level/tool gates and must not be cleared when the cooldown ends (blacksmith-ui.js). */
.smith-btns button.cooling, .smith-actions button.cooling { opacity: 0.45; pointer-events: none; }
.smith-card h3 { margin: 14px 0 6px; font-size: 16px; color: #f2dfad; }

/* Running-smelt banner. Had no styling of its own at all — the Stop button was a raw browser
   default tacked onto the end of a sentence, on the one control a player wants to find in a
   hurry. Sticky to the top of the panel so it stays reachable however far down the recipe book
   you have scrolled. */
.smith-active {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: clamp(9px, calc(2.2 * var(--u)), 16px) clamp(11px, calc(2.8 * var(--u)), 18px);
  border: 1px solid #6a5228; border-radius: 10px;
  background: linear-gradient(180deg, #2a2113f2, #1d1810f2);
}
.smith-active .sa-text {
  flex: 1 1 auto; color: #f0c878;
  font-size: clamp(13px, calc(3.4 * var(--u)), 22px);
}
.smith-active .sa-text strong { color: var(--ink); }
.smith-active .sa-stop {
  flex: 0 0 auto; cursor: pointer; font-weight: 700;
  padding: clamp(8px, calc(2 * var(--u)), 14px) clamp(18px, calc(4.4 * var(--u)), 32px);
  font-size: clamp(13px, calc(3.4 * var(--u)), 22px);
  color: #ffdad2; background: #4a1f1a; border: 1px solid var(--danger); border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
}
.smith-active .sa-stop:active { transform: translateY(1px); background: #612821; }

/* ---------------- Tempering bench ---------------- */
.temper-card { max-width: 560px; position: relative; } /* keeps .modal-card's internal scroll */
.temper-intro { color: #ded7bd; line-height: 1.45; margin-bottom: 10px; }
.temper-gold {
  display: flex; justify-content: flex-end; align-items: center; gap: 4px;
  color: #bdb394; font-size: 13px; margin: -4px 0 8px;
}
.temper-gold strong { color: #f0c878; font-size: 14px; }
.temper-slots { display: flex; gap: 8px; margin-bottom: 10px; }
.temper-slot {
  flex: 1; min-width: 0; min-height: 86px; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; padding: 8px 4px; text-align: center;
  border: 1px solid #6a5734; border-left-width: 4px; border-radius: 8px; background: #241c11; cursor: pointer;
}
.temper-slot.empty { border-style: dashed; cursor: default; opacity: 0.7; }
.ts-plus { font-size: 22px; color: #8a7a52; line-height: 1; }
.ts-hint { color: #8a7a52; font-size: 12px; }
.ts-icon { width: 34px; height: 34px; image-rendering: pixelated; }
.ts-name { font-size: 12px; color: #f2dfad; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.ts-rar { font-size: 11px; }
.temper-out { border: 1px solid rgba(242, 223, 173, 0.22); border-radius: 8px; padding: 10px 12px; margin-bottom: 10px; background: #221a10; }
.temper-out.temper-waiting, .temper-out.temper-blocked { color: #bdb394; font-size: 13px; }
.temper-out.temper-blocked { color: #e0a08a; }
.to-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 2px 0; }
.to-row > span { color: #bdb394; font-size: 13px; }
.to-bt { color: #f0c878; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; }
.temper-note { color: #a89c78; font-size: 12px; line-height: 1.4; margin-top: 6px; }
.temper-result { border-radius: 8px; padding: 8px 12px; margin-bottom: 10px; font-size: 13px; line-height: 1.4; }
.temper-result.win { background: rgba(111, 174, 106, 0.16); color: #b5e2ae; border: 1px solid rgba(111, 174, 106, 0.4); }
.temper-result.fail { background: rgba(160, 90, 60, 0.16); color: #e0b49a; border: 1px solid rgba(160, 90, 60, 0.4); }
/* Forge progress while a temper is rolling. The fill's width is set inline from elapsed time
   (see temper-ui.js) — no CSS transition, or the panel's full re-render would restart it. */
/* The furnace/anvil "working" bar reuses the bench's track and fill verbatim (blacksmith-ui.js
   builds .tp-track/.tp-fill), so the three crafting surfaces read as one place. */
.temper-progress { margin-bottom: 10px; }
/* Sits between recipe rows, directly under the one being made — so it needs air above it too. */
.forge-progress { margin: 4px 0 14px; }
.forge-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.forge-head .tp-label { margin-bottom: 0; }
/* The forge lights up while it works: a hot glow that breathes in time with the strike. */
.forge-icon { font-size: 18px; line-height: 1; animation: forge-glow 0.62s ease-in-out infinite alternate; }
@keyframes forge-glow {
  from { filter: drop-shadow(0 0 2px #8a4a12); transform: scale(1); opacity: 0.75; }
  to { filter: drop-shadow(0 0 11px #ffcf7a) drop-shadow(0 0 4px #ff9a3c); transform: scale(1.14); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .forge-icon { animation: none; filter: drop-shadow(0 0 6px #ffb055); }
}
.tp-label { color: #d8b070; font-size: 12px; letter-spacing: 0.04em; margin-bottom: 5px; }
.tp-track {
  height: 10px; border-radius: 6px; overflow: hidden;
  background: rgba(0, 0, 0, 0.42); border: 1px solid #6a5228;
}
.tp-fill {
  height: 100%; border-radius: 5px 0 0 5px;
  background: linear-gradient(90deg, #8a5a1e, #f0c878 70%, #ffe9b0);
  box-shadow: 0 0 10px rgba(240, 200, 120, 0.55);
}
.temper-go {
  width: 100%; padding: 12px; margin-bottom: 6px; border-radius: 8px; border: 1px solid #8a6a34;
  background: linear-gradient(#4a3a1e, #33260f); color: #ffe9b0; font-size: 17px; font-weight: 800; letter-spacing: 0.02em;
}
.temper-go[disabled] { opacity: 0.5; cursor: not-allowed; }
.temper-controls { display: flex; gap: 8px; margin: 2px 0 8px; }
.tc-field { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; font-size: 12px; color: var(--muted); }
.tc-field select {
  width: 100%; padding: 7px 9px; border-radius: 7px; border: 1px solid #6a5734;
  background: #241c11; color: #f2dfad; font-size: 13px; font-weight: 600;
}
.temper-list { display: flex; flex-direction: column; gap: 6px; max-height: 32vh; overflow-y: auto; }
/* The Repair tab pages its list instead of nesting a scroll box (players read "what fits in
   the box" as "all I have" — bug #1002741), so the paged list always shows in full. */
.temper-list.paged { max-height: none; overflow-y: visible; }
.temper-pager { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 8px; }
.temper-pager button {
  border: 1px solid #6a5734; background: #2b2116; color: #f2dfad;
  border-radius: 6px; padding: 9px 14px; font-weight: 800; cursor: pointer;
}
.temper-pager button[disabled] { opacity: 0.4; cursor: default; }
.temper-pager .tp-page { color: #bdb394; font-size: 13px; white-space: nowrap; }
.temper-item {
  display: flex; align-items: center; gap: 10px; padding: 7px 10px; cursor: pointer;
  border: 1px solid rgba(242, 223, 173, 0.14); border-left-width: 4px; border-radius: 8px; background: #241c11;
}
.temper-item button { border: 1px solid #6a5734; background: #2b2116; color: #f2dfad; border-radius: 6px; padding: 7px 12px; font-weight: 800; }
.temper-item button[disabled] { opacity: 0.5; cursor: not-allowed; }
.ti-icon { width: 30px; height: 30px; image-rendering: pixelated; flex: none; }
.ti-body { flex: 1; min-width: 0; }
.ti-name { color: #f2dfad; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ti-sub { font-size: 12px; display: flex; align-items: center; gap: 8px; }
/* "equipped" tag on a repair row — worn gear is repaired in place, so the list has to say
   which pieces are on your back rather than in your pack. */
.ti-worn {
  color: #d8c48a; background: rgba(216, 196, 138, 0.12); border: 1px solid rgba(216, 196, 138, 0.35);
  border-radius: 999px; padding: 1px 7px; font-size: 10px; font-weight: 800; letter-spacing: 0.4px;
  text-transform: uppercase; white-space: nowrap;
}
/* rarity name inks inside the temper screen (the shared .rar-* rules only ink .ii-name/.es-item) */
.temper-card .rar-worn { color: #b7c0b0; } .temper-card .rar-standard { color: #e6e6e6; }
.temper-card .rar-refined { color: #93d68c; } .temper-card .rar-rare { color: #8db4ec; }
.temper-card .rar-epic { color: #d29cef; } .temper-card .rar-relic { color: #f0c878; }
/* forge-beat overlay: rising sparks (success) or drifting smoke puffs (failure) */
.temper-fx { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.temper-fx span { position: absolute; bottom: 18%; width: 6px; height: 6px; border-radius: 50%; animation: temper-spark 1.1s ease-out forwards; }
.temper-fx.fx-success span { background: #ffd070; box-shadow: 0 0 8px 2px rgba(255, 180, 60, 0.8); }
.temper-fx.fx-fail span { background: #5a544c; width: 10px; height: 10px; box-shadow: 0 0 10px 4px rgba(70, 66, 60, 0.7); animation-duration: 1.3s; }
@keyframes temper-spark {
  0% { transform: translateY(0) scale(1); opacity: 0; }
  15% { opacity: 1; }
  100% { transform: translateY(-140px) scale(0.4); opacity: 0; }
}

/* ---------------- AFK & Power Saving (js/afk.js) ---------------- */
/* The settings submenu modal: same control language as the ⚙ popover (mp-mute buttons),
   sized for a modal card. Opened by the single "AFK & Power Saving" popover button. */
/* Sound submenu — same shape as the AFK submenu below it. */
.sound-settings { display: flex; flex-direction: column; gap: 8px; text-align: left; }
.sound-settings h2 { margin: 0 0 2px; }
.sound-settings .muted { margin: 0 0 2px; font-size: 12.5px; line-height: 1.35; }
.sound-settings .mp-mute {
  width: 100%; padding: 10px; border-radius: 8px; border: 1px solid #314733;
  background: var(--panel-2); color: var(--ink); cursor: pointer; font-size: 14px; font-weight: 600;
}
.sound-settings .mp-mute.active { border-color: var(--accent-2); color: var(--accent-2); }
.sound-settings .mp-label { color: var(--muted); font-size: 12.5px; margin: 4px 0 -4px; }
.sound-settings .mp-range { width: 100%; accent-color: var(--accent-2); }
.sound-settings .mp-credit { margin-top: 2px; font-size: 10px; color: var(--muted); text-align: center; line-height: 1.3; }

.afk-settings { display: flex; flex-direction: column; gap: 8px; text-align: left; }
.afk-settings h2 { margin: 0 0 2px; }
.afk-settings .mp-mute {
  width: 100%; padding: 10px; border-radius: 8px; border: 1px solid #314733;
  background: var(--panel-2); color: var(--ink); cursor: pointer; font-size: 14px; font-weight: 600;
}
.afk-settings .mp-mute.active { border-color: var(--accent-2); }
.afk-settings .mp-select {
  width: 100%; padding: 9px; border-radius: 8px; border: 1px solid #314733;
  background: var(--panel-2); color: var(--ink); font-size: 14px;
}
.afk-settings .mp-select:disabled, .afk-settings .mp-mute:disabled { opacity: .45; }
.afk-settings .mp-label { color: var(--muted); font-size: 12.5px; margin: 4px 0 -4px; }
.afk-settings .mp-note { margin: -2px 0 0; color: var(--muted); font-size: 11.5px; line-height: 1.35; }
.music-pop .mp-mute.active { border-color: var(--accent-2); }

/* The low-power AFK screen. Pure black base (an OLED power win in itself), no gradients,
   no animation — the only motion is a JS-toggled dot opacity once per slow refresh. */
.afk-screen {
  position: fixed; inset: 0; z-index: 60;
  background: #000;
  display: flex; align-items: center; justify-content: center;
  padding: calc(20px + env(safe-area-inset-top, 0px)) 20px calc(20px + env(safe-area-inset-bottom, 0px));
  cursor: pointer;
}
.afk-inner { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; max-width: 420px; width: 100%; }
.afk-title { color: var(--accent); font-weight: 700; font-size: clamp(20px, calc(5.6 * var(--u)), 34px); letter-spacing: .5px; }
.afk-portrait { width: clamp(72px, calc(20 * var(--u)), 120px); height: clamp(72px, calc(20 * var(--u)), 120px);
  image-rendering: pixelated; border: 1px solid #223528; border-radius: 12px; background: #0a0d08; }
.afk-name { color: var(--ink); font-weight: 700; font-size: clamp(18px, calc(4.8 * var(--u)), 30px); }
.afk-activity { color: var(--muted); font-size: clamp(15px, calc(4 * var(--u)), 24px); }
.afk-conn { display: flex; align-items: center; gap: 8px; font-size: clamp(13px, calc(3.4 * var(--u)), 20px); color: var(--muted); }
.afk-conn-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent-2); opacity: .35; transition: opacity 2.5s linear; }
.afk-conn.tick .afk-conn-dot { opacity: 1; }
.afk-conn.warn .afk-conn-dot { background: #d8b24a; }
.afk-conn.bad .afk-conn-dot { background: var(--danger); }
.afk-conn.bad { color: #d8a08a; }
.afk-duration { color: var(--ink); font-size: clamp(15px, calc(4 * var(--u)), 24px); margin-top: 4px; font-variant-numeric: tabular-nums; }
.afk-hp { color: var(--muted); font-size: clamp(13px, calc(3.4 * var(--u)), 20px); font-variant-numeric: tabular-nums; }
.afk-stats { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; min-height: 1em; }
.afk-stat { color: var(--ink); font-size: clamp(14px, calc(3.8 * var(--u)), 22px); font-variant-numeric: tabular-nums; }
.afk-alert { color: #e8c06a; font-size: clamp(13px, calc(3.6 * var(--u)), 21px); border: 1px solid #4a3f1e;
  border-radius: 10px; padding: 8px 12px; background: #14100a; margin-top: 4px; }
.afk-return {
  margin-top: 14px; width: 100%; max-width: 320px;
  padding: clamp(14px, calc(3.6 * var(--u)), 22px);
  border-radius: 12px; border: 1px solid #4a5a3a; background: #22301c; color: #e8d9a0;
  font-size: clamp(17px, calc(4.6 * var(--u)), 28px); font-weight: 700; cursor: pointer;
}
.afk-hint { color: #4a5647; font-size: clamp(11px, calc(3 * var(--u)), 17px); }
/* Dim: an extra-dark presentation for phones left face-up. Static opacity only. */
.afk-screen.dim .afk-inner { opacity: .5; }
/* Waking hold: exit was tapped; keep covering the world until fresh data has landed. */
.afk-screen.waking .afk-inner { opacity: .35; }

/* ── Graphics settings screen (js/graphics-ui.js) ─────────────────────────────────────
   Phone-portrait first: one column of compact rows, collapsible categories, segmented
   selectors that wrap rather than shrinking below a comfortable touch target, and a
   sticky action bar so Apply/Cancel stay reachable however long the list gets. */
.gfx-card { max-width: 420px; }
.gfx-head { display: flex; align-items: center; gap: 6px; }
.gfx-head h2 { margin: 0 0 10px; }
.gfx-backbtn {
  background: none; border: none; color: var(--accent); cursor: pointer;
  font-size: 30px; line-height: 1; padding: 0 8px 10px 0; min-width: 34px;
}
.gfx-intro { margin: 0 0 12px; font-size: 12px; line-height: 1.4; }

/* Collapsible category. The body is display:none when closed rather than max-height
   animated — with UI animation off there'd be nothing to animate anyway, and a hard
   toggle can't leave a half-open section behind. */
.gfx-sec { border: 1px solid #314733; border-radius: 10px; background: #0e130d; margin: 0 0 8px; }
.gfx-sechead {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  padding: 11px 12px; background: none; border: none; cursor: pointer; color: var(--ink);
  font-size: 14px; font-weight: 700; text-align: left;
}
.gfx-sectitle { color: var(--accent); }
.gfx-secarrow { color: var(--muted); transition: transform .18s ease; }
.gfx-sec.open .gfx-secarrow { transform: rotate(180deg); }
.gfx-secbody { display: none; padding: 0 12px 12px; }
.gfx-sec.open .gfx-secbody { display: block; }

.gfx-row { padding: 8px 0; border-top: 1px solid #1e2a1c; }
.gfx-row:first-child { border-top: none; }
.gfx-rowlabel { font-size: 13px; font-weight: 600; color: var(--ink); margin: 0 0 6px; }
.gfx-note { margin: 6px 0 0; font-size: 11px; line-height: 1.35; color: var(--muted); }

/* Segmented selector. Equal-width columns on one row: five options across a 412px phone
   is ~64px each, comfortably above the ~44px touch target, and a single row reads as one
   scale (which is what these settings are) instead of a 4+1 block. Very narrow screens
   fall back to wrapping so nothing is ever clipped. */
.gfx-seg { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 4px; }
.gfx-segbtn {
  /* Two-word labels ("Very Low", "Ultra Low") wrap onto a second line rather than being
     truncated — grid rows size to the tallest button, so the strip stays even. */
  min-height: 38px; padding: 5px 3px; line-height: 1.15;
  border: 1px solid #314733; border-radius: 8px; background: var(--panel-2);
  color: var(--ink); font-size: 11px; font-weight: 600; cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
@media (max-width: 359px) {
  .gfx-seg { display: flex; flex-wrap: wrap; }
  .gfx-segbtn { flex: 1 1 30%; }
}
.gfx-segbtn:hover { border-color: var(--accent); }
.gfx-segbtn.active { border-color: var(--accent); background: #2c3f2a; color: var(--accent); }

/* Switch row: full-width hit target with the label on the left and the track on the right. */
.gfx-switch {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; min-height: 40px; padding: 6px 0;
  background: none; border: none; cursor: pointer; color: var(--ink);
  font-size: 13px; font-weight: 600; text-align: left;
}
.gfx-switch-label { flex: 1; min-width: 0; }
.gfx-switch-track {
  flex: 0 0 auto; position: relative; width: 42px; height: 24px; border-radius: 999px;
  border: 1px solid #314733; background: #11160f; transition: background .15s ease, border-color .15s ease;
}
.gfx-switch-knob {
  position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%;
  background: #6b7a66; transition: transform .15s ease, background .15s ease;
}
.gfx-switch.on .gfx-switch-track { background: #2c3f2a; border-color: var(--accent); }
.gfx-switch.on .gfx-switch-knob { transform: translateX(18px); background: var(--accent); }

/* Low-Power Mode: the one control on this screen that gets visual weight of its own. */
.gfx-lowpower {
  margin: 10px 0 0; padding: 8px 12px; border: 1px solid #4a5c3a; border-radius: 10px;
  background: #12180f;
}
.gfx-lowpower.on { border-color: var(--accent); background: #1a2113; }

.gfx-actions { display: flex; gap: 8px; margin-top: 14px; }
.gfx-actions button { flex: 1; min-height: 42px; }
.gfx-actions-reset { margin-top: 8px; }
.gfx-resetbtn {
  border: 1px solid #314733; border-radius: 10px; background: none;
  color: var(--muted); font-size: 12px; font-weight: 600; padding: 9px 6px; cursor: pointer;
}
.gfx-resetbtn:hover { border-color: var(--accent); color: var(--ink); }

/* ── UI Animation setting (body classes, set by js/graphics.js) ───────────────────────
   Only durations are neutralised — never `display`, `visibility` or `opacity` end states —
   so every menu, popup and toast still appears, still closes, and still responds. An
   element that relies on a transition to become visible ends up visible instantly rather
   than never. `gfx-no-motion` covers the "Motion effects" toggle: the ambient drifting /
   breathing overlays (dread vignette, announcement pulses) hold still instead of moving,
   but keep their intensity, because those are danger cues rather than decoration. */
body.gfx-ui-off *, body.gfx-ui-off *::before, body.gfx-ui-off *::after {
  transition-duration: 0s !important;
  animation-duration: 0s !important;
  animation-iteration-count: 1 !important;
}
body.gfx-ui-reduced *, body.gfx-ui-reduced *::before, body.gfx-ui-reduced *::after {
  transition-duration: .06s !important;
  animation-duration: .18s !important;
}
/* …with one exception, because that blanket duration is not as harmless as it looks:
   `transition-property` defaults to `all`, so handing a duration to every element makes
   properties that were never meant to animate start interpolating. On the minimap that was
   a real bug — minimap.js sizes #minimap-body from JS and welds a Phaser camera to its rect,
   so an interpolated height meant the camera measured a box on its way from 72vh down to the
   dial, and the map rendered from the dial all the way down the screen. Nothing here should
   ever tween; the frame is a live camera surface, not decoration. Id specificity beats the
   `body.gfx-ui-* *` rules above, so this holds at every UI-animation level. */
#minimap, #minimap * { transition-property: none !important; }
/* The always-on HUD warnings get their opacity FROM their keyframes, so simply zeroing the
   duration above would leave them at full strength rather than their intended resting level.
   These are the same calm static states the prefers-reduced-motion block already defines —
   the danger cue stays fully visible, it just stops throbbing. */
body.gfx-ui-off .lowhp-vignette, body.gfx-no-motion .lowhp-vignette {
  animation: none !important; opacity: .72;
}
body.gfx-ui-off #hp-bar.low, body.gfx-no-motion #hp-bar.low {
  animation: none !important; box-shadow: 0 0 8px 1px rgba(210,74,74,.5);
}
body.gfx-ui-off #hp-bar.healing, body.gfx-no-motion #hp-bar.healing {
  animation: none !important; box-shadow: 0 0 8px 1px rgba(90,200,90,.45);
}
/* Dread stays fully visible — the darkness is a danger cue, the movement is the garnish. */
body.gfx-no-motion .dread-vignette { animation: none !important; }
body.gfx-no-motion .dread-vignette::after { animation: none !important; opacity: .5; }

/* ---------------- Onboarding coaching (js/onboarding.js) ----------------
   A ring around the control the beginner quest is asking for, plus one short line beside
   it. Deliberately small: no scrim, no blocking overlay, nothing that has to be dismissed —
   a new player can ignore it and keep playing, which is the whole point of an idle game.
   The ring is drawn OUTSIDE the control's box (inset ring + offset shadow, never a fill) so
   it can't swallow the tap it is asking for; pointer-events stay off for the same reason. */
.coach-ring {
  position: fixed; z-index: 60; pointer-events: none;
  border: 2px solid var(--accent); border-radius: 14px;
  box-shadow: 0 0 0 2px #0008, 0 0 14px 2px rgba(201,160,60,.45);
  animation: coach-pulse 1.9s ease-in-out infinite;
}
.coach-ring.hidden, .coach-chip.hidden { display: none; }
@keyframes coach-pulse {
  0%, 100% { opacity: .95; transform: scale(1); }
  50%      { opacity: .55; transform: scale(1.035); }
}
/* The line itself: the same dark parchment chip the rest of the HUD uses, capped narrow so
   it stays one or two lines on a 320px screen. */
.coach-chip {
  /* The whole coaching window is a drag surface; its actual button remains a normal control.
     Automatic placement keeps it away from the current objective, and dragging is clamped so
     it can never be lost outside the viewport. */
  pointer-events: auto; cursor: grab; touch-action: none;
  user-select: none; -webkit-user-select: none;
  position: fixed; z-index: 61; max-width: min(78vw, 340px);
  display: flex; align-items: center; gap: 10px;
  padding: clamp(7px, calc(1.7 * var(--u)), 13px) clamp(10px, calc(2.4 * var(--u)), 16px);
  background: linear-gradient(#1b2216f2, #141a10ee);
  border: 1px solid #4b5c39; border-left: 3px solid var(--accent);
  border-radius: 11px; box-shadow: 0 4px 18px #0009;
  color: var(--ink); font-size: clamp(13px, calc(3.2 * var(--u)), 19px); line-height: 1.35;
}
.coach-chip.dragging { cursor: grabbing; }
.coach-chip .coach-text { flex: 1 1 auto; }
.coach-chip .coach-show {
  pointer-events: auto;   /* the one part of the chip that IS a control */
  flex: 0 0 auto; padding: 6px 11px; min-height: 34px;   /* thumb-sized on a phone */
  border: 1px solid var(--accent); border-radius: 8px; background: #2a3320;
  color: var(--accent-2); font-weight: 700; font-size: .92em; cursor: pointer;
}
.coach-chip .coach-show:active { background: #38431f; }
.coach-chip .coach-show.hidden { display: none; }


/* ---------------- Friends (phase 5) ---------------- */
.friends-card { max-width: 460px; }
.friends-card h3 { margin: 14px 0 6px; font-size: 15px; color: #f2dfad; }
.fr-count { color: var(--muted); font-size: 12px; font-weight: 400; }
.fr-row {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 0; border-bottom: 1px solid #22301f;
}
.fr-row:last-child { border-bottom: 0; }
.fr-name { flex: 1 1 auto; color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fr-status { color: var(--muted); font-size: 11px; white-space: nowrap; }
.fr-status.ended { color: #c7aa78; }
/* Presence, at the smallest scale that still reads: lit = online now. Nothing else is shown —
   last-seen and location were deliberately cut from v1 (FRIENDS-PLAN.md §1). */
.fr-dot { width: 8px; height: 8px; border-radius: 50%; background: #33402f; flex: 0 0 auto; }
.fr-dot.on { background: var(--accent-2); box-shadow: 0 0 6px var(--accent-2); }
/* Someone who has not accepted you does not get a presence light. The dot still occupies its
   space so names stay in one column, but a filled grey circle reads as "offline", and we do not
   know that — we are not told, and are not entitled to be. */
.fr-dot.unknown { background: transparent; }
.fr-btns { display: flex; gap: 6px; flex: 0 0 auto; }
.fr-btn {
  cursor: pointer; border-radius: 8px; font-weight: 600;
  padding: clamp(6px, calc(1.6 * var(--u)), 12px) clamp(10px, calc(2.6 * var(--u)), 18px);
  font-size: clamp(12px, calc(3.2 * var(--u)), 20px);
  border: 1px solid #314733; background: var(--panel-2); color: var(--ink);
}
.fr-btn.primary { border-color: #6a5228; background: #3a2f16; color: #f2dfad; }
.fr-btn.danger { border-color: var(--danger); background: #3a1a16; color: #ffdad2; }
.fr-btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.fr-add { display: flex; gap: 8px; margin-top: 6px; }
.fr-add input {
  flex: 1 1 auto; min-width: 0; border-radius: 8px; padding: 9px 11px;
  border: 1px solid #314733; background: #0e130d; color: var(--ink);
  font-size: clamp(13px, calc(3.4 * var(--u)), 20px);
}
/* Relationship controls in the profile header — above the private-profile cut-off, so a player
   who hides their stats can still be added or blocked. */
.pf-social { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 4px; }
.pf-social:empty { display: none; }
.menu-count {
  display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: 999px;
  background: var(--danger); color: #fff; font-size: 11px; font-weight: 700; line-height: 17px;
}

/* ---------------- DM thread (phase 6) ---------------- */
.dm-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.dm-title { font-weight: 700; color: #f2dfad; font-size: clamp(14px, calc(3.6 * var(--u)), 22px); }
/* The conversation scrolls inside the card, so the send box stays put at the bottom — on a
   phone a send box that scrolls away is a send box you cannot find. */
.dm-scroll { max-height: 46vh; overflow-y: auto; padding-right: 4px; margin-bottom: 10px; }
.dm-line { display: flex; align-items: baseline; gap: 6px; padding: 5px 0; border-bottom: 1px solid #1c2719; }
.dm-line:last-child { border-bottom: 0; }
.dm-who { flex: 0 0 auto; font-weight: 700; color: var(--muted); font-size: clamp(11px, calc(2.9 * var(--u)), 17px); }
.dm-line.mine .dm-who { color: var(--accent-2); }
.dm-text { flex: 1 1 auto; color: var(--ink); font-size: clamp(13px, calc(3.4 * var(--u)), 20px); word-break: break-word; }
/* Report is a quiet flag, not a button — it should be findable without inviting idle taps. */
.fr-report { flex: 0 0 auto; background: none; border: 0; color: #6b5a3a; cursor: pointer; font-size: 14px; padding: 0 4px; }
.fr-report:hover, .fr-report:active { color: var(--danger); }
.dm-send { display: flex; gap: 8px; }
.dm-send input {
  flex: 1 1 auto; min-width: 0; border-radius: 8px; padding: 9px 11px;
  border: 1px solid #314733; background: #0e130d; color: var(--ink);
  font-size: clamp(13px, calc(3.4 * var(--u)), 20px);
}
.fr-unread {
  flex: 0 0 auto; margin-right: 4px; padding: 0 7px; border-radius: 999px;
  background: var(--danger); color: #fff; font-size: 11px; font-weight: 700; line-height: 18px;
}
.fr-report {
  flex: 0 0 auto; background: none; border: 1px solid #4a3a22; border-radius: 6px;
  color: #b79a63; cursor: pointer; font-size: 11px; font-weight: 600; padding: 2px 7px; white-space: nowrap;
}
.fr-report:hover, .fr-report:active { color: #ffdad2; border-color: var(--danger); background: #3a1a16; }
.fr-hint { color: var(--muted); font-size: 12.5px; margin: 6px 0 2px; line-height: 1.4; }
/* Private-messages tab inside the chat box. Rendered into the same log element as the channels,
   so it inherits the scrolling and phone sizing rather than growing a second scroll region. */
.pm-pick { background: none; border: 0; color: var(--accent-2); font-weight: 700; cursor: pointer; padding: 0; font-size: inherit; }
.pm-title { margin-left: 8px; font-weight: 700; color: #f2dfad; }
.pm-preview { color: var(--muted); margin-left: 8px; }
.pm-exfriend { color: #8a7b52; margin-left: 6px; font-size: 11px; font-style: italic; }
.pm-unread { margin-left: 6px; padding: 0 6px; border-radius: 999px; background: var(--danger); color: #fff; font-size: 10px; font-weight: 700; }
.dm-count { margin-top: 5px; text-align: right; color: var(--muted); font-size: 11px; }
.dm-count.low { color: #e0a86a; font-weight: 600; }
.pm-block { margin-left: auto; background: none; border: 1px solid #4a2a22; border-radius: 6px; color: #b78a7a; cursor: pointer; font-size: 11px; font-weight: 600; padding: 2px 8px; }
.pm-block:hover, .pm-block:active { color: #ffdad2; border-color: var(--danger); background: #3a1a16; }
/* The whole conversation row is the tap target, not just the name. */
.pm-row { cursor: pointer; padding: 7px 6px; border-radius: 8px; }
.pm-row:hover, .pm-row:active { background: #1b2a1f88; }
.pm-row .pm-pick { pointer-events: none; }
/* Private messages borrow general chat's line styling outright — same shape, same rhythm — so
   the only bespoke rules left are the report control and the other player's name colour. */
.dm-line { display: block; padding: 4px 0; border-bottom: 0; }
.dm-line .fr-report { margin-left: 8px; vertical-align: baseline; }
/* The person you are talking TO is the thing your eye should find first. */
.chat-name.other { color: #7fb4e6; font-weight: 700; }
/* Two counts, two colours, so a glance tells you WHICH kind of message is waiting:
   amber on 💬 = public chat (the .chat-badge default), green on 🤝 = a friend.
   Entro's point: a private message announced with the same badge as room chatter does not
   read as a private message at all. */
#friends-badge { background: var(--accent-2); color: #08110a; }
#friends-badge.reqs { background: #7fb4e6; color: #08111a; }   /* friend REQUESTS, not messages */

/* ── Level-up celebration ─────────────────────────────────────────────────────
   Dave's brief: the skill's own tool bursts out of the middle of the screen and flies off every
   edge, with the new level glowing in the centre. Deliberately NOT a card like item-found —
   a level is something that happens to YOU, so it takes the whole screen for a moment instead
   of arriving as an object in a frame.

   Cost control for low-end Android: particles are plain spans animated with ONE transform each
   (no per-frame JS, no canvas), the count is scaled by W.Gfx.particleScale() and drops to zero
   when particles are off — at which point the number and skill name still play, so the
   information never depends on the decoration. */
.lu-wrap {
  position: fixed; inset: 0; z-index: 71;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;                 /* never traps the player */
}
.lu-wrap.hidden { display: none; }
.lu-wrap.tappable { pointer-events: auto; cursor: pointer; }

.lu-burst { position: absolute; inset: 0; overflow: hidden; }
.lu-bit {
  position: absolute; left: 50%; top: 50%;
  font-size: 26px; line-height: 1;
  will-change: transform, opacity;
  animation: lu-fly var(--dur, 1100ms) cubic-bezier(.15,.6,.35,1) forwards;
  transform: translate(-50%, -50%);
}
@keyframes lu-fly {
  0%   { opacity: 0; transform: translate(-50%,-50%) rotate(0deg) scale(.35); }
  12%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) rotate(var(--rot)) scale(1); }
}

.lu-core { position: relative; text-align: center; }
.lu-ring {
  position: absolute; left: 50%; top: 50%; width: 120px; height: 120px; margin: -60px 0 0 -60px;
  border-radius: 50%; border: 3px solid var(--lu, #7fc9a0); opacity: 0;
}
.lu-ring.pop { animation: lu-ring .7s ease-out 1; }
.lu-heading {
  font-size: 14px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--luInk, #a8e6c4); opacity: .92;
  text-shadow: 0 0 12px var(--luGlow, rgba(127,201,160,.6));
}
.lu-num {
  font-size: clamp(64px, 18vw, 132px); font-weight: 900; line-height: 1;
  color: #fff;
  text-shadow: 0 0 10px var(--lu, #7fc9a0), 0 0 34px var(--luGlow, rgba(127,201,160,.75)),
               0 0 70px var(--luGlow, rgba(127,201,160,.5)), 0 4px 14px rgba(0,0,0,.75);
}
.lu-num.land { animation: lu-land .55s cubic-bezier(.2,1.6,.4,1) 1; }
.lu-skill {
  font-size: 20px; font-weight: 700; letter-spacing: .04em; color: #eaf0e4;
  text-shadow: 0 2px 10px rgba(0,0,0,.8);
}
.lu-seal { margin-top: 8px; font-size: 14px; color: var(--luInk, #a8e6c4); text-shadow: 0 2px 8px rgba(0,0,0,.8); }
.lu-rows { margin-top: 10px; display: flex; flex-direction: column; gap: 3px; }
.lu-row { font-size: 15px; color: #dfe6d8; text-shadow: 0 2px 8px rgba(0,0,0,.8); opacity: 0; animation: lu-row .3s ease-out forwards; }
.lu-row b { color: var(--luInk, #a8e6c4); }
.lu-seal.hidden, .lu-rows.hidden { display: none; }

/* Skill family tints the glow. The skill NAME is always the identifier — colour only supports. */
.lu-wrap.fam-combat { --lu:#e08a6a; --luGlow:rgba(224,138,106,.75); --luInk:#f5b795; }
.lu-wrap.fam-gather { --lu:#7fc9a0; --luGlow:rgba(127,201,160,.75); --luInk:#a8e6c4; }
.lu-wrap.fam-craft  { --lu:#e0b45a; --luGlow:rgba(224,180,90,.75);  --luInk:#f2d68e; }
/* Milestones escalate the same moment rather than adding a second design. */
.lu-wrap.ms-100 { --lu:#ffd45a; --luGlow:rgba(255,212,90,.85); --luInk:#ffe9a3; }

@keyframes lu-core-in  { 0%{opacity:0;transform:scale(.8)} 100%{opacity:1;transform:scale(1)} }
@keyframes lu-core-out { 0%{opacity:1} 100%{opacity:0;transform:scale(1.06)} }
@keyframes lu-land { 0%{transform:scale(.5);opacity:.3} 60%{transform:scale(1.16)} 100%{transform:scale(1);opacity:1} }
@keyframes lu-ring { 0%{transform:scale(.4);opacity:.95} 100%{transform:scale(3.2);opacity:0} }
@keyframes lu-row  { to { opacity: 1; } }
.lu-core.in  { animation: lu-core-in .28s ease-out 1; }
.lu-core.out { animation: lu-core-out .38s ease-in forwards; }
@media (prefers-reduced-motion: reduce) {
  .lu-bit { display: none; }
  .lu-core.in, .lu-core.out, .lu-num.land, .lu-ring.pop { animation: none; }
  .lu-row { opacity: 1; animation: none; }
}

/* Confetti — small explosions at random points behind the level number, one a second while it
   is up. Same cost model as the tool glyphs: one CSS transform per piece, self-removing, and
   the whole layer is skipped when particles are off. Pieces fall as they fly (the dy keyframe
   is biased downward) so it reads as confetti rather than a starburst. */
.lu-conf { position: absolute; inset: 0; overflow: hidden; }
.lu-confbit {
  position: absolute; width: 7px; height: 10px; border-radius: 1px;
  will-change: transform, opacity;
  animation: lu-conf-fly var(--dur, 1400ms) cubic-bezier(.1,.5,.3,1) forwards;
}
@keyframes lu-conf-fly {
  0%   { opacity: 0; transform: translate(0,0) rotate(0deg) scale(.5); }
  10%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1); }
}
@media (prefers-reduced-motion: reduce) { .lu-confbit { display: none; } }
