/*
 * components.css — ShadowDark DM Companion
 * Buttons, inputs, cards, panels, stat blocks, dice display, toasts.
 * All colors reference tokens from base.css.
 * No border-radius anywhere — enforced by global reset in base.css.
 */

/* ============================================================
   Module tile cards (home screen)
   ============================================================ */

.module-tile {
  /* Reset — it's a button element for semantics */
  appearance: none;
  background: none;
  border: none;
  text-align: left;
  font: inherit;
  cursor: pointer;

  /* Surface treatment per KDL 2026-06-27 */
  display: block;
  width: 100%;
  background: radial-gradient(ellipse at 30% 20%, #252018 0%, #1e1a16 70%);
  border: 1px solid var(--color-border);

  /* Cornflower Blue top accent stripe */
  border-top: 3px solid var(--color-accent);

  padding: var(--space-lg);
  position: relative;
  overflow: hidden;

  /* Transition for hover state — bottom stripe */
  transition:
    background-color var(--transition-fast),
    border-left-color var(--transition-fast);
}

/* Hover: surface lightens, left accent appears (per KDL 2026-06-27's spec) */
.module-tile:hover {
  background: radial-gradient(ellipse at 30% 20%, #2e2820 0%, #282320 70%);
  border-left: 3px solid var(--color-accent);
  border-bottom: 1px solid var(--color-accent);
}

.module-tile:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.module-tile__name {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  font-weight: 600;
  color: var(--color-heading);
  letter-spacing: 0.03em;
  line-height: var(--lh-heading);
  margin-bottom: var(--space-sm);
  display: block;
}

.module-tile__desc {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
  line-height: 1.5;
  margin: 0;
}

/* ============================================================
   Buttons
   ============================================================ */

/* Base shared properties */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: 0.6rem 1.4rem;
  /* Cinzel: 0.8rem uppercase is below the blackletter readability threshold */
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1;
  /* Buttons are single-line: a longer label (e.g. "Add another stock item") must
     not wrap to two lines, which made it taller than its row-mates (the shop edit
     stock row) even with identical padding. One shared rule for every .btn so
     sibling buttons in a row always share the same box height (KDL 2026-06-27). */
  white-space: nowrap;
  cursor: pointer;
  border: 1px solid transparent;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast),
    color var(--transition-fast);
}

/* Themed FALLBACK so a *bare* `.btn` — one carrying NO `btn--*` colour/size
   variant at all — can never render as browser-default white again. This is the
   single central guard that stops the recurring white-button bug (Ken, 2026-06-11).
   Scoped with [class*="btn--"] so it touches ONLY truly variant-less buttons and
   never interferes with .btn--secondary / .btn--item-light / .btn--attack / etc.,
   which combine .btn with their own colour rules. Mirrors the .btn--secondary
   resting look: black-backed, accent outline + accent label, accent-fill on hover. */
.btn:not([class*="btn--"]) {
  background-color: var(--color-bg);
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.btn:not([class*="btn--"]):hover {
  background-color: var(--color-accent);
  color: var(--ink-on-accent, var(--color-text));
  border-color: var(--color-accent);
}

.btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Primary — accent-FILLED. Text uses the auto-contrast ink (KDL 2026-06-27 #1) so the
   label stays >=4.5:1 readable on any of the 11 presets AND a custom accent;
   var(--color-text) is the no-JS fallback (today's white). */
.btn--primary {
  background-color: var(--color-accent);
  color: var(--ink-on-accent, var(--color-text));
  border-color: var(--color-accent);
}

.btn--primary:hover {
  background-color: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}

/* Secondary — black-backed accent-outlined button. Theme-aware per Ken's spec:
   REST = darkest theme token (Dungeon Floor, reads black) + accent text + accent
   border; HOVER/FOCUS = accent fill with auto-contrast ink. This is the ONE
   secondary standard used app-wide (Unshare, Cancel, Back, etc.). Using
   --color-bg (not transparent) guarantees a black read regardless of the parent
   surface a secondary button sits on. */
.btn--secondary {
  background-color: var(--color-bg);
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.btn--secondary:hover,
.btn--secondary:focus-visible {
  background-color: var(--color-accent);
  /* Auto-contrast ink (theme.js paint()): >=4.5:1 on any of the 11 presets and a
     custom accent. var(--color-text) is the no-JS fallback, same pattern as .btn--primary. */
  color: var(--ink-on-accent, var(--color-text));
  border-color: var(--color-accent);
}

.btn--secondary:active {
  background-color: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}

/* The ONLY place a secondary button falls back to the muted Mortar Line look —
   the old resting style becomes the disabled state, which is semantically correct. */
.btn--secondary[disabled],
.btn--secondary[aria-disabled="true"] {
  color: var(--color-text-muted);
  border-color: var(--color-border);
  background-color: var(--color-bg);
  cursor: not-allowed;
}

/* Danger — transparent with Void Red border */
.btn--danger {
  background-color: transparent;
  color: var(--color-danger);
  border-color: var(--color-danger);
}

.btn--danger:hover {
  background-color: var(--color-danger-bg);
}

/* ============================================================
   List row / selectable card — canonical interactive row
   ============================================================ */

/* Canonical interactive list row / selectable card. Three states shared app-wide:
   rest = Mortar Line border on surface; hover/focus = accent border;
   .is-selected = accent border held + faint surface lift. Matches the
   enc-roster-row / ancestry-card / spell-item idiom already in use. */
.list-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xs);
  width: 100%;
  text-align: left;
  padding: var(--space-md);
  background-color: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  cursor: pointer;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast);
}

.list-row:hover,
.list-row:focus-visible {
  background-color: var(--color-surface-hover);
  border-color: var(--color-accent);
}

.list-row.is-selected {
  border-color: var(--color-accent);
  background-color: var(--color-surface-hover);
}

/* ============================================================
   Input fields and selects
   ============================================================ */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.field__label {
  /* Cinzel: form labels are small-caps UI chrome */
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
}

.field__optional {
  font-size: 0.78rem;
  opacity: 0.55;
  letter-spacing: 0;
  text-transform: none;
}

.char-bg-desc {
  font-size: 0.82rem;
  color: var(--color-text-muted);
  font-style: italic;
  margin-top: 0.3rem;
  min-height: 1.1em;
}

.field__input,
.field__textarea {
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: var(--lh-body);
  padding: 0.5rem 0.75rem;
  width: 100%;
  transition: border-color var(--transition-fast);
}

.field__input::placeholder,
.field__textarea::placeholder {
  color: rgba(255, 255, 255, 0.4);  /* more dimmed than --color-text-muted; clearly placeholder */
  font-style: italic;
}

.field__input:focus,
.field__textarea:focus {
  border-color: var(--color-accent);
  outline: none;
}

.field__textarea {
  resize: vertical;
  min-height: 6rem;
}

/* Select — Cinzel for short label options (ancestry, class, etc.) */
.field__select {
  appearance: none;
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: var(--font-heading-ui);
  font-size: 0.9rem;
  letter-spacing: var(--ls-cinzel-caps);
  padding: 0.5rem 2rem 0.5rem 0.75rem;
  width: 100%;
  cursor: pointer;
  transition: border-color var(--transition-fast);

  /* Custom arrow in dimmed white using a CSS data URI triangle */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M0 0L6 8L12 0' fill='%23ffffff' opacity='0.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 10px 7px;
}

.field__select:focus {
  border-color: var(--color-accent);
  outline: none;
}

.field__select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ============================================================
   Panel — generic content container
   Used for oracle answers, parchment-within-stone sections
   ============================================================ */

.panel {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-md) var(--space-lg);
}

/* Parchment panel — oracle answer, active content output */
.panel--parchment {
  background-color: var(--color-oracle-bg);
  border-left: 3px solid var(--color-accent);
  border-top: 1px solid var(--color-border);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

/* ============================================================
   Oracle response panel
   ============================================================ */

.oracle-panel {
  background-color: var(--color-oracle-bg);
  border-left: 3px solid var(--color-accent);
  border-top: 1px solid var(--color-border);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-md) var(--space-lg);

  opacity: 0;
  transition: opacity var(--transition-reveal);
}

.oracle-panel.is-visible {
  opacity: 1;
}

.oracle-panel__label {
  /* Cinzel: 0.75rem is below the blackletter readability threshold */
  font-family: var(--font-heading-ui);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  display: block;
  margin-bottom: var(--space-sm);
}

/* Loading state — "Searching the rulebooks..." (no spinner per KDL 2026-06-27's rule 11) */
.oracle-panel__loading {
  font-family: var(--font-body);
  font-style: italic;
  color: var(--color-text-muted);
}

/* ============================================================
   Dice roll display
   ============================================================ */

.dice-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
}

.dice-display__container {
  width: 120px;
  text-align: center;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-md);
}

.dice-display__number {
  font-family: var(--font-mono);
  font-size: 4rem;
  line-height: 1;
  color: var(--color-text);
  display: block;
  min-height: 1em;
}

/* During roll: Cornflower Blue color + flicker animation */
.dice-display__number.is-rolling {
  color: var(--color-accent);
  animation: dice-flicker 0.08s steps(1) infinite;
}

@keyframes dice-flicker {
  0%   { opacity: 1; }
  48%  { opacity: 1; }
  50%  { opacity: 0.6; }
  52%  { opacity: 1; }
  100% { opacity: 1; }
}

.dice-display__label {
  /* Cinzel: small-caps die label is UI chrome at --text-sm */
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  font-variant: small-caps;
  color: var(--color-text-muted);
  letter-spacing: var(--ls-cinzel-caps);
}

/* ============================================================
   Stat block (encounter generator)
   ============================================================ */

.stat-block {
  background-color: var(--color-surface);
  border-left: 3px solid var(--color-accent);
  border-top: 1px solid var(--color-border);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-md) var(--space-lg);
}

/* Multiple stat blocks: double-rule divider between them */
.stat-block + .stat-block {
  border-top: none;
  position: relative;
  margin-top: var(--space-sm);
}

.stat-block + .stat-block::before {
  content: '';
  display: block;
  border-top: 1px solid var(--color-border);
  box-shadow: 0 2px 0 0 var(--color-border);
  margin-bottom: var(--space-md);
}

.stat-block__name {
  font-family: var(--font-heading);
  font-size: var(--text-h4);
  font-weight: 600;
  color: var(--color-heading);
  letter-spacing: 0.05em;
  margin-bottom: var(--space-sm);
}

/* Stats row: AC | HP | ATK | Speed in Share Tech Mono */
.stat-block__row {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  line-height: var(--lh-mono);
  color: var(--color-text);
  display: flex;
  gap: 0;
  flex-wrap: wrap;
  margin-bottom: var(--space-sm);
}

.stat-block__stat {
  display: flex;
  align-items: baseline;
  gap: 0.3em;
}

/* Pipe separator between stats */
.stat-block__stat + .stat-block__stat::before {
  content: '|';
  color: var(--color-border);
  margin: 0 0.5em;
}

.stat-block__stat-label {
  color: var(--color-text-muted);
  font-size: 0.8rem;
}

.stat-block__stat-value {
  color: var(--color-text);
  font-weight: 400;
}

.stat-block__abilities {
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  line-height: var(--lh-body);
  margin-top: var(--space-sm);
}

.stat-block__ability-label {
  color: var(--color-text-muted);
  font-style: italic;
}

/* ============================================================
   Oracle module — layout, controls, offline banner, history,
   solo tools panel, and form.
   These rules extend the existing .oracle-panel/* rules above.
   ============================================================ */

/* Module header row: title + control buttons */
.oracle-module__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

/* ============================================================
   Rules-as-written search results — ranked excerpt cards inside
   the parchment .oracle-panel. Each result: source citation line,
   clamped excerpt, expand affordance when the clamp cuts text.
   ============================================================ */

/* Result count line — "3 passages found" */
.oracle-results__meta {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-sm);
}

/* Empty state / inline error message */
.oracle-results__message {
  font-family: var(--font-body);
  font-style: italic;
  color: var(--color-text-muted);
  margin: 0;
}

.oracle-results__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Each result is a discrete card so a scan reads section -> context top-down.
   A subtle inset border + faint surface lifts it off the dark panel without
   competing with the parchment consultation chrome above. */
.oracle-result {
  position: relative;            /* anchor for the rule card's stretched ::after */
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-sm, 3px);
  background-color: color-mix(in srgb, var(--color-text) 3%, transparent);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

/* Every rule card is now clickable (it opens the full-passage modal in place),
   so EVERY rule card gets the accent left-rule + hover affordance — including
   non-Lore books, which used to render as dead, non-interactive cards. The
   stretched title button (below) makes the whole card a single click target. */
.oracle-result--rule {
  cursor: pointer;
}

.oracle-result--rule:hover {
  border-left-color: var(--color-accent);
  background-color: color-mix(in srgb, var(--color-accent) 6%, transparent);
}

/* Breadcrumb — the SECTION (parentTitle) a finer sub-topic lives in, shown as a
   subtle eyebrow above the title, e.g. "Casting Spells ›". Muted Cinzel caps so
   the finer title stays the headline; the › connector hints containment. */
.oracle-result__crumb {
  display: flex;
  align-items: baseline;
  gap: 0.25em;
  margin: 0 0 2px;
  font-family: var(--font-heading-ui);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  line-height: 1.2;
}

.oracle-result__crumb-sep {
  color: var(--color-text-muted);
  opacity: 0.7;
}

/* Eyebrow — de-emphasized source/page line above the title. */
.oracle-result__source {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: 0 0 2px;
}

.oracle-result__book {
  /* Cinzel: source label is UI chrome, small-caps feel */
  font-family: var(--font-heading-ui);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
}

.oracle-result__page {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-text-muted);
}

/* Title — the section heading, the card's headline. Cinzel caps, accent so it
   reads as the structural anchor of the card. It is an <h3> wrapping a real
   <button> (the open control) — the heading stays a navigable landmark for
   assistive tech while the button carries the click/keyboard action. */
.oracle-result__title {
  font-family: var(--font-heading-ui);
  font-size: 1.05rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-accent);
  line-height: var(--lh-tight, 1.2);
  margin: 0 0 var(--space-xs);
}

/* The open control — a real button styled as the heading. Its ::after stretches
   over the whole position:relative card so a mouse click anywhere opens the
   passage modal; keyboard users activate it directly (Enter/Space). Left-aligned,
   underline-on-hover so it reads as actionable, full keyboard focus ring. */
.oracle-result__title-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  letter-spacing: inherit;
  color: var(--color-accent);
}

.oracle-result__title-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.oracle-result__title-btn:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Focus ring on the whole card (the button overlays it), so keyboard focus is
   unmistakable rather than a thin ring around just the heading text. */
.oracle-result__title-btn:focus-visible {
  outline: none;
}

.oracle-result--rule:focus-within {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* The inline snippet expand control sits ABOVE the stretched card overlay so it
   stays independently clickable (peek without opening the modal). */
.oracle-result__expand {
  position: relative;
  z-index: 1;
}

/* Section context — the chunk text rendered as readable section content. */
.oracle-result__text {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--color-text);
  line-height: var(--lh-body);
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Clamped preview — expand button removes this class */
.oracle-result__text.is-clamped {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  line-clamp: 6;
  overflow: hidden;
}

/* Searched-term highlight — accent-tinted marker on the dark oracle card.
   Tinted background keeps the term legible (text stays --color-text) while a
   solid accent underline makes the hit unmistakable. Works inside the 6-line
   clamp and the expanded state; inside the accent title it inverts to
   dark-on-accent so the mark reads against the accent-colored title. */
.oracle-result__hl {
  background-color: color-mix(in srgb, var(--color-accent) 26%, transparent);
  color: var(--color-text);
  border-radius: 2px;
  padding: 0 0.1em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  box-shadow: inset 0 -2px 0 0 var(--color-accent);
}

/* Inside the accent-colored title, paint the mark solid so it stays readable. */
.oracle-result__title .oracle-result__hl {
  background-color: var(--color-accent);
  color: var(--color-bg);
  box-shadow: none;
}

/* ============================================================
   Rule passage modal (oracle.js buildRulePassageCard)
   Opened in place from a rule result — the full RAW passage as readable prose
   inside the shared .action-modal / .enc-sheet chrome. The section heading is
   the dialog title; this is the body. Styled for PROSE (not a stat grid).
   ============================================================ */

/* De-emphasized source/page sub-label under the dialog title. */
.oracle-passage__source {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: 0 0 var(--space-sm);
}

.oracle-passage__book {
  font-family: var(--font-heading-ui);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
}

.oracle-passage__page {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-text-muted);
}

/* Passage prose — comfortable measure + spacing so a long RAW section reads
   like a rulebook page, not a cramped card snippet. */
.oracle-passage__body {
  margin: 0;
}

.oracle-passage__p {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: var(--lh-body);
  color: var(--color-text);
  margin: 0 0 var(--space-sm);
  white-space: pre-wrap;
  word-break: break-word;
}

.oracle-passage__p:last-child {
  margin-bottom: 0;
}

/* Highlighted terms inside the passage — same accent marker as the result cards
   so the searched words are unmistakable in the full text too. */
.oracle-passage__p .oracle-result__hl {
  background-color: color-mix(in srgb, var(--color-accent) 26%, transparent);
  color: var(--color-text);
  border-radius: 2px;
  padding: 0 0.1em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  box-shadow: inset 0 -2px 0 0 var(--color-accent);
}

/* Secondary footer action — the explicit "read the whole section in Lore"
   deep-link (Lore-backed results only). A real secondary button, set apart
   above with a hairline divider so it reads as a distinct, optional path. */
.oracle-passage__footer {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
}

.oracle-passage__lore-link {
  width: 100%;
  /* This is now a real anchor (opens Lore in a new tab so the search results
     survive in the original tab). Strip the anchor's default underline so it
     reads as the .btn--secondary button it is styled as; .btn--secondary already
     supplies the colour. box-sizing keeps the full-width padding inside 100%. */
  box-sizing: border-box;
  text-decoration: none;
}

/* Breadcrumb in the passage modal — "parentTitle › title". Subtle muted Cinzel
   caps under the dialog title; the current topic (here) is slightly less muted so
   the trail reads parent -> here. Sits above the source/page sub-label. */
.oracle-passage__crumb {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.3em;
  margin: 0 0 2px;
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  line-height: 1.25;
}

.oracle-passage__crumb-sep {
  opacity: 0.7;
}

.oracle-passage__crumb-here {
  color: var(--color-text);
}

/* Roll tables embedded in a rule passage. Set apart above with a hairline divider
   so the table(s) read as a distinct, rollable affordance under the prose. When a
   topic is table-led (little prose), this still reads clearly as the main payload. */
.oracle-passage__tables {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.oracle-passage__table-loading {
  margin: 0;
  font-style: italic;
}

/* The interactive embed reuses the Fortunes .rt-roll component (tables.css), so
   it inherits the grid + Roll button chrome. We only constrain its width here. */
.oracle-passage__table-embed {
  margin: 0;
}

/* Lore-open fallback button — full width so it reads as a clear single action. */
.oracle-passage__table-open {
  width: 100%;
}

/* Expand affordance — text link, accent color */
.oracle-result__expand {
  background: none;
  border: none;
  padding: var(--space-xs) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--color-accent);
  cursor: pointer;
}

.oracle-result__expand:hover {
  text-decoration: underline;
}

.oracle-result__expand:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ============================================================
   Universal grouped results (Phase B).
   #oracle-results-list is repurposed as the group wrapper: each child <li>
   .oracle-group is one labelled category (Rules, Spells, Monsters, Characters,
   ...) holding its own card list. Groups arrive from the backend already in a
   fixed display order with empty groups omitted; we render them as given.
   ============================================================ */

/* Space the groups apart so each category reads as a distinct band. */
.oracle-group {
  margin: 0;
  padding: 0;
}

.oracle-group + .oracle-group {
  margin-top: var(--space-lg);
}

/* Group heading — label + count. Cinzel caps for the label so it reads as a
   structural section header; the count is a quiet pill to its right. A faint
   rule under the row separates the header from its cards without heavy chrome. */
.oracle-group__heading {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin: 0 0 var(--space-sm);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--color-border);
}

.oracle-group__label {
  font-family: var(--font-heading-ui);
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-accent);
}

.oracle-group__count {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-text-muted);
  background-color: color-mix(in srgb, var(--color-text) 8%, transparent);
  border-radius: var(--radius-sm, 3px);
  padding: 0 0.4em;
  line-height: 1.6;
}

.oracle-group__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* ----------------------------------------------------------
   Compact result card — every non-rule kind (spell, item, magic item, monster,
   table, character, custom item, map, world map, encounter). The whole card is
   a single <button> so a click anywhere navigates and it is one Tab stop. Reads
   as a quieter sibling of the rule card: same surface + left rule, but the title
   sits inline with the snippet rather than as a Cinzel headline.
   ---------------------------------------------------------- */
.oracle-result--compact {
  padding: 0;
}

.oracle-result__hit {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  /* Match the .oracle-result card padding so compact + rule cards align. */
  padding: var(--space-sm) var(--space-md);
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  border-radius: inherit;
}

.oracle-result__hit:hover {
  /* The parent .oracle-result already paints its border; tint the surface on
     hover to signal the whole card is the control. */
  background-color: color-mix(in srgb, var(--color-accent) 6%, transparent);
}

.oracle-result--compact:has(.oracle-result__hit:hover) {
  border-left-color: var(--color-accent);
}

.oracle-result__hit:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: inherit;
}

.oracle-result__hit-title {
  display: block;
  font-family: var(--font-heading-ui);
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-accent);
  line-height: var(--lh-tight, 1.2);
}

.oracle-result__hit-snippet {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  margin-top: 2px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text);
  line-height: var(--lh-body);
}

/* Highlight inside the compact title inverts to dark-on-accent like the rule
   card title; inside the snippet it uses the standard tinted marker. */
.oracle-result__hit-title .oracle-result__hl {
  background-color: var(--color-accent);
  color: var(--color-bg);
  box-shadow: none;
}

/* ============================================================
   Oracle query form
   ============================================================ */

/* ============================================================
   Consultation family — Ask the Oracle / Consult Fate /
   Something Happens. Three sibling blocks sharing one chrome so
   they read as a single consultation group. Stacked vertically.
   ============================================================ */

.oracle-consults {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  /* No trailing divider/padding — Conjurings sits snugly below; the .gen-section
     header provides its own separation. */
  margin-bottom: var(--space-lg);
}

/* Consult Fate + Something Happens sit side-by-side as a 2-up row beneath
   Ask the Oracle. Equal-width columns, equal height (grid stretch), reusing the
   shared .oracle-consult card chrome. Stacks to one column on narrow viewports. */
.oracle-consult-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  align-items: stretch;
}

@media (max-width: 700px) {
  .oracle-consult-pair {
    grid-template-columns: 1fr;
  }
}

/* Shared block chrome — surface card with accent top rule */
.oracle-consult {
  background: radial-gradient(ellipse at 30% 20%, #252018 0%, #1e1a16 70%);
  border: 1px solid var(--color-border);
  border-top: 3px solid var(--color-accent);
  padding: var(--space-md) var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.oracle-consult__title {
  font-family: var(--font-heading);
  font-size: var(--text-h4);
  font-weight: 600;
  color: var(--color-heading);
  letter-spacing: 0.05em;
  margin: 0;
}

/* Tighten the gap between a field and its preceding title/hint */
.oracle-consult__field {
  margin: 0;
}

.oracle-consult__hint {
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-style: italic;
  color: var(--color-text-muted);
  margin: 0;
}

.oracle-consult__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

/* Result panel — parchment, matches the oracle response panel.
   Empty until first roll; the inner .solo-tool__result provides the
   parchment chrome, so this wrapper only adds spacing when populated. */
.oracle-consult__result:not(:empty) {
  margin-top: 0;
}

.oracle-qa {
  /* Ask the Oracle is a consult block; its internal pieces (form,
     results panel) stack with the shared block gap. */
  gap: var(--space-md);
}

/* Results panel sits below the form (search affordance: type, then read). */
#oracle-response-panel {
  margin-bottom: 0;
}

.oracle-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Auto-expanding textarea — rows="1" default, JS expands to max 3 */
.oracle-form__textarea {
  resize: none;
  overflow: hidden;
  min-height: unset;
  line-height: var(--lh-body);
  transition:
    border-color var(--transition-fast),
    height 80ms ease-out;
}

.oracle-form__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.oracle-form__hint {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
}

/* ============================================================
   SoloDark tool result panels
   The result-panel renderers still emit .solo-tool__result* markup
   for Consult Fate / Something Happens / Name a Dungeon. The old
   .solo-tools container/card rules were retired when those tools
   moved into the .oracle-consult family and the card grid.
   ============================================================ */

/* Tool result panel — parchment feel, persists between rolls */
.solo-tool__result {
  /* Inherits .panel--parchment border treatment */
  background-color: var(--color-oracle-bg);
  border-left: 3px solid var(--color-accent);
  border-top: 1px solid var(--color-border);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-sm) var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* Roll number — Share Tech Mono, Cornflower Blue when freshly rolled */
.solo-tool__result-roll {
  font-family: var(--font-mono);
  font-size: 1.8rem;
  line-height: 1;
  color: var(--color-accent);
}

/* Die label below the number — small Cinzel small-caps */
.solo-tool__result-label {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  font-variant: small-caps;
  color: var(--color-text-muted);
  letter-spacing: var(--ls-cinzel-caps);
}

/* Fate/dungeon/hazard result text */
.solo-tool__result-text {
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  line-height: var(--lh-body);
  margin: 0;
  font-style: italic;
}

/* ---- Consult Fate: odds-based yes/no -------------------------------------
   Likelihood segmented control echoes the EG cast-outcome radiogroup
   (.enc-cast-outcome), sitting inline with the Roll button. No new tokens. */
.fate-likelihood {
  border: none;
  margin: 0;
  padding: 0;
}

.fate-likelihood__row {
  flex-wrap: wrap;
}

.fate-likelihood__opts {
  display: flex;
  gap: var(--space-xs);
}

/* Both-dice strip (advantage / disadvantage) — show the pair, dim the dropped
   die and brand-highlight the kept one. */
.fate-result__dice {
  display: flex;
  gap: var(--space-xs);
  align-items: center;
}

.fate-result__die {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  line-height: 1;
  min-width: 2ch;
  text-align: center;
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  opacity: 0.6;
}

.fate-result__die--kept {
  color: var(--ink-on-accent, var(--color-bg));
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  opacity: 1;
  font-weight: 700;
}

/* Inline error for tool failures */
.solo-tool__error {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
  margin: 0;
}

/* ============================================================
   Oracle Generators — composite roll-table clusters.
   Lives below the consultation blocks on the Oracle page. Reuses the
   Tables two-pane shell (.rt-library / .rt-menu / .rt-library__display,
   defined in tables.css) for the picker + display column, plus .btn and
   the .solo-tool__result chrome for the summary. New presentation is under
   the gen-* namespace. No new color tokens — accents reuse --color-accent.
   ============================================================ */

/* The Generators area sits snugly below the consultation group. The
   .oracle-consults group provides a small margin-bottom (no divider/padding),
   so no extra top margin is needed here. */
.gen-section {
  margin-top: 0;
}

.gen-section__header {
  /* Full-width section banner: the heading + intro span the entire Oracle
     column (matching the two-pane shell below) and the accent rule carries the
     eye across the page so there is no empty band beside the short heading. */
  margin-bottom: var(--space-md);
  width: 100%;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-border);
}

.gen-section__title {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  font-weight: 600;
  color: var(--color-heading);
  letter-spacing: 0.04em;
  margin: 0 0 var(--space-xs);
}

.gen-section__intro {
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-style: italic;
  margin: 0;
  /* Span the full header width to fill the right-side whitespace rather than
     stopping at a narrow measure. */
  max-width: none;
}

/* ---- Result sheet ---- */

.gen-sheet {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* Assembled one-liner for combine generators — prominent, parchment chrome
   (reuses .solo-tool__result for the border treatment). */
.gen-sheet__summary {
  margin-bottom: var(--space-md);
}

.gen-sheet__summary-label {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.gen-sheet__summary-text {
  font-family: var(--font-body);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--color-heading);
  line-height: var(--lh-body);
  margin: 0;
}

/* Enriched commoner fields (contract #1, 2026-06-28) — a compact labelled chip row
   above the rolled components on the "Just a Commoner" sheet, mirroring the enriched
   set the roster Surprise Me presents. Wraps responsively; each item is a label over
   a value. Uses existing tokens only (no new scale). */
.gen-enriched {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-border);
  background-color: var(--color-surface);
}

.gen-enriched__item {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.gen-enriched__label {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.gen-enriched__value {
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

/* Full building section preview (Ken 2026-06-29 follow-up) — the rich sections a
   conjured tavern/shop/house carries beyond its name-spine rows (food/drinks/people/
   stock/goods/description), shown read-only above the component rows so the user sees
   everything that will be saved. Reuses existing tokens only. */
.gen-building {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0 0 var(--space-md);
}

.gen-building-section {
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  background-color: var(--color-surface);
  padding: var(--space-sm) var(--space-md);
}

.gen-building-section__title {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.gen-building-section__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.gen-building-section__list li,
.gen-building-section__text {
  font-family: var(--font-body);
  color: var(--color-text);
  overflow-wrap: anywhere;
  margin: 0;
}

/* A component row — label + subtle die/roll meta, the result, and reroll. */
.gen-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  background-color: var(--color-surface);
  padding: var(--space-sm) var(--space-md);
}

.gen-row__body {
  min-width: 0;
  flex: 1;
}

.gen-row__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.gen-row__label {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Subtle die/roll provenance, e.g. "d20=14". */
.gen-row__meta {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-text-muted);
  opacity: 0.8;
}

.gen-row__result {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--color-text);
  line-height: var(--lh-body);
  margin: 0.15rem 0 0;
}

/* SET-AT-CREATION identity input (Ken #4) — the demeanor/appearance/quirk lines on the
   person conjure preview become typed fields the GM sets before saving. Same field
   idiom as .field__input (bg, border, focus accent) but sized to sit in the result
   slot (full-width, compact) so the row still reads as "label + value + reroll". */
.gen-row__edit {
  display: block;
  width: 100%;
  margin: 0.15rem 0 0;
  padding: 0.35rem 0.6rem;
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: var(--lh-body);
  transition: border-color var(--transition-fast);
}

.gen-row__edit:focus {
  border-color: var(--color-accent);
  outline: none;
}

/* ---- Umbrella cluster (Magic Item build.*) — grouped + indented ---- */

.gen-cluster {
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  background-color: var(--color-surface);
  padding: var(--space-sm) var(--space-md);
}

.gen-cluster__head {
  margin-bottom: var(--space-xs);
}

.gen-cluster__label {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-accent);
}

.gen-cluster__list {
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--space-md);
  border-left: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* Sub-row inside a cluster — lighter chrome than a top-level row. */
.gen-subrow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
}

.gen-subrow + .gen-subrow {
  border-top: 1px solid var(--color-border);
}

/* ---- Per-row reroll control — quiet until hover/focus, then accents ---- */

.gen-row__reroll {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  border: 1px solid var(--color-border);
  background-color: var(--color-bg);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, background-color 0.12s ease;
}

.gen-row__reroll:hover,
.gen-row__reroll:focus-visible {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.gen-row__reroll:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.gen-row__reroll[disabled] {
  opacity: 0.5;
  cursor: default;
}

.gen-row__reroll-icon {
  width: 0.95rem;
  height: 0.95rem;
}

/* ---- Sheet header actions (Create NPC + Reroll All) sit beside the title ---- */
.gen-sheet__header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* ---- Reroll-all sits in the sheet header beside the title ---- */
.gen-sheet__reroll-all {
  flex: none;
}

/* Save NPC (npc generator only) — primary action, now anchored at the BOTTOM of
   the sheet in its own footer row (was a header action beside Reroll All).
   The optional Gender field (person conjure, Ken batch-9 #4) sits at the LEFT of
   the same row, baseline-aligned with the Save Commoner button on the right. The
   row wraps on narrow widths so the field drops above the button rather than
   crushing it. */
.gen-sheet__footer-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: flex-end;
  gap: var(--space-md);
  margin-top: var(--space-md);
}
.gen-sheet__create-npc {
  flex: none;
}

/* Gender control on the person conjure sheet. Reuses the shared .field idiom
   (label + .field__select) so it matches every other select in the app — custom
   OSR arrow + Cinzel options — instead of the native select chrome; only layout is
   namespaced here. mr-auto pushes Save Commoner to the right edge. */
.gen-sheet__gender {
  margin-right: auto;
  margin-bottom: 0;
}
.gen-sheet__gender-select {
  width: auto;
  min-width: 12rem;
}

/* ---- Inline error state ---- */
.gen-sheet__error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-md);
  background-color: var(--color-danger-bg);
  border-left: 3px solid var(--color-danger);
  color: var(--color-text);
  font-family: var(--font-body);
  padding: var(--space-md);
}

.gen-sheet__error p {
  margin: 0;
  font-style: italic;
}

/* ---- Flourish — brief reveal/pulse on a single rerolled row (and any
   cascade rows). Roll-all lands instant; this fires only on single reroll.
   prefers-reduced-motion disables it (handled in JS + the guard below). ---- */
.gen-row.is-flourish,
.gen-subrow.is-flourish {
  animation: gen-reveal 420ms ease-out 1;
}

@keyframes gen-reveal {
  0% {
    background-color: rgba(100, 149, 237, 0.30);
    transform: translateY(2px);
    opacity: 0.4;
  }
  100% {
    background-color: var(--color-surface);
    transform: none;
    opacity: 1;
  }
}

/* Sub-rows have no surface fill of their own, so settle to transparent. */
.gen-subrow.is-flourish {
  animation-name: gen-reveal-sub;
}

@keyframes gen-reveal-sub {
  0% {
    background-color: rgba(100, 149, 237, 0.30);
    transform: translateY(2px);
    opacity: 0.4;
  }
  100% {
    background-color: transparent;
    transform: none;
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gen-row.is-flourish,
  .gen-subrow.is-flourish {
    animation: none;
  }
}

/* ============================================================
   Toast notifications
   ============================================================ */

.toast-region {
  position: fixed;
  bottom: var(--space-lg);
  right: var(--space-lg);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  pointer-events: none;
}

.toast {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  padding: var(--space-sm) var(--space-md);
  max-width: 280px;
  pointer-events: all;
  position: relative;

  /* Enter animation — slide up + fade in */
  animation: toast-enter 150ms ease-out forwards;
}

.toast--error {
  border-left-color: var(--color-danger);
}

@keyframes toast-enter {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toast.is-exiting {
  animation: toast-exit 200ms ease-in forwards;
}

@keyframes toast-exit {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.toast__message {
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  line-height: 1.4;
  margin: 0;
  padding-right: var(--space-lg);
}

.toast__sub {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: var(--space-xs) 0 0;
}

/* Dismiss button — error toasts only, persists until dismissed */
.toast__dismiss {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  background: none;
  border: none;
  color: var(--color-text-muted);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1;
  padding: 0.2rem 0.3rem;
  transition: color var(--transition-fast);
}

.toast__dismiss:hover {
  color: var(--color-text);
}

.toast__dismiss:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Inline action button (e.g. invite "Accept"). Action toasts persist (no auto-
   dismiss) and reserve room on the right for the Dismiss control. */
.toast--action .toast__message {
  padding-right: var(--space-lg);
}

.toast__action {
  margin-top: var(--space-sm);
  background-color: transparent;
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.2;
  padding: 0.3rem 0.7rem;
  cursor: pointer;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.toast__action:hover {
  background-color: var(--color-accent);
  color: var(--color-bg);
}

.toast__action:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ============================================================
   Shared: tooltip-on-hover + inline refresh button
   Used across the app (character sheet talents/abilities/spells/
   equipment). Originally defined with the retired DM-table cards;
   kept here because other modules still rely on them.
   ============================================================ */

/* Inline refresh / reroll button (circular arrow) — no background,
   accent on hover. Used for talent rerolls on the character sheet. */
.dm-refresh-btn {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  width: 16px;
  height: 16px;
  line-height: 1;
  font-size: 1rem;
  color: var(--color-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--transition-fast);
}

.dm-refresh-btn:hover {
  color: var(--color-accent);
}

.dm-refresh-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Tooltip host: reveals data-description as a popover.
   - Pointer/hover devices (desktop): reveal on :hover (unchanged behavior).
   - Touch devices (no hover): app.js promotes the host to a focusable
     role="button" and toggles .is-open on tap/Enter/Space; outside-tap and
     Esc dismiss. Both paths drive the SAME ::after popover.
   Mobile-optimization Phase 3, 2026-06-14. */
.dm-tooltip-host {
  position: relative;
  cursor: help;
  border-bottom: 1px dotted var(--color-text-muted);
}

.dm-tooltip-host::after {
  content: attr(data-description);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 0.8rem;
  line-height: 1.5;
  padding: 6px 10px;
  /* Clamp to the viewport so the popover never runs off a 390px screen.
     220px stays the comfortable desktop width; min() caps it on narrow
     phones with a gutter on each side. */
  width: min(220px, calc(100vw - 2 * var(--space-md)));
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 100;
}

/* Desktop hover path — only where a true hover pointer exists, so touch
   devices don't get a "stuck" hover tooltip from an emulated hover. */
@media (hover: hover) and (pointer: fine) {
  .dm-tooltip-host:hover::after {
    opacity: 1;
  }
}

/* Tap-to-reveal path (touch + keyboard activation, any device). */
.dm-tooltip-host.is-open::after {
  opacity: 1;
}

/* When promoted to a tap target on touch, the host is operable: give it a
   visible focus ring and a pointer cursor instead of cursor:help. */
.dm-tooltip-host[role="button"] {
  cursor: pointer;
}

.dm-tooltip-host[role="button"]:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Edge-aware nudging: app.js adds these when the centered popover would clip
   the left/right viewport edge, pinning it to that edge with a small gutter
   instead of overflowing off-screen. */
.dm-tooltip-host.dm-tt--left::after {
  left: 0;
  transform: none;
}

.dm-tooltip-host.dm-tt--right::after {
  left: auto;
  right: 0;
  transform: none;
}

/* ============================================================
   Roll-Table library + Tables hub styles moved to tables.css
   (the roll-table library now lives in the Tables section, not on the
   Oracle page). See app/public/css/tables.css.
   ============================================================ */

/* ============================================================
   Character Builder — module wrapper and home screen
   ============================================================ */

.char-module {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.char-home-actions {
  display: flex;
  gap: var(--space-md);
  flex-wrap: wrap;
}

/* Loading / note text inside steps */
.char-loading {
  font-family: var(--font-body);
  font-style: italic;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-sm);
}

/* ============================================================
   Character Builder — progress indicator bar
   ============================================================ */

.char-progress {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-md);
}

.char-progress__step {
  /* Shared base: square numbered circle (actually square — no border-radius) */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  background: transparent;
  letter-spacing: 0;
  line-height: 1;
}

/* Done steps are clickable buttons */
button.char-progress__step {
  cursor: pointer;
  border: none;
  padding: 0;
  /* Inherit display and sizing from .char-progress__step */
  background: transparent;
  font: inherit;
}

.char-progress__step--done {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.char-progress__step--done:hover,
.char-progress__step--done:focus-visible {
  background-color: color-mix(in srgb, var(--color-accent) 15%, transparent);
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.char-progress__step--current {
  color: var(--ink-on-accent, var(--color-bg));
  background-color: var(--color-accent);
  border-color: var(--color-accent);
}

.char-progress__label {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  letter-spacing: var(--ls-cinzel-caps);
  margin-left: var(--space-sm);
}

/* ============================================================
   Character Builder — step content area
   ============================================================ */

.char-step {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.char-step__note {
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--color-text-muted);
  margin: 0;
}

.char-step__actions {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* Bottom navigation row — Back + Next */
.char-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-sm);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
}

/* ============================================================
   Character Builder — HP-roll step (SPEC B, 2026-06-21c)
   The class hit die + CON roll is animated in the shared roll
   modal (roll-modal--no-glow); this panel shows the persisted,
   WYSIWYG result + RAW formula once rolled.
   ============================================================ */

.char-step__lead {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.5;
  margin-bottom: var(--space-md);
}

.char-step__lead strong {
  color: var(--color-text);
}

.hp-roll {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-lg) var(--space-md);
  margin-bottom: var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 3px);
  background: var(--color-surface-2, rgba(255, 255, 255, 0.02));
  text-align: center;
}

.hp-roll__total {
  font-family: var(--font-heading-ui);
  font-size: 1.5rem;
  color: var(--color-text);
  margin: 0;
}

.hp-roll__total strong {
  color: var(--color-accent, var(--color-text));
}

.hp-roll__total--pending {
  font-size: var(--text-base);
  color: var(--color-text-muted);
}

.hp-roll__formula {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
  margin: 0;
  min-height: 1.1em;
}

/* ============================================================
   Character Builder — ruleset selector (Step 1)
   ============================================================ */

.ruleset-selector {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.ruleset-selector__heading {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text-muted);
  letter-spacing: var(--ls-cinzel-caps);
  margin-bottom: var(--space-xs);
}

.ruleset-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  cursor: pointer;
  padding: var(--space-xs) 0;
  user-select: none;
}

.ruleset-item--locked {
  opacity: 0.7;
  cursor: default;
}

.ruleset-item__label {
  flex: 1;
}

.ruleset-item__badge {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--color-border);
}

/* Custom checkbox style — square, Cornflower accent */
.ruleset-checkbox {
  appearance: none;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.ruleset-checkbox:checked {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
}

.ruleset-checkbox:checked::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 4px;
  width: 4px;
  height: 7px;
  border: 1.5px solid var(--color-bg);
  border-top: none;
  border-left: none;
  transform: rotate(45deg);
}

.ruleset-checkbox:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.ruleset-checkbox:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* ============================================================
   Character Builder — ancestry grid and cards (Step 1)
   ============================================================ */

.ancestry-section {
  margin-top: var(--space-md);
}

.ancestry-section__heading {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text-muted);
  letter-spacing: var(--ls-cinzel-caps);
  margin-bottom: var(--space-md);
}

.ancestry-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
}

@media (max-width: 720px) {
  .ancestry-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .ancestry-grid {
    grid-template-columns: 1fr;
  }
}

/* Shared card base — used by both ancestry and class cards */
.ancestry-card,
.class-card {
  /* Reset button styles */
  appearance: none;
  background: radial-gradient(ellipse at 30% 20%, #252018 0%, #1e1a16 70%);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  position: relative;
  width: 100%;
  transition: border-color var(--transition-fast);
}

.ancestry-card:hover,
.class-card:hover {
  background: radial-gradient(ellipse at 30% 20%, #2e2820 0%, #282320 70%);
}

.ancestry-card.is-selected,
.class-card.is-selected {
  border: 2px solid var(--color-accent);
}

.ancestry-card:focus-visible,
.class-card:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.ancestry-card__name,
.class-card__name {
  font-family: var(--font-heading);
  font-size: var(--text-h4);
  line-height: var(--lh-heading);
  color: var(--color-heading);
  display: block;
}

.ancestry-card__hitdie,
.class-card__hitdie {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-accent);
  border: 1px solid var(--color-border);
  padding: 0.1rem 0.4rem;
  display: inline-block;
  align-self: flex-start;
}

.ancestry-card__trait {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
  line-height: 1.4;
  margin-top: var(--space-xs);
}

.class-card__desc {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
  line-height: 1.4;
  margin-top: var(--space-xs);
}

/* Source tag — "CS1", "CS2", etc. — small Cinzel badge */
.char-source-tag {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  padding: 0 0.3rem;
  align-self: flex-start;
}

/* Spellcaster badge */
.char-badge {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  padding: 0.1rem 0.4rem;
  border: 1px solid;
  align-self: flex-start;
}

.char-badge--spell {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* ============================================================
   Character Builder — class grid (Step 2)
   ============================================================ */

.class-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
}

@media (max-width: 720px) {
  .class-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .class-grid {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   Character Builder — stat rows (Step 3)
   ============================================================ */

.stat-block-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-md) var(--space-lg);
}

.stat-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--color-border);
}

.stat-row:last-child {
  border-bottom: none;
}

/* Stat name — Cinzel, uppercase, UI chrome (identifier) */
.stat-row__name {
  font-family: var(--font-heading-ui);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  width: 3rem;
  flex-shrink: 0;
}

/* Stat total — large Share Tech Mono value */
.stat-total {
  font-family: var(--font-mono);
  font-size: 1.4rem;
  color: var(--color-text);
  line-height: 1;
  min-width: 2.5rem;
  text-align: right;
  flex-shrink: 0;
}

.stat-total--empty {
  color: var(--color-text-muted);
  font-size: 1rem;
}

.stat-total.is-rolling {
  color: var(--color-accent);
  animation: dice-flicker 0.08s steps(1) infinite;
}

/* Stat modifier — Cornflower Blue if positive, Void Red if negative */
.stat-modifier {
  font-family: var(--font-mono);
  font-size: 1rem;
  color: var(--color-text);
  min-width: 2.5rem;
  text-align: right;
  flex-shrink: 0;
}

.stat-modifier--pos {
  color: var(--color-accent);
}

.stat-modifier--neg {
  color: var(--color-danger);
}

/* ============================================================
   Character Builder — derived stats panel (Step 4)
   ============================================================ */

.derived-panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  padding: var(--space-md) var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* In the character sheet, the derived panel has no left accent */
.derived-panel--sheet {
  border-left: 1px solid var(--color-border);
}

.derived-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-xs) 0;
  border-bottom: 1px solid var(--color-border);
}

.derived-row:last-child {
  border-bottom: none;
}

/* Label: Cinzel uppercase — stat identifier */
.derived-label {
  font-family: var(--font-heading-ui);
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  width: 7rem;
  flex-shrink: 0;
}

/* Value: Share Tech Mono */
.derived-value {
  font-family: var(--font-mono);
  font-size: 1.1rem;
  color: var(--color-text);
  flex-shrink: 0;
}

.derived-value--large {
  font-size: 1.6rem;
  color: var(--color-accent);
}

/* Formula: Crimson Pro italic, muted */
.derived-formula {
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  flex: 1;
}

/* ============================================================
   Character Builder — equipment list (Step 5)
   ============================================================ */

.equipment-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-sm);
}

.equipment-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  padding: var(--space-sm);
  cursor: pointer;
  border-bottom: 1px solid var(--color-border);
  font-family: var(--font-body);
  transition: background-color var(--transition-fast);
  user-select: none;
}

.equipment-item:last-child {
  border-bottom: none;
}

.equipment-item:hover {
  background-color: var(--color-surface-hover);
}

.equipment-item--locked {
  opacity: 0.75;
  cursor: default;
}

.equipment-item--locked:hover {
  background-color: transparent;
}

/* Checkbox inside equipment item — same square style as ruleset */
.equip-checkbox {
  appearance: none;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.equip-checkbox:checked {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
}

.equip-checkbox:checked::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 4px;
  width: 4px;
  height: 7px;
  border: 1.5px solid var(--color-bg);
  border-top: none;
  border-left: none;
  transform: rotate(45deg);
}

.equip-checkbox:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.equip-checkbox:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.equip-name {
  font-size: 1rem;
  color: var(--color-text);
  flex: 1;
  min-width: 8rem;
}

.equip-type {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  padding: 0.1rem 0.3rem;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  flex-shrink: 0;
}

.equip-type--weapon { border-color: var(--color-danger); color: var(--color-danger); }
.equip-type--armor  { border-color: var(--color-accent); color: var(--color-accent); }

.equip-damage,
.equip-note,
.equip-slots,
.equip-cost {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  flex-shrink: 0;
}

.equip-locked {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-accent);
  flex-shrink: 0;
}

/* ============================================================
   Character Builder — level selector field (Step 1)
   ============================================================ */

.char-level-field {
  max-width: 360px;
  margin-bottom: var(--space-md);
}

/* ============================================================
   Character Builder — suggestion badge on ancestry/class cards
   Appears inline in the card name row when a card is a strong
   fit for the player's rolled stats.
   No border-radius — consistent with global no-radius rule.
   ============================================================ */

.ancestry-card--suggested,
.class-card--suggested {
  background: rgba(100, 149, 237, 0.08);
}

.suggestion-badge {
  display: inline-block;
  font-family: var(--font-heading-ui);
  font-size: 0.6rem;
  letter-spacing: 0.05em;
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
  padding: 1px 5px;
  margin-left: 6px;
  vertical-align: middle;
}

/* ============================================================
   Character Builder — equipment step additions (Step 5)
   Auto-Roll button row, auto badge, auto-selected row modifier,
   and choice sub-selector.
   Note: badge uses border-radius: 2px — the one deliberate
   exception to the global no-border-radius rule. Pill shape is
   a micro-affordance for tag-shaped elements only.
   ============================================================ */

/* Button row above the equipment list */
.equipment-auto-btn-row {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  align-items: center;
}

/* [auto] pill badge — appears on auto-selected item rows */
.equipment-badge {
  font-family: var(--font-heading-ui);
  font-size: 0.65rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
  background: transparent;
  padding: 0.1rem 0.35rem;
  border-radius: 2px; /* deliberate exception — badge micro-affordance only */
  flex-shrink: 0;
  line-height: 1.4;
}

/* Auto-selected row modifier — brighter left border */
.equipment-item--auto {
  border-left: 3px solid var(--color-accent);
  padding-left: calc(var(--space-sm) - 2px); /* compensate for extra border width */
}

/* Inline choice sub-selector row */
.equipment-choice-row {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  width: 100%;
  margin-top: var(--space-xs);
  padding-top: var(--space-xs);
  border-top: 1px dotted var(--color-border);
}

.equipment-choice-row__label {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  flex-shrink: 0;
  cursor: default;
}

.equipment-choice-row__select {
  /* Inherit .field__select treatment but constrain width */
  width: auto;
  min-width: 10rem;
  max-width: 18rem;
  font-size: 0.8rem;
  padding-top: 0.3rem;
  padding-bottom: 0.3rem;
}

/* ============================================================
   Character Builder — carry capacity panel (Step 5, fixed)
   Anchored upper-right, visible while scrolling the equipment
   list. Only present in the DOM during Step 5 — no JS toggle needed.
   No border-radius per global reset.
   ============================================================ */

.capacity-panel {
  position: fixed;
  top: 1.5rem;
  right: 1.5rem;
  width: 200px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.75rem 1rem;
  font-family: var(--font-heading-ui);
  font-size: 0.75rem;
  z-index: 50;
}

.capacity-panel__title {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  margin-bottom: 0.5rem;
}

.capacity-panel__bar-wrap {
  width: 100%;
  height: 6px;
  background: var(--color-border);
  margin-bottom: 0.4rem;
}

.capacity-panel__bar {
  height: 100%;
  background: var(--color-accent);
  transition: width 0.2s ease;
}

.capacity-panel__bar--full {
  background: var(--color-danger);
}

.capacity-panel__label {
  color: var(--color-text);
  margin-bottom: 0.2rem;
}

.capacity-panel__remaining {
  color: var(--color-text-muted);
}

.capacity-panel__remaining--danger {
  color: var(--color-danger);
}

/* ============================================================
   Character Builder — Spell count panel (fixed upper-left, Spells step)
   ============================================================ */

.spell-count-panel {
  position: fixed;
  top: 1.5rem;
  right: 1.5rem;
  width: 160px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.75rem 1rem;
  font-family: var(--font-heading-ui);
  font-size: 0.75rem;
  z-index: 50;
}

.spell-count-panel__title {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  margin-bottom: 0.4rem;
}

.spell-count-panel__count {
  font-size: 1.4rem;
  font-family: var(--font-heading-ui);
  color: var(--color-accent);
  line-height: 1;
  margin-bottom: 0.3rem;
}

.spell-count-panel__count--full {
  color: var(--color-text-muted);
}

.spell-count-panel__note {
  font-size: 0.7rem;
  color: var(--color-text-muted);
}

/* Phase 1 mobile optimization (KDL 2026-06-27, 2026-06-14): on phones (<= --bp-tab,
   700px) these two older fixed wizard panels overlapped the step content and the
   header because they had NO media query (the newer .gear-float already reflows
   to a full-width in-flow banner at <=700px — character.js GEAR_FLOAT_CSS). Give
   them the same treatment for consistency: drop out of position:fixed into
   normal flow, span the column. Both panels are the last child of their step
   container, so static flow places them cleanly without overlap. Desktop
   (> 700px) is unchanged. Placed AFTER both panels' base rules so this override
   wins the equal-specificity source-order cascade. */
@media (max-width: 700px) {
  .capacity-panel,
  .spell-count-panel {
    position: static;
    top: auto;
    right: auto;
    width: auto;
    margin: 0 0 var(--space-md) 0;
  }
}

/* Character Builder — Class card feature badges */

.class-card__features {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.5rem;
}

.class-card__feature {
  display: inline-block;
  font-family: var(--font-heading-ui);
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--color-border);
  padding: 0.15rem 0.4rem;
  cursor: default;
}

/* Spell granted-by-class badge */

.spell-granted-badge {
  display: inline-block;
  font-family: var(--font-heading-ui);
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
  padding: 0.1rem 0.35rem;
  margin-left: 0.4rem;
  vertical-align: middle;
}

.spell-item--granted {
  opacity: 0.85;
}

/* Wizard step — validation error messages */

.step2-error,
.step3-error,
.step4-error,
.lang-error,
.spell-error {
  color: var(--color-danger);
  font-size: 0.85rem;
  margin-top: 0.4rem;
  min-height: 1.2em;
}

/* Character Builder — Name field error state */

.field__input--error {
  border-color: var(--color-danger) !important;
  outline: 1px solid var(--color-danger);
}

.char-name-error {
  color: var(--color-danger);
  font-size: 0.85rem;
  margin-top: 0.35rem;
}

/* ============================================================
   Character Builder — Roll Random row (Steps 3 and 4)
   Sits between the card grid and the nav row.
   ============================================================ */

.char-step__roll-row {
  display: flex;
  justify-content: flex-start;
  margin: 0.75rem 0 0.5rem;
}

/* Flash outline on the card that was randomly selected */
.ancestry-card.is-rolled,
.class-card.is-rolled {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Equipment items that cannot be added because capacity is full */
.equipment-item--at-cap {
  opacity: 0.5;
}

/* ============================================================
   Character Builder — equipment category sections (Step 5)
   Items are grouped under named headings: Armor, Weapons, Gear, Utility.
   The heading uses letter-spacing (not text-transform: uppercase) for
   distinction, keeping it readable at small sizes in Cinzel.
   ============================================================ */

.equipment-category {
  margin-bottom: 1.5rem;
}

.equipment-category__heading {
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 0.3rem;
  margin-bottom: 0.5rem;
}

/* ============================================================
   Character Builder — spell list (Step 6)
   ============================================================ */

.spell-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-sm);
}

.spell-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm);
  cursor: pointer;
  border-bottom: 1px solid var(--color-border);
  font-family: var(--font-body);
  transition: background-color var(--transition-fast);
  user-select: none;
}

.spell-item:last-child {
  border-bottom: none;
}

.spell-item:hover {
  background-color: var(--color-surface-hover);
}

.spell-item.is-selected {
  background-color: color-mix(in srgb, var(--color-accent) 8%, transparent);
}

/* Spell checkbox — reuse equip-checkbox size/style */
.spell-checkbox {
  appearance: none;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.spell-checkbox:checked {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
}

.spell-checkbox:checked::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 4px;
  width: 4px;
  height: 7px;
  border: 1.5px solid var(--color-bg);
  border-top: none;
  border-left: none;
  transform: rotate(45deg);
}

.spell-checkbox:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.spell-checkbox:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.spell-name {
  font-size: 1rem;
  color: var(--color-text);
  flex: 1;
}

.spell-meta {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  flex-shrink: 0;
}

/* ============================================================
   Character Builder — spell category/tier groupings (Step 6)
   ============================================================ */

.spell-category-section {
  margin-bottom: 1.25rem;
}

.spell-category-heading {
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 0.3rem;
  margin-bottom: 0.4rem;
}

.spell-category-desc {
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin: 0 0 0.5rem 0;
  font-style: italic;
}

.spell-tier-heading {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  color: var(--color-text-muted);
  margin: 0.5rem 0 0.25rem 0;
  opacity: 0.7;
}

.spell-damage-badge {
  font-family: var(--font-heading-ui);
  font-size: 0.72rem;
  color: var(--color-cornflower);
  background: rgba(100, 149, 237, 0.1);
  padding: 1px 6px;
  margin-left: 8px;
  vertical-align: middle;
  letter-spacing: 0.03em;
}

/* ============================================================
   Character Builder — name entry and summary (Step 7)
   ============================================================ */

.char-name-field {
  max-width: 480px;
}

.char-name-row {
  display: flex;
  gap: var(--space-sm);
  align-items: stretch;
}

.char-name-row .field__input {
  flex: 1;
}

.char-name-generate {
  flex-shrink: 0;
  white-space: nowrap;
}

/* Summary panel — read-only preview of all choices */
.char-summary {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-md) var(--space-lg);
}

.char-summary__heading {
  font-family: var(--font-heading);
  font-size: var(--text-h4);
  color: var(--color-heading);
  margin-bottom: var(--space-sm);
}

.char-summary__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}

.summary-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3rem;
  padding: var(--space-xs);
  border: 1px solid var(--color-border);
}

.summary-stat__name {
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
}

.summary-stat__val {
  font-family: var(--font-mono);
  font-size: 1.1rem;
  color: var(--color-text);
}

.summary-stat__mod {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.char-summary__derived {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--color-text-muted);
  margin-bottom: var(--space-sm);
}

.char-summary__list {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text-muted);
  columns: 2;
}

.char-summary__list--described {
  list-style: none;
  padding: 0;
  margin: 0;
  columns: unset;
}

.summary-list-item {
  margin-bottom: 0.6rem;
}

.summary-list-item__header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.15rem;
}

.summary-list-item--equipped .summary-list-item__header::before {
  content: "";
  display: inline-block;
  width: 0.55em;
  height: 0.65em;
  background: rgba(240,200,90,0.9);
  clip-path: polygon(50% 0%, 93% 14%, 93% 58%, 50% 100%, 7% 58%, 7% 14%);
  flex-shrink: 0;
}

.summary-list-item__name {
  font-family: var(--font-heading-ui);
  font-size: 0.85rem;
  color: var(--color-text);
}

.summary-list-item__desc {
  display: block;
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--color-text-muted);
  line-height: 1.4;
}

/* ============================================================
   Character Builder — summary panel labeled sections (Step 7)
   ============================================================ */

.summary-section {
  margin-bottom: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--color-border);
}

.summary-section:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}

.summary-section__label {
  font-family: var(--font-heading-ui);
  font-size: 0.9rem;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  margin-bottom: 0.4rem;
}

.summary-section__value {
  font-family: var(--font-body);
  font-size: 1.1rem;
  color: var(--color-text);
}

.summary-section__note {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin-top: 0.15rem;
}

/* ============================================================
   Character Builder — roster table  (REMOVED 2026-06-24, KDL 2026-06-27)

   The old scrollable `.roster-table` / `.roster-th` / `.roster-row` /
   `.roster-cell*` idiom was the sixth, dead, table idiom flagged by the
   consistency audit. It was already superseded by the Character Forge cards page
   (see the .char-forge block immediately below, whose own comment notes it
   "Replaces the old scrollable roster table"), and a repo-wide search confirmed
   ZERO markup ever emits these classes. Rather than fold a dead idiom onto the
   shared .sd-dtable base, the unused rules are removed outright so it can no
   longer drift. If a Builder roster table is ever reintroduced, build it as
   `class="sd-dtable"` (+ a thin `--roster` modifier), the same as the other
   data/reference tables.
   ============================================================ */

/* ============================================================
   Character Forge — landing (cards page)
   The Forge IS the cards page: a Tools row, a header with a
   Create action, then the character cards grid. Replaces the
   old scrollable roster table.
   ============================================================ */

.char-forge {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.char-forge__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}

/* Forge action cluster — Create + Surprise Me sit together in the head and
   in the empty-state panel. */
.char-forge__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.char-forge__empty .char-forge__actions {
  margin-top: var(--space-md);
  justify-content: center;
}

/* Import button carries the Shadowdarklings logo as a leading brand icon (the
   d20 brands the Foundry EXPORT side; this logo brands the import side). Same
   treatment as .char-dash__foundry-icon: a fixed footprint with object-fit so
   the raster mark stays crisp. The .btn flex/gap aligns it with the label, which
   stays visible to keep import distinct from export and to stay discoverable. */
.char-forge__import-icon {
  width: 1.05em;
  height: 1.05em;
  object-fit: contain;
  flex: 0 0 auto;
}

/* Empty state — friendly OSR panel with a prominent Create CTA. */
.char-forge__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-sm);
  padding: var(--space-2xl) var(--space-xl);
}

.char-forge__empty-lede {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  color: var(--color-heading);
  margin: 0;
}

.char-forge__empty .btn {
  margin-top: var(--space-md);
}

/* --- Cards grid --- */

.char-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-md);
}

.char-card {
  position: relative;
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  background-color: var(--color-surface);
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

/* Whole-card hover/focus lift — matches the Bestiary/Treasure card treatment. */
.char-card:hover,
.char-card:focus-within {
  background-color: var(--color-surface-hover);
  border-color: var(--color-accent);
}

/* The whole card is a button (open-on-click). Reset button chrome. */
.char-card__open {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  width: 100%;
  text-align: left;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  /* Trim the horizontal padding (md->sm) to free room for the larger portrait
     (#2, 2026-06-27). Vertical padding unchanged so card height grows only with
     the bigger media. */
  padding: var(--space-md) var(--space-sm);
  color: inherit;
  transition: background-color var(--transition-ui);
}

.char-card__open:hover {
  background-color: var(--color-surface-hover);
}

.char-card__open:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* Portrait medallion — mirrors the dashboard identity portrait treatment.
   Enlarged 3.5rem->5rem (#2, 2026-06-27) for a more prominent roster image; the
   240px-min grid track still holds it with the trimmed card padding. */
.char-card__portrait {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 5rem;
  height: 5rem;
  flex: none;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background-color: var(--color-bg);
  color: var(--color-text-muted);
  overflow: hidden;
}

.char-card__portrait-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 18%;
  display: block;
}

.char-card__portrait-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
}

.char-card__portrait-fallback svg {
  /* Scaled up with the 5rem portrait frame (#2) so the glyph fallback keeps the
     same visual weight relative to its medallion. */
  width: 2.25rem;
  height: 2.25rem;
}

.char-card__body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.char-card__name {
  font-family: var(--font-heading);
  font-size: var(--text-h4);
  color: var(--color-heading);
  line-height: 1.1;
  /* Truncate gracefully on narrow cards. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.char-card__meta {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text);
}

.char-card__updated {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Delete control — a small glyph button in the top-right corner. Sits above
   the card open-button (stopPropagation in JS keeps it from opening the card).
   Dim by default; reveals on card hover / its own focus. */
.char-card__delete {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  padding: var(--space-xs);
  line-height: 0;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--transition-ui), color var(--transition-ui);
}

.char-card:hover .char-card__delete,
.char-card__delete:focus-visible {
  opacity: 1;
}

.char-card__delete:hover {
  color: var(--color-accent);
}

.char-card__delete:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.char-card__delete svg {
  width: 1.1rem;
  height: 1.1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: square;
  stroke-linejoin: miter;
}

/* Keyboard users and reduced-motion users always see the delete control. */
@media (prefers-reduced-motion: reduce) {
  .char-card__open,
  .char-card__delete {
    transition: none;
  }
  .char-card__delete {
    opacity: 1;
  }
}

/* ============================================================
   Tools row — Oracle / Encounter / Names, on the Forge landing.
   Compact links that keep the non-Forge tools one click away.
   ============================================================ */

.tools-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--color-border);
}

.tools-row__heading {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.tools-row__links {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.tools-row__link {
  appearance: none;
  background: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-md);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  letter-spacing: var(--ls-cinzel-caps);
  transition: border-color var(--transition-ui), color var(--transition-ui);
}

.tools-row__link:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.tools-row__link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.tools-row__icon {
  width: 1rem;
  height: 1rem;
  flex: none;
}

@media (prefers-reduced-motion: reduce) {
  .tools-row__link {
    transition: none;
  }
}

/* ============================================================
   Roll Tables — coming-soon placeholder
   ============================================================ */

.roll-tables__soon {
  text-align: center;
  padding: var(--space-2xl) var(--space-xl);
}

.roll-tables__soon-lede {
  font-family: var(--font-heading);
  font-size: var(--text-h3);
  color: var(--color-heading);
  margin: 0 0 var(--space-sm);
}

/* Language list — used by the builder's Languages/Review steps (the only
   surviving .char-sheet__* rules after the sheet view was removed 2026-06-07). */
.char-sheet__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.char-sheet__list-item {
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--color-text);
  padding: var(--space-xs) 0;
  border-bottom: 1px solid var(--color-border);
}

.char-sheet__list-item:last-child {
  border-bottom: none;
}


/* ============================================================
   Character Builder — alignment cards (Step 4)
   ============================================================ */

.alignment-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
  margin: var(--space-sm) 0;
}

.alignment-card {
  appearance: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font: inherit;
  padding: var(--space-sm);
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.alignment-card:hover {
  background: var(--color-surface-hover);
}

.alignment-card.is-selected {
  border-color: var(--color-accent);
  background: rgba(100, 149, 237, 0.08);
}

.alignment-card:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.alignment-card__name {
  display: block;
  font-family: var(--font-heading-ui);
  font-size: 0.9rem;
  color: var(--color-text);
  margin-bottom: 0.25rem;
}

.alignment-card__desc {
  display: block;
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--color-text-muted);
  line-height: 1.3;
}

/* ============================================================
   Character Builder — deity cards (Step 4)
   ============================================================ */

.deity-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin: var(--space-sm) 0;
}

@media (max-width: 540px) {
  .deity-grid {
    grid-template-columns: 1fr;
  }
}

.deity-card {
  appearance: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font: inherit;
  padding: var(--space-sm);
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.deity-card:hover {
  background: var(--color-surface-hover);
}

.deity-card.is-selected {
  border-color: var(--color-accent);
  background: rgba(100, 149, 237, 0.08);
}

.deity-card:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.deity-card__name {
  display: block;
  font-family: var(--font-heading-ui);
  font-size: 0.85rem;
  color: var(--color-text);
  margin-bottom: 0.2rem;
}

.deity-card__alignment {
  display: block;
  font-family: var(--font-heading-ui);
  font-size: 0.65rem;
  color: var(--color-accent);
  letter-spacing: 0.06em;
  margin-bottom: 0.2rem;
}

.deity-card__domains {
  display: block;
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--color-text-muted);
  margin-bottom: 0.2rem;
}

.deity-card__symbol {
  display: block;
  font-family: var(--font-body);
  font-size: 0.72rem;
  color: var(--color-text-muted);
  font-style: italic;
  margin-bottom: 0.15rem;
}

.deity-card__desc {
  display: block;
  font-family: var(--font-body);
  font-size: 0.75rem;
  color: var(--color-text-muted);
  line-height: 1.3;
}

/* Section headings and notes — shared by alignment and deity */

.alignment-selector__heading,
.deity-selector__heading {
  font-family: var(--font-heading-ui);
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  margin-bottom: 0.25rem;
}

.alignment-selector__note,
.deity-selector__note {
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--color-text-muted);
  font-style: italic;
  margin-bottom: 0.5rem;
}

/* Responsive: collapse alignment grid to single column on narrow viewports */
@media (max-width: 480px) {
  .alignment-grid {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   Character Builder — talent display (Review panel + Sheet)
   ============================================================ */

.talent-entry {
  margin-bottom: 0.5rem;
}

.talent-entry:last-child {
  margin-bottom: 0;
}

.talent-entry__roll {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-accent);
  margin-right: 0.5rem;
}

.talent-entry__effect {
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--color-text);
}

/* ============================================================
   Talent roll cards (Talents step)
   ============================================================ */

.talent-roll-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.talent-roll-card {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.6rem 0.75rem;
}

.talent-roll-card__roll {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-accent);
  white-space: nowrap;
  padding-top: 0.1rem;
  min-width: 3.5rem;
}

.talent-roll-card__effect {
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  flex: 1;
  line-height: 1.4;
}

/* ============================================================
   Language selection (Languages step)
   ============================================================ */

.languages-section {
  margin-bottom: 1.25rem;
}

.languages-section__heading {
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 0.3rem;
  margin-bottom: 0.5rem;
}

.language-list {
  list-style: none;
  padding: 0;
  margin: 0 0 0.5rem 0;
}

.language-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
}

.language-item--locked {
  color: var(--color-text-muted);
}

.language-lock {
  font-size: 0.75rem;
  opacity: 0.5;
}

.language-extra-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 0.5rem;
}

.language-extra-row .field__label {
  min-width: 130px;
  margin-bottom: 0;
}


/* ============================================================
   CRIT OVERLAY — shared by dice rolls across the app
   Populated and cleared by JS; fixed, pointer-events: none.
   ============================================================ */

#crit-overlay-container {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
}

.crit-overlay {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.crit-overlay__text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(1);
  font-family: var(--font-heading);
  font-size: clamp(3rem, 10vw, 7rem);
  line-height: 1.1;
  text-align: center;
  white-space: nowrap;
  animation: critTextIn 2s ease-out forwards;
  z-index: 2;
}

.crit-overlay__text--success {
  color: #ddeeff;
  text-shadow:
    0 0 20px rgba(100,149,237,0.8),
    0 0 50px rgba(80,130,255,0.55),
    2px 2px 4px rgba(0,0,20,0.95);
}

.crit-overlay__text--failure {
  color: #cc1020;
  text-shadow:
    0 0 20px rgba(200,10,10,0.85),
    0 0 50px rgba(160,0,0,0.6),
    2px 2px 4px rgba(20,0,0,0.98);
}

.crit-overlay__flash-failure {
  position: absolute;
  inset: 0;
  background: rgba(80,0,0,0);
  z-index: 0;
  animation: failureFlash 2s ease-out forwards;
}

.flame-particle {
  position: absolute;
  bottom: 0;
  border-radius: 50% 50% 20% 20%;
  animation: flameRise var(--flame-dur) ease-in var(--flame-delay) forwards;
  z-index: 1;
}

.crit-overlay__light-rise {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 40vh;
  background: radial-gradient(
    ellipse 80% 100% at 50% 100%,
    rgba(220,235,255,0.92) 0%,
    rgba(180,210,255,0.55) 20%,
    rgba(140,180,255,0.22) 50%,
    transparent 80%
  );
  animation: lightRiseAnim 2s ease-out forwards;
  z-index: 1;
}

.starburst-particle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--sb-size);
  height: var(--sb-size);
  border-radius: 50%;
  background: var(--sb-color);
  animation: starburstOut var(--sb-dur) ease-out var(--sb-delay) forwards;
  z-index: 1;
}

.crit-overlay__shimmer {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    transparent 0%,
    rgba(160,200,255,0.12) 30%,
    rgba(210,230,255,0.65) 50%,
    rgba(160,200,255,0.12) 70%,
    transparent 100%
  );
  background-size: 100% 200%;
  background-position: 50% 110%;
  animation: shimmerSweep 2s ease-in-out forwards;
  z-index: 1;
}

#shatter-canvas-main {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

@keyframes critTextIn {
  0%   { transform: translate(-50%, -50%) scale(2.0); opacity: 0; }
  20%  { transform: translate(-50%, -50%) scale(1.0); opacity: 1; }
  75%  { transform: translate(-50%, -50%) scale(1.0); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0.95); opacity: 0; }
}

@keyframes failureFlash {
  0%   { background: rgba(80,0,0,0); }
  10%  { background: rgba(80,0,0,0.42); }
  50%  { background: rgba(60,0,0,0.22); }
  100% { background: rgba(80,0,0,0); }
}

@keyframes flameRise {
  0%   { transform: translateY(0) scaleX(1.0);   opacity: 0; }
  8%   { opacity: 0.85; }
  85%  { opacity: 0.6; }
  100% { transform: translateY(-90vh) scaleX(0.7); opacity: 0; }
}

@keyframes lightRiseAnim {
  0%   { transform: translateY(8%); opacity: 0; }
  18%  { transform: translateY(0);  opacity: 1; }
  75%  { transform: translateY(0);  opacity: 1; }
  100% { transform: translateY(-4%); opacity: 0; }
}

@keyframes starburstOut {
  0%   { transform: translate(-50%, -50%) rotate(var(--angle)) translateX(0)    scale(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) rotate(var(--angle)) translateX(var(--dist)) scale(0); opacity: 0; }
}

@keyframes shimmerSweep {
  0%   { background-position: 50% 110%; opacity: 0; }
  12%  { opacity: 0.9; }
  88%  { opacity: 0.7; }
  100% { background-position: 50% -30%; opacity: 0; }
}

@keyframes critShake {
  0%   { transform: translate(0, 0) rotate(0deg); }
  5%   { transform: translate(-8px, 2px) rotate(-0.8deg); }
  10%  { transform: translate(8px, -2px) rotate(0.8deg); }
  15%  { transform: translate(-7px, 1px) rotate(-0.6deg); }
  20%  { transform: translate(7px, -1px) rotate(0.6deg); }
  25%  { transform: translate(-5px, 1px) rotate(-0.4deg); }
  30%  { transform: translate(5px, -1px) rotate(0.4deg); }
  35%  { transform: translate(-3px, 0px) rotate(-0.2deg); }
  40%  { transform: translate(3px, 0px) rotate(0.2deg); }
  45%  { transform: translate(-2px, 0px) rotate(0deg); }
  50%  { transform: translate(2px, 0px) rotate(0deg); }
  55%  { transform: translate(-1px, 0px); }
  60%  { transform: translate(1px, 0px); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

/* ------------------------------------------------------------------
   Darkness overlay — the torch equivalent of a crit beat. Fires when the
   last active torch goes out. Lives in #crit-overlay-container (z-index 9999,
   pointer-events:none) and rides the same queue. The interactive variant
   (.darkness-overlay--interactive, below) holds at full opacity until the user
   acknowledges, then fades — it does NOT auto-clear. No new tokens: uses
   near-black voids + the warm ember amber already on brand.
   ------------------------------------------------------------------ */
/* Flat backdrop scrim — dims the whole page behind the darkness beat. Mirrors
   the standard .action-modal scrim (rgba(0,0,0,0.72)) and its fade timing so this
   reads as the app's familiar modal darkening, just deliberately applied to the
   torch-out beat. Sits BELOW the ember vignette (z 0 vs the vignette also at z 0
   but appended after, and the text at z 2). Fades in smoothly on mount; the
   --leaving modifier on the overlay fades it out on dismiss. */
.darkness-overlay__scrim {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: rgba(0, 0, 0, 0.72);
  opacity: 0;
  animation: darknessScrimIn 600ms ease-out forwards;
}

@keyframes darknessScrimIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.darkness-overlay__vignette {
  position: absolute;
  inset: 0;
  z-index: 0;
  /* A warm dying-ember core that the dark swallows: closes from edges to
     near-black. The animation drives opacity + the radial extent. */
  background: radial-gradient(
    ellipse 70% 70% at 50% 50%,
    rgba(40, 22, 6, 0.0) 0%,
    rgba(20, 10, 2, 0.55) 42%,
    rgba(6, 4, 2, 0.9) 68%,
    rgba(0, 0, 0, 0.99) 100%
  );
  opacity: 0;
  animation: darknessCloseIn 2.8s ease-in forwards;
}

.darkness-overlay__text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  text-align: center;
  width: 90%;
  opacity: 0;
  animation: darknessTextIn 2.8s ease-out forwards;
}

.darkness-overlay__heading {
  font-family: var(--font-heading);
  font-size: clamp(2.6rem, 9vw, 6rem);
  line-height: 1.05;
  letter-spacing: 0.12em;
  color: #c9bfae;
  text-shadow:
    0 0 18px rgba(240, 162, 60, 0.35),
    0 0 44px rgba(120, 70, 20, 0.45),
    2px 2px 6px rgba(0, 0, 0, 0.98);
}

.darkness-overlay__sub {
  margin-top: 0.5rem;
  font-family: var(--font-heading-ui);
  font-size: clamp(0.95rem, 2.4vw, 1.3rem);
  letter-spacing: 0.04em;
  color: #8c8170;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.95);
}

@keyframes darknessCloseIn {
  0%   { opacity: 0; }
  35%  { opacity: 0.85; }
  78%  { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes darknessTextIn {
  0%   { transform: translate(-50%, -50%) scale(1.08); opacity: 0; }
  22%  { transform: translate(-50%, -50%) scale(1.0);  opacity: 1; }
  74%  { transform: translate(-50%, -50%) scale(1.0);  opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0.98); opacity: 0; }
}

/* ------------------------------------------------------------------
   Interactive darkness overlay — must be acknowledged before it fades.
   The base animations above fade to dark then auto-clear; the interactive
   variant instead CLOSES IN and HOLDS at full opacity until the user
   acknowledges (button / backdrop click / Esc / Enter), then plays a
   short fade-out (driven by the --leaving modifier). Re-enables pointer
   events since #crit-overlay-container is normally pointer-events:none.
   ------------------------------------------------------------------ */
.darkness-overlay--interactive {
  pointer-events: auto;
  cursor: pointer;
}

.darkness-overlay--interactive .darkness-overlay__vignette {
  /* Close in and stay closed — hold the dark instead of fading back out. */
  animation: darknessCloseInHold 2.8s ease-in forwards;
  opacity: 1;
}

.darkness-overlay--interactive .darkness-overlay__text {
  /* Reveal and stay — no exit on the in-animation. */
  animation: darknessTextInHold 1.2s ease-out forwards;
  opacity: 1;
}

@keyframes darknessCloseInHold {
  0%   { opacity: 0; }
  35%  { opacity: 0.85; }
  78%  { opacity: 1; }
  100% { opacity: 1; }
}

@keyframes darknessTextInHold {
  0%   { transform: translate(-50%, -50%) scale(1.08); opacity: 0; }
  60%  { transform: translate(-50%, -50%) scale(1.0);  opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.0);  opacity: 1; }
}

/* Acknowledge affordance */
.darkness-overlay__ack {
  display: inline-block;
  margin-top: 1.5rem;
  padding: 0.6rem 1.4rem;
  font-family: var(--font-heading-ui);
  font-size: clamp(0.8rem, 1.8vw, 0.95rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #d8cdb8;
  background: rgba(20, 12, 4, 0.7);
  border: 1px solid rgba(240, 162, 60, 0.5);
  border-radius: 2px;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 0 14px rgba(240, 162, 60, 0.18);
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.darkness-overlay__ack:hover {
  background: rgba(40, 24, 8, 0.85);
  border-color: rgba(240, 162, 60, 0.85);
  color: #f0e6cf;
}

.darkness-overlay__ack:focus-visible {
  outline: 2px solid rgba(240, 162, 60, 0.9);
  outline-offset: 2px;
}

/* Acknowledged — fade the whole beat out, then JS clears it. */
.darkness-overlay--leaving {
  animation: darknessLeave 0.55s ease-out forwards;
}

@keyframes darknessLeave {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* Scrim: drop the fade, hold the dim immediately. */
  .darkness-overlay__scrim {
    animation: none;
    opacity: 1;
  }
  /* Degrade to a plain brief fade-to-dark + the text, no closing-in motion. */
  .darkness-overlay__vignette {
    background: rgba(2, 2, 2, 0.92);
    animation: darknessFadeReduced 2.8s ease-out forwards;
  }
  .darkness-overlay__text {
    transform: translate(-50%, -50%);
    animation: darknessFadeReduced 2.8s ease-out forwards;
  }
  @keyframes darknessFadeReduced {
    0%   { opacity: 0; }
    18%  { opacity: 1; }
    80%  { opacity: 1; }
    100% { opacity: 0; }
  }

  /* Interactive variant: still must hold until acknowledged — degrade to a
     plain fade-IN that stays visible, no exit until --leaving is applied. */
  .darkness-overlay--interactive .darkness-overlay__vignette {
    background: rgba(2, 2, 2, 0.92);
    animation: darknessFadeReducedHold 0.45s ease-out forwards;
    opacity: 1;
  }
  .darkness-overlay--interactive .darkness-overlay__text {
    transform: translate(-50%, -50%);
    animation: darknessFadeReducedHold 0.45s ease-out forwards;
    opacity: 1;
  }
  @keyframes darknessFadeReducedHold {
    0%   { opacity: 0; }
    100% { opacity: 1; }
  }
  /* Keep the acknowledged fade-out snappy under reduced motion. */
  .darkness-overlay--leaving {
    animation: darknessLeave 0.2s ease-out forwards;
  }
}

/* ============================================================
   COMBAT LOADOUT — character sheet in-game equipment panel
   ============================================================ */

.loadout-panel {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.loadout-ac-strip {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.5rem 0.75rem;
  background: rgba(100,149,237,0.07);
  border: 1px solid rgba(100,149,237,0.22);
  width: fit-content;
}

.loadout-ac-strip__label {
  font-family: var(--font-heading-ui);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.45);
}

.loadout-ac-strip__value {
  font-family: var(--font-mono);
  font-size: 1.6rem;
  line-height: 1;
  color: #c0d8ff;
  text-shadow: 0 0 10px rgba(100,149,237,0.45);
}

.loadout-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
}

.loadout-row--hands {
  align-items: flex-start;
}

.loadout-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 160px;
}

.loadout-field__label {
  font-family: var(--font-heading-ui);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.40);
}

.loadout-field__select {
  font-family: var(--font-heading-ui);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.35rem 1.6rem 0.35rem 0.65rem;
  cursor: pointer;
  transition: border-color 120ms ease-out;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23ffffff' opacity='0.45'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.5rem center;
  min-width: 160px;
}

.loadout-field__select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-color: var(--color-accent);
}

.loadout-field__select:hover:not(:disabled) {
  border-color: rgba(255,255,255,0.22);
}

.loadout-field__select:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.loadout-field--locked .loadout-field__label::after {
  content: ' (occupied)';
  color: rgba(255,255,255,0.28);
  font-style: italic;
  letter-spacing: 0;
  text-transform: none;
}

.loadout-damage-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-top: 0.2rem;
  min-height: 0;
}


/* ============================================================
   SHARED — die hero image mask element
   Used by dice_test.html (via inline <style>) and mini tiles (here).
   display: none by default; each context enables it selectively.
   ============================================================ */

.die-hero-img {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-color: currentColor;
  -webkit-mask-image: var(--hero-img);
  mask-image: var(--hero-img);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  pointer-events: none;
  transition: background-color 0.35s ease;
}


/* ============================================================
   MINI DIE TILES — 3D Polyhedral, compact rollable damage dice
   Used in Combat Loadout weapon slots.
   Shape from PNG mask; white at rest → warm gold on hover.
   No crit overlays — D20 stat rolls only.
   ============================================================ */

.die-mini-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
}

.die-mini-group__label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  color: rgba(255,255,255,0.40);
  letter-spacing: 0.04em;
  pointer-events: none;
  user-select: none;
}

/* Base tile — transparent bg; shape/colour from PNG mask via currentColor */
.die-tile--mini {
  position: relative;
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  border: none;
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  background: transparent;
  box-shadow: none;
  color: rgba(255,255,255,0.88);
  filter:
    drop-shadow(0 0 3px rgba(220,190,110,0.55))
    drop-shadow(0 0 10px rgba(170,135,55,0.28));
  transition: color 0.35s ease, filter 0.35s ease;
}

.die-tile--mini .die-hero-img {
  display: block;
}

.die-tile--mini:hover {
  color: rgba(240,200,100,0.90);
  filter:
    drop-shadow(0 0 6px rgba(240,200,90,0.85))
    drop-shadow(0 0 18px rgba(180,140,40,0.50));
}

.die-tile--mini:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

.die-tile--mini:active {
  color: rgba(130,179,255,0.90);
  filter:
    drop-shadow(0 0 6px rgba(100,149,237,0.85))
    drop-shadow(0 0 14px rgba(80,120,220,0.50));
}

/* D4 and D6 hero images are smaller than the tile — boost mask size to fill */
.die-tile--mini-d4 .die-hero-img { -webkit-mask-size: 130%; mask-size: 130%; }
.die-tile--mini-d6 .die-hero-img { -webkit-mask-size: 115%; mask-size: 115%; }

/* ---- Result number (overlays the PNG artwork) ---- */
.die-mini-result {
  position: relative;
  z-index: 1;
  font-family: var(--font-mono);
  font-size: 1.0rem;
  font-weight: 700;
  line-height: 1;
  color: currentColor;
  text-shadow:
    0 0 4px rgba(0,0,0,0.95),
    1px 1px 2px rgba(0,0,0,0.98),
    -1px -1px 1px rgba(0,0,0,0.95);
  pointer-events: none;
  min-height: 1rem;
}


/* ============================================================
   EQUIPMENT BADGES — inline damage/AC tags in Equipment list
   ============================================================ */

.equip-badge-row {
  display: inline-flex;
  gap: 0.3rem;
  align-items: center;
}

.equip-badge {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  padding: 0.1rem 0.38rem;
  border: 1px solid currentColor;
  line-height: 1.4;
}

.equip-badge--damage {
  color: rgba(240,200,90,0.85);
  border-color: rgba(240,200,90,0.35);
  background: rgba(240,200,90,0.06);
}

.equip-badge--ac {
  color: rgba(100,180,255,0.85);
  border-color: rgba(100,180,255,0.35);
  background: rgba(100,180,255,0.06);
}


/* DAMAGE button — filled red */
.btn--damage {
  background-color: var(--color-damage);
  color: var(--color-text);
  border-color: var(--color-damage);
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.6rem 1.2rem;
  cursor: pointer;
  border-width: 1px;
  border-style: solid;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--damage:hover {
  background-color: var(--color-damage-hover);
  border-color: var(--color-damage-hover);
}

.btn--damage:focus-visible {
  outline: 2px solid var(--color-damage);
  outline-offset: 2px;
}

/* HEAL button — filled muted green */
.btn--heal {
  background-color: var(--color-heal);
  color: var(--color-text);
  border-color: var(--color-heal);
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.6rem 1.2rem;
  cursor: pointer;
  border-width: 1px;
  border-style: solid;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--heal:hover {
  background-color: var(--color-heal-hover);
  border-color: var(--color-heal-hover);
}

.btn--heal:focus-visible {
  outline: 2px solid var(--color-heal);
  outline-offset: 2px;
}


/* ============================================================
   ACTION LOG DRAWER — Phase 7
   Scribe's ledger at the bottom of the Actions panel.
   Collapsed by default: header + 2-entry preview strip.
   Expanded: header + scrollable full history (last 50 entries).
   ============================================================ */

.action-log-drawer {
  margin-top: var(--space-lg);
  border: 1px solid var(--color-border);
  /* Subtle ledger-page tint distinct from the Actions panel above */
  background: linear-gradient(180deg, #161310 0%, #100e0c 100%);
}

/* ---- Header (clickable toggle bar) ---- */
.action-log-drawer__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
  user-select: none;
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: var(--ls-cinzel-caps);
  color: var(--color-text-muted);
  background: linear-gradient(180deg, #1a1612 0%, #141110 100%);
  border-bottom: 1px solid var(--color-border);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.action-log-drawer__header:hover {
  color: var(--color-text);
  background: linear-gradient(180deg, #221d18 0%, #1a1612 100%);
}

.action-log-drawer__header:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.action-log-drawer__label {
  text-transform: uppercase;
}

.action-log-drawer__toggle {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--color-accent);
  letter-spacing: 0;
  text-transform: none;
}

/* ---- Preview strip (last 2 entries when collapsed) ---- */
.action-log-drawer__preview {
  padding: var(--space-xs) var(--space-md);
  background: rgba(0, 0, 0, 0.18);
}

.action-log-drawer__preview[hidden] {
  display: none;
}

/* ---- Body (full scrollable history when expanded) ----
   Class-driven collapse: max-height + padding transition. The body is always
   in the DOM; aria-hidden carries the AT semantic for the collapsed state. */
.action-log-drawer__body {
  max-height: 0;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.22);
  padding: 0 var(--space-md);
  border-top: 0 solid var(--color-border);
  transition:
    max-height var(--transition-reveal),
    padding var(--transition-reveal),
    border-top-width var(--transition-reveal);
}

.action-log-drawer--expanded .action-log-drawer__body {
  max-height: 25rem;
  overflow-y: auto;
  padding: var(--space-sm) var(--space-md);
  border-top-width: 1px;
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .action-log-drawer__body {
    transition: none;
  }
}

/* ---- Single entry row ---- */
.action-log-entry {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: baseline;
  gap: var(--space-sm);
  padding: 0.18rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}

.action-log-drawer__preview .action-log-entry:last-child,
.action-log-drawer__body .action-log-entry:last-child {
  border-bottom: none;
}

.action-log-entry--empty {
  grid-template-columns: 1fr;
  font-style: italic;
  color: var(--color-text-muted);
  padding: var(--space-xs) 0;
}

.action-log-entry--empty .action-log-entry__summary {
  white-space: normal;
  text-overflow: clip;
  color: var(--color-text-muted);
}

/* Give the expanded body's empty state a bit more breathing room
   per the spec ("more vertical room"). */
.action-log-drawer__body .action-log-entry--empty {
  padding: var(--space-md) 0;
  text-align: center;
}

.action-log-entry__ts {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-text-muted);
  white-space: nowrap;
  flex-shrink: 0;
}

.action-log-entry__kind {
  font-family: var(--font-heading-ui);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.05rem 0.4rem;
  border: 1px solid currentColor;
  border-radius: 0;
  color: var(--color-text-muted);
  white-space: nowrap;
  flex-shrink: 0;
  min-width: 3.4rem;
  text-align: center;
  line-height: 1.3;
}

/* Per-kind chip accents — reuse existing semantic tokens where possible. */
.action-log-entry__kind--hp     { color: var(--color-damage); }
.action-log-entry__kind--attack { color: var(--color-attack); }
.action-log-entry__kind--spell  { color: var(--color-cast); }
.action-log-entry__kind--item   { color: #d39a4a; }            /* warm amber matches .btn--item-light */
.action-log-entry__kind--rest   { color: var(--color-rest); }
.action-log-entry__kind--dice   { color: var(--color-text-muted); }
.action-log-entry__kind--class  { color: var(--color-accent); }
.action-log-entry__kind--luck   { color: var(--color-luck); }            /* topaz/gold — fortune */

.action-log-entry__summary {
  font-family: var(--font-body);
  font-size: 0.88rem;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0; /* allow ellipsis inside grid cell */
}

/* ============================================================
   WEAPONS SECTION — Phase 3
   Cards rendered inside #section-weapons from char.loadout.
   Each row: name (left) · damage formula (mono center) · ATTACK btn (right).
   Inline result strip appears below the card after an attack resolves.
   ============================================================ */

.weapon-card {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid rgba(255,255,255,0.06);
}

.weapon-card:last-of-type {
  border-bottom: none;
}

.weapon-card__name {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  color: var(--color-text);
  letter-spacing: 0.02em;
}

.weapon-card__formula {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: rgba(255,255,255,0.72);
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Combat weapon-card / spell-card name — single line, ellipsis on overflow
   so the formula + button stay aligned on the same row. */
.weapon-card__name,
.spell-card__name {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ATTACK button — steel/silver, distinct from the red DAMAGE button so the
   player never confuses "attack roll" (offensive action) with "damage to
   self" (HP loss). */
.btn--attack {
  background-color: transparent;
  color: var(--color-attack);
  border: 1px solid var(--color-attack);
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.1rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--attack:hover:not(:disabled) {
  background-color: var(--color-attack);
  color: var(--color-bg);
  border-color: var(--color-attack);
}

.btn--attack:focus-visible {
  outline: 2px solid var(--color-attack);
  outline-offset: 2px;
}

.btn--attack:disabled {
  opacity: 0.45;
  cursor: wait;
}

/* Inline result strip — appears below the card row after [ATTACK] resolves */
.weapon-card__result {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding-top: var(--space-sm);
}

.weapon-card__dice-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-left: var(--space-xs);
}

/* Dice tiles rendered into the result strip are non-interactive — they
   exist only to animate the roll result. */
.weapon-card__dice-row .die-tile--mini[disabled] {
  cursor: default;
}

.weapon-card__chip {
  font-family: var(--font-heading-ui);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.18rem 0.55rem;
  border: 1px solid currentColor;
}

.weapon-card__chip--crit {
  color: var(--color-accent);
  background: rgba(100,149,237,0.08);
}

.weapon-card__chip--fumble {
  color: var(--color-damage);
  background: rgba(176,48,48,0.08);
}

.weapon-card__result-lines {
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--color-text-muted);
  padding-left: var(--space-xs);
}

.weapon-card__result-line {
  margin: 0;
  padding: 0.1rem 0;
}

.weapon-card__result-line strong {
  font-family: var(--font-mono);
  color: var(--color-text);
  font-weight: 600;
}

.weapon-card__result-line--damage strong {
  color: var(--color-accent);
}

/* ============================================================
   SPELLS SECTION — Phase 4
   Cards rendered inside #section-spells from char.spells.
   Section is hidden entirely for non-spellcasters.
   Each row: name (left) · tier (mid) · DC (mid-right) · CAST btn or LOST chip (right).
   Inline result strip appears below the card after a cast resolves.
   Lost cards remain visible but muted until Rest.
   ============================================================ */

.spell-card {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid rgba(255,255,255,0.06);
}

.spell-card:last-of-type {
  border-bottom: none;
}

.spell-card__name {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  color: var(--color-text);
  letter-spacing: 0.02em;
}

.spell-card__tier,
.spell-card__dc {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: rgba(255,255,255,0.72);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* CAST button — arcane/violet, distinct from attack steel and damage red. */
.btn--cast {
  background-color: transparent;
  color: var(--color-cast);
  border: 1px solid var(--color-cast);
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.1rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--cast:hover:not(:disabled) {
  background-color: var(--color-cast);
  color: var(--color-bg);
  border-color: var(--color-cast);
}

.btn--cast:focus-visible {
  outline: 2px solid var(--color-cast);
  outline-offset: 2px;
}

.btn--cast:disabled {
  opacity: 0.45;
  cursor: wait;
}

/* LOST state — card stays visible but muted. The CAST button is replaced by
   the LOST chip so the player sees what is unavailable until Rest. */
.spell-card--lost {
  opacity: 0.5;
}

.spell-card__chip {
  font-family: var(--font-heading-ui);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.18rem 0.55rem;
  border: 1px solid currentColor;
}

.spell-card__chip--lost {
  color: var(--color-text-muted);
  background: rgba(255,255,255,0.04);
}

.spell-card__chip--crit {
  color: var(--color-accent);
  background: rgba(100,149,237,0.08);
}

.spell-card__chip--fumble {
  color: var(--color-damage);
  background: rgba(176,48,48,0.08);
}

/* Inline result strip — appears below the card row after [CAST] resolves */
.spell-card__result {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding-top: var(--space-sm);
}

.spell-card__dice-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-left: var(--space-xs);
}

.spell-card__dice-row .die-tile--mini[disabled] {
  cursor: default;
}

.spell-card__result-lines {
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--color-text-muted);
  padding-left: var(--space-xs);
}

.spell-card__result-line {
  margin: 0;
  padding: 0.1rem 0;
}

.spell-card__result-line strong {
  font-family: var(--font-mono);
  color: var(--color-text);
  font-weight: 600;
}

.spell-card__result-line--success strong {
  color: var(--color-accent);
}

.spell-card__result-line--fail strong {
  color: var(--color-damage);
}

/* ------------------------------------------------------------
   Wizard Mishap line (Phase 8)
   Distinct from --fail: the spell already lost (red); the mishap
   itself is a separate, wild/chaotic event from the rulebook's
   mishap tables. Hellfire-orange accent + warning glyph + extra
   vertical padding so the 1-3 sentences breathe and don't crash
   into the next spell card.
   ------------------------------------------------------------ */
.spell-card__result-line--mishap {
  margin-top: 0.35rem;
  padding: 0.45rem 0.55rem;
  border-left: 2px solid var(--color-wizard-mishap, #d35a1a);
  background: rgba(211, 90, 26, 0.07);
  color: var(--color-text);
  line-height: 1.45;
}

.spell-card__result-line--mishap::before {
  content: "\26A0\FE0E\00A0";  /* warning sign, text-style */
  color: var(--color-wizard-mishap, #d35a1a);
  font-weight: 700;
}

.spell-card__result-line--mishap strong {
  font-family: var(--font-ui, var(--font-mono));
  color: var(--color-wizard-mishap, #d35a1a);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
}

.spell-card__result-line--mishap em {
  color: var(--color-text-muted);
  font-style: italic;
}

/* Fallback variant when the mishap roll API is unreachable. */
.spell-card__result-line--mishap-unavailable {
  border-left-color: var(--color-text-muted);
  background: rgba(255, 255, 255, 0.03);
}

.spell-card__result-line--mishap-unavailable::before {
  color: var(--color-text-muted);
}

.spell-card__empty {
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text-muted);
  font-style: italic;
  padding: var(--space-sm) 0;
}

/* ============================================================
   ITEMS SECTION — Phase 5
   One card per consumable in char.equipment. Card layout mirrors
   spell-card (name on the left, action button on the right).
   ============================================================ */

.item-card {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.item-card:last-of-type {
  border-bottom: none;
}

.item-card__name {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  color: var(--color-text);
  letter-spacing: 0.02em;
}

/* Action buttons — USE (neutral steel), DRINK (heal green), LIGHT (warm amber)
   USE shares the steel palette with attack to feel like a generic action;
   DRINK borrows --color-heal because every drinkable in SD is a heal/buff;
   LIGHT gets its own warm amber token so torches read as fire, not poison. */
.btn--item {
  background-color: transparent;
  border: 1px solid currentColor;
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.1rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--item:disabled {
  opacity: 0.45;
  cursor: wait;
}

.btn--item-use {
  color: var(--color-attack);
}

.btn--item-use:hover:not(:disabled) {
  background-color: var(--color-attack);
  color: var(--color-bg);
}

.btn--item-use:focus-visible {
  outline: 2px solid var(--color-attack);
  outline-offset: 2px;
}

.btn--item-drink {
  color: var(--color-heal);
}

.btn--item-drink:hover:not(:disabled) {
  background-color: var(--color-heal);
  color: var(--color-bg);
}

.btn--item-drink:focus-visible {
  outline: 2px solid var(--color-heal);
  outline-offset: 2px;
}

/* Warm amber for fire-based items — torch, lantern, oil. Local token,
   limited blast radius; promote to base.css if other components want it. */
.btn--item-light {
  --color-item-light: #d39a4a;
  color: var(--color-item-light);
}

.btn--item-light:hover:not(:disabled) {
  background-color: var(--color-item-light);
  color: var(--color-bg);
}

.btn--item-light:focus-visible {
  outline: 2px solid var(--color-item-light);
  outline-offset: 2px;
}

/* ============================================================
   ACTION MODAL — Phase 5 (reusable confirmation dialog)
   Full-page dim scrim, centred card, focus-trapped. Used for any
   destructive in-app action that needs explicit confirmation
   (consumable use today; future class abilities tomorrow).
   See _team/agatha.md and memory/feedback_destructive_confirmations.md.
   ============================================================ */

.action-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background-color: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  /* Subtle fade-in so the scrim doesn't feel jarring on every consumable */
  animation: action-modal-fade-in 120ms ease-out;
}

@keyframes action-modal-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .action-modal { animation: none; }
}

.action-modal__card {
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04), 0 12px 32px rgba(0, 0, 0, 0.6);
  padding: var(--space-lg);
  min-width: min(420px, 100%);
  max-width: 520px;
  /* Phase 1 mobile optimization (KDL 2026-06-27, 2026-06-14): cap the card to the
     viewport and scroll internally so a tall body (long monster stat block,
     multi-paragraph spell/rule) never clips off-screen on a 375px phone with the
     close button out of reach. dvh tracks the iOS URL bar. A no-op on desktop /
     short content (the card stays its natural height). The scrim already pads
     the card off the edges (.action-modal padding: --space-lg). */
  max-height: calc(100dvh - 2 * var(--space-lg));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.action-modal__title {
  font-family: var(--font-heading-ui);
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--color-border);
  box-shadow: 0 2px 0 0 var(--color-border);
}

.action-modal__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

.action-modal__body-line {
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--color-text);
  margin: 0;
  line-height: 1.4;
}

.action-modal__body-line strong {
  font-family: var(--font-mono);
  color: var(--color-text);
}

.action-modal__body-line--warn {
  color: var(--color-text-muted);
  font-style: italic;
}

.action-modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

/* Level-up / graduation wizard footers carry Back | (Reroll) | Continue. The
   Reroll is js-reroll and hides in Hardcore, which would leave Back hugging
   Continue at the right. Pin Back to the left so the row brackets cleanly with
   or without the Reroll — also tidies the normal-mode three-button row. */
.action-modal__actions [data-lvl-back],
.action-modal__actions [data-grad-back] {
  margin-right: auto;
}

/* ============================================================
   FOUNDRY / SHADOWDARKLINGS IMPORT RESULT MODAL  (F3 Phase 1, KDL 2026-06-27)
   Reuses the .action-modal scrim + .action-modal__card chrome (which already
   caps to the viewport and scrolls internally on a 390px phone). Only the
   "Imported with notes" list needs its own scaffolding. All values are inserted
   via textContent in character.js (F-6 output-safety gate); no styling here ever
   relies on imported markup.
   ============================================================ */
.char-import-result__notes-head {
  font-family: var(--font-heading-ui);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.78rem;
  color: var(--color-text);
  margin-top: var(--space-sm);
}

.char-import-result__flags {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.char-import-result__flag {
  font-family: var(--font-body);
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--color-text);
  /* A thin left rule marks each note item without a heavy bullet — keeps the
     OSR restraint while still scanning as a list. */
  border-left: 2px solid var(--color-border);
  padding-left: var(--space-sm);
  /* Long, attacker-controlled values must wrap, never force horizontal scroll
     on a 390px phone (also blunts a single-long-token layout-break attempt). */
  overflow-wrap: anywhere;
  word-break: break-word;
}

.char-import-result__flag-label {
  color: var(--color-text-muted);
}

.char-import-result__flag-value {
  color: var(--color-text);
}

/* ============================================================
   CENTRAL MODAL / OVERLAY CLOSE BUTTON  (Ken, 2026-06-11)
   One themed close-button rule every modal/overlay shares, so the
   recurring "white close button" bug stops being patched per-file.
   Two forms:
     .modal-close       — a full text "Close" button (secondary look)
     .modal-close--icon — an icon-only "X" close (accent on hover,
                          theme text colour at rest — never white)
   Existing per-feature close classes (admin-view-overlay__close,
   maps-roomedit__close, world-legend__close) are mapped onto these
   tokens below so they read identically across the app.
   ============================================================ */
.modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background-color: var(--color-bg);
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.1rem;
  cursor: pointer;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.modal-close:hover,
.modal-close:focus-visible {
  background-color: var(--color-accent);
  color: var(--ink-on-accent, var(--color-text));
  border-color: var(--color-accent);
}

.modal-close:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Icon-only "X" close (no chrome at rest; accent on hover; theme text colour —
   NEVER white). Used by overlay corner-close glyphs. */
.modal-close--icon {
  width: 2rem;
  height: 2rem;
  padding: 0;
  background-color: transparent;
  border-color: transparent;
  color: var(--color-text-muted);
  font-size: 1.3rem;
  line-height: 1;
}

.modal-close--icon:hover,
.modal-close--icon:focus-visible {
  background-color: transparent;
  border-color: transparent;
  color: var(--color-accent);
}

/* The admin / campaign read-only viewer "Close" lives in the overlay top bar.
   Map it onto the central secondary-look close so it is never white again. The
   class still positions it (margin-left:auto in admin.css); colours come here. */
.admin-view-overlay__close.btn {
  background-color: var(--color-bg);
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
}

.admin-view-overlay__close.btn:hover,
.admin-view-overlay__close.btn:focus-visible {
  background-color: var(--color-accent);
  color: var(--ink-on-accent, var(--color-text));
  border-color: var(--color-accent);
}

.btn--modal-cancel {
  background-color: transparent;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.1rem;
  cursor: pointer;
  transition:
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--modal-cancel:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}

.btn--modal-cancel:focus-visible {
  outline: 2px solid var(--color-text-muted);
  outline-offset: 2px;
}

/* Primary confirm button — base treatment matches .btn--item.
   The caller passes a verb class (btn--item-use / -drink / -light) to
   colour-match the trigger button on the card. Layout-only here; the
   verb class supplies the colour via currentColor on the border + fill
   on hover. */
.btn--modal-confirm {
  background-color: transparent;
  border: 1px solid currentColor;
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.4rem;
  cursor: pointer;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

/* Fallback colour ONLY when no verb class is present. Lower specificity
   than .btn--item-use|-drink|-light (which are single-class selectors that
   appear earlier in the file) so the verb class always wins when both are
   applied. The :not() chain keeps this rule from clobbering verb colours. */
.btn--modal-confirm:not(.btn--item-use):not(.btn--item-drink):not(.btn--item-light) {
  color: var(--color-attack);
}
.btn--modal-confirm:not(.btn--item-use):not(.btn--item-drink):not(.btn--item-light):hover {
  background-color: var(--color-attack);
  color: var(--color-bg);
}
.btn--modal-confirm:not(.btn--item-use):not(.btn--item-drink):not(.btn--item-light):focus-visible {
  outline: 2px solid var(--color-attack);
  outline-offset: 2px;
}

/* When the caller passes one of the verb classes, the existing
   .btn--item-* rules above already cover the colour, hover, and focus styles.
   Since those rules are defined EARLIER in the file with the same specificity,
   we use :not() above to ensure the verb classes win when both apply. */


/* ============================================================
   QUICK DICE — Phase 6
   Six utility roller buttons (d4..d20) inside #section-dice.
   Buttons render in a wrap-friendly flex row. Below them, a result
   strip hosts a single shared mini-die tile + a text readout that
   shows the latest roll. Only the D20 fires the existing crit overlay.
   ============================================================ */

.dice-button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: var(--space-sm);
  align-items: center;
}

/* Square-ish utility roller button. Outlined treatment matches the
   Phase 3/4/5 button family (.btn--attack, .btn--cast, .btn--item)
   so the dice row reads as part of the Actions panel vocabulary. */
.dice-btn {
  min-width: 3rem;
  height: 3rem;
  padding: 0 0.5rem;
  background-color: transparent;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    color var(--transition-fast),
    border-color var(--transition-fast),
    background-color var(--transition-fast);
}

.dice-btn:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
  background-color: rgba(255,255,255,0.03);
}

.dice-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.dice-btn:disabled {
  opacity: 0.45;
  cursor: wait;
}

/* Mark the D20 visually — it's the only utility roll that can trigger
   the crit/fumble overlay, so it carries a faint accent border. */
.dice-btn--d20 {
  border-color: rgba(100, 149, 237, 0.45); /* cornflower at 45% */
  color: var(--color-text);
}

.dice-btn--d20:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background-color: rgba(100, 149, 237, 0.06);
}

/* Result strip — appears below the button row after the first roll.
   Hosts the shared mini-die tile on the left and a mono text readout
   on the right. */
.dice-result-strip {
  margin-top: var(--space-sm);
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background-color: rgba(0, 0, 0, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.04);
}

.dice-result-strip__tile-host {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 52px;
}

/* Disable hover/active colour shifts on the shared tile — it's animation-
   only here, not a clickable affordance. Same approach as the weapon and
   spell cards' inline result dice. */
.dice-result-strip__tile-host .die-tile--mini[disabled] {
  cursor: default;
  filter: none;
  color: rgba(255,255,255,0.88);
}

.dice-result-strip__text {
  font-family: var(--font-mono);
  font-size: 1.1rem;
  color: var(--color-text);
  letter-spacing: 0.02em;
}


/* ============================================================
   REST — Phase 6
   Single [Rest] button + descriptive caption (dashboard rest action).
   Sage-green colour set apart from heal-green so the player reads the
   action as restorative-and-significant, not just topping off HP.
   ============================================================ */

.rest-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  margin-top: var(--space-sm);
}

.rest-row__caption {
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--color-text-muted);
  font-style: italic;
}

/* Outlined rest button — matches the Phase 3/4/5 outlined button family.
   Used both on the section row AND inside the confirm modal (passed as
   confirmClass: 'btn--rest'). */
.btn--rest {
  background-color: transparent;
  color: var(--color-rest);
  border: 1px solid var(--color-rest);
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.5rem 1.4rem;
  cursor: pointer;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.btn--rest:hover {
  background-color: var(--color-rest);
  color: var(--color-bg);
  border-color: var(--color-rest-hover);
}

.btn--rest:focus-visible {
  outline: 2px solid var(--color-rest-hover);
  outline-offset: 2px;
}

.btn--rest:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}


/* ============================================================
   Modal body bullet list — used by the Rest confirm modal to show
   the consequences of resting. Compact, indented, body-font.
   ============================================================ */
.action-modal__body-list {
  margin: 0;
  padding-left: 1.4rem;
  list-style: disc;
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.5;
}

.action-modal__body-list li {
  margin: 0;
}


/* ============================================================
   POLYHEDRAL 3D DICE TILES — Combat + Quick Dice + Roll Modal
   Extracted from app/public/dice_test.html ([data-style="3d"]
   selectors, stone-texture default). Used by:
     - Roll modal (.roll-modal__dice host) — attack + cast + damage
     - Quick Dice section (#section-dice, in-place click-to-roll)
   The hero PNG mask is loaded via inline --hero-img style attribute
   (mirrored from dice_test.html). The number overlay (.die-face-num)
   shows the rolled value centred on the die.
   ============================================================ */

/* Tile shell — 94px square host; the PNG mask gives it its shape and
   the currentColor fill tints the artwork. The animated face number
   sits on top via z-index. */
.die-tile-3d {
  --glow:      rgba(220,190,110,0.50);
  --glow-wide: rgba(170,135, 55,0.25);
  position: relative;
  width: 94px;
  height: 94px;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
  border: none;
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  background: transparent;
  cursor: pointer;
  color: rgba(255,255,255,0.88);
  filter: drop-shadow(0 0 6px var(--glow)) drop-shadow(0 0 18px var(--glow-wide));
  transition: color 0.35s ease, filter 0.35s ease;
}

.die-tile-3d:hover {
  color: rgba(240,200,100,0.90);
  filter: drop-shadow(0 0 12px var(--glow)) drop-shadow(0 0 28px var(--glow-wide));
}

/* Mouse-press feedback — matches dice_test.html where the base .die-tile uses
   :active bluePulse to signal "this die is being pressed/rolled". The pulse
   stops when the press is released; the number-cycling on click carries the
   rest of the roll-in-progress feedback. */
.die-tile-3d:active {
  animation: bluePulse 2.2s ease-in-out infinite;
  color: rgba(180,210,255,0.94);
}

.die-tile-3d:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
}

.die-tile-3d:disabled,
.die-tile-3d[disabled] {
  cursor: default;
}

/* PNG hero artwork tinted via currentColor — the `.die-hero-img` block
   defined earlier in this file (the shared SHARED — die hero image mask)
   already provides the mask plumbing. */

/* Rolled-number overlay — shown on every 3D tile.
   Mirrors dice_test.html .die-face-num exactly: plain currentColor, no
   text-shadow stack. The hero PNG mask underneath provides enough contrast
   that the number reads cleanly against the polyhedral artwork. */
.die-tile-3d .die-face-num {
  display: flex;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading-ui);
  font-size: 2rem;
  font-weight: 700;
  color: currentColor;
  pointer-events: none;
  z-index: 1;
}

/* The shared .die-hero-img class is display:none by default; enable for 3D */
.die-tile-3d .die-hero-img {
  display: block;
}

/* Per-die clip-path shaping for accurate touch-area + outline.
   Mirrors dice_test.html geometry. */
.die-tile-3d--d4 {
  clip-path: polygon(50% 2%, 98% 98%, 2% 98%);
  transform: scale(0.82);
}
.die-tile-3d--d4 .die-hero-img {
  -webkit-mask-size: 130%; mask-size: 130%;
}

.die-tile-3d--d6 {
  clip-path: polygon(12% 0%, 88% 0%, 100% 12%, 100% 88%, 88% 100%, 12% 100%, 0% 88%, 0% 12%);
  transform: scale(0.86);
}
.die-tile-3d--d6 .die-hero-img {
  -webkit-mask-size: 115%; mask-size: 115%;
}

.die-tile-3d--d8  { clip-path: polygon(50% 2%, 98% 50%, 50% 98%, 2% 50%); }
.die-tile-3d--d10 { clip-path: polygon(50% 2%, 100% 40%, 80% 98%, 20% 98%, 0% 40%); }
.die-tile-3d--d12 { clip-path: polygon(50% 0%, 75% 7%, 93% 25%, 100% 50%, 93% 75%, 75% 93%, 50% 100%, 25% 93%, 7% 75%, 0% 50%, 7% 25%, 25% 7%); }
.die-tile-3d--d20 { clip-path: polygon(50% 0%, 79% 9%, 98% 35%, 96% 64%, 78% 88%, 50% 98%, 22% 88%, 4% 64%, 2% 35%, 21% 9%); }

/* LASER wireframe override — when the Laser engine is mounted onto a tile
   (.die-tile-3d--laser, added by sdMountLaserDie), the visual is a tumbling 3D
   polyhedron drawn on a fill canvas, NOT the PNG hero silhouette. The per-die
   clip-paths above are sized to the static PNG outline and shear off the
   wireframe's outermost projected vertices, edge stroke, and glow. Drop the
   clip entirely for laser tiles, and undo the d4/d6 static down-scale, so the
   full die renders with breathing room. Wins via later source order + the
   extra class (same specificity as the per-die rule, declared after it). */
.die-tile-3d--laser { clip-path: none; transform: none; }

/* Tumble animation — engaged during the roll. Faster than forgePulse;
   pairs with the JS number-cycling so the visual movement matches the
   number swap. Removed when the roll lands so the final face reads still. */
.die-tile-3d.is-rolling {
  animation: dieTumble 0.6s ease-in-out infinite;
}

@keyframes dieTumble {
  0%   { transform: rotate(-8deg) scale(1); filter: drop-shadow(0 0 8px rgba(220,190,110,0.65)) drop-shadow(0 0 22px rgba(170,135,55,0.36)); }
  50%  { transform: rotate( 6deg) scale(1.05); filter: drop-shadow(0 0 14px rgba(240,200,100,0.88)) drop-shadow(0 0 30px rgba(200,150,60,0.50)); }
  100% { transform: rotate(-8deg) scale(1); filter: drop-shadow(0 0 8px rgba(220,190,110,0.65)) drop-shadow(0 0 22px rgba(170,135,55,0.36)); }
}

/* Per-die scale-aware tumble — d4 and d6 use a scaled-down resting
   transform; the tumble keyframe needs to multiply that, not replace it. */
.die-tile-3d--d4.is-rolling {
  animation: dieTumbleD4 0.6s ease-in-out infinite;
}
.die-tile-3d--d6.is-rolling {
  animation: dieTumbleD6 0.6s ease-in-out infinite;
}
@keyframes dieTumbleD4 {
  0%, 100% { transform: scale(0.82) rotate(-8deg); }
  50%      { transform: scale(0.86) rotate( 6deg); }
}
@keyframes dieTumbleD6 {
  0%, 100% { transform: scale(0.86) rotate(-8deg); }
  50%      { transform: scale(0.90) rotate( 6deg); }
}

@media (prefers-reduced-motion: reduce) {
  .die-tile-3d.is-rolling,
  .die-tile-3d--d4.is-rolling,
  .die-tile-3d--d6.is-rolling {
    animation: none;
  }
}

/* Crit / fumble glow tint for the landed face on a D20 — applied via
   class added by JS after the roll lands. Kept on the modal long enough
   for the player to register it. */
.die-tile-3d--crit {
  --glow:      rgba(240,200,100,0.85);
  --glow-wide: rgba(200,140, 30,0.45);
  color: rgba(255,230,170,0.96);
}
.die-tile-3d--fumble {
  --glow:      rgba(200, 60, 60,0.78);
  --glow-wide: rgba(160, 30, 30,0.42);
  color: rgba(255,180,180,0.92);
}


/* ============================================================
   ROLL MODAL — display-only modal for ATTACK / CAST dice reveals.
   Distinct from .action-modal (Phase 5 confirmation dialog) — this
   has no buttons, no Cancel/Confirm. Pure visual feedback. The
   inline result line on the card remains as the permanent record;
   this modal is the celebration beat.
   ============================================================ */

.roll-modal {
  position: fixed;
  inset: 0;
  z-index: 1100; /* above .action-modal (1000) so a stacked Rest+cast read clean */
  background-color: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  animation: roll-modal-fade-in 220ms ease-out;
  cursor: pointer; /* hint that backdrop click dismisses */
}

.roll-modal.is-closing {
  animation: roll-modal-fade-out 300ms ease-in forwards;
}

@keyframes roll-modal-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes roll-modal-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .roll-modal,
  .roll-modal.is-closing {
    animation: none;
  }
}

.roll-modal__card {
  background:
    radial-gradient(ellipse at 30% 20%, #2a241c 0%, #1e1a16 75%);
  border: 1px solid var(--color-border);
  border-top: 3px solid var(--color-accent);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04), 0 18px 48px rgba(0, 0, 0, 0.78);
  padding: var(--space-xl) var(--space-xl) var(--space-lg);
  min-width: min(440px, 100%);
  max-width: 560px;
  /* Phase 1 mobile optimization (KDL 2026-06-27, 2026-06-14): same viewport cap +
     internal scroll as .action-modal__card so the dice overlay fits a 375px
     phone. No-op on desktop / the short dice content. */
  max-height: calc(100dvh - 2 * var(--space-lg));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  cursor: default;
}

.roll-modal__title {
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
  text-align: center;
}

/* Dice arena — hosts 1-3 polyhedral tiles (single-die cast, attack+damage,
   etc.). Spacing scales with tile count via gap. */
.roll-modal__dice {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  padding: var(--space-md) 0;
  min-height: 110px;
}

/* No-glow variant — the item roller opts in via overlayClass (showRollModal).
   Same treatment as the stat-check modal: strip the shared .die-tile-3d glow
   filter so no fog/halo sits behind the dice, and swap the tumble keyframes for
   the glow-free variant (dieTumbleNoGlow, defined in char-dashboard.css) so no
   haze flashes mid-roll. Opted into by the item roller and the spell/combat
   roll modals (attack/cast/initiative/save) so none show a halo. */
.roll-modal--no-glow .roll-modal__dice .die-tile-3d,
.roll-modal--no-glow .roll-modal__dice .die-tile-3d:hover {
  filter: none;
}
.roll-modal--no-glow .roll-modal__dice .die-tile-3d.is-rolling {
  animation-name: dieTumbleNoGlow;
}

.roll-modal__die-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
}

.roll-modal__die-label {
  font-family: var(--font-heading-ui);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Final result text — appears after dice land. Shows the totals + outcome
   tag (e.g. "17 vs ?,  6 dmg" or "Cast Magic Missile (DC 11): 14 — SUCCESS"). */
.roll-modal__result {
  font-family: var(--font-mono);
  font-size: 1.15rem;
  color: var(--color-text);
  text-align: center;
  min-height: 1.3em;
  letter-spacing: 0.02em;
  padding-top: var(--space-xs);
}

.roll-modal__result strong {
  color: var(--color-text);
}

/* Secondary line under a roll result (e.g. the HP formula). */
.roll-modal__sub {
  display: inline-block;
  font-size: 0.8rem;
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
}

.roll-modal__result--crit    strong { color: rgba(240,200,100,0.96); }
.roll-modal__result--fumble  strong { color: rgba(220, 90, 90,0.96); }
.roll-modal__result--success strong { color: rgba(160,230,180,0.92); }
.roll-modal__result--fail    strong { color: rgba(220,180,180,0.85); }


/* ============================================================
   COMBAT SECTION — Weapons + Spells merged (Actions Panel revision)
   Single heading; weapons render first, spells second (optionally
   divided by a thin rule). The existing .weapon-card / .spell-card
   styling stays untouched — this only adds the wrapper layout.
   ============================================================ */

.combat-divider {
  height: 1px;
  border: 0;
  margin: var(--space-sm) 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.10) 22%,
    rgba(255, 255, 255, 0.18) 50%,
    rgba(255, 255, 255, 0.10) 78%,
    transparent 100%
  );
}


/* ============================================================
   QUICK DICE POLYHEDRAL OVERRIDE
   Replaces the outlined .dice-btn appearance with full 3D polyhedral
   tiles when the button carries .dice-btn--3d. The button itself is
   the rollable die; the result lands on its face. The text dice label
   ("d4", "d6") moves below the tile as a caption.
   ============================================================ */

.dice-button-row--3d {
  gap: var(--space-md);
  align-items: flex-start;
}

.quick-dice-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.18rem;
}

.quick-dice-cell__label {
  font-family: var(--font-heading-ui);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  user-select: none;
}

/* Cornflower-accent variant for the D20 quick-dice tile so it reads as
   "the die that fires crits/fumbles" — matches the Phase 6 visual cue
   that the rest of the dice family does not get crit overlays. */
.die-tile-3d--d20.die-tile-3d--accented {
  --glow:      rgba(100,149,237,0.62);
  --glow-wide: rgba( 70,110,200,0.32);
}

.die-tile-3d--d20.die-tile-3d--accented:hover {
  color: rgba(160,200,255,0.96);
  --glow:      rgba(130,180,255,0.85);
  --glow-wide: rgba(100,149,237,0.48);
}

/* ============================================================
   Mobile touch-target floor — Phase 1 mobile optimization
   (KDL 2026-06-27, 2026-06-14)

   MOBILE-ONLY (<= --bp-tab, 700px) per Ken's decision: preserve the desktop
   density KDL 2026-06-27 tuned, but on phones bring interactive controls up to the ~44px
   minimum touch target (WCAG 2.5.5 / Apple HIG). We grow the HIT AREA, not the
   visual glyph: icon buttons keep their small icon but gain padding/min-size so
   the tappable region is finger-sized. Scoped to <=700px so nothing above the
   breakpoint changes. Kept last in the file so it wins the cascade for these
   base controls without needing !important (except where a tighter rule sets a
   fixed px width that we must override — reroll/refresh).
   ============================================================ */
@media (max-width: 700px) {
  .btn {
    min-height: 44px;
  }

  /* Per-row reroll (was 1.7rem ~27px square) and the inline refresh glyph (was
     16px) — grow the tap area to 44px while the SVG/glyph stays its drawn size,
     centered. !important overrides their fixed width/height. */
  .gen-row__reroll,
  .dm-refresh-btn {
    min-width: 44px !important;
    min-height: 44px !important;
    width: auto !important;
    height: 44px !important;
  }

  /* Shared icon-only close "X" (modal corners, drawer) — 2rem -> 44px hit area. */
  .modal-close--icon {
    min-width: 44px;
    min-height: 44px;
  }

  /* Text "Close" / confirm buttons already clear 44px via .btn above; the
     standalone .modal-close (non-.btn) gets the floor explicitly. */
  .modal-close {
    min-height: 44px;
  }
  /* NOTE: .char-dash__icon-btn (2.4rem fixed) is bumped to 44px in
     char-dashboard.css, which loads AFTER this file and sets a fixed width/height
     we must override there to win the cascade. */

  /* Phase 3 sweep (KDL 2026-06-27, 2026-06-14): per-row icon buttons still under ~44px
     in the touch path. Hit area grows to 44px; the glyph/SVG is unchanged. */

  /* Roster card delete (was svg 1.1rem + xs padding ~26px). Two fixes for touch:
     (1) it was opacity:0 until card-hover — unreachable without hover, so reveal
     it; (2) grow the tap area to 44px. */
  .char-card__delete {
    opacity: 1;
    min-width: 44px;
    min-height: 44px;
  }

  /* NOTE: .enc-card__remove (1.3rem fixed) is bumped to 44px in encounter.css,
     which loads AFTER this file and sets a fixed width/height we must override
     there to win the cascade. */

  /* Buy-Gear quantity stepper (-/+) — UA-default buttons in the wizard touch
     path; give them an explicit 44px tap target. The number field between them
     keeps its own width. */
  .buy-gear__qty-btn {
    min-width: 44px;
    min-height: 44px;
  }
}


/* ============================================================
   Hardcore mode — universal re-roll hide (ADDENDUM 2026-06-21b #7)
   ------------------------------------------------------------
   THE single rule that hides every re-roll affordance when the
   user's per-user `hardcore` profile flag is on. app.js sets
   `body.hardcore` from window.ShadowDark.hardcoreMode() (on boot
   and on the Profile toggle). CONVENTION: every re-roll control
   app-wide carries the shared class `js-reroll` (in addition to
   its own classes) so this one rule hides them all at once.
   Programmatic controls may instead guard on
   window.ShadowDark.rerollAllowed() at render time (form a) — the
   two forms are driven by the same flag and behave identically.
   This hides RE-rolls only; first-roll/PRODUCE controls and RAW
   rules mechanics (luck token, encounter initiative) are NOT
   tagged `js-reroll` and stay visible. !important wins over any
   per-control display so a single class reliably hides all.
   ============================================================ */
body.hardcore .js-reroll {
  display: none !important;
}


/* ============================================================
   Info-icon + tooltip (reusable; pilot on Taleweaver labels)
   ------------------------------------------------------------
   An "i" affordance beside a label that reveals a short plain-
   language explanation on hover / focus / tap. The tooltip is
   absolutely positioned (popover) so revealing never reflows the
   page. Reuses existing tokens only — no new colours.
   ============================================================ */
.info-icon {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
  margin-left: 0.3em;
  line-height: 1;
}

/* The trigger — a real, focusable button. Sized for a comfortable tap
   target while reading as a quiet inline glyph. */
.info-icon__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  font-family: var(--font-heading-ui);
  color: var(--color-text-muted);
  background-color: transparent;
  border: 1px solid var(--color-border);
  border-radius: 50%;          /* the one circular affordance — reads as "info" */
  cursor: help;
  transition: color var(--transition-ui), border-color var(--transition-ui);
}

.info-icon__btn:hover,
.info-icon:hover .info-icon__btn,
.info-icon__btn:focus-visible {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.info-icon__btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.info-icon__glyph {
  font-size: 0.7rem;
  font-style: italic;
  font-weight: 600;
  line-height: 1;
}

/* The tooltip — a small popover above the icon. [hidden] keeps it out of the
   a11y tree + layout when collapsed; .is-open reveals it. */
.info-icon__tip {
  position: absolute;
  bottom: calc(100% + 0.4rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  width: max-content;
  max-width: 18rem;
  padding: var(--space-xs) var(--space-sm);
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-style: normal;
  font-weight: 400;
  line-height: 1.4;
  text-transform: none;
  letter-spacing: var(--ls-default);
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
  white-space: normal;
}

@media (max-width: 520px) {
  .info-icon__tip {
    max-width: min(18rem, 80vw);
  }
}

/* Reduced motion: drop the trigger's colour transition (the reveal itself is a
   hard show/hide via [hidden], so there is nothing else to disable). */
@media (prefers-reduced-motion: reduce) {
  .info-icon__btn { transition: none; }
}

/* ==========================================================================
   Breadcrumb bar (app_roster_restructure_breadcrumbs.md #3, DN-11)
   App-level, interactive trail rendered into <nav id="app-breadcrumb"> above
   #app-content. Reuses the de-emphasized "parent › here" cue of the oracle
   eyebrow, but as a distinct NAV component (its own classes — the oracle crumb
   classes stay passive). Hidden at depth < 2; app.js toggles [hidden].
   Clickable crumbs are subtle links (underline on hover, accent focus ring);
   the current crumb is muted plain text. No new colour tokens.
   ========================================================================== */
.breadcrumb {
  max-width: 60rem;
  margin: 0;
  /* SITE-WIDE top spacing. The bar carries the top gap under the mode bar so it
     does not stack with #app-content's own top padding; a small bottom gap
     separates it from the content below.
     Top-rhythm tightening (KDL 2026-06-27, Ken): the top pad was
     --content-pad-top (~17px) which COMPOUNDED with the header's bottom padding
     (~25.5px) into a ~42px dead band above the breadcrumb. Tighten the bar's own
     top pad to --space-sm so the wordmark -> breadcrumb gap is a single tight
     rhythm (~8px), not a double band. --content-pad-top still drives #app-content
     for depth-1 (no-breadcrumb) pages — unchanged, so those don't regress.
     Left-alignment fix (KDL 2026-06-27, Ken): the horizontal pad was
     --space-md (16px), but #app-content and .app-header both inset their content
     by --space-xl (32px). That left the breadcrumb's left edge 16px LEFT of the
     wordmark / body-content left edge. Match the side pad to --space-xl so the
     breadcrumb's first crumb lines up exactly with the content column. Vertical
     pads (top --space-sm, bottom --space-xs) are unchanged — top-alignment and
     the tight-band rhythm stay intact. */
  padding: var(--space-sm) var(--space-xl) var(--space-xs);
}

.breadcrumb[hidden] { display: none; }

.breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25em;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  line-height: 1.3;
}

.breadcrumb__item {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25em;
  min-width: 0;
}

/* Clickable crumb — a button styled as a subtle link. */
.breadcrumb__crumb {
  appearance: none;
  background: none;
  border: 0;
  margin: 0;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--color-text-muted);
  cursor: pointer;
  max-width: 14em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.breadcrumb__crumb:hover {
  color: var(--color-text);
  text-decoration: underline;
}

.breadcrumb__crumb:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* A non-navigating group label (no onClick) — muted like the crumbs but inert. */
.breadcrumb__crumb--plain {
  cursor: default;
  text-decoration: none;
}

.breadcrumb__crumb--plain:hover {
  color: var(--color-text-muted);
  text-decoration: none;
}

.breadcrumb__sep {
  color: var(--color-text-muted);
  opacity: 0.7;
}

/* Current location — the last crumb. Muted plain text, never interactive. */
.breadcrumb__current {
  color: var(--color-text-muted);
  max-width: 18em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==========================================================================
   Pagination bar (pagination_profile_persisted, 2026-06-28)
   ONE shared control for every long roster/list page (built by app.js
   mountPagination). A per-page-size <select> (the value is the per-user
   profile listPageSize), a count summary, and Prev/Next + page indicator.
   Reuses existing tokens only; hard corners per the house rule.
   ========================================================================== */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm) var(--space-md);
  margin-top: var(--space-lg);
  padding: var(--space-sm) var(--space-md);
  border-top: 1px solid var(--color-border);
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
}

/* When there is nothing meaningful to page through AND the size is default, the
   helper marks the bar single — keep only the size selector visible so the user
   can still raise the page size; drop the empty Prev/Next + indicator. */
.pager--single .pager__nav { display: none; }

.pager__size {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.pager__size-label {
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: var(--ls-cinzel-caps);
}

.pager__size-select {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.2rem 0.4rem;
  cursor: pointer;
  transition: border-color var(--transition-ui);
}

.pager__size-select:hover { border-color: var(--color-accent); }
.pager__size-select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: var(--color-accent);
}

.pager__summary {
  margin: 0;
  flex: 1 1 auto;
  text-align: center;
  color: var(--color-text-muted);
}

.pager__nav {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.pager__btn {
  font-family: var(--font-heading-ui);
  font-size: var(--text-sm);
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.3rem 0.7rem;
  cursor: pointer;
  transition: color var(--transition-ui), border-color var(--transition-ui),
    background-color var(--transition-ui);
}

.pager__btn:hover:not([disabled]),
.pager__btn:focus-visible {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background-color: var(--color-surface-hover);
}

.pager__btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.pager__btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.pager__indicator {
  color: var(--color-text);
  white-space: nowrap;
  min-width: 7rem;
  text-align: center;
}

@media (max-width: 560px) {
  .pager { justify-content: center; }
  .pager__summary { order: 3; flex-basis: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .pager__btn,
  .pager__size-select { transition: none; }
}
