/* ===========================================================================
   Andromida DB v2 - design system
   ---------------------------------------------------------------------------
   Built on the tokens the existing site already established, so it still reads
   as the same database to players:

     background #222 -> a proper dark scale
     text       #ccc
     accent     #FC0 / #f7ef79  (gold)
     headings   Georgia serif

   Mobile-first: every layout rule starts at phone width and widens with
   min-width queries. No fixed pixel widths (the old style.css had .w680,
   .w800, .w330 classes that forced horizontal scrolling on a phone).
   =========================================================================== */

:root {
  /* --- surfaces (dark is the default, matching today's site) ------------- */
  --bg:            #1b1b1d;
  --surface:       #232326;
  --surface-2:     #2b2b2f;
  --surface-3:     #34343a;
  --border:        #3d3d44;
  --border-strong: #54545d;

  /* --- text -------------------------------------------------------------- */
  --text:          #d6d6d2;
  --text-dim:      #9a9a96;
  --text-faint:    #74746f;

  /* --- brand ------------------------------------------------------------- */
  --gold:          #ffcc00;
  --gold-soft:     #f7ef79;
  --gold-dim:      #a8880c;

  /* --- semantic ---------------------------------------------------------- */
  --good:          #6fcf78;
  --bad:           #ef5f5e;
  --info:          #6cb6e8;

  /* --- stat group accents (used as thin rails, never as text colour) ----- */
  --g-core:    #ffcc00;
  --g-element: #ff8a3d;
  --g-pen:     #e8617f;
  --g-res:     #6cb6e8;
  --g-attr:    #9b8cf0;
  --g-bane:    #6fcf78;
  --g-status:  #c98ad6;
  --g-misc:    #8a8a92;
  --g-detail:  #6b6b72;
  --g-other:   #6b6b72;

  /* --- type ------------------------------------------------------------- */
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-head: Georgia, "Times New Roman", Times, serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* --- rhythm ----------------------------------------------------------- */
  --gutter: 16px;
  --radius: 8px;
  --radius-sm: 5px;
  --shadow: 0 2px 10px rgb(0 0 0 / 0.35);

  color-scheme: dark;
}

/* The database has always been dark, and players expect that, so dark is the
   base theme for everyone regardless of OS preference. Light is opt-in only,
   via data-theme="light" (a toggle can set it and persist it in localStorage). */
@media (prefers-color-scheme: light) {
  :root[data-theme="light-auto"] {
    --bg:            #f3f1ec;
    --surface:       #fffefb;
    --surface-2:     #eceae4;
    --surface-3:     #e0ddd5;
    --border:        #d3cfc5;
    --border-strong: #b5b0a3;
    --text:          #26251f;
    --text-dim:      #5d5b52;
    --text-faint:    #8b887d;
    --gold:          #9a7500;
    --gold-soft:     #7d5f00;
    --gold-dim:      #c9a63a;
    --shadow: 0 2px 10px rgb(0 0 0 / 0.10);
    color-scheme: light;
  }
}
:root[data-theme="light"] {
  --bg: #f3f1ec; --surface: #fffefb; --surface-2: #eceae4; --surface-3: #e0ddd5;
  --border: #d3cfc5; --border-strong: #b5b0a3;
  --text: #26251f; --text-dim: #5d5b52; --text-faint: #8b887d;
  --gold: #9a7500; --gold-soft: #7d5f00; --gold-dim: #c9a63a;
  --shadow: 0 2px 10px rgb(0 0 0 / 0.10);
  color-scheme: light;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 var(--font-body);
  overflow-x: hidden;           /* nothing should ever scroll sideways */
}

h1, h2, h3, h4 { font-family: var(--font-head); font-weight: 600; margin: 0 0 .4em; line-height: 1.2; }
h1 { font-size: 1.5rem; }
h2 { font-size: 1.25rem; }
h3 { font-size: 1.05rem; }

a { color: var(--gold); text-decoration: none; }
a:hover, a:focus-visible { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* =========================================================== layout ====== */

.wrap {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* ===========================================================================
   The header: the old site's, rebuilt.
   ===========================================================================
   A Granado Espada banner with the logo on it, the version and build date at
   the top right, and a translucent brass bar of sections carrying their row
   counts. The two background images are the originals out of the old site's
   images/ folder, in images/chrome/ here (prolog.webp resized from 8000px wide
   to 2400 - the original was 1.3 MB for a strip nobody sees at full size).

   The bar is sticky and the banner is not: scrolling leaves the navigation
   where a visitor can reach it, and takes the decoration away. That is the one
   deliberate departure from the original, which scrolled the whole thing off.
   ========================================================================= */

.site-header {
  position: relative;
  z-index: 40;
  background: #0b0a08 url('../../images/chrome/prolog.webp') no-repeat center top;
  background-size: cover;
}
/* The logo, at the size the original wore it: about two thirds of the window,
   capped at its own 900px so it is never upscaled into mush.
 *
 * The banner is SHORTER than the logo on purpose. The artwork is 900x297 and
 * most of the bottom third is the "G" flourish's tail, which the original let
 * run off the bottom behind the brass bar - that overlap is why the header
 * reads as a masthead rather than as a picture with a menu under it. So the
 * anchor is a fixed height with overflow hidden, and the height is derived from
 * the logo's own width so the crop is the same proportion at every size.
 *
 * 0.72 of the logo's height is not arbitrary: the gold "Andromida" ends at 0.61
 * of the artwork and the flourish runs on to 0.96, so 0.72 keeps every readable
 * part with room to spare and cuts only the tail. At 0.64 that fit on a desktop
 * and guillotined "Andromida" on a phone, where the same proportion is a dozen
 * pixels rather than forty. 0.72 / 3.0303 (the aspect ratio) = 0.2376. */
.site-header__logo {
  --logo-w: min(900px, 68vw);
  display: block;
  /* The floor matters on a phone: below ~300px of logo the proportional crop
     is a dozen pixels, and the bar's own inset highlight eats the last of
     them, so "Andromida" ends up sitting under the brass. */
  height: max(72px, calc(var(--logo-w) * 0.2376));
  padding: 0;
  overflow: hidden;
  text-align: center;
  line-height: 0;
  border: 0;
}
.site-header__logo img {
  width: var(--logo-w); height: auto;
  /* A shade of the top margin trimmed, so the script sits where it did. */
  margin-top: calc(var(--logo-w) / -112);
  filter: drop-shadow(0 2px 6px rgb(0 0 0 / .8));
}
.site-header__meta {
  position: absolute; top: 10px; right: 14px;
  text-align: right; font-size: .72rem; line-height: 1.5;
  color: var(--text-dim);
  text-shadow: 0 1px 3px #000;
}
.site-header__meta a { color: var(--gold-dim); }

/* --- the brass bar ------------------------------------------------------- */

.site-nav {
  position: sticky; top: 0; z-index: 41;
  background: linear-gradient(to bottom, rgb(52 54 44 / .92), rgb(31 28 20 / .96));
  box-shadow: inset 0 3px 8px rgb(255 255 255 / .18),
              inset 0 -3px 8px rgb(255 255 255 / .18),
              0 2px 10px rgb(0 0 0 / .5);
  backdrop-filter: blur(6px);
}
.site-nav__row {
  display: flex; align-items: center; gap: 6px;
  min-height: 40px;
}
.site-nav__inner {
  display: flex; align-items: center; gap: 2px;
  flex: 1 1 auto; min-width: 0;
}
.site-nav a {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 13px;
  color: #ffcc00; font-size: .92rem; white-space: nowrap;
  border: 0; border-radius: 0;
  transition: background-color .25s ease;
}
.site-nav a span { color: rgb(255 255 255 / .45); font-size: .82em; }
.site-nav a:hover { background: rgb(0 0 0 / .45); text-decoration: none; }
.site-nav a[aria-current="page"] { background: rgb(0 0 0 / .45); }

/* The icon set, inline SVG so there is no icon font to fetch. */
.ge-ico { width: 15px; height: 15px; flex: none; fill: currentColor; opacity: .9; }

.site-nav__burger { display: none; }

@media (max-width: 560px) { .site-nav a { padding: 8px 9px; font-size: .84rem; } }

/* Portraits are taller than item icons. */
.card__icon.portrait { width: 44px; height: 56px; }
.detail__head .card__icon.portrait { flex: none; }

.grade[data-grade="cash"]        { background: var(--g-pen);  color: #1b1b1d; }
.grade[data-grade="recruit"]     { background: var(--g-res);  color: #1b1b1d; }
.grade[data-grade="basic"]       { background: var(--g-bane); color: #1b1b1d; }
.grade[data-grade="accomplish"]  { background: var(--gold);   color: #1b1b1d; }

/* The search sits at the right end of the brass bar, with the magnifier inside
   the field. The old site had a magnifier that linked to a separate
   search.php; this one searches as you type, so the glyph is decoration and
   the field is the control. */
.quick-search {
  position: relative; flex: 1 1 auto; min-width: 0;
  margin-left: auto; max-width: 340px;
  display: flex; align-items: center;
}
.quick-search > .ge-ico {
  position: absolute; left: 9px;
  color: #ffcc00; opacity: .65; pointer-events: none;
}
.quick-search input {
  width: 100%;
  padding: 6px 10px 6px 29px;
  background: rgb(0 0 0 / .4);
  border: 1px solid rgb(255 204 0 / .28);
  border-radius: 2px;
  color: var(--text);
  font: inherit; font-size: .88rem;
}
.quick-search input:focus {
  outline: none;
  border-color: rgb(255 204 0 / .7);
  background: rgb(0 0 0 / .6);
}
.quick-search input::placeholder { color: rgb(255 255 255 / .38); }
.quick-search__results {
  position: absolute; inset-inline: 0; top: calc(100% + 6px);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  max-height: min(60vh, 420px);
  overflow-y: auto;
  z-index: 50;
}
.quick-search__results:empty { display: none; }
.quick-search__results a {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 9px 12px; border-bottom: 1px solid var(--border);
  color: var(--text);
}
.quick-search__results a:last-child { border-bottom: 0; }
.quick-search__results a:hover,
.quick-search__results a[aria-selected="true"] { background: var(--surface-3); text-decoration: none; }
.quick-search__results small { color: var(--text-faint); white-space: nowrap; }

.layout {
  display: grid;
  /* minmax(0, 1fr), not 1fr: a grid item's automatic minimum is its content's,
     so one wide child - a panel with a max-width, a long table - pushes the
     column past the viewport and gives the page a horizontal scrollbar with
     nothing at the end of it. The desktop rule below has always said
     minmax(0, ...) for the results column; the phone layout needs it too. */
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
  padding: 18px 0 40px;
  align-items: start;
}
/* .layout's padding shorthand overrides the .wrap gutter on the same element,
   which on a desktop is right - the sidebar and the results run to the edge of
   the 1400px column, as the old site's did. On a phone there is no sidebar in
   the flow, so it just put the headings and the cards hard against both edges
   of the screen. The gutter comes back at the width where that happens. */
@media (max-width: 899px) {
  .layout { padding-left: var(--gutter); padding-right: var(--gutter); }
}
@media (min-width: 900px) {
  .layout { grid-template-columns: 290px minmax(0, 1fr); }
}

/* ========================================================== filters ====== */

/* On a phone the filter panel is a slide-over sheet; on a desktop it is a
   sticky sidebar. Same markup, no duplication. */
/* The old site's search column: a dark panel inside a thin brass frame, with a
   gold bar per section. The frame and the type scale come from the game UI
   (same tokens as the item and stance panels further down this file) rather
   than from the generic card styling, because this column sits beside those
   panels and the two should read as one screen. */
.filters {
  background: var(--ge-bg, var(--surface));
  /* Near-black, with the same corner the crest slice already has. The frame
     follows the artwork's curve instead of cutting across it; see --ge-radius.
     The title bar is rounded to match, which is the half that was missing the
     first time and left a lit notch at each end of it. */
  border: 1px solid #241f14;
  border-radius: var(--ge-radius);
  padding: 0;
}
@media (max-width: 899px) {
  .filters {
    position: fixed; inset: 0 0 0 auto;
    width: min(92vw, 380px);
    z-index: 60;
    border-radius: 0;
    overflow-y: auto; overflow-x: hidden;   /* see the desktop rule below */
    transform: translateX(100%);
    transition: transform .22s ease;
    box-shadow: var(--shadow);
  }
  .filters[data-open="true"] { transform: none; }
  .scrim {
    position: fixed; inset: 0; z-index: 55;
    background: rgb(0 0 0 / .55);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  .scrim[data-open="true"] { opacity: 1; pointer-events: auto; }
}
@media (min-width: 900px) {
  /* Clears the sticky section bar (40px) with a little air.
   *
   * overflow-x has to be named, not left to `auto` by implication: the title
   * bar overhangs the panel by 1px on each side so its crests cover the frame
   * corners, which makes the content 2px wider than the box and gives a
   * horizontal scrollbar across the bottom of the sidebar. There is nothing
   * along that axis worth scrolling to. */
  .filters {
    position: sticky; top: 52px; max-height: calc(100vh - 70px);
    overflow-y: auto; overflow-x: hidden;
  }
  .filters__close { display: none; }
}

/* The gold title bar with the crest at each end. These are the client's own
   slices - titleleft/titlemid/titleright - which is why it looks like the game
   rather than like a CSS gradient pretending to.
 *
 * The bar sits INSIDE the frame, not overhanging it. It used to overhang by
 * 1px so the crests covered the frame corners, which is what the original did
 * (`left: -2px`) - but that only mattered while the frame was bright brass.
 * Against the near-black frame it buys nothing, and it made the panel content
 * 2px wider than the panel, which put a horizontal scrollbar along the bottom
 * of the sidebar. */
.ge-title, .filters__head {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  height: 27px; padding: 0 34px;
  /* Rounded with the panel, never on its own: a square bar inside a rounded
     frame is what produced the notch the comment above remembers.
     overflow has to be named as well - the crests are ::before/::after, and a
     border-radius clips the element's own background but not its children, so
     without this the two 30px slices would keep their square corners and the
     rounding would be invisible exactly where it matters. */
  border-radius: var(--ge-radius) var(--ge-radius) 0 0;
  overflow: hidden;
  background: #2b2416 url('../../images/chrome/titlemid.jpg') repeat-x center;
  font-family: var(--font-head);
  font-size: .92rem; font-weight: 600;
  color: #f7ef79;
  text-shadow: 0 1px 2px #000;
}
.ge-title::before, .filters__head::before,
.ge-title::after,  .filters__head::after {
  content: ''; position: absolute; top: 0;
  width: 30px; height: 27px;
}
.ge-title::before, .filters__head::before {
  left: 0; background: url('../../images/chrome/titleleft.jpg') no-repeat left top;
}
.ge-title::after, .filters__head::after {
  right: 0; background: url('../../images/chrome/titleright.jpg') no-repeat right top;
}
.filters__close {
  position: absolute; right: 4px; top: 0;
  background: none; border: 0; color: #f7ef79;
  font-size: 1.3rem; line-height: 27px; cursor: pointer; padding: 0 6px;
  z-index: 1;
}

/* --- one section ---------------------------------------------------------
   Rendered as <details>, so the summary IS the clickable gold bar and the
   browser owns the open state. `fieldset.facet` is still styled the same way:
   the monster, map and shop pages build their facets with ui.js's facetGroup(),
   which emits a fieldset, and both shapes have to look identical in the same
   column. */
details.facet, fieldset.facet {
  border: 0; border-bottom: 1px solid var(--ge-frame-2, var(--border));
  padding: 0; margin: 0;
}
details.facet:last-child, fieldset.facet:last-child { border-bottom: 0; }

/* The section bar is the same brass the nav bar is made of.
 *
 * It used to be the client's tab strip (tabmid.gif) stretched to height, which
 * was a different metal from everything around it: a flat olive band with pale
 * cream type, sitting directly under a nav bar of lit brass with gold type and
 * directly above a panel of neither. Three treatments in fifteen vertical pixels
 * is what read as "not arranged with the rest".
 *
 * So it borrows the nav's gradient, its two inset highlights and its #ffcc00,
 * and the column now reads as one piece of furniture from the menu down. The
 * highlights are inset box-shadows rather than borders so they do not add height
 * - a facet list is a dozen of these stacked, and a pixel each is a row. */
.facet__head, fieldset.facet > legend {
  display: flex; align-items: center; gap: 8px;
  width: 100%; box-sizing: border-box;
  padding: 7px 11px;
  background: linear-gradient(to bottom, rgb(52 54 44 / .92), rgb(31 28 20 / .96));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14),
              inset 0 -1px 0 rgb(255 255 255 / .07),
              0 1px 4px rgb(0 0 0 / .45);
  border-top: 0;
  border-bottom: 1px solid #000;
  font-family: var(--font-head);
  font-size: .88rem; letter-spacing: .02em;
  color: #ffcc00;
  text-shadow: 0 1px 2px #000;
  cursor: pointer;
  user-select: none;
  transition: background-color .2s ease;
}
.facet__head::-webkit-details-marker { display: none; }
.facet__head { list-style: none; }
/* The nav bar darkens on hover rather than brightening; same here, so the two
   respond to the pointer the same way. */
.facet__head:hover, fieldset.facet > legend:hover { background-image: none; background-color: rgb(0 0 0 / .45); }
details.facet[open] > .facet__head { color: #ffe066; }
/* The category glyph. Dimmer than the label at rest so the bar still reads as a
   row of words, and lit with it on hover and while the panel is open - the icon
   is there to be recognised at a glance, not to compete with the name. */
.cat-ico {
  width: 15px; height: 15px; flex: none;
  fill: currentColor; opacity: .62;
  transition: opacity .2s ease;
}
.facet__head:hover .cat-ico,
details.facet[open] > .facet__head .cat-ico { opacity: 1; }
.start__cat .cat-ico { width: 17px; height: 17px; opacity: .8; }
.start__cat:hover .cat-ico { opacity: 1; }

.facet__title { flex: 1 1 auto; min-width: 0; }
.facet__count {
  color: var(--ge-dim, var(--text-faint)); font-size: .78em;
  font-variant-numeric: tabular-nums;
}

/* The chevron: a CSS triangle, so there is no icon font and no SVG to load. */
.facet__head::before {
  content: ''; flex: none;
  width: 0; height: 0;
  border: 4px solid transparent;
  border-left-color: currentColor;
  transition: transform .15s ease;
}
details.facet[open] > .facet__head::before { transform: rotate(90deg) translateX(1px); }

.facet__body {
  display: flex; flex-direction: column; gap: 0;
  max-height: 300px; overflow-y: auto;
  padding: 4px 0;
  background: var(--ge-bg-2, var(--surface));
}
.facet__body--grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 2px; }

.check {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 10px;
  cursor: pointer; font-size: .875rem;
  color: var(--ge-text, var(--text));
  min-height: 28px;
}
.check:hover { background: color-mix(in srgb, var(--ge-gold, #e3c265) 10%, transparent); }
.check input { accent-color: var(--ge-gold-2, var(--gold)); width: 14px; height: 14px; flex: none; }
.check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.check input:checked + .check__label { color: var(--ge-gold, var(--gold)); }
.check em, .check__n {
  margin-left: auto; color: var(--ge-dim, var(--text-faint));
  font-style: normal; font-size: .8em; font-variant-numeric: tabular-nums;
}

.range { display: flex; align-items: center; gap: 6px; margin: 0; padding: 3px 10px; }
.range label { flex: 1 1 auto; font-size: .85rem; color: var(--ge-text, var(--text-dim)); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.range input {
  width: 62px; flex: none; padding: 4px 6px; font: inherit; font-size: .82rem;
  background: var(--ge-bg, var(--surface-2)); border: 1px solid var(--ge-frame-2, var(--border));
  border-radius: 2px; color: var(--ge-text, var(--text));
}
.range input:not(:placeholder-shown) { border-color: var(--ge-gold-2, var(--gold-dim)); }

/* Touch targets are tight by design here - the old column was dense and that
   is half of why it was quick to use - so give them back on a phone, where
   the panel is a full-height sheet and has the room. */
@media (max-width: 899px) {
  .check { min-height: 36px; font-size: .92rem; }
  .facet__body { max-height: none; }
}

/* Light mode. The --ge-* tokens are deliberately fixed - inside an item tooltip
   they carry meaning and must not flip - but this column is site chrome, not a
   tooltip, so here the brass frame is re-stated in light values instead. */
:root[data-theme="light"] .filters,
:root[data-theme="light-auto"] .filters {
  background: var(--surface); border-color: var(--border-strong);
}
:root[data-theme="light"] .filters__head,
:root[data-theme="light-auto"] .filters__head,
:root[data-theme="light"] .facet__head,
:root[data-theme="light-auto"] .facet__head,
:root[data-theme="light"] fieldset.facet > legend,
:root[data-theme="light-auto"] fieldset.facet > legend {
  background-image: none;
  background-color: var(--surface-3); color: var(--gold-soft);
  box-shadow: none;
  border-bottom-color: var(--border);
}
:root[data-theme="light"] .facet__body,
:root[data-theme="light-auto"] .facet__body,
:root[data-theme="light"] .facet__list,
:root[data-theme="light-auto"] .facet__list {
  background: var(--surface);
}
:root[data-theme="light"] .check,
:root[data-theme="light-auto"] .check { color: var(--text); }
:root[data-theme="light"] details.facet,
:root[data-theme="light-auto"] details.facet,
:root[data-theme="light"] fieldset.facet,
:root[data-theme="light-auto"] fieldset.facet { border-bottom-color: var(--border); }

.btn {
  display: inline-flex; align-items: center; gap: 6px; justify-content: center;
  padding: 8px 14px; min-height: 38px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  font: inherit; cursor: pointer;
}
.btn:hover { background: var(--surface-3); }
.btn--primary { background: var(--gold); color: #1b1b1d; border-color: var(--gold); font-weight: 600; }
.btn--primary:hover { background: var(--gold-soft); }
.btn--ghost { background: none; border-color: var(--border); color: var(--text-dim); }

/* ========================================================== results ====== */

.toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 12px;
}
.toolbar__count { color: var(--text-dim); font-size: .9rem; margin-inline-end: auto; }
.toolbar select {
  padding: 7px 10px; min-height: 38px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm); font: inherit;
}

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.chips:empty { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 6px 4px 10px; font-size: .82rem;
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: 999px; color: var(--text-dim);
}
.chip button {
  background: none; border: 0; color: var(--text-faint);
  cursor: pointer; font-size: 1rem; line-height: 1; padding: 0 2px;
}
.chip button:hover { color: var(--bad); }

.cards { display: grid; grid-template-columns: 1fr; gap: 10px; }
@media (min-width: 620px)  { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1150px) { .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 12px;
  cursor: pointer;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  text-align: left;
  width: 100%;
  font: inherit;
  color: inherit;
}
.card:hover { border-color: var(--border-strong); border-left-color: var(--gold); background: var(--surface-2); }
.card[aria-current="true"] { border-left-color: var(--gold); background: var(--surface-2); }

/* No background: the artwork is chroma-keyed and transparent everywhere it is
   not the subject, so a filled square behind it draws a grey tile around every
   portrait instead of letting it sit on the card. */
.card__icon {
  width: 44px; height: 44px; flex: none; position: relative;
  border-radius: var(--radius-sm);
  display: grid; place-items: center; overflow: hidden;
  font-size: .62rem; font-weight: 700; letter-spacing: .03em;
  color: var(--text-faint); text-transform: uppercase;
}
/* The sprite is painted over the abbreviation, so a missing sheet degrades to
   a readable type label instead of an empty grey square. */
.card__icon i {
  position: absolute; inset: 0;
  background-repeat: no-repeat;
  transform-origin: top left;
  transform: scale(var(--icon-scale, 1));
}

.card__name { font-weight: 600; font-size: .98rem; line-height: 1.25; overflow-wrap: anywhere; }
.card__meta { color: var(--text-dim); font-size: .8rem; margin-top: 3px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
.card__stats {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px; margin-top: 4px;
}

/* A pill is a flex row, not a line of inline boxes.
   It was three inline things - a ::before dot nudged with `vertical-align: 1px`,
   a label, and a bold number - each sitting on the text baseline at a different
   height, so a row of pills had dots that stepped up and down and numbers that
   did not line up with their own labels. One pixel of vertical-align cannot fix
   that, because the dot has no baseline to nudge FROM: it is a 6px block in a
   .76rem line box. Centring the three on the cross axis is exact at any font
   size, and the gap replaces the margin so the spacing is one number. */
.stat-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .76rem; line-height: 1.5; padding: 2px 8px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-dim); white-space: nowrap;
}
.stat-pill b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.stat-pill[data-group]::before,
.stat-pill[data-stat]::before {
  content: ""; flex: none; width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--g-other);
}
.stat-pill[data-group="core"]::before    { background: var(--g-core); }
.stat-pill[data-group="element"]::before { background: var(--g-element); }
.stat-pill[data-group="pen"]::before     { background: var(--g-pen); }
.stat-pill[data-group="res"]::before     { background: var(--g-res); }
.stat-pill[data-group="attr"]::before    { background: var(--g-attr); }
.stat-pill[data-group="bane"]::before    { background: var(--g-bane); }
.stat-pill[data-group="status"]::before  { background: var(--g-status); }
.stat-pill[data-group="misc"]::before    { background: var(--g-misc); }
.stat-pill[data-group="detail"]::before  { background: var(--g-detail); }

/* The six primary character stats keep the old site's colours, which players
   read by hue before they read the label. They are set on the DOT, not on the
   text: #330066 and #3300ff are almost black against this background and would
   be unreadable as type, but as a 6px marker beside a light label they are the
   colours people know. A ring lifts the two darkest off the pill. */
.stat-pill[data-stat]::before { box-shadow: 0 0 0 1px rgb(255 255 255 / .22); }
.stat-pill[data-stat="STR"]::before { background: #ff6600; }  /* orange */
.stat-pill[data-stat="AGI"]::before { background: #ffff00; }  /* yellow */
.stat-pill[data-stat="CON"]::before { background: #cc0000; }  /* red, shown as HP  */
.stat-pill[data-stat="DEX"]::before { background: #3300ff; }  /* blue */
.stat-pill[data-stat="INT"]::before { background: #00cc00; }  /* green */
.stat-pill[data-stat="CHA"]::before { background: #330066; }  /* purple, shown as SEN */

/* --- the label palette --------------------------------------------------
   Everything else a card can show - a monster's resistances, a map's counts, a
   raid's ratings - keyed by the LABEL rather than by a group, so "Fire" is the
   same red on every page it appears on and a card is scannable by colour.

   Seven hues, all from one family (Material 200/300), so they sit together at
   6px on a dark pill and none of them shouts. Within a single card no two
   labels share one:

     monster   HP green · EXP amber · Fire red · Ice cyan · Lgt violet · Psy pink
     map       Monsters orange · NPCs cyan · Baron red · Stalls amber
     raid      Rewards amber · A.R. orange · D.R. cyan

   The five elemental ones are the values Yanuck specified; the rest follow them
   so the set reads as one palette. */
.stat-pill[data-stat="EXP"]::before,
.stat-pill[data-stat="Stalls"]::before,
.stat-pill[data-stat="Rewards"]::before   { background: #ffd54f; }  /* amber   */
.stat-pill[data-stat="Fire"]::before,
.stat-pill[data-stat="Baron"]::before     { background: #ff5252; }  /* red     */
.stat-pill[data-stat="Ice"]::before,
.stat-pill[data-stat="NPCs"]::before,
.stat-pill[data-stat="D.R."]::before      { background: #64d8ff; }  /* cyan    */
.stat-pill[data-stat="Lgt"]::before       { background: #b388ff; }  /* violet  */
.stat-pill[data-stat="Psy"]::before       { background: #f48fb1; }  /* pink    */
.stat-pill[data-stat="HP"]::before        { background: #81c784; }  /* green   */
.stat-pill[data-stat="Monsters"]::before,
.stat-pill[data-stat="A.R."]::before      { background: #ff8a65; }  /* orange  */

.grade {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  padding: 1px 6px; border-radius: 3px;
  background: var(--surface-3); color: var(--text-dim);
}
/* Grand Master is the top of the ladder (EnchantLv 156), so it outranks the
   gold High Master wears: a pale rim on dark, which reads as "above gold"
   without inventing a brighter gold that would fight the brass chrome. */
.grade[data-grade="Grand Master"] {
  background: #241d10; color: var(--gold, #ffd76a);
  box-shadow: inset 0 0 0 1px var(--gold, #ffd76a);
  font-weight: 700;
}
.grade[data-grade="High Master"] { background: var(--gold); color: #1b1b1d; font-weight: 600; }
.grade[data-grade="Master"]      { background: var(--gold-dim); color: #16160f; }
.grade[data-grade="Expert"]      { background: #5a5a63; color: #eceae4; }

.empty, .loading {
  padding: 40px 16px; text-align: center; color: var(--text-dim);
  border: 1px dashed var(--border); border-radius: var(--radius);
}

/* --- the "pick a filter" screen -----------------------------------------
   The item page's opening state. Not a dashed empty box: nothing has gone
   wrong here, the page is waiting for a decision, so it looks like a panel
   rather than like a hole. */
.start {
  grid-column: 1 / -1; min-width: 0;
  padding: 34px 22px;
  background: var(--ge-bg-2, var(--surface));
  border: 1px solid var(--ge-frame, var(--border-strong));
  border-radius: var(--radius);
  text-align: center;
}
.start__title {
  font-family: var(--font-head);
  font-size: 1.25rem; color: var(--ge-gold, var(--gold));
  margin: 0 0 8px;
}
.start__lead {
  max-width: min(54ch, 100%); margin: 0 auto 22px;
  color: var(--text-dim); font-size: .92rem; line-height: 1.55;
}
.start__grid {
  /* 176px, not 150: the button carries an icon, a name and a count, and at 150
     the two longest names ("Accessory", "Material") were ellipsised down to
     "Access...". The grid still drops to two columns on a phone and one on a
     narrow one. */
  display: grid; grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));
  gap: 8px; max-width: min(740px, 100%); margin: 0 auto;
}
.start__cat {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 13px; min-height: 42px;
  background: linear-gradient(to bottom, rgb(52 54 44 / .92), rgb(31 28 20 / .96));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14);
  border: 1px solid var(--ge-frame-2, var(--border));
  border-radius: var(--radius-sm);
  color: #ffcc00; font: inherit; font-size: .92rem; cursor: pointer;
  transition: background-color .2s ease, border-color .2s ease;
}
.start__cat:hover {
  background-image: none; background-color: rgb(0 0 0 / .5);
  border-color: var(--ge-gold, var(--gold));
}
.start__catname { flex: 1 1 auto; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.start__cat em {
  flex: none; font-style: normal; font-size: .8em;
  color: rgb(255 255 255 / .45); font-variant-numeric: tabular-nums;
}

.pager { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 18px; }
.pager span { color: var(--text-dim); font-size: .9rem; }

/* =========================================================== detail ====== */

/* Bottom sheet on a phone, right-hand drawer on a desktop. */
.detail {
  position: fixed; z-index: 70;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow);
  overflow-y: auto;
  transition: transform .24s ease;
}
@media (max-width: 899px) {
  .detail {
    inset: auto 0 0 0; max-height: 88vh;
    border-radius: var(--radius) var(--radius) 0 0;
    transform: translateY(101%);
  }
  .detail[data-open="true"] { transform: none; }
}
@media (min-width: 900px) {
  .detail {
    inset: 0 0 0 auto; width: min(560px, 46vw);
    transform: translateX(101%);
  }
  .detail[data-open="true"] { transform: none; }
}
/* A stance panel is five skill columns wide; 560px turns each of them into a
   40-character-per-line ribbon. Pages that show one get a wider drawer. */
@media (min-width: 1100px) {
  .detail--wide { width: min(1000px, 72vw); }
}
.detail__head {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 14px 16px;
  display: flex; align-items: flex-start; gap: 12px;
}
.detail__head h2 { margin: 0; font-size: 1.15rem; }
.detail__head .meta { color: var(--text-dim); font-size: .85rem; margin-top: 3px; }
.detail__close {
  margin-left: auto; background: none; border: 0; color: var(--text-dim);
  font-size: 1.7rem; line-height: 1; cursor: pointer; padding: 0 4px;
}
.detail__body { padding: 14px 16px 40px; }
.detail__body > section { margin-bottom: 20px; }
.detail__body h3 {
  font-size: .82rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-faint); font-family: var(--font-body); font-weight: 700;
  border-bottom: 1px solid var(--border); padding-bottom: 5px; margin-bottom: 8px;
}
.detail .desc { color: var(--text-dim); font-size: .92rem; overflow-wrap: anywhere; }
.detail .desc span { font-weight: 600; }

.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; }
.kv > div {
  background: var(--surface-2); border-radius: var(--radius-sm);
  padding: 7px 10px; border-left: 2px solid var(--border-strong);
}
.kv dt { font-size: .74rem; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
.kv dd { margin: 1px 0 0; font-weight: 600; font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere; }

.stat-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.stat-table th, .stat-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--border); }
.stat-table td:last-child { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.stat-table tbody tr:hover { background: var(--surface-2); }
.stat-table caption {
  text-align: left; color: var(--text-faint); font-size: .76rem;
  text-transform: uppercase; letter-spacing: .05em; padding: 8px 8px 3px;
}

.ing { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid var(--border); font-size: .9rem; }
.ing:last-child { border-bottom: 0; }
.ing b { margin-left: auto; font-variant-numeric: tabular-nums; }

.piece-list { display: flex; flex-wrap: wrap; gap: 6px; }
.piece-list a { font-size: .84rem; padding: 3px 9px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 999px; }

/* ============================================================ misc ======= */

.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;
}

.skeleton {
  background: linear-gradient(90deg, var(--surface) 25%, var(--surface-2) 37%, var(--surface) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
  border-radius: var(--radius);
  height: 86px;
}
@keyframes shimmer { 0% { background-position: 100% 0 } 100% { background-position: 0 0 } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

.site-footer {
  border-top: 1px solid var(--border);
  padding: 18px 0 30px; color: var(--text-faint); font-size: .84rem;
}

/* ===========================================================================
   Sections added after the items/characters pilot: monsters, maps, shops and
   the reference pages. Everything below reuses the tokens defined at the top,
   so light mode and dark mode both come out right without a second set of
   rules - the pilot's mistake was defining light as the base and then patching
   dark on top, which meant every new component had to be fixed twice.
   ========================================================================= */

/* --- skip link and the More menu ---------------------------------------- */

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  background: var(--gold); color: #1b1b1d; padding: 10px 16px; font-weight: 600;
}
.skip:focus { left: 0; }

/* The More menu. The old site opened its dropdowns on hover, which is a trap on
   a touchscreen, so this is a <details>: the browser handles the open state and
   the keyboard, and a tap opens it. */
.site-nav__more { position: relative; }
.site-nav__more > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 5px;
  padding: 9px 13px;
  color: #ffcc00; font-size: .92rem; white-space: nowrap;
  transition: background-color .25s ease;
}
.site-nav__more > summary::-webkit-details-marker { display: none; }
.site-nav__more > summary .caret {
  width: 0; height: 0;
  border: 4px solid transparent; border-top-color: currentColor;
  margin-top: 3px;
}
.site-nav__more > summary:hover,
.site-nav__more[open] > summary { background: rgb(0 0 0 / .45); }
.site-nav__menu {
  position: absolute; left: 0; top: 100%; z-index: 42;
  min-width: 210px; padding: 0;
  background: rgb(255 204 0 / .94);
  box-shadow: 0 4px 12px rgb(0 0 0 / .6);
  display: flex; flex-direction: column;
}
.site-nav__menu a {
  white-space: nowrap; color: #1b1b1d; padding: 8px 14px;
  font-size: .9rem; transition: background-color .25s ease;
}
.site-nav__menu a:hover { background: rgb(0 0 0 / .78); color: #ffcc00; text-decoration: none; }

/* The Filters button lives on the bar at every width, beside the search on a
   desktop and beside the ☰ on a phone. */
.btn--filters {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  min-height: 30px; padding: 5px 12px;
  background: rgb(0 0 0 / .4);
  border: 1px solid rgb(255 204 0 / .35);
  border-radius: 2px;
  color: #ffcc00; font-size: .88rem;
}
.btn--filters:hover { background: rgb(0 0 0 / .65); border-color: rgb(255 204 0 / .7); }
.btn--filters .ge-ico { width: 13px; height: 13px; }
@media (min-width: 900px) { .btn--filters { display: none; } }

/* On a phone the section links become a stack behind the ☰ button, and the
   dropdown would hang off the edge, so it becomes a plain inline list. */
@media (max-width: 860px) {
  /* The version and build date sit over the logo once the banner narrows; the
     old site dropped them on a phone for the same reason. */
  .site-header__meta { display: none; }
  .site-header__logo { padding: 14px 0 18px; }

  .site-nav__burger {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    background: rgb(0 0 0 / .4); border: 1px solid rgb(255 204 0 / .35);
    color: #ffcc00; font-size: 1.1rem; line-height: 1;
    min-height: 30px; padding: 4px 11px; border-radius: 2px; cursor: pointer;
  }
  .site-nav__burger[aria-expanded="true"] { background: rgb(0 0 0 / .7); }

  /* The row keeps ☰ and Filters on one line; the links drop below it, which is
     why the stack is absolutely positioned rather than pushing the row apart. */
  .site-nav__row { flex-wrap: wrap; padding-top: 5px; padding-bottom: 5px; }
  .site-nav__inner {
    display: none; order: 3; flex-basis: 100%;
    flex-direction: column; align-items: stretch;
    padding: 6px 0 2px;
    border-top: 1px solid rgb(255 204 0 / .2);
    margin-top: 5px;
  }
  .site-nav[data-open="true"] .site-nav__inner { display: flex; }
  .site-nav a, .site-nav__more > summary { justify-content: center; padding: 10px; }
  .quick-search { max-width: none; margin: 6px 0 2px; }
  .site-nav__menu { position: static; box-shadow: none; min-width: 0; }
}

/* --- facet list (the generic name ui.js emits) --------------------------- */

.facet__list {
  display: flex; flex-direction: column; gap: 0;
  max-height: 300px; overflow-y: auto;
  padding: 4px 0;
  background: var(--ge-bg-2, var(--surface));
}
.facet__more { display: flex; flex-direction: column; gap: 0; }
.facet__toggle {
  align-self: flex-start; background: none; border: 0; padding: 4px 10px;
  color: var(--ge-gold-2, var(--gold-soft)); font: inherit; font-size: .82rem; cursor: pointer;
}
.facet__toggle:hover { text-decoration: underline; }
.check--tri { gap: 8px; }
.check--tri select {
  flex: none; width: 74px; padding: 4px 6px; font: inherit; font-size: .84rem;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.range__input {
  width: 74px; flex: none; padding: 6px; font: inherit; font-size: .85rem;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text);
}
.range__input:not(:placeholder-shown) { border-color: var(--gold-dim); }

/* --- chips and pager (button form) -------------------------------------- */

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; font: inherit; font-size: .82rem; cursor: pointer;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
}
.chip:hover { border-color: var(--bad); }
.chip__x { color: var(--text-faint); }
.chip:hover .chip__x { color: var(--bad); }

.pager { flex-wrap: wrap; gap: 4px; }
.pager__btn {
  min-width: 38px; min-height: 38px; padding: 0 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font: inherit; cursor: pointer; font-variant-numeric: tabular-nums;
}
/* The current page is excluded, or hovering it repaints it as an ordinary
   button - `:hover:not(:disabled)` outranks `[aria-current]` on specificity, so
   the page you are on stopped looking like the page you are on the moment the
   pointer crossed it. */
.pager__btn:hover:not(:disabled):not([aria-current="page"]) { background: var(--surface-3); }
.pager__btn:disabled { opacity: .4; cursor: default; }
.pager__btn[aria-current="page"] {
  background: var(--gold); border-color: var(--gold); color: #1b1b1d; font-weight: 700;
}
/* First and last: the same size, marked out by colour so the strip reads as
   "jump" | "step" | pages | "step" | "jump" rather than as nine equal buttons. */
.pager__btn--end { color: var(--gold-soft, var(--gold)); font-weight: 700; }
.pager__gap { color: var(--text-faint); padding: 0 2px; align-self: center; }

/* --- tags --------------------------------------------------------------- */

.tag {
  display: inline-block; margin-left: 6px; padding: 1px 7px;
  font-size: .7rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  border-radius: 999px; background: var(--surface-3); color: var(--text-dim);
  vertical-align: middle;
}
.tag--boss   { background: var(--g-element); color: #1b1b1d; }
.tag--raid   { background: var(--g-pen);     color: #1b1b1d; }
.tag--barter { background: var(--g-attr);    color: #1b1b1d; }
.tag--muted  { background: var(--surface-3); color: var(--text-faint); }
.tag[data-kind="debuff"] { background: var(--bad);  color: #1b1b1d; }
.tag[data-kind="buff"]   { background: var(--good); color: #1b1b1d; }

/* Race, map-type and currency reuse the grade swatch, so a card's leading
   badge always looks the same wherever it appears. */
.grade[data-grade="Wildlife"] { background: var(--g-bane);    color: #1b1b1d; }
.grade[data-grade="Human"]    { background: var(--g-res);     color: #1b1b1d; }
.grade[data-grade="Demon"]    { background: var(--g-pen);     color: #1b1b1d; }
.grade[data-grade="Undead"]   { background: var(--g-attr);    color: #1b1b1d; }
.grade[data-grade="Golem"]    { background: var(--g-misc);    color: #fffefb; }
.grade[data-grade="Misc"]     { background: var(--surface-3); color: var(--text-dim); }
.grade[data-grade="City"]     { background: var(--g-core);    color: #1b1b1d; }
.grade[data-grade="Field"]    { background: var(--g-bane);    color: #1b1b1d; }
.grade[data-grade="Mission"]  { background: var(--g-res);     color: #1b1b1d; }
.grade[data-grade="Vis"]      { background: var(--g-core);    color: #1b1b1d; }
.grade[data-grade="Feso"]     { background: var(--g-element); color: #1b1b1d; }
.grade[data-grade="Mixed"]    { background: var(--g-status);  color: #1b1b1d; }
.grade[data-grade="none"]     { background: var(--surface-3); color: var(--text-faint); }
.grade--inline { margin-left: 6px; }

.card__icon.portrait[data-race] { font-weight: 700; }
.cards--tight { gap: 6px; }

/* --- map thumbnails ----------------------------------------------------- */

.card__icon.thumb { width: 80px; height: 44px; padding: 0; overflow: hidden; position: relative; }
.card__icon.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb__fallback {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: var(--text-faint); font-size: .74rem; z-index: -1;
}
.map-image-wrap { margin-bottom: 18px; }
.map-image {
  width: 100%; max-width: 492px; height: auto; display: block;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-2);
}

/* --- detail panel additions -------------------------------------------- */

.detail__section { margin-bottom: 20px; }

.facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 14px; margin: 0; font-size: .9rem; }
.facts dt { color: var(--text-faint); }
.facts dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.facts--inline { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 4px 12px; }
.facts--inline dt { grid-column: auto; }

.grid { width: 100%; border-collapse: collapse; font-size: .89rem; }
.grid th, .grid td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border);
  vertical-align: top; }
.grid th {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); font-weight: 700; white-space: nowrap;
}
.grid tbody tr:hover { background: var(--surface-2); }
.grid td:not(:first-child) { font-variant-numeric: tabular-nums; white-space: nowrap; }
.grid .small { white-space: normal; font-size: .82rem; color: var(--text-dim); }

.inline-item { display: inline-flex; align-items: center; gap: 6px; }
.icon-sm {
  width: 22px; height: 22px; flex: none; position: relative; overflow: hidden;
  border-radius: 3px; background: var(--surface-2);
  font-size: .58rem; color: var(--text-faint); display: grid; place-items: center;
}
.icon-sm i {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(calc(var(--icon-scale, 1) * 0.5));
  background-repeat: no-repeat;
}

.plain { list-style: none; margin: 0; padding: 0; font-size: .9rem; }
.plain li { padding: 4px 0; border-bottom: 1px solid var(--border); }
.plain li:last-child { border-bottom: 0; }

.linkrow { display: flex; flex-wrap: wrap; gap: 6px; }
.pill-link {
  font-size: .84rem; padding: 4px 10px; cursor: pointer;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px; font-family: inherit;
}
.pill-link:hover { border-color: var(--gold-dim); text-decoration: none; }
.pill-link--flat { cursor: default; color: var(--text-dim); }
.pill-link--flat:hover { border-color: var(--border); }

h4.sub {
  font-size: .76rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); margin: 10px 0 6px;
}

.rules { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 2px 12px; font-size: .88rem; }
.rule { display: flex; align-items: center; gap: 7px; padding: 3px 0; }
.rule__mark { width: 16px; text-align: center; font-weight: 700; }
.rule--on  .rule__mark { color: var(--good); }
.rule--off { color: var(--text-faint); }
.rule--off .rule__mark { color: var(--bad); }

.costs { display: inline-flex; flex-wrap: wrap; gap: 4px 8px; }
.cost { display: inline-flex; align-items: center; gap: 6px; }
.cost__plus { color: var(--text-faint); font-weight: 700; }
.price { display: inline-block; margin-right: 8px; font-weight: 600; color: var(--gold-soft); }
.price--feso { color: var(--g-element); }

/* --- shops index -------------------------------------------------------- */

.shop-region { margin-bottom: 22px; }
.shop-region__title {
  font-family: var(--font-head); font-size: 1rem; color: var(--gold-soft);
  border-bottom: 1px solid var(--border); padding-bottom: 5px; margin: 0 0 8px;
}

/* --- reference rows ----------------------------------------------------- */

.tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 14px; }
.tab {
  padding: 7px 14px; font: inherit; font-size: .9rem; cursor: pointer;
  background: var(--surface-2); color: var(--text-dim);
  border: 1px solid var(--border); border-radius: 999px;
}
.tab:hover { background: var(--surface-3); text-decoration: none; }
.tab[aria-current="true"] {
  background: var(--gold); border-color: var(--gold); color: #1b1b1d; font-weight: 600;
}

.rows { display: flex; flex-direction: column; gap: 8px; }
.row {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong); border-radius: var(--radius);
}
.row:hover { border-left-color: var(--gold); }
.row__body { min-width: 0; flex: 1 1 auto; }
.row__name { margin: 0 0 5px; font-size: .98rem; font-family: var(--font-body); font-weight: 600; }
.row .desc { color: var(--text-dim); font-size: .9rem; overflow-wrap: anywhere; }
.row .desc span { font-weight: 600; }

.small { font-size: .82rem; }
.muted { color: var(--text-faint); }
body.no-scroll { overflow: hidden; }

@media (max-width: 680px) {
  .facts { grid-template-columns: 1fr; gap: 0 0; }
  .facts dt { margin-top: 6px; }
  .grid { font-size: .84rem; }
  .grid th, .grid td { padding: 5px 5px; }
  /* The "from which monsters" column is the first thing to go on a phone. */
  .grid td.small, .grid th:last-child:not(:nth-child(-n+2)) { display: none; }
}

/* A page with no filter sidebar (raids) gets the full width. */
.layout--wide { grid-template-columns: 1fr; }

/* The section a legacy #tab- link landed on, highlighted briefly so it is
   obvious which of a merchant's twenty tabs you asked for. */
.detail__section.is-target > h3 { color: var(--gold); border-bottom-color: var(--gold-dim); }
.detail__section.is-target { animation: target-flash 1.6s ease-out 1; }
@keyframes target-flash {
  0%   { background: color-mix(in srgb, var(--gold) 14%, transparent); }
  100% { background: transparent; }
}

/* ===========================================================================
   The in-game panels: stances, skills and item tooltips.
   ===========================================================================
   These reproduce the client's own layout, because that is the one players can
   already read at a glance. The colours are the game's, kept as their own
   tokens rather than reusing the site palette: they carry meaning (blue is a
   skill's own text, orange is its target, gold is a bonus) and they must stay
   the same in light mode, where the site's own greys all flip.
   ========================================================================= */

:root {
  --ge-gold:    #e3c265;   /* titles, column headers, bonus lines */
  --ge-gold-2:  #d9a441;   /* set effects, enchant chip */
  --ge-text:    #d7d3c8;   /* body text */
  --ge-blue:    #7fb1d6;   /* stance stat lines */
  --ge-orange:  #d79a4b;   /* older accent, kept for the item panel */

  /* The skill tooltip's own palette, sampled from the client.
     Every line in a skill card is one of these, and which one it is carries
     meaning: gold is the spec block - what the skill costs and how it behaves
     against each armour class - cream is what it does at the level you have,
     grey is what it will do at the levels past it, blue is the flavour text and
     salmon is the targeting. Getting them wrong makes the card readable but not
     skimmable - the whole point of the original layout is that you find the
     number you want by colour. */
  --sk-name:    #faf4e9;   /* the skill's name */
  --sk-spec:    #ecd070;   /* SP, casting time, cool down, duration, knockdown */
  --sk-level:   #f9e9b8;   /* "Lv.1 <skill>" and its effect lines */
  --sk-next:    #bfbfbf;   /* "Next Level" block */
  --sk-desc:    #95d7ed;   /* the description sentence */
  --sk-target:  #f38a8a;   /* range, radius, number of targets */
  --ge-dim:     #8d8778;
  --ge-frame:   #6d5b37;
  --ge-frame-2: #3b3320;   /* inner frames, one step darker than the outer */
  --ge-bg:      #100f0d;
  --ge-bg-2:    #191712;
  --ge-head:    #262218;

  /* The corner radius of every panel that wears a crest title bar.
   *
   * Not a taste decision - it is measured off titleleft.jpg. The client's own
   * corner slice is black for the first rows and the brass frame curves in
   * behind it: lit pixels start at x=9 on row 1, x=5 on row 3, x=3 on row 5 and
   * x=1 by row 8. So the artwork's corner is already round, by roughly eight
   * pixels, and a square 1px frame drawn across it reads as the corner having
   * been sliced off - which is exactly what it looked like.
   *
   * An earlier attempt at 3px made it worse, and the reason is worth keeping:
   * the radius went on the PANEL only, so the title bar kept its square corner
   * and stuck out past the rounded frame, leaving a lit notch at each end. The
   * bar and the box it sits in have to be rounded together, which is what the
   * rules below now do. Nothing is lost to the clip: the pixels inside that
   * corner are black in the artwork already. */
  --ge-radius:  8px;
}

.ge-panel {
  /* The panel sits in a drawer, a page column or a phone screen, so it sizes
     itself against its own container rather than the viewport: a media query
     here would widen the skill grid on a big monitor even when the drawer it
     lives in is 400px. */
  container-type: inline-size;
  container-name: gepanel;
  /* Rounded to the crest slice's own curve, same as .filters. */
  border: 1px solid #241f14;
  border-radius: var(--ge-radius);
  background:
    linear-gradient(180deg, #17150f 0%, var(--ge-bg) 120px, var(--ge-bg) 100%);
  color: var(--ge-text);
  font-size: .88rem;
  line-height: 1.45;
  overflow: hidden;
  margin-bottom: 14px;
}

/* The ornamental title bar, minus the ornaments. */
/* The panel title uses the same client slices as .ge-title - this is the bar
   the game draws above "Stances", "Set Effect" and every window heading. */
.ge-panel__title {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  height: 27px; padding: 0 34px;
  border-radius: var(--ge-radius) var(--ge-radius) 0 0;
  overflow: hidden;
  background: #2b2416 url('../../images/chrome/titlemid.jpg') repeat-x center;
  font-family: var(--font-head);
  font-size: .95rem; font-weight: 600;
  color: #f7ef79;
  text-shadow: 0 1px 2px #000;
}
.ge-panel__title::before,
.ge-panel__title::after {
  content: ''; position: absolute; top: 0;
  width: 30px; height: 27px;
}
.ge-panel__title::before { left: 0;  background: url('../../images/chrome/titleleft.jpg') no-repeat left top; }
.ge-panel__title::after  { right: 0; background: url('../../images/chrome/titleright.jpg') no-repeat right top; }

.ge-panel__box {
  margin: 10px;
  padding: 10px;
  border: 1px solid var(--ge-frame-2);
  border-radius: var(--radius-sm);
  background: var(--ge-bg-2);
}

/* --- columns (Information | Stats | Level 25 Bonus) ---------------------- */

.ge-cols { display: grid; gap: 8px; }
.ge-cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ge-cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 10px; }

.ge-col {
  border: 1px solid var(--ge-frame-2);
  border-radius: var(--radius-sm);
  background: #0c0b09;
  min-width: 0;
}
.ge-col__head {
  text-align: center;
  font-family: var(--font-head);
  color: var(--ge-gold);
  font-size: .85rem;
  padding: 5px 8px;
  background: var(--ge-head);
  border-bottom: 1px solid var(--ge-frame-2);
}
.ge-col__body { padding: 9px 11px; }

.ge-lines > div { overflow-wrap: anywhere; }
.ge-lines__gap { height: .7em; }
.ge-muted { color: var(--ge-dim); }
.ge-bonus { margin: 6px 0 0; padding-left: 16px; color: var(--ge-gold); }
.ge-bonus li { margin-bottom: 3px; }

/* --- stance header ------------------------------------------------------- */

.ge-stance {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px 14px;
  align-items: start;
}
.ge-stance__art {
  padding: 6px;
  border: 1px solid var(--ge-frame-2);
  border-radius: var(--radius-sm);
  background: #0c0b09;
  line-height: 0;
}
.ge-stance__intro { min-width: 0; }
.ge-stance__kind {
  color: var(--ge-gold);
  font-family: var(--font-head);
  margin-bottom: 4px;
}
.ge-stance__desc { margin: 0; color: var(--ge-text); }
.ge-stance .ge-cols { grid-column: 1 / -1; }

/* The list of stances on a character page. */
.ge-stance-item { margin-bottom: 10px; }
.ge-stance-item > summary {
  cursor: pointer;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.ge-stance-item > summary:hover { border-color: var(--gold-dim); }
.ge-stance-item[open] > summary { border-color: var(--gold-dim); margin-bottom: 8px; }
.ge-stance-item__hands { color: var(--text-faint); font-size: .85em; }

/* --- skills -------------------------------------------------------------- */

.ge-skills {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
  align-items: start;
}
.ge-skill {
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
}
/* A <button>, because on a narrow panel it is the accordion's handle. On a wide
   one every card is open and there is nothing to toggle, so it is reset back to
   looking and behaving like the block it used to be. */
.ge-skill__head {
  display: block; width: 100%; box-sizing: border-box;
  border: 1px solid var(--ge-frame-2);
  border-radius: var(--radius-sm);
  background: #0c0b09;
  padding: 8px 6px;
  font: inherit; color: inherit;
  text-align: center;
  position: relative;
  cursor: default;
}
.ge-skill__body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* The chevron belongs to the collapsed layout and is switched on there. */
.ge-skill__caret { display: none; }
.ge-skill__no {
  position: absolute; top: 4px; left: 7px;
  color: var(--ge-gold); font-weight: 700; font-size: .78rem;
}
.ge-skill__name {
  margin-top: 5px;
  color: var(--sk-name);           /* white in the client, not gold */
  font-family: var(--font-head);
  font-size: .85rem;
  overflow-wrap: anywhere;
}
.ge-skill--empty .ge-skill__name,
.ge-skill--empty .ge-skill__no { color: var(--ge-dim); }

.ge-skill__box {
  border: 1px solid var(--ge-frame-2);
  border-radius: var(--radius-sm);
  background: #0c0b09;
  padding: 7px 9px;
  font-size: .82rem;
  line-height: 1.4;
  overflow-wrap: break-word;   /* break long words, not every word */
  hyphens: auto;
}
.ge-skill__desc   { color: var(--sk-desc); }
.ge-skill__target { color: var(--sk-target); }
.ge-skill__spec   { color: var(--sk-spec); }
.ge-skill__bp     { color: var(--sk-spec); }
/* The armour multipliers belong to the spec block and read in its gold, like
   every other line in it. */
.ge-skill__armour { color: var(--sk-spec); }

.ge-skill__level     { color: var(--sk-level); }
.ge-skill__level b   { color: var(--sk-level); display: block; margin-bottom: 2px; }
.ge-skill__leveldesc { color: inherit; }
.ge-skill__leveldesc span { font-weight: 600; }
/* Everything past the first level block is the "Next Level" half of the
   tooltip, which the client greys out so the level you actually have reads
   first. */
.ge-skill__level--next,
.ge-skill__level--next b { color: var(--sk-next); }

/* --- item tooltip -------------------------------------------------------- */

.ge-item__head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  padding: 12px 12px 4px;
}
.ge-item__art {
  padding: 4px;
  border: 1px solid var(--ge-frame-2);
  border-radius: var(--radius-sm);
  background: #000;
  line-height: 0;
  align-self: start;
}
.ge-item__intro { min-width: 0; }
.ge-item__name {
  color: var(--ge-gold);
  font-family: var(--font-head);
  font-size: 1rem;
  margin-bottom: 3px;
}
.ge-item__req  { color: var(--ge-blue); }
.ge-item__desc { color: var(--ge-text); margin-top: 2px; }
.ge-item__desc span { font-weight: 600; }
.ge-item__set {
  margin-top: 5px; padding: 0;
  background: none; border: 0; font: inherit; cursor: pointer;
  color: var(--ge-gold-2); text-align: left;
}
.ge-item__set:hover { text-decoration: underline; }
.ge-chip-line { color: var(--ge-gold-2); margin-top: 4px; }

.ge-setrow { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 0 0; }
.ge-setrow__piece {
  font-size: .82rem; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--ge-frame-2); color: var(--ge-text);
}
.ge-setrow__piece[aria-current="true"] { border-color: var(--ge-gold); color: var(--ge-gold); }

/* --- recipe -------------------------------------------------------------- */

.ge-recipe { margin: 10px; border: 1px solid var(--ge-frame-2); border-radius: var(--radius-sm); overflow: hidden; }
.ge-recipe__head {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  background: var(--ge-head);
  color: var(--ge-gold);
  font-family: var(--font-head);
  border-bottom: 1px solid var(--ge-frame-2);
}
.ge-recipe__table { width: 100%; border-collapse: collapse; background: #0c0b09; }
.ge-recipe__table th {
  text-align: left; padding: 5px 10px;
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ge-dim); font-weight: 700;
  border-bottom: 1px solid var(--ge-frame-2);
}
.ge-recipe__table th:last-child, .ge-recipe__table td:last-child { text-align: right; width: 1%; white-space: nowrap; }
.ge-recipe__table td { padding: 5px 10px; border-bottom: 1px solid #1c1a15; }
.ge-recipe__table tr:last-child td { border-bottom: 0; }
.ge-recipe__table a { color: var(--ge-text); }
.ge-recipe__table a:hover { color: var(--ge-gold); }
.ge-ing { display: inline-flex; align-items: center; gap: 8px; }
.ge-num { font-variant-numeric: tabular-nums; color: var(--ge-text); }

/* --- not in this version ------------------------------------------------- */
/* Shown in a detail panel, and server-side in the page body, when an address
   from another edition names something this database does not have. */

.nothere { text-align: center; }
.nothere__lead { margin: 0 0 6px; color: var(--text); font-weight: 600; }
.nothere__alt { margin: 0; color: var(--text-dim); font-size: .9rem; }
.nothere__alt a { color: var(--ge-gold); }

/* --- where to get it ----------------------------------------------------- */
/* Framed like the recipe block above, because it answers the same kind of
   question and sits directly under it: one list per kind of source, each with
   its own small heading and an optional link out to the page that holds all of
   them. */

.ge-sources { margin: 10px; border: 1px solid var(--ge-frame-2); border-radius: var(--radius-sm); overflow: hidden; }
.ge-sources__head {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  background: var(--ge-head);
  color: var(--ge-gold);
  font-family: var(--font-head);
  border-bottom: 1px solid var(--ge-frame-2);
}
.ge-sources__body { background: #0c0b09; }
.ge-sources__body > .ge-muted { padding: 9px 11px; }

.ge-src + .ge-src { border-top: 1px solid var(--ge-frame-2); }
.ge-src__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 8px 10px 2px;
  font-family: var(--font-head); font-size: .82rem; color: var(--ge-gold);
}
.ge-src__more { font-size: .75rem; color: var(--ge-dim); white-space: nowrap; }
.ge-src__more:hover { color: var(--ge-gold); }

.ge-src__table { width: 100%; border-collapse: collapse; }
.ge-src__table th {
  text-align: left; padding: 4px 10px;
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ge-dim); font-weight: 700;
  border-bottom: 1px solid var(--ge-frame-2);
}
.ge-src__table td { padding: 5px 10px; border-bottom: 1px solid #1c1a15; vertical-align: top; }
.ge-src__table tr:last-child td { border-bottom: 0; }
.ge-src__table td:not(:first-child) { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ge-src__table a { color: var(--ge-text); }
.ge-src__table a:hover { color: var(--ge-gold); }
.ge-src__sub { display: block; font-size: .78rem; color: var(--ge-dim); }
.ge-src .tag { margin-left: 6px; }

/* On a phone the drop table's fourth column - the quantity, which is 1 almost
   everywhere - is what gets squeezed, so it goes rather than the chance. */
@media (max-width: 560px) {
  .ge-src__table th:nth-child(4), .ge-src__table td:nth-child(4) { display: none; }
  .ge-src__table td:not(:first-child) { white-space: normal; }
}

/* --- sprites ------------------------------------------------------------- */

/* The sheets keep their native pixel size; the box scales the sprite into the
   space the layout wants, so one sheet serves 24px and 64px icons alike. */
.ge-sprite { display: inline-block; position: relative; overflow: hidden; flex: none; }
.ge-sprite > i {
  position: absolute; top: 0; left: 0;
  transform-origin: top left;
  background-repeat: no-repeat;
  image-rendering: auto;
}
.ge-sprite--blank { background: #0c0b09; border-radius: 2px; }

/* --- a flat stat filter list needs a taller scroller --------------------- */
.facet__list--tall { max-height: 420px; padding: 0; }
.facet__search {
  width: calc(100% - 20px); margin: 6px 10px; padding: 5px 8px;
  font: inherit; font-size: .85rem;
  background: var(--ge-bg, var(--surface-2)); color: var(--ge-text, var(--text));
  border: 1px solid var(--ge-frame-2, var(--border)); border-radius: 2px;
}

/* --- Basic Character Information ----------------------------------------- */

/* The portrait sits left with the six primary stats in two columns beside it,
   which is how the game's own character window reads: STR/HP/INT down one side,
   AGI/DEX/SEN down the other. */
.ge-char__head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  padding: 12px;
}
/* Rounded, and clipping what it holds.
   The barrack bitmaps are chroma-keyed on magenta and the key does not quite
   reach the last pixel of each corner - a few stray pink dots survive the key,
   which are invisible in the game because its own frame covers them. Rounding
   the frame by the same few pixels covers them here too, and costs nothing on
   the portraits that have no such edge. */
.ge-char__portrait {
  border: 1px solid var(--ge-frame);
  border-radius: 6px;
  background: #0c0b09;
  padding: 3px;
  line-height: 0;
  overflow: hidden;
}
/* Native size: 63x100 is what the client draws, and scaling it up only softens
   it. `block` so the line box does not add a descender gap under it. */
.ge-char__barrack {
  display: block;
  width: 63px; height: 100px;
  border-radius: 4px;
}
.ge-char__ident { min-width: 0; }
.ge-char__name {
  font-family: var(--font-head);
  font-size: 1.15rem; color: var(--ge-gold);
  margin-bottom: 8px;
}

.ge-char__ring {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px 14px; margin-bottom: 10px;
}
.ge-char__side { display: flex; flex-direction: column; gap: 2px; }
.ge-char__stat {
  display: flex; align-items: baseline; gap: 8px;
  border-bottom: 1px solid var(--ge-frame-2);
  padding: 2px 0;
}
.ge-char__statname {
  color: var(--ge-gold); font-size: .78rem;
  letter-spacing: .06em; min-width: 3.2em;
}
.ge-char__statval { margin-left: auto; color: var(--ge-text); font-variant-numeric: tabular-nums; }

.ge-char__facts { color: var(--ge-blue); font-size: .9rem; }
.ge-char__key { color: var(--ge-dim); }

/* Biography on the left, starting gear and the buff on the right. */
.ge-char__story {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 0;
  padding: 0;
}
.ge-char__story > * + * { border-left: 1px solid var(--ge-frame-2); }
.ge-char__story > .ge-col { border: 0; }
.ge-char__bio { color: var(--ge-text); margin: 0; }

.ge-char__gear { display: flex; flex-direction: column; gap: 6px; padding: 8px 11px; }
.ge-char__gearrow { display: flex; align-items: center; gap: 8px; }
.ge-char__gearslot { color: var(--ge-dim); font-size: .74rem; text-transform: uppercase; letter-spacing: .05em; }
.ge-char__item { color: var(--ge-text); }
.ge-char__item:hover { color: var(--ge-gold); }
.ge-char__buff { padding: 8px 11px; color: var(--ge-gold-2); }
.ge-char__bufftip { color: var(--ge-blue); margin-top: 2px; font-size: .9em; }

/* The job skill reuses the stance skill card, but it is one wide card rather
   than one of five columns, so the head runs along a row - icon, name, label -
   instead of stacking centred. */
.ge-char__job .ge-skill__head {
  display: flex; align-items: center; gap: 10px;
  text-align: left; padding: 8px 10px;
}
.ge-char__job .ge-skill__name { margin-top: 0; font-size: 1rem; }
.ge-char__job .ge-skill__no {
  position: static; margin-left: auto;
  color: var(--ge-dim); font-weight: 400; text-transform: uppercase;
  letter-spacing: .05em; white-space: nowrap;
}

/* Category | Usable Equipment */
.ge-eqtable { width: 100%; border-collapse: collapse; background: #0c0b09; }
.ge-eqtable th {
  text-align: left; padding: 5px 10px;
  background: var(--ge-head); color: var(--ge-gold);
  font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
  font-weight: 600;
}
.ge-eqtable tbody th {
  background: transparent; color: var(--ge-dim);
  white-space: nowrap; vertical-align: top; width: 1%;
  text-transform: none; letter-spacing: 0; font-size: .85rem;
}
.ge-eqtable td { padding: 5px 10px; border-bottom: 1px solid #1c1a15; }
.ge-eqtable tbody th { border-bottom: 1px solid #1c1a15; }
.ge-eqtable tr:last-child td, .ge-eqtable tr:last-child th { border-bottom: 0; }

.ge-eq {
  display: inline-flex; align-items: baseline; gap: 5px;
  margin: 0 6px 3px 0; padding: 1px 7px;
  border: 1px solid var(--ge-frame-2); border-radius: 2px;
  font-size: .85rem;
}
.ge-eq a { color: var(--ge-text); }
.ge-eq a:hover { color: var(--ge-gold); }
.ge-eq em { font-style: normal; font-size: .8em; color: var(--ge-dim); }
.ge-eq__sep { color: var(--ge-frame); }
/* Gear no other character can use: the point of the table.
   Marked by the text colour alone. A gold border round the whole group read as
   a box drawn round the answer rather than as emphasis on it, and the rows it
   lands on - Head and Costume - are the ones with the longest values, so the
   box was also the widest thing in the table. */
.ge-eq--only { border-color: transparent; padding-left: 0; }
.ge-eq--only a, .ge-eq--only > span { color: var(--ge-gold-2); }

/* --- the Stances table --------------------------------------------------- */

/* Equipped Items | icons | Available Stances, the way the game indexes them.
   The open stance's panel sits UNDER the whole table rather than inside a row,
   so opening one never moves the row you clicked. */
.ge-stances { margin-top: 16px; }
.ge-stancetable {
  width: 100%;
  /* `separate` with zero spacing rather than `collapse`, purely so the two
     bottom corners can be rounded: in the collapsed border model a table's
     border-radius is ignored, and this table is the bottom half of the box
     whose top half is the crest bar above it. Nothing doubles up, because
     every rule below sets one side of a cell, never a full box. */
  border-collapse: separate; border-spacing: 0;
  background: #0c0b09;
  border: 1px solid var(--ge-frame-2);
  border-radius: 0 0 var(--ge-radius) var(--ge-radius);
}
.ge-stancetable thead th {
  padding: 5px 10px; text-align: center;
  background: var(--ge-head); color: var(--ge-gold);
  font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
  border-bottom: 1px solid var(--ge-frame-2);
}
.ge-stancetable td, .ge-stancetable tbody th {
  padding: 4px 10px; border-bottom: 1px solid #1c1a15;
  vertical-align: middle;
}
.ge-stancetable tr:last-child td, .ge-stancetable tr:last-child th { border-bottom: 0; }

.ge-stances__hands {
  text-align: left; white-space: nowrap; width: 1%;
  color: var(--ge-gold-2); font-weight: 400; font-size: .88rem;
  border-right: 1px solid #1c1a15;
}
.ge-stances__icons {
  width: 1%; white-space: nowrap;
  border-right: 1px solid #1c1a15;
  text-align: center;
}
.ge-stances__icon {
  background: none; border: 1px solid transparent; border-radius: 2px;
  padding: 1px; margin: 1px; cursor: pointer; line-height: 0;
}
.ge-stances__icon:hover { border-color: var(--ge-gold-2); }
.ge-stances__icon[aria-expanded="true"] { border-color: var(--ge-gold); background: rgb(227 194 101 / .14); }

.ge-stances__names { color: var(--ge-dim); }
.ge-stances__name {
  background: none; border: 0; padding: 1px 0; cursor: pointer;
  font: inherit; font-size: .88rem; color: var(--ge-gold-2);
}
.ge-stances__name:hover { color: var(--ge-gold); text-decoration: underline; }
.ge-stances__name[aria-expanded="true"] { color: var(--ge-gold); font-weight: 600; }
.ge-stances__sep { color: var(--ge-dim); }

.ge-stances__panel { margin-top: 10px; }
.ge-stances__panel[hidden] { display: none; }

/* --- the stance table on a phone -----------------------------------------
   Three columns do not fit in a 350px drawer. "Rod" and the icon strip each
   claim a fixed width, the stance names get whatever is left - which is not
   enough for "Telekinesis (Lv 100)" - and the table pushes a horizontal
   scrollbar under the whole panel.

   So below 560px it stops being a table and becomes a list: the header row and
   the icon column go, and the weapon stays as a one-line heading above its own
   stances rather than as a column beside them. It costs no width there and it
   is the only place the pairing is recorded - the panel that opens is titled
   with the stance's name alone, so dropping it outright would leave no way to
   tell which weapon a stance belongs to.

   The names stack instead of running together comma-separated: one per line is
   both easier to read and a tap target a thumb can hit. */
@media (max-width: 560px) {
  .ge-stancetable, .ge-stancetable tbody, .ge-stancetable tr { display: block; }
  .ge-stancetable thead { display: none; }
  .ge-stances__icons { display: none; }

  .ge-stancetable tr { border-bottom: 1px solid #1c1a15; }
  .ge-stancetable tr:last-child { border-bottom: 0; }
  .ge-stancetable td, .ge-stancetable tbody th { border-bottom: 0; }

  .ge-stances__hands {
    display: block; width: auto; border-right: 0;
    padding: 7px 10px 2px;
    font-size: .76rem; text-transform: uppercase; letter-spacing: .05em;
    color: var(--ge-gold);
  }
  .ge-stances__names { display: block; padding: 0 10px 7px; }
  .ge-stances__sep { display: none; }
  .ge-stances__name { display: block; width: 100%; text-align: left; padding: 5px 0; }
}

/* A skill icon that is its own file rather than a rect on a sheet. */
/* inline-block, not block: the card head centres its contents with text-align,
   which a block-level image ignores - it sat hard against the left edge while
   the skill name under it was centred. */
.ge-skillicon { display: inline-block; object-fit: contain; image-rendering: auto; }

/* --- narrow screens ------------------------------------------------------ */

/* Five columns is the game's own layout and it holds down to ~860px, which is
   about what the original page used. Below that the cards would be ribbons, so
   they step down rather than shrink. */
@container gepanel (max-width: 860px) {
  .ge-skills { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ge-cols--3 { grid-template-columns: 1fr; }
}
/* --- the phone layout: one column, one skill open ------------------------
   Below this the five cards are stacked, and stacked they are enormous: a skill
   is a description, a targeting line, thirteen spec lines and up to seven level
   blocks, so reaching the fifth skill of a stance meant scrolling past four of
   those. The original site collapsed them for exactly this reason.
   So each head becomes a row you tap, one body is open at a time (see
   wireSkillAccordion in gepanel.js), and the whole stance fits on a screen.
   Two columns would fight the accordion - one open card beside a collapsed one
   leaves a tall ragged hole - so this is one column from here down. */
@container gepanel (max-width: 620px) {
  .ge-skills { grid-template-columns: 1fr; gap: 6px; }
  .ge-char__story { grid-template-columns: 1fr; }
  .ge-char__story > * + * { border-left: 0; border-top: 1px solid var(--ge-frame-2); }

  .ge-skill__head {
    display: flex; align-items: center; gap: 10px;
    text-align: left; padding: 6px 10px 6px 26px;
    cursor: pointer;
  }
  .ge-skill--empty .ge-skill__head { cursor: default; }
  .ge-skill__no { top: 50%; left: 8px; transform: translateY(-50%); }
  .ge-skill__name { margin-top: 0; flex: 1 1 auto; min-width: 0; }

  .ge-skill__caret {
    display: block; flex: none;
    width: 0; height: 0;
    border: 5px solid transparent; border-top-color: var(--ge-gold);
    margin-top: 3px;
    transition: transform .15s ease;
  }
  .ge-skill[data-open="true"] .ge-skill__caret { transform: rotate(180deg); margin-top: -3px; }

  .ge-skill[data-open="false"] .ge-skill__body { display: none; }
  .ge-skill[data-open="true"] > .ge-skill__head { border-color: var(--ge-frame); }
}
@container gepanel (max-width: 430px) {
  .ge-skills { grid-template-columns: 1fr; }
  .ge-cols--2 { grid-template-columns: 1fr; }
  .ge-stance { grid-template-columns: 1fr; }
  .ge-stance__art { justify-self: start; }
  .ge-char__head { grid-template-columns: 1fr; }
  .ge-char__ring { grid-template-columns: 1fr; }
}
/* Browsers without container queries still get a readable page. */
@supports not (container-type: inline-size) {
  .ge-skills { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ge-cols--3, .ge-cols--2 { grid-template-columns: 1fr; }
  .ge-char__story { grid-template-columns: 1fr; }
  .ge-char__story > * + * { border-left: 0; border-top: 1px solid var(--ge-frame-2); }
}

/* Set bonuses, one block per "wear N pieces" tier. */
.ge-settiers { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1px; background: var(--ge-frame-2); }
.ge-settier { background: #0c0b09; padding: 8px 10px; }
.ge-settier__n {
  color: var(--ge-gold); font-size: .74rem; text-transform: uppercase;
  letter-spacing: .05em; margin-bottom: 4px;
}

/* ============================================================= portal ====
   home.php, news.php and search.php: the front page's search panel and the
   news list, rebuilt on this site's chrome rather than the old stylesheet's.
   Both panels are the game's window - a gold title bar over a dark frame - so
   the portal reads as the same product as the pages it leads to.
   ======================================================================= */

.home {
  display: flex; flex-direction: column; align-items: center;
  gap: 22px;
  padding: 26px var(--gutter) 48px;
}
.home > * { width: 100%; max-width: 760px; }

.home__search, .home__news, .search__group {
  border: 1px solid #241f14;
  background: var(--ge-bg, var(--surface));
  /* Same corner as the crest bar that tops each of these, and overflow so the
     news table's first row is clipped to it rather than squaring it off again. */
  border-radius: var(--ge-radius);
  overflow: hidden;
}
.home__search { max-width: 460px; }
.home--search .home__search { margin-bottom: 4px; }

/* The search row. `position: relative` is the anchor for the suggestion list. */
.home__form {
  position: relative;
  display: flex; flex-wrap: wrap; gap: 8px;
  padding: 16px;
}
.home__form select { flex: 1 1 130px; min-width: 0; }
.home__form input[type="search"] { flex: 999 1 190px; min-width: 0; }
.home__form .btn { flex: 1 1 100%; justify-content: center; }
/* Brass, not a slab of gold: the original portal's Search was a full-width bar
   in the frame's own metal with gold type, and a solid gold rectangle that size
   outshouts the news panel it sits above. */
.btn--gold {
  background: linear-gradient(to bottom, rgb(52 54 44 / .92), rgb(31 28 20 / .96));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14);
  border-color: var(--ge-frame, #6d5b37);
  color: #ffcc00; font-weight: 600; letter-spacing: .02em;
}
.btn--gold:hover {
  background-image: none; background-color: rgb(0 0 0 / .5);
  border-color: var(--ge-gold, var(--gold));
}
.btn--gold .ge-ico { width: 14px; height: 14px; }

/* Suggestions hang under the box; the shared dropdown styling, re-anchored to
   the form rather than to the header's narrow search field. */
.home__suggest { top: calc(100% - 8px); left: 16px; right: 16px; }

/* The row of section links under the box: "Characters 296". The counts are the
   reason it earns its space, the same way the nav bar's do - they say at a
   glance that the database is current. */
.home__counts {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 6px;
  margin: 0; padding: 0 16px 16px;
  font-size: .82rem;
}
.home__counts a {
  padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--ge-frame-2, var(--border));
  color: var(--text-dim);
}
.home__counts a:hover { border-color: var(--ge-gold, var(--gold)); color: var(--ge-gold, var(--gold)); text-decoration: none; }
.home__counts b { color: var(--ge-gold, var(--gold)); font-variant-numeric: tabular-nums; }

/* The flourish between the two panels. The old portal had a drawn one; this is
   the same gesture in two rules, with a diamond where the strokes meet. */
.home__rule {
  height: 1px; max-width: 420px;
  background: linear-gradient(to right, transparent, var(--ge-frame, #6d5b37), transparent);
  position: relative;
}
.home__rule::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 7px; height: 7px; margin: -4px 0 0 -4px;
  background: var(--ge-gold, var(--gold)); transform: rotate(45deg);
}

/* --- the news table ----------------------------------------------------- */

.newstable { width: 100%; border-collapse: collapse; font-size: .9rem; }
.newstable thead th {
  text-align: left; padding: 7px 12px;
  background: var(--ge-head, var(--surface-2));
  color: var(--ge-gold, var(--gold));
  font-family: var(--font-head); font-size: .82rem;
  letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid #000;
}
.newstable td { padding: 9px 12px; border-top: 1px solid var(--ge-frame-2, var(--border)); vertical-align: top; }
.newstable tbody tr:nth-child(even) { background: rgb(255 255 255 / .022); }
/* The date is one unbreakable column so a three-word message never squeezes
   "Jul 28," onto its own line, and it is gold because it is the thing you scan
   this table by. */
.newstable__date {
  white-space: nowrap; width: 1%;
  color: var(--ge-gold-2, var(--gold-soft)); font-weight: 600;
}
.newstable__body { color: var(--text); line-height: 1.5; }
.newstable__body ul { margin: 0; padding-left: 18px; }
.newstable__body li { margin: 2px 0; }
.newstable__body a { color: var(--ge-gold-2, var(--gold-soft)); }

.home__more { text-align: center; margin: 0; padding: 12px; }
.home__more a { color: var(--ge-gold-2, var(--gold-soft)); }

/* --- all-sections search results ---------------------------------------- */

.home--search #results { width: 100%; max-width: 760px; }
.search__count { color: var(--text-dim); text-align: center; margin: 0 0 16px; }
.search__group { margin-bottom: 14px; }
.search__list { list-style: none; margin: 0; padding: 4px 0; }
.search__list li {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  padding: 5px 14px;
}
.search__list li + li { border-top: 1px solid var(--ge-frame-2, var(--border)); }
.search__list small { color: var(--text-faint); font-size: .8rem; }

@media (max-width: 560px) {
  .home__form { padding: 12px; }
  .home__form select, .home__form input[type="search"] { flex-basis: 100%; }
  .home__suggest { left: 12px; right: 12px; }
  .newstable { font-size: .86rem; }
  .newstable td { padding: 8px 9px; }
}

/* ======================================================== back to top ====
   The old site's button, in the same place: bottom left, a gold tile with a
   chevron. Bottom LEFT rather than the usual right because that is where it
   was, and because the right-hand edge of this site is where the detail drawer
   and the filter sheet come from.

   It appears only once there is something to go back up from, and it sits below
   the drawer and its scrim (z-index 70 and 55), so opening one covers it rather
   than leaving a button floating over the dimmed page.
   ======================================================================= */

.to-top {
  position: fixed; z-index: 50;
  left: 14px; bottom: 14px;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  padding: 0; cursor: pointer;
  background: var(--gold); color: #1b1b1d;
  border: 1px solid rgb(0 0 0 / .45);
  border-radius: 9px;
  box-shadow: 0 3px 10px rgb(0 0 0 / .55);
  /* Hidden state: still laid out, so showing it costs no reflow, and it fades
     and lifts rather than appearing under the pointer. */
  opacity: 0; transform: translateY(10px); pointer-events: none;
  transition: opacity .18s ease, transform .18s ease, background-color .18s ease;
}
.to-top[data-show="true"] { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { background: var(--gold-soft); }
.to-top svg { width: 22px; height: 22px; fill: currentColor; display: block; }

@media (max-width: 560px) {
  .to-top { left: 10px; bottom: 10px; width: 42px; height: 42px; }
}
/* Someone who has asked for less motion gets the button, not the animation. */
@media (prefers-reduced-motion: reduce) {
  .to-top { transition: none; transform: none; }
}

/* The server-rendered summary a deep link lands on, before the detail drawer
   opens over it. Short, and styled as a lede rather than as a panel: it is the
   page's own answer to "what is this", not a second copy of the drawer. */
.lede {
  margin: 0 0 14px;
  padding: 12px 14px;
  background: var(--ge-bg-2, var(--surface));
  border: 1px solid var(--ge-frame-2, var(--border));
  border-left: 3px solid var(--ge-gold, var(--gold));
  border-radius: var(--radius-sm);
  color: var(--text-dim); font-size: .92rem; line-height: 1.55;
}
.lede p { margin: 0 0 6px; }
.lede p:last-child { margin-bottom: 0; }
.lede strong { color: var(--ge-gold, var(--gold)); }
.lede__stats { color: var(--text); font-variant-numeric: tabular-nums; }
.lede__stats b { color: var(--ge-gold-2, var(--gold-soft)); }
.lede__bio { color: var(--text-dim); }

/* ====================================================== edition switcher ==
   The stack of game versions at the top left of every page, over the banner.
   Absolutely positioned, as the old site had it: it must not take a row of its
   own, because on a phone the banner is already most of the first screen.

   The current edition is a <span>, not a link, and it is the one in plain
   white: the others are gold because gold means "you can go there" everywhere
   else on this site, and a highlighted link to the page you are already on is
   the oldest navigation mistake there is.
   ======================================================================= */

.site-ver {
  position: absolute; top: 0; left: 0; z-index: 3;
  padding: 5px 9px;
  font-size: .8rem; line-height: 1.5;
  text-shadow: 0 1px 3px #000, 0 0 8px #000;
}
.site-ver a, .site-ver span { display: block; }
.site-ver a { color: var(--gold-soft, #e3c265); text-decoration: none; }
.site-ver a:hover { color: var(--gold, #ffd76a); text-decoration: underline; }
.site-ver [aria-current="true"] { color: #fff; font-weight: 600; }

@media (max-width: 560px) {
  .site-ver { font-size: .74rem; padding: 3px 7px; }
}

/* ========================================================= version picker ==
   The site root: which of the databases do you want. Nothing else on it, on
   purpose - it is a fork in the road, and the old site got that right.
   ======================================================================= */

.picker { display: flex; justify-content: center; padding: 20px var(--gutter) 60px; }
.picker__panel { width: 100%; max-width: 420px; }
.picker__list { display: block; }
.picker__row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border-top: 1px solid var(--ge-frame-2, var(--border));
  color: var(--text); text-decoration: none;
}
.picker__row:first-child { border-top: 0; }
.picker__row:hover { background: rgb(255 255 255 / .04); }
.picker__row img {
  width: 120px; height: 61px; flex: none;
  object-fit: contain;
  border: 1px solid #45423a;
  background-color: #25282d;
}
.picker__name {
  display: block;
  font-family: var(--font-head); font-size: 1.05rem;
  color: var(--ge-gold-2, var(--gold-soft));
}
.picker__row:hover .picker__name { color: var(--gold, #ffd76a); }
.picker__when { display: block; font-size: .78rem; color: var(--text-faint); margin-top: 2px; }
/* An edition that is announced but not open yet: listed, not a link, and
   visibly quieter than the ones you can click. */
.picker__row--soon { cursor: default; }
.picker__row--soon:hover { background: none; }
.picker__row--soon .picker__name,
.picker__row--soon:hover .picker__name { color: var(--text-dim); }
.picker__row--soon .picker__when { font-style: italic; }

@media (max-width: 420px) {
  .picker__row img { width: 88px; height: 45px; }
}


/* ========================================================= launch countdown ==
   The clock to the Andromida Rising server opening, on the version picker and
   across that edition's masthead. The database is open before the server is, so
   this is the one thing a visitor most wants to know and the page cannot show
   any other way.

   The numbers are monospaced-by-metric (tabular-nums) and the boxes have a
   floor width, so a second ticking from 9 to 10 does not shove the row sideways
   sixty times a minute. */

.cd {
  --cd-accent: var(--ge-gold, var(--gold, #ffcc00));
  cursor: pointer;
  user-select: none;
}
.cd__lead {
  display: flex; align-items: center; gap: 6px;
  margin: 0 0 5px;
  font-size: .74rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-dim);
}
.cd__dot {
  width: 7px; height: 7px; flex: none; border-radius: 50%;
  background: var(--cd-accent);
  box-shadow: 0 0 0 0 rgb(255 204 0 / .55);
  animation: cd-pulse 2.4s ease-out infinite;
}
@keyframes cd-pulse {
  70%  { box-shadow: 0 0 0 7px rgb(255 204 0 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(255 204 0 / 0); }
}

.cd__units { display: flex; gap: 6px; margin: 0; }
.cd__unit {
  min-width: 46px; padding: 5px 6px 4px;
  text-align: center;
  background: var(--surface-2, #2b2b2f);
  border: 1px solid var(--ge-frame-2, var(--border, #3d3d44));
  border-radius: var(--radius-sm, 5px);
}
.cd__unit b {
  display: block;
  font-family: var(--font-head, Georgia, serif);
  font-size: 1.2rem; line-height: 1.1; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--cd-accent);
}
.cd__unit i {
  display: block; font-style: normal;
  font-size: .6rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-faint);
}

.cd__bar {
  height: 3px; margin: 7px 0 0;
  background: var(--surface-3, #34343a);
  border-radius: 2px; overflow: hidden;
}
.cd__bar i {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--gold-dim, #a8880c), var(--cd-accent));
  transition: width .9s linear;
}

.cd__local {
  margin: 0;
  font-family: var(--font-head, Georgia, serif);
  font-size: 1rem; color: var(--cd-accent);
}
.cd__hint {
  margin: 5px 0 0;
  font-size: .68rem; color: var(--text-faint);
}
.cd:hover .cd__hint { color: var(--text-dim); }

/* Clicked: the remaining time gives way to the moment itself, in the reader's
   own timezone - the question every countdown raises. */
.cd[data-mode="when"] .cd__units { display: none; }
.cd[data-mode="when"] .cd__local { display: block; }

/* Zero. The lead line says so; the boxes would only read 0 0 00 00. */
.cd.is-live .cd__units,
.cd.is-live .cd__bar,
.cd.is-live .cd__hint { display: none; }
.cd.is-live .cd__lead { color: var(--cd-accent); font-size: .95rem; text-transform: none; letter-spacing: 0; }

/* --- on the version picker, under the edition's name --------------------- */
.cd--full { margin-top: 7px; }

/* --- across a site header, above the brass bar --------------------------- */
.cd--strip {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 4px 14px;
  padding: 7px 14px;
  background: linear-gradient(to bottom, rgb(12 11 9 / .55), rgb(12 11 9 / .85));
  border-top: 1px solid rgb(255 204 0 / .18);
  text-shadow: 0 1px 3px #000;
}
.cd--strip .cd__lead { margin: 0; }
.cd--strip .cd__unit { min-width: 42px; padding: 3px 6px 2px; background: rgb(0 0 0 / .35); }
.cd--strip .cd__unit b { font-size: 1rem; }
.cd--strip .cd__bar { display: none; }
.cd--strip .cd__hint { margin: 0; }
.cd--strip .cd__local { font-size: .9rem; }

@media (max-width: 560px) {
  .cd--strip { gap: 4px 10px; padding: 6px 10px; }
  .cd--strip .cd__hint { display: none; }
  .cd__unit { min-width: 40px; }
}

@media (prefers-reduced-motion: reduce) {
  .cd__dot { animation: none; }
  .cd__bar i { transition: none; }
}

/* ====================================================== status references ==
   [Deep Sleep] inside a skill description: what the game makes a link, and what
   this site rendered as three plain words for no reason. Gold like every other
   link here, with a dotted underline rather than a solid one so a sentence with
   four of them does not turn into a row of buttons.
   ======================================================================= */

.statusref {
  color: var(--ge-gold-2, var(--gold-soft));
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: help;
}
.statusref:hover, .statusref:focus-visible { color: var(--gold, #ffd76a); text-decoration-style: solid; }

/* The hover card. Fixed and on <body> because the panels it appears in are
   overflow: hidden - drawn in the flow it would be cut off at the panel edge,
   which is exactly where a skill card's last line is. ui.js positions it. */
.statustip {
  position: fixed; z-index: 90;
  max-width: min(320px, calc(100vw - 16px));
  padding: 8px 10px;
  border: 1px solid var(--ge-frame, #6d5b37);
  border-radius: var(--radius-sm);
  background: #16140f;
  box-shadow: 0 6px 20px rgb(0 0 0 / .6);
  color: var(--ge-text, var(--text));
  font-size: .82rem; line-height: 1.5;
  white-space: pre-line;          /* the client writes these with <br> breaks */
  pointer-events: none;
  opacity: 0; transition: opacity .12s ease;
}
.statustip[data-show="true"] { opacity: 1; }

/* The row a status link landed on, flashed the way a legacy #tab- link is. */
.row.is-target { animation: target-flash 1.6s ease-out 1; }

/* ============================================================== footer ====
   Two paragraphs: what this site is, and whose trademarks it is about. The
   rights notice is small and dim on purpose - it has to be there, it is not
   what anyone came for, and the old site set it the same way.
   ======================================================================= */

/* Centred, like the original's. The rights notice keeps its measure - 96ch of
   trademark text set flush across a 1400px page is unreadable - but the box
   itself is centred with `margin-inline: auto` rather than sitting at the left
   edge, which is what `text-align` alone would have left it doing. */
.site-footer { text-align: center; }
.site-footer__meta { margin: 0 0 10px; }
.site-footer__legal {
  margin: 0 auto;
  max-width: 96ch;
  font-size: .74rem; line-height: 1.7;
  color: var(--text-faint);
}
.site-footer__legal a { color: var(--gold-dim, var(--gold-soft)); }
.site-footer__legal a:hover { color: var(--gold); }

/* ========================================================= data freshness ==
   Laid out as an answer rather than as a dump: a verdict, then the evidence.
   ======================================================================= */

.data__verdict { padding: 12px 14px; }
.data__line { margin: 0 0 6px; color: var(--text-dim); line-height: 1.55; }
.data__line:last-child { margin-bottom: 0; }
.data__line b { color: var(--ge-gold-2, var(--gold-soft)); }
/* A coloured bullet rather than coloured text: the sentence stays readable and
   the state is still scannable down the left edge. */
.data__line--good::before, .data__line--bad::before {
  content: ""; display: inline-block; vertical-align: 1px;
  width: 8px; height: 8px; border-radius: 50%; margin-right: 8px;
}
.data__line--good::before { background: #81c784; }
.data__line--bad::before  { background: #ff5252; }
.data__line--bad b { color: #ff8a80; }

.datatable { width: 100%; border-collapse: collapse; font-size: .85rem; }
.datatable thead th {
  text-align: left; padding: 7px 12px;
  background: var(--ge-head, var(--surface-2));
  color: var(--ge-gold, var(--gold));
  font-family: var(--font-head); font-size: .78rem;
  letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid #000; white-space: nowrap;
}
.datatable td {
  padding: 7px 12px; border-top: 1px solid var(--ge-frame-2, var(--border));
  color: var(--text-dim); vertical-align: top;
}
.datatable tbody tr:nth-child(even) { background: rgb(255 255 255 / .022); }
.datatable .num { text-align: right; font-variant-numeric: tabular-nums; color: var(--text); }
.datatable__key { color: var(--ge-gold-2, var(--gold-soft)); white-space: nowrap; }
.datatable__note { font-size: .95em; }

.data__pill {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: .76rem; white-space: nowrap;
}
.data__pill--bad  { background: rgb(255 82 82 / .16); color: #ff8a80; border: 1px solid rgb(255 82 82 / .35); }
.data__pill--good { background: rgb(129 199 132 / .14); color: #a5d6a7; border: 1px solid rgb(129 199 132 / .32); }

/* The routing section: a copyable vhost snippet and the three live checks. */
.data__pre {
  margin: 8px 0 10px; padding: 10px 12px;
  background: rgb(0 0 0 / .35);
  border: 1px solid var(--ge-frame-2, var(--border));
  border-radius: var(--radius-sm);
  font-size: .78rem; line-height: 1.5; color: var(--text-dim);
  overflow-x: auto;
}
.data__probe { margin-top: 8px; }
.data__probe .data__line code { color: var(--text-faint); }
.data__line code {
  background: rgb(255 255 255 / .05); padding: 1px 5px; border-radius: 3px;
  font-size: .95em;
}

.data__counts {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
  gap: 1px; background: var(--ge-frame-2, var(--border));
  border-top: 1px solid var(--ge-frame-2, var(--border));
}
.data__count { background: var(--ge-bg, var(--surface)); padding: 10px 12px; }
.data__count-n {
  display: block; font-size: 1.05rem; font-weight: 600;
  color: var(--ge-gold-2, var(--gold-soft)); font-variant-numeric: tabular-nums;
}
.data__count-l { display: block; font-size: .76rem; color: var(--text-faint); margin-top: 1px; }

.data__hint {
  margin: 0; padding: 10px 14px;
  font-size: .82rem; color: var(--text-faint); line-height: 1.6;
  border-top: 1px solid var(--ge-frame-2, var(--border));
}
.data__hint code {
  background: rgb(255 255 255 / .05); padding: 1px 5px; border-radius: 3px;
  color: var(--text-dim); font-size: .95em;
}

@media (max-width: 560px) {
  .datatable { font-size: .8rem; }
  .datatable td, .datatable thead th { padding: 6px 8px; }
  .datatable__note { display: none; }
}

/* A wide table scrolls inside its own panel rather than widening the page.
   Six columns of build log do not fit on a phone and there is no honest way to
   stack them, so the table keeps its shape and the panel takes the scroll -
   which is contained, obvious, and does not drag the whole layout sideways. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-wrap > .datatable { min-width: 100%; }
@media (max-width: 560px) {
  /* Below this the columns that are context rather than answer come off, and
     what is left fits without scrolling at all. */
  .datatable__opt { display: none; }
  .table-wrap > .datatable { min-width: 0; }
}

/* ============================================================== chatbox ==
   A tab docked to the bottom-right corner of every page: a title bar until
   you click it, then a log with a composer under it. The old site's #cb-main,
   in this site's brass and dark rather than its yellow.
   ======================================================================= */

.cb {
  position: fixed; right: 24px; bottom: 0; z-index: 60;
  width: min(380px, calc(100vw - 32px));
  font-size: .86rem;
  border: 1px solid var(--ge-frame-2, #4a4538);
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
  background: var(--surface-2, #1b1812);
  box-shadow: 0 -4px 24px rgb(0 0 0 / .55);
  /* The panel below animates its height; without this the rounded top corners
     let the log's first row poke out over the title bar while it moves. */
  overflow: hidden;
}

/* --- the tab ----------------------------------------------------------- */

.cb__tab {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 12px;
  background: linear-gradient(180deg, #3a3224, #241f16);
  border: 0; border-bottom: 1px solid var(--ge-frame-2, #4a4538);
  color: var(--gold, #ffd76a);
  font: inherit; font-weight: 600; letter-spacing: .02em;
  cursor: pointer; text-align: left;
}
.cb__tab:hover { background: linear-gradient(180deg, #463c2a, #2b251a); }
.cb__tab:focus-visible { outline: 2px solid var(--gold, #ffd76a); outline-offset: -2px; }
.cb__tab-title { flex: 1 1 auto; }

.cb__unread {
  flex: none; min-width: 1.35em; padding: 1px 5px;
  border-radius: 999px;
  background: var(--gold, #ffd76a); color: #1b1b1d;
  font-size: .72rem; font-weight: 700; text-align: center;
}

/* The caret points down when open, so it reads as "put it away". */
.cb__caret {
  flex: none; width: 0; height: 0;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-bottom: 6px solid currentColor;
  transition: transform .18s ease;
}
.cb[data-open="true"] .cb__caret { transform: rotate(180deg); }

/* Something arrived while it was shut. A slow pulse on the tab only - the old
   box flashed its whole background every second, which is hard to sit next to
   while reading an item page. */
@keyframes cb-pulse {
  0%, 100% { box-shadow: inset 0 0 0 0 rgb(255 215 106 / 0); }
  50%      { box-shadow: inset 0 0 14px 0 rgb(255 215 106 / .35); }
}
.cb--alert .cb__tab { animation: cb-pulse 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .cb--alert .cb__tab { animation: none; background: linear-gradient(180deg, #4d4028, #2f2819); }
  .cb__caret { transition: none; }
}

/* --- the panel --------------------------------------------------------- */

.cb__panel {
  display: grid; grid-template-rows: 1fr auto;
  height: 0; opacity: 0;
  transition: height .2s ease, opacity .15s ease;
}
.cb[data-open="true"] .cb__panel {
  height: min(60vh, 420px);
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) { .cb__panel { transition: none; } }

.cb__list {
  overflow-y: auto; overscroll-behavior: contain;
  padding: 6px 0;
  scrollbar-width: thin;
  /* The log holds the newest message at the bottom and grows upward, so a
     short conversation sits on the composer rather than floating at the top. */
  display: flex; flex-direction: column; justify-content: flex-end;
}
.cb__list:focus-visible { outline: 2px solid var(--gold, #ffd76a); outline-offset: -2px; }

.cb__msg {
  padding: 5px 12px;
  line-height: 1.45;
  overflow-wrap: anywhere;
  /* flex-direction:column on the list would otherwise stretch every row to
     share the space; each message is exactly as tall as it needs to be. */
  flex: none;
}
.cb__msg + .cb__msg { border-top: 1px solid rgb(255 255 255 / .045); }
.cb__msg:hover { background: rgb(255 255 255 / .03); }
.cb__msg.is-pending { opacity: .5; }

.cb__meta { display: flex; align-items: baseline; gap: 7px; }
.cb__who { color: var(--gold-soft, #e3c265); font-weight: 600; }
.cb__when {
  color: var(--text-faint, #8a8578); font-size: .72rem;
  font-variant-numeric: tabular-nums; cursor: help;
}
.cb__text { color: var(--text, #eceae4); margin-top: 1px; }
.cb__text a { color: var(--gold, #ffd76a); }

/* "/me did a thing" - the old box's one piece of syntax, and three rows of
   history use it. An action is not speech, so it runs on from the name. */
.cb__msg--action .cb__meta { display: inline-flex; }
.cb__msg--action .cb__text { display: inline; font-style: italic; color: var(--text-dim, #b9b3a5); }

.cb__emoji { vertical-align: text-bottom; width: auto; height: 1.2em; }
.cb__empty { padding: 28px 12px; text-align: center; color: var(--text-faint, #8a8578); }

/* Arrived while you were reading further up. */
.cb__jump {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 96px; z-index: 2;
  padding: 4px 12px; border-radius: 999px;
  background: var(--gold, #ffd76a); color: #1b1b1d;
  border: 0; font: inherit; font-size: .76rem; font-weight: 600;
  cursor: pointer; box-shadow: 0 2px 10px rgb(0 0 0 / .5);
}

/* --- the composer ------------------------------------------------------ */

.cb__form {
  padding: 8px 10px 10px;
  border-top: 1px solid var(--ge-frame-2, #4a4538);
  background: var(--surface-3, #221e17);
}
.cb__row { display: flex; gap: 6px; align-items: flex-start; }
.cb__row + .cb__row { margin-top: 6px; }
.cb__name { flex: 1 1 auto; min-width: 0; }
.cb__text-in {
  flex: 1 1 auto; min-width: 0;
  resize: none;
  /* Hidden, not auto: a one-line box whose content is one line still counts as
     overflowing by a pixel or two on Windows, and 'auto' answers that with a
     full scrollbar - arrows and all - sitting inside the composer. The class
     goes on only once autoGrow() has actually hit the 96px cap. */
  overflow-y: hidden;
  height: 2.2rem; max-height: 96px;
  font: inherit; line-height: 1.4;
}
.cb__text-in.is-full { overflow-y: auto; }

/* Thin, dark scrollbars inside the box. scrollbar-width covers Firefox; the
   ::-webkit rules cover Chrome and Edge, where the default is the wide light
   one with arrows on it. */
.cb *::-webkit-scrollbar { width: 8px; height: 8px; }
.cb *::-webkit-scrollbar-track { background: transparent; }
.cb *::-webkit-scrollbar-thumb {
  background: var(--ge-frame-2, #4a4538);
  border-radius: 4px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.cb *::-webkit-scrollbar-thumb:hover { background: var(--gold-dim, #8a7433); background-clip: content-box; }
.cb *::-webkit-scrollbar-button { display: none; }
.cb__text-in { scrollbar-width: thin; scrollbar-color: var(--ge-frame-2, #4a4538) transparent; }
.cb__list { scrollbar-color: var(--ge-frame-2, #4a4538) transparent; }
.cb__send {
  flex: none; align-self: stretch;
  padding: 0 14px;
  background: linear-gradient(180deg, #e3c265, #b8933a);
  color: #1b1b1d; font: inherit; font-weight: 700;
  border: 1px solid #8a7433; border-radius: var(--radius, 4px);
  cursor: pointer;
}
.cb__send:hover { background: linear-gradient(180deg, #ffd76a, #c9a243); }
.cb__send:disabled { opacity: .5; cursor: default; }

.cb__emoji-btn {
  flex: none; padding: 0 10px; font-size: 1rem; line-height: 2.1rem;
  background: var(--surface-2, #1b1812); color: var(--gold-soft, #e3c265);
  border: 1px solid var(--border, #3a372f); border-radius: var(--radius, 4px);
  cursor: pointer;
}
.cb__emoji-btn:hover { color: var(--gold, #ffd76a); border-color: var(--gold-dim, #8a7433); }

.cb__picker {
  display: flex; flex-wrap: wrap; gap: 2px;
  margin-top: 6px; padding: 6px;
  max-height: 132px; overflow-y: auto;
  background: var(--surface-2, #1b1812);
  border: 1px solid var(--border, #3a372f); border-radius: var(--radius, 4px);
}
.cb__pick {
  background: none; border: 1px solid transparent; border-radius: 3px;
  padding: 3px; cursor: pointer; line-height: 0;
}
.cb__pick:hover { border-color: var(--gold-dim, #8a7433); background: rgb(255 255 255 / .06); }
.cb__pick img { height: 20px; width: auto; }

/* `display:flex` above and the browser's own `[hidden] { display:none }` have
   the same specificity, so this sheet won on source order and the picker sat
   on screen as an empty strip above the composer whether it was open or shut.
   This has to come AFTER the rule it corrects. */
.cb__picker[hidden] { display: none; }

/* The owner's key field: absent for everybody until a reserved name is typed
   into the name box, which is almost nobody and never twice on the same
   machine. [hidden] has to beat .cb__row's display:flex, and both are one
   class, so it is written after it rather than louder than it.

   It lines up exactly with the name field above it - an indent or a rule down
   its side makes the composer look broken rather than making the field look
   related. What marks it as the owner's is the gold tint on the field itself. */
.cb__key-row[hidden] { display: none; }
.cb__key {
  flex: 1 1 auto; min-width: 0;
  letter-spacing: .16em;
  border-color: var(--gold-dim, #8a7433);
  background: rgb(227 194 101 / .07);
}
.cb__key::placeholder { letter-spacing: normal; }

.cb__note { margin: 5px 0 0; min-height: 1.05em; font-size: .74rem; color: var(--text-dim, #b9b3a5); }
.cb__note--bad { color: var(--bad, #e06c5e); }

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields they can tell are hidden - and aria-hidden plus tabindex=-1 keeps it
   away from anyone reading the page properly. */
.cb__hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

@media (max-width: 560px) {
  .cb { right: 8px; left: 8px; width: auto; }
  .cb[data-open="true"] .cb__panel { height: min(70vh, 380px); }
}

/* The back-to-top button sits in the same corner, so it steps over the tab
   rather than under it. */
.cb ~ .to-top, .to-top { bottom: 56px; }

/* The alchemy line on an item panel: the same gold the enchant chip line uses,
   because both answer "what else does the game let me do with this". */
.ge-alchemy-line { color: var(--ge-gold-2, #d9a441); margin-top: 4px; }

/* A single-choice filter row. The control is round because only one of them
   can be on at a time - see radioRow() in render.js, which also makes a second
   click on the chosen row clear it, since a radio cannot turn itself off. */
.check--radio input { border-radius: 50%; }
