/* Rift of the Hollow Crown
   Portrait-first mobile layout, centred in a device frame on wide screens. */

@import url('assets/fonts.css');

/* Tokens live in styles/tokens.css, which is the single source. This file
   used to carry a second :root that loaded last and therefore won — which
   is why the rarity ramp on screen was grey/blue/purple/orange/pink rather
   than the documented grey/green/blue/purple/gold. Removed. */

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: #05070c;
  color: var(--text-cream);
  font: 15px/1.5 var(--font-body);
  overscroll-behavior: none;
}

body {
  display: flex;
  justify-content: center;
  align-items: stretch;
}

img { max-width: 100%; display: block; }
button { font: inherit; color: inherit; cursor: pointer; }
h1, h2, h3, h4 {
  margin: 0; font-weight: 700; letter-spacing: 0.01em;
  font-family: var(--font-display);
}
p { margin: 0 0 0.6em; }

/* ---------------------------------------------------------------- boot ---- */
.boot {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: radial-gradient(ellipse at 50% 30%, #1b2440 0%, #05070c 70%);
  transition: opacity 0.45s ease;
}
.boot.gone { opacity: 0; pointer-events: none; }
.boot-inner { text-align: center; padding: 2rem; max-width: 460px; }
.boot-title {
  font-size: clamp(26px, 7vw, 40px); line-height: 1.08; letter-spacing: 0.12em;
  color: #e8ecf7; text-shadow: 0 0 26px rgba(120, 170, 255, 0.35);
}
.boot-title span {
  background: linear-gradient(180deg, #ffe4a8, #d79a35);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.boot-sub { color: var(--text-muted); margin-top: 0.9rem; font-size: 13px; letter-spacing: 0.04em; }
.boot-bar {
  margin: 1.6rem auto 0.6rem; width: min(280px, 70vw); height: 5px;
  background: rgba(255, 255, 255, 0.08); border-radius: 99px; overflow: hidden;
}
.boot-bar-fill {
  height: 100%; width: 10%; border-radius: 99px;
  background: linear-gradient(90deg, #5ea9ff, #ffd98a);
  transition: width 0.3s ease;
}
.boot-status { color: var(--text-muted); font-size: 12px; min-height: 1.2em; }

/* ----------------------------------------------------------------- app ---- */
.app {
  position: relative;
  display: flex; flex-direction: column;
  width: 100%; max-width: 520px; height: 100dvh;
  background: var(--ink-900);
  box-shadow: 0 0 80px rgba(0, 0, 0, 0.8);
  overflow: hidden;
}

.topbar {
  flex: 0 0 auto; min-height: var(--topbar-h);
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px calc(8px);
  padding-top: max(8px, env(safe-area-inset-top));
  background: linear-gradient(180deg, rgba(14, 19, 31, 0.98), rgba(14, 19, 31, 0.82));
  border-bottom: 1px solid var(--line);
  z-index: 20;
}

.screen {
  flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

.navbar {
  flex: 0 0 auto; display: grid; grid-template-columns: repeat(5, 1fr);
  min-height: var(--navbar-h);
  padding-bottom: env(safe-area-inset-bottom);
  background: linear-gradient(0deg, #0b0f19, #121828);
  border-top: 1px solid var(--line);
  z-index: 20;
}
.nav-btn {
  background: none; border: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px;
  color: var(--text-muted); font-size: 10.5px; letter-spacing: 0.04em;
  padding: 6px 2px; position: relative; transition: color 0.15s;
}
.nav-btn .nav-ico { font-size: 20px; line-height: 1; filter: grayscale(0.4) opacity(0.8); }
.nav-btn.active { color: var(--gold-300); }
.nav-btn.active .nav-ico { filter: none; transform: translateY(-1px); }
.nav-btn.active::after {
  content: ''; position: absolute; top: 0; left: 22%; right: 22%; height: 2px;
  background: linear-gradient(90deg, transparent, var(--gold-300), transparent);
}
.nav-badge {
  position: absolute; top: 5px; right: calc(50% - 20px);
  min-width: 16px; height: 16px; padding: 0 4px;
  background: var(--alert); color: #fff; font-size: 10px; font-weight: 700;
  border-radius: 99px; display: grid; place-items: center;
}

/* --------------------------------------------------------- top bar bits --- */
.avatar-btn {
  display: flex; align-items: center; gap: 7px; background: var(--panel-veil);
  border: 1px solid var(--line); border-radius: 99px; padding: 3px 10px 3px 3px;
}
.avatar {
  width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  background: linear-gradient(145deg, #3a4straight, #2a3450);
  background: linear-gradient(145deg, #4a5878, #273049);
  font-size: 14px; border: 1px solid var(--line-strong);
}
.avatar-meta { display: flex; flex-direction: column; line-height: 1.1; }
.avatar-name { font-size: 11.5px; font-weight: 600; }
.avatar-lv { font-size: 10px; color: var(--gold-300); }

.cur-strip { margin-left: auto; display: flex; gap: 6px; align-items: center; }
.cur-chip {
  display: flex; align-items: center; gap: 4px;
  background: rgba(10, 14, 24, 0.7); border: 1px solid var(--line);
  border-radius: 99px; padding: 3px 9px 3px 4px; font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
button.cur-chip:hover { border-color: var(--line-strong); }
.cur-icon { width: 18px; height: 18px; object-fit: contain; }
.cur-glyph { display: grid; place-items: center; font-size: 13px; }
.cur-amt { color: #f2f5fb; font-variant-numeric: tabular-nums; }

/* Any figure the player compares — damage, power, costs, stats — uses the
   body face with lining tabular numerals so columns line up and digits do
   not shift width as values change. */
.power, .stat-value, .cur-amt, .battle-clock, .score-table td, .bar-label {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum' 1, 'lnum' 1;
}

/* ---------------------------------------------------------- generic ui ---- */
.pad { padding: 12px; }
.stack { display: flex; flex-direction: column; gap: 12px; }
.row { display: flex; align-items: center; gap: 8px; }
.row.wrap { flex-wrap: wrap; }
.spacer { flex: 1; }
.muted { color: var(--text-muted); }
.center { text-align: center; }
.small { font-size: 12px; }
.tiny { font-size: 11px; }

.panel {
  background: var(--panel-veil); border: 1px solid var(--line);
  border-radius: var(--rad-md); padding: 12px; backdrop-filter: blur(8px);
}
.panel.tight { padding: 9px; }

.section-title {
  display: flex; align-items: center; gap: 8px; margin: 2px 0 2px;
}
.section-title h2 {
  font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--gold-300);
}
/* An icon and the number beside it are one token. Left as flow content the
   icon takes its own line and the count drops under it, which reads as two
   facts instead of one. */
.section-title > *:last-child {
  margin-left: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  white-space: nowrap;
}

/* Painted icons sit on the text baseline when they are used inline. */
.gi-painted { vertical-align: -0.18em; }

.btn {
  border: 1px solid var(--line-strong); background: linear-gradient(180deg, #2b3450, #1b2236);
  color: var(--text-cream); border-radius: 10px; padding: 9px 14px; font-weight: 600; font-size: 14px;
  min-height: var(--tap); min-width: var(--tap);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  transition: transform 0.08s, filter 0.15s, opacity 0.15s;
}
.btn:hover:not(:disabled) { filter: brightness(1.16); }
.btn:active:not(:disabled) { transform: translateY(1px) scale(0.99); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn.primary {
  background: linear-gradient(180deg, #ffd98a, #d4972c);
  border-color: #ffe9bd; color: #2a1c05; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
}
.btn.danger { background: linear-gradient(180deg, #d4544c, #8c2a26); border-color: #ff9d97; }
.btn.good { background: linear-gradient(180deg, #46c46b, #227a3c); border-color: #9af0b4; color: #052010; }
.btn.ghost { background: rgba(255, 255, 255, 0.04); }
.btn.block { width: 100%; }
.btn.lg { padding: 13px 18px; font-size: 16px; }
/* `sm` stays visually small but keeps a full-size hit area: the padding
   grows, the type does not. */
.btn.sm { padding: 4px 12px; font-size: 12.5px; border-radius: 8px; min-height: var(--tap); }

.icon-btn {
  background: rgba(255, 255, 255, 0.06); border: 1px solid var(--line);
  width: var(--tap); height: var(--tap); border-radius: 10px;
  display: grid; place-items: center; font-size: 17px;
}
.icon-btn:hover { background: rgba(255, 255, 255, 0.12); }

/* The legacy button classes get the same painted plates as the kit ones.
   Once the art is on, the CSS face has to go: the gradient, the drawn edge
   and the rounding all belong to the plate now, and leaving them on
   double-draws every one of them. */
.btn.is-painted, .icon-btn.is-painted {
  background: none;
  border-radius: 0;
}
/* The painted frame IS the border, so the content box is already inset by
   it. Leaving the CSS padding on top of that shifted the glyph by exactly
   its horizontal padding — the back chevron sat 6px right of the centre
   of its own medallion. */
.icon-btn.is-painted { padding: 0; }
.btn.is-painted:disabled, .icon-btn.is-painted:disabled { filter: grayscale(0.6); }

.pill {
  display: inline-flex; align-items: center; gap: 4px;
  background: rgba(255, 255, 255, 0.07); border: 1px solid var(--line);
  border-radius: 99px; padding: 2px 9px; font-size: 11px; font-weight: 600;
}
.pill.gold { color: var(--gold-300); border-color: rgba(240, 192, 112, 0.4); }
.pill.good { color: var(--success); border-color: rgba(73, 214, 111, 0.4); }
.pill.bad { color: var(--alert); border-color: rgba(229, 87, 79, 0.4); }
.pill.warn { color: var(--warn); border-color: rgba(255, 182, 72, 0.4); }

.bar {
  position: relative; height: 8px; border-radius: 99px; overflow: hidden;
  background: rgba(255, 255, 255, 0.09); border: 1px solid rgba(0, 0, 0, 0.4);
}
.bar-fill {
  height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, #5ea9ff, #8fd4ff);
  transition: width 0.4s ease;
}
.bar.xp .bar-fill { background: linear-gradient(90deg, #ffd98a, #d4972c); }
.bar.hp .bar-fill { background: linear-gradient(90deg, #49d66f, #2c9c4a); }
.bar.tall { height: 14px; }
/* Tall enough for the text it is carrying. `.bar` clips its overflow so the
   fill keeps its rounded ends, which means a 10px label in an 8px bar is
   shown as a horizontal slice through the middle of its own letters. */
.bar.has-label { height: 15px; }
.bar-label {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 10px; font-weight: 700; line-height: 1; text-shadow: 0 1px 2px #000;
}

.empty { padding: 28px 16px; text-align: center; color: var(--text-muted); }

.tabs {
  display: flex; gap: 6px; overflow-x: auto; padding: 2px; scrollbar-width: none;
  /* The tab strip is a flex item in a `.stack`, and a scroll container's
     automatic minimum size is zero rather than its content. So on any tab
     whose list overflowed — Achievements, mostly — the siblings squeezed the
     strip and the pills were served sliced in half. */
  flex: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  flex: 0 0 auto; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--line);
  border-radius: 99px; padding: 4px 15px; font-size: 12.5px; font-weight: 600; color: var(--text-muted);
  min-height: var(--tap);
  display: inline-flex; align-items: center;
}
.tab.active {
  background: linear-gradient(180deg, #3a4straight, #222c45);
  background: linear-gradient(180deg, #38456a, #222c45);
  color: var(--text-cream); border-color: var(--line-strong);
}

/* -------------------------------------------------------------- heroes ---- */
.portrait {
  position: relative; border-radius: 10px; overflow: hidden;
  /* The rarity's own bed, not a fixed slate. `.rar-<letter>` is already
     on this element and bound in tokens.css; the fallback is for the
     handful of callers that draw a portrait with no hero behind it. */
  background: var(--rar-bed, linear-gradient(160deg, #1a2235, #0d1220));
  border: 2px solid var(--rar, #9aa6b2);
  box-shadow: inset 0 0 18px rgba(0, 0, 0, 0.6);
  aspect-ratio: 1;
}
/* The art sits over a saturated field now, so it carries its own top
   scrim rather than depending on the field being dark. */
.portrait-img { position: relative; z-index: 1; }
.portrait-img { width: 100%; height: 100%; object-fit: cover; }
.portrait-sm { width: 38px; }
.portrait-md { width: 100%; }
.portrait-lg { width: 100%; }
.portrait.rar-legendary { box-shadow: inset 0 0 18px rgba(0,0,0,.6), 0 0 12px rgba(240,169,59,.35); }
.portrait.rar-mythic { box-shadow: inset 0 0 18px rgba(0,0,0,.6), 0 0 14px rgba(255,95,141,.45); }
.portrait-level {
  position: absolute; left: 3px; bottom: 3px; font-size: 10px; font-weight: 700;
  background: rgba(5, 8, 14, 0.82); padding: 1px 5px; border-radius: 5px;
}
.portrait-shards {
  position: absolute; right: 3px; bottom: 3px; font-size: 10px; font-weight: 700;
  background: rgba(5, 8, 14, 0.82); padding: 1px 5px; border-radius: 5px; color: var(--f-void);
  display: inline-flex; align-items: center; gap: 3px;
}
.portrait-stars { position: absolute; left: 0; right: 0; bottom: 16px; text-align: center; }
.portrait-faction { position: absolute; top: 3px; right: 3px; }
.fac-dot {
  display: block; width: 10px; height: 10px; border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.6);
}
/* Illustrated faction crest. The coloured ring keeps the faction readable
   even where the emblem itself is too small to make out. */
.fac-mark {
  width: 18px; height: 18px; border-radius: 50%;
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, .55);
}
.portrait-sm .fac-mark, .portrait-sm ~ .fac-mark { width: 13px; height: 13px; }
.fac-chip {
  display: inline-flex; align-items: center; gap: 5px;
}
.fac-chip .fac-mark { width: 20px; height: 20px; }
/* The dot the crest falls back to before its image decodes. One class per
   faction the game actually has — these named the old elemental set long
   after the factions became peoples, so four of the six painted no colour
   at all. */
.fac-human { background: var(--f-human); }
.fac-beast { background: var(--f-beast); }
.fac-elf { background: var(--f-elf); }
.fac-covenant { background: var(--f-covenant); }
.fac-god { background: var(--f-god); }
.fac-void { background: var(--f-void); }

.stars { display: inline-flex; gap: 1px; }
.star { font-style: normal; font-size: 9px; color: #ffd166; text-shadow: 0 1px 2px #000; }

.hero-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 9px;
}
.hero-card {
  position: relative; display: flex; flex-direction: column; gap: 5px;
  /* The rarity's bed, like every other surface that shows a hero. A
     fixed slate-blue here meant the collection card and the roster tile
     disagreed about what colour an SSR is. */
  background: var(--rar-bed, linear-gradient(180deg, rgba(34, 43, 67, 0.9), rgba(18, 23, 37, 0.9)));
  border: 1px solid var(--line); border-top: 2px solid var(--rar, #9aa6b2);
  border-radius: 12px; padding: 7px; text-align: left;
  transition: transform 0.1s, border-color 0.15s;
}
.hero-card:hover { transform: translateY(-2px); border-color: var(--line-strong); }
.hero-card.selected { border-color: var(--gold-300); box-shadow: 0 0 0 2px rgba(240, 192, 112, 0.35); }

/* Picking one hero out of a set, inside a sheet: the starter SSR gift and
   the faction-shard exchange. Narrower than .hero-grid because it lives in
   a modal rather than a full screen, and the choice is the only thing in
   it — six legible faces beat twelve cramped ones. */
.choice-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px;
  max-height: 46vh; overflow-y: auto;
}
.choice-card {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: rgba(24, 31, 50, 0.8); border: 1px solid var(--line);
  border-radius: 10px; padding: 7px 5px; text-align: center; color: inherit;
  transition: transform 0.1s, border-color 0.15s;
}
.choice-card:hover { transform: translateY(-2px); border-color: var(--line-strong); }
.choice-card.selected {
  border-color: var(--gold-300); box-shadow: 0 0 0 2px rgba(240, 192, 112, 0.35);
}
.choice-name { font-size: 11px; font-weight: 600; line-height: 1.2; }
.hero-card.locked { opacity: 0.62; filter: saturate(0.55); }
.hero-card-name {
  font-size: 11.5px; font-weight: 600; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hero-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 4px; }
.role-chip {
  font-size: 9.5px; text-transform: capitalize; color: var(--text-muted);
  background: rgba(255, 255, 255, 0.06); border-radius: 5px; padding: 1px 5px;
}
.power { font-size: 10.5px; color: var(--gold-300); font-weight: 700; font-variant-numeric: tabular-nums; }
.card-badge {
  position: absolute; top: -6px; right: -4px; background: var(--alert); color: #fff;
  font-size: 9.5px; font-weight: 700; border-radius: 99px; padding: 1px 6px;
}
.card-lock {
  position: absolute; inset: auto 7px 36px 7px; text-align: center; font-size: 10px;
  background: rgba(5, 8, 14, 0.8); border-radius: 5px; padding: 1px 0; color: var(--f-void);
  display: flex; align-items: center; justify-content: center; gap: 3px;
}

/* A card whose AI art has not been generated yet still needs to look
   deliberate rather than empty. */
.art-pending {
  background-image:
    radial-gradient(ellipse at 20% 0%, rgba(120, 150, 255, 0.22), transparent 60%),
    radial-gradient(ellipse at 90% 100%, rgba(255, 180, 90, 0.16), transparent 55%),
    linear-gradient(140deg, #222c47, #141a29);
  background-size: cover;
}
.art-pending::before {
  content: ''; position: absolute; inset: 0;
  background-image:
    repeating-linear-gradient(115deg, rgba(255,255,255,.035) 0 2px, transparent 2px 9px);
}

/* ------------------------------------------------------------------ hub --- */
.hub {
  position: relative; min-height: 100%;
  display: flex; flex-direction: column;
}
.hub-bg {
  position: absolute; inset: 0; background-size: cover; background-position: center 30%;
  opacity: 0.85;
}
.hub-bg::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8,11,18,.55) 0%, rgba(8,11,18,.1) 35%, rgba(8,11,18,.94) 100%);
}
.hub-fallback {
  background: radial-gradient(ellipse at 50% 20%, #24304f 0%, #0b0f18 70%);
}
.hub-content { position: relative; z-index: 1; display: flex; flex-direction: column; flex: 1; padding: 10px; gap: 10px; }
.hub-hero {
  flex: 1 1 auto; min-height: 180px; display: grid; place-items: end center; position: relative;
}
.hub-hero img { max-height: 46vh; width: auto; filter: drop-shadow(0 18px 24px rgba(0,0,0,.7)); }
.hub-hero-name {
  position: absolute; left: 0; bottom: 2px; font-size: 12px; color: var(--text-muted);
}
.hub-cta { display: flex; gap: 8px; }
.afk-card {
  display: flex; align-items: center; gap: 10px;
}
.afk-icon { font-size: 26px; }
.afk-lines { flex: 1; min-width: 0; }
.afk-title { font-size: 13px; font-weight: 700; }
.afk-detail { font-size: 11.5px; color: var(--text-muted); }

.quick-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.quick-btn {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: var(--panel-veil); border: 1px solid var(--line); border-radius: 12px;
  padding: 9px 4px; font-size: 10.5px; color: var(--text-cream); position: relative;
  min-height: calc(var(--tap) + 14px);
}
.quick-btn .q-ico { font-size: 19px; }
.quick-btn:hover { border-color: var(--line-strong); }

/* ------------------------------------------------------------- world map -- */
.chapter-card {
  position: relative; border-radius: var(--rad-md); overflow: hidden;
  border: 1px solid var(--line); min-height: 108px; display: flex; align-items: flex-end;
  background: #141a28; text-align: left; width: 100%; padding: 0;
}
.chapter-card .cc-bg { position: absolute; inset: 0; background-size: cover; background-position: center; }
.chapter-card .cc-shade {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(8,11,18,.92) 10%, rgba(8,11,18,.35) 70%, rgba(8,11,18,.6));
}
.chapter-card .cc-body { position: relative; padding: 11px; width: 100%; }
.chapter-card.locked { filter: grayscale(0.8) brightness(0.65); }
.cc-name { font-size: 15px; font-weight: 700; }
.cc-blurb { font-size: 11.5px; color: var(--text-muted); margin: 2px 0 6px; }

.stage-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: var(--panel-veil); border: 1px solid var(--line); border-radius: 12px; padding: 9px 11px;
  min-height: var(--tap);
}
.stage-row.locked { opacity: 0.5; }
.stage-row.cleared { border-color: rgba(73, 214, 111, 0.35); }
.stage-row.boss { border-color: rgba(255, 95, 141, 0.45); background: rgba(50, 20, 40, 0.5); }
.stage-row.elite { border-color: rgba(169, 112, 232, 0.4); }
.stage-badge {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  font-size: 13px; font-weight: 800; background: rgba(0, 0, 0, 0.4); border: 1px solid var(--line);
}
.stage-main { flex: 1; min-width: 0; }
.stage-name { font-size: 13.5px; font-weight: 600; }
.stage-meta { font-size: 11px; color: var(--text-muted); }

/* --------------------------------------------------------------- battle --- */
.battle-screen {
  position: absolute; inset: 0; z-index: 40; display: flex; flex-direction: column;
  background: #05070c;
}
.battle-canvas {
  flex: 1 1 auto; width: 100%; height: 100%; display: block; touch-action: none;
  /* `min-height: 0` so the flex item may shrink below its content box. A
     canvas carries an intrinsic size from its width/height attributes, and
     the renderer writes those every resize — without this the element and
     the attribute push each other larger on every pass. */
  min-height: 0;
}
.battle-top {
  position: absolute; top: 0; left: 0; right: 0; padding: 8px 10px;
  padding-top: max(8px, env(safe-area-inset-top));
  display: flex; align-items: center; gap: 8px; z-index: 2;
  background: linear-gradient(180deg, rgba(5,7,12,.85), transparent);
}
.battle-title { font-size: 12.5px; font-weight: 600; }
.battle-clock {
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 14px;
  background: rgba(5, 8, 14, 0.7); border: 1px solid var(--line); border-radius: 8px; padding: 2px 8px;
}
.battle-clock.warn { color: var(--warn); border-color: rgba(255,182,72,.5); }
.battle-clock.danger { color: var(--alert); border-color: rgba(229,87,79,.6); }
.battle-bottom {
  position: absolute; bottom: 0; left: 0; right: 0; z-index: 2;
  padding: 8px 10px; padding-bottom: max(8px, env(safe-area-inset-bottom));
  display: flex; gap: 8px; align-items: center;
  background: linear-gradient(0deg, rgba(5,7,12,.9), transparent);
}
.team-bar { position: absolute; left: 10px; right: 10px; z-index: 2; display: flex; gap: 4px; }
.team-bar.top { top: 46px; }
.team-strip { flex: 1; height: 4px; border-radius: 99px; background: rgba(255,255,255,.12); overflow: hidden; }
.team-strip i { display: block; height: 100%; background: var(--success); }
.team-bar.enemy .team-strip i { background: var(--alert); }

/* -------------------------------------------------------------- results --- */
.result-hero { text-align: center; padding: 18px 12px 8px; }
.result-title { font-size: 32px; font-weight: 800; letter-spacing: 0.08em; }
.result-title.win { color: var(--gold-300); text-shadow: 0 0 24px rgba(240,192,112,.45); }
.result-title.lose { color: var(--alert); }
.mvp-card { display: flex; gap: 10px; align-items: center; }
.mvp-card .portrait { width: 58px; flex: 0 0 58px; }

.score-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.score-table th {
  text-align: right; color: var(--text-muted); font-weight: 600; font-size: 10.5px;
  text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 5px;
}
.score-table th:first-child, .score-table td:first-child { text-align: left; }
.score-table td { padding: 5px; border-top: 1px solid var(--line); text-align: right; font-variant-numeric: tabular-nums; }
.score-table tr.dead td { opacity: 0.55; }

/* -------------------------------------------------------------- summon ---- */
.banner-card {
  position: relative; border-radius: var(--rad-md); overflow: hidden; min-height: 150px;
  border: 1px solid var(--line-strong); display: flex; align-items: flex-end;
}
.banner-card .bc-bg { position: absolute; inset: 0; background-size: cover; background-position: center; }
.banner-card .bc-shade { position: absolute; inset: 0; background: linear-gradient(0deg, rgba(8,11,18,.95), rgba(8,11,18,.15)); }
.banner-card .bc-body { position: relative; padding: 12px; width: 100%; }

.pull-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 7px; }
.pull-cell {
  position: relative; border-radius: 10px; overflow: hidden; border: 2px solid var(--rar);
  animation: pop 0.35s ease backwards;
}
@keyframes pop { from { transform: scale(0.6); opacity: 0; } }
.pull-cell .tag {
  position: absolute; left: 0; right: 0; bottom: 0; font-size: 9px; text-align: center;
  background: rgba(5, 8, 14, 0.85); padding: 1px 0;
}
.pull-cell.new .tag { background: var(--success); color: #042; font-weight: 800; }

.rates-table { width: 100%; font-size: 12px; border-collapse: collapse; }
.rates-table td { padding: 4px 6px; border-top: 1px solid var(--line); }
.rates-table td:last-child { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------- hero detail --- */
.detail-top { position: relative; min-height: 300px; display: flex; align-items: flex-end; }
/* The faction shrine behind the hero. Held well back — it is a room, and the
   hero has to stay the brightest thing in it — and faded out at the bottom so
   it hands over to `.detail-shade` rather than ending on a line. */
.detail-plate {
  position: absolute; inset: 0;
  background-size: cover; background-position: center 35%;
  opacity: 0.5; filter: saturate(0.85) brightness(0.8);
  -webkit-mask-image: linear-gradient(0deg, transparent 4%, #000 46%);
  mask-image: linear-gradient(0deg, transparent 4%, #000 46%);
}
.detail-art {
  position: absolute; inset: 0; display: grid; place-items: center center; overflow: hidden;
  padding: 6px 0 56px;
}
.detail-art img {
  max-height: 100%; width: auto; object-fit: contain;
  filter: drop-shadow(0 14px 22px rgba(0,0,0,.7));
}
.detail-shade { position: absolute; inset: 0; background: linear-gradient(0deg, rgba(8,11,18,1) 2%, rgba(8,11,18,0) 55%); }
.detail-head { position: relative; padding: 10px 12px; width: 100%; }
.detail-name { font-size: 20px; font-weight: 800; }
.detail-sub { font-size: 12px; color: var(--text-muted); }

.skill-card { display: flex; gap: 10px; }
.skill-ico {
  width: 42px; height: 42px; flex: 0 0 42px; border-radius: 10px; display: grid; place-items: center;
  font-size: 19px; border: 1px solid var(--line-strong);
  background: linear-gradient(145deg, #2d3a5c, #19203200);
}
.skill-ico.ult { background: linear-gradient(145deg, #6b4f12, #2b2008); border-color: var(--gold-300); }
.skill-ico.passive { background: linear-gradient(145deg, #24495c, #12222b); }
.skill-name { font-size: 13.5px; font-weight: 700; }
.skill-desc { font-size: 11.5px; color: var(--text-muted); }

.stat-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,.05); }
.stat-label { color: var(--text-muted); width: 46px; font-size: 11px; letter-spacing: .06em; }
.stat-value { font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-delta { margin-left: auto; font-size: 11px; font-weight: 700; }
.stat-delta.up { color: var(--success); }
.stat-delta.down { color: var(--alert); }

.gear-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
.gear-slot {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; padding: 7px 3px; min-height: calc(var(--tap) + 24px);
  background: var(--panel-veil); border: 1px solid var(--line); border-radius: 10px; font-size: 10px;
}
/* Once the painted socket is on, the CSS rounding has to go: a border-image
   does not follow a radius, so the two together leave the background's round
   corner poking out past the painted one. */
.gear-slot[data-frame] { border-radius: 0; background: rgba(8, 11, 18, 0.55); }
.gear-slot img { width: 26px; height: 26px; object-fit: contain; }
.gear-slot .g-glyph { font-size: 20px; }
.gear-tier { font-weight: 700; color: var(--gold-300); }

/* ----------------------------------------------------------- formation ---- */
.formation-field {
  position: relative; border-radius: var(--rad-md); overflow: hidden;
  border: 1px solid var(--line); min-height: 210px; padding: 12px;
  background-size: cover; background-position: center;
}
.formation-field::before {
  content: ''; position: absolute; inset: 0; background: rgba(8, 11, 18, 0.62);
}
.slot-grid {
  position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: center;
}
.slot-col { display: flex; flex-direction: column; gap: 9px; }
.slot-col.back { padding-top: 12px; }
.slot {
  position: relative; border: 1.5px dashed var(--line-strong); border-radius: 12px;
  background: rgba(8, 12, 22, 0.6); min-height: 72px; display: grid; place-items: center;
  padding: 5px; color: var(--text-muted); font-size: 11px;
}
/* Settings rows put a small toggle next to text; keep the row itself tappable. */
.panel .row > .btn.sm { flex: 0 0 auto; }
.slot.filled { border-style: solid; }
.slot .portrait { width: 52px; }
.slot-tag {
  position: absolute; top: -8px; left: 8px; font-size: 9px; letter-spacing: .1em;
  background: var(--ink-800); padding: 0 5px; border-radius: 5px; color: var(--text-muted);
}
.slot-name { font-size: 10px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --------------------------------------------------------------- shop ----- */
.offer-card {
  display: flex; align-items: center; gap: 10px;
  background: var(--panel-veil); border: 1px solid var(--line); border-radius: 12px; padding: 10px;
}
.offer-card.sold { opacity: 0.5; }
.offer-ico { width: 40px; height: 40px; display: grid; place-items: center; font-size: 22px; flex: 0 0 40px; }
.offer-ico img { width: 100%; height: 100%; object-fit: contain; }
.offer-main { flex: 1; min-width: 0; }
.offer-name { font-size: 13px; font-weight: 600; }
.offer-sub { font-size: 11px; color: var(--text-muted); }

/* -------------------------------------------------------------- quests ---- */
.quest-row { display: flex; align-items: center; gap: 10px; }
.quest-main { flex: 1; min-width: 0; }
.quest-name { font-size: 13px; font-weight: 600; }
.quest-desc { font-size: 11px; color: var(--text-muted); }
.milestones { display: flex; gap: 6px; align-items: center; }
.ms-node {
  flex: 1; text-align: center; border: 1px solid var(--line); border-radius: 10px;
  padding: 6px 2px; font-size: 10px; background: var(--panel-veil);
  min-height: var(--tap);
}
.ms-node.reached { border-color: var(--gold-300); }
.ms-node.claimed { opacity: 0.5; }

/* -------------------------------------------------------------- modals ---- */
.overlay {
  position: absolute; inset: 0; z-index: 80; display: grid; place-items: center;
  background: rgba(3, 5, 10, 0.72); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity 0.2s; padding: 14px;
}
.overlay.in { opacity: 1; }
.modal {
  width: min(460px, 100%); max-height: 86dvh; display: flex; flex-direction: column;
  background: linear-gradient(180deg, #1b2235, #111623);
  border: 1px solid var(--line-strong); border-radius: 16px; box-shadow: var(--cast-md);
  transform: translateY(12px) scale(0.98); transition: transform 0.2s;
}
.overlay.in .modal { transform: none; }
.modal-head {
  display: flex; align-items: center; gap: 10px; padding: 12px 12px 8px;
  border-bottom: 1px solid var(--line);
}
.modal-head h3 { font-size: 15px; flex: 1; }
.modal-body { padding: 12px; overflow-y: auto; }
.modal-actions { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line); }
.modal-actions .btn { flex: 1; }

.reward-box { text-align: center; }
.reward-grid { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 8px; }
.reward-chip {
  display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600;
  background: rgba(255, 255, 255, 0.06); border: 1px solid var(--line);
  border-radius: 10px; padding: 5px 9px;
}
.reward-chip.new { border-color: var(--success); }
.reward-chip .portrait { width: 30px; }

.toast-host {
  position: absolute; left: 50%; bottom: calc(var(--navbar-h) + 18px); transform: translateX(-50%);
  z-index: 90; display: flex; flex-direction: column; gap: 6px; align-items: center;
  pointer-events: none; width: min(460px, 94vw);
}
.toast {
  background: rgba(16, 21, 34, 0.96); border: 1px solid var(--line-strong);
  border-radius: 10px; padding: 8px 14px; font-size: 13px; box-shadow: var(--cast-md);
  opacity: 0; transform: translateY(8px); transition: all 0.25s;
  max-width: 100%; text-align: center;
}
.toast.in { opacity: 1; transform: none; }
.toast-bad { border-color: rgba(229, 87, 79, 0.6); color: #ffd4d1; }
.toast-good { border-color: rgba(73, 214, 111, 0.6); color: #cdf7d9; }

/* ---------------------------------------------------------- back header --- */
.sub-head {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px 2px;
}
.sub-head h2 { font-size: 17px; flex: 1; }
/* On the plaque the title stops stretching: a plaque spanning the whole row
   with one word at its left end reads as a bar, not as a nameplate. */
.sub-head h2.is-painted {
  flex: 0 1 auto;
  text-align: center;
  color: var(--gold-200);
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.6);
}

@media (min-width: 560px) {
  body { background: radial-gradient(ellipse at 50% 0%, #141c30 0%, #05070c 65%); padding: 0; }
  .app { border-left: 1px solid rgba(120,142,190,.14); border-right: 1px solid rgba(120,142,190,.14); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ===========================================================================
   Campaign maps
   ---------------------------------------------------------------------------
   Two screens, one idea: show the player where they are rather than how much
   is left. The world map is a parchment sheet the six regions sit on; each
   region opens a strip of terrain with a route winding up it. The trail and
   the route are SVG so they stay sharp at any size and can take the region's
   colour; the paper and the dioramas are painted art.
   ========================================================================= */

/* One screen tall, split between the map and the two places under it.
   The map was `aspect-ratio: 3/4` on a full-width sheet, which comes to
   613px — taller than the 398px the 9:16 phone has to give it, so the
   Tower and the Arena were 260px below the fold and the map itself had
   to be scrolled to see its own top. The nodes are positioned in
   PERCENTAGES, so the sheet can take whatever height is left and the
   six regions follow it. */
.worldmap {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 100%;
  padding: 10px 10px 14px;
}

.wm-frame {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  border-radius: 18px;
  padding: 10px;
  /* Tooled leather holding the sheet: a warm band with a stitched inner edge.
     The inset ring is the stitching, the outer shadow lifts it off the app
     background. */
  background:
    linear-gradient(160deg, #8d5a33, #5e3a1f 42%, #7a4b29);
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 170, 0.35),
    inset 0 -2px 6px rgba(0, 0, 0, 0.45),
    0 10px 26px rgba(0, 0, 0, 0.5);
}

.wm-title {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: clamp(1.15rem, 5.4vw, 1.6rem);
  letter-spacing: 0.01em;
  text-align: center;
  margin: 2px 0 8px;
  color: #ffeccd;
  text-shadow: 0 2px 0 rgba(62, 34, 14, 0.85), 0 4px 12px rgba(0, 0, 0, 0.5);
}

.wm-sheet {
  position: relative;
  /* Fills what the frame has left. The 3/4 ratio is kept as a ceiling so
     the map never stretches absurdly wide on a tablet. */
  flex: 1 1 auto;
  min-height: 210px;
  max-height: calc(100vw * 1.34);
  border-radius: 10px;
  overflow: hidden;
  outline: 2px dashed rgba(255, 228, 188, 0.35);
  outline-offset: -7px;
  box-shadow: inset 0 0 36px rgba(72, 44, 20, 0.5);
}

.wm-paper {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
}

/* Used until the parchment asset exists, and as the colour bed under it. */
.wm-paper-plain {
  background:
    radial-gradient(120% 90% at 30% 20%, #f0dcb4, #d8bf92 55%, #b99a6c);
}

.wm-trail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The route is ink on paper: it has to hold its own against the printed
   mountains underneath, so it is a real dashed line, not a hint of one. */
.wm-leg {
  fill: none;
  stroke: rgba(104, 70, 38, 0.55);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 1 9;
  vector-effect: non-scaling-stroke;
}

.wm-leg.walked {
  stroke: #5e3410;
  stroke-width: 4;
  stroke-dasharray: 1 8;
}

.wm-node {
  position: absolute;
  transform: translate(-50%, -50%);
  display: grid;
  justify-items: center;
  gap: 3px;
  padding: 4px;
  min-width: var(--tap);
  min-height: var(--tap);
  background: none;
  border: 0;
  cursor: pointer;
  color: #4a2d14;
  transition: transform 140ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

.wm-node:hover { transform: translate(-50%, -52%) scale(1.04); }
.wm-node:active { transform: translate(-50%, -49%) scale(0.98); }

.wm-tile {
  width: clamp(66px, 20vw, 96px);
  aspect-ratio: 1;
  background-size: contain;
  background-position: center bottom;
  background-repeat: no-repeat;
  filter: drop-shadow(0 6px 7px rgba(70, 44, 18, 0.45));
}

/* Stands in for a region whose diorama has not been generated. It is a
   painted island rather than a flat disc: the same chunky base, a lit top and
   a rim, so a missing asset reads as part of the map instead of a hole in it.
   The region's number sits on it, which a placeholder has to carry anyway. */
.wm-tile-plain {
  position: relative;
  border-radius: 52% 48% 46% 54% / 46% 44% 58% 56%;
  display: grid;
  place-items: center;
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, 0.4),
    inset 0 -10px 14px rgba(0, 0, 0, 0.4),
    0 7px 12px rgba(60, 38, 16, 0.45);
}

.wm-tile-plain::after {
  content: '';
  position: absolute;
  inset: 8% 14% 52%;
  border-radius: 50%;
  background: radial-gradient(70% 100% at 40% 18%, rgba(255, 255, 255, 0.4), transparent 70%);
}

.wm-tile-mark {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: 1.5rem;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.55);
}

.wm-plate {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: 0.78rem;
  line-height: 1.1;
  padding: 2px 9px;
  border-radius: 999px;
  white-space: nowrap;
  color: #fff3dd;
  background: linear-gradient(180deg, #6f4322, #4a2a12);
  box-shadow: 0 2px 5px rgba(50, 28, 10, 0.5), inset 0 1px 0 rgba(255, 215, 165, 0.3);
}

/* Locked still shows the place. Draining it to near-black turned five of the
   six regions into holes punched in the paper; this reads as somewhere the
   player has not been yet, which is the actual state. */
.wm-node.locked .wm-tile {
  filter: grayscale(0.72) brightness(0.84) sepia(0.25)
    drop-shadow(0 5px 6px rgba(60, 38, 16, 0.4));
}
.wm-node.locked .wm-plate { background: linear-gradient(180deg, #6a5c4a, #463b2e); color: #ded2bd; }

.wm-seal {
  position: absolute;
  top: 46%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  color: #ffe7c8;
  background: radial-gradient(90% 90% at 36% 28%, #c2534a, #86281f);
  box-shadow: 0 3px 8px rgba(40, 10, 8, 0.55), inset 0 1px 0 rgba(255, 190, 170, 0.55);
}

.stars { display: inline-flex; gap: 1px; align-items: center; }

.level-node .stars {
  padding: 2px 5px;
  border-radius: 999px;
  background: rgba(12, 16, 10, 0.6);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
}
.level-node .star-off { color: rgba(228, 236, 220, 0.3); }
.star-on { color: var(--gold-300); filter: drop-shadow(0 1px 1px rgba(90, 50, 0, 0.5)); }
.star-off { color: rgba(74, 45, 20, 0.38); }

/* An unearned star is the same painted star unlit, not a different shape.
   `color` did that job while these were drawn SVGs and does nothing to an
   image, so the two states are a filter now. */
.gi-painted.star-on { filter: drop-shadow(0 1px 1px rgba(90, 50, 0, 0.55)); }
.gi-painted.star-off { filter: grayscale(1) brightness(0.42); opacity: 0.55; }

/* Tower and Arena, side by side as painted places rather than stacked as
   two list rows under a map. They are destinations like the six regions
   above them, and a row with a chevron said "setting" instead. */
.wm-modes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 12px;
}

.wm-place {
  position: relative;
  display: grid;
  padding: 0;
  overflow: hidden;
  border: 2px solid var(--gold-800);
  border-radius: var(--rad-md);
  background: var(--panel-face);
  box-shadow: var(--bevel), var(--shelf-sm) var(--ink-800), var(--cast-sm);
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease-out), border-color var(--t-fast);
}
.wm-place:hover { border-color: var(--gold-300); }
.wm-place:active { transform: translateY(2px); }

.wm-place-art {
  height: clamp(52px, 9vh, 84px);
  background-size: cover;
  background-position: center 38%;
}
/* No painting yet: the icon on a tinted field, which is still a place. */
.wm-place-art.is-plain {
  display: grid;
  place-items: center;
  background: radial-gradient(120% 100% at 50% 20%, var(--panel-face), var(--ink-800));
  color: var(--gold-200);
}

.wm-place-plate {
  display: grid;
  gap: 1px;
  padding: 7px 9px 8px;
  text-align: left;
  background: linear-gradient(180deg, rgba(8, 10, 16, 0.92), rgba(8, 10, 16, 0.98));
}
.wm-place-name {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: 0.9rem;
  color: var(--text-cream);
}
.wm-place-sub { font-size: 0.7rem; color: var(--text-muted); }

/* --- region nodes --------------------------------------------------------- */

/* Name and status share the plate, stacked. "Chapter 3/5" is what a
   region owes the player, and a sealed one owes the reason it is sealed —
   both of which need a line of their own rather than being crammed onto a
   72px disc, where "Clear Celestial Spire" ran out over its neighbours. */
.wm-plate { display: grid; justify-items: center; gap: 0; line-height: 1.2; }
.wm-plate-name { font-size: 0.68rem; }
.wm-plate-sub {
  font-size: 0.58rem;
  color: var(--gold-200);
  opacity: 0.9;
}
.wm-plate-sub.is-locked { color: var(--text-muted); }


.mode-card {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: var(--tap);
  padding: 10px 12px;
  text-align: left;
  color: var(--text-cream);
  border: 0;
  border-radius: var(--rad-md);
  background: var(--panel-face);
  box-shadow: var(--bevel), var(--shelf-sm) var(--ink-800), var(--cast-sm);
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}

.mode-card:active { transform: translateY(2px); box-shadow: var(--bevel), var(--cast-sm); }
.mode-ico {
  display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: var(--rad-sm);
  background: var(--panel-sunken);
  box-shadow: var(--carve);
}
.mode-text { display: grid; gap: 1px; }
.mode-name { font-family: var(--font-display, 'Philosopher'), serif; font-size: 0.98rem; }
.mode-sub { font-size: 0.76rem; color: var(--text-muted); }
.mode-go { margin-left: auto; color: var(--text-muted); }

/* --- the level path inside a region -------------------------------------- */

.levelmap { display: block; }

.lm-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(10, 14, 24, 0.97), rgba(10, 14, 24, 0.86));
  backdrop-filter: blur(8px);
  /* Sticks to the top of the page scroll rather than owning a second
     scroller: nested scroll areas are miserable on a touch screen. */
  position: sticky;
  top: 0;
  z-index: 5;
}

.lm-head-text { display: grid; gap: 1px; min-width: 0; }
.lm-head-name {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: 1.02rem;
  line-height: 1.15;
}
.lm-head-sub {
  font-size: 0.73rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lm-head .btn.sm { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; }

.lm-scroll { position: relative; }

.lm-strip {
  position: relative;
  width: 100%;
  /* The region's own colour bleeds through the terrain, so Frostveil and
     Ember Abyss do not read as the same hill with a different photo. */
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--region) 34%, #141d16),
      color-mix(in srgb, var(--region) 16%, #0d1410));
}

.lm-terrain {
  position: absolute;
  inset: 0;
  /* Tiles down the strip at a fixed tier height. `cover` on a strip this
     tall would zoom one corner of the painting to fill 2500px. */
  background-size: 100% 420px;
  background-repeat: repeat-y;
  background-position: top center;
  /* Bright enough to be a place rather than a texture. The nodes sit on a
     road with its own dark bed, so the terrain does not have to be dimmed to
     keep them legible. */
  opacity: 0.72;
  filter: saturate(1.1) brightness(0.92);
}

/* A band of mist on the same rhythm as the terrain tile. Without it the
   repeat shows as a hard horizontal seam every tier; with it the strip reads
   as a climb through layered fog, which is what the seam wanted to be. */
.lm-terrain::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    180deg,
    rgba(12, 18, 14, 0) 0 324px,
    rgba(12, 18, 14, 0.5) 404px,
    rgba(12, 18, 14, 0.5) 436px,
    rgba(12, 18, 14, 0) 516px
  );
}

.lm-path { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

/* A road, not a dotted line: a dark shoulder with a packed-earth surface on
   top, the way the reference maps draw it. Two strokes of the same path. */
.lm-path-bed {
  fill: none;
  stroke: rgba(22, 16, 8, 0.55);
  stroke-width: 42;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.lm-path-line {
  fill: none;
  stroke: #e3cfa1;
  stroke-width: 32;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  opacity: 0.92;
}

.level-node {
  position: absolute;
  transform: translate(-50%, 50%);
  display: grid;
  justify-items: center;
  /* Clears the disc's 4px drop edge so the chips below never touch it. */
  gap: 8px;
  padding: 3px;
  min-width: var(--tap);
  min-height: var(--tap);
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-cream);
}

.ln-disc {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  color: #2a1a06;
  background: linear-gradient(180deg, #ffe9a8, #e0a948);
  border: 3px solid #7a4a16;
  box-shadow:
    0 4px 0 #6a3f12,
    0 8px 15px rgba(0, 0, 0, 0.5),
    inset 0 3px 0 rgba(255, 255, 255, 0.55);
  transition: transform 130ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

.level-node:hover .ln-disc { transform: translateY(-2px); }
.level-node:active .ln-disc { transform: translateY(1px); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.28), 0 4px 8px rgba(0, 0, 0, 0.4), inset 0 2px 0 rgba(255, 255, 255, 0.6); }

.ln-num {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: 1.22rem;
  font-weight: 700;
  line-height: 1;
}

/* Cleared levels step back so the eye lands on where the player is going. */
.level-node.is-cleared .ln-disc {
  background: linear-gradient(180deg, #cfd8c2, #93a383);
  border-color: #4e5a42;
  box-shadow: 0 4px 0 #44503a, 0 7px 12px rgba(0, 0, 0, 0.45), inset 0 3px 0 rgba(255, 255, 255, 0.45);
  color: #1b2417;
}

.level-node.is-locked { cursor: not-allowed; }
.level-node.is-locked .ln-disc {
  background: linear-gradient(180deg, #9aa3b4, #6b7489);
  border-color: #3c4253;
  color: #2b3142;
  box-shadow: 0 3px 0 #333848, 0 5px 10px rgba(0, 0, 0, 0.45), inset 0 2px 0 rgba(255, 255, 255, 0.35);
}

/* A mini-boss and a boss differ in silhouette, not only in colour. */
.level-node.is-elite .ln-disc {
  width: 64px;
  height: 64px;
  background: linear-gradient(180deg, #c9a0f0, #7c4fc0);
  border-color: #452a70;
  color: #fdf4ff;
  box-shadow: 0 4px 0 #3d2563, 0 8px 16px rgba(60, 20, 110, 0.45), inset 0 3px 0 rgba(255, 255, 255, 0.45);
}

.level-node.is-boss .ln-disc {
  width: 74px;
  height: 74px;
  background: linear-gradient(180deg, #e2645a, #9c2b23);
  color: #fff0e6;
  border-color: #5c1510;
  box-shadow:
    0 5px 0 #4d120e,
    0 11px 22px rgba(120, 20, 14, 0.5),
    inset 0 3px 0 rgba(255, 190, 170, 0.5);
}

.level-node.is-boss.is-cleared .ln-disc {
  background: linear-gradient(180deg, #b79a94, #7d615c);
  border-color: #4b3833;
  box-shadow: 0 5px 0 #43312d, 0 8px 16px rgba(0, 0, 0, 0.45), inset 0 3px 0 rgba(255, 255, 255, 0.35);
  color: #2b1d1a;
}
.level-node.is-elite.is-cleared .ln-disc {
  background: linear-gradient(180deg, #b7a8c8, #85769a);
  border-color: #453c54;
  box-shadow: 0 4px 0 #3d3549, 0 7px 13px rgba(0, 0, 0, 0.42), inset 0 3px 0 rgba(255, 255, 255, 0.35);
  color: #251d33;
}

/* The one node the player can actually play next. Breathes, so it is findable
   without reading anything. */
.level-node.is-next .ln-disc {
  animation: ln-beat 2.4s cubic-bezier(0.4, 0, 0.3, 1) infinite;
  outline: 3px solid var(--gold-300);
  outline-offset: 3px;
}

@keyframes ln-beat {
  0%, 100% { box-shadow: 0 4px 0 rgba(0, 0, 0, 0.28), 0 7px 14px rgba(0, 0, 0, 0.45), 0 0 0 0 rgba(240, 192, 112, 0.5), inset 0 2px 0 rgba(255, 255, 255, 0.7); }
  55% { box-shadow: 0 4px 0 rgba(0, 0, 0, 0.28), 0 7px 14px rgba(0, 0, 0, 0.45), 0 0 0 11px rgba(240, 192, 112, 0), inset 0 2px 0 rgba(255, 255, 255, 0.7); }
}

@media (prefers-reduced-motion: reduce) {
  .level-node.is-next .ln-disc { animation: none; }
}

.ln-you {
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: 999px;
  color: #2a1c02;
  background: var(--gold-300);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
}

.ln-warn {
  font-size: 0.66rem;
  padding: 1px 7px;
  border-radius: 999px;
  color: #3a2200;
  background: var(--warn);
}

/* Region accent, consumed by the path, the scrollbar and the strip bed.
   One rule per faction the game has: the screen sets `f-${chapter.faction}`,
   and these named the old elemental set long after the factions became
   peoples, so five of the six chapters set no accent at all and every path
   fell back to the same colour. */
.levelmap.f-human { --region: var(--f-human); }
.levelmap.f-beast { --region: var(--f-beast); }
.levelmap.f-elf { --region: var(--f-elf); }
.levelmap.f-covenant { --region: var(--f-covenant); }
.levelmap.f-god { --region: var(--f-god); }
.levelmap.f-void { --region: var(--f-void); }

.ico { display: block; flex: none; }

/* Painted icons need the same protection the drawn ones have. Inside a flex
   button an <svg>/<img> with no `flex: none` is shrinkable to nothing, and an
   icon button whose plate narrowed its content box squeezed the glyph to zero
   width — a gold ring with an empty middle, which reads as missing art. */
.gi { flex: none; }

/* Drawn icons sit inline with their label rather than as a leading glyph. */
.role-chip { display: inline-flex; align-items: center; gap: 4px; }
.pill { display: inline-flex; align-items: center; gap: 4px; }
.q-ico, .nav-ico, .avatar { display: grid; place-items: center; }
.ms-mark { display: grid; place-items: center; }
.icon-btn { display: grid; place-items: center; }
.g-glyph { display: grid; place-items: center; color: var(--text-muted); }

/* --- battle count-in ------------------------------------------------------
   Two seconds between tapping a level and the first blow. The word sits on a
   darkened field so it reads over any battlefield, and scales down into
   place rather than appearing, which gives the pause a shape. */
.battle-countin {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  z-index: 30;
  pointer-events: none;
  background: radial-gradient(60% 40% at 50% 46%, rgba(4, 7, 13, 0.5), rgba(4, 7, 13, 0.78));
  animation: ci-in 220ms ease-out;
}

.ci-word {
  font-family: var(--font-display, 'Philosopher'), serif;
  font-size: clamp(3rem, 17vw, 5.5rem);
  letter-spacing: 0.02em;
  color: #fff6e2;
  text-shadow:
    0 3px 0 rgba(84, 42, 10, 0.9),
    0 0 36px rgba(255, 196, 108, 0.55),
    0 10px 30px rgba(0, 0, 0, 0.7);
  animation: ci-drop 420ms cubic-bezier(0.18, 1.1, 0.3, 1);
}

.ci-word.go {
  color: #fff1c9;
  animation: ci-go 420ms cubic-bezier(0.12, 1.3, 0.3, 1);
}

@keyframes ci-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ci-drop {
  from { opacity: 0; transform: scale(1.6) translateY(-14px); filter: blur(6px); }
  to { opacity: 1; transform: none; filter: none; }
}
@keyframes ci-go {
  from { opacity: 0; transform: scale(0.55); }
  60% { opacity: 1; transform: scale(1.12); }
  to { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .battle-countin, .ci-word, .ci-word.go { animation: none; }
}
.ach-mark { display: grid; place-items: center; color: var(--gold-300); }
.power { display: inline-flex; align-items: center; gap: 3px; }
.cur-glyph { display: grid; place-items: center; color: var(--text-muted); }
.cost-sep { opacity: 0.5; margin: 0 1px; }
.new-tag {
  margin-left: 5px; padding: 0 5px; border-radius: 999px;
  font-size: 0.62rem; letter-spacing: 0.04em; text-transform: uppercase;
  color: #10210f; background: var(--success);
}

/* ---------------------------------------------------------- tower ladder --
   The climb ahead. A tower that shows one rung is a tower you cannot plan
   against: the thing that makes it motivating is seeing where the next
   guardian sits and what power it will take. Highest floor on top, so the
   list reads upward the way the fiction does. */

.tw-ladder {
  display: grid;
  gap: 3px;
}

.tw-rung {
  display: grid;
  grid-template-columns: 2.4rem 20px 1fr auto;
  align-items: center;
  gap: var(--s2);
  min-height: 30px;
  padding: 0 var(--s2);
  border-radius: var(--rad-sm);
  background: var(--panel-sunken);
  box-shadow: var(--carve);
  font-size: var(--fz-small);
  color: var(--text-dim);
}

.tw-rung-n { color: var(--text-muted); text-align: right; }
.tw-rung-mark { display: grid; place-items: center; color: var(--alert); }
.tw-rung-pw { color: var(--text-dim); }

/* Power you can already field reads as reachable. One colour, one meaning:
   this floor is open to the team you have right now. */
.tw-rung-pw.is-reachable { color: var(--success); }

/* The floor you are standing on. */
.tw-rung.is-next {
  background: linear-gradient(90deg, rgba(245, 200, 80, 0.16), transparent 70%);
  box-shadow: inset 0 0 0 1px rgba(245, 200, 80, 0.4);
  color: var(--text-cream);
}
.tw-rung.is-next .tw-rung-n { color: var(--gold-200); }

/* Guardian floors are marked by shape, not only by colour. */
.tw-rung.is-guardian .tw-rung-n { color: var(--alert); font-weight: 800; }
