/* ============================================================
   Farrago — UI utility classes
   Extracted from repeated inline style props across JSX files.
   ============================================================ */

/* ── Wizard step animation ─────────────────────────────────── */

/* Initial hidden state; anime.js animates from opacity 0 → 1 */
.step-anim { opacity: 0; }

/* ── Wizard step typography ────────────────────────────────── */

/* Step number eyebrow: "Step Two · Ancestry" */
.step-eyebrow {
  font-family: var(--font-body);
  font-size: 0.625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 6px;
}

/* Step main heading: "My calling is…" */
.step-title {
  font-family: var(--font-display);
  font-size: 1.875rem;
  color: var(--ink-strong);
  line-height: 1.1;
}

/* Step explanatory sub-text */
.step-hint {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  color: var(--fg-muted);
  margin-top: 8px;
  line-height: 1.5;
}

/* Interior section label within a step body */
.section-eyebrow {
  font-family: var(--font-body);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 8px;
}

/* ── Wizard navigation bar ─────────────────────────────────── */

/* Back / Next row at the foot of each step */
.wizard-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* Ghost back button */
.wizard-back {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  padding: 9px 22px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.wizard-back:disabled {
  color: var(--fg-muted);
  cursor: default;
}

/* Primary continue / confirm button */
.wizard-next {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 500;
  padding: 9px 26px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.wizard-next:disabled {
  border-color: var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: default;
}

/* ── Step layout ───────────────────────────────────────────────────────── */

/* Root container for each wizard step */
.step-root {
  padding: 16px 24px 24px;
  max-width: 900px;
  margin: 0 auto;
}

/* Header block with rule beneath */
.step-header {
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 12px;
}

/* ── Modal overlays ────────────────────────────────────────── */

.modal-overlay {
  position: fixed;
  inset: 0;
  /* Sit above the fixed hand dock (z-index: 200) so modals like the
     ability-activation prompt aren't clipped at the bottom of the
     screen. The dock falls behind the overlay's backdrop-filter and
     gets the same blur + dim treatment as the rest of the page. */
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(30, 20, 10, 0.6);
  backdrop-filter: blur(2px);
}

.modal-header {
  padding: 16px 20px 14px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.modal-body {
  padding: 16px 20px;
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.modal-footer {
  padding: 12px 20px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

/* ── Choice cards (ChoiceCard component) ───────────────────────────────── */

.choice-card {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  border: 1.5px solid var(--border);
  padding: 4px;
  cursor: pointer;
  background: var(--surface);
  transition: border-color 180ms, background 180ms, box-shadow 180ms;
  user-select: none;
  box-sizing: border-box;
}

.choice-card.is-selected {
  border-color: var(--sepia);
  background: var(--paper-deep);
  box-shadow: 0 2px 10px rgba(0,0,0,0.12);
}

/* "Common for this location" accent. Lower specificity than .is-selected
   so the selection state overrides it on click; the inner border picks up
   the same cinnabar so the accent reads when the card sits unselected. A
   second cinnabar arc chases around the outer border, drawing the eye to
   the guidebook-paired picks. */
@property --chase-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

.choice-card--common {
  position: relative;
  border-color: var(--rubric);
}
.choice-card--common .choice-card__inner {
  border-color: var(--rubric);
}
.choice-card--common::before {
  content: '';
  position: absolute;
  box-sizing: border-box;
  inset: -3px;
  pointer-events: none;
  z-index: 2;
  padding: 3px;
  background: conic-gradient(
    from var(--chase-angle),
    transparent 0deg,
    color-mix(in srgb, var(--rubric) 35%, transparent) 18deg,
    var(--rubric) 45deg,
    color-mix(in srgb, var(--rubric) 50%, #fff) 70deg,
    var(--rubric) 95deg,
    color-mix(in srgb, var(--rubric) 35%, transparent) 125deg,
    transparent 160deg,
    transparent 360deg
  );
  filter: drop-shadow(0 0 3px var(--rubric)) drop-shadow(0 0 7px color-mix(in srgb, var(--rubric) 70%, transparent));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: choice-card-chase 2.4s linear infinite;
}

@keyframes choice-card-chase {
  to { --chase-angle: 360deg; }
}

/* The Profile "Animations" toggle (body.animations-forced) keeps the chase
   alive even when the OS reports reduce-motion. Only kill it when the user
   has turned the toggle off AND the OS asks for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  body:not(.animations-forced) .choice-card--common::before { animation: none; }
}

/* Disabled variant — the card still presents its full description so
   the user can read about the option, but it can't be selected and is
   visually muted. The WIP badge anchors to the card's top-right. */
.choice-card--disabled {
  cursor: not-allowed;
  opacity: 0.55;
  filter: grayscale(60%);
  position: relative;
}
.choice-card--disabled:hover {
  border-color: var(--border);
  background: var(--surface);
  box-shadow: none;
}
.choice-card__wip-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--rubric);
  padding: 3px 8px;
  border-radius: 2px;
  pointer-events: none;
  opacity: 0.95;
  filter: grayscale(0%); /* don't double-grey the badge */
}

/* "Learn more" button on class choice cards. Pinned bottom-right and
   always visible — no hover-to-reveal trick, so players can see at a
   glance which classes have full info available. */
.choice-card__learn-more {
  position: absolute;
  bottom: 8px;
  right: 8px;
  z-index: 1;
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.06em;
  padding: 3px 8px;
  color: var(--fg-muted);
  background: var(--paper);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: color var(--dur-1), border-color var(--dur-1);
}
.choice-card__learn-more:hover {
  color: var(--ink-strong);
  border-color: var(--ink);
}

.choice-card__inner {
  flex: 1;
  border: 1px solid var(--border-soft);
  padding: 14px 12px 12px;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "label icon" "desc desc";
  align-items: start;
  gap: 8px;
  transition: border-color 180ms;
  overflow: hidden;
}

.is-selected .choice-card__inner {
  border-color: var(--sepia-soft);
}

.choice-card__icon {
  grid-area: icon;
  font-size: 1.75rem;
  line-height: 1;
  align-self: start;
}

.choice-card__label-group {
  grid-area: label;
  text-align: left;
  align-self: start;
}

.choice-card__name {
  font-family: var(--font-display);
  font-size: 1.2rem;
  color: var(--fg-strong);
  letter-spacing: 0.03em;
  line-height: 1.2;
}

.is-selected .choice-card__name {
  color: var(--ink-strong);
}

.choice-card__sublabel {
  font-family: var(--font-body);
  font-size: 0.625rem;
  color: var(--fg-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 3px;
}

.choice-card__desc {
  grid-area: desc;
  min-height: 0;
  overflow-y: auto;
  font-family: var(--font-body);
  font-size: 0.75rem;
  color: var(--fg);
  line-height: 1.55;
  text-align: left;
  width: 100%;
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
}

.choice-card__desc-mechanic {
  margin: 0;
}

.choice-card__desc-flavour {
  margin: 0.5em 0 0;
  font-style: italic;
}

/* ── Choice grid pagination (PaginatedChoiceGrid) ──────────────────────── */

/* Card grid wrapper — dynamic gridTemplateColumns/gridAutoRows/gap kept inline */
.choice-grid {
  display: grid;
  grid-template-columns: repeat(var(--cg-cols, 1), 1fr);
  grid-auto-rows: var(--cg-auto-rows, auto);
  gap: var(--cg-gap, 12px);
}

.choice-grid__pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 20px;
}

.choice-grid__dots {
  display: flex;
  gap: 6px;
  align-items: center;
}

.choice-grid__dot {
  width: 8px;
  height: 8px;
  border-radius: 4px;
  background: var(--border);
  cursor: pointer;
  transition: width 250ms ease, background 200ms;
}

.choice-grid__dot.is-active {
  width: 20px;
  background: var(--sepia);
}

.choice-grid__page-label {
  display: none;
  text-align: center;
  margin-top: 8px;
  font-family: var(--font-body);
  font-size: 0.6875rem;
  color: var(--fg-muted);
  letter-spacing: 0.06em;
}

.choice-grid__nav-btn {
  font-family: var(--font-display);
  font-size: 1rem;
  width: 36px;
  height: 36px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: color 150ms, border-color 150ms;
}

.choice-grid__nav-btn:disabled {
  border-color: var(--border-soft);
  color: var(--border);
  cursor: default;
}

/* ═══════════════════════════════════════════════════════════════
   Responsive — Wizard  (≤ 639 px, i.e. phone portrait)
═══════════════════════════════════════════════════════════════ */
@media (max-width: 639px) {

  /* ── Step root ── */
  .step-root {
    padding: 12px 14px 20px;
  }

  /* ── Step header typography ── */
  .step-title  { font-size: 1.25rem; }
  .step-hint   { margin-top: 4px; }

  /* ── Step header button row: wrap below title, right-align ── */
  .step-header {
    flex-wrap: wrap;
    row-gap: 10px;
  }
  /* Button container is always the last direct-child div */
  .step-header > div:last-child {
    margin-left: auto;
  }
  /* Both nav buttons share the available width equally */
  .step-header > div:last-child > .wizard-back,
  .step-header > div:last-child > .wizard-next {
    flex: 1;
    text-align: center;
    padding: 10px 8px;
  }

  /* ── Name step: trim excessive vertical padding ── */
  #step-name {
    padding: 32px 16px 40px !important;
  }
  #step-name .anim-in[style*="fontSize: 42"],
  #step-name [style*="fontSize: 42"] {
    font-size: 2rem !important;
  }

  /* ── Attributes grid: 3 col → 1 col ── */
  .wiz-attrs-grid {
    grid-template-columns: 1fr !important;
  }

  /* ── Skills grid: 2 col → 1 col; remove dividing border ── */
  .wiz-skills-grid {
    grid-template-columns: 1fr !important;
  }
  .wiz-skill-row-right {
    border-left: none !important;
  }
  /* Fix bottom-border: last item was previously 2nd-to-last in 2-col layout */
  .wiz-skills-grid > div:last-child {
    border-bottom: none !important;
  }

  /* ── Derived values grid: 4 col → 2 col ── */
  .wiz-derived-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }

  /* ── Class abilities grid: 3 col → 1 col; no fixed min-height ── */
  .wiz-abilities-grid {
    grid-template-columns: 1fr !important;
  }
  .wiz-abilities-grid .ability-preview-card > div {
    min-height: 0 !important;
  }

  /* ── Facets step: handled via JS conditional render (ChooseFacetsStep) ── */

  /* ── Shop panel: sidebar stacks below item list ── */
  #shop-layout {
    grid-template-columns: 1fr !important;
  }
  #shop-item-list  { max-height: 240px !important; }

  /* ── Choice cards: allow natural height in 1-col layout ── */
  /* (gridAutoRows switches to 'auto' via JS in PaginatedChoiceGrid) */
  .choice-card__desc {
    max-height: 120px;
  }

  /* ── Slotted inventory: item category hidden on mobile (space too tight) ── */
  .inv-grid-item .inv-item-type {
    display: none;
  }
}

/* ═══════════════════════════════════════════════════════════════
   Character Sheet — extracted inline styles
   cs- prefix = character sheet component
   inv- prefix = inventory (additions to existing)
   shop- prefix = shop panel (additions to existing)
   panel- prefix = draggable panel wrapper
═══════════════════════════════════════════════════════════════ */

/* ── FitLabel helper ────────────────────────────────────── */

/* Absolute probe span for text-width measurement */
.cs-fit-probe {
  position: absolute;
  visibility: hidden;
  white-space: nowrap;
  pointer-events: none;
}

/* ── ResourceBar ────────────────────────────────────────── */

.cs-resource-bar-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 4px;
}

.cs-resource-label {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.cs-resource-value {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--fg-strong);
  display: flex;
  align-items: center;
  gap: 1px;
}

.cs-resource-track {
  height: 10px;
  background: var(--paper-deep);
  border: 1px solid var(--border);
  position: relative;
}

/* Fill width is set inline (dynamic %) */
.cs-resource-fill {
  position: absolute;
  inset: 0;
  transition: width var(--dur-2, 200ms) var(--ease-quill, ease);
  width: var(--bar-pct, 0%);
  background: var(--bar-fill, var(--rubric));
}

.cs-resource-sep {
  color: var(--fg-muted);
}

/* ── AttrCluster ────────────────────────────────────────── */
.cs-attr-cluster {
  border: 1px solid var(--border);
  background: var(--surface);
  border-top: 2px solid var(--cluster-accent, var(--border));
}

.cs-attr-cluster-heading {
  padding: 6px 10px 0;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--cluster-accent, var(--fg));
}
/* Cluster-level penalty badge — appears next to the heading whenever
   untreated minor trauma or Shattered Soul is dragging the cluster
   down. Tabular numerals so the badge doesn't dance as values change. */
.cs-attr-cluster-pen {
  margin-left: 6px;
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 10px;
  font-weight: 600;
  color: var(--rubric);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  text-transform: none;
}
/* Per-attribute "→ N" annotation, shown only when effective ≠ base. */
.cs-attr-effective {
  margin-left: 6px;
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 11px;
  color: var(--rubric);
  font-variant-numeric: tabular-nums;
}
/* Positive (buff-raised) effective value reads green, not the trauma red. */
.cs-attr-effective--up { color: var(--verdigris-deep); }

.cs-attr-input--accent { color: var(--cluster-accent, var(--fg)); }

.cs-attr-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 5px 10px;
}

.cs-attr-row + .cs-attr-row {
  border-top: 1px solid var(--border-soft);
}

.cs-attr-name {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-strong);
}

/* ── SkillRow ───────────────────────────────────────────── */

.cs-skill-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border-soft);
}
.cs-skill-row--bg { background: var(--row-bg); }

.cs-skill-name {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
}

.cs-skill-pips {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cs-skill-pip-row {
  display: flex;
  gap: 3px;
}

/* Individual pip: background/border driven by JS value — kept inline */

.cs-skill-value {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--fg-strong);
  min-width: 16px;
  text-align: right;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  justify-content: flex-end;
}
.cs-skill-value--buffed {
  color: var(--verdigris-deep, var(--verdigris));
}
.cs-skill-value__buff-badge {
  font-size: 11px;
  font-style: italic;
  color: var(--verdigris-deep, var(--verdigris));
  padding: 1px 4px;
  border: 1px solid color-mix(in srgb, var(--verdigris) 50%, transparent);
  border-radius: 3px;
  background: color-mix(in srgb, var(--verdigris) 8%, transparent);
}

/* ── FacetCard / AbilityCard shared ─────────────────────── */

/* Flip container. Height comes from the front face (in normal flow);
   the back face overlays via absolute positioning so the 3D flip works
   without removing the front from the layout. Without this, the card's
   frame would stay pinned to .cs-ability-perspective's min-height and
   any expanded body content would overflow past the visible border. */
.cs-card-flipper {
  position: relative;
  width: 100%;
  min-height: inherit;
  transform-style: preserve-3d;
}

/* Common face shell (front & back). The front face stays in flow so it
   drives parent height as the body expands/collapses; the back face is
   absolutely positioned so it lands on top of the front when flipped. */
.cs-card-face {
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border: 1.5px solid var(--border);
  padding: 4px;
  background: var(--surface);
  box-sizing: border-box;
}

.cs-card-face:not(.cs-card-face--back) {
  position: relative;
  min-height: inherit;
}

.cs-card-face--back {
  position: absolute;
  inset: 0;
  transform: rotateY(180deg);
}

/* Inner framed content area */
.cs-card-inner {
  border: 1px solid var(--border-soft);
  padding: 10px 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  box-sizing: border-box;
}

/* Always-rendered body wrapper for AbilityCard / FacetCard. The
   collapse/expand animation drives `height` and `opacity` inline via
   anime.js; this class just makes sure mid-animation overflow is
   clipped and that the body lays out as a vertical stack matching the
   surrounding .cs-card-inner gap. */
.cs-card-body {
  display: flex;
  flex-direction: column;
  gap: inherit;
  overflow: hidden;
}

/* Name row inside a card face */
.cs-card-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cs-card-drag-handle {
  cursor: grab;
  color: var(--fg-muted);
  font-size: 15px;
  flex-shrink: 0;
  user-select: none;
  line-height: 1;
}

.cs-card-name {
  flex: 1;
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--ink-strong);
}

/* Thin divider line used inside cards */
.cs-card-divider {
  height: 1px;
  background: var(--border-soft);
}

/* Activate button shown on the expanded face of ability/facet cards.
   Mirrors the drag-to-play-zone path so a player who hasn't grasped the
   drag affordance has a discoverable click pathway to activation. */
.cs-card-activate-btn {
  align-self: flex-start;
  margin-top: 4px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 3px 10px;
  background: var(--surface);
  border: 1px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-1, 120ms), color var(--dur-1, 120ms);
}
.cs-card-activate-btn:hover {
  background: var(--ink);
  color: var(--paper);
}

/* Scrollable text body inside expanded card */
.cs-card-body-text {
  flex: 1;
  min-height: 0;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg);
  line-height: 1.55;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
}

/* Back-face: confirmation panel */
.cs-card-back-inner {
  border: 1px solid var(--border-soft);
  height: 100%;
  min-height: inherit;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  padding: 20px;
  text-align: center;
}

.cs-card-confirm-title {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--ink-strong);
  line-height: 1.5;
}

.cs-card-confirm-btns {
  display: flex;
  gap: 10px;
}

/* Ghost-style button (No / Cancel) */
.cs-btn-ghost {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 7px 22px;
  cursor: pointer;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
}

/* Primary / confirm button (Yes / Save) */
.cs-btn-primary {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 7px 22px;
  cursor: pointer;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
}

/* ── FacetCard specifics ────────────────────────────────── */

.cs-facet-cost {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--fg-muted);
  width: 24px;
  text-align: right;
  flex-shrink: 0;
}

.cs-facet-subtype {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sepia);
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: 2px;
  min-height: 14px;
}
/* Tag pills displayed on a facet card when the class facet carries
   a `tags` string (only class facets do; player-bought facets leave
   the tags blank). Visual language mirrors the ability-card tag
   pills in the Spend XP modal. */
.cs-facet-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}
.cs-facet-tag-pill {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
  border: 1px solid var(--rubric);
  border-radius: 2px;
  padding: 1px 6px;
  line-height: 1.4;
}

/* Five-stage progress strip displayed on a partially-paid facet
   card on the character sheet. Mirrors the picker's bar so a
   player can read their progress from either surface. */
.cs-facet-progress {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 6px 0 2px;
}
.cs-facet-progress-label {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
}
.cs-facet-progress-bar {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 3px;
}
.cs-facet-progress-cell {
  height: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 2px;
}
.cs-facet-progress-cell.is-paid {
  background: var(--verdigris-deep, #2f5e3b);
  border-color: var(--verdigris-deep, #2f5e3b);
}

/* ── AbilityCard specifics ──────────────────────────────── */

/* cost span — color/style driven by isPassive flag, kept inline;
   these are the fixed layout dimensions */
.cs-ability-cost {
  font-family: var(--font-body);
  font-size: 15px;
  /* Sized to two digits — the system cap is 60 WP. Passive cards
     (which carry the longer 'Passive' label) get their own min-width
     override below so the word doesn't truncate. */
  min-width: 24px;
  text-align: right;
  flex-shrink: 0;
  color: var(--fg-muted);
}
.cs-ability-cost--passive {
  min-width: 0;
}
.cs-ability-cost--passive { color: var(--sepia); font-style: italic; }

.cs-ability-tags {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.06em;
  color: var(--sepia);
  text-transform: uppercase;
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: 4px;
  min-height: 13px;
}

.cs-ability-meta-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.cs-ability-meta-label {
  font-family: var(--font-body);
  font-size: 8px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 2px;
  opacity: 0.7;
}

.cs-ability-meta-value {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
}

.cs-ability-meta-row {
  display: flex;
  align-items: baseline;
  gap: 3px;
}

.cs-ability-check-row {
  display: flex;
  align-items: baseline;
  gap: 4px;
}

.cs-ability-sep-dot {
  color: var(--border-soft);
  margin: 0 2px;
}

.cs-ability-vs {
  font-family: var(--font-body);
  font-size: 9px;
  font-weight: 700;
  color: var(--fg-muted);
  flex-shrink: 0;
}

/* ── SheetSection / DraggableSection ────────────────────── */

.cs-section {
  margin-top: 22px;
}

.cs-section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.cs-section-title {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fg-muted);
}
.cs-section-title--accent { color: var(--section-accent); }

.cs-section-rule {
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* Right-side label slot in a section header. Currently used by the
   Combat section to show the chosen Combat Style; could host other
   section-level info in the future. */
.cs-section-aside {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 1px 6px;
  color: var(--rubric-deep);
  cursor: help;
  border-bottom: 1px dotted var(--rubric-deep);
}
.cs-section-aside--empty {
  color: var(--fg-muted);
  border-bottom-style: dashed;
  border-bottom-color: var(--border-soft);
  font-style: italic;
  cursor: default;
}

/* Tooltip body sections used inside TooltipHint for Combat Style. */
.cs-style-tip-line {
  margin-bottom: 6px;
}
.cs-style-tip-line--notes {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--rubric-deep);
}
.cs-style-tip-line--flavor {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  margin-top: 6px;
  margin-bottom: 0;
}
.cs-style-tip-reactions {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 6px;
  font-size: 12px;
}
/* Log-line inline hover-target for a combat-style name (e.g. the
   Warmaster downtime log line). Dotted underline + help cursor signal
   that hovering shows the panel-style detail tooltip — the body of
   that tooltip reuses the .cs-style-tip-* classes below. */
.log-entry-style-name {
  cursor: help;
  border-bottom: 1px dotted currentColor;
  text-underline-offset: 2px;
}
.cs-style-tip-reactions strong {
  font-family: var(--font-display);
  color: var(--fg-strong);
}

/* Tooltip body sections used inside TooltipHint for attack tiles. The
   base typography (Ubuntu, 14px) is inherited from .tooltip-hint-body —
   these classes only add the per-line spacing and the verdigris/muted
   colour tints. */
.cs-attack-tip-target {
  margin-top: 4px;
  color: var(--verdigris-deep);
}
.cs-attack-tip-hint {
  margin-top: 4px;
  color: var(--fg-muted);
}

.cs-section-drag-handle {
  cursor: grab;
  color: var(--fg-muted);
  font-size: 15px;
  user-select: none;
  line-height: 1;
  padding: 0 2px;
  flex-shrink: 0;
}

/* ── CharacterLogTab ────────────────────────────────────── */

.cs-log-root {
  padding: 20px 24px 48px;
}

.cs-log-empty {
  padding: 32px 24px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  font-style: italic;
  text-align: center;
}

.cs-log-step-block {
  margin-bottom: 28px;
}

.cs-log-step-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}

.cs-log-step-title {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  white-space: nowrap;
}

.cs-log-entries {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.cs-log-card {
  border: 1.5px solid var(--border);
  padding: 4px;
}

.cs-log-card-inner {
  border: 1px solid var(--border-soft);
  padding: 12px 14px;
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.cs-log-icon {
  font-size: 24px;
  line-height: 1;
  flex-shrink: 0;
  margin-top: 2px;
}

.cs-log-entry-body {
  flex: 1;
  min-width: 0;
}

/* Entry label — marginBottom varies (2px if isOther, else 3px); keep inline */
.cs-log-entry-label {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--ink-strong);
  margin-bottom: 3px;
}
.cs-log-entry-label--tight { margin-bottom: 2px; }

.cs-log-custom-badge {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 5px;
}

.cs-log-skills-list {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sepia);
  margin-bottom: 5px;
}

.cs-log-textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
  line-height: 1.5;
  border: 1px solid var(--border-soft);
  background: var(--paper-deep);
  padding: 6px 8px;
  resize: vertical;
  min-height: 60px;
  box-sizing: border-box;
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
}

.cs-log-desc {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  line-height: 1.5;
}

/* ── Inventory modals ───────────────────────────────────── */

.inv-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.inv-modal-box {
  background: var(--paper);
  border: 1px solid var(--border);
  padding: 24px;
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.inv-modal-title {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--ink-strong);
}

.inv-modal-field-label {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.inv-modal-input {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 5px 8px;
  width: 100%;
  box-sizing: border-box;
}
.inv-modal-input--mono { font-family: var(--font-mono); }

.inv-modal-textarea {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 5px 8px;
  resize: vertical;
  line-height: 1.5;
}

.inv-modal-row {
  display: flex;
  gap: 12px;
}

.inv-modal-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}

.inv-modal-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 4px;
}

.inv-modal-btn-group {
  display: flex;
  gap: 8px;
}

.inv-modal-btn-remove {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 14px;
  border: 1px solid var(--rubric);
  background: transparent;
  color: var(--rubric);
  cursor: pointer;
}

.inv-modal-btn-cancel {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg-muted);
  cursor: pointer;
}

.inv-modal-btn-save {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 14px;
  border: 1px solid var(--border);
  background: var(--ink-strong);
  color: var(--paper);
  cursor: pointer;
}

/* ── InvItemRow ─────────────────────────────────────────── */

.inv-row {
  display: flex;
  align-items: center;
  background: var(--surface);
  cursor: grab;
  overflow: hidden;
  min-width: 0;
}

/* Normal list row */
.inv-row--list {
  gap: 6px;
  padding: 5px 8px;
  border-bottom: 1px solid var(--border-soft);
  box-sizing: border-box;
}

/* Compact grid row */
.inv-row--grid {
  gap: 4px;
  padding: 3px 6px;
  height: 100%;
  box-sizing: border-box;
}

/* Item name span — font-size and color depend on inGrid + item.name; keep inline */

.inv-row-ward {
  font-family: var(--font-mono);
  color: var(--verdigris-deep);
  flex-shrink: 0;
  font-size: 11px;
}

.inv-row-dmg {
  font-family: var(--font-mono);
  color: var(--rubric);
  flex-shrink: 0;
  font-size: 11px;
}

.inv-row-type {
  font-family: var(--font-body);
  color: var(--fg-muted);
  font-style: italic;
  flex-shrink: 0;
  max-width: 80px;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 11px;
  white-space: nowrap;
}

/* ── InventorySection ───────────────────────────────────── */

.inv-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 4px;
}

.inv-section-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--fg-strong);
  /* an accent override may be applied inline. */
}

.inv-section-cap-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

/* capacity text — color depends on isOver; kept inline */
.inv-section-cap-text {
  font-family: var(--font-mono);
  font-size: 11px;
}

.inv-section-bar {
  height: 2px;
  background: var(--border);
  margin-bottom: 6px;
}

.inv-section-bar-fill {
  height: 100%;
  transition: width 250ms;
  width: var(--bar-pct, 0%);
  background: var(--bar-fill, var(--rubric));
}

.inv-section-body {
  border: 1px solid var(--border);
  background: var(--surface);
}

.inv-section-list-inner {
  min-height: 28px;
}

.inv-section-empty {
  padding: 8px 12px;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
}

/* ── SlotGrid background cells ──────────────────────────── */
/* gridColumn/gridRow are dynamic — kept inline */
/* borderColor and background are also dynamic (usable flag) — kept inline */

/* ── InventoryTab — equipped section ───────────────────── */

.inv-equipped-panel {
  margin-bottom: 20px;
  border: 1px solid var(--border);
  background: var(--surface);
}

.inv-equipped-heading {
  padding: 6px 12px 0;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.inv-equipped-row {
  padding: 10px 12px 12px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: flex-start;
}

.inv-equip-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.inv-equip-group-label {
  font-family: var(--font-body);
  font-size: 8px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.inv-equip-weapons-row {
  display: flex;
  gap: 8px;
}

.inv-two-handed-note {
  align-self: flex-end;
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  font-style: italic;
  padding-bottom: 4px;
}

/* ── Equip slots (weapon / armour) ─────────────────────── */
/* width/height, border and background are static;
   border-color, cursor depend on isOn → kept inline for border
   via template literal; width/height fully static → CSS */

.inv-equip-slot {
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  transition: border-color 200ms;
  user-select: none;
  border: 1px dashed var(--border);
  cursor: default;
}
.inv-equip-slot--on {
  border: 2px solid var(--equip-accent, var(--ink));
  cursor: context-menu;
}
.inv-equip-slot--container {
  border-style: solid;
  border-width: 2px;
  border-color: var(--border);
  cursor: pointer;
  width: var(--equip-size, 56px);
  height: var(--equip-size, 56px);
  gap: 4px;
}
.inv-equip-slot--container.inv-equip-slot--dashed {
  border-style: dashed;
  border-width: 1px;
}
.inv-equip-slot--container.inv-equip-slot--on {
  border-style: solid;
  border-width: 2px;
  border-color: var(--equip-accent, var(--ink));
  cursor: context-menu;
}

.inv-equip-slot--weapon {
  width: 88px;
  height: 60px;
}

/* Tint / dot / label-on / sub-on share --equip-accent colour */
.inv-equip-slot-tint--accent { background: var(--equip-accent); }
.inv-equip-slot-dot--accent  { background: var(--equip-accent); }
.inv-equip-slot-label--accent { color: var(--equip-accent); }
.inv-equip-slot-label--muted  { color: var(--fg-muted); }
.inv-equip-slot-sub--accent   { color: var(--equip-accent); }
.inv-equip-armour-ward--accent { color: var(--equip-accent); }

.inv-equip-slot-tint {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.inv-equip-slot-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
}

/* Slot label / item name */
/* font-size 9px is the active default. Use --small (8px) for inactive/dashed
   states. Color may be set inline when an active accent applies. */
.inv-equip-slot-label {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: 1.3;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  padding: 0 4px;
  color: var(--fg-strong);
}

.inv-equip-slot-label--weapon {
  padding: 0 5px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.inv-equip-slot-sub {
  font-family: var(--font-body);
  font-size: 7px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

/* armour Ward badge */
.inv-equip-armour-ward {
  font-family: var(--font-mono);
  font-size: 9px;
}

/* ── InventoryTab top bar ───────────────────────────────── */

.inv-topbar {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 8px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.inv-topbar-buy-btn {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 16px;
  height: 56px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg-muted);
  cursor: pointer;
}

.inv-container-slots-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.inv-containers-label {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
  align-self: center;
  margin-right: 4px;
}

/* ── Gold input in tuckerbag header ─────────────────────── */

.inv-gold-row {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.inv-gold-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ── Buy Items modal ────────────────────────────────────── */

.inv-buy-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1000;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  padding: 40px 16px;
}

.inv-buy-modal-box {
  background: var(--paper);
  border: 1px solid var(--border);
  padding: 24px;
  width: 100%;
  max-width: 900px;
  position: relative;
}

.inv-buy-modal-title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--ink-strong);
  margin-bottom: 20px;
}

/* ── CharacterSheet tab bar ──────────────────────────────── */

.cs-tab-bar {
  display: flex;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  padding-left: 8px;
}

/* Tab button — active state uses inline styles (border-bottom color, text color) */
.cs-tab-btn {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 8px 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  transition: color 150ms;
}

/* ── cs-header section ───────────────────────────────────── */

.cs-header-layout {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
}

.cs-header-name-block {
  flex: 1;
  min-width: 0;
}

/* Name + level row: name fills the available width, level is pushed
   to the far right corner. */
.cs-header-name-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.cs-header-name-row .cs-header-name {
  flex: 1;
  min-width: 0;
}

/* Compound selector raises specificity above the base .cs-header-level-row
   rule (defined further down the file), which would otherwise win on
   source order and reapply the muted 13px Ubuntu styling. */
.cs-header-level-row.cs-header-level-row--corner {
  flex: 0 0 auto;
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: normal;
  letter-spacing: 0.02em;
  color: var(--ink-strong);
  gap: 10px;
}

/* The numeric value uses Ubuntu, sized down to match the optical
   height of the IM Fell SC "Level" word it sits beside — Ubuntu has
   a larger x-height than IM Fell, so a smaller pt size lands the
   tops/bottoms of the digits on the cap line/baseline of the label. */
.cs-header-level-value {
  font-family: var(--font-body);
  font-size: 28px;
  color: inherit;
  line-height: 1;
}


/* Name input — font-size is isMobile-conditional, kept inline */
.cs-header-name {
  font-family: var(--font-display);
  color: var(--ink-strong);
  letter-spacing: 0.02em;
  display: block;
  width: 100%;
  margin-bottom: 2px;
  /* Let glyph bearings extend past the input's content edge so
     IM Fell English SC's heavy left serifs (T, F, E, etc.) aren't
     clipped. Chromium-based browsers honour this on <input>;
     Firefox/Safari ignore it silently, so the padding/margin
     pair below remains as a fallback for those engines. */
  overflow: visible;
  padding-left: 4px;
  margin-left: -4px;
}

.cs-header-ancestry-row {
  display: flex;
  align-items: baseline;
  margin-bottom: 8px;
  gap: 12px;
}

/* Class badge/select pushed to the right corner of the ancestry row,
   landing visually beneath the Level readout in the row above. */
.cs-header-class-corner {
  margin-left: auto;
  flex: 0 0 auto;
}

.cs-header-ancestry {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 14px;
  color: var(--fg-muted);
}

.cs-header-class-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.cs-header-class-badge {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  padding: 3px 8px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
}
.cs-header-class-badge--clickable {
  cursor: pointer;
  transition: background 120ms var(--ease-quill), color 120ms var(--ease-quill);
}
.cs-header-class-badge--clickable:hover {
  background: var(--rubric);
  border-color: var(--rubric);
}

/* Character Log sections — Experience Log and the collapsible
   Character Creation Log share .cs-log-section / -header / -title
   so the two stack with consistent typography and rule lines. */
.cs-log-section { margin-bottom: 28px; }
.cs-log-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 8px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
/* Experience Log header — three-column layout so the XP input
   anchors the left edge, the title sits centered, and the Spend
   XP button anchors the right edge regardless of input width. */
.cs-log-section-header--xp {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.cs-log-section-header-side {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}
.cs-log-section-header-side--left  { justify-content: flex-start; }
.cs-log-section-header-side--right { justify-content: flex-end; }

/* Character Creation Log header — centered title flanked by a pair
   of ornamental rules running to the edges of the section so the
   title reads like a manuscript caption. */
.cs-log-section-header--creation {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 18px;
  border-bottom: none;
}
.cs-log-section-flourish {
  height: 1px;
  background:
    linear-gradient(to right,
      transparent 0,
      var(--border) 12%,
      var(--border) 88%,
      transparent 100%);
  position: relative;
}
.cs-log-section-flourish::before,
.cs-log-section-flourish::after {
  content: '❦';
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 14px;
  color: var(--rubric);
  background: var(--paper);
  padding: 0 6px;
}
.cs-log-section-flourish--left::after {
  right: -2px;
}
.cs-log-section-flourish--left::before { display: none; }
.cs-log-section-flourish--right::before {
  left: -2px;
}
.cs-log-section-flourish--right::after { display: none; }

.cs-log-section-header--clickable {
  cursor: pointer;
  user-select: none;
}
.cs-log-section-header--clickable:hover .cs-log-section-title {
  color: var(--rubric);
}
.cs-log-section-title {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-strong);
  margin: 0;
  font-weight: 500;
}
.cs-log-section-title--centered {
  text-align: center;
  white-space: nowrap;
}
/* Two title labels per header — the full one shows on desktop and
   the abbreviated one (XP Log / Creation Log) takes its place on
   narrow viewports so the centered header still fits without
   wrapping or clipping the side controls. */
.cs-log-title-full  { display: inline; }
.cs-log-title-short { display: none; }
@media (max-width: 640px) {
  .cs-log-title-full  { display: none; }
  .cs-log-title-short { display: inline; }
}
.cs-log-section-chevron {
  margin-left: 8px;
  color: var(--fg-muted);
  font-size: 14px;
}

/* Empty-state line under the Experience Log header. Sits in the
   same place a real entries-list would appear once XP-spend events
   start logging. */
.cs-log-empty--xp {
  text-align: center;
  padding: 14px 0 0;
}

/* XP-spend entries — one row per cart item committed through the
   Spend XP modal. Each row shows an icon (✦ by default), the
   purchase label (e.g. "Level Up to L4 · 30 XP"), and a desc line
   that lists what was advanced. */
.cs-log-xp-list {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cs-log-xp-entry {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 10px;
  align-items: baseline;
  padding: 8px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
}
.cs-log-xp-icon {
  font-family: var(--font-display);
  color: var(--rubric);
  font-size: 14px;
  text-align: center;
}
.cs-log-xp-body { min-width: 0; }
.cs-log-xp-entry-label {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--fg-strong);
}
.cs-log-xp-entry-desc {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  margin-top: 2px;
}

/* Experience Log header inputs — XP readout label + numeric input
   sit to the left of the section title; Spend XP button to the
   right. Input keeps the manuscript feel (dotted underline only). */
.cs-log-xp-label {
  font-family: var(--font-body);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--fg-muted);
}
.cs-log-xp-input {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--fg-strong);
  background: transparent;
  border: none;
  border-bottom: 1px dotted var(--border);
  width: 64px;
  text-align: right;
  padding: 0 4px 2px;
}
.cs-log-xp-input:focus {
  outline: none;
  border-bottom-color: var(--rubric);
}
.cs-log-spend-btn {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 14px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  transition: background 120ms var(--ease-quill), color 120ms var(--ease-quill);
}
.cs-log-spend-btn:hover {
  background: var(--rubric);
  border-color: var(--rubric);
}

/* ── Spend-XP modal ──────────────────────────────────────────────────
   Wider than the default .pull-modal-box because the home view
   stages three choice cards side by side. Body grows to fit the
   active view (home / abilities / levelup / facets) and the footer
   stays pinned with the cart tally + commit controls. */
/* Compound selectors below override the base .pull-modal-box width
   (`min(680px, 95vw)`) which would otherwise win on specificity
   ties due to its later position in the cascade. */
.pull-modal-box.spend-xp-modal-box {
  width: 70vw;
  max-width: 70vw;
  max-height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
  /* Both axes transition between the natural home-view size and the
     larger sub-view stage. Numeric endpoints on both ends let the
     browser interpolate cleanly. */
  min-height: 0;
  transition:
    min-height 380ms var(--ease-quill),
    width      380ms var(--ease-quill),
    max-width  380ms var(--ease-quill);
}
.pull-modal-box.spend-xp-modal-box--expanded {
  min-height: 88vh;
  width: 80vw;
  max-width: 80vw;
}
.spend-xp-modal-header {
  padding: 18px 24px 12px;
  border-bottom: 1px solid var(--border);
}
.spend-xp-modal-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-strong);
  font-weight: 500;
}
.spend-xp-modal-body {
  padding: 24px;
  overflow: auto;
  flex: 1 1 auto;
}

/* Three side-by-side choice cards on the home view. Drops to one
   column on narrow viewports so each card stays readable. */
.spend-xp-choice-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
@media (max-width: 768px) {
  .spend-xp-choice-grid { grid-template-columns: 1fr; }
}
.spend-xp-choice-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 22px 18px;
  background: var(--surface);
  border: 1px solid var(--ink);
  text-align: left;
  cursor: pointer;
  transition:
    transform 140ms var(--ease-quill),
    box-shadow 140ms var(--ease-quill),
    border-color 140ms var(--ease-quill);
  font: inherit;
  color: inherit;
}
.spend-xp-choice-card:hover {
  border-color: var(--rubric);
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}
.spend-xp-choice-card--disabled,
.spend-xp-choice-card[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  background: var(--paper-aged, var(--surface));
}
.spend-xp-choice-card--disabled:hover,
.spend-xp-choice-card[disabled]:hover {
  border-color: var(--ink);
  transform: none;
  box-shadow: none;
}
.spend-xp-choice-locked {
  margin-top: auto;
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--rubric);
}
.spend-xp-choice-title {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-strong);
}
.spend-xp-choice-body {
  font-family: var(--font-body-italic);
  font-size: 14px;
  line-height: 1.45;
  color: var(--fg-muted);
}

/* Pending purchases — itemized cart readout on the home view,
   below the three choice cards. Lets the player review and remove
   selections without diving back into a sub-view. */
.spend-xp-home {
  display: flex;
  flex-direction: column;
  gap: 26px;
}
.spend-xp-pending {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.spend-xp-pending-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 6px;
}
.spend-xp-pending-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-strong);
  font-weight: 500;
}
.spend-xp-pending-count {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--fg-muted);
}
.spend-xp-pending-empty {
  font-family: var(--font-body-italic);
  font-size: 13px;
  color: var(--fg-muted);
  padding: 8px 0;
}
.spend-xp-pending-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.spend-xp-pending-item {
  background: var(--surface);
  border: 1px solid var(--border);
}
.spend-xp-pending-row {
  display: grid;
  grid-template-columns: 90px 1fr auto auto;
  gap: 12px;
  align-items: baseline;
  padding: 8px 10px;
}
.spend-xp-pending-item--level .spend-xp-pending-row {
  border-bottom: 1px solid var(--border);
}

/* Expanded sub-list for the level-up cart item — each line is a
   clickable shortcut back into the level-up picker at the right
   step. Auto-line items (HP / WP) skip the hover state since they
   can't be edited. */
.spend-xp-pending-sublist {
  list-style: none;
  margin: 0;
  padding: 6px 10px 10px 18px;
}
.spend-xp-pending-subitem {
  position: relative;
  display: grid;
  grid-template-columns: 36px 1fr auto;
  gap: 10px;
  align-items: baseline;
  padding: 4px 10px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  border-radius: 2px;
  transition:
    background 240ms var(--ease-quill),
    padding-left 240ms var(--ease-quill);
}
/* Animated hover indicator — a rubric accent bar grows down the
   left edge of any sub-item the cursor lands on, paired with a
   soft paper-aged background tint and a small label shift to the
   right. Applies to every line in the level-up box (auto rows too)
   so the entry as a whole feels uniformly responsive. */
.spend-xp-pending-subitem::before {
  content: '';
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 4px;
  width: 3px;
  background: var(--rubric);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform 240ms var(--ease-quill);
  border-radius: 2px;
}
.spend-xp-pending-subitem:hover {
  background: var(--paper-aged, var(--paper));
  padding-left: 14px;
}
.spend-xp-pending-subitem:hover::before {
  transform: scaleY(1);
}
.spend-xp-pending-subitem--clickable {
  cursor: pointer;
}
.spend-xp-pending-bullet {
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--rubric);
  text-align: right;
}
.spend-xp-pending-sublabel {
  color: var(--fg-strong);
}
.spend-xp-pending-subkind {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
}

/* Class-facet grant row — highlighted block sub-item that appears
   beneath a level-up entry when the transition crosses a tier and
   the class facet for that tier is auto-granted. The fleuron bullet,
   verdigris banner, and ornamental border mark it as a reward
   distinct from the regular attribute / skill / HP / WP rows. */
.spend-xp-pending-subitem--class-facet {
  grid-template-columns: 36px 1fr auto;
  align-items: start;
  background: var(--paper-aged, var(--paper));
  border: 1px solid var(--verdigris-deep, #2f5e3b);
  border-radius: 3px;
  padding: 10px 12px;
  margin: 4px 0;
}
.spend-xp-pending-subitem--class-facet::before {
  display: none; /* hide the standard hover-accent bar — this row has its own border */
}
.spend-xp-pending-subitem--class-facet:hover {
  background: var(--paper-aged, var(--paper));
  padding-left: 12px;
}
.spend-xp-pending-subitem--class-facet .spend-xp-pending-bullet {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--verdigris-deep, #2f5e3b);
  text-align: center;
  line-height: 1;
  align-self: center;
}
.spend-xp-pending-classfacet-block {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.spend-xp-pending-classfacet-banner {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--verdigris-deep, #2f5e3b);
  font-weight: 500;
}
.spend-xp-pending-classfacet-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.spend-xp-pending-classfacet-name {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.04em;
  color: var(--fg-strong);
}
.spend-xp-pending-classfacet-cost {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--rubric);
  white-space: nowrap;
}
.spend-xp-pending-classfacet-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.spend-xp-pending-classfacet-tag {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
  border: 1px solid var(--rubric);
  border-radius: 2px;
  padding: 1px 6px;
}
.spend-xp-pending-classfacet-desc {
  font-family: var(--font-body);
  font-size: 12px;
  line-height: 1.45;
  color: var(--fg);
}
/* HTML block tags inside the class-facet description — descriptions
   were migrated from plain text into <p>/<ul>/<li>/<br>, so tighten
   the default browser margins and bullet styling to fit the small
   sub-item surface. */
.spend-xp-pending-classfacet-desc p {
  margin: 0 0 4px;
}
.spend-xp-pending-classfacet-desc p:last-child { margin-bottom: 0; }
.spend-xp-pending-classfacet-desc ul {
  margin: 3px 0 4px;
  padding-left: 16px;
  list-style: none;
}
.spend-xp-pending-classfacet-desc li {
  position: relative;
  padding-left: 4px;
  margin-bottom: 2px;
}
.spend-xp-pending-classfacet-desc li::before {
  content: '❧';
  position: absolute;
  left: -14px;
  top: 0;
  color: var(--rubric);
  font-size: 11px;
}
.spend-xp-pending-kind {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
}
.spend-xp-pending-label {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--fg-strong);
}
.spend-xp-pending-cost {
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--fg);
}
.spend-xp-pending-remove {
  font-family: var(--font-body);
  font-size: 14px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-muted);
  width: 26px;
  height: 26px;
  border-radius: 3px;
  cursor: pointer;
  transition: background 120ms var(--ease-quill), color 120ms var(--ease-quill);
}
.spend-xp-pending-remove:hover {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}

/* Placeholder rendered until each branch's picker is implemented. */
.spend-xp-stub {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 24px;
  text-align: center;
  align-items: center;
}
.spend-xp-stub-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-strong);
}
.spend-xp-stub-body {
  margin: 0;
  font-family: var(--font-body-italic);
  font-size: 14px;
  color: var(--fg-muted);
  max-width: 480px;
}
.spend-xp-back-btn {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.05em;
  padding: 6px 12px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg);
  cursor: pointer;
}
.spend-xp-back-btn:hover {
  border-color: var(--rubric);
  color: var(--rubric);
}

/* Footer — left column tallies XP available / cart cost / remaining;
   right column has Cancel + Confirm. Confirm button disables when
   the cart is empty or unaffordable. */
.spend-xp-modal-footer {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  padding: 16px 24px 20px;
  border-top: 1px solid var(--border);
}
.spend-xp-tally {
  display: grid;
  grid-template-columns: auto auto;
  gap: 4px 16px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
}
.spend-xp-tally-row {
  display: contents;
}
.spend-xp-tally-label {
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.spend-xp-tally-value {
  font-family: var(--font-display);
  text-align: right;
  color: var(--fg-strong);
}
.spend-xp-tally-row--total .spend-xp-tally-label,
.spend-xp-tally-row--total .spend-xp-tally-value {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg-strong);
}
.spend-xp-tally-row--insufficient .spend-xp-tally-value {
  color: var(--rubric);
}
.spend-xp-actions {
  display: flex;
  gap: 10px;
}
.spend-xp-cancel-btn,
.spend-xp-confirm-btn {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px 18px;
  border: 1px solid var(--ink);
  cursor: pointer;
  transition: background 120ms var(--ease-quill), color 120ms var(--ease-quill);
}
.spend-xp-cancel-btn {
  background: transparent;
  color: var(--fg);
  border-color: var(--border);
}
.spend-xp-cancel-btn:hover {
  border-color: var(--fg);
  color: var(--fg-strong);
}
.spend-xp-confirm-btn {
  background: var(--ink);
  color: var(--paper);
}
.spend-xp-confirm-btn:hover:not(:disabled) {
  background: var(--rubric);
  border-color: var(--rubric);
}
.spend-xp-confirm-btn:disabled {
  background: var(--surface);
  color: var(--fg-muted);
  border-color: var(--border);
  cursor: not-allowed;
}

/* ── Spend XP · Buy Abilities ───────────────────────────────────────
   Two sections — Available now and Available next level — each a
   responsive grid of ability cards. Next-level cards are visibly
   faded and not interactive so the player can read what's coming
   without misreading them as purchasable. */
.sxp-abil-view {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.sxp-abil-view-head {
  display: flex;
  justify-content: flex-start;
}
.sxp-abil-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sxp-abil-section-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-strong);
  font-weight: 500;
  border-bottom: 1px solid var(--border);
  padding-bottom: 4px;
}
.sxp-abil-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@media (max-width: 900px) {
  .sxp-abil-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .sxp-abil-grid { grid-template-columns: 1fr; }
}
/* When the last row has a single orphan card (count % cols === 1),
   center it in the row instead of leaving it left-aligned with empty
   trailing cells. Only applied at viewport widths where the grid has
   more than one column — single-column layouts don't need it. */
@media (min-width: 901px) {
  .sxp-abil-grid > .sxp-abil-card:nth-last-child(1):nth-child(3n+1) {
    grid-column: 2 / 3;
  }
}
@media (min-width: 601px) and (max-width: 900px) {
  .sxp-abil-grid > .sxp-abil-card:nth-last-child(1):nth-child(2n+1) {
    grid-column: 1 / -1;
    justify-self: center;
    max-width: calc((100% - 14px) / 2);
    width: 100%;
  }
}
.sxp-abil-grid--locked {
  opacity: 0.55;
}
.sxp-abil-empty {
  font-family: var(--font-body-italic);
  font-size: 13px;
  color: var(--fg-muted);
  padding: 8px 2px;
}
.sxp-abil-empty--error { color: var(--rubric); }

.sxp-abil-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--ink);
  padding: 14px;
  text-align: left;
  color: inherit;
  font: inherit;
  cursor: pointer;
  /* Capped at 40% of the modal's height (modal min-height tops out at
     900px → ~360px cards; on shorter viewports falls back to 40% of
     the available viewport). Inner description scrolls when content
     exceeds the cap; header/meta/footer stay anchored. */
  max-height: min(360px, calc((100vh - 64px) * 0.4));
  overflow: hidden;
  transition:
    transform 140ms var(--ease-quill),
    box-shadow 140ms var(--ease-quill),
    border-color 140ms var(--ease-quill),
    background 140ms var(--ease-quill);
}
.sxp-abil-card:hover {
  border-color: var(--rubric);
  transform: translateY(-1px);
  box-shadow: var(--shadow-2);
}
.sxp-abil-card--selected {
  border-color: var(--rubric);
  background: var(--paper-aged, var(--surface));
  box-shadow: var(--shadow-2);
}
.sxp-abil-card--locked {
  cursor: default;
}
.sxp-abil-card--locked:hover {
  transform: none;
  box-shadow: none;
  border-color: var(--ink);
}

.sxp-abil-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.sxp-abil-name {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.05em;
  color: var(--fg-strong);
}
.sxp-abil-cost {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.05em;
  color: var(--rubric);
  white-space: nowrap;
}
/* Tag pills — small rubric-bordered chips per tag, matching the
   visual language we use for weapon-trait chips in the log entry. */
.sxp-abil-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.sxp-abil-tag-pill {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
  background: var(--paper-aged, var(--paper));
  border: 1px solid var(--rubric);
  border-radius: 3px;
  padding: 1px 8px;
  line-height: 1.4;
}
.sxp-abil-check {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--fg);
  display: flex;
  gap: 6px;
  align-items: baseline;
}
.sxp-abil-check-vs {
  color: var(--fg-muted);
  font-style: italic;
  margin: 0 2px;
}
.sxp-abil-check-diff {
  color: var(--rubric);
  font-weight: 500;
}
.sxp-abil-desc {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  color: var(--fg);
  border-top: 1px dotted var(--border);
  padding-top: 8px;
  /* Description carries the bulk of the text; when the card's overall
     max-height is reached this is the part that scrolls so the head,
     tags, check, and footer rows stay anchored at the edges. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
/* Inner block-level elements inside ability descriptions. Descriptions
   now ship as HTML (<p> + <ul><li> after the migration) so we tighten
   the default browser margins to fit the small card surface and use
   a manuscript-flavoured bullet marker. */
.sxp-abil-desc p {
  margin: 0 0 6px;
}
.sxp-abil-desc p:last-child { margin-bottom: 0; }
.sxp-abil-desc ul {
  margin: 4px 0 6px;
  padding-left: 18px;
  list-style: none;
}
.sxp-abil-desc li {
  position: relative;
  padding-left: 4px;
  margin-bottom: 3px;
}
.sxp-abil-desc li::before {
  content: '❧';
  position: absolute;
  left: -16px;
  top: 0;
  color: var(--rubric);
  font-size: 13px;
}
.sxp-abil-footer {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: auto;
  padding-top: 6px;
}
.sxp-abil-xp-badge {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.08em;
  color: var(--fg-strong);
  border: 1px solid var(--border);
  padding: 2px 10px;
}
.sxp-abil-selected-tag {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rubric);
}

/* ── Spend XP · Buy Facets ───────────────────────────────────────
   Header carries a wide search box plus a Name/Cost sort toggle.
   Below it, facets are grouped by subtype with a manuscript-flavoured
   fleuron heading; the grid auto-fits to viewport width. Owned and
   in-cart cards are visually distinguished but the in-cart card
   remains clickable (clicking again removes it from the cart). */
.sxp-facet-view {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.sxp-facet-view-head {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sxp-facet-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.sxp-facet-search {
  flex: 1 1 220px;
  font-family: var(--font-body);
  font-size: 14px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
}
.sxp-facet-search:focus {
  outline: none;
  border-color: var(--rubric);
}
.sxp-facet-sort {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sxp-facet-sort-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.sxp-facet-sort-btn {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.06em;
  padding: 5px 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  transition: background 120ms var(--ease-quill), border-color 120ms var(--ease-quill), color 120ms var(--ease-quill);
}
.sxp-facet-sort-btn:hover {
  border-color: var(--rubric);
  color: var(--rubric);
}
.sxp-facet-sort-btn.is-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.sxp-facet-empty {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
  padding: 12px 2px;
}

.sxp-facet-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sxp-facet-group-header {
  display: flex;
  align-items: baseline;
  gap: 10px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 4px;
}
.sxp-facet-group-fleuron {
  color: var(--rubric);
  font-size: 14px;
}
.sxp-facet-group-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-strong);
  font-weight: 500;
  flex: 1 1 auto;
}
.sxp-facet-group-count {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--fg-muted);
}

.sxp-facet-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px;
}
@media (max-width: 600px) {
  .sxp-facet-grid { grid-template-columns: 1fr; }
}

.sxp-facet-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--ink);
  padding: 12px;
  text-align: left;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition:
    transform 140ms var(--ease-quill),
    box-shadow 140ms var(--ease-quill),
    border-color 140ms var(--ease-quill),
    background 140ms var(--ease-quill);
}
.sxp-facet-card:hover:not(:disabled) {
  border-color: var(--rubric);
  transform: translateY(-1px);
  box-shadow: var(--shadow-2);
}
.sxp-facet-card--selected {
  border-color: var(--rubric);
  background: var(--paper-aged, var(--surface));
  box-shadow: var(--shadow-2);
}
.sxp-facet-card--owned {
  opacity: 0.45;
  cursor: not-allowed;
}
.sxp-facet-card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.sxp-facet-card-name {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: 0.04em;
  color: var(--fg-strong);
}
.sxp-facet-card-cost {
  font-family: var(--font-body);
  font-size: 18px;
  letter-spacing: 0;
  color: var(--rubric);
  white-space: nowrap;
}
.sxp-facet-card-desc {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
}
.sxp-facet-card-footer {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.sxp-facet-card-flag {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 1px 8px;
  border: 1px solid var(--border);
  color: var(--fg-muted);
  border-radius: 2px;
}
.sxp-facet-card-flag--in-cart {
  color: var(--rubric);
  border-color: var(--rubric);
}
.sxp-facet-card-flag--owned {
  color: var(--verdigris-deep, #2f5e3b);
  border-color: var(--verdigris-deep, #2f5e3b);
}

/* Five-cell stage progress strip on facet cards (Spend XP picker).
   Cells fill as the player pays each stage; an in-cart stage shows
   half-state via the --pending modifier. */
.sxp-facet-stages {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
  margin: 6px 0 2px;
}
.sxp-facet-stage {
  display: block;
  height: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 2px;
  transition: background 140ms var(--ease-quill), border-color 140ms var(--ease-quill);
}
.sxp-facet-stage--paid {
  background: var(--verdigris-deep, #2f5e3b);
  border-color: var(--verdigris-deep, #2f5e3b);
}
.sxp-facet-stage--pending {
  background: repeating-linear-gradient(
    45deg,
    var(--rubric),
    var(--rubric) 3px,
    var(--paper-aged, var(--paper)) 3px,
    var(--paper-aged, var(--paper)) 6px);
  border-color: var(--rubric);
}
.sxp-facet-card-meta {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
}

/* ── Spend XP · Level Up ────────────────────────────────────────────
   Two-step picker. Step A spreads attributes by their cluster across
   columns; Step B is a denser auto-fit grid for skills. Tapping a
   card toggles a +1 selection; selected state turns verdigris so the
   delta is visible at a glance. */
.sxp-lvl-view {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.sxp-lvl-view-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}
.sxp-lvl-summary {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 13px;
}
.sxp-lvl-summary strong {
  color: var(--fg-strong);
  font-family: var(--font-display);
  font-style: normal;
  letter-spacing: 0.05em;
}

.sxp-lvl-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sxp-lvl-section-title {
  margin: 0;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-strong);
  font-weight: 500;
  border-bottom: 1px solid var(--border);
  padding-bottom: 6px;
}
.sxp-lvl-section-count {
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--rubric);
  text-transform: none;
}
.sxp-lvl-section-hint {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
}

/* Attribute layout: three columns side-by-side (Physical / Mental /
   Social) on wide screens. Collapses progressively so each cluster
   stays readable on smaller viewports — 2 columns mid-range, single
   column with stacked clusters on phones. */
.sxp-lvl-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}
@media (max-width: 900px) {
  .sxp-lvl-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .sxp-lvl-grid { grid-template-columns: 1fr; }
}
.sxp-lvl-cluster {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sxp-lvl-cluster-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.sxp-lvl-cards,
.sxp-lvl-skill-grid {
  display: grid;
  gap: 8px;
}
.sxp-lvl-cards {
  grid-template-columns: 1fr;
}
/* 22 skills laid out as two even columns of 11 — column-first
   auto-flow so skills cascade down the left column before starting
   the right one (instead of zig-zagging left-right across rows).
   Drops to a single column on narrow viewports. */
.sxp-lvl-skill-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(11, auto);
  grid-auto-flow: column;
}
@media (max-width: 700px) {
  .sxp-lvl-skill-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    grid-auto-flow: row;
  }
}

.sxp-lvl-card {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition:
    background 120ms var(--ease-quill),
    border-color 120ms var(--ease-quill),
    box-shadow 120ms var(--ease-quill);
}
.sxp-lvl-card:hover {
  border-color: var(--rubric);
}
.sxp-lvl-card--selected {
  background: var(--verdigris-deep, #2f5e3b);
  border-color: var(--verdigris-deep, #2f5e3b);
  color: var(--paper);
  box-shadow: var(--shadow-2);
}
.sxp-lvl-card--capped,
.sxp-lvl-card:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.sxp-lvl-card--capped:hover,
.sxp-lvl-card:disabled:hover {
  border-color: var(--border);
}
.sxp-lvl-card-name {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.05em;
}

/* Dot row — 9 dots, filled for current value, rubric for the +1
   delta when this card is selected, hollow for the rest. When the
   surrounding card flips to its selected (verdigris) state the dot
   colours invert to read against the deep-green fill. */
.sxp-lvl-dots {
  display: inline-flex;
  gap: 3px;
  align-items: center;
}
.sxp-lvl-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: transparent;
  flex: 0 0 auto;
  transition: background 120ms var(--ease-quill), border-color 120ms var(--ease-quill);
}
.sxp-lvl-dot--filled { background: var(--ink); }
.sxp-lvl-dot--delta  {
  background: var(--rubric);
  border-color: var(--rubric);
}
.sxp-lvl-card--selected .sxp-lvl-dot {
  border-color: var(--paper);
}
.sxp-lvl-card--selected .sxp-lvl-dot--filled {
  background: var(--paper);
  border-color: var(--paper);
}
.sxp-lvl-card--selected .sxp-lvl-dot--delta {
  background: var(--paper-aged, var(--paper));
  border-color: var(--paper-aged, var(--paper));
}

.sxp-lvl-actions {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
}
.sxp-lvl-actions--top {
  justify-content: flex-end;
  margin-top: 0;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.sxp-lvl-action {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px 18px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  transition: background 120ms var(--ease-quill), color 120ms var(--ease-quill);
}
.sxp-lvl-action:hover:not(:disabled) {
  border-color: var(--rubric);
  color: var(--rubric);
}
.sxp-lvl-action--primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.sxp-lvl-action--primary:hover:not(:disabled) {
  background: var(--rubric);
  border-color: var(--rubric);
  color: var(--paper);
}
.sxp-lvl-action:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ── Level Up · mobile (≤640px) ───────────────────────────────────
   Compact phone layout. The head row stacks (back button above the
   leveling-to summary so neither truncates), the action bar wraps
   so primary/secondary buttons each get full width, attribute /
   skill cards shrink and the dot row is allowed to wrap if the
   card width can't hold all 6 dots in a single line. */
@media (max-width: 640px) {
  .sxp-lvl-view { gap: 12px; }

  .sxp-lvl-view-head {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
  }
  .sxp-lvl-summary {
    text-align: left;
    font-size: 12px;
  }

  .sxp-lvl-actions,
  .sxp-lvl-actions--top {
    flex-wrap: wrap;
  }
  .sxp-lvl-actions--top .sxp-lvl-action {
    flex: 1 1 0;
    text-align: center;
  }

  .sxp-lvl-section-title {
    font-size: 14px;
    letter-spacing: 0.06em;
    flex-wrap: wrap;
    gap: 6px;
  }
  .sxp-lvl-section-count { font-size: 14px; }
  .sxp-lvl-section-hint  { font-size: 12px; }

  .sxp-lvl-card {
    padding: 8px 10px;
    gap: 8px;
    flex-wrap: wrap;
  }
  .sxp-lvl-card-name { font-size: 13px; }
  .sxp-lvl-dots {
    flex-wrap: wrap;
    gap: 2px;
  }
  .sxp-lvl-dot { width: 8px; height: 8px; }
}

/* ── Spend XP · mobile (≤640px) ──────────────────────────────────────
   Compact phone layout. The modal goes essentially full-screen,
   internal paddings shrink, the footer stacks vertically so the
   tally and the action buttons can each have full width, and the
   pending-purchases grid collapses into a tighter flex layout that
   the narrow viewport can actually hold. */
@media (max-width: 640px) {
  .pull-modal-box.spend-xp-modal-box,
  .pull-modal-box.spend-xp-modal-box--expanded {
    width: 100vw;
    max-width: 100vw;
    min-height: 100vh;
    max-height: 100vh;
    border-radius: 0;
  }

  .spend-xp-modal-header { padding: 12px 14px 8px; }
  .spend-xp-modal-title  { font-size: 16px; letter-spacing: 0.06em; }
  .spend-xp-modal-body   { padding: 14px; }

  .spend-xp-home { gap: 18px; }
  .spend-xp-choice-card {
    padding: 16px 14px;
    gap: 8px;
  }
  .spend-xp-choice-title { font-size: 16px; }
  .spend-xp-choice-body  { font-size: 13px; line-height: 1.4; }

  /* Pending purchases — drop the 4-column grid and use a 2x2
     layout where each cell has room to breathe. */
  .spend-xp-pending-item {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "kind   remove"
      "label  cost";
    row-gap: 4px;
    column-gap: 10px;
  }
  .spend-xp-pending-kind   { grid-area: kind;   }
  .spend-xp-pending-label  { grid-area: label;  }
  .spend-xp-pending-cost   { grid-area: cost; text-align: right; }
  .spend-xp-pending-remove { grid-area: remove; justify-self: end; }

  /* Footer stacks vertically — tally first, then action buttons
     side by side at full width below. */
  .spend-xp-modal-footer {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 12px 14px 16px;
  }
  .spend-xp-tally {
    width: 100%;
  }
  .spend-xp-actions {
    width: 100%;
    justify-content: stretch;
  }
  .spend-xp-cancel-btn,
  .spend-xp-confirm-btn {
    flex: 1 1 0;
    padding: 10px 14px;
  }

  /* Buy Abilities sub-view — tighter card padding and font scale
     so the per-card description doesn't dominate the narrow
     viewport. The 40vh card cap still applies. */
  .sxp-abil-card { padding: 12px; gap: 6px; }
  .sxp-abil-name { font-size: 18px; }
  .sxp-abil-cost { font-size: 18px; }
  .sxp-abil-desc { font-size: 14px; }
}

.cs-header-class-select {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  padding: 3px 8px;
  border-radius: 2px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
}

.cs-header-sep {
  color: var(--border);
}

.cs-header-level-row {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  display: flex;
  align-items: center;
  gap: 4px;
}

/* ── cs-resources grid ──────────────────────────────────── */
/* grid-template-columns gap is isMobile-conditional; kept inline */

/* ── cs-attr-clusters grid ──────────────────────────────── */
/* grid-template-columns is isMobile-conditional; kept inline */

/* ── cs-combat section ──────────────────────────────────── */

/* Side-by-side row for combat stats + attacks grid. Stacks on narrow
   viewports (mobile) so neither side is squeezed below readability. */
.cs-combat-stats-row {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 10px;
  margin-bottom: 10px;
}
.cs-combat-stats-row > .cs-combat-stats-grid {
  margin-bottom: 0;        /* the row's gap handles spacing now */
  flex: 1 1 50%;
  min-width: 0;
}
.cs-combat-stats-row > .cs-attacks-grid {
  flex: 1 1 50%;
  min-width: 0;
}
@media (max-width: 640px) {
  .cs-combat-stats-row {
    flex-direction: column;
  }
}

.cs-combat-stats-grid {
  border: 1px solid var(--border);
  background: var(--surface);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  margin-bottom: 10px;
}

/* ── Attacks grid ──────────────────────────────────────────
   Mirrors the visual weight of the combat-stats grid (paper surface,
   sepia border) but uses a vertical button list rather than a grid of
   stat cells. Each button represents one attack the character can
   make; buttons are draggable (drop on a token / initiative entry to
   set the attack target) and clickable (when a target is already
   selected). Disabled state drains contrast so it reads as awaiting
   a target rather than a generic disabled control. */
.cs-attacks-grid {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  background: var(--surface);
  min-height: 0;
}
.cs-attacks-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 8px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border);
  gap: 6px;
}
.cs-attacks-target {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--rubric);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60%;
}
.cs-attacks-target--none {
  color: var(--fg-muted);
  opacity: 0.7;
}
.cs-attacks-target-row {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 70%;
}
.cs-attacks-target-clear {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg-muted);
  border-radius: 2px;
  cursor: pointer;
}
.cs-attacks-target-clear:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.cs-attacks-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  padding: 4px;
  gap: 3px;
  overflow-y: auto;
}
/* Drop to one column when the panel is too narrow for two buttons to
   sit side-by-side without truncating their labels to nothing. */
@media (max-width: 480px) {
  .cs-attacks-list { grid-template-columns: 1fr; }
}
.cs-attack-button {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: var(--paper);
  color: var(--fg);
  font-family: var(--font-body);
  cursor: grab;
  text-align: left;
  min-width: 0;
  transition: background var(--dur-1, 120ms) ease, border-color var(--dur-1, 120ms) ease;
}
.cs-attack-button:hover:not(:disabled) {
  background: var(--surface-hi, var(--paper-deep));
  border-color: var(--ink);
}
.cs-attack-button:active:not(:disabled) {
  cursor: grabbing;
  background: var(--paper-deep);
}
/* No-target state — visually muted to telegraph "not directly
   clickable yet" but still draggable. Cursor stays grab so the user
   knows the affordance: drop onto a target to designate + fire. */
.cs-attack-button--no-target {
  opacity: 0.6;
  filter: saturate(0.55);
}
.cs-attack-button--no-target:hover {
  opacity: 0.85;
  filter: saturate(0.85);
}
/* When a tile has fullWidth=true (currently W1 with no W2, or the
   "No Weapon Equipped" placeholder), span both columns of the 2-col
   grid so the bottom row reads as a single deliberate entry rather
   than a half-filled one. */
.cs-attack-button--span {
  grid-column: 1 / -1;
}
/* Informational placeholder ("No Weapon Equipped") — fills the
   bottom row's footprint so the attacks panel doesn't change height
   when the player un-equips, but reads as flat / non-interactive. */
.cs-attack-button--informational {
  cursor: default;
  color: var(--fg-muted);
  background: color-mix(in srgb, var(--sepia) 6%, var(--surface));
  border-style: dashed;
  opacity: 0.85;
  filter: saturate(0.5);
}
.cs-attack-button--informational:hover {
  background: color-mix(in srgb, var(--sepia) 6%, var(--surface));
  border-color: var(--border);
}
.cs-attack-button--informational .cs-attack-label {
  font-family: var(--font-display-italic);
  font-style: italic;
}
.cs-attack-button--social { background: color-mix(in srgb, var(--indigo) 9%, var(--paper)); }
.cs-attack-button--unarmed,
.cs-attack-button--grapple,
.cs-attack-button--trip,
.cs-attack-button--tackle { background: color-mix(in srgb, var(--rubric) 6%, var(--paper)); }
.cs-attack-icon {
  font-size: 16px;
  line-height: 1;
  flex: 0 0 18px;
  text-align: center;
}
.cs-attack-text {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}
.cs-attack-label {
  font-family: var(--font-display);
  font-size: 12px;
  line-height: 1.1;
  color: var(--fg-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cs-attack-subtitle {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cs-attack-dmg {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--rubric);
  font-weight: 700;
  flex: 0 0 auto;
  padding: 0 2px;
}

/* ── Attack modals ──────────────────────────────────────────
   Sit on top of the existing .pull-modal-box / .pull-math-bar /
   .pull-card-row / .pull-sweep-row layout from PullModal so the
   modal furniture is visually consistent. The atk-* classes here
   only style the attack-specific extras: range disambiguation,
   conditional bonuses, difficulty adjuster, social-attack picker. */
/* Called Shot "Aiming for" input — slots in above ConditionalBonusesRow
   and renders only when attack.kind === 'called-shot'. Free-form text;
   the GM uses what the player typed to adjudicate the trauma rider. */
.atk-called-shot-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid var(--border-soft);
}
.atk-called-shot-label {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-right: 4px;
  flex: 0 0 auto;
}
.atk-called-shot-input {
  flex: 1 1 auto;
  font-family: var(--font-body);
  font-size: 13px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
  border-radius: 2px;
}

.atk-range-row,
.atk-conditional-row,
.atk-diff-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid var(--border-soft);
  flex-wrap: wrap;
}

/* ── Weapon-trait trigger indicators ──────────────────────────────
   When the currently-active cards would activate a weapon trait, the
   triggering cards get a glowing chase border, and matching trait tags
   appear pinned to the right end of the difficulty row.
   The chase uses a conic-gradient masked into a thin frame, rotated
   via the @property-registered --chase-angle custom property so the
   gradient animation is smooth (a plain @keyframes on a gradient
   doesn't interpolate without @property). */
@property --chase-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
@keyframes atk-trait-chase {
  to { --chase-angle: 360deg; }
}
.atk-card-trait-trigger {
  position: relative;
}
.atk-card-trait-trigger::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 7px;
  padding: 2px;
  background: conic-gradient(from var(--chase-angle),
    transparent 0deg,
    var(--rubric) 60deg,
    var(--sepia) 140deg,
    transparent 220deg,
    var(--rubric) 320deg,
    transparent 360deg);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  animation: atk-trait-chase 3.2s linear infinite;
  z-index: 2;
}
/* Fallback for browsers without @property — the conic gradient won't
   animate, but the static rubric-accent border still flags the card. */
@supports not (background: conic-gradient(from 0deg, red, blue)) {
  .atk-card-trait-trigger::after {
    background: none;
    border: 2px solid var(--rubric);
  }
}

/* Trait tag pills pinned right-of-row inside .atk-diff-row. The same
   chase animation runs on each pill's border so the tag visually
   pairs with the highlighted card(s). */
.atk-trait-tag-row {
  margin-left: auto;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.atk-trait-tag {
  position: relative;
  display: inline-block;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-strong);
  background: var(--paper);
  padding: 4px 10px;
  cursor: help;
}
.atk-trait-tag::after {
  content: '';
  position: absolute;
  inset: 0;
  padding: 1.5px;
  background: conic-gradient(from var(--chase-angle),
    transparent 0deg,
    var(--rubric) 60deg,
    var(--sepia) 140deg,
    transparent 220deg,
    var(--rubric) 320deg,
    transparent 360deg);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  animation: atk-trait-chase 3.2s linear infinite;
}
@supports not (background: conic-gradient(from 0deg, red, blue)) {
  .atk-trait-tag::after { background: none; }
  .atk-trait-tag { border: 1.5px solid var(--rubric); }
}

/* Ability info block in the attack modal — shown when an Attack-tag
   ability is the source of the attack, so the player can read the
   ability's tags + effects text without leaving the modal. */
.atk-ability-block {
  padding: 10px 16px 12px;
  border-top: 1px solid var(--border-soft);
  background: color-mix(in srgb, var(--rubric) 4%, var(--surface));
}
.atk-ability-eyebrow {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--rubric-deep);
  margin-bottom: 2px;
}
.atk-ability-name {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--ink-strong);
  margin-bottom: 4px;
}
.atk-ability-effects {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
}
.atk-range-label,
.atk-conditional-label,
.atk-diff-label {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-right: 4px;
}
.atk-range-btn,
.atk-diff-btn {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
  border-radius: 2px;
  cursor: pointer;
}
.atk-range-btn--on {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.atk-range-btn:hover:not(.atk-range-btn--on),
.atk-diff-btn:hover:not(:disabled) {
  background: var(--surface-hi, var(--paper-deep));
}
.atk-diff-btn--reset { font-size: 14px; padding: 0 8px; }
.atk-diff-value {
  font-family: var(--font-mono);
  min-width: 28px;
  text-align: center;
}
.atk-conditional-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.atk-conditional-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 11px;
  background: var(--paper);
  cursor: pointer;
  user-select: none;
}
.atk-conditional-chip input { margin: 0; }
.atk-conditional-chip--on {
  background: color-mix(in srgb, var(--verdigris) 16%, var(--paper));
  border-color: var(--verdigris-deep);
}
.atk-conditional-mod {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--verdigris-deep);
  margin-left: 2px;
}

/* Social-attack sub-picker — first phase of SocialAttackModal */
.atk-social-pick-list {
  display: flex;
  flex-direction: column;
  padding: 8px;
  gap: 4px;
}
.atk-social-pick-row {
  display: grid;
  grid-template-columns: 1.5fr 2fr 1fr;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 2px;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-body);
  transition: background var(--dur-1, 120ms) ease, border-color var(--dur-1, 120ms) ease;
}
.atk-social-pick-row:hover {
  background: var(--surface-hi, var(--paper-deep));
  border-color: var(--ink);
}
.atk-social-pick-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg-strong);
}
.atk-social-pick-pull {
  font-size: 11px;
  color: var(--fg-muted);
}
.atk-social-pick-trauma {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--rubric);
  text-align: right;
}

.cs-combat-stat-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 4px 2px;
  text-align: center;
}
.cs-combat-stat-cell--with-right { border-right: 1px solid var(--border); }
.cs-combat-stat-cell--with-bottom { border-bottom: 1px solid var(--border); }
.cs-weapon-loadout-row--red { background: color-mix(in srgb, var(--rubric) 9%, var(--surface)); }

.cs-combat-stat-label {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 2px;
}

.cs-combat-stat-hint {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  margin-top: 1px;
}

.cs-combat-stat-badge {
  font-family: var(--font-body);
  font-size: 7px;
  color: var(--indigo);
  margin-top: 1px;
  letter-spacing: 0.04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  padding: 0 2px;
}

.cs-combat-stat-style {
  font-family: var(--font-body);
  font-size: 8px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--sepia);
  margin-top: 1px;
}
/* Invisible placeholder used by Base Def to reserve a styleLabel row
   so its label + number sit on the same baseline as the CD / RD
   cells that do carry a defense-reaction label. */
.cs-combat-stat-style--placeholder {
  visibility: hidden;
}

.cs-combat-panel {
  border: 1px solid var(--border);
  background: var(--surface);
}

.cs-combat-sub-header {
  padding: 3px 10px;
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border);
}

.cs-combat-sub-header--top-border {
  border-top: 1px solid var(--border);
}

.cs-combat-half-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.cs-combat-half--left {
  border-right: 1px solid var(--border-soft);
}

/* Weapon loadout row — background is dynamic (RED_BG); kept inline */
.cs-weapon-loadout-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border-top: 1px solid var(--border-soft);
}

.cs-weapon-loadout-dmg {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--rubric);
  min-width: 18px;
}

.cs-weapon-loadout-name {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-strong);
  flex: 1;
}

.cs-weapon-loadout-type {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

/* ── cs-skills section ──────────────────────────────────── */
/* grid-template-columns is isMobile-conditional; kept inline */

.cs-skills-grid-outer {
  border: 1px solid var(--border);
  background: var(--surface);
}

/* borderRight depends on i%2; kept inline */

/* ── cs-status / minor trauma ───────────────────────────── */
/* grid-template-columns + gap are isMobile-conditional; kept inline */

.cs-status-section-title {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--rubric);
  margin-bottom: 10px;
}
.cs-status-section-title--rubric { color: var(--rubric-deep); }

/* Trauma blocks now render inside the Resources section, beneath the
   HP/WP grid. The top margin keeps them from crowding the resource bars
   while staying inside the same draggable section frame. */
.cs-resources-trauma-block { margin-top: 16px; }

.cs-status-empty {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
}

.cs-status-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
/* Pair the main chip with its optional action button (Bound's "−1") so
   they share a border-line and read as a single unit. */
.cs-status-chip-group {
  display: inline-flex;
  align-items: stretch;
}
.cs-status-chip-group .cs-status-chip { border-right: none; }
.cs-status-chip-icon {
  width: 14px;
  height: 14px;
  vertical-align: -3px;
  margin-right: 4px;
  object-fit: contain;
}
.cs-status-chip-action {
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 11px;
  font-weight: 700;
  padding: 4px 8px;
  border: 1px solid var(--rubric);
  background: var(--rubric);
  color: var(--paper);
  cursor: pointer;
  letter-spacing: 0;
  border-radius: 0 2px 2px 0;
}
.cs-status-chip-action:hover {
  background: var(--ink);
  border-color: var(--ink);
}

.cs-status-chip {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  padding: 4px 10px;
  border: 1px solid var(--rubric);
  background: transparent;
  color: var(--rubric);
  border-radius: 2px;
  cursor: pointer;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cs-trauma-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

.cs-trauma-th {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
  font-weight: 500;
  padding: 4px 8px;
  text-align: left;
}

.cs-trauma-thead-row {
  border-bottom: 1px solid var(--border);
}

.cs-trauma-row {
  border-bottom: 1px solid var(--border-soft);
}

.cs-trauma-td {
  padding: 6px 8px;
  font-family: var(--font-body);
}

.cs-trauma-td--name   { font-weight: 500; }
.cs-trauma-icon {
  width: 16px;
  height: 16px;
  object-fit: contain;
  vertical-align: -3px;
  margin-right: 5px;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.18));
}
.cs-trauma-icon--lg {
  width: 22px;
  height: 22px;
  vertical-align: -5px;
  margin-right: 6px;
}
.cs-trauma-td--penalty { color: var(--rubric); }
.cs-trauma-td--muted  { color: var(--fg-muted); }

.cs-trauma-td--btn {
  padding: 6px 4px;
  text-align: right;
}

.cs-trauma-btn {
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 11px;
  padding: 2px 6px;
}

.cs-trauma-btn--treat {
  color: var(--verdigris-deep);
}
.cs-trauma-btn--bump {
  color: var(--rubric);
}
.cs-trauma-btn--cure {
  color: var(--fg-muted);
}

/* Treated trauma rows are visually muted — the penalty is suppressed
   so the row reads as inactive, but the stack count stays bold so the
   player can see how close they are to advancement. */
.cs-trauma-row--treated .cs-trauma-td--penalty,
.cs-trauma-row--treated .cs-trauma-td--name {
  opacity: 0.55;
  text-decoration: line-through solid var(--fg-muted) 1px;
}
.cs-trauma-row--treated .cs-trauma-stack-count {
  font-weight: 700;
}
.cs-trauma-treated-tag {
  font-style: italic;
  color: var(--verdigris-deep);
  text-decoration: none;
  font-size: 11px;
}
.cs-trauma-td--stacks {
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.cs-trauma-stack-count {
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 11px;
  color: var(--rubric);
}
.cs-trauma-group-btn {
  background: transparent;
  border: 1px dashed var(--border-soft);
  color: var(--rubric);
  font-family: inherit;
  font-size: inherit;
  padding: 1px 6px;
  cursor: pointer;
  border-radius: 2px;
}
.cs-trauma-group-btn:hover {
  border-color: var(--rubric);
  background: var(--paper-deep);
}

/* ── cs-major trauma ────────────────────────────────────── */

.cs-major-trauma-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cs-major-trauma-card {
  padding: 8px 12px;
  border: 1px solid var(--rubric);
  background: transparent;
}

.cs-major-trauma-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.cs-major-trauma-name {
  font-family: var(--font-body);
  font-weight: 500;
  color: var(--rubric);
}
.cs-major-trauma-cure {
  font-family: var(--font-body);
  font-size: 11px;
  font-style: italic;
  color: var(--fg-muted);
  margin-top: 2px;
}
.cs-major-trauma-note {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dotted var(--border-soft);
}

.cs-major-trauma-effect {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}

/* ── Abilities / Facets empty state ─────────────────────── */

.cs-card-list-empty {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
  margin-bottom: 6px;
}

/* ── cs-abilities / cs-facets grid ─────────────────────── */
/* grid-template-columns is isMobile-conditional; kept inline */

/* ── panel- draggable panel wrapper (panels.jsx) ─────────── */

.panel-outer {
  flex: 0 0 100%;
  padding: 0 6px 12px;
  box-sizing: border-box;
  min-width: 0;
}

.panel-inner {
  border: 1px solid var(--border);
  background: var(--paper);
  display: flex;
  flex-direction: column;
}

.panel-handle {
  /* Standardised handle height across every panel — fixed min-height
     keeps the Initiative / Game Board / Log / etc. headers visually
     aligned even when one of them carries an inline control row in
     `headerRight`. position:relative so absolutely-positioned children
     (e.g., the world-map travel-time pill) can centre against the
     full header width. */
  position: relative;
  min-height: 36px;
  padding: 0 12px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  cursor: grab;
  display: flex;
  align-items: center;
  gap: 10px;
  user-select: none;
  flex-shrink: 0;
  /* Desktop: full JS gesture control (drag from any point on the
     header). Mobile: only block horizontal pan + pinch-zoom; let
     the browser handle vertical scroll so the user can scroll past
     stacked panels. Drag-reorder on touch still works because
     Sortable's `delay: 150` waits for a long-press — once active,
     its preventDefault on touchmove overrides the pan-y allowance.
     A quick swipe stays under the delay → browser handles it →
     page scrolls. */
  touch-action: none;
}
@media (hover: none) and (pointer: coarse) {
  .panel-handle { touch-action: pan-y; }
}
.panel-handle-right {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: default;
}

.panel-handle-icon {
  color: var(--fg-muted);
  font-size: 14px;
  line-height: 1;
}

.panel-handle-label {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  /* Take whatever space is left after the icon, the right-slot, and
     the optional expand button. flex:1 here would stretch the label
     past its ink so the right-slot drifts to the far edge — fine,
     but `margin-right: auto` keeps the label tight-left and lets the
     right-slot govern its own positioning. */
  margin-right: auto;
}
.panel-handle-expand {
  font-family: var(--font-display);
  font-size: 14px;
  line-height: 1;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  border-radius: 2px;
}
.panel-handle-expand:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Collapse caret — same chrome as the expand button, just a chevron
   instead of the arrow. The button is rendered only on collapsible
   panels (i.e., everything except the Game Board). Double-clicking
   the header is the primary toggle; the caret is the explicit
   affordance. */
.panel-handle-collapse {
  font-size: 12px;
  line-height: 1;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  border-radius: 2px;
}
.panel-handle-collapse:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
/* Collapsed-state styling — the body stays mounted (useAnimatedCollapse
   shrinks its height + fades it out via anime.js), and the outer chrome
   reads as a compact banner. The handle stays visible so the user can
   drag and re-expand. */
.panel-outer--collapsed { min-height: 0; }
.panel-outer--collapsed .panel-handle { border-bottom: 0; }
.panel-outer--collapsed .panel-body { border-top: 0; }

.panel-body {
  min-width: 0;
  /* Height + opacity transitions are driven imperatively by
     useAnimatedCollapse so we don't need CSS transition values here. */
}

/* Expanded panel — overlays the viewport on top of the rest of the
   layout. Clicking the dimmed backdrop or double-clicking the header
   again collapses. The MapCanvas inside auto-fills via ResizeObserver
   and the parent passes a larger cellSize for a more zoomed view. */
.panel-expanded-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 800;
  cursor: pointer;
}
.panel-outer--expanded {
  position: fixed;
  inset: 4vh 4vw;
  z-index: 801;
  padding: 0;
  pointer-events: auto;
}
.panel-outer--expanded .panel-inner {
  width: 100%;
  height: 100%;
  box-shadow: var(--shadow-3);
}
.panel-outer--expanded .panel-body {
  flex: 1;
  display: flex;
  min-height: 0;
}
.panel-outer--expanded .panel-body > * {
  flex: 1;
  min-height: 0;
}

/* PanelContainer scroll wrapper */
.panel-container {
  display: flex;
  flex-wrap: wrap;
  padding: 12px 6px 64px;
  align-items: flex-start;
}

/* ── Shop panel (panels.jsx additions) ───────────────────── */

.shop-layout-grid {
  display: grid;
  grid-template-columns: 1fr 290px;
  gap: 16px;
  margin-bottom: 24px;
  border: 1px solid var(--border);
  padding: 12px;
}

.shop-browser {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.shop-category-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

/* active/inactive state driven by JS; kept inline on button */
.shop-filter-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 10px;
  cursor: pointer;
}

.shop-search-row {
  display: flex;
  gap: 8px;
}

.shop-search-input {
  flex: 1;
  font-family: var(--font-body);
  font-size: 12px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  outline: none;
}

.shop-sort-select {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
}

.shop-col-headers {
  display: grid;
  grid-template-columns: 1fr 38px 60px 28px;
  gap: 4px;
  padding: 2px 8px;
  border-bottom: 1px solid var(--border-soft);
}

.shop-col-label {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.shop-item-list {
  overflow-y: auto;
  max-height: 320px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.shop-item-empty {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
  padding: 12px 8px;
}

/* Item row — background depends on isOpen; kept inline */
.shop-item-row {
  display: grid;
  grid-template-columns: 1fr 38px 60px 28px;
  gap: 4px;
  padding: 5px 8px;
  border: 1px solid var(--border-soft);
  align-items: center;
}

.shop-item-name {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink-strong);
}

.shop-item-free-tag {
  font-family: var(--font-body);
  font-size: 9px;
  margin-left: 6px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.shop-item-expand-arrow {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-muted);
  margin-left: 5px;
  opacity: 0.5;
}

.shop-item-dmg {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--rubric);
}

.shop-item-ward {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--verdigris-deep);
}

.shop-item-trait-tag {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--ink-faded);
  background: var(--paper-deep);
  border: 1px solid var(--border-soft);
  padding: 1px 5px;
  cursor: help;
}

.shop-item-traits {
  display: flex;
  gap: 3px;
  flex-wrap: wrap;
  margin-top: 3px;
}

.shop-item-slots {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
  text-align: center;
}

/* cost color is dynamic (Unpriced = sepia); kept inline */
.shop-item-cost {
  font-family: var(--font-mono);
  font-size: 11px;
  text-align: right;
  color: var(--fg);
}

.shop-item-add-btn {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 16px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1.3;
}

/* Expanded item detail panel */
.shop-item-detail {
  padding: 10px 12px 12px;
  border: 1px solid var(--border-soft);
  border-top: none;
  background: rgba(0, 0, 0, 0.04);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.shop-item-detail-desc {
  margin: 0;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
  line-height: 1.6;
}

.shop-item-detail-ward {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}

.shop-item-trait-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  /* border-top and padding-top are conditional; kept inline */
}

.shop-item-trait-row {
  display: flex;
  gap: 6px;
  align-items: baseline;
}

.shop-item-trait-name {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 600;
  color: var(--fg);
  flex-shrink: 0;
}

.shop-item-trait-desc {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  line-height: 1.5;
}

/* Weapon subtype group heading */
.shop-subtype-heading {
  font-family: var(--font-display);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 5px 8px 3px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--paper-deep);
  cursor: pointer;
  user-select: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* expand arrow — transform is dynamic; kept inline */
.shop-subtype-arrow {
  display: inline-block;
  transition: transform 0.2s ease;
  line-height: 1;
}

/* Collapsible group rows — grid-template-rows is dynamic; kept inline */
.shop-subtype-group {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 0.22s ease;
}

.shop-subtype-group-inner {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* ── Shop sidebar ────────────────────────────────────────── */

.shop-sidebar {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.shop-allowances {
  border: 1px solid var(--border);
  padding: 10px 12px;
}

.shop-allowances-title {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 10px;
}

.shop-allowance-item {
  margin-bottom: 8px;
}

.shop-allowance-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  margin-bottom: 2px;
}

/* color driven by claimed state; kept inline */
.shop-allowance-value {
  font-family: var(--font-body);
  font-size: 11px;
  line-height: 1.4;
}

.shop-gold-remaining {
  border: 1px solid var(--border);
  padding: 8px 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.shop-gold-label {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}

/* color depends on canAfford; kept inline */
.shop-gold-value {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
}

.shop-basket-panel {
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  flex: 1;
}

.shop-basket-header {
  padding: 7px 12px;
  border-bottom: 1px solid var(--border-soft);
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.shop-basket-list {
  flex: 1;
  overflow-y: auto;
  max-height: 200px;
  padding: 4px 0;
}

.shop-basket-empty {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  font-style: italic;
  padding: 12px;
}

.shop-basket-item {
  display: flex;
  align-items: center;
  padding: 4px 12px;
  gap: 6px;
}

.shop-basket-item-name {
  flex: 1;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
  line-height: 1.3;
}

/* color = free ? freeCol : fg-muted; kept inline */
.shop-basket-item-cost {
  font-family: var(--font-mono);
  font-size: 11px;
  flex-shrink: 0;
}

.shop-basket-remove {
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--fg-muted);
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
}

.shop-basket-footer {
  padding: 7px 12px;
  border-top: 1px solid var(--border-soft);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.shop-basket-total-label {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}

/* color depends on canAfford; kept inline */
.shop-basket-total-value {
  font-family: var(--font-mono);
  font-size: 13px;
}

.shop-cant-afford {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--rubric);
  line-height: 1.4;
}

.shop-actions-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* ── Inventory item slot classes ────────────────────────── */

.inv-item-slot-0 {}
.inv-item-slot-1 {}
.inv-item-slot-2 {}
.inv-item-slot-3 {}
.inv-item-slot-4 {}
.inv-item-slot-5 {}

/* ── Inventory slot-count badge ─────────────────────────── */

.inv-slot-badge {
  background: transparent;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  width: 20px;
  height: 20px;
  text-align: center;
  flex-shrink: 0;
  border-width: 1px;
  border-style: solid;
}

.inv-slot-0 { color: var(--fg-muted);    border-color: var(--fg-muted); }
.inv-slot-1 { color: var(--verdigris);   border-color: var(--verdigris); }
.inv-slot-2 { color: var(--verdigris);   border-color: var(--verdigris); }
.inv-slot-3 { color: var(--sepia);       border-color: var(--sepia); }
.inv-slot-4 { color: var(--rubric);      border-color: var(--rubric); }
.inv-slot-5 { color: var(--rubric-deep); border-color: var(--rubric-deep); }

/* ── Shop purchase feedback ─────────────────────────────── */

@keyframes shopAddFlash {
  0%, 100% { background: transparent; color: var(--fg);     border-color: var(--border); }
  30%, 70%  { background: var(--verdigris-deep); color: var(--paper); border-color: var(--verdigris-deep); }
}

.shop-add-flash {
  animation: shopAddFlash 0.5s ease;
}

@keyframes shopToastIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-10px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes shopToastOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.shop-toast {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: 0.8125rem;
  padding: 9px 18px;
  border: 1px solid var(--border);
  white-space: nowrap;
  pointer-events: none;
  animation: shopToastIn 0.2s ease, shopToastOut 0.3s ease 1.7s forwards;
}

/* ═══════════════════════════════════════════════════════════════
   app.jsx extracted inline styles
   Prefixes:
     pull-   Pull resolution / free-play modal
     gm-     GM panel (GmPanelFull, GmCharacterGrid)
     seat-   Seat / play-zone panel rows
     app-    App-level layout
     login-  Login screen
     lobby-  Lobby page
     cm-     CharacterManager
     init-   InitiativeTrackerPanel
     cbt-    CombatTracker
     math-   MathTerm / MathOp helpers
     sctl-   ServerControls
     li-     LabeledInput helper
     cgm-    CreateGameModal
═══════════════════════════════════════════════════════════════ */

/* ── Shared field label (uppercase eyebrow on form fields) ──── */

/* Used in GmPanelFull selects, FreePlayModal, LoginScreen, LobbyPage, CreateGameModal */
.field-eyebrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Taller variant used in LoginScreen inputs */
.field-eyebrow--login {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Generic form field: column flex with small gap */
.form-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Generic select inside panels / modals */
.form-select {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  width: 100%;
}

/* ── MathTerm / MathOp (PullModal / FreePlayModal) ─────────── */

.math-term {
  text-align: center;
}

.math-term__label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 2px;
}

.math-term__value {
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1;
  color: var(--fg-strong);
}
.math-term__value--lg { font-size: 38px; }

.math-op {
  font-family: var(--font-body);
  font-size: 20px;
  color: var(--fg-muted);
  align-self: center;
  padding-top: 14px;
}

/* ── PullModal inner dialog box ─────────────────────────────── */

.pull-modal-box {
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 6px;
  width: min(680px, 95vw);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-3);
  overflow: hidden;
}

.pull-modal-box--narrow {
  width: min(560px, 95vw);
}

/* Ally target picker — opens when buff/heal/cure auto-apply needs an
   ally target the activator didn't pre-pick. Single column of names,
   first row is the activator (self-buff is one click). */
.ally-picker-modal__body {
  padding: 12px 16px;
}
.ally-picker-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ally-picker-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--paper) 92%, var(--sepia));
  border: 1px solid var(--sepia);
  border-radius: 4px;
  font-family: var(--font-body, Georgia, serif);
  font-size: 1em;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: background-color 100ms ease, border-color 100ms ease;
}
.ally-picker-row:hover {
  background: color-mix(in srgb, var(--paper) 78%, var(--sepia));
  border-color: var(--ink);
}
.ally-picker-row--self {
  border-color: var(--verdigris-deep, var(--verdigris));
}
.ally-picker-row--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.ally-picker-row--disabled:hover {
  background: color-mix(in srgb, var(--paper) 92%, var(--sepia));
  border-color: var(--sepia);
}
.ally-picker-row__name {
  flex: 1 1 auto;
  font-family: var(--font-display, Georgia, serif);
}
.ally-picker-row__tag {
  flex: 0 0 auto;
  font-size: 0.85em;
  color: var(--fg-muted, var(--ink));
  font-style: italic;
}

/* Alleviate trauma-cure picker (Cleric) — target + trauma selection. */
.alleviate-modal__body {
  padding: 12px 16px;
}
.alleviate-empty-notice {
  margin: 0 0 10px;
  padding: 8px 12px;
  border: 1px solid var(--rubric);
  border-radius: 4px;
  background: color-mix(in srgb, var(--paper) 88%, var(--rubric));
  color: var(--ink);
  font-family: var(--font-body, Georgia, serif);
  font-style: italic;
  font-size: 0.95em;
}
.alleviate-trauma-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.alleviate-trauma-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--paper) 92%, var(--sepia));
  border: 1px solid var(--sepia);
  border-radius: 4px;
  font-family: var(--font-body, Georgia, serif);
  font-size: 1em;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: background-color 100ms ease, border-color 100ms ease;
}
.alleviate-trauma-row:hover {
  background: color-mix(in srgb, var(--paper) 78%, var(--sepia));
  border-color: var(--ink);
}
.alleviate-trauma-row--selected {
  border-color: var(--verdigris-deep, var(--verdigris));
  background: color-mix(in srgb, var(--paper) 82%, var(--verdigris));
}
.alleviate-trauma-row__name {
  flex: 1 1 auto;
  font-family: var(--font-display, Georgia, serif);
}
.alleviate-trauma-row__lvl {
  flex: 0 0 auto;
  font-size: 0.85em;
  color: var(--fg-muted, var(--ink));
  font-variant-numeric: tabular-nums;
}

.pull-modal-box--medium {
  width: min(600px, 95vw);
}

/* Class "Learn more" modal — wide enough for prose-paragraph reading
   without becoming unwieldy. Body is plain-block so headings, paragraphs,
   and tables render with their natural document layout instead of the
   modal-body default flex column. */
.class-learn-more-box {
  width: min(760px, 95vw);
  max-height: 88vh;
}
.class-learn-more-body {
  display: block;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.6;
  color: var(--fg);
}
.class-learn-more-body h1 {
  font-family: var(--font-display);
  font-size: 32px;
  color: var(--ink-strong);
  letter-spacing: 0.02em;
  margin: 0 0 16px;
}
.class-learn-more-body h2 {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--ink-strong);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 22px 0 10px;
}
.class-learn-more-body h3 {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--ink-strong);
  margin: 18px 0 8px;
}
.class-learn-more-body p { margin: 0 0 10px; }
.class-learn-more-body em {
  font-family: var(--font-display-italic);
  font-style: italic;
}
.class-learn-more-body strong { color: var(--ink-strong); }
.class-learn-more-body ul, .class-learn-more-body ol { margin: 0 0 10px 22px; }
.class-learn-more-body li { margin-bottom: 4px; }

/* Core Attributes / Key Skills spread at the end of each class
   page — two columns side-by-side, centered horizontally in the
   modal body. Collapses to a single stack on narrow viewports. */
.class-learn-more-body .class-spread {
  display: flex;
  justify-content: center;
  gap: 64px;
  margin: 24px auto 16px;
  max-width: 520px;
  flex-wrap: wrap;
}
.class-learn-more-body .class-spread__col {
  flex: 0 1 auto;
  text-align: center;
}
.class-learn-more-body .class-spread__col h3 {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
}
.class-learn-more-body .class-spread__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.class-learn-more-body .class-spread__col li {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--fg-strong);
  margin: 0 0 4px;
  position: relative;
  padding-left: 16px;
  display: inline-block;
  text-align: left;
}
.class-learn-more-body .class-spread__col li::before {
  content: '❧';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--rubric);
  font-size: 12px;
}
@media (max-width: 480px) {
  .class-learn-more-body .class-spread { gap: 24px; flex-direction: column; align-items: center; }
}
.class-learn-more-body table {
  border-collapse: collapse;
  margin: 10px 0;
  font-size: 13px;
  width: 100%;
}
.class-learn-more-body th,
.class-learn-more-body td {
  border: 1px solid var(--border-soft);
  padding: 5px 9px;
  text-align: left;
  vertical-align: top;
}
.class-learn-more-body th {
  background: var(--surface);
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.class-learn-more-body hr {
  border: none;
  border-top: 1px solid var(--border-soft);
  margin: 16px 0;
}
.class-learn-more-loading,
.class-learn-more-error {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  text-align: center;
  padding: 32px 0;
}

/* Opening quote + attribution at the top of each class page. The quote
   is enlarged and italicized; surrounding quote marks are stripped by
   the preprocessor. Attribution sits on its own line below with an
   em-dash prefix. Multi-line poetic quotes (Bard / Theurge / Warrior)
   render each source line as its own row via .class-opening__line. */
.class-opening {
  margin: 4px 0 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border-soft);
}
.class-opening__quote {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 20px;
  line-height: 1.55;
  color: var(--ink-strong);
}
.class-opening__line { display: block; }
.class-opening__attribution {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 16px;
  color: var(--fg-muted);
  margin-top: 10px;
}

/* Wider variant used by the EffectsModal — the three trauma/status
   grids need extra horizontal room so each tile can fit longer
   single-word names like "Dehydration" or "Mental Shock" without
   forcing a mid-word break. */
.pull-modal-box--effects {
  width: min(760px, 95vw);
}

/* The header "The Call" eyebrow */
.pull-header-eyebrow {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 4px;
}

/* Modal h2 heading */
.pull-heading {
  font-family: var(--font-display);
  font-size: 28px;
  margin: 0 0 4px;
  color: var(--ink-strong);
  line-height: 1;
}

.pull-heading--lg {
  font-size: 26px;
  margin: 0;
}

/* Call note italic */
.pull-call-note {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 14px;
  color: var(--fg-muted);
  margin: 0;
}

/* Difficulty block (right side of modal header) */
.pull-difficulty-block {
  text-align: right;
  flex-shrink: 0;
}

.pull-difficulty-label {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.pull-difficulty-value {
  font-family: var(--font-display);
  font-size: 52px;
  color: var(--rubric);
  line-height: 1;
}

.pull-difficulty-name {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
}

/* Live math bar */
.pull-math-bar {
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
}
/* Damage variant — used in AttackModal beneath the pull math row.
   Faintly red-washed so it reads as the "damage" half at a glance,
   distinct from the pull half above it. */
.pull-math-bar--damage {
  background: color-mix(in srgb, var(--rubric) 8%, var(--surface));
}

.pull-vs-text {
  align-self: center;
  padding-top: 14px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
}

.pull-result-tag {
  align-self: center;
  padding-top: 14px;
}

/* Hand area */
.pull-hand-area {
  padding: 16px 20px;
  flex: 1;
  overflow-y: auto;
}

.pull-hand-section-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 12px;
}

.pull-card-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  padding: 4px 0 12px;
}

/* Card lift wrapper — sweepMode lifts every card via a class, and
   selection lifts the single picked card. The lift lives on the wrapper
   (not the inner .pc) so any sibling overlays — like the trait-trigger
   chase border drawn on .atk-card-trait-trigger::after — travel with
   the card instead of staying behind. The inner .pc--lifted /
   .pc--selected transforms are neutralised here so they don't compound
   on top of the wrapper's lift. */
.pull-card-lift {
  transition: transform var(--dur-2) var(--ease-quill);
}
.pull-card-lift--sweep { transform: translateY(-14px); }
.pull-card-lift--selected { transform: translateY(-18px); }
.pull-card-lift--sweep .pc--lifted,
.pull-card-lift--selected .pc--selected { transform: none; }

.pull-empty-hand {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  text-align: center;
  padding: 20px 0;
}

/* Sweep toggle row */
.pull-sweep-row {
  margin-top: 8px;
  padding: 10px 14px;
  border-radius: 2px;
  border: 1px solid var(--border);
  background: transparent;
}
.pull-sweep-row--active {
  border-color: var(--verdigris);
  background: rgba(74, 117, 86, 0.06);
}
.pull-sweep-row--active.pull-sweep-row--critfail {
  border-color: var(--rubric);
  background: rgba(165, 42, 42, 0.06);
}

.pull-sweep-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.pull-sweep-title {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 13px;
  color: var(--fg);
}
.pull-sweep-title--active { color: var(--verdigris-deep); }
.pull-sweep-title--active.pull-sweep-title--critfail { color: var(--rubric); }

.pull-sweep-sub {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}

/* Header cards row (free-play modal) */
.pull-header-cards {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Free-play italic hint */
.pull-freeplay-hint {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
  margin: 0;
}

/* Free-play computed-value display */
.pull-freeplay-computed {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
}

/* Live total surface */
.pull-total-surface {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 12px 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* ── GmPanelFull ────────────────────────────────────────────── */

.gm-panel-root {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* .gm-header / .gm-header-label retired — the panel-handle hosts the
   crown icon next to the "Game Master" label now (see DraggablePanel
   mount in renderPanel). */

.gm-tab-bar {
  display: flex;
  border-bottom: 1px solid var(--border);
}

/* Tab button — borderBottom and color are driven by active state; kept inline */
.gm-tab-btn {
  flex: 1;
  padding: 7px 4px;
  border: none;
  background: transparent;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  margin-bottom: -1px;
}

.gm-body {
  position: relative;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  flex: 1;
}

/* Inline difficulty stepper — minus / value+name / plus. Lives in
   the action row at the bottom of the GM panel, pushed to the right
   edge so it sits opposite the Post-call / Open-modal cluster.
   Ubuntu font for the readout. */
.gm-difficulty-inline {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* Push the stepper to the right edge of the action row, opposite
   the Post-call / Open-modal buttons. */
.gm-difficulty-inline--end { margin-left: auto; }
.gm-difficulty-inline-readout {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 80px;
  justify-content: center;
}
.gm-difficulty-inline .gm-difficulty-value {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 700;
  font-size: 18px;
  color: var(--rubric);
  line-height: 1;
}
.gm-difficulty-inline .gm-difficulty-name {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 400;
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: 0;
  letter-spacing: 0.04em;
  text-transform: lowercase;
}

/* Target tile grid — replaces the multiselect with a wrapping flex
   row of toggle buttons. Selected tiles invert (ink bg / paper text);
   unselected tiles show a paper bg with the ink outline. Tile width
   sizes to its label so a long display name doesn't cram the grid. */
.gm-target-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.gm-target-tile {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  padding: 6px 12px;
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  border-radius: 4px;
  cursor: pointer;
  transition: background var(--dur-1, 120ms) ease, color var(--dur-1, 120ms) ease;
}
.gm-target-tile:hover {
  background: var(--paper-deep);
}
.gm-target-tile.is-selected {
  background: var(--ink);
  color: var(--paper);
}
.gm-target-tile.is-selected:hover {
  background: var(--ink);
}

/* Social Attack presets in the GM panel — a compact grid of preset
   buttons. Click fills the call form with the canonical Attribute +
   Skill for that attack; the trauma label below the name reminds
   the GM what to apply on a successful resolution. */
.gm-social-attacks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 4px;
  margin: 4px 0 8px;
}
.gm-social-attack-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: rgba(193, 75, 58, 0.04);
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--ink);
  cursor: pointer;
  border-radius: 2px;
  transition: background 120ms ease, border-color 120ms ease;
  text-align: left;
}
.gm-social-attack-tile:hover {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.gm-social-attack-name { font-weight: 600; }
.gm-social-attack-trauma {
  font-size: 9px;
  letter-spacing: 0.04em;
  font-style: italic;
  opacity: 0.75;
  font-variant-caps: all-small-caps;
}
.gm-social-attack-tile:hover .gm-social-attack-trauma { opacity: 1; }

/* Open-calls list — surfaces every call the session currently carries
   so the GM can resolve any one of them individually. Each row reads
   target / formula / progress (X/N answered) plus a Resolve button. */
.gm-open-calls {
  list-style: none;
  margin: 4px 0 10px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.gm-open-call-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 8px;
  border: 1px solid var(--border-soft, rgba(0,0,0,0.12));
  border-radius: 3px;
  background: rgba(255, 250, 235, 0.5);
}
.gm-open-call-text {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 12px;
  flex: 1;
  min-width: 0;
}
.gm-open-call-progress {
  opacity: 0.7;
  font-style: italic;
}

/* Attribute + Skill side-by-side — flex row with each field flexed
   so the dropdowns share width evenly. */
.gm-pull-fields {
  display: flex;
  gap: 10px;
}
.gm-pull-fields .gm-pull-field { flex: 1; min-width: 0; }

.gm-btn-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

/* Effect apply button — base; border/color vary per type */
.gm-effect-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 8px;
  border-radius: 2px;
  cursor: pointer;
}

.gm-trauma-btn {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
}

.gm-status-btn {
  border: 1px solid var(--rubric);
  background: transparent;
  color: var(--rubric);
}

.gm-effect-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}

.gm-target-select {
  width: 100%;
  font-family: var(--font-body);
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  margin-top: 6px;
}

/* ── GmCharacterGrid ────────────────────────────────────────── */

.gm-grid-outer {
  padding: 16px 20px 80px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: 16px;
  align-items: start;
}

.gm-grid-empty {
  padding: 40px 24px;
  text-align: center;
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 16px;
}

.gm-grid-card {
  border: 1px solid var(--border);
  background: var(--paper);
  overflow: hidden;
}

/* Offline players (no live presence) are dimmed; still fully editable. */
.gm-grid-card--offline {
  opacity: 0.72;
}

.gm-grid-status-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 7px;
  vertical-align: middle;
}

.gm-grid-status-dot--online {
  background: var(--verdigris);
}

.gm-grid-status-dot--offline {
  background: var(--fg-muted);
  opacity: 0.6;
}

/* Ambient-audio control bar — Storyteller view center column. Starts silent;
   a player clicks the toggle to opt in (also satisfies browser autoplay). */
.ambient-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  background: var(--surface);
  margin-bottom: 6px;
}

.ambient-toggle {
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 2px 4px;
}

.ambient-volume {
  flex: 1;
  max-width: 160px;
  accent-color: var(--verdigris-deep);
}

.ambient-bar__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border);
  margin-left: auto;
  transition: background var(--dur-1) var(--ease-quill);
}

.ambient-bar--live .ambient-bar__dot {
  background: var(--verdigris);
  box-shadow: 0 0 5px var(--verdigris);
}

/* Situational facet toggles in the pull modal — the player taps the conditional
   facets that fit this pull; checked chips fold into the total. */
.pull-situational-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 16px 0;
}

.pull-sit-chip {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 11px;
  padding: 3px 10px;
  cursor: pointer;
  transition: all var(--dur-1) var(--ease-quill);
}

.pull-sit-chip:hover { border-color: var(--border); color: var(--fg); }

.pull-sit-chip--on {
  color: var(--paper);
  background: var(--verdigris-deep);
  border-color: var(--verdigris-deep);
}

.gm-grid-card-header {
  background: var(--surface);
  padding: 10px 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--border);
}

.gm-grid-card-name-block {
  min-width: 0;
  flex: 1;
}

.gm-grid-card-name {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gm-grid-card-meta {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}

.gm-grid-hp-pad {
  padding: 10px 14px;
}

/* ── InitiativeTrackerPanel ─────────────────────────────────── */

.init-tracker-root {
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.init-tracker-no-conn {
  padding: 32px 14px;
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  color: var(--fg-muted);
}

.init-tracker-section {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* Player rows in the initiative tracker fit two side-by-side when the
   panel has the room — falls back to a single column at narrow widths
   so each row still has space for its name + vitals. The grid lives
   under the GM/foe section so the GM row stays full-width while the
   PCs share a row beneath. */
/* One player per row — earlier we tried a 2-col grid here, but the
   row contents (name + status + vitals + targeting chips) cram up
   too tightly. Single column gives each player room to breathe. */
.init-tracker-players-grid {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* "+ Add effect" button now rides at the right edge of the stat
   cells row (init-foe-stats) so it shares vertical space with
   CD/RD/RS/Ward instead of opening a new line for itself. The
   .init-peer-effect-row class is retired but kept harmlessly above
   for any external reference; the new container is below. */
.init-foe-stats { display: flex; align-items: center; }
.init-foe-stats__effect { margin-left: auto; }

/* EffectsModal — applied per-character via the "+ Add effect" pip
   on each PC row. Dense grid of trauma / status tiles; clicking a
   tile applies it (modal stays open so the GM can stack effects).
   Visually sibling to the rest of our pull-modal-box content. */
/* ── EffectsModal — three labelled grids ──────────────────────────────
   Minor / Major / Status each get an equal-column grid so all tiles
   read at the same size and the grids align column-for-column down
   the modal. We use a shared CSS variable (--effects-modal-cols) so
   the column count can be tuned in one place; current value of 7
   produces 2 rows for Minor (13) and Major (14), and just over a
   row for Status (9) — the last row of Status leaves trailing empty
   cells but tile widths stay consistent across all three sections. */
.effects-modal__body {
  padding-top: 4px;
  /* Phase-1 → phase-2 transition: when `.is-detail` is added to the
     body, the grid zone shrinks into a compact strip pinned at the
     top and the detail card slides in below. CSS transitions handle
     the timing — anime.js isn't necessary here since both the grid
     zone and the detail card animate the same standard properties
     in a tightly synced timing window. */
}
.effects-modal__grid-zone {
  transform-origin: top center;
  transition:
    max-height 280ms cubic-bezier(0.32, 0.08, 0.24, 1),
    transform  280ms cubic-bezier(0.32, 0.08, 0.24, 1),
    opacity    220ms ease;
  overflow: hidden;
  max-height: 1200px;
  /* Phase 1: full-size grid. */
}
.effects-modal__body.is-detail .effects-modal__grid-zone {
  /* Phase 2: collapsed strip pinned at the top. The remaining
     row is interactive (the GM can still click another effect to
     swap). Pointer-events stay on so the user can switch
     selections without backing out first. */
  max-height: 84px;
  transform: scale(0.92);
  opacity: 0.55;
}
.effects-modal__section {
  margin-bottom: 14px;
}
.effects-modal__section:last-child { margin-bottom: 0; }
.effects-modal__body.is-detail .effects-modal__section {
  margin-bottom: 6px;
}
.effects-modal__grid {
  display: grid;
  grid-template-columns: repeat(var(--effects-modal-cols, 7), minmax(0, 1fr));
  gap: 6px;
  margin-top: 4px;
}
.effects-modal__tile {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 11px;
  font-weight: 500;
  padding: 8px 4px;
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Fixed minimum height so every tile across all three grids reads
     as the same card size, regardless of how short or long the name
     is. The icon row (36px) and the two-line name slot (2.4em ≈ 27px)
     plus padding/gap come out to ~96px. */
  min-height: 96px;
  justify-content: flex-start;
  gap: 6px;
  min-width: 0;
  text-align: center;
  line-height: 1.2;
  transition: background var(--dur-1, 120ms) ease, color var(--dur-1, 120ms) ease, border-color var(--dur-1, 120ms) ease;
}
.effects-modal__tile-name {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Reserve vertical room for two lines so single- and double-line
     names share the same overall tile height. Vertical centering
     keeps short names visually balanced inside that slot. */
  min-height: calc(2 * 1.2em);
  width: 100%;
  /* Words stay whole unless they can't fit at all. The previous
     `word-break: break-word` was breaking "Dehydration" mid-word
     even when the next column over had room; `normal` keeps it
     intact and lets `overflow-wrap` only kick in for genuinely
     too-wide single tokens. */
  word-break: normal;
  overflow-wrap: break-word;
  hyphens: none;
}
@media (max-width: 720px) {
  /* Narrower viewports get fewer columns so tiles stay legible. */
  .effects-modal__body { --effects-modal-cols: 5; }
}
@media (max-width: 480px) {
  .effects-modal__body { --effects-modal-cols: 4; }
}
.effects-modal__tile:hover {
  background: var(--ink);
  color: var(--paper);
}
.effects-modal__tile--trauma {
  border-color: var(--rubric);
  color: var(--rubric);
}
.effects-modal__tile--trauma:hover {
  background: var(--rubric);
  color: var(--paper);
}
/* Inline trauma-gate banners shown at the top of AbilityActivationModal
   when the active character has a condition that affects this
   activation. The block variant (Incapacitated) reads in solid
   cinnabar; lighter warns are outlined. */
.aam-effect-warn {
  font-family: "IM Fell English Italic", "EB Garamond Italic", Georgia, serif;
  font-size: 13px;
  padding: 8px 10px;
  margin: 8px 0;
  border: 1px solid var(--rubric);
  border-left-width: 3px;
  color: var(--rubric);
  background: rgba(193, 75, 58, 0.06);
}
.aam-effect-warn--block {
  background: var(--rubric);
  color: var(--paper);
  font-style: normal;
  font-weight: 600;
}

/* Major trauma — heavier weight than minor; the canonical conditions
   here (Mental Shock, Tainted, Undead, Shattered Soul, …) are
   permanent until cured, so the tile reads in solid cinnabar with a
   double-stroked border. */
.effects-modal__tile--major-trauma {
  border-color: var(--rubric);
  color: var(--paper);
  background: var(--rubric);
  box-shadow: inset 0 0 0 2px var(--paper), 0 0 0 1px var(--rubric);
  font-weight: 600;
}
.effects-modal__tile--major-trauma:hover {
  background: var(--ink);
  border-color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--paper), 0 0 0 1px var(--ink);
}
/* In-tile icon — sits above the label so the tile reads as a small
   card. The icon's gold disc shows in front of the tile background
   uniformly, even on the cinnabar major-trauma tiles, so the same
   sizing works for all three sections. */
.effects-modal__tile-icon {
  width: 36px;
  height: 36px;
  object-fit: contain;
  flex-shrink: 0;
}

/* ── Effects modal — detail breakout (phase 2) ──────────────────────
   Slides up beneath the collapsed grid when an option is selected.
   The colour family follows the picked tile: rubric for trauma,
   indigo for status. */
.effects-detail {
  margin-top: 12px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--paper-deep, var(--paper));
  position: relative;
  /* Slide-in. The body's transition-delay on the grid zone keeps
     the two animations sequential so the detail card appears once
     the grid has finished collapsing. */
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity   240ms ease 120ms,
    transform 280ms cubic-bezier(0.32, 0.08, 0.24, 1) 120ms;
}
.effects-modal__body.is-detail .effects-detail {
  opacity: 1;
  transform: translateY(0);
}
.effects-detail--trauma { border-color: var(--rubric); }
.effects-detail--major  { border-color: var(--rubric); border-width: 2px; }
.effects-detail--status { border-color: var(--indigo); }
.effects-detail__back {
  position: absolute;
  top: 8px;
  right: 10px;
  font-family: var(--font-body);
  font-size: 11px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-muted);
  padding: 2px 8px;
  border-radius: 2px;
  cursor: pointer;
}
.effects-detail__back:hover { color: var(--fg); border-color: var(--fg); }
.effects-detail__head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 10px;
}
.effects-detail__icon {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
}
.effects-detail__eyebrow {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 2px;
}
.effects-detail__name {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 0.04em;
  color: var(--ink);
}
.effects-detail__sub {
  font-family: var(--font-body-italic, var(--font-body));
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.effects-detail__body {
  margin: 6px 0 12px;
}
.effects-detail__effect {
  margin: 0 0 6px 0;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink);
}
.effects-detail__cure {
  margin: 0 0 4px 0;
  font-family: var(--font-body-italic, var(--font-body));
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
}
/* Multi-cluster minor trauma cluster picker — visible on Anger /
   Humiliation / Shaken / Intoxication / Tired only. Selecting a
   button flips the chosen Attribute group the −1 will hit; the
   detail-card description above updates live to match. */
.effects-detail__cluster {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px dotted var(--border-soft, var(--border));
}
.effects-detail__cluster-label {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.effects-detail__cluster-btn {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 3px 10px;
  border: 1px solid var(--rubric);
  background: transparent;
  color: var(--rubric);
  border-radius: 2px;
  cursor: pointer;
  transition: background var(--dur-1, 120ms) ease, color var(--dur-1, 120ms) ease;
}
.effects-detail__cluster-btn:hover { background: rgba(193, 75, 58, 0.12); }
.effects-detail__cluster-btn.is-selected {
  background: var(--rubric);
  color: var(--paper);
}

.effects-detail__duration-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 10px;
  border-top: 1px dotted var(--border-soft, var(--border));
  margin-bottom: 12px;
}
.effects-detail__duration-eyebrow {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.effects-detail__field {
  display: flex;
  align-items: center;
  gap: 8px;
}
.effects-detail__field-label {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}
.effects-detail__input {
  width: 64px;
  padding: 6px 8px;
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 14px;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: var(--paper);
  color: var(--ink);
}
.effects-detail__input:focus {
  outline: none;
  border-color: var(--rubric);
  box-shadow: 0 0 0 2px rgba(193, 75, 58, 0.15);
}
.effects-detail__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.effects-modal__tile--status {
  border-color: var(--indigo);
  color: var(--indigo);
}
.effects-modal__tile--status:hover {
  background: var(--indigo);
  color: var(--paper);
}

/* GM's face-down hand inline on the Narrator's row in the initiative
   tracker — replaces the standalone Narrator's Hand panel. Lives on
   the same line as the name (right-aligned via `margin-left: auto`)
   at xs size so it stays out of the way of the row's other content.
   Cards overlap so a hand of 5+ stays compact. */
.init-peer-gm-hand {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  flex-shrink: 0;
}
.init-peer-gm-hand > * + * {
  margin-left: -14px;
}

/* Peer row — background is isMe-conditional; kept inline */
.init-peer-row {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  overflow: hidden;
}
.init-peer-row.is-self { background: var(--surface); }

.init-peer-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.init-peer-name {
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--fg-strong);
  flex: 1;
}

.init-peer-status-label {
  font-family: var(--font-body);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--status-color, var(--fg));
}

.init-peer-btn-row {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
}

.init-status-btn {
  font-family: var(--font-body);
  font-size: 0.6875rem;
  padding: 4px 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
}
.init-status-btn.is-active {
  border-color: var(--status-color, var(--ink));
  background: var(--status-color, var(--ink));
  color: var(--paper);
}

.init-status-icon {
  font-size: 0.75rem;
}

/* Foe row — border and bg are isTargeted-conditional; kept inline */
.init-foe-row {
  position: relative;
  margin-left: 14px;
  padding: 7px 10px;
  overflow: hidden;
  transition: border-color 150ms, background 150ms;
}

.init-foe-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* foe name — color is isTargeted-conditional; kept inline */
.init-foe-name {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  flex: 1;
}

.init-foe-class {
  font-family: var(--font-body);
  font-size: 0.625rem;
  color: var(--fg-muted);
  margin-left: 6px;
}

.init-foe-stats {
  display: flex;
  gap: 6px;
}

.init-foe-stat-cell {
  text-align: center;
  padding: 4px 8px;
  border: 1px solid var(--ink);
  background: var(--paper);
  min-width: 38px;
  border-radius: 3px;
  box-shadow: 0 1px 2px rgba(20, 12, 4, 0.18);
}

.init-foe-stat-label {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 500;
  font-size: 0.6875rem;
  color: var(--fg-muted);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.init-foe-stat-value {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--fg-strong);
  line-height: 1.05;
}

.init-foe-bars {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Barrier tray — small bezel-style strip rendered above the player's
   hand fan only when the active character has at least one Barrier
   active. Each card is face-down; clicking it spends the card and
   credits its value to the player's HP. Hides itself entirely when
   no barriers are active so it doesn't shift the layout at rest. */
.barrier-tray {
  display: flex;
  gap: 12px;
  padding: 6px 12px;
  background: linear-gradient(180deg, var(--surface-hi, var(--surface)) 0%, var(--surface) 100%);
  border: 1px solid var(--ink);
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(30, 20, 10, 0.15) inset;
  flex-wrap: wrap;
  justify-content: center;
}
.barrier-tray__entry {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 6px;
}
.barrier-tray__entry + .barrier-tray__entry {
  border-left: 1px solid var(--border);
  padding-left: 12px;
}
.barrier-tray__header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.barrier-tray__label {
  color: var(--indigo, var(--fg-strong));
  font-weight: 600;
}
.barrier-tray__count {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg);
}
.barrier-tray__rounds {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg-muted);
}
.barrier-tray__dismiss {
  width: 14px;
  height: 14px;
  margin-left: 2px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: 9px;
  line-height: 1;
  padding: 0;
}
.barrier-tray__dismiss:hover {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.barrier-tray__cards {
  display: flex;
  gap: 3px;
  align-items: flex-end;
}
.barrier-tray__card {
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: transform var(--dur-2, 240ms) var(--ease-quill, cubic-bezier(0.32, 0.08, 0.24, 1));
}
.barrier-tray__card:hover {
  transform: translateY(-3px);
}

/* Be Prepared tray — Hand topSlot strip for the Ranger's armed
   Unexpected Hazard card. Mirrors the Barrier tray surface/bezel. */
.bp-tray {
  display: flex;
  gap: 12px;
  padding: 6px 12px;
  background: linear-gradient(180deg, var(--surface-hi, var(--surface)) 0%, var(--surface) 100%);
  border: 1px solid var(--ink);
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(30, 20, 10, 0.15) inset;
  justify-content: center;
}
.bp-tray__entry { display: flex; flex-direction: column; gap: 4px; padding: 0 6px; }
.bp-tray__header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.bp-tray__label { color: var(--rubric); font-weight: 600; }
.bp-tray__dismiss {
  width: 14px; height: 14px; margin-left: 2px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: 9px;
  line-height: 1;
  padding: 0;
}
.bp-tray__dismiss:hover { background: var(--rubric); color: var(--paper); border-color: var(--rubric); }
.bp-tray__body { display: flex; gap: 6px; align-items: flex-end; }
.bp-tray__trigger {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 10px;
  border: 1px solid var(--rubric);
  background: var(--rubric);
  color: var(--paper);
  cursor: pointer;
  align-self: center;
}
.bp-tray__trigger:hover { background: var(--ink); border-color: var(--ink); }

/* Backpalm (Rogue T3) — stored-card tray (up to 3 face-up cards).
   Mirrors .bp-tray's chrome so the Hand topSlot reads as a uniform
   row when multiple in-tray facets are active at once. Click a card
   to play it (no action cost — see BackpalmTray's onUseCard). */
.backpalm-tray {
  display: flex;
  gap: 12px;
  padding: 6px 12px;
  background: linear-gradient(180deg, var(--surface-hi, var(--surface)) 0%, var(--surface) 100%);
  border: 1px solid var(--ink);
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(30, 20, 10, 0.15) inset;
  flex-direction: column;
  align-items: stretch;
}
.backpalm-tray__header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.backpalm-tray__label { color: var(--ink-strong); font-weight: 600; }
.backpalm-tray__count { color: var(--fg-muted); }
.backpalm-tray__body { display: flex; gap: 6px; align-items: flex-end; }
.backpalm-tray__card {
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease-quill);
}
.backpalm-tray__card:hover { transform: translateY(-2px); }

/* Be Prepared modal — three-option picker. */
.bp-modal__hint {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
  margin: 6px 0;
}
.bp-modal__options { display: flex; gap: 8px; flex-wrap: wrap; }
.bp-modal__opt {
  flex: 1 1 0;
  min-width: 120px;
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.04em;
  padding: 10px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-quill), background var(--dur-1) var(--ease-quill);
}
.bp-modal__opt:hover { border-color: var(--rubric); }
.bp-modal__opt.is-sel { border-color: var(--rubric); background: var(--rubric); color: var(--paper); }
.bp-modal__sub {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border-soft);
}
.bp-modal__subopt {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  text-align: left;
}
.bp-modal__subopt:hover { border-color: var(--rubric); }
.bp-modal__subopt.is-sel { border-color: var(--rubric); background: var(--rubric); color: var(--paper); }
.bp-modal__hand { display: flex; gap: 4px; flex-wrap: wrap; }
.bp-modal__handcard {
  background: transparent;
  border: 2px solid transparent;
  border-radius: 4px;
  padding: 1px;
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease-quill);
}
.bp-modal__handcard:hover { transform: translateY(-3px); }
.bp-modal__handcard.is-sel { border-color: var(--rubric); }
.bp-modal__empty {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
}

/* Yield to None tray — bezel-style strip rendered above the player's
   hand fan when the active character has activated Yield to None.
   Visually mirrors the Barrier tray (same surface, bezel, header
   typography) but adds a row of action buttons (Trigger Intercede,
   Spend All) and switches to a "spent" state once the finishing
   attack fires — the cards flip face-up and the tray dims until the
   round-prune effect clears it on the next round tick. */
.yield-tray {
  display: flex;
  gap: 12px;
  padding: 6px 12px;
  background: linear-gradient(180deg, var(--surface-hi, var(--surface)) 0%, var(--surface) 100%);
  border: 1px solid var(--ink);
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(30, 20, 10, 0.15) inset;
  flex-wrap: wrap;
  justify-content: center;
}
.yield-tray--spent {
  opacity: 0.78;
  filter: saturate(0.7);
}
.yield-tray__entry {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 6px;
  min-width: 220px;
}
.yield-tray__header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.yield-tray__label {
  color: var(--rubric, var(--fg-strong));
  font-weight: 600;
  flex: 1;
}
.yield-tray__count {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg);
}
.yield-tray__rounds {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg-muted);
}
.yield-tray__rounds--closed {
  color: var(--rubric, var(--fg-muted));
  font-style: italic;
}
.yield-tray__rounds--spent {
  color: var(--verdigris, var(--fg-strong));
  font-weight: 600;
}
.yield-tray__dismiss {
  width: 14px;
  height: 14px;
  margin-left: 2px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: 9px;
  line-height: 1;
  padding: 0;
}
.yield-tray__dismiss:hover {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.yield-tray__cards {
  display: flex;
  gap: 3px;
  align-items: flex-end;
  min-height: 30px;
}
.yield-tray__card {
  display: inline-block;
}
.yield-tray__empty {
  font-family: var(--font-body-italic, var(--font-body));
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
  align-self: center;
}
.yield-tray__actions {
  display: flex;
  gap: 6px;
  margin-top: 2px;
}
.yield-tray__btn {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 3px 8px;
  background: var(--surface);
  border: 1px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-1, 120ms), color var(--dur-1, 120ms);
}
.yield-tray__btn:hover:not(:disabled) {
  background: var(--ink);
  color: var(--paper);
}
.yield-tray__btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.yield-tray__btn--primary:not(:disabled) {
  background: var(--rubric, var(--ink));
  color: var(--paper);
  border-color: var(--rubric, var(--ink));
}
.yield-tray__btn--primary:hover:not(:disabled) {
  background: var(--ink);
  border-color: var(--ink);
}

/* Round counter — small strip at the top of the Initiative panel.
   Round-bound effects (Mark, future timed abilities) read from this
   value. Only the GM sees the advance / rewind buttons. */
.init-round {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.init-round-value {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 16px;
  letter-spacing: 0;
  color: var(--fg-strong);
}
.init-round-buttons {
  display: flex;
  gap: 2px;
}
.init-round-btn {
  width: 22px;
  height: 22px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 0;
}
.init-round-btn:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Header variant — same controls, but stripped of the standalone
   bordered-box treatment so they slot cleanly into the panel handle.
   The handle already provides the surrounding chrome. */
.init-round--header {
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  gap: 6px;
}
.init-round--header .init-round-value {
  margin-left: 0;
  font-size: 14px;
}
.init-round--header .init-round-btn {
  width: 20px;
  height: 20px;
}

/* Marks attached to a foe (e.g., the Ranger's Mark ability). Each mark
   is a small card + the marking player's name + the modifier value, with
   a dismiss × that only appears for the local marker's own marks. */
.init-foe-marks {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed var(--border-soft);
}
.init-foe-mark {
  display: flex;
  align-items: center;
  gap: 6px;
  position: relative;
  padding: 2px 4px;
}
.init-foe-mark-meta {
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1;
}
.init-foe-mark-from {
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.init-foe-mark-mod {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--rubric);
  font-weight: 600;
}
.init-foe-mark-dismiss {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  font-size: 10px;
  line-height: 1;
  padding: 0;
}
.init-foe-mark-dismiss:hover {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}

.init-foe-bar-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 3px;
}

.init-foe-bar-label {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 600;
  font-size: 11px;
  color: var(--fg);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.init-foe-bar-val {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 600;
  font-size: 12px;
  color: var(--fg);
}

.init-foe-bar-track {
  height: 7px;
  background: var(--paper-deep);
  border: 1px solid var(--ink);
  border-radius: 2px;
}

.init-foe-bar-fill {
  height: 100%;
  transition: width var(--dur-2);
  width: var(--bar-pct, 0%);
  background: var(--bar-fill, var(--rubric));
}

.init-tracker-no-players {
  padding: 32px 0;
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  color: var(--fg-muted);
}

/* ── CombatTracker ──────────────────────────────────────────── */

.cbt-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
  border-bottom: 1px solid var(--border-soft);
}

.cbt-name-block {
  flex: 1;
  min-width: 0;
}

/* name — fontWeight and color are isPlayer-conditional; kept inline */
.cbt-name {
  font-family: var(--font-body);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cbt-btn-group {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

.cbt-add-row {
  display: flex;
  gap: 6px;
  margin-top: 10px;
  align-items: center;
}

.cbt-add-input {
  flex: 1;
  font-family: var(--font-body);
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
}

.cbt-hp-input {
  width: 44px;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 5px 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
}

/* ── NoActiveCharacter ──────────────────────────────────────── */

.no-active-char {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 64px 24px;
  gap: 16px;
  text-align: center;
}

.no-active-char__title {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 20px;
  color: var(--fg-muted);
}

.no-active-char__body {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  max-width: 260px;
}

.no-active-char__btns {
  display: flex;
  gap: 8px;
}

/* ── CharacterManager ───────────────────────────────────────── */

/* outer — padding is isMobile-conditional; kept inline */
.cm-root {
  max-width: 640px;
  margin: 0 auto;
  padding: 40px 24px 80px;
}

.cm-player-section {
  margin-bottom: 36px;
}

.cm-field-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 8px;
}

.cm-name-input {
  font-family: var(--font-display);
  font-size: 26px;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--fg-strong);
  width: 100%;
  padding: 4px 0;
  outline: none;
}

.cm-narrator-box {
  border: 1px solid var(--ink);
  background: var(--surface);
  padding: 16px 18px;
  margin-bottom: 24px;
}

.cm-narrator-title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
  margin-bottom: 4px;
}

.cm-narrator-sub {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
}

.cm-section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
}

.cm-section-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.cm-gm-section {
  margin-bottom: 36px;
  margin-top: 8px;
}

.cm-gm-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.cm-gm-badge {
  font-family: var(--font-body);
  font-size: 11px;
  background: var(--indigo);
  color: #fff;
  border-radius: 2px;
  padding: 2px 8px;
  letter-spacing: 0.06em;
}

.cm-char-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* char card — border-color and background are isActive-conditional; kept inline */
.cm-char-card {
  padding: 14px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.cm-char-info {
  flex: 1;
  min-width: 0;
}

.cm-char-name {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--fg-strong);
  margin-bottom: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cm-char-meta {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}

.cm-char-actions {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-shrink: 0;
}

.cm-delete-confirm-text {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--rubric);
}

.cm-delete-btn {
  border: none;
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 12px;
  padding: 2px 4px;
  line-height: 1;
}

.cm-empty {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  text-align: center;
  padding: 48px 0;
}

/* ── App-level layout ───────────────────────────────────────── */

/* root div — filter/opacity/pointerEvents are wizard-conditional; kept inline */
.app-root {
  min-height: 100vh;
  /* `padding-bottom` (set further down) reserves space for the fixed
     hand dock. border-box folds the padding back into the 100vh so
     the root never exceeds the viewport. */
  box-sizing: border-box;
  background: var(--bg);
  transition: filter 380ms ease, opacity 380ms ease;
}
/* Hard-cap the root to the viewport when the table view is active
   (signaled by the body class the HandDock writes). Combined with
   the grid-bounded two-column layout below, this guarantees the page
   itself never scrolls — each column owns its own scrollbar instead.
   Other views (sheet, profile, lobby) don't add `has-hand-dock` to
   the body, so they keep their normal page-flow scrolling. */
body.has-hand-dock .app-root {
  height: 100vh;
  overflow: hidden;
}
/* Mobile reuses the desktop "root is viewport-sized + each column
   scrolls internally" pattern via the new two-column carousel —
   keep .app-root capped at the viewport so the carousel can be
   absolute / fixed-size and the hand dock stays anchored. */

.app-table-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  max-width: 1400px;
  margin: 0 auto;
  /* Pin the grid itself to the available viewport area (top nav 56px
     and the bottom dock are subtracted). Without this, the grid sizes
     to its tallest column's NATURAL pre-overflow height — even when
     each column has its own max-height + overflow-y, the grid asks
     for the un-clamped size, which spills past 100vh and produces
     a page-level scrollbar in addition to the column's. */
  height: calc(100vh - 56px - var(--hand-dock-h, 0px));
}

/* Mobile carousel variant — same viewport-cap as the desktop grid,
   but it doesn't use grid-template-columns (carousel handles its
   own layout). Pulled into the wrapper here so the height calc is
   one source of truth. */
.app-table-grid--mobile {
  display: block;
  grid-template-columns: none;
  max-width: none;
}

/* Both columns scroll independently within the grid-bounded region
   between the sticky top nav and the fixed hand dock. Each column
   gets its own scrollbar on overflow; the page itself never needs
   to scroll because the grid above caps the layout to 100vh-and-no-
   more. */
.app-table-left,
.app-table-right {
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
}
.app-table-left {
  border-right: 1px solid var(--border);
}

.app-creation-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  overflow-y: auto;
  background: var(--bg);
}

/* Profile page sections */
.app-profile-server {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 16px 24px;
}

.app-profile-server-inner {
  border-top: 1px solid var(--border);
  padding-top: 24px;
  margin-bottom: 8px;
}

.app-profile-server-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 12px;
}

/* Player display colour picker. Lives in the Profile view; sets the
   user's drawing/token/trail colour. */
.player-color-picker { display: flex; flex-direction: column; gap: 10px; }
.player-color-picker__row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.player-color-picker__swatch {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--swatch, #888);
  cursor: pointer;
  padding: 0;
  transition: transform var(--dur-1, 120ms), border-color var(--dur-1, 120ms);
}
.player-color-picker__swatch:hover { transform: scale(1.08); }
.player-color-picker__swatch.is-active {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--ink);
}
.player-color-picker__hex {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.player-color-picker__hex-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}
.player-color-picker__color-input {
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border);
  background: transparent;
  cursor: pointer;
  border-radius: 2px;
}
.player-color-picker__hex-input {
  width: 90px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 12px;
  border-radius: 2px;
}
.player-color-picker__clear {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  border-radius: 2px;
}
.player-color-picker__clear:hover { color: var(--fg); border-color: var(--ink); }
.player-color-picker__peers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding-top: 6px;
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}
.player-color-picker__peers-label {
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.player-color-picker__peer {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.player-color-picker__peer-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--swatch, #888);
}

.app-profile-tool-link {
  display: inline-block;
  padding: 8px 16px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--fg);
  text-decoration: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-1);
  transition: background var(--dur-1) var(--ease-quill),
              color var(--dur-1) var(--ease-quill);
}
.app-profile-tool-link:hover {
  background: var(--surface-hi);
  color: var(--fg-strong);
}

.app-profile-signout {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 24px 40px;
  display: flex;
  justify-content: flex-end;
}

/* Seat tiles panel row */
.app-seats-row {
  display: flex;
  gap: 10px;
  padding: 12px 14px;
  overflow-x: auto;
  flex-wrap: wrap;
}

/* Hand wrapper */
.app-hand-pad {
  padding: 0 12px 12px;
}

/* ─── Fixed bottom hand dock ─────────────────────────────────────────────
   Pinned to the viewport bottom across both columns of the panel grid so
   the player's hand is always one glance away. Includes a small chevron
   handle that collapses the dock to a thin strip — useful on mobile, and
   harmless on desktop. The body class `has-hand-dock` adds the bottom
   padding the panel grid needs so its content doesn't slide behind the
   dock; `has-hand-collapsed` halves that padding when collapsed. */
.app-hand-dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  background: var(--paper);
  border-top: 1px solid var(--ink);
  box-shadow: 0 -6px 18px rgba(30, 20, 10, 0.12);
  display: flex;
  flex-direction: column;
  max-height: min(60vh, 360px);
  /* Dock shrinks in lockstep with the inner content's anime.js height
     animation (matching duration / easing) so collapsing actually
     reclaims viewport space — without this, content height = 0 but
     the dock's flex container holds the original box. */
  transition: max-height 280ms cubic-bezier(0.32, 0.08, 0.24, 1);
}
.app-hand-dock--collapsed {
  max-height: 32px;
}

.app-hand-dock__handle {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 6px 14px;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  background: var(--surface);
  border: none;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
  flex-shrink: 0;
}

.app-hand-dock__handle:hover {
  color: var(--fg);
  background: var(--paper);
}

.app-hand-dock__chevron {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
  width: 14px;
  text-align: center;
}

.app-hand-dock__label {
  flex: 1;
  text-align: left;
}

.app-hand-dock__badge {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  padding: 2px 8px;
  background: var(--ink);
  color: var(--paper);
  border-radius: 10px;
  line-height: 1.2;
}

.app-hand-dock__content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 16px 12px;
  /* Height + opacity are driven by useAnimatedCollapse (anime.js) so
     the gesture matches the panel collapse animation. The hook writes
     inline styles directly; no CSS transition needed. */
}

/* Companion mini-hand — sits inside the player's Hand component between
   the deck stack and the main fan, styled to mirror the player's hand
   fan (rotation, overlap, hover lift) at a smaller scale. Card scale is
   driven by the inner PlayingCard's "sm" prop; this CSS just owns
   layout, label, and the dashed draw slot. */
/* Movement budget — overlay in the bottom-right corner of the
   tactical board, surfaced only while the active player has leftover
   cells of movement (a previous Speed + card spend that didn't fully
   reach its drag destination). Sits inside the MapCanvas wrapper
   (which is `position: relative`) so the offset from the board's
   inner edge stays consistent regardless of viewport size. Layered
   above the SVG grid via z-index. Tagged `role=status` and
   aria-live=polite so screen readers announce changes. */
.mapcanvas__movement {
  position: absolute;
  right: 18px;
  bottom: 18px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 18px;
  background: rgba(255, 248, 232, 0.92);
  color: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 4px;
  box-shadow: 0 4px 12px rgba(20, 12, 4, 0.35);
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  user-select: none;
  pointer-events: none;
}
.mapcanvas__movement-label {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: lowercase;
  color: var(--fg-muted);
  line-height: 1.1;
}
.mapcanvas__movement-value {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 600;
  font-size: 22px;
  line-height: 1.05;
  color: var(--verdigris-deep, var(--fg-strong));
}
/* Dodge cue — the movement box edge-glows while a dodge-granted
   square is available, with a "Dodged" tag above it so the player
   knows where the free movement came from. */
.mapcanvas__movement--dodge {
  border-color: var(--verdigris, #3a8a6a);
  animation: mapcanvas-movement-dodge 0.8s ease-in-out infinite;
}
@keyframes mapcanvas-movement-dodge {
  0%, 100% { box-shadow: 0 4px 12px rgba(20, 12, 4, 0.35), 0 0 5px 1px var(--verdigris, #3a8a6a); }
  50%      { box-shadow: 0 4px 12px rgba(20, 12, 4, 0.35), 0 0 18px 6px var(--verdigris, #3a8a6a); }
}
.mapcanvas__movement-dodge-tag {
  position: absolute;
  top: -22px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-display, 'IM Fell English SC', Georgia, serif);
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper, #f4ead7);
  background: var(--verdigris-deep, #1a4a3a);
  border-radius: 3px;
  padding: 2px 10px;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(20, 12, 4, 0.4);
}

.companion-mini {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  margin: 0 14px;
  padding: 0 10px;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
}
.companion-mini__header {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.companion-mini__label {
  color: var(--fg);
}
.companion-mini__count {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
}
.companion-mini__dismiss {
  width: 16px;
  height: 16px;
  margin-left: 2px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 0;
}
.companion-mini__dismiss:hover {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.companion-mini__fan {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 80px;
}
.companion-mini__card {
  cursor: pointer;
  transition: transform var(--dur-2, 240ms) var(--ease-quill, cubic-bezier(0.32, 0.08, 0.24, 1));
  margin-left: var(--card-margin, 0px);
  transform: rotate(var(--card-rot, 0deg)) translateY(var(--card-y, 0px));
  z-index: var(--card-z, 0);
}
.companion-mini__card:hover {
  transform: translateY(-4px) !important;
  z-index: 10 !important;
}
.companion-mini__draw {
  width: 28px;
  min-height: 50px;
  margin-left: 6px;
  border: 1px dashed var(--border);
  background: transparent;
  color: var(--fg-muted);
  font-family: var(--font-display);
  font-size: 16px;
  cursor: pointer;
}
.companion-mini__draw:hover {
  border-style: solid;
  color: var(--fg);
  border-color: var(--ink);
}

/* Bank mini-hand — sits in the hand row's right track, mirroring the
   companion mini-hand's left-track look. Click a banked card to ask
   the parent to unbank it (return it to the player's hand). Suppressed
   entirely when the bank is empty so the right track stays a phantom
   placeholder for grid symmetry. */
.bank-mini {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  margin: 0 14px;
  padding: 0 10px;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
}
.bank-mini__header {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.bank-mini__label { color: var(--fg); }
.bank-mini__count {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
}
.bank-mini__fan {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 80px;
}
.bank-mini__card {
  cursor: pointer;
  transition: transform var(--dur-2, 240ms) var(--ease-quill, cubic-bezier(0.32, 0.08, 0.24, 1));
  margin-left: var(--card-margin, 0px);
  transform: rotate(var(--card-rot, 0deg)) translateY(var(--card-y, 0px));
  z-index: var(--card-z, 0);
}
.bank-mini__card:hover {
  transform: translateY(-4px) !important;
  z-index: 10 !important;
}

/* Right track of the player-hand row. Inherits the same flex layout
   as the deck track on the left so the bank mini-hand pins to the
   inner edge (closest to the fan) and any spare 1fr space sits on the
   outside, keeping the centred fan visually balanced. */
.player-hand__right-track {
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  width: 100%;
  gap: 8px;
}

/* Active-status tray — sits in the right-track of the player's hand
   row, pinned to the bottom of the row alongside the bank mini-hand.
   Whole tray is wrapped in a rounded card with a small centred
   "Status" header so the row reads as a discrete unit. Hidden
   entirely when the active char has no active status (the React
   component returns null in that case).
   Each badge is the same gold-disc + dark-brown silhouette icon
   used everywhere else (initiative tracker, sheet trauma table,
   EffectsModal). Click or right-click to clear; for actions-kind
   status (Bound) a paired "−1" button decrements the count. */
.status-tray {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  margin-left: auto;     /* push the tray to the right edge of the track */
  padding: 4px 10px 6px;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.status-tray__header {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  text-align: center;
  line-height: 1;
}
.status-tray__row {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  justify-content: center;
}
.status-tray__group {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
/* Tray badges scale at 1.5× the original sizing — ~33px icon, 15px
   typography, proportionally bumped padding and gap. The −1
   decrement button matches so the pair reads as a single unit.
   border-radius: 999px gives a true pill shape so the icon's gold
   disc and the pill ends share the same curve, eliminating the
   tiny visual gap that used to show at the rounded corners. */
.status-tray__badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 12px 2px 2px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 15px;
  font-weight: 700;
  color: var(--fg);
  line-height: 1;
  box-sizing: border-box;
  transition: background var(--dur-1, 120ms) ease, color var(--dur-1, 120ms) ease;
}
.status-tray__badge:hover {
  background: var(--ink);
  color: var(--paper);
}
/* Buff variant: a verdigris-tinted border so positive effects read
   distinct from debuffs without breaking the row's visual rhythm. */
.status-tray__badge--buff {
  border-color: var(--verdigris-deep);
  background: color-mix(in srgb, var(--verdigris) 8%, var(--paper));
  padding: 4px 12px;
}
.status-tray__badge--buff:hover {
  background: var(--verdigris-deep);
  color: var(--paper);
}
.status-tray__label {
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: none;
  padding: 0 2px;
}
.status-tray__icon {
  width: 33px;
  height: 33px;
}
.status-tray__suffix {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.status-tray__decrement {
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 15px;
  font-weight: 700;
  padding: 0 12px;
  height: 39px;          /* matches badge total height (icon 33 + 2+2+2 box-sizing border) */
  border: 1px solid var(--rubric);
  background: var(--rubric);
  color: var(--paper);
  border-radius: 999px;
  cursor: pointer;
  letter-spacing: 0;
  line-height: 1;
  box-sizing: border-box;
}
.status-tray__decrement:hover {
  background: var(--ink);
  border-color: var(--ink);
}

/* Shroud (Ranger T3) buff carries an optional player-driven Invisible
   toggle — flipped when the Ranger took no action this turn / breaks
   Invisibility when they act. Sized to match the action-decrement
   button next to it; turns verdigris when active. */
.status-tray__shroud-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 39px;
  height: 39px;
  padding: 0;
  border: 1px solid var(--sepia);
  background: var(--paper-aged);
  border-radius: 999px;
  cursor: pointer;
  box-sizing: border-box;
  opacity: 0.65;
  transition: opacity var(--dur-1) var(--ease-quill),
              background-color var(--dur-1) var(--ease-quill),
              border-color var(--dur-1) var(--ease-quill);
}
.status-tray__shroud-toggle:hover { opacity: 1; }
.status-tray__shroud-toggle.is-on {
  background: var(--verdigris-deep);
  border-color: var(--verdigris-deep);
  opacity: 1;
}
.status-tray__shroud-toggle.is-on .status-tray__shroud-toggle-icon { filter: brightness(0) invert(1); }
.status-tray__shroud-toggle-icon {
  width: 22px;
  height: 22px;
  object-fit: contain;
}

/* Drive panel-content offsets via a CSS variable so the right-column
   sticky panel and the bottom padding share a single source of truth.
   The variable is bound by body classes set by the React HandDock. */
:root { --hand-dock-h: 0px; }
body.has-hand-dock          { --hand-dock-h: 280px; }
body.has-hand-collapsed     { --hand-dock-h: 40px; }

.app-root {
  padding-bottom: var(--hand-dock-h);
}

/* Mobile dock is shorter when expanded. The --collapsed override has to
   be re-asserted inside the media query: same specificity as the base
   rule above (one class), so without this the later mobile max-height
   would shadow it and tapping the handle would only hide the cards
   while leaving the dock at its full expanded height. */
@media (max-width: 767px) {
  .app-hand-dock {
    max-height: min(55vh, 260px);
  }
  .app-hand-dock--collapsed {
    max-height: 32px;
  }
  body.has-hand-dock      { --hand-dock-h: 220px; }
  body.has-hand-collapsed { --hand-dock-h: 36px; }
}

/* Foe hand panel */
.app-foe-hand {
  padding: 12px 14px;
  display: flex;
  gap: 4px;
  justify-content: center;
  flex-wrap: wrap;
  min-height: 80px;
  align-items: center;
}

.app-foe-hand-empty {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
}

/* Play zone — active-call bar */
.app-call-bar {
  border-bottom: 1px solid var(--border);
  padding: 10px 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  background: var(--surface);
}
/* Droppable affordance — only on the player whose turn it is to answer
   the call. Hint via a soft ring; SortableJS's drag-over class adds the
   "actively hovered" emphasis. */
.app-call-bar--droppable {
  outline: 1px dashed transparent;
  outline-offset: -3px;
  transition: outline-color var(--dur-1, 120ms) ease, background var(--dur-1, 120ms) ease;
}
.app-call-bar--droppable:hover {
  outline-color: var(--cinnabar, #c14b3a);
}

.app-call-bar-left {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.app-call-difficulty {
  font-family: var(--font-display);
  font-size: 44px;
  color: var(--rubric);
  line-height: 1;
}

.app-call-diff-label {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.app-call-diff-name {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
}

.app-call-bar-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

.app-call-attr-skill {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
}

.app-call-muted-plus {
  color: var(--fg-muted);
}

.app-no-call {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
}

.app-play-zone-pad {
  padding: 12px;
}

/* ServerControls */
.sctl-row {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 6px 0;
}
.sctl-btn {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 6px 14px;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
  background: transparent;
  color: var(--fg);
}
.sctl-btn--danger {
  border-color: var(--rubric);
  color: var(--rubric);
}

.sctl-status-text {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  font-style: italic;
}

/* ── LabeledInput helper ────────────────────────────────────── */

.li-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.li-span {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.li-input {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 5px 8px;
  border-radius: 2px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  width: 100%;
}
.li-input--num { font-family: var(--font-mono); }

/* GM tab bar buttons (app.jsx) */
.gm-tab-btn.is-active {
  border-bottom: 2px solid var(--rubric);
  color: var(--rubric);
}
.gm-tab-btn.is-inactive {
  border-bottom: 2px solid transparent;
  color: var(--fg-muted);
}

/* GM body small bits */
.gm-target-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}
.gm-empty-italic {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 13px;
}
.gm-target-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.gm-target-summary {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 11px;
  margin-top: 2px;
}
.gm-contested-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
  margin-bottom: 12px;
}
.gm-contested-label {
  color: var(--fg);
}
.gm-contested-label--disabled { color: var(--fg-muted); }
.gm-contested-hint {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 11px;
}
.cbt-name--player { font-weight: 500; color: var(--fg-strong); }
.cbt-name--foe { font-weight: 400; color: var(--rubric); }

/* Target chips & picker (app.jsx initiative panel) */
.target-chip-row {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.target-chip-row--targeting { margin-top: 4px; }
.target-chip-row--picker { margin-top: 6px; }
.target-chip-eyebrow {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.target-chip {
  font-family: var(--font-body);
  font-size: 10px;
  padding: 1px 6px;
  border-width: 1px;
  border-style: solid;
  border-radius: 2px;
  background: rgba(0,0,0,0.02);
  border-color: var(--chip-accent, var(--border));
  color: var(--chip-accent, var(--fg));
}
.target-chip-btn {
  font-family: var(--font-body);
  font-size: 10px;
  padding: 2px 7px;
  cursor: pointer;
  border-width: 1px;
  border-style: solid;
  border-radius: 2px;
  background: transparent;
  border-color: var(--chip-accent, var(--border));
  color: var(--chip-accent, var(--fg));
}
.target-chip-btn.is-on {
  color: var(--paper);
  background: var(--chip-accent, var(--ink));
}

/* Init peer row (app.jsx) */
.init-peer-row.is-targeting {
  background: rgba(160,40,40,0.05);
  border: 1px solid var(--rubric);
}
/* Attack-target row treatment — a thicker indigo left bar so it can
   coexist with the cinnabar Mark border on the row when both are set,
   and a faint indigo backdrop wash for at-a-glance scanning. */
.init-peer-row.is-attack-target,
.init-foe-row.is-attack-target {
  box-shadow: inset 4px 0 0 0 var(--indigo);
  background-image: linear-gradient(90deg, rgba(42, 58, 110, 0.10), rgba(42, 58, 110, 0));
}
.init-peer-row.is-clickable { cursor: pointer; }
.init-peer-status-icon {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 4rem;
  opacity: 0.13;
  pointer-events: none;
  z-index: 0;
  color: var(--status-color, var(--fg));
}
.init-peer-status-icon--lg { font-size: 5.5rem; }
.init-peer-content { position: relative; z-index: 1; }
.init-peer-name-row--gap { margin-bottom: 8px; }
.init-peer-name--targeted { color: var(--rubric); }

/* Player display name shown below the character name in initiative
   rows. Smaller, muted, italicised so it reads as a "by ..." subtitle
   rather than a primary identifier. */
.init-peer-subtitle {
  font-family: var(--font-body-italic, var(--font-body));
  font-size: 0.75rem;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
  margin-top: -4px;
  margin-bottom: 6px;
}

/* Compact HP / WP pair shown inline on the same row as the character
   name. Mini bars with the cur/max stamped on the right; designed
   to fold into the right side of the name row when the row has
   enough horizontal room. */
.init-peer-vitals {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
}

/* Compact at-a-glance trauma + status badge cluster on each peer row.
   Major trauma reads in solid cinnabar (most severe), minor in
   outlined cinnabar with a count, status in indigo. Treated minor
   trauma is muted (line-through) so the GM still sees the stack but
   knows the penalty is suppressed. */
.init-peer-effects {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  align-items: center;
  margin-left: 6px;
}
.init-peer-effect {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 9px;
  letter-spacing: 0.04em;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 2px;
  border: 1px solid transparent;
  white-space: nowrap;
  line-height: 1.4;
}
.init-peer-effect--major {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.init-peer-effect--minor {
  border-color: var(--rubric);
  color: var(--rubric);
  background: transparent;
}
.init-peer-effect--minor.init-peer-effect--treated {
  opacity: 0.55;
  text-decoration: line-through;
}
.init-peer-effect--status {
  border-color: var(--indigo);
  color: var(--indigo);
  background: transparent;
}

/* SVG-variant effect icons — picked from the bundled game-icons.net
   library to fill in coverage. Two-layer rendering so the result
   colour-matches the existing PNG status art exactly:
   - Outer wrap (.effect-icon-svg-wrap) draws a #e8d090 gold disc.
   - Inner span (.effect-icon-svg) uses the SVG as a CSS `mask-image`,
     letting its `background-color: #371000` show through the alpha.
   The wrap inherits its width/height from the size class the caller
   passes (init-peer-effect-icon, cs-trauma-icon, …) so layout is
   byte-identical to the PNG render path. */
.effect-icon-svg-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #e8d090;
  border-radius: 50%;
  vertical-align: -3px;
}
.effect-icon-svg {
  display: inline-block;
  width: 72%;
  height: 72%;
  background-color: #371000;
  -webkit-mask-image: var(--effect-icon-url);
          mask-image: var(--effect-icon-url);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}
/* Treated minor trauma — softer disc + slightly desaturated symbol
   so the row reads as inactive without fighting the surrounding
   grayscale filter on the wrapper. */
.init-peer-effect-iconwrap--treated .effect-icon-svg-wrap { background: #d8be78; }
.init-peer-effect-iconwrap--treated .effect-icon-svg     { background-color: #5a3318; }

/* GM-removable affordance — when the GM hovers a peer's effect badge
   in the initiative tracker, the cursor flips to context-menu and a
   subtle red ring telegraphs that right-click will cure/remove it. */
.is-gm-removable {
  cursor: context-menu;
}
.is-gm-removable:hover {
  outline: 1px dashed var(--rubric, #c14b3a);
  outline-offset: 2px;
  border-radius: 50%;
}

/* Icon-variant badges — use bundled status PNGs when available so the
   row reads at a glance without text. The wrap span pairs the icon
   with a small "×N" count for stacked minor trauma. */
.init-peer-effect-icon {
  width: 18px;
  height: 18px;
  object-fit: contain;
  display: inline-block;
  vertical-align: middle;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));
}
.init-peer-effect-icon--minor {
  /* Minor-trauma icons use a sepia tint to distinguish from major. */
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2)) sepia(0.4);
}
.init-peer-effect-iconwrap {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 6px 1px 1px;
  border: 1px solid var(--border-soft, rgba(0, 0, 0, 0.12));
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.55);
  line-height: 1;
}
/* When there's no count text trailing the icon, drop the right
   padding so the icon disc sits centred inside a circular shell. */
.init-peer-effect-iconwrap:not(:has(.init-peer-effect-count)) {
  padding: 1px;
}
.init-peer-effect-iconwrap--treated .init-peer-effect-icon {
  opacity: 0.45;
  filter: grayscale(100%);
}
.init-peer-effect-count {
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: 10px;
  font-weight: 700;
  color: var(--rubric);
  font-variant-numeric: tabular-nums;
}
.init-peer-effect-iconwrap--treated .init-peer-effect-count {
  color: var(--fg-muted);
  text-decoration: line-through;
}
.init-peer-vital {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.init-peer-vital-label {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg);
}
.init-peer-vital-track {
  position: relative;
  width: 64px;
  height: 9px;
  background: var(--paper-deep, rgba(0, 0, 0, 0.12));
  border: 1px solid var(--ink);
  border-radius: 2px;
  overflow: hidden;
}
.init-peer-vital-fill {
  display: block;
  height: 100%;
  width: var(--bar-pct, 0%);
  background: var(--bar-fill, var(--verdigris));
  transition: width var(--dur-2, 240ms) var(--ease-quill, cubic-bezier(0.32, 0.08, 0.24, 1));
}
.init-peer-vital-val {
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--fg);
  white-space: nowrap;
}
.init-peer-targeting-bullseye {
  margin-left: 6px;
  color: var(--rubric);
  font-size: 11px;
}
.init-foe-stats--gap { margin-bottom: 6px; }
.init-peer-last {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}
.init-peer-last-eyebrow {
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.init-peer-last-card {
  padding: 1px 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--ink-strong);
}
.init-peer-last-card--red { color: var(--rubric); }

/* Init foe row */
.init-foe-row {
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  background: transparent;
  cursor: default;
}
.init-foe-row.is-targeted {
  border-color: var(--rubric);
  background: rgba(160,40,40,0.05);
}
.init-foe-row.is-clickable { cursor: pointer; }
.init-foe-name-row--gap { margin-bottom: 6px; }
.init-foe-name--targeted { color: var(--rubric); }

/* Quick-foe inline form */
.quick-foe-wrap { margin-bottom: 8px; }
.quick-foe-row {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}
.quick-foe-input {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
}
.quick-foe-input--name { flex: 1 1 120px; }
.quick-foe-input--hp {
  font-family: var(--font-mono);
  width: 56px;
  text-align: center;
}

/* Forethought panel (app.jsx) */
.forethought-pad { padding: 12px; }
.forethought-hint {
  font-size: 11px;
  color: var(--fg-muted);
  margin-bottom: 8px;
}
.forethought-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.forethought-slot {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
}
.forethought-slot-name {
  font-family: var(--font-display);
  font-size: 13px;
}
.forethought-slot-cost {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
}
.forethought-spacer { flex: 1; }
.forethought-empty-slot {
  padding: 6px;
  border: 1px dashed var(--border-soft);
  transition: background 120ms, border-color 120ms;
}
.forethought-empty-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.forethought-empty-name-input {
  padding: 4px 6px;
  font-size: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
}
.forethought-empty-card-select {
  padding: 4px 6px;
  font-size: 12px;
}
.forethought-empty-actions {
  display: flex;
  gap: 6px;
}
.forethought-empty-display {
  display: flex;
  align-items: center;
  gap: 8px;
}
.forethought-empty-msg {
  flex: 1;
  font-style: italic;
  color: var(--fg-muted);
  font-size: 12px;
}
.forethought-no-slots {
  font-style: italic;
  color: var(--fg-muted);
}

/* Marks/Charges panel (app.jsx) */
.charges-pad { padding: 12px; }
.charges-hint {
  font-size: 11px;
  color: var(--fg-muted);
  margin-bottom: 8px;
}
.charges-list { display: flex; flex-direction: column; gap: 8px; }
.charges-row {
  border: 1px solid var(--border);
  padding: 8px;
}
.charges-row-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.charges-row-name { flex: 1; }
.charges-row-val {
  font-family: var(--font-mono);
  font-size: 16px;
  color: var(--rubric);
}
.charges-row-cards {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-top: 6px;
}
/* Stored cards held "in tray" by a charge — face-up thumbnails so the
   player can see what value is feeding the running total. Cards
   contributed by another player get a verdigris underline so the
   activator knows those need to be returned to that player's discard
   on release. */
.charges-row-stored {
  display: flex;
  gap: 3px;
  flex-wrap: wrap;
  margin-top: 6px;
  padding: 4px;
  border: 1px dashed var(--border-soft);
  background: var(--surface);
}
.charges-row-stored-card {
  display: inline-block;
}
.charges-row-stored-card--other {
  outline: 1.5px solid var(--verdigris);
  outline-offset: 1px;
  border-radius: 2px;
}

/* Translator panel */
.translator-panel {
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.translator-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 10px;
  align-items: end;
}
.translator-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.translator-swap-btn {
  padding: 6px 10px;
  font-family: var(--font-body);
  font-size: 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  border-radius: 2px;
  align-self: end;
}

/* Character manager (cm-) */
.cm-root--mobile { padding: 24px 16px 80px; }
.cm-char-card.is-active {
  border-color: var(--ink);
  background: var(--surface-hi);
}

/* Active-call descriptor lines */
.app-call-target-line {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.app-call-not-yours {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
}

/* Inline (optional) hint inside login & cgm sections */
.optional-hint { opacity: 0.5; }

/* Reduced-motion preference label */
.tweak-anim-label {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  cursor: pointer;
}
.tweak-anim-hint {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 12px;
  margin-top: 4px;
}

/* Lobby small bits */
.lobby-game-gm--mb { margin-bottom: 8px; }
.cgm-confirm-btn--busy {
  cursor: default;
  opacity: 0.6;
}

/* App root blur while creation wizard is mounted */
.app-root--wizard-blur {
  filter: blur(5px);
  opacity: 0.35;
  pointer-events: none;
}

/* Item shop (panels.jsx) */
.shop-filter-btn.is-active {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--paper);
}
.shop-item-row.is-open { background: rgba(0,0,0,0.04); }
.shop-item-row.is-clickable { cursor: pointer; }
.shop-item-row.is-static { cursor: default; }
.shop-item-name-block { min-width: 0; }
.shop-col-label--center { text-align: center; }
.shop-col-label--right { text-align: right; }
.shop-item-row-mt { margin-top: 1px; }
.shop-item-cost--unpriced { color: var(--sepia); }
.shop-item-detail-ward-num { color: var(--fg); }
.shop-item-trait-list--bordered {
  border-top: 1px solid var(--border-soft);
  padding-top: 8px;
}
.shop-subtype-heading--mt { margin-top: 4px; }
.shop-subtype-arrow.is-collapsed { transform: rotate(-90deg); }
.shop-subtype-group.is-collapsed { grid-template-rows: 0fr; }
.shop-allowance-value--used { color: var(--fg-muted); }
.shop-allowance-value--free { color: var(--verdigris-deep); }
.shop-gold-value--low { color: var(--rubric); }
.shop-basket-item-cost--free { color: var(--verdigris-deep); }
.shop-basket-total-value--low { color: var(--rubric); }
.shop-confirm-btn--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.shop-item-free-tag--col { color: var(--verdigris-deep); }

/* Primitives: FleuronRule */
.fleuron-rule {
  position: relative;
  text-align: center;
  margin: 24px 0;
  color: var(--sepia);
  height: 1em;
}
.fleuron-rule--inline { margin: 12px 0; }
.fleuron-rule-line {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  border-top: 1px solid var(--border);
}
.fleuron-rule-glyph {
  font-family: var(--font-display);
  font-size: 16px;
  background: var(--bg);
  padding: 0 12px;
  position: relative;
  z-index: 1;
  color: var(--fg-muted);
}
.fleuron-rule-glyph--label {
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* Primitives: StatBlock */
.stat-block { display: flex; flex-direction: column; gap: 2px; }
.stat-block-label {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.stat-block-value {
  font-family: var(--font-display);
  line-height: 1;
  color: var(--fg-strong);
}
.stat-block-value--sm { font-size: 22px; }
.stat-block-value--md { font-size: 32px; }
.stat-block-value--lg { font-size: 44px; }
.stat-block-hint {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
}

/* Primitives: Avatar */
.avatar {
  border-radius: 50%;
  border: 1px solid var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  color: var(--ink-strong);
}
.avatar--ring-rubric { border-color: var(--rubric); }

/* Markdown editor */
.md-editor--styled {
  padding: 8px 12px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.5;
  outline: none;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  min-height: var(--md-min-h, 140px);
}

/* ── LoginScreen ────────────────────────────────────────────── */

.login-root {
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.login-box {
  width: min(400px, 100%);
  background: var(--paper);
  border: 1px solid var(--border);
  padding: 32px 28px;
  box-shadow: var(--shadow-2);
}

.login-header {
  margin-bottom: 28px;
  text-align: center;
}

.login-title {
  font-family: var(--font-display);
  font-size: 32px;
  color: var(--fg-strong);
  line-height: 1;
  margin-bottom: 6px;
}

.login-subtitle {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 14px;
  color: var(--fg-muted);
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.login-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.login-field-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.login-error {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--rubric);
  padding: 6px 10px;
  border: 1px solid var(--rubric);
  border-radius: 2px;
  background: rgba(165, 42, 42, 0.05);
}

.login-switch-row {
  margin-top: 20px;
  text-align: center;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}

.login-switch-btn {
  background: none;
  border: none;
  color: var(--fg);
  cursor: pointer;
  font-family: inherit;
  font-size: inherit;
  text-decoration: underline;
  padding: 0;
}

/* Root loading screen */
.root-loading {
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
}

.root-loading-text {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 16px;
  color: var(--fg-muted);
}

/* ── AppErrorBoundary fallback ─────────────────────────────────
   Centered panel shown when <App> throws during render. Keeps the
   user from being stranded on a blank page — they can reload or
   bail back to the lobby. */
.app-error-boundary {
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
}
.app-error-boundary__panel {
  width: 100%;
  max-width: 480px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-2);
  padding: 28px 28px 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.app-error-boundary__title {
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 0.04em;
  color: var(--rubric);
}
.app-error-boundary__body {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 15px;
  color: var(--fg);
  line-height: 1.5;
}
.app-error-boundary__details {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg-muted);
}
.app-error-boundary__details summary {
  cursor: pointer;
  padding: 4px 0;
}
.app-error-boundary__pre {
  margin: 6px 0 0;
  padding: 10px;
  background: var(--paper-aged);
  border: 1px solid var(--border-soft);
  border-radius: 3px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.app-error-boundary__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 6px;
}
.app-error-boundary__btn {
  flex: 1 1 auto;
  padding: 10px 14px;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.04em;
  background: var(--paper);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 3px;
  cursor: pointer;
  min-height: 44px; /* touch target */
}
.app-error-boundary__btn:hover {
  border-color: var(--rubric);
  color: var(--rubric);
}
.app-error-boundary__btn--primary {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.app-error-boundary__btn--primary:hover {
  background: var(--rubric-deep, var(--rubric));
  border-color: var(--rubric-deep, var(--rubric));
  color: var(--paper);
}

/* ── LobbyPage ──────────────────────────────────────────────── */

.lobby-root {
  min-height: 100vh;
  background: var(--bg);
}

.lobby-header {
  background: var(--paper);
  border-bottom: 1px solid var(--border);
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 52px;
}

.lobby-title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
  letter-spacing: 0.03em;
}

.lobby-header-right {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* connection — color is lobby.connected-conditional; kept inline */
.lobby-connection {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}
.lobby-connection--connected { color: var(--verdigris); }

.lobby-username {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}

.lobby-gm-label {
  margin-left: 6px;
  color: var(--indigo);
  font-weight: 600;
}

.lobby-signout-btn {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 4px 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
}
/* Mirrors `.top-nav-tab` (Table / Sheet / Profile in the in-game top
   nav) so the lobby's Guide Book entry-point reads as part of the same
   button vocabulary. Same font, padding, radius, transparent border
   in the resting state; hover bumps to the surface fill + ink border
   the table nav uses for `.is-active`. */
.lobby-guidebook-link {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  padding: 6px 14px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--fg);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-quill), border-color var(--dur-1) var(--ease-quill), background var(--dur-1) var(--ease-quill);
}
.lobby-guidebook-link:hover {
  border-color: var(--ink);
  background: var(--surface);
}

.lobby-body {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 20px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  align-items: start;
}

.lobby-panel {
  background: var(--paper);
  border: 1px solid var(--border);
  padding: 20px 22px;
}

.lobby-panel-title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
  margin-bottom: 4px;
}

.lobby-panel-title-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 4px;
}

.lobby-panel-sub {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
  margin-bottom: 20px;
}

.lobby-section {
  margin-bottom: 18px;
}

.lobby-section-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.lobby-user-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border-soft);
}
/* Draggable affordance — only set when the viewer is GM and the row
   represents a non-GM user (GMs can invite players to their own
   games). Cursor + faint hover lift signal "you can grab this." */
.lobby-user-row--draggable {
  cursor: grab;
  padding-left: 6px;
  padding-right: 6px;
  border-radius: 3px;
  transition: background var(--dur-1, 120ms) ease, transform var(--dur-1, 120ms) ease;
}
.lobby-user-row--draggable:hover {
  background: var(--paper);
  transform: translateX(2px);
}
.lobby-user-row--draggable:active { cursor: grabbing; }

.lobby-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--border);
}
.lobby-dot--online { background: var(--verdigris); }

.lobby-user-name-block {
  flex: 1;
  min-width: 0;
}

.lobby-user-name {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
}

.lobby-user-handle {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: 6px;
}

.lobby-gm-badge {
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--indigo);
  background: rgba(60, 80, 160, 0.1);
  padding: 2px 7px;
  border-radius: 2px;
}

.lobby-empty-text {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  font-style: italic;
  padding: 12px 0;
}

/* GameCard */
.lobby-game-card {
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 14px 16px;
  margin-bottom: 10px;
  transition: border-color var(--dur-1, 120ms) ease, box-shadow var(--dur-1, 120ms) ease;
}
/* Drop-target highlight — fires while a draggable user-row hovers
   over a game card the GM owns. The verdigris border + soft glow
   signal "release to invite." */
.lobby-game-card--drop-target {
  border-color: var(--verdigris);
  box-shadow: 0 0 0 2px var(--verdigris-soft, rgba(58, 138, 58, 0.25)) inset;
}

.lobby-game-card-inner {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 6px;
}

.lobby-game-info {
  min-width: 0;
  flex: 1;
}

.lobby-game-name {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--fg-strong);
  margin-bottom: 2px;
}

/* marginBottom is conditional; kept inline */
.lobby-game-gm {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}

.lobby-game-desc {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
  line-height: 1.5;
  margin-bottom: 8px;
}

.lobby-game-players {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.lobby-player-tag {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  background: var(--paper-deep);
  border: 1px solid var(--border-soft);
  padding: 2px 7px;
  border-radius: 2px;
}

/* online dot — background is online-conditional; kept inline */
.lobby-player-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
  background: var(--border);
}
.lobby-player-dot--online { background: var(--verdigris); }

.lobby-game-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex-shrink: 0;
}

.lobby-join-btn {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  padding: 6px 16px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  white-space: nowrap;
}

.lobby-delete-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
}

.lobby-confirm-row {
  display: flex;
  gap: 4px;
}

.lobby-confirm-yes-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 10px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
}

.lobby-confirm-no-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}

.lobby-create-btn {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  padding: 6px 16px;
  border: 1px solid var(--sepia);
  background: var(--sepia);
  color: var(--paper);
  cursor: pointer;
}

/* ── Lobby mobile (≤ 720px) ──────────────────────────────────────
   Header wraps so Guide Book / connection / username / Sign out
   don't shove the title off-screen. Two-column body grid collapses
   to a stacked single column so the lobby and games panels each
   take full width. The Sign-out button shrinks to just an icon so
   the header still fits the title comfortably. */
@media (max-width: 720px) {
  .lobby-header {
    flex-wrap: wrap;
    height: auto;
    padding: 8px 12px;
    gap: 6px 12px;
  }
  .lobby-title { font-size: 18px; }
  .lobby-header-right {
    gap: 8px;
    flex-wrap: wrap;
  }
  .lobby-connection { display: none; }
  .lobby-username   { font-size: 11px; }
  .lobby-guidebook-link { font-size: 12px; padding: 3px 10px; }
  .lobby-signout-btn    { font-size: 11px; padding: 3px 10px; }
  .lobby-body {
    grid-template-columns: 1fr;
    gap: 16px;
    padding: 16px 12px;
  }
  .lobby-panel {
    padding: 14px;
  }
  /* On mobile, joinable games take precedence — surface them at the
     top of the stack so a thumb-on-phone user can join in one tap
     without scrolling past the players-online roster first. */
  .lobby-body .lobby-panel--games   { order: 1; }
  .lobby-body .lobby-panel--players { order: 2; }
}

/* ── CreateGameModal ────────────────────────────────────────── */

.cgm-box {
  background: var(--paper);
  border: 1px solid var(--border);
  width: min(500px, 94vw);
  display: flex;
  flex-direction: column;
  max-height: 90vh;
}

.cgm-title {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--fg-strong);
}

.cgm-close-btn {
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: var(--fg-muted);
  line-height: 1;
}

.cgm-section-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 8px;
}

.cgm-player-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.cgm-player-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  cursor: pointer;
  user-select: none;
  border: 1px solid var(--border);
  background: var(--surface);
}
.cgm-player-row.is-checked {
  border-color: var(--sepia);
  background: var(--paper-deep);
}

.cgm-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--border);
}
.cgm-dot--online { background: var(--verdigris); }

.cgm-player-info {
  flex: 1;
  min-width: 0;
}

.cgm-player-name {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cgm-player-handle {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}

.cgm-checkbox {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--border);
  background: transparent;
}
.cgm-checkbox.is-checked {
  border-color: var(--sepia);
  background: var(--sepia);
}

.cgm-check-mark {
  color: var(--paper);
  font-size: 10px;
  line-height: 1;
}

.cgm-empty-text {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  font-style: italic;
}

.cgm-error {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--rubric);
  padding: 6px 10px;
  border: 1px solid var(--rubric);
  background: rgba(165, 42, 42, 0.05);
}

.cgm-cancel-btn {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 7px 18px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}

/* opacity is busy-conditional; kept inline */
.cgm-confirm-btn {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  padding: 7px 22px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
}

/* ── New classes added by CSS refactoring pass ──────────────────
   (inline styles extracted from app.jsx)
═══════════════════════════════════════════════════════════════ */

/* modal-header variant: top-align items (PullModal header) */
.modal-header--top-align {
  align-items: flex-start;
}

/* GmPanelFull — stacked form fields column */
.gm-field-col {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Icon button (difficulty ± in GM panel) — matches iconBtn JS object */
.icon-btn {
  width: 24px;
  height: 24px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  border-radius: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

/* CharacterManager — GM auth row layout */
.cm-gm-auth-row {
  display: flex;
  gap: 8px;
  align-items: flex-end;
}

.cm-gm-auth-label {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* GM password error message */
.cm-gm-error {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--rubric);
}
.cm-gm-auth-btn--shifted { margin-bottom: 22px; }

/* FreePlayModal — two-column attribute/skill grid */
.pull-freeplay-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

/* GmFreePlayModal — four-column attr/val/skill/val grid */
.pull-gmfreeplay-grid {
  display: grid;
  grid-template-columns: 1fr 80px 1fr 80px;
  gap: 10px;
}

/* Initiative foe row — large watermark bullseye icon */
.init-foe-bullseye-bg {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 2.5rem;
  color: var(--rubric);
  opacity: 0.15;
  pointer-events: none;
}

/* Initiative foe row — small inline targeted icon */
.init-foe-bullseye-sm {
  font-size: 0.6875rem;
  color: var(--rubric);
  flex-shrink: 0;
}

/* Login submit button top margin */
.login-submit-btn {
  margin-top: 4px;
}


/* Drop-hint state for the Unstored inventory section while an item is being dragged. */
.inv-section-drag-hint {
  border: 2px dashed var(--fg-muted);
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.02);
  transition: border-color 120ms ease, background 120ms ease;
}

.inv-section-drop-hint-label {
  color: var(--fg-muted);
  font-style: italic;
  text-align: center;
  padding: 12px;
}

/* ============================================================
   Tooltip hint — manuscript marginalia slip
   Hover/focus reveal with deckle paper edge, rubric small-caps
   heading, iron-gall italic body. Used for attribute/skill descs.
   ============================================================ */

.tooltip-hint {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9000;
  width: max-content;
  max-width: 280px;
  min-width: 180px;
  pointer-events: none;
  font-family: var(--font-body);
  /* Outer holds the position translate only; the inner .tooltip-hint-slip
     carries the unfurl animation transforms so the two don't fight. */
  transform: translate(var(--tt-x, 0), var(--tt-y, 0));
  /* Soft sepia drop-shadow as if the slip is slightly lifted off the page. */
  filter:
    drop-shadow(0 1px 1px rgba(67, 41, 23, 0.18))
    drop-shadow(0 6px 14px rgba(67, 41, 23, 0.16));
}

:root[data-theme="dark"] .tooltip-hint {
  filter:
    drop-shadow(0 1px 1px rgba(0, 0, 0, 0.55))
    drop-shadow(0 8px 18px rgba(0, 0, 0, 0.55));
}

/* Inner slip — anime.js animates scaleY / rotate / opacity from `top center`
   so the parchment unfurls downward like a rolled scroll opening. */
.tooltip-hint-slip {
  position: relative;
  transform-origin: top center;
  will-change: transform, opacity;
  opacity: 0;
  transform: scaleY(0) rotate(-0.8deg);
}

/* Background paper layer — gets the deckle filter so its edges are torn,
   while the content layer above stays crisp text. */
.tooltip-hint-paper {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 18% 6%, rgba(255, 248, 230, 0.55), transparent 60%),
    radial-gradient(ellipse at 84% 94%, rgba(120, 86, 50, 0.12), transparent 70%),
    linear-gradient(160deg, var(--paper) 0%, var(--paper-aged) 100%);
  background-blend-mode: multiply;
  filter: url(#tooltip-hint-deckle);
  border-radius: 2px;
}

:root[data-theme="dark"] .tooltip-hint-paper {
  background:
    radial-gradient(ellipse at 18% 6%, rgba(80, 60, 40, 0.55), transparent 60%),
    radial-gradient(ellipse at 84% 94%, rgba(0, 0, 0, 0.30), transparent 70%),
    linear-gradient(160deg, var(--paper-aged) 0%, var(--paper-deep) 100%);
}

.tooltip-hint-content {
  position: relative;
  padding: 13px 16px 9px;
  z-index: 1;
}

/* Rubric heading — IM Fell SC, small caps, cinnabar */
.tooltip-hint-label {
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.10em;
  color: var(--rubric);
  text-transform: uppercase;
  line-height: 1;
  margin-bottom: 6px;
  border-bottom: 1px solid rgba(165, 42, 42, 0.30);
  padding-bottom: 5px;
}

:root[data-theme="dark"] .tooltip-hint-label {
  border-bottom-color: rgba(196, 90, 74, 0.40);
}

/* Body — italic, iron-gall ink, gentle hyphenation for prose feel */
.tooltip-hint-body {
  font-family: var(--font-body);
  font-style: normal;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  text-wrap: pretty;
  hyphens: auto;
  white-space: pre-line;
}

:root[data-theme="dark"] .tooltip-hint-body {
  color: var(--fg);
}

/* Closing fleuron — small sepia ornament centered below body */
.tooltip-hint-fleuron {
  font-family: var(--font-display);
  font-size: 11px;
  text-align: center;
  color: var(--sepia);
  margin-top: 6px;
  opacity: 0.7;
  line-height: 1;
}

/* Wax seal — tiny rubric circle pinning the slip to the page */
.tooltip-hint-seal {
  position: absolute;
  top: -3px;
  left: 10px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, #c44a4a 0%, var(--rubric) 55%, var(--rubric-deep) 100%);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.32),
    inset -1px -1px 1px rgba(0, 0, 0, 0.30);
  z-index: 2;
  pointer-events: none;
  transform: scale(0);
}

/* Tooltip accent overrides — both label and fleuron read --tooltip-accent
   when the trigger sets it. */
.tooltip-hint-label--accent {
  color: var(--tooltip-accent);
  border-bottom-color: color-mix(in srgb, var(--tooltip-accent) 35%, transparent);
}
.tooltip-hint-fleuron--accent { color: var(--tooltip-accent); }

/* The Profile "Animations" toggle (default on) puts body.animations-forced
   on the document, which keeps anime.js / CSS animations alive even when
   the OS reports reduce-motion. Users who turn it off get OS-respecting
   behaviour. */
@media (prefers-reduced-motion: reduce) {
  body:not(.animations-forced) .tooltip-hint { animation-duration: 1ms; }
}

/* Markdown editor (Quick Translator panel). The contenteditable element is
   styled to look like a textarea but renders inline HTML produced by marked.
   :empty::before exposes a placeholder since contenteditable has no native
   placeholder attribute. */
.md-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--fg-muted);
  font-style: italic;
  pointer-events: none;
}
.md-editor:focus { border-color: var(--ink); box-shadow: var(--focus); }
.md-editor h1,
.md-editor h2,
.md-editor h3 { font-family: var(--font-display); margin: 0.4em 0 0.2em; line-height: 1.2; }
.md-editor h1 { font-size: 20px; }
.md-editor h2 { font-size: 17px; }
.md-editor h3 { font-size: 15px; }
.md-editor p  { margin: 0 0 0.4em; }
.md-editor ul,
.md-editor ol { margin: 0 0 0.4em; padding-left: 24px; }
.md-editor blockquote {
  border-left: 2px solid var(--rubric);
  margin: 0.4em 0;
  padding: 0.2em 0.6em;
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
}
.md-editor code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 1px 4px;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  border-radius: 2px;
}
.md-editor pre {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  overflow-x: auto;
}
.md-editor pre code { padding: 0; border: none; background: none; }
.md-editor hr { border: 0; border-top: 1px solid var(--border); margin: 0.6em 0; }
.md-editor a  { color: var(--link); }
.md-editor strong { font-weight: 600; }
.md-editor em { font-style: italic; }

/* ── Shared utility classes — replace local inline-style objects.
   Each block here was a `const X = {…}` defined inside a component
   and spread into JSX with `style={X}` / `style={{ ...X, … }}`. The
   dynamic overrides became modifier classes (e.g. PANEL_BTN +
   ink/paper background → `.panel-btn--ink`). */

/* In-panel utility button (was app.jsx PANEL_BTN). */
.panel-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  border-radius: 2px;
}
.panel-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.panel-btn--ink {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.panel-btn--rubric {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}

/* Form input (was app.jsx inputStyle in three places). */
.app-input {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  width: 100%;
  box-sizing: border-box;
}
.app-input--lg {
  font-size: 14px;
  padding: 8px 12px;
}
.app-input--invalid {
  border-color: var(--rubric);
}
.app-input--textarea {
  resize: vertical;
  line-height: 1.5;
}

/* Combat-tracker tiny ±/✕ control buttons (was app.jsx miniButtonStyle). */
.combat-mini-btn {
  width: 20px;
  height: 20px;
  border: 1px solid var(--border);
  background: transparent;
  cursor: pointer;
  border-radius: 2px;
  font-size: 13px;
  padding: 0;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.combat-mini-btn--rubric    { color: var(--rubric); }
.combat-mini-btn--verdigris { color: var(--verdigris-deep); }
.combat-mini-btn--muted     { color: var(--fg-muted); }

/* Inventory-row remove-confirmation buttons (was sheet.jsx FullInventoryItemRow btnBase). */
.inv-confirm-btn {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 3px 14px;
  cursor: pointer;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  flex-shrink: 0;
}
.inv-confirm-btn--solid {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--paper);
}

/* ── Shared modal helpers — used by AbilityActivationModal,
     YieldToNoneFinishModal, and any future ability modal that shares
     the same form-field/eyebrow/stepper patterns. */
.modal-tagline {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.modal-meta-block {
  text-align: right;
}
.modal-meta-eyebrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.modal-meta-num {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
}
.modal-form-field {
  margin-bottom: 12px;
}
.modal-readout {
  padding: 6px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
}
.modal-readout--row {
  display: flex;
  justify-content: space-between;
}
.modal-readout--empty {
  font-style: italic;
  color: var(--fg-muted);
}
.modal-readout-value {
  font-family: var(--font-mono);
  color: var(--fg-muted);
}
.modal-select {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  font-family: var(--font-body);
  font-size: 14px;
}
.modal-card-section {
  margin-top: 12px;
}
.modal-card-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.modal-card-row--clickable > div { cursor: pointer; }
.modal-empty-hand {
  margin-top: 6px;
  font-style: italic;
  color: var(--fg-muted);
  font-size: 12px;
}
.modal-total-surface {
  margin-top: 14px;
}
.modal-stepper {
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.modal-stepper-btn {
  padding: 2px 10px;
  font-family: var(--font-display);
  font-size: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  cursor: pointer;
}
.modal-stepper-label {
  font-family: var(--font-body-italic);
  font-size: 12px;
  color: var(--fg-muted);
}
.modal-result-line {
  margin-top: 8px;
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 13px;
  text-align: center;
}
.modal-result-line--hit  { color: var(--verdigris); }
.modal-result-line--miss { color: var(--rubric); }
.modal-mark-hint {
  margin-top: 4px;
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
  text-align: center;
}

/* AbilityActivationModal-specific variants. The stepper here uses
   small-caps + mono buttons + a colour-coded ±N badge inside the
   label, distinct from the YieldToNoneFinishModal flavour above. */
.aam-stepper-btn {
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--fg);
}
.aam-stepper-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.aam-stepper-adjust {
  margin-left: 6px;
}
.aam-stepper-adjust--up   { color: var(--rubric); }
.aam-stepper-adjust--down { color: var(--verdigris); }
.aam-breakdown {
  margin-top: 4px;
  text-align: center;
  font-size: 11px;
  color: var(--fg-muted);
}
.aam-result {
  margin-top: 8px;
  text-align: center;
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 13px;
}
.aam-result--hit  { color: var(--verdigris); }
.aam-result--miss { color: var(--rubric); }
.aam-effects-hint {
  margin-bottom: 12px;
}

/* ── Player Hand (and GM Hand reuse) ─────────────────────────────────── */
/* The Hand component lives inside the fixed bottom dock and is shared
   between the player and GM mounts. All static layout lives here; only
   per-card transforms (driven by computed offset/index) stay inline in
   the JSX. */
#player-hand {
  position: relative;
  border-top: 1px solid var(--border);
  padding-top: 12px;
  margin-top: 16px;
}
.player-hand__top-slot {
  display: flex;
  justify-content: center;
  margin-bottom: 6px;
}
/* Answer-the-call cutout tray (above the fan). The wrap is the animated
   collapser that slides down on mount; the tray itself reads as a drawer
   descending from the panel's top edge — open top, rounded bottom corners. */
/* Floating overlay: the tray is taken out of flow and z-indexed above the
   hand so its appearance never resizes or displaces the panel — it overlays
   the top of the hand, anchored to the dock-handle edge. pointer-events are
   off on the full-width layer so only the tray itself is interactive. */
.player-hand__call-tray-wrap {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 60;
  overflow: hidden;
  display: flex;
  justify-content: center;
  pointer-events: none;
  will-change: height, opacity, transform;
}
.player-hand__call-tray {
  pointer-events: auto;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 14px;
  padding: 5px 12px 6px;
  background: linear-gradient(180deg, var(--surface-hi, var(--surface)) 0%, var(--surface) 100%);
  border: 1px solid var(--ink);
  border-top: none;
  border-radius: 0 0 6px 6px;
  box-shadow: 0 2px 6px rgba(30, 20, 10, 0.15) inset;
}
.player-hand__call-tray-readout {
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: left;
  min-width: 0;
}
.player-hand__call-tray-pull {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  white-space: nowrap;
}
.player-hand__call-tray-plus { color: var(--fg-muted); }
.player-hand__call-tray-diff {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.player-hand__call-tray-diff-num {
  font-family: var(--font-display);
  font-size: 22px;
  line-height: 1;
  color: var(--rubric);
}
.player-hand__call-tray-diff-name {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
}
.player-hand__call-tray-btn { flex-shrink: 0; }
#player-hand--header {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  margin-bottom: 6px;
  flex-wrap: wrap;
  gap: 6px;
}
.player-hand__actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.player-hand__row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  padding: 10px 0;
  min-height: 140px;
}
@media (max-width: 720px) {
  .player-hand__row { min-height: 110px; }
}
.player-hand__deck-track {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 100%;
  gap: 0;
}
.player-hand__deck-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  margin-right: 16px;
}
@media (max-width: 720px) {
  .player-hand__deck-wrap { margin-right: 10px; }
}
.player-hand__deck-stack {
  position: relative;
  user-select: none;
  cursor: pointer;
}
.player-hand__deck-stack--empty {
  cursor: default;
  opacity: 0.3;
}
/* Parry cue — the deck edge-glows while a parry counterattack is
   armed; the next flip strikes back at the attacker. */
.player-hand__deck-stack--parry {
  border-radius: 6px;
  animation: deck-parry-glow 0.8s ease-in-out infinite;
}
@keyframes deck-parry-glow {
  0%, 100% { box-shadow: 0 0 5px 1px var(--rubric, #b3402a); }
  50%      { box-shadow: 0 0 20px 7px var(--rubric, #b3402a); }
}
.player-hand__deck-parry-prompt {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-7deg);
  z-index: 6;
  font-family: var(--font-display, 'IM Fell English SC', Georgia, serif);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
  color: var(--paper, #f4ead7);
  background: var(--rubric, #b3402a);
  border: 1px solid var(--ink, #3a2a1a);
  border-radius: 3px;
  padding: 4px 8px;
  max-width: 110px;
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(20, 12, 4, 0.5);
}
.player-hand__deck-shadow {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.player-hand__deck-shadow--3 { transform: translate(6px, 6px); z-index: 0; }
.player-hand__deck-shadow--2 { transform: translate(3px, 3px); z-index: 1; }
.player-hand__deck-top { position: relative; z-index: 2; }
.player-hand__deck-count {
  position: absolute;
  bottom: -8px;
  right: -8px;
  z-index: 4;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  border-radius: 50%;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--paper);
  pointer-events: none;
}
.player-hand__deck-caption {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  color: var(--fg-muted);
}
.player-hand__deck-caption-num {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg);
  line-height: 1;
}
.player-hand__deck-caption-label {
  margin-top: 4px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}
.player-hand__fan-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
}
.player-hand__empty {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 16px;
  margin-bottom: 8px;
}
@media (max-width: 720px) {
  .player-hand__empty { font-size: 13px; }
}
.player-hand__fan {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 0;
  min-height: 100px;
}
@media (max-width: 720px) {
  .player-hand__fan { min-height: 80px; }
}
.player-hand__card {
  cursor: grab;
  transition: transform var(--dur-2) var(--ease-quill);
  margin-left: var(--card-margin, 0px);
  transform: rotate(var(--card-rot, 0deg)) translateY(var(--card-y, 0px));
  z-index: var(--card-z, 0);
}
.player-hand__card--multi {
  outline: 2px solid var(--rubric);
  outline-offset: 1px;
  border-radius: 8px;
}
.player-hand__action-row {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.player-hand__place-popover {
  margin-top: 8px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.player-hand__place-title {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.player-hand__place-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.player-hand__place-targets {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.player-hand__place-target-label {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}
.player-hand__discard-row {
  margin-top: 8px;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 12px;
}
.player-hand__handsize {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* ── Layout utilities (rows / cols) ─────────────────────────────────────────
   Generic flex utilities used across creation, table, sheet, and modal code.
   Naming: .row / .col with size modifiers --gap-N, --center, --between, etc. */
.row { display: flex; flex-direction: row; align-items: center; }
.row--start { align-items: flex-start; }
.row--baseline { align-items: baseline; }
.row--end { align-items: flex-end; }
.row--between { justify-content: space-between; }
.row--end-justify { justify-content: flex-end; }
.row--center-justify { justify-content: center; }
.row--wrap { flex-wrap: wrap; }
.row--gap-2 { gap: 2px; }
.row--gap-3 { gap: 3px; }
.row--gap-4 { gap: 4px; }
.row--gap-5 { gap: 5px; }
.row--gap-6 { gap: 6px; }
.row--gap-8 { gap: 8px; }
.row--gap-10 { gap: 10px; }
.row--gap-12 { gap: 12px; }
.row--header-split { display: flex; justify-content: space-between; align-items: flex-start; }

.col { display: flex; flex-direction: column; }
.col--gap-2 { gap: 2px; }
.col--gap-4 { gap: 4px; }
.col--gap-6 { gap: 6px; }
.col--gap-8 { gap: 8px; }
.col--gap-10 { gap: 10px; }
.col--gap-12 { gap: 12px; }
.col--gap-16 { gap: 16px; }
.col--center { align-items: center; }

.divider-h { flex: 1; height: 1px; background: var(--border); }
.divider-h-soft { height: 1px; background: var(--border-soft); }
.divider-v { width: 1px; background: var(--border); align-self: stretch; }

/* Faded label-eyebrow used in a few wizard substeps */
.tiny-eyebrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* ── Creation wizard — shared frame & step structure ────────────────────── */
.creation-shell { min-height: 100%; background: var(--bg); }
.creation-header {
  position: sticky; top: 0; z-index: 10;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  display: flex; align-items: center; gap: 12px;
}
.creation-header-eyebrow {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.creation-header-name {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
}
.creation-header-exit {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
}

/* ── Creation wizard — GM chrome editing ─────────────────────────────── */
.creation-chrome-toggle {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 12px;
  border: 1px solid var(--rubric);
  background: transparent;
  color: var(--rubric);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-1) var(--ease-quill), color var(--dur-1) var(--ease-quill);
}
.creation-chrome-toggle.is-active {
  background: var(--rubric);
  color: var(--paper);
}
.creation-chrome-banner {
  padding: 7px 20px;
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--rubric);
  background: var(--surface);
  border-bottom: 1px solid var(--border-soft);
  text-align: center;
}
.chrome-edit {
  display: inline-block;
  min-width: 1ch;
  padding: 0 2px;
  border-radius: 2px;
  border-bottom: 1px dashed var(--rubric);
  cursor: text;
  transition: background var(--dur-1) var(--ease-quill);
}
.chrome-edit:hover { background: var(--surface); }
.chrome-edit:focus {
  outline: 2px solid var(--rubric);
  outline-offset: 1px;
  background: var(--paper);
  white-space: pre-wrap;
}

/* ── Creation wizard — name step ─────────────────────────────────────── */
.cc-name-shell {
  max-width: 480px;
  margin: 0 auto;
  padding: 64px 28px 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
}
.cc-name-eyebrow {
  opacity: 0;
  margin-bottom: 12px;
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.cc-name-title {
  opacity: 0;
  font-family: var(--font-display);
  font-size: 42px;
  color: var(--ink-strong);
  letter-spacing: 0.01em;
  text-align: center;
  line-height: 1.1;
  margin-bottom: 8px;
}
.cc-name-tagline {
  opacity: 0;
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 15px;
  color: var(--fg-muted);
  text-align: center;
  margin-bottom: 48px;
}
.cc-name-rule {
  opacity: 0;
  width: 100%;
  margin-bottom: 40px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.cc-name-rule-star {
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--border);
  letter-spacing: 0.2em;
}
.cc-name-input-wrap {
  opacity: 0;
  width: 100%;
  margin-bottom: 8px;
}
.cc-name-input-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.cc-name-input {
  font-family: var(--font-display);
  background: transparent;
  outline: none;
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 6px 0;
  color: var(--ink-strong);
  font-size: 28px;
}
.cc-name-button-wrap { opacity: 0; width: 100%; margin-top: 40px; }
/* Subdued Back link beneath the main Continue button on the (now mid-flow)
   Name screen. */
.cc-name-back { display: block; margin: 14px auto 0; opacity: 0.7; }

/* Ancestry example-name tables shown below the name field for reference. The
   .cc-name-shell is narrow (560px) and centered; this section breaks wider and
   tiles the guidebook's :::narrow tables into an easy-to-read responsive grid. */
.cc-name-tables {
  margin: 44px auto 0;
  max-width: 920px;
  width: 92vw;
  text-align: left;
}
.cc-name-tables-eyebrow {
  font-family: var(--font-body);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
  text-align: center;
  margin-bottom: 18px;
}
.cc-name-tables-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 18px 22px;
  align-items: start;
}
/* Override the guidebook's centered 520px cap so each table fills its grid cell. */
.cc-name-tables-grid .gb-narrow { max-width: none; margin: 0; }
.cc-name-tables-grid .gb-table-wrap { margin: 0; }
.cc-name-tables-grid .gb-table { font-size: 0.72rem; }
.cc-name-tables-grid .gb-table th { text-align: left; border-bottom: 1px solid var(--border); padding: 4px 8px; }
.cc-name-tables-grid .gb-table td { padding: 3px 8px; }
.cc-name-button {
  width: 100%;
  padding: 14px 24px;
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.08em;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  transition: background 200ms, border-color 200ms, color 200ms;
}
.cc-name-button:disabled {
  border-color: var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: default;
}

/* ── Creation wizard — persona dots ──────────────────────────────────── */
.cc-step-title-large {
  opacity: 0;
  font-family: var(--font-display);
  font-size: 30px;
  color: var(--ink-strong);
  line-height: 1.1;
  margin-bottom: 14px;
}
.cc-persona-dots {
  opacity: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.cc-persona-dot {
  width: 8px;
  height: 8px;
  border-radius: 4px;
  background: var(--border);
  transition: width 250ms, background 200ms;
}
.cc-persona-dot.is-active { width: 20px; background: var(--sepia); }
.cc-persona-dot.is-done { background: var(--ink); }
.cc-persona-summary-row { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; align-items: center; }
.cc-persona-summary-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border: 1px solid var(--border);
  background: var(--paper-deep);
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
}
.cc-persona-summary-label { color: var(--fg); }
.cc-persona-summary-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  cursor: pointer;
  user-select: none;
}
.cc-persona-summary-toggle input { cursor: pointer; }
.cc-persona-summary-toggle:hover { color: var(--fg); }

/* ── Creation wizard — profession step ──────────────────────────────── */
.cc-prof-search-wrap { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
.cc-prof-search-input {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  outline: none;
  width: 100%;
  box-sizing: border-box;
}
.cc-prof-cat-row { display: flex; gap: 5px; flex-wrap: wrap; }
.cc-prof-cat-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 11px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  letter-spacing: 0.04em;
}
.cc-prof-cat-btn.is-active {
  border-color: var(--sepia);
  background: var(--sepia);
  color: var(--paper);
}
.cc-prof-empty {
  text-align: center;
  padding: 48px 0;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
}
.cc-prof-summary {
  margin-top: 14px;
  padding: 10px 14px;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.cc-prof-summary-strong { color: var(--fg-strong); }
.cc-prof-summary-edit {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}

/* ── Custom-profession modal ──────────────────────────────────────────── */
.cc-other-modal {
  position: fixed;
  inset: 0;
  z-index: 1100;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.cc-other-modal-card {
  background: var(--paper);
  border: 1.5px solid var(--border);
  padding: 4px;
  max-width: 540px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
}
.cc-other-modal-inner { border: 1px solid var(--border-soft); padding: 24px 20px; }
.cc-other-modal-title {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--ink-strong);
  margin-bottom: 4px;
}
.cc-other-modal-hint {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  margin-bottom: 22px;
  line-height: 1.5;
}
.cc-other-modal-section { margin-bottom: 18px; }
.cc-other-modal-section--last { margin-bottom: 22px; }
.cc-other-modal-input {
  width: 100%;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 8px 10px;
  box-sizing: border-box;
}
.cc-other-modal-textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  line-height: 1.55;
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 8px 10px;
  resize: vertical;
  min-height: 80px;
  box-sizing: border-box;
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
}
.cc-other-skill-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; }
.cc-other-skill-btn {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 7px 8px;
  text-align: left;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  transition: border-color 150ms, background 150ms;
}
.cc-other-skill-btn.is-selected {
  border-color: var(--sepia);
  background: var(--paper-deep);
  color: var(--ink-strong);
}
.cc-other-skill-btn:disabled { color: var(--fg-muted); cursor: default; }
.cc-other-modal-footer { display: flex; gap: 10px; justify-content: flex-end; }

/* ── Wizard dots (attribute / skill steppers) ────────────────────────── */
.wiz-dot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  flex-shrink: 0;
  background: transparent;
  border: 1.5px solid var(--border);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.wiz-dot.is-locked { background: var(--sepia); border-color: var(--sepia-soft); cursor: default; }
.wiz-dot.is-filled { background: var(--ink); border-color: var(--ink); }
.wiz-dot.is-filled.is-accented { background: var(--dot-accent); border-color: var(--dot-accent); }

/* ── Pool label (remaining points) ───────────────────────────────────── */
.cc-pool-row { display: flex; align-items: baseline; gap: 10px; margin-bottom: 24px; }
.cc-pool-num {
  font-family: var(--font-display);
  font-size: 36px;
  line-height: 1;
  color: var(--ink-strong);
}
.cc-pool-num--zero { color: var(--verdigris-deep); }
.cc-pool-num--low { color: var(--sepia); }
.cc-pool-of {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}
.cc-pool-warn {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  font-style: italic;
}

/* ── Attributes grid step ─────────────────────────────────────────────── */
.cc-attr-cluster { border: 1.5px solid var(--border); padding: 4px; }
.cc-attr-cluster-inner { border: 1px solid var(--border-soft); padding: 14px 14px 18px; }
.cc-attr-cluster-eyebrow {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 16px;
  color: var(--cluster-accent, var(--fg));
}
.cc-attr-num.is-set { color: var(--cluster-accent, var(--fg-strong)); }
.cc-attr-list { display: flex; flex-direction: column; gap: 16px; }
.cc-attr-row { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px; }
.cc-attr-name {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink-strong);
}
.cc-attr-num {
  font-family: var(--font-mono);
  font-size: 22px;
  line-height: 1;
  color: var(--border);
}
.cc-attr-num.is-set { color: var(--ink-strong); }

/* ── Skills grid step ─────────────────────────────────────────────────── */
.cc-skill-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border-soft);
}
.cc-skill-row--last { border-bottom: none; }
.cc-skill-row--right { border-left: 1px solid var(--border-soft); }
.cc-skill-name {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink-strong);
}

/* ── Creation: class core attributes / key skills highlight ──────────── */
.cc-stat-name-wrap { display: flex; align-items: center; gap: 7px; }
.cc-stat-chip {
  font-family: var(--font-body);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--rubric);
  border-radius: 2px;
  padding: 1px 5px;
  line-height: 1.6;
}
.cc-stat-chip--inline { vertical-align: middle; }
.cc-attr-row--core {
  box-shadow: inset 2px 0 0 0 var(--rubric);
  padding-left: 7px;
}
.cc-attr-row--core .cc-attr-name { color: var(--rubric); }
.cc-skill-row--key { box-shadow: inset 2px 0 0 0 var(--rubric); }
.cc-skill-row--key .cc-skill-name { color: var(--rubric); }
.cc-stat-hint { margin-top: 2px; }

/* ── Derived values cards ─────────────────────────────────────────────── */
.cc-derived-card {
  opacity: 0;
  border: 1.5px solid var(--border);
  padding: 4px;
}
.cc-derived-inner {
  border: 1px solid var(--border-soft);
  padding: 12px 12px 14px;
  display: flex;
  flex-direction: column;
  height: 100%;
  box-sizing: border-box;
}
.cc-derived-label {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 8px;
}
.cc-derived-numrow {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: 12px;
}
.cc-derived-before {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
  text-decoration: line-through;
  opacity: 0.6;
}
.cc-derived-arrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
}
.cc-derived-after {
  font-family: var(--font-display);
  font-size: 34px;
  line-height: 1;
  color: var(--derived-accent, var(--fg-strong));
}
.cc-derived-formula {
  margin-top: auto;
  display: flex;
  gap: 5px;
  align-items: flex-end;
  flex-wrap: wrap;
}
.cc-derived-plus {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-muted);
  padding-bottom: 2px;
}
.cc-derived-term { text-align: center; }
.cc-derived-term-num {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  color: var(--ink-strong);
}
.cc-derived-term-lbl {
  font-family: var(--font-body);
  font-size: 8px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
  margin-top: 2px;
  white-space: nowrap;
}
.cc-derived-term--colored .cc-derived-term-num,
.cc-derived-term--colored .cc-derived-term-lbl { color: var(--term-color); }

/* ── Class abilities preview cards ────────────────────────────────────── */
.cc-abil-card {
  opacity: 0;
  border: 1.5px solid var(--border);
  padding: 4px;
}
.cc-abil-inner {
  border: 1px solid var(--border-soft);
  padding: 12px 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 260px;
  box-sizing: border-box;
}
.cc-abil-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
}
.cc-abil-name {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--ink-strong);
  line-height: 1.2;
}
.cc-abil-cost {
  font-family: var(--font-body);
  font-size: 11px;
  flex-shrink: 0;
  color: var(--fg-muted);
}
.cc-abil-cost--passive { color: var(--sepia); font-style: italic; }
.cc-abil-tags {
  font-family: var(--font-body);
  font-size: 9px;
  letter-spacing: 0.06em;
  color: var(--sepia);
  text-transform: uppercase;
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: 5px;
}
.cc-abil-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.cc-abil-meta-eyebrow {
  font-family: var(--font-body);
  font-size: 8px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 2px;
  opacity: 0.7;
}
.cc-abil-meta-value {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
}
.cc-abil-meta-value--row {
  display: flex;
  align-items: baseline;
  gap: 3px;
}
.cc-abil-check {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  display: flex;
  align-items: baseline;
  gap: 4px;
  flex-wrap: wrap;
}
.cc-abil-check-vs { font-size: 9px; opacity: 0.7; }
.cc-abil-check-diff { color: var(--ink-strong); }
.cc-abil-effects {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg);
  line-height: 1.55;
  flex: 1;
}
.cc-sep-dot { color: var(--border); margin: 0 2px; }

/* ── Choose Facets step ────────────────────────────────────────────────── */
.cc-facet-points-bar {
  border: 1px solid var(--border);
  padding: 10px 14px;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.cc-facet-points-label {
  font-family: var(--font-body);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
  flex: 1;
}
.cc-facet-points-num {
  font-family: var(--font-display);
  font-size: 1.75rem;
  line-height: 1;
  transition: color 200ms;
  color: var(--points-color, var(--fg-strong));
}
.cc-facet-points-bonus {
  font-family: var(--font-body);
  font-size: 0.625rem;
  color: var(--sepia);
}
.cc-facet-points-short {
  font-family: var(--font-body);
  font-size: 0.625rem;
  color: var(--rubric);
}
.cc-facet-tabs {
  display: flex;
  border-bottom: 1px solid var(--border);
}
.cc-facet-tab {
  flex: 1;
  padding: 10px 8px;
  font-family: var(--font-body);
  font-size: 0.75rem;
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.cc-facet-tab.is-active {
  border-bottom-color: var(--tab-accent);
  color: var(--tab-accent);
}
.cc-facet-tab-badge {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  padding: 1px 5px;
  color: var(--paper);
  border-radius: 2px;
  background: var(--tab-accent, var(--ink));
}
.cc-facet-tab-content {
  border: 1px solid var(--border);
  border-top: none;
  margin-bottom: 32px;
}
.cc-facet-selected-box {
  padding: 10px;
  border-bottom: 1px solid var(--border);
  min-height: 52px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-content: flex-start;
}
.cc-facet-selected-empty {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  font-style: italic;
}
.cc-facet-controls-row {
  padding: 6px 8px;
  display: flex;
  gap: 6px;
}
.cc-facet-control-input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-body);
  font-size: 0.6875rem;
  padding: 4px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
}
.cc-facet-control-select {
  font-family: var(--font-body);
  font-size: 0.6875rem;
  padding: 4px 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
}
.cc-facet-list {
  overflow-y: auto;
  max-height: 380px;
  padding: 8px;
  display: grid;
  gap: 6px;
}
.cc-facet-points-cell {
  grid-row: span 2;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}
.cc-facet-points-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
}
.cc-facet-points-num--lg {
  font-family: var(--font-display);
  font-size: 2rem;
  line-height: 1;
  transition: color 200ms;
  color: var(--points-color, var(--fg-strong));
}
.cc-facet-points-extra {
  font-family: var(--font-body);
  font-size: 0.625rem;
  margin-top: 6px;
  line-height: 1.4;
}
.cc-facet-points-extra--bonus { color: var(--sepia); }
.cc-facet-points-extra--short { color: var(--rubric); }

.cc-facet-shell { border: 1px solid var(--border); margin-bottom: 32px; }
.cc-facet-eyebrow-row {
  display: grid;
  grid-template-columns: 1fr 80px 1fr;
  margin-bottom: 4px;
}
.cc-facet-eyebrow-pos { color: var(--indigo); }
.cc-facet-eyebrow-neg { color: var(--verdigris-deep); }
.cc-facet-list--rtl { overflow-y: auto; max-height: 361px; direction: rtl; }
.cc-facet-list--rtl-inner { direction: ltr; padding: 8px; display: grid; gap: 6px; }
.cc-facet-list--ltr { overflow-y: auto; max-height: 361px; padding: 8px; display: grid; gap: 6px; }

.cc-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 6px 3px 10px;
  background: var(--surface);
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg);
  border: 1px solid var(--indigo);
}
.cc-chip--neg { border-color: var(--verdigris-deep); }
.cc-chip-cost {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-muted);
  flex-shrink: 0;
}
.cc-chip-x {
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--fg-muted);
  font-size: 13px;
  padding: 0 0 0 2px;
  line-height: 1;
}

.cc-browse-card {
  border: 1px solid var(--border);
  padding: 4px;
  cursor: pointer;
  background: var(--surface);
}
.cc-browse-card.is-blocked { cursor: default; opacity: 0.4; }
.cc-browse-inner {
  border: 1px solid var(--border-soft);
  padding: 8px 10px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cc-browse-head { display: flex; align-items: baseline; gap: 6px; }
.cc-browse-name {
  font-family: var(--font-display);
  font-size: 0.8125rem;
  color: var(--ink-strong);
  line-height: 1.2;
}
.cc-browse-sublabel {
  font-family: var(--font-display);
  font-size: 0.8125rem;
  color: var(--fg-muted);
  flex: 1;
  text-align: center;
  line-height: 1.2;
}
.cc-browse-meta { display: flex; align-items: baseline; gap: 5px; flex-shrink: 0; }
.cc-browse-count {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  color: var(--fg-muted);
}
.cc-browse-cost {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--fg-muted);
}
.cc-browse-cost--neg { color: var(--verdigris-deep); }
.cc-browse-cost--pos { color: var(--sepia); }
.cc-browse-desc {
  font-family: var(--font-body);
  font-size: 0.625rem;
  color: var(--fg-muted);
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: pre-line;
}

/* ── Generic confirm overlay (unspent points modal etc) ─────────────── */
.cc-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
}
.cc-overlay-card {
  background: var(--surface);
  border: 1.5px solid var(--border);
  padding: 4px;
  max-width: 400px;
  width: 90%;
}
.cc-overlay-inner {
  border: 1px solid var(--border-soft);
  padding: 28px 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.cc-overlay-title {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--ink-strong);
}
.cc-overlay-body {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  line-height: 1.65;
}
.cc-overlay-footer {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

/* Header spacing variants for wizard step headers */
.step-header--mb-20 { margin-bottom: 20px; padding-bottom: 20px; }
.step-header--mb-14 { margin-bottom: 14px; padding-bottom: 14px; }

/* Grid layouts for the wizard substeps (replacing inline grid-template-columns) */
.cc-grid-attr {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.cc-grid-skills {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  border: 1px solid var(--border);
}
.cc-grid-derived {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-bottom: 32px;
}
.cc-grid-abilities {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 32px;
}

/* Class Facet section on the Class Abilities wizard step — sits below the
   starter-ability grid, reusing the ability-card styling. */
.cc-facet-section {
  margin-top: 28px;
}

/* Centered variant: H2-style heading flanked by rule lines, with the
   facet card centered beneath it (constrained so it doesn't stretch full
   width). */
.cc-facet-section--center {
  margin-top: 36px;
}
.cc-facet-heading {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 18px;
}
.cc-facet-heading-rule {
  flex: 1;
  height: 1px;
  background: var(--border);
}
.cc-facet-heading-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  color: var(--fg-strong);
}
.cc-facet-center-card {
  display: flex;
  justify-content: center;
}
.cc-facet-center-card .cc-abil-card {
  max-width: 420px;
  width: 100%;
}
.cc-facet-grid-3 {
  display: grid;
  grid-template-columns: 1fr 80px 1fr;
}
.cc-facet-grid-3--with-spine {
  display: grid;
  grid-template-columns: 1fr 6px 1fr;
}

/* Step-level shell modifiers */
.cc-step-root--narrow { max-width: 960px; }

/* Inline-block inert label inside a flex grid (no flex grow) */
.cc-points-inline-label { flex: 0 0 auto; }

/* Vertical-line column used between two facet lists in the desktop layout */
.cc-facet-spine {
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
}

/* Hairline divider that fills horizontally without flex-grow */
.cc-hairline {
  height: 1px;
  background: var(--border);
}

/* ── Foe sheet & roster ──────────────────────────────────────────────── */
.foe-active-toggle {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 10px;
  line-height: 1.5;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  border-radius: 2px;
}
.foe-active-toggle.is-active {
  border-color: var(--verdigris-deep);
  background: rgba(74,117,86,0.12);
  color: var(--verdigris-deep);
}
.foe-sheet { padding: 10px 14px; display: flex; flex-direction: column; gap: 10px; }
.foe-sheet--edit { gap: 12px; }
.foe-toolbar { display: flex; justify-content: space-between; align-items: center; }
.foe-toolbar--wrap { flex-wrap: wrap; gap: 6px; }
.foe-toolbar-actions { display: flex; gap: 6px; align-items: center; }
.foe-confirm-prompt {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--rubric);
}

/* HP / WP MiniBar */
.foe-bar-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
.foe-bar-label {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.foe-bar-value {
  font-family: var(--font-mono);
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 1px;
}
.foe-bar-value-input {
  font-family: var(--font-mono);
  font-size: 12px;
  width: 26px;
  border: none;
  background: transparent;
  color: var(--fg-strong);
  text-align: right;
  padding: 0;
}
.foe-bar-value-max { color: var(--fg-muted); }
.foe-bar-track {
  height: 8px;
  background: var(--paper-deep);
  border: 1px solid var(--border);
}
.foe-bar-fill {
  height: 100%;
  transition: width var(--dur-2);
  width: var(--bar-pct, 0%);
  background: var(--bar-fill, var(--rubric));
}

/* Compact stat blocks (CD/RD/WD/SPD/RS) */
.foe-stat-row { display: flex; gap: 6px; flex-wrap: wrap; }
.foe-stat-block {
  text-align: center;
  padding: 3px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  min-width: 38px;
}
.foe-stat-block-lbl {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.foe-stat-block-val {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
  line-height: 1;
}
.foe-stat-block-sub {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 9px;
  color: var(--rubric);
  margin-top: 2px;
  line-height: 1;
}

/* Combat-style chip + picker. The display chip sits below the stat
   row when a style is set; the edit-mode button sits in the combat-
   stats edit block and opens CombatStylePickerModal. */
.foe-style-display {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 8px;
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: var(--surface);
  font-family: var(--font-body);
  font-size: 12px;
}
.foe-style-display-name {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--fg-strong);
  letter-spacing: 0.04em;
}
.foe-style-display-notes {
  color: var(--fg-muted);
}
.foe-style-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.foe-style-btn {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: var(--paper);
  cursor: pointer;
  font-family: var(--font-body);
  text-align: left;
  transition: background var(--dur-1, 120ms) ease, border-color var(--dur-1, 120ms) ease;
}
.foe-style-btn:hover {
  background: var(--surface-hi, var(--paper-deep));
  border-color: var(--ink);
}
.foe-style-btn-name {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--fg-strong);
}
.foe-style-btn-reactions {
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: 6px;
}
.foe-style-btn-empty {
  font-style: italic;
  color: var(--fg-muted);
}

/* CombatStylePickerModal — list of all 12 styles in foes.jsx. */
.combat-style-pick-list {
  display: flex;
  flex-direction: column;
  padding: 8px;
  gap: 4px;
  max-height: 60vh;
  overflow-y: auto;
}
.combat-style-pick-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 2px;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-body);
  transition: background var(--dur-1, 120ms) ease, border-color var(--dur-1, 120ms) ease;
}
.combat-style-pick-row:hover {
  background: var(--surface-hi, var(--paper-deep));
  border-color: var(--ink);
}
.combat-style-pick-row.is-current {
  background: color-mix(in srgb, var(--verdigris) 14%, var(--paper));
  border-color: var(--verdigris-deep);
}
.combat-style-pick-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.combat-style-pick-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg-strong);
}
.combat-style-pick-reactions {
  display: inline-flex;
  gap: 8px;
  font-size: 11px;
  color: var(--fg-muted);
}
.combat-style-pick-reaction {
  font-family: var(--font-mono);
}
.combat-style-pick-notes {
  font-size: 11px;
  color: var(--rubric);
  font-family: var(--font-display-italic);
  font-style: italic;
}
.combat-style-pick-desc {
  font-size: 11px;
  color: var(--fg-muted);
  line-height: 1.3;
}

/* Attribute / skill listings (display) */
.foe-attr-list { display: flex; flex-direction: column; gap: 3px; }
.foe-attr-line {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
}
.foe-attr-cluster-label {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cluster-accent, var(--fg));
}
.foe-skill-line {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
  line-height: 1.7;
}
.foe-abil-block {
  border-top: 1px solid var(--border-soft);
  padding-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.foe-abil-name {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-strong);
}
.foe-abil-text {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}

/* Edit-mode form pieces */
.foe-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.foe-grid-2--gap-10 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.foe-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.foe-form-label { display: flex; flex-direction: column; gap: 3px; }
.foe-form-label-eyebrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.foe-form-input {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  width: 100%;
  box-sizing: border-box;
}
.foe-form-input--sm { font-size: 12px; }
.foe-form-num {
  font-family: var(--font-mono);
  font-size: 14px;
  width: 44px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  text-align: center;
}
.foe-hpwp-row { display: flex; gap: 4px; align-items: center; }
.foe-hpwp-sep {
  color: var(--fg-muted);
  font-family: var(--font-body);
}
.foe-section-eyebrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.foe-stat-edit-label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: center;
}
.foe-stat-edit-eyebrow {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
}
.foe-stat-edit-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* Attribute cluster cards in edit mode */
.foe-attr-cluster {
  border: 1px solid var(--border);
  border-top: 2px solid var(--cluster-accent, var(--border));
}
.foe-attr-cluster-head {
  padding: 4px 8px;
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--cluster-accent, var(--fg));
}
.foe-attr-edit-input--accent { color: var(--cluster-accent, var(--fg)); }
.foe-attr-edit-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 5px 8px;
  border-top: 1px solid var(--border-soft);
}
.foe-attr-edit-name {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
}
.foe-attr-edit-input {
  font-family: var(--font-display);
  font-size: 20px;
  width: 32px;
  border: none;
  background: transparent;
  text-align: right;
  padding: 0;
}

/* Skill grid edit */
.foe-skill-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--border);
}
.foe-skill-edit-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 4px 8px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--surface);
}
.foe-skill-edit-name {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
}
.foe-skill-edit-input {
  font-family: var(--font-mono);
  font-size: 13px;
  width: 32px;
  border: none;
  background: transparent;
  color: var(--fg-strong);
  text-align: right;
  padding: 0;
}

/* Abilities edit */
.foe-abil-row {
  display: grid;
  grid-template-columns: 1fr 2fr auto;
  gap: 6px;
  margin-bottom: 6px;
}
.foe-abil-remove {
  border: none;
  background: none;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 14px;
  padding: 0 4px;
}
.foe-abil-eyebrow {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
}

/* ── Threat-built foe sheet ──────────────────────────────────────────── */
.foe-sheet--threat { display: flex; flex-direction: column; gap: 10px; }
.foe-threat-badge {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rubric);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 2px 7px;
}
.foe-stat-block--derived { background: var(--paper-deep); }
.foe-threat-meta { display: flex; flex-wrap: wrap; gap: 6px; }
.foe-threat-chip {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  padding: 2px 7px;
}
.foe-threat-desc {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg);
  line-height: 1.4;
}
.foe-pick-row { display: flex; flex-direction: column; gap: 4px; }
.foe-form-textarea { resize: vertical; font-family: var(--font-body); line-height: 1.4; }

/* Weapon / armour reference lines */
.foe-gear-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-top: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
  padding: 8px 0;
}
.foe-gear-row { display: flex; align-items: baseline; gap: 8px; }
.foe-gear-kind {
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
  min-width: 56px;
}
.foe-gear-name {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
}
.foe-gear-trait {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
}

/* Reserved Foe Facets slot (mechanics not yet implemented) */
.foe-facets-slot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border: 1px dashed var(--border);
  border-radius: 2px;
  opacity: 0.65;
}
.foe-facets-slot-lbl {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.foe-facets-slot-cap {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg-muted);
}

/* Weapon / armour picker modal */
.foe-picker-search { margin-bottom: 8px; }
.foe-item-pick-list {
  display: flex;
  flex-direction: column;
  max-height: 50vh;
  overflow-y: auto;
  border: 1px solid var(--border);
}
.foe-item-pick-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border: none;
  border-bottom: 1px solid var(--border-soft);
  background: var(--surface);
  text-align: left;
  cursor: pointer;
}
.foe-item-pick-row:hover { background: var(--paper-deep); }
.foe-item-pick-row.is-current { background: rgba(74, 117, 86, 0.14); }
.foe-item-pick-name {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-strong);
}
.foe-item-pick-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-muted);
  white-space: nowrap;
}

/* Foe roster (FoeManager) */
.foe-add-row {
  padding: 10px 14px;
  display: flex;
  gap: 8px;
  border-bottom: 1px solid var(--border-soft);
}
.foe-add-input {
  flex: 1;
  font-family: var(--font-body);
  font-size: 13px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
}
.foe-bulk-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--paper-deep);
}
.foe-bulk-count {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-strong);
}
.foe-bulk-spacer { flex: 1; }
.foe-hint-row {
  padding: 4px 14px;
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border-soft);
}
.foe-empty {
  padding: 32px 14px;
  text-align: center;
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  font-size: 13px;
}
.foe-item { border-bottom: 1px solid var(--border-soft); }
.foe-item-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: var(--surface);
  border-left: 3px solid transparent;
  cursor: pointer;
  user-select: none;
}
.foe-item-header.is-selected {
  background: rgba(74, 117, 86, 0.14);
  border-left-color: var(--verdigris-deep);
}
.foe-item-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--border);
}
.foe-item-dot.is-active { background: var(--verdigris-deep); }
.foe-item-body { flex: 1; min-width: 0; }
.foe-item-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg-strong);
}
.foe-item-class {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: 8px;
}
.foe-item-chev {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  flex-shrink: 0;
}

/* Active-foes panel (read-only) */
.foe-active-empty { padding: 32px 14px; text-align: center; font-family: var(--font-display-italic); font-style: italic; color: var(--fg-muted); font-size: 13px; }
.foe-active-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 10px;
  padding: 12px;
}
.foe-active-card {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.foe-active-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg-strong);
  line-height: 1.2;
}
.foe-active-class {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.foe-active-stats { display: flex; gap: 4px; }
.foe-active-stat {
  flex: 1;
  text-align: center;
  padding: 3px 4px;
  border: 1px solid var(--border);
  background: var(--paper);
  border-radius: 4px;
}
.foe-active-stat-lbl {
  font-family: var(--font-body);
  font-size: 8px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.foe-active-stat-val {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--fg-strong);
  line-height: 1;
}
.foe-active-bars { display: flex; flex-direction: column; gap: 5px; }
.foe-active-bar-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
.foe-active-bar-lbl {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.foe-active-bar-val {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-muted);
}
.foe-active-bar-track {
  height: 5px;
  background: var(--paper-deep);
  border: 1px solid var(--border);
  border-radius: 2px;
}
.foe-active-bar-fill {
  height: 100%;
  border-radius: 2px;
  transition: width var(--dur-2);
  width: var(--bar-pct, 0%);
  background: var(--bar-fill, var(--rubric));
}

/* ── Top nav (table.jsx) ─────────────────────────────────────────────── */
.top-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.top-nav--desktop {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 12px 24px;
  gap: 16px;
}
.top-nav-mobile-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 16px;
}
.top-nav-brand {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.06em;
  color: var(--fg-strong);
}
.top-nav-brand--lg { font-size: 24px; }
.top-nav-fleuron {
  color: var(--rubric);
  font-family: var(--font-display);
  font-size: 14px;
}
.top-nav-mobile-tabs {
  display: flex;
  border-top: 1px solid var(--border-soft);
}
.top-nav-mobile-tab {
  flex: 1;
  padding: 11px 4px;
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--fg-muted);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  cursor: pointer;
  margin-bottom: -1px;
  /* Anchor variants (Guide Book) need the underline + link-blue
     stripped so they read as part of the same tab vocabulary. */
  text-decoration: none;
  text-align: center;
}
.top-nav-mobile-tab:hover,
.top-nav-mobile-tab:focus,
.top-nav-mobile-tab:visited { color: var(--fg-muted); }
.top-nav-mobile-tab.is-active,
.top-nav-mobile-tab.is-active:hover,
.top-nav-mobile-tab.is-active:focus,
.top-nav-mobile-tab.is-active:visited {
  border-bottom-color: var(--ink);
  color: var(--fg-strong);
}
.top-nav-tabs {
  display: flex;
  gap: 4px;
  justify-content: center;
}
.top-nav-tab {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  padding: 6px 14px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  letter-spacing: 0.04em;
  /* Anchor variants (Guide Book) need underline + visited blue
     stripped so they look like the other button tabs. */
  text-decoration: none;
  transition: color var(--dur-1) var(--ease-quill), border-color var(--dur-1) var(--ease-quill), background var(--dur-1) var(--ease-quill);
}
.top-nav-tab:hover,
.top-nav-tab:focus,
.top-nav-tab:visited { color: var(--fg); }
.top-nav-tab:hover { border-color: var(--ink); background: var(--surface); }
.top-nav-tab.is-active,
.top-nav-tab.is-active:hover,
.top-nav-tab.is-active:focus,
.top-nav-tab.is-active:visited {
  border-color: var(--ink);
  background: var(--surface);
  color: var(--fg);
}
.top-nav-left { display: flex; align-items: baseline; gap: 12px; }
.top-nav-session-row { display: flex; align-items: center; gap: 8px; }
.top-nav-session-id {
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 2px 8px;
  letter-spacing: 0.1em;
  color: var(--fg-muted);
  cursor: pointer;
  user-select: all;
}
.top-nav-session-id.is-connected { color: var(--verdigris-deep); }
.top-nav-session-title {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  font-style: italic;
  letter-spacing: 0.04em;
}
.top-nav-right {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
}
.top-nav-icon-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 10px;
  color: var(--fg);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-body);
  font-size: 12px;
}
.top-nav-icon-btn--mobile { padding: 7px 10px; }
.top-nav-icon-btn--muted { color: var(--fg-muted); padding: 6px 12px; }

/* ── SFX volume control (header bar) ─────────────────────────────────
   Per-player volume slider + mute toggle. The speaker button shares
   the .top-nav-icon-btn visual language; the inline range input is
   custom-styled to fit the manuscript aesthetic (ink track, rubric
   thumb). Compact variant collapses the slider on mobile where the
   header row is space-constrained. */
.sfx-volume {
  display: flex;
  align-items: center;
  gap: 6px;
}
.sfx-volume__btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
  color: var(--fg);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.sfx-volume__btn:hover {
  color: var(--rubric);
  border-color: var(--rubric);
}
.sfx-volume__slider {
  appearance: none;
  -webkit-appearance: none;
  width: 90px;
  height: 4px;
  background: var(--border);
  border-radius: 2px;
  outline: none;
  cursor: pointer;
}
.sfx-volume__slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  background: var(--rubric);
  border-radius: 50%;
  cursor: pointer;
  border: 1px solid var(--paper);
  box-shadow: var(--shadow-2);
}
.sfx-volume__slider::-moz-range-thumb {
  width: 12px;
  height: 12px;
  background: var(--rubric);
  border-radius: 50%;
  cursor: pointer;
  border: 1px solid var(--paper);
}
.sfx-volume--compact .sfx-volume__slider { width: 70px; }

/* ── Scene banner ────────────────────────────────────────────────────── */
.scene-banner {
  border: 1px solid var(--ink);
  background: var(--surface);
  padding: 18px 22px;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 24px;
  align-items: flex-start;
}
.scene-banner--mobile {
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.scene-banner-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}
.scene-banner-eyebrow {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.scene-banner-title {
  font-family: var(--font-display);
  font-size: 32px;
  line-height: 1.15;
  margin: 0 0 8px;
  color: var(--ink-strong);
}
.scene-banner-title--mobile { font-size: 22px; }
.scene-banner-narration {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 16px;
  color: var(--fg);
  margin: 0;
  max-width: 60ch;
  line-height: 1.45;
}
.scene-banner-factors {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.scene-banner-factor {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 2px;
  border: 1px solid var(--border);
  background: var(--paper);
  letter-spacing: 0.04em;
}
.scene-banner-factor--pos { color: var(--rubric); }
.scene-banner-factor--neg { color: var(--verdigris-deep); }
.scene-banner-call {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}
.scene-banner-call--mobile {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}
.scene-banner-call-block { display: flex; align-items: baseline; gap: 8px; }
.scene-banner-call-num {
  font-family: var(--font-display);
  font-size: 56px;
  color: var(--rubric);
  line-height: 1;
}
.scene-banner-call-num--mobile { font-size: 36px; }
.scene-banner-call-eyebrow {
  font-family: var(--font-body);
  font-size: 9px;
  color: var(--fg-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.scene-banner-call-name {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
}
.scene-banner-call-action {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
.scene-banner-call-formula {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
}
.scene-banner-call-plus { color: var(--fg-muted); }
.scene-banner-call-empty {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 14px;
  color: var(--fg-muted);
  text-align: right;
}

/* ── HP row ────────────────────────────────────────────────────────── */
.hp-row { display: flex; align-items: center; gap: 6px; }
.hp-row-label {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.hp-row-label--compact { font-size: 10px; }
.hp-row-track {
  flex: 1;
  height: 8px;
  background: var(--paper-deep);
  border: 1px solid var(--border);
  position: relative;
}
.hp-row-track--compact { height: 6px; }
.hp-row-fill {
  position: absolute;
  inset: 0;
  width: var(--hp-pct, 0%);
  background: var(--hp-fill, var(--rubric));
}
.hp-row-fill--good { --hp-fill: var(--verdigris); }
.hp-row-fill--mid  { --hp-fill: var(--sepia); }
.hp-row-fill--bad  { --hp-fill: var(--rubric); }
.hp-row-text {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg);
}
.hp-row-text--compact { font-size: 10px; }

/* ── Play zone ─────────────────────────────────────────────────────── */
.play-zone {
  min-height: 220px;
  padding: 18px;
  position: relative;
}
.play-zone--empty {
  border: 1px dashed var(--border);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 10px;
}
.play-zone--filled {
  border: 1px solid var(--ink);
  background: var(--paper);
}
.play-zone-empty-headline {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 16px;
  color: var(--fg-muted);
}
.play-zone-empty-hint {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
}
.play-zone-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}
.play-zone-eyebrow {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.play-zone-headline {
  font-family: var(--font-display);
  font-size: 24px;
  line-height: 1.1;
}
.play-zone-headline--rubric    { color: var(--rubric); }
.play-zone-headline--verdigris { color: var(--verdigris-deep); }
.play-zone-headline--sepia     { color: var(--fg-muted); }
.play-zone-headline-sweep {
  font-style: italic;
  font-size: 16px;
}
.play-zone-formula {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 2px;
}
.play-zone-formula-strong { color: var(--fg); }
.play-zone-margin-pos { margin-left: 10px; color: var(--verdigris-deep); }
.play-zone-margin-neg { margin-left: 10px; color: var(--rubric); }
.play-zone-cards {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: flex-end;
  flex-wrap: wrap;
  padding: 8px 0;
}

/* ── Deck context menu / draw modal ────────────────────────────────── */
.deck-ctx-menu {
  position: fixed;
  z-index: 400;
  background: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: var(--shadow-3);
  min-width: 160px;
  overflow: hidden;
  left: var(--ctx-x, 0px);
  top: var(--ctx-y, 0px);
}
.deck-ctx-item {
  display: block;
  width: 100%;
  padding: 9px 14px;
  font-family: var(--font-body);
  font-size: 13px;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--fg);
  cursor: pointer;
}
.deck-ctx-item + .deck-ctx-item { border-top: 1px solid var(--border); }
.deck-ctx-item:disabled { color: var(--fg-muted); cursor: default; }
.deck-ctx-item:not(:disabled):hover { background: var(--surface); }

.draw-dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(30,20,10,0.45);
  display: flex;
  align-items: center;
  justify-content: center;
}
.draw-dialog {
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 22px 24px;
  min-width: 240px;
  box-shadow: var(--shadow-3);
}
.draw-dialog-title {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--fg-strong);
  margin-bottom: 16px;
}
.draw-dialog-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.draw-dialog-eyebrow {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.draw-dialog-eyebrow-paren {
  font-family: var(--font-mono);
  letter-spacing: 0;
  text-transform: none;
}
.draw-dialog-input {
  font-family: var(--font-mono);
  font-size: 24px;
  text-align: center;
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  width: 100%;
}
.draw-dialog-filter {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 16px;
}
.draw-dialog-filter-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  cursor: pointer;
}
.draw-dialog-footer { display: flex; gap: 8px; justify-content: flex-end; }

/* ── GM panel ──────────────────────────────────────────────────────── */
.gm-panel {
  border: 1px solid var(--ink);
  background: var(--paper);
  padding: 0;
  position: sticky;
  top: 84px;
  max-height: calc(100vh - 110px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.gm-panel-header {
  background: var(--ink);
  color: var(--paper);
  padding: 10px 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.gm-panel-header-left { display: flex; align-items: center; gap: 10px; }
.gm-panel-title {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.gm-panel-toggle {
  background: transparent;
  border: none;
  color: var(--paper);
  cursor: pointer;
  padding: 4px;
  display: flex;
}
.gm-panel-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overflow: auto;
}
.gm-call-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 6px; }
.gm-section-eyebrow-row {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 4px;
}
.gm-difficulty-row { display: flex; align-items: center; gap: 8px; }
.gm-difficulty-stack { display: flex; align-items: baseline; gap: 6px; flex: 1; justify-content: center; }
.gm-difficulty-num {
  font-family: var(--font-display);
  font-size: 28px;
  color: var(--rubric);
}
.gm-difficulty-name {
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 12px;
  color: var(--fg-muted);
}
.gm-difficulty-scale {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  margin-top: 2px;
  padding: 0 24px;
}
.gm-call-actions { margin-top: 10px; display: flex; gap: 6px; }
.gm-section-block { margin-top: 10px; }
.gm-handrow {
  display: flex;
  gap: 4px;
  margin-top: 8px;
  flex-wrap: wrap;
  justify-content: center;
}
.gm-deck-row {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  margin-top: 8px;
}
.gm-trauma-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}
.gm-trauma-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  border-radius: 2px;
  cursor: pointer;
  letter-spacing: 0.04em;
}
.gm-scene-text {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  margin-top: 4px;
}
.gm-scene-beat {
  color: var(--fg-muted);
  font-style: italic;
  margin-top: 2px;
}
.gm-scene-actions { display: flex; gap: 6px; margin-top: 8px; }
.gm-icon-btn {
  width: 24px;
  height: 24px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  border-radius: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

/* Section label / labeled input */
.section-label-display {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.labeled-input { display: flex; flex-direction: column; gap: 2px; }
.labeled-input-eyebrow {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.labeled-input-field {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 5px 8px;
  border-radius: 2px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  width: 100%;
}
.labeled-input-field--num { font-family: var(--font-mono); }

/* ── Table log ─────────────────────────────────────────────────────── */
.table-log-card {
  padding: 0;
  height: 280px;
  display: flex;
  flex-direction: column;
}
.table-log-card--mobile { height: 200px; }
.table-log-toolbar {
  display: flex;
  justify-content: flex-end;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border-soft, var(--border));
}
.table-log-clear-btn {
  font-family: var(--font-body, Georgia, serif);
  font-size: 0.8em;
  color: var(--fg-muted, var(--ink));
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 10px;
  cursor: pointer;
  transition: color 100ms ease, border-color 100ms ease;
}
.table-log-clear-btn:hover {
  color: var(--rubric);
  border-color: var(--rubric);
}
.table-log-header {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
}
.table-log-header-italic {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  font-style: italic;
}
.table-log-entries {
  flex: 1;
  overflow: auto;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.log-entry { padding-left: 10px; border-left: 2px solid var(--border); }
.log-entry--rubric    { border-left-color: var(--rubric); }
.log-entry--verdigris { border-left-color: var(--verdigris-deep); }
.log-entry--sepia     { border-left-color: var(--fg-muted); }
.log-entry--ink       { border-left-color: var(--ink); }
.log-entry-head {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}
.log-entry-who {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  color: var(--fg-strong);
  letter-spacing: 0.04em;
}
.log-entry-kind {
  font-family: var(--font-body);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* Per-entry timestamp pill — server-stamped epoch ms rendered in the
   client's locale. Same-day entries collapse to the time only; older
   entries include a short date prefix. Hover shows the full
   timestamp. */
.log-entry-time,
.cs-log-entry-time {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.02em;
  white-space: nowrap;
  cursor: help;
}
.cs-log-entry-time { font-size: 11px; }

.cs-log-entry-label-row,
.cs-log-xp-entry-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.log-entry-body {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  margin-top: 2px;
  line-height: 1.45;
}
.log-entry-body--narration {
  font-family: var(--font-display-italic);
  font-style: italic;
}
/* Attack log entries — verbed result with hover tooltip on the pull
   breakdown, then a separate damage / trauma line on its own row with
   a hover tooltip on the damage breakdown. */
.log-entry-body--attack {
  line-height: 1.5;
}
.log-entry-attack-verb {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.04em;
  border-bottom: 1px dotted currentColor;
  cursor: help;
  white-space: nowrap;
}
.log-entry-attack-verb--hit      { color: var(--verdigris-deep); }
.log-entry-attack-verb--miss     { color: var(--rubric); }
.log-entry-attack-verb--critfail { color: var(--rubric-deep); }
.log-entry-attack-damage {
  font-family: var(--font-body);
  font-size: 13px;
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dashed var(--border-soft);
  color: var(--fg);
}
.log-entry-attack-damage strong {
  font-family: var(--font-body);
  color: var(--rubric);
}
/* Called-shot "Aimed for: …" line — italic body font, sepia tint so
   it reads as a GM-adjudication cue rather than a fact like damage. */
.log-entry-attack-aim {
  font-family: var(--font-body);
  font-size: 13px;
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dashed var(--border-soft);
  color: var(--fg-muted);
}
.log-entry-attack-aim em {
  color: var(--fg);
  font-style: italic;
}
/* Numerical values (margin, damage) inside an attack log entry —
   Ubuntu, slightly larger than the surrounding body text for
   readability. Dotted underline keeps the "hoverable" affordance
   the verb's surrounding span and the damage row already use. */
.log-entry-attack-num {
  font-family: var(--font-body);
  font-size: 17px;
  font-weight: 700;
  color: inherit;
  border-bottom: 1px dotted currentColor;
  cursor: help;
  padding: 0 2px;
  font-variant-numeric: tabular-nums;
}
.log-entry-attack-damage .log-entry-attack-num {
  color: var(--rubric);
  /* The number's dotted underline is supplied by the parent pair's
     border below, so suppress its own to avoid a doubled stroke. */
  border-bottom: 0;
  padding: 0 2px;
}

/* Damage value + type share one hover affordance: a single dotted
   underline runs across both, the title tooltip surfaces the
   breakdown when the user hovers anywhere over the pair. */
.log-entry-attack-damage-pair {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  border-bottom: 1px dotted var(--rubric);
  cursor: help;
  padding-bottom: 1px;
}
.log-entry-attack-dmg-type {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--rubric);
  font-size: 13px;
}
/* Inline trait fold-in on the Damage line — e.g.
   "Damage: 8 + Card Flip: 9 [Swift Tag], Total: 17". The label sits
   plain on the line, the number reuses the underlined-number style
   from .log-entry-attack-num, and the trait name is rendered as a
   small hoverable chip (wrapped in TooltipHint) showing the trait's
   canonical mechanical description on hover. */
.log-entry-attack-trait-mod {
  display: inline;
}
.log-entry-attack-trait-chip {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
  background: var(--paper-aged, var(--paper));
  border: 1px solid var(--rubric);
  border-radius: 3px;
  padding: 1px 6px;
  line-height: 1.4;
  cursor: help;
  vertical-align: baseline;
}
.log-entry-attack-trait-chip:hover {
  background: var(--rubric);
  color: var(--paper);
}
/* Block / Reckless damage-mitigation chip on the attack log entry. */
.log-entry-attack-defense-mod {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--verdigris-deep, var(--fg-strong));
  background: var(--paper-aged, var(--paper));
  border: 1px solid var(--verdigris, var(--border));
  border-radius: 3px;
  padding: 1px 6px;
  line-height: 1.4;
  vertical-align: baseline;
}
.log-entry-result {
  font-family: var(--font-mono);
  font-size: 11px;
  margin-top: 2px;
}
.log-entry-result--rubric    { color: var(--rubric); }
.log-entry-result--verdigris { color: var(--verdigris-deep); }
.log-entry-result--sepia     { color: var(--fg-muted); }
.log-entry-result--ink       { color: var(--ink); }

/* ── Card primitive (primitives.jsx) ─────────────────────────────────── */
.app-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 16px;
  box-shadow: var(--shadow-1);
  cursor: default;
  transition: box-shadow var(--dur-2) var(--ease-quill);
}
.app-card--clickable { cursor: pointer; }
.app-card--lifted { box-shadow: var(--shadow-2); }

/* ── Inputs that strip default chrome (sheet.jsx) ────────────────────── */
.input-reset {
  border: none;
  background: transparent;
  padding: 0;
  margin: 0;
}
.cs-resource-input-cur {
  width: 32px;
  color: var(--fg-strong);
  font-family: var(--font-mono);
  font-size: 13px;
  text-align: right;
}
.cs-resource-input-max {
  width: 32px;
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: 13px;
}
.cs-attr-input {
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1;
  width: 36px;
  text-align: right;
}
.cs-attr-row--bordered { border-top: 1px solid var(--border-soft); }
.cs-skill-pip {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--border);
  cursor: pointer;
  transition: background var(--dur-1);
}
/* Static pips (read-only sheet display). Same look, no pointer affordance. */
.cs-skill-pip--static { cursor: default; }
.cs-skill-pip--editable {
  cursor: pointer;
  transition: border-color 100ms ease, transform 80ms ease;
}
.cs-skill-pip--editable:hover {
  border-color: var(--ink);
  transform: scale(1.25);
}

/* Read-only numeric inputs across the top-level character sheet. They
   keep their position and font styling but lose every editing affordance:
   no caret cursor, no focus outline, no spinner arrows. The HTML
   `readOnly` attribute is what actually blocks edits — these styles just
   make the input read as static text. Item / ability editor modals are
   exempt because they use different class roots (.inv-modal-*, etc.). */
.cs-attr-input[readonly],
.cs-resource-input-cur[readonly],
.cs-resource-input-max[readonly],
.cs-combat-stat-input[readonly] {
  cursor: default;
  user-select: none;
}
.cs-attr-input[readonly]:focus,
.cs-resource-input-cur[readonly]:focus,
.cs-resource-input-max[readonly]:focus,
.cs-combat-stat-input[readonly]:focus {
  outline: none;
}
.cs-skill-pip.is-locked {
  background: var(--sepia);
  border-color: var(--sepia);
  cursor: default;
}
.cs-skill-pip.is-filled {
  background: var(--ink);
  border-color: var(--ink);
}
/* Card outer wrappers: keep a comfortable floor on the name row so the
   collapsed state looks like a substantial tile, and let body content
   (animated open/closed by .cs-card-body) drive height beyond that. */
.cs-card-perspective { perspective: 900px; min-height: 52px; }
.cs-card-face--inherit { min-height: inherit; }
.cs-card-inner--gap-6 { gap: 6px; }
.cs-card-inner--gap-5 { gap: 5px; }
.cs-ability-perspective {
  perspective: 900px;
  min-height: 52px;
}
.cs-ability-meta-value--flex1 { flex: 1; min-width: 0; }
.cs-ability-meta-value--w28 { width: 28px; }
.cs-ability-meta-value--w36 { width: 36px; }
.cs-ability-meta-value--w62 { width: 62px; }
.cs-ability-meta-value--block { display: block; }
.cs-ability-effects-min { min-height: 60px; }

/* HTML block elements inside on-sheet ability descriptions (migrated
   from plain text into <p>/<ul>/<li> via .tmp/format-descriptions.js).
   Tight default margins so the small ability-card body holds the
   structure without bloating. */
.cs-card-body-text p {
  margin: 0 0 4px;
}
.cs-card-body-text p:last-child { margin-bottom: 0; }
.cs-card-body-text ul {
  margin: 3px 0 4px;
  padding-left: 14px;
  list-style: none;
}
.cs-card-body-text li {
  position: relative;
  padding-left: 3px;
  margin-bottom: 2px;
}
.cs-card-body-text li::before {
  content: '❧';
  position: absolute;
  left: -12px;
  top: 0;
  color: var(--rubric);
  font-size: 10px;
}

/* Inventory section (sheet.jsx) */
.cs-inv-section { margin-bottom: 14px; }
.cs-inv-section--hidden { display: none; }
.inv-section-label--accent { color: var(--inv-accent); }
.cs-inv-row-name {
  flex: 1;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  font-style: italic;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.cs-inv-row-name.is-named { color: var(--fg-strong); font-style: normal; }
.cs-inv-row-name--grid { font-size: 10px; }

/* Inv-row size variants for ward/dmg/type chips */
.inv-row-ward--grid { font-size: 9px; }
.inv-row-dmg--grid { font-size: 9px; }
.inv-row-type--grid { font-size: 9px; }
.inv-row--confirm { gap: 8px; }
.cs-inv-equipped-name {
  flex: 1;
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--ink-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.cs-inv-row-badge-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.cs-inv-row-badge--grid { font-size: 9px; }
.cs-inv-row-badge--full { font-size: 11px; }

/* Sheet error fallback */
.sheet-error {
  padding: 24px;
  border: 1px solid var(--rubric);
  background: rgba(160,40,40,0.05);
  color: var(--fg-strong);
}
.sheet-error-title {
  font-family: var(--font-display);
  font-size: 16px;
  margin-bottom: 8px;
  color: var(--rubric);
}
.sheet-error-stack {
  font-family: monospace;
  font-size: 12px;
  margin-bottom: 12px;
  white-space: pre-wrap;
}
.sheet-error-btn {
  padding: 4px 12px;
  cursor: pointer;
}

/* Sheet header inputs */
.cs-header-name-input {
  font-size: 28px;
  padding-left: 3px;
}
.cs-header-name-input--mobile { font-size: 22px; }
.cs-header-ancestry-input {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg-muted);
  min-width: 60px;
}
.cs-header-ancestry-nowrap { white-space: nowrap; }

/* Sheet grids */
.cs-resources-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.cs-resources-grid--mobile { gap: 8px; }
.cs-attr-clusters-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px;
}
.cs-attr-clusters-grid--mobile { grid-template-columns: 1fr; }
.cs-skills-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.cs-skills-grid--mobile { grid-template-columns: 1fr; }
.cs-skill-col-divider { border-right: 1px solid var(--border); }
.cs-status-trauma-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  align-items: start;
}
.cs-status-trauma-grid--mobile { grid-template-columns: 1fr; gap: 18px; }
.cs-pair-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  justify-content: center;
  align-items: start;
}
.cs-pair-grid--mobile { grid-template-columns: 1fr; }

/* Sheet content scaffolding */
.sheet-shell {
  max-width: 1060px;
  margin: 0 auto;
}
.sheet-content {
  padding: 24px 24px 64px;
}
.sheet-content--mobile { padding: 14px 14px 64px; }
.sheet-content--parchment {
  background-image: var(--parchment-img);
  background-size: 400px;
  background-repeat: repeat;
}

/* ── World Map view ───────────────────────────────────────────────
   Travel-scale map: the game-world image at the bottom layer with
   a 45°-rotated square grid (visually a diamond grid) overlaid.
   Outer wrap captures pointer events for pan / click; inner stage
   transforms by pan + zoom; tokens and the grid share the stage
   so they translate / scale together. */
.world-map {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 480px;
  overflow: hidden;
  background: var(--paper-deep, #2a1d0e);
  cursor: grab;
  touch-action: none;
  user-select: none;
}
.world-map:active { cursor: grabbing; }
.world-map__stage {
  position: absolute;
  top: 0; left: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.world-map__image {
  display: block;
  pointer-events: none;
}
.world-map__grid {
  position: absolute;
  top: 0; left: 0;
  pointer-events: none;
}
.world-map__grid-line {
  stroke: rgba(67, 41, 23, 0.45);
  stroke-width: 1;
  fill: none;
  vector-effect: non-scaling-stroke;
}
.world-map__party {
  position: absolute;
  pointer-events: none;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
}
.world-map__party-svg {
  display: block;
  overflow: visible;
}
.world-map__party-crown {
  opacity: 0.92;
}
.world-map__note.is-gm {
  cursor: grab;
}
.world-map__note.is-gm.is-dragging {
  cursor: grabbing;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
  z-index: 5;
}
.world-map__note.is-gm.is-dragging .world-map__note-symbol {
  transform: scale(1.08);
  transform-origin: center bottom;
  transition: transform 100ms ease-out;
}
.world-map__hint {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 14px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 3px;
  font-family: var(--font-display-italic, var(--font-body-italic));
  font-size: 13px;
  color: var(--fg);
  letter-spacing: 0.04em;
  box-shadow: var(--shadow-2);
  pointer-events: none;
}
.world-map__note {
  position: absolute;
  transform: translate(-50%, -100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: auto;
  cursor: context-menu;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.35));
}
.world-map__note-icon {
  display: block;
}
/* Game-icons-net silhouette rendered via mask. Background colour is
   what shows through — pulled from the alchemical token so notes
   match the manuscript palette regardless of map background. The
   drop-shadows fake a paper-coloured rim so the silhouette holds
   contrast against busy map regions. */
.world-map__note-symbol {
  display: inline-block;
  background-color: var(--ink);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  filter: drop-shadow(0 0 1px var(--paper))
          drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
}
.world-map__note-label {
  margin-top: 2px;
  padding: 2px 8px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 2px;
  font-family: var(--font-display, var(--font-body));
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--ink);
  white-space: nowrap;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.world-map__note-delete {
  position: absolute;
  top: -6px;
  right: -8px;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid var(--ink);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 14px;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.world-map__note-delete:hover {
  background: var(--rubric);
  border-color: var(--rubric);
  color: var(--paper);
}
.world-map__note-tooltip-body p:first-child  { margin-top: 0; }
.world-map__note-tooltip-body p:last-child   { margin-bottom: 0; }
.world-map__note-tooltip-body ul,
.world-map__note-tooltip-body ol             { margin: 0.3em 0 0.3em 1.1em; padding: 0; }

/* ── Map-note editor modal ────────────────────────────────────────
   Replaces the legacy window.prompt flow. Inherits .modal-overlay,
   .modal-header, .modal-body, .modal-footer from the modal primitive
   classes. The internal layout is unique enough to deserve its own
   namespace for the form fields. */
.map-note-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  width: min(560px, 90vw);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.map-note-modal__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
}
.map-note-modal__field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 14px;
}
.map-note-modal__field:last-child { margin-bottom: 0; }
.map-note-modal__hint {
  color: var(--fg-muted);
  font-size: 11px;
  font-style: italic;
}
.map-note-modal__icon-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
  gap: 6px;
}
.map-note-modal__icon-slot {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--surface, var(--paper));
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}
.map-note-modal__icon-slot:hover {
  border-color: var(--rubric);
}
.map-note-modal__icon-slot.is-active {
  border-color: var(--ink);
  background: var(--paper);
  box-shadow: 0 0 0 1px var(--ink) inset;
}
.map-note-modal__input,
.map-note-modal__textarea {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 6px 8px;
}
.map-note-modal__input:focus,
.map-note-modal__textarea:focus {
  outline: none;
  border-color: var(--rubric);
}
.map-note-modal__textarea {
  resize: vertical;
  min-height: 120px;
  font-family: var(--font-body);
  line-height: 1.5;
}
.map-note-modal__footer-spacer { flex: 1; }
.map-note-modal__icon-slot.is-disabled {
  cursor: default;
  opacity: 0.4;
}
.map-note-modal__icon-slot.is-disabled.is-active {
  opacity: 1;     /* keep the chosen icon legible in read-only mode */
}
.map-note-modal__input[readonly] {
  background: rgba(0, 0, 0, 0.025);
  color: var(--fg);
  cursor: default;
}
.map-note-modal__readonly-body {
  font-family: var(--font-body-italic, var(--font-body));
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink);
  background: rgba(0, 0, 0, 0.025);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 10px 12px;
  min-height: 80px;
  max-height: 40vh;
  overflow-y: auto;
}
.map-note-modal__readonly-body p:first-child { margin-top: 0; }
.map-note-modal__readonly-body p:last-child  { margin-bottom: 0; }
.map-note-modal__readonly-body ul,
.map-note-modal__readonly-body ol            { margin: 0.3em 0 0.3em 1.4em; padding: 0; }

/* ── Confirm modal (yes/no) ──────────────────────────────────────
   Used in place of window.confirm wherever the chrome should match
   the rest of the app. Yes-button autoFocuses so Enter confirms;
   Esc and overlay-click both cancel. */
.confirm-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  width: min(440px, 90vw);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.confirm-modal__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--fg-strong);
}
.confirm-modal__message {
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.55;
  color: var(--fg);
}
.world-map__hud {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 3px;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
  box-shadow: var(--shadow-2);
  pointer-events: auto;
}
.world-map__hud-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}
.world-map__hud-btn:hover {
  border-color: var(--rubric);
  color: var(--rubric);
}
.world-map__hud-btn.is-off {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.world-map__roadway-layer {
  position: absolute;
  top: 0; left: 0;
  pointer-events: none;
  overflow: visible;
}
.world-map__roadway {
  fill-opacity: 0.45;
  stroke: none;
}
.world-map__roadway--highway {
  fill: #2f7a3f;   /* verdigris-deep green */
}
.world-map__roadway--road {
  fill: #2a5d8f;   /* indigo-leaning blue */
}
.world-map__roadway--harsh {
  fill: #b22a2a;   /* cinnabar-leaning red — harsh, slow terrain (2.0 d/cell) */
}
.world-map__roadway--urban {
  fill: #6a358a;   /* deep purple — urban / rapid-transit (0 d/cell) */
}

/* ── Planned-route overlay ───────────────────────────────────────
   Bright-blue route polyline drawn from party → destination after
   pathfinding. Two stacked strokes: a wider semi-transparent
   underline (shadow) under a brighter dashed line that anime.js
   marches forward (chase effect).                                  */
.world-map__route-layer {
  position: absolute;
  top: 0; left: 0;
  pointer-events: none;
  overflow: visible;
}
.world-map__route-line {
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.world-map__route-line--shadow {
  stroke: #3a85ff;
  stroke-width: 6;
  opacity: 0.28;
  filter: blur(2px);
}
.world-map__route-line--chase {
  stroke: #4ea7ff;
  stroke-width: 4;
  stroke-dasharray: 12 24;
  filter: drop-shadow(0 0 6px #3a85ff)
          drop-shadow(0 0 12px rgba(58, 133, 255, 0.7));
}

/* Destination flag-objective marker — fills the entire destination
   cell (width/height passed inline from cellSize). The 4-directional
   drop-shadow stack fakes a 1.5px black stroke around the silhouette
   (mask-image doesn't support real strokes), then the blue glow and
   soft drop-shadow finish the lift. */
.world-map__destination {
  position: absolute;
  transform: translate(-50%, -50%);   /* centre on cell */
  pointer-events: none;
}
.world-map__destination-icon {
  display: block;
  width: 100%;
  height: 100%;
  background-color: #3a85ff;
  -webkit-mask-image: url(/assets/game-icons.net/delapouite/position-marker.svg);
          mask-image: url(/assets/game-icons.net/delapouite/position-marker.svg);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  filter: drop-shadow( 1.5px 0     0 #000)
          drop-shadow(-1.5px 0     0 #000)
          drop-shadow(0      1.5px 0 #000)
          drop-shadow(0     -1.5px 0 #000)
          drop-shadow(0 0 8px #3a85ff)
          drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}

/* Waypoint markers — numbered blue dots showing route order. Smaller
   than the destination flag (intermediate stops, not the endpoint). */
.world-map__waypoint {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #3a85ff;
  border: 2px solid #fff;
  box-shadow: 0 0 6px rgba(58, 133, 255, 0.65),
              0 1px 2px rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  cursor: context-menu;
}
.world-map__waypoint-number {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  color: #ffffff;
  line-height: 1;
  user-select: none;
}

/* ── Travel-plan proposal UI ─────────────────────────────────────
   When a proposal exists, a banner stretches across the top of the
   world map showing the vote bar + GM-only action buttons. A
   smaller "Propose Travel" button sits in the same band when no
   proposal is active but a route is on the map. */
.world-map__propose-cta {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  padding: 7px 18px;
  background: #3a85ff;
  color: #ffffff;
  border: 1px solid #2a6fdc;
  border-radius: 18px;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  box-shadow: 0 0 8px rgba(58, 133, 255, 0.5);
  cursor: pointer;
  pointer-events: auto;
}
.world-map__propose-cta:hover {
  background: #4d92ff;
}
.world-map__proposal {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  width: min(720px, calc(100% - 32px));
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  padding: 8px 14px;
  pointer-events: auto;
  font-family: var(--font-body);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.world-map__proposal-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}
.world-map__proposal-title {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
}
.world-map__proposal-meta {
  font-size: 12px;
  color: var(--fg-muted);
}
.world-map__vote-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.world-map__vote-empty {
  font-style: italic;
  color: var(--fg-muted);
  font-size: 12px;
}
.world-map__vote-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
  border: 1px solid var(--border);
  background: var(--surface, var(--paper));
  color: var(--fg);
}
.world-map__vote-chip.is-yes {
  background: #2f7a3f;
  border-color: #2f7a3f;
  color: #ffffff;
}
.world-map__vote-chip.is-no {
  background: var(--rubric);
  border-color: var(--rubric);
  color: #ffffff;
}
.world-map__vote-chip.is-pending {
  color: var(--fg-muted);
  border-style: dashed;
}
.world-map__vote-cancel-flag {
  display: inline-flex;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ffffff;
  color: var(--rubric);
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 10px;
  line-height: 1;
}
.world-map__proposal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.world-map__btn {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  padding: 5px 14px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
  cursor: pointer;
}
.world-map__btn:hover { border-color: var(--rubric); }
.world-map__btn--yes {
  background: #2f7a3f;
  border-color: #2f7a3f;
  color: #ffffff;
}
.world-map__btn--no {
  background: var(--rubric);
  border-color: var(--rubric);
  color: #ffffff;
}
.world-map__btn--ghost {
  background: transparent;
  color: var(--fg-muted);
  border-style: dashed;
}
.world-map__btn--approve {
  background: #3a85ff;
  border-color: #2a6fdc;
  color: #ffffff;
  font-weight: 600;
}
.world-map__btn--approve.is-disabled,
.world-map__btn--approve[disabled] {
  background: var(--surface, #ddd);
  border-color: var(--border);
  color: var(--fg-muted);
  cursor: not-allowed;
}
.world-map__btn--cancel {
  background: var(--rubric);
  border-color: var(--rubric);
  color: #ffffff;
}
.world-map__proposal-cancel-note {
  margin-left: 8px;
  font-size: 11px;
  font-style: italic;
  color: var(--rubric);
}
.world-map__proposal-pause-note {
  padding: 4px 10px;
  background: var(--rubric);
  color: var(--paper);
  font-size: 12px;
  border-radius: 4px;
  margin: 2px 0;
}

/* Smooth slide between cells while traveling so the per-second token
   moves visually glide instead of teleporting. */
.world-map__party.is-traveling {
  transition: left 900ms linear, top 900ms linear;
}

.gb-tab-btn.is-disabled,
.gb-tab-btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* GM-only Downtime trigger in the Game Board panel header. Sepia
   fill to stand it apart from the cinnabar/ink tabs so the GM doesn't
   click it by accident. */
.gb-tab-btn--downtime {
  background: var(--sepia, #8a6a3f);
  color: var(--paper);
  border-color: var(--sepia, #8a6a3f);
  margin-right: 4px;
}
.gb-tab-btn--downtime:hover {
  background: var(--rubric);
  border-color: var(--rubric);
  color: var(--paper);
}

/* ── Downtime mode picker (GM quick-choose Rural vs Urban) ─────── */
.downtime-mode-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  width: min(520px, 90vw);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Bard Mnemonic Descriptions — log section + bespoke modals. */
.cs-log-mnemonics-cap {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg-muted);
  margin-left: 12px;
  letter-spacing: 0.05em;
}
.cs-log-mnemonics-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cs-log-mnemonics-entry {
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--indigo);
  border-radius: 3px;
  position: relative;
}
.cs-log-mnemonics-entry-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.cs-log-mnemonics-name {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--fg-strong);
  letter-spacing: 0.04em;
}
.cs-log-mnemonics-source {
  font-family: var(--font-body-italic);
  font-size: 12px;
  color: var(--fg-muted);
}
.cs-log-mnemonics-desc {
  margin-top: 4px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  white-space: pre-wrap;
}
.cs-log-mnemonics-link {
  margin-top: 4px;
  font-size: 12px;
}
.cs-log-mnemonics-link a {
  color: var(--rubric);
  text-decoration: none;
}
.cs-log-mnemonics-link a:hover {
  text-decoration: underline;
}
.cs-log-mnemonics-confirm {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 6px 10px;
  background: color-mix(in srgb, var(--rubric) 8%, var(--surface));
  border: 1px solid var(--rubric);
  border-radius: 3px;
  font-size: 12px;
}
.cs-log-mnemonics-confirm-btn {
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: var(--paper);
  cursor: pointer;
  font-size: 12px;
  border-radius: 3px;
}
.cs-log-mnemonics-confirm-btn--yes {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric-deep);
}
.cs-log-mnemonics-confirm-btn--no:hover { background: var(--surface); }

/* MnemonicImitationModal — bespoke activation modal for Bard L1
   Mnemonic Imitation. Tabbed target picker + name/description/link. */
.mnemonic-modal,
.mnemonic-forget-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  width: min(560px, 90vw);
  padding: 22px 26px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.mnemonic-modal__title,
.mnemonic-forget-modal__title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
}
.mnemonic-modal__tagline,
.mnemonic-forget-modal__tagline {
  font-family: var(--font-body-italic);
  font-size: 13px;
  color: var(--fg-muted);
  margin-top: -6px;
}
.mnemonic-modal__tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
}
.mnemonic-modal__tab {
  flex: 1;
  padding: 8px 6px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--fg-muted);
  text-transform: uppercase;
}
.mnemonic-modal__tab:hover { color: var(--fg); }
.mnemonic-modal__tab.is-active {
  color: var(--fg-strong);
  border-bottom-color: var(--indigo);
}
.mnemonic-modal__picker {
  display: flex;
  gap: 6px;
}
.mnemonic-modal__picker-select {
  flex: 1;
  padding: 6px 8px;
  font-family: var(--font-body);
  font-size: 13px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 3px;
}
.mnemonic-modal__picker-empty {
  flex: 1;
  padding: 6px 8px;
  font-family: var(--font-body-italic);
  font-size: 12px;
  color: var(--fg-muted);
}
.mnemonic-modal__field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.mnemonic-modal__field-label {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.mnemonic-modal__optional {
  font-family: var(--font-body-italic);
  text-transform: none;
  font-weight: normal;
  font-size: 11px;
}
.mnemonic-modal__input,
.mnemonic-modal__textarea {
  padding: 8px 10px;
  font-family: var(--font-body);
  font-size: 13px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 3px;
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
}
.mnemonic-modal__footer,
.mnemonic-forget-modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 6px;
}
.mnemonic-forget-modal__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 50vh;
  overflow-y: auto;
}
.mnemonic-forget-modal__entry { margin: 0; }
.mnemonic-forget-modal__forget-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--rubric);
  border-radius: 3px;
  cursor: pointer;
  text-align: left;
}
.mnemonic-forget-modal__forget-btn:hover {
  background: color-mix(in srgb, var(--rubric) 6%, var(--surface));
}
.mnemonic-forget-modal__forget-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg-strong);
}
.mnemonic-forget-modal__forget-desc {
  margin-top: 3px;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
}

/* GM Attention panel — line items aggregated from peer state for
   surfacing long-running player effects to the GM (Pursuit, etc.). */
.gm-attention {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 4px 0;
}
.gm-attention__row {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 10px;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--rubric);
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
  text-align: left;
  transition: background var(--dur-1, 120ms) ease, border-color var(--dur-1, 120ms) ease;
}
.gm-attention__row:hover {
  background: color-mix(in srgb, var(--rubric) 6%, var(--surface));
  border-left-color: var(--rubric-deep);
}
.gm-attention__source {
  font-family: var(--font-display);
  color: var(--fg-strong);
  letter-spacing: 0.04em;
}
.gm-attention__label {
  color: var(--fg-muted);
  font-style: italic;
}
.gm-attention__sep {
  color: var(--fg-muted);
  opacity: 0.6;
}
.gm-attention__target {
  font-family: var(--font-display);
  color: var(--rubric);
}
.gm-attention__detail {
  margin-left: auto;
  font-family: var(--font-body-italic);
  font-size: 12px;
  color: var(--fg-muted);
}
/* Stockpile drag-gate confirm modal (Magister Stockpile L1). */
.stockpile-gate-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  width: min(460px, 90vw);
  padding: 22px 26px 18px;
}
.stockpile-gate-modal__title {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
  margin-bottom: 12px;
}
.stockpile-gate-modal__body {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.55;
  color: var(--fg);
}
.stockpile-gate-modal__wp {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--fg-muted);
}
.stockpile-gate-modal__warn {
  color: var(--rubric);
}
.stockpile-gate-modal__footer {
  margin-top: 16px;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.downtime-mode-modal__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--fg-strong);
}
.downtime-mode-modal__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.downtime-mode-modal__choice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 12px 16px;
  border: 1px solid var(--border);
  background: var(--surface, var(--paper));
  border-radius: 4px;
  cursor: pointer;
  text-align: left;
  transition: border-color 100ms ease, background 100ms ease;
}
.downtime-mode-modal__choice:hover {
  border-color: var(--rubric);
  background: var(--paper);
}
.downtime-mode-modal__choice-label {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
}
.downtime-mode-modal__choice-desc {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg-muted);
  line-height: 1.5;
}

/* ── Downtime modal (non-closeable while session is active) ────── */
.downtime-modal-overlay {
  /* Inherits .modal-overlay positioning + dim. Lifted z-index so it
     covers any other modal stack that happens to be open. */
  z-index: 2000;
}
/* A call/downtime pull a player must resolve has to sit ABOVE the downtime
   ledger (2000) — otherwise the ledger covers the resolution window and the
   player can't pull. Stays below the flourish/tweaks overlays (9000/9500). */
.modal-overlay--above-downtime {
  z-index: 2100;
}
.downtime-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3);
  width: min(900px, 92vw);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.downtime-modal__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--fg-strong);
}
.downtime-modal__body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  overflow-y: auto;
}
@media (max-width: 720px) {
  .downtime-modal__body { grid-template-columns: 1fr; }
}
.downtime-modal__section { display: flex; flex-direction: column; gap: 8px; }
.downtime-modal__section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
  padding-bottom: 4px;
}
.downtime-modal__section-head h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
}
.downtime-modal__hint {
  font-size: 11px;
  font-style: italic;
  color: var(--fg-muted);
}
.downtime-modal__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.downtime-modal__action {
  padding: 8px 10px;
  background: var(--surface, var(--paper));
  border: 1px solid var(--border);
  border-radius: 3px;
}
.downtime-modal__action-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg-strong);
}
.downtime-modal__action-effect {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--fg);
  line-height: 1.45;
  margin-top: 2px;
}
.downtime-modal__footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
}
.downtime-modal__footer-spacer { flex: 1; }
.downtime-modal__waiting {
  font-family: var(--font-body-italic, var(--font-body));
  font-size: 12px;
  color: var(--fg-muted);
}

/* Action interactivity (claim / personal toggle) */
.downtime-modal__action-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.downtime-modal__action.is-taken {
  opacity: 0.5;
}
.downtime-modal__action.is-mine {
  border-color: var(--rubric);
  background: var(--paper);
}
.downtime-modal__action-claimer {
  font-size: 11px;
  font-style: italic;
  color: var(--rubric);
}
.downtime-modal__action-pull {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: normal;
  color: var(--fg-muted);
  font-style: italic;
}
/* Difficulty chip: read-only number for players, ± steppers for GM */
.downtime-modal__action-diff {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  font-family: var(--font-body);
  font-size: 12px;
  background: var(--paper);
}
.downtime-modal__diff-label {
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.downtime-modal__diff-val {
  font-weight: 700;
  min-width: 18px;
  text-align: center;
  color: var(--ink);
}
.downtime-modal__diff-step {
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.downtime-modal__diff-step:hover { border-color: var(--rubric); color: var(--rubric); }
.downtime-modal__diff-step:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* Sub-choice picker for class-granted downtime actions that branch
   (Tend to the Flame, Adherent, We Serve, etc.). Renders inline
   below the action card once that action is claimed. */
.downtime-modal__subchoices {
  margin-top: 8px;
  padding: 8px 10px;
  background: rgba(0, 0, 0, 0.03);
  border-left: 2px solid var(--rubric);
  border-radius: 0 3px 3px 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.downtime-modal__subchoices-label {
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--fg-strong);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.downtime-modal__subchoice {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--paper);
  cursor: pointer;
  transition: border-color 100ms ease;
}
.downtime-modal__subchoice:hover { border-color: var(--rubric); }
.downtime-modal__subchoice.is-picked {
  border-color: var(--rubric);
  background: var(--paper);
  box-shadow: 0 0 0 1px var(--rubric) inset;
}
.downtime-modal__subchoice input[type="radio"] {
  margin-top: 3px;
}
.downtime-modal__subchoice-name {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--fg-strong);
}
.downtime-modal__subchoice-effect {
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg);
  margin-top: 2px;
  line-height: 1.4;
}
.downtime-modal__subchoice-chosen {
  margin-top: 6px;
  padding: 4px 8px;
  background: var(--paper);
  border: 1px solid var(--rubric);
  border-radius: 3px;
  font-family: var(--font-body);
  font-size: 12px;
  font-style: italic;
  color: var(--rubric);
}
.downtime-modal__btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg);
  border-radius: 12px;
  cursor: pointer;
}
.downtime-modal__btn:hover { border-color: var(--rubric); color: var(--rubric); }
.downtime-modal__btn--claim {
  background: var(--rubric);
  border-color: var(--rubric);
  color: var(--paper);
}
.downtime-modal__btn--claim:hover { background: #ad3b3b; }
.downtime-modal__btn--claim:disabled,
.downtime-modal__btn--claim[disabled] {
  background: var(--surface, #ddd);
  border-color: var(--border);
  color: var(--fg-muted);
  cursor: not-allowed;
}
.downtime-modal__btn--unclaim {
  background: transparent;
  color: var(--fg-muted);
  border-style: dashed;
}
.downtime-modal__check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  cursor: pointer;
  user-select: none;
}

/* Player roster strip at the bottom of the picker phase */
.downtime-modal__roster {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 18px 0;
  border-top: 1px solid var(--border);
}
.downtime-modal__chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  font-size: 11px;
  font-family: var(--font-body);
  border-radius: 12px;
  border: 1px dashed var(--border);
  color: var(--fg-muted);
  background: transparent;
}
.downtime-modal__chip.is-active {
  border-style: solid;
  color: var(--fg);
  background: var(--surface, var(--paper));
}
.downtime-modal__chip.is-submitted {
  background: #2f7a3f;
  border-color: #2f7a3f;
  color: #ffffff;
}
.downtime-modal__chip-tick { margin-left: 2px; font-weight: 700; }

/* Ledger view (status='resolved') */
.downtime-modal__ledger {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.downtime-modal__ledger-row {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) 3fr;
  gap: 14px;
  padding: 10px 12px;
  background: var(--surface, var(--paper));
  border: 1px solid var(--border);
  border-radius: 4px;
}
.downtime-modal__ledger-name {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
}
.downtime-modal__ledger-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.downtime-modal__ledger-line {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--fg);
}
.downtime-modal__ledger-label {
  font-weight: 600;
  color: var(--fg-muted);
  margin-right: 4px;
}
.downtime-modal__ledger-pending {
  color: var(--sepia, #8a6a3f);
}
.downtime-modal__ledger-skip {
  color: var(--fg-muted);
}
.downtime-modal__ledger-success {
  color: var(--verdigris-deep, #2f7a3f);
  font-weight: 600;
}
.downtime-modal__ledger-fail {
  color: var(--rubric);
  font-weight: 600;
}
.downtime-modal__ledger-critfail {
  color: var(--rubric);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.downtime-modal__ledger-bens {
  margin-top: 4px;
  padding: 6px 8px;
  background: #2f7a3f;
  color: #ffffff;
  border-radius: 3px;
  font-size: 12px;
}

/* Warmaster (Warrior T3) self-apply row in the resolved ledger —
   inline picker + Apply button rendered on the Warrior's own row only.
   Styled as a minor ledger-line variant. */
.downtime-modal__warmaster-apply { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.downtime-modal__warmaster-select {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 3px 6px;
  background: var(--paper-aged);
  border: 1px solid var(--sepia-soft, var(--sepia));
  border-radius: 3px;
  color: var(--ink);
}
.downtime-modal__warmaster-apply-btn {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.05em;
  padding: 4px 12px;
  background: var(--verdigris-deep);
  color: var(--paper);
  border: 1px solid var(--verdigris-deep);
  border-radius: 3px;
  cursor: pointer;
}
.downtime-modal__warmaster-apply-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ── Paint mode HUD strip ─────────────────────────────────────────
   GM-only control row beneath the standard HUD. Selects which kind
   of cell the next drag-stroke paints — or 'Off' to resume normal
   pan / token-placement behaviour. */
.world-map__paint-strip {
  position: absolute;
  top: 44px;
  right: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 3px;
  font-family: var(--font-body);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
  box-shadow: var(--shadow-2);
  pointer-events: auto;
}
.world-map__paint-label {
  font-family: var(--font-display, var(--font-body));
  color: var(--fg);
  margin-right: 2px;
}
.world-map__paint-btn {
  font-family: var(--font-body);
  font-size: 11px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}
.world-map__paint-btn:hover {
  border-color: var(--rubric);
  color: var(--rubric);
}
.world-map__paint-btn.is-active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.world-map__paint-btn--highway.is-active {
  background: #2f7a3f;
  border-color: #2f7a3f;
  color: var(--paper);
}
.world-map__paint-btn--road.is-active {
  background: #2a5d8f;
  border-color: #2a5d8f;
  color: var(--paper);
}
.world-map__paint-btn--erase.is-active {
  background: var(--rubric);
  border-color: var(--rubric);
  color: var(--paper);
}
.world-map__paint-btn--harsh.is-active {
  background: #b22a2a;
  border-color: #b22a2a;
  color: var(--paper);
}
.world-map__paint-btn--urban.is-active {
  background: #6a358a;
  border-color: #6a358a;
  color: var(--paper);
}

/* Live preview of the cells being painted during a drag stroke. The
   actual synced overlay replaces this once the stroke commits. */
.world-map__stroke-layer {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  overflow: visible;
}
.world-map__stroke-cell {
  fill-opacity: 0.6;
  stroke: none;
}
.world-map__stroke-cell--highway { fill: #2f7a3f; }
.world-map__stroke-cell--road    { fill: #2a5d8f; }
.world-map__stroke-cell--harsh   { fill: #b22a2a; }
.world-map__stroke-cell--urban   { fill: #6a358a; }
.world-map__stroke-cell--erase   { fill: var(--rubric); }

/* ── Game-board panel tabs ────────────────────────────────────────
   Two-tab strip ('Board' / 'World') rendered into the
   DraggablePanel's headerRight slot. Compact inline pills so they
   coexist with the existing panel chrome (drag handle, expand
   button) without crowding the title. */
.gb-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
}
.gb-tabs--header { padding: 0; }
/* Travel-time pill rendered when a world-map destination is selected.
   Absolutely centred in the panel header (the panel-handle is now
   position:relative so this anchors to the whole header rather than
   to the headerRight slot). Glowing blue to match the route polyline
   and destination marker. */
.gb-route-pill {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  padding: 4px 14px;
  border-radius: 14px;
  background: #3a85ff;
  color: #ffffff;
  border: 1px solid #2a6fdc;
  box-shadow: 0 0 6px rgba(58, 133, 255, 0.55);
  line-height: 1.3;
  white-space: nowrap;
  user-select: none;
  pointer-events: auto;   /* keep title tooltip working over the drag handle */
}
.gb-tab-btn {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 10px;
  border: 1px solid var(--border);
  background: var(--paper);
  color: var(--fg-muted);
  cursor: pointer;
  border-radius: 2px;
  line-height: 1.4;
}
.gb-tab-btn:hover { color: var(--fg-strong); border-color: var(--rubric); }
.gb-tab-btn.is-active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Folio-style sheet background — paints the parchment artwork
   used by class-tree.html behind the sheet content. The image
   stretches to fill the bounded content box so the painted frame
   on the artwork sits at the sheet's outer edges. NOTE: rule only
   targets the root element (`#sheet-content`), never its children
   — child sections own their own layouts and would break under
   extra padding or backgrounds. */
.sheet-content--folio {
  background-image: url("/assets/farrago-page-portrait.webp");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-position: center;
  box-shadow:
    0 1px 1px rgba(67, 41, 23, 0.18),
    0 14px 40px rgba(67, 41, 23, 0.30);
}
/* Inset wrapper around the panels — sits just inside the painted
   parchment frame. 3px margin + 6px padding keeps the panel borders
   from touching the artwork edge while still letting the content
   read at its existing widths. */
.cs-sheet-inset {
  margin: 3px;
  padding: 6px;
}
.sheet-inventory-shell {
  max-width: 1060px;
  margin: 0 auto;
  padding: 20px 24px 80px;
}
.sheet-inventory-shell--mobile { padding: 14px 14px 80px; }

/* Sheet tab buttons */
.sheet-tab.is-active {
  border-bottom: 2px solid var(--ink);
  color: var(--fg-strong);
}
.sheet-tab.is-inactive {
  border-bottom: 2px solid transparent;
  color: var(--fg-muted);
}

.cs-fit-label { position: relative; }
/* Combat stat label fitter */
.cs-combat-stat-fit-label {
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 2px;
}
.cs-combat-stat-fit-paren {
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.7;
}
.cs-combat-stat-input {
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1;
  width: 100%;
  text-align: center;
}
.cs-combat-stat-input--accent { color: var(--stat-accent); }
.cs-fit-label-help { cursor: help; }

/* Equip slots */
.inv-equip-slot-tint--7pct { opacity: 0.07; }
.inv-equip-slot-tint--6pct { opacity: 0.06; }
.inv-equip-slot-label--small { font-size: 8px; }
.inv-equip-slot-label--inactive { color: var(--fg-muted); }
.inv-equip-slot-sub--7px {
  font-size: 7px;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
}

/* Section header label color (when no accent) */
.cs-section-title--default { color: var(--fg-muted); }

/* Inventory grid item border */
.inv-grid-item--ferry {
  border: 2px solid var(--sepia-soft);
  border-radius: 12px;
  box-sizing: border-box;
  z-index: 1;
  min-width: 0;
  overflow: hidden;
  grid-column: var(--grid-col-start) / span var(--grid-col-span);
  grid-row: var(--grid-row);
}

/* Inventory slot grid container — cols/rows wired through CSS vars. */
.inv-slot-grid {
  display: grid;
  grid-template-columns: repeat(var(--slot-cols, 1), minmax(0, 1fr));
  grid-template-rows: repeat(var(--slot-rows, 1), var(--slot-cell-h, 28px));
  gap: 2px;
  padding: 2px;
}

/* Background cell for empty slot grid spaces */
.inv-slot-bg {
  border: 1px dashed transparent;
  grid-column: var(--grid-col);
  grid-row: var(--grid-row);
}
.inv-slot-bg--usable { border-color: var(--border-soft); }
.inv-slot-bg--unusable { background: rgba(0, 0, 0, 0.18); }

/* Drop preview overlay in the slot grid */
.inv-slot-preview {
  z-index: 3;
  pointer-events: none;
  grid-column: var(--grid-col-start) / span var(--grid-col-span);
  grid-row: var(--grid-row);
  border-width: 2px;
  border-style: solid;
}
.inv-slot-preview--valid {
  background: rgba(0, 150, 130, 0.22);
  border-color: var(--verdigris);
}
.inv-slot-preview--invalid {
  background: rgba(160, 40, 40, 0.22);
  border-color: var(--rubric);
}
.inv-section-cap-text--over { color: var(--rubric); }
.inv-section-cap-text--ok { color: var(--fg-muted); }

/* Gold input */
.inv-gold-input {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--fg-strong);
  width: 44px;
  text-align: right;
}

/* Generic select / number input shared across modals */
.app-select {
  font-family: var(--font-body);
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  width: 100%;
}
.app-select--md { font-size: 13px; }
.app-number {
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 2px;
  width: 100%;
}

/* ============================================================
   Primitives — Button, Tag, PlayingCard, Avatar, StatBlock
   ============================================================ */

/* Button primitive */
.prim-btn {
  font-family: var(--font-body);
  font-weight: 500;
  border-radius: 4px;
  border: 1px solid transparent;
  cursor: pointer;
  letter-spacing: 0.02em;
  transition: background var(--dur-1) var(--ease-quill),
              color var(--dur-1) var(--ease-quill),
              transform var(--dur-1) var(--ease-quill),
              border-color var(--dur-1) var(--ease-quill);
  opacity: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.prim-btn:disabled,
.prim-btn[disabled] {
  cursor: not-allowed;
  opacity: 0.45;
}
.prim-btn--sm { font-size: 12px; padding: 4px 10px; }
.prim-btn--md { font-size: 14px; padding: 8px 16px; }
.prim-btn--lg { font-size: 16px; padding: 10px 22px; }
.prim-btn--primary { background: var(--ink-strong); color: var(--paper); border-color: var(--ink-strong); }
.prim-btn--ghost   { background: transparent; color: var(--ink); border-color: var(--ink); }
.prim-btn--subtle  { background: var(--surface); color: var(--ink); border-color: var(--border); }
.prim-btn--danger  { background: var(--rubric); color: var(--paper); border-color: var(--rubric-deep); }
.prim-btn--rubric  { background: transparent; color: var(--rubric); border-color: var(--rubric); }
.prim-btn--indigo  { background: transparent; color: var(--indigo); border-color: var(--indigo); }
.prim-btn--verdigris { background: transparent; color: var(--verdigris-deep); border-color: var(--verdigris); }

/* Tag primitive */
.prim-tag {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  padding: 3px 8px;
  border-radius: 2px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid;
  display: inline-block;
}
.prim-tag--sepia     { color: var(--fg-muted); border-color: var(--border); background: var(--surface); }
.prim-tag--rubric    { color: var(--rubric); border-color: var(--rubric); background: transparent; }
.prim-tag--verdigris { color: var(--verdigris-deep); border-color: var(--verdigris); background: transparent; }
.prim-tag--indigo    { color: var(--indigo); border-color: var(--indigo); background: transparent; }
.prim-tag--ink       { color: var(--paper); border-color: var(--ink); background: var(--ink); }
.prim-tag--paper     { color: var(--ink); border-color: var(--ink); background: var(--paper); }
.prim-tag--xs        { font-size: 9px; }
.prim-tag--sm        { font-size: 12px; }

/* PlayingCard primitive */
.pc {
  border-radius: 6px;
  position: relative;
  font-family: var(--font-display);
  overflow: hidden;
  transition: transform var(--dur-2) var(--ease-quill),
              box-shadow var(--dur-2) var(--ease-quill),
              border-color var(--dur-1) var(--ease-quill);
  box-shadow: var(--shadow-1);
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  transform: translateY(0);
  cursor: default;
}
.pc--clickable { cursor: pointer; }
.pc--xs { width: 28px;  height: 39px; }
.pc--sm { width: 56px;  height: 78px; }
.pc--md { width: 88px;  height: 124px; }
.pc--lg { width: 112px; height: 158px; }
.pc--lifted   { box-shadow: var(--shadow-2); transform: translateY(-8px); }
.pc--selected { box-shadow: var(--shadow-2); transform: translateY(-18px); border-color: var(--rubric); outline: 1px solid var(--rubric); outline-offset: 1px; }
.pc--dimmed   { opacity: 0.55; }
.pc--rubric   { color: var(--rubric); }
.pc--facedown { background: var(--paper-aged); display: flex; align-items: center; justify-content: center; color: var(--rubric); }
.pc--facedown.pc--sm { font-size: 19.6px; }
.pc--facedown.pc--md { font-size: 30.8px; }
.pc--facedown.pc--lg { font-size: 39.2px; }
.pc-face-inner {
  position: absolute;
  inset: 4px;
  border: 1px solid var(--sepia);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: repeating-linear-gradient(45deg, transparent 0 6px, rgba(165, 42, 42, 0.05) 6px 7px);
}
.pc-corner {
  position: absolute;
  line-height: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.pc-corner--tl { top: 5px; left: 7px; }
.pc-corner--br { bottom: 5px; right: 7px; transform: rotate(180deg); }
.pc--sm .pc-corner { font-size: 11px; }
.pc--md .pc-corner { font-size: 16px; }
.pc--lg .pc-corner { font-size: 20px; }
.pc-center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pc--sm .pc-center { font-size: 28px; }
.pc--md .pc-center { font-size: 44px; }
.pc--lg .pc-center { font-size: 56px; }

/* Joker — wild card. Distinguished from regular ranks by an orpiment-
   yellow tint on the corner pips and a bold gold star at center. The
   red joker (id JKR) inherits the rubric tint via .pc--rubric. */
.pc--joker {
  background:
    radial-gradient(circle at 30% 25%, rgba(255, 232, 168, 0.35), transparent 55%),
    var(--paper);
  border-color: var(--orpiment, var(--rubric));
}
.pc--joker .pc-corner { color: var(--orpiment, #c2952a); }
.pc--joker.pc--rubric .pc-corner { color: var(--rubric); }
.pc-center--joker {
  color: var(--orpiment, #c2952a);
  filter: drop-shadow(0 0 4px rgba(194, 149, 42, 0.35));
}
.pc--joker.pc--rubric .pc-center--joker {
  color: var(--rubric);
  filter: drop-shadow(0 0 4px rgba(193, 75, 58, 0.45));
}

/* Avatar — sizing/font via custom properties */
.avatar {
  width: var(--avatar-size, 36px);
  height: var(--avatar-size, 36px);
  min-width: var(--avatar-size, 36px);
  background: var(--avatar-bg, var(--paper-deep));
  font-size: var(--avatar-font, 15px);
}

/* StatBlock accent value */
.stat-block-value--accent { color: var(--stat-accent, var(--fg-strong)); }

/* ── Map canvas (tactical board surface) ─────────────────────────────
   Wood chess-board + cut-in grid. World-coordinate origin (0,0) sits at
   the center of the viewport; pan adjusts a translate offset. Tools
   added in later steps overlay on top of this base. */
/* Game Board panel layout — wraps the MapCanvas plus a footer that
   mirrors the old Play Zone (active call bar + resolution display).
   The canvas takes the available height; the footer sits underneath
   it, outside the SVG dimensions but inside the panel's content
   surface. The flex layout keeps the canvas filling available space
   no matter how tall the footer grows (it stretches to fit the call
   bar plus a resolved-play card row). */
.game-board-stack {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.game-board-stack > .mapcanvas {
  flex: 1 1 auto;
  min-height: 320px;
}
/* Mobile: clamp the mapcanvas to a sensible default height when the
   panel ISN'T expanded. Without this, MapCanvas's ResizeObserver
   locks the SVG to whatever clientHeight was during the expanded
   state, and on collapse the wrapper holds that height (no flex
   parent to shrink it), so the panel stays oversized. Forcing a
   concrete height here makes collapse actually shrink the wrapper —
   ResizeObserver then fires and the SVG re-renders smaller. The
   height is 7 × 44px (mobile baseCellSize) = 308px so the board
   shows roughly 7 vertical squares at the default zoom; pinch can
   zoom in/out within ±25% of that. */
@media (max-width: 767px) {
  .panel-outer:not(.panel-outer--expanded) .game-board-stack > .mapcanvas {
    flex: 0 0 auto;
    height: 308px;
    max-height: 308px;
  }
  /* Hide-colour-picker rule moved below (after the base
     .mapcanvas__color declaration) so source-order doesn't shadow it. */
}
.game-board-footer {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  flex-direction: column;
}
/* Footer's call bar reuses the existing `.app-call-bar` rules but
   needs its bottom border suppressed (the footer sits at the bottom
   of the panel; no separator below it). */
.game-board-footer__call {
  border-bottom: 0 !important;
}
/* Idle placeholder ("No active Call.") — bold, non-italic display
   font so it reads as a deliberate header rather than a faded
   nothing-here state. Overrides the shared `.app-no-call` rule
   that scopes to the legacy Play Zone panel. */
.game-board-footer__call--idle {
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 700;
  color: var(--fg);
}

.mapcanvas {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 320px;
  max-height: 100%;
  /* Wood-surface placeholder: a warm sepia gradient until the real
     texture asset lands. Replace `background` with
     `background-image: url('/assets/wood-tile.webp');
      background-repeat: repeat;` once the file is available. */
  background:
    radial-gradient(ellipse at 30% 25%, rgba(255, 240, 210, 0.10), transparent 60%),
    radial-gradient(ellipse at 70% 75%, rgba(60, 30, 10, 0.18), transparent 65%),
    linear-gradient(135deg, var(--sepia-soft, #c89a5e) 0%, var(--sepia, #8a5a2a) 100%);
  border: 1px solid var(--ink);
  border-radius: 4px;
  overflow: hidden;
  user-select: none;
  cursor: grab;
}
/* Cursor varies by active tool. Pan is no longer a discrete tool —
   it's a global right-click / middle-click drag gesture. */
.mapcanvas--tool-select   { cursor: default; }
.mapcanvas--tool-token    { cursor: copy; }

/* Drop-target highlight when a foe row is being dragged over the
   canvas. A subtle parchment-tinted glow inside the border. */
.mapcanvas--drop-target {
  box-shadow: inset 0 0 0 3px var(--verdigris, #3a8a3a);
}

/* SortableJS sink behaviour. While an ability/card is dragged over the
   canvas, SortableJS inserts the dragged DOM node into this wrapper to
   show its drop position. Without intervention that node lands in
   normal flow and stretches the wrapper to fit the card's natural
   height. We absolutely-position any Sortable-inserted artefact off-
   screen so the layout stays glued to the parent panel. The drop
   revert in onAdd then restores the original to the source, no DOM
   left behind. */
.mapcanvas > .sortable-ghost,
.mapcanvas > .sortable-chosen,
.mapcanvas > .sortable-drag,
.mapcanvas > .sortable-fallback {
  position: absolute !important;
  left: -9999px !important;
  top: -9999px !important;
  width: 1px !important;
  height: 1px !important;
  pointer-events: none !important;
}

/* While a sheet ability/facet card is mid-drag, suppress text
   selection document-wide — forceFallback drags are pointer-driven
   and would otherwise highlight everything between the card and the
   cursor. The class is added in the Sortable onStart, removed onEnd. */
body.sheet-card-dragging,
body.sheet-card-dragging * {
  user-select: none !important;
  -webkit-user-select: none !important;
}

/* Batch placement mode: cursor changes to crosshair so the user knows
   each click drops the next foe; the active tool's cursor is
   suppressed. */
.mapcanvas--placement-mode { cursor: crosshair !important; }

/* Placement banner — sits above the toolbar at the top of the
   canvas. Pointer-events: none on the wrapper; only the cancel
   button receives clicks. */
.mapcanvas__placement-banner {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 14px;
  background: var(--ink);
  color: var(--paper);
  border-radius: 3px;
  box-shadow: var(--shadow-3);
  font-family: var(--font-body);
  font-size: 13px;
  pointer-events: auto;
}
.mapcanvas__placement-text { letter-spacing: 0.02em; }
.mapcanvas__placement-cancel {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 4px 10px;
  border: 1px solid var(--paper);
  background: transparent;
  color: var(--paper);
  cursor: pointer;
  border-radius: 2px;
}
.mapcanvas__placement-cancel:hover {
  background: var(--paper);
  color: var(--ink);
}

/* Foe rows that are draggable — a small grab cursor on hover so users
   discover the affordance without an explicit hint. */
.init-foe-row.is-draggable,
.foe-item-header.is-draggable,
.foe-active-card.is-draggable {
  cursor: grab;
}
.init-foe-row.is-draggable:active,
.foe-item-header.is-draggable:active,
.foe-active-card.is-draggable:active {
  cursor: grabbing;
}

/* Place-foes button row in the InitiativeTracker panel. */
.init-place-foes-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.init-place-foes-count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--rubric);
}
.mapcanvas--tool-line,
.mapcanvas--tool-rect,
.mapcanvas--tool-circle,
.mapcanvas--tool-freehand { cursor: crosshair; }
.mapcanvas--tool-paint    { cursor: cell; }
.mapcanvas--tool-erase    { cursor: not-allowed; }

.mapcanvas__svg {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}
.mapcanvas__toolbar {
  position: absolute;
  top: 8px;
  left: 8px;
  right: 8px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  pointer-events: none; /* let pan/draw start under the toolbar's gap */
}
.mapcanvas__tool-group {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: rgba(255, 250, 240, 0.92);
  border: 1px solid var(--ink);
  border-radius: 3px;
  box-shadow: var(--shadow-2);
  pointer-events: auto;
}
.mapcanvas__btn {
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  border-radius: 2px;
  font-size: 16px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  transition: background var(--dur-1, 120ms), color var(--dur-1, 120ms);
}
.mapcanvas__btn:hover { background: var(--ink); color: var(--paper); }
.mapcanvas__btn--active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.mapcanvas__btn--small {
  width: 26px;
  height: 26px;
  font-size: 14px;
}
.mapcanvas__btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.mapcanvas__btn:disabled:hover {
  background: transparent;
  color: var(--ink);
}
.mapcanvas__color {
  position: relative;
  display: inline-flex;
  width: 30px;
  height: 30px;
  cursor: pointer;
}
.mapcanvas__color input[type="color"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  padding: 0;
  background: transparent;
  cursor: pointer;
  opacity: 0; /* hide native control; show our swatch on top */
}
.mapcanvas__color-swatch {
  position: absolute;
  inset: 0;
  border: 1px solid var(--ink);
  border-radius: 2px;
  background: var(--swatch, #888);
  pointer-events: none;
}
/* Mobile: hide the colour picker so the toolbar has room for the
   drawing tools + the recenter button without wrapping. Mobile users
   can still set a colour via the desktop view (the choice persists
   per-user). Lives after the base rule above so source-order keeps
   `display: none` winning over `display: inline-flex`. */
@media (max-width: 767px) {
  .mapcanvas__color { display: none; }
}
/* GM-only wind setpoint. The slider rides the toolbar; the value is
   a hidden state field on the map that drives the tree-stamp sway and
   wisp animation for everyone. Compact so it sits beside the existing
   square buttons without dominating. */
.mapcanvas__wind {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 6px;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: var(--surface);
}
.mapcanvas__wind-icon {
  font-size: 13px;
  color: var(--fg-muted);
  line-height: 1;
  cursor: pointer;
  user-select: none;
  padding: 0 2px;
  border-radius: 2px;
  transition: color var(--dur-1, 120ms) ease, background var(--dur-1, 120ms) ease;
}
.mapcanvas__wind-icon:hover {
  color: var(--ink);
  background: rgba(58, 42, 26, 0.08);
}
.mapcanvas__wind-icon--off {
  color: var(--rubric);
  opacity: 0.85;
}
/* Reset the global input chrome (rounded pill, padding, surface fill)
   for the range slider — those defaults pinch the thumb away from the
   ends, making it impossible to drag fully to 0 or to max. */
.mapcanvas__wind-slider {
  width: 90px;
  height: 18px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  accent-color: var(--ink);
  cursor: ew-resize;
}
.mapcanvas__wind-slider:focus {
  outline: none;
  box-shadow: none;
}
.mapcanvas__empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  pointer-events: none;
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: 16px;
  color: var(--paper);
  opacity: 0.55;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
.mapcanvas__empty-hint {
  font-family: var(--font-body);
  font-style: normal;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.7;
}

/* Surface fill (under the grid). The wood texture lives on the wrapper;
   this rect is here in case we want a tinted vignette later. */
.mc-surface { fill: transparent; }

/* Bounded play area: a slightly-warmer parchment wash inside the
   board's 100×100 cell extent, framed by a fine sepia border. The
   surrounding wood gradient (set on .mapcanvas) shows beyond it. */
.mc-play-area {
  fill: rgba(244, 234, 215, 0.16);
  stroke: rgba(40, 20, 10, 0.55);
  stroke-width: 1.5;
}

/* Grid lines — fine sepia hatching cut into the wood. */
.mc-grid-line {
  fill: none;
  stroke: rgba(40, 20, 10, 0.42);
  stroke-width: 0.75;
}

/* Origin crosshair — faintly visible to orient the player. */
.mc-origin {
  stroke: var(--paper);
  stroke-width: 1;
  opacity: 0.5;
}

/* Painted cells — semi-transparent fill so the grid shows through. */
.mc-painted-cell {
  fill: var(--cell-color, #888);
  fill-opacity: 0.55;
  stroke: var(--cell-color, #888);
  stroke-width: 0.5;
  stroke-opacity: 0.7;
}

/* Drawn shapes */
.mc-drawing {
  fill: var(--fill, none);
  stroke: var(--stroke, currentColor);
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
}
.mc-drawing--filled { fill-opacity: 0.3; }
.mc-drawing--freehand { fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* Stamps */
/* Single chained filter on the parent layer — one pass per shadow
   step for every stamp on the board, instead of one full filter per
   element. Composes cleanly with the per-stamp tint filters (children
   render through their tint first, parent layer's chain runs on the
   composited result).
   The two zero-offset black drop-shadows stack into a soft solid stroke
   around each stamp's silhouette; the third is the warm sepia
   drop-shadow underneath. */
.mc-layer--stamps {
  filter:
    drop-shadow(0 0 0.6px rgba(0, 0, 0, 0.9))
    drop-shadow(0 0 0.6px rgba(0, 0, 0, 0.9))
    drop-shadow(1px 2px 2px rgba(58, 42, 26, 0.45));
}
.mc-stamp { pointer-events: none; }
/* Tackle dust burst — transient SVG nodes appended imperatively by
   the dust-poof useEffect. anime.js drives their fade + expand;
   these rules just supply the colour, weight, and pointer-events
   isolation. The puffs are warm sepia (kicked-up dirt) with a hint
   of cinnabar to read against the wood; the ring is a softer halo. */
.mc-layer--dust {
  pointer-events: none;
  /* Light blur across the whole layer so each puff reads as hazy
     dust rather than a crisp circle. Layer-level (not per-element)
     keeps the GPU cost cheap and gives consistent softness. */
  filter: blur(1.5px);
}

/* Pale, slightly warm grey — visible against both the wood gradient
   and any lighter painted cells. Higher alpha than before so the
   trail isn't lost on the brown background. */
.mc-dust-ring {
  fill: rgba(232, 226, 218, 0.20);
  stroke: rgba(232, 226, 218, 0.55);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}
.mc-dust-puff {
  fill: rgba(228, 222, 214, 0.75);
  stroke: rgba(180, 175, 168, 0.40);
  stroke-width: 0.75;
  vector-effect: non-scaling-stroke;
}

/* Combat-text layer — sibling to the dust layer but without the
   blur filter, so the "Tackle!" word stays crisp. */
.mc-layer--combat-text {
  pointer-events: none;
}
/* Floating "Tackle!" combat text. Display font + heavy ink stroke
   makes it readable on the wood gradient AND over the dust cloud.
   paint-order: stroke fill draws the dark outline beneath the
   light fill so legibility holds against any background. */
.mc-tackle-text {
  font-family: var(--font-display, 'IM Fell English SC', Georgia, serif);
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 0.04em;
  fill: var(--paper, #f4ead7);
  stroke: var(--ink, #3a2a1a);
  stroke-width: 4;
  paint-order: stroke fill;
  pointer-events: none;
}
/* Dodge floater — same scrolling-combat-text treatment, verdigris
   outline so an evasion reads differently from a tackle impact. */
.mc-tackle-text--dodge {
  stroke: var(--verdigris-deep, #1a4a3a);
}

/* Phase-specific tints. Impact sparks read whiter (collision flash
   tone). Trail particles a touch warmer. Settling cloud is neutral
   pale grey with a slight haze. */
.mc-dust-ring--impact {
  stroke: rgba(245, 240, 230, 0.85);
  stroke-width: 2.5;
  fill: none;
}
.mc-dust-puff--spark {
  fill: rgba(245, 240, 230, 0.90);
  stroke: none;
}
.mc-dust-puff--trail {
  fill: rgba(225, 218, 208, 0.65);
}
.mc-dust-ring--settle {
  fill: rgba(225, 220, 212, 0.18);
  stroke: rgba(210, 204, 195, 0.45);
}
.mc-dust-puff--settle {
  fill: rgba(228, 222, 214, 0.65);
}

/* Wind wisps — atmospheric overlay. Fixed to the SVG viewport (not the
   map's panning group), pointer-events off so they never block clicks.
   No blend-mode: SVG mix-blend-modes need an isolated stacking context
   to behave predictably and the trade-off in visibility wasn't worth
   it; per-path opacity is enough. */
.mc-layer--wisps {
  pointer-events: none;
}
.mc-wisp {
  /* Soft blur so wisps read as drifting mist rather than crisp white
     pen-strokes. Tiny radius keeps them recognizable as streaks. */
  filter: blur(0.6px);
}

/* Tree-stamp sway target. Anime.js animates `transform: rotate(...)`
   on this <g>; transform-box: fill-box anchors the rotation origin to
   the element's own bounding box, and 50% 100% puts the pivot at the
   bottom-center of the stamp so the trunk stays planted while the
   crown sways. will-change: transform hints the compositor to keep
   the layer warm. */
.mc-stamp-sway {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  will-change: transform;
}
.mc-stamp-ghost { pointer-events: none; opacity: 0.7; }
.mc-stamp-ghost__halo {
  fill: rgba(255, 240, 200, 0.18);
  stroke: var(--cinnabar, #c14b3a);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
  vector-effect: non-scaling-stroke;
}
.mc-stamp-ghost__image { mix-blend-mode: multiply; }
.mapcanvas--stamp-placement { cursor: copy; }

/* AOE templates — distinguished from drawn shapes by a dashed stroke
   and lower opacity so they read as transient overlays. */
.mc-aoe {
  fill: var(--aoe-color, #c44);
  fill-opacity: 0.18;
  stroke: var(--aoe-color, #c44);
  stroke-width: 2;
  stroke-dasharray: 6 4;
  vector-effect: non-scaling-stroke;
}
/* AOE templates in select mode: hover affords a grab cursor; selected
   templates render a brighter outline so the user can confirm which
   one they're about to translate or rotate. */
.mc-layer--aoe.mc-layer--select-target .mc-aoe-group { cursor: grab; }
.mc-layer--aoe.mc-layer--select-target .mc-aoe-group:active { cursor: grabbing; }
.mc-aoe-group--selected .mc-aoe {
  fill-opacity: 0.32;
  stroke-width: 3;
  filter: drop-shadow(0 0 6px var(--aoe-color, #c44));
}
.mc-aoe-group--hidden { opacity: 0; }
/* Translate-in-progress preview rendered by DraftLayer — slightly
   muted vs. the committed style so the difference is visible mid-drag. */
/* Token movement trails — chains of dots placed every couple of cells
   along each token's `history`. Current-turn dots ride a slow crawl
   animation (anime.js scale-bump in stagger sequence) so the eye
   reads the wave as moving toward the token; last-turn dots are
   static and faded so they read as a secondary "yesterday's path"
   overlay. Older history is preserved on the token (audit log) but
   not rendered. */
.mc-trail-dot {
  fill: var(--trail-color, #888);
  pointer-events: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}
.mc-trail-dot--current {
  opacity: 0.65;
}
.mc-trail-dot--last {
  opacity: 0.25;
}

/* Floating undo/redo pips next to the most-recently-moved token.
   Owner-or-GM only; click ↶ to step the token back through this
   round's history, ↷ to step forward. Round-bound on the server, so
   undoing into the previous round is silently no-op'd (the disabled
   styling here mirrors the same boundary client-side). */
.mc-token-controls__pip {
  cursor: pointer;
}
.mc-token-controls__pip circle {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.5;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}
.mc-token-controls__pip text {
  fill: var(--ink);
  font-family: var(--font-display);
  font-size: 16px;
  pointer-events: none;
  user-select: none;
}
.mc-token-controls__pip:hover circle {
  fill: var(--ink);
}
.mc-token-controls__pip:hover text {
  fill: var(--paper);
}
.mc-token-controls__pip--disabled {
  cursor: default;
  opacity: 0.35;
}
.mc-token-controls__pip--disabled:hover circle {
  fill: var(--paper);
}
.mc-token-controls__pip--disabled:hover text {
  fill: var(--ink);
}

.mc-aoe-translate-ghost .mc-aoe {
  fill-opacity: 0.32;
  stroke-dasharray: 4 3;
}
/* AOE placement ghost — preview rendered under the cursor while the
   player picks an anchor for an AOE ability. Slightly more opaque fill
   than the committed template, with a steady dash-march so the ghost
   reads as "alive and tracking your cursor." */
.mc-aoe-ghost {
  fill: var(--aoe-color, #c44);
  fill-opacity: 0.28;
  stroke: var(--aoe-color, #c44);
  stroke-width: 2.5;
  stroke-dasharray: 5 4;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  filter: drop-shadow(0 0 6px rgba(193, 75, 58, 0.45));
  animation: mc-aoe-ghost-march 1.6s linear infinite;
}
.mc-aoe-ghost--line {
  fill: none;
  stroke-width: 6;
  stroke-linecap: round;
}
@keyframes mc-aoe-ghost-march {
  to { stroke-dashoffset: -36; }
}
/* Flash (Magister T3) — 15sq range ring rendered around the source
   cell while a teleport is armed. Verdigris glow + dashed border so
   the ring reads as "magic, kinetic, click to commit." */
.mc-flash-range {
  fill: var(--verdigris, #2f8f6b);
  fill-opacity: 0.08;
  stroke: var(--verdigris-deep, #1d6b4d);
  stroke-width: 2.5;
  stroke-dasharray: 6 4;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  filter: drop-shadow(0 0 8px rgba(47, 143, 107, 0.45));
  animation: mc-aoe-ghost-march 1.6s linear infinite;
}

/* AOE placement: cursor sticks to a crosshair like foe-placement, and
   the dedicated banner uses the player's cinnabar accent. */
.mapcanvas--aoe-placement { cursor: crosshair !important; }
.mapcanvas__placement-banner--aoe {
  background: var(--cinnabar, #c14b3a);
  color: var(--paper);
}

/* Tokens — wooden-piece look. The visual is composed of an inlined
   cloth SVG (driven by `--token-color`) plus a PNG ring overlay; see
   TokenGamepiece in mapcanvas.jsx. The label sits above both.
   A soft drop-shadow at rest makes them read as physical pieces
   sitting on the board surface; the ghost copy (during drag) gets a
   longer shadow to suggest it's been lifted. */
.mc-token {
  cursor: grab;
  filter: drop-shadow(0 3px 3px rgba(20, 12, 4, 0.45));
}
.mc-token:active { cursor: grabbing; }
.mc-token--hidden { opacity: 0; }
.mc-token--ghost {
  opacity: 0.92;
  pointer-events: none;
  filter: drop-shadow(0 6px 8px rgba(20, 12, 4, 0.55));
}
.mc-token__label {
  fill: var(--paper);
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 700;
  font-size: 16px;
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}
.mc-token:hover {
  filter: drop-shadow(0 3px 3px rgba(20, 12, 4, 0.45))
          drop-shadow(0 0 6px rgba(255, 240, 200, 0.65));
}
/* Token facing — the arrow rotates to `facing` (degrees clockwise
   from North); the transition eases each shift+wheel 45° snap. */
.mc-token-facing {
  transition: transform var(--dur-2, 240ms) var(--ease-quill, cubic-bezier(0.32, 0.08, 0.24, 1));
  pointer-events: none;
}
.mc-token-facing-arrow {
  fill: var(--token-color, #888);
  stroke: rgba(0, 0, 0, 0.55);
  stroke-width: 1.5;
  stroke-linejoin: round;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}
/* Rear-cell highlight — a token's 3 rear cells (per its facing), shown
   while the token is a target or under the cursor. Faint so it reads
   as a hint. pointer-events:none so it never intercepts token clicks
   or elementFromPoint hit-testing. */
.mc-rear-cells rect {
  fill: var(--rubric, #b3402e);
  fill-opacity: 0.16;
  stroke: var(--rubric, #b3402e);
  stroke-opacity: 0.5;
  stroke-width: 1.5;
  pointer-events: none;
}
/* Target ring — drawn by TokensLayer when the local client has this
   token's foeId or ownerUid in `myTargets`. Sits just outside the
   gamepiece border; cinnabar dashed stroke matches the targeting
   accent used by the initiative tracker. The tween isn't a hover
   effect — it's a steady "this is locked on" pulse. */
.mc-token__target-ring {
  fill: none;
  stroke: var(--cinnabar, #c14b3a);
  stroke-width: 2.5;
  stroke-dasharray: 6 4;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  filter: drop-shadow(0 0 4px rgba(193, 75, 58, 0.7));
  animation: mc-token-target-spin 6s linear infinite;
}
.mc-token--targeted .mc-token__target-ring {
  opacity: 1;
}
@keyframes mc-token-target-spin {
  to { stroke-dashoffset: -40; }
}

/* Attack-target ring — sits inside the regular target ring so they
   coexist when both are active. Stroke colour comes from an inline
   attribute (the attacking player's display colour). The reticle
   pulses opacity gently to telegraph "this is what your next attack
   fires at" without competing with the spinning Mark ring. */
.mc-token__attack-ring {
  fill: none;
  stroke-width: 2;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  animation: mc-token-attack-pulse 1.6s ease-in-out infinite;
}
@keyframes mc-token-attack-pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* Dodge glow ring — pulses around the token that just dodged an
   attack, so the table sees which token (PC or foe) reacted. */
.mc-token__dodge-ring {
  fill: none;
  stroke: var(--verdigris, #3a8a6a);
  stroke-width: 3;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  filter: drop-shadow(0 0 4px var(--verdigris, #3a8a6a));
  animation: mc-token-dodge-pulse 0.7s ease-in-out infinite;
}
@keyframes mc-token-dodge-pulse {
  0%, 100% { opacity: 0.4; stroke-width: 2.5; }
  50%      { opacity: 1;   stroke-width: 4; }
}

/* Clear-board confirmation modal (GM only). The toolbar's bin button
   opens this; categories are checkboxes mapping 1:1 to server reducer
   keys. Backdrop click cancels. */
.mapcanvas__btn--danger { color: var(--cinnabar, #c14b3a); }
.mapcanvas__btn--danger:hover {
  background: var(--cinnabar, #c14b3a);
  color: var(--paper);
}
.mapcanvas__clear-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mapcanvas__clear-modal {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 4px;
  padding: 1.5em 1.75em;
  min-width: 360px;
  max-width: 90vw;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.45);
  font-family: "IM Fell English", "EB Garamond", Georgia, serif;
}
.mapcanvas__clear-title {
  margin: 0 0 0.35em 0;
  font-family: "IM Fell English SC", "IM Fell English", Georgia, serif;
  font-size: 1.4em;
  color: var(--cinnabar, #c14b3a);
  letter-spacing: 0.04em;
}
.mapcanvas__clear-hint {
  margin: 0 0 1em 0;
  font-style: italic;
  opacity: 0.8;
}
.mapcanvas__clear-list {
  list-style: none;
  margin: 0 0 1.25em 0;
  padding: 0;
}
.mapcanvas__clear-list li { margin: 0.35em 0; }
.mapcanvas__clear-list label {
  display: flex;
  gap: 0.5em;
  align-items: baseline;
  cursor: pointer;
}
.mapcanvas__clear-list input[type="checkbox"] {
  accent-color: var(--cinnabar, #c14b3a);
}
.mapcanvas__clear-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5em;
}
.mapcanvas__clear-actions .btn--danger {
  background: var(--cinnabar, #c14b3a);
  color: var(--paper);
  border-color: var(--cinnabar, #c14b3a);
}
.mapcanvas__clear-actions .btn--danger:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Stamp picker — GM-only modal sheet listing the manifest's categories
   and tiles. Matches the clear-modal aesthetic (manuscript paper, ink
   border) but wider, with a tab bar and a thumbnail grid. */
.stamp-picker__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 950;
  display: flex;
  align-items: center;
  justify-content: center;
}
.stamp-picker {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 4px;
  width: min(720px, 92vw);
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.45);
  font-family: "IM Fell English", "EB Garamond", Georgia, serif;
  overflow: hidden;
}
.stamp-picker__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.85em 1.1em 0.35em;
  border-bottom: 1px solid var(--border-soft, rgba(0, 0, 0, 0.12));
}
.stamp-picker__title {
  margin: 0;
  font-family: "IM Fell English SC", "IM Fell English", Georgia, serif;
  font-size: 1.4em;
  color: var(--cinnabar, #c14b3a);
  letter-spacing: 0.04em;
}
.stamp-picker__close {
  background: none;
  border: none;
  font-size: 1.5em;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
  padding: 0 0.25em;
}
.stamp-picker__close:hover { color: var(--cinnabar, #c14b3a); }
.stamp-picker__tabs {
  display: flex;
  gap: 0.25em;
  padding: 0.5em 1.1em 0;
  border-bottom: 1px solid var(--border-soft, rgba(0, 0, 0, 0.12));
}
.stamp-picker__tab {
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  padding: 0.4em 0.85em;
  font-family: inherit;
  font-size: 0.95em;
  color: var(--ink);
  cursor: pointer;
  border-radius: 4px 4px 0 0;
  letter-spacing: 0.02em;
}
.stamp-picker__tab:hover { background: rgba(0, 0, 0, 0.05); }
.stamp-picker__tab--active {
  background: var(--paper);
  border-color: var(--border-soft, rgba(0, 0, 0, 0.12));
  color: var(--cinnabar, #c14b3a);
  font-weight: 600;
  position: relative;
  top: 1px;
}
.stamp-picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 0.6em;
  padding: 1em 1.1em;
  overflow-y: auto;
  flex: 1;
}
.stamp-picker__tile {
  background: rgba(255, 250, 235, 0.4);
  border: 1px solid var(--border-soft, rgba(0, 0, 0, 0.18));
  border-radius: 3px;
  padding: 0.4em 0.4em 0.3em;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35em;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.stamp-picker__tile:hover {
  background: rgba(255, 240, 200, 0.7);
  border-color: var(--cinnabar, #c14b3a);
  transform: translateY(-1px);
}
.stamp-picker__thumb {
  width: 100%;
  height: 72px;
  object-fit: contain;
  image-rendering: auto;
}
.stamp-picker__label {
  font-size: 0.78em;
  text-align: center;
  line-height: 1.1;
  letter-spacing: 0.02em;
  opacity: 0.85;
}
.stamp-picker__empty,
.stamp-picker__error {
  padding: 1.5em 1.25em;
  font-style: italic;
  opacity: 0.85;
}
.stamp-picker__empty--cat {
  grid-column: 1 / -1;
  text-align: center;
}
.stamp-picker__error { color: var(--cinnabar, #c14b3a); }
.stamp-picker__footer {
  padding: 0.55em 1.1em 0.7em;
  border-top: 1px solid var(--border-soft, rgba(0, 0, 0, 0.12));
}
.stamp-picker__hint {
  font-size: 0.85em;
  font-style: italic;
  opacity: 0.75;
}
.stamp-picker__hint kbd {
  font-family: "Ubuntu Mono", "Consolas", monospace;
  font-size: 0.85em;
  padding: 0.05em 0.35em;
  border: 1px solid var(--ink);
  border-radius: 2px;
  background: rgba(255, 250, 235, 0.6);
}

/* Stamp placement banner: matches the AOE banner family but uses a
   verdigris accent so the GM can distinguish "I'm dropping decor" from
   "I'm dropping an AOE template." */
.mapcanvas__placement-banner--stamp {
  background: var(--verdigris-deep, #2e6e63);
  color: var(--paper);
}
.mapcanvas__placement-swap {
  background: rgba(255, 255, 255, 0.15);
  border: 1px solid rgba(255, 255, 255, 0.45);
  color: inherit;
  font-family: inherit;
  padding: 0.18em 0.6em;
  border-radius: 3px;
  cursor: pointer;
  margin-right: 0.4em;
}
.mapcanvas__placement-swap:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Inline tint chooser nested in the stamp placement banner. The
   native color input is small but keeps the gesture frictionless;
   the optional × button clears the tint so white pixels render
   transparent again. */
.mapcanvas__placement-tint {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  margin-right: 0.4em;
  font-family: inherit;
  font-size: 0.85em;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 3px;
  padding: 0.1em 0.4em;
  cursor: pointer;
}
.mapcanvas__placement-tint-label {
  font-style: italic;
  opacity: 0.9;
}
.mapcanvas__placement-tint input[type="color"] {
  width: 22px;
  height: 18px;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
}
.mapcanvas__placement-tint-clear {
  background: transparent;
  border: none;
  color: inherit;
  font-size: 1em;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.15em;
}
.mapcanvas__placement-tint-clear:hover {
  color: rgba(255, 255, 255, 0.7);
}
/* "+ Tint" enable button shown when tint is opt-out. Click bumps
   pendingStamp.tint to a sensible default so the colour picker
   appears in the next render. */
.mapcanvas__placement-tint-add {
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: inherit;
  font-family: inherit;
  font-size: 0.85em;
  font-style: italic;
  padding: 0.18em 0.6em;
  border-radius: 3px;
  cursor: pointer;
  margin-right: 0.4em;
}
.mapcanvas__placement-tint-add:hover {
  background: rgba(255, 255, 255, 0.25);
}

/* In-progress shape preview while the user is drawing. Same colour as
   the eventual committed shape, but with a dashed stroke and slightly
   reduced opacity so it reads as "not yet placed." */
.mc-draft {
  fill: var(--fill, none);
  stroke: var(--stroke, currentColor);
  stroke-width: 2.5;
  stroke-dasharray: 5 4;
  vector-effect: non-scaling-stroke;
  opacity: 0.85;
  pointer-events: none;
}
.mc-draft--filled { fill-opacity: 0.22; }
.mc-draft--freehand { fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* Dimension labels —
   – `.mc-dim-label` is the bright label shared by DraftLayer (drawn
     during a drag) and DimLabelsTopLayer (focus pass).
   – `.mc-dim-label--focus` adds an opacity transition so the focus
     pass fades in/out with hover/select. */
.mc-dim-label {
  fill: #fff;
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 700;
  font-size: 22px;
  letter-spacing: 0.02em;
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.7));
}
.mc-dim-label--focus {
  opacity: 0;
  transition: opacity 180ms ease-out;
}
.mc-dim-label--focus.is-visible {
  opacity: 1;
}

/* Stamped dimension labels — bottom-layer ambient watermarks. Sit
   between the grid and painted-cells layers; same Ubuntu Bold size as
   the focus pass, but in iron-gall ink at reduced opacity so they
   read as faded annotations without competing with the drawing
   strokes above. */
.mc-dim-label-stamped {
  fill: var(--ink, #28190c);
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 700;
  font-size: 22px;
  letter-spacing: 0.02em;
  opacity: 0.32;
  pointer-events: none;
  user-select: none;
}

/* Be Prepared zone tag — same legibility treatment as .mc-dim-label
   (white fill + dark drop-shadow halo) so it reads over any board
   content; sized down for the longer text, anchored centred just
   below the zone (centre-bottom-outside). */
.mc-aoe-label {
  fill: #fff;
  font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.02em;
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.7));
}

/* Chase animation — a marching-ants stroke effect that runs while a
   drawing is hovered or selected. dasharray + animated dashoffset
   cycles through one pattern unit (8 + 4 = 12px) per loop, giving a
   steady chase along the stroke. */
@keyframes mc-stroke-chase {
  to { stroke-dashoffset: -12; }
}
.mc-drawing-group--focused .mc-drawing {
  stroke-dasharray: 8 4;
  animation: mc-stroke-chase 0.7s linear infinite;
}

/* Each drawing now renders inside an <g.mc-drawing-group> with two
   children: a transparent wide "hit" shape and the visible shape. The
   visible shape is inert (`pointer-events: none`); the hit shape is
   activated only when the layer is in erase or select mode. This gives
   lines a 14px-wide click target (much easier to delete) and unfilled
   rects/circles a full bounding-box hit zone. */
.mc-drawing { pointer-events: none; }

.mc-drawing-hit {
  fill: transparent;
  stroke: transparent;
  stroke-width: 14;
  /* Always hit-testable so document.elementFromPoint can find the
     drawing under the cursor (used by drag-erase and shift+wheel
     rotate). The visible shape stays inert via .mc-drawing
     pointer-events: none, and pointer events bubble up to the SVG
     root so the active tool's pointer-down handler still fires. */
  pointer-events: all;
  cursor: inherit; /* don't override the wrapper's tool-specific cursor */
  vector-effect: non-scaling-stroke;
}
.mc-drawing-hit--stroke {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Tool-specific cursor when hovering a hittable drawing. Erase still
   uses pointer; select is a grab affordance because click-drag now
   translates the shape (resize handles get their own pointer cursor). */
.mc-layer--erase-target .mc-drawing-hit { cursor: pointer; }
.mc-layer--select-target .mc-drawing-hit { cursor: grab; }
.mc-layer--select-target .mc-drawing-group:active .mc-drawing-hit { cursor: grabbing; }

/* Painted cells use their own visible rect as the hit target — fill is
   semi-transparent but `all` covers the bounding box. */
.mc-layer--erase-target .mc-painted-cell {
  cursor: pointer;
  pointer-events: all;
}

/* Erase-mode hover: red glow to mark the click target. Hover bubbles
   to the drawing group; the visible shape inside picks up the filter. */
.mc-layer--erase-target .mc-drawing-group:hover .mc-drawing {
  filter: drop-shadow(0 0 4px var(--rubric, #c44));
}
.mc-layer--erase-target .mc-painted-cell:hover {
  fill-opacity: 0.85;
  filter: drop-shadow(0 0 3px var(--rubric, #c44));
}

/* Select-mode hover: a softer ink-coloured glow to distinguish it
   from "about to delete" (red). */
.mc-layer--select-target .mc-drawing-group:hover .mc-drawing {
  filter: drop-shadow(0 0 4px rgba(40, 25, 10, 0.7));
}

/* Selected drawing — persistent halo + dashed bounding frame from
   SelectionHandlesLayer. */
.mc-drawing-group--selected .mc-drawing {
  filter: drop-shadow(0 0 5px rgba(110, 80, 30, 0.85));
}
.mc-drawing-group--hidden .mc-drawing {
  opacity: 0;
}

/* Selection bounding frame (rect/circle outline shown around the
   selected shape, behind the handles). */
.mc-selection-frame {
  fill: none;
  stroke: var(--ink, #444);
  stroke-width: 1.25;
  stroke-dasharray: 4 4;
  pointer-events: none;
  vector-effect: non-scaling-stroke;
  opacity: 0.65;
}

/* Cardinal-direction resize handles. Each handle is a small square
   placed at the midpoint of an edge (rect) or at a cardinal extent
   (circle). The cursor reflects which axis the handle adjusts. */
.mc-handle {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.5;
  pointer-events: all;
  vector-effect: non-scaling-stroke;
}
.mc-handle[data-handle="N"],
.mc-handle[data-handle="S"] { cursor: ns-resize; }
.mc-handle[data-handle="E"],
.mc-handle[data-handle="W"] { cursor: ew-resize; }
.mc-handle:hover { fill: var(--ink); }

/* ── Guide Book ─────────────────────────────────────────────────────
   Reference / setting / rules viewer. Top-bar search + left TOC
   (chapter > page > in-page outline) + right content area. Renders
   markdown via window.marked; the in-component highlighter wraps
   search-term matches in <mark class="gb-hl">. */
/* Standalone-page wrapper. The host div sits between #app (100vh)
   and .gb-root. It needs an explicit height so .gb-root's
   `height: 100%` resolves immediately — without this, hard refreshes
   (Ctrl+F5) can land on a frame where the rAF-deferred
   `gb-root--standalone` class hasn't been added yet, .gb-root has
   height 0, and the inner scroll panes compute against a 0-height
   parent (no scrollbars). */
.gb-root-host {
  height: 100%;
  display: flex;
  flex-direction: column;
}
.gb-root-host > .gb-root {
  flex: 1;
  min-height: 0;
}

.gb-root {
  display: flex;
  flex-direction: column;
  height: 100%;                /* fills modal box, or whole viewport for the standalone page */
  /* CRITICAL: overflow:hidden caps the root at its CSS height. Without
     it, `height` is just a preferred size — descendants with tall
     intrinsic content (like StPageFlip's internal DOM) push the box
     past it, and the standalone view's height:100vh stops behaving
     like a hard cap. The modal happens to be saved by .gb-modal's own
     overflow:hidden one level up; standalone needs this here. */
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body-italic, var(--font-body), "IM Fell English", Georgia, serif);
  /* Guide Book headers use Harrington (per the manuscript aesthetic
     this view leans into). The global --font-display stays at IM Fell
     English SC for the main table view; this override is scoped to
     the Guide Book root only. */
  --font-display: "Harrington", "IM Fell English SC", Georgia, serif;
}
/* Modal wrapper used when GuideBook is opened from the in-app nav. */
.gb-modal-overlay { padding: 24px; }
.gb-modal {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: var(--shadow-3);
  width: min(1400px, 96vw);
  height: min(900px, 92vh);
  position: relative;
  overflow: hidden;
  display: flex;
}
.gb-modal > .gb-root { flex: 1; }
/* Modal close lives inside the search row (.gb-search-close) on
   mobile; desktop dismisses via Esc + backdrop click. No absolute
   button needed. */
@media (max-width: 720px) {
  /* Reclaim the 24px overlay padding on small viewports — at 375px
     that padding is ~13% of horizontal space, which leaves the modal
     visually fenced in. Edge-to-edge reads better. dvh accounts for
     mobile browser chrome; vh is the fallback. */
  .gb-modal-overlay { padding: 0; }
  .gb-modal {
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-width: none;
    max-height: none;
    border: none;
    border-radius: 0;
  }
  /* Search bar at narrow width: smaller padding, smaller gap, slightly
     smaller font so the Contents button + input + × close all fit one
     row without wrapping or clipping at the viewport edge.
     `min-width: 0` on the input lets it shrink freely instead of
     defaulting to its `size` attribute's content-based intrinsic
     width (which was pushing the close button off the right edge). */
  .gb-search-bar {
    padding: 10px 10px 8px;
    gap: 6px;
  }
  .gb-search-input {
    font-size: 14px;
    padding: 6px 10px;
    min-width: 0;
  }
}
.gb-search-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 24px 12px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--paper-aged);
  /* Never shrink — search bar is always visible at the top of the
     guide book. .gb-body underneath absorbs all leftover height and
     handles its own scrolling per-pane (TOC + content). */
  flex-shrink: 0;
}
.gb-search-input {
  flex: 1;
  padding: 8px 14px;
  font-family: var(--font-body-italic, "IM Fell English", Georgia, serif);
  font-size: 15px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--sepia);
  border-radius: 3px;
  outline: none;
}
.gb-search-input:focus {
  border-color: var(--rubric);
}
.gb-search-clear {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--sepia-soft);
  color: var(--ink-faded);
  cursor: pointer;
  border-radius: 3px;
  font-size: 18px;
  line-height: 1;
}
.gb-search-clear:hover {
  background: var(--paper);
  color: var(--rubric);
  border-color: var(--rubric);
}
/* Mobile-only modal close — sits at the end of the search row so it
   doesn't overlap the input. Matches the .gb-search-clear silhouette
   so the row reads as one set of controls. JSX only renders this
   button on mobile and when opened as a modal. */
.gb-search-close {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--sepia-soft);
  color: var(--ink-faded);
  cursor: pointer;
  border-radius: 3px;
  font-size: 20px;
  line-height: 1;
  flex-shrink: 0;
}
.gb-search-close:hover {
  background: var(--paper-aged);
  color: var(--rubric);
  border-color: var(--rubric);
}
.gb-body {
  /* Flex row instead of grid — grid's row sizing was letting the
     TOC's tall content push the row past the viewport, which made
     .gb-content--rich equally tall and placed the centered book
     far below the visible area. Flex with explicit child widths is
     more predictable: children stretch to fill the bounded height
     and only fight over horizontal space. */
  display: flex;
  flex-direction: row;
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.gb-root--toc-collapsed .gb-toc {
  /* Width animation handled on .gb-toc itself (below). Hide the
     border too so a 1px sliver doesn't remain visible. */
  border-right: 0;
}

/* TOC toggle button — sits at the very left of the search bar. The
   chevron flips direction based on collapsed state. */
/* Vertical fold handle sitting between the TOC and the content
   area. Full-height strip, narrow, with a chevron centered vertically
   that flips direction based on collapse state. */
.gb-toc-toggle {
  flex-shrink: 0;
  width: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper-aged);
  color: var(--ink-faded);
  border: 0;
  border-right: 1px solid var(--border-soft);
  padding: 0;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 18px;
  line-height: 1;
  transition: color var(--dur-1) var(--ease-quill), background var(--dur-1) var(--ease-quill);
}
.gb-toc-toggle:hover {
  color: var(--rubric);
  background: var(--paper);
}
.gb-toc-toggle-chevron {
  display: block;
}
.gb-toc {
  width: 340px;
  flex-shrink: 0;
  background: var(--paper-aged);
  border-right: 1px solid var(--border-soft);
  padding: 18px 10px 28px;
  overflow-y: scroll; /* keep the gutter so width is stable when contents grow */
  overflow-x: hidden;
  min-width: 0;
  /* Match the rest of the app's scrollbar treatment. */
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
  /* Isolation so the book view doesn't jostle when the outline
     expands or collapses under the active page. */
  contain: layout;
  scrollbar-gutter: stable;
  /* TOC fold animation — width 340 ↔ 0. */
  transition: width 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
.gb-root--toc-collapsed .gb-toc {
  width: 0;
}
.gb-toc-empty {
  padding: 14px 16px;
  font-style: italic;
  color: var(--ink-faded);
  font-size: 14px;
}
.gb-toc-chapter {
  margin-bottom: 20px;
}
.gb-toc-chapter-title {
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 0.02em;
  color: var(--ink-strong);
  padding: 5px 14px 7px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 5px;
  display: flex;
  align-items: baseline;
  gap: 6px;
  /* The wider column means we shouldn't need wrapping, but if a
     title ever did, keep it tidy. */
  white-space: nowrap;
}
.gb-toc-chapter-title__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* GM-only "+ new page" affordance on chapter headers. Hidden by
   default, revealed when the chapter row is hovered. Sits inside
   the title row so it doesn't shift layout when it appears. */
.gb-toc-chapter-add {
  flex: 0 0 auto;
  appearance: none;
  background: transparent;
  border: 1px solid var(--rubric);
  color: var(--rubric);
  font-family: var(--font-display);
  font-size: 18px;
  line-height: 1;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms ease, background-color 120ms ease, color 120ms ease;
}
.gb-toc-chapter:hover .gb-toc-chapter-add,
.gb-toc-chapter-add:focus-visible {
  opacity: 1;
}
.gb-toc-chapter-add:hover {
  background: var(--rubric);
  color: var(--paper);
}
/* SortableJS drag feedback for GM TOC reordering: the placeholder
   ghost gets a faint sepia tint + dotted outline so the drop target
   reads clearly; the dragging item itself stays visible at the
   pointer. */
.gb-toc-page--ghost {
  opacity: 0.4;
  background: color-mix(in srgb, var(--paper) 60%, var(--sepia));
  outline: 1px dashed var(--rubric);
  outline-offset: -2px;
}
.gb-toc-pages {
  list-style: none;
  padding: 0;
  margin: 0;
}
.gb-toc-page {
  margin: 0;
}
.gb-toc-page-btn {
  display: block;
  width: 100%;
  text-align: left;
  padding: 6px 14px;
  background: transparent;
  border: 0;
  border-left: 3px solid transparent;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 15px;
  letter-spacing: 0;
  color: var(--ink);
}
.gb-toc-page-btn:hover {
  background: color-mix(in srgb, var(--rubric) 6%, transparent);
  color: var(--ink-strong);
}
.gb-toc-page.is-active > .gb-toc-page-btn {
  background: color-mix(in srgb, var(--rubric) 9%, transparent);
  border-left-color: var(--rubric);
  color: var(--ink-strong);
  font-weight: 600;
}
.gb-toc-outline {
  list-style: none;
  padding: 0;
  margin: 3px 0 10px;
  border-left: 1px dashed var(--sepia-soft);
  margin-left: 22px;
}
.gb-toc-outline-item {
  margin: 0;
}
.gb-toc-outline-btn {
  display: block;
  width: 100%;
  text-align: left;
  padding: 4px 0 4px 10px;
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: var(--font-body-italic, "IM Fell English", Georgia, serif);
  font-size: 14px;
  color: var(--ink-faded);
}
.gb-toc-outline-btn:hover {
  color: var(--rubric);
}
.gb-toc-outline-item--h2 .gb-toc-outline-btn { padding-left: 10px; }
.gb-toc-outline-item--h3 .gb-toc-outline-btn { padding-left: 22px; }
.gb-toc-outline-item--h4 .gb-toc-outline-btn { padding-left: 34px; font-size: 13px; }
.gb-content--plain {
  /* Claim the remaining horizontal space after the TOC + toggle, and
     a stable vertical bound from .gb-body so internal overflow scrolls
     instead of pushing the layout. `min-height: 0` / `min-width: 0`
     override flex's default min-content sizing — without them, a long
     page bleeds out the bottom and the pane refuses to scroll. */
  flex: 1 1 0;
  min-height: 0;
  min-width: 0;
  overflow-y: scroll; /* always show the gutter so layout doesn't shift */
  padding: 24px 48px 80px;
  /* Match the TOC's manuscript-thin scrollbar treatment. */
  scrollbar-width: thin;
  scrollbar-color: var(--sepia-soft) transparent;
  scrollbar-gutter: stable;
}
.gb-content--plain::-webkit-scrollbar {
  width: 10px;
}
.gb-content--plain::-webkit-scrollbar-thumb {
  background: var(--sepia-soft);
  border-radius: 5px;
}
/* Wheel-driven page navigation telegraph. A thin bar sticks to the
   top or bottom edge of the visible scroll area and scales from 0 to
   1 across the available width as the user wheels past the at-edge
   threshold. When the bar reaches 100% the page flips. Idle for
   ~220ms and the bar decays back to 0, so a user just reaching for
   the bottom of the page won't accidentally page-flip. The negative
   margin keeps the bar from adding layout space; pointer-events
   none prevents it from blocking text selection or clicks. */
.gb-content--plain .gb-push-bar {
  position: sticky;
  left: 0;
  width: 100%;
  height: 4px;
  background: var(--rubric);
  transform: scaleX(0);
  transform-origin: center;
  pointer-events: none;
  z-index: 20;
  will-change: transform;
  opacity: 0.85;
}
.gb-content--plain .gb-push-bar--top {
  top: 0;
  margin-bottom: -4px;
}
.gb-content--plain .gb-push-bar--bottom {
  bottom: 0;
  margin-top: -4px;
}

.gb-content--plain::-webkit-scrollbar-thumb:hover {
  background: var(--sepia);
}
.gb-content--plain::-webkit-scrollbar-track {
  background: transparent;
}
.gb-toc::-webkit-scrollbar {
  width: 10px;
}
.gb-toc::-webkit-scrollbar-thumb {
  background: var(--sepia-soft);
  border-radius: 5px;
}
.gb-toc::-webkit-scrollbar-thumb:hover {
  background: var(--sepia);
}
.gb-toc::-webkit-scrollbar-track {
  background: transparent;
}
/* Fixed-height wrapper around the rich-view content section. Sized
   to the viewport so .gb-content--rich (and the book inside) get a
   stable, bounded height regardless of what StPageFlip puts in its
   internal DOM. Sits as a flex item of .gb-body so it takes the
   remaining width after TOC + toggle. */
.gb-content-wrapper {
  flex: 1;
  min-width: 0;
  min-height: 0;
  height: 100vh;
  max-height: 100vh;
  display: flex;
  overflow: hidden;
}
.gb-content-wrapper > .gb-content--rich {
  /* When wrapped, the inner content section flexes to fill the
     wrapper exactly — no need for its own viewport-height calc. */
  height: 100%;
  width: 100%;
}

.gb-content--rich {
  /* The book area: takes whatever horizontal space remains after the
     TOC + toggle. Flex-centers the book inside, giving it equal
     vertical spacing top/bottom. The small padding keeps the book
     "just short" of the edges.
     IMPORTANT: explicit height:100% + max-height:100%. StPageFlip's
     internal DOM has an intrinsic vertical content size larger than
     the page (extra stacked elements for the flip animation). Flex's
     `align-items: stretch` does NOT shrink children below their
     intrinsic content size, so without these caps the section grows
     to 2000+px tall, pushing the book off the bottom of the screen
     (most visible in the standalone view; the modal happens to bound
     it through other constraints). */
  flex: 1;
  min-width: 0;
  min-height: 0;
  height: 100%;
  max-height: 100%;
  overflow: hidden;
  padding: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper-aged);
}
/* Book size is set by inline width/height that StPageFlip applies at
   init time, based on the dim calc in the lifecycle effect. No CSS
   width/height/aspect-ratio here — they fight the library's inline
   styles and create a feedback loop with autoSize. The library is
   configured with size:'fixed' and autoSize:false; a ResizeObserver
   on the content area triggers re-init with new dimensions when the
   container changes size. */
.gb-book {
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
}

/* ── View-mode toggle (Plain / Rich) ────────────────────────────── */
.gb-view-toggle {
  display: inline-flex;
  margin-left: auto;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-2);
  overflow: hidden;
  background: var(--paper);
}
.gb-view-toggle-btn {
  background: transparent;
  border: 0;
  padding: 4px 12px;
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--ink-faded);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease-quill), color var(--dur-1) var(--ease-quill);
}
.gb-view-toggle-btn + .gb-view-toggle-btn {
  border-left: 1px solid var(--border-soft);
}
.gb-view-toggle-btn:hover { color: var(--ink-strong); }
.gb-view-toggle-btn.is-active {
  background: color-mix(in srgb, var(--rubric) 16%, transparent);
  color: var(--ink-strong);
}

/* ── Manuscript (Rich) view ─────────────────────────────────────── */
/* The webp is the entire open spread (5100×3300). The fold line is
   drawn down the middle by .gb-manuscript-fold; the two text columns
   sit on either side of it within the painted page area. */
/* ── Rich view (StPageFlip-managed book) ─────────────────────────
   The flip animation is driven entirely by the StPageFlip library
   (loaded from CDN). The library handles the visual page-turn,
   shadows, drag-to-turn corners, and two-page spread layout. Our
   CSS only styles the static page surfaces; everything to do with
   the flip motion itself lives in the library. */
.gb-book-page {
  /* The page background is the spread image, rendered 200% wide so
     one half lands on each page. data-side controls which half each
     page shows, so the visible spread (two pages side-by-side) reads
     as the single continuous webp painted across both. */
  background-image: url('/assets/farrago-page-landscape.webp');
  background-size: 200% 100%;
  background-repeat: no-repeat;
  padding: 36px 40px;
  box-sizing: border-box;
  overflow: hidden;
  font-family: var(--font-body-italic, "IM Fell English", Georgia, serif);
  font-size: 17px;
  line-height: 1.65;
  color: var(--ink);
  /* Flex column so the title sits above the body and the body fills
     the rest of the page height — necessary for the body's CSS
     columns (below) to know how tall to make each column.
     `!important` because StPageFlip writes inline `display: block` on
     active pages, which would otherwise win specificity over our
     class rule. (Class rule with !important beats inline without.) */
  display: flex !important;
  flex-direction: column;
}
.gb-book-page[data-side="verso"] {
  background-position: 0% center;
}
.gb-book-page[data-side="recto"] {
  background-position: 100% center;
}
.gb-book-page-title {
  font-family: var(--font-display);
  font-size: 40px;
  letter-spacing: 0.03em;
  color: var(--ink-strong);
  margin: 0 0 0.6em;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--sepia-soft);
}
.gb-book-page-body {
  /* Two-column page flow with a visible gap between the columns.
     Fills the page height below the title (the parent .gb-book-page
     is a flex column). `column-fill: auto` fills column 1 to the
     bottom before flowing into column 2.
     IMPORTANT sizing notes:
       - `flex: 1 1 0` (not `auto`) — `auto` would seed the basis from
         the content's intrinsic height (= 5000+px for long pages),
         which then propagates up the flex chain and makes the body
         taller than the page.
       - `min-height: 0` + `max-height: 100%` belt-and-braces cap so
         the body can never exceed its parent's content area.
       - `overflow: hidden` clips anything past the second column
         (until proper multi-page pagination is in). */
  flex: 1 1 0;
  min-height: 0;
  max-height: 100%;
  column-count: 2;
  column-gap: 28px;
  column-fill: auto;
  overflow: hidden;
}
/* Per-page opt-out: forces single-column flow for pages where a
   floating insert needs the full page width. Set via the page's
   `bodyClass` field (stored on farrago_guidebook_pages.body_class,
   editable through the GM editor). `position: relative` makes the
   body the containing block for absolutely-positioned inserts
   (e.g. `gb-insert--bottom-band`).
   Both class names match — `gb-page-body--single` is the canonical
   plain-view name; `gb-book-page-body--single` is the older rich-
   view variant kept alive for back-compat with pages authored
   before the rich view was shelved. */
.gb-book-page-body--single,
.gb-page-body--single {
  column-count: 1;
  position: relative;
}

/* Frontispiece page — single column, body collapses to a centered
   stage whose single image figure fills the page width edge-to-edge.
   Used for full-page plates: a [img …] is expected to be the only
   content. The page title is suppressed via :has() on the parent
   article, and the hand-drawn page-load animation is skipped in JS
   (see useEffect gate in guidebook.jsx). Dropcap is also suppressed
   defensively in case any text creeps in. */
.gb-page:has(.gb-page-body--frontispiece) .gb-page-title {
  display: none;
}
.gb-page-body--frontispiece {
  column-count: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 72vh;
  padding: 0;
}
.gb-page-body--frontispiece .gb-insert--image {
  margin: 0;
  width: 100%;
  max-width: none;
}
.gb-page-body--frontispiece .gb-insert--image img {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
  max-height: none;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
}
.gb-page-body--frontispiece > p:first-of-type::first-letter,
.gb-page-body--frontispiece .gb-dropcap {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  float: none;
  padding: 0;
  color: inherit;
}

/* ── Hand-drawn page-load animation (local-dev experiment) ────────
   Per-page animation runs once per navigation when enabled (toggle
   sits in the search bar; gated to localhost). The .gb-dropcap span
   is injected by wrapFirstLetter at runtime — when it's present, the
   ::first-letter dropcap is suppressed so we don't get both. Brown
   then red colour states match the "first pass / re-ink" beat. */
.gb-page-body.has-animated-dropcap > p:first-of-type::first-letter,
.gb-page-body.has-animated-dropcap > .gb-page-body__section:first-child > p:first-of-type::first-letter {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  float: none;
  padding: 0;
  color: inherit;
}
.gb-page-body .gb-dropcap {
  font-family: var(--font-display);
  font-size: 5.6em;
  line-height: 0.78;
  float: left;
  padding: 0.06em 0.14em 0 0;
  display: inline-block;
  transform-origin: bottom left;
  will-change: opacity, color, transform;
}
.gb-page-body .gb-dropcap--red { color: var(--rubric); }

/* ── Guide Book mobile (≤ 720px) ─────────────────────────────────
   The desktop layout (sticky search bar + fixed-width TOC sidebar +
   fluid content) cracks at narrow widths. Mobile mode collapses the
   TOC into a slide-in drawer triggered by a header button, drops the
   2-column body to single column, and adds edge tap-zones for prev/
   next article nav so a thumb on the left or right edge advances the
   reading flow without scrolling back to the TOC. Animations and the
   inline editor still work; just laid out differently. */
.gb-mobile-toc-btn {
  flex-shrink: 0;
  display: none;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.04em;
  background: var(--paper);
  color: var(--ink-faded);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  cursor: pointer;
}
.gb-mobile-toc-btn__icon { font-size: 15px; line-height: 1; }
.gb-mobile-toc-btn:hover { color: var(--rubric); border-color: var(--rubric); }
.gb-mobile-toc-btn.is-open { color: var(--rubric); border-color: var(--rubric); }
.gb-mobile-toc-backdrop { display: none; }
.gb-tap-nav { display: none; }

.gb-root--mobile .gb-mobile-toc-btn { display: inline-flex; }

/* Slide-in drawer — TOC re-purposed as an overlay. The .gb-body
   keeps its flex row layout; we just absolute-position the TOC on
   top of it. transform animates the slide so opening the drawer
   has a sense of paper sliding out from the spine. */
.gb-root--mobile .gb-body { position: relative; }
.gb-root--mobile .gb-toc {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: min(86vw, 360px);
  z-index: 10;
  transform: translateX(-105%);
  transition: transform 280ms cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 4px 0 16px rgba(0, 0, 0, 0.18);
  border-right: 1px solid var(--border);
}
.gb-root--mobile.gb-root--mobile-toc-open .gb-toc {
  transform: translateX(0);
}
.gb-root--mobile.gb-root--mobile-toc-open .gb-mobile-toc-backdrop {
  display: block;
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background: rgba(20, 14, 8, 0.35);
  z-index: 9;
}

/* Hide the desktop fold-chevron — mobile uses the header drawer
   button instead. */
.gb-root--mobile .gb-toc-toggle { display: none; }

/* Single-column body, reduced padding and title size for narrow
   viewports. Page-body modifier --single also kicks in here. */
.gb-root--mobile .gb-content--plain {
  padding: 14px 16px 60px;
}
.gb-root--mobile .gb-page { max-width: none; }
.gb-root--mobile .gb-page-title { font-size: 28px; padding-bottom: 8px; margin-bottom: 12px; }
.gb-root--mobile .gb-page-body {
  font-size: 17px;
  line-height: 1.6;
  column-count: 1;
  column-gap: 0;
  column-rule: none;
}
.gb-root--mobile .gb-page-body > p:first-of-type::first-letter,
.gb-root--mobile .gb-page-body > .gb-page-body__section:first-child > p:first-of-type::first-letter {
  font-size: 4.6em;
}
/* Sectioned pages (Attributes, Skills, …) wrap each body section in a
   nested 2-column div. The desktop rule lives further down the file at
   .gb-page-body > .gb-page-body__section { column-count: 2 }, which
   the parent-only mobile override above does NOT reach. Force the
   inner sections single-column too, otherwise column 2 overflows the
   narrow modal viewport. */
.gb-root--mobile .gb-page-body > .gb-page-body__section {
  column-count: 1;
  column-gap: 0;
  column-rule: none;
}
/* Class-page bottom slot (Core Attributes / Key Skills, plus the
   3-up variant) is a 1fr-grid on desktop; on mobile stack the cells
   vertically so the text isn't crammed into 150-px columns. The
   vertical dividers between cells become meaningless once stacked,
   so hide them. Drop the top rule's left/right padding to give the
   centered titles a touch more breathing room as well. */
.gb-root--mobile .gb-page-body .gb-col-slot,
.gb-root--mobile .gb-page-body .gb-col-slot--3up,
.gb-root--mobile .gb-page-body .class-spread {
  grid-template-columns: 1fr;
  row-gap: 18px;
}
.gb-root--mobile .gb-page-body .gb-col-slot--pair > .gb-col-slot__left,
.gb-root--mobile .gb-page-body .gb-col-slot--pair > .gb-col-slot__right,
.gb-root--mobile .gb-page-body .gb-col-slot--left > *,
.gb-root--mobile .gb-page-body .gb-col-slot--right > *,
.gb-root--mobile .gb-page-body .gb-col-slot--3up > .gb-col-slot__cell,
.gb-root--mobile .gb-page-body .class-spread__col {
  grid-column: 1;
}
.gb-root--mobile .gb-page-body .gb-col-slot--pair > .gb-col-slot__right::before,
.gb-root--mobile .gb-page-body .gb-col-slot--3up > .gb-col-slot__cell:not(:first-child)::before {
  display: none;
}
/* Inserts: centered/float modifiers assume there's room beside the
   box for prose to wrap or for the band to feel "centered". At mobile
   widths both look broken — stretch them to full body width instead. */
.gb-root--mobile .gb-page-body .gb-insert--center {
  width: auto;
  margin-left: 0;
  margin-right: 0;
}
.gb-root--mobile .gb-page-body .gb-insert--float-left,
.gb-root--mobile .gb-page-body .gb-insert--float-right {
  float: none;
  width: auto;
  margin: 1em 0;
}
/* Bottom-band codex prose folds to a single column on mobile —
   18px-gap two-column inside a 350-px wrapper is unreadable. */
.gb-root--mobile .gb-page-body .gb-insert--bottom-band .gb-insert__body {
  column-count: 1;
  column-gap: 0;
}
/* Tables: long manuscript tables would overflow on mobile. Allow the
   wrapper to scroll horizontally so the table is reachable without
   blowing out the page width. */
.gb-root--mobile .gb-page-body .gb-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* `^^^` column-break markers still force a break with column-count: 1,
   which the browser honors by laying out a phantom column 2 off the
   right edge — pushing the following content (including inserts) out
   of view. On mobile (always single-column) the break is meaningless;
   neutralise it so the content stays in the flow. Same treatment for
   gb-section-break sentinel in case it leaks through. */
.gb-root--mobile .gb-page-body .gb-col-break,
.gb-root--mobile .gb-page-body .gb-section-break {
  break-before: auto;
  -webkit-column-break-before: auto;
  break-after: auto;
  -webkit-column-break-after: auto;
}

/* Tap-zone overlay — two transparent buttons pinned to the left and
   right edges of the *visible content pane* (not the viewport). The
   wrapper is `position: sticky; top: 0; height: 0` inside the
   scrolling .gb-content--plain so it stays at the top of that pane
   as content scrolls past, without taking layout space. The zone
   buttons are `position: absolute` from that sticky anchor, so they
   sit below the search bar and never intercept taps on the header
   row. Visible chevron fades in on active/hover so users discover
   the affordance; pointer-events: none on the wrapper keeps scroll/
   select working in the middle. */
.gb-root--mobile .gb-tap-nav {
  display: block;
  position: sticky;
  top: 0;
  height: 0;
  pointer-events: none;
  z-index: 2;
}
.gb-root--mobile .gb-tap-nav__zone {
  position: absolute;
  top: 0;
  height: 100vh;        /* extends past the pane bottom; clipped by parent */
  width: min(18%, 84px);
  pointer-events: auto;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--rubric);
  font-family: var(--font-display);
  font-size: 56px;
  line-height: 1;
  opacity: 0;
  transition: opacity var(--dur-1) var(--ease-quill), background var(--dur-1) var(--ease-quill);
  -webkit-tap-highlight-color: transparent;
}
.gb-root--mobile .gb-tap-nav__zone--prev { left: 0;  }
.gb-root--mobile .gb-tap-nav__zone--next { right: 0; }
.gb-root--mobile .gb-tap-nav__zone:active,
.gb-root--mobile .gb-tap-nav__zone:focus-visible {
  opacity: 0.85;
  background: linear-gradient(
    to var(--gb-tap-dir, right),
    color-mix(in srgb, var(--paper) 60%, transparent),
    transparent
  );
}
.gb-root--mobile .gb-tap-nav__zone--next { --gb-tap-dir: left; }
.gb-root--mobile .gb-tap-nav__zone[disabled] { opacity: 0; pointer-events: none; cursor: default; }
.gb-root--mobile .gb-tap-nav__chevron {
  display: block;
  padding: 0 6px;
}

/* On mobile the editor's textarea + preview need to stretch full
   width too (the desktop pane assumes ~1000px max). */
.gb-root--mobile .gb-edit-textarea { min-height: 320px; }
.gb-root--mobile .gb-edit-header { gap: 8px; }
.gb-root--mobile .gb-edit-title  { font-size: 22px; }

.gb-anim-toggle {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.04em;
  background: var(--paper);
  color: var(--ink-faded);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-quill), border-color var(--dur-1) var(--ease-quill);
}
.gb-anim-toggle__icon { font-size: 14px; line-height: 1; }
.gb-anim-toggle:hover { color: var(--rubric); border-color: var(--rubric); }
.gb-anim-toggle.is-on  { color: var(--rubric-deep); border-color: var(--rubric-deep); }
.gb-anim-toggle.is-off { color: var(--ink-faded); }

/* Class-page imports — the .md files under classes/ render via the
   shared .class-opening (verse + attribution above each class) and
   .class-spread (Core Attributes / Key Skills two-up) markup. The
   base .class-opening styles upstream are unscoped so they carry
   over, but in the guidebook's 2-column flow both blocks need to
   break out and span the full body width (otherwise the opening
   verse splits across columns and the spread squeezes too narrow). */
.gb-page-body .class-opening,
.gb-page-body .class-spread {
  column-span: all;
  -webkit-column-span: all;
  break-inside: avoid;
}
/* Two-column slot — children align with the body's two text
   columns above so Core Attributes sits centered below the left
   column and Key Skills below the right. The grid template's
   column-gap matches the body's column-gap (36px in styles.css)
   so the boundary between the two cells lines up with the column
   rule. */
.gb-page-body .class-spread {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 36px;
  margin: 1.6em 0 1em;
}
.gb-page-body .class-spread__col {
  text-align: center;
}
/* Apply the same section-block styling whether the parent is the
   legacy .class-spread__col or the new .gb-col-slot (rendered from
   the :::left / :::right markdown shorthand). Rubric uppercase
   title, fleuron-bullet centered list items. */
.gb-page-body .class-spread__col h3,
.gb-page-body .gb-col-slot h3 {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rubric);
  border-bottom: none;
  padding-bottom: 0;
}
.gb-page-body .class-spread__col ul,
.gb-page-body .gb-col-slot ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.gb-page-body .class-spread__col li,
.gb-page-body .gb-col-slot li {
  display: block;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--ink);
  margin: 0 0 4px;
  padding-left: 0;
  position: static;
  text-align: center;
}
.gb-page-body .class-spread__col li::before,
.gb-page-body .gb-col-slot li::before {
  content: '❧ ';
  position: static;
  color: var(--rubric);
  font-size: 13px;
  margin-right: 4px;
}

/* Markdown shorthand `:::left ... :::` / `:::right ... :::` — a
   block that breaks out of the two-column body flow and aligns
   its content centered below one of the two body columns.
   Adjacent `:::left` + `:::right` blocks are merged at parse time
   into a `--pair` container so both halves share one grid row and
   render side-by-side. */
.gb-page-body .gb-col-slot {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 36px;
  column-span: all;
  -webkit-column-span: all;
  margin: 1em 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--border-soft);
  break-inside: avoid;
  text-align: center;
}
.gb-page-body .gb-col-slot--left  > * { grid-column: 1; }
.gb-page-body .gb-col-slot--right > * { grid-column: 2; }
.gb-page-body .gb-col-slot--pair > .gb-col-slot__left  { grid-column: 1; }
.gb-page-body .gb-col-slot--pair > .gb-col-slot__right { grid-column: 2; }
/* Three-up modifier: same column-span: all wrapper, but the grid
   splits into three equal cells centered across the page. Cells
   are auto-placed into columns 1/2/3 by source order. */
.gb-page-body .gb-col-slot--3up {
  grid-template-columns: 1fr 1fr 1fr;
}

/* Paragraph text inside col-slot cells reads left-aligned even
   though the wrapper centers headings — long descriptive prose
   centered every line is hard to read. */
.gb-page-body .gb-col-slot p {
  text-align: left;
}

/* Vertical divider between cells — matches the body's column-rule
   (1px var(--border-soft)) and sits centered in the 36px column-
   gap. Rendered as a ::before pseudo so the JS animation can hide
   it via the `--gb-divider-color` custom property (pseudo-elements
   can't be inline-styled directly, but they DO inherit custom
   properties from their parent). Cleanup of the animation clears
   the property and the static divider takes back over. */
.gb-page-body .gb-col-slot--pair > .gb-col-slot__left,
.gb-page-body .gb-col-slot--pair > .gb-col-slot__right,
.gb-page-body .gb-col-slot--3up > .gb-col-slot__cell {
  position: relative;
}
.gb-page-body .gb-col-slot--pair > .gb-col-slot__right::before,
.gb-page-body .gb-col-slot--3up > .gb-col-slot__cell:not(:first-child)::before {
  content: '';
  position: absolute;
  /* Extend up through the slot's padding-top (16px) + border-top
     (1px) so the divider touches the horizontal top rule, matching
     the SVG trace's extent. Without this the CSS pseudo only spans
     the cell box, leaving a ~17px gap at the top of the divider
     when the SVG hand-off completes. */
  top: -17px;
  bottom: 0;
  left: -18px;
  width: 1px;
  background: var(--gb-divider-color, var(--border-soft));
}

/* ── Manuscript tables ───────────────────────────────────────────
   Markdown tables (`| a | b |` syntax) get wrapped at render time
   in `.gb-table-wrap` so the entry animation can mount an absolute
   bg-paint overlay alongside the table (a <div> child of <table>
   would be hoisted out by the browser). The wrapper handles
   column-span + spacing; the table itself carries the manuscript
   border + sepia tint + double-line inset shadow that matches the
   text-insert aesthetic. */
.gb-page-body .gb-table-wrap {
  position: relative;
  column-span: all;
  -webkit-column-span: all;
  margin: 1.2em 0;
  break-inside: avoid;
}
/* Column-bound block — `:::narrow … :::` markdown shorthand wraps
   its contents in `.gb-narrow`, which suppresses column-span on
   any tables (or other normally-spanning inserts) inside so they
   stay confined to the current body column. Useful for compact
   reference tables that don't need to fill the full body width.
   `break-inside: avoid` keeps the whole block together so a 4-row
   table doesn't get split across a column boundary. */
.gb-page-body .gb-narrow {
  break-inside: avoid;
  /* Bottom-only spacing: the browser absorbs `margin-top` at column
     breaks, so a narrow block that lands at the top of a new column
     sits flush against the column top while a narrow block at the
     top of the first column keeps its margin. That makes adjacent
     side-by-side narrow blocks mis-align by exactly the absorbed
     margin. Putting all the spacing in margin-bottom keeps the top
     of every column aligned. Same fix as `.gb-card`. */
  margin: 0 0 1em;
}
.gb-page-body .gb-narrow .gb-table-wrap {
  column-span: none;
  -webkit-column-span: none;
  margin: 0;
}
.gb-page-body .gb-narrow .gb-table-wrap table {
  font-size: 0.9em;
}
.gb-page-body .gb-narrow .gb-table-wrap th,
.gb-page-body .gb-narrow .gb-table-wrap td {
  padding: 6px 10px;
}
.gb-page-body .gb-table-wrap table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid var(--sepia);
  background: color-mix(in srgb, var(--paper) 92%, var(--sepia));
  box-shadow: inset 0 0 0 2px var(--paper-aged),
              inset 0 0 0 3px var(--sepia-soft);
  font-family: var(--font-body-italic, "IM Fell English", Georgia, serif);
  font-size: 0.95em;
  color: var(--ink);
}
.gb-page-body th,
.gb-page-body td {
  padding: 8px 14px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--sepia-soft);
}
.gb-page-body th {
  font-family: var(--font-display);
  font-style: normal;
  color: var(--rubric-deep);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.85em;
  background: color-mix(in srgb, var(--paper) 78%, var(--sepia));
  border-bottom: 1px solid var(--sepia);
}
.gb-page-body tr:last-child td { border-bottom: none; }
.gb-page-body tr:nth-child(even) td {
  background: color-mix(in srgb, var(--paper) 96%, var(--sepia));
}

/* ── Quote frames (shared: cycle + static) ───────────────────────
   `.gb-cycle-quotes` holds a series of `.gb-cycle-quote` children
   that cycle (one paints in, dwells, paints out, next).
   `.gb-quote-frame` is the static variant: same visual treatment,
   single inner `.gb-quote-frame__body` instead of cycling.

   Both spans both columns of the two-column body via column-span,
   sit in a rubric-dotted, sepia-tinted manuscript frame, and use
   italic centered display-italic text inside. */
.gb-cycle-quotes,
.gb-quote-frame {
  position: relative;
  column-span: all;
  -webkit-column-span: all;
  margin: 0.8em 0 1.2em;
  padding: 18px 24px;
  border: 2px dotted var(--rubric);
  background: color-mix(in srgb, var(--paper) 72%, var(--sepia));
  border-radius: 3px;
  font-family: var(--font-display-italic, var(--font-body-italic), Georgia, serif);
  font-style: italic;
  color: var(--ink-strong);
  font-size: 1.05em;
  line-height: 1.6;
  text-align: center;
  break-inside: avoid;
}
.gb-cycle-quote,
.gb-quote-frame__body {
  /* Cycle quotes: the JS prepare step absolute-stacks them; before
     it runs they default to normal block flow. Static frames: just
     a single content wrapper. */
  margin: 0 auto;
  max-width: 38em;
}
/* Cycle box: shift the 18px/24px padding from the parent onto each
   absolutely-positioned quote, then flex-center the content inside.
   The parent's padding is invisible to absolute children (they sit
   against the padding-box edges), so leaving it there produced
   asymmetric spacing: 31px above (border + H3 margin), -1.5px below
   (overflow). Moving padding onto each quote restores symmetric
   breathing room, and flex centering balances any leftover space
   when a quote is shorter than the tallest. Zeroing the first/last
   child margins ensures the measured height = content + own padding
   (no margin contribution), keeping the height calculation honest. */
.gb-cycle-quotes {
  padding: 0;
}
.gb-cycle-quote {
  padding: 18px 24px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.gb-cycle-quote > *:first-child { margin-top: 0; }
.gb-cycle-quote > *:last-child  { margin-bottom: 0; }
.gb-cycle-quote p,
.gb-quote-frame__body p { margin: 0 0 0.4em; }
.gb-cycle-quote p:last-child,
.gb-quote-frame__body p:last-child { margin-bottom: 0; }
/* Em-dash attribution lines (lone paragraph starting with —) read
   as the attribution; let them sit slightly smaller. */
.gb-cycle-quote p:last-child:not(:first-child),
.gb-quote-frame__body p:last-child:not(:first-child) {
  font-size: 0.92em;
  color: var(--ink-faded);
  font-style: normal;
}

/* Forced column break — emitted by the `^^^` marker in markdown
   (see preprocessMarkdown in guidebook.jsx). Invisible block whose
   only job is to push the next element to the top of the next
   column. `-webkit-column-break-before` is the legacy alias kept
   for Safari compatibility. */
.gb-col-break {
  display: block;
  break-before: column;
  -webkit-column-break-before: always;
  height: 0;
  margin: 0;
  padding: 0;
}
/* Anything that lands at the top of a column thanks to ^^^ shouldn't
   add its normal top margin — that empty space at column-top reads
   as a layout bug. Zero the leading margin on the very next element,
   whatever it is (heading, paragraph, list, blockquote, insert box).
   Specificity bumped via .gb-page-body / .gb-book-page-body so this
   beats per-element margin rules defined further down in this file
   (CSS source order plus equal specificity would otherwise let those
   rules win). */
.gb-page-body .gb-col-break + *,
.gb-book-page-body .gb-col-break + * {
  margin-top: 0;
}
.gb-book-page-body h2,
.gb-book-page-body h3,
.gb-book-page-body h4 {
  font-family: var(--font-display);
  color: var(--ink-strong);
  letter-spacing: 0.04em;
  margin: 1.0em 0 0.35em;
  /* Keep a heading with the paragraph that follows it within a column. */
  break-after: avoid;
}
.gb-book-page-body h2 { font-size: 22px; color: var(--rubric-deep); border-bottom: 1px solid var(--border-soft); padding-bottom: 3px; }
.gb-book-page-body h3 { font-size: 18px; }
.gb-book-page-body h4 { font-size: 14px; color: var(--sepia); text-transform: uppercase; letter-spacing: 0.06em; }
.gb-book-page-body p { margin: 0 0 0.7em; }
.gb-book-page-body > p:first-of-type::first-letter {
  font-family: var(--font-display);
  font-size: 4.4em;
  line-height: 0.78;
  float: left;
  padding: 0.06em 0.14em 0 0;
  color: var(--rubric);
}

/* ── Page inserts (text callout + image inset) ─────────────────── */
/*
   Two insert styles for special content drops on a guide-book page:
     <div class="gb-insert gb-insert--text">
       <div class="gb-insert__title">Codex Entry: Foo</div>
       <div class="gb-insert__body">…paragraphs…</div>
     </div>

     <figure class="gb-insert gb-insert--image">
       <img src="/assets/foo.webp" alt="…" />
       <figcaption class="gb-insert__caption">A caption.</figcaption>
     </figure>

   Add `gb-insert--wide` to either to make the insert span both
   columns (via column-span: all) instead of fitting inside one. */
.gb-insert {
  margin: 0.7em 0;
  /* Prevent the insert from being split across columns mid-content. */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
}
.gb-insert--wide {
  column-span: all;
  -webkit-column-span: all;
  margin: 1em 0;
}

/* Single-column block — `:::single` … `:::` markdown shorthand
   compiles to `<div class="gb-single-col">`. Column-spans the body
   so the contained content (prose, images, tables) flows top-to-
   bottom across the full body width instead of being split into
   the page's two columns. Each `[img …]` inside renders centered
   at the full body width via the existing `.gb-insert--image`
   text-align rule. */
.gb-single-col {
  column-span: all;
  -webkit-column-span: all;
  margin: 1em 0;
}

/* Manuscript card — `:::card Title \n body \n :::`. Compact rounded
   box used for compact details like combat styles, ancestries,
   archetypes. Sepia hairline border, faint cream tint background,
   slightly rounded corners so the card reads as a contained unit
   without breaking the parchment feel. break-inside: avoid keeps
   the card whole inside the body's 2-column flow — with 12 cards
   on a Combat Styles page, they naturally split across the two
   columns one row at a time. */
.gb-card {
  border: 1px solid var(--sepia);
  border-radius: 6px;
  background: color-mix(in srgb, var(--paper) 90%, var(--sepia));
  box-shadow: 0 1px 3px rgba(40, 24, 12, 0.08);
  padding: 12px 16px;
  /* Bottom-only spacing: the browser absorbs `margin-top` at column
     breaks, so a card that lands at the top of a new column sits
     flush against the column top while a card at the top of the
     first column keeps its margin. That makes the first cards of
     adjacent columns mis-align by exactly the absorbed margin.
     Putting all the inter-card spacing in margin-bottom keeps the
     top of every column aligned. */
  margin: 0 0 0.9em;
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
}
.gb-card__title {
  font-family: var(--font-display, "IM Fell English SC", Georgia, serif);
  font-size: 1.15em;
  letter-spacing: 0.03em;
  color: var(--ink-strong);
  border-bottom: 1px solid color-mix(in srgb, var(--sepia) 60%, transparent);
  padding-bottom: 4px;
  margin-bottom: 8px;
}
.gb-card__body {
  font-family: var(--font-body-italic, var(--font-body), Georgia, serif);
  font-style: italic;
  color: var(--ink);
  font-size: 0.97em;
  line-height: 1.55;
}
.gb-card__body > p { margin: 0 0 0.4em; }
.gb-card__body > p:last-child { margin-bottom: 0; }
.gb-card__body > p > strong {
  font-style: normal;
  color: var(--ink-strong);
}
/* When the next element after a single-col block is a heading, the
   block's own bottom margin already provides the separation — the
   heading's default top margin would stack on top and feel like an
   awkward gap. Zero it out. Scoped to `.gb-page-body` so the
   specificity beats the page-body's own heading-margin rule
   further down the stylesheet. */
.gb-page-body .gb-single-col + h1,
.gb-page-body .gb-single-col + h2,
.gb-page-body .gb-single-col + h3,
.gb-page-body .gb-single-col + h4,
.gb-page-body .gb-single-col + h5,
.gb-page-body .gb-single-col + h6 {
  margin-top: 0;
}
/* Centered band — column-spans the body so the rule breaks around
   it, but constrained to ~half the body width and centered with
   `margin: 0 auto`. Result: a centered text box with bare space on
   either side; prose flows in the two columns above and below it. */
.gb-insert--center {
  column-span: all;
  -webkit-column-span: all;
  width: clamp(240px, 52%, 440px);
  margin: 1.2em auto;
  break-inside: avoid;
}
/* Centered cross-column heading — `:::head [level] Title` shorthand.
   Spans both body columns and centers the text horizontally, with
   no background or box chrome. The existing per-level h2/h3/h4
   typography (display font, rubric colour, border-bottom rule)
   stays intact so it reads like a chapter / section title. */
.gb-page-body .gb-centered-head {
  column-span: all;
  -webkit-column-span: all;
  text-align: center;
  margin-left: 0;
  margin-right: 0;
}
/* Split heading — `## Left | Right` markdown shorthand emits a
   heading with two child spans. Flex pushes the left text to the
   left edge and the right text to the right edge of the same line.
   Both spans inherit the parent heading's font / size / color so
   the two pieces read as one heading split across the line. */
.gb-page-body .gb-header-split {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1em;
}
.gb-page-body .gb-header-split__left,
.gb-page-body .gb-header-split__right {
  font: inherit;
  color: inherit;
}
.gb-page-body .gb-header-split__right {
  text-align: right;
}

/* Subtext — `-# fine print` markdown shorthand. Smaller font, muted
   ink-faded colour. Sits inline in the column flow as a plain block
   paragraph so it column-breaks naturally and respects two-column
   layout. Margin-top trimmed so a subtext line right after a regular
   paragraph reads as an addendum to that paragraph rather than
   floating in its own visual block. */
.gb-page-body .gb-subtext {
  font-family: var(--font-body, Georgia, serif);
  font-size: 0.82em;
  line-height: 1.45;
  color: var(--ink-faded, color-mix(in srgb, var(--ink) 60%, var(--paper)));
  margin-top: 0.2em;
  margin-bottom: 0.6em;
}
/* Whatever directly follows a centered head shouldn't carry its
   own top-margin — CSS multi-column flow drops the top margin of
   column 2's first element (rendered at the column-top edge) but
   honors column 1's first element's margin, leaving the two
   columns' first content out of alignment by ~1.4em. Zeroing the
   margin-top on `.gb-centered-head + *` puts column 1 back on the
   same baseline as column 2. */
.gb-page-body .gb-centered-head + * {
  margin-top: 0;
}

/* Float modifiers — let an insert sit in the upper-right (or
   upper-left) of its column so prose flows around it. `shape-outside`
   tightens the wrap to the box's border-box; `margin: 0 0 …` zeroes
   the top margin so the insert hugs the top of the column. The
   asymmetric horizontal margin (gutter on the wrap side, none on the
   page edge) keeps the box visually anchored to the page edge. */
/* Float modifiers in the 2-column layout act as column-width blocks:
   the insert fills its containing column edge-to-edge, with prose
   above/below it in the same column flowing naturally and the OTHER
   column unaffected. The float keeps the source-order placement
   predictable; `width: 100%` means there's no horizontal room for
   text to wrap beside the box, so wrap reduces to vertical only. */
.gb-insert--float-right,
.gb-insert--float-left {
  float: right;
  width: 100%;
  margin: 0 0 0.7em;
  clear: both;
}
.gb-insert--float-left {
  float: left;
}
/* Bottom-band: in the plain (scrolling) view this just spans both
   columns at the insert's source-order position — the "anchored to
   the bottom of a fixed page" idea only makes sense in a paginated
   view. The rich-view override below restores the absolute pin when
   the insert lives inside `.gb-book-page-body--single`. */
.gb-insert--bottom-band {
  column-span: all;
  -webkit-column-span: all;
  margin: 1em 0;
}
.gb-book-page-body--single .gb-insert--bottom-band {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* `height: auto` + only-bottom anchoring lets the band grow to fit
     its content; the floor keeps it visually band-like even when the
     codex is short, and the ceiling preserves room for prose above. */
  height: auto;
  min-height: 33.333%;
  max-height: 72%;
  margin: 0;
  box-sizing: border-box;
  overflow: hidden;
  column-span: none;
  -webkit-column-span: none;
}
.gb-insert--bottom-band.gb-insert--text {
  /* Slight font shrink so 4+ paragraphs of codex prose fit the band. */
  font-size: 0.86em;
  line-height: 1.4;
  padding: 10px 14px;
}
.gb-insert--bottom-band .gb-insert__body {
  /* Two columns inside the band so the codex prose reads as a
     compact footer panel rather than a thin single column. */
  column-count: 2;
  column-gap: 18px;
  column-fill: balance;
}
.gb-insert--bottom-band .gb-insert__body p {
  margin: 0 0 0.45em;
  break-inside: avoid;
}

/* Text callout — double-line manuscript border with a small fleuron
   in each corner, slightly darker paper background, optional title
   bar in the display face. */
.gb-insert--text {
  position: relative;
  border: 1px solid var(--sepia);
  box-shadow: inset 0 0 0 3px var(--paper-aged),
              inset 0 0 0 4px var(--sepia-soft);
  background: color-mix(in srgb, var(--paper) 90%, var(--sepia));
  padding: 14px 18px;
  color: var(--ink);
}
.gb-insert--text::before,
.gb-insert--text::after {
  content: "❦";
  position: absolute;
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--sepia);
  line-height: 1;
  background: color-mix(in srgb, var(--paper) 90%, var(--sepia));
  padding: 0 4px;
}
.gb-insert--text::before { top: -7px;    left: 50%; transform: translateX(-50%); }
.gb-insert--text::after  { bottom: -7px; left: 50%; transform: translateX(-50%); }
.gb-insert--text .gb-insert__title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  color: var(--rubric-deep);
  margin: 0 0 0.4em;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--sepia-soft);
}
.gb-insert--text .gb-insert__body > :first-child { margin-top: 0; }
.gb-insert--text .gb-insert__body > :last-child  { margin-bottom: 0; }
/* Inserts inside the rich-book body strip the dropcap when a text
   callout happens to be the first paragraph child — only the page's
   prose-first-paragraph should get one. */
.gb-insert--text p::first-letter {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  float: none;
  padding: 0;
  color: inherit;
}

/* Image inset — sepia frame with a soft drop-shadow, optional
   caption underneath in italic display face. */
.gb-insert--image {
  text-align: center;
  margin: 0.7em 0;
  /* Reset the default figure margins so it fits the column flow. */
  padding: 0;
}
.gb-insert--image img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  border: 1px solid var(--sepia);
  padding: 4px;
  background: var(--paper);
  box-shadow: 0 2px 6px rgba(40, 24, 12, 0.25);
}
.gb-insert--image .gb-insert__caption {
  font-family: var(--font-display-italic, "IM Fell English", Georgia, serif);
  font-style: italic;
  font-size: 0.88em;
  color: var(--ink-faded);
  margin-top: 6px;
  line-height: 1.35;
}

/* ── Layered image stack ─────────────────────────────────────────
   When a `[img ./path]` resolves to a registry entry with the
   `layered-portrait` effect, the runner replaces the single <img>
   with a stack of per-layer <img>s — each absolutely positioned
   over the first one, addressable via [data-layer="<name>"] for
   per-layer animation (transform, filter, etc).

   First layer renders in normal flow so the stack inherits its
   intrinsic size; subsequent layers absolute-position over it.
   `pointer-events: none` on layers keeps clicks/drags from being
   intercepted by them. `will-change: transform` hints that we'll
   animate transforms; lifted off for layers that don't move via
   inline style if needed. */
/* ── Layered image stack — paired sepia + colour ────────────────
   Each animated layer is a `.gb-imgfx-layer-pair` wrapper holding
   two images: a sepia base (always visible) and a colour copy on
   top whose CSS mask hides it until the on-hover paint-in
   animation reveals it. Solid-fill layers (backplate) stay as a
   single `.gb-imgfx-layer.gb-imgfx-layer--fill` div.

   Sepia is applied per-layer rather than on the stack so the
   colour overlay can show full colour without being darkened by
   an ancestor filter. The colour copy's mask is driven by
   `--reveal` (linear sweeps) or per-droplet `--drop-N` custom
   properties (multi-radial splash on the flower). anime.js
   animates these properties on mouse-enter / -leave. */

.gb-imgfx-stack {
  position: relative;
  display: inline-block;
  max-width: 100%;
  line-height: 0;
  /* Sepia border + drop-shadow + paper background live on the
     wrapper, not the layer imgs (where they'd cover everything
     beneath). */
  border: 1px solid var(--sepia);
  padding: 4px;
  background: var(--paper);
  box-shadow: 0 2px 6px rgba(40, 24, 12, 0.25);
  margin: 0 auto;
}

/* Invisible sizer at the head of the stack — its intrinsic
   dimensions dictate the stack's size, since every actual layer
   pair is position:absolute. */
.gb-imgfx-sizer {
  display: block;
  max-width: 100%;
  height: auto;
  visibility: hidden;
  pointer-events: none;
  user-select: none;
  border: none;
  padding: 0;
  background: transparent;
  box-shadow: none;
  margin: 0;
}

/* Pair wrappers + their inner sepia/colour imgs all share the
   "absolutely-fill the stack" geometry. */
.gb-imgfx-stack .gb-imgfx-layer-pair,
.gb-imgfx-stack .gb-imgfx-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* Per-layer node hygiene — strip the .gb-insert--image img
   styling (border, bg, padding, shadow) so layers composite
   cleanly. */
.gb-imgfx-stack .gb-imgfx-layer {
  display: block;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  border: none;
  padding: 0;
  box-shadow: none;
  margin: 0;
}
.gb-imgfx-stack img.gb-imgfx-layer { background: transparent; }

/* Sepia base — always visible, woodcut-toned. Filter order matters:
   saturate(0) first kills the original hue cast (pink flower, tan
   skin, green leaves all flatten to grayscale), THEN sepia(1)
   applies a clean uniform brown tone on the desaturated values.
   Doing sepia before desaturating left residual colour in the
   warmer regions (pink flower / skin coming through orange-red,
   green leaves coming through olive-yellow). */
.gb-imgfx-stack .gb-imgfx-layer--sepia {
  filter: saturate(0) sepia(1) contrast(1.3);
}
/* Foreground sepia — desaturate + sepia only. Contrast and
   brightness adjustments removed pending visual check; can be
   layered back in if the woman reads too bright against the
   darker leaves. */
.gb-imgfx-stack [data-layer="foreground"] > .gb-imgfx-layer--sepia {
  filter: saturate(0) sepia(1);
}

/* Background sepia held at 0.7 opacity so the sunshaft underneath
   (in the gaps + softly through the leaves) reads visibly. Doesn't
   affect the hover-painted state — the colour overlay on top
   becomes fully opaque, covering whatever's beneath. */
.gb-imgfx-stack [data-layer="background"] > .gb-imgfx-layer--sepia {
  opacity: 0.7;
}

/* Colour overlay — full colour, opacity 0 by default; the hover
   timeline tweens opacity → 1 to fade each layer in (and back to
   0 on mouse-leave). Per-layer easing/duration is driven by the
   timeline in guidebook-image-effects.js. */
.gb-imgfx-stack .gb-imgfx-layer--color {
  filter: none;
  opacity: 0;
}

/* ── Sunshaft eye-catcher ───────────────────────────────────────
   A diagonal light beam that sweeps across the image at random
   3-8s intervals, regardless of hover state. Sits at the
   sunshaft layer's z-index (same as backplate, source-ordered
   above it) so it shines through the gaps in the background
   layer but is covered by the leaves' opaque pixels and by the
   foreground (woman). `overflow: hidden` on the wrapper clips
   the beam so it doesn't bleed outside the picture frame. */
.gb-imgfx-stack .gb-imgfx-sunshaft {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}
/* Skew lives on the tilt wrapper so the beam child can animate
   translateX cleanly without anime.js having to juggle a
   compound transform. */
.gb-imgfx-stack .gb-imgfx-sunshaft__tilt {
  position: absolute;
  inset: 0;
  transform: skewX(18deg);
  transform-origin: top left;
}
.gb-imgfx-stack .gb-imgfx-sunshaft__beam {
  position: absolute;
  top: -40%;
  bottom: -40%;
  left: 0;
  /* Single wide soft column — a column of dusty sunlight rather
     than a hard ray. Width is wide enough to read as "a volume of
     illuminated air" and the gradient has gentle intermediate
     stops so the edges fall off slowly. */
  width: 45%;
  background: linear-gradient(
    to right,
    rgba(255, 240, 200, 0)    0%,
    rgba(255, 240, 200, 0.08) 18%,
    rgba(255, 240, 200, 0.22) 35%,
    rgba(255, 240, 200, 0.40) 50%,
    rgba(255, 240, 200, 0.22) 65%,
    rgba(255, 240, 200, 0.08) 82%,
    rgba(255, 240, 200, 0)    100%
  );
  transform: translateX(-100%);
  /* Strong blur turns the gradient into an atmospheric column of
     light + suspended dust rather than a defined beam. */
  filter: blur(10px);
  will-change: transform;
}

.gb-empty {
  font-style: italic;
  color: var(--ink-faded);
  text-align: center;
  padding: 80px 0;
}
.gb-page {
  max-width: 1000px;
  margin: 0 auto;
  position: relative;
}

/* ── GM editor (inline page edit) ───────────────────────────────────
   When a GM clicks "Edit" on a page, the article swaps into edit
   mode: header with title input + body-class selector + save/cancel,
   monospace textarea spanning the page width, and a true-to-render
   live preview below it (uses .gb-page-body so column layout, insert
   modifiers, and dropcaps all match what saves). */
.gb-edit-btn {
  position: absolute;
  top: 0;
  right: 0;
  padding: 4px 12px;
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: var(--paper-aged);
  color: var(--ink-faded);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-quill), border-color var(--dur-1) var(--ease-quill);
}
.gb-edit-btn:hover {
  color: var(--rubric);
  border-color: var(--rubric);
}
.gb-edit-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--sepia);
  flex-wrap: wrap;
}
.gb-edit-title {
  flex: 1 1 320px;
  min-width: 200px;
  font-family: var(--font-display);
  font-size: 30px;
  color: var(--ink-strong);
  padding: 6px 10px;
  background: var(--paper);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
}
.gb-edit-title:focus {
  outline: none;
  border-color: var(--rubric);
}
.gb-edit-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.gb-edit-bodyclass-label {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink-faded);
  display: flex;
  align-items: center;
  gap: 6px;
}
.gb-edit-bodyclass {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 6px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--border-soft);
}
.gb-edit-cancel,
.gb-edit-save {
  padding: 6px 14px;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.04em;
  border: 1px solid var(--border-soft);
  background: var(--paper);
  color: var(--ink-faded);
  cursor: pointer;
  border-radius: 2px;
}
.gb-edit-cancel:hover { color: var(--ink); border-color: var(--ink); }
.gb-edit-save {
  background: var(--rubric);
  color: var(--paper);
  border-color: var(--rubric);
}
.gb-edit-save:hover { background: var(--rubric-deep); border-color: var(--rubric-deep); }
/* Delete is destructive — outlined-red, separated from Save by Cancel
   so an accidental click is unlikely. Hover fills it to signal that
   the next click commits the deletion. */
.gb-edit-delete {
  padding: 6px 14px;
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.04em;
  background: transparent;
  color: var(--rubric-deep);
  border: 1px solid var(--rubric-deep);
  border-radius: 2px;
  cursor: pointer;
  margin-right: 6px;
}
.gb-edit-delete:hover {
  background: var(--rubric-deep);
  color: var(--paper);
}
.gb-edit-save:disabled,
.gb-edit-cancel:disabled,
.gb-edit-delete:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.gb-edit-error {
  margin-bottom: 10px;
  padding: 8px 12px;
  background: color-mix(in srgb, var(--rubric) 14%, var(--paper));
  border: 1px solid var(--rubric);
  color: var(--rubric-deep);
  font-family: var(--font-body);
  font-size: 14px;
}
/* ── GM editor: markdown cheatsheet ──────────────────────────────
   Collapsible <details> reference of every markdown shortcut the
   guidebook supports. Sits above the textarea so the author can
   peek without leaving the editor. Two-column grid inside, with
   <code> on the left and the description on the right. */
.gb-edit-cheatsheet {
  margin: 0 0 12px;
  padding: 0;
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  background: color-mix(in srgb, var(--paper) 94%, var(--sepia));
}
.gb-edit-cheatsheet > summary {
  cursor: pointer;
  padding: 6px 14px;
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-strong);
  list-style: none;
  user-select: none;
}
.gb-edit-cheatsheet > summary::marker,
.gb-edit-cheatsheet > summary::-webkit-details-marker { display: none; }
.gb-edit-cheatsheet > summary::before {
  content: '▸';
  display: inline-block;
  width: 1.2em;
  color: var(--rubric);
  font-size: 11px;
  transition: transform var(--dur-1) var(--ease-quill);
}
.gb-edit-cheatsheet[open] > summary::before {
  transform: rotate(90deg);
}
.gb-edit-cheatsheet > summary:hover { color: var(--rubric); }
.gb-edit-cheatsheet__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px 24px;
  padding: 8px 14px 14px;
  border-top: 1px solid var(--border-soft);
}
.gb-edit-cheatsheet__group {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 12px;
  align-items: baseline;
}
.gb-edit-cheatsheet__heading {
  grid-column: 1 / -1;
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rubric);
  margin-bottom: 2px;
}
.gb-edit-cheatsheet code {
  font-family: var(--font-mono);
  font-size: 12px;
  background: color-mix(in srgb, var(--paper) 78%, var(--sepia));
  color: var(--ink-strong);
  padding: 1px 6px;
  border-radius: 2px;
  white-space: nowrap;
}
.gb-edit-cheatsheet kbd {
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  padding: 1px 5px;
  border-radius: 3px;
  color: var(--ink-strong);
  white-space: nowrap;
}
.gb-edit-cheatsheet span {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
}
.gb-edit-cheatsheet span code,
.gb-edit-cheatsheet span kbd {
  font-size: 11px;
  padding: 0 4px;
}
@media (max-width: 720px) {
  .gb-edit-cheatsheet__grid { grid-template-columns: 1fr; }
}
.gb-edit-textarea {
  display: block;
  width: 100%;
  min-height: 420px;
  padding: 14px 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  resize: vertical;
  box-sizing: border-box;
}
.gb-edit-textarea:focus {
  outline: none;
  border-color: var(--rubric);
}

/* Spellcheck side panel — sits between the textarea and the live
   preview in the GM editor. Server-fed list of unknown words with
   click-to-replace suggestion chips. Browser native red squiggles
   handle inline visual feedback inside the textarea; this panel is
   the actionable surface. Compact, low-vibe — slides under the
   textarea, not a modal. */
.gb-edit-spellcheck {
  margin: 10px 0 14px;
  padding: 8px 12px;
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  background: color-mix(in srgb, var(--paper) 96%, var(--sepia));
  font-family: var(--font-body, Georgia, serif);
  font-size: 13px;
  color: var(--ink);
}
.gb-edit-spellcheck__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.gb-edit-spellcheck__label {
  font-family: var(--font-display, Georgia, serif);
  letter-spacing: 0.03em;
  color: var(--ink-strong);
}
.gb-edit-spellcheck__loading {
  font-style: italic;
  color: var(--ink-faded);
  margin-left: 4px;
}
.gb-edit-spellcheck__count {
  font-size: 0.88em;
  color: var(--ink-faded);
}
.gb-edit-spellcheck__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 220px;
  overflow-y: auto;
}
.gb-edit-spellcheck__item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  padding: 4px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border-soft) 50%, transparent);
}
.gb-edit-spellcheck__item:last-child { border-bottom: none; }
.gb-edit-spellcheck__word {
  font-family: var(--font-mono);
  font-size: inherit;
  color: var(--rubric);
  min-width: 120px;
  flex: 0 0 auto;
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  border-bottom: 1px dashed transparent;
  transition: border-color 100ms ease, color 100ms ease;
}
.gb-edit-spellcheck__word:hover {
  color: var(--rubric-deep, var(--rubric));
  border-bottom-color: var(--rubric);
}
.gb-edit-spellcheck__actions {
  margin-left: auto;
  display: inline-flex;
  flex: 0 0 auto;
  gap: 4px;
  align-items: baseline;
}
.gb-edit-spellcheck__action {
  font: inherit;
  font-size: 11px;
  padding: 2px 7px;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--ink-faded) 50%, transparent);
  border-radius: 3px;
  color: var(--ink-faded);
  cursor: pointer;
  transition: background-color 100ms ease, border-color 100ms ease, color 100ms ease;
}
.gb-edit-spellcheck__action:hover {
  background: color-mix(in srgb, var(--paper) 80%, var(--sepia));
  border-color: var(--ink);
  color: var(--ink);
}
.gb-edit-spellcheck__action--accept {
  color: var(--verdigris-deep, var(--verdigris));
  border-color: color-mix(in srgb, var(--verdigris) 60%, transparent);
}
.gb-edit-spellcheck__action--accept:hover {
  background: color-mix(in srgb, var(--paper) 75%, var(--verdigris));
  border-color: var(--verdigris-deep, var(--verdigris));
  color: var(--verdigris-deep, var(--verdigris));
}
.gb-edit-spellcheck__suggestions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}
.gb-edit-spellcheck__suggest {
  font: inherit;
  font-size: 12px;
  padding: 2px 8px;
  background: color-mix(in srgb, var(--paper) 88%, var(--sepia));
  border: 1px solid var(--sepia);
  border-radius: 3px;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 100ms ease, border-color 100ms ease;
}
.gb-edit-spellcheck__suggest:hover {
  background: color-mix(in srgb, var(--paper) 70%, var(--verdigris));
  border-color: var(--verdigris-deep, var(--verdigris));
}
.gb-edit-spellcheck__no-suggest {
  font-style: italic;
  color: var(--ink-faded);
  font-size: 12px;
}
.gb-edit-spellcheck__item--replacement .gb-edit-spellcheck__word {
  color: var(--rubric-deep, var(--rubric));
}
.gb-edit-spellcheck__tag {
  flex: 0 0 auto;
  font-size: 11px;
  font-style: italic;
  padding: 1px 5px;
  border: 1px solid var(--rubric);
  border-radius: 3px;
  color: var(--rubric);
  background: color-mix(in srgb, var(--rubric) 8%, transparent);
}
.gb-edit-preview-label {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faded);
  margin: 24px 0 10px;
  padding-bottom: 4px;
  border-bottom: 1px dashed var(--border-soft);
}
.gb-edit-preview {
  /* Inherits .gb-page-body styling (columns, dropcap, headings) so
     the preview is true-to-rendered. The dashed border distinguishes
     it from the saved-version view. */
  border: 1px dashed var(--border-soft);
  padding: 16px;
  background: color-mix(in srgb, var(--paper) 96%, var(--sepia));
}
.gb-page-title {
  font-family: var(--font-display);
  font-size: 36px;
  letter-spacing: 0.03em;
  color: var(--ink-strong);
  margin: 0 0 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--sepia);
}
/* Two-column newspaper-style flow for the plain-view page body.
   `column-fill: balance` (the default) balances column heights, so
   short pages don't leave column 2 mostly empty. Floated inserts
   (gb-insert--float-right / --float-left) float within their column;
   `gb-insert--wide` / `gb-insert--bottom-band` use `column-span: all`
   to break out and span the full body width. */
.gb-page-body {
  font-family: var(--font-body-italic, "IM Fell English", Georgia, serif);
  font-size: 18px;
  line-height: 1.7;
  color: var(--ink);
  column-count: 2;
  column-gap: 36px;
  column-rule: 1px solid var(--border-soft);
}
/* When the body has at least one `===` section break, it's
   rendered as a stack of `.gb-page-body__section` divs each with
   its own 2-column flow. Drop the body's own columns so the
   sections stack vertically and don't get re-balanced. */
.gb-page-body:has(> .gb-page-body__section) {
  column-count: 1;
  column-gap: 0;
  column-rule: none;
}
.gb-page-body > .gb-page-body__section {
  column-count: 2;
  column-gap: 36px;
  column-rule: 1px solid var(--border-soft);
}
.gb-page-body > .gb-page-body__section + .gb-page-body__section {
  margin-top: 1.6em;
  padding-top: 1.6em;
  border-top: 1px solid var(--border-soft);
}
/* First heading inside a section shouldn't carry the default
   header top-margin — otherwise it sits below the top of the
   section's column 2 and the two columns look misaligned at the
   top edge. Same rule for an unsectioned body's first-child
   heading (rare, but harmless). */
.gb-page-body > h2:first-child,
.gb-page-body > h3:first-child,
.gb-page-body > h4:first-child,
.gb-page-body > .gb-page-body__section > h2:first-child,
.gb-page-body > .gb-page-body__section > h3:first-child,
.gb-page-body > .gb-page-body__section > h4:first-child {
  margin-top: 0;
}
/* Same fix for a table sitting at the top of a section. The
   `.gb-table-wrap` carries a 1.2em margin-top that stacks on top
   of the section's own 1.6em padding-top, producing an awkward
   gap. Zero the table's margin-top when it's the first child. */
.gb-page-body > .gb-table-wrap:first-child,
.gb-page-body > .gb-page-body__section > .gb-table-wrap:first-child {
  margin-top: 0;
}
.gb-page-body h2,
.gb-page-body h3,
.gb-page-body h4 {
  font-family: var(--font-display);
  color: var(--ink-strong);
  letter-spacing: 0.04em;
  margin: 1.4em 0 0.45em;
  scroll-margin-top: 24px;
  /* Keep a heading with the paragraph that follows it within a column. */
  break-after: avoid;
}
.gb-page-body h2 { font-size: 26px; border-bottom: 1px solid var(--border-soft); padding-bottom: 4px; }
.gb-page-body h3 { font-size: 21px; color: var(--rubric-deep); }
.gb-page-body h4 { font-size: 17px; color: var(--sepia); text-transform: uppercase; letter-spacing: 0.07em; }
.gb-page-body p {
  margin: 0 0 0.95em;
}
.gb-page-body > p:first-of-type::first-letter,
.gb-page-body > .gb-page-body__section:first-child > p:first-of-type::first-letter {
  font-family: var(--font-display);
  font-size: 5.6em;
  line-height: 0.78;
  float: left;
  padding: 0.06em 0.14em 0 0;
  color: var(--rubric);
}
.gb-page-body ul,
.gb-page-body ol {
  /* Indent shrunk 75% from the original ~60px (1.2em margin + ~40px
     browser padding). Now ~15px total via a single padding-left so
     list markers (roman numerals, bullets) sit close to the body
     text instead of hanging way out in the column gutter. */
  margin: 0.4em 0 1em 0;
  padding-left: 1em;
}
/* Manuscript-style ordered lists — upper roman numerals at top
   level, lower roman one level deep. Matches the period feel of the
   IM Fell display face. Lists that explicitly start from 0 fall
   back to decimal markers (roman numerals have no representation
   for zero); use `0. First item` in markdown to opt in. */
.gb-page-body ol {
  list-style-type: upper-roman;
}
.gb-page-body ol ol {
  list-style-type: lower-roman;
}
.gb-page-body ol[start="0"] {
  list-style-type: decimal;
}
.gb-page-body ol[start="0"] ol {
  list-style-type: decimal;
}
.gb-page-body code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: color-mix(in srgb, var(--sepia) 12%, transparent);
  padding: 1px 5px;
  border-radius: 2px;
}
/* ── Search highlight (medieval rubrication wash) ── */
.gb-hl {
  background: color-mix(in srgb, var(--rubric) 22%, transparent);
  color: var(--rubric-deep);
  padding: 0 2px;
  border-radius: 2px;
  /* Preserve the underlying weight; this is a wash, not a swap-out. */
  font-style: inherit;
  font-weight: inherit;
}

/* ─── Mobile two-column carousel ──────────────────────────────────
   Mobile reimagining of the desktop two-column panel layout.
   Absolute-positioned columns, each with its own transform (no
   translating outer "track"). Inactive column sits off-screen at
   ±100%; active sits at 0. Only one column visible at a time; swipe /
   pager dots / edge-hold-during-drag switch between them.

   Why per-column transforms vs one translating track: animating a
   transform on a touched element's ancestor fires touchcancel on
   mobile browsers, killing any in-flight drag. With per-column
   transforms, the `.mobile-carousel--drag-swap` modifier (added
   while a SortableJS drag is in flight) overrides the inactive
   column's off-screen position to 0 — the SOURCE column never moves
   during the swap, so the touched element's ancestor chain stays
   put and the drag survives. Only the INCOMING column animates
   (from off-screen into view). See MobileTwoColumnCarousel in
   panels.jsx for the JS half. */
.mobile-carousel {
  position: relative;
  /* Inherit the same viewport cap the desktop grid uses, so columns
     scroll internally rather than the page. */
  height: calc(100vh - 56px - var(--hand-dock-h, 0px));
  overflow: hidden;
}
.mobile-carousel__col {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Browser handles vertical pan inside the column; horizontal-touch
     bubbles to the carousel-root swipe handler. */
  touch-action: pan-y;
  transition: transform 280ms cubic-bezier(0.32, 0.08, 0.24, 1);
  will-change: transform;
  z-index: 1;
}
.mobile-carousel__col--left  { transform: translate3d(-100%, 0, 0); }
.mobile-carousel__col--right { transform: translate3d(100%, 0, 0); }
.mobile-carousel__col.is-active {
  transform: translate3d(0, 0, 0);
  z-index: 2;
}

/* During a SortableJS drag: the previously-active column stays put
   at translate(0) instead of sliding off-screen. The incoming column
   still animates from its default off-screen position into view, but
   on top (higher z-index). After the drag ends, the modifier class
   is removed and the now-inactive column animates to its default
   off-screen position — the slide-off serves as visual cleanup. */
.mobile-carousel--drag-swap .mobile-carousel__col:not(.is-active) {
  transform: translate3d(0, 0, 0);
}

/* Pager — two dots pinned above the hand dock. Tapping a dot jumps
   to that column. `position: fixed` + `bottom: calc(...)` so the
   pager sits just above the hand dock regardless of how the dock
   sizes itself (its actual rendered height can exceed
   --hand-dock-h, which is only a layout-reservation hint). z-index
   beats the hand dock's 200 so the dots are always tappable. */
.mobile-carousel__pager {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--hand-dock-h, 0px) + 6px);
  display: flex;
  justify-content: center;
  gap: 10px;
  pointer-events: none;  /* let touches pass through the empty space */
  z-index: 250;
}
.mobile-carousel__dot {
  pointer-events: auto;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
  /* The dot itself is rendered via a ::before so we keep a 28px
     touch target around an 8px visual dot. */
}
.mobile-carousel__dot::before {
  content: '';
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--fg-muted) 60%, transparent);
  transition: background var(--dur-1) var(--ease-quill), transform var(--dur-1) var(--ease-quill);
}
.mobile-carousel__dot.is-active::before {
  background: var(--rubric);
  transform: scale(1.2);
}



/* ── Attack-tap toast (0 marked targets) ──────────────────────── */
.cs-attacks-toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--hand-dock-h, 0px) + 24px);
  transform: translateX(-50%);
  z-index: 800;
  max-width: min(440px, 92vw);
  padding: 12px 18px;
  font-family: var(--font-body-italic, "IM Fell English", Georgia, serif);
  font-size: 14px;
  line-height: 1.45;
  text-align: center;
  color: var(--paper);
  background: var(--ink-strong, #2a1d0e);
  border: 1px solid var(--rubric);
  border-radius: 4px;
  box-shadow: var(--shadow-3, 0 6px 18px rgba(0,0,0,0.35));
  animation: cs-attacks-toast-in 180ms ease-out;
}
@keyframes cs-attacks-toast-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ── Attack target picker modal (2+ marked targets) ────────────
   Opened from AttacksGrid via onChooseAttackTarget. Lists the
   targets the player has marked, with a tappable button per
   target. Tap → fires triggerAttack with the chosen target. */
.attack-picker-overlay { z-index: 950; }
.attack-picker {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-3, 0 6px 18px rgba(0,0,0,0.35));
  width: min(420px, 94vw);
  max-height: min(620px, 92vh);
  display: flex;
  flex-direction: column;
}
.attack-picker__head {
  padding: 16px 18px 10px;
  border-bottom: 1px solid var(--border-soft);
}
.attack-picker__title {
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 0.04em;
  color: var(--rubric);
}
.attack-picker__sub {
  font-family: var(--font-body-italic, var(--font-body));
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
  margin-top: 4px;
}
.attack-picker__list {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.attack-picker__option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--fg);
  text-align: left;
  min-height: 48px; /* touch target */
}
.attack-picker__option:hover,
.attack-picker__option:focus-visible {
  border-color: var(--rubric);
  background: var(--paper-aged);
}
.attack-picker__swatch {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,0.25);
}
.attack-picker__name {
  flex: 1 1 auto;
  font-weight: 500;
}
.attack-picker__kind {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.attack-picker__cancel {
  margin: 10px 14px 14px;
  padding: 10px 14px;
  font-family: var(--font-display);
  font-size: 13px;
  background: var(--paper);
  color: var(--fg-muted);
  border: 1px solid var(--border);
  border-radius: 3px;
  cursor: pointer;
  min-height: 44px;
}
.attack-picker__cancel:hover {
  color: var(--rubric);
  border-color: var(--rubric);
}

/* ── XP Award Tray (GM only) ───────────────────────────────────────
   Slide-in panel on the left edge of the table view, vertically
   centered. Closed state shows a thin vertical handle with the
   running pool tally; open state slides out a ~360px panel that
   floats above the left column. */
.xp-tray {
  position: fixed;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 70;
  display: flex;
  align-items: stretch;
  font-family: var(--font-body, Georgia, serif);
  color: var(--ink);
  pointer-events: none;
}
.xp-tray > * { pointer-events: auto; }
.xp-tray__handle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 28px;
  padding: 14px 4px;
  background: color-mix(in srgb, var(--paper) 92%, var(--sepia));
  border: 1px solid var(--sepia);
  border-left: none;
  border-radius: 0 6px 6px 0;
  box-shadow: 2px 2px 6px rgba(40, 24, 12, 0.18);
  cursor: pointer;
  font-family: var(--font-display, Georgia, serif);
  letter-spacing: 0.08em;
  color: var(--ink-strong);
}
.xp-tray__handle-label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 11px;
}
.xp-tray__handle-pool {
  background: var(--ink-strong);
  color: var(--paper);
  border: 1px solid var(--sepia);
  border-radius: 10px;
  font-size: 10px;
  padding: 2px 5px;
  min-width: 22px;
  text-align: center;
  font-family: var(--font-display);
  letter-spacing: 0.02em;
}
.xp-tray__handle-chevron { font-size: 14px; color: var(--ink-faded); }
.xp-tray__panel {
  /* Always rendered so the slide transition has something to animate.
     Closed state: scaled to width 0 with opacity 0; open state: full
     width fades in. The inner wrapper holds the actual content so the
     panel can transition width without resizing the layout
     mid-animation. */
  display: flex;
  align-items: stretch;
  max-height: 80vh;
  overflow: hidden;
  background: var(--paper);
  border: 1px solid var(--sepia);
  border-left: none;
  border-radius: 0 6px 6px 0;
  box-shadow: 4px 4px 12px rgba(40, 24, 12, 0.24);
  width: 360px;
  opacity: 1;
  transform: translateX(0);
  transition: width 280ms cubic-bezier(0.32, 0.72, 0, 1),
              opacity 200ms ease,
              transform 280ms cubic-bezier(0.32, 0.72, 0, 1);
  pointer-events: auto;
}
.xp-tray:not(.xp-tray--open) .xp-tray__panel {
  width: 0;
  opacity: 0;
  transform: translateX(-12px);
  border-color: transparent;
  box-shadow: none;
  pointer-events: none;
}
.xp-tray__panel-inner {
  width: 360px;
  flex: 0 0 360px;
  overflow-y: auto;
  padding: 12px 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.xp-tray__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: 6px;
}
.xp-tray__title {
  font-family: var(--font-display, Georgia, serif);
  font-size: 14px;
  letter-spacing: 0.05em;
  color: var(--ink-strong);
}
.xp-tray__pool { font-size: 12px; color: var(--ink-faded); }
.xp-tray__pool strong { color: var(--ink-strong); font-size: 14px; font-family: var(--font-display); }
.xp-tray__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.xp-tray__row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  padding: 5px 0;
  border-bottom: 1px dotted color-mix(in srgb, var(--border-soft) 50%, transparent);
  font-size: 12px;
}
.xp-tray__row-main { display: flex; align-items: center; gap: 8px; }
.xp-tray__row-label { flex: 1 1 auto; min-width: 0; color: var(--ink); }
.xp-tray__row-xp {
  flex: 0 0 auto; color: var(--sepia); font-family: var(--font-display);
  font-size: 11px; min-width: 28px; text-align: right;
}
.xp-tray__award {
  appearance: none; background: transparent;
  border: 1px solid color-mix(in srgb, var(--verdigris) 50%, transparent);
  border-radius: 3px; font: inherit; font-size: 11px; padding: 2px 8px;
  color: var(--verdigris-deep, var(--verdigris)); cursor: pointer;
  transition: background-color 100ms ease, border-color 100ms ease;
}
.xp-tray__award:hover {
  background: color-mix(in srgb, var(--paper) 70%, var(--verdigris));
  border-color: var(--verdigris-deep, var(--verdigris));
}
.xp-tray__picker { display: inline-flex; flex-wrap: wrap; gap: 3px; align-items: baseline; }
.xp-tray__picker-name {
  font: inherit; font-size: 11px; padding: 2px 6px;
  background: color-mix(in srgb, var(--paper) 80%, var(--sepia));
  border: 1px solid var(--sepia); border-radius: 3px; color: var(--ink); cursor: pointer;
}
.xp-tray__picker-name:hover {
  background: color-mix(in srgb, var(--paper) 60%, var(--verdigris));
  border-color: var(--verdigris-deep, var(--verdigris));
}
.xp-tray__picker-cancel {
  font: inherit; font-size: 14px; line-height: 1; background: transparent;
  border: none; color: var(--ink-faded); cursor: pointer; padding: 0 4px;
}
.xp-tray__picker-empty { font-size: 11px; font-style: italic; color: var(--ink-faded); }
.xp-tray__auto-hint {
  flex: 0 0 auto; font-size: 14px; line-height: 1;
  color: color-mix(in srgb, var(--verdigris) 60%, transparent);
}
.xp-tray__auto-hint--active {
  font-size: 10px; color: var(--verdigris-deep, var(--verdigris));
}

/* Auto-detect hint sub-row — verdigris-tinted (oxidised copper) to
   read as an actionable positive prompt, distinct from the rubric
   revoke affordance. */
.xp-tray__detect {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--paper) 84%, var(--verdigris));
  border: 1px solid color-mix(in srgb, var(--verdigris) 45%, transparent);
}
.xp-tray__detect-text {
  flex: 1 1 auto; min-width: 0;
  font-size: 11px; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.xp-tray__detect-text strong { font-family: var(--font-display); }
.xp-tray__detect-detail { color: var(--ink-faded); }
.xp-tray__detect-award {
  flex: 0 0 auto;
  appearance: none; font: inherit; font-size: 11px; padding: 2px 8px;
  background: var(--verdigris-deep, var(--verdigris));
  border: 1px solid var(--verdigris-deep, var(--verdigris));
  border-radius: 3px; color: var(--paper); cursor: pointer;
  transition: filter 100ms ease;
}
.xp-tray__detect-award:hover { filter: brightness(1.15); }
.xp-tray__detect-dismiss {
  flex: 0 0 auto;
  font: inherit; font-size: 14px; line-height: 1; background: transparent;
  border: none; color: var(--ink-faded); cursor: pointer; padding: 0 2px;
}
.xp-tray__detect-dismiss:hover { color: var(--rubric); }
.xp-tray__custom {
  display: flex; gap: 5px; padding-top: 4px;
  border-top: 1px solid var(--border-soft);
}
.xp-tray__custom-label {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 12px; padding: 4px 6px;
  background: var(--paper); border: 1px solid var(--sepia); border-radius: 3px; color: var(--ink);
}
.xp-tray__custom-xp {
  width: 50px; font: inherit; font-size: 12px; padding: 4px 6px;
  background: var(--paper); border: 1px solid var(--sepia); border-radius: 3px;
  text-align: right; color: var(--ink);
}
.xp-tray__custom-add {
  font: inherit; font-size: 16px; line-height: 1; padding: 0 10px;
  background: var(--verdigris); color: var(--paper);
  border: 1px solid var(--verdigris-deep, var(--verdigris));
  border-radius: 3px; cursor: pointer;
}
.xp-tray__recent { border-top: 1px solid var(--border-soft); padding-top: 6px; }
.xp-tray__recent-heading {
  font-family: var(--font-display); font-size: 11px;
  letter-spacing: 0.05em; color: var(--ink-faded); margin-bottom: 4px;
}
.xp-tray__recent-list {
  list-style: none; margin: 0; padding: 0; font-size: 11px;
  display: flex; flex-direction: column; gap: 2px;
}
.xp-tray__recent-row {
  display: flex; gap: 6px; color: var(--ink-faded);
  /* Right-click reveals the GM revoke affordance; cursor hint signals
     a context menu is available without grabbing keyboard focus. */
  cursor: context-menu;
  padding: 1px 4px;
  border-radius: 2px;
  transition: background-color 80ms ease, color 80ms ease;
}
.xp-tray__recent-row:hover {
  background: color-mix(in srgb, var(--paper) 70%, var(--rubric));
  color: var(--ink);
}
.xp-tray__recent-row:hover .xp-tray__recent-xp { color: var(--rubric); }
.xp-tray__recent-row:hover::after {
  content: '⤬';
  flex: 0 0 auto;
  font-size: 10px;
  color: var(--rubric);
  align-self: center;
  margin-left: -2px;
  opacity: 0.9;
}
.xp-tray__recent-name { flex: 0 0 auto; font-family: var(--font-display); color: var(--ink); min-width: 70px; }
.xp-tray__recent-label { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.xp-tray__recent-xp { flex: 0 0 auto; color: var(--sepia); font-family: var(--font-display); }
.xp-tray__footer { display: flex; flex-direction: column; gap: 6px; padding-top: 6px; border-top: 1px solid var(--border-soft); }
.xp-tray__commit {
  font: inherit; font-size: 12px; padding: 6px 10px;
  background: var(--ink-strong); color: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 3px; cursor: pointer;
  font-family: var(--font-display); letter-spacing: 0.04em;
  box-shadow: 0 1px 0 color-mix(in srgb, var(--paper) 50%, transparent) inset,
              0 2px 4px rgba(40, 24, 12, 0.24);
}
.xp-tray__commit:hover:not(:disabled) {
  background: var(--ink);
  border-color: var(--verdigris-deep);
}
.xp-tray__commit:disabled { opacity: 0.4; cursor: not-allowed; }
.xp-tray__commit-toast {
  font-size: 11px; color: var(--verdigris-deep, var(--verdigris));
  padding: 4px 6px; background: color-mix(in srgb, var(--paper) 80%, var(--verdigris));
  border: 1px solid color-mix(in srgb, var(--verdigris) 50%, transparent);
  border-radius: 3px;
}
.xp-tray__commit-toast--error {
  color: var(--rubric);
  background: color-mix(in srgb, var(--paper) 80%, var(--rubric));
  border-color: color-mix(in srgb, var(--rubric) 50%, transparent);
}

/* Centered milestone toast — pops over the game board for every
   milestone awarded at the table. Driven by anime.js for entry +
   dwell + exit (see XpToastCard in xp-tray.jsx); the CSS here is
   only the static look. Multiple concurrent awards stack vertically
   with a small gap. */
.xp-toast-stack-center {
  /* Centered both axes over the game board, above every other UI
     surface (the hand dock sits at z-index 200, panels at 800,
     modals at 1000–1100, expanded overlays at 9000). 9500 keeps us
     clear of all of them without colliding with absolute-top
     debug/dev overlays. translate(-50%, -50%) re-centers because
     the inner toasts can vary in size between the entry pop and
     resting state. */
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 9500;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  pointer-events: none;
  width: min(520px, 92vw);
}
.xp-toast-centered {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 18px 24px;
  /* Aged parchment leaf — warm cream-to-deep-cream gradient, like a
     page lifted from a hand-bound book. */
  background: linear-gradient(
    140deg,
    var(--paper) 0%,
    var(--paper-aged) 60%,
    var(--paper-deep) 100%
  );
  /* Thick iron-gall border = the page's leather edge. Inner sepia
     hairline + warm brown drop-shadow keep it grounded in the
     manuscript palette. */
  border: 3px solid var(--ink);
  border-radius: 8px;
  box-shadow:
    0 8px 28px rgba(40, 24, 12, 0.36),
    0 0 0 1px var(--sepia) inset,
    0 1px 0 color-mix(in srgb, var(--paper) 50%, var(--paper-deep)) inset;
  font-family: var(--font-body, Georgia, serif);
  color: var(--ink);
  position: relative;
  will-change: transform, opacity;
}
.xp-toast-centered--for-me {
  /* Recipient variant — deeper leather-bound feel: ink-strong border,
     verdigris hairline inset (oxidised copper trim on a leather case),
     and a slightly richer paper grain. No more rubric anywhere. */
  border-color: var(--ink-strong);
  background: linear-gradient(
    140deg,
    var(--paper-aged) 0%,
    var(--paper-deep) 60%,
    color-mix(in srgb, var(--paper-deep) 70%, var(--sepia)) 100%
  );
  box-shadow:
    0 10px 32px rgba(40, 24, 12, 0.46),
    0 0 0 1px var(--verdigris-deep) inset,
    0 0 0 2px var(--paper) inset;
}
.xp-toast-centered__icon {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  /* Icons render as iron-gall ink strokes — the same warm brown as
     body text — via CSS mask-image. Matches the woodcut / manuscript
     illustration style. */
  background-color: var(--ink);
  -webkit-mask-image: var(--icon-url);
  mask-image: var(--icon-url);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.xp-toast-centered--for-me .xp-toast-centered__icon {
  background-color: var(--ink-strong);
  filter: drop-shadow(0 2px 4px rgba(40, 24, 12, 0.4));
}
.xp-toast-centered__body {
  flex: 1 1 auto;
  min-width: 0;
}
.xp-toast-centered__eyebrow {
  font-family: var(--font-display, Georgia, serif);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faded);
  margin-bottom: 4px;
}
.xp-toast-centered__label {
  font-family: var(--font-display, Georgia, serif);
  font-size: 18px;
  letter-spacing: 0.03em;
  color: var(--ink-strong);
  line-height: 1.3;
}
.xp-toast-centered__label strong {
  /* Names + milestone titles read as inked emphasis — slightly
     heavier than body text, no colour shift away from the parchment
     palette. */
  color: var(--ink-strong);
  font-weight: 600;
}
.xp-toast-centered__sub {
  font-size: 12px;
  color: var(--ink-faded);
  margin-top: 6px;
  font-style: italic;
}

/* Per-row icon in the player milestone tray — same mask-image trick
   as the toast, smaller scale. */
.xp-tray__milestone-icon {
  width: 18px;
  height: 18px;
  /* Iron-gall ink mask, matching the toast palette — small woodcut-style
     glyph rendered in the same warm brown as body text. */
  background-color: var(--ink);
  -webkit-mask-image: var(--icon-url);
  mask-image: var(--icon-url);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  align-self: center;
}

/* Player-side milestone tray (left edge). Same handle/panel shape as
   the GM tray but with a bell + pip instead of XP-pool indicator. */
.xp-tray__handle--player {
  background: color-mix(in srgb, var(--paper) 92%, var(--verdigris));
}
.xp-tray__handle-bell {
  font-size: 18px;
  color: var(--ink-strong);
  line-height: 1;
}
.xp-tray__handle-pip {
  background: var(--verdigris-deep, var(--verdigris));
  color: var(--paper);
  border-radius: 10px;
  font-size: 10px;
  padding: 2px 5px;
  min-width: 22px;
  text-align: center;
  font-family: var(--font-display);
  animation: xp-pip-pop 280ms ease-out;
}
@keyframes xp-pip-pop {
  from { transform: scale(0.3); }
  to   { transform: scale(1); }
}
.xp-tray__panel--player { width: 380px; }
.xp-tray:not(.xp-tray--open) .xp-tray__panel--player { width: 0; }
.xp-tray__panel--player .xp-tray__panel-inner { width: 380px; flex: 0 0 380px; }
.xp-tray__empty {
  font-size: 12px;
  font-style: italic;
  color: var(--ink-faded);
  padding: 12px 4px;
}
.xp-tray__milestones {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.xp-tray__milestone {
  display: grid;
  grid-template-columns: 22px 90px 1fr auto auto;
  gap: 8px;
  align-items: baseline;
  padding: 6px 4px;
  font-size: 12px;
  border-bottom: 1px dotted color-mix(in srgb, var(--border-soft) 50%, transparent);
}
.xp-tray__milestone:last-child { border-bottom: none; }
.xp-tray__milestone--mine {
  background: color-mix(in srgb, var(--paper) 80%, var(--verdigris));
  border-radius: 3px;
  padding-left: 8px;
  padding-right: 8px;
}
.xp-tray__milestone-name {
  font-family: var(--font-display);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.xp-tray__milestone-label {
  color: var(--ink-faded);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.xp-tray__milestone-xp {
  color: var(--sepia);
  font-family: var(--font-display);
}
.xp-tray__milestone-time {
  color: var(--ink-faded);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}
.xp-toast__icon { font-size: 24px; color: var(--rubric); flex: 0 0 auto; }
.xp-toast__body { flex: 1 1 auto; min-width: 0; }
.xp-toast__label {
  font-family: var(--font-display); font-size: 14px;
  color: var(--ink-strong); letter-spacing: 0.03em;
}
.xp-toast__sub { font-size: 11px; color: var(--ink-faded); margin-top: 2px; }

/* ══ Class Abilities Tree ═══════════════════════════════════════════
   The manuscript-style ability tree, rehomed from class-tree.html into
   the guide book. Rendered by ClassAbilityTree (class-abilities-tree.jsx)
   on each Chapter V "<Class> Abilities" page. Unlike the standalone
   folio, this has no parchment image, no fixed width, and no pan canvas
   — the guide-book page supplies the frame and the scroll. Per-class
   identity lives in the accent palette + watermark sigil.
   ─────────────────────────────────────────────────────────────────── */

/* The class-tree page body opts out of the guide book's two-column
   newspaper flow — the tree owns the full page width and lays out its
   own archetype columns. Defined here, after `.gb-page-body`'s base
   `column-count: 2` rule, so it wins the cascade at equal specificity. */
.gb-page-body--class-tree {
  column-count: 1;
  column-gap: 0;
  column-rule: none;
}

.cat-tree {
  position: relative;
  isolation: isolate;
  /* Dark ink used for text-strokes + box hairlines (see legibility
     block below) — keeps light archetype palettes readable on paper. */
  --cat-stroke: #14110a;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  color: var(--ink);
  font-family: var(--font-body);
  /* Farrago parchment-portrait sheet behind the whole tree; the
     artwork's painted frame sits at the edges, so the content is
     inset past it with generous padding. */
  background-image: url('/assets/farrago-page-portrait.webp');
  background-size: 100% 100%;
  background-repeat: no-repeat;
  padding: 54px 56px 60px;
}
.cat-tree-loading {
  padding: 80px 24px;
  text-align: center;
  font-family: var(--font-display-italic);
  font-style: italic;
  font-size: var(--fs-400);
  color: var(--fg-muted);
}

/* Watermark — large faded class sigil behind everything. */
.cat-watermark {
  position: absolute;
  top: 44%; left: 50%;
  width: 66%;
  max-width: 540px;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) rotate(var(--cls-watermark-rot, 0deg));
  pointer-events: none;
  opacity: var(--cls-watermark-opacity, 0.06);
  color: var(--cls-accent);
  z-index: 0;
}
.cat-watermark--img { object-fit: contain; }
.cat-tree[data-class="Druid"]   .cat-watermark--img { opacity: 0.16; }
.cat-tree[data-class="Theurge"] .cat-watermark--img { opacity: 0.22; }

/* ── Frontispiece — sigil + ornament rule (class name = page title) ── */
.cat-frontispiece {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: 4px 0 18px;
}
.cat-sigil {
  width: 96px;
  height: 96px;
  margin: 0 auto 6px;
  color: var(--cls-accent, var(--ink));
  filter: drop-shadow(0 2px 4px rgba(67, 41, 23, 0.22));
}
.cat-sigil--img { display: block; object-fit: contain; }
/* Mask-rendered sigil/watermark — a game-icons.net SVG used as a CSS
   mask, painted with the class accent. `--cls-sigil` is set per class. */
.cat-sigil--mask,
.cat-watermark--mask {
  -webkit-mask-image: var(--cls-sigil);
  mask-image: var(--cls-sigil);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  background-color: var(--cls-accent);
}
.cat-rule-line {
  margin: 10px auto 0;
  width: 320px;
  max-width: 70%;
  height: 14px;
  position: relative;
}
.cat-rule-line::before,
.cat-rule-line::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 42%;
  height: 1px;
  background: var(--cls-accent);
}
.cat-rule-line::before { left: 0; }
.cat-rule-line::after  { right: 0; }
.cat-rule-line__glyph {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--cls-accent);
  background: var(--paper);
  padding: 0 10px;
}

/* ── Archetype rubrics — three colour-coded display names ──────────── */
.cat-rubrics {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  align-items: end;
  margin: 6px 0 4px;
  padding: 4px 0 18px;
}
.cat-rubrics::after {
  content: "";
  position: absolute;
  left: 8%; right: 8%;
  bottom: 4px;
  border-bottom: 1px solid var(--cls-accent);
  opacity: 0.40;
}
.cat-rubric {
  text-align: center;
  font-family: var(--font-display);
  letter-spacing: 0.03em;
  line-height: 1.05;
  padding: 0 4px;
  overflow-wrap: break-word;
  background: linear-gradient(135deg,
    var(--arch, var(--cls-accent)) 0%,
    color-mix(in srgb, var(--arch, var(--cls-accent)) 45%, #fff5dc 55%) 22%,
    var(--arch, var(--cls-accent)) 50%,
    color-mix(in srgb, var(--arch, var(--cls-accent)) 50%, #fff5dc 50%) 78%,
    var(--arch, var(--cls-accent)) 100%);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  -webkit-text-stroke: 1.2px #14110a;
  filter: drop-shadow(0 1px 1.5px rgba(67, 41, 23, 0.30));
}
.cat-rubric[data-arch-pos="left"]   { font-size: var(--fs-400); }
.cat-rubric[data-arch-pos="center"] { font-size: var(--fs-500); }
.cat-rubric[data-arch-pos="right"]  { font-size: var(--fs-400); }

/* ── Tiers wrapper — carries the dotted central trunk ──────────────── */
.cat-tiers {
  position: relative;
  z-index: 2;
}
.cat-tiers::before {
  content: "";
  position: absolute;
  top: 0; bottom: 40px;
  left: 50%;
  width: 0;
  border-left: 2px dotted var(--cls-accent);
  opacity: 0.42;
  z-index: 0;
  pointer-events: none;
}
.cat-tier { margin: 22px 0 0; position: relative; }

/* Tier-grant tag — bordered, centred, sits on the trunk. */
.cat-tier-grant {
  position: relative;
  z-index: 3;
  width: max-content;
  max-width: 86%;
  margin: 0 auto;
  padding: 9px 24px;
  background: var(--paper-aged);
  border: 1.5px solid var(--cls-accent);
  border-radius: 4px;
  box-shadow: var(--shadow-1);
  text-align: center;
  font-family: var(--font-display);
  letter-spacing: 0.05em;
  display: flex;
  align-items: center;
  gap: 14px;
}
.cat-tier-grant__roman {
  font-size: var(--fs-300);
  color: var(--cls-accent);
  padding-right: 12px;
  border-right: 1px solid var(--cls-accent);
  opacity: 0.80;
}
.cat-tier-grant__facet {
  font-size: var(--fs-400);
  color: var(--ink-strong);
}
.cat-tier-grant__eyebrow {
  display: block;
  font-size: 9px;
  letter-spacing: 0.20em;
  color: var(--cls-accent);
  opacity: 0.75;
  text-transform: uppercase;
  margin-bottom: 2px;
}
.cat-tier-grant--none { border-style: dashed; opacity: 0.6; }
.cat-tier-grant--none .cat-tier-grant__facet {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
}

/* ── Level block — banner + dotted rule + 3 archetype cells ────────── */
.cat-level {
  position: relative;
  margin: 22px 0 6px;
  padding-top: 52px;
}
.cat-level::before {
  content: "";
  position: absolute;
  top: 26px;
  left: 12%; right: 12%;
  border-top: 2px dotted var(--cls-accent);
  opacity: 0.50;
  z-index: 1;
}
.cat-level__banner {
  position: absolute;
  top: 0; left: 50%;
  transform: translateX(-50%);
  background: var(--paper-aged);
  border: 1.5px solid var(--cls-accent);
  border-radius: 4px;
  padding: 5px 22px;
  font-family: var(--font-display);
  font-size: var(--fs-400);
  color: var(--cls-accent);
  letter-spacing: 0.06em;
  z-index: 2;
  box-shadow: var(--shadow-1);
  white-space: nowrap;
}

.cat-arch-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px;
  position: relative;
  z-index: 2;
}
.cat-arch-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-height: 40px;
}
.cat-arch-cell::before {
  content: "";
  position: absolute;
  top: -26px; left: 50%;
  width: 0; height: 26px;
  border-left: 2px dotted var(--arch, var(--cls-accent));
  opacity: 0.55;
}
/* Per-cell archetype label — only shown when the grid stacks (mobile). */
.cat-arch-cell__label {
  display: none;
  font-family: var(--font-display);
  font-size: var(--fs-200);
  letter-spacing: 0.08em;
  color: var(--arch, var(--cls-accent));
  text-transform: uppercase;
}
.cat-level-empty {
  font-family: var(--font-display);
  font-size: var(--fs-400);
  color: var(--arch, var(--cls-accent));
  opacity: 0.30;
  padding: 2px 0;
}

/* ── Ability tag — archetype-coloured; hover tip + tap-to-expand ───── */
.cat-ability {
  display: inline-block;
  max-width: 100%;
  padding: 7px 16px;
  background: var(--paper-aged);
  border: 1px solid var(--arch, var(--cls-accent));
  border-radius: 3px;
  color: var(--arch, var(--ink-strong));
  text-align: center;
  box-shadow: var(--shadow-1);
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease-quill),
              box-shadow var(--dur-1) var(--ease-quill);
}
.cat-ability:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-2);
}
.cat-ability:focus-visible {
  outline: 2px solid var(--arch, var(--cls-accent));
  outline-offset: 2px;
}
.cat-ability__name {
  font-family: var(--font-display);
  font-size: var(--fs-300);
  letter-spacing: 0.03em;
  line-height: 1.18;
  overflow-wrap: break-word;
}
.cat-ability--gratis .cat-ability__name::before {
  content: "★ ";
  color: var(--sepia);
  font-size: 10px;
  letter-spacing: 0.4em;
}
.cat-ability--open {
  display: block;
  background: var(--paper);
  box-shadow: var(--shadow-2);
}
.cat-ability__detail {
  display: block;
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px solid color-mix(in srgb, var(--arch, var(--cls-accent)) 40%, transparent);
  text-align: left;
}
/* ── Tooltip / tap-expand body — ability + facet detail ──────────────
   Used inside the table-view TooltipHint on hover, and inside
   .cat-ability__detail on tap. Field set mirrors the character sheet's
   ability card: cost · the check · tags · effects. */
.cat-tip {
  display: flex;
  flex-direction: column;
  gap: 5px;
  max-width: 300px;
  overflow-wrap: break-word;
}
.cat-tip__cost {
  font-family: var(--font-mono, monospace);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.cat-tip__check {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--ink-strong);
}
.cat-tip__sep { color: var(--fg-muted); margin: 0 5px; }
.cat-tip__vs {
  font-family: var(--font-display-italic);
  font-style: italic;
  color: var(--fg-muted);
  margin: 0 5px;
}
.cat-tip__tags {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--sepia);
}
.cat-tip__effects {
  font-family: var(--font-body);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink);
}
.cat-tip__effects p { margin: 0 0 0.45em; }
.cat-tip__effects p:last-child { margin-bottom: 0; }
.cat-tip__effects ul { margin: 0.2em 0 0.35em; padding-left: 1.15em; }
.cat-tip__effects li { margin: 0.1em 0; }

/* Missing-ability highlight + hand-drawn circle. */
.cat-ability-wrap {
  position: relative;
  display: inline-block;
  max-width: 100%;
}
.cat-ability-wrap .cat-ability { position: relative; z-index: 1; }
.cat-missing-circle {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% + 28px);
  height: calc(100% + 22px);
  pointer-events: none;
  z-index: 0;
  filter: url(#cat-hand-drawn-circle);
  overflow: visible;
}
.cat-missing-circle ellipse {
  fill: none;
  stroke: var(--rubric);
  stroke-width: 2.6;
  opacity: 0.9;
}
.cat-ability--missing {
  background: rgba(165, 42, 42, 0.10);
  border-color: var(--rubric);
  color: var(--rubric);
}

/* ── L10 capstone — single centred tag anchoring the trunk ─────────── */
.cat-capstone {
  position: relative;
  margin: 16px 0 6px;
  padding-top: 52px;
  display: flex;
  justify-content: center;
  z-index: 2;
}
.cat-capstone::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: 0;
  border-left: 2px dotted var(--arch, var(--cls-accent));
  opacity: 0.55;
  z-index: 1;
}
.cat-capstone .cat-ability {
  position: relative;
  z-index: 2;
  background: var(--paper-deep);
  border-width: 2px;
  border-color: var(--arch, var(--cls-accent));
  color: var(--arch, var(--cls-accent));
  box-shadow: 0 4px 14px var(--cls-accent-shadow, rgba(67, 41, 23, 0.20));
}
.cat-capstone .cat-ability__name { font-size: var(--fs-500); letter-spacing: 0.06em; }
.cat-capstone__eyebrow {
  position: absolute;
  top: 20px; left: 50%;
  transform: translateX(-50%);
  background: var(--paper-aged);
  padding: 1px 10px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--arch, var(--cls-accent));
  opacity: 0.85;
  border: 1px solid var(--arch, var(--cls-accent));
  border-radius: 999px;
  z-index: 3;
}

.cat-closer {
  text-align: center;
  margin-top: 34px;
  font-size: 22px;
  color: var(--cls-accent);
  opacity: 0.7;
  position: relative;
  z-index: 2;
}

/* ── Responsive — archetype grid stacks on the mobile guide book ───── */
.gb-root--mobile .cat-rubrics { display: none; }
.gb-root--mobile .cat-arch-row { grid-template-columns: 1fr; gap: 14px; }
.gb-root--mobile .cat-arch-cell__label { display: block; }
.gb-root--mobile .cat-tiers::before,
.gb-root--mobile .cat-level::before,
.gb-root--mobile .cat-arch-cell::before,
.gb-root--mobile .cat-capstone::before { display: none; }
.gb-root--mobile .cat-level { padding-top: 44px; }
.gb-root--mobile .cat-arch-cell { align-items: stretch; }
.gb-root--mobile .cat-ability { display: block; }

/* ── Per-class accent palettes ─────────────────────────────────────── */
.cat-tree[data-class="Bard"] {
  --cls-accent: #2f6a4d; --cls-accent-2: #2a3a6e;
  --cls-accent-shadow: rgba(42, 58, 110, 0.26);
  --cls-watermark-opacity: 0.06; --cls-watermark-rot: -4deg;
  --cls-arch-left: #2a3a6e; --cls-arch-center: #2f6a4d; --cls-arch-right: #8a6f4a;
  --cls-sigil: url('/assets/game-icons.net/delapouite/harp.svg');
}
.cat-tree[data-class="Berserker"] {
  --cls-accent: #a02525; --cls-accent-2: #2a1d10;
  --cls-accent-shadow: rgba(160, 37, 37, 0.36);
  --cls-watermark-opacity: 0.07; --cls-watermark-rot: 6deg;
  --cls-arch-left: #2a1d10; --cls-arch-center: #a02525; --cls-arch-right: #8a4a2a;
  --cls-sigil: url('/assets/game-icons.net/lorc/fist.svg');
}
.cat-tree[data-class="Cleric"] {
  --cls-accent: #b08027; --cls-accent-2: #a52a2a;
  --cls-accent-shadow: rgba(176, 128, 39, 0.34);
  --cls-watermark-opacity: 0.07; --cls-watermark-rot: 0deg;
  --cls-arch-left: #a52a2a; --cls-arch-center: #b08027; --cls-arch-right: #2a3a6e;
  --cls-sigil: url('/assets/game-icons.net/lorc/cartwheel.svg');
}
.cat-tree[data-class="Druid"] {
  --cls-accent: #2f5a3a; --cls-accent-2: #6b5a30;
  --cls-accent-shadow: rgba(47, 90, 58, 0.32);
  --cls-watermark-opacity: 0.07; --cls-watermark-rot: -3deg;
  --cls-arch-left: #6b5a30; --cls-arch-center: #2f5a3a; --cls-arch-right: #8a4a2a;
}
.cat-tree[data-class="Knight"] {
  --cls-accent: #25336c; --cls-accent-2: #b08027;
  --cls-accent-shadow: rgba(37, 51, 108, 0.32);
  --cls-watermark-opacity: 0.06; --cls-watermark-rot: 0deg;
  --cls-arch-left: #b08027; --cls-arch-center: #25336c; --cls-arch-right: #a02525;
  --cls-sigil: url('/assets/game-icons.net/delapouite/two-handed-sword.svg');
}
.cat-tree[data-class="Magister"] {
  --cls-accent: #1c2750; --cls-accent-2: #b08027;
  --cls-accent-shadow: rgba(28, 39, 80, 0.36);
  --cls-watermark-opacity: 0.075; --cls-watermark-rot: -8deg;
  --cls-arch-left: #b08027; --cls-arch-center: #1c2750; --cls-arch-right: #4a7556;
  --cls-sigil: url('/assets/game-icons.net/delapouite/heptagram.svg');
}
.cat-tree[data-class="Ranger"] {
  --cls-accent: #3f6a4f; --cls-accent-2: #7a5b30;
  --cls-accent-shadow: rgba(122, 91, 48, 0.32);
  --cls-watermark-opacity: 0.07; --cls-watermark-rot: -2deg;
  --cls-arch-left: #7a5b30; --cls-arch-center: #3f6a4f; --cls-arch-right: #8a3a2a;
  --cls-sigil: url('/assets/game-icons.net/delapouite/bow-arrow.svg');
}
.cat-tree[data-class="Rogue"] {
  --cls-accent: #1f150b; --cls-accent-2: #a02525;
  --cls-accent-shadow: rgba(31, 21, 11, 0.40);
  --cls-watermark-opacity: 0.06; --cls-watermark-rot: 8deg;
  --cls-arch-left: #a02525; --cls-arch-center: #1f150b; --cls-arch-right: #4a7556;
  --cls-sigil: url('/assets/game-icons.net/lorc/thrown-daggers.svg');
}
.cat-tree[data-class="Theurge"] {
  --cls-accent: #14080a; --cls-accent-2: #6a1f1f;
  --cls-accent-shadow: rgba(20, 8, 10, 0.50);
  --cls-watermark-opacity: 0.10; --cls-watermark-rot: -6deg;
  --cls-arch-left: #6a1f1f; --cls-arch-center: #14080a; --cls-arch-right: #5a4a36;
}
.cat-tree[data-class="Theurge"] .cat-ability { background: #d6cbae; }
.cat-tree[data-class="Warrior"] {
  --cls-accent: #7a5b30; --cls-accent-2: #2a3a6e;
  --cls-accent-shadow: rgba(122, 91, 48, 0.32);
  --cls-watermark-opacity: 0.06; --cls-watermark-rot: 0deg;
  --cls-arch-left: #2a3a6e; --cls-arch-center: #7a5b30; --cls-arch-right: #a02525;
  --cls-sigil: url('/assets/game-icons.net/lorc/bordered-shield.svg');
}

/* ── Dark stroke for legibility ──────────────────────────────────────
   Light archetype palettes (Bard's Creator gold, Cleric's gilt, etc.)
   sit close to the parchment. A dark text-stroke painted BEHIND the
   fill (paint-order: stroke fill) haloes the glyphs without muddying
   them, and a dark hairline ring on each box separates it from the
   page. Both are no-ops where the colour is already dark. */
.cat-ability__name,
.cat-level__banner,
.cat-tier-grant__roman,
.cat-tier-grant__facet {
  -webkit-text-stroke: 0.6px var(--cat-stroke);
  paint-order: stroke fill;
}
.cat-capstone .cat-ability__name {
  -webkit-text-stroke: 0.8px var(--cat-stroke);
  paint-order: stroke fill;
}
.cat-ability,
.cat-tier-grant,
.cat-level__banner {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--cat-stroke) 55%, transparent),
              var(--shadow-1);
}
.cat-ability:hover,
.cat-ability--open {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--cat-stroke) 55%, transparent),
              var(--shadow-2);
}
.cat-capstone .cat-ability {
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--cat-stroke) 62%, transparent),
              0 4px 14px var(--cls-accent-shadow, rgba(67, 41, 23, 0.20));
}

/* ── Foebook ────────────────────────────────────────────────────────────────
   GM-only creature reference. Mirrors the guidebook's modal-overlay shell
   but renders typed creature pages instead of markdown. Iron-gall ink on
   parchment palette, matching the rest of the app. All `.fb-*` classes
   are scoped here so they can't leak. */

.fb-modal-overlay {
  align-items: stretch;
  justify-content: stretch;
  padding: 24px;
}
.fb-frame {
  background: var(--paper);
  color: var(--ink);
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  height: 100%;
  border-radius: 6px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: var(--font-body);
}
.fb-root {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}
.fb-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--paper-aged, var(--paper));
  flex: 0 0 auto;
}
.fb-toolbar-spacer { flex: 1; }
.fb-saving-flash {
  color: var(--verdigris-deep);
  font-style: italic;
  font-size: 13px;
}
.fb-layout {
  display: flex;
  flex: 1;
  min-height: 0;
}
.fb-sidebar {
  width: 260px;
  flex: 0 0 260px;
  border-right: 1px solid var(--border-soft);
  background: var(--paper-aged, color-mix(in srgb, var(--paper) 92%, var(--ink) 8%));
  overflow-y: auto;
  padding: 14px 12px;
}
.fb-sidebar-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 12px;
}
.fb-sidebar-title {
  font-family: var(--font-display);
  font-size: 20px;
  margin: 0;
  color: var(--ink-strong, var(--ink));
}
.fb-chapter { margin-bottom: 14px; }
.fb-chapter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: 2px;
  margin-bottom: 4px;
}
.fb-chapter-title {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sepia);
}
.fb-page-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.fb-page-list li {
  display: flex;
  align-items: center;
  gap: 4px;
}
.fb-page-link {
  flex: 1;
  background: none;
  border: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  padding: 4px 8px;
  border-radius: 3px;
  cursor: pointer;
}
.fb-page-link:hover {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}
.fb-page-link.is-active {
  background: color-mix(in srgb, var(--rubric) 10%, transparent);
  color: var(--ink-strong, var(--ink));
  font-weight: 600;
}
.fb-main {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 28px 36px 60px;
}

/* Creature page (read-only) */
.fb-creature-page {
  max-width: 920px;
  margin: 0 auto;
}
.fb-creature-header {
  display: flex;
  align-items: flex-start;
  gap: 24px;
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-soft);
}
.fb-creature-titles {
  flex: 1;
  min-width: 0;
}
.fb-creature-name {
  font-family: var(--font-display);
  font-size: 38px;
  line-height: 1.1;
  margin: 0 0 4px;
  color: var(--ink-strong, var(--ink));
}
.fb-creature-type {
  font-family: var(--font-display-italic, var(--font-body-italic));
  font-size: 17px;
  color: var(--sepia);
}
.fb-creature-image {
  width: 160px;
  height: 160px;
  flex: 0 0 160px;
  object-fit: contain;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
}
.fb-creature-desc {
  font-size: 15px;
  line-height: 1.55;
  margin: 0 0 18px;
}
.fb-creature-vitals {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.fb-vital {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 3px;
  background: color-mix(in srgb, var(--paper) 92%, var(--ink) 8%);
}
.fb-vital-label {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sepia);
}
.fb-vital-value {
  font-size: 17px;
  font-weight: 600;
  color: var(--ink-strong, var(--ink));
}
.fb-creature-section { margin-bottom: 18px; }
.fb-section-title {
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sepia);
  margin: 0 0 8px;
  border-bottom: 1px dashed var(--border-soft);
  padding-bottom: 2px;
}

.fb-tag-row { display: flex; flex-wrap: wrap; gap: 6px; }
.fb-tag {
  display: inline-block;
  padding: 2px 9px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  font-size: 12px;
  color: var(--ink);
  background: color-mix(in srgb, var(--paper) 88%, var(--ink) 12%);
}

/* Stat index */
.fb-stat-index {
  margin-top: 24px;
  padding-top: 14px;
  border-top: 1px solid var(--border-soft);
}
.fb-attr-index, .fb-skill-index, .fb-facet-index, .fb-ability-index {
  margin-bottom: 14px;
}
.fb-attr-index {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
}
.fb-attr-cluster {
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  padding: 6px 10px;
  background: color-mix(in srgb, var(--paper) 95%, var(--ink) 5%);
}
.fb-attr-cluster-name {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sepia);
  margin-bottom: 4px;
}
.fb-attr-cluster-items {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fb-attr-item, .fb-skill-item {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
}
.fb-attr-name, .fb-skill-name { color: var(--ink); }
.fb-attr-value, .fb-skill-value {
  font-weight: 600;
  color: var(--ink-strong, var(--ink));
  font-variant-numeric: tabular-nums;
}
.fb-skill-index {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 4px 14px;
}
.fb-facet-index { display: flex; flex-wrap: wrap; gap: 6px; }
.fb-facet-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border: 1px solid var(--verdigris-deep);
  border-radius: 12px;
  font-size: 12px;
  background: color-mix(in srgb, var(--verdigris) 14%, var(--paper) 86%);
  color: var(--ink);
}
.fb-facet-class {
  color: var(--sepia);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.fb-facet-name { font-weight: 600; }
.fb-ability-index { display: flex; flex-direction: column; gap: 10px; }
.fb-ability-card {
  border: 1px solid var(--border);
  border-left: 3px solid var(--rubric);
  border-radius: 3px;
  padding: 8px 12px;
  background: color-mix(in srgb, var(--paper) 96%, var(--ink) 4%);
}
.fb-ability-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 2px;
}
.fb-ability-name {
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--ink-strong, var(--ink));
}
.fb-ability-cost {
  font-size: 12px;
  color: var(--rubric);
  font-weight: 600;
}
.fb-ability-tags { font-size: 11px; color: var(--sepia); }
.fb-ability-pull { font-size: 12px; color: var(--ink); margin-top: 3px; font-style: italic; }
.fb-ability-desc { font-size: 13px; line-height: 1.5; margin-top: 4px; }

/* Editor */
.fb-editor { max-width: 920px; margin: 0 auto; }
.fb-editor-toolbar {
  display: flex; gap: 8px; margin-bottom: 16px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-soft);
}
.fb-editor-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 16px;
}
.fb-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
}
.fb-field > span {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sepia);
}
.fb-field input,
.fb-field textarea {
  font: inherit;
  font-size: 14px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 5px 8px;
  color: var(--ink);
}
.fb-field input:focus,
.fb-field textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--verdigris-deep) 50%, transparent);
  outline-offset: -1px;
}
.fb-field--full { grid-column: 1 / -1; }
.fb-editor-section {
  margin: 22px 0;
  padding-top: 12px;
  border-top: 1px dashed var(--border-soft);
}
.fb-editor-section h3 {
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sepia);
  margin: 0 0 10px;
}
.fb-multiselect {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.fb-chip-toggle {
  background: var(--paper);
  border: 1px solid var(--border);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 14px;
  cursor: pointer;
}
.fb-chip-toggle.is-on {
  background: color-mix(in srgb, var(--rubric) 18%, var(--paper) 82%);
  border-color: var(--rubric);
  color: var(--ink-strong, var(--ink));
}
.fb-empty-note {
  font-size: 12px;
  font-style: italic;
  color: var(--fg-muted, var(--sepia));
}

.fb-attr-edit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
}
.fb-attr-edit-cluster {
  border: 1px solid var(--border-soft);
  border-radius: 3px;
  padding: 6px 10px;
  background: color-mix(in srgb, var(--paper) 95%, var(--ink) 5%);
}
.fb-attr-edit-cluster-name {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sepia);
  margin-bottom: 6px;
}
.fb-attr-edit-row, .fb-skill-edit-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  margin-bottom: 3px;
}
.fb-attr-edit-row > span, .fb-skill-edit-row > span { color: var(--ink); }
.fb-attr-edit-row input, .fb-skill-edit-row input {
  width: 60px;
  text-align: right;
  font: inherit;
  font-size: 13px;
  padding: 3px 6px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--paper);
  color: var(--ink);
}
.fb-skill-edit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 4px 14px;
}

.fb-facet-edit-list, .fb-ability-edit-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}
.fb-facet-row, .fb-ability-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid var(--border-soft);
  border-radius: 3px;
  background: color-mix(in srgb, var(--paper) 96%, var(--ink) 4%);
}
.fb-facet-row-name, .fb-ability-row-name {
  flex: 1;
  font-size: 13px;
}

/* Buttons */
.fb-btn {
  background: var(--paper);
  border: 1px solid var(--border);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  padding: 5px 12px;
  border-radius: 3px;
  cursor: pointer;
}
.fb-btn:hover {
  background: color-mix(in srgb, var(--ink) 6%, var(--paper) 94%);
}
.fb-btn--primary {
  background: var(--verdigris-deep);
  color: var(--paper);
  border-color: var(--verdigris-deep);
}
.fb-btn--primary:hover {
  background: color-mix(in srgb, var(--verdigris-deep) 88%, var(--ink) 12%);
}
.fb-btn--active {
  background: color-mix(in srgb, var(--rubric) 20%, var(--paper) 80%);
  border-color: var(--rubric);
}
.fb-btn--small { font-size: 11px; padding: 2px 8px; }
.fb-btn--tiny  { font-size: 11px; padding: 1px 6px; }
.fb-btn--danger {
  color: var(--rubric);
  border-color: color-mix(in srgb, var(--rubric) 60%, var(--border));
}
.fb-btn--danger:hover {
  background: color-mix(in srgb, var(--rubric) 12%, var(--paper) 88%);
}
.fb-btn--disabled { opacity: 0.5; cursor: not-allowed; }

/* Sub-modals */
.fb-modal {
  background: var(--paper);
  color: var(--ink);
  max-width: 520px;
  width: 92%;
  max-height: 88vh;
  border-radius: 6px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  font-family: var(--font-body);
}
.fb-modal--wide { max-width: 880px; }
.fb-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-soft);
}
.fb-modal-head h3 {
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sepia);
  margin: 0;
}
.fb-modal-body { padding: 12px 14px; overflow-y: auto; flex: 1; }
.fb-modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 10px 14px; border-top: 1px solid var(--border-soft);
}
.fb-modal-filter {
  width: 100%;
  font: inherit;
  font-size: 14px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--paper);
  color: var(--ink);
  margin-bottom: 10px;
}
.fb-facet-picker-list {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.fb-facet-picker-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border: 1px solid var(--border-soft);
  border-radius: 3px;
}
.fb-facet-picker-meta {
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.fb-facet-tier {
  font-size: 10px;
  color: var(--sepia);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 0 5px;
}

/* Misc shells */
.fb-loading,
.fb-locked-card,
.fb-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 60px 20px;
  text-align: center;
}
.fb-locked-card h2,
.fb-empty h2 {
  font-family: var(--font-display);
  margin: 0;
}

.fb-image-preview-wrap {
  display: flex;
  align-items: flex-start;
  padding: 8px;
  border: 1px dashed var(--border-soft);
  border-radius: 3px;
  background: color-mix(in srgb, var(--paper) 96%, var(--ink) 4%);
}

/* Foebook — narrative (non-foe) page styling */

.fb-chapter-add {
  display: flex;
  gap: 4px;
}

.fb-page-link.is-narrative {
  font-style: italic;
}
.fb-page-glyph {
  color: var(--sepia);
  font-style: normal;
  margin-right: 1px;
}

.fb-narrative-page {
  max-width: 760px;
  margin: 0 auto;
}
.fb-narrative-header {
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-soft);
}
.fb-narrative-title {
  font-family: var(--font-display);
  font-size: 32px;
  line-height: 1.1;
  margin: 0;
  color: var(--ink-strong, var(--ink));
}
.fb-narrative-para {
  font-size: 15px;
  line-height: 1.65;
  margin: 0 0 14px;
}
.fb-narrative-para:first-of-type::first-letter {
  font-family: var(--font-display);
  font-size: 2.6em;
  float: left;
  line-height: 0.9;
  margin: 0.04em 0.08em 0 0;
  color: var(--ink-strong, var(--ink));
}

.fb-narrative-textarea {
  font-family: var(--font-body);
  line-height: 1.55;
  min-height: 320px;
  resize: vertical;
}

/* Foebook drag-reorder ghost (SortableJS) */
.fb-page-item--ghost {
  opacity: 0.45;
  background: color-mix(in srgb, var(--rubric) 14%, transparent);
}
.fb-page-item {
  list-style: none;
}

/* ── Storyteller Session ─────────────────────────────────────────────── */
.st-grid {
  display: grid;
  grid-template-columns: 27.5% 45% 27.5%;
  gap: 12px;
  /* Top nav (56px) + the hand dock. --hand-dock-h tracks the dock's
     collapse/expand state (280px ↔ 40px), so expanding the card tray
     shrinks this grid — the feed (flex:1) gives up the space and the
     composer stays parked above the dock instead of hidden behind it. */
  height: calc(100vh - 56px - var(--hand-dock-h, 0px));
  min-height: 0;
  padding: 12px;
  box-sizing: border-box;
}
.st-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; }
.st-col--left, .st-col--right {
  overflow-y: auto;
  transition: opacity var(--dur-2, 240ms) var(--ease-quill, ease);
}
.st-col--left.is-faded { opacity: 0.22; }
.st-col--left.is-faded:hover { opacity: 1; }
.st-col--left.is-active { opacity: 1; }
.st-col--right { opacity: 0.18; }
.st-col--right:hover { opacity: 1; }
.st-panel-wrap { min-height: 0; }

/* Storyteller columns host a real .panel-container (cross-column drag), so
   trim its desktop padding for the narrower columns. */
.st-col .st-panel-container { padding: 6px 4px 20px; }

/* The narrow left/right columns can't give the Game Board the full-height
   flex parent it expects (.game-board-stack { height: 100% }), so the canvas
   overflowed and overlapped the panels above/below. Clamp it to a definite
   height that scrolls internally; the header's minimize control collapses it
   entirely when the board isn't needed. */
.st-col .game-board-stack { height: auto; }
.st-col .game-board-stack > .mapcanvas {
  flex: 0 0 auto;
  height: 544px; /* ~4 desktop squares (4×56px) taller than the prior 320px */
  max-height: 60vh; /* raised from 42vh so the taller board isn't clamped on a 1080p desktop */
  min-height: 0;
}
/* When the Storyteller Game Board is EXPANDED, the panel becomes a fixed
   full-screen overlay (.panel-outer--expanded) but stays a DOM descendant of
   .st-col — so the narrow-column clamp above keeps pinning the board to ~320px
   (a thin wedge) and the footer's "No active Call." fills the empty remainder.
   Undo the clamp for the expanded state so the board fills the overlay; the
   footer falls back to its natural thin bar at the bottom. */
.st-col .panel-outer--expanded .game-board-stack { height: 100%; }
.st-col .panel-outer--expanded .game-board-stack > .mapcanvas {
  flex: 1 1 auto;
  height: auto;
  max-height: none;
  min-height: 320px;
}

.st-col--center { overflow: hidden; }
.st-feed {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 14px;
  border: 1px solid var(--border-soft);
  background: var(--surface);
}
.st-msg { margin-bottom: 14px; }
.st-msg--chat { margin-bottom: 8px; }
.st-msg-meta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 1px; }
.st-msg-time { font-family: var(--font-mono); font-size: 10px; color: var(--fg-muted); }
.st-msg-name { font-family: var(--font-display); font-size: 16px; color: var(--fg-strong); cursor: default; }
.st-msg-body { font-size: 18px; }
.st-msg--chat .st-msg-body { color: var(--fg); }
/* Chat lines render their own simple markdown (NOT the guidebook page
   compiler). Paragraphs are tightly spaced so a multi-line post stays
   visually one post — guidebook paragraph spacing is wider than the gap
   between separate messages, which made one post look like two. */
.st-chat-md {
  font-family: var(--font-body-italic);
  line-height: 1.6;
}
.st-chat-md p { margin: 0 0 0.3em; }
.st-chat-md p:last-child { margin-bottom: 0; }
.st-chat-md ul, .st-chat-md ol { margin: 0.2em 0 0.3em; padding-left: 1.4em; }
.st-msg--story { padding: 4px 0; }
.st-story-header {
  text-align: center;
  font-family: var(--font-display);
  font-size: 16px;
  letter-spacing: 0.14em;
  color: var(--sepia);
  margin: 2px 0 6px;
}
.st-msg--system { opacity: 0.85; }
.st-msg--emote { margin-bottom: 8px; display: flex; align-items: baseline; gap: 8px; }
.st-emote {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 18px;
  color: var(--fg-muted);
}

/* Edit affordance — hover-revealed, only on the author's own posts. */
.st-msg-edit-btn {
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-muted);
  background: none;
  border: none;
  padding: 0 2px;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-1, 140ms) ease;
}
.st-msg:hover .st-msg-edit-btn { opacity: 1; }
.st-msg-edit-btn:hover { color: var(--verdigris-deep); }
.st-msg-edited {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 10px;
  color: var(--fg-muted);
}

/* In-place post editor */
.st-msg--editing { margin-bottom: 10px; }
.st-edit-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 60px;
  resize: vertical;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.4;
  padding: 6px 8px;
  border: 1px solid var(--verdigris-deep);
  background: var(--surface);
  color: var(--fg);
}
.st-edit-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 4px; }
.st-edit-save, .st-edit-cancel {
  font-family: var(--font-display);
  font-size: 11px;
  padding: 3px 12px;
  border: 1px solid var(--border);
  border-radius: 2px;
  cursor: pointer;
}
.st-edit-save { background: var(--verdigris-deep); color: var(--paper); }
.st-edit-save:hover { background: var(--verdigris); }
.st-edit-cancel { background: var(--surface); color: var(--fg); }

.st-typing-line { min-height: 16px; padding: 2px 14px; display: flex; gap: 10px; flex-wrap: wrap; }
.st-typing {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 11px;
  color: var(--fg-muted);
}

.st-composer { display: flex; flex-direction: column; gap: 6px; padding-top: 8px; flex: 0 0 auto; }
/* Grow with content (Toast UI height:auto). Capped at ~half the column so a
   long post can't swallow the whole feed; beyond that the editor scrolls.
   Since the feed above is flex:1 (min-height:0), the composer growing pushes
   its top upward and shrinks the feed — like expanding the Hand tray. */
.st-composer-editor { min-height: 96px; max-height: 48vh; overflow-y: auto; }
.st-composer-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 90px;
  resize: vertical;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.4;
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
}
.st-composer-actions { display: flex; justify-content: flex-end; }
.st-send-btn {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.08em;
  padding: 6px 18px;
  border: 1px solid var(--border);
  background: var(--verdigris-deep);
  color: var(--paper);
  cursor: pointer;
  border-radius: 2px;
}
.st-send-btn:hover { background: var(--verdigris); }

/* Theme the Toast UI composer — its default palette is stark white (#fff
   editing area) + cool blue-grey (#f7f9fc toolbar), which clashes with the
   iron-gall parchment look. Scoped under .st-composer so it beats Toast's
   single-class rules without !important. */
.st-composer .toastui-editor-defaultUI {
  border: 1px solid var(--border);
  border-radius: 2px;
}
.st-composer .toastui-editor-defaultUI-toolbar {
  background-color: var(--paper-deep);
  border-bottom: 1px solid var(--border-soft);
}
.st-composer .toastui-editor-defaultUI-toolbar button:not(:disabled):hover {
  background-color: var(--surface);
}
.st-composer .toastui-editor-main,
.st-composer .toastui-editor-ww-container {
  background-color: var(--surface);
}
.st-composer .toastui-editor-contents,
.st-composer .toastui-editor-contents p,
.st-composer .toastui-editor-ww-container .ProseMirror {
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 14px;
}
.st-composer .toastui-editor-ww-container .ProseMirror .placeholder {
  color: var(--fg-muted);
}

/* Story messages reuse guidebook prose styling but never show a drop-cap. */
.gb-page-body.story-md--no-dropcap > p:first-of-type::first-letter,
.gb-page-body.story-md--no-dropcap > .gb-page-body__section:first-child > p:first-of-type::first-letter {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  float: none;
  padding: 0;
  color: inherit;
}

/* The story feed is a single column. The guidebook body defaults to a
   2-column book layout (.gb-page-body + its __section children both set
   column-count: 2) — distracting for a chat/narration stream, so force
   one column for everything inside the feed. */
.st-feed .gb-page-body,
.st-feed .gb-page-body > .gb-page-body__section {
  column-count: 1;
  column-gap: 0;
  column-rule: none;
}

/* ── Character sheet: Biography tab ──────────────────────────────────── */
.cs-bio-tab { display: flex; flex-direction: column; gap: 18px; padding: 16px; }
.cs-bio-section { display: flex; flex-direction: column; gap: 6px; }
.cs-bio-header { display: flex; align-items: center; justify-content: space-between; }
.cs-bio-label {
  font-family: var(--font-display);
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-strong);
}
.cs-bio-body {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--fg);
  border: 1px solid var(--border-soft);
  background: var(--surface);
  padding: 10px 14px;
  border-radius: 2px;
}
.cs-bio-body p { margin: 0 0 0.6em; }
.cs-bio-body p:last-child { margin-bottom: 0; }
.cs-bio-empty {
  font-family: var(--font-body-italic);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-muted);
  padding: 6px 0;
}
.cs-bio-editor { display: flex; flex-direction: column; gap: 6px; }
.cs-bio-editor-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 220px;
  resize: vertical;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
}
.cs-bio-editor-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* Recovered-draft notice — shown above an editor (Biography / Appearance /
   Storyteller composer) when an unsaved draft was restored after a reload. */
.editor-draft-restored {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
  padding: 5px 9px;
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--rubric);
  border-radius: 4px;
  background: var(--surface);
  font-size: 0.78rem;
  color: var(--fg-muted);
}
.editor-draft-restored__label { font-style: italic; }
.editor-draft-restored__discard {
  flex: none;
  background: none;
  border: none;
  padding: 0 2px;
  font: inherit;
  color: var(--rubric);
  cursor: pointer;
  text-decoration: underline;
}
.editor-draft-restored__discard:hover { color: var(--verdigris-deep); }

/* Theme the Toast UI editor inside the bio tab (mirrors the composer's
   parchment overrides; scoped to .cs-bio-editor so it affects nothing else). */
.cs-bio-editor .toastui-editor-defaultUI { border: 1px solid var(--border); border-radius: 2px; }
.cs-bio-editor .toastui-editor-defaultUI-toolbar { background-color: var(--paper-deep); border-bottom: 1px solid var(--border-soft); }
.cs-bio-editor .toastui-editor-defaultUI-toolbar button:not(:disabled):hover { background-color: var(--surface); }
.cs-bio-editor .toastui-editor-main,
.cs-bio-editor .toastui-editor-ww-container { background-color: var(--surface); }
.cs-bio-editor .toastui-editor-contents,
.cs-bio-editor .toastui-editor-contents p,
.cs-bio-editor .toastui-editor-ww-container .ProseMirror { color: var(--fg); font-family: var(--font-body); font-size: 15px; }
.cs-bio-editor .toastui-editor-ww-container .ProseMirror .placeholder { color: var(--fg-muted); }
