:root {
  --bg-primary: #0d0b0e;
  --bg-card: #18141b;
  --accent-gold: #c9a050;
  --text-primary: #e2d8c3;
  --text-muted: #8c8275;
  --health-from: #8b0000;
  --health-to: #e63946;
  --energy-from: #1d3557;
  --energy-to: #457b9d;
  --stamina-from: #2a9d8f;
  --stamina-to: #3a5a40;
  --vp-from: #6a0dad;
  --vp-to: #9d4edd;
  --xp-from: #c9a050;
  --xp-to: #e6c86e;
}

* { box-sizing: border-box; }

html {
  scrollbar-gutter: stable;
  overflow-y: scroll;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: 'Georgia', 'Times New Roman', serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body {
  overflow-y: hidden;
}

#app {
  max-width: 480px;
  width: 100%;
  margin: 0 auto;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  border-left: 1px solid #000;
  border-right: 1px solid #000;
  background: var(--bg-primary);
  overflow: hidden;
}

header.app-header {
  padding: 14px 16px;
  border-bottom: 2px solid var(--accent-gold);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: linear-gradient(180deg, #16121a, var(--bg-primary));
}

header.app-header h1 {
  font-size: 1.1rem;
  letter-spacing: 1px;
  color: var(--accent-gold);
  margin: 0;
  text-transform: uppercase;
}

main {
  flex: 1;
  padding: 12px 14px 84px;
  overflow-y: auto;
}

.card {
  background: var(--bg-card);
  border: 1px solid var(--accent-gold);
  border-radius: 6px;
  padding: 12px 14px;
  margin-bottom: 12px;
}

.card h2, .card h3 {
  margin-top: 0;
  color: var(--accent-gold);
}

.muted { color: var(--text-muted); font-size: 0.85rem; }

.stat-bar {
  height: 14px;
  border-radius: 4px;
  background: #241f28;
  border: 1px solid #000;
  overflow: hidden;
  margin: 4px 0 10px;
  position: relative;
}

.stat-bar-fill {
  height: 100%;
  transition: width 0.3s ease;
}

.stat-bar-fill.health { background: linear-gradient(90deg, var(--health-from), var(--health-to)); }
.stat-bar-fill.energy { background: linear-gradient(90deg, var(--energy-from), var(--energy-to)); }
.stat-bar-fill.stamina { background: linear-gradient(90deg, var(--stamina-from), var(--stamina-to)); }
.stat-bar-fill.vp { background: linear-gradient(90deg, var(--vp-from), var(--vp-to)); }
.stat-bar-fill.xp { background: linear-gradient(90deg, var(--xp-from), var(--xp-to)); }
/* XP hole: negative progress within a level, dug by dying. Deliberately reads
   as debt rather than progress — hatched and desaturated, filling from the
   RIGHT so it visibly grows backwards away from the next level. */
.stat-bar.xp-hole { box-shadow: inset 0 0 0 1px rgba(190, 70, 60, 0.55); }
.stat-bar-fill.xp-hole {
  margin-left: auto;
  background-color: rgba(150, 55, 48, 0.55);
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, 0.16) 0 5px,
    rgba(255, 255, 255, 0) 5px 10px
  );
}

.xp-bar-row { width: 100%; margin-top: 2px; }
.xp-bar-row .stat-bar { margin: 0; height: 12px; }
.xp-bar-row .stat-bar-label { font-size: 0.62rem; }

.stat-bar-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  color: var(--text-primary);
  text-shadow: 0 1px 2px #000;
}

button, .btn {
  display: inline-block;
  background: linear-gradient(180deg, #2a2430, #18141b);
  color: var(--accent-gold);
  border: 1px solid var(--accent-gold);
  border-radius: 4px;
  padding: 10px 14px;
  font-family: inherit;
  font-size: 0.95rem;
  cursor: pointer;
  width: 100%;
  margin-top: 6px;
}

button:active { transform: translateY(1px); }

.btn-sm {
  width: auto;
  padding: 3px 10px;
  font-size: 0.75rem;
  margin-top: 0;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-danger {
  color: #ff6b6b;
  border-color: #ff6b6b;
}

input, select, textarea {
  width: 100%;
  padding: 10px;
  margin-top: 6px;
  background: #0d0b0e;
  border: 1px solid var(--text-muted);
  border-radius: 4px;
  color: var(--text-primary);
  font-family: inherit;
}

.list-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid #241f28;
  flex-wrap: wrap;
  gap: 4px;
}

.list-item:last-child { border-bottom: none; }

.list-item > div, .list-item > span, .list-item > strong {
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* SEVEN EQUAL SLOTS.
   As flex the slots sized to their labels, so "Battlefield" took almost twice
   the width of "PvP" and a seventh button could not fit without wrapping every
   label. A 7-column grid makes each slot exactly 1/7 regardless of its text,
   which is what made the Estate button possible. */
nav.bottom-nav {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  border-top: 2px solid var(--accent-gold);
  background: #100d12;
  max-width: 480px;
  margin: 0 auto;
  width: 100%;
}

nav.bottom-nav button {
  border: none;
  border-radius: 0;
  background: transparent;
  padding: 6px 1px;
  min-height: 60px;
  /* min-width:0 is what actually lets a grid item shrink below its content;
     without it the label still forces the column wider and the bar overflows. */
  min-width: 0;
  margin: 0;
}

/* The label is a caption under the sprite, not the thing doing the work — so
   it can be small, and it ellipsises rather than wrapping to a second line and
   pushing the bar taller for everyone. */
nav.bottom-nav .nav-label {
  display: block;
  font-size: 0.58rem;
  line-height: 1.1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

nav.bottom-nav button.active { color: #fff; background: #201a24; }

nav.bottom-nav .nav-icon { display: block; width: 32px; height: 32px; margin: 0 auto 2px; }

/* ==========================================================================
   BOSS PRESENTATION
   The boss is the centrepiece of a raid/event page, not a row glyph, so the
   portrait is staged: authored sprite at 128px (2x native, an integer scale)
   standing on a radial ember plate that grounds it without needing an image
   asset. Previously this was a 40px icon baseline-aligned inside the name's
   flex row, which read as bolted on beside the heading.
   ========================================================================== */
.boss-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 2px 0 12px;
}
/* Compound `.pixel-icon.x`, NOT a bare `.x`. PixelArt.renderIcon() adds the
   pixel-icon class to every element it draws into, and the base .pixel-icon
   rule (32px) is defined further down this file — at equal specificity the
   later rule wins, so a bare .boss-portrait silently rendered at 32px. This
   bit three separate size fixes before it was spotted. */
.pixel-icon.boss-portrait {
  width: 192px;
  height: 192px;
  background: radial-gradient(ellipse 58% 44% at 50% 78%,
              rgba(224, 121, 60, 0.30), rgba(224, 121, 60, 0.07) 58%, transparent 76%);
  filter: drop-shadow(0 5px 9px rgba(0, 0, 0, 0.6));
}
/* 192 and 128 are both integer multiples of the 64px mask, so the sprite stays
   pixel-exact at either size. Narrow phones take the smaller one. */
@media (max-width: 380px) {
  .pixel-icon.boss-portrait { width: 128px; height: 128px; }
}
.boss-stage .raid-boss-head { justify-content: center; margin: 0; }
.boss-stage-name {
  font-size: 1.05rem;
  font-weight: 600;
  color: #f0e6d2;
  text-align: center;
  line-height: 1.25;
}
/* The event hero already carries its own ember wash, so the plate is dialled
   back there to avoid doubling up into a muddy orange blob. */
.boss-stage-event .pixel-icon.boss-portrait {
  background: radial-gradient(ellipse 58% 44% at 50% 78%,
              rgba(224, 121, 60, 0.18), transparent 68%);
}

/* Battlefield raid list: portrait beside the name + HP bar, so a player can
   tell the bosses apart at a glance instead of reading every heading. */
.raid-card-top { display: flex; gap: 12px; align-items: center; }
.pixel-icon.raid-card-portrait { width: 128px; height: 128px; flex: 0 0 auto; }
@media (max-width: 380px) {
  .pixel-icon.raid-card-portrait { width: 64px; height: 64px; }
}
.raid-card-titles { flex: 1 1 auto; min-width: 0; }

.error-banner {
  background: #3a0d10;
  border: 1px solid var(--health-to);
  color: #ffd9dc;
  padding: 8px 10px;
  border-radius: 4px;
  margin-bottom: 10px;
  font-size: 0.85rem;
}

/* Toast/notification system — fixed position, out of document flow, so showing
   a toast never pushes page content down (unlike the old .error-banner which
   was prepended into #main). Stacks newest-on-top; each toast dismisses on its
   own timer independently. */
#toast-container {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 84px;
  z-index: 40;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  align-items: center;
  padding: 0 14px;
  pointer-events: none;
  max-width: 480px;
  margin: 0 auto;
}

.toast {
  pointer-events: auto;
  width: 100%;
  max-width: 452px;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 0.85rem;
  box-shadow: 0 4px 16px rgba(0,0,0,0.5);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.toast-visible { opacity: 1; transform: translateY(0); }
.toast-leaving { opacity: 0; transform: translateY(-6px); }

.toast-info {
  background: #1a2e1c;
  border: 1px solid var(--accent-gold);
  color: var(--text-primary);
}

.toast-error {
  background: #3a0d10;
  border: 1px solid var(--health-to);
  color: #ffd9dc;
}

/* Confirmation modal — used in front of VP-spending actions instead of native confirm() */
.confirm-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(0,0,0,0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.confirm-modal-card {
  flex-direction: column;
  align-items: stretch;
  width: 100%;
  max-width: 380px;
  margin: 0;
}

.gold-text { color: #e6c86e; }
.vp-text { color: #c98dfa; }

@media (min-width: 481px) {
  body { background: #050405; }
  #app { box-shadow: 0 0 40px rgba(0,0,0,0.8); }
}

#update-banner {
  position: sticky;
  top: 0;
  z-index: 20;
  max-width: 480px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  background: linear-gradient(180deg, #2a2430, var(--accent-gold));
  background: var(--accent-gold);
  color: #1a1620;
  font-size: 0.85rem;
  font-weight: bold;
}

#chat-fab, #notif-fab {
  position: fixed;
  right: 16px;
  bottom: 76px;
  z-index: 25;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--accent-gold);
  color: #1a1620;
  font-size: 1.4rem;
  box-shadow: 0 2px 10px rgba(0,0,0,0.6);
  border: none;
  margin: 0;
}

#notif-fab {
  right: 76px;
}

.notif-panel {
  max-width: 380px;
}

.notif-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border: none;
  border-bottom: 1px solid #2a2430;
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.notif-item:last-child { border-bottom: none; }
.notif-item.notif-unread { background: rgba(240, 200, 80, 0.08); }
.notif-item .notif-message { font-size: 0.88rem; }
.notif-item .notif-time { font-size: 0.7rem; color: var(--parchment-dim, #8a8390); margin-top: 3px; }

.chat-unread-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  background: var(--health-to, #c0392b);
  color: #fff;
  font-size: 0.65rem;
  border-radius: 50%;
  min-width: 16px;
  height: 16px;
  line-height: 16px;
  padding: 0 3px;
}

.chat-panel {
  position: fixed;
  right: 12px;
  bottom: 132px;
  left: 12px;
  max-width: 456px;
  margin: 0 auto;
  z-index: 25;
  background: #150f18;
  border: 1px solid #2a2430;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  height: 60vh;
  max-height: 520px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.7);
  overflow: hidden;
}

.chat-panel-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 8px 0 8px;
}

.chat-tabs { flex: 1; }

.chat-tab-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--health-to, #c0392b);
  margin-left: 4px;
}

.chat-contact-unread {
  background: rgba(240, 200, 80, 0.08);
}

.chat-unread-count {
  display: inline-block;
  min-width: 16px;
  height: 16px;
  line-height: 16px;
  padding: 0 3px;
  margin-left: 6px;
  border-radius: 50%;
  background: var(--health-to, #c0392b);
  color: #fff;
  font-size: 0.65rem;
  text-align: center;
}

.chat-presence-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #5a5560;
  margin-right: 6px;
}

.chat-presence-dot.online {
  background: #4caf50;
}

.chat-panel-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 8px;
  overflow: hidden;
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}

.chat-message { font-size: 0.82rem; display: flex; gap: 6px; align-items: flex-start; }
.chat-message .pixel-icon { flex-shrink: 0; }
.chat-message-text { word-break: break-word; }
.chat-note { font-size: 0.72rem; color: var(--text-muted, #8a8290); margin-bottom: 6px; }

.chat-input-row { display: flex; gap: 6px; }
.chat-input-row input { flex: 1; margin: 0; }
.chat-input-row button { width: auto; margin: 0; }

.chat-online-list .list-item { cursor: pointer; }
.chat-status { font-size: 0.75rem; color: var(--text-muted, #8a8290); padding: 4px 0; }


.admin-credit-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin: 6px 0;
}
.admin-credit-row input { margin: 0; }
.admin-credit-row select { margin: 0; width: auto; }
.admin-credit-row input[type="text"] { flex: 1; min-width: 120px; }

/* Balances/spend strip on an admin player row. Wraps rather than scrolls —
   it is six or seven short chips, not tabular data. */
.admin-balance-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin: 6px 0;
  font-size: 0.75rem;
}

/* Key/value blocks on the Player Detail tab. */
.admin-kv {
  display: grid;
  grid-template-columns: minmax(120px, 40%) 1fr;
  gap: 4px 10px;
  font-size: 0.8rem;
  align-items: baseline;
}
.admin-kv-key { color: var(--text-muted, #8a8290); }

/* Economy tables. Wide content scrolls inside its own container so the page
   body never scrolls horizontally on a phone. */
.admin-table-scroll { overflow-x: auto; }
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.75rem;
  white-space: nowrap;
}
.admin-table th,
.admin-table td { padding: 5px 8px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); text-align: left; }
.admin-table th { color: var(--text-muted, #8a8290); font-weight: 600; }
.admin-table td.num,
.admin-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-table tfoot td { border-top: 1px solid rgba(255, 255, 255, 0.2); border-bottom: none; }

/* Audit rows: the JSON payload is the long part, so let it wrap and shrink
   rather than pushing the timestamp off screen. */
.audit-row { align-items: flex-start; gap: 10px; }
.audit-payload {
  font-family: monospace;
  font-size: 0.68rem;
  word-break: break-all;
  margin-top: 2px;
}

/* Used for unattributed-gem and suspension figures — a number that should be
   zero and is not. */
.error-text { color: #e06c75; }

#update-banner button {
  width: auto;
  margin: 0;
  padding: 6px 12px;
  font-size: 0.8rem;
  background: #1a1620;
  color: var(--accent-gold);
  border: none;
}

/* Fixed stat header */
header.app-header {
  position: sticky;
  top: 0;
  z-index: 5;
  flex-wrap: wrap;
  gap: 8px 10px;
}

header.app-header.stat-mode {
  align-items: center;
}

.stat-chip-row {
  display: flex;
  align-items: center;
  gap: 8px 10px;
  flex-wrap: wrap;
  width: 100%;
}

.stat-chip {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  background: #100d12;
  border: 1px solid #2a2430;
  border-radius: 10px;
  padding: 3px 8px;
  white-space: nowrap;
}

.stat-chip .chip-icon { width: 16px; height: 16px; flex: none; }

.stat-chip-bar-wrap {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 74px;
}

.stat-chip-bar {
  height: 8px;
  width: 100%;
  background: #241f28;
  border: 1px solid #000;
  border-radius: 3px;
  overflow: hidden;
}

.stat-chip-bar-fill { height: 100%; }
.stat-chip-bar-fill.energy { background: linear-gradient(90deg, var(--energy-from), var(--energy-to)); }
.stat-chip-bar-fill.stamina { background: linear-gradient(90deg, var(--stamina-from), var(--stamina-to)); }

/* OVERCHARGED — energy/stamina above max, from stacking potions. The fill is
   already pinned at 100% (see barFillPct in app.js), so a full-but-ordinary bar
   and a 690/200 bar would otherwise look identical. The sheen says "this is a
   bonus", not "this bar is broken". */
.stat-chip-bar-fill.overcharged,
.stat-bar-fill.overcharged {
  background-image: linear-gradient(90deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0) 55%),
    linear-gradient(90deg, var(--energy-from), var(--energy-to));
  box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.65);
  animation: overcharge-pulse 2.4s ease-in-out infinite;
}
.stat-bar-fill.stamina.overcharged,
.stat-chip-bar-fill.stamina.overcharged {
  background-image: linear-gradient(90deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0) 55%),
    linear-gradient(90deg, var(--stamina-from), var(--stamina-to));
}

@keyframes overcharge-pulse {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.25); }
}

.timer-chip {
  font-size: 0.62rem;
  color: var(--text-muted);
}

[data-bounty-timer] { margin-top: 4px; }
[data-bounty-timer] button { font-size: 0.7rem; padding: 4px 10px; }

.header-glyph { display: flex; align-items: center; gap: 6px; }
.header-glyph > div { min-width: 0; }

/* Pixel icon frame */
.pixel-icon {
  width: 32px;
  height: 32px;
  flex: none;
  display: block;
  image-rendering: pixelated;
  shape-rendering: crispEdges;
  /* TRANSPARENT, not a tile.
     This carried `background: #100d12` and a solid black border, which was
     invisible while icons were generated noise - that filled the square
     edge to edge. Authored sprites are a centred object with transparent
     margins, so the tile showed through as a black square behind every item.
     The border stays in the box model as transparent so nothing shifts by 2px. */
  border: 1px solid transparent;
  border-radius: 3px;
  background: transparent;
}

.pixel-icon.large { width: 64px; height: 64px; }
/* Sizes are integer fractions/multiples of the authored 64x64 sprite (16/32/64/
   128). Anything else (40px, 20px, 96px all shipped at one point) scales each
   1-unit rect to a fractional CSS px, and shape-rendering: crispEdges then
   snaps neighbouring pixels to different device-pixel widths — the sprite comes
   out visibly uneven and mushy. */
.pixel-icon.small { width: 16px; height: 16px; border-radius: 2px; }

/* Class picker cards (signup) */
.class-picker-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 6px;
}

.class-card {
  background: var(--bg-card);
  border: 2px solid #2a2430;
  border-radius: 6px;
  padding: 8px 4px;
  text-align: center;
  cursor: pointer;
}

.class-card .pixel-icon { margin: 0 auto 6px; }

.class-card.selected { border-color: var(--accent-gold); box-shadow: 0 0 8px rgba(201,160,80,0.4); }
.class-card .class-name { font-size: 0.8rem; color: var(--text-primary); text-transform: capitalize; }

/* Skill cards */
.skill-card {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 8px 0;
  border-bottom: 1px solid #241f28;
}

.skill-card:last-child { border-bottom: none; }
.skill-card.locked { opacity: 0.45; }
.skill-card .skill-name { color: var(--accent-gold); font-weight: bold; }
.skill-card .skill-desc { font-size: 0.8rem; color: var(--text-muted); }

/* The Shop's pill-fill tab styling lived here and is gone: every tab strip is
   the canonical `.loa-tabs` defined under "THE TAB BAR" below. `.admin-tabs` is
   a modifier on it, not a style of its own — Admin's many tabs used to wrap
   onto extra rows, and now scroll like every other bar. */
.admin-tabs > button { font-size: 0.78rem; padding: 8px 10px; }

.item-row { display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap; padding: 8px 0; }
.item-row .item-meta { flex: 1 1 150px; min-width: 130px; }

.qty-input { width: 56px; flex: none; text-align: center; }

/* Groups qty input + cost preview + action buttons so they wrap together as one
   unit under item-meta on narrow screens, instead of each control wrapping alone. */
.item-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  /* Shrinkable (was `flex: none`, which laid out at max-content and refused to
     narrow, so multi-button groups overflowed on 360px phones). */
  flex: 0 1 auto;
  min-width: 0;
  margin-left: auto;
}

.item-actions .qty-input { margin: 0; }

/* Fixed-width, non-wrapping cost text so "Total: 12,345g" never breaks mid-word */
.cost-preview {
  white-space: nowrap;
  font-size: 0.8rem;
  min-width: 76px;
  text-align: right;
}

.shop-category-card { padding: 0; overflow: hidden; }

.shop-category-header {
  width: 100%;
  margin: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px;
  font-family: inherit;
  font-size: 1rem;
  font-weight: bold;
  color: var(--accent-gold, #c9a24b);
  cursor: pointer;
}

.shop-category-chevron { color: var(--text-muted, #8c8577); }

.shop-category-body { padding: 2px 14px 12px; }
.shop-category-body .item-row { border-bottom: 1px solid #241f28; }
.shop-category-body .item-row:last-child { border-bottom: none; }
.shop-category-body .item-row:first-child { border-top: 1px solid rgba(255,255,255,0.06); padding-top: 12px; }

.btn-link { display: inline-flex; align-items: center; padding: 4px 10px; text-decoration: none; color: var(--accent-gold, #c9a24b); font-size: 0.85rem; }

/* Power breakdown rows (Profile → Overview): label + Attack + Defense as 3 columns. */
.power-breakdown-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 4px 12px;
  padding: 4px 0;
  font-size: 0.85rem;
  border-bottom: 1px solid #241f28;
}
.power-breakdown-row:last-of-type { border-bottom: none; }
.power-breakdown-row span:nth-child(2),
.power-breakdown-row span:nth-child(3) {
  text-align: right;
  min-width: 64px;
}

/* Gear tiles (Profile → Inventory/Gear) — icon+name+stats up top, then a row
   of full-text action buttons below. Built to replace bare icon-only
   buttons (relying on a hover `title` tooltip, which doesn't exist on
   touch/mobile — the direct cause of "can't tell what does what" on mobile). */
.gear-tile {
  background: var(--bg-card, #17131b);
  border: 1px solid #2a2430;
  border-radius: 6px;
  padding: 10px;
  margin-bottom: 8px;
}
.gear-tile-header { display: flex; gap: 10px; align-items: flex-start; }
/* Item art at full authored resolution. These are the surfaces the catalogue
   art exists for — shop rows, inventory tiles, auction cards — and 32px threw
   away half of every sprite. 64px is the native mask size, so it is also the
   one scale with no pixel snapping at all. */
.item-row > .pixel-icon,
.gear-tile-header > .pixel-icon,
.pixel-icon.av-card-icon { width: 64px; height: 64px; }
.gear-tile-title { flex: 1 1 auto; min-width: 0; }
.gear-tile-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.gear-tile-actions button {
  width: auto;
  flex: 1 1 auto;
  min-width: 84px;
  font-size: 0.78rem;
  padding: 7px 8px;
  margin-top: 0;
}
.rarity-line { font-size: 0.82rem; margin-top: 2px; }

.recharge-card { margin-top: 10px; }
.recharge-card .qty-input { margin: 0 8px; }

/* Raid boss cards & live feed */
.raid-card { cursor: pointer; }
.raid-card .timer-chip { margin: 2px 0 8px; font-size: 0.72rem; }

.feed-list {
  display: flex;
  flex-direction: column;
  max-height: 320px;
  overflow-y: auto;
}

.feed-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid #241f28;
  font-size: 0.82rem;
}

.feed-row:last-child { border-bottom: none; }
.feed-user { font-weight: bold; color: var(--accent-gold); }
.feed-damage { color: var(--health-to); }
.feed-time { margin-left: auto; font-size: 0.72rem; }

/* Estate / lands overview */
.estate-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 8px;
  text-align: center;
}

.wallet-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  text-align: center;
}

.estate-stat-value {
  font-size: 1.15rem;
  font-weight: bold;
  color: var(--accent-gold);
}

.estate-accrued-value { font-size: 1.4rem; font-weight: bold; margin: 4px 0; }

.upkeep-breakdown { margin-top: 10px; border-top: 1px solid rgba(255,255,255,0.08); padding-top: 6px; }
.upkeep-text { color: #d9605a; }

.inv-category { margin-bottom: 10px; }
.inv-category-title { color: var(--accent-gold); margin: 6px 0 4px; font-size: 0.9rem; }

.land-row {
  display: flex;
  align-items: center;
  gap: 10px 12px;
  flex-wrap: wrap;
  padding: 10px 0;
  border-bottom: 1px solid #241f28;
}

.land-row:last-child { border-bottom: none; }
.land-row.owned { background: rgba(201,160,80,0.06); border-radius: 4px; padding: 10px 6px; margin: 0 -6px; }
.land-row .item-meta { flex: 1 1 150px; min-width: 130px; }
.land-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* Estate price/income facts as visible label -> value pairs. Prices used to
   live only in `title=` tooltips on emoji icon buttons, which do not exist on
   touch — a phone player could not learn what a deed cost without buying it.
   Same class of bug (and same fix idiom) as the gear tiles above. */
.estate-price-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  margin-top: 6px;
  font-size: 0.82rem;
}
.estate-price-grid > span:nth-child(odd) { color: var(--text-muted); }
.estate-price-grid > span:nth-child(even) { text-align: right; overflow-wrap: anywhere; }

.land-row-tile.owned { border-color: var(--accent-gold); background: rgba(201,160,80,0.06); }

/* Highlighted summary cards (accrual / holdings overview) that sit above a
   scrollable list — visually distinct from the plain list card below them */
.estate-summary, .estate-highlight-card {
  background: rgba(201,160,80,0.08);
  border-color: var(--accent-gold);
}

.attr-grid {
  display: grid;
  /* auto-fit rather than a hard 5 columns: 5 fixed columns were borderline at
     360px and looked broken when the grid holds only 2 items (Combat Power). */
  grid-template-columns: repeat(auto-fit, minmax(58px, 1fr));
  gap: 6px;
  text-align: center;
  margin-bottom: 8px;
}

.attr-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: #100d12;
  border: 1px solid #241f28;
  border-radius: 4px;
  padding: 6px 2px;
  font-size: 0.85rem;
}

.attr-alloc-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
}

.dash-stat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-top: 10px;
  text-align: center;
}

.callout-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  border-color: var(--accent-gold);
  background: rgba(201,160,80,0.08);
}

.callout-card button { width: auto; margin-top: 0; }

/* Visually separates distinct sub-sections stacked inside one card (e.g. the
   Attributes card's "Allocate Stat Points" vs "Respec" clusters) so they read
   as separate groups rather than one undifferentiated wall of controls. */
.card-subsection {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid #241f28;
}
.card-subsection h4 {
  margin: 0 0 6px;
  font-size: 0.85rem;
  color: var(--text-muted, #8c8577);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* The specials card stacks its header/slide/nav vertically — override callout-card's
   row layout (which is meant for simple single-line banners like the bounty callout). */
#dashboard-specials-card { flex-direction: column; align-items: stretch; }

/* Dashboard specials carousel slide — fixed-height flex column so switching
   slides (varying name/description/countdown length) never shifts the card's
   height or the vertical position of the Buy button/Max Owned badge. */
.special-slide {
  display: flex;
  flex-direction: column;
  min-height: 108px;
  width: 100%;
}
.special-slide-info { flex: 1 1 auto; }
.special-slide-action {
  margin-top: auto;
  padding-top: 6px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
.special-slide-action button { width: auto; margin-top: 0; }

/* Neutral (non-color-coded) badge-pill variant for inert status labels like "Max Owned" */
.badge-pill-neutral {
  background: rgba(255,255,255,0.12);
  color: var(--text-primary);
}

.home-tile-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 10px;
}

.home-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  aspect-ratio: 1;
  background: var(--bg-card);
  /* A muted border at rest, gold on hover/press. Eleven tiles each outlined in
     full accent gold read as eleven warnings; the accent is worth more when it
     marks the one you are touching. */
  border: 1px solid #2a2430;
  border-radius: 6px;
  cursor: pointer;
  padding: 8px;
  text-align: center;
  transition: background 0.12s ease, border-color 0.12s ease;
}

.home-tile:hover { background: #201a24; border-color: var(--accent-gold); }
.home-tile:active { background: #2a222f; border-color: var(--accent-gold); }

.home-tile .pixel-icon {
  width: 64px;
  height: 64px;
}

.home-tile-label {
  font-size: 0.75rem;
  color: #e2d8c3;
}

.leaderboard-me {
  background: rgba(201,160,80,0.12);
  border-radius: 4px;
}

/* Compact icon buttons (Shop / Estate) — reuse button base look, square + centered glyph */
.icon-btn {
  width: 40px;
  height: 40px;
  flex: none;
  padding: 0;
  margin-top: 0;
  font-size: 1.1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Clickable player name + class icon, used wherever a username is shown */
.player-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}

.player-link-name { font-weight: bold; color: var(--accent-gold); }
.player-link:hover .player-link-name { text-decoration: underline; }

/* Small colored badge dot shown to the right of a player's name (playerLinkHtml 4th arg) */
.badge-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-left: 2px;
  border: 1px solid rgba(255,255,255,0.4);
  flex: none;
  cursor: help;
}

.online-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  vertical-align: middle;
  margin-left: 2px;
  cursor: help;
}
.online-dot.online { background: #3ecf5f; box-shadow: 0 0 4px #3ecf5f; }
.online-dot.offline { background: #6b6b6b; }

/* Full badge pill (name + color), used prominently at top of a player's own profile */
.badge-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 10px;
  border-radius: 12px;
  font-size: 0.78rem;
  font-weight: bold;
  color: #100d12;
  margin-top: 4px;
}

/* Badge picker chips (self-profile only) */
.badge-picker-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}
.badge-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.15);
  background: rgba(255,255,255,0.05);
  font-size: 0.78rem;
  cursor: pointer;
}
.badge-chip.selected { border-color: var(--accent-gold); background: rgba(201,160,80,0.15); }
.badge-chip .badge-dot { cursor: default; }

/* THE TAB BAR — one idiom for every tabbed surface in the app.
   Four competing styles used to coexist: this underline strip (Profile), a
   pill-fill swap (.shop-tabs, 7 call sites), the Auction's bespoke .av-tab and
   an unstyled #auth-tabs. Underline won because it is the only one that stays
   legible while scrolled and does not reflow as you tab through.

   `.loa-tabs` is now the only tab class; all ten call sites emit it through
   LoaUI.tabsHtml(). `.admin-tabs`, `.av-tabs` and `.chat-tabs` survive only as
   modifiers layered on top of it, not as tab styles of their own.

   Horizontal scroll with snap rather than wrapping: with PvP History added
   these no longer fit two tidy rows on a phone, and a strip that reflows as
   you tab through moves the content under your thumb. */
.loa-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 10px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  border-bottom: 1px solid #2a2430;
}
.loa-tabs::-webkit-scrollbar { display: none; }

.loa-tabs > button {
  width: auto;
  flex: 0 0 auto;
  min-width: 0;
  scroll-snap-align: start;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: 9px 12px;
  margin: 0;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-muted);
  transition: color 0.12s ease, border-color 0.12s ease;
}

.loa-tabs > button:hover { color: var(--text-primary); }

/* An indicator plus weight and colour, not just a fill — the active tab has to
   be legible at a glance while scrolled. */
.loa-tabs > button.active {
  color: var(--accent-gold);
  font-weight: 700;
  border-bottom-color: var(--accent-gold);
  background: none;
}

/* Destructive-consequence line inside a confirm modal — the thing the player
   loses if they go ahead, not just a restatement of the action. */
.warn-text { color: #e08a3c; }

/* "Sell on Auction" on a VP/mythic inventory row. A link, not a button, because
   it navigates rather than acting — VP items have no store sell-back at all. */
.inv-auction-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--accent-gold);
  border: 1px solid rgba(201, 160, 80, 0.45);
  border-radius: 4px;
  padding: 5px 8px;
  white-space: nowrap;
}
.inv-auction-link:hover {
  background: rgba(201, 160, 80, 0.12);
}

.heal-link { color: var(--health-to); text-decoration: none; margin-left: 4px; }

/* PvP battle screen */
.battle-vs-grid {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  text-align: center;
}

.battle-side {
  flex: 1;
  min-width: 130px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.battle-side .pixel-icon.large { margin-bottom: 4px; }
.battle-stat { font-size: 0.85rem; }
.battle-vs-label { font-weight: bold; color: var(--accent-gold); font-size: 1.2rem; flex: none; }

.forgot-password-link { display: inline-block; margin-top: 8px; font-size: 0.85rem; color: var(--text-muted); cursor: pointer; text-decoration: underline; }

.health-loss-line { font-size: 0.85rem; margin: 4px 0; }
.health-loss-line strong { color: var(--health-to); }

.reward-secured {
  color: #3ecf6e;
  font-weight: bold;
  font-size: 0.85rem;
  margin: 6px 0 0;
}

.battle-inline-panel {
  margin: -4px 0 10px;
  padding: 10px;
  border: 1px solid var(--border-color, rgba(255,255,255,0.1));
  border-radius: 6px;
}
.battle-inline-panel:empty { display: none !important; padding: 0; border: none; margin: 0; }

.qty-badge {
  background: var(--accent-gold);
  color: #100d12;
  font-size: 0.68rem;
  font-weight: bold;
  border-radius: 8px;
  padding: 1px 6px;
}

/* ============================================================
   Battle Pass — "The Ashen Crown"
   A deliberately distinct visual world (dark war-ledger/campaign-map
   theme) rather than reusing the app's default card styling — scoped
   entirely under .bp-page so nothing here leaks elsewhere.
   ============================================================ */
.bp-page {
  --bp-ink: #15110d;
  --bp-ink-2: #1d1712;
  --bp-panel: #241d16;
  --bp-parchment: #e7dcc3;
  --bp-parchment-dim: #b8ab8c;
  --bp-iron: #4a453a;
  --bp-iron-line: #3a352c;
  --bp-ember: #c1442d;
  --bp-ember-bright: #e2603f;
  --bp-gold-seal: #b8903f;
  --bp-gold-bright: #e0b563;
  --bp-moss: #5c6e49;
  --bp-moss-bright: #7c9463;
  --bp-font-display: 'Cinzel', Georgia, serif;
  --bp-font-body: 'Crimson Pro', Georgia, serif;
  --bp-font-mono: 'Space Mono', 'Courier New', monospace;

  background: var(--bp-ink);
  color: var(--bp-parchment);
  font-family: var(--bp-font-body);
  margin: -12px -14px 0;
  padding-bottom: 40px;
}
.bp-mono { font-family: var(--bp-font-mono); font-variant-numeric: tabular-nums; }
.bp-muted { color: var(--bp-parchment-dim); }
.bp-eyebrow {
  font-family: var(--bp-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bp-gold-bright);
}

.bp-banner {
  position: relative;
  padding: 26px 16px 18px;
  border-bottom: 1px solid var(--bp-iron-line);
  background: radial-gradient(ellipse 80% 100% at 50% 0%, rgba(193,68,45,0.16), transparent 70%);
  overflow: hidden;
}
.bp-banner-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.bp-title {
  font-family: var(--bp-font-display);
  font-weight: 600;
  letter-spacing: 0.03em;
  font-size: clamp(1.5rem, 6vw, 2.1rem);
  color: var(--bp-parchment);
  margin: 4px 0 0;
}
.bp-meter {
  min-width: 220px;
  background: var(--bp-panel);
  border: 1px solid var(--bp-iron-line);
  padding: 12px 14px;
}
.bp-meter-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; font-size: 0.85rem; }
.bp-gauge { height: 8px; background: var(--bp-ink-2); border: 1px solid var(--bp-iron-line); margin: 8px 0 2px; overflow: hidden; }
.bp-gauge > i { display: block; height: 100%; background: linear-gradient(90deg, var(--bp-moss), var(--bp-moss-bright)); }

.bp-legend {
  display: flex;
  gap: 16px;
  align-items: center;
  padding-top: 14px;
  flex-wrap: wrap;
  font-family: var(--bp-font-mono);
  font-size: 0.74rem;
  color: var(--bp-parchment-dim);
}
.bp-legend span { display: inline-flex; align-items: center; gap: 7px; }
.bp-swatch { width: 10px; height: 10px; display: inline-block; border: 1px solid var(--bp-iron-line); }
.bp-swatch-gold { background: var(--bp-gold-seal); border-color: var(--bp-gold-bright); }
.bp-swatch-moss { background: var(--bp-moss); }
.bp-owned { color: var(--bp-moss-bright); font-weight: 600; }

.bp-btn {
  border: 1px solid var(--bp-iron-line);
  background: var(--bp-panel);
  color: var(--bp-parchment);
  font-family: var(--bp-font-mono);
  font-size: 0.78rem;
  padding: 7px 14px;
  cursor: pointer;
  width: auto;
}
.bp-btn-gold { border-color: var(--bp-gold-bright); color: var(--bp-gold-bright); }
.bp-btn-gold:hover { background: rgba(184,144,63,0.14); }

.bp-section { padding: 26px 16px 0; }
.bp-h2 {
  font-family: var(--bp-font-display);
  font-size: 1.2rem;
  color: var(--bp-gold-bright);
  letter-spacing: 0.02em;
  margin: 0 0 12px;
}

.bp-contracts { display: flex; flex-direction: column; gap: 10px; }
.bp-contract {
  border: 1px dashed var(--bp-iron);
  background: var(--bp-panel);
  padding: 12px 14px;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
}
.bp-contract-done { border-style: solid; border-color: var(--bp-moss-bright); }
.bp-contract-info { flex: 1; min-width: 0; }
.bp-contract-name { font-weight: 600; color: var(--bp-parchment); }
.bp-contract-bar { height: 5px; background: var(--bp-ink-2); border: 1px solid var(--bp-iron-line); margin-top: 6px; overflow: hidden; }
.bp-contract-bar > i { display: block; height: 100%; background: var(--bp-ember-bright); }
.bp-contract-pts { font-family: var(--bp-font-mono); color: var(--bp-gold-bright); white-space: nowrap; font-size: 0.82rem; text-align: right; }

.bp-gear-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
.bp-gear-card {
  background: linear-gradient(180deg, var(--bp-panel), var(--bp-ink-2));
  border: 1px solid var(--bp-iron-line);
  padding: 18px;
  position: relative;
  overflow: hidden;
}
.bp-gear-card::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--bp-gold-seal), var(--bp-ember));
}
.bp-gear-tag {
  display: inline-block;
  font-family: var(--bp-font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.08em;
  color: var(--bp-ink);
  background: var(--bp-gold-bright);
  padding: 2px 7px;
  margin-bottom: 10px;
}
.bp-gear-name { font-family: var(--bp-font-display); font-size: 1.05rem; color: var(--bp-parchment); margin: 0; }
.bp-gear-stats { display: flex; gap: 18px; margin: 14px 0; font-family: var(--bp-font-mono); }
.bp-stat b { display: block; font-size: 1.2rem; color: var(--bp-parchment); }
.bp-stat span { font-size: 0.64rem; color: var(--bp-parchment-dim); text-transform: uppercase; letter-spacing: 0.07em; }
.bp-stat-atk b { color: var(--bp-ember-bright); }
.bp-stat-def b { color: #7fb0c9; }
.bp-gear-flavor { font-style: italic; color: var(--bp-parchment-dim); font-size: 0.88rem; border-left: 2px solid var(--bp-iron); padding-left: 10px; margin: 0 0 10px; }
.bp-gear-status { font-family: var(--bp-font-mono); font-size: 0.72rem; color: var(--bp-moss-bright); }

.bp-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 16px 0 14px;
  font-size: 0.8rem;
}

.bp-tier-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px;
}

.bp-tier-card {
  background: var(--bp-panel);
  border: 1px solid var(--bp-iron-line);
  padding: 12px 14px 14px;
}
.bp-tier-card-current { border-color: var(--bp-ember-bright); box-shadow: 0 0 0 1px rgba(226,96,63,0.3) inset; }
.bp-tier-card-milestone { border-color: var(--bp-gold-bright); }

.bp-tier-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--bp-iron-line);
}
.bp-tier-num { font-family: var(--bp-font-display); font-weight: 600; color: var(--bp-parchment); font-size: 0.95rem; }
.bp-tier-milestone-tag {
  font-family: var(--bp-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bp-ink);
  background: var(--bp-gold-bright);
  padding: 2px 6px;
}
.bp-tier-card-head .bp-mono { margin-left: auto; }

.bp-reward-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
}
.bp-reward-row + .bp-reward-row { border-top: 1px dashed var(--bp-iron-line); }
.bp-reward-row-label {
  font-family: var(--bp-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  width: 62px;
  flex-shrink: 0;
}
.bp-reward-row-label-gold { color: var(--bp-gold-bright); }
.bp-reward-row-label-moss { color: var(--bp-moss-bright); }
.bp-reward-icon { border-color: var(--bp-iron-line); }
.bp-reward-row-text { flex: 1; min-width: 0; font-size: 0.88rem; }
.bp-reward-sub { font-family: var(--bp-font-mono); font-size: 0.68rem; color: var(--bp-parchment-dim); margin-top: 2px; }
.bp-reward-state { font-family: var(--bp-font-mono); font-size: 0.68rem; color: var(--bp-parchment-dim); white-space: nowrap; }
.bp-reward-state-done { color: var(--bp-moss-bright); }

/* ============================================================
   Auction House — "Auction Vault"
   A separate, distinct visual identity from the Battle Pass (cool stone +
   arcane teal vault instead of the war-ledger's warm ember/gold), scoped
   entirely under .av-page. Diablo-inspired category-sidebar navigation.
   ============================================================ */
.av-page {
  --av-stone: #0f1113;
  --av-stone-2: #181b1e;
  --av-panel: #1f2327;
  --av-line: #33383d;
  --av-fog: #8b96a1;
  --av-fog-dim: #5c656e;
  --av-arcane: #3ecbc0;
  --av-arcane-bright: #6ee6da;
  --av-gold: #d9b25b;
  --av-common: #9aa1a8;
  --av-uncommon: #5fbf6a;
  --av-rare: #4f8fe0;
  --av-epic: #a45fe0;
  --av-legendary: #e0a13f;
  --av-font-display: 'Metamorphous', Georgia, serif;
  --av-font-body: 'Public Sans', system-ui, sans-serif;
  --av-font-mono: 'IBM Plex Mono', 'Courier New', monospace;

  background: var(--av-stone);
  color: var(--av-fog);
  font-family: var(--av-font-body);
  margin: -12px -14px 0;
  padding: 0 0 30px;
}
.av-mono { font-family: var(--av-font-mono); font-variant-numeric: tabular-nums; }
.av-muted { color: var(--av-fog-dim); }
.av-eyebrow {
  font-family: var(--av-font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--av-arcane-bright);
}

.av-topbar {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 14px;
  flex-wrap: wrap;
  padding: 22px 16px 14px;
  border-bottom: 1px solid var(--av-line);
  background: radial-gradient(ellipse 70% 100% at 20% 0%, rgba(62,203,192,0.10), transparent 65%);
}
.av-title {
  font-family: var(--av-font-display);
  font-size: clamp(1.4rem, 5vw, 1.9rem);
  color: #eef2f3;
  margin: 2px 0 0;
  font-weight: 400;
}
/* The Auction's bespoke .av-tab pills are gone; `.av-tabs` is an alias of the
   canonical `.loa-tabs` strip, and its buttons use `.active` like every other
   tab bar. The arcane accent survives as the active underline colour below. */
.av-tabs > button.active {
  color: var(--av-arcane-bright);
  border-bottom-color: var(--av-arcane);
}

.av-layout { display: flex; gap: 14px; padding: 14px 16px 0; align-items: flex-start; }
.av-sidebar {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 168px;
  flex-shrink: 0;
  background: var(--av-panel);
  border: 1px solid var(--av-line);
  padding: 6px;
}
.av-slot {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  background: transparent;
  border: 1px solid transparent;
  color: var(--av-fog);
  padding: 7px 8px;
  cursor: pointer;
  text-align: left;
  font-size: 0.82rem;
}
.av-slot:hover { background: rgba(62,203,192,0.06); }
.av-slot-active { border-color: var(--av-arcane); background: rgba(62,203,192,0.1); color: #eef2f3; }
.av-slot-icon { flex-shrink: 0; }
.av-slot-icon-all {
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--av-line);
  border-radius: 3px;
  background: var(--av-stone-2);
  color: var(--av-gold);
}
.av-slot-count { font-family: var(--av-font-mono); font-size: 0.68rem; color: var(--av-fog-dim); }

.av-main { flex: 1; min-width: 0; }
.av-main-head { margin-bottom: 10px; font-size: 0.86rem; }

.av-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
}
.av-empty { padding: 30px 0; text-align: center; }

.av-card {
  background: var(--av-panel);
  border: 1px solid var(--av-line);
  border-left: 3px solid var(--av-line);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-family: var(--av-font-body);
  color: var(--av-fog);
  text-align: left;
  cursor: default;
}
.av-card-pick { cursor: pointer; width: 100%; }
.av-card-pick:hover { border-color: var(--av-arcane); }
.av-card-selected { border-color: var(--av-arcane); box-shadow: 0 0 0 1px var(--av-arcane) inset; }

.av-card-top { display: flex; gap: 10px; align-items: flex-start; }
.av-card-icon { flex-shrink: 0; }
.av-card-info { min-width: 0; }
.av-card-name { font-weight: 600; color: #eef2f3; font-size: 0.9rem; line-height: 1.3; }
.av-card-rarity { font-family: var(--av-font-mono); font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 2px; }
.av-card-stats { font-family: var(--av-font-mono); font-size: 0.74rem; color: var(--av-fog-dim); margin-top: 4px; }

.av-card-foot { display: flex; justify-content: space-between; align-items: center; margin-top: auto; padding-top: 8px; border-top: 1px dashed var(--av-line); }
.av-card-price { font-family: var(--av-font-mono); font-weight: 600; color: var(--av-gold); font-size: 0.92rem; }

.av-btn {
  font-family: var(--av-font-mono);
  font-size: 0.76rem;
  background: var(--av-stone-2);
  color: var(--av-fog);
  border: 1px solid var(--av-line);
  padding: 6px 12px;
  cursor: pointer;
  width: auto;
}
.av-btn:disabled { opacity: 0.4; cursor: default; }
.av-btn-buy { border-color: var(--av-arcane); color: var(--av-arcane-bright); }
.av-btn-buy:hover { background: rgba(62,203,192,0.12); }

.av-pagination { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 16px; }

.av-h3 { font-family: var(--av-font-display); font-weight: 400; font-size: 1.1rem; color: #eef2f3; margin: 4px 0 12px; }
.av-sell-form {
  margin-top: 16px;
  background: var(--av-panel);
  border: 1px solid var(--av-arcane);
  padding: 16px;
  max-width: 360px;
}
.av-sell-form-row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.av-sell-form-row label { font-size: 0.74rem; letter-spacing: 0.03em; text-transform: uppercase; font-family: var(--av-font-mono); }
/* Replaces the removed gold/VP currency picker — the auction house is
   gold-only, so this states the fact rather than offering a one-item choice. */
.av-currency-fixed { font-size: 0.9rem; padding: 6px 0; opacity: 0.75; }
.av-sell-form input, .av-sell-form select {
  background: var(--av-stone-2);
  border: 1px solid var(--av-line);
  color: #eef2f3;
  padding: 7px 9px;
  font-family: var(--av-font-body);
}

/* Rarity accents — classic ARPG color convention, adapted to this dark palette. */
.av-rarity-common { border-left-color: var(--av-common); }
.av-rarity-uncommon { border-left-color: var(--av-uncommon); }
.av-rarity-rare { border-left-color: var(--av-rare); }
.av-rarity-epic { border-left-color: var(--av-epic); }
.av-rarity-legendary { border-left-color: var(--av-legendary); box-shadow: 0 0 12px rgba(224,161,63,0.15); }
.av-rarity-text-common { color: var(--av-common); }
.av-rarity-text-uncommon { color: var(--av-uncommon); }
.av-rarity-text-rare { color: var(--av-rare); }
.av-rarity-text-epic { color: var(--av-epic); }
.av-rarity-text-legendary { color: var(--av-legendary); }

@media (max-width: 640px) {
  .av-layout { flex-direction: column; }
  .av-sidebar { width: 100%; flex-direction: row; overflow-x: auto; gap: 6px; }
  .av-slot { flex-direction: column; gap: 4px; width: auto; flex-shrink: 0; text-align: center; padding: 8px 10px; }
  .av-slot-label { font-size: 0.68rem; white-space: nowrap; }
}

/* ============================================================
   Clan War — "War Room"
   A third distinct visual identity (martial/steel-and-blood), scoped under
   .cw-page. Layout is deliberately STABLE across refreshes — participant
   order never changes, cards don't reflow their surroundings — since the
   whole point of this rework was to stop the page from jumping around
   during an active war.
   ============================================================ */
.cw-page {
  --cw-iron: #1c1f22;
  --cw-iron-2: #262a2e;
  --cw-line: #383e43;
  --cw-fog: #b7bec4;
  --cw-fog-dim: #7a828a;
  --cw-blood: #b0293f;
  --cw-blood-bright: #e0435c;
  --cw-loyal: #3f7fb0;
  --cw-loyal-bright: #63a6d9;
  --cw-gold: #c9a227;
  --cw-hp-high: #4caf50;
  --cw-hp-mid: #d9a63e;
  --cw-hp-low: #c0392b;
  --cw-font-display: 'Oswald', 'Arial Narrow', sans-serif;
  --cw-font-body: 'Public Sans', system-ui, sans-serif;
  --cw-font-mono: 'IBM Plex Mono', 'Courier New', monospace;

  display: block;
  color: var(--cw-fog);
  font-family: var(--cw-font-body);
}
.cw-mono { font-family: var(--cw-font-mono); font-variant-numeric: tabular-nums; }
.cw-muted { color: var(--cw-fog-dim); font-size: 0.86rem; }

.cw-card {
  background: var(--cw-iron);
  border: 1px solid var(--cw-line);
  padding: 14px 16px;
}
.cw-h3 {
  font-family: var(--cw-font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: #f2f4f5;
  font-size: 1.1rem;
  margin: 0 0 8px;
}
.cw-h4 {
  font-family: var(--cw-font-display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  margin: 16px 0 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--cw-line);
}
.cw-h4-enemy { color: var(--cw-blood-bright); }
.cw-h4-mine { color: var(--cw-loyal-bright); }

.cw-war-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.cw-countdown-chip {
  background: var(--cw-iron-2);
  border: 1px solid var(--cw-line);
  padding: 4px 10px;
  font-size: 0.82rem;
  color: var(--cw-gold);
  white-space: nowrap;
}

.cw-tugbar {
  position: relative;
  height: 14px;
  background: linear-gradient(90deg, var(--cw-loyal), var(--cw-iron-2) 50%, var(--cw-blood));
  border: 1px solid var(--cw-line);
  overflow: hidden;
  margin-bottom: 4px;
}
.cw-tugbar-fill {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  background: var(--cw-loyal-bright);
  mix-blend-mode: screen;
  transition: width 0.4s ease;
}
.cw-tugbar-mid {
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: 2px;
  background: rgba(255,255,255,0.5);
  transform: translateX(-1px);
}
.cw-tugbar-labels {
  display: flex;
  justify-content: space-between;
  font-family: var(--cw-font-mono);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--cw-fog-dim);
  margin-bottom: 12px;
}

.cw-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
.cw-btn {
  font-family: var(--cw-font-mono);
  font-size: 0.78rem;
  background: var(--cw-iron-2);
  color: var(--cw-fog);
  border: 1px solid var(--cw-line);
  padding: 7px 14px;
  cursor: pointer;
  width: auto;
}
.cw-btn:disabled { opacity: 0.4; cursor: default; }
.cw-btn-attack { border-color: var(--cw-blood); color: var(--cw-blood-bright); }
.cw-btn-attack:hover:not(:disabled) { background: rgba(176,41,63,0.14); }

.cw-roster { display: flex; flex-direction: column; gap: 6px; }
.cw-row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "name action" "bar action";
  align-items: center;
  gap: 2px 12px;
  background: var(--cw-iron-2);
  border: 1px solid var(--cw-line);
  padding: 8px 10px;
}
.cw-row-downed { opacity: 0.55; }
.cw-row-name { grid-area: name; font-size: 0.88rem; color: #eef1f2; }
.cw-row-action { grid-area: action; align-self: center; }
.cw-tag {
  font-family: var(--cw-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.05em;
  padding: 1px 6px;
  margin-left: 6px;
}
.cw-tag-downed { background: var(--cw-blood); color: #fff; }
.cw-tag-guard { background: var(--cw-gold); color: #221c05; }

.cw-hp-bar {
  grid-area: bar;
  position: relative;
  height: 14px;
  background: var(--cw-iron);
  border: 1px solid var(--cw-line);
  overflow: hidden;
  min-width: 120px;
}
.cw-hp-fill { position: absolute; inset: 0; width: 0; transition: width 0.4s ease; }
.cw-hp-high { background: var(--cw-hp-high); }
.cw-hp-mid { background: var(--cw-hp-mid); }
.cw-hp-low { background: var(--cw-hp-low); }
.cw-hp-label {
  position: relative;
  z-index: 1;
  font-family: var(--cw-font-mono);
  font-size: 0.68rem;
  text-align: center;
  line-height: 14px;
  color: #fff;
  text-shadow: 0 1px 1px rgba(0,0,0,0.7);
}

@media (max-width: 480px) {
  .cw-row { grid-template-columns: 1fr; grid-template-areas: "name" "bar" "action"; }
  .cw-row-action { justify-self: start; margin-top: 4px; }
}

/* ============================================================
   7-Day Login Rewards — "First Light"
   A fourth distinct visual identity (warm sunrise, for a new player's
   first week) scoped under .fl-page. Each day card carries a small "sun
   disc" whose lit fraction encodes real progress (day/7), not decoration.
   ============================================================ */
.fl-page {
  --fl-ink: #1b1420;
  --fl-ink-2: #2c2035;
  --fl-panel: #322639;
  --fl-line: #453653;
  --fl-cream: #f3e4d0;
  --fl-cream-dim: #b9a68f;
  --fl-amber: #e8934a;
  --fl-amber-bright: #ffb066;
  --fl-rose: #e2607a;
  --fl-font-display: 'Fraunces', Georgia, serif;
  --fl-font-body: 'Nunito Sans', system-ui, sans-serif;
  --fl-font-mono: 'IBM Plex Mono', 'Courier New', monospace;

  color: var(--fl-cream);
  font-family: var(--fl-font-body);
  margin: -12px -14px 0;
  padding-bottom: 30px;
}
.fl-muted { color: var(--fl-cream-dim); }
.fl-eyebrow {
  font-family: var(--fl-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fl-amber-bright);
}

.fl-banner {
  padding: 24px 16px 20px;
  border-bottom: 1px solid var(--fl-line);
  background: radial-gradient(ellipse 90% 100% at 50% 0%, rgba(232,147,74,0.16), transparent 70%);
}
.fl-title {
  font-family: var(--fl-font-display);
  font-weight: 600;
  font-size: clamp(1.6rem, 6vw, 2.2rem);
  color: var(--fl-cream);
  margin: 4px 0 8px;
}
.fl-countdown-chip {
  display: inline-block;
  margin-top: 10px;
  background: var(--fl-panel);
  border: 1px solid var(--fl-line);
  padding: 5px 12px;
  font-family: var(--fl-font-mono);
  font-size: 0.8rem;
  color: var(--fl-amber-bright);
}

.fl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  padding: 18px 16px 0;
}

.fl-card {
  background: var(--fl-panel);
  border: 1px solid var(--fl-line);
  padding: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
}
.fl-card-next { border-color: var(--fl-amber); box-shadow: 0 0 0 1px rgba(232,147,74,0.3) inset; }
.fl-card-claimed { opacity: 0.75; }

.fl-sun {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--fl-line);
}

.fl-card-day {
  font-family: var(--fl-font-display);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--fl-cream);
}
.fl-card-reward {
  font-size: 0.76rem;
  color: var(--fl-cream-dim);
  line-height: 1.5;
}
.fl-card-state { margin-top: 2px; }

.fl-tag {
  font-family: var(--fl-font-mono);
  font-size: 0.68rem;
  color: var(--fl-cream-dim);
}
.fl-tag-claimed { color: var(--fl-amber-bright); }

.fl-btn {
  font-family: var(--fl-font-mono);
  font-size: 0.76rem;
  background: var(--fl-ink-2);
  color: var(--fl-cream);
  border: 1px solid var(--fl-line);
  padding: 6px 14px;
  cursor: pointer;
  width: auto;
}
.fl-btn-claim { border-color: var(--fl-amber); color: var(--fl-amber-bright); }
.fl-btn-claim:hover { background: rgba(232,147,74,0.14); }

/* ===========================================================================
   Gem economy — Gem Vault + Cosmetics (BALANCE_SPEC.md §5)
   Gems are the purchased currency, so they get their own violet accent to read
   as distinct from gold (currency) and the gold accent (VP / premium).
   =========================================================================== */
:root {
  --gem-from: #6a0dad;
  --gem-to: #c98dfa;
  --gem-bright: #d9b3ff;
}

.stat-chip.gem-chip span { color: var(--gem-bright); }

.gem-notice-card {
  align-items: flex-start;
  border-color: var(--gem-to);
  background: rgba(157,78,221,0.10);
}
.gem-notice-card strong { color: var(--gem-bright); }

.gem-pack-glyph {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  border: 1px solid var(--gem-to);
  border-radius: 3px;
  background: linear-gradient(180deg, rgba(106,13,173,0.35), rgba(24,20,27,0.9));
}

/* A player-chosen title, shown beside the username everywhere players see each
   other. Always rendered through escapeHtmlText. */
.cosmetic-title {
  color: var(--gem-bright);
  font-size: 0.85em;
  font-style: italic;
  /* No `white-space: nowrap` here: this span is spliced beside the username in
     profile headers, leaderboards, wall comments, chat and clan rosters, and a
     nowrap descendant cannot be broken by an ancestor's overflow-wrap — it
     broke the layout for exactly the players who bought a title. */
  overflow-wrap: anywhere;
}

.cosmetic-row-equipped { border-left: 2px solid var(--accent-gold); padding-left: 6px; }

.cosmetic-title-preview {
  /* Shrinkable: was flex-shrink:0 at max-content width and overflowed. */
  flex: 1 1 140px;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--gem-bright);
  font-style: italic;
  font-size: 0.9rem;
}

.cosmetic-preview-card { text-align: center; }
.cosmetic-preview-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 0;
}
.cosmetic-preview-stage .pixel-icon.large { width: 128px; height: 128px; }
.cosmetic-preview-name { color: var(--text-primary); font-size: 0.95rem; }

.bp-btn-gem { border-color: var(--gem-to); color: var(--gem-bright); }
.bp-btn-gem:hover { background: rgba(157,78,221,0.16); }

/* .pixel-icon sets shape-rendering: crispEdges, which inherits into the SVG and
   would otherwise jag the cosmetic aura/frame curves. A directly-matched rule
   beats an inherited one, so these layers opt back out. */
.pixel-icon .loa-smooth,
.pixel-icon .loa-smooth * {
  shape-rendering: geometricPrecision;
  image-rendering: auto;
}

/* ---------------------------------------------------------------------------
   Purchased entitlements — loadout presets, account settings, auction waiver
   (BALANCE_SPEC.md §5). Same violet accent as the rest of the gem economy.
   --------------------------------------------------------------------------- */

/* A preset row pairs a growing name/summary column with its action cluster.
   The name input must not stretch the row taller than a plain preset row. */
.loadout-preset-row { align-items: center; gap: 8px; }
.loadout-preset-name-input {
  margin-top: 0;
  flex: 1 1 140px;
  min-width: 0;
  padding: 8px;
  font-size: 0.85rem;
}

/* Appearance picker: one tile per avatar class, portrait above the label. */
.appearance-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 10px;
}
.appearance-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 6px;
  border: 1px solid #241f28;
  border-radius: 4px;
  text-align: center;
  font-size: 0.85rem;
}
.appearance-option .pixel-icon.large { width: 64px; height: 64px; }
.appearance-option button { margin-top: 0; }
.appearance-option-current {
  border-color: var(--gem-to);
  background: rgba(157,78,221,0.10);
}

/* Auction fee-waiver strip. Sits under the Auction Vault topbar on every tab,
   so "am I being charged to list?" is answered before the seller commits. */
.av-waiver {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 10px 12px;
  border: 1px solid var(--av-line);
  background: var(--av-stone-2);
}
.av-waiver strong { color: var(--av-fog); }
.av-waiver .av-muted { margin-top: 2px; }
.av-waiver-active {
  border-color: var(--gem-to);
  background: rgba(157,78,221,0.10);
}
.av-waiver-active strong { color: var(--gem-bright); }

/* ==========================================================================
   BATTLEFIELD / RAID / BOUNTY  —  added 2026-09-08
   Mobile-first: every rule below is written for a phone and only widens at
   481px+. Raid bosses now damage the attacking player, so the raid page has to
   show two health bars and a heal action without pushing the boss off screen.
   ========================================================================== */

/* --- shared bars -------------------------------------------------------- */
/* A dedicated bar rather than reusing .stat-bar: this one carries a label ROW
   above it (name on the left, numbers on the right) instead of text overlaid on
   the fill, because overlaid text on a nearly-empty bar is unreadable — and a
   boss at 2% HP is exactly when you most want to read it. */
.raid-bar-block { margin: 10px 0; position: relative; }
.raid-bar-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
  font-size: 0.78rem;
}
.raid-bar-name { color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.raid-bar-num { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.raid-bar-num b { color: var(--accent-gold); }
.raid-bar {
  position: relative;
  height: 14px;
  border-radius: 7px;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(201, 160, 80, 0.25);
  overflow: hidden;
}
.raid-bar-fill { height: 100%; transition: width 0.25s ease; }
.raid-bar-fill.boss { background: linear-gradient(90deg, var(--health-from), var(--health-to)); }
.raid-bar-fill.mine { background: linear-gradient(90deg, #2a9d8f, #52b788); }
/* Your own bar turns red below the attack floor — the same colour language the
   boss bar uses, so "red means in trouble" reads the same way on both. */
.raid-bar-fill.mine.danger { background: linear-gradient(90deg, var(--health-from), var(--health-to)); }

/* A tick marking the min-health-to-attack floor, so the lockout is something
   you can SEE coming rather than discover by being refused. */
.raid-hp-floor {
  position: absolute;
  bottom: 0;
  width: 2px;
  height: 14px;
  background: var(--accent-gold);
  opacity: 0.85;
  pointer-events: none;
}

/* --- raid arena (detail page) ------------------------------------------ */
.raid-arena { position: relative; }
.raid-back { margin-bottom: 10px; }
.raid-boss-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 4px;
}
.raid-boss-head h2 { margin: 0; font-size: 1.25rem; line-height: 1.2; }
.raid-lvl {
  font-size: 0.72rem;
  padding: 2px 7px;
  border-radius: 10px;
  border: 1px solid var(--accent-gold);
  color: var(--accent-gold);
  white-space: nowrap;
}
.raid-meta-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0 4px;
  font-size: 0.8rem;
}
.raid-yours { color: var(--text-muted); }
.raid-yours b { color: var(--text-primary); font-variant-numeric: tabular-nums; }

.raid-weak-note {
  margin: 10px 0 0;
  padding: 8px 12px;
  border-radius: 6px;
  border: 1px solid var(--health-to);
  background: rgba(230, 57, 70, 0.12);
  color: var(--text-primary);
  font-size: 0.85rem;
}

/* Two big thumb targets side by side. 48px min height is the accessible tap
   minimum, and Attack stays wider than Heal because it is the action you take
   twenty times per boss and Heal is the one you take twice. */
.raid-actions {
  display: flex;
  gap: 10px;
  margin-top: 12px;
}
.raid-actions button {
  flex: 2 1 0;
  min-height: 48px;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 8px 10px;
  width: auto;
}
.raid-actions .raid-heal { flex: 1 1 0; }
.raid-attack-label { font-size: 0.95rem; font-weight: bold; line-height: 1.1; }
.raid-attack-sub {
  font-size: 0.65rem;
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
/* Kept clickable when too weak on purpose — the server's 400 explains WHY,
   which is more useful than a dead button with no explanation. Just dimmed. */
.raid-attack.is-disabled { opacity: 0.55; }
.raid-heal.urgent {
  border-color: var(--health-to);
  box-shadow: 0 0 10px rgba(230, 57, 70, 0.35);
}

/* --- raid cards (battlefield list) ------------------------------------- */
.raid-card.tappable { cursor: pointer; }
.raid-card.tappable:active { transform: scale(0.995); }
.raid-card-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}
.raid-card-head h3 { margin: 0; font-size: 1rem; }
.raid-card-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.raid-card-go { color: var(--accent-gold); font-size: 0.82rem; white-space: nowrap; }

/* --- bounty board ------------------------------------------------------ */
/* Replaces a four-child flex `.list-item` that wrapped into an unreadable
   stack on a phone. Two columns instead: identity on the left, the two things
   you actually decide on (reward, whether you can hit it yet) on the right. */
.bounty-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.14);
}
.bounty-row:last-of-type { border-bottom: none; }
.bounty-main { min-width: 0; flex: 1 1 auto; }
.bounty-target {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 0.95rem;
}
.bounty-lvl {
  font-size: 0.68rem;
  padding: 1px 6px;
  border-radius: 9px;
  border: 1px solid rgba(201, 160, 80, 0.5);
  color: var(--text-muted);
  white-space: nowrap;
}
.bounty-sub {
  margin-top: 3px;
  font-size: 0.75rem;
  color: var(--text-muted);
  /* The poster line is the least important thing here, so it truncates rather
     than pushing the reward off screen on a narrow phone. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bounty-dot { opacity: 0.45; margin: 0 2px; }
.bounty-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 5px;
  flex: 0 0 auto;
}
.bounty-reward {
  color: var(--accent-gold);
  font-weight: bold;
  font-size: 1.05rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bounty-g { font-size: 0.7rem; opacity: 0.75; margin-left: 1px; }
.bounty-status { font-size: 0.7rem; }

@media (min-width: 481px) {
  .raid-boss-head h2 { font-size: 1.5rem; }
  .raid-bar { height: 18px; border-radius: 9px; }
  .raid-hp-floor { height: 18px; }
  .bounty-sub { white-space: normal; }
}

/* ==========================================================================
   BUILD IDENTITY  —  added 2026-09-09
   Archetype tags and stat-vs-even-spread bars. The whole point is legibility:
   every number these render was ALREADY public on the profile endpoint, it was
   just unreadable. "Stamina 132" says nothing until you can see that an even
   spread at that level would be 30.
   ========================================================================== */

/* --- archetype tag ----------------------------------------------------- */
/* Each archetype gets its own colour so it is recognisable at a glance in a
   list of twenty. Colours are meaning-bearing, not decoration: warm = offence,
   cool = defence, green = economy. */
.build-tag {
  display: inline-block;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 3px;
  white-space: nowrap;
  vertical-align: 2px;
  border: 1px solid currentColor;
}
.build-warlord    { color: #e0796c; background: rgba(224, 121, 108, 0.12); }
.build-bulwark    { color: #6ba3d6; background: rgba(107, 163, 214, 0.12); }
.build-marathoner { color: #d8a63c; background: rgba(216, 166, 60, 0.12); }
.build-prospector { color: #59b39c; background: rgba(89, 179, 156, 0.12); }
.build-juggernaut { color: #b48ad6; background: rgba(180, 138, 214, 0.12); }
.build-balanced   { color: var(--text-muted); background: rgba(140, 130, 117, 0.12); }

.build-blurb-line {
  margin: 2px 0 10px;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* --- stat vs even spread ---------------------------------------------- */
.spread-list { margin: 12px 0 6px; }
.spread-row { margin-bottom: 12px; }
.spread-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 0.82rem;
}
.spread-name {
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.72rem;
}
.spread-num { color: var(--text-primary); font-variant-numeric: tabular-nums; }
/* The multiple is the number that does the work — an outlier should be
   impossible to miss, and a dumped stat equally so. */
.spread-mult {
  font-weight: bold;
  margin-left: 5px;
  color: var(--text-muted);
}
.spread-mult.high { color: var(--accent-gold); }
.spread-mult.low { color: #e0796c; }

.spread-bar {
  position: relative;
  height: 8px;
  margin: 4px 0 3px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid rgba(201, 160, 80, 0.18);
  overflow: hidden;
}
/* The bar is scaled so 1x sits at a third of the width. That leaves room to
   SEE a 2x or 3x commitment rather than pinning everything at full — which is
   what a 0-100% scale would do to every stat at or above the spread. */
.spread-bar::after {
  content: '';
  position: absolute;
  left: 33.33%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: rgba(226, 216, 195, 0.35);
}
.spread-fill {
  height: 100%;
  background: linear-gradient(90deg, #2a9d8f, #52b788);
  transition: width 0.2s ease;
}
.spread-fill.high { background: linear-gradient(90deg, var(--xp-from), var(--xp-to)); }
.spread-fill.low { background: linear-gradient(90deg, var(--health-from), var(--health-to)); }

.spread-foot {
  font-size: 0.68rem;
  color: var(--text-muted);
  opacity: 0.8;
}

/* --- PvP target rows --------------------------------------------------- */
/* Replaces a `.list-item` that put the name and the Attack button on one flex
   line with nothing else. A build tag needs a second line, and on a phone the
   name and the tag must not fight the button for width. */
.target-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.14);
}
.target-row:last-of-type { border-bottom: none; }
.target-main { min-width: 0; flex: 1 1 auto; }
.target-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 0.95rem;
}
.target-lvl {
  font-size: 0.68rem;
  padding: 1px 6px;
  border-radius: 9px;
  border: 1px solid rgba(201, 160, 80, 0.5);
  color: var(--text-muted);
  white-space: nowrap;
}
.target-build { margin-top: 4px; }
.target-attack {
  width: auto;
  flex: 0 0 auto;
  min-height: 40px;
  margin: 0;
}

/* ==========================================================================
   CLAN WAR INTELLIGENCE  —  added 2026-09-09
   Odds badges, scouting, stances. The design premise: a fight in this game is
   near-deterministic (a 1.44x power ratio wins 100% of the time), so the whole
   strategy is choosing WHICH fight to take. That makes the odds badge the most
   important element on the screen, and it is styled accordingly.
   ========================================================================== */

/* --- odds badge -------------------------------------------------------- */
/* Five bands, and the colour IS the information — a player should be able to
   scan a roster and see green/red without reading a word. */
.cw-odds {
  display: inline-block;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
  white-space: nowrap;
  vertical-align: 2px;
  border: 1px solid currentColor;
}
.cw-odds-good  { color: #52b788; background: rgba(82, 183, 136, 0.14); }
.cw-odds-ok    { color: #9ccc65; background: rgba(156, 204, 101, 0.13); }
.cw-odds-warn  { color: #d8a63c; background: rgba(216, 166, 60, 0.14); }
.cw-odds-bad   { color: #e08a5c; background: rgba(224, 138, 92, 0.14); }
/* "Hopeless" means literally 0% — it should look like a wall, not a warning. */
.cw-odds-worst { color: #e0796c; background: rgba(224, 121, 108, 0.18); border-style: solid; }

/* --- HP pips (enemies) ------------------------------------------------- */
/* Enemies show HP as five pips rather than a number, because exact HP is a
   tell you should have to spend stamina to learn. */
.cw-pips { display: inline-flex; gap: 3px; vertical-align: middle; }
.cw-pip {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(201, 160, 80, 0.28);
}
.cw-pip.on { background: linear-gradient(180deg, var(--health-to), var(--health-from)); border-color: transparent; }

/* --- intel line -------------------------------------------------------- */
.cw-intel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin: 5px 0 2px;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.cw-intel-stat { font-family: var(--av-font-mono, ui-monospace, monospace); white-space: nowrap; }
.cw-intel-stat b { color: var(--text-primary); font-variant-numeric: tabular-nums; }
/* Revealed skills are the highest-value thing a scout learns — iron_skin
   doubles defence, which can make a target literally unbeatable. */
.cw-intel-skill {
  font-size: 0.63rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 1px 5px;
  border-radius: 3px;
  color: #6ba3d6;
  border: 1px solid rgba(107, 163, 214, 0.5);
  background: rgba(107, 163, 214, 0.1);
  white-space: nowrap;
}
/* Age is deliberately quiet but always present: intel decays, and a player
   acting on 80-second-old stamina numbers should have been able to notice. */
.cw-intel-age { font-size: 0.65rem; opacity: 0.65; font-style: italic; }
.cw-intel-coarse { opacity: 0.85; }

/* --- stance picker ----------------------------------------------------- */
.cw-stance { margin: 10px 0 4px; }
.cw-stance-label {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
  margin-bottom: 5px;
}
.cw-stance-row { display: flex; gap: 6px; }
.cw-stance-btn {
  flex: 1 1 0;
  min-height: 38px;
  margin: 0;
  padding: 6px 8px;
  width: auto;
  font-size: 0.78rem;
}
.cw-stance-btn.active {
  border-color: var(--accent-gold);
  color: var(--accent-gold);
  box-shadow: inset 0 0 0 1px var(--accent-gold);
}
.cw-stance-hint {
  margin-top: 5px;
  font-size: 0.7rem;
  color: var(--text-muted);
  min-height: 1.1em;
}

/* --- tags -------------------------------------------------------------- */
/* CONCEALED only ever appears on your OWN side — it tells you what the enemy
   is currently being shown, which is what makes coordinating a fake weak
   flank possible. */
.cw-tag-feint {
  color: #b48ad6;
  border: 1px solid rgba(180, 138, 214, 0.6);
  background: rgba(180, 138, 214, 0.12);
}

/* --- roster row layout ------------------------------------------------- */
/* Rows now carry three stacked lines (name+odds, intel, actions) instead of
   one, so the old single-line flex layout no longer applies. Mobile-first:
   the action buttons sit on their own row below on a narrow screen rather
   than squeezing the name. */
.cw-row {
  padding: 10px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.14);
}
.cw-row:last-child { border-bottom: none; }
.cw-row-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 0.92rem;
}
.cw-row-action {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}
.cw-row-action .cw-btn { flex: 1 1 0; min-height: 38px; margin: 0; width: auto; }
.cw-btn-scout { opacity: 0.92; }
.cw-row-downed { opacity: 0.5; }

.cw-h4-hint {
  display: block;
  font-size: 0.68rem;
  font-weight: 400;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
  margin-top: 2px;
}

@media (min-width: 481px) {
  /* On a wider screen the actions can sit inline with the name again. */
  .cw-row-action { justify-content: flex-start; }
  .cw-row-action .cw-btn { flex: 0 0 auto; min-width: 90px; }
}

/* ==========================================================================
   CLAN PAGE + AUCTION  —  rebuilt 2026-09-09
   Both screens previously used inline styles and a flat `.list-item` flex row
   that crammed a name and three action buttons onto one line. On a phone that
   wraps into an unreadable stack, and on the clan page it also put "Disband"
   directly beside "Leave Clan".
   ========================================================================== */

/* --- clan hero --------------------------------------------------------- */
.clan-hero { padding-bottom: 12px; }
.clan-id { display: flex; align-items: center; gap: 12px; }
/* The tag is the clan's identity in every other screen (war headers, rival
   lists), so it gets treated as a crest rather than bracketed text. */
.clan-tag {
  flex: 0 0 auto;
  min-width: 52px;
  padding: 8px 10px;
  text-align: center;
  border-radius: 6px;
  border: 1px solid var(--accent-gold);
  background: rgba(201, 160, 80, 0.1);
  color: var(--accent-gold);
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
}
.clan-id-text { min-width: 0; }
.clan-name { margin: 0; font-size: 1.2rem; line-height: 1.2; }
.clan-sub { margin-top: 2px; font-size: 0.78rem; color: var(--text-muted); }
.clan-dot { opacity: 0.45; margin: 0 3px; }
.clan-desc { margin: 10px 0 0; font-size: 0.88rem; color: var(--text-primary); opacity: 0.9; }

.clan-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.clan-act { width: auto; margin: 0; min-height: 38px; padding: 6px 12px; font-size: 0.82rem; }
/* Destructive actions are visually distinct AND live in their own card at the
   bottom of the page — previously Disband sat inline next to Leave in the
   header, one mis-tap apart. */
.clan-act-danger { border-color: rgba(224, 121, 108, 0.6); color: #e0796c; }
.clan-danger-zone { border-color: rgba(224, 121, 108, 0.28); }
.clan-danger-note { font-size: 0.8rem; margin: 2px 0 4px; }

/* --- section heads ----------------------------------------------------- */
.clan-section-head { margin-bottom: 8px; }
.clan-h4 { margin: 0; font-size: 1rem; }
.clan-h4-hint {
  display: block;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--text-muted);
  margin-top: 2px;
}

/* --- composition ------------------------------------------------------- */
/* War-planning information: four Warlords and no Bulwark means a soft flank. */
.clan-comp {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(201, 160, 80, 0.14);
}
.clan-comp-item {
  font-size: 0.7rem;
  padding: 3px 8px;
  border-radius: 3px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.clan-comp-item b { font-weight: 700; }
.clan-comp-warn { font-size: 0.7rem; color: #d8a63c; align-self: center; font-style: italic; }

/* --- roster ------------------------------------------------------------ */
.clan-member {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.12);
}
.clan-member:last-child { border-bottom: none; }
.clan-member-main { min-width: 0; flex: 1 1 auto; }
.clan-member-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 0.93rem;
}
.clan-member-lvl {
  font-size: 0.68rem;
  padding: 1px 6px;
  border-radius: 9px;
  border: 1px solid rgba(201, 160, 80, 0.45);
  color: var(--text-muted);
  white-space: nowrap;
}
.clan-rank {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
  white-space: nowrap;
}
.clan-rank-leader { color: var(--accent-gold); border: 1px solid var(--accent-gold); background: rgba(201, 160, 80, 0.12); }
.clan-rank-officer { color: #6ba3d6; border: 1px solid rgba(107, 163, 214, 0.6); background: rgba(107, 163, 214, 0.1); }
.clan-member-build { margin-top: 4px; }
/* Member actions wrap to their own column on a phone rather than squeezing the
   name into two characters. */
.clan-member-actions { display: flex; flex-direction: column; gap: 4px; flex: 0 0 auto; }
.clan-mini { width: auto; margin: 0; min-height: 30px; padding: 3px 9px; font-size: 0.72rem; }
.clan-mini-danger { border-color: rgba(224, 121, 108, 0.55); color: #e0796c; }

@media (min-width: 481px) {
  .clan-member-actions { flex-direction: row; }
}

/* --- auction: sorting -------------------------------------------------- */
.av-main-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.av-sorts { display: flex; flex-wrap: wrap; gap: 4px; }
.av-sort {
  width: auto;
  margin: 0;
  min-height: 30px;
  padding: 3px 9px;
  font-size: 0.72rem;
}
.av-sort.active { border-color: var(--accent-gold); color: var(--accent-gold); }

/* --- auction: level requirement --------------------------------------- */
/* The buy path enforces min_level, so the card states it. A locked card is
   dimmed but still fully readable — you should be able to see what you are
   working toward. */
.av-card-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.av-req {
  font-size: 0.65rem;
  padding: 1px 6px;
  border-radius: 9px;
  border: 1px solid rgba(201, 160, 80, 0.4);
  color: var(--text-muted);
  white-space: nowrap;
}
.av-req.locked { color: #e0796c; border-color: rgba(224, 121, 108, 0.6); }
.av-card-locked { opacity: 0.72; }
.av-btn-locked {
  opacity: 0.6;
  cursor: not-allowed;
  border-color: rgba(224, 121, 108, 0.5);
  color: #e0796c;
}

/* --- auction: price value --------------------------------------------- */
/* Bots anchor to catalogue value at 0.7-1.0x, so a ratio tells a buyer
   immediately whether a human is dumping or gouging. Without it the board is
   a wall of numbers with no reference. */
.av-card-pricecol { min-width: 0; }
.av-value {
  display: block;
  font-size: 0.66rem;
  margin-top: 1px;
  white-space: nowrap;
}
.av-value-steal { color: #52b788; font-weight: 600; }
.av-value-good  { color: #9ccc65; }
.av-value-fair  { color: var(--text-muted); }
.av-value-over  { color: #d8a63c; }
.av-value-gouge { color: #e0796c; }
.av-card-seller { font-size: 0.72rem; color: var(--text-muted); margin-top: 1px; }

/* ==========================================================================
   EVENT SEASON  —  added 2026-09-09
   The communal boss and the six-column leaderboard. The event is meant to feel
   like a different place from the rest of the game, so this is the one screen
   that leans on a warm ember palette rather than the app's gold-on-dark.
   ========================================================================== */

.ev-hero {
  /* A faint ember wash from the bottom, so the boss card reads as lit from
     below without needing an image asset. */
  background-image: linear-gradient(0deg, rgba(224, 121, 60, 0.09), transparent 62%);
  border-color: rgba(224, 121, 60, 0.34);
}
.ev-hero-down {
  /* Once it is dead the warmth drains out — the page should look different
     after two weeks of work, not just say "defeated". */
  background-image: none;
  border-color: rgba(201, 160, 80, 0.22);
}

.ev-hero-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.ev-name { margin: 0; font-size: 1.25rem; line-height: 1.15; }
.ev-clock { margin-top: 3px; font-size: 0.76rem; color: var(--text-muted); }

.ev-stagechip {
  flex: 0 0 auto;
  padding: 5px 9px;
  border-radius: 4px;
  border: 1px solid rgba(224, 121, 60, 0.55);
  background: rgba(224, 121, 60, 0.12);
  color: #e8a06c;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
}
.ev-stagechip span { opacity: 0.6; font-weight: 400; }

/* --- boss bar, segmented by stage ------------------------------------- */
/* Segments rather than one continuous bar: over two weeks a single bar moving
   98% -> 97% looks like nothing is happening, where "stage 2 is two-thirds
   down" is progress a player can see between sessions. */
.ev-bossbar {
  display: flex;
  gap: 3px;
  margin: 12px 0 6px;
  height: 26px;
}
.ev-seg {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  border-radius: 3px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(224, 121, 60, 0.3);
}
.ev-seg-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: linear-gradient(180deg, #f0b465, #d15f2a);
  transition: width 0.35s ease;
}
/* A cleared stage keeps its number but goes cold and empty, so the bar doubles
   as a record of how far the group has come. */
.ev-seg-cleared { border-color: rgba(201, 160, 80, 0.2); background: rgba(255, 255, 255, 0.03); }
.ev-seg-no {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.68rem;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.75);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

.ev-hpline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-variant-numeric: tabular-nums;
}
.ev-hp { font-size: 1.05rem; font-weight: 700; color: #e8a06c; }
.ev-hp-of { font-size: 0.76rem; color: var(--text-muted); }
.ev-hp-pct { margin-left: auto; font-size: 0.78rem; color: var(--text-muted); }

/* --- the strike button ------------------------------------------------- */
/* Full width and 56px tall. This is the one control on the page a player will
   tap hundreds of times over two weeks, on a phone, probably one-handed —
   so it gets the whole width and a thumb-sized target rather than sitting in a
   row of equals. */
.ev-strike {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin: 12px 0 0;
  min-height: 56px;
  border-radius: 6px;
  border: 1px solid rgba(224, 121, 60, 0.7);
  background: linear-gradient(180deg, rgba(224, 121, 60, 0.22), rgba(224, 121, 60, 0.1));
  color: #f2b878;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
/* Strike now shares a row with Heal (reusing .raid-actions/.raid-heal from the
   raid page verbatim). Only the strike button needs adjusting: it was
   full-width and top-margined on its own. 2:1 keeps Strike the primary action. */
.ev-actions { margin-top: 12px; }
.ev-actions .ev-strike {
  flex: 2 1 0;
  width: auto;
  margin: 0;
}
.ev-actions .raid-heal { flex: 1 1 0; }

.ev-strike:active { background: rgba(224, 121, 60, 0.3); }
.ev-strike:disabled { opacity: 0.5; }
.ev-strike-cost {
  font-size: 0.72rem;
  font-weight: 400;
  opacity: 0.8;
  padding: 2px 7px;
  border-radius: 9px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.ev-strike-result {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  min-height: 1.4em;
  margin-top: 8px;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.8rem;
}
.ev-hit-dmg { font-size: 1.05rem; font-weight: 700; color: #f0b465; }
.ev-hit-ds  { color: #b48ad6; font-weight: 700; }
.ev-hit-hp  { color: #e0796c; }
.ev-hit-xp  { color: #9ccc65; }

.ev-victory { margin: 12px 0 0; font-size: 0.9rem; color: var(--accent-gold); }

.ev-lore { margin-top: 12px; }
.ev-lore summary {
  font-size: 0.78rem;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px 0;
}
.ev-lore p { margin: 6px 0 0; font-size: 0.85rem; line-height: 1.55; opacity: 0.9; }

/* --- qualification notice --------------------------------------------- */
.ev-qual { border-color: rgba(216, 166, 60, 0.35); }
.ev-qual-note { font-size: 0.84rem; margin: 4px 0 0; line-height: 1.5; }

/* --- section heads ----------------------------------------------------- */
.ev-section-head { margin-bottom: 8px; }
.ev-h4 { margin: 0; font-size: 1rem; }
.ev-h4-hint {
  display: block;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--text-muted);
  margin-top: 2px;
}
.ev-explain { font-size: 0.8rem; line-height: 1.5; margin: 0 0 12px; }

/* --- KPI rows ---------------------------------------------------------- */
.ev-kpi { padding: 9px 0; border-bottom: 1px solid rgba(201, 160, 80, 0.12); }
.ev-kpi:last-child { border-bottom: none; }
.ev-kpi-head {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-bottom: 5px;
}
.ev-kpi-label { font-size: 0.86rem; }
.ev-kpi-build {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 1px 5px;
  border-radius: 3px;
  color: var(--text-muted);
  border: 1px solid rgba(201, 160, 80, 0.3);
  white-space: nowrap;
}
.ev-kpi-val {
  margin-left: auto;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-variant-numeric: tabular-nums;
  font-size: 0.88rem;
  font-weight: 700;
}

.ev-kpi-bar {
  position: relative;
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}
.ev-kpi-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 4px;
  background: linear-gradient(90deg, rgba(201, 160, 80, 0.5), var(--accent-gold));
  transition: width 0.3s ease;
}
/* At or above the field's top tier the bar goes ember — the same colour as the
   boss, so "I am carrying this column" reads without a legend. */
.ev-kpi-fill-top { background: linear-gradient(90deg, #d15f2a, #f0b465); }
/* The 1.0x line. The track runs past it because the score keeps paying above
   the top tier, up to the cap — without the mark, a bar that stops at
   two-thirds looks like a failure rather than a par score. */
.ev-kpi-mark {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 1px;
  background: rgba(255, 255, 255, 0.5);
}

.ev-kpi-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 4px;
  font-size: 0.67rem;
  color: var(--text-muted);
}
.ev-kpi-weight { white-space: nowrap; opacity: 0.8; }

/* --- leaderboard ------------------------------------------------------- */
.ev-board-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.12);
}
.ev-board-row:last-child { border-bottom: none; }
/* The viewer's own row is marked, because on a board of 25 the first thing
   anyone looks for is themselves. */
.ev-board-me {
  margin: 0 -10px;
  padding: 9px 10px;
  border-radius: 4px;
  background: rgba(201, 160, 80, 0.08);
  box-shadow: inset 2px 0 0 var(--accent-gold);
}
.ev-board-main { flex: 1 1 auto; min-width: 0; }
.ev-board-name { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 0.9rem; }
.ev-board-lvl {
  font-size: 0.66rem;
  padding: 1px 6px;
  border-radius: 9px;
  border: 1px solid rgba(201, 160, 80, 0.4);
  color: var(--text-muted);
  white-space: nowrap;
}

.ev-rank {
  flex: 0 0 auto;
  width: 24px;
  text-align: center;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text-muted);
}
.ev-rank-1 { color: #f0c460; }
.ev-rank-2 { color: #cfd3d8; }
.ev-rank-3 { color: #cd8b52; }

/* --- six micro-bars per player ----------------------------------------- */
/* The SHAPE of a contribution, not its size. Two players on the same score can
   have completely different silhouettes here, and that is the scouting
   information that makes the board worth reading rather than only topping. */
.ev-spark {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 16px;
  margin-top: 4px;
}
.ev-spark-bar {
  display: flex;
  align-items: flex-end;
  width: 7px;
  height: 100%;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 1px;
}
.ev-spark-bar i {
  display: block;
  width: 100%;
  border-radius: 1px;
  background: var(--accent-gold);
}

.ev-score {
  flex: 0 0 auto;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-variant-numeric: tabular-nums;
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent-gold);
}

.ev-unqual { font-size: 0.72rem; margin: 10px 0 0; font-style: italic; }
.ev-empty h2 { margin-top: 0; }

/* --- dashboard event banner ------------------------------------------- */
/* The season's home-screen presence. Tappable whole-card because it is the
   entry point to the event — a small "view" link would be the wrong target
   size on a phone. */
.ev-banner {
  cursor: pointer;
  border-color: rgba(224, 121, 60, 0.4);
  background-image: linear-gradient(0deg, rgba(224, 121, 60, 0.1), transparent 70%);
}
.ev-banner:active { background-color: rgba(224, 121, 60, 0.08); }
.ev-banner-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ev-banner-tag {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  padding: 2px 7px;
  border-radius: 3px;
  color: #e8a06c;
  border: 1px solid rgba(224, 121, 60, 0.6);
  background: rgba(224, 121, 60, 0.12);
}
.ev-banner-clock { font-size: 0.7rem; color: var(--text-muted); }
.ev-banner-name { margin: 7px 0 8px; font-size: 1.02rem; font-weight: 700; }
.ev-banner-bar {
  height: 7px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}
.ev-banner-fill {
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, #d15f2a, #f0b465);
  transition: width 0.35s ease;
}
.ev-banner-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 5px;
  font-size: 0.7rem;
  color: var(--text-muted);
}

/* --- event qualification checklist ------------------------------------ */
/* A checklist with the player's own progress on each line, not a restatement
   of the rule. "34 of 60 hits" tells them where they stand; "you need 60 hits"
   leaves them to go and work it out. */
.ev-qual-reqs { display: flex; flex-direction: column; gap: 7px; margin-top: 9px; }
.ev-qual-req {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text-primary);
  opacity: 0.9;
}
.ev-qual-tick {
  flex: 0 0 auto;
  width: 16px;
  text-align: center;
  font-weight: 700;
  color: var(--text-muted);
}
.ev-qual-done { opacity: 0.6; }
.ev-qual-done .ev-qual-tick { color: #52b788; }

/* --- event boss cycle -------------------------------------------------- */
/* Replaces the stage chip. Which boss you are on and how many have fallen is
   the season's real progress now that the bar itself resets — the stage moved
   down to the clock line, where it belongs as a detail rather than a headline. */
.ev-cyclechip {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  padding: 5px 9px;
  border-radius: 4px;
  border: 1px solid rgba(224, 121, 60, 0.55);
  background: rgba(224, 121, 60, 0.12);
  white-space: nowrap;
}
.ev-cyclechip-n {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.95rem;
  font-weight: 700;
  color: #e8a06c;
}
.ev-cyclechip-l { font-size: 0.6rem; color: var(--text-muted); }
.ev-dot { opacity: 0.4; margin: 0 3px; }

/* --- season prizes ---------------------------------------------------- */
/* Shown to everyone from day one. A 14-day ranking with unstated prizes is
   just a scoreboard, so the prizes get their own card above the board rather
   than being buried behind a tooltip. */
.ev-prizes { display: flex; flex-direction: column; gap: 8px; }
.ev-prize {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 5px;
  border: 1px solid rgba(201, 160, 80, 0.2);
  background: rgba(255, 255, 255, 0.02);
}
/* Only first place gets the gold treatment. Medal-colouring all three flattens
   the thing the ranking exists to create. */
.ev-prize-1 { border-color: rgba(240, 196, 96, 0.5); background: rgba(240, 196, 96, 0.07); }
.ev-prize-rank {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-muted);
}
.ev-prize-1 .ev-prize-rank { color: #f0c460; }
.ev-prize-2 .ev-prize-rank { color: #cfd3d8; }
.ev-prize-3 .ev-prize-rank { color: #cd8b52; }
.ev-prize-body { min-width: 0; }
.ev-prize-loot {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.85rem;
  font-weight: 700;
}
.ev-prize-title { margin-top: 2px; font-size: 0.72rem; color: #f0c460; }

/* --- kill log --------------------------------------------------------- */
.ev-killlog { display: flex; flex-direction: column; }
.ev-kill {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.12);
}
.ev-kill:last-child { border-bottom: none; }
.ev-kill-n {
  flex: 0 0 auto;
  width: 30px;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.76rem;
  color: var(--text-muted);
}
.ev-kill-main { flex: 1 1 auto; min-width: 0; }
.ev-kill-who { font-size: 0.88rem; }
/* The drop is the point of the row, so it is coloured rather than muted. */
.ev-kill-loot { margin-top: 1px; font-size: 0.7rem; color: #e8a06c; }
.ev-kill-dmg {
  flex: 0 0 auto;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-variant-numeric: tabular-nums;
  font-size: 0.74rem;
  color: var(--text-muted);
}

/* --- event prize cosmetics -------------------------------------------- */
/* Each cosmetic is chipped with its TYPE. The prize structure deliberately
   overlaps (1st takes all three, 2nd takes badge+aura, 3rd takes the title),
   so a player comparing 2nd against 3rd has to be able to see WHICH of the
   three each rank carries — a single merged label would hide exactly the
   distinction that makes third place worth finishing. */
.ev-prize-cosset { display: flex; flex-wrap: wrap; gap: 5px; }
.ev-prize-cos {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  font-size: 0.76rem;
  padding: 3px 8px;
  border-radius: 3px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.ev-prize-cos-type {
  font-size: 0.56rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  opacity: 0.7;
}
/* One colour per type, consistent with how each reads elsewhere in the game:
   badges are warm metal, titles gold text, auras violet. */
.ev-prize-cos-badge { color: #d9762b; background: rgba(217, 118, 43, 0.1); }
.ev-prize-cos-title { color: #f0c460; background: rgba(240, 196, 96, 0.09); }
.ev-prize-cos-aura  { color: #b48ad6; background: rgba(180, 138, 214, 0.1); }
.ev-prize-loot {
  margin-top: 4px;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.8rem;
}
.ev-prize-note { font-size: 0.72rem; margin: 10px 0 0; font-style: italic; }

/* --- earned (award-only) cosmetics ------------------------------------ */
/* These are the ones that cannot be bought at any price, so they are marked
   and sorted to the top of the cosmetics list. A trophy buried under ten
   things for sale is barely better than not showing it at all. */
.cosmetic-row-earned { border-left: 2px solid #d9762b; }
.cosmetic-earned-tag {
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 1px 5px;
  margin-left: 4px;
  border-radius: 3px;
  color: #d9762b;
  border: 1px solid rgba(217, 118, 43, 0.6);
  background: rgba(217, 118, 43, 0.1);
  white-space: nowrap;
}
/* Badges have no sprite to render, so they preview as their own label in their
   own colour — the same way titles preview as text. */
.cosmetic-badge-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 64px;
  padding: 4px 8px;
  border-radius: 3px;
  border: 1px solid currentColor;
  font-size: 0.68rem;
  font-weight: 700;
  text-align: center;
  line-height: 1.15;
}

/* --- mythic rarity: the set-item dark red ----------------------------- */
/* Set pieces are the only mythic items in the game. Dark red, deliberately
   unlike the gold used for legendary, so a set piece is identifiable in a
   list of loot at a glance without reading the rarity word. */
:root { --av-mythic: #a3122b; }
.av-rarity-mythic { border-left-color: var(--av-mythic); box-shadow: 0 0 14px rgba(163, 18, 43, 0.22); }
.av-rarity-text-mythic { color: var(--av-mythic); font-weight: 700; }

/* --- Ember Cache ------------------------------------------------------- */
.cache-card { border-color: rgba(224, 121, 60, 0.28); }
.cache-tier { font-size: 0.8rem; line-height: 1.5; margin: 0 0 10px; }
.cache-tier-locked { color: #d8a63c; }

/* --- the pity counter -------------------------------------------------- */
/* Always visible, never collapsed. The low set-piece rate is only defensible
   because this exists, so hiding it would remove the justification. */
.cache-pity {
  padding: 9px 10px;
  border-radius: 5px;
  border: 1px solid rgba(201, 160, 80, 0.22);
  background: rgba(255, 255, 255, 0.02);
  margin-bottom: 10px;
}
.cache-pity-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 0.8rem;
  margin-bottom: 6px;
}
.cache-pity-ready { color: #52b788; }
.cache-pity-count {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.74rem;
  color: var(--text-muted);
}
.cache-pity-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,0.07); overflow: hidden; }
.cache-pity-fill {
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, rgba(201,160,80,0.5), var(--accent-gold));
  transition: width 0.3s ease;
}
.cache-pity-fill-ready { background: linear-gradient(90deg, #3f8f68, #52b788); }
.cache-pity-note { font-size: 0.66rem; color: var(--text-muted); margin-top: 5px; font-style: italic; }

/* --- buy / open ------------------------------------------------------- */
.cache-buy { display: flex; gap: 6px; margin-bottom: 8px; }
.cache-btn { flex: 1 1 0; width: auto; margin: 0; min-height: 38px; padding: 5px 6px; font-size: 0.74rem; }
/* The open button is the one people press repeatedly, so it gets the thumb
   target, same reasoning as the boss strike button. */
.cache-open {
  width: 100%;
  margin: 0;
  min-height: 52px;
  font-size: 0.95rem;
  font-weight: 700;
  border: 1px solid rgba(224, 121, 60, 0.7);
  background: linear-gradient(180deg, rgba(224,121,60,0.2), rgba(224,121,60,0.08));
  color: #f2b878;
}
.cache-open:disabled { opacity: 0.45; }

.cache-result { min-height: 2.2em; margin-top: 8px; }
.cache-reward {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 4px;
  border: 1px solid currentColor;
  background: rgba(255, 255, 255, 0.03);
}
.cache-reward-label { font-size: 0.92rem; font-weight: 700; }
.cache-reward-flash {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 2px 6px;
  border-radius: 3px;
  border: 1px solid currentColor;
}

/* Reward colours. Set pieces take the same dark red as the mythic rarity, so
   the drop and the item in your inventory read as the same thing. */
.cache-r-set    { color: var(--av-mythic, #a3122b); }
.cache-r-gear   { color: #9ccc65; }
.cache-r-vp     { color: #6ba3d6; }
.cache-r-gold   { color: var(--accent-gold); }
.cache-r-potion { color: #b48ad6; }

/* --- odds table ------------------------------------------------------- */
.cache-odds, .cache-history { margin-top: 10px; }
.cache-odds summary, .cache-history summary {
  font-size: 0.76rem;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px 0;
}
.cache-odds-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.cache-odd {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.78rem;
  padding: 3px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.1);
}
.cache-odd-pct {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.cache-odds-note { font-size: 0.68rem; color: var(--text-muted); margin: 7px 0 0; font-style: italic; }

/* --- collection + history --------------------------------------------- */
.cache-collection {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(201, 160, 80, 0.16);
}
.cache-collection-head {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.cache-piece {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 0;
  font-size: 0.84rem;
}
.cache-piece-name { color: var(--av-mythic, #a3122b); font-weight: 700; }
.cache-piece-tier { font-size: 0.7rem; color: var(--text-muted); }
.cache-hist-row { font-size: 0.78rem; padding: 3px 0; }
.cache-hist-pity {
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 1px 5px;
  border-radius: 3px;
  border: 1px solid currentColor;
  opacity: 0.8;
}

/* --- set pieces in the inventory -------------------------------------- */
/* The reported bug: a Cinderbound piece rendered in the same colour as a
   common item, with no set indication and no level. All three are fixed here
   and in RARITY_COLORS (two separate places map rarity to a colour, and only
   the auction one had been updated). */
.gear-tile-set { border-left: 2px solid var(--av-mythic, #a3122b); }
.item-name-mythic {
  color: #d4213f;
  font-weight: 700;
  text-shadow: 0 0 8px rgba(212, 33, 63, 0.3);
}
.item-set-tag {
  display: inline-block;
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  padding: 1px 6px;
  margin: 2px 0;
  border-radius: 3px;
  color: #d4213f;
  border: 1px solid rgba(212, 33, 63, 0.55);
  background: rgba(212, 33, 63, 0.1);
}
/* The level requirement was not shown anywhere in the inventory at all. */
.item-lvl {
  display: inline-block;
  font-size: 0.64rem;
  padding: 0 5px;
  margin-left: 4px;
  border-radius: 8px;
  border: 1px solid rgba(201, 160, 80, 0.35);
  white-space: nowrap;
}
.item-lvl-low { color: #e0796c; border-color: rgba(224, 121, 108, 0.6); }

/* --- the set showcase ------------------------------------------------- */
/* The case for spending. Its absence was the fair half of a 3/10: without the
   bonuses stated, the Cache reads as a lottery for one slightly better helm. */
.cache-set {
  padding: 11px 12px;
  border-radius: 6px;
  border: 1px solid rgba(212, 33, 63, 0.35);
  background: linear-gradient(180deg, rgba(212, 33, 63, 0.08), rgba(212, 33, 63, 0.02));
  margin-bottom: 12px;
}
.cache-set-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.cache-set-name {
  font-size: 1.02rem;
  font-weight: 700;
  color: #d4213f;
  text-shadow: 0 0 10px rgba(212, 33, 63, 0.3);
}
.cache-set-count {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.72rem;
  color: var(--text-muted);
}
.cache-set-pitch { font-size: 0.8rem; line-height: 1.5; margin: 0 0 9px; color: var(--text-primary); opacity: 0.92; }

.cache-set-bonuses { display: flex; flex-direction: column; gap: 7px; }
.cache-set-bonus {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 8px 9px;
  border-radius: 4px;
  border: 1px solid rgba(201, 160, 80, 0.18);
  background: rgba(255, 255, 255, 0.02);
  opacity: 0.72;
}
/* An earned threshold lights up. The contrast between the two states is what
   makes the unearned one read as a target rather than as flavour text. */
.cache-set-bonus-on {
  opacity: 1;
  border-color: rgba(212, 33, 63, 0.5);
  background: rgba(212, 33, 63, 0.08);
}
.cache-set-bonus-n {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-muted);
}
.cache-set-bonus-on .cache-set-bonus-n { color: #d4213f; }
.cache-set-bonus-body { min-width: 0; }
.cache-set-bonus-name { font-size: 0.88rem; font-weight: 700; }
.cache-set-bonus-tag {
  font-size: 0.56rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 1px 5px;
  margin-left: 5px;
  border-radius: 3px;
  color: #52b788;
  border: 1px solid rgba(82, 183, 136, 0.6);
}
.cache-set-bonus-desc { font-size: 0.76rem; line-height: 1.45; color: var(--text-muted); margin-top: 2px; }
.cache-set-skill {
  margin-top: 5px;
  padding: 5px 7px;
  border-radius: 3px;
  border: 1px dashed rgba(180, 138, 214, 0.5);
  font-size: 0.72rem;
  color: #b48ad6;
  line-height: 1.4;
}
.cache-set-soon { opacity: 0.75; font-style: italic; }

.cache-set-have {
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid rgba(212, 33, 63, 0.18);
  font-size: 0.76rem;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: baseline;
}
.cache-set-have-none { color: var(--text-muted); font-style: italic; }
.cache-set-piece {
  color: #d4213f;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 3px;
  border: 1px solid rgba(212, 33, 63, 0.4);
}
.cache-set-piece em { font-style: normal; font-weight: 400; opacity: 0.7; font-size: 0.9em; }
.cache-set-foot { font-size: 0.7rem; color: var(--text-muted); margin: 8px 0 0; }

/* Sub-rows under an odds line, for the VP bands. */
.cache-odd-sub { padding-left: 12px; opacity: 0.75; font-size: 0.72rem; border-bottom: none; }

/* ==========================================================================
   GEAR SCREEN  —  rebuilt 2026-09-09
   The old screen could show what you were wearing but not change it: swapping
   meant leaving for the Inventory tab and hunting the category. Slots now
   expand in place with the alternatives you own and a stat delta for each.
   ========================================================================== */

/* --- set progress ------------------------------------------------------ */
.gear-setpanel {
  border-color: rgba(212, 33, 63, 0.35);
  background: linear-gradient(180deg, rgba(212, 33, 63, 0.07), transparent 70%);
}
.gear-setpanel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.gear-setpanel-name { font-size: 1rem; font-weight: 700; color: #d4213f; }
.gear-setpanel-worn {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.74rem;
  color: var(--text-muted);
}
.gear-setpanel-tiers { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 6px; }
.gear-settier {
  font-size: 0.72rem;
  padding: 3px 8px;
  border-radius: 3px;
  border: 1px solid rgba(201, 160, 80, 0.2);
  color: var(--text-muted);
  white-space: nowrap;
}
.gear-settier span { opacity: 0.7; }
.gear-settier-on {
  color: #d4213f;
  border-color: rgba(212, 33, 63, 0.55);
  background: rgba(212, 33, 63, 0.1);
}
.gear-setpanel-note { font-size: 0.78rem; line-height: 1.45; color: var(--text-primary); opacity: 0.9; }

/* --- slots -------------------------------------------------------------- */
.gear-slots { display: flex; flex-direction: column; gap: 6px; }
.gear-slot {
  border-radius: 5px;
  border: 1px solid rgba(201, 160, 80, 0.16);
  background: rgba(255, 255, 255, 0.015);
  overflow: hidden;
}
/* An empty slot is a problem to solve, not a neutral state, so it is marked. */
.gear-slot-vacant { border-style: dashed; border-color: rgba(216, 166, 60, 0.35); }
.gear-slot-set { border-color: rgba(212, 33, 63, 0.4); }
.gear-slot-open { border-color: var(--accent-gold); }

/* The whole header is the tap target. A bare chevron would be a 20px hit area
   on a phone, which is the kind of thing that reads as the app ignoring you. */
.gear-slot-head {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  margin: 0;
  padding: 10px;
  border: none;
  border-radius: 0;
  background: transparent;
  text-align: left;
  min-height: 60px;
}
.gear-slot-icon { flex: 0 0 auto; }
.gear-slot-main { flex: 1 1 auto; min-width: 0; }
.gear-slot-label {
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 2px;
}
.gear-slot-name { font-size: 0.92rem; font-weight: 700; }
.gear-slot-empty { font-size: 0.86rem; color: #d8a63c; font-style: italic; }
.gear-slot-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  margin-top: 2px;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.74rem;
}
.gear-stat-atk { color: #e08a5c; }
.gear-stat-def { color: #6ba3d6; }
.gear-slot-count {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.66rem;
  color: var(--text-muted);
  white-space: nowrap;
}
.gear-slot-chev { font-size: 0.8rem; transition: transform 0.2s ease; display: inline-block; }
.gear-slot-open .gear-slot-chev { transform: rotate(180deg); }

/* --- the swap drawer ---------------------------------------------------- */
.gear-slot-drawer {
  padding: 0 10px 10px;
  border-top: 1px solid rgba(201, 160, 80, 0.12);
}
.gear-drawer-actions { display: flex; gap: 6px; padding: 9px 0; flex-wrap: wrap; }
.gear-drawer-actions .btn-sm { width: auto; margin: 0; }
.gear-alt-list { display: flex; flex-direction: column; gap: 5px; }
.gear-alt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 8px;
  border-radius: 4px;
  border: 1px solid rgba(201, 160, 80, 0.14);
  background: rgba(255, 255, 255, 0.02);
}
.gear-alt-set { border-color: rgba(212, 33, 63, 0.4); }
.gear-alt-main { flex: 1 1 auto; min-width: 0; }
.gear-alt-name { font-size: 0.85rem; font-weight: 700; }
.gear-alt-stats {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.7rem;
  color: var(--text-muted);
  margin-top: 1px;
}
/* The delta is what makes this list usable. A raw stat pair asks the player to
   do arithmetic before every swap. */
.gear-alt-delta {
  display: flex;
  gap: 8px;
  margin-top: 3px;
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.72rem;
  font-weight: 700;
}
.gear-delta-up { color: #52b788; }
.gear-delta-down { color: #e0796c; }
.gear-delta-same { color: var(--text-muted); }
.gear-alt-btn { flex: 0 0 auto; width: auto; margin: 0; min-height: 34px; }
.gear-alt-none { font-size: 0.78rem; padding: 9px 0 0; margin: 0; line-height: 1.45; }

/* --- settings page ------------------------------------------------------ */
.settings-back { display: inline-block; margin-bottom: 8px; }
.settings-hero { padding-bottom: 10px; }
.settings-title { margin: 0; font-size: 1.2rem; }
.settings-sub { margin: 3px 0 0; font-size: 0.8rem; }

/* --- appearance tab ----------------------------------------------------- */
.appearance-hero { padding-bottom: 12px; }
.appearance-preview { display: flex; align-items: center; gap: 14px; }
.appearance-portrait { flex: 0 0 auto; width: 64px; height: 64px; }
.appearance-nameline { min-width: 0; }
/* Rendering the name with the title applied, rather than describing where the
   title goes. A title's entire value is how the name reads with it on. */
.appearance-name {
  font-size: 1rem;
  font-weight: 700;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 5px;
}
.appearance-title { color: var(--accent-gold); font-weight: 400; font-style: italic; }
.appearance-badge {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 1px 6px;
  border-radius: 3px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.appearance-count { font-size: 0.72rem; margin-top: 3px; }
.appearance-note { font-size: 0.74rem; margin: 10px 0 0; line-height: 1.45; }
.appearance-shop-btn { width: auto; margin: 8px 0 0; min-height: 38px; }
.appearance-clear { width: auto; margin: 0 0 8px; min-height: 32px; font-size: 0.74rem; }

.appearance-grid { display: flex; flex-wrap: wrap; gap: 6px; }
.appearance-item {
  position: relative;
  width: auto;
  margin: 0;
  min-height: 40px;
  padding: 7px 11px;
  font-size: 0.8rem;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.appearance-item-on { border-color: var(--accent-gold); color: var(--accent-gold); }
.appearance-item-earned { border-color: rgba(217, 118, 43, 0.6); }
.appearance-item-name { font-weight: 700; }
.appearance-earned {
  font-size: 0.54rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #d9762b;
}
.appearance-on-tag { font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.8; }

/* --- cache: two wallets ------------------------------------------------ */
/* VP and gems buy the same chest at the converter's own rate. Labelling both
   rows matters: an unlabelled row of six buttons where three say VP and three
   say gems reads as six different products. */
.cache-buy-label {
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 8px 0 4px;
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.cache-buy-rate { text-transform: none; letter-spacing: 0; font-style: italic; opacity: 0.75; }
.cache-btn-gem { border-color: rgba(107, 163, 214, 0.5); color: #8fc4ea; }

/* --- slot level badge (idea from the reference UI) --------------------- */
/* The item level in the corner of the slot icon. Answers "what level is this
   thing" without opening anything, which was a direct complaint. */
.gear-slot-iconwrap { position: relative; flex: 0 0 auto; }
.gear-slot-badge {
  position: absolute;
  top: -4px;
  left: -4px;
  min-width: 18px;
  padding: 0 3px;
  border-radius: 9px;
  background: #171018;
  border: 1px solid var(--accent-gold);
  color: var(--accent-gold);
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.58rem;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}

/* --- the currently worn item, at the top of the swap list ------------- */
/* Named explicitly rather than left implicit. A column of deltas is only
   meaningful once you can see what they are deltas FROM. */
.gear-alt-worn {
  border-color: rgba(82, 183, 136, 0.5);
  background: rgba(82, 183, 136, 0.08);
}
.gear-alt-worntag {
  flex: 0 0 auto;
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #52b788;
  padding: 2px 6px;
  border-radius: 3px;
  border: 1px solid rgba(82, 183, 136, 0.5);
}

/* --- auction filters (idea from the reference UI) ---------------------- */
/* The reference client puts a "Usable Only" checkbox and a min/max level
   range on its auction, and it is obviously right for a level-gated market:
   without it the board is padded with rows the buy endpoint will refuse, and
   the player discovers that one tap at a time. */
.av-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.av-filter-toggle {
  width: auto;
  margin: 0;
  min-height: 32px;
  padding: 4px 10px;
  font-size: 0.74rem;
}
.av-filter-toggle.active { border-color: var(--accent-gold); color: var(--accent-gold); }
.av-filter-range {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.av-filter-range input {
  width: 52px;
  margin: 0;
  min-height: 32px;
  padding: 2px 5px;
  font-size: 0.74rem;
  text-align: center;
}
.av-filter-apply, .av-filter-clear {
  width: auto;
  margin: 0;
  min-height: 32px;
  padding: 4px 9px;
  font-size: 0.72rem;
}

/* ==========================================================================
   INVENTORY  —  restructured 2026-09-09
   Was eight collapsed accordions with no summary, so finding one item meant
   opening categories until you hit it. Now a summary, filters, a sort and one
   flat list, with the category as a chip on each row.
   ========================================================================== */
.inv-summary { padding-bottom: 10px; }
.inv-counts {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 5px;
  font-size: 0.86rem;
}
.inv-counts b { font-family: var(--av-font-mono, ui-monospace, monospace); }
.inv-dot { opacity: 0.4; }
.inv-setcount { color: #d4213f; }
.inv-hint { font-size: 0.74rem; line-height: 1.45; margin: 6px 0 10px; }

.inv-filters { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
.inv-filter {
  width: auto;
  margin: 0;
  min-height: 32px;
  padding: 4px 9px;
  font-size: 0.74rem;
}
.inv-filter.active { border-color: var(--accent-gold); color: var(--accent-gold); }
.inv-filter-n {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.66rem;
  opacity: 0.7;
  margin-left: 2px;
}

.inv-sorts { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.inv-sort-label {
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-right: 2px;
}
.inv-sort {
  width: auto;
  margin: 0;
  min-height: 28px;
  padding: 2px 8px;
  font-size: 0.7rem;
}
.inv-sort.active { border-color: var(--accent-gold); color: var(--accent-gold); }

.inv-list { display: flex; flex-direction: column; gap: 6px; }
/* The category, as a chip on the row. It used to be the container you had to
   open, which is why nothing was findable. */
/* A row repainted in place after Awaken/Reroll. The row deliberately does NOT
   move (the list would otherwise re-sort by the new rarity and the item you
   just acted on would teleport), so it needs to say "this is the one that
   changed" some other way. */
.gear-tile-flash {
  animation: gear-tile-flash 1.4s ease-out;
}

@keyframes gear-tile-flash {
  0% { box-shadow: inset 0 0 0 2px var(--accent-gold); background-color: rgba(212, 175, 55, 0.16); }
  100% { box-shadow: inset 0 0 0 2px transparent; background-color: transparent; }
}

.inv-slot-chip {
  display: inline-block;
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 1px 5px;
  margin-right: 5px;
  border-radius: 3px;
  border: 1px solid rgba(201, 160, 80, 0.3);
  white-space: nowrap;
  /* Now a real <button> (it filters the list to its slot), so it has to shed
     the global button styling and keep looking like the chip it was. */
  width: auto;
  background: none;
  color: inherit;
  font-family: inherit;
  cursor: pointer;
}
.inv-slot-chip:hover {
  border-color: var(--accent-gold);
  color: var(--accent-gold);
}
/* Slot strip sits under the main filters as a secondary row — quieter, so the
   two strips read as a hierarchy rather than twelve competing buttons. */
.inv-slot-filters {
  margin-top: 4px;
  opacity: 0.9;
}
.inv-slot-filters .inv-filter { font-size: 0.72rem; }

/* Worn items are labelled rather than given a disabled Equip button — a dead
   control is worse than a word. */
.inv-worn {
  align-self: center;
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: #52b788;
  padding: 3px 8px;
  border-radius: 3px;
  border: 1px solid rgba(82, 183, 136, 0.5);
  white-space: nowrap;
}

/* --- recharge price ladder -------------------------------------------- */
/* The price is per-player and moves, so the row has to explain itself: what it
   costs now, whether that is raised, and when it drops. A bare Fill button
   would leave a player wondering why the number changed. */
.rc-explain { font-size: 0.78rem; line-height: 1.5; margin-bottom: 10px; }
.rc-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid rgba(201, 160, 80, 0.12);
}
.rc-row:last-child { border-bottom: none; }
.rc-main { flex: 1 1 auto; min-width: 0; }
.rc-label { font-size: 0.9rem; }
.rc-bar {
  font-family: var(--av-font-mono, ui-monospace, monospace);
  font-size: 0.74rem;
  color: var(--text-muted);
  margin-left: 5px;
}
.rc-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-top: 3px;
  font-size: 0.68rem;
}
.rc-base { color: var(--text-muted); }
.rc-raised { color: #d8a63c; }
.rc-decay { color: var(--text-muted); font-style: italic; }
.rc-btn { flex: 0 0 auto; width: auto; margin: 0; min-height: 38px; white-space: nowrap; }
/* An elevated price is amber, so the cost being above base is visible before
   you read the number. */
.rc-btn-raised { border-color: rgba(216, 166, 60, 0.6); color: #d8a63c; }
.rc-btn:disabled { opacity: 0.5; }


/* ==========================================================================
   SHARED UI PRIMITIVES  (see public/js/ui.js)

   Declared last on purpose. `.pixel-icon` sets a 32px base late in this file,
   so a size class has to be a compound `.pixel-icon.is-64` selector to beat it
   on specificity — a bare `.is-64` ties and loses, which is the bug that ate
   three consecutive fixes in v2.6.0.
   ========================================================================== */

/* The size ladder. 16 / 32 / 64 / 128 only: each is an integer scale of the
   authored 64x64 sprite, so every 1-unit rect lands on a whole CSS pixel and
   crispEdges has nothing to round. 40, 80 and 96 have all shipped here at some
   point and all read visibly uneven. */
.pixel-icon.is-16 { width: 16px; height: 16px; border-radius: 2px; }
.pixel-icon.is-32 { width: 32px; height: 32px; }
.pixel-icon.is-64 { width: 64px; height: 64px; }
.pixel-icon.is-128 { width: 128px; height: 128px; }

/* --- Page header ------------------------------------------------------- */
.loa-page-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 2px 0 10px;
  min-width: 0;
}

.loa-page-back {
  width: auto;
  flex: none;
  margin: 0;
  min-height: 36px;
  padding: 4px 12px;
  font-size: 1rem;
  line-height: 1;
}

.loa-page-headings { min-width: 0; flex: 1 1 auto; }

.loa-page-eyebrow {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.loa-page-title {
  margin: 0;
  font-size: 1.15rem;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.loa-page-actions { flex: none; display: flex; gap: 6px; }

/* A tab strip sitting inside a card heading row rather than spanning it. */
.loa-tabs-inline { border-bottom: none; margin-bottom: 0; }
.loa-tabs-inline > button { padding: 5px 9px; font-size: 0.76rem; }

/* --- The catalogue grid ------------------------------------------------ */
/* minmax(150px, 1fr) matches the auto-fill grids already proven elsewhere in
   this file; at 360px that is two columns with a 64px sprite centred in each,
   and it grows to four or five on a tablet without a media query. */
.loa-cat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
  padding: 4px 0 8px;
}

.loa-cat-tile {
  position: relative;
  background: var(--bg-card);
  /* --tile-tier is set per tile from the item's tier band (PixelArt
     .tierBandForLevel) — a catalogue row has no rarity, rarity is rolled per
     copy at purchase, so the tier is the only progression a shop tile can
     honestly show. */
  border: 1px solid color-mix(in srgb, var(--tile-tier, #2a2430) 55%, transparent);
  border-radius: 6px;
  overflow: hidden;
}

.loa-cat-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
  margin: 0;
  padding: 10px 8px 8px;
  background: transparent;
  border: none;
  border-radius: 0;
  text-align: center;
  min-height: 0;
  color: var(--text-primary);
}

/* The placeholder box. The sprite is painted lazily, so the tile has to hold
   its own layout before the SVG arrives or the whole grid reflows as you
   scroll. */
.loa-cat-art {
  margin: 0 auto;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 4px;
}
.loa-cat-art[data-painted] { background: transparent; }

.loa-cat-name {
  font-size: 0.76rem;
  line-height: 1.25;
  overflow-wrap: anywhere;
  color: var(--text-primary);
}

.loa-cat-price { font-size: 0.78rem; color: var(--accent-gold); }

.loa-cat-band {
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tile-tier, var(--text-muted));
}

.loa-cat-owned,
.loa-cat-lock {
  position: absolute;
  top: 6px;
  font-size: 0.62rem;
  padding: 1px 5px;
  border-radius: 8px;
  background: #100d12;
  border: 1px solid #2a2430;
}
.loa-cat-owned { right: 6px; color: var(--accent-gold); }
.loa-cat-lock { left: 6px; color: var(--text-muted); }

.loa-cat-tile.is-locked .loa-cat-art { opacity: 0.45; }
.loa-cat-tile.is-open { border-color: var(--accent-gold); }

.loa-cat-detail {
  padding: 0 8px 10px;
  border-top: 1px solid #2a2430;
}

.loa-cat-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-size: 0.72rem;
  color: var(--text-muted);
  padding: 6px 0;
}

.loa-cat-note { font-size: 0.7rem; margin: 0 0 6px; }

/* --- Profile hero ------------------------------------------------------ */
/* The character, at the top of the size ladder, is the page. Everything that
   used to be a muted <p> beside a 64px glyph is a chip below it now. */
.profile-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  padding-top: 16px;
}

.profile-hero-portrait {
  display: grid;
  place-items: center;
  width: 152px;
  height: 152px;
  border-radius: 50%;
  /* Class-tinted backdrop. --hero-tint is set by the per-class rules below and
     falls back to the accent so an unknown class still reads deliberate. */
  background: radial-gradient(
    circle at 50% 45%,
    color-mix(in srgb, var(--hero-tint, var(--accent-gold)) 34%, transparent) 0%,
    color-mix(in srgb, var(--hero-tint, var(--accent-gold)) 12%, transparent) 58%,
    transparent 72%
  );
}

.profile-hero-warrior { --hero-tint: #c9a050; }
.profile-hero-mage { --hero-tint: #9d4edd; }
.profile-hero-rogue { --hero-tint: #2a9d8f; }

.profile-hero-name {
  margin: 0;
  font-size: 1.35rem;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.profile-hero-chips { justify-content: center; }
.profile-hero-chips a.stat-chip { text-decoration: none; color: inherit; }

@media (max-width: 380px) {
  .profile-hero-portrait { width: 136px; height: 136px; }
}


/* ==========================================================================
   THE LEADERBOARD PODIUM

   A leaderboard exists to display status, and displayed it as a 32px glyph
   beside a number. The top three now get their character at size, wearing the
   cosmetics they paid for. Three only — see the note in app.js on why this is
   not every row.
   ========================================================================== */
.loa-podium {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 6px;
  margin-bottom: 12px;
  padding: 14px 8px 12px;
  background: var(--bg-card);
  border: 1px solid #2a2430;
  border-radius: 6px;
}

.loa-podium-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}

/* DOM order stays 1-2-3 so the markup reads as a ranking; the champion is
   moved to the centre visually, where a podium puts them. */
.loa-podium-1 { order: 2; }
.loa-podium-2 { order: 1; }
.loa-podium-3 { order: 3; }

.loa-podium-portrait {
  display: grid;
  place-items: center;
  width: 92px;
  height: 92px;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 45%,
    color-mix(in srgb, var(--hero-tint, var(--accent-gold)) 30%, transparent) 0%,
    color-mix(in srgb, var(--hero-tint, var(--accent-gold)) 10%, transparent) 58%,
    transparent 72%
  );
}

.loa-podium-slot.is-champion .loa-podium-portrait {
  width: 160px;
  height: 160px;
  background: radial-gradient(
    circle at 50% 45%,
    color-mix(in srgb, var(--hero-tint, var(--accent-gold)) 42%, transparent) 0%,
    color-mix(in srgb, var(--hero-tint, var(--accent-gold)) 15%, transparent) 58%,
    transparent 74%
  );
}

.loa-podium-medal { font-size: 0.78rem; color: var(--accent-gold); }
.loa-podium-slot.is-champion .loa-podium-medal { font-size: 0.92rem; font-weight: 700; }

.loa-podium-name {
  font-size: 0.76rem;
  line-height: 1.2;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.loa-podium-slot.is-champion .loa-podium-name { font-size: 0.92rem; font-weight: 600; }

.loa-podium-metric { font-size: 0.7rem; }
.loa-podium-metric .muted { font-size: 0.7rem; }

/* Three portraits will not fit a narrow phone. The champion keeps its 128px
   sprite; the runners-up drop to 32px — still on the 16/32/64/128 ladder, so
   they stay pixel-exact rather than being scaled to some in-between size. */
@media (max-width: 480px) {
  .loa-podium { gap: 2px; padding: 12px 4px 10px; }
  .loa-podium-portrait { width: 56px; height: 56px; }
  .loa-podium-slot:not(.is-champion) .pixel-icon.is-64 { width: 32px; height: 32px; }
  .loa-podium-slot.is-champion .loa-podium-portrait { width: 148px; height: 148px; }
  .loa-podium-name { font-size: 0.68rem; }
}

/* ==========================================================================
   PROFILE WALL
   ========================================================================== */
.wall-comment { align-items: flex-start; gap: 8px; }
.wall-comment-main { flex: 1 1 auto; min-width: 0; }

.wall-comment-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
}

.wall-comment-time { font-size: 0.7rem; white-space: nowrap; }

.wall-comment-text {
  margin-top: 2px;
  overflow-wrap: anywhere;
}

/* A pinned comment is the owner's pick, so it earns the accent — but as a left
   edge rather than a full border, so a wall with three pinned comments does not
   turn into three boxes. */
.wall-comment.is-pinned {
  border-left: 3px solid var(--accent-gold);
  padding-left: 8px;
  background: rgba(201, 160, 80, 0.06);
  border-radius: 0 4px 4px 0;
}

.wall-pin-mark { font-size: 0.8rem; }

.wall-comment-actions {
  display: flex;
  gap: 4px;
  flex: none;
}

/* --- presence ---------------------------------------------------------- */
/* "Offline" alone said nothing useful. Whether a rival stepped away two minutes
   ago or has not logged in for a week decides whether you attack them. */
.presence-chip.is-online { color: var(--accent-gold); }

/* --- auction: live listing-fee quote ----------------------------------- */
.av-sell-fee-row .av-sell-fee { text-align: left; min-width: 0; }
.av-sell-fee.av-fee-waived { color: var(--accent-gold); }


/* ==========================================================================
   THE EVENT SCREEN — tabbed

   Was seven identical cards in one scroll. Four tabs now, each with its own
   register, built from the `.ev-*` idioms that already existed rather than a
   new set: Fight keeps the ember wash, Your Event is a plain ledger, the
   Leaderboard runs edge to edge, Rewards borrows the gold prize treatment.
   ========================================================================== */

/* The strip card holds the tabs and the always-visible boss readout, so a
   player on the Leaderboard still sees the boss dropping. */
.ev-tabcard { padding-bottom: 8px; }

.ev-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.ev-strip-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev-strip .ev-banner-bar { flex: 1 1 auto; margin: 0; }
.ev-strip-pct { flex: none; font-variant-numeric: tabular-nums; color: var(--accent-gold); }

/* --- Fight: the forge ---------------------------------------------------
   The only tab that keeps the hero's ember wash. The kill log below shares the
   card rather than starting a new one — one surface, two registers — divided
   by the same hairline `.ev-kpi` already uses. */
.ev-tab-fight .ev-killlog { margin-top: 2px; }
.ev-tab-fight > .card + .card {
  margin-top: -4px;
  border-top: 1px solid rgba(201, 160, 80, 0.12);
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

/* --- Your Event: the ledger ---------------------------------------------
   Deliberately unornamented. This tab is read, not tapped, and the KPI bars
   are the whole content — anything else competes with them. */
.ev-tab-you .card { background: var(--bg-card); border-color: #2a2430; }
.ev-tab-you .ev-h4-hint {
  display: block;
  margin-top: 2px;
  font-size: 0.9rem;
  color: var(--accent-gold);
}

/* --- Leaderboard: the board ---------------------------------------------
   Full-bleed rows: the card's side padding is removed so the row rules run
   edge to edge, and your own row sticks to the top of the list while you
   scroll past the people above you. */
.ev-tab-board .card { padding-left: 0; padding-right: 0; }
.ev-tab-board .ev-section-head,
.ev-tab-board .ev-unqual { padding: 0 14px; }
.ev-tab-board .ev-board-row { padding-left: 14px; padding-right: 14px; }
.ev-tab-board .ev-board-me {
  position: sticky;
  top: 0;
  z-index: 1;
  /* Opaque, or the rows scrolling under it show through. */
  background: #1b1720;
}

/* --- Rewards: the vault -------------------------------------------------
   The Cache is a purchase and the prizes are what it is for, so the Cache
   borrows the gold-bordered treatment `.ev-prize-1` already defines rather
   than introducing a third card style. */
.ev-tab-rewards .cache-card {
  border-color: rgba(240, 196, 96, 0.5);
  background: rgba(240, 196, 96, 0.07);
}

/* ===========================================================================
   Signed-out surfaces: login, character select, the beta gate, policy pages.

   These are the only screens a visitor sees before they have an account, and
   the only ones with room for a footer — the signed-in app gives the bottom
   32px to a seven-slot nav bar. Sized for a phone first like everything else.
   =========================================================================== */

.auth-card {
  max-width: 460px;
  margin: 0 auto;
}

.auth-subcard {
  margin-top: 12px;
  background: rgba(0, 0, 0, 0.25);
}

.auth-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.auth-brand h2 {
  margin: 0;
}

/* The beta flag. Deliberately loud: a player who loses progress to a reset
   should never be able to say they were not told, and it is the first thing an
   investor clicking around should see about the build's maturity. */
.beta-badge {
  flex: 0 0 auto;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  padding: 3px 7px;
  border-radius: 3px;
  color: var(--bg-primary);
  background: var(--accent-gold);
  text-transform: uppercase;
}

/* --- footer --------------------------------------------------------------- */

.auth-footer {
  max-width: 460px;
  margin: 18px auto 28px;
  padding: 14px 16px;
  border-top: 1px solid rgba(201, 160, 80, 0.18);
  text-align: center;
}

.auth-footer-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.auth-footer-link {
  color: var(--text-muted);
  font-size: 0.8rem;
  text-decoration: none;
  padding: 2px 0;
}

.auth-footer-link:hover,
.auth-footer-link:focus {
  color: var(--accent-gold);
  text-decoration: underline;
}

.auth-footer-sep {
  color: var(--text-muted);
  opacity: 0.4;
  font-size: 0.8rem;
}

.auth-footer-meta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}

.auth-footer-version {
  font-size: 0.72rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.auth-footer-note {
  margin: 10px 0 0;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--text-muted);
}

.auth-footer-note a {
  color: var(--text-muted);
  text-decoration: underline;
}

/* --- beta agreement gate -------------------------------------------------- */

.beta-summary {
  margin: 10px 0;
  padding-left: 18px;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-primary);
}

.beta-summary li {
  margin-bottom: 6px;
}

/* Alignment matters here: the checkbox must sit against the FIRST line of a
   label that wraps to three or four lines on a phone, not float in the middle
   of the block. */
.beta-accept-row {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 14px 0 10px;
  font-size: 0.82rem;
  line-height: 1.45;
  text-align: left;
  cursor: pointer;
}

.beta-accept-row input {
  margin-top: 2px;
  flex: 0 0 auto;
}

/* --- character select ----------------------------------------------------- */

/* auto-fit rather than a fixed column count: one character centres, two sit
   side by side, three wrap to a second row on a narrow phone without any
   breakpoint. */
.character-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin: 12px 0;
}

.character-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
  padding: 14px 10px;
  border: 1px solid rgba(201, 160, 80, 0.25);
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.25);
  color: var(--text-primary);
  cursor: pointer;
  text-align: center;
}

.character-card:hover:not(:disabled),
.character-card:focus-visible:not(:disabled) {
  border-color: var(--accent-gold);
  background: rgba(201, 160, 80, 0.08);
}

.character-card:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.character-name {
  font-weight: 600;
  font-size: 0.95rem;
  /* A 20-character name must not widen the grid column. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.character-meta {
  font-size: 0.72rem;
  color: var(--text-muted);
  text-transform: capitalize;
}

.character-suspended-tag {
  margin-top: 4px;
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--health-to);
}

.character-card-new {
  border-style: dashed;
  justify-content: center;
}

/* The login-screen variant: same slot, but it explains why it cannot be used
   rather than offering a button that could not be honoured (gems belong to a
   character, and nobody is signed in yet). Not a <button>, so it must not
   look or behave like one. */
.character-card-note {
  cursor: default;
  opacity: 0.6;
}

.character-new-plus {
  font-size: 1.6rem;
  line-height: 1;
  color: var(--accent-gold);
}

/* --- policy pages --------------------------------------------------------- */

.policy-card {
  max-width: 720px;
  margin: 0 auto;
}

.policy-draft-banner {
  margin: 10px 0 14px;
  padding: 10px 12px;
  border-left: 3px solid var(--accent-gold);
  border-radius: 3px;
  background: rgba(201, 160, 80, 0.1);
  font-size: 0.8rem;
  line-height: 1.5;
}

.policy-updated {
  font-size: 0.75rem;
  margin-bottom: 16px;
}

.policy-section {
  margin-bottom: 18px;
}

.policy-section h3 {
  margin: 0 0 6px;
  font-size: 0.95rem;
  color: var(--accent-gold);
}

.policy-section p {
  margin: 0 0 8px;
  font-size: 0.86rem;
  line-height: 1.6;
}

.policy-other {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid rgba(201, 160, 80, 0.18);
}

.policy-other a {
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* ===========================================================================
   INVENTORY — the category grid and the per-category view.

   Replaces the old `.inv-filters` / `.inv-slot-filters` / `.inv-sorts` chip
   strips, which put up to seventeen controls above the first item on a phone.
   =========================================================================== */

.inv-head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.inv-head-title {
  font-weight: 600;
  letter-spacing: 0.02em;
}

.inv-head-cap {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.inv-head-hint {
  margin: 8px 0 0;
  font-size: 0.78rem;
}

/* THREE COLUMNS, fixed. There are exactly nine categories, so 3x3 fills
   completely — auto-fit gave four across at the app's 480px width and left a
   single orphan tile on a third row, which reads as a mistake rather than a
   grid. A fixed count is safe here precisely because the category list is
   fixed; if it ever stops being nine, revisit this rather than the tile size. */
.inv-cat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 10px;
}

.inv-cat-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 100%;
  /* A grid item defaults to min-width:auto, so it refuses to shrink below its
     widest unbreakable content — the equipped item's name. That pushed the
     third column past the container and clipped it. Overflow-hidden on the
     name alone does not help: the TILE is what was being sized. */
  min-width: 0;
  padding: 10px 6px 8px;
  border: 1px solid rgba(201, 160, 80, 0.25);
  border-radius: 6px;
  background: var(--bg-card);
  color: var(--text-primary);
  cursor: pointer;
  text-align: center;
}

.inv-cat-tile:hover,
.inv-cat-tile:focus-visible {
  border-color: var(--accent-gold);
  background: rgba(201, 160, 80, 0.08);
}

/* An empty category is still shown — you learn where boots live once, and a
   grid that changes shape as you gain and lose items is harder to aim at than
   one that does not. It is just dimmed. */
.inv-cat-tile-empty {
  opacity: 0.45;
}

.inv-cat-icon {
  width: 48px;
  height: 48px;
}

.inv-cat-label {
  font-size: 0.76rem;
  font-weight: 600;
  line-height: 1.2;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The equipped item's name. Clamped hard: a 20-character item name must not
   make one tile taller than its neighbours and break the grid's rhythm. */
.inv-cat-worn {
  font-size: 0.66rem;
  line-height: 1.2;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inv-cat-count {
  position: absolute;
  top: 4px;
  right: 6px;
  min-width: 18px;
  padding: 1px 4px;
  border-radius: 9px;
  background: rgba(0, 0, 0, 0.45);
  color: var(--accent-gold);
  font-size: 0.66rem;
  font-variant-numeric: tabular-nums;
}

/* --- one category ---------------------------------------------------------- */

.inv-cat-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.inv-back {
  width: auto;
  flex: 0 0 auto;
  padding: 4px 10px;
  font-size: 1rem;
  line-height: 1;
}

.inv-cat-header-icon {
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
}

.inv-cat-header-title {
  font-weight: 600;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inv-cat-header-count {
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* --- Companions ------------------------------------------------------------
   A pet is not gear, so it does not reuse the equipment grid's density. The
   card leads with the passive, because that is the thing a player chooses on. */
.companion-card .gear-tile-title { min-width: 0; }
.companion-passive {
  font-size: 13px;
  color: var(--ink-soft, #666);
  margin: 3px 0;
  line-height: 1.45;
}
.companion-bonuses,
.effect-line {
  font-size: 12.5px;
  color: var(--good, #2c6446);
  margin-top: 3px;
}

/* The Shop and the Auction describe an item the same way the Inventory and the
   Companions page already did. Same green as .effect-line on purpose: a lever
   should read identically wherever a player meets it, so what they compared in
   the Shop is recognisably the same thing they equip. */
.loa-cat-effects,
.av-card-effects {
  font-size: 12.5px;
  color: var(--good, #2c6446);
  margin-top: 4px;
  line-height: 1.35;
}

.loa-cat-slot {
  font-size: 12px;
  margin-bottom: 2px;
}

.loa-cat-passive,
.av-card-passive {
  margin-top: 5px;
  font-size: 12.5px;
  line-height: 1.35;
}

.loa-cat-passive-name,
.av-card-passive {
  display: block;
  font-weight: 600;
  color: var(--accent-gold, #c9a050);
}
.companion-equipped { border-color: var(--good, #2c6446); }
.companion-current {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  margin-top: 10px;
  padding: 12px;
  border-radius: 8px;
  background: var(--sunk, rgba(0, 0, 0, 0.04));
}
.companion-current .pixel-icon { width: 64px; height: 64px; flex: 0 0 64px; }
/* The companion stands beside its owner on the profile hero, smaller than the
   character so the character still reads as the subject. */
/* The companion stands BESIDE its owner, on the baseline of the portrait
   circle. It used to be a child of .profile-hero-portrait, which is a grid with
   place-items:center — so it was centred into the same cell as the character
   and overflowed downward across the player's name. `align-self: flex-end` was
   a flex property on a grid item and had never taken effect. */
.profile-hero-stage {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 4px;
}

.profile-companion {
  width: 72px;
  height: 72px;
  /* Sits low against the portrait so the pair reads as standing together,
     and tucks into the circle's transparent edge rather than beside it. */
  margin-bottom: 14px;
  margin-left: -10px;
  flex: 0 0 auto;
}

@media (max-width: 380px) {
  .profile-companion { width: 56px; height: 56px; margin-bottom: 10px; }
}


/* ==========================================================================
   THE STATS SCREEN
   A stat with no attribution is not actionable, so every row carries the
   things that granted it. Sources are colour-coded by KIND rather than
   labelled, because the label is already the item's name and repeating
   "pet"/"gear" beside it doubles the text for no information.
   ========================================================================== */
.stat-row {
  padding: 9px 0;
  border-bottom: 1px solid var(--border, #2a2a2a);
}
.stat-row:last-child { border-bottom: 0; }
/* A lever nothing grants is kept visible but recessive: absence is information
   (it is how you notice you have no armour penetration at all), but it must
   not compete with the stats you actually have. */
.stat-row.is-zero { opacity: 0.55; }

.stat-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.stat-row-label { font-size: 14px; }
.stat-row-value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}
.stat-row-value.at-cap { color: var(--accent-gold, #c9a050); }
.stat-cap {
  font-weight: 400;
  font-size: 12px;
  opacity: 0.7;
}
.stat-translate {
  display: block;
  font-size: 12.5px;
  color: var(--good, #2c6446);
  margin-top: 2px;
}

.stat-src-line {
  margin-top: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  font-size: 11.5px;
}
.stat-src {
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--border, #2a2a2a);
  white-space: nowrap;
}
.stat-src-pet { border-color: #4a72a8; }
.stat-src-gear { border-color: #6c7b8a; }
.stat-src-skill { border-color: #7b52c9; }
.stat-src-awakening { border-color: #c9a050; }
.stat-src-set { border-color: #c0392b; }

.stat-foot { margin: 0; font-size: 12.5px; line-height: 1.45; }


/* The running build, in Settings. Deliberately small and quiet — it is a
   diagnostic, not a feature — but reachable without devtools, which is the
   whole point on a phone. */
.settings-build {
  margin: 4px 0 0;
  font-size: 11.5px;
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}
