/*
 * REALMS UNBOUND
 *
 * ── The design thesis ───────────────────────────────────────────────────────
 *
 * EVERY NUMBER OPENS.
 *
 * D&D 3.5 is a modifier-stacking game. An armour class is not 35, it is
 * `10 −1 size +7 Dex +19 natural`. A saving throw is a base progression plus an ability
 * modifier plus a resistance bonus plus a morale bonus that does not stack with the other
 * morale bonus. The entire felt experience of playing 3.5 — and the entire reason its
 * character sheets take an afternoon — is that every number on the page is a sum whose
 * parts came from six different books.
 *
 * No virtual tabletop shows that. They show the total and make you trust it. This one
 * makes every derived value an object you can open into its components, each carrying its
 * bonus type, where it came from, and the page it is printed on — with the bonuses that
 * did NOT stack printed and struck, and the reason given.
 *
 * That is the product. Everything else in this file exists to serve it.
 *
 * ── The identity ────────────────────────────────────────────────────────────
 *
 * Taken from what 3.5 actually is, not from fantasy in general and not from a sci-fi
 * console. The core books are ink and dark leather with a deep oxblood and gold foil.
 * So the ground is warm near-black rather than blue-black, oxblood carries structure,
 * GOLD MEANS "THIS IS A RULE" and is never spent on anything else, and a single ember
 * orange is the only colour that ever means "do this".
 *
 * The tired route — Cinzel, parchment textures, scroll ornaments — is deliberately
 * refused. That reads as a 2004 fan site. A slab serif for headers reads as a rulebook
 * table, which is what these actually are.
 */

:root {
  color-scheme: dark;

  /* ── ground: ink and leather, warm, never blue ── */
  --black: #100c0a;
  --ground: #16110e;
  --surface: #1d1714;
  --raised: #251d19;
  --sunken: #0c0908;

  --line: #35291f;
  --line-soft: #241c17;

  /* ── ink ── */
  --ink: #ece3d4;
  --ink-dim: #a8998a;
  --ink-faint: #90857c;

  /* ── oxblood: structure, the spine of the thing ── */
  --blood: #7b1e22;
  --blood-lit: #a32a2b;

  /*
   * GOLD MEANS "THIS IS A RULE".
   * Citations, bonus types, rule names, the stack. It is never used for a button, a
   * heading, or decoration — so when a reader sees gold they know without being told
   * that they are looking at something the books say.
   */
  --gold: #c9a227;
  --gold-dim: #9d8742;

  /* ── ember: the one colour that means "do this" ── */
  --ember: #d86543;
  --ember-lit: #e8703f;

  /* ── outcomes ── */
  --pass: #7fa650;
  /* The FILL colour for a failed verdict — a bar, a chip, a border. Fine as a shape. */
  --fail: #cf6960;
  /*
   * The TEXT colour for the same thing, and a separate token because the fill is 3.66:1 on
   * --surface and the reason lines all set it at 11px or smaller.
   *
   * "Strength 12; Power Attack needs 13" is the single most important sentence on a blocked
   * option — it is the entire difference between a refusal and an explanation — and it was
   * the least readable text in the product. A fill colour and a body-text colour are
   * different jobs and were being asked of one value. 6.5:1 on surface, 7.3:1 on sunken.
   */
  --fail-text: #e87f6e;
  /* Unproven takes no hue. Give it amber and it reads as a warning people learn to
     dismiss; a neutral hatch reads as unfinished, which is what it is.
     Lifted from #8a7d70 (4.43:1) for the same reason: it carries reasons, not just marks. */
  --unproven: #a5988b;

  --crit: #e0b93a;
  --fumble: #8c2f26;

  --focus: #6ea8d8;

  --rail: 82px;
  --hud: 56px;
  --shadow: 0 18px 44px -14px rgba(0, 0, 0, 0.8);

  --display: 'Bitter', Georgia, serif;
  --prose: 'Spectral', Georgia, 'Times New Roman', serif;
  --data: 'JetBrains Mono', ui-monospace, Consolas, monospace;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--black);
  color: var(--ink);
  font-family: var(--prose);
  font-size: 1rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* Dark on ember, as .btn-go is: white on #d86543 is 3.6:1, under AA for 16px text (GCC re-gate,
   2026-09-29). --on-ember turns white in the light theme, where ember deepens. */
.skip a { position: absolute; left: -9999px; top: 0; z-index: 300; background: var(--ember); color: var(--on-ember, #170d08); padding: .6rem 1rem; }
/* fixed while focused, so it shows where the reader is even on a page scrolled away from its top
   (device gate round three: the workshop's skip link, focused mid-page, was 800px above the screen) */
.skip a:focus { left: 0; position: fixed; }

/* ═══════════════════════════════════════════════════════════════════════════
   Ambient
   ══════════════════════════════════════════════════════════════════════════ */
.ambient { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.ambient-img {
  position: absolute; inset: -5%; background-size: cover; background-position: center;
  opacity: 0; transition: opacity 1.4s ease;
  filter: saturate(.6) contrast(1.05) brightness(.85);
  animation: breathe 60s ease-in-out infinite alternate;
}
.ambient-img.on { opacity: 1; }
@keyframes breathe { from { transform: scale(1.02); } to { transform: scale(1.09) translate3d(-1.2%, -0.8%, 0); } }
.ambient-scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(16,12,10,.80), rgba(16,12,10,.93) 45%, rgba(16,12,10,.99)),
    radial-gradient(130% 95% at 50% 12%, transparent 26%, rgba(12,9,8,.85) 100%);
}
@media (prefers-reduced-motion: reduce) { .ambient-img { animation: none; } }

/* ═══════════════════════════════════════════════════════════════════════════
   Shell
   ══════════════════════════════════════════════════════════════════════════ */
.shell { position: relative; z-index: 1; display: grid; min-height: 100dvh;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  grid-template-rows: var(--hud) minmax(0, 1fr);
  grid-template-areas: 'mark hud' 'rail main';
}

/* ── the mark ── */
.mark {
  grid-area: mark; display: grid; place-items: center;
  border-right: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft);
  background: var(--sunken); cursor: pointer; border-top: 0; border-left: 0;
  color: var(--ink); font-family: var(--display); font-weight: 700;
}
.mark b { font-size: 1.1875rem; letter-spacing: .06em; color: var(--ember); line-height: 1; }
.mark small { display: block; font-family: var(--data); font-size: 0.4375rem; letter-spacing: .18em; color: var(--ink-faint); margin-top: 2px; }

/*
 * ── the HUD ──
 * The party lives here and never leaves. A game where you cannot see your people at all
 * times is a spreadsheet; their portraits and their health are the first thing a player
 * looks at and the last thing a DM wants to hunt for.
 */
.hud {
  grid-area: hud; display: flex; align-items: center; gap: 14px; padding: 0 14px;
  border-bottom: 1px solid var(--line-soft);
  background: linear-gradient(180deg, rgba(29,23,20,.9), rgba(22,17,14,.7));
  backdrop-filter: blur(8px); min-width: 0;
}
.hud-campaign { display: flex; flex-direction: column; min-width: 0; max-width: 15rem; }
.hud-campaign b { font-family: var(--display); font-size: 0.875rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hud-campaign span { font-family: var(--data); font-size: 0.5625rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.party-strip { display: flex; align-items: center; gap: 6px; min-width: 0; overflow-x: auto; }
.party-strip::-webkit-scrollbar { height: 0; }
.pc {
  position: relative; width: 38px; height: 38px; flex: none; cursor: pointer;
  background: var(--raised); border: 0; padding: 0;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 700; font-size: 0.875rem; color: var(--ink-dim);
  /* A ring, not a bar. Health as an arc around a face is legible at 38px in peripheral
     vision, which is where the party actually lives while you are reading something else. */
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--line);
}
.pc img { width: 100%; height: 100%; object-fit: cover; object-position: center top; border-radius: 50%; }
.pc[data-band='well'] { box-shadow: 0 0 0 2px var(--pass); }
.pc[data-band='hurt'] { box-shadow: 0 0 0 2px var(--gold); }
.pc[data-band='bloodied'] { box-shadow: 0 0 0 2px var(--fail); }
.pc[data-band='down'] { box-shadow: 0 0 0 2px var(--fumble); filter: grayscale(.7); }
.pc .hp {
  position: absolute; bottom: -3px; left: 50%; transform: translateX(-50%);
  font-family: var(--data); font-size: 0.5313rem; line-height: 1;
  background: var(--black); border: 1px solid var(--line); padding: 1px 3px; white-space: nowrap;
}
.hud-right { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* ── the rail: six modules, each one a place you go, not a menu item ── */
.rail {
  grid-area: rail; border-right: 1px solid var(--line-soft); background: var(--sunken);
  display: flex; flex-direction: column; padding: 6px 0; overflow-y: auto;
}
.rail::-webkit-scrollbar { width: 0; }
.mod {
  position: relative; width: 100%; min-height: 62px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  background: none; border: 0; cursor: pointer; color: var(--ink-faint); padding: 8px 4px;
}
.mod .glyph { font-size: 1.1875rem; line-height: 1; filter: grayscale(1) opacity(.7); }
.mod .name { font-family: var(--data); font-size: 0.5313rem; letter-spacing: .12em; text-transform: uppercase; }
.mod:hover { color: var(--ink-dim); background: rgba(255,255,255,.02); }
.mod:hover .glyph { filter: none; }
.mod.on { color: var(--ink); background: linear-gradient(90deg, rgba(123,30,34,.3), transparent); }
.mod.on .glyph { filter: none; }
.mod.on::before { content: ''; position: absolute; left: 0; top: 8px; bottom: 8px; width: 3px; background: var(--ember); }
.mod[data-soon]::after {
  content: ''; position: absolute; top: 10px; right: 12px; width: 5px; height: 5px;
  border: 1px solid var(--ink-faint); transform: rotate(45deg);
}
.rail-foot { margin-top: auto; padding: 8px 4px; text-align: center; }
.rail-foot .live { font-family: var(--data); font-size: 0.4688rem; letter-spacing: .14em; text-transform: uppercase; color: var(--pass); }
.rail-foot small { display: block; font-family: var(--data); font-size: 0.4375rem; color: var(--ink-faint); }

.main { grid-area: main; overflow-y: auto; padding: 22px 24px 90px; min-width: 0; }
.stack-col { max-width: 1240px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }

/* ═══════════════════════════════════════════════════════════════════════════
   Type
   ══════════════════════════════════════════════════════════════════════════ */
h1, h2, h3, h4 { font-family: var(--display); font-weight: 700; margin: 0; color: var(--ink); }
h1 { font-size: clamp(26px, 3vw, 40px); line-height: 1.08; letter-spacing: -.015em; }
h2 { font-size: 1.3125rem; letter-spacing: -.01em; }
h3 { font-size: 0.9375rem; }

.kicker {
  font-family: var(--data); font-size: 0.5938rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ember); margin: 0 0 7px;
}
.kicker .of { color: var(--ink-faint); }
.lede { font-size: 0.9375rem; color: var(--ink-dim); max-width: 60ch; margin: 9px 0 0; }
.rule {
  display: flex; align-items: center; gap: 11px; margin: 0 0 12px;
  font-family: var(--data); font-size: 0.5938rem; letter-spacing: .2em; text-transform: uppercase; color: var(--ink-faint);
}
.rule::after { content: ''; flex: 1; height: 1px; background: var(--line-soft); }

/* ═══════════════════════════════════════════════════════════════════════════
   Panels
   ══════════════════════════════════════════════════════════════════════════ */
.card {
  background: linear-gradient(180deg, var(--surface), var(--ground));
  border: 1px solid var(--line-soft); box-shadow: var(--shadow); position: relative;
}
/* A hairline of oxblood along the head. The books' own spine colour, used as structure. */
.card::before { content: ''; position: absolute; inset: -1px -1px auto -1px; height: 2px; background: var(--blood); }
.card-pad { padding: 17px 19px; }

.btn {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; border: 1px solid var(--line); color: var(--ink-dim);
  font-family: var(--data); font-size: 0.6563rem; letter-spacing: .12em; text-transform: uppercase;
  padding: 7px 13px; cursor: pointer; white-space: nowrap;
}
/* Gold means "this is a rule". A hover state is not a rule, and spending the rule colour
   on every button on every surface is what makes it stop meaning anything where it counts. */
.btn:hover { color: var(--ink); border-color: var(--ink-dim); }
.btn-go { background: var(--ember); border-color: var(--ember); color: var(--on-ember, #170d08); font-weight: 500; }
.btn-go:hover { background: var(--ember-lit); border-color: var(--ember-lit); color: var(--on-ember, #170d08); }
.btn-sm { padding: 4px 8px; font-size: 0.5938rem; }
.actions { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 15px; }

/* ═══════════════════════════════════════════════════════════════════════════
   ★ THE STACK — the signature interaction
   ═══════════════════════════════════════════════════════════════════════════
   A derived value and the sum it came from. Closed it is a number with a hairline
   under it saying how many parts it has; open it is the whole derivation, one line
   per component, each with its BONUS TYPE in gold — because in 3.5 the type is the
   rule: two dodge bonuses stack, two deflection bonuses do not.
   ────────────────────────────────────────────────────────────────────────── */
.stat {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  background: none; border: 0; padding: 0; cursor: pointer; color: inherit; text-align: left;
  font: inherit;
}
.stat-v {
  font-family: var(--data); font-size: 1.3125rem; font-weight: 500; line-height: 1.1;
  color: var(--ink); font-variant-numeric: tabular-nums;
  border-bottom: 1px dashed var(--gold-dim); padding-bottom: 1px;
}
.stat:hover .stat-v { color: var(--gold); border-bottom-color: var(--gold); }
.stat-k { font-family: var(--data); font-size: 0.5313rem; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); margin-top: 3px; }
.stat-n { font-family: var(--data); font-size: 0.5rem; color: var(--gold-dim); margin-top: 1px; }
.stat[aria-expanded='true'] .stat-v { color: var(--gold); border-bottom-style: solid; }

/* The opened derivation. */
.stack {
  margin: 9px 0 4px; border-left: 2px solid var(--gold-dim);
  background: var(--sunken); padding: 9px 12px;
}
.stack-line {
  display: grid; grid-template-columns: 3.6rem 1fr auto; gap: 4px 10px; align-items: baseline;
  padding: 3px 0; border-bottom: 1px solid var(--line-soft);
}
.stack-line:last-of-type { border-bottom: 0; }
.stack-amt { font-family: var(--data); font-size: 0.8125rem; color: var(--ink); font-variant-numeric: tabular-nums; text-align: right; }
/* The bonus type, in gold, because the type IS the rule. */
.stack-type { font-family: var(--data); font-size: 0.5938rem; letter-spacing: .1em; text-transform: uppercase; color: var(--gold); }
.stack-src { font-size: 0.7813rem; color: var(--ink-dim); }
.stack-cite { font-family: var(--data); font-size: 0.5625rem; color: var(--ink-faint); white-space: nowrap; }
/*
 * A bonus that did not stack is PRINTED AND STRUCK, never removed. Deleting it hides
 * the ruling, and "why is my AC lower than I calculated" is the single most common
 * argument at a 3.5 table.
 */
.stack-line.dead .stack-amt, .stack-line.dead .stack-src { text-decoration: line-through; text-decoration-color: var(--fail); color: var(--ink-faint); }
.stack-line.dead .stack-type { color: var(--ink-faint); }
.stack-why { grid-column: 2 / -1; font-size: 0.7188rem; font-style: italic; color: var(--fail); }
.stack-total {
  display: grid; grid-template-columns: 3.6rem 1fr auto; gap: 10px; align-items: baseline;
  margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line);
}
.stack-total .stack-amt { font-size: 1rem; color: var(--gold); }
.stack-total .stack-type { color: var(--ink-dim); }

/* When the engine could not establish a component. */
.stack-line.open-question .stack-amt { color: var(--unproven); font-style: italic; }
.unproven-note {
  margin-top: 8px; padding: 8px 10px; border: 1px dashed var(--line);
  background-image: repeating-linear-gradient(45deg, rgba(255,255,255,.03) 0 2px, transparent 2px 8px);
  font-size: 0.75rem; color: var(--ink-dim);
}
.unproven-note b { font-family: var(--data); font-size: 0.5938rem; letter-spacing: .14em; text-transform: uppercase; color: var(--unproven); display: block; margin-bottom: 3px; }

/* A row of stats, the way a sheet prints them. */
.stat-row { display: flex; flex-wrap: wrap; gap: 22px 30px; }
.abilities { display: grid; grid-template-columns: repeat(6, 1fr); gap: 9px; }
@media (max-width: 38.75em) { .abilities { grid-template-columns: repeat(3, 1fr); } }
.abil { border: 1px solid var(--line-soft); background: var(--sunken); padding: 9px 4px 7px; text-align: center; cursor: pointer; }
.abil:hover { border-color: var(--gold-dim); }
/* Each part on its own line.
   These three carried `margin-top` and no `display`, and every caller appends them as bare
   spans with no whitespace between — so an ability box rendered `18+4STR` as one run and the
   margins were dropped, inline boxes having no vertical margin. */
.abil .score, .abil .mod, .abil .nm { display: block; }
/* An engine-produced number, so it is set in the data face like every other one. It was in
   the display serif, which is the only place in the product a computed number was not. */
.abil .score { font-family: var(--data); font-size: 1.4375rem; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }
/* `.abil .mod` used to collide with `.mod`, the navigation rail's module button, which is a
   62px-tall flex box with padding and a pointer cursor. Restating only the font left all of
   that applying to a `+4`. Every colliding property is undone by name rather than by hoping
   the cascade orders them the way it did last week. */
.abil .mod {
  font-family: var(--data); font-size: 0.8125rem; color: var(--gold); margin-top: 1px;
  position: static; width: auto; min-height: 0; min-width: 0;
  flex-direction: initial; align-items: initial; justify-content: initial; gap: 0;
  padding: 0; cursor: inherit; background: none; border: 0;
}
.abil .nm { font-family: var(--data); font-size: 0.5rem; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); margin-top: 4px; }

/* ═══════════════════════════════════════════════════════════════════════════
   The Codex
   ══════════════════════════════════════════════════════════════════════════ */
.find {
  width: 100%; background: var(--sunken); border: 1px solid var(--line); color: var(--ink);
  font-family: var(--data); font-size: 0.875rem; padding: 10px 12px;
}
/* `outline: none` here outranked the :focus-visible rule above, so the codex search box —
   the way into 25,129 records — had no focus ring at all. The border change is the
   decoration; the outline is the affordance, and only one of them is optional. */
.find:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.find:focus { border-color: var(--gold-dim); }
.find::placeholder { color: var(--ink-faint); }

.lens { display: flex; flex-wrap: wrap; gap: 4px; margin: 10px 0; }
.lens button {
  font-family: var(--data); font-size: 0.5938rem; letter-spacing: .1em; text-transform: uppercase;
  background: none; border: 1px solid var(--line-soft); color: var(--ink-dim);
  padding: 3px 8px; cursor: pointer; display: inline-flex; gap: 6px; align-items: baseline;
}
.lens button .n { color: var(--ink-faint); font-size: 0.5625rem; }
.lens button:hover { border-color: var(--gold-dim); color: var(--ink); }
.lens button[aria-pressed='true'] { background: var(--blood); border-color: var(--blood-lit); color: #ece3d4; }

.hits { list-style: none; margin: 0; padding: 0; }
.hit {
  display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: baseline;
  width: 100%; padding: 7px 8px; background: none; border: 0;
  border-bottom: 1px solid var(--line-soft); color: inherit; font: inherit; cursor: pointer; text-align: left;
}
.hit:hover { background: rgba(201,162,39,.06); }
.hit .n { font-family: var(--display); font-size: 0.875rem; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hit .src { font-family: var(--data); font-size: 0.5938rem; color: var(--ink-faint); white-space: nowrap; }
/* Authority in gold — it is a statement about the rules, so it takes the rule colour. */
.hit .auth { font-family: var(--data); font-size: 0.5938rem; border: 1px solid var(--gold-dim); color: var(--gold); padding: 0 5px; min-width: 18px; text-align: center; }
.hit .auth.weak { border-color: var(--line); color: var(--ink-faint); text-decoration: line-through; }
.hit .note { grid-column: 1 / -1; font-size: 0.6875rem; color: var(--ink-faint); font-style: italic; }

.cite-line {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline;
  font-family: var(--data); font-size: 0.6563rem; color: var(--gold);
  padding-bottom: 11px; margin-bottom: 13px; border-bottom: 1px solid var(--line-soft);
}
.cite-line .auth { border: 1px solid var(--gold-dim); padding: 0 5px; }
.cite-line .weak { color: var(--fail); }

/* A creature's plate on its record card. Floated so the numbers — the reason the card was
   opened — still start at the top; the caption is plain ink, not gold, because gold here
   means "the books say this" and the painting is ours, not the book's. */
.card-pad.has-plate, .pane.has-plate { display: flow-root; }
.creature-plate { float: right; width: min(11rem, 38%); margin: 0 0 12px 16px; }
.creature-plate img { display: block; width: 100%; height: auto; border: 1px solid var(--line); background: var(--sunken); }
.creature-plate figcaption { margin-top: 5px; font-family: var(--data); font-size: 0.5625rem; letter-spacing: .06em; line-height: 1.45; color: var(--ink-faint); }
@media (max-width: 40rem) { .creature-plate { float: none; width: 9rem; margin: 0 0 12px; } }

.kv { display: grid; grid-template-columns: minmax(7.5rem, auto) 1fr; gap: 5px 16px; margin: 0; }
.kv dt { font-family: var(--data); font-size: 0.5938rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); padding-top: 3px; }
.kv dd { margin: 0; font-family: var(--data); font-size: 0.8125rem; overflow-wrap: anywhere; }

/* ═══════════════════════════════════════════════════════════════════════════
   Dice — the atomic unit of the game
   ═══════════════════════════════════════════════════════════════════════════
   A roll is the most emotionally loaded thing that happens, and 3.5 has two results
   that are categorically different from all others. A natural 20 and a natural 1 get
   real treatment; everything else stays quiet so that they can.
   ────────────────────────────────────────────────────────────────────────── */
.dice { position: fixed; left: 18px; bottom: 18px; z-index: 80; /* the dice live on the LEFT; the rail (app.css) makes this static on wide screens */ display: flex; flex-direction: column; align-items: flex-end; gap: 7px; }
.rolls { display: flex; flex-direction: column-reverse; gap: 6px; }
.roll {
  display: grid; grid-template-columns: auto 1fr; gap: 11px; align-items: center;
  background: linear-gradient(180deg, var(--raised), var(--surface));
  border: 1px solid var(--line); border-left: 3px solid var(--gold-dim);
  padding: 10px 15px 10px 13px; min-width: 268px; box-shadow: var(--shadow);
  animation: toss .3s cubic-bezier(.18, .9, .28, 1.25);
}
@keyframes toss { from { transform: translateY(10px) rotate(-1.5deg) scale(.94); opacity: 0; } to { transform: none; opacity: 1; } }
.roll .tot { font-family: var(--display); font-weight: 700; font-size: 1.875rem; line-height: 1; min-width: 48px; text-align: right; font-variant-numeric: tabular-nums; }
.roll.nat20 { border-left-color: var(--crit); }
.roll.nat20 .tot { color: var(--crit); }
.roll.nat1 { border-left-color: var(--fumble); }
.roll.nat1 .tot { color: var(--fail); }
.roll .meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.roll .expr { font-family: var(--data); font-size: 0.7813rem; }
/* Faces, modifier and stream position stay separate: a total alone cannot be checked,
   and being checkable is the only thing that makes this better than a chat message. */
.roll .parts { font-family: var(--data); font-size: 0.8125rem; color: var(--ink-dim); }
.roll .flag { font-family: var(--data); font-size: 0.6875rem; letter-spacing: .14em; text-transform: uppercase; }
.roll.nat20 .flag { color: var(--crit); }
.roll.nat1 .flag { color: var(--fail); }
.roll .why { font-size: 0.7188rem; color: var(--ink-dim); }

/* The named breakdown — flanking, cover, the bonus a condition priced — when the roll
   carries one. Spans both grid columns: it sits under the die glyph and the head alike,
   because the trace it renders belongs to the whole roll, not to one side of the card.
   Gold marks only the numbers the rules produced (the die face, each signed modifier,
   the total); the labels naming them stay --ink-dim so the eye finds the arithmetic first. */
.roll-breakdown { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 3px; margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line-soft); }
.rb-line { display: flex; align-items: baseline; gap: 9px; }
.rb-die .rb-val, .rb-total .rb-val { font-family: var(--display); font-weight: 700; font-size: 0.9375rem; }
.rb-mod .rb-val { font-family: var(--data); font-size: 0.8125rem; min-width: 2.6em; }
.rb-val.gold { color: var(--gold); }
.rb-label { font-family: var(--prose); font-size: 0.8125rem; color: var(--ink-dim); }
.rb-total .rb-label { color: var(--ink); }

.dicebar { display: flex; gap: 4px; }
.dicebar input {
  width: 96px; background: var(--sunken); border: 1px solid var(--line); color: var(--ink);
  font-family: var(--data); font-size: 0.75rem; padding: 7px 8px;
}
.dicebar input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.dicebar input:focus { border-color: var(--gold-dim); }
.dicebar button {
  background: var(--surface); border: 1px solid var(--line); color: var(--ink-dim);
  font-family: var(--data); font-size: 0.6875rem; padding: 7px 10px; cursor: pointer;
}
.dicebar button:hover { color: var(--gold); border-color: var(--gold-dim); }

/* ═══════════════════════════════════════════════════════════════════════════
   Fold
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 51.25em) {
  .shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: var(--hud) minmax(0,1fr) 66px;
           grid-template-areas: 'hud' 'main' 'rail'; }
  .mark { display: none; }
  .rail { flex-direction: row; border-right: 0; border-top: 1px solid var(--line-soft); padding: 0; overflow-x: auto; }
  .mod { min-width: 66px; min-height: 0; flex: 1; }
  .mod.on::before { left: 6px; right: 6px; top: 0; bottom: auto; width: auto; height: 3px; }
  .rail-foot { display: none; }
  .main { padding: 14px 12px 24px; }
  .dice { right: 10px; bottom: 76px; left: 10px; align-items: stretch; }
  .roll { min-width: 0; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } }

/* A part the engine assumed rather than read. */
.stack-line.unproven .stack-amt,
.stack-line.unproven .stack-type { color: var(--unproven); }
.stack-line.unproven .stack-src { color: var(--unproven); font-style: italic; }

/* ── reasons are read, not glanced at ─────────────────────────────────────
   Every "why this is blocked" line in the product used --fail, which is a fill colour, at
   11px or smaller. None of them reached AA. These are the sentences that turn a refusal
   into an explanation, so they take the text token. */
.stack-why, .slot-why, .opt .why, .featchip span, .err, .lv-why { color: var(--fail-text); }

/* The citation under a derived part. It was --ink-faint at 9px, about 3.3:1 — a citation
   nobody can read is decoration, and this product's whole argument is that it is not. */
.stack-cite { color: var(--ink-dim); }


/* ──────────────────────────────────────────────────────────────────────────
   THE SHEET — chips, a band of tiles, and one tabbed surface

   In THIS file because the sheet itself is shared (sheet-view.js): the
   campaign screen loads app.css and the roster loads characters.css, and a
   component drawn by both with styles in either would be styled on one page
   and bare markup on the other.

   The grammar every modern sheet shares: the six as chips with the modifier
   huge and the score small; the numbers a table asks for as tiles; the rest
   layered behind tabs instead of scrolling headings. Ours differ in one way
   that matters — every chip and tile is a statNode, so it opens into the
   components it was summed from, struck parts included.
   ────────────────────────────────────────────────────────────────────────── */

.sheet-chips {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: .45rem;
  margin: .2rem 0 .8rem;
}
@media (max-width: 40rem) { .sheet-chips { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.sheet-chips .stat,
.sheet-band .stat {
  width: 100%;
  display: flex; flex-direction: column; align-items: center; gap: .1rem;
  background: var(--sunken);
  border: 1px solid var(--line);
  border-top: 2px solid var(--blood);
  padding: .55rem .3rem .45rem;
  cursor: pointer;
}
.sheet-chips .stat:hover, .sheet-band .stat:hover { border-color: var(--gold-dim); border-top-color: var(--blood-lit); }
.sheet-chips .stat[aria-expanded='true'], .sheet-band .stat[aria-expanded='true'] { border-color: var(--gold); }

.sheet-chips .stat-v, .sheet-band .stat-v {
  font-family: var(--display); font-weight: 700; font-size: 1.45rem; line-height: 1;
  color: var(--ink);
}
.stat-sub {
  font-family: var(--data); font-size: .5625rem; letter-spacing: .06em;
  color: var(--ink-faint);
}
.sheet-chips .stat-k, .sheet-band .stat-k {
  font-family: var(--data); font-size: .5625rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-dim);
}
/* "7 parts" — the invitation to open the number. Gold, because what is behind
   it is the books. */
.sheet-chips .stat-n, .sheet-band .stat-n {
  font-family: var(--data); font-size: .5rem; letter-spacing: .08em; color: var(--gold-dim);
}

.sheet-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6.2rem, 1fr));
  gap: .45rem;
  margin: 0 0 .9rem;
}

/*
 * The opened derivation escapes its grid track.
 *
 * statNode's panel is in-flow inside the wrapper that is the grid item, and both grids
 * above use minmax(0, 1fr) — which removes the content-based floor, so a track is hard-
 * capped at its share of the row. An armour class with seven components opened into a
 * column about a hundred pixels wide, its citations wrapping one word per line over the
 * neighbouring chips. The panel is a popover now: anchored to its chip, wide enough to
 * print `+2 shield Heavy Shield, Steel — PHB p123` on one line, floated over what is
 * below rather than reshaping the sheet every time a number opens.
 *
 * The last columns anchor right instead of left, or a Will save's panel walks off the
 * viewport's right edge by most of its own width.
 */
.sheet-chips > div,
.sheet-band > div { position: relative; }
.sheet-chips .stack,
.sheet-band .stack {
  position: absolute;
  left: 0; top: calc(100% + 4px);
  z-index: 60;
  width: max-content;
  min-width: 20rem;
  max-width: min(34rem, 92vw);
  background: var(--raised);
  border: 1px solid var(--gold-dim);
  box-shadow: var(--shadow);
  padding: .6rem .75rem;
}
.sheet-chips > div:nth-child(n+4) .stack,
.sheet-band > div:nth-last-child(-n+3) .stack { left: auto; right: 0; }

/* ── the tabs ── */
.sheet-tabs {
  display: flex; gap: 0; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 1rem 0 0;
}
.sheet-tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: .45rem .8rem; cursor: pointer;
  font-family: var(--data); font-size: .625rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-dim);
}
.sheet-tab:hover { color: var(--ink); }
.sheet-tab.on { color: var(--ink); border-bottom-color: var(--ember); }

.sheet-panel { padding: .8rem 0 .2rem; }
.tab-lede { margin: 0 0 .6rem; font-family: var(--prose); font-size: .875rem; color: var(--ink-dim); }
.tab-lede b { color: var(--ink); }
.unproven-inline { color: var(--unproven); }
.tab-h {
  margin: .7rem 0 .25rem;
  font-family: var(--data); font-size: .625rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-faint);
}
.tab-h:first-child { margin-top: 0; }
.tab-row {
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
  padding: .28rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.tab-row:last-child { border-bottom: 0; }
.tab-name { font-family: var(--prose); font-size: .9375rem; font-weight: 600; color: var(--ink); }
.tab-cite { margin-left: auto; font-family: var(--data); font-size: .6875rem; color: var(--gold); }
.tab-rank { margin-left: auto; font-family: var(--data); font-size: .75rem; color: var(--ink-dim); }
.tab-why { flex-basis: 100%; font-family: var(--prose); font-size: .8125rem; color: var(--unproven); }
.tab-empty { margin: 0; font-family: var(--prose); font-size: .875rem; color: var(--ink-faint); }

/* Verdicts wear their colours; unproven takes none. */
.pill {
  font-family: var(--data); font-size: .5625rem; letter-spacing: .12em; text-transform: uppercase;
  padding: .1rem .4rem; border: 1px solid currentColor;
}
.pill.legal { color: var(--pass); }
.pill.blocked { color: var(--fail-text); }
.pill.unproven { color: var(--unproven); }

/* ── the build's verdict, stated at full strength on every screen ── */
.verdict { display: flex; flex-wrap: wrap; gap: .3rem .7rem; align-items: baseline; margin: 0 0 .8rem; font-size: .86rem; color: var(--ink-dim); }
.verdict b { font-family: var(--data); font-size: .64rem; letter-spacing: .16em; padding: .1rem .4rem; border: 1px solid currentColor; }
.verdict.v-legal b { color: var(--pass); }
.verdict.v-blocked b { color: var(--fail); }
.verdict.v-unproven b { color: var(--unproven); }
/* The exception that let a blocked build save: gold, the colour of a decision on the record. */
.verdict.v-exception b { color: var(--gold); }

/* A 3.5 word that explains itself (public/glossary.js, 2026-09-25): dotted, and its gloss in a
   tip on hover, focus or tap. The tip is fixed and placed by script so no scrolling list clips it. */
.gloss { text-decoration: underline dotted; text-decoration-color: var(--ink-dim); text-underline-offset: 3px; cursor: help; }
.gloss:focus-visible { outline: 1px solid var(--ink-dim); outline-offset: 1px; border-radius: 2px; }
#gloss-tip { position: fixed; z-index: 1000; max-width: min(22rem, calc(100vw - 16px)); padding: 7px 10px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: 6px;
  font: 14px/1.45 var(--prose); box-shadow: 0 6px 18px rgba(0,0,0,.45); pointer-events: none; }

/* ── THE LOOK, AS THE PLAYER CHOSE IT (setup audit, 2026-09-25; public/prefs.js) ──────────────
   Light is the same table by daylight: vellum ground, iron-gall ink, the same reds and golds
   deepened until they read on a pale page. Every colour on the site is one of these tokens, so
   the swap is the whole theme. 'device' follows the operating system's own choice. */
:root[data-theme="light"] {
  color-scheme: light;
  --black: #e6ddcd; --ground: #f5efe4; --surface: #fbf7f0; --raised: #ffffff; --sunken: #ebe3d5; --paper: #fbf7f0;
  --line: #d2c4ae; --line-soft: #e3d9c8;
  --ink: #231a12; --ink-dim: #5e4f40; --ink-faint: #665849;
  --blood: #9c2a2d; --blood-lit: #b83a36;
  --gold: #795c0b; --gold-dim: #725e28;
  --ember: #a63d1c; --ember-lit: #9a3a18;
  --pass: #436a23; --fail: #ab382d; --fail-text: #a3322a;
  --unproven: #61574c; --crit: #8f700f; --fumble: #8c2f26; --focus: #2f6fb0;
  --shadow: 0 18px 44px -14px rgba(60, 40, 20, 0.28);
}
@media (prefers-color-scheme: light) {
  :root[data-theme="device"] {
    color-scheme: light;
    --black: #e6ddcd; --ground: #f5efe4; --surface: #fbf7f0; --raised: #ffffff; --sunken: #ebe3d5; --paper: #fbf7f0;
    --line: #d2c4ae; --line-soft: #e3d9c8;
    --ink: #231a12; --ink-dim: #5e4f40; --ink-faint: #665849;
    --blood: #9c2a2d; --blood-lit: #b83a36;
    --gold: #795c0b; --gold-dim: #725e28;
    --ember: #a63d1c; --ember-lit: #9a3a18;
    --pass: #436a23; --fail: #ab382d; --fail-text: #a3322a;
    --unproven: #61574c; --crit: #8f700f; --fumble: #8c2f26; --focus: #2f6fb0;
    --shadow: 0 18px 44px -14px rgba(60, 40, 20, 0.28);
  }
}
/* Text size: the whole page, as a browser's own zoom would, since sizes here are set in px. */
:root[data-text-size="small"] body { zoom: 0.9; }
:root[data-text-size="large"] body { zoom: 1.15; }
/* Reduced motion, chosen in Settings, whatever the operating system says. */
:root[data-motion="reduce"] *, :root[data-motion="reduce"] *::before, :root[data-motion="reduce"] *::after {
  animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important; scroll-behavior: auto !important;
}
/* In the light look, what sits ON a painting stays dark: the top bars and the painted banners.
   Custom properties cascade, so restating the dark tokens here keeps every word inside them
   light on dark, exactly as the dark look draws them. The table's full-page painting takes a
   pale scrim instead, since the page's own words sit on it. */
:root[data-theme="light"] :is(.page-top, .forge-top, .lv-top, .setup-top, .setup-bar, .sb-top, .plate, .lede-screen, .bar, .scene-card, .act-bar) {
  color-scheme: dark;
  --black: #100c0a; --ground: #16110e; --surface: #1d1714; --raised: #251d19; --sunken: #0c0908;
  --line: #35291f; --line-soft: #241c17; --ink: #ece3d4; --ink-dim: #a8998a; --ink-faint: #90857c;
  --gold: #c9a227; --gold-dim: #9d8742; --ember: #d86543; --ember-lit: #e8703f; --focus: #6ea8d8; --on-ember: #170d08;
  color: var(--ink);
}
:root[data-theme="light"] .ambient-scrim {
  background: linear-gradient(180deg, rgba(245,239,228,.86), rgba(245,239,228,.95) 45%, rgba(245,239,228,.99));
}
@media (prefers-color-scheme: light) {
  :root[data-theme="device"] :is(.page-top, .forge-top, .lv-top, .setup-top, .setup-bar, .sb-top, .plate, .lede-screen, .bar, .scene-card, .act-bar) {
    color-scheme: dark;
    --black: #100c0a; --ground: #16110e; --surface: #1d1714; --raised: #251d19; --sunken: #0c0908;
    --line: #35291f; --line-soft: #241c17; --ink: #ece3d4; --ink-dim: #a8998a; --ink-faint: #90857c;
    --gold: #c9a227; --gold-dim: #9d8742; --ember: #d86543; --ember-lit: #e8703f; --focus: #6ea8d8; --on-ember: #170d08;
    color: var(--ink);
  }
  :root[data-theme="device"] .ambient-scrim {
    background: linear-gradient(180deg, rgba(245,239,228,.86), rgba(245,239,228,.95) 45%, rgba(245,239,228,.99));
  }
}

/* A row a filter hides stays hidden: .opt sets its own display, which beat the hidden attribute,
   so the fighter's bonus-feat search typed and nothing changed (fresh-eyes round two, 2026-09-25). */
.opt[hidden], label[hidden] { display: none !important; }

/* Settings, round two (2026-09-25): the required mark, the error beside its field, the tier's model as small print. */
.req-tag { font: 600 10px/1 var(--data); letter-spacing: .08em; text-transform: uppercase; color: var(--ember); margin-left: .4rem; }
.field-err { color: var(--fail-text); margin: .35rem 0 0; font-size: .95rem; }
.tier-model { color: var(--ink-dim); font-size: .85em; } /* AA at .85em (2026-09-27) */

/*
 * On a touch screen every button is a finger's target: 44 CSS pixels at least (phone and tablet
 * walks, 2026-09-27: most buttons were 23 to 32 pixels tall). Only where the pointer is coarse,
 * so a desktop's layout is untouched.
 */
/* A control no stylesheet sizes is drawn at the browser's own size, 13px in WebKit, and the
   generated floor below cannot see a size nobody wrote (the workshop's ability steppers, device
   gate round two, 2026-09-29). On a touch screen the browser's default is 16px instead; any rule
   that names a size for a control outranks this one, as it always did. */
@media (pointer: coarse) {
  button, input, select, textarea { font-size: 1rem; }
}
/* And with a mouse, the desktop floor: 14px, not the browser's 13.33px (GCC's re-gate of PR 11,
   Tom's desktop: the steppers' + and -). */
@media (pointer: fine) {
  button, input, select, textarea { font-size: .875rem; }
}
@media (pointer: coarse) {
  .btn, .btn-sm, #dm-toggle, #acct, .adv-toggle, .dice-toggle, .sec-btn, #panel-close {
    min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
  }
  .dice-set { min-width: 44px; min-height: 44px; }
  select, input[type='text'], input[type='search'], input[type='number'] { min-height: 44px; }
  /* the key fields on Settings are passwords, which the line above never reached (35px, gate 2026-09-29) */
  input[type='password'], input[type='email'], input[type='url'] { min-height: 44px; }
  .skip a { min-height: 44px; display: inline-flex; align-items: center; } /* was 43.98px */
  summary { min-height: 44px; padding-block: .65rem; box-sizing: border-box; }
  .wordmark, .camp { min-height: 44px; display: flex; align-items: center; }
}
/* The rest of the controls the phone and tablet audits measured under a finger's size. */
@media (pointer: coarse) {
  .bm-act-go, .bm-act-sight, .bm-reroll, .bm-mode, .bm-act-ranged, #sheet-peek,
  #signin, #new-campaign, .brand, .ch-camp, .ch-camp-go, footer a {
    min-height: 44px; display: inline-flex; align-items: center;
  }
  #dm-toggle, .stat { min-width: 44px; }
}
@media (pointer: coarse) {
  .mode button, .roll-ov { min-height: 44px; min-width: 44px; }
}
@media (pointer: coarse) {
  .stat, .dlx-feat-open, .link, .bm-tok { min-height: 44px; min-width: 44px; }
  .link { display: inline-flex; align-items: center; }
  input[type='radio'], input[type='checkbox'] { min-width: 24px; min-height: 24px; }
  label[for] { min-height: 44px; display: inline-flex; align-items: center; }
}

/* Text on an ember fill (AA, 2026-09-27): dark on the dark theme, white on the light. */
:root { --on-ember: #170d08; }
:root[data-theme="light"] { --on-ember: #ffffff; }
@media (prefers-color-scheme: light) { :root[data-theme="device"] { --on-ember: #ffffff; } }

/* The table's top bar and dice rail by daylight (AA, 2026-09-27): the bar keeps the dark tokens
   (above), so its see-through dark gradient became grey over the pale page and its faint words
   read 3.6:1; it is solid here. The rail's dark wash greyed the pale page under the dice note. */
:root[data-theme="light"] .bar { background: linear-gradient(180deg, #1d1714, #16110e); }
:root[data-theme="light"] .rail { background: rgba(251, 247, 240, .55); }
@media (prefers-color-scheme: light) {
  :root[data-theme="device"] .bar { background: linear-gradient(180deg, #1d1714, #16110e); }
  :root[data-theme="device"] .rail { background: rgba(251, 247, 240, .55); }
}
/* The archive's opening by daylight: its dark wash turned the pale page grey under the lede and
   the counts (3.2:1). The same fall of shade, in the light look's own line colour. */
:root[data-theme="light"] .opening { background: linear-gradient(180deg, rgba(210, 196, 174, .2), transparent 70%); }
@media (prefers-color-scheme: light) {
  :root[data-theme="device"] .opening { background: linear-gradient(180deg, rgba(210, 196, 174, .2), transparent 70%); }
}

/* A glossed word is a word inside running text, and under WCAG 2.5.8's inline exception it is not
   held to the 44px rule (GCC's ruling, device gate round three, 2026-09-29: "keep normal line
   spacing and give the gloss a clear focus and tap highlight"). Round two had given each one an
   invisible 44px box reaching 13px over the lines above and below and 14px over its neighbours;
   in every dense line - the initiative roster, what an ability does, the workshop's notes - those
   boxes lay over the next line's words and buttons, so a tap near one word opened another. The
   word is its own target again, and a touch shows plainly which word it took. */
@media (pointer: coarse) {
  .gloss { -webkit-tap-highlight-color: rgba(201, 162, 39, .35); border-radius: 2px; }
  .gloss:active { background: rgba(201, 162, 39, .22); }
  /* A glossed word that is the whole of its label, or an item of a row (glossary.js standsAlone),
     is not inside a sentence and is held to a finger's size like any control. Its own box, in
     the flow - the row it sits in grows to hold it, nothing reaches over a neighbour. */
  .gloss.gloss-solo { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; }
}
.gloss:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── label floor (generated by tools/label-floor.mjs; do not edit by hand) ── */
@media (pointer: coarse) {
  .hud-campaign b,
  .pc,
  .pc .hp,
  .rail-foot small,
  h3,
  .lede,
  .btn-sm,
  .stat-n,
  .stack-amt,
  .stack-src,
  .stack-cite,
  .stack-why,
  .unproven-note,
  .abil .mod,
  .find,
  .lens button .n,
  .hit .n,
  .hit .src,
  .hit .auth,
  .hit .note,
  .cite-line,
  .kv dd,
  .roll .expr,
  .roll .parts,
  .roll .why,
  .rb-die .rb-val, .rb-total .rb-val,
  .rb-mod .rb-val,
  .rb-label,
  .dicebar input,
  .dicebar button,
  .tab-lede,
  .tab-name,
  .tab-cite,
  .tab-rank,
  .tab-why,
  .tab-empty,
  .verdict,
  #gloss-tip,
  .field-err { font-size: 1rem; }
  .tier-model { font-size: max(1rem, .85em); }
}
@media (pointer: coarse) and (pointer: fine) {
  button, input, select, textarea { font-size: 1rem; }
}
@media (pointer: coarse) {
  .mark small,
  .hud-campaign span,
  .mod .name,
  .rail-foot .live,
  .kicker,
  .rule,
  .stat-k,
  .stack-type,
  .unproven-note b,
  .abil .nm,
  .lens button,
  .creature-plate figcaption,
  .kv dt,
  .roll .flag,
  .stat-sub,
  .sheet-chips .stat-k, .sheet-band .stat-k,
  .sheet-chips .stat-n, .sheet-band .stat-n,
  .sheet-tab,
  .tab-h,
  .pill,
  .verdict b,
  .req-tag { font-size: 1rem; letter-spacing: .05em; }
  .btn { font-size: 1rem; letter-spacing: .05em; white-space: normal; overflow-wrap: break-word; }
}
@media (pointer: fine) {
  .pc .hp,
  .rail-foot small,
  .btn-sm,
  .stat-n,
  .stack-amt,
  .stack-src,
  .stack-cite,
  .stack-why,
  .unproven-note,
  .abil .mod,
  .lens button .n,
  .hit .src,
  .hit .auth,
  .hit .note,
  .cite-line,
  .kv dd,
  .roll .expr,
  .roll .parts,
  .roll .why,
  .rb-mod .rb-val,
  .rb-label,
  .dicebar input,
  .dicebar button,
  .tab-cite,
  .tab-rank,
  .tab-why,
  .verdict { font-size: .875rem; }
  .tier-model { font-size: max(.875rem, .85em); }
}
@media (pointer: fine) {
  .mark small,
  .hud-campaign span,
  .mod .name,
  .rail-foot .live,
  .kicker,
  .rule,
  .stat-k,
  .stack-type,
  .unproven-note b,
  .abil .nm,
  .lens button,
  .creature-plate figcaption,
  .kv dt,
  .roll .flag,
  .stat-sub,
  .sheet-chips .stat-k, .sheet-band .stat-k,
  .sheet-chips .stat-n, .sheet-band .stat-n,
  .sheet-tab,
  .tab-h,
  .pill,
  .verdict b,
  .req-tag { font-size: .875rem; letter-spacing: .05em; }
  .btn { font-size: .875rem; letter-spacing: .05em; white-space: normal; overflow-wrap: break-word; }
}
/* ── end label floor ── */
