/* ==========================================================================
   Return to Morroc: Refuge - core stylesheet
   Palette taken straight off the mark: black void, shattered silver, purple
   flame. Nothing below writes a raw colour; everything points at a token so
   the whole site can be re-themed by editing section 1.

   1. Tokens
   2. Reset and base
   3. Layout helpers
   4. Header, nav, drawer
   5. Footer
   6. Buttons, chips, badges
   7. Cards and panels
   8. Tables
   9. Hero
  10. Content blocks (prose, changelog, timeline, faq, stat rows)
  11. Utilities and motion
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  color-scheme: dark;

  /* Brand - purple flame */
  --pur-100: #f3e6ff;
  --pur-200: #ddc0ff;
  --pur-300: #c48dff;
  --pur-400: #a95cff;
  --pur-500: #9127f0;
  --pur-600: #7415c8;
  --pur-700: #4d0d86;
  --pur-900: #1d0533;

  /* Brand - shattered silver */
  --sil-100: #f6f5fa;
  --sil-200: #e2e0ea;
  --sil-300: #c6c2d3;
  --sil-400: #a49fb4;
  --sil-500: #837d93;
  --sil-700: #504a5d;

  /* Void */
  --ink-000: #000000;
  --ink-050: #06040a;
  --ink-100: #0a0710;
  --ink-200: #0f0b17;
  --ink-300: #171122;
  --ink-400: #241b33;
  --ink-500: #362a4a;

  /* Semantic */
  --bg: var(--ink-050);
  --bg-soft: var(--ink-100);
  --bg-elevated: rgba(255, 255, 255, 0.035);
  --bg-panel: rgba(15, 11, 23, 0.72);
  --line: rgba(255, 255, 255, 0.09);
  --line-strong: rgba(255, 255, 255, 0.17);

  --text: #efedf5;
  --text-muted: #a8a2b6;
  --text-dim: #8b8499;

  --accent: var(--pur-400);
  --accent-strong: var(--pur-500);
  --accent-ink: #ffffff;
  --silver: var(--sil-300);
  --glow: rgba(169, 92, 255, 0.4);
  --live: #45d99a;
  --warn: #f0b429;

  /* Ranks, used by the dungeon tables and anywhere difficulty is shown. */
  --rank-b: #7fb2f0;
  --rank-a: #f0b429;
  --rank-s: #ff7a59;
  --rank-ss: #ff4d8d;

  /* Change families, used by every "what changed" list on the site. */
  --tag-added: #45d99a;
  --tag-changed: var(--pur-300);
  --tag-removed: #ff6b7d;

  /* Type */
  /* Amarante is Art Nouveau and carries the big statements. It has one
     weight, so nothing may ask it for bold. Atkinson Hyperlegible was designed
     for low-vision readability and does all the actual reading, including h3
     and below where a decorative face stops helping. */
  --font-display: "Amarante", Georgia, "Times New Roman", serif;
  --font-body: "Atkinson Hyperlegible", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Fluid scale. No media query ever changes a font size. */
  --step--1: clamp(0.8rem, 0.78rem + 0.1vw, 0.86rem);
  --step-0: clamp(0.95rem, 0.92rem + 0.15vw, 1.05rem);
  --step-1: clamp(1.12rem, 1.06rem + 0.3vw, 1.32rem);
  --step-2: clamp(1.38rem, 1.26rem + 0.6vw, 1.8rem);
  --step-3: clamp(1.7rem, 1.45rem + 1.2vw, 2.5rem);
  --step-4: clamp(2.1rem, 1.6rem + 2.4vw, 3.6rem);
  --step-5: clamp(2.6rem, 1.8rem + 4.2vw, 5.2rem);

  /* Readability floor. Dense tables never negotiate below this. */
  --font-min: 0.72rem;

  --gap-xs: 0.5rem;
  --gap-sm: 0.85rem;
  --gap-md: 1.4rem;
  --gap-lg: 2.4rem;
  --gap-xl: 4rem;
  --gap-2xl: 6.5rem;

  --radius-sm: 9px;
  --radius: 14px;
  --radius-lg: 22px;

  --shell: 1200px;
  --shell-narrow: 820px;
  --header-h: 68px;

  --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.5);
  --shadow-md: 0 18px 45px rgba(0, 0, 0, 0.55);
  --shadow-glow: 0 0 0 1px rgba(169, 92, 255, 0.32), 0 14px 50px -12px var(--glow);

  --ease: cubic-bezier(0.22, 0.8, 0.3, 1);
}

html[data-theme="light"] {
  color-scheme: light;

  --bg: #f2f0f5;
  --bg-soft: #ffffff;
  --bg-elevated: rgba(29, 5, 51, 0.04);
  --bg-panel: rgba(255, 255, 255, 0.82);
  --line: rgba(29, 5, 51, 0.14);
  --line-strong: rgba(29, 5, 51, 0.28);

  --text: #16101f;
  --text-muted: #574e66;
  --text-dim: #6a6278;

  --accent: var(--pur-600);
  --accent-strong: var(--pur-700);
  --silver: var(--sil-700);
  --glow: rgba(116, 21, 200, 0.24);
  --live: #0e7048;

  --rank-b: #2a63ac;
  --rank-a: #8a6000;
  --rank-s: #b13d1c;
  --rank-ss: #b01850;

  --warn: #8a6000;

  --tag-added: #0e7048;
  --tag-changed: var(--pur-600);
  --tag-removed: #c0263a;

  --shadow-md: 0 18px 45px rgba(29, 5, 51, 0.12);
  --shadow-glow: 0 0 0 1px rgba(116, 21, 200, 0.22), 0 14px 40px -14px var(--glow);
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: calc(var(--header-h) + 16px); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  overflow-x: hidden;
  /* The wiki-derived text contains the occasional unbreakable run - a long
     item name, a URL - and one of them was pushing a 375px page 47px wide. */
  overflow-wrap: break-word;
}

/* The ambient purple bloom. Fixed so it does not scroll with content, and
   pointer-events off so it never eats a click. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(60rem 40rem at 78% -10%, rgba(145, 39, 240, 0.22), transparent 65%),
    radial-gradient(48rem 34rem at 8% 8%, rgba(77, 13, 134, 0.2), transparent 60%);
}

html[data-theme="light"] body::before {
  background:
    radial-gradient(60rem 40rem at 78% -10%, rgba(145, 39, 240, 0.13), transparent 65%),
    radial-gradient(48rem 34rem at 8% 8%, rgba(116, 21, 200, 0.09), transparent 60%);
}

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--accent); text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); text-underline-offset: 3px; }
a:hover { color: var(--pur-300); }
html[data-theme="light"] a:hover { color: var(--pur-500); }

h1, h2, h3, h4 {
  margin: 0 0 var(--gap-sm);
  line-height: 1.2;
  text-wrap: balance;
}

/* The decorative face is reserved for headings big enough to carry it. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: 0.01em;
}

h3, h4 {
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: -0.005em;
}

h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }

p { margin: 0 0 var(--gap-md); }
p:last-child { margin-bottom: 0; }

ul, ol { margin: 0 0 var(--gap-md); padding-left: 1.3em; }
li { margin-bottom: 0.35em; }

code, kbd { font-family: var(--font-mono); font-size: 0.88em; }

hr { border: 0; border-top: 1px solid var(--line); margin: var(--gap-lg) 0; }

/* The surfaces we did not draw. A scrollbar, a caret and a selection ship with
   browser defaults that belong to no design system, and on this site they are
   not incidental: the results list and the detail drawer are both things you
   sit inside and scroll, and the search field is the first thing you touch. */
::selection { background: var(--pur-600); color: #fff; }

* { scrollbar-color: var(--ink-500) transparent; scrollbar-width: thin; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--ink-500);
  border: 2px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--pur-600); background-clip: content-box; }

html[data-theme="light"] * { scrollbar-color: var(--sil-300) transparent; }
html[data-theme="light"] ::-webkit-scrollbar-thumb { background: var(--sil-300); background-clip: content-box; }
html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--pur-400); background-clip: content-box; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  /* Not --accent: white on it measures 3.71:1, and the one control a keyboard
     user meets before anything else should not be the one that fails. The
     deeper purple takes white at 7.8:1 in either theme. */
  background: var(--pur-600);
  color: #fff;
  padding: 0.7em 1.2em;
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

/* --------------------------------------------------------------------------
   3. Layout helpers
   -------------------------------------------------------------------------- */

.shell { width: min(100% - 2.4rem, var(--shell)); margin-inline: auto; }
.shell-narrow { width: min(100% - 2.4rem, var(--shell-narrow)); margin-inline: auto; }

.section { padding-block: var(--gap-2xl); }
.section--tight { padding-block: var(--gap-xl); }
.section--alt { background: var(--bg-elevated); border-block: 1px solid var(--line); }

.section-head { max-width: 62ch; margin-bottom: var(--gap-lg); }
.section-head p { color: var(--text-muted); }

.eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--gap-xs);
}

/* The one grid used everywhere. min() is what stops narrow screens from
   scrolling sideways; minmax(320px, 1fr) alone overflows. */
.grid {
  display: grid;
  gap: var(--gap-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
}
.grid--wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr)); }
.grid--tight { grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); }

.stack > * + * { margin-top: var(--gap-md); }
.cluster { display: flex; flex-wrap: wrap; gap: var(--gap-sm); align-items: center; }

/* --------------------------------------------------------------------------
   4. Header, nav, drawer
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}

/* min-width: 0 lets the row shrink instead of pushing the page sideways, and
   the nav scrolls inside itself if it is ever still too wide. Belt and braces:
   the breakpoint below should mean it never has to. */
.header-inner { display: flex; align-items: center; gap: var(--gap-md); width: 100%; min-width: 0; }

.brand { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; color: var(--text); flex: 0 0 auto; }
.brand img { width: 34px; height: 34px; filter: drop-shadow(0 0 10px var(--glow)); }
.brand-text { display: flex; flex-direction: column; line-height: 1.05; }
.brand-name { font-family: var(--font-display); font-weight: 400; font-size: 1.02rem; letter-spacing: 0.07em; }
.brand-sub { font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.28em; text-transform: uppercase; color: var(--accent); }

/* Five entries fit without scrolling, which is what lets the group panels hang
   below the row: an overflow-x on this element would have clipped them. */
.nav {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  margin-left: auto;
  min-width: 0;
}
.nav-top {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.45rem 0.6rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.nav-top:hover { color: var(--text); background: var(--bg-elevated); }
.nav-top[aria-current="page"],
.nav-top[data-here="true"] { color: var(--text); background: color-mix(in srgb, var(--accent) 16%, transparent); }
.nav-caret { width: 13px; height: 13px; opacity: 0.6; transition: transform 0.15s ease; }
.nav-group[data-open="true"] .nav-caret { transform: rotate(180deg); }

/* The one link that is an action rather than a destination. */
.nav-cta {
  margin-left: 0.35rem;
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.nav-cta:hover { background: color-mix(in srgb, var(--accent) 26%, transparent); }

.nav-group { position: relative; }
.nav-menu {
  position: absolute;
  top: calc(100% + 0.45rem);
  left: 50%;
  translate: -50% 0;
  min-width: 13rem;
  padding: 0.35rem;
  display: grid;
  gap: 0.1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-soft);
  box-shadow: 0 18px 40px rgb(0 0 0 / 0.45);
  /* visibility rather than display keeps the panel focusable-in-order and
     lets it fade; a hidden panel cannot be tabbed into by accident. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s;
}
/* Hover and focus-within are the whole interaction without JavaScript. The
   click handler in main.js only adds touch and a way to close. */
.nav-group:hover .nav-menu,
.nav-group:focus-within .nav-menu,
.nav-group[data-open="true"] .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: none;
}
/* The gap between button and panel would drop the hover halfway across it. */
.nav-group::after {
  content: "";
  position: absolute;
  inset: 100% 0 auto;
  height: 0.5rem;
}
.nav-menu a {
  display: block;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-sm);
  font-size: 0.86rem;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
}
.nav-menu a:hover { color: var(--text); background: var(--bg-elevated); }
.nav-menu a[aria-current="page"] { color: var(--text); background: color-mix(in srgb, var(--accent) 16%, transparent); }

/* margin-left:auto keeps the buttons on the right edge once the nav collapses
   into the drawer - without it they bunch up next to the mark and the
   language menu, which is right-anchored to its button, hangs off screen. */
.header-actions { display: flex; align-items: center; gap: 0.4rem; margin-left: auto; }

.icon-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.icon-btn:hover { color: var(--text); border-color: var(--line-strong); }
.icon-btn svg { width: 18px; height: 18px; }

.nav-toggle { display: none; }

/* Four labels plus the Join pill measure about 430px, and the brand and action
   buttons about 440px. Under 900px that stops fitting and the drawer takes
   over - three hundred pixels earlier than the eleven flat links managed. */
@media (max-width: 900px) {
  .nav { display: none; }
  .nav-toggle { display: grid; }
}

/* The keyboard hint is the first thing to go when space gets tight. */
@media (max-width: 1320px) { .search-btn kbd { display: none; } }

/* On a narrow phone the wordmark plus three buttons is wider than the shell,
   and the header is the one row that cannot be allowed to push the page
   sideways. Measured, not guessed: 375px was overflowing by 13px. */
/* Below 400px the wordmark and four buttons stop fitting. The mark alone is
   still a recognisable home link, and it was overflowing 64px at 320px. */
@media (max-width: 400px) {
  .brand-text { display: none; }
}

@media (max-width: 560px) {
  .header-inner { gap: var(--gap-sm); }
  .brand img { width: 30px; height: 30px; }
  .brand-name { font-size: 0.78rem; letter-spacing: 0.03em; }
  .brand-sub { font-size: 0.55rem; letter-spacing: 0.2em; }
  .icon-btn { width: 34px; height: 34px; }
  .icon-btn svg { width: 16px; height: 16px; }
}

.drawer {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--bg);
  padding: var(--gap-lg) 0;
  overflow-y: auto;
  display: none;
}
.drawer[data-open="true"] { display: block; }
.drawer nav { display: flex; flex-direction: column; gap: 0.2rem; margin-top: var(--gap-md); }
.drawer nav a {
  padding: 0.85rem 0.9rem;
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-size: 1.05rem;
  border: 1px solid transparent;
}
.drawer nav a:hover { background: var(--bg-elevated); }
.drawer nav a[aria-current="page"] { border-color: var(--line); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.drawer-head { display: flex; align-items: center; justify-content: space-between; }

/* Same grouping as the desktop row: five things to read, not eleven. */
.drawer-group summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.85rem 0.9rem;
  border-radius: var(--radius-sm);
  font-size: 1.05rem;
  color: var(--text);
  cursor: pointer;
  list-style: none;
}
.drawer-group summary::-webkit-details-marker { display: none; }
.drawer-group summary::after { content: "+"; color: var(--accent); font-family: var(--font-mono); }
.drawer-group[open] summary::after { content: "\2212"; }
.drawer-group-body { display: flex; flex-direction: column; gap: 0.2rem; padding: 0 0 0.4rem 0.9rem; }
.drawer-group-body a { font-size: 0.98rem; color: var(--text-muted); }

body.no-scroll { overflow: hidden; }

/* --------------------------------------------------------------------------
   5. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--line);
  padding-block: var(--gap-xl) var(--gap-lg);
  margin-top: var(--gap-2xl);
  background: var(--bg-soft);
}
.footer-grid { display: grid; gap: var(--gap-lg); grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); }
.footer-grid h4 { font-family: var(--font-mono); font-size: var(--step--1); letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-dim); margin-bottom: var(--gap-sm); }
.footer-grid ul { list-style: none; padding: 0; margin: 0; }
.footer-grid li { margin-bottom: 0.45em; }
.footer-grid a { color: var(--text-muted); text-decoration: none; font-size: 0.92rem; }
.footer-grid a:hover { color: var(--accent); }
.footer-legal {
  margin-top: var(--gap-lg);
  padding-top: var(--gap-md);
  border-top: 1px solid var(--line);
  color: var(--text-dim);
  font-size: var(--step--1);
  max-width: 78ch;
}

/* --------------------------------------------------------------------------
   6. Buttons, chips, badges
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.35rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease), background 0.18s var(--ease);
}
.btn:hover { transform: translateY(-2px); }
.btn svg { width: 18px; height: 18px; }

.btn--primary {
  /* The flat colour is what shows if the gradient ever fails to paint; white
     text on nothing is the failure mode worth avoiding. */
  background-color: var(--pur-600);
  background-image: linear-gradient(135deg, var(--pur-500), var(--pur-700));
  color: #fff;
  box-shadow: var(--shadow-glow);
}
.btn--primary:hover { color: #fff; box-shadow: 0 0 0 1px var(--accent), 0 18px 55px -14px var(--glow); }

.btn--ghost { border-color: var(--line-strong); color: var(--text); background: var(--bg-elevated); }
.btn--ghost:hover { color: var(--text); border-color: var(--accent); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.28rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-elevated);
  font-size: var(--step--1);
  color: var(--text-muted);
  white-space: nowrap;
}
.chip strong { color: var(--text); font-weight: 600; }

.badge {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 5px;
  font-family: var(--font-mono);
  font-size: var(--font-min);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  background: color-mix(in srgb, currentColor 12%, transparent);
}

.rank-b  { color: var(--rank-b); }
.rank-a  { color: var(--rank-a); }
.rank-s  { color: var(--rank-s); }
.rank-ss { color: var(--rank-ss); }

.tag-added   { color: var(--tag-added); }
.tag-changed { color: var(--tag-changed); }
.tag-removed { color: var(--tag-removed); }

.live-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 22%, transparent);
}

/* --------------------------------------------------------------------------
   7. Cards and panels
   -------------------------------------------------------------------------- */

.card {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--gap-md);
  backdrop-filter: blur(6px);
}
.card h3 { font-size: var(--step-1); }
.card p:last-child { margin-bottom: 0; }
.card--link { text-decoration: none; color: inherit; display: block; transition: border-color 0.18s var(--ease), transform 0.18s var(--ease); }
.card--link:hover { border-color: var(--accent); transform: translateY(-3px); color: inherit; }

.card-kicker {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  display: block;
  margin-bottom: 0.35rem;
}

/* An aside. It used to carry a 3px accent border down its left edge, which is
   the single most recognisable tell of a generated interface, and which the
   panel never needed: it is already a raised glass surface, so the coloured
   tab was a second signal for the same thing.

   Border, fill and shadow all come from the shared glass rule further down;
   only the radius and the padding are set here. Setting a border on this rule
   as well looks like it works and does not, because glass is later in the file
   at the same specificity and wins. */
.panel {
  border-radius: var(--radius-sm);
  padding: var(--gap-md);
}
/* Doubled class so it outranks the shared glass border, which is later in
   the file and would otherwise repaint this edge. */
.panel.panel--warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--glass-edge)); }

.stat { text-align: left; }
.stat dt { font-family: var(--font-mono); font-size: var(--step--1); letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-dim); }
.stat dd { margin: 0.2rem 0 0; font-family: var(--font-display); font-size: var(--step-2); font-weight: 400; color: var(--text); }

.stat-row { display: grid; gap: var(--gap-md); grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); margin: 0; }

/* --------------------------------------------------------------------------
   8. Tables
   -------------------------------------------------------------------------- */

.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-panel); }

table { border-collapse: collapse; width: 100%; font-size: 0.92rem; }
thead th {
  position: sticky;
  top: 0;
  background: var(--bg-soft);
  text-align: left;
  font-family: var(--font-mono);
  font-size: var(--font-min);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 0.75rem 0.9rem;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
tbody td { padding: 0.7rem 0.9rem; border-bottom: 1px solid var(--line); vertical-align: top; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--bg-elevated); }
td.num { font-family: var(--font-mono); white-space: nowrap; }

/* --------------------------------------------------------------------------
   9. Hero
   -------------------------------------------------------------------------- */

.hero { position: relative; padding-block: clamp(3rem, 8vw, 7rem) var(--gap-2xl); overflow: hidden; }
.hero-inner { display: grid; gap: var(--gap-xl); align-items: center; grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }

@media (max-width: 900px) {
  .hero-inner { grid-template-columns: 1fr; }
  .hero-art { order: -1; max-width: 320px; margin-inline: auto; }
}

.hero-lede { font-size: var(--step-1); color: var(--text-muted); max-width: 52ch; }

.hero-art { position: relative; }
.hero-art img { filter: drop-shadow(0 0 60px var(--glow)); }

html[data-theme="light"] .hero h1 .accent {
  background: linear-gradient(120deg, var(--pur-600), var(--sil-700) 55%, var(--pur-700));
}

/* The mark turns slowly. Purely decorative, and switched off for anyone who
   asked the OS for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .hero-art img { animation: slow-spin 90s linear infinite; }
}
@keyframes slow-spin { to { transform: rotate(360deg); } }

.page-hero { padding-block: var(--gap-xl) var(--gap-lg); border-bottom: 1px solid var(--line); }
.page-hero h1 { max-width: 20ch; }
.page-hero p { color: var(--text-muted); max-width: 66ch; font-size: var(--step-1); }

.crumbs { font-size: var(--step--1); color: var(--text-dim); margin-bottom: var(--gap-sm); }
.crumbs a { color: var(--text-dim); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }
.crumbs span { margin-inline: 0.4rem; }

/* --------------------------------------------------------------------------
  10. Content blocks
   -------------------------------------------------------------------------- */

.prose { max-width: 72ch; }
.prose h2 { margin-top: var(--gap-xl); }
.prose h3 { margin-top: var(--gap-lg); }

/* Change lists: one line per change, the tag column fixed so the eye can
   scan down the verbs instead of hunting for them. */
.changelist { list-style: none; padding: 0; margin: 0; }
.changelist li {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: var(--gap-sm);
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
  margin: 0;
}
.changelist li:last-child { border-bottom: 0; }
@media (max-width: 560px) {
  .changelist li { grid-template-columns: 1fr; gap: 0.25rem; }
}

.skill-block { border-top: 1px solid var(--line); padding-top: var(--gap-md); margin-top: var(--gap-md); }
.skill-block h4 { margin-bottom: 0.4rem; font-family: var(--font-body); font-size: 1rem; letter-spacing: 0; }
.skill-block ul { margin: 0; padding-left: 1.15em; color: var(--text-muted); font-size: 0.94rem; }
.skill-block ul strong { color: var(--text); font-weight: 600; }

.job-tree { display: grid; gap: var(--gap-md); grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.job-node { border: 1px solid var(--line); border-radius: var(--radius); padding: var(--gap-sm) var(--gap-md); background: var(--bg-panel); }
.job-node h4 { margin-bottom: 0.25rem; }
.job-path { font-family: var(--font-mono); font-size: var(--step--1); color: var(--accent); letter-spacing: 0.05em; }

.timeline { list-style: none; padding: 0; margin: 0; border-left: 1px solid var(--line); }
.timeline li { position: relative; padding: 0 0 var(--gap-lg) var(--gap-md); margin: 0; }
.timeline li::before {
  content: "";
  position: absolute;
  left: calc(var(--gap-md) * -0.5 - 4px);
  top: 0.55rem;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 20%, transparent);
}
.timeline time { display: block; font-family: var(--font-mono); font-size: var(--step--1); color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; }
.timeline h3 { font-size: var(--step-1); margin: 0.15rem 0 0.35rem; }
.timeline p { color: var(--text-muted); margin: 0; }

details.faq {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  padding: 0 var(--gap-md);
  margin-bottom: var(--gap-sm);
}
details.faq summary {
  cursor: pointer;
  padding: var(--gap-sm) 0;
  font-weight: 400;
  font-family: var(--font-display);
  font-size: var(--step-1);
  list-style: none;
  display: flex;
  gap: var(--gap-sm);
  align-items: baseline;
}
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::before { content: "+"; color: var(--accent); font-family: var(--font-mono); }
details.faq[open] summary::before { content: "\2212"; }
details.faq > *:not(summary) { color: var(--text-muted); }
details.faq > *:last-child { padding-bottom: var(--gap-md); }

.cta-band {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--gap-lg);
  background:
    radial-gradient(40rem 20rem at 15% 0%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 70%),
    var(--bg-panel);
  text-align: center;
}
.cta-band h2 { margin-bottom: var(--gap-sm); }
.cta-band p { color: var(--text-muted); max-width: 56ch; margin-inline: auto; }
.cta-band .cluster { justify-content: center; margin-top: var(--gap-md); }

/* --------------------------------------------------------------------------
  11. Utilities and motion
   -------------------------------------------------------------------------- */

.muted { color: var(--text-muted); }
.dim { color: var(--text-dim); }
/* Tabular figures, because on this site the mono face is only ever carrying
   numbers that sit above and below each other: drop rates down a column,
   levels down a list, HP and EXP in a stat row. Proportional digits make a 1
   narrower than a 7, so those columns shimmer as you scroll and two rates are
   harder to compare than they should be. This is the single cheapest fix on a
   page that is mostly numbers. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.center { text-align: center; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Scroll reveal. Elements start visible; the observer only adds the "from"
   state once it is known to be running, so no-JS never hides content. */
.js .reveal { opacity: 0; transform: translateY(14px); }
.js .reveal.is-in { opacity: 1; transform: none; transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
  html { scroll-behavior: auto; }
  .js .reveal { opacity: 1; transform: none; }
}

/* ==========================================================================
  12. The mark
   ==========================================================================
   The logo is artwork on a black field, cut to a circle. It is NOT recoloured
   per theme: on the light theme it simply reads as a black medallion, which
   is what the artwork actually is. Anything else changes the brand colours.
   -------------------------------------------------------------------------- */

.mark { border-radius: 50%; }

html[data-theme="light"] .mark {
  box-shadow: 0 0 0 1px rgba(29, 5, 51, 0.18), 0 2px 12px rgba(29, 5, 51, 0.12);
}

html[data-theme="light"] .hero-art img {
  filter: drop-shadow(0 18px 60px rgba(77, 13, 134, 0.28));
}

/* ==========================================================================
  13. Class pages and the job tree
   ========================================================================== */

.lede { font-size: var(--step-1); color: var(--text-muted); max-width: 60ch; }

.page-hero--class {
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent);
}

.class-body { padding-block: var(--gap-xl) 0; }
.class-body .prose { max-width: 78ch; }
.prose-flow h2 { margin-top: var(--gap-xl); }
.prose-flow p, .prose-flow li { color: var(--text-muted); }

/* Fact bar: the four things you want before reading a word of prose. */
.factbar {
  display: grid;
  gap: var(--gap-sm) var(--gap-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  margin: 0 0 var(--gap-lg);
  padding: var(--gap-md);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elevated);
}
.factbar dt {
  font-family: var(--font-mono);
  font-size: var(--font-min);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.factbar dd { margin: 0.2rem 0 0; font-weight: 700; }
.factbar a { font-weight: 400; }

/* Strengths and weaknesses side by side, because that is how the decision
   actually gets made. */
.verdict {
  display: grid;
  gap: 0;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: var(--gap-lg);
}
.verdict-col { padding: var(--gap-md); }
.verdict-col + .verdict-col { border-left: 1px solid var(--line); }
.verdict-col h3 {
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 0.4rem;
}
.verdict-col p { color: var(--text-muted); margin: 0; font-size: 0.96rem; }
.verdict-col--good { background: color-mix(in srgb, var(--tag-added) 8%, transparent); }
.verdict-col--good h3 { color: var(--tag-added); }
.verdict-col--bad { background: color-mix(in srgb, var(--tag-removed) 8%, transparent); }
.verdict-col--bad h3 { color: var(--tag-removed); }

@media (max-width: 560px) {
  .verdict-col + .verdict-col { border-left: 0; border-top: 1px solid var(--line); }
}


.source-note {
  font-size: var(--step--1);
  color: var(--text-dim);
  border-top: 1px solid var(--line);
  padding-top: var(--gap-md);
  margin-top: var(--gap-xl);
}

/* Skill table. The name column is a row header so screen readers announce it
   with every cell. */
#skilltable th[scope="row"] { font-weight: 700; white-space: nowrap; }
.skill-max { display: block; font-size: var(--font-min); color: var(--text-dim); font-weight: 400; }
.skill-extra { margin-top: 0.35rem; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.skill-note { font-size: var(--step--1); color: var(--text-dim); }

.chip--sm { padding: 0.05rem 0.45rem; font-size: var(--font-min); }

.field {
  flex: 1 1 240px;
  padding: 0.7rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--bg-elevated);
  color: var(--text);
  caret-color: var(--accent);
  font: inherit;
}
.field::placeholder { color: var(--text-dim); }

/* Skill type badges. Quieter than the rank badges: they sit in a dense table
   and must not shout over the text beside them. */
.sk-physical { color: #e8677a; }
.sk-magic    { color: #7f9dff; }
.sk-support  { color: #4fd3a0; }
.sk-debuff   { color: #c07df0; }
.sk-passive  { color: var(--text-dim); }
.sk-stance   { color: var(--warn); }
.sk-summon   { color: #40c4c4; }
.sk-active   { color: var(--sil-400); }
.sk-other    { color: var(--text-dim); }

html[data-theme="light"] .sk-physical { color: #b02a3d; }
html[data-theme="light"] .sk-magic    { color: #2f4fb0; }
html[data-theme="light"] .sk-support  { color: #0e7048; }
html[data-theme="light"] .sk-debuff   { color: #7a2fae; }
html[data-theme="light"] .sk-stance   { color: #8a6000; }
html[data-theme="light"] .sk-summon   { color: #0d6f6f; }
html[data-theme="light"] .sk-active   { color: var(--sil-700); }

.pill-row { display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: center; margin: var(--gap-lg) 0 0; }
.pill {
  display: inline-block;
  padding: 0.3rem 0.8rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-elevated);
  font-size: var(--step--1);
  color: var(--text-muted);
  text-decoration: none;
}
.pill:hover { border-color: var(--accent); color: var(--text); }
.pill--strong { border-color: var(--accent); color: var(--accent); }

/* --- the tree ----------------------------------------------------------- */

.tree { padding-block: var(--gap-lg) var(--gap-xl); }

.tree-group {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--gap-lg) var(--gap-md);
  margin-bottom: var(--gap-lg);
  background: var(--bg-panel);
}
.tree-head { max-width: 62ch; margin-bottom: var(--gap-lg); }
.tree-head h2 { font-size: var(--step-2); margin-bottom: 0.3rem; }
.tree-head p { color: var(--text-muted); margin: 0; font-size: 0.96rem; }

.tree-row {
  display: grid;
  gap: var(--gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
}

/* Three of the groups are lanes rather than rows: the card above really does
   become the card below. Drawing that as a column with an arrow says in one
   glance what two anonymous rows never did. */
.tree-lanes {
  display: grid;
  gap: var(--gap-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
  align-items: start;
}
.tree-lane { display: grid; gap: 0.35rem; }
.tree-arrow {
  justify-self: center;
  width: 1px;
  height: 1.1rem;
  background: linear-gradient(var(--line-strong), var(--accent));
  position: relative;
}
/* The head of the arrow. Two borders and a rotation is cheaper than an SVG
   and scales with the text size. */
.tree-arrow::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -1px;
  width: 5px;
  height: 5px;
  border-right: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  transform: translateX(-50%) rotate(45deg);
}

/* The connector between rows. Purely decorative, and it disappears rather
   than lying about the shape of the tree on a narrow screen. */
.tree-link {
  height: var(--gap-md);
  margin-inline: auto;
  width: 1px;
  background: linear-gradient(var(--line-strong), transparent);
}

.node {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--line);
  border-top: 2px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.16s var(--ease), transform 0.16s var(--ease);
}
.node:hover { border-color: var(--accent); border-top-color: var(--accent); transform: translateY(-2px); color: inherit; }
/* The sprites are 130x150 pixel art. Halved they stay on whole pixels, and
   pixelated rendering keeps the edges hard instead of smearing them. */
.node-art {
  width: 44px;
  height: 51px;
  object-fit: contain;
  object-position: center bottom;
  image-rendering: pixelated;
  filter: drop-shadow(0 2px 6px rgb(0 0 0 / 0.45));
}
html[data-theme="light"] .node-art { filter: drop-shadow(0 2px 5px rgb(29 5 51 / 0.28)); }
.node-body { min-width: 0; }
.node-name { display: block; font-weight: 700; font-size: 0.98rem; }
.node-tag { display: block; font-size: var(--step--1); color: var(--text-dim); margin-top: 0.15rem; line-height: 1.4; }

/* The class page hero: sprite on the right at full size, text on the left.
   Below the breakpoint it goes back to one column with the sprite first, so
   the picture is the thing that greets you rather than a stray image sitting
   under three buttons. */
.class-hero--art {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--gap-lg);
  align-items: center;
}
.class-hero-text { min-width: 0; }

/* The sprite is an animated PNG - six frames of idle. There is no way to stop
   one from CSS or from script, so the still frame ships as its own file and
   the media query chooses between them. */
.class-art-wrap { display: block; line-height: 0; }
.class-art--still { display: none; }
@media (prefers-reduced-motion: reduce) {
  .class-art--motion { display: none; }
  .class-art--still { display: block; }
}

.class-art {
  width: 130px;
  height: 150px;
  object-fit: contain;
  object-position: center bottom;
  image-rendering: pixelated;
  filter: drop-shadow(0 6px 18px rgb(0 0 0 / 0.5));
}
html[data-theme="light"] .class-art { filter: drop-shadow(0 6px 16px rgb(29 5 51 / 0.3)); }

@media (max-width: 720px) {
  .class-hero--art { grid-template-columns: minmax(0, 1fr); }
  .class-hero--art .class-art-wrap { order: -1; justify-self: start; }
}

/* One colour per tier, on the top edge, so a row reads as a rank. */
.node--orphan { border-top-color: var(--sil-300); }
.node--first  { border-top-color: var(--rank-b); }
.node--second { border-top-color: var(--pur-300); }
.node--spec   { border-top-color: var(--rank-a); }
.node--final  { border-top-color: var(--rank-s); }
.node--expert { border-top-color: var(--rank-ss); }
.node--alt    { border-top-color: var(--sil-500); }

/* ==========================================================================
  15. Glass
   ==========================================================================
   Every raised surface on the site is the same piece of glass: a translucent
   fill, a hairline edge, a blur of whatever is behind it, and a single
   highlight along the top edge where the light would catch. Defining it once
   as tokens is what keeps forty-two class pages looking like one object
   instead of forty-two.

   The blur is the expensive part. It is applied only to surfaces that are
   actually large enough to show it, and `@supports` gives everything else an
   opaque fallback rather than an unreadable transparent one.
   -------------------------------------------------------------------------- */

:root {
  --glass-fill: rgba(255, 255, 255, 0.045);
  --glass-fill-strong: rgba(255, 255, 255, 0.07);
  --glass-edge: rgba(255, 255, 255, 0.12);
  --glass-edge-strong: rgba(255, 255, 255, 0.22);
  --glass-shine: rgba(255, 255, 255, 0.28);
  --glass-blur: 18px;
  --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.42);
  --glass-tint: rgba(145, 39, 240, 0.06);
}

html[data-theme="light"] {
  --glass-fill: rgba(255, 255, 255, 0.55);
  --glass-fill-strong: rgba(255, 255, 255, 0.72);
  --glass-edge: rgba(29, 5, 51, 0.1);
  --glass-edge-strong: rgba(29, 5, 51, 0.2);
  --glass-shine: rgba(255, 255, 255, 0.9);
  --glass-shadow: 0 8px 32px rgba(29, 5, 51, 0.1);
  --glass-tint: rgba(145, 39, 240, 0.05);
}

/* The pane itself. Everything that looks like glass composes this. */
.glass,
.card,
.panel,
.tree-group,
.table-wrap,
.factbar,
.verdict,
.cta-band,
.search-panel,
details.faq {
  position: relative;
  background-image: linear-gradient(160deg, var(--glass-fill-strong), var(--glass-fill) 42%, var(--glass-tint));
  background-color: transparent;
  border: 1px solid var(--glass-edge);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  backdrop-filter: blur(var(--glass-blur)) saturate(140%);
}

/* The highlight along the top edge. A pseudo-element rather than a border so
   it can fade out at the corners the way a real specular line does. */
.glass::before,
.card::before,
.panel::before,
.tree-group::before,
.cta-band::before,
.search-panel::before,
details.faq::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--glass-shine) 22%, var(--glass-shine) 78%, transparent);
  opacity: 0.55;
  border-radius: inherit;
}


.card--link:hover,
.node:hover { border-color: var(--glass-edge-strong); }

/* Header and drawer: heavier blur, because they sit over moving content and
   a light blur reads as a smeared mistake. */
.site-header {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg) 78%, transparent), color-mix(in srgb, var(--bg) 62%, transparent));
  border-bottom: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
}

.drawer {
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  -webkit-backdrop-filter: blur(26px) saturate(140%);
  backdrop-filter: blur(26px) saturate(140%);
}

/* Controls */
.icon-btn,
.field,
.chip,
.pill,
.btn--ghost {
  background-image: linear-gradient(160deg, var(--glass-fill-strong), var(--glass-fill));
  background-color: transparent;
  border-color: var(--glass-edge);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.icon-btn:hover,
.btn--ghost:hover,
.pill:hover { border-color: var(--glass-edge-strong); }

.btn--primary {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent),
              0 10px 34px -10px var(--glow),
              inset 0 1px 0 rgba(255, 255, 255, 0.28);
}

thead th {
  /* Opaque, not translucent: a sticky header that lets rows show through as
     they scroll under it is unreadable, and it measured 3.61:1 on light. */
  background: var(--bg-soft);
  color: var(--text-muted);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

.section--alt {
  background: linear-gradient(180deg, transparent, var(--glass-fill) 30%, var(--glass-fill) 70%, transparent);
  border-block-color: var(--glass-edge);
}

/* Nested glass reads as mud. A pane inside a pane drops its blur and most of
   its fill, keeping only the edge so the nesting is still legible. */
.card .panel,
.card .callout,
.tree-group .node,
.doc-body .callout .callout {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}

/* Without backdrop-filter the fills are simply too transparent to read
   against the aurora, so give those browsers an opaque surface instead. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.glass,
.card,
.panel,
.tree-group,
.table-wrap,
.factbar,
.verdict,
.cta-band,
.search-panel,
details.faq,
.site-header,
.drawer,
.icon-btn,
.field,
.chip,
.pill,
.btn--ghost {
    background-image: none;
    background-color: var(--bg-soft);
  }
}

/* ==========================================================================
  16. Aurora
   ==========================================================================
   A fixed backdrop for the home page. Three broad colour fields drifting at
   different speeds behind a heavy blur - no canvas, no WebGL, no library, and
   nothing for the main thread to do once the animation is handed to the
   compositor.

   Only `transform` and `opacity` are animated, deliberately: animating a
   gradient's colour stops repaints the whole layer every frame.
   -------------------------------------------------------------------------- */

.aurora {
  position: fixed;
  inset: -20vmax;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  filter: blur(72px) saturate(135%);
  opacity: 0.85;
  /* Not `strict`, which adds size containment. The shader sizes its canvas
     from this element's clientWidth, and an element that is allowed to be
     laid out as if it had no contents is a bad thing to measure. The rest of
     what strict buys - no layout, paint or style crossing the boundary - is
     still here. */
  contain: layout paint style;
}

html[data-theme="light"] .aurora { opacity: 0.5; filter: blur(80px) saturate(120%); }

.aurora span {
  position: absolute;
  display: block;
  border-radius: 46% 54% 60% 40% / 50% 45% 55% 50%;
  mix-blend-mode: screen;
  will-change: transform;
}

html[data-theme="light"] .aurora span { mix-blend-mode: multiply; }

.aurora .a1 {
  width: 70vmax; height: 55vmax;
  top: -8vmax; left: -6vmax;
  background: radial-gradient(circle at 35% 40%, var(--pur-500), transparent 68%);
  animation: aurora-a 34s ease-in-out infinite alternate;
}
.aurora .a2 {
  width: 62vmax; height: 62vmax;
  top: 6vmax; right: -12vmax;
  background: radial-gradient(circle at 60% 45%, var(--pur-700), transparent 66%);
  animation: aurora-b 46s ease-in-out infinite alternate;
}
.aurora .a3 {
  width: 54vmax; height: 44vmax;
  bottom: -10vmax; left: 22vmax;
  background: radial-gradient(circle at 50% 50%, var(--sil-500), transparent 70%);
  animation: aurora-c 40s ease-in-out infinite alternate;
  opacity: 0.5;
}

@keyframes aurora-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(12vmax, 8vmax, 0) scale(1.18); }
}
@keyframes aurora-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-14vmax, 10vmax, 0) scale(0.92); }
}
@keyframes aurora-c {
  from { transform: translate3d(0, 0, 0) scale(0.95); }
  to   { transform: translate3d(9vmax, -12vmax, 0) scale(1.22); }
}

/* Reduced motion keeps the colour and drops the drift - the backdrop still
   does its job standing still. */
@media (prefers-reduced-motion: reduce) {
  .aurora span { animation: none; }
}

/* The aurora replaces the static bloom on pages that carry it. */
body:has(.aurora)::before { display: none; }

/* ==========================================================================
  18. Language switcher
   ========================================================================== */

.lang { position: relative; }
.lang-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  min-width: 168px;
  padding: 0.3rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--glass-edge);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  box-shadow: var(--glass-shadow);
  display: none;
  z-index: 80;
}
.lang[data-open="true"] .lang-menu { display: block; }
/* Below this width the menu is wider than the space left of its button, so
   pin it to the viewport instead of to the button. */
@media (max-width: 420px) {
  .lang-menu { position: fixed; right: 0.75rem; left: auto; top: calc(var(--header-h) - 6px); }
}

.lang-menu button {
  display: flex;
  width: 100%;
  gap: 0.5rem;
  align-items: center;
  padding: 0.5rem 0.7rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
}
.lang-menu button:hover { background: var(--glass-fill-strong); }
.lang-menu button[aria-current="true"] { color: var(--accent); font-weight: 700; }
.lang-code { font-family: var(--font-mono); font-size: var(--font-min); color: var(--text-dim); margin-left: auto; }

/* ==========================================================================
  19. 404
   ========================================================================== */

.lost { min-height: 62vh; display: grid; place-items: center; text-align: center; padding-block: var(--gap-2xl); }
.lost h1 { font-size: var(--step-5); margin-bottom: var(--gap-sm); }
.lost .code {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.32em;
  color: var(--accent);
  text-transform: uppercase;
}
.lost p { color: var(--text-muted); max-width: 52ch; margin-inline: auto; }
.lost-links { margin-top: var(--gap-lg); }

/* ==========================================================================
  20. The database
   ==========================================================================
   A filter rail and a result list, both scrolling independently on desktop
   and stacking on a phone. The list is painted in blocks, so these rules have
   to hold for sixty rows and for six thousand without changing.
   -------------------------------------------------------------------------- */

.page-hero--tight { padding-block: var(--gap-lg) var(--gap-md); }

.db { padding-block: var(--gap-lg) var(--gap-xl); }

.db-tabs { display: flex; gap: 0.4rem; margin-bottom: var(--gap-md); }
.db-tab {
  padding: 0.55rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--glass-edge);
  /* A <button> keeps the UA's opaque buttonface colour unless it is cleared,
     and background-image alone does not clear it. Without this line every
     tab renders as a grey pill. */
  background-color: transparent;
  background-image: linear-gradient(160deg, var(--glass-fill-strong), var(--glass-fill));
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--text-muted);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.db-tab[aria-selected="true"] {
  color: var(--accent-ink);
  border-color: transparent;
  background-color: var(--pur-600);
  background-image: linear-gradient(135deg, var(--pur-500), var(--pur-700));
  box-shadow: var(--shadow-glow);
}
html[data-theme="light"] .db-tab[aria-selected="true"] { color: #fff; }

.db-main { min-width: 0; }

/* The front door: one field and a row of dropdowns, across the top rather than
   down a rail. The rail held nine open facet lists and made the page look like
   an admin tool before it had shown a single item. */
.db-hunt {
  min-width: 0;
  margin-bottom: var(--gap-md);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius);
  padding: var(--gap-sm);
  background-image: linear-gradient(160deg, var(--glass-fill-strong), var(--glass-fill));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
}
.db-search .field { width: 100%; font-size: 1rem; padding-block: 0.7rem; }

.db-picks {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.6rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
}
.db-picks .field { width: 100%; }

.db-extra { margin-top: 0.6rem; border-top: 1px solid var(--glass-edge); }
.db-extra summary {
  cursor: pointer;
  padding: 0.6rem 0.1rem 0.2rem;
  font-size: 0.85rem;
  color: var(--text-dim);
  list-style: none;
}
.db-extra summary::-webkit-details-marker { display: none; }
.db-extra summary::before { content: "+ "; color: var(--accent); font-family: var(--font-mono); }
.db-extra[open] summary::before { content: "\2212 "; }
.db-extra-body {
  display: grid;
  gap: 0 var(--gap-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  padding-bottom: 0.4rem;
}

/* The resting state. Six chips instead of two thousand rows, and no payload
   fetched until one of them is pressed. */
.db-start { padding: var(--gap-lg) 0; max-width: 46ch; }
.db-start p { color: var(--text-dim); }

.db-facet { border-top: 1px solid var(--glass-edge); }
.db-facet:first-of-type { border-top: 0; }
.db-facet summary {
  cursor: pointer;
  padding: 0.6rem 0.2rem;
  font-weight: 700;
  font-size: 0.9rem;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.db-facet summary::-webkit-details-marker { display: none; }
.db-facet summary::after { content: "+"; color: var(--accent); font-family: var(--font-mono); }
.db-facet[open] summary::after { content: "\2212"; }
.db-facet-body { padding: 0 0.2rem 0.7rem; }

.db-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.22rem 0;
  font-size: 0.88rem;
  color: var(--text-muted);
  cursor: pointer;
}
.db-check:hover { color: var(--text); }
.db-check input { accent-color: var(--accent); flex: 0 0 auto; }
.db-check span:first-of-type { flex: 1 1 auto; }
.db-n { flex: 0 0 auto; font-size: var(--font-min); color: var(--text-dim); }

/* Long facet lists collapse rather than filling the rail. */
.db-check.is-extra { display: none; }
.db-facet-body.is-expanded .db-check.is-extra { display: flex; }
.db-more {
  margin-top: 0.35rem;
  padding: 0.2rem 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: var(--step--1);
  cursor: pointer;
}

.db-range { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 0.4rem; align-items: center; }
.db-range .field {
  padding: 0.4rem 0.5rem;
  text-align: center;
  /* Number inputs carry an intrinsic width that ignores the track. Without
     this the two of them push the whole page sideways on a phone. */
  min-width: 0;
  width: 100%;
}
.db-range span { color: var(--text-dim); font-size: var(--step--1); }

.btn--block { width: 100%; justify-content: center; margin-top: var(--gap-sm); }

.db-bar {
  display: flex;
  gap: var(--gap-sm);
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--gap-sm);
  flex-wrap: wrap;
}
.db-count { color: var(--text-dim); font-size: var(--step--1); margin: 0; }
.field--select { flex: 0 0 auto; padding-block: 0.5rem; }

/* The open dropdown is drawn by the operating system, and it takes the closed
   control's text colour with it while supplying its own background. On the
   dark theme that meant near-white options on the popup's white sheet, which
   is the one part of this page you could not read. Both halves are set here,
   from tokens that flip with the theme, so the list is legible in either. */
.field--select option {
  background-color: var(--bg-soft);
  color: var(--text);
}

/* How much of everything there is, under the lede. Deliberately not a row of
   big numbers with labels under them: that template turns four facts into a
   hero and this is a reference tool, where the corpus size is context you read
   once and then ignore. One quiet line, hairline-separated, at the size of a
   caption. */
/* Scoped to the hero, because `.page-hero p` sets the lede size and colour and
   outranks a single class. Without the extra specificity this line renders at
   1.3rem in the lede's own colour, which makes four counts look like a second
   paragraph instead of a caption under the first.

   Separated by space rather than by rules between the items. The four counts
   measure 354px of text on their own, so at 375px they wrap whatever the gap
   is, and a divider drawn with `border-left` then hangs off the front of the
   second row with nothing to its left. Space wraps correctly at every width. */
.page-hero .db-volumes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.6rem;
  margin: var(--gap-sm) 0 0;
  max-width: none;
  font-size: var(--font-min);
  letter-spacing: 0.04em;
  color: var(--text-dim);
}

/* One colour per kind of item. The row carries it as a spine and the category
   badge as its text colour, so a list of two thousand names has some shape
   before anybody reads a word of it. Both sets are measured against the card
   background, not guessed. */
:root {
  --h-wpn: #ffa768;
  --h-shd: #8fbcff;
  --h-arm: #86d2f5;
  --h-hat: #ffd479;
  --h-acc: #ff9fd0;
  --h-crd: #c4a6ff;
  --h-cos: #ff93b6;
  --h-sha: #a8b2c6;
  --h-run: #74dfbe;
  --h-amm: #d3b78d;
  --h-use: #8ade90;
  --h-mat: #bdb4a4;
  --h-rel: #ffc861;
  --h-oth: #a49dae;
}

html[data-theme="light"] {
  --h-wpn: #a1500b;
  --h-shd: #1c5aa8;
  --h-arm: #0f6285;
  --h-hat: #8a6000;
  --h-acc: #a63576;
  --h-crd: #6b46c1;
  --h-cos: #a82c55;
  --h-sha: #4a5568;
  --h-run: #0e7048;
  --h-amm: #7a5a26;
  --h-use: #2b6b2f;
  --h-mat: #5f5a4e;
  --h-rel: #8a5a00;
  --h-oth: #5a5464;
}

.badge--wpn, .db-chip--wpn { color: var(--h-wpn); }
.badge--shd, .db-chip--shd { color: var(--h-shd); }
.badge--arm, .db-chip--arm { color: var(--h-arm); }
.badge--hat, .db-chip--hat { color: var(--h-hat); }
.badge--acc, .db-chip--acc { color: var(--h-acc); }
.badge--crd, .db-chip--crd { color: var(--h-crd); }
.badge--cos, .db-chip--cos { color: var(--h-cos); }
.badge--sha, .db-chip--sha { color: var(--h-sha); }
.badge--run, .db-chip--run { color: var(--h-run); }
.badge--amm, .db-chip--amm { color: var(--h-amm); }
.badge--use, .db-chip--use { color: var(--h-use); }
.badge--mat, .db-chip--mat { color: var(--h-mat); }
.badge--rel, .db-chip--rel { color: var(--h-rel); }
.badge--oth, .db-chip--oth { color: var(--h-oth); }

/* The same idea on the skills page, which has three kinds instead of thirteen.
   Purple is the one you press, green the one you press for somebody else, and
   cool grey the one that is simply always on: a passive is the only row here
   that is not an action, and it should read as quieter than the rest rather
   than as another colour competing for attention.

   These are their own tokens rather than borrowed item hues. Skills and items
   never share a list, so reuse would have worked, but a name that says what it
   colours is the difference between changing one and changing both. */
:root {
  --h-act: #b98cff;
  --h-sup: #5fd6b4;
  --h-pas: #a8b2c6;
}

html[data-theme="light"] {
  --h-act: #6b46c1;
  --h-sup: #0e7048;
  --h-pas: #4a5568;
}

.badge--sk-active,  .db-chip--sk-active  { color: var(--h-act); }
.badge--sk-support, .db-chip--sk-support { color: var(--h-sup); }
.badge--sk-passive, .db-chip--sk-passive { color: var(--h-pas); }

.db-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.db-row { margin: 0; }

.db-entry {
  display: grid;
  gap: 0.3rem;
  width: 100%;
  padding: 0.7rem 0.9rem;
  text-align: left;
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-sm);
  /* Same buttonface trap as .db-tab - this one turned all eleven thousand
     rows into grey boxes. */
  background-color: transparent;
  background-image: linear-gradient(160deg, var(--glass-fill-strong), var(--glass-fill));
  color: inherit;
  font: inherit;
  cursor: pointer;
  /* The spine or the sprite sits in the first track; everything readable
     stacks in the second. */
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 0.75rem;
}
.db-entry:hover { border-color: var(--accent); }
.db-body { display: grid; gap: 0.3rem; min-width: 0; }

/* Four pixels of colour. It is the cheapest way to make a long list sortable
   by eye, and it costs no layout. */
.db-chip {
  width: 4px;
  align-self: stretch;
  min-height: 2.2rem;
  border-radius: 999px;
  background: currentColor;
}

.db-sprite {
  width: 56px;
  height: 64px;
  object-fit: contain;
  object-position: center bottom;
  image-rendering: pixelated;
  flex: 0 0 auto;
}
.db-detail-head { display: flex; gap: var(--gap-sm); align-items: flex-end; padding-right: 3rem; }
.db-detail-head h2 { padding-right: 0; margin-bottom: 0.15rem; }
.db-detail-head p { margin: 0; font-size: var(--step--1); }
.db-detail-kind { margin: 0 3rem 0.35rem 0; }

.db-name { font-weight: 700; }
.db-tags { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.db-sub { font-size: var(--step--1); color: var(--text-dim); }
.db-stats { display: flex; flex-wrap: wrap; gap: 0.7rem; font-size: var(--font-min); color: var(--text-muted); }
.db-fx {
  font-size: var(--step--1);
  color: var(--text-dim);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.db-empty { padding: var(--gap-xl) 0; text-align: center; color: var(--text-dim); }

/* The description the game itself shows. Kept as separate short paragraphs
   because that is how it is written - one effect per line, not prose. */
.db-desc { margin: var(--gap-sm) 0 0; font-size: 0.95rem; }
.db-desc p { margin: 0 0 0.2rem; }
.db-desc p:last-child { margin-bottom: 0; }

/* A drop rate is the number people came for, so it gets its own weight. */
.db-rate { flex: 0 0 auto; font-weight: 700; }
.db-rate.is-common { color: var(--h-use); }
.db-rate.is-uncommon { color: var(--text-muted); }
.db-rate.is-rare { color: var(--h-hat); }
.db-rate.is-veryrare { color: var(--h-acc); }

/* Every name in a drop list opens the other record. It looks like text until
   it is hovered, because a list of forty underlined links is noise. */
.db-link {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-bottom: 1px dotted var(--glass-edge);
}
.db-link:hover, .db-link:focus-visible { color: var(--accent); border-bottom-color: currentColor; }

/* The one line that says what a relic is for, and the one that says how a
   boss is reached. Given room of its own because on those two records it is
   the most useful thing on the card. */
.db-summons {
  margin: var(--gap-sm) 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  font-size: var(--step-0);
}

.db-maps {
  margin: var(--gap-sm) 0 0;
  font-size: var(--font-min);
  color: var(--text-dim);
  line-height: 1.7;
  word-break: break-word;
}

/* The detail panel: a drawer on desktop, a sheet on a phone. */
.db-detail {
  position: fixed;
  z-index: 100;
  right: 0;
  top: var(--header-h);
  bottom: 0;
  width: min(100%, 420px);
  overflow-y: auto;
  border-left: 1px solid var(--glass-edge);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  -webkit-backdrop-filter: blur(26px);
  backdrop-filter: blur(26px);
  box-shadow: var(--glass-shadow);
  transform: translateX(100%);
  transition: transform 0.24s var(--ease);
}
.db-detail[data-open="true"] { transform: none; }
.db-detail-inner { padding: var(--gap-md); position: relative; }
.db-close { position: absolute; top: var(--gap-sm); right: var(--gap-sm); }
.db-detail h2 { font-size: var(--step-2); padding-right: 3rem; }
.db-detail h3 { margin-top: var(--gap-lg); font-size: 1rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--accent); }

@media (max-width: 640px) {
  .db-detail {
    top: auto;
    left: 0;
    width: 100%;
    max-height: 78vh;
    border-left: 0;
    border-top: 1px solid var(--glass-edge);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    transform: translateY(100%);
  }
}

.db-detail-stats { display: grid; gap: 0; margin: var(--gap-md) 0 0; }
.db-detail-stats > div {
  display: grid;
  grid-template-columns: 45% 1fr;
  gap: var(--gap-sm);
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--glass-edge);
}
.db-detail-stats dt { color: var(--text-dim); font-size: 0.88rem; }
.db-detail-stats dd { margin: 0; font-weight: 700; }

.db-drops { list-style: none; margin: var(--gap-sm) 0 0; padding: 0; }
.db-drops li {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--glass-edge);
  font-size: 0.9rem;
}
.db-drops li span:first-child { flex: 1 1 auto; }

/* ==========================================================================
  21. Quick actions
   ==========================================================================
   Big, obvious, few. The home page had six cards of prose above the fold and
   nobody read them; these are the four things a visitor actually wants.
   -------------------------------------------------------------------------- */

.quick {
  display: grid;
  gap: var(--gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
}

.quick a {
  display: grid;
  gap: 0.2rem;
  align-content: center;
  min-height: 132px;
  padding: var(--gap-md);
  border-radius: var(--radius);
  border: 1px solid var(--glass-edge);
  background-image: linear-gradient(150deg, var(--glass-fill-strong), var(--glass-fill) 55%, var(--glass-tint));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
  text-decoration: none;
  color: inherit;
  transition: transform 0.18s var(--ease), border-color 0.18s var(--ease);
}
.quick a:hover { transform: translateY(-3px); border-color: var(--accent); color: inherit; }
.quick .q-title { font-family: var(--font-display); font-size: var(--step-2); line-height: 1.1; }
.quick .q-sub { font-size: var(--step--1); color: var(--text-dim); }
.quick svg { width: 26px; height: 26px; color: var(--accent); margin-bottom: 0.35rem; }

/* The one card that should pull harder than the others. */
.quick a.q-lead {
  background-image: linear-gradient(140deg, color-mix(in srgb, var(--accent) 34%, transparent), color-mix(in srgb, var(--pur-700) 30%, transparent));
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.quick a.q-lead .q-sub { color: var(--text-muted); }

/* The hero pair. Big enough to be the obvious next move on a phone, where
   the fold arrives right underneath them. */
.btn--lg { padding: 0.95rem 1.7rem; font-size: 1.02rem; }

@media (max-width: 480px) {
  .btn--lg { width: 100%; justify-content: center; }
}

@media print { .js .reveal { opacity: 1; transform: none; } }

/* ==========================================================================
  22. The codex
   ==========================================================================
   Item and skill text leans on about fifty words it never explains. Every one
   of them is underlined in its group's colour wherever it appears, and links
   to its line on the codex page. The colour is the point: after a few minutes
   a reader stops needing the link, because "this is a timing word" and "this
   is a status effect" are legible at a glance.

   A dotted underline rather than a link colour - forty links in one skill
   table would read as noise, and these are annotations, not navigation. */

:root {
  --t-st: #c4a6ff;  /* states you build up */
  --t-ti: #74dfbe;  /* timing */
  --t-ge: #ffd479;  /* words on gear */
  --t-of: #ffa768;  /* attack */
  --t-de: #8fbcff;  /* defence */
  --t-sx: #ff93b6;  /* status effects */
  --t-sa: #a8b2c6;  /* the six stats */
}

html[data-theme="light"] {
  --t-st: #6b46c1;
  --t-ti: #0e7048;
  --t-ge: #8a6000;
  --t-of: #a1500b;
  --t-de: #1c5aa8;
  --t-sx: #a82c55;
  --t-sa: #4a5568;
}

.term {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
  /* The colour lives on a custom property so the underline, the hover fill
     and the codex chip all read from one value. */
  border-bottom-color: var(--term);
  cursor: help;
}
.term:hover, .term:focus-visible {
  color: var(--term);
  border-bottom-style: solid;
}
.term--st { --term: var(--t-st); }
.term--ti { --term: var(--t-ti); }
.term--ge { --term: var(--t-ge); }
.term--of { --term: var(--t-of); }
.term--de { --term: var(--t-de); }
.term--sx { --term: var(--t-sx); }
.term--sa { --term: var(--t-sa); }

/* On the codex page itself the term is the heading of its own row, so it gets
   the full colour instead of a hint of it. */
.term--static {
  color: var(--term);
  border-bottom: 0;
  font-weight: 700;
  cursor: default;
}

.codex { padding-block: var(--gap-xl) var(--gap-2xl); }
.codex-tools { max-width: 26rem; margin-bottom: var(--gap-lg); }
.codex-tools .field { width: 100%; }

.codex-group { margin-bottom: var(--gap-2xl); scroll-margin-top: calc(var(--header-h) + 1rem); }
.codex-group h2 {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: var(--step-2);
}
/* A bar of the group's colour, so the heading and its terms are obviously the
   same family. */
.codex-group h2::before {
  content: "";
  width: 5px;
  align-self: stretch;
  min-height: 1.4em;
  border-radius: 999px;
  background: var(--group);
}
.codex-group--st { --group: var(--t-st); }
.codex-group--ti { --group: var(--t-ti); }
.codex-group--ge { --group: var(--t-ge); }
.codex-group--of { --group: var(--t-of); }
.codex-group--de { --group: var(--t-de); }
.codex-group--sx { --group: var(--t-sx); }
.codex-group--sa { --group: var(--t-sa); }

.codex-blurb { color: var(--text-muted); max-width: 60ch; margin-bottom: var(--gap-md); }

.codex-list {
  display: grid;
  gap: 0;
  margin: 0;
  border-top: 1px solid var(--glass-edge);
}
.codex-row {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
  gap: var(--gap-md);
  padding: 0.7rem 0.2rem;
  border-bottom: 1px solid var(--glass-edge);
  scroll-margin-top: calc(var(--header-h) + 1rem);
}
.codex-row:target { background: var(--glass-fill-strong); border-radius: var(--radius-sm); }
.codex-row dt { min-width: 0; }
.codex-row dd { margin: 0; color: var(--text-muted); min-width: 0; }

@media (max-width: 620px) {
  .codex-row { grid-template-columns: minmax(0, 1fr); gap: 0.2rem; }
}

.codex-empty { padding: var(--gap-xl) 0; text-align: center; color: var(--text-dim); }

/* ==========================================================================
  23. Effects
   ==========================================================================
   Ports of four React Bits components. Everything here is decoration: it all
   respects prefers-reduced-motion and the page is complete without any of it.

   The blurred CSS aurora stays as the fallback and is only hidden once the
   shader has actually compiled, so a browser without WebGL2 still gets one. */

.aurora canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.aurora[data-gl="on"] span { display: none; }
.aurora[data-gl="on"] {
  /* The shader carries its own softness, so the heavy blur that the CSS
     version needs would only cost fill rate. */
  filter: none;
  opacity: 0.85;
}
html[data-theme="light"] .aurora[data-gl="on"] { opacity: 0.32; }

/* The spark canvas sits over everything and catches nothing. */
.fx-spark {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 300;
}

/* --- FoldText ------------------------------------------------------------
   Each character is a panel hinged on its top edge. One custom property per
   character turns into an animation-delay, which is what gsap's stagger does
   and what the compositor can run on its own. */

/* A word is one unwrappable unit. Without it the browser breaks lines between
   any two panels, which turned "Orphans" into "Orphan" and "s". */
.fold-word { display: inline-block; white-space: nowrap; }

.fold-seg {
  display: inline-block;
  white-space: pre;
  transform-origin: 50% 0%;
  backface-visibility: hidden;
}
/* The gradient word folds whole. Splitting it made each letter its own
   painting context, and an ancestor's background-clip: text cannot reach
   inside one - the letters came out transparent and the word disappeared. */
.fold-seg--whole { display: inline-block; }

/* No separate "folded" state. `backwards` makes the animation's first frame
   apply during its own delay, which is the only thing a starting state was
   for - and it means a browser that never runs the animation shows the words
   instead of hiding them forever. Text must never depend on a frame firing. */
[data-fold="in"] .fold-seg {
  animation: fold-in 0.65s cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(var(--i) * 0.045s);
}
@keyframes fold-in {
  from { opacity: 0; transform: perspective(700px) rotateX(-92deg); }
  to   { opacity: 1; transform: perspective(700px) rotateX(0deg); }
}

/* --- SpecularButton ------------------------------------------------------
   A bright arc travelling the border, steered by the pointer. The angle and
   the brightness arrive from script as two custom properties; everything else
   is a conic gradient masked to the border box, which the compositor draws
   without a repaint. */

.is-specular { position: relative; }
.is-specular::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: conic-gradient(
    from var(--sb-angle, 0deg),
    transparent 0deg,
    rgb(255 255 255 / 0.9) 6deg,
    transparent 22deg,
    transparent 180deg,
    rgb(255 255 255 / 0.65) 186deg,
    transparent 202deg,
    transparent 360deg);
  /* Composite the gradient down to the ring only. Both spellings: Safari
     still wants the prefix. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: var(--sb-bright, 0);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .is-specular::after { display: none; }
}

/* The inherited reference, folded away. The page reads as a table of contents
   by default and the wiki material is one click behind it. */
.more {
  margin-top: var(--gap-md);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-sm);
  background-image: linear-gradient(160deg, var(--glass-fill), transparent);
}
.more > summary {
  cursor: pointer;
  padding: 0.7rem 0.9rem;
  font-size: var(--step--1);
  color: var(--text-muted);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.more > summary::-webkit-details-marker { display: none; }
.more > summary::before {
  content: "+";
  font-family: var(--font-mono);
  color: var(--accent);
  width: 1ch;
}
.more[open] > summary::before { content: "2"; }
.more > summary:hover { color: var(--text); }
.more-body {
  padding: 0 0.9rem 0.9rem;
  border-top: 1px solid var(--glass-edge);
}
.more-body > :first-child { margin-top: var(--gap-sm); }
