/* Iron Realms — free tools. Layered on site.css; no external requests. */

/* THE HIDDEN ATTRIBUTE HAS TO WIN. Every interactive control on these pages
 * ships in the HTML with `hidden` set and is revealed by script, so that a
 * reader without JavaScript sees prose and worked examples instead of four
 * buttons that do nothing. The browser's own rule for that is
 * `[hidden] { display: none }` — at author level zero — which ANY display
 * declaration in this file silently outranks. Without the line below, a
 * .forge-row or a .gen-actions with `display: flex` stays on screen while
 * still reporting itself as hidden, which is the worst of both: dead controls
 * for a crawler, and a division-tincture picker that never goes away when the
 * division is plain. !important because the whole point is to beat every
 * layout rule in this file, present and future. */
[hidden] { display: none !important; }

.tool-kicker {
  font-family: var(--display); color: var(--gold-bright); letter-spacing: 2px;
  text-transform: uppercase; font-size: 12px; margin: 0 0 6px;
}
.tool-h1 { font-family: var(--fancy); color: var(--gold); font-size: clamp(30px, 5.4vw, 46px); margin: 0 0 10px; line-height: 1.1; }
.tool-prose { max-width: 760px; }
.tool-prose h2 { font-family: var(--fancy); color: var(--gold); font-size: clamp(22px, 3.4vw, 30px); margin: 34px 0 10px; }
.tool-prose h3 { font-family: var(--display); color: var(--gold-bright); font-size: 19px; margin: 24px 0 6px; }
.tool-status { color: var(--muted); font-size: 13px; min-height: 1.3em; margin: 6px 0 0; }

/* ── The forge ───────────────────────────────────────────────────────────── */

.forge { margin: 8px 0 26px; }
.forge-stage { display: flex; gap: 28px; flex-wrap: wrap; align-items: flex-start; }
.crest-frame { flex: 0 0 auto; }
.crest {
  width: min(360px, 78vw); aspect-ratio: 1 / 1;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(70% 70% at 50% 35%, rgba(255,255,255,.05), rgba(0,0,0,0));
  border-radius: 18px;
}
.crest svg { width: 100%; height: auto; display: block; filter: drop-shadow(0 12px 26px rgba(0,0,0,.55)); }
.blazon {
  font-family: var(--display); color: var(--gold-bright); font-size: 15px;
  text-align: center; margin: 10px 0 0; max-width: min(360px, 78vw); line-height: 1.45;
}
.forge-actions { display: flex; flex-direction: column; gap: 10px; align-items: stretch; min-width: 240px; flex: 1 1 240px; }
.forge-controls {
  margin: 26px 0 0; padding: 18px 20px; border-radius: 16px;
  background: rgba(255,255,255,.03); border: 1px solid var(--line);
}
.forge-row { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; padding: 9px 0; border-bottom: 1px solid rgba(227,193,104,.10); }
.forge-row:last-child { border-bottom: 0; }
.forge-label {
  font-family: var(--display); color: var(--muted); font-size: 12px;
  letter-spacing: 1.5px; text-transform: uppercase; flex: 0 0 132px;
}
.chip-row, .swatch-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* The motto box. Furniture borrowed from the Discord tools' monogram box, but a
   phrase rather than two letters: no uppercasing (a motto has sentence case and
   apostrophes), no letter-spacing, and wide enough that two dozen characters are
   all visible at once — a field that scrolls while you type hides the very thing
   you are trying to judge the length of. */
.motto-input {
  font: inherit; font-family: var(--display); font-size: 16px;
  width: 15em; max-width: 100%; padding: 8px 12px; border-radius: 9px; color: var(--gold-bright);
  background: rgba(255,255,255,.05); border: 1px solid var(--line);
}
.motto-input:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.tool-chip {
  font: inherit; font-size: 13.5px; cursor: pointer; color: var(--text);
  background: rgba(255,255,255,.04); border: 1px solid var(--line);
  border-radius: 999px; padding: 7px 14px; transition: border-color .15s, background .15s;
}
.tool-chip:hover { border-color: var(--gold); }
.tool-chip.on { border-color: var(--gold); background: rgba(227,193,104,.16); color: var(--gold-bright); }
.tool-chip:focus-visible, .tincture:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.tincture {
  width: 34px; height: 34px; border-radius: 9px; cursor: pointer; padding: 0;
  background: var(--t); border: 2px solid rgba(255,255,255,.18); position: relative;
}
.tincture:hover { border-color: var(--gold); }
.tincture.on { border-color: var(--gold-bright); box-shadow: 0 0 0 2px rgba(227,193,104,.45); }

.crest-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 18px; margin: 18px 0 8px; }
.crest-gallery figure { margin: 0; text-align: center; }
.crest-gallery svg { width: 100%; height: auto; filter: drop-shadow(0 6px 14px rgba(0,0,0,.5)); }
.crest-gallery figcaption { color: var(--muted); font-size: 12.5px; margin-top: 8px; line-height: 1.4; }

.vocab-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 8px 28px; }
.vocab-list { list-style: none; padding: 0; margin: 6px 0 14px; }
.vocab-list li { margin: 5px 0; font-size: 14.5px; }
.vocab-list code { color: var(--muted); font-size: 12.5px; }
.vocab-dot { display: inline-block; width: 13px; height: 13px; border-radius: 4px; background: var(--t); border: 1px solid rgba(255,255,255,.25); vertical-align: -1px; }

/* ── Name generators ─────────────────────────────────────────────────────── */

.gen { margin: 8px 0 26px; }
.gen-panel { padding: 20px 22px; border-radius: 16px; background: rgba(255,255,255,.03); border: 1px solid var(--line); max-width: 760px; }
.gen-list { list-style: none; padding: 0; margin: 0 0 16px; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px 18px; }
.gen-list li { margin: 0; }
.gen-name {
  display: block; font-family: var(--display); color: var(--gold-bright); font-size: 17px;
  padding: 7px 10px; border-radius: 9px; cursor: pointer; border: 1px solid transparent;
}
.gen-name:hover { background: rgba(227,193,104,.10); border-color: var(--line); }
.gen-actions { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.gen-buttons { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.gen-hint { color: var(--muted); font-size: 12.5px; margin: 0; }

.sample-cols { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 4px 18px; margin: 16px 0; }
.sample { color: var(--text); font-size: 14.5px; padding: 3px 0; }

/* ── Quiz ────────────────────────────────────────────────────────────────── */

.quiz { margin: 8px 0 26px; }
.quiz-panel { padding: 24px 24px 26px; border-radius: 16px; background: rgba(255,255,255,.03); border: 1px solid var(--line); max-width: 700px; }
.quiz-track { height: 6px; border-radius: 999px; background: rgba(255,255,255,.08); overflow: hidden; margin: 0 0 16px; }
.quiz-bar { height: 100%; background: linear-gradient(90deg, var(--gold-dim), var(--gold)); transition: width .25s ease; }
.quiz-count { color: var(--muted); font-size: 12.5px; letter-spacing: 1.4px; text-transform: uppercase; margin: 0 0 10px; }
.quiz-prompt { font-family: var(--fancy); font-size: clamp(19px, 3vw, 24px); font-weight: 400; color: #fff; margin: 0 0 18px; line-height: 1.35; }
.quiz-options { display: flex; flex-direction: column; gap: 10px; }
.quiz-option {
  display: block; width: 100%; text-align: left; font: inherit; cursor: pointer;
  background: rgba(255,255,255,.03); border: 1px solid var(--line); border-radius: 12px;
  color: var(--text); padding: 13px 17px; transition: border-color .15s, background .15s, transform .1s;
}
.quiz-option:hover { border-color: var(--gold); background: rgba(227,193,104,.06); transform: translateY(-1px); }
.quiz-option.picked { border-color: var(--gold); background: rgba(227,193,104,.12); }
.quiz-back { margin-top: 16px; }
.quiz-outcome-name { font-family: var(--fancy); color: var(--gold); font-size: clamp(26px, 5vw, 38px); margin: 0 0 4px; }
.quiz-outcome-line { font-style: italic; color: var(--gold-bright); margin: 0 0 14px; }
.quiz-dragon-name { font-size: 17px; margin: 0 0 10px; }
.quiz-dragon-label { color: var(--muted); }
.quiz-outcome-body { margin: 16px 0; }
.quiz-verdict-head { font-family: var(--display); color: var(--gold); font-size: 17px; margin: 22px 0 6px; }
.quiz-verdict { border-left: 3px solid var(--gold); padding: 4px 0 4px 16px; margin: 0 0 18px; color: var(--text); }
.quiz-axes { display: flex; flex-direction: column; gap: 8px; margin: 0 0 20px; }
.quiz-axis { display: flex; align-items: center; gap: 12px; }
.quiz-axis-name { flex: 0 0 64px; color: var(--muted); font-size: 12.5px; letter-spacing: 1.2px; text-transform: uppercase; }
.quiz-axis-track { flex: 1; height: 8px; border-radius: 999px; background: rgba(255,255,255,.08); overflow: hidden; }
.quiz-axis-fill { height: 100%; background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright)); }
.quiz-result-actions { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-top: 4px; }
/* The result's ranked bars: every dragon, or an affinity quiz's top three. A
   name column and a bar, reusing the axis track so the palette pass below
   styles both at once. The name column stacks over the bar on a phone, where
   "The Emerald-Veined" beside a bar leaves the bar too short to compare. */
.quiz-meters { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.quiz-meter { display: grid; grid-template-columns: minmax(0, 12em) minmax(0, 1fr); gap: 4px 12px; align-items: center; font-size: 14.5px; }
.quiz-meter-name { color: var(--text); }
.quiz-meter:first-child .quiz-meter-name { color: var(--gold); font-weight: 600; }
.quiz-meter-line { grid-column: 1 / -1; color: var(--muted); font-size: 13px; margin: 0 0 4px; }
@media (max-width: 480px) { .quiz-meter { grid-template-columns: minmax(0, 1fr); } }
/* Focus is moved to these by script (tabindex="-1"), so they must not scroll
   up under the sticky nav when a browser does its own scrolling. */
.quiz-prompt, .quiz-outcome-name { scroll-margin-top: 96px; }
/* The progress bar slides and the options lift on hover. Neither carries
   meaning, so under reduced motion both simply stop. */
@media (prefers-reduced-motion: reduce) {
  .quiz-bar, .quiz-option { transition: none; }
  .quiz-option:hover { transform: none; }
}

.outcome-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; margin: 18px 0; }
.outcome { padding: 16px 18px; border-radius: 14px; background: rgba(255,255,255,.03); border: 1px solid var(--line); }
.outcome h3 { margin: 0 0 4px; }
.outcome-line { font-style: italic; color: var(--gold-bright); margin: 0 0 8px; font-size: 14.5px; }
.outcome p { font-size: 14.5px; }
.question-list li { margin: 12px 0; }
.question-list ul { margin: 6px 0 0; color: var(--muted); }
.question-list ul li { margin: 3px 0; font-size: 14.5px; }

/* ── Shared bits ─────────────────────────────────────────────────────────── */

.tool-grid { margin: 8px 0 30px; }
.tool-card { display: block; }
.db-table { overflow-x: auto; margin: 18px 0; }
.db-table table { min-width: 420px; margin: 0; border-collapse: collapse; }
.db-table caption { text-align: left; color: var(--muted); font-size: 13px; padding-bottom: 6px; }
.db-table th, .db-table td { text-align: left; padding: 8px 14px 8px 0; border-bottom: 1px solid var(--line); font-size: 14.5px; }
.db-table th { color: var(--gold); font-family: var(--display); font-size: 13.5px; }

@media (max-width: 620px) {
  .forge-label { flex: 0 0 100%; }
  .forge-stage { gap: 18px; }
}

/* ── TOUCH TARGETS ───────────────────────────────────────────────────────────
 *
 * Apple's guidance is a minimum tappable area of 44x44pt, and it is not an
 * arbitrary round number — it is roughly the contact patch of an adult
 * fingertip. Below it, the tap that misses is not the user's fault.
 *
 * Measured against that, the controls on these pages come up short by default:
 *
 *     .btn / .tool-btn          38px      .tool-chip               33px
 *     .quiz-option              47px ✓    .tincture                34x34
 *     .gen-name                 34px
 *
 * KEYED TO THE INPUT, NOT THE VIEWPORT. "pointer: coarse" asks the real
 * question — is this being driven by a finger — where "max-width" asks a proxy
 * that is wrong at both ends: a 1024px tablet is a finger, and a small desktop
 * window is a mouse. A mouse is precise and the desktop layout is deliberately
 * left exactly as it is.
 */
@media (pointer: coarse) {
  .tool-btn,
  .tool-chip,
  .gen-name,
  .quiz-option { min-height: 44px; }

  .tool-chip { display: inline-flex; align-items: center; }
  .gen-name { display: flex; align-items: center; }
  .motto-input { min-height: 44px; }

  /* THE ONE THAT HAS TO STAY SMALL. Eight tinctures in a row at 44px wide
   * plus gaps is 400px, which wraps to three lines on a phone and turns a
   * palette — a thing you read at a glance — into a list. So the BOX stays
   * 34px and the TARGET grows underneath it: a transparent pseudo-element,
   * centred and absolutely positioned, extends the hit area to the full 44px
   * without moving a single pixel of layout. This is the standard way to
   * satisfy the guideline when the visual design genuinely needs to be
   * smaller than the finger using it.
   */
  .tincture::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
    /* Purely a target. It must never paint. */
    background: transparent;
  }
  /* The swatch row's own gap has to grow with the hit areas, or two adjacent
     44px targets overlap and the edge of one steals taps meant for the next. */
  .swatch-row { gap: 12px; }
}

/* ── THE GUILD BANNER MAKER ──────────────────────────────────────────────────
   The preview is an inline SVG authored in a 0..100 box, so without a rule here
   it renders at its intrinsic hundred pixels — drawn perfectly and far too small
   to judge, which is exactly what the browser pass caught. The width is capped
   rather than fluid because the point of the preview is to show what the crest
   looks like SMALL: a banner filling a desktop column tells you nothing about
   the roster row it will actually live in. */
.bm { display: grid; gap: 1.5rem; grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); align-items: start; margin: 1.5rem 0 2rem; }
@media (max-width: 45rem) { .bm { grid-template-columns: 1fr; } }

.bm-art { display: block; }
.bm-art svg { display: block; width: 100%; max-width: 17rem; height: auto; margin: 0 auto; }
.bm-reading { color: var(--text-dim); font-size: 0.88rem; margin: 0.75rem 0 0; text-align: center; }
.bm-acts { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; justify-content: center; }
.bm-said { color: var(--text-faint); font-size: 0.82rem; margin: 0.6rem 0 0; text-align: center; min-height: 1.2em; }

.bm-controls { display: grid; gap: 1rem; }
.bm-field { display: grid; gap: 0.3rem; }
.bm-field span { color: var(--text-dim); font-size: 0.8rem; letter-spacing: 0.03em; }
.bm-field input {
  background: var(--surface-2); color: var(--text-strong);
  border: 1px solid var(--hair-strong); border-radius: 6px;
  padding: 0.5rem 0.65rem; font: inherit; font-size: 0.95rem; width: 100%;
}
.bm-field input:focus-visible { outline: 2px solid var(--gold-500); outline-offset: 1px; }

.bm-set { border: 0; margin: 0; padding: 0; }
.bm-set legend { color: var(--text-dim); font-size: 0.8rem; letter-spacing: 0.03em; padding: 0 0 0.35rem; }
.bm-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.bm-chip {
  background: var(--surface-2); color: var(--text-dim); cursor: pointer;
  border: 1px solid var(--hair-strong); border-radius: 999px;
  padding: 0.32rem 0.7rem; font: inherit; font-size: 0.82rem; min-height: 32px;
}
.bm-chip:hover { color: var(--text-strong); border-color: var(--edge); }
.bm-chip.is-on { background: var(--gold-700); border-color: var(--gold-500); color: var(--gold-ink); }
.bm-chip:focus-visible { outline: 2px solid var(--gold-500); outline-offset: 1px; }

.bm-swatches { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.bm-sw {
  width: 30px; height: 30px; border-radius: 6px; cursor: pointer; padding: 0;
  border: 2px solid transparent; box-shadow: 0 0 0 1px var(--hair-strong) inset;
}
.bm-sw.is-on { border-color: var(--gold-500); }
.bm-sw:focus-visible { outline: 2px solid var(--gold-500); outline-offset: 2px; }

/* A control that does not apply says why, rather than sitting there live and
   inert. Kept visible rather than hidden: a row that vanishes and returns as
   you change fields is more confusing than one that explains itself. */
.bm-set.is-inert .bm-swatches { opacity: 0.38; }
.bm-set.is-inert .bm-sw { cursor: not-allowed; }
.bm-off { color: var(--text-faint); font-size: 0.75rem; letter-spacing: 0; }

/* Touch targets. A 30px swatch is fine under a mouse and short of the 44px
   minimum under a thumb, so the squares grow and the chips gain height —
   twelve 44px swatches in one row would push the controls off a phone. */
@media (pointer: coarse) {
  .bm-sw { width: 40px; height: 40px; }
  .bm-chip { min-height: 44px; padding: 0.5rem 0.85rem; }
}

/* ── Tabletop cards ──────────────────────────────────────────────────────── */

.cardgen, .almanac { margin: 8px 0 26px; }
.card-panel {
  padding: 20px 22px; border-radius: 16px; background: rgba(255,255,255,.03);
  border: 1px solid var(--line); max-width: 820px;
}
.card-kicker {
  font-family: var(--display); color: var(--muted); font-size: 12px;
  letter-spacing: 1.6px; text-transform: uppercase; margin: 0 0 6px;
}
.card-title { font-family: var(--fancy); color: var(--gold); font-size: clamp(24px, 4.4vw, 34px); margin: 0 0 14px; line-height: 1.15; }
/* A hook's title is a sentence, not a name — see the note at its call site. */
.hook-panel .card-title { font-size: clamp(19px, 2.7vw, 25px); line-height: 1.3; }

/* Two columns on a desktop, one on a phone. The label column is fixed rather
   than sized to content so that fourteen rows line up instead of stepping. */
.card-rows { display: grid; grid-template-columns: 170px minmax(0, 1fr); gap: 8px 18px; margin: 0; }
.card-rows dt {
  font-family: var(--display); color: var(--gold-bright); font-size: 12.5px;
  letter-spacing: 1.1px; text-transform: uppercase; padding-top: 2px;
}
.card-rows dd { margin: 0; color: var(--text); font-size: 15.5px; line-height: 1.5; }
.card-note { display: block; color: var(--muted); font-size: 13px; margin-top: 3px; }
.card-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 20px; }
.card-actions .tool-status { flex: 1 0 100%; }

.sign-stage { display: flex; flex-direction: column; align-items: center; margin: 0 0 18px; }
.tavern-sign { width: min(260px, 62vw); }
.tavern-sign svg { width: 100%; height: auto; display: block; filter: drop-shadow(0 10px 22px rgba(0,0,0,.55)); }
.sign-blazon {
  font-family: var(--display); color: var(--gold-bright); font-size: 13.5px;
  text-align: center; margin: 10px 0 0; max-width: min(320px, 80vw); line-height: 1.45;
}

.baked-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 18px; margin: 18px 0; }
.baked-card { padding: 16px 18px; border-radius: 14px; background: rgba(255,255,255,.03); border: 1px solid var(--line); }
.baked-card h3 { margin: 0 0 10px; font-family: var(--fancy); color: var(--gold); font-size: 19px; }
.baked-card .card-rows { grid-template-columns: 130px minmax(0, 1fr); gap: 6px 14px; }
.baked-card .card-rows dd { font-size: 14.5px; }

/* ── Quiz result extras ──────────────────────────────────────────────────── */

.quiz-rank { margin: 0 0 10px; font-size: 16px; }
.quiz-rank-label { color: var(--muted); }
/* Label ABOVE value, one stat per column. A definition list flows dt, dd, dt,
   dd, so laid out row-wise it reads "Attack 119 Defence 60" as one line and the
   eye has to pair them up. Two rows plus column auto-flow puts each dd directly
   under its own dt, which is how anybody reads a stat block. Four columns of
   64px and three gaps is 298px, so it survives a 390px screen without
   wrapping. */
.quiz-stats {
  display: grid; grid-template-rows: auto auto; grid-auto-flow: column;
  grid-auto-columns: minmax(64px, max-content); gap: 2px 18px; margin: 6px 0;
}
.quiz-stats dt { font-family: var(--display); color: var(--muted); font-size: 11.5px; letter-spacing: 1.1px; text-transform: uppercase; }
.quiz-stats dd { margin: 0; font-size: 19px; color: var(--gold-bright); font-family: var(--display); }
.quiz-stats-note { color: var(--muted); font-size: 12.5px; margin: 0 0 14px; }
.quiz-counter, .outcome-counter { border-left: 3px solid var(--gold-dim); padding: 3px 0 3px 14px; margin: 12px 0; color: var(--text); font-size: 14.5px; }

/* A table caption lays out at its MAX-CONTENT width — one long line — because
   the box it sits in is the table wrapper, which is itself being sized to its
   content inside .db-table's overflow-x: auto. The page never scrolls
   horizontally as a result (the container clips), but on a phone it means the
   caption runs off the right edge and has to be scrolled to be read, which for
   a caption is the same as not being there.
   A max-width of 100% cannot fix it — the percentage resolves against the box the
   caption is stretching, so it is circular and does nothing (measured: no
   change at all). A definite length is required, and the honest one is the
   viewport minus .wrap's own 20px of padding on each side, which is exactly the
   width the table's container has. Applies to every table under
   public/site/tools/, which is the right scope: they all share this stylesheet
   and none of them wants a caption that has to be scrolled. */
.db-table caption { max-width: calc(100vw - 40px); }

@media (max-width: 640px) {
  /* One column. A 170px label column beside a long sentence at 390px leaves the
     value about 150px wide, which wraps every field to five lines. */
  .card-rows, .baked-card .card-rows { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .card-rows dt { margin-top: 10px; }
  .card-panel { padding: 16px 15px; }
}

@media (pointer: coarse) {
  /* Same argument as the block above this one: the target is keyed to the
     pointer rather than to the viewport, because a tablet is a finger and a
     narrow desktop window is a mouse. These are the controls the tabletop
     pages add — the shared .tool-btn rule already covers the buttons. */
  .card-actions .tool-btn,
  .quiz-option { min-height: 44px; }
}

/* ── Naming tools ────────────────────────────────────────────────────────── */

.roster { margin: 8px 0 26px; }
.roster-panel {
  padding: 20px 22px; border-radius: 16px; background: rgba(255,255,255,.03);
  border: 1px solid var(--line); max-width: 820px;
}
.roster-list {
  list-style: none; padding: 0; margin: 0 0 6px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 4px 18px;
}
.roster-list li { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: 4px 0; }
.roster-name {
  font-family: var(--display); color: var(--gold-bright); font-size: 17px;
  padding: 4px 8px; border-radius: 9px; cursor: pointer; border: 1px solid transparent;
}
.roster-name:hover { background: rgba(227,193,104,.10); border-color: var(--line); }

/* THE BRACKETS ARE CSS, NOT TEXT, and that is a functional decision rather than
   a cosmetic one: tapping a tag copies its textContent, and a tag copied as
   "[CLDA]" gets pasted into a field that wants "CLDA". The game draws its own
   brackets; ours are decoration and must never reach the clipboard. */
.roster-tag {
  font-family: var(--display); color: var(--muted); font-size: 13px; letter-spacing: 1.5px;
  padding: 3px 7px; border-radius: 7px; cursor: pointer; border: 1px solid var(--line);
  background: rgba(255,255,255,.03);
}
.roster-tag::before { content: "["; opacity: .6; }
.roster-tag::after { content: "]"; opacity: .6; }
.roster-tag:hover { color: var(--gold-bright); border-color: var(--gold); }
.roster-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 18px; }
.roster-actions .tool-status, .roster-actions .gen-hint { flex: 1 0 100%; }

.tool-example { color: var(--muted); font-size: 13px; margin: 8px 0 0; }
.tool-example-label { text-transform: uppercase; letter-spacing: 1.2px; font-size: 11px; margin-right: 5px; }

/* ── Discord asset tools ─────────────────────────────────────────────────── */

/* The maker kit's tokens, scoped to the Discord app so they cannot collide
   with another tool's. Written in terms of the site's own custom properties,
   never hexes, except the generic app greys the theme previews need.
   FOCUS IS FROST, SELECTION IS GOLD: a focused selected chip used to be the
   same gold as a selected one, so keyboard users could not see where they were. */
.disc-app {
  --mk-panel: var(--surface-1); --mk-well: var(--surface-2); --mk-line: var(--hair); --mk-line-strong: var(--hair-strong);
  --mk-sel: var(--gold-500); --mk-sel-text: var(--gold-300); --mk-sel-ink: var(--gold-ink); --mk-sel-wash: rgba(223,191,102,.16);
  --mk-focus: var(--frost-400);
  --mk-check: repeating-conic-gradient(var(--surface-2) 0 25%, var(--surface-1) 0 50%) 0 0 / 20px 20px;
  display: grid; grid-template-columns: minmax(280px, 400px) minmax(0, 1fr); gap: 28px; margin: 8px 0 34px;
  grid-template-areas: "tpl tpl" "stage controls" "where where" "export export" "dock dock";
  align-items: start;
}
.disc-app :focus-visible { outline: 2px solid var(--mk-focus); outline-offset: 2px; }
.disc-app h2 { font-family: var(--display); font-weight: 700; font-size: 20px; color: var(--mk-sel-text); margin: 0; }
.disc-app h3 { font-family: var(--display); font-weight: 600; font-size: 13px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--mk-sel-text); margin: 0; }
.mk-ico { width: 18px; height: 18px; flex: 0 0 auto; margin-right: 6px; vertical-align: -3px; }

/* Template band */
.mk-gallery { grid-area: tpl; min-width: 0; padding: 18px 20px; border-radius: 18px; background: var(--mk-panel); border: 1px solid var(--mk-line); display: flex; flex-direction: column; gap: 14px; }
.mk-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 14px; }
.mk-head p { margin: 0; font-size: 13px; color: var(--text-dim); }
.mk-count { color: var(--text-dim); margin-left: 2px; }
.tool-chip.on .mk-count { color: inherit; opacity: .75; }
.mk-grid { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 10px; }
.mk-tile {
  display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; min-height: 44px;
  padding: 12px 6px 10px; border-radius: 14px; background: var(--mk-well); border: 1px solid var(--mk-line);
  color: var(--text-strong); font: 600 12.5px/1.2 var(--body); text-align: center; cursor: pointer;
}
.mk-tile:hover { border-color: var(--mk-line-strong); }
.mk-tile[aria-pressed="true"] { border: 2px solid var(--mk-sel); padding: 11px 5px 9px; }
.mk-thumb { width: 72px; height: 72px; max-width: 100%; border-radius: 50%; overflow: hidden; display: block; }
.mk-thumb svg { width: 100%; height: 100%; display: block; }
.mk-tile-name { overflow-wrap: anywhere; }
.mk-more { display: none; align-self: flex-start; }

/* Stage column */
/* Sticky on a wide screen: the preview stays beside the controls it answers to
   (D6 in the audit — it used to scroll away the moment you reached a control).
   On a phone the stage is not sticky; the dock below carries a live thumbnail. */
/* The column is the grid cell and stretches to the row's height; the sticky box
   is the INNER one. A sticky grid item is held inside the grid CONTAINER, not its
   own cell, so the column used to float on over "Where Discord shows it" and
   Download once the controls were scrolled past. Its parent here is exactly as
   tall as the controls row, which is as far as it should ride. */
.disc-stagecol { grid-area: stage; min-width: 0; align-self: stretch; }
.disc-stagein { display: flex; flex-direction: column; gap: 12px; align-items: stretch; min-width: 0; }
.disc-stage { position: relative; width: min(340px, 100%); aspect-ratio: 1 / 1; margin: 0 auto; padding: 18px; box-sizing: border-box; border-radius: 18px; background: var(--mk-check); }
/* Sticky only where the whole column fits the window. A sticky box taller than
   the viewport cannot be scrolled to its own bottom — the toolbar and the status
   line, which are the end of this column, would be unreachable on a short
   laptop screen (1366 x 768 leaves about 650 px) or a phone turned sideways. So:
   not under 620 px of height, and below ~780 the stage shrinks to leave room for
   everything under it (the 440 is the column's own height without the stage,
   measured on the profile picture page, which has the extra note, plus margin). */
@media (min-width: 761px) and (min-height: 620px) {
  .disc-stagein { position: sticky; top: 16px; }
  .disc-stage { width: min(340px, 100%, max(220px, calc(100vh - 440px))); }
}
/* The preview is round because the platform makes it round. The DOWNLOAD is
   square — see the page copy: baking one surface's crop into the file is a
   thing that cannot be undone at the other end. */
.disc-main { width: 100%; height: 100%; border-radius: 50%; overflow: hidden; filter: drop-shadow(0 12px 26px rgba(0,0,0,.55)); }
.disc-main svg { width: 100%; height: 100%; display: block; }
.disc-guides, .disc-dotguide { position: absolute; inset: 18px; width: calc(100% - 36px); height: calc(100% - 36px); pointer-events: none; }
.disc-guides { opacity: 0; transition: opacity .15s; }
.disc-stage.guides-on .disc-guides { opacity: 1; }
.disc-label { font-family: var(--display); color: var(--gold-bright); font-size: 15px; margin: 0; line-height: 1.45; text-align: center; }
.contrast-badge {
  margin: 0 auto; font-size: 13.5px; line-height: 1.45; padding: 7px 14px; border-radius: 999px; text-align: center;
  border: 1px solid var(--line); background: rgba(255,255,255,.03); color: var(--text);
}
.contrast-badge.is-bad { border-color: rgba(249,128,120,.6); background: rgba(249,128,120,.10); color: var(--danger); }
.contrast-badge.is-weak { border-color: rgba(246,180,92,.6); background: rgba(246,180,92,.10); color: var(--warn); }
.contrast-badge.is-ok, .contrast-badge.is-good { border-color: rgba(103,216,156,.45); background: rgba(103,216,156,.10); color: var(--ok); }
.contrast-badge.is-flat { color: var(--muted); }
.disc-legal { margin: 0; text-align: center; font-size: 13px; color: var(--text-dim); }
.disc-tools { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.disc-tools .tool-btn { min-height: 44px; display: inline-flex; align-items: center; }
.disc-tools .tool-btn[disabled] { opacity: .45; cursor: default; }
.disc-tools .tool-btn[aria-pressed="true"] { border-color: var(--mk-sel); color: var(--mk-sel-text); }
.disc-stagecol .tool-status { min-height: 1.4em; text-align: center; margin: 0; }

/* Control groups */
.disc-controls { grid-area: controls; min-width: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
.mk-group { display: flex; flex-direction: column; gap: 14px; padding: 16px 18px; border-radius: 16px; background: var(--mk-panel); border: 1px solid var(--mk-line); min-width: 0; }
.mk-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.mk-label, .mk-range label { font-size: 12px; color: var(--text-dim); }
.mk-note, .mk-caption { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-dim); }
.mk-seg { display: flex; flex-wrap: wrap; gap: 4px; padding: 3px; border-radius: 12px; background: var(--mk-well); border: 1px solid var(--mk-line); }
.mk-seg button {
  flex: 1 1 auto; min-height: 44px; padding: 0 10px; border-radius: 9px; border: 0; background: transparent;
  color: var(--text); font: 600 13px var(--body); cursor: pointer;
}
.mk-seg button[aria-pressed="true"] { background: var(--mk-sel); color: var(--mk-sel-ink); }
.disc-app .tool-chip { min-height: 44px; padding: 0 14px; }
.disc-app .tool-chip[aria-pressed="true"] { border-color: var(--mk-sel); background: var(--mk-sel-wash); color: var(--mk-sel-text); }
.mk-swatches { display: flex; flex-wrap: wrap; gap: 10px 14px; }
.mk-swgroup { display: flex; flex-direction: column; gap: 4px; }
.mk-swcap { font-size: 11.5px; color: var(--text-dim); }
.mk-swrow { display: flex; flex-wrap: wrap; gap: 6px; }
.disc-app .tincture { width: 44px; height: 44px; border-radius: 10px; }
.disc-app .tincture[aria-pressed="true"] { border: 2px solid var(--mk-sel-text); box-shadow: 0 0 0 3px rgba(223,191,102,.45); }
.mk-syms { display: grid; grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); gap: 6px; }
.mk-sym {
  min-height: 48px; padding: 6px; border-radius: 10px; background: var(--mk-well); border: 1px solid var(--mk-line);
  color: var(--text-strong); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.mk-sym svg { width: 30px; height: 30px; display: block; }
.mk-sym[aria-pressed="true"] { border: 2px solid var(--mk-sel); background: var(--mk-sel-wash); color: var(--mk-sel-text); }
.mk-range { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px 12px; }
.mk-range label { grid-column: 1 / -1; }
.mk-range input[type="range"] { width: 100%; min-height: 44px; accent-color: var(--mk-sel); margin: 0; }
.mk-range output { font: 600 13px var(--body); color: var(--text-strong); min-width: 3.2em; text-align: right; }
.mk-input {
  font: inherit; font-size: 15px; min-height: 44px; padding: 0 12px; border-radius: 10px; box-sizing: border-box; width: 100%;
  color: var(--text-strong); background: var(--mk-well); border: 1px solid var(--mk-line-strong);
}
#use-initials { align-self: flex-start; min-height: 44px; }

/* Where Discord shows it */
.disc-where { grid-area: where; min-width: 0; padding: 18px 20px; border-radius: 18px; background: var(--mk-panel); border: 1px solid var(--mk-line); display: flex; flex-direction: column; gap: 16px; }
.disc-mocks { display: flex; flex-wrap: wrap; gap: 16px; align-items: stretch; }
.disc-rail, .disc-invite { background: #1e1f24; color: #f2f3f5; border-radius: 14px; }
.disc-rail { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 12px 10px 12px 16px; background: #16171b; }
.disc-ghost { width: 48px; height: 48px; border-radius: 50%; background: #2b2d33; }
.disc-me { position: relative; display: block; width: 48px; height: 48px; }
.disc-me .disc-mock-icon { width: 48px; height: 48px; }
.disc-pill { position: absolute; left: -12px; top: 6px; width: 4px; height: 36px; border-radius: 0 4px 4px 0; background: #f2f3f5; }
.disc-mock-icon { display: block; border-radius: 50%; overflow: hidden; }
.disc-mock-icon svg { width: 100%; height: 100%; display: block; }
.disc-invite { flex: 1 1 280px; min-width: 0; display: flex; align-items: center; gap: 14px; padding: 16px; align-self: center; }
.disc-invite-icon { width: 56px; height: 56px; border-radius: 16px; flex: 0 0 auto; }
.disc-invite-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.disc-invite-text strong { overflow-wrap: anywhere; font-size: 16px; }
.disc-invite-kicker { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: #a5a9b2; }
.disc-join { flex: 0 0 auto; padding: 8px 16px; border-radius: 6px; background: #3f8f5f; color: #fff; font: 600 14px var(--body); }
.disc-where.is-light .disc-rail { background: #e3e5e8; }
.disc-where.is-light .disc-ghost { background: #c9ccd1; }
.disc-where.is-light .disc-pill { background: #1e1f24; }
.disc-where.is-light .disc-invite { background: #f2f3f5; color: #1e1f24; }
.disc-where.is-light .disc-invite-kicker { color: #4e5058; }
.disc-where.is-light .disc-strip { background: #f2f3f5; }
.disc-where.is-light .disc-size figcaption { color: #4e5058; }
.disc-dots { display: flex; flex-wrap: wrap; gap: 16px; }
.disc-dotpanel { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 12px; padding: 16px; border-radius: 12px; background: #1e1f24; color: #f2f3f5; }
.disc-dotpanel.is-light { background: #f2f3f5; color: #1e1f24; }
.disc-dotname { font-size: 12px; color: #a5a9b2; }
.disc-dotpanel.is-light .disc-dotname { color: #4e5058; }
.disc-dotrow { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px; }
.disc-av { position: relative; display: block; width: var(--px); height: var(--px); }
.disc-av .disc-mock-icon { width: 100%; height: 100%; }
/* The dot is a third of the avatar's width on the lower-right edge, ringed in
   the panel colour — the shape the corner guide on the stage warns about. */
.disc-dot { position: absolute; right: -2%; bottom: -2%; width: 30%; height: 30%; border-radius: 50%; background: #3f8f5f; box-shadow: 0 0 0 3px #1e1f24; }
.disc-dotpanel.is-light .disc-dot { box-shadow: 0 0 0 3px #f2f3f5; }
.disc-chat { margin: 0; display: flex; gap: 10px; align-items: center; }
.disc-chat-av { width: 40px; height: 40px; flex: 0 0 auto; }
.disc-chat-body { display: flex; flex-direction: column; min-width: 0; font-size: 14px; }
.disc-chat-body strong { overflow-wrap: anywhere; }
.disc-dotnote { text-align: center; }
/* Wraps rather than scrolls: a ladder whose small end is off-screen is a ladder
   nobody uses, and the small end is the whole product. */
.disc-strip { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px 20px; margin: 0; padding: 16px; border-radius: 12px; background: #1e1f24; }
.disc-size { margin: 0; text-align: center; }
.disc-size figcaption { color: #a5a9b2; font-size: 11.5px; margin-top: 8px; letter-spacing: .6px; }
.disc-shot { width: var(--px); height: var(--px); border-radius: 50%; overflow: hidden; margin: 0 auto; }
.disc-shot svg { width: 100%; height: 100%; display: block; }

/* The Discord makers' lettering. Cinzel and Cinzel Decorative are declared by
   site.css; these two are the other site faces (all OFL, self-hosted under
   /fonts, same origin, so the content policy's font-src 'self' covers them).
   The canvas can only draw them once the browser has loaded them, which the page
   script waits for before every export. */
@font-face { font-family: "Barlow Condensed"; src: url("/fonts/barlow-condensed/barlow-condensed-latin-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Barlow Condensed"; src: url("/fonts/barlow-condensed/barlow-condensed-latin-700.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Anton"; src: url("/fonts/wrestling/anton-latin.woff2") format("woff2"); font-weight: 400; font-display: swap; }

/* The disclosure for the vivid row and the picker: a native <details>, so it is
   keyboard- and screen-reader-operable with no script. */
.mk-vivid > summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; color: var(--mk-sel-text); font: 600 13px var(--body); list-style-position: inside; }
.mk-vivid > summary:focus-visible { outline: 2px solid var(--mk-focus, var(--frost-400)); outline-offset: 2px; border-radius: 8px; }
.mk-vivid[open] > summary { margin-bottom: 6px; }
/* The default marker sits hard against the first word inside a flex summary. */
.mk-vivid > summary::marker { content: "\25B8\00a0\00a0"; }
.mk-vivid[open] > summary::marker { content: "\25BE\00a0\00a0"; }
/* A custom colour: the native picker, 44 px, beside the swatches. */
.mk-custom { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; color: var(--text); font: 600 13px var(--body); cursor: pointer; }
.mk-color { width: 44px; height: 44px; padding: 2px; border-radius: 10px; border: 1px solid var(--mk-line-strong); background: var(--mk-well); cursor: pointer; box-sizing: border-box; }
.mk-color:focus-visible, .mk-sym:focus-visible, .mk-tile:focus-visible, .mk-format:focus-visible, .mk-recent-tile:focus-visible { outline: 2px solid var(--mk-focus, var(--frost-400)); outline-offset: 2px; }
.mk-dodge { align-self: flex-start; min-height: 44px; }
.mk-match .tool-btn, .mk-match a.btn { min-height: 44px; display: inline-flex; align-items: center; text-decoration: none; }

/* The status dot, laid over the big preview where the platform draws it: a
   third of the width, ringed in the panel colour, on the lower-right of the
   circle. Off until asked for (the profile picture maker's toggle). */
.disc-statusdot { position: absolute; inset: 18px; pointer-events: none; display: none; }
.disc-stage.dot-on .disc-statusdot { display: block; }
.disc-statusdot::after { content: ""; position: absolute; right: 0; bottom: 0; width: 30%; height: 30%; border-radius: 50%; background: #3f8f5f; box-shadow: 0 0 0 4px #1e1f24; }

/* Recent designs: the last eight, kept in this browser only. */
.mk-recent-row { display: flex; flex-wrap: wrap; gap: 8px; }
.mk-recent-tile { width: 56px; height: 56px; padding: 0; border-radius: 50%; overflow: hidden; border: 2px solid var(--mk-line-strong); background: var(--mk-well); cursor: pointer; }
.mk-recent-tile svg { width: 100%; height: 100%; display: block; }
.mk-recent-tile:hover { border-color: var(--mk-sel); }

/* Download */
.mk-export { grid-area: export; min-width: 0; padding: 20px 22px; border-radius: 18px; background: var(--mk-panel); border: 1px solid rgba(223,191,102,.36); display: flex; flex-direction: column; gap: 16px; }
.mk-formats { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
.mk-format {
  display: flex; align-items: center; gap: 6px; min-height: 56px; padding: 10px 14px; border-radius: 12px; text-align: left;
  background: var(--mk-well); border: 1px solid var(--mk-line); color: var(--text-strong); cursor: pointer; font: inherit;
}
.mk-format:hover { border-color: var(--mk-line-strong); }
.mk-format.is-primary { background: var(--mk-sel); color: var(--mk-sel-ink); border-color: var(--mk-sel); }
.mk-format-text { display: flex; flex-direction: column; min-width: 0; }
.mk-format-name { font: 700 15px var(--body); }
.mk-format-sub { font-size: 12px; opacity: .85; }
.mk-share { display: flex; flex-direction: column; gap: 8px; }
.mk-share-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.mk-link {
  flex: 1 1 280px; min-width: 0; min-height: 44px; padding: 0 12px; border-radius: 10px; box-sizing: border-box;
  border: 1px solid var(--mk-line-strong); background: var(--ink-800); color: var(--frost-300);
  font: 13.5px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.mk-copy { min-height: 44px; display: inline-flex; align-items: center; border: 1px solid var(--frost-400); background: rgba(133,206,249,.10); color: var(--frost-300); }
.mk-share-row .tool-btn { min-height: 44px; display: inline-flex; align-items: center; }

/* The phone dock: the two things a phone user came for, pinned to the bottom
   of the screen while the maker is in view (sticky, so it leaves with it). */
.mk-dock { grid-area: dock; display: none; }

/* THE TOAST: the visual echo of the status line (see say() in the page script).
   Fixed, so it is on screen wherever the person is on the page; aria-hidden in
   the markup, so it is never announced a second time. */
.mk-toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 6; max-width: min(92vw, 520px); box-sizing: border-box;
  padding: 10px 16px; border-radius: 12px; background: var(--surface-3); color: var(--text-strong);
  border: 1px solid var(--mk-line-strong); box-shadow: 0 10px 30px rgba(0,0,0,.45);
  font: 600 14px/1.4 var(--body); text-align: center; pointer-events: none;
  opacity: 0; visibility: hidden; transform: translate(-50%, 8px);
  transition: opacity .16s, transform .16s, visibility 0s linear .16s;
}
.mk-toast.is-on { opacity: 1; visibility: visible; transform: translate(-50%, 0); transition-delay: 0s; }

.mono-input {
  font: inherit; font-family: var(--display); font-weight: 700; font-size: 20px; letter-spacing: 3px; text-transform: uppercase;
  width: 6.5em; min-height: 44px; padding: 0 12px; border-radius: 10px; color: var(--gold-bright); box-sizing: border-box;
  background: var(--mk-well, rgba(255,255,255,.05)); border: 1px solid var(--mk-line-strong, var(--line));
}
.mono-input:focus-visible { outline: 2px solid var(--frost-400); outline-offset: 2px; }

@media (max-width: 1100px) {
  .disc-controls { grid-template-columns: minmax(0, 1fr); }
  .mk-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
  .disc-app { grid-template-columns: minmax(0, 1fr); grid-template-areas: "stage" "tpl" "controls" "where" "export" "dock"; gap: 20px; }
  /* The dock is a sticky bar over the bottom of the screen; without this, the
     browser's minimal scroll-to-focus leaves a tabbed-to control UNDER it
     (WCAG 2.4.11, focus not obscured) and a keyboard user cannot see where
     they are. The padding is the dock's height plus the home-indicator inset. */
  html { scroll-padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
  .disc-stage { width: min(300px, 100%); padding: 14px; }
  .disc-guides, .disc-dotguide { inset: 14px; width: calc(100% - 28px); height: calc(100% - 28px); }
  .mk-gallery, .mk-group, .disc-where, .mk-export { padding: 14px; }
  /* The rail scrolls inside its own box, never the page, and shows a partial
     next tile as the cue that there is more. */
  .mk-grid { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; gap: 10px; padding-bottom: 4px; overscroll-behavior-x: contain; }
  .mk-grid .mk-tile { flex: 0 0 112px; scroll-snap-align: start; }
  .mk-grid.is-open { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); overflow: visible; }
  .mk-grid.is-open .mk-tile { flex: none; }
  .mk-more { display: inline-flex; }
  .mk-formats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mk-format { padding: 8px 10px; }
  .mk-format .mk-ico { display: none; }
  .mk-dock {
    display: flex; gap: 10px; align-items: center; position: sticky; bottom: 0; z-index: 5;
    margin: 0 -16px; padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
    background: rgba(15,19,26,.94); border-top: 1px solid var(--mk-line); transition: opacity .16s;
  }
  /* Out of the way while the preview is on screen (the page script toggles it
     from an IntersectionObserver); hidden, not removed, so the page does not
     jump when it comes back, and not focusable while it is hidden. */
  .mk-dock.is-quiet { opacity: 0; visibility: hidden; }
  .mk-toast { bottom: calc(92px + env(safe-area-inset-bottom)); }
  .mk-dock .tool-btn { flex: 1 1 auto; min-height: 48px; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; padding-inline: 12px; }
  .mk-dock-shot { width: 44px; height: 44px; flex: 0 0 auto; }
}
/* On the narrowest phones (320-380 px: most Androids) the two dock labels wrap
   to two lines each beside the icons; without the icons they fit on one. */
@media (max-width: 380px) {
  .mk-dock { gap: 8px; }
  .mk-dock .mk-ico { display: none; }
  .mk-dock .tool-btn { padding-inline: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .disc-guides { transition: none; }
  .mk-toast, .mk-toast.is-on { transition: none; transform: translate(-50%, 0); }
  .mk-dock { transition: none; }
}

@media (max-width: 640px) {
  .roster-panel { padding: 16px 15px; }
  .roster-list { grid-template-columns: minmax(0, 1fr); }
  /* Eight rungs at 20px of gap overflow a 390px screen by a few pixels once the
     128px rung is in the row. Tightening the gap rather than allowing a
     horizontal scrollbar on the document, which is the one thing this
     stylesheet may never cause. */
  .disc-strip { gap: 14px; }
}

@media (pointer: coarse) {
  /* Keyed to the pointer, not the viewport: a tablet is a finger and a narrow
     desktop window is a mouse. See the block at the foot of the tools
     stylesheet for the full argument. These are the controls these pages add. */
  .roster-name, .roster-tag, .mono-input { min-height: 44px; display: inline-flex; align-items: center; }
  .roster-list li { gap: 10px; }
}

/* ── Heraldry makers: bench, gallery, segments, download, share, dock ────── */

/* Barlow Condensed is the banner maker's "Condensed" lettering. It is NOT
   declared here: the Discord makers' block (GAMER_CSS, in gamer-tools.mjs) already
   declares 400 and 700 from the same self-hosted files, and the stylesheet is one
   file for every page. This block used to repeat the 700 face, and when the two
   branches met the sheet carried it twice (the browser takes the last, so nothing
   broke, but a duplicate face is a second thing to keep in step). Declared once,
   there; heraldryTools.test.ts holds it to exactly once. */

.forge .tool-chip, .bm .bm-chip, .bm-export .bm-chip { min-height: 44px; display: inline-flex; align-items: center; }
.forge .tool-chip:focus-visible, .bm .bm-chip:focus-visible, .bm-export .bm-chip:focus-visible, .bm .bm-sw:focus-visible, .forge .tincture:focus-visible,
.hx-tile:focus-visible, .hx-seg button:focus-visible, .hx-url:focus-visible, .forge .btn:focus-visible, .bm .btn:focus-visible, .bm-export .btn:focus-visible,
.hx-dock .btn:focus-visible, .bm-field input:focus-visible, .motto-input:focus-visible {
  outline: 2px solid var(--frost-400, #85cef9); outline-offset: 2px;
}
.bm .bm-sw { width: 44px; height: 44px; border-radius: 10px; }
.hx-h2 { font-family: var(--display); font-weight: 700; font-size: 20px; color: var(--gold-300, #f5dc98); margin: 0 0 4px; }
.hx-sub { color: var(--text-dim); font-size: 13.5px; margin: 0 0 12px; }
.hx-fine { color: var(--text-dim); font-size: 12.5px; margin: 2px 0 8px; }
.hx-coach {
  max-width: 380px; margin: 10px auto 0; padding: 8px 14px; border-radius: 12px;
  font-size: 13.5px; line-height: 1.4; text-align: center; border: 1px solid var(--hair-strong);
}
.hx-coach.is-ok { color: var(--ok, #67d89c); background: rgba(103,216,156,.10); border-color: rgba(103,216,156,.35); }
.hx-coach.is-warn { color: var(--warn, #f6b45c); background: rgba(246,180,92,.10); border-color: rgba(246,180,92,.35); }
.hx-tools { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 12px auto 0; max-width: 380px; }
/* max-content as the floor: three equal thirds of the 380px column left "Surprise
   me" 3px short of its own label at every width the three sit in one row. */
.bm-acts.hx-tools { grid-template-columns: repeat(3, minmax(max-content, 1fr)); }
.hx-tool, .hx-dlbtn, .hx-copy { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; }
.hx-tool { padding-inline: 8px; gap: 6px; font-size: 13px; letter-spacing: .02em; white-space: nowrap; }
.hx-tool[disabled] { opacity: .45; cursor: not-allowed; }
.hx-ico { flex: 0 0 auto; }

/* THE BENCH: a preview that stays put while the controls scroll past it. Two
   columns from 1000px up (the preview sticks under the site nav); below that
   they stack, and the phone dock below keeps the preview in reach instead. */
.hx-bench { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; align-items: start; margin: 0 0 8px; }
.hx-col { display: grid; gap: 16px; min-width: 0; }
@media (min-width: 1000px) {
  .hx-bench { grid-template-columns: 380px minmax(0, 1fr); gap: 28px; }
}
/* Sticky only where the whole preview column fits under the nav. Taller than
   the viewport, a sticky box cannot be scrolled to its own bottom until the
   section ends, so its toolbar (Undo, Redo, Surprise me) would sit below the
   fold for as long as the controls beside it are on screen. On a short window
   it simply scrolls with the page. */
@media (min-width: 1000px) and (min-height: 720px) {
  .hx-sticky { position: sticky; top: 88px; }
}
.hx-gallery { min-width: 0; padding: 16px 18px; border-radius: 16px; background: var(--surface-1); border: 1px solid var(--hair); }
.hx-filters { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
.hx-count { color: var(--text-dim); margin-left: 2px; }
.tool-chip.on .hx-count, .bm-chip.is-on .hx-count { color: inherit; opacity: .75; }
.hx-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.hx-gallery:not(.is-open):not(.is-filtered) .hx-grid > :nth-child(n+9) { display: none; }
.hx-tile {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 6px; min-width: 0;
  padding: 12px 6px 10px; border-radius: 14px; cursor: pointer; font: inherit;
  background: var(--surface-2); border: 1px solid var(--hair); color: var(--text-strong);
}
.hx-tile:hover { border-color: var(--hair-strong); }
.hx-tile[aria-pressed="true"] { border: 2px solid var(--gold-500); padding: 11px 5px 9px; }
.hx-thumb { display: flex; align-items: center; justify-content: center; width: 84px; height: 92px; max-width: 100%; }
.hx-thumb svg { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; }
.hx-name { font-size: 12.5px; font-weight: 600; line-height: 1.25; text-align: center; overflow-wrap: anywhere; }
.hx-more { margin: 12px 0 0; min-height: 44px; }

.hx-seg { display: flex; flex-wrap: wrap; gap: 4px; padding: 3px; border-radius: 12px; background: var(--surface-2); border: 1px solid var(--hair); }
.hx-seg button {
  flex: 1 1 auto; min-height: 44px; padding: 0 12px; border-radius: 9px; border: 0; cursor: pointer;
  background: transparent; color: var(--text); font: inherit; font-size: 13px; font-weight: 600;
}
.hx-seg button[aria-pressed="true"] { background: var(--gold-500); color: var(--gold-ink); }

.hx-export {
  margin: 28px 0 0; padding: 18px 20px; border-radius: 18px;
  background: var(--surface-1); border: 1px solid rgba(223,191,102,.36);
}
.hx-dl { display: flex; flex-wrap: wrap; gap: 10px; margin: 8px 0 14px; }
.hx-dlbtn { min-height: 56px; text-align: left; }
.hx-dlbtn small { display: block; font-size: 12px; font-weight: 400; opacity: .8; }
.hx-row { border-bottom: 0; }
.hx-share { margin: 14px 0 0; }
.hx-share-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 6px 0 0; }
.hx-url {
  flex: 1 1 260px; min-width: 0; min-height: 44px; padding: 0 12px; border-radius: 9px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13.5px;
  color: var(--frost-300, #b5e2fc); background: var(--ink-800, #0f131a); border: 1px solid var(--hair-strong);
}
.hx-copy { border: 1px solid var(--frost-400, #85cef9); background: rgba(133,206,249,.10); color: var(--frost-300, #b5e2fc); }
.hx-note, .hx-next { color: var(--text-dim); font-size: 13px; margin: 8px 0 0; }

/* The banner maker: the bench's two columns, its controls as one panel. */
.bm { margin: 1.5rem 0 0; }
.bm .bm-stage { text-align: center; }
.bm .bm-art svg { max-width: 17rem; }
.bm .bm-controls { padding: 16px 18px; border-radius: 16px; background: var(--surface-1); border: 1px solid var(--hair); gap: 14px; }
.bm-names { display: grid; grid-template-columns: minmax(0, 1fr) 7rem; gap: 12px; }
.bm-field input { min-height: 44px; }
.bm-getnames { margin: -6px 0 0; }
.bm-set.is-inert .bm-chips { opacity: .45; }
.bm-set.is-inert .bm-chip { cursor: not-allowed; }
.bm-dl { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 0; }
.bm-dl .btn { min-height: 48px; }
.bm-size-art { margin: 12px 0 0; }
.bm-size-art svg { display: block; width: 100%; max-width: 480px; height: auto; border-radius: 10px; border: 1px solid var(--hair); }
.bm .bm-same { margin-right: 4px; }
.bm-acts .btn[disabled] { opacity: .45; cursor: not-allowed; }
.bm-export .bm-set { margin: 0 0 12px; }

/* The phone dock: a mini preview, the primary download and Copy link, pinned to
   the bottom once the big preview has scrolled out of view (the page script
   adds .is-on from an IntersectionObserver; with none it stays on). Only ever
   shown where the bench is stacked, and clear of the home indicator. */
.hx-dock {
  display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 25; align-items: center; gap: 10px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom)); background: rgba(15,19,26,.94);
  border-top: 1px solid var(--hair-strong);
}
.hx-dock .btn { min-height: 48px; padding: 0 14px; display: inline-flex; align-items: center; justify-content: center; }
.hx-dock .btn.primary { flex: 1 1 auto; }
.hx-dock-art { flex: 0 0 44px; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; }
.hx-dock-art svg { max-width: 44px; max-height: 44px; width: auto; height: auto; }

/* The visual echo of the status line (see say() in each page's script): fixed,
   above the phone dock when there is one, never focusable and aria-hidden. */
.hx-toast {
  position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom)); z-index: 30;
  max-width: min(92vw, 420px); padding: 10px 16px; border-radius: 12px; box-sizing: border-box;
  background: var(--surface-3, #262c37); color: var(--text-strong, #f1f3f7); border: 1px solid var(--hair-strong);
  font-size: 13.5px; line-height: 1.4; text-align: center; pointer-events: none;
  opacity: 0; transform: translate(-50%, 8px); transition: opacity .16s ease, transform .16s ease;
}
.hx-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.hx-dock-on .hx-toast { bottom: calc(86px + env(safe-area-inset-bottom)); }
@media (max-width: 999px) {
  .hx-dock.is-on { display: flex; }
  .hx-dock-on body { padding-bottom: 72px; }
  /* A control the keyboard focuses must never end up behind the dock: the browser
     scrolls it into view but knows nothing about a fixed bar over the bottom edge. */
  .hx-dock-on { scroll-padding-bottom: 92px; }
}

/* Phone: the gallery becomes a rail that scrolls inside its own box (a partial
   next tile is the cue), and "See all" turns it into a three-column grid. */
@media (max-width: 620px) {
  .hx-gallery:not(.is-open) .hx-grid {
    display: grid; grid-auto-flow: column; grid-auto-columns: 112px; grid-template-columns: none;
    overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 6px;
  }
  .hx-gallery:not(.is-open) .hx-grid > * { scroll-snap-align: start; }
  .hx-gallery:not(.is-open):not(.is-filtered) .hx-grid > :nth-child(n+9) { display: flex; }
  .hx-gallery:not(.is-open) .hx-grid > [hidden] { display: none !important; }
  .hx-gallery.is-open .hx-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .hx-dlbtn { flex: 1 1 100%; }
  .hx-gallery { padding: 14px; }
  .hx-export { padding: 14px; }
  .bm .bm-controls { padding: 14px; }
  .bm-names { grid-template-columns: minmax(0, 1fr) 6rem; }
}
@media (max-width: 480px) {
  .hx-seg button { padding: 0 8px; font-size: 12.5px; }
  /* Three buttons in a 358px column leave 114px each, and "Surprise me" with its
     icon needs 121: the label ran out of its own button. Two on the first row,
     the third across the second, as the forge's four sit in a two-by-two. */
  .bm-acts.hx-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bm-acts.hx-tools > :nth-child(3) { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .hx-tile, .hx-seg button, .hx-toast { transition: none; }
  .hx-toast { transform: translate(-50%, 0); }
}

/* ── Name machine (scripts/namegen-kit.mjs) ──────────────────────────────── */

.ng-scope {
  --ng-panel: var(--surface-1);
  --ng-well: var(--surface-2);
  --ng-line: var(--hair);
  --ng-line-strong: var(--hair-strong);
  --ng-sel: var(--gold-500);
  --ng-sel-text: var(--gold-300);
  --ng-sel-ink: var(--gold-ink, #17130a);
  --ng-focus: var(--frost-300);
}
.ng-scope .ng-svg, .ng-scope svg { flex: 0 0 auto; }

.ng-layout {
  display: grid; gap: 16px; align-items: start;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "filters" "results" "tray";
}
.ng-filters { grid-area: filters; }
.ng-results { grid-area: results; min-width: 0; }
.ng-tray { grid-area: tray; }
@media (min-width: 900px) {
  .ng-layout {
    grid-template-columns: 300px minmax(0, 1fr);
    grid-template-areas: "filters results" "tray results";
    grid-template-rows: auto 1fr;
  }
}
.ng-layout .roster-panel { max-width: none; }

/* Control group: a titled panel of related controls. */
.ng-filters, .ng-tray, .ng-card, .ng-own {
  border-radius: 16px; background: var(--ng-panel); border: 1px solid var(--ng-line);
}
.ng-sum {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  min-height: 48px; padding: 0 16px; cursor: pointer; list-style: none;
  font-family: var(--display); font-weight: 700; font-size: 15px; letter-spacing: .04em;
  color: var(--text-strong);
}
.ng-sum::-webkit-details-marker { display: none; }
.ng-sumcount { margin-left: auto; font: 600 12.5px var(--body); color: var(--ng-sel-text); }
.ng-fbody, .ng-traybody, .ng-cardbody { display: flex; flex-direction: column; gap: 14px; padding: 4px 16px 16px; }
.ng-group { display: flex; flex-direction: column; gap: 8px; border: 0; margin: 0; padding: 0; min-width: 0; }
.ng-gh {
  margin: 0; font: 600 12px var(--display); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ng-sel-text);
}
.ng-hint { margin: 0; font-size: 12.5px; color: var(--text-dim); }
.ng-opt { color: var(--text-dim); font-weight: 400; }
.ng-lab { font-size: 12.5px; color: var(--text-dim); }
.ng-fields { flex-direction: row; flex-wrap: wrap; gap: 10px 14px; }
.ng-field { display: flex; flex-direction: column; gap: 6px; }
.ng-grow { flex: 1 1 160px; min-width: 0; }
.ng-input {
  width: 100%; box-sizing: border-box; min-height: 44px; padding: 0 12px; border-radius: 10px;
  font: 500 15px var(--body); letter-spacing: 0; text-transform: none; max-width: 100%;
  background: var(--ink-900); color: var(--text-strong); border: 1px solid var(--edge);
}
.ng-input::placeholder { color: var(--text-dim); opacity: 1; letter-spacing: 0; text-transform: none; }
select.ng-input { padding-right: 8px; }
.ng-input:focus-visible, .ng-segb:focus-visible, .ng-ic:focus-visible, .ng-sum:focus-visible,
.ng-crosslink:focus-visible, .ng-traybody:focus-visible { outline: 2px solid var(--ng-focus); outline-offset: 2px; }

/* Segmented control: one choice of several, as native buttons. */
.ng-seg {
  display: flex; flex-wrap: wrap; gap: 4px; padding: 3px; border-radius: 12px;
  background: var(--ng-well); border: 1px solid var(--ng-line);
}
.ng-segb {
  flex: 1 1 auto; min-height: 44px; padding: 0 12px; border-radius: 9px; border: 0;
  background: transparent; color: var(--text); font: 600 13px var(--body); cursor: pointer;
}
.ng-segb:hover { background: var(--surface-3); }
.ng-segb[aria-pressed="true"] { background: var(--ng-sel); color: var(--ng-sel-ink); }
.ng-filters .tool-chip { min-height: 44px; padding: 0 14px; display: inline-flex; align-items: center; }
.ng-reset { align-self: flex-start; min-height: 44px; }

/* Result row. */
.ng-results .roster-list { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 8px 12px; margin-bottom: 8px; }
.ng-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center;
  padding: 8px 8px 8px 12px; border-radius: 12px; background: var(--ng-well);
  border: 1px solid var(--ng-line);
}
.roster-list li.ng-row { display: grid; flex-wrap: nowrap; }
.ng-row.is-kept { border-color: var(--ng-sel); box-shadow: inset 3px 0 0 var(--ng-sel); }
.ng-rline { display: flex; flex-wrap: wrap; gap: 2px 8px; align-items: center; }
.ng-tags { display: inline-flex; gap: 6px; flex-wrap: nowrap; }
.ng-row .roster-name {
  background: transparent; border: 1px solid transparent; padding: 6px 4px; text-align: left;
  font: 600 17px var(--display); color: var(--text-strong); border-radius: 8px; min-height: 44px;
  overflow-wrap: anywhere;
}
.ng-row .roster-name:hover { background: var(--surface-3); }
.ng-row .roster-tag, .ng-trayrow .roster-tag, .ng-ownrow2 .roster-tag {
  font: 600 13px var(--display); letter-spacing: 1.5px; min-height: 36px; padding: 0 8px;
  border: 1px solid var(--ng-line-strong); border-radius: 999px; background: transparent; color: var(--text);
}
.ng-mean { margin: 0 0 0 4px; font-size: 13.5px; color: var(--text-dim); line-height: 1.4; }
.ng-acts { display: flex; gap: 2px; }
.ng-ic {
  display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px;
  border-radius: 10px; border: 1px solid transparent; background: transparent; color: var(--text-dim);
  cursor: pointer; text-decoration: none;
}
.ng-ic:hover { background: var(--surface-3); color: var(--text-strong); }
.ng-star[aria-pressed="true"] { color: var(--ng-sel); }
.ng-note {
  margin: 4px 0 8px; padding: 10px 12px; border-radius: 10px; font-size: 13.5px;
  background: var(--ng-well); border: 1px solid var(--ng-line-strong); color: var(--warn);
}

/* Actions under the list. */
.ng-scope .roster-actions .btn, .ng-scope .ng-trayacts .btn, .ng-scope .ng-cardgo, .ng-scope .ng-reset {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
}
.ng-scope .roster-actions .btn.primary { min-height: 48px; }

/* Shortlist tray. */
.ng-trayl { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ng-trayrow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 6px;
  padding: 2px 4px 2px 10px; border-radius: 10px; background: var(--ng-well); border: 1px solid var(--ng-line);
}
.ng-trayname { display: flex; flex-wrap: wrap; align-items: center; gap: 0 8px; min-width: 0; }
.ng-trayrow .roster-name {
  text-align: left; background: transparent; border: 0; min-height: 44px; padding: 0;
  font: 600 15px var(--display); color: var(--text-strong);
}
.ng-empty { margin: 0; color: var(--text-dim); font-size: 14px; }
.ng-trayacts { display: grid; grid-template-columns: 1fr; gap: 8px; }
@media (max-width: 899px) { .ng-trayacts { grid-template-columns: 1fr 1fr; } }
.ng-trayacts .btn { justify-content: center; }
@media (max-width: 599px) {
  .ng-scope .roster-actions .btn { flex: 1 1 calc(50% - 10px); justify-content: center; }
  .ng-scope .roster-actions .btn.primary { flex-basis: 100%; }
}
.ng-quiet { background: transparent; color: var(--text-dim); }
/* The sticky bar must not sit on top of whatever keyboard focus just moved to. */
html:has(.ng-tray) { scroll-padding-bottom: 72px; }
@media (max-width: 899px) {
  .ng-tray { position: sticky; bottom: 0; z-index: 5; box-shadow: 0 -8px 24px rgba(0,0,0,.35); }
  .ng-tray .ng-sum { min-height: 52px; }
  .ng-traybody { max-height: 50vh; overflow: auto; }
}

/* Image card, the cross-sell line, the own-name tool. */
.ng-card { margin-top: 12px; }
.ng-card > summary {
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 16px; cursor: pointer;
  font: 600 14px var(--body); color: var(--text-strong); list-style: none;
}
.ng-card > summary::-webkit-details-marker { display: none; }
.ng-card > summary:focus-visible { outline: 2px solid var(--ng-focus); outline-offset: 2px; }
.ng-cardgo { align-self: flex-start; }
.ng-cross { margin: 14px 2px 0; font-size: 14.5px; color: var(--text); }
.ng-crosslab { color: var(--text-dim); margin-right: 4px; }
.ng-crosslink { display: inline-flex; align-items: center; min-height: 44px; padding: 0 2px; }
.ng-own { margin-top: 16px; padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 10px; max-width: 820px; }
.ng-ownrow { display: flex; flex-direction: column; gap: 6px; }
.ng-ownout { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.ng-none { color: var(--text-dim); font-size: 14px; }

/* Toast: the visual echo of the status line (aria-hidden; the status line speaks). */
.ng-toast {
  position: fixed; left: 50%; bottom: 84px; z-index: 30; max-width: min(92vw, 420px);
  transform: translate(-50%, 8px); opacity: 0; pointer-events: none;
  padding: 10px 16px; border-radius: 12px; font-size: 14px;
  background: var(--surface-3); color: var(--text-strong); border: 1px solid var(--ng-line-strong);
  transition: opacity .16s ease, transform .16s ease;
}
.ng-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) {
  .ng-toast, .ng-segb, .ng-ic { transition: none; }
  .ng-toast { transform: translate(-50%, 0); }
}

@media (pointer: coarse) {
  .roster-name, .roster-tag, .ng-segb, .ng-ic, .ng-input, .ng-sum,
  .ng-row .roster-tag, .ng-trayrow .roster-tag, .ng-ownrow2 .roster-tag { min-height: 44px; }
  .ng-ic { width: 44px; }
}

/* ── The hub: featured row, sections, cards ──────────────────────────────── */

.hub { margin: 4px 0 8px; }
.hub-start { margin: 22px 0 0; }
.hub-label {
  margin: 0 0 14px; font-family: var(--display); font-weight: 700; font-size: 13px; line-height: 1.2;
  letter-spacing: 2px; text-transform: uppercase; color: var(--gold-300, #f5dc98);
}
.hub-sec { margin: 34px 0 0; }
.hub-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 14px; margin: 0 0 12px; }
.hub-sec h2 {
  margin: 0; font-family: var(--fancy); font-weight: 700; font-size: clamp(21px, 3vw, 26px); line-height: 1.15;
  letter-spacing: 0; color: var(--gold-500, #dfbf66);
}
.hub-sub { margin: 0; max-width: none; font-size: 14px; line-height: 1.45; color: var(--text-dim); }
/* The section's own index page. A door, not a tool, so a link in the heading
   row and not a card; 44px tall because it is a touch target like any other. */
.hub-door {
  margin-left: auto; display: inline-flex; align-items: center; min-height: 44px; padding: 0 2px;
  font-size: 14px; font-weight: 600; color: var(--frost-400, #85cef9);
}
.hub-door:hover { color: var(--frost-300, #aae4ff); }

.hub-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.hub-grid--feat { gap: 16px; }

.hub-card {
  position: relative; display: flex; flex-direction: column; gap: 6px; min-width: 0; max-width: none; min-height: 44px;
  margin: 0; padding: 16px 18px; border-radius: 14px;
  background: var(--surface-1); border: 1px solid var(--hair);
  transition: border-color .15s ease, background-color .15s ease;
}
.hub-card:hover { border-color: var(--hair-strong); background: var(--surface-2); }
.hub-title {
  margin: 0; max-width: none; font-family: var(--display); font-weight: 600; font-size: 18px; line-height: 1.25;
  letter-spacing: 0; color: var(--text-strong);
}
/* THE STRETCHED LINK: the anchor is only the tool's name, and its ::after is the
   card, so the whole card is the target while the link text stays the name. */
.hub-title a, .hub-title a:hover { color: inherit; text-decoration: none; }
.hub-title a::after { content: ""; position: absolute; inset: 0; border-radius: 14px; }
.hub-title a:focus-visible { outline: none; }
.hub-title a:focus-visible::after { outline: 2px solid var(--frost-400, #85cef9); outline-offset: 2px; }
.hub-promise { margin: 0; max-width: none; font-size: 14.5px; line-height: 1.5; color: var(--text); }
.hub-eg { margin: 2px 0 0; max-width: none; font-size: 13px; line-height: 1.45; color: var(--text-dim); overflow-wrap: anywhere; }
.hub-eg-label { text-transform: uppercase; letter-spacing: 1.2px; font-size: 11px; margin-right: 5px; }
.hub-meta { margin: auto 0 0; padding-top: 4px; max-width: none; font-size: 12px; line-height: 1.4; color: var(--text-dim); }

/* Featured: a drawing over the title, a promise, a verb. The drawing's row is a
   fixed 96px so the cards line up whatever each preview is. */
.hub-feat {
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto 1fr auto;
  grid-template-areas: "art" "title" "promise" "meta"; gap: 12px; min-height: 260px; padding: 20px; border-radius: 18px;
  border-color: rgba(223, 191, 102, .36);
}
.hub-feat:hover { background: var(--surface-1); border-color: rgba(223, 191, 102, .62); }
.hub-feat .hub-title a::after { border-radius: 18px; }
.hub-feat .hub-art { grid-area: art; display: flex; align-items: center; gap: 8px; height: 96px; min-width: 0; }
.hub-feat .hub-title { grid-area: title; font-weight: 700; font-size: 21px; line-height: 1.2; }
.hub-feat .hub-promise { grid-area: promise; font-size: 15px; }
.hub-feat .hub-meta { grid-area: meta; display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin: 0; padding: 0; }
.hub-cta { font-size: 13px; font-weight: 600; white-space: nowrap; color: var(--frost-400, #85cef9); }
.hub-feat:hover .hub-cta { color: var(--frost-300, #aae4ff); }

/* The previews. Discord crops an icon to a circle, so the circle is CSS; the
   status-dot ring is laid over the first picture where the maker marks it. */
.hub-ico { position: relative; display: inline-flex; flex: 0 0 auto; line-height: 0; }
.hub-ico > svg:first-child { display: block; border-radius: 50%; }
.hub-dot { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.hub-dot circle { stroke: var(--danger, #f98078); }
.hub-roster { display: block; width: 150px; height: 96px; max-width: 100%; }
.hub-row { fill: var(--surface-2); stroke: var(--hair-strong); stroke-width: 1; }
.hub-bar { fill: var(--text-dim); opacity: .8; }
.hub-bar--tag { fill: var(--gold-300, #f5dc98); opacity: 1; }
.hub-pill { fill: none; stroke: var(--gold-500, #dfbf66); stroke-width: 1.2; }
.hub-star { fill: none; stroke: var(--text-dim); stroke-width: 1.2; stroke-linejoin: round; }
.hub-star.is-kept { fill: var(--gold-500, #dfbf66); stroke: var(--gold-500, #dfbf66); }

/* THE FEATURED TITLE'S TWO LINES ARE RESERVED (review, 22 Oct 2026). Cinzel is
   wider than the serif the browser shows until it arrives (font-display: swap,
   site-wide), so "Discord server icon generator" is one line, then two, and the
   card grows under the reader and pushes the sections down: measured, that was
   the hub's own share of the page's layout shift (0.027 of 0.049 at 390px).
   Holding two lines means the swap changes the letters and not the layout. It is
   only reserved where the title can wrap: a three-up card on a desktop and the
   narrow column beside the picture on a phone. In between, where the card is wide
   and the title sits on one line in either face, nothing is held back. */
@media (min-width: 900px) {
  .hub-feat .hub-title { min-height: 2.4em; }
}

/* Two columns, and the featured row becomes a stack of wide cards with the
   largest preview beside the words. */
@media (max-width: 899px) {
  .hub-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hub-grid--feat { grid-template-columns: minmax(0, 1fr); }
  .hub-feat {
    grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto 1fr auto;
    grid-template-areas: "art title" "art promise" "art meta"; column-gap: 18px; row-gap: 6px; min-height: 0; padding: 16px 18px;
  }
  .hub-feat .hub-art { height: auto; align-self: center; }
  .hub-feat .hub-art > :not(:first-child) { display: none; }
}
/* Phone: one column, a 72px preview beside the words. */
@media (max-width: 560px) {
  .hub-grid { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .hub-grid--feat { gap: 10px; }
  .hub-sec { margin-top: 28px; }
  .hub-door { margin-left: 0; }
  .hub-card { padding: 12px 14px; }
  .hub-title { font-size: 16px; }
  .hub-promise { font-size: 13.5px; }
  .hub-feat { grid-template-columns: 72px minmax(0, 1fr); column-gap: 14px; padding: 14px; }
  .hub-feat .hub-title { font-size: 17px; min-height: 2.4em; }
  /* At 320px the words beside the picture are ~170px, which is less than the meta
     line and the verb side by side: let the verb drop under it rather than split
     "Free · no / account" across two lines. */
  .hub-feat .hub-meta { flex-wrap: wrap; row-gap: 2px; }
  .hub-feat .hub-meta > span:first-child { white-space: nowrap; }
  .hub-feat .hub-promise { font-size: 14px; }
  .hub-ico > svg:first-child { width: 72px; height: 72px; }
  .hub-roster { width: 72px; height: auto; }
}
@media (prefers-reduced-motion: reduce) {
  .hub-card { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PALETTE PASS — these pages onto the site's tokens
   ───────────────────────────────────────────────────────────────────────────
   Everything above predates the palette documented at the top of site.css and
   carries two kinds of value this design system no longer has a place for:

     • `rgba(255,255,255,.03)` as a panel fill. It works on a dark page by
       accident — it is not a colour, it is "a bit of whatever is behind me" —
       and it renders as literally nothing once the ground is pale. Every one
       of those is now a real surface from the ramp.
     • Gold as the hover, the focus ring, the selected state and the heading.
       Gold's job is narrow now: the brand, the primary action, and a number
       worth reading. Interaction is frost; identity is the section tone.

   Appended rather than edited in place so the diff shows one intent in one
   block, and so nothing above is renamed out from under the pages that use it.
   The tools section's tone is CYAN (site.css picks it up from the canonical).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Surfaces ────────────────────────────────────────────────────────────── */
.forge-controls, .gen-panel, .quiz-panel, .outcome, .baked-card,
.card-panel, .roster-panel, .disc-panel {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--hair);
  box-shadow: var(--lift), var(--e1);
}
.forge-row { border-bottom: 1px solid var(--hair); }
.crest { background: radial-gradient(70% 70% at 50% 35%, var(--tone-wash), transparent 72%); }
.quiz-track, .quiz-axis-track { background: var(--surface-3); }
.quiz-bar, .quiz-axis-fill { background: linear-gradient(90deg, var(--tone-edge), var(--tone)); }

/* ── Type ────────────────────────────────────────────────────────────────── */
.tool-kicker { color: var(--tone); letter-spacing: .2em; font-size: var(--fs-xs); }
.tool-h1 { color: var(--gold-500); }
.tool-prose h2 { font-family: var(--display); color: var(--text-strong); }
.tool-prose h3 { color: var(--text-strong); }
.quiz-prompt, .card-title { color: var(--text-strong); }
.quiz-outcome-name, .quiz-meter:first-child .quiz-meter-name { color: var(--gold-500); }
.quiz-outcome-line, .outcome-line, .blazon, .disc-label { color: var(--text-dim); }
.quiz-verdict-head, .baked-card h3 { font-family: var(--display); color: var(--text-strong); }
.quiz-verdict { border-left-color: var(--tone); }
.quiz-counter, .outcome-counter { border-left-color: var(--tone); }
.quiz-stats dd { color: var(--gold-500); }
.db-table th { color: var(--text-strong); }

/* ── Interaction: frost for "you can act on this", tone for "you have". ──── */
.tool-chip, .gen-name, .roster-name, .roster-tag, .mono-input, .contrast-badge {
  background: var(--surface-2); border: 1px solid var(--hair);
}
.tool-chip:hover, .gen-name:hover, .roster-name:hover, .roster-tag:hover, .tincture:hover {
  border-color: var(--frost-600); background: var(--surface-3);
}
.tool-chip.on {
  border-color: var(--tone); background: var(--tone-wash); color: var(--tone);
}
.tincture.on { border-color: var(--tone); box-shadow: 0 0 0 2px var(--tone-edge); }
.gen-name, .roster-name { color: var(--text-strong); }
.roster-tag:hover { color: var(--text-strong); }
.mono-input, .motto-input { color: var(--text-strong); background: var(--ink-900); border-color: var(--edge); }
.quiz-option { background: var(--surface-2); border-color: var(--hair); }
.quiz-option:hover { border-color: var(--frost-600); background: var(--surface-3); }
.quiz-option.picked { border-color: var(--tone); background: var(--tone-wash); }
.tincture { border: 2px solid var(--hair-strong); }
.vocab-dot { border: 1px solid var(--hair-strong); }

/* Focus is frost everywhere, without exception — one ring means one thing. */
.tool-chip:focus-visible, .tincture:focus-visible, .mono-input:focus-visible,
.gen-name:focus-visible, .roster-name:focus-visible, .roster-tag:focus-visible,
.quiz-option:focus-visible, .quiz-prompt:focus-visible, .quiz-outcome-name:focus-visible {
  outline: 2px solid var(--frost-300); outline-offset: 2px;
}

/* The contrast badges are a readout, so they take the semantic colours. */
.contrast-badge.is-bad { border-color: rgba(249,128,120,.55); color: var(--danger); }
.contrast-badge.is-weak { border-color: rgba(246,180,92,.55); color: var(--warn); }
.contrast-badge.is-ok, .contrast-badge.is-good { border-color: rgba(103,216,156,.5); color: var(--ok); }
