:root {
  /* Tonal indigo/slate-violet palette, kept dark - hierarchy comes from translucent-white
     overlays lifting off a near-black indigo base, rather than from a bright mid-tone base
     or a dark-neutral+bright-accent split. */
  --bg: #14162a;
  --bg-rail: #101122;
  --bg-glow: radial-gradient(1400px 900px at 20% -10%, rgba(120, 110, 200, 0.14), transparent 60%),
    radial-gradient(1100px 700px at 100% 0%, rgba(90, 100, 190, 0.09), transparent 55%);
  --bg-elevated: rgba(255, 255, 255, 0.055);
  --bg-card: rgba(255, 255, 255, 0.045);
  --bg-card-hover: rgba(255, 255, 255, 0.085);
  --border: rgba(255, 255, 255, 0.13);
  --border-soft: rgba(255, 255, 255, 0.07);
  --text: #f2f3fb;
  --text-dim: rgba(242, 243, 251, 0.7);
  --text-faint: rgba(242, 243, 251, 0.46);
  --accent: #f2c14e;
  --accent-soft: #ffdd85;
  --accent-glow: rgba(242, 193, 78, 0.3);
  --violet: #a9a6ff;
  --rarity-1: #9ea3d6;
  --rarity-2: #6fc1f2;
  --rarity-3: #c98bf0;
  --rarity-4: #f2c14e;
  /* Heroes' and pets' shared real rarity colors - kept separate from --rarity-1..4 above (still
     used by items, whose grade badges keep their original colors - a genuinely different game
     concept, not just a different content type) so this rename doesn't bleed into those. */
  --unit-rarity-1: #9ea3d6;
  --unit-rarity-2: #6fd694;
  --unit-rarity-3: #a9a6ff;
  --unit-rarity-4: #f2c14e;
  /* Costumes' own grade colors - same green/blue-violet as --unit-rarity-N above, kept as its own
     separate set (not literally reused) so items' --rarity-N stays untouched despite sharing the
     identical 1-4 grade scale/labels with costumes. */
  --costume-grade-1: #9ea3d6;
  --costume-grade-2: #6fd694;
  --costume-grade-3: #a9a6ff;
  --costume-grade-4: #f2c14e;
  --radius-lg: 18px;
  --radius-md: 13px;
  --radius-sm: 9px;
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35), 0 10px 26px -16px rgba(0, 0, 0, 0.6);
  /* Content gutter and cap: the shell (sidebar, section backgrounds, grids) always spans the
     full viewport - only this controls how far the CONTENT within it can stretch, so a 4K
     monitor gets more grid columns and breathing room instead of a wide void, but dense
     text/tables still read comfortably instead of stretching edge-to-edge. */
  --gutter: clamp(1.25rem, 3.2vw, 3rem);
  --content-max: 1800px;
  --sidebar-w: 72px;
  --sidebar-w-expanded: 208px;
  font-family: -apple-system, "Segoe UI", "Inter", system-ui, sans-serif;
  /* Tells the browser to use dark defaults for native controls (select dropdown popups,
     scrollbars) instead of the OS light theme - without this, an open <select>'s option
     list renders on a plain white system background regardless of our own CSS. */
  color-scheme: dark;
}

* { box-sizing: border-box; }

::selection { background: var(--accent-glow); color: #1a1200; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }

body {
  margin: 0;
  padding-left: var(--sidebar-w);
  background: var(--bg-glow), var(--bg);
  background-attachment: fixed;
  color: var(--text);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  transition: padding-left 0.18s ease;
}
@media (min-width: 721px) {
  body.sidebar-expanded { padding-left: var(--sidebar-w-expanded); }
}
@media (max-width: 720px) {
  body { padding-left: 0; padding-bottom: calc(var(--sidebar-w) / 2); }
}

a { color: inherit; text-decoration: none; }

/* ---------- Sidebar (icon rail, expandable to show labels) ---------- */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--sidebar-w);
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 1rem 0;
  background: var(--bg-rail);
  border-right: 1px solid var(--border-soft);
  overflow-x: hidden;
  overflow-y: auto;
  transition: width 0.18s ease;
}
@media (min-width: 721px) {
  .sidebar.expanded { width: var(--sidebar-w-expanded); align-items: stretch; padding: 1rem 0.7rem; }
}
/* Wraps .sidebar-mark + .sidebar-settings-btn (built by sidebar.js) so the pair can lay out as a
   horizontal row once the rail is wide enough to show them side by side (expanded/mobile-drawer),
   while stacking vertically - settings button below the mark - in the narrow icon-only rail where
   there isn't room for both side by side. */
.sidebar-top { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; margin-bottom: 0.55rem; width: 100%; }
.sidebar.expanded .sidebar-top { flex-direction: row; align-items: center; justify-content: flex-start; gap: 0.5rem; padding-left: 0.3rem; margin-bottom: 0.85rem; }

.sidebar-mark {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  background: linear-gradient(135deg, var(--accent-soft), var(--accent) 60%);
  color: #1a1200;
  flex-shrink: 0;
}
.sidebar-mark.home-active { box-shadow: 0 0 0 2px var(--bg-rail), 0 0 0 4px var(--accent-soft); }
.sidebar-mark-icon { width: 24px; height: 24px; object-fit: contain; }

/* Site settings trigger, injected right after .sidebar-mark by sidebar.js - identical 38x38
   footprint to the mark itself (confirmed via getBoundingClientRect - a bare 1px outline on a
   transparent background just reads as visually smaller/lighter than the mark's solid gold fill,
   even at the same box size, hence the solid --bg-card fill here to match its visual weight). */
.sidebar-settings-btn {
  width: 38px;
  height: 38px;
  box-sizing: border-box;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  color: var(--text-dim);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.sidebar-settings-btn:hover { background: var(--bg-card-hover); color: var(--text); border-color: var(--text-faint); }
.sidebar-settings-btn .menu-icon { width: 24px; height: 24px; }

.sidebar-divider { width: 28px; height: 1px; flex-shrink: 0; background: var(--border-soft); margin: 0.15rem auto 0.5rem; }
.sidebar.expanded .sidebar-divider { width: 100%; margin: 0.15rem 0 0.5rem; }

/* ---------- Settings modal (language + scenario username) ---------- */
.settings-modal-backdrop {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(8, 6, 18, 0.65);
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
}
.settings-modal-backdrop[hidden] { display: none; }
/* var(--bg-elevated) is a translucent white tint meant to sit over the page's own OPAQUE body -
   over this backdrop's semi-transparent overlay instead, it let the page behind bleed through and
   read as barely-there. A solid, fully opaque color (the same tone --bg-elevated composites to
   over --bg) keeps the modal readable regardless of what's behind it. */
.settings-modal {
  background: #212236; border: 1px solid var(--border-soft); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 1.4rem 1.6rem; max-width: 320px; width: 100%;
}
.settings-modal h3 { margin: 0 0 1.1rem; font-size: 1rem; font-weight: 700; }
.settings-field { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.9rem; font-size: 0.82rem; font-weight: 600; color: var(--text-dim); }
.settings-field select, .settings-field input {
  font: inherit; font-size: 0.88rem; font-weight: 600; color: var(--text);
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 0.5rem 0.7rem;
}
.settings-field select:focus, .settings-field input:focus { outline: 2px solid var(--accent-soft); outline-offset: 1px; }
.settings-modal-actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.6rem; }
.settings-modal-actions .tab-btn { border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 0.5rem 1.1rem; }
.settings-modal-actions #settings-save-btn { border-color: transparent; background: linear-gradient(135deg, var(--accent-soft), var(--accent)); color: #1a1200; }

.settings-account { margin-bottom: 1.1rem; padding-bottom: 1.1rem; border-bottom: 1px solid var(--border-soft); }
.settings-account-row { display: flex; align-items: center; gap: 0.6rem; }
.settings-account-avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.settings-account-name { flex: 1; font-weight: 600; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Discord's own brand color ("blurple") - a real "Log in with Discord" button reads as more
   trustworthy/official when it matches Discord's own visual identity instead of the site's own
   gold accent. */
.settings-discord-login {
  display: flex; align-items: center; justify-content: center; gap: 0.55rem;
  background: #5865f2; color: #fff; font-weight: 700; font-size: 0.85rem;
  padding: 0.6rem 1rem; border-radius: var(--radius-sm);
  transition: background 0.15s ease;
}
.settings-discord-login:hover { background: #4752c4; }
.settings-discord-login .menu-icon { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar-link {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  color: var(--text-dim);
  transition: background 0.15s ease, color 0.15s ease;
}
.sidebar.expanded .sidebar-link { width: 100%; justify-content: flex-start; padding: 0 0.65rem; }
.sidebar-link svg { width: 21px; height: 21px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.7; }
/* Menu icon PNGs (SevenKnightsRebirth/MenuIcons) are white-on-transparent, wildly inconsistent
   native sizes/aspect ratios (22x37 up to 58x48), and each nav item needs a different file - a
   plain <img> can't recolor via currentColor the way the old SVGs did, so instead the element is
   an empty span whose own PNG is used as a CSS mask (--icon-url, set inline per link) filled with
   background-color:currentColor. That brings back full state-color parity with the SVGs (dim by
   default, brighter on hover, accent-gold when active) while mask-size:contain still scales each
   icon proportionally into a fixed square slot without stretching or cropping. */
.menu-icon {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-image: var(--icon-url);
  mask-image: var(--icon-url);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}
.sidebar-link .menu-icon { width: 22px; height: 22px; flex-shrink: 0; }
.sidebar-link .label { display: none; font-size: 0.85rem; font-weight: 600; white-space: nowrap; }
.sidebar.expanded .sidebar-link .label { display: inline; }
.sidebar-link:hover { background: var(--bg-card-hover); color: var(--text); }
.sidebar-link.active { background: var(--bg-elevated); color: var(--accent-soft); }

/* Lives outside <nav class="sidebar"> (a fixed-position sibling, not a flex child of the
   scrollable rail) so it stays put at a fixed spot next to the sidebar regardless of the rail's
   own scroll position or content length - only its `left` needs to track the rail's width. */
.sidebar-toggle {
  position: fixed;
  top: 50%;
  left: var(--sidebar-w);
  transform: translateY(-50%);
  width: 28px;
  height: 44px;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  border: 1px solid var(--border-soft);
  border-left: none;
  background: var(--bg-rail);
  color: var(--text-faint);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 31;
  transition: left 0.18s ease, background 0.15s ease, color 0.15s ease;
}
body.sidebar-expanded .sidebar-toggle { left: var(--sidebar-w-expanded); }
.sidebar-toggle:hover { background: var(--bg-card-hover); color: var(--text); }
.sidebar-toggle svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; transition: transform 0.18s ease; }
body.sidebar-expanded .sidebar-toggle svg { transform: rotate(180deg); }

/* ---------- Mobile nav (bottom bar + burger, bottom-sheet drawer, <=720px) ---------- */
/* Below 720px the icon rail can't fit all 19 destinations as a row, so the bar keeps its old
   fixed-bottom position/look but now holds just the burger button; tapping it slides the same
   <nav> up from behind the bar as a bottom-sheet drawer with icon+label rows, closed by default. */
.mobile-nav-bar, .mobile-nav-backdrop { display: none; }

@media (max-width: 720px) {
  .mobile-nav-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    height: calc(var(--sidebar-w) / 2);
    z-index: 40;
    background: var(--bg-rail);
    border-top: 1px solid var(--border-soft);
  }
  .mobile-nav-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: calc(var(--sidebar-w) / 2);
    height: 100%;
    border-radius: 0;
    border: none;
    background: none;
    color: var(--text-dim);
    cursor: pointer;
  }
  .mobile-nav-toggle .menu-icon { width: 22px; height: 22px; flex-shrink: 0; }
  body.mobile-nav-open .mobile-nav-toggle { color: var(--accent-soft); }

  .mobile-nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 35;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  body.mobile-nav-open .mobile-nav-backdrop { opacity: 1; pointer-events: auto; }

  .sidebar {
    top: auto; left: 0; right: 0; bottom: calc(var(--sidebar-w) / 2);
    width: auto; max-height: 60vh;
    flex-direction: column; align-items: stretch;
    padding: 0.7rem;
    border-right: none; border-top: 1px solid var(--border-soft);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    z-index: 36;
    overflow-y: auto;
    transform: translateY(100%);
    transition: transform 0.22s ease;
  }
  body.mobile-nav-open .sidebar { transform: translateY(0); }

  .sidebar-mark { margin-left: 0.3rem; }
  .sidebar-top { flex-direction: row; align-items: center; justify-content: flex-start; gap: 0.5rem; padding-left: 0.3rem; margin-bottom: 0.85rem; }
  .sidebar-divider { width: 100%; margin: 0.15rem 0 0.5rem; }
  .sidebar-link { width: 100%; justify-content: flex-start; padding: 0.75rem 0.65rem; }
  .sidebar-link .label { display: inline; }
  .sidebar-toggle { display: none; }
}

/* ---------- Language corner ---------- */
.lang-corner {
  position: fixed;
  top: 1rem;
  right: var(--gutter);
  z-index: 30;
}

/* ---------- Page header ---------- */
.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.2rem;
  flex-wrap: wrap;
  margin-bottom: 1.3rem;
  padding-top: 0.4rem;
}
.page-header .eyebrow {
  margin: 0 0 0.15rem;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.page-header h1 { margin: 0; font-size: clamp(1.5rem, 2vw, 1.9rem); font-weight: 800; letter-spacing: -0.01em; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 1.1rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 700;
  background: linear-gradient(135deg, var(--accent-soft), var(--accent) 60%);
  color: #1a1200;
  flex-shrink: 0;
  transition: transform 0.14s ease, box-shadow 0.14s ease;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 8px 20px -10px var(--accent-glow); }
/* .btn's own `display: inline-flex` outranks the browser default [hidden] { display: none } rule
   (an author-stylesheet class selector always beats a UA-stylesheet attribute selector, even at
   equal specificity) - without this, a hidden .btn (e.g. #my-guild-btn before login state is
   known) shows up anyway. */
.btn[hidden] { display: none; }

.scenario-banner {
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  margin-bottom: 0.5rem;
  max-height: 170px;
}
/* Source art mixes a wide 510x112 banner with a much taller ~414x274 cinematic cover - object-fit
   keeps either from blowing past max-height instead of pushing page content down. */
.scenario-banner img { width: 100%; height: 100%; max-height: 170px; object-fit: cover; display: block; }
@media (max-width: 720px) {
  .scenario-banner, .scenario-banner img { max-height: 150px; }
}
.page-controls {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

/* Visually hidden but still reachable by screen readers/SEO - used where an image (the game
   logo) already carries the visible heading text. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- Home page ---------- */
.home-page { display: flex; flex-direction: column; gap: 2.4rem; padding-top: 1.2rem; }
.home-logo { display: block; max-width: min(420px, 100%); height: auto; margin: 0 auto 1.1rem; filter: drop-shadow(0 8px 26px rgba(0, 0, 0, 0.45)); }
/* Short centered content (eyebrow label + language picker) now, not a long paragraph, so it
   centers directly across the full hero width instead of needing its own readable-width column. */
.home-hero-text { text-align: center; }
.home-hero-text .eyebrow {
  margin: 0 0 0.7rem;
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint);
}
.home-lang-select { min-width: 220px; }

.home-section h2 {
  margin: 0 0 0.9rem;
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-faint);
}

.shortcut-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1rem; }
.shortcut-card {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 1.3rem;
  box-shadow: var(--shadow-card);
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.shortcut-card:hover { transform: translateY(-3px); border-color: var(--accent); background: var(--bg-card-hover); }
.shortcut-icon {
  width: 42px; height: 42px; border-radius: var(--radius-md);
  background: var(--bg-elevated); color: var(--accent-soft);
  display: flex; align-items: center; justify-content: center;
}
.shortcut-icon svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.7; }
.shortcut-icon .menu-icon { width: 24px; height: 24px; }
.shortcut-icon.landmark-icon { width: 64px; height: 64px; background: none; }
.shortcut-icon.landmark-icon img { width: 100%; height: 100%; object-fit: contain; }
.shortcut-name { font-weight: 700; font-size: 1.05rem; }
.shortcut-desc { color: var(--text-faint); font-size: 0.83rem; line-height: 1.5; }

input[type="search"], select {
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 999px;
  padding: 0.5rem 1rem;
  font-size: 0.85rem;
  transition: border-color 0.15s ease, background 0.15s ease;
}
input[type="search"] { min-width: 230px; }
input[type="search"]:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--bg-card-hover);
}
/* The popup option list is drawn by the OS/browser, not by our translucent `select`
   background - it needs its own solid, explicit dark color or it falls back to a plain
   white system background (white text on white = unreadable). */
select option {
  background: #1b1d33;
  color: var(--text);
}
input[type="search"]::placeholder { color: var(--text-faint); }

.checkbox {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}
.checkbox input { accent-color: var(--accent); }

main { padding: 1.75rem var(--gutter) 5rem; max-width: var(--content-max); margin: 0 auto; }

/* ---------- Filters ---------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.2rem;
  margin-bottom: 1.2rem;
  padding: 0.9rem 1.2rem;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
}
.filter-group { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.filter-group .filter-label {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-faint); margin-right: 0.3rem; font-weight: 600;
}
.chip {
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-dim);
  border-radius: 999px;
  padding: 0.3rem 0.8rem;
  font-size: 0.78rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.14s ease;
  user-select: none;
}
.chip:hover { border-color: var(--text-faint); color: var(--text); transform: translateY(-1px); }
.chip.active {
  background: linear-gradient(135deg, var(--accent-soft), var(--accent));
  border-color: var(--accent);
  color: #1a1200;
  font-weight: 700;
}
.filter-spacer { flex: 1; }
.clear-filters {
  font-size: 0.76rem;
  color: var(--text-faint);
  cursor: pointer;
  border: none;
  background: none;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.clear-filters:hover { color: var(--text-dim); }

.result-count { color: var(--text-faint); font-size: 0.8rem; margin-bottom: 0.85rem; }

/* ---------- Hero grid ---------- */
.hero-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
  gap: 1.1rem;
}

.hero-card {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 3 / 4;
  box-shadow: var(--shadow-card);
  transition: transform 0.16s ease, box-shadow 0.16s ease;
}
.hero-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 1px 2px rgba(20, 18, 45, 0.3), 0 16px 32px -14px var(--rarity-c-glow, var(--accent-glow));
}

.hero-card .portrait-wrap {
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.02));
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.hero-card img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.4s ease; }
.hero-card:hover img { transform: scale(1.05); }
.hero-card .no-art { color: var(--text-faint); font-size: 0.72rem; }
.hero-card .rarity-edge { display: none; }
.hero-card .role-tag {
  position: absolute; top: 0.5rem; right: 0.5rem;
  font-size: 0.63rem; font-weight: 700; letter-spacing: 0.03em;
  padding: 0.14rem 0.45rem; border-radius: 999px;
  background: rgba(15, 12, 30, 0.55); backdrop-filter: blur(6px);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.14);
}

.hero-card .info {
  position: absolute; inset: auto 0 0 0;
  padding: 1.8rem 0.7rem 0.65rem;
  background: linear-gradient(180deg, transparent, rgba(12, 10, 26, 0.78) 55%, rgba(12, 10, 26, 0.92));
}
.hero-card .name { font-weight: 700; font-size: 0.9rem; letter-spacing: 0.01em; color: #fff; }
.hero-card .subname { color: rgba(255, 255, 255, 0.62); font-size: 0.72rem; margin-top: 0.1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hero-card .stars { color: var(--accent-soft); font-size: 0.72rem; margin-top: 0.3rem; letter-spacing: 0.03em; }

.badge {
  display: inline-flex;
  align-items: center;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.18rem 0.55rem;
  border-radius: 999px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.rarity-1 { --rarity-c: var(--rarity-1); --rarity-c-glow: rgba(124,128,146,.35); border-color: var(--rarity-1); color: var(--rarity-1); }
.rarity-2 { --rarity-c: var(--rarity-2); --rarity-c-glow: rgba(79,163,227,.35); border-color: var(--rarity-2); color: var(--rarity-2); }
.rarity-3 { --rarity-c: var(--rarity-3); --rarity-c-glow: rgba(176,107,224,.35); border-color: var(--rarity-3); color: var(--rarity-3); }
.rarity-4 { --rarity-c: var(--rarity-4); --rarity-c-glow: rgba(227,178,63,.4); border-color: var(--rarity-4); color: var(--rarity-4); }
/* Heroes' and pets' shared rarity badge colors - see --unit-rarity-N above for why this is a
   separate class from .rarity-N (items) rather than reusing it. Legendary+/++ share tier 4's own
   gold (same badge class, the label text alone carries the +/++ distinction). */
.unit-rarity-1 { --rarity-c: var(--unit-rarity-1); --rarity-c-glow: rgba(124,128,146,.35); border-color: var(--unit-rarity-1); color: var(--unit-rarity-1); }
.unit-rarity-2 { --rarity-c: var(--unit-rarity-2); --rarity-c-glow: rgba(111,214,148,.35); border-color: var(--unit-rarity-2); color: var(--unit-rarity-2); }
.unit-rarity-3 { --rarity-c: var(--unit-rarity-3); --rarity-c-glow: rgba(169,166,255,.35); border-color: var(--unit-rarity-3); color: var(--unit-rarity-3); }
.unit-rarity-4 { --rarity-c: var(--unit-rarity-4); --rarity-c-glow: rgba(227,178,63,.4); border-color: var(--unit-rarity-4); color: var(--unit-rarity-4); }
/* Costumes' own grade badge colors - see --costume-grade-N above; items keep .rarity-N unchanged
   despite the identical 1-4 scale/labels. */
.costume-grade-1 { --rarity-c: var(--costume-grade-1); --rarity-c-glow: rgba(124,128,146,.35); border-color: var(--costume-grade-1); color: var(--costume-grade-1); }
.costume-grade-2 { --rarity-c: var(--costume-grade-2); --rarity-c-glow: rgba(111,214,148,.35); border-color: var(--costume-grade-2); color: var(--costume-grade-2); }
.costume-grade-3 { --rarity-c: var(--costume-grade-3); --rarity-c-glow: rgba(169,166,255,.35); border-color: var(--costume-grade-3); color: var(--costume-grade-3); }
.costume-grade-4 { --rarity-c: var(--costume-grade-4); --rarity-c-glow: rgba(227,178,63,.4); border-color: var(--costume-grade-4); color: var(--costume-grade-4); }
/* Awakening isn't a real rarity - reuses the same purple accent as its other markers (see
   .hero-meta .stars-big.awakened) so the filter chip reads as the same concept everywhere. */
.chip.awakened { border-color: #b06be0; color: #b06be0; }
.chip.awakened.active { background: linear-gradient(135deg, #b06be0, #7c3fae); border-color: #b06be0; color: #fff; }

.loading { color: var(--text-dim); padding: 2rem; text-align: center; }

/* ---------- Gacha (summon banner) grid ---------- */
.gacha-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.1rem;
}
.gacha-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
}
.gacha-card:hover { transform: translateY(-4px); border-color: var(--accent); box-shadow: 0 1px 2px rgba(20, 18, 45, 0.3), 0 16px 32px -14px var(--accent-glow); }
.gacha-card .banner-img-wrap {
  position: relative;
  aspect-ratio: 77 / 30;
  background: var(--bg-elevated);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.gacha-card .banner-img-wrap img { width: 100%; height: 100%; object-fit: cover; }
.gacha-card .no-art { color: var(--text-faint); font-size: 0.72rem; }
.gacha-card .banner-img-wrap .role-tag {
  position: absolute; top: 0.5rem; right: 0.5rem;
  font-size: 0.63rem; font-weight: 700; letter-spacing: 0.03em;
  padding: 0.14rem 0.45rem; border-radius: 999px;
  background: rgba(15, 12, 30, 0.55); backdrop-filter: blur(6px);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.gacha-card .info .type-badges { display: flex; gap: 0.35rem; flex-wrap: wrap; margin-top: 0.45rem; }
.gacha-card .info .type-badges .badge { font-size: 0.65rem; padding: 0.12rem 0.45rem; }
.gacha-card .info { padding: 0.75rem 0.9rem 0.9rem; }
.gacha-card .name { font-weight: 700; font-size: 0.94rem; }
.gacha-card .subname { color: var(--text-faint); font-size: 0.76rem; margin-top: 0.2rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gacha-card .gacha-dates { color: var(--text-faint); font-size: 0.72rem; margin-top: 0.4rem; font-variant-numeric: tabular-nums; }

.status-tag {
  position: absolute; top: 0.5rem; right: 0.5rem;
  font-size: 0.63rem; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  padding: 0.14rem 0.5rem; border-radius: 999px;
  background: rgba(15, 12, 30, 0.6); backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--text-dim);
}
.status-active { color: #6fd694; border-color: #6fd694; }
.status-upcoming { color: var(--rarity-2); border-color: var(--rarity-2); }
.status-ended { color: var(--text-faint); border-color: var(--border); }
.status-badge { background: var(--bg-elevated); }

.viewer.landscape { aspect-ratio: 77 / 30; }
.event-card .banner-img-wrap { aspect-ratio: 2 / 1; }
.viewer.event-landscape { aspect-ratio: 2 / 1; }

.featured-hero {
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: 999px;
  padding: 0.3rem 0.8rem 0.3rem 0.3rem; font-size: 0.82rem; font-weight: 600; color: var(--text);
  width: fit-content;
}
.featured-hero img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }

.gacha-tier + .gacha-tier { margin-top: 1.3rem; }
.gacha-tier h3 {
  display: flex; align-items: baseline; gap: 0.5rem;
  margin: 0 0 0.5rem; font-size: 0.85rem; font-weight: 700; color: var(--text);
}
.gacha-tier .tier-pct { color: var(--accent-soft); font-weight: 700; font-variant-numeric: tabular-nums; }

/* Card grid for one tier's own rewards, matching the game's own "Summon Rates" popup - a
   percentage badge and optional "UP!" pickup badge overlaid on the portrait, star rarity below
   the name - instead of a plain data table. */
.gacha-reward-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 0.6rem; }
.gacha-reward-card {
  position: relative;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 0.5rem 0.4rem 0.45rem; overflow: hidden;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
.gacha-reward-card:hover { border-color: var(--text-faint); transform: translateY(-2px); }
.gacha-reward-card img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; border-radius: 4px; background: var(--bg-elevated); }
.gacha-reward-card .no-art { display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 3 / 4; font-size: 0.65rem; color: var(--text-faint); background: var(--bg-elevated); border-radius: 4px; }
.gacha-reward-pct {
  position: absolute; top: 0.3rem; left: 0.3rem; z-index: 1;
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.01em; font-variant-numeric: tabular-nums;
  padding: 0.08rem 0.35rem; border-radius: 999px;
  background: rgba(10, 8, 22, 0.72); color: var(--text); backdrop-filter: blur(2px);
}
.gacha-reward-up {
  position: absolute; top: 0.3rem; right: 0.3rem; z-index: 1;
  font-size: 0.58rem; font-weight: 800; letter-spacing: 0.02em;
  padding: 0.08rem 0.35rem; border-radius: 999px;
  background: var(--accent); color: #1a1200;
}
.gacha-reward-name { margin-top: 0.35rem; font-size: 0.7rem; font-weight: 600; width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gacha-reward-stars { margin-top: 0.1rem; font-size: 0.62rem; color: var(--accent-soft); letter-spacing: 0.02em; }
.reward-cell { display: inline-flex; align-items: center; gap: 0.55rem; }
.reward-cell img { width: 28px; height: 28px; border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; }
/* A reward-cell dropped inline into hint/paragraph text (e.g. Potential's unlock-requirement
   line) needs a much smaller icon to match the surrounding font size, not the 28px table-cell default. */
.hint .reward-cell { gap: 0.3rem; }
.hint .reward-cell img { width: 16px; height: 16px; }
.pickup-badge { background: var(--accent); border-color: var(--accent); color: #1a1200; margin-left: 0.4rem; }
.stat-block { margin-bottom: 1.3rem; }
.stat-block:last-child { margin-bottom: 0; }
.stat-block h3 { margin: 0 0 0.7rem; font-size: 0.95rem; font-weight: 700; color: var(--text); }
.chip-row { display: flex; flex-wrap: wrap; gap: 0.6rem 1.1rem; }
/* Spread evenly across the full section width (not clumped on the left) - each column gets
   equal room whether there are 2 stats or 6, matching a scoreboard-style stat overview. */
.stat-icon-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; }
.stat-icon-col { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; text-align: center; flex: 1 1 0; min-width: 78px; }
.stat-icon-col img { width: 34px; height: 34px; object-fit: contain; }
.stat-icon-col span { font-size: 0.78rem; color: var(--text-dim); font-weight: 600; line-height: 1.25; }

/* One card for the whole recommended Set - icon+name once up top, each piece-count tier a
   plain row underneath (not a repeated card per tier, since it's always the same Set). */
.set-card { background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-md); padding: 1rem 1.1rem; }
.set-card-head { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.85rem; }
.set-card-head img, .set-card-head .icon-fallback { width: 44px; height: 44px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg-elevated); flex-shrink: 0; }
.set-card-head .skill-name { font-size: 1.05rem; }
.set-tier-row { display: flex; align-items: baseline; gap: 0.6rem; padding: 0.5rem 0; border-top: 1px solid var(--border-soft); font-size: 0.86rem; color: var(--text-dim); line-height: 1.5; }
.set-tier-row span:last-child { white-space: pre-line; }
.set-tier-row .badge { flex-shrink: 0; }

/* Example gear pieces: a real bordered card per item (icon + name), not a bare inline link -
   same visual weight as the Set card above so the tab doesn't taper off into plain text. */
.gear-example-row { display: flex; flex-wrap: wrap; gap: 0.7rem; }
.gear-example-card {
  display: flex; align-items: center; gap: 0.7rem;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: 0.6rem 1rem 0.6rem 0.6rem; color: inherit; text-decoration: none;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
.gear-example-card:hover { border-color: var(--text-faint); transform: translateY(-2px); }
.gear-example-card img { width: 44px; height: 44px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg-elevated); object-fit: cover; flex-shrink: 0; }
.gear-example-card span { font-weight: 600; font-size: 0.88rem; }

/* Set Bonus + Example Gear side by side on desktop, stacked on tablet/mobile - same 860px
   breakpoint the rest of the detail page (.detail-layout, .tab-nav) already collapses at. */
.gear-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1.3rem; align-items: start; }
@media (max-width: 860px) {
  .gear-columns { grid-template-columns: 1fr; }
}

/* ---------- Milestone track (star rewards, mission points) ---------- */
.milestone-track-scroll { overflow-x: auto; padding-bottom: 0.3rem; }
.milestone-track {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  margin: 0.9rem 0 0.4rem;
  padding-top: 0.2rem;
  min-width: 100%;
  /* Without an explicit width, an overflowing flex row's own box stays clipped to the visible
     viewport (scrollWidth grows, but width/offsetWidth doesn't) - the ::before line's percentage
     left/right would then only span the initially-visible portion instead of the full scrollable
     content. width:max-content makes the box match its children's true total width; min-width
     still wins (clamps up to 100%) when there's no overflow, so short tracks still stretch full width. */
  width: max-content;
}
.milestone-track::before {
  content: "";
  position: absolute;
  top: 23px;
  left: calc(50% / var(--milestone-count, 1));
  right: calc(50% / var(--milestone-count, 1));
  height: 2px;
  background: var(--border-soft);
  z-index: 0;
}
.milestone-node {
  position: relative;
  z-index: 1;
  flex: 1;
  min-width: 68px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.35rem;
}
.milestone-dot {
  width: 46px; height: 46px;
  border-radius: 50%;
  background: var(--bg-card);
  border: 2px solid var(--border-soft);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; flex-shrink: 0;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
.milestone-node a.milestone-dot:hover { border-color: var(--accent); transform: translateY(-2px); }
.milestone-dot img { width: 100%; height: 100%; object-fit: cover; }
.milestone-label { font-size: 0.68rem; font-weight: 700; color: var(--accent-soft); letter-spacing: 0.02em; }
.milestone-reward { font-size: 0.68rem; color: var(--text-dim); line-height: 1.25; max-width: 96px; }
.milestone-reward .value { color: var(--text); font-weight: 600; }

/* ---------- Battle Pass horizontal track ---------- */
.bp-track-scroll { overflow-x: auto; padding-bottom: 0.6rem; }
.bp-track {
  display: grid;
  grid-template-columns: 92px repeat(var(--bp-levels, 1), 82px);
  gap: 3px;
  width: max-content;
}
.bp-corner { background: transparent; }
.bp-level-header {
  display: flex; align-items: center; justify-content: center;
  font-size: 0.72rem; font-weight: 700; color: var(--text-faint);
  background: var(--bg-card); border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  padding: 0.45rem 0;
}
.bp-row-label {
  display: flex; align-items: center; gap: 0.45rem; padding: 0.5rem 0.7rem;
  font-size: 0.78rem; font-weight: 700; color: var(--text-dim);
  background: var(--bg-card); border-radius: var(--radius-sm);
}
.bp-row-label img { width: 22px; height: 22px; border-radius: 4px; object-fit: cover; flex-shrink: 0; }
.bp-row-label.premium { color: var(--accent-soft); }
.bp-cell {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.15rem;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 0.4rem 0.2rem; min-height: 68px;
}
.bp-cell.premium { border-color: rgba(212, 175, 90, 0.35); background: linear-gradient(160deg, rgba(212, 175, 90, 0.14), transparent); }
.bp-cell a { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; }
.bp-cell img { width: 30px; height: 30px; border-radius: 4px; object-fit: cover; }
.bp-cell .value { font-size: 0.62rem; color: var(--text-dim); font-weight: 600; }
.bp-cell .empty { color: var(--text-faint); font-size: 0.75rem; }
.bp-cost-label { font-size: 0.62rem; color: var(--accent-soft); font-weight: 700; margin-bottom: 0.1rem; }

/* renderCellRewards' own markup (main-event-detail.js) - used both inside a .bp-cell (already
   sized via .bp-cell img above) and bare inside a plain table cell (Leaderboard Rank Rewards),
   which had no icon sizing at all there and rendered at the raw source image's full resolution -
   this makes it correctly sized in either context on its own. */
.cell-reward { display: inline-flex; flex-direction: column; align-items: center; gap: 0.15rem; margin: 0 0.3rem; }
.cell-reward img { width: 30px; height: 30px; border-radius: 4px; object-fit: cover; }
.cell-reward .value { font-size: 0.62rem; color: var(--text-dim); font-weight: 600; }
.cell-reward .empty { color: var(--text-faint); font-size: 0.75rem; }

/* ---------- Main Event mission list (matches the in-game escalating-mission-card layout, stacked vertically) ---------- */
.mission-track { display: flex; flex-direction: column; gap: 0.5rem; }
.mission-card {
  display: flex; flex-direction: row; align-items: center; gap: 0.9rem; text-align: left;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: 0.55rem 0.9rem;
}
.mission-card .mission-title { font-size: 0.82rem; font-weight: 700; color: var(--text); flex: 1 1 auto; min-width: 0; }
.mission-card img { width: 38px; height: 38px; border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; }
.mission-card .empty { color: var(--text-faint); font-size: 0.75rem; flex-shrink: 0; }
.mission-card .mission-count { font-size: 0.76rem; color: var(--text-dim); font-weight: 600; flex-shrink: 0; }
.mission-card .mission-target {
  font-size: 0.68rem; font-weight: 700; color: var(--accent-soft); flex-shrink: 0;
  border-left: 1px solid var(--border-soft); padding-left: 0.9rem; white-space: nowrap;
}

/* ---------- Pet Hatch egg selector ---------- */
.egg-select-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 1.4rem; }
.egg-select-btn {
  display: flex; align-items: center; gap: 0.55rem;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: 0.5rem 0.9rem 0.5rem 0.5rem; cursor: pointer; font: inherit; color: var(--text-dim);
  transition: border-color 0.14s ease, color 0.14s ease, transform 0.14s ease;
}
.egg-select-btn:hover { transform: translateY(-2px); border-color: var(--text-faint); color: var(--text); }
.egg-select-btn.active { border-color: var(--accent); color: var(--text); box-shadow: 0 0 0 1px var(--accent); }
.egg-select-btn img { width: 36px; height: 36px; border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; }
.egg-select-btn span { font-size: 0.82rem; font-weight: 600; }

/* ---------- Attendance calendar ---------- */
.attendance-calendar {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 0.6rem;
}
.attendance-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.3rem;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.4rem 0.5rem;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
.attendance-day:hover { transform: translateY(-2px); border-color: var(--text-faint); }
.attendance-day-num {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.03em;
  color: var(--text-faint); text-transform: uppercase;
}
.attendance-day img { width: 34px; height: 34px; border-radius: var(--radius-sm); object-fit: cover; }
.attendance-day .no-art { width: 34px; height: 34px; }
.attendance-day-value { font-size: 0.72rem; font-weight: 700; color: var(--text); }
.attendance-day-name {
  font-size: 0.66rem; color: var(--text-dim); line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* ---------- Character detail page ---------- */
.character-page { display: flex; flex-direction: column; gap: 1.4rem; }

.back-link {
  color: var(--text-faint); font-size: 0.82rem; display: inline-flex; align-items: center; gap: 0.3rem;
  width: fit-content;
}
.back-link:hover { color: var(--accent); }

.hero-header {
  display: grid;
  grid-template-columns: minmax(260px, 360px) 1fr;
  gap: 2rem;
  padding: 1.8rem;
  background: linear-gradient(155deg, var(--bg-elevated), var(--bg-card) 65%);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  position: relative;
  overflow: hidden;
}
/* Base Stats grew a 3rd Transcendence-only row group and no longer fits squeezed under the
   name/badges/summary in the middle column - it gets its own column instead. */
.hero-header.has-stats { grid-template-columns: minmax(260px, 320px) minmax(260px, 1fr) minmax(460px, 560px); }
.hero-header.no-portrait.has-stats { grid-template-columns: minmax(260px, 1fr) minmax(460px, 560px); }
/* The 3 columns' minmax floors (320+260+460, plus gaps/padding) need ~1230px before they all fit
   side by side - below that down to the full single-column collapse at 860px, the 3rd column was
   overflowing the card's own `overflow: hidden` and getting silently clipped rather than wrapping
   (confirmed: at 1024px wide, .hero-stats' right edge sat ~190px past .hero-header's own right
   edge). Stack Base Stats under portrait+name instead in that tablet band, same as it already
   does below 860px, rather than trying to force 3 columns into a space they don't fit in. */
@media (max-width: 1230px) and (min-width: 861px) {
  .hero-header.has-stats {
    grid-template-columns: minmax(260px, 320px) 1fr;
    grid-template-areas: "portrait meta" "stats stats";
  }
  .hero-header.no-portrait.has-stats {
    grid-template-columns: 1fr;
    grid-template-areas: "meta" "stats";
  }
  .hero-header.has-stats .viewer { grid-area: portrait; }
  .hero-header.has-stats .hero-meta,
  .hero-header.no-portrait.has-stats .hero-meta { grid-area: meta; }
  .hero-header.has-stats .hero-stats,
  .hero-header.no-portrait.has-stats .hero-stats {
    grid-area: stats;
    padding-left: 0;
    border-left: none;
    padding-top: 1.4rem;
    border-top: 1px solid var(--border-soft);
  }
}
.hero-header::before {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(900px 460px at 0% 0%, var(--rarity-c-glow, transparent), transparent 70%);
  pointer-events: none;
}
.hero-header::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(700px 400px at 100% 100%, var(--rarity-c-glow, transparent), transparent 65%);
  opacity: 0.5;
  pointer-events: none;
}
@media (max-width: 860px) {
  .hero-header, .hero-header.has-stats, .hero-header.no-portrait.has-stats { grid-template-columns: 1fr; grid-template-areas: none; padding: 1.3rem; }
}
.hero-header.no-portrait { grid-template-columns: 1fr; }

.viewer {
  position: relative;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  aspect-ratio: 3 / 4;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.viewer img { width: 100%; height: 100%; object-fit: cover; }
.viewer .no-art { color: var(--text-faint); font-size: 0.8rem; }

.hero-meta { position: relative; display: flex; flex-direction: column; min-width: 0; max-width: 62ch; }
.hero-meta h1 { margin: 0 0 0.2rem; font-size: clamp(1.9rem, 2.4vw, 2.5rem); font-weight: 800; letter-spacing: -0.015em; line-height: 1.05; }
.hero-meta .subname { color: var(--text-dim); margin-bottom: 0.9rem; font-size: 1.02rem; }
.hero-meta .badges { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.hero-stats-quickset { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
.hero-stats-quickset .tab-btn { border: 1px solid var(--border-soft); flex-shrink: 0; }
.hero-meta .stars-big { color: var(--accent); font-size: 1.2rem; margin-bottom: 0.85rem; letter-spacing: 0.06em; }
/* Awakening isn't a real 7th star tier - it's the same 6-star hero, recolored purple instead of
   gold to mark the upgrade (see AWAKENED_STAR in detail.js). */
.hero-meta .stars-big.awakened { color: #b06be0; }

/* The character page's own star row uses the game's real Atl_Symbol_Star_M0X.png icon set
   instead of text glyphs - see renderStarRow in detail.js. Transcendence recolors these same 6
   slots in place rather than adding a second row underneath. */
.star-icon-row { display: flex; gap: 0.2rem; margin-bottom: 0.85rem; }
.star-icon { width: 22px; height: 22px; object-fit: contain; }

.hero-stats {
  position: relative;
  display: grid;
  grid-template-columns: minmax(140px, 160px) 1fr;
  gap: 1.4rem;
  padding-left: 1.8rem;
  border-left: 1px solid var(--border-soft);
  min-width: 0;
}
.hero-stats .stat-grid { grid-template-columns: repeat(2, 1fr); }
.hero-stats-sliders { min-width: 0; }
.hero-stats-values { min-width: 0; }
@media (max-width: 860px) {
  .hero-stats { grid-template-columns: 1fr; padding-left: 0; border-left: none; padding-top: 1.4rem; border-top: 1px solid var(--border-soft); }
}

.star-tabs { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.star-tabs a {
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.3rem 0.75rem;
  font-size: 0.8rem; color: var(--text-dim); background: var(--bg-card);
  transition: all 0.14s ease;
}
.star-tabs a:hover { border-color: var(--text-faint); color: var(--text); }
.star-tabs a.active { color: #1a1200; border-color: var(--accent); background: linear-gradient(135deg, var(--accent-soft), var(--accent)); font-weight: 700; }
.star-tabs a.awakened { color: #b06be0; border-color: rgba(176, 107, 224, 0.4); }
.star-tabs a.awakened:hover { border-color: #b06be0; color: #b06be0; }
.star-tabs a.awakened.active { color: #fff; border-color: #b06be0; background: linear-gradient(135deg, #b06be0, #7c3fae); }

.summary { color: var(--text-dim); line-height: 1.65; white-space: pre-line; font-size: 0.94rem; }

.detail-layout {
  display: grid;
  grid-template-columns: 208px 1fr;
  gap: 1.6rem;
  align-items: start;
}
@media (max-width: 860px) {
  .detail-layout { grid-template-columns: 1fr; }
}

.tab-nav {
  position: sticky;
  top: 5.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 0.6rem;
}
@media (max-width: 860px) {
  .tab-nav { position: static; flex-direction: row; overflow-x: auto; }
}

.shop-group { margin-bottom: 0.8rem; }
.shop-group-label {
  display: block;
  margin: 0 0 0.4rem 0.1rem;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Grids/tables reflow naturally at any width, but stacked prose+card content (skill lists,
   potential blocks, lore) reads best capped rather than stretched edge-to-edge on wide
   monitors - the same "full-bleed shell, comfortable content column" split the hero grid pages
   use in reverse (they want the width; this content doesn't). */
.tab-content { min-width: 0; max-width: 1180px; }
/* Hero detail page: skill/potential/transcendence blocks read fine full-width, and Base Stats
   moved into the header instead of a tab - no reason to cap this one page's tab content. */
#character-root .tab-content { max-width: none; }

.tab-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  text-align: left;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  transition: all 0.14s ease;
}
/* The vertical sidebar tab-nav is a fixed 208px, and .tab-btn's own ellipsis truncation (needed
   elsewhere to keep every button a uniform single-line height) turns a longer label like
   "Guaranteed Rate Up x200" into illegible "...". Nav labels need to stay readable more than they
   need to stay one line, so let sidebar tab buttons wrap to 2 lines instead of truncating -
   harmless for every existing label that already fits on one line. */
.tab-nav .tab-btn { white-space: normal; overflow: visible; text-overflow: clip; }
.tab-btn:hover { background: var(--bg-card); color: var(--text); }
.tab-btn.active { background: linear-gradient(135deg, var(--accent-soft), var(--accent)); color: #1a1200; }
.tab-btn:disabled { opacity: 0.4; cursor: default; pointer-events: none; }

/* Many pages render a horizontal .tab-nav with an inline `flex-wrap: wrap; width: fit-content`
   style (Formation, Mercure's Lab, item Refining sub-tabs, etc.) - fine on desktop, but on a
   narrow phone (or, just as badly, a tablet: e.g. Mercure's 21-button rank selector wraps into 3
   cramped rows at 900px) that wraps into a cramped multi-row grid where flex-shrink squeezes each
   .tab-btn's text down to 1-2 unreadable characters. Forcing a single scrollable row instead
   needs !important since inline styles otherwise win over this external stylesheet. */
@media (max-width: 1024px) {
  .tab-nav {
    flex-wrap: nowrap !important;
    width: auto !important;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
  }
  .tab-btn { flex-shrink: 0; }
}

.tab-panel { display: flex; flex-direction: column; }
.tab-panel[hidden] { display: none; }

.section {
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 1.3rem 1.4rem;
  box-shadow: var(--shadow-card);
}

/* ---------- Story (Adventure Act/Episode list, Event Story arc list) ---------- */
.story-act-header {
  margin: 0 0 1.1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border-soft);
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
}
.story-episode-header {
  margin: 1.3rem 0 0.5rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--border-soft);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
}
.story-episode-header:first-of-type { margin-top: 0; }
.story-scenario-list { display: flex; flex-direction: column; gap: 0.5rem; }
.story-scenario-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  min-height: 96px;
  padding: 0.55rem 0.9rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-soft);
  background-color: var(--bg-card);
  background-size: cover;
  background-position: left center;
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
/* The thumbnail (when present) is set as this element's own background-image inline (see
   story.js) rather than a child <img>, so this gradient can sit on top of it as a readable
   scrim for the title/badges - with no thumbnail it just tints the plain --bg-card fill. */
.story-scenario-row::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(10, 9, 22, 0.88) 0%, rgba(10, 9, 22, 0.68) 55%, rgba(10, 9, 22, 0.42) 100%);
}
.story-scenario-row:hover { border-color: var(--accent); transform: translateY(-1px); }
.story-scenario-row .name { position: relative; z-index: 1; flex: 1; min-width: 0; font-weight: 600; font-size: 0.87rem; color: #fff; }
.story-scenario-row .badges-inline { position: relative; z-index: 1; display: flex; align-items: center; gap: 0.3rem; flex-shrink: 0; }
.story-scenario-row .badges-inline .hint { margin: 0; }

.story-arc-layout { display: flex; gap: 1.4rem; align-items: flex-start; flex-wrap: wrap; }
.story-arc-cover {
  width: 220px;
  flex-shrink: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--bg-card);
}
.story-arc-cover img { width: 100%; aspect-ratio: 340 / 368; object-fit: cover; display: block; }
.story-arc-cover-info { padding: 0.9rem; }
.story-arc-cover-title { font-weight: 700; font-size: 1rem; margin-bottom: 0.6rem; }
.story-arc-cover-tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }

/* ---------- Tutorials ---------- */
.tutorial-page + .tutorial-page { margin-top: 0.9rem; }
.tab-btn .badge { margin-left: 0.3rem; padding: 0.05rem 0.4rem; }
.tutorial-block-title { margin: 1rem 0 0.3rem; font-size: 0.95rem; font-weight: 700; color: var(--text); }
.tutorial-block-title:first-child { margin-top: 0; }
.tutorial-block-text { margin: 0 0 0.7rem; color: var(--text-dim); font-size: 0.9rem; line-height: 1.6; }
.tutorial-block-image { display: block; max-width: 480px; width: 100%; height: auto; border-radius: var(--radius-md); border: 1px solid var(--border-soft); margin: 0 0 0.7rem; }
.section h2 {
  margin: 0 0 1rem;
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.section h2::before {
  content: "";
  width: 3px; height: 1.05em;
  background: linear-gradient(180deg, var(--accent-soft), var(--accent));
  border-radius: 2px;
  display: inline-block;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.5rem 1.4rem;
}
.stat-row { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.87rem; padding: 0.35rem 0; border-bottom: 1px solid var(--border-soft); }
.stat-row .label { color: var(--text-faint); }
.stat-row span:last-child { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- Skills ---------- */
.skill-list { display: flex; flex-direction: column; gap: 0.85rem; }
.skill-card {
  display: flex; gap: 0.9rem;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 0.9rem;
}
.skill-card img { width: 52px; height: 52px; border-radius: var(--radius-sm); border: 1px solid var(--border); flex-shrink: 0; background: var(--bg-elevated); }
.skill-card .icon-fallback {
  width: 52px; height: 52px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--bg-elevated); flex-shrink: 0;
}
.skill-card .skill-head { display: flex; align-items: baseline; gap: 0.55rem; flex-wrap: wrap; margin-bottom: 0.25rem; }
.skill-card .skill-name { font-weight: 700; font-size: 0.98rem; }
.skill-card .skill-target { color: var(--text-faint); font-size: 0.78rem; margin-bottom: 0.4rem; }
.skill-card .skill-desc { color: var(--text-dim); font-size: 0.86rem; white-space: pre-line; line-height: 1.5; }
.skill-card .skill-tags { margin-top: 0.5rem; display: flex; gap: 0.35rem; flex-wrap: wrap; }
.badge.order-badge { background: var(--bg-elevated); border-color: var(--violet); color: var(--violet); }
.badge.ct-badge::before { content: "⏱ "; }

.skill-extra {
  margin-top: 0.65rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-line;
  border: 1px solid;
}
.skill-apply-group {
  margin-top: 0.65rem;
  padding-top: 0.65rem;
  border-top: 1px dashed var(--border);
}
.skill-apply-group .skill-target { margin-top: 0; }
.skill-extra .skill-extra-label {
  display: block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; margin-bottom: 0.3rem;
}
.skill-extra.enhancement {
  background: rgba(79, 200, 130, 0.08); border-color: rgba(79, 200, 130, 0.3); color: #a9e8bf;
}
.skill-extra.enhancement .skill-extra-label { color: #6fd694; }
.skill-extra.transcend {
  background: rgba(139, 123, 255, 0.08); border-color: rgba(139, 123, 255, 0.3); color: #cfc7ff;
}
.skill-extra.transcend .skill-extra-label { color: var(--violet); }

/* ---------- Media ---------- */
/*
 * Spine assets don't all bundle their own backdrop (some, like Ace's, paint their own
 * moon/petal scene straight into the skeleton's art; others, like Yeonhee's, only contain the
 * character/foreground effects, and rely on the game's UI to paint a showcase background
 * behind them separately - that panel isn't part of the spine file, so it can't be extracted
 * from game data). This is a stand-in backdrop for the latter case: a starfield + nebula glow
 * behind whatever the spine/illustration renders, rather than plain black. Purely decorative
 * chrome, not sourced from game assets.
 */
.media-viewer {
  position: relative;
  background:
    radial-gradient(1px 1px at 12% 22%, #fff, transparent), radial-gradient(1px 1px at 28% 68%, #fff, transparent),
    radial-gradient(1px 1px at 44% 15%, #fff, transparent), radial-gradient(1.5px 1.5px at 61% 78%, #fff, transparent),
    radial-gradient(1px 1px at 73% 34%, #fff, transparent), radial-gradient(1px 1px at 85% 58%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 93% 12%, #fff, transparent), radial-gradient(1px 1px at 20% 88%, #fff, transparent),
    radial-gradient(1px 1px at 55% 45%, #fff, transparent), radial-gradient(1px 1px at 8% 50%, #fff, transparent),
    radial-gradient(700px 500px at 50% 55%, rgba(140, 100, 210, 0.4), transparent 70%),
    linear-gradient(160deg, #232045, #0e0c1c);
  background-repeat: no-repeat;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  aspect-ratio: 16 / 10;
  max-width: 88%;
  margin: 0 auto 0.6rem;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer canvas, .media-viewer img { width: 100%; height: 100%; object-fit: contain; }
.media-viewer canvas { touch-action: none; }
.viewer-hint {
  position: absolute; left: 50%; bottom: 0.6rem; transform: translateX(-50%);
  font-size: 0.68rem; color: var(--text-faint);
  background: rgba(0, 0, 0, 0.4); padding: 0.2rem 0.6rem; border-radius: 999px;
  pointer-events: none; opacity: 0; transition: opacity 0.2s ease;
}
.media-viewer:hover .viewer-hint { opacity: 1; }
.media-viewer:fullscreen, .media-viewer.manual-fullscreen {
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: unset;
  border-radius: 0;
}
/* iOS Safari has no Fullscreen API for arbitrary elements (only <video> gets one) - this class is
   the JS fallback (see addFullscreenButton in detail.js) for that case and for any browser whose
   requestFullscreen() call rejects. Fixed over the whole viewport like the native :fullscreen
   state, since unlike :fullscreen this one doesn't get real viewport layout for free. Needs the
   OPAQUE page background (--bg) rather than --bg-elevated - that token is a ~5% white tint meant
   to be layered on top of something solid, not a solid color on its own, so using it here left
   the fixed overlay almost fully see-through to whatever was scrolled behind it. */
.media-viewer.manual-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 200;
  width: 100vw;
  height: 100vh;
  background: var(--bg);
}
body.manual-fullscreen-lock { overflow: hidden; }
.viewer-fullscreen-btn {
  position: absolute; top: 0.6rem; right: 0.6rem;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.45); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  color: var(--text); cursor: pointer;
  opacity: 0; transition: opacity 0.2s ease;
}
.media-viewer:hover .viewer-fullscreen-btn,
.media-viewer:fullscreen .viewer-fullscreen-btn,
.media-viewer.manual-fullscreen .viewer-fullscreen-btn { opacity: 1; }
/* Touch screens never fire :hover, so the button must default to visible there - otherwise it's
   both invisible AND the only way to ever reach fullscreen in the first place. */
@media (hover: none) {
  .viewer-fullscreen-btn { opacity: 1; }
}
.viewer-fullscreen-btn:hover { background: rgba(0, 0, 0, 0.65); }
.viewer-fullscreen-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.media-caption { text-align: center; font-size: 0.8rem; color: var(--text-dim); margin: -0.1rem 0 0.7rem; white-space: pre-line; }
.media-caption strong { color: var(--text); }
.media-scenario { margin-top: 0.4rem; font-style: italic; color: var(--text-faint); }
.media-detail-chips { display: flex; justify-content: center; gap: 0.35rem; flex-wrap: wrap; margin-top: 0.55rem; }
.media-detail-chips .badge img { width: 14px; height: 14px; margin-right: 0.25rem; vertical-align: -2px; }

.viewer-nav { display: flex; gap: 0.45rem; margin-top: 0.4rem; flex-wrap: wrap; justify-content: center; }
.viewer-nav button {
  background: var(--bg-card); border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 999px; padding: 0.3rem 0.75rem; font-size: 0.76rem; cursor: pointer;
  transition: all 0.14s ease;
}
.viewer-nav button:hover { border-color: var(--text-faint); color: var(--text); }
.viewer-nav button.active { color: #1a1200; border-color: var(--accent); background: var(--accent); font-weight: 700; }
.viewer-nav:empty { display: none; }

.strategy-groups { display: flex; flex-wrap: wrap; gap: 1rem; }
.strategy-group {
  flex: 1 1 260px;
  min-width: 240px;
  padding: 0.9rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-soft);
  background: var(--bg-card);
}
.strategy-group-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text);
}
.strategy-group-header img { width: 24px; height: 24px; object-fit: contain; flex-shrink: 0; }
.strategy-group .media-gallery { margin-top: 0.6rem; padding-top: 0; border-top: none; }

.media-gallery {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border-soft);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: 0.6rem;
}
.media-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  overflow: hidden;
  cursor: pointer;
  padding: 0;
  font: inherit;
  color: inherit;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
.media-card:hover { transform: translateY(-2px); border-color: var(--text-faint); }
.media-card.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.media-card img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; }
.media-card .no-art { display: flex; align-items: center; justify-content: center; aspect-ratio: 1 / 1; font-size: 0.65rem; color: var(--text-faint); }
.media-card-badge {
  position: absolute; top: 0.3rem; right: 0.3rem;
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.02em;
  padding: 0.1rem 0.4rem; border-radius: 999px;
  background: var(--accent); color: #1a1200;
}
.media-card-label {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 0.72rem; font-weight: 600; padding: 0.35rem 0.45rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.media-card-license {
  font-size: 0.62rem; color: var(--text-faint); padding: 0 0.45rem 0.35rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.media-card.active .media-card-label { color: var(--accent); }

.hint { color: var(--text-faint); font-size: 0.76rem; margin: 0.7rem 0 0; }

/* ---------- Tables ---------- */
.table-scroll { overflow-x: auto; border-radius: var(--radius-sm); }
.data-table { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
.data-table th, .data-table td { padding: 0.4rem 0.7rem; text-align: left; border-bottom: 1px solid var(--border-soft); white-space: nowrap; }
.data-table th { color: var(--text-faint); font-weight: 700; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; position: sticky; top: 0; background: var(--bg-elevated); }
.data-table tbody tr:hover { background: var(--bg-card); }
.data-table td:not(:first-child) { font-variant-numeric: tabular-nums; }

.stat-slider-block { min-width: 0; }
.stat-slider-row + .stat-slider-row { margin-top: 0.7rem; }
.stat-slider-label { display: block; font-size: 0.78rem; font-weight: 600; color: var(--text-dim); margin-bottom: 0.35rem; }
.stat-slider-label strong { color: var(--accent); font-weight: 800; }
.stat-slider {
  width: 100%; appearance: none; height: 6px; border-radius: 999px;
  background: var(--border); outline: none; cursor: pointer;
}
.stat-slider:disabled { opacity: 0.4; cursor: not-allowed; }
.stat-slider::-webkit-slider-thumb {
  appearance: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); border: 2px solid #1a1200; cursor: grab;
}
.stat-slider::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: var(--accent); border: 2px solid #1a1200; cursor: grab;
}

.potential-block + .potential-block { margin-top: 1.2rem; }
.potential-block h3 { display: flex; align-items: center; gap: 0.45rem; font-size: 0.9rem; margin: 0 0 0.5rem; font-weight: 700; }
.potential-block h3 img { width: 20px; height: 20px; }

.transcendence-timeline { position: relative; display: flex; flex-direction: column; gap: 1.1rem; }
.transcendence-timeline::before {
  content: "";
  position: absolute;
  left: 27px;
  top: 27px;
  bottom: 27px;
  width: 2px;
  background: linear-gradient(180deg, var(--accent) 0%, var(--violet) 100%);
  opacity: 0.35;
}
.transcendence-node { position: relative; display: flex; gap: 1rem; align-items: flex-start; }
.transcendence-badge {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 4px var(--bg-elevated), 0 4px 14px -4px rgba(20, 18, 45, 0.45);
}
.transcendence-badge img { width: 82%; height: 82%; object-fit: contain; }
.transcendence-badge-fallback { font-weight: 800; color: var(--accent); font-size: 1.15rem; }
.transcendence-card {
  flex: 1;
  min-width: 0;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
  margin-top: 0.3rem;
}
.transcendence-step-label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); margin-bottom: 0.55rem; }
.transcendence-effects { display: flex; flex-direction: column; gap: 0.45rem; }
.transcendence-effect { display: flex; align-items: center; gap: 0.6rem; font-size: 0.85rem; color: var(--text-dim); }
.transcendence-effect-icon { width: 24px; height: 24px; flex-shrink: 0; display: flex; }
.transcendence-effect-icon img { width: 24px; height: 24px; border-radius: 5px; }
.transcendence-effect-icon .icon-fallback { width: 24px; height: 24px; border-radius: 5px; background: var(--bg-elevated); border: 1px solid var(--border); }

.story-block + .story-block { margin-top: 1.1rem; }
.story-block h3 { margin: 0 0 0.3rem; font-size: 0.75rem; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }

/* ---------- Story chat reader ---------- */
.chat-thread { display: flex; flex-direction: column; gap: 0.7rem; }

.chat-narration {
  margin: 0.6rem 0;
  padding: 0.9rem 1.1rem;
  background: #0a0a0f;
  border-radius: var(--radius-sm);
  text-align: center;
  font-style: italic;
  color: var(--text-faint);
  font-size: 0.88rem;
  line-height: 1.6;
}

.chat-row { display: flex; align-items: flex-end; gap: 0.55rem; max-width: min(640px, 100%); }
.chat-row.own { align-self: flex-end; flex-direction: row-reverse; }

.chat-avatar { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 1px solid var(--border-soft); }
.chat-avatar-fallback { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; background: var(--bg-elevated); border: 1px solid var(--border-soft); }

.chat-bubble {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 0.55rem 0.85rem;
  box-shadow: var(--shadow-card);
}
.chat-row:not(.own) .chat-bubble { border-top-left-radius: 4px; }
.chat-row.own .chat-bubble { border-top-right-radius: 4px; background: color-mix(in srgb, var(--accent) 16%, var(--bg-card)); border-color: color-mix(in srgb, var(--accent) 35%, var(--border-soft)); }

.chat-speaker { font-size: 0.72rem; font-weight: 700; margin-bottom: 0.15rem; letter-spacing: 0.01em; }
.chat-row.own .chat-speaker { text-align: right; }
.chat-text { margin: 0; font-size: 0.9rem; line-height: 1.5; color: var(--text); white-space: pre-line; }

.voiceline-category { margin-bottom: 1.4rem; }
.voiceline-category h3 { font-size: 0.9rem; font-weight: 700; margin: 0 0 0.6rem; color: var(--text-faint); }
.voiceline-topic { margin-bottom: 1rem; }
.voiceline-question { font-size: 0.85rem; font-weight: 600; color: var(--text-faint); margin: 0 0 0.45rem; }

/* ---------- Achievements & Titles ---------- */
.achievement-list { display: flex; flex-direction: column; gap: 0.6rem; }
.achievement-row {
  display: flex; align-items: center; gap: 0.8rem;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: 0.7rem 0.9rem;
}
.achievement-subject-icon, .achievement-subject-fallback {
  width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
  border: 1px solid var(--border-soft); background: var(--bg-elevated);
}
.achievement-desc { flex: 1; min-width: 0; font-size: 0.88rem; color: var(--text); }
.achievement-reward { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.achievement-reward-thumb {
  width: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
  border: 1px solid var(--border-soft); background: var(--bg-elevated);
}
.achievement-reward-thumb.kind-frame { object-fit: contain; }
.achievement-reward-thumb.kind-namecard { width: 52px; height: 22px; border-radius: var(--radius-sm); object-fit: cover; }
.achievement-reward-name { font-size: 0.78rem; color: var(--accent-soft); font-weight: 600; white-space: nowrap; }

.title-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 1rem; }
.title-card {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.6rem;
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-lg);
  padding: 1rem; box-shadow: var(--shadow-card);
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.title-card:hover { transform: translateY(-3px); border-color: var(--accent); }
.title-card-art { display: flex; align-items: center; justify-content: center; }
.title-card.kind-title .title-card-art {
  width: 72px; height: 72px; border-radius: 50%; overflow: hidden;
  background: var(--bg-elevated); border: 1px solid var(--border-soft);
}
.title-card.kind-title .title-card-art img { width: 100%; height: 100%; object-fit: cover; }
.title-card.kind-title .title-card-art .no-art { font-size: 0.65rem; color: var(--text-faint); }
.title-card.kind-frame .title-card-art { width: 110px; aspect-ratio: 1 / 1; }
.title-card.kind-frame .title-card-art img { width: 100%; height: 100%; object-fit: contain; }
.title-card.kind-namecard .title-card-art { width: 100%; aspect-ratio: 1024 / 184; border-radius: var(--radius-sm); overflow: hidden; }
.title-card.kind-namecard .title-card-art img { width: 100%; height: 100%; object-fit: cover; }
.title-card-name { font-weight: 700; font-size: 0.86rem; }

/* ---------- 7K Chess Simulator ---------- */
.sena-sim-header {
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
  margin-bottom: 1.1rem; text-align: center;
}
.sena-sim-stat-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1.1rem 1.8rem; }
.sena-sim-stat { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; }
.sena-sim-stat .label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-faint); font-weight: 700; }
.sena-sim-stat .value { font-size: 1.05rem; font-weight: 800; color: var(--text); }
.sena-sim-stat .value.currency { display: inline-flex; align-items: center; gap: 0.35rem; }
.sena-sim-stat .value.currency img { width: 20px; height: 20px; object-fit: contain; }
.sena-sim-actions { display: flex; align-items: center; gap: 0.6rem; }

/* Wish Hero gets its own centered row up top, portrait enlarged and ringed by the game's own
   circular crown frame art (Atl_SenaChess_08) rather than the small inline chip used before. */
.sena-sim-wish-banner { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
.sena-sim-wish-portrait { position: relative; width: 128px; height: 128px; }
/* Atl_SenaChess_08's own ring isn't centered in its canvas - the crown ornament on top pushes
   the ring's true center down, not 50% - so the portrait is anchored there directly instead of
   flexbox-centering it in the whole frame box. */
.sena-sim-wish-portrait img:not(.sena-sim-wish-frame) {
  position: absolute; left: 50%; top: 60%; transform: translate(-50%, -50%);
  width: 90px; height: 90px; border-radius: 50%; object-fit: cover; background: var(--bg-elevated);
}
.sena-sim-wish-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
.sena-sim-wish-banner select { margin-top: 0.1rem; }
.sena-sim-wish-banner .value { display: flex; align-items: center; justify-content: center; gap: 0.5rem; font-size: 1rem; font-weight: 700; }

.sena-sim-reached { border-color: var(--accent) !important; box-shadow: 0 0 0 1px var(--accent); }

.sena-board-wrap {
  position: relative;
  border-radius: var(--radius-lg);
  /* Generous on purpose - Atl_SenaChess_01 isn't a small decal, it's a full frame PANEL (solid
     background fill inside its own ornate border, not transparent) meant to sit behind the whole
     board and show as a visible border ring all the way around it, not just barely peek out from
     under the tiles. */
  padding: 3.6rem;
  background: radial-gradient(120% 90% at 50% 0%, rgba(242, 193, 78, 0.1), transparent 65%), var(--bg-card);
  border: 1px solid var(--border-soft);
  overflow-x: auto;
  display: flex; align-items: center; justify-content: center;
  /* Fixed to the board's own 10x8 shape, sized purely from this wrap's own width (100% of its
     parent .section) - NOT from .sena-board's size. Without this, shrinking .sena-board to fit
     inside the frame (below) also shrank the wrap around it (its height was only ever "however
     tall the 100%-wide board used to be + padding"), so the whole frame shrank too instead of
     staying full size with the tiles inset inside it. */
  aspect-ratio: 10 / 8;
}
/* The 4 corners together form the board's own frame: each fills a quarter of the wrap (50%
   width x 50% height) and object-fit:contain stops it exactly where it'd touch the corner on
   the other end of the same edge, so all 4 combine into one continuous frame with no gap or
   overlap. They sit BEHIND the tiles (z-index below .sena-board) - the tiles are fully opaque,
   so only the part of this frame that falls OUTSIDE the tile grid (in the padding above) ends
   up visible, exactly like a real board's backing showing through its own border. Needs
   opacity:1 (not the translucent 0.8/0.9 tried earlier) since the frame's own dark panel fill
   is meant to render solid, not see-through.
   object-position:right - the source art (460x412) is proportionally narrower than its own
   50%x50% box, so contain-fit leaves empty space on ONE side; anchoring right pushes that empty
   space to the true outer corner (which fades into the wrap's own padding/background, barely
   noticeable) instead of leaving a gap at the seam where two corners meet along an edge (which
   sat right in the middle of the visible checkerboard - much more noticeable). Every corner uses
   the same "right" value here - each one's own scaleX/scaleY mirror transform (below) is what
   turns that into "toward the seam" for all 4, not "toward the same physical side" for all 4. */
.sena-board-corner {
  position: absolute; z-index: 0; width: 50%; height: 50%;
  object-fit: contain; object-position: right top;
  pointer-events: none;
}
.sena-board-corner.tl { top: 0; left: 0; }
.sena-board-corner.tr { top: 0; right: 0; transform: scaleX(-1); }
.sena-board-corner.bl { bottom: 0; left: 0; transform: scaleY(-1); }
.sena-board-corner.br { bottom: 0; right: 0; transform: scale(-1, -1); }

/* Always exactly 10x8 (a real chessboard's own proportions, see tileArt() in
   sena-chess-sim.js) - never reflows to fewer columns, so the alternating tile colors always
   stay correct; a too-narrow viewport scrolls the board horizontally (see overflow-x above)
   rather than breaking the grid shape. */
.sena-board {
  display: grid;
  grid-template-columns: repeat(10, minmax(46px, 1fr));
  gap: 0;
  position: relative;
  z-index: 1; /* above .sena-board-corner (z-index:0) - see comment there */
  /* A definite width (not max-content) - 1fr tracks only resolve against the wrap's real width
     during layout this way; under max-content sizing, a 1fr track's own intrinsic contribution
     falls back to its cell's max-content size instead, which blew the whole board out to a
     revealed tile's raw portrait-image pixel size (naturalWidth, ignoring the 78% CSS asks
     for) the instant one was drawn. Only the minmax() floor (46px) can still force horizontal
     scroll now (see overflow-x on the wrap), never runaway growth from image content.
     Sized smaller than the wrap's own content box (not 100%) and centered by the wrap's own
     flex centering above, so the checkerboard sits inset within the ornate frame instead of
     touching it edge to edge - each tile keeps aspect-ratio:1/1, so shrinking just the width
     shrinks the whole board's height to match automatically. */
  width: 80.5%;
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.sena-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  background-color: var(--bg-elevated);
  background-size: cover;
  background-position: center;
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: none;
  /* A grid item's default min-width/min-height is "auto", which resolves to its CONTENT's
     min-content size - for a percentage-sized <img> that's the image's raw intrinsic pixel
     size (256x256 portraits etc.), not the 78% the CSS actually asks for. Without this reset,
     revealing a tile blew its own grid track (and so the whole 10-column board) out to that
     intrinsic size the instant .sena-tile-icon was inserted. */
  min-width: 0; min-height: 0;
}
button.sena-tile.unrevealed {
  cursor: pointer;
  transition: filter 0.14s ease;
}
button.sena-tile.unrevealed:hover:not(:disabled) { filter: brightness(1.12); }
button.sena-tile.unrevealed:disabled { cursor: not-allowed; opacity: 0.55; }
/* The hero icon sits ON the checkered square once revealed, rather than covering it edge to
   edge - the tile's own alternating color stays visible as a border around it. */
.sena-tile-icon { width: 78%; height: 78%; object-fit: cover; border-radius: 5px; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35); }
.sena-tile.revealed .no-art { font-size: 0.55rem; color: var(--text-faint); }
.sena-tile.revealed.wish { box-shadow: inset 0 0 0 2px var(--accent), 0 0 10px rgba(242, 193, 78, 0.55); z-index: 1; }
/* Shared by both the pending-selection frame (_06, while the confirm popup is open) and the
   confirmed/revealed frame (_07, once a draw is locked in) - always the topmost layer on a tile. */
.sena-tile-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; z-index: 2; }

@media (max-width: 720px) {
  .sena-board { grid-template-columns: repeat(10, minmax(34px, 1fr)); }
}

/* ---------- 7K Chess Simulator - draw confirmation popup ---------- */
/* pointer-events:none on the backdrop (dimming is purely visual) so the tile grid underneath
   stays clickable while the popup is open - clicking the same pending tile again is how the
   user backs out of a draw, which wouldn't reach the tile at all if this backdrop blocked clicks. */
.sena-sim-modal-backdrop {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(8, 6, 18, 0.5);
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
  pointer-events: none;
}
.sena-sim-modal {
  background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 1.4rem 1.6rem; max-width: 320px; width: 100%;
  text-align: center;
  pointer-events: auto;
}
.sena-sim-modal p { margin: 0 0 1rem; font-size: 0.95rem; font-weight: 600; color: var(--text); }
.sena-sim-modal-cost { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 1.2rem; font-size: 0.9rem; color: var(--text-dim); }
.sena-sim-modal-cost img { width: 22px; height: 22px; object-fit: contain; }
.sena-sim-modal-actions { display: flex; align-items: center; justify-content: center; gap: 0.7rem; }
.sena-sim-modal-actions .tab-btn { border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 0.5rem 1.2rem; }
.title-card-via { font-size: 0.7rem; color: var(--text-faint); }

/* ---------- Sidebar section label ("Community") ---------- */
/* Text-only heading above a group of sidebar links - hidden in the narrow icon-only rail (no room
   for text there, same convention as .sidebar-link .label), shown once expanded or in the always-
   labeled mobile drawer. */
.sidebar-section-label { display: none; font-size: 0.65rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); padding: 0.5rem 0.8rem 0.15rem; }
.sidebar.expanded .sidebar-section-label { display: block; }
@media (max-width: 720px) {
  .sidebar-section-label { display: block; }
}

/* ---------- Guilds (community feature) ---------- */
.guild-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 1rem; }
.guild-card {
  display: flex; flex-direction: column;
  background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--radius-lg);
  overflow: hidden; transition: border-color 0.15s ease, transform 0.15s ease;
}
.guild-card:hover { border-color: var(--text-faint); transform: translateY(-2px); }
.guild-flag {
  position: relative; aspect-ratio: 3 / 4;
  background-size: cover; background-position: center; background-color: var(--bg-card);
  display: flex; align-items: center; justify-content: center;
}
.guild-flag-large { width: 100%; height: 100%; border-radius: var(--radius-md); }
/* Guild symbols are white silhouettes (SevenKnightsRebirth/04_UI/02_Icon/GuildSymbol) - same CSS
   mask technique as the site's own MenuIcons - tinted per-guild via an inline background-color
   rather than currentColor, since the tint is a stored user choice, not a theme color. */
.guild-symbol {
  width: 45%; height: 45%;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-image: var(--icon-url); mask-image: var(--icon-url);
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.4));
}
.guild-card-info { padding: 0.7rem 0.8rem; }
.guild-card-name { font-weight: 700; font-size: 0.95rem; margin-bottom: 0.35rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.guild-card-meta { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* Visual pickers (flag/symbol/color) on the create/edit form - a grid of clickable swatch buttons,
   one .selected at a time, plain JS click handling rather than native radios so the swatch itself
   (image/color) can carry the whole selectable surface. */
.picker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); gap: 0.5rem; max-width: 480px; }
.picker-swatch {
  width: 100%; aspect-ratio: 1; border-radius: var(--radius-sm);
  border: 2px solid var(--border-soft); background: var(--bg-card); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background-size: cover; background-position: center;
  transition: border-color 0.15s ease;
}
.picker-swatch:hover { border-color: var(--text-faint); }
.picker-swatch.selected { border-color: var(--accent); }
.guild-flag-swatch { aspect-ratio: 3 / 4; }
.guild-symbol-swatch .guild-symbol { width: 65%; height: 65%; background-color: var(--text-dim); filter: none; }
.guild-symbol-swatch.selected .guild-symbol { background-color: var(--accent); }
.picker-checks { display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; margin: 0.3rem 0 0.9rem; font-size: 0.85rem; color: var(--text-dim); }
.picker-checks label { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
.castle-rush-inputs { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0.7rem 1rem; margin-bottom: 1rem; }
