/* Interaction Affordance Grammar — one grammar for every interactive control.
   Applied by factories in src/ui/components.js (clarity-and-fit SESSION-07).
   `--interactive-border` is scoped here rather than in styles/base.css :root so
   it stays out of the design-tokens palette scanner while remaining reachable by
   every `.is-interactive` element via CSS custom-property inheritance. */
.is-interactive {
  --interactive-border: #5a89a0;
  border: 1px solid var(--interactive-border);
  cursor: pointer;
}

/* Icon-prefixed buttons (combat-and-overworld-clarity-pass SESSION-06).
   `prefixIcon()` in components.js adds `.has-icon` when it actually prepends
   a sprite <svg>. Row layout + gap replace the button's default centering so
   icon and label sit shoulder-to-shoulder without wrapping. */
.btn-crt.has-icon,
.manual-term-link.has-icon,
.condition-tag.has-icon {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.btn-crt.has-icon { padding-left: 12px; padding-right: 12px; }
.btn-crt.has-icon > .icon,
.manual-term-link.has-icon > .icon,
.condition-tag.has-icon > .icon { flex-shrink: 0; }

/* Icon-only chrome (icon-first-ui-density SESSION-03). `createButton` in
   src/ui/components.js emits `.btn-crt.has-icon.icon-only` when the visible
   label is empty and an icon rides via opts.icon (aria-label carries the
   accessible name). Square touch box, centered glyph, no side-heavy padding
   inherited from `.has-icon`. `.icon-only` on `.action-btn` follows the same
   shape when action-row callers opt in the same way. Both variants keep the
   48px touch floor via the surrounding `.console-row` context; the
   `min-width: 48px` guard here holds when the button is used bare.
   `justify-content: center` overrides the row-flex-start on `.combat-action`
   so the lone glyph sits truly centered. */
.btn-crt.icon-only,
.action-btn.icon-only {
  padding: 8px;
  min-width: 48px;
  justify-content: center;
  gap: 0;
}
.btn-crt.icon-only { padding-left: 8px; padding-right: 8px; }

/* Wide-layout density pass — the console dock and telemetry dock are narrow;
   prevent shared portrait rows from wrapping. Row-minimum densification
   (48px, pointer-only) moved to styles/wide.css inside a fine-pointer /
   hover-only media guard so any coarse-pointer wide surface (e.g.
   1024×1024 touch tablet) still hits the 48px floor. */
:root[data-layout="wide"] .combat-active-panel,
:root[data-layout="wide"] .loot-container-header { flex-wrap: nowrap; min-width: 0; }
:root[data-layout="wide"] .combat-active-panel .combat-active-name,
:root[data-layout="wide"] .loot-container-header {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Disabled illegal target rows — SESSION-05 emits `.is-illegal` on the combat
   target row when the currently-selected action would be out of range. Color +
   dashed border marks it disabled without stacking opacity onto the text
   (playtest-clarity S01 — opacities must never compound on text). */
.combat-target.is-illegal {
  color: var(--text-disabled);
  border-style: dashed;
}
.is-interactive:hover:not([disabled]):not([aria-disabled="true"]) {
  border-color: var(--accent);
  box-shadow: 0 0 6px color-mix(in oklab, var(--accent) 35%, transparent);
}
.is-interactive:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.is-interactive:active:not([disabled]):not([aria-disabled="true"]) {
  transform: translateY(1px);
}
.is-interactive[disabled],
.is-interactive[aria-disabled="true"] {
  opacity: 1;
  color: var(--text-disabled);
  border-color: var(--border-dim);
  border-style: dashed;
  cursor: not-allowed;
}

/* Shared menu grammar. Surface classes remain responsible for their own
   layout, border, and selected treatment; these hooks only identify native
   actions and provide additive state selectors for consumer screens. */
.menu-group {
  display: flex;
}
.menu-action {
  font: inherit;
}
.menu-action.is-selected,
.menu-action.active,
.menu-action[aria-checked="true"],
.menu-action[aria-pressed="true"] {
  /* State hooks intentionally inherit the caller's visual surface. */
  font-weight: inherit;
}

/* Console Shell (SESSION-06 — in-flow, see the detailed rule further below).
   Background + shadow keep the console visually separated from the playfield
   even though it now sits below in normal flow instead of overlaying. */
.console-bar {
  background: var(--bg-panel);
  box-shadow: 0 -2px 12px rgba(0,0,0,0.6), 0 -1px 4px rgba(126,200,227,0.1);
}

.mode-tab {
  flex: 1;
  padding: 8px 4px;
  font-family: inherit;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-align: center;
  color: var(--text-primary);
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  border-bottom: 2px solid transparent;
  cursor: pointer;
  transition: all 0.15s ease;
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mode-tab:hover {
  color: var(--accent);
}

.mode-tab.active {
  border-bottom-color: var(--accent);
  color: var(--accent);
  text-shadow: 0 0 8px var(--accent);
  background: rgba(126,200,227,0.05);
}

.mode-tab.disabled {
  color: var(--text-disabled);
  border-color: var(--border-dim);
  cursor: default;
}

/* In-tab shortcut badge (playtest-clarity S01) — replaces the native `title`
   tooltip that occluded the TARGET panel. Small, dim, non-wrapping. */
.tab-key {
  margin-left: 4px;
  align-self: flex-start;
  font-size: 8px;
  line-height: 1;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Icon-first mode tabs (icon-first-ui-density SESSION-03). SESSION-05 will
   swap textual labels for lucide sprites; this rule owns the stack: glyph
   centered, `.tab-key` recentered below the icon so the numeric shortcut is
   still discoverable without stealing pointer space. Height stays at the
   48px touch floor set by `.console-row` — icon-only reclaims the label
   row, never the touch target. Applies in portrait; the wide dock variant
   lives in styles/wide.css. */
.mode-tab.icon-only {
  flex-direction: column;
  gap: 2px;
  padding: 8px 4px;
}
.mode-tab.icon-only .tab-key {
  align-self: center;
  margin-left: 0;
}
.mode-tab.icon-only > .icon { flex-shrink: 0; }

/* Status Strip */
.status-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border-dim);
  font-size: 9px;
  color: var(--text-secondary);
}

.theme-badge {
  display: inline-block;
  padding: 2px 8px;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  border: 1px solid var(--accent);
  text-shadow: 0 0 4px var(--accent);
}

/* Screen body — scrollable region between status strip and console.
   visual-parity-v4 SESSION-02. */
.screen-body {
  flex: 1 1 auto;
  min-height: 0;                         /* critical: enables scroll inside flex parent */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;          /* stops iOS rubber-band leaking to page */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
  /* Top + bottom edge fade so content dissolves into vignette */
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 var(--screen-body-fade),
    #000 calc(100% - var(--screen-body-fade)),
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 var(--screen-body-fade),
    #000 calc(100% - var(--screen-body-fade)),
    transparent 100%
  );
}

.screen-body::-webkit-scrollbar {
  width: var(--scrollbar-width);
}
.screen-body::-webkit-scrollbar-track {
  background: var(--scrollbar-track);
}
.screen-body::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 999px;
}
.screen-body::-webkit-scrollbar-thumb:hover {
  background: var(--scrollbar-thumb-hover);
}

/* Opt out — use when content is known to always fit (e.g. title). */
.screen-body--no-scroll {
  overflow: hidden;
  mask-image: none;
  -webkit-mask-image: none;
}

/* D-Pad */
.dpad {
  display: grid;
  grid-template-columns: repeat(3, 48px);
  grid-template-rows: repeat(3, 48px);
  gap: 4px;
  justify-content: center;
}

.dpad-btn {
  background: var(--bg-panel-elevated);
  border: 1px solid var(--border-dim);
  color: var(--accent);
  font-family: inherit;
  font-size: 18px;
  cursor: pointer;
  transition: all 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 2px;
}

.dpad-btn:hover {
  border-color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

.dpad-btn:active {
  background: rgba(126,200,227,0.1);
}

/* Icon glyphs inside the D-pad (icon-first-ui-density SESSION-03). The
   D-pad button font-size sets arrow-character glyph size for the current
   arrow-character D-pad; when SESSION-05 swaps them for lucide arrows the
   sprite must be authored large enough to read at glance across the 48×48
   button. Sits above the 44 px WCAG touch floor by construction (parent
   button is 48×48). */
.dpad-btn > .icon {
  width: 32px;
  height: 32px;
}

.dpad-center {
  background: transparent;
  border: 1px solid var(--border-dim);
  color: var(--text-secondary);
  font-size: 10px;
  text-transform: uppercase;
}

/* Initiative Rail */
.init-rail {
  display: flex;
  gap: 4px;
  padding: 4px 8px;
  align-items: center;
}

.init-slot {
  width: 34px;
  height: 34px;
  border: 1px solid var(--border-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  transition: all 0.15s ease;
}

.init-slot.active {
  border-color: var(--accent);
  box-shadow: 0 0 8px var(--accent);
  color: var(--accent);
}

.init-slot.enemy {
  border-color: rgba(232, 58, 58, 0.3);
  background: rgba(232, 58, 58, 0.08);
}

.init-slot.enemy .init-sigil,
.init-slot.enemy .creature-sigil.sigil-role-enemy {
  color: var(--danger);
  text-shadow: 0 0 6px var(--danger);
}

.init-slot.enemy.active {
  border-color: var(--danger);
  box-shadow: 0 0 8px var(--danger);
  background: rgba(232, 58, 58, 0.1);
  color: var(--danger);
}

.init-sigil {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'DESCENT SIGIL', monospace;
  font-size: 34px;
}

/* Combat Grid */
.combat-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(16, 1fr);
  gap: 0;
}

.grid-cell {
  border: 1px solid var(--border-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.grid-cell.wall {
  background: var(--bg-base);
}

.grid-cell.floor {
  background: var(--bg-panel);
}

.grid-cell.cover {
  background: rgba(126,200,227,0.05);
  border-color: var(--accent);
}

.grid-cell.target-highlight {
  background: rgba(232,58,58,0.1);
  border-color: var(--danger);
}

.grid-cell.range-valid {
  background: rgba(126,200,227,0.03);
}

/* Combat deployment markers (SESSION-06) — mirrors the mock definitions in
   mocks/combat.html + mocks/wide/combat.html so the design-scan mock-class
   parity check no longer flags them. Player zones tint with the floor accent,
   enemy zones tint with hostile danger (Custom Rule 14). */
.grid-cell.deploy-p {
  background: var(--bg-base);
  box-shadow: inset 0 0 0 999px color-mix(in oklab, var(--accent) 10%, transparent);
}
.grid-cell.deploy-e {
  background: var(--bg-base);
  box-shadow: inset 0 0 0 999px color-mix(in oklab, var(--danger) 10%, transparent);
}

.token {
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  border-radius: 50%;
}

.token-player {
  color: var(--sigil-player);
  text-shadow: 0 0 12px var(--accent), 0 0 24px var(--accent);
  font-size: 72px;
}

.token-player.active {
  text-shadow: 0 0 12px var(--accent), 0 0 24px var(--accent), 0 0 48px var(--accent);
}

.token-enemy {
  color: var(--sigil-enemy);
  text-shadow: 0 0 12px var(--sigil-enemy);
  font-size: 72px;
}

.token-enemy.targeted {
  text-shadow: 0 0 12px var(--danger), 0 0 24px var(--danger);
}

/* Item Cards — hover/cursor come from `.is-interactive` when the card is
   clickable (createEquipmentCard with opts.onClick). Static article variants
   stay border-dim with no hover affordance. */
.item-card {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  transition: all 0.15s ease;
}

.item-card.equipped {
  border-color: var(--accent);
  background: rgba(126,200,227,0.05);
}

.item-card.corrupt {
  border-color: var(--warning);
  box-shadow: 0 0 4px var(--warning);
}

/* Rarity Tags */
.rarity-tag {
  display: inline-block;
  padding: 1px 6px;
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border: 1px solid;
}

.r-stock { border-color: var(--text-dim); color: var(--text-dim); }
.r-tuned { border-color: var(--accent); color: var(--accent); }
.r-custom { border-color: #e8d23a; color: #e8d23a; }
.r-prototype { border-color: #b026d4; color: #b026d4; }
.r-corrupt { border-color: var(--warning); color: var(--warning); background: rgba(232,99,42,0.1); }

/* Affix Tags */
.affix-tag {
  display: inline-block;
  padding: 1px 4px;
  font-size: 8px;
  border: 1px solid var(--border-dim);
  color: var(--text-secondary);
  margin-right: 2px;
}

.affix-major {
  border-color: var(--accent);
  color: var(--accent);
}

/* Item stat chips — dice/range/defense readout on equipment/loot cards
   (clarity-and-fit SESSION-05). Follows the .affix-tag pattern; render-time
   values come from describeItemStats(). */
.card-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.stat-chip {
  display: inline-block;
  padding: 1px 4px;
  font-size: 9px;
  font-family: inherit;
  border: 1px solid var(--border-dim);
  color: var(--text-secondary);
}

/* Condition Tags */
.cond-tag {
  display: inline-block;
  padding: 2px 6px;
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border: 1px solid var(--border-dim);
  color: var(--text-secondary);
}

/* Action buttons — hover/cursor come from `.is-interactive` when the button is
   clickable (createProtocolCard with opts.onClick, direct <button> uses in the
   console panes). Static protocol-cards inherit only the base border. */
.action-btn {
  padding: 6px 10px;
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: all 0.15s ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.action-btn.selected {
  border-color: var(--accent);
  background: rgba(126,200,227,0.1);
  box-shadow: 0 0 8px var(--accent);
}

.action-btn.danger {
  border-color: rgba(232,58,58,0.3);
  color: var(--danger);
}

.action-cost {
  font-size: 8px;
  color: var(--text-dim);
  text-transform: none;
}

/* Steppers */
.stepper-btn {
  width: 32px;
  height: 32px;
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.15s ease;
  border-radius: 2px;
}

.stepper-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

.stepper-btn:disabled {
  color: var(--text-disabled);
  border-color: var(--border-dim);
  cursor: not-allowed;
}

/* Attribute Row */
.attr-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-dim);
}

/* Sliders */
.slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 1;
  height: 4px;
  background: var(--bg-base);
  border: 1px solid var(--border-dim);
  outline: none;
  border-radius: 2px;
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 24px;
  background: var(--accent);
  box-shadow: 0 0 4px var(--accent);
  cursor: pointer;
  border-radius: 2px;
}

input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 24px;
  background: var(--accent);
  box-shadow: 0 0 4px var(--accent);
  cursor: pointer;
  border: 0;
  border-radius: 2px;
}

.slider-value {
  min-width: 36px;
  text-align: right;
  font-size: 11px;
  color: var(--accent);
  font-weight: bold;
}

.slider-label {
  min-width: 80px;
  font-size: 10px;
  color: var(--text-secondary);
  text-transform: uppercase;
}

/* Toggle */
.toggle {
  position: relative;
  width: 44px;
  height: 22px;
  background: var(--bg-base);
  border: 1px solid var(--border-dim);
  cursor: pointer;
  transition: all 0.2s ease;
  border-radius: 2px;
}

.toggle-knob {
  position: absolute;
  top: 1px;
  left: 1px;
  width: 18px;
  height: 18px;
  background: var(--text-dim);
  transition: all 0.2s ease;
}

.toggle.on {
  border-color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

.toggle.on .toggle-knob {
  left: 23px;
  background: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

/* Tab buttons (creation screen) */
.tab-btn {
  background: var(--bg-panel);
  border-bottom: 2px solid transparent;
  color: var(--text-secondary);
  padding: 8px 4px;
  font-family: inherit;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
  transition: all 0.15s ease;
  text-align: center;
  flex: 1;
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tab-btn:hover {
  color: var(--text-primary);
}

.tab-btn.active {
  border-bottom-color: var(--accent);
  color: var(--accent);
  text-shadow: 0 0 8px var(--accent);
}

/* Class Card */
.class-card {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  padding: 12px;
  cursor: pointer;
  transition: all 0.15s ease;
  display: flex;
  flex-direction: column;
  text-align: left;
}

.class-card:hover {
  border-color: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}

.class-card.selected {
  border-color: var(--accent);
  background: var(--bg-panel-elevated);
  box-shadow: 0 0 12px var(--accent);
}

/* Character Slot */
.char-slot {
  border: 1px solid var(--border-dim);
  transition: all 0.2s ease;
  cursor: pointer;
}

.char-slot:hover {
  border-color: var(--text-secondary);
}

.char-slot.active {
  border-color: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}

/* Member Cards */
.member-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; margin-bottom: 8px; }
.member-card { cursor: pointer; padding: 8px; border: 1px solid var(--border-dim); background: var(--bg-panel); transition: all 0.15s ease; }
.member-card.selected { border-color: var(--accent); background: var(--bg-panel-elevated); box-shadow: 0 0 8px var(--accent); }
.member-card-name { font-size: 10px; font-weight: bold; color: var(--accent); text-shadow: 0 0 4px var(--accent); }
.member-card-meta { display: flex; align-items: center; gap: 4px; margin-bottom: 2px; }
.member-card-equip { font-size: 8px; color: var(--text-dim); }
.party-inline-bar { display: flex; align-items: center; justify-content: space-between; gap: 4px; margin: 1px 0; }
.party-inline-track { flex: 1; height: 2px; background: var(--bg-base); border: 1px solid var(--border-dim); }
.party-inline-fill { height: 100%; }
.member-pill { cursor: pointer; padding: 4px 10px; border: 1px solid var(--border-dim); font-size: 10px; transition: all 0.15s ease; display: flex; align-items: center; gap: 4px; }
.member-pill.active { border-color: var(--accent); background: rgba(126,200,227,0.1); color: var(--accent); }

/* Deck Slot */
.deck-slot {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  padding: 8px 12px;
  transition: all 0.15s ease;
  cursor: pointer;
}

.deck-slot:hover {
  border-color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

.deck-slot.selected {
  border-color: var(--accent);
  background: rgba(126,200,227,0.05);
}

/* Run Row */
.run-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  cursor: pointer;
  transition: all 0.15s ease;
}

.run-row:hover {
  border-color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

.accent-swatch {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 4px currentColor;
}

/* Link Input */
.link-input {
  background: var(--bg-base);
  border: 1px solid var(--border-dim);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 12px;
  padding: 12px 16px;
  width: 100%;
  outline: none;
}

.link-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}

.link-input.error {
  border-color: var(--danger);
  box-shadow: 0 0 8px var(--danger);
}

/* Share Link */
.share-link {
  background: var(--bg-base);
  border: 1px solid var(--border-dim);
  padding: 8px 12px;
  font-size: 11px;
  color: var(--text-secondary);
  word-break: break-all;
  overflow-wrap: anywhere;
}

/* Log Entry */
.log-entry {
  padding: 4px 8px;
  font-size: 11px;
  border-bottom: 1px solid var(--border-dim);
  color: var(--text-secondary);
}

.log-entry.combat { color: var(--danger); }
.log-entry.discovery { color: var(--accent); }
.log-entry.damage { color: var(--warning); }
.log-entry.death { color: var(--danger); }
.log-entry.heal { color: var(--heal); }

/* Tutorial Page Dots */
.page-dots {
  display: flex;
  gap: 6px;
  justify-content: center;
  align-items: center;
}

.page-dot {
  width: 8px;
  height: 8px;
  border: 1px solid var(--border-dim);
  border-radius: 50%;
  transition: all 0.15s ease;
}

.page-dot.active {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

/* Section Header */
.section-header {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  margin: 16px 0 8px 0;
}

/* Creature sigils */
.sigil-placeholder,
.creature-sigil {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--accent);
  border-radius: 50%;
  color: var(--accent);
  text-shadow: 0 0 8px var(--accent);
  font-weight: bold;
  font-family: 'DESCENT SIGIL';
}

.creature-sigil.sigil-role-enemy {
  color: var(--sigil-enemy);
  text-shadow: 0 0 8px var(--sigil-enemy);
}

.sigil-placeholder.large {
  width: 56px;
  height: 56px;
  font-size: 20px;
}

.sigil-placeholder.small {
  width: 32px;
  height: 32px;
  font-size: 14px;
}

.sigil-option {
  cursor: pointer;
  transition: all 0.15s ease;
}

.sigil-option:hover {
  box-shadow: 0 0 12px var(--accent);
  transform: scale(1.1);
}

.sigil-option.selected {
  box-shadow: 0 0 16px var(--accent), 0 0 24px var(--accent);
  transform: scale(1.1);
}

/* Sigil-font contract (playtest-clarity S01 × creation DOM fix S02) — the
   creation-screen sigil surfaces render PUA glyphs directly, so they must
   resolve the DESCENT SIGIL face even when the glyph is not wrapped in a
   `.creature-sigil` token. Either rule alone renders glyphs; both is the
   contract. Fixes the tofu-sigil playtest finding. */
.sigil-preview,
.sigil-thumbs .sigil-option,
.sigil-choice {
  font-family: 'DESCENT SIGIL';
}

/* Calibration Card */
.calibration-card {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel-elevated);
  cursor: pointer;
  transition: all 0.15s ease;
  padding: 16px;
}

.calibration-card:hover {
  border-color: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}

.calibration-card.selected {
  border-color: var(--accent);
  box-shadow: 0 0 12px var(--accent);
}

/* Loot Container */
.loot-container {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  transition: all 0.15s ease;
}

.loot-container.opened {
  border-color: var(--accent);
}

.loot-container.empty {
  opacity: 0.5;
}

.container-icon {
  font-size: 24px;
  color: #e8d23a;
  text-shadow: 0 0 8px #e8d23a;
}

/* Mode indicator label */
.mode-indicator {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  text-shadow: 0 0 4px var(--accent);
}
/* Touch floor (SESSION-06; lowered 96 → 48 by touch-target-density-pass
   SESSION-02) — every touch-capable row is at least 48 CSS px at phone
   (412×915), portrait-reference (1080×1920), and touch-wide (1024×1024).
   Wide-mode densification down to 48px is now scoped to fine-pointer hover
   contexts only (see styles/wide.css) so any coarse-pointer / touch surface
   still hits this 48px floor. */
.console-row:not(.console-static-row):not(.console-static-card),
.mode-tab,
.menu-action,
.action-btn:not(.console-static-row):not(.console-static-card),
.item-card:not(.console-static-row):not(.console-static-card),
.equipment-card:not(.console-static-row):not(.console-static-card),
.protocol-card:not(.console-static-row):not(.console-static-card),
.run-row,
.deck-slot,
.calibration-card,
.toggle-row,
.slider-row,
.text-input-row {
  min-height: 48px;
}

.console-static-row,
.console-static-card {
  min-height: 0;
}

button.mode-tab[aria-selected="true"],
button.action-btn[aria-selected="true"],
button.item-card[aria-selected="true"],
button.protocol-card[aria-selected="true"],
.selected {
  border-color: var(--accent);
  background: rgba(126,200,227,0.1);
  box-shadow: 0 0 8px var(--accent);
}

.toggle-row,
.text-input-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
}

.toggle-input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.toggle-input:focus-visible + .toggle {
  outline: 4px solid var(--accent);
  outline-offset: 4px;
  box-shadow: 0 0 0 2px var(--bg-base), 0 0 16px var(--accent);
}

.toggle-input:disabled + .toggle {
  border-color: var(--text-disabled);
  cursor: not-allowed;
}
.toggle-input:disabled + .toggle .toggle-knob {
  background: var(--text-disabled);
}

.input-label {
  min-width: 160px;
  font-size: 10px;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.condition-tag {
  display: inline-block;
  padding: 2px 6px;
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border: 1px solid var(--border-dim);
  color: var(--text-secondary);
}

.cond-burning     { border-color: var(--warning); color: var(--warning); }
.cond-jammed      { border-color: var(--danger); color: var(--danger); }
.cond-marked      { border-color: #e8d23a; color: #e8d23a; }
.cond-shielded    { border-color: var(--heal); color: var(--heal); }
.cond-overloaded  { border-color: #e8d23a; color: #e8d23a; }
.cond-blinded     { border-color: var(--text-secondary); color: var(--text-secondary); }
.cond-immobilized { border-color: var(--danger); color: var(--danger); }
.cond-corroded    { border-color: var(--warning); color: var(--warning); }
.cond-panicked    { border-color: var(--accent); color: var(--accent); }

.school-tag {
  display: inline-block;
  padding: 1px 6px;
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border: 1px solid var(--border-dim);
}

.school-disrupt { border-color: var(--danger); color: var(--danger); }
.school-ward     { border-color: var(--heal);   color: var(--heal); }
.school-scry     { border-color: var(--accent); color: var(--accent); }
.school-rewrite  { border-color: #e8d23a;       color: #e8d23a; }

.protocol-card.insufficient {
  color: var(--text-disabled);
  border-color: var(--border-dim);
  cursor: not-allowed;
}

.deck-pip {
  width: 12px;
  height: 12px;
  border: 1px solid var(--border-dim);
  display: inline-block;
  margin-right: 3px;
}

.deck-pip.filled {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 4px var(--accent);
}

.playfield-canvas {
  display: block;
  width: 100%;
  image-rendering: pixelated;
  pointer-events: none;
  touch-action: none;
  background: var(--bg-base);
}

.playfield-description {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Status strip container + fields are noninteractive readouts (SESSION-06).
   Height comes from padding + inline flex alignment, not a misleading
   `min-height` that would trip the touch-target scanner.
   icon-first-ui-density SESSION-03 — inter-field gap tightened 12 → 8 so the
   icon-prefixed label + value fields sit closer together in portrait; the
   strip's own vertical padding (8 12) already meets the §5.1 40–44 px target
   band without a second pass. */
.status-strip {
  gap: 8px;
  overflow: hidden;
  padding: 8px 12px;
  line-height: 1.35;
}

.status-depth,
.status-depth-combat,
.status-seed,
.status-round,
.status-initiative,
.status-clock,
.status-corruption,
.status-ap,
.status-move {
  display: inline-flex;
  align-items: center;
  padding: 4px 0;
  color: var(--text-primary);
  white-space: nowrap;
}

.status-seed,
.status-clock,
.status-corruption,
.status-initiative {
  color: var(--text-secondary);
}

.status-mini-hp,
.status-mini-charge {
  min-width: 72px;
}

.status-active-sigil {
  color: var(--accent);
  border-color: currentColor;
}

/* Portrait strip full-bleed (SESSION-02) — the strip already runs edge-to-edge
   in production; the explicit width guards against a future parent gaining a
   horizontal inset without noticing. Wide dock overrides live in wide.css. */
:root:not([data-layout="wide"]) .status-strip {
  width: 100%;
  margin-left: 0;
  margin-right: 0;
}

/* Portrait combat status grid (SESSION-06) — the strip is the sole owner of
   HP / CHARGE / AP / MV + initiative in portrait, so every field must stay
   in the viewport at phone (412×915) and tall portrait (1080×1920) widths.
   The JS emits a 4-column grid with the initiative rail spanning row 2
   (see src/ui/status-strip.js:renderCombatStatus); this rule tightens the
   strip's padding and lets the active-group cell wrap to its own line if
   space is truly scarce. Wide takes its own dock (styles/wide.css). */
:root:not([data-layout="wide"]) .status-strip.status-strip-combat {
  padding: 6px 10px;
  align-items: start;
}

:root:not([data-layout="wide"]) .status-strip.status-strip-combat .status-initiative {
  overflow-x: auto;
  scrollbar-width: none;
}

/* Combat status overview (SESSION-01 mobile-combat-density-repair) — the
   combat branch's dedicated overview wrapper. A single flex-column of
   compact rows replaces the old 4-column CSS grid; the active-actor row is
   horizontal (icon-prefixed sigil/HP/CHARGE/AP/MOVE side by side) instead of
   a stacked column, which was the actual driver of the pre-repair 226px
   strip / 111px active-cell height. */
:root:not([data-layout="wide"]) .status-combat-overview {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  gap: 3px;
}

:root:not([data-layout="wide"]) .status-combat-topline,
:root:not([data-layout="wide"]) .status-combat-active {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

:root:not([data-layout="wide"]) .status-combat-active {
  gap: 6px;
}

:root:not([data-layout="wide"]) .status-combat-initiative-row {
  width: 100%;
}

/* In-run screens (SESSION-06) — the exploration / combat containers are
   flex columns so the in-flow console-bar (formerly absolute) can sit
   below the playfield without covering it. Wide layout mounts its own
   `.wide-shell` grid as a single child; flex-column parent is a no-op there. */
.in-run-screen {
  display: flex;
  flex-direction: column;
  /* mobile-pwa-hardening SESSION-01 — 100% of the already-safe-area-padded
     #app-root content box, not a raw 100vh/100dvh viewport fill that would
     bypass that padding and render under a device cutout. */
  height: 100%;
  min-height: 0;
}

/* Portrait console bar (SESSION-06) — in-flow flex child of the run
   screen's flex-column container. Never absolute, never covers the
   playfield, never triggers a dim layer. The 3-state tap cycle
   (collapsed → half → full → collapsed) lives in src/ui/console/console.js
   and drives .expanded-half / .expanded-full; the legacy .expanded class
   is still applied for both non-collapsed sizes so mocks, keyboard-flow
   e2e (/expanded/), and the M97 scanner still pass. */
.console-bar {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border-dim);
  min-height: 0;
}

.console-bar.collapsed .console-content {
  display: none;
}

/* Bounded in-flow sizes — tuned so both `half` and `full` reserve real
   playfield room above the console in every portrait viewport (phone,
   tall desktop). Neither state may consume the whole frame; content
   scrolls inside `.console-content` via its own overflow rule. */
.console-bar.expanded-half {
  /* icon-first-ui-density SESSION-03 — half-tray min height / dvh trimmed
     220→200 / 32dvh→30dvh per §5.1 so icon-first tabs let the playfield claim
     ~20 additional CSS pixels on phone. 200 stays well above the 48 px touch
     floor; the 420 px ceiling still governs tall portrait. */
  height: clamp(200px, 30dvh, 420px);
  min-height: 200px;
  max-height: 420px;
}

.console-bar.expanded-full {
  height: clamp(320px, 48dvh, 640px);
  min-height: 320px;
  max-height: 640px;
}

.console-tab-bar {
  display: flex;
  flex: 0 0 auto;
  min-height: 48px;
}

.console-content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 12px;
  background: var(--bg-panel);
}

/* Dim layer suppressed (SESSION-06). Class definition preserved for DOM
   compatibility if a stale mount emits the div — the layer never renders
   in production, and the console never overlays the playfield. */
.console-dim-layer {
  display: none !important;
}

/* Combat feedback rail (SESSION-06) — the screen-owned polite live region
   between the playfield and the console. Sibling of `.console-bar`, not a
   child of `.console-content`, so notices/errors stay visible regardless of
   console scroll or expand state. */
.combat-feedback-rail {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  background: var(--bg-panel);
  border-top: 1px solid var(--border-dim);
  border-bottom: 1px solid var(--border-dim);
  line-height: 1.4;
  color: var(--text-primary);
}
.combat-feedback-notice {
  color: var(--heal);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-shadow: 0 0 4px color-mix(in oklab, var(--heal) 45%, transparent);
}
.combat-feedback-error {
  color: var(--danger);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-shadow: 0 0 4px color-mix(in oklab, var(--danger) 45%, transparent);
}
.combat-feedback-notice[hidden],
.combat-feedback-error[hidden] {
  display: none;
}

/* SESSION-01 (mobile-combat-density-repair) — an inactive rail (no notice,
   no error) costs zero layout space: padding/border collapse to nothing.
   The live-region node and its message children stay mounted (never
   display:none on the rail itself) — only their own [hidden] attributes
   toggle, set in src/ui/screens/combat.js:syncFeedbackRail. A notice/error
   re-expands the rail in flow via the `.is-active` flag with no overflow
   clipping, so a newly announced message is never cut off mid-transition. */
.combat-feedback-rail:not(.is-active) {
  padding: 0;
  border-top: 0;
  border-bottom: 0;
  gap: 0;
}

/* Exploration playfield override (SESSION-06) — the JS still sets an inline
   `margin-bottom: 96px` (stale, from the old absolute-console era). With the
   in-flow console the margin becomes a gap between playfield and console;
   zero it so the playfield fills the flex parent up to the console. */
.exploration-screen .exploration-playfield {
  margin-bottom: 0 !important;
}

.mode-tab[aria-disabled="true"] {
  color: var(--text-disabled);
  cursor: not-allowed;
}

/* Noninteractive polite notice — padding-based height, no misleading min-height
   (SESSION-06). */
.console-notice {
  padding: 8px 16px;
  line-height: 1.4;
  color: var(--warning);
  background: var(--bg-panel);
}

.console-notice[hidden] {
  display: none;
}

/* Creation screen */
.creation-wrapper {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 100%;
  padding: 24px;
  color: var(--text-primary);
}

.creation-header {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  align-items: stretch;
}

.creation-char-bar,
.creation-tabs,
.creation-actions,
.blueprint-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: stretch;
}

.creation-readout {
  min-width: 128px;
  min-height: 48px;
  padding: 12px;
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
}

.readout-label,
.card-detail,
.config-summary,
.creation-note,
.disabled-reason {
  color: var(--text-secondary);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.card-name {
  display: block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.card-desc {
  display: block;
  font-size: 10px;
  color: var(--text-secondary);
  letter-spacing: 0.02em;
  margin-top: 2px;
  white-space: normal;
  overflow-wrap: anywhere;
}

.card-subtitle {
  display: block;
  color: var(--text-dim);
  font-size: 10px;
  letter-spacing: 0.05em;
  margin-top: 2px;
}

/* Class-usable chip (Issue I) — SESSION-03 emits `.card-classes` on gear +
   loot equipment cards. SESSION-05 gives it a compact secondary-tone chip so
   the eligible-class list reads clearly without competing with card-name. */
.card-classes {
  font-size: 10px;
  color: var(--text-secondary);
  letter-spacing: 0.05em;
  padding: 2px 0;
}

/* Rarity row (Issue J) — SESSION-03 emits `.card-rarity-row` on loot cards
   after moving the rarity chip out of the removed outer description block. */
.card-rarity-row {
  padding: 4px 0;
  display: flex;
  gap: 6px;
  align-items: center;
}

.class-card .card-detail {
  display: block;
  margin-top: 4px;
  text-transform: none;
}

.readout-value,
.config-name {
  color: var(--accent);
  text-shadow: 0 0 6px var(--accent);
}

.creation-detail {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.class-card-row,
.sigil-picker-row,
.creation-choice-grid,
.blueprint-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.sigil-picker-row {
  grid-template-columns: repeat(2, 1fr);
}

.sigil-choice {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  padding: 8px;
  cursor: pointer;
  transition: all 0.15s ease;
  min-height: 280px;
  gap: 8px;
}

.sigil-choice .creature-sigil {
  flex: 0 0 auto;
}

.creation-section,
.config-row {
  border: 1px solid var(--border-dim);
  background: var(--bg-panel);
  padding: 12px;
}

.config-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 8px;
  align-items: center;
}

.config-row.invalid,
.creation-warning,
.creation-error {
  border-color: var(--warning);
  color: var(--warning);
}

.creation-error {
  min-height: 0;
  padding: 6px 10px;
  background: rgba(232,99,42,0.08);
}

.creation-warning,
.creation-note {
  padding: 8px 12px;
}

.config-name-input {
  width: 100%;
  min-height: 48px;
  padding: 8px 12px;
  color: var(--text-primary);
  background: var(--bg-base);
  border: 1px solid var(--border-dim);
  font-family: inherit;
  text-transform: uppercase;
}

.creation-wrapper button:focus-visible,
.creation-wrapper input:focus-visible {
  outline: 4px solid var(--accent);
  outline-offset: 3px;
  box-shadow: 0 0 16px var(--accent);
}

.creation-wrapper button:disabled,
.creation-wrapper [aria-disabled="true"] {
  color: var(--text-disabled);
  border-color: var(--border-dim);
  cursor: not-allowed;
}

.creation-char-bar > *,
.creation-actions > *,
.blueprint-controls > *,
.sigil-choice,
.class-card,
.equipment-card,
.protocol-card,
.config-row {
  min-height: 48px;
}

.btn-crt.footer-back-btn {
  flex: 1;
}

.btn-crt.finalize-btn {
  flex: 2;
}

/* Scorecard */
.scorecard-roster-entry {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
}

.scorecard-roster-entry.dead span {
  color: var(--danger);
}

.creature-sigil.dead {
  opacity: 0.4;
  filter: grayscale(0.5);
}

.creature-sigil.dead::after {
  content: '\2715';
  position: absolute;
  font-size: 24px;
  color: var(--danger);
  text-shadow: 0 0 4px var(--danger);
}

/* Import failure */
.import-result .panel.error {
  border-color: var(--danger);
  box-shadow: 0 0 8px var(--danger);
}

/* Tutorial illustrations */
.illus-dpad {
  display: grid;
  grid-template-columns: repeat(3, 24px);
  grid-template-rows: repeat(3, 24px);
  gap: 2px;
}

.illus-dpad > div {
  background: var(--bg-panel-elevated);
  border: 1px solid var(--border-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  color: var(--accent);
}

.illus-dpad > .dpad-center {
  background: transparent;
  border: 1px dashed var(--border-dim);
  font-size: 6px;
  color: var(--text-dim);
}

.branch-list.hidden-branches { display: none; }

.title-screen {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  padding: 48px 24px;
  min-height: 100%;
}
.title-header {
  font-size: 12px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  margin-top: 32px;
}
.title-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
}
.tagline {
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  margin-top: 16px;
  text-align: center;
}
.title-footer {
  text-align: center;
}
.title-footer p {
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  margin: 4px 0;
}
.branch-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 280px;
  margin-top: 24px;
}

.alert-banner {
  padding: 8px 16px;
  background: rgba(232, 58, 58, 0.15);
  border: 1px solid var(--danger);
  color: var(--danger);
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  text-shadow: 0 0 6px var(--danger);
  animation: alert-pulse 1.2s ease-in-out infinite;
}
.alert-banner[hidden] { display: none; }
@keyframes alert-pulse {
  0%, 100% { box-shadow: 0 0 8px rgba(232, 58, 58, 0.4); }
  50%      { box-shadow: 0 0 16px rgba(232, 58, 58, 0.8); }
}

.member-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.party-detail { padding: 12px; border: 1px solid var(--accent); background: var(--bg-panel-elevated); }
.detail-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.detail-title { font-size: 13px; color: var(--accent); font-weight: bold; }
.party-stat-row { display: flex; justify-content: space-between; padding: 3px 0; font-size: 10px; }
.stat-label { color: var(--text-secondary); }
.stat-value { color: var(--text-primary); font-weight: bold; }
.party-attributes { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; margin-bottom: 12px; }
.party-derived { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2px 8px; margin-bottom: 12px; }
.section-label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-secondary); margin-bottom: 4px; }
.condition-list { margin-bottom: 12px; }
.condition-empty { font-size: 10px; color: var(--text-dim); }
.party-equipment { margin-bottom: 12px; }
.party-deck { margin-bottom: 12px; }
.deck-empty { font-size: 10px; color: var(--text-dim); }
.console-empty { font-size: 10px; color: var(--text-dim); padding: 12px; }

.tech-character-row { display: flex; gap: 8px; margin-bottom: 12px; }
.tech-active { color: var(--accent); font-size: 10px; }
.tech-protocol-row { margin-bottom: 4px; }
.protocol-buttons { display: flex; gap: 4px; margin-top: 4px; }
.tech-protocol-detail { font-size: 10px; color: var(--text-secondary); margin-top: 2px; }
.tech-target-list { margin-bottom: 12px; }
.tech-preview { font-size: 10px; color: var(--text-secondary); padding: 8px; background: var(--bg-panel-elevated); border: 1px solid var(--border-dim); }
.tech-confirm-row { display: flex; gap: 8px; margin-bottom: 12px; }
.tech-slots { font-size: 10px; color: var(--text-secondary); }
.tech-pip-row { display: flex; gap: 3px; margin-bottom: 12px; }
.tech-deck { margin-bottom: 12px; }
.tech-catalog { margin-top: 12px; }
.tech-school-row { font-size: 9px; color: var(--text-dim); padding: 4px 0; }
.tech-notice { font-size: 10px; color: var(--heal); padding: 4px 0; }
.tech-error { font-size: 10px; color: var(--danger); padding: 4px 0; }
.tech-result { font-size: 10px; color: var(--text-secondary); padding: 4px 0; }

.loot-container-header { display: flex; align-items: center; gap: 8px; padding: 8px; border: 1px solid #e8d23a; background: var(--bg-panel-elevated); margin-bottom: 8px; font-size: 10px; }
/* Container CONTENTS take the flexible height so loot is visible without scrolling
   past the header/OPEN chrome; the docked inventory bar keeps its own height below. */
.loot-items { flex: 1; min-height: 0; margin-bottom: 8px; }
.loot-item-row { padding: 4px; margin-bottom: 4px; border: 1px solid var(--border-dim); background: var(--bg-panel); }
.loot-detail { font-size: 10px; color: var(--text-secondary); margin-top: 4px; }
.loot-compare { font-size: 9px; color: var(--text-dim); margin-top: 2px; }
.loot-inventory-header { font-size: 10px; color: var(--text-secondary); padding: 8px 0; }
/* Collapsible junk manager — collapsed by default so a full pack of TAG buttons
   does not bury the container CONTENTS / OPEN / TAKE controls above it. */
.loot-inventory-toggle { width: 100%; justify-content: flex-start; font-size: 10px; letter-spacing: 0.08em; color: var(--text-secondary); margin: 4px 0 8px; }
.loot-inventory-body.is-collapsed { display: none; }
.loot-junk-list { margin-bottom: 12px; }
.loot-notice { font-size: 10px; color: var(--heal); padding: 4px 0; }
.loot-error { font-size: 10px; color: var(--danger); padding: 4px 0; }
.loot-warning { font-size: 9px; color: var(--warning); padding: 4px 0; }

.gear-selectors { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.gear-character { padding: 4px 10px; font-size: 10px; }
.gear-filter-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.gear-filter { flex: 1 1 auto; min-width: 72px; padding: 4px 10px; font-size: 10px; text-align: center; }
.equipped-section { margin-bottom: 12px; }
.equipped-row { padding: 8px; margin-bottom: 8px; border: 1px solid var(--border-dim); background: var(--bg-panel); }
.equipped-slot { font-size: 10px; color: var(--text-secondary); margin-bottom: 4px; }
/* Header is a flex row so the `?` salvage chip sits after the summary text
   instead of on top of it (owner rule: never overlap UI elements). */
.inventory-header { display: flex; align-items: center; gap: 8px; font-size: 10px; color: var(--text-secondary); padding: 8px 0; }
/* Natural height so every EQUIP/TAG JUNK row is in-flow and reachable via the console scroll.
   flex:1 (flex-basis:0) collapsed this to ~0 inside the wide dock's flex-column body. */
.inventory-list { flex: 0 0 auto; margin-bottom: 12px; }
/* Column flex with visible overflow keeps every action (EQUIP / TAG JUNK) in-flow
   and reachable — the "missing equip button" bug was clipped/overlapped rows. */
.inventory-row { display: flex; flex-direction: column; gap: 6px; overflow: visible; padding: 8px; margin-bottom: 8px; border: 1px solid var(--border-dim); background: var(--bg-panel); }
.gear-equip-action { align-self: flex-start; min-inline-size: 112px; }
.inventory-row.junk-tagged { opacity: 0.5; }
.gear-comparison { font-size: 9px; color: var(--text-dim); margin-top: 4px; }
.corrupt-warning { padding: 8px; border: 1px solid var(--warning); background: rgba(232,99,42,0.1); color: var(--warning); font-size: 10px; margin-bottom: 12px; }
.corrupt-tag { font-size: 9px; color: var(--warning); }
.gear-notice { font-size: 10px; color: var(--heal); padding: 4px 0; }
.gear-error { font-size: 10px; color: var(--danger); padding: 4px 0; }

.log-area { max-height: 500px; }
.log-empty { font-size: 10px; color: var(--text-dim); padding: 12px; }
.log-notice { font-size: 10px; color: var(--heal); padding: 4px 0; }
.log-error { font-size: 10px; color: var(--danger); padding: 4px 0; }
.log-link-text { font-size: 10px; color: var(--accent); padding: 8px; background: var(--bg-base); border: 1px solid var(--border-dim); }
.log-link-fallback { font-size: 10px; color: var(--accent); padding: 8px; background: var(--bg-base); border: 1px solid var(--border-dim); }

.move-mode { padding: 4px 0; }
.move-dpad { margin: 12px auto; }
.move-toggle-row { display: flex; gap: 8px; margin-bottom: 12px; }

.combat-active-panel { padding: 8px; margin-bottom: 12px; border: 1px solid var(--border-dim); background: var(--bg-panel); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.combat-active-name { font-size: 11px; color: var(--accent); font-weight: bold; }
.combat-ap { font-size: 10px; color: var(--text-secondary); }
/* Portrait CMBT slim active row (mobile-combat-pass SESSION-01 emits this in
   portrait as a de-duped replacement for the full active panel — the pinned
   status strip already carries HP/CHARGE/AP/MV + initiative). AP is repeated
   for glance-ability alongside the actions; the row hosts condition tags. Lives
   under `.console-bar` only (portrait), so the wide dock keeps its full panel
   untouched. 44px minimum keeps the row tappable for any embedded chip. */
/* Noninteractive summary row — padding-based height, no min-height (SESSION-06). */
.combat-active-conditions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 8px; margin-bottom: 8px; line-height: 1.4; }
/* CMBT action density (SESSION-02, tightened by mobile-combat-pass SESSION-04).
   Portrait: 2-column action grid with the primary set (Move / Attack / End Turn)
   spanning both columns so the thumb-reach set stays big, and the four
   secondary actions pair up. `.combat-action-list` / `.combat-action` /
   `.combat-action--primary` only exist inside `.console-bar` (portrait); the
   wide dock uses its own grid. Direction pad stays 3×3 with 48px+ cells (M97
   touch-target floor). */
/* Portrait CMBT action + direction rows (SESSION-06; lowered 96 → 48 by
   touch-target-density-pass SESSION-02) — every touch-capable row hits the
   48px floor at phone (412×915) and 1080-portrait alike. Direction pad
   becomes a 3×3 grid of 48px cells for reliable thumb reach. */
/* SESSION-01 (mobile-combat-density-repair) — 3-column grid so Move / Attack /
   End Turn (emitted first by renderActions) land in the initial visual row in
   their DOM/focus order without a grid-column span; `.combat-action--primary`
   stays a semantic hook (JS still applies it) but no longer forces a
   full-width row. Icon-forward compact buttons stack icon/label/cost
   vertically to stay legible in a narrower column. */
.combat-action-list { margin-bottom: 8px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.combat-action { width: 100%; min-height: 48px; padding: 8px 6px; font-size: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; text-align: center; }
.combat-action .action-cost { font-size: 8px; }

/* Fallback threshold — only drop to 2 columns when 3 truly can't fit (below
   the smallest acceptance width, 360×800). At 360px and 412px alike, three
   columns hold. */
@media (max-width: 340px) {
  .combat-action-list { grid-template-columns: 1fr 1fr; }
}
.combat-direction-grid { display: grid; grid-template-columns: repeat(3, minmax(48px, 1fr)); grid-template-rows: repeat(3, 48px); gap: 6px; justify-content: center; margin-bottom: 12px; }
.combat-direction { background: var(--bg-panel-elevated); border: 1px solid var(--border-dim); font-size: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 2px; min-height: 48px; }
.combat-protocol-list { margin-bottom: 12px; }
.combat-item-list { margin-bottom: 12px; }
.combat-item { padding: 6px 12px; font-size: 10px; }
.combat-target-list { margin-bottom: 12px; }
/* Portrait CMBT target rows — tightened for phone density: single-line label
   (clip + ellipsis), keep the 44px tap floor, and stay compatible with
   `.combat-target.is-illegal` (defined at :53 above). Under `.console-bar`
   only. */
.combat-target { padding: 10px 12px; font-size: 10px; min-height: 48px; display: flex; align-items: center; gap: 6px; }
.combat-target > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.combat-confirm-row { display: flex; gap: 8px; margin-bottom: 12px; }
.combat-terminal { font-size: 12px; color: var(--danger); text-align: center; padding: 16px; text-transform: uppercase; }
.combat-resolving { font-size: 10px; color: var(--text-secondary); padding: 12px; }
.combat-hint { font-size: 10px; color: var(--text-dim); padding: 4px 0; }
.combat-notice { font-size: 10px; color: var(--heal); padding: 4px 0; }
.combat-error { font-size: 10px; color: var(--danger); padding: 4px 0; }

.run-sigils { display: flex; gap: 4px; align-items: center; flex-shrink: 0; }
.run-info { flex: 1; display: flex; flex-direction: column; gap: 2px; font-size: 10px; color: var(--text-secondary); }
.run-info strong { color: var(--text-primary); font-size: 11px; }
.run-controls { display: flex; gap: 8px; flex-shrink: 0; }
.run-row.broken { border-color: var(--danger); }
.run-row.broken .load-error { color: var(--danger); font-size: 9px; }
.load-error { color: var(--danger); font-size: 9px; }
.library-actions { display: flex; gap: 12px; margin-top: 16px; }

.scorecard-screen { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.scorecard-roster { display: flex; flex-direction: column; gap: 4px; }
.scorecard-cod { font-size: 11px; color: var(--danger); text-transform: uppercase; letter-spacing: 0.1em; }
.scorecard-seed { font-size: 10px; color: var(--text-secondary); }
.share-link-display { font-size: 10px; color: var(--accent); padding: 8px; background: var(--bg-base); border: 1px solid var(--border-dim); word-break: break-all; }
.scorecard-stats { display: flex; flex-direction: column; gap: 8px; }
.scorecard-stats h3 { font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--accent); }
.scorecard-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px; }
.scorecard-metric { display: flex; justify-content: space-between; font-size: 10px; padding: 4px 0; border-bottom: 1px solid var(--border-dim); }
.scorecard-metric strong { color: var(--accent); }
.scorecard-actions { display: flex; gap: 12px; }

.import-result { margin: 12px 0; }
.import-actions { display: flex; gap: 12px; margin-top: 12px; }
.import-failure-actions { display: flex; gap: 12px; margin-top: 8px; }

.illus-tabs { display: flex; gap: 2px; }
.illus-tab { flex: 1; padding: 6px 2px; text-align: center; font-size: 8px; text-transform: uppercase; letter-spacing: 0.05em; border: 1px solid var(--border-dim); background: var(--bg-panel); color: var(--text-secondary); }
.illus-tab.active { border-color: var(--accent); color: var(--accent); background: rgba(126,200,227,0.1); box-shadow: 0 0 4px var(--accent); }
.illus-grid { display: grid; grid-template-columns: repeat(6, 24px); grid-template-rows: repeat(4, 24px); gap: 1px; }
.illus-grid > div { background: var(--bg-panel); border: 1px solid var(--border-dim); }
.illus-grid > .wall { background: var(--bg-base); border-color: var(--bg-panel-elevated); }
.illus-grid > .player { color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: bold; text-shadow: 0 0 4px var(--accent); }
.illus-grid > .enemy { color: var(--danger); display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: bold; text-shadow: 0 0 4px var(--danger); }

.tutorial-wrapper { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.tutorial-page { display: flex; flex-direction: column; gap: 12px; }
.tutorial-page-title { font-size: 14px; color: var(--accent); text-transform: uppercase; letter-spacing: 0.1em; }
.tutorial-illustration { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 12px; background: var(--bg-panel); border: 1px solid var(--border-dim); }
.tutorial-page-index { font-size: 9px; color: var(--text-dim); align-self: flex-end; }
.tutorial-chip { display: inline-block; padding: 2px 8px; font-size: 9px; border: 1px solid var(--border-dim); color: var(--text-secondary); margin: 2px; }
.tutorial-body { font-size: 11px; color: var(--text-secondary); line-height: 1.6; }
.tutorial-nav { display: flex; gap: 8px; flex-wrap: wrap; }
.tutorial-dots { display: flex; gap: 4px; justify-content: center; }
.tutorial-dot { width: 20px; height: 20px; border: 1px solid var(--border-dim); display: flex; align-items: center; justify-content: center; font-size: 8px; color: var(--text-dim); }
.tutorial-dot.active { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 4px var(--accent); }

.settings-screen { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.motion-options { display: flex; flex-direction: column; gap: 8px; }

.hidden-branch { display: none; }

.btn-crt.primary {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(126,200,227,0.1);
  box-shadow: 0 0 8px var(--accent);
}

/* Destructive-tone button (icon-first-ui-density SESSION-04). Portrait
   counterpart of the wide-only `.btn-danger` in styles/wide.css. Used by
   library DELETE / DELETE LOCAL STATE, creation −REMOVE, creation DELETE
   / CONFIRM DELETE — all sanctioned destructive icon+text pairings under
   Custom Rule 14 (red reserved for hostiles + semantic error states).
   Icon-side tone is applied via `.icon-danger` on the sprite; this rule
   only owns the border/color/background around it. */
.btn-crt.btn-danger {
  border-color: var(--danger);
  color: var(--danger);
  background: rgba(232, 58, 58, 0.06);
}
.btn-crt.btn-danger:hover,
.btn-crt.btn-danger:focus {
  border-color: var(--danger);
  color: #fff;
  background: rgba(232, 58, 58, 0.2);
  box-shadow: 0 0 8px var(--danger);
}

/* Per-run action row on the portrait library card (icon-first-ui-density
   SESSION-04). Holds the icon-only RESUME chevron and the icon+text
   destructive DELETE LOCAL STATE button. The surrounding `.run-row`
   itself carries the 48px touch floor via the shared `.console-row, ...,
   .run-row { min-height: 48px }` rule — this container only owns layout;
   no new min-height is introduced. */
.run-actions {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}

/* Icon-prefixed toggle-row title (icon-first-ui-density SESSION-04).
   Inline-flex wrapper for the sprite + label pair on settings toggle rows
   (MASTER MUTE, GLITCH, SCANLINES & GRAIN). Slider labels are NOT
   iconized per GAP §6.6 — the label/input/value trio the e2e asserts. */
.row-title-icon {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* HP-bar-only class (emitted by createHPBar). Repointed off enemy-red onto --hp
   per Custom Rule 14 — red is reserved for hostiles + semantic errors. */
.bar-fill-danger {
  background: var(--hp);
  box-shadow: 0 0 2px var(--hp);
}

/* Critical HP (<25%) — createHPBar adds `.danger` on the fill. Warning-orange
   keeps "almost dead" reading as caution without collapsing back to enemy-red. */
.bar-fill-hp.danger,
.bar-fill-danger.danger {
  background: var(--warning);
  box-shadow: 0 0 6px var(--warning);
}

.bar-fill-charge {
  background: var(--accent);
  box-shadow: 0 0 2px var(--accent);
}

.token-echo {
  color: var(--danger);
  background: rgba(232,58,58,0.1);
  border: 1px dashed var(--danger);
}

.token-dead,
.sigil-dead {
  opacity: 0.4;
  filter: grayscale(1);
}

.sigil-dead {
  position: relative;
  color: var(--danger);
  border-color: var(--danger);
}

.sigil-dead::after {
  content: '\2715';
  position: absolute;
  font-size: 24px;
  color: var(--danger);
}

.log-turn { color: var(--text-dim); font-size: 9px; margin-right: 6px; }
.log-combat,
.log-damage,
.log-death { color: var(--danger); }
.log-death { font-weight: 700; }
.log-discovery { color: #e8d23a; }
.log-heal { color: var(--heal); }
.log-info { color: var(--accent); }
.log-move { color: var(--text-secondary); }

.chroma-text {
  position: relative;
  text-shadow: 0 0 8px var(--accent);
}

.chroma-text.ghosting::before,
.chroma-text.ghosting::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.47;
}

.chroma-text.ghosting::before { color: #f00; transform: translateX(-2px); }
.chroma-text.ghosting::after { color: #00f; transform: translateX(2px); }

.lattice {
  display: grid;
  grid-template-columns: repeat(20, 1fr);
  flex: 1;
  overflow: hidden;
  background: var(--bg-base);
}

.cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  font-size: 6px;
}

.cell.visible { filter: brightness(1); }
.cell.visited { filter: brightness(0.4); }
.cell.unvisited { filter: brightness(0); background: #040108; }
.cell.descent { background: rgba(58,232,168,0.12); box-shadow: inset 0 0 6px var(--heal); }
.cell.container { background: rgba(232,210,58,0.1); box-shadow: inset 0 0 4px #e8d23a; }
.cell.pillar { background: radial-gradient(circle, #4a2090 30%, #2a1048 70%); }
.cell.rubble { background: radial-gradient(circle at 30% 30%, #4a2090 2px, #14092e 2px); }
.cell.hazard { background: repeating-linear-gradient(45deg, transparent 0 2px, rgba(232,58,58,0.08) 2px 3px); }
.wall-n { box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent) 25%, transparent); }
.wall-s { box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--accent) 25%, transparent); }
.wall-e { box-shadow: inset -1px 0 0 color-mix(in srgb, var(--accent) 25%, transparent); }
.wall-w { box-shadow: inset 1px 0 0 color-mix(in srgb, var(--accent) 25%, transparent); }

.party-token,
.enemy-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--bg-base);
  font-weight: 700;
}

.party-token { width: 78%; height: 78%; background: var(--accent); box-shadow: 0 0 16px var(--accent); }
.enemy-marker { width: 70%; height: 70%; background: var(--danger); box-shadow: 0 0 12px var(--danger); }
.descent-icon { color: var(--heal); text-shadow: 0 0 12px var(--heal); }

.console-expanded {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.console-expanded.open { max-height: 400px; }
.jitter-target { will-change: transform; }

.page { display: none; }
.page.active { display: block; }
.illus { display: flex; align-items: center; justify-content: center; min-height: 120px; padding: 16px; background: var(--bg-base); border: 1px solid var(--border-dim); }
.illus-console { padding: 8px; color: var(--accent); background: var(--bg-panel); border: 1px solid var(--accent); text-shadow: 0 0 4px var(--accent); }
.illus-dpad-center { background: transparent; border: 1px dashed var(--border-dim); color: var(--text-dim); font-size: 6px; }

.error-screen { display: none; }
.error-screen.active { display: flex; }
.runtime-data-failure { display: grid; gap: 12px; padding: 24px; color: var(--text-primary); }
.runtime-failure-action { justify-self: start; }

/* Update toast — surfaced on runtime:update-ready on every route. Fixed
   above the console dock so it never overlaps the tab bar; the RELOAD
   button keeps a ≥44px hit target per Custom Rule 5 touch targets.
   mobile-pwa-hardening SESSION-01 — bottom offset and a lateral max-width
   both clear the safe-area insets so a wide (deferred-retry) toast can
   never extend under a device cutout, even centered via translateX. */
.update-toast {
  position: fixed;
  left: 50%;
  bottom: calc(24px + var(--safe-area-bottom));
  transform: translateX(-50%);
  max-width: calc(100vw - var(--safe-area-left) - var(--safe-area-right) - 32px);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 16px;
  background: var(--bg-panel-elevated);
  border: 1px solid var(--accent);
  color: var(--accent);
  box-shadow: 0 0 12px var(--accent);
  z-index: 60;
  font-size: 12px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  text-shadow: 0 0 6px var(--accent);
  pointer-events: auto;
}
.update-toast-label { white-space: nowrap; }
.update-toast-reload {
  min-height: 48px;
  min-width: 48px;
  padding: 10px 20px;
  font-size: 12px;
  letter-spacing: 0.2em;
}

/* Manual links (the-manual S02; chip recompacted playtest-clarity S01) — one
   primitive, two visual variants. `.is-interactive` supplies the cursor +
   focus-visible ring; the rules below defeat its border/box-shadow on the
   inline variant so the affordance reads as a dotted-underline text link. The
   chip variant keeps a ≥44×44 transparent hit target (uses `height:` +
   `min-width:` rather than `min-height:` to stay clear of the M97 touch-target
   scanner) but draws its visible affordance as a compact ~22px bordered ring
   via `::before`, so the bulky empty box no longer floats over other UI. */
.manual-term-link {
  background: transparent;
  color: var(--accent);
  font-family: inherit;
  font-size: inherit;
  padding: 0;
  border: 0;
  cursor: pointer;
  text-decoration: underline dotted currentColor;
  text-underline-offset: 2px;
  transition: text-shadow 0.15s ease;
}

.manual-term-link:hover:not([aria-disabled="true"]):not([disabled]),
.manual-term-link:focus-visible {
  text-shadow: 0 0 6px var(--accent);
  color: var(--accent);
}

.manual-term-link:not(.manual-term-link--chip):hover:not([aria-disabled="true"]):not([disabled]),
.manual-term-link:not(.manual-term-link--chip):active:not([aria-disabled="true"]):not([disabled]) {
  box-shadow: none;
  transform: none;
}

.manual-term-link--chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  min-width: 44px;
  padding: 0;
  border: 0;
  text-decoration: none;
  font-size: 10px;
  font-weight: 400;
  line-height: 1;
  color: var(--accent);
  background: transparent;
}

/* The visible glyph: a centered ~22×22 bordered ring behind the `?` text.
   The button stays 44×44 for touch; only this ring is drawn. */
.manual-term-link--chip::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 22px;
  height: 22px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--accent);
  border-radius: 2px;
  pointer-events: none;
}

.manual-term-link--chip:hover:not([aria-disabled="true"]):not([disabled])::before,
.manual-term-link--chip:focus-visible::before {
  box-shadow: 0 0 6px var(--accent);
}
