/* ==========================================================================
   AI STORY FORGE — "ARCANE LEDGER" THEME, v2
   Deep, moody, atmospheric grimoire aesthetic: a near-void ink base, a
   warm ember accent that's tuned to actually glow (not just sit flat as
   a solid fill), hairline borders instead of heavy ones, and layered
   shadows (a tight dark contact shadow + a soft low-opacity ember bloom)
   in place of flat drop-shadows. Every existing selector below still
   works unchanged — only token values and the shared component patterns
   (buttons, panels, modals, badges, meters) were deepened; nothing that
   reads app-init.js / story-ui.js / character-sheet-ui.js's element IDs
   was touched.
   ========================================================================== */

:root {
  /* Base surfaces — darkest at the true background, each panel level a
     small, deliberate step up so depth reads even with all the borders
     removed; never pure black, which flattens everything under an amber
     light source instead of receding into it. */
  --bg-dark: #0c0a08;
  --panel-bg: #17130f;
  --panel-bg-alt: #201a13;
  --panel-bg-inset: #090705;
  --border-color: #2a2117;
  --border-accent: #4a3a26;

  /* Ember accent — the one warm, glowing thing in the palette. Kept a
     shade richer/darker than a flat "gold" so it can bloom via shadow
     without blowing out to pastel. */
  --primary-purple: #b8842f;
  --primary-purple-hover: #d69f42;
  --primary-purple-rgb: 184, 132, 47;

  --danger-red: #8f2c22;
  --danger-red-hover: #a8352b;
  --secondary-btn: #322a1f;
  --secondary-btn-hover: #403525;

  --text-main: #e4d7bd;
  --text-bright: #f5ecd6;
  --text-muted: #8f8168;

  --accent-gold: #c9a13a;
  --health-green: #4f7a45;
  --resolve-blue: #47698c;

  /* Shared elevation/shape/motion tokens. Shadows are layered: a tight
     near-black contact shadow for physical lift, plus (on interactive/
     accented elements only, via --glow-*) a soft ember bloom so the
     brass accent reads as lit rather than painted on. */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.6);
  --shadow-md: 0 10px 24px -4px rgba(0, 0, 0, 0.65);
  --shadow-lg: 0 28px 56px -16px rgba(0, 0, 0, 0.78);
  --glow-sm: 0 0 0 1px rgba(var(--primary-purple-rgb), 0.15), 0 2px 10px -2px rgba(var(--primary-purple-rgb), 0.25);
  --glow-md: 0 0 0 1px rgba(var(--primary-purple-rgb), 0.18), 0 4px 22px -2px rgba(var(--primary-purple-rgb), 0.3);
  --ease: cubic-bezier(0.4, 0, 0.2, 1);

  --font-display: 'Cinzel', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --app-height: 100vh;

  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: var(--font-body);
}

/* Display type — reserved for headings large enough for Cinzel's carved
   capitals to actually read well; small uppercase labels (.section-title
   etc.) deliberately stay in the body face at a wider letter-spacing
   instead, since a display serif goes mushy under ~13px. */
.logo,
.auth-title,
.modal-header h2,
.panel-header-title h2,
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.01em;
}

html, body {
  width: 100%;
  height: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  background-color: var(--bg-dark);
  /* Fixed, subtle vignette: barely-there warmth at center, sinking to
     the void base at the edges. attachment:fixed so it reads as one
     continuous atmosphere behind scrolling content, not a tile that
     repeats per-panel. */
  background-image:
    radial-gradient(ellipse 120% 90% at 50% -10%, rgba(184, 132, 47, 0.05) 0%, transparent 55%),
    radial-gradient(ellipse 140% 100% at 50% 110%, rgba(0, 0, 0, 0.5) 0%, transparent 60%);
  background-attachment: fixed;
  color: var(--text-main);
  overflow-x: hidden;
  font-size: 14px;
  /* Prevent iOS Safari from auto-bumping text size in landscape, and
     stop the blue tap-flash Android/Chrome shows on touch. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

/* Thin, palette-matched scrollbars everywhere the app scrolls (story
   feed, sidebars, modals) instead of the browser's default chrome —
   supported in Firefox via scrollbar-color and Chromium/Safari via the
   ::-webkit- pseudo-elements; degrades harmlessly to the OS default on
   anything else. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-accent) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background-color: var(--border-accent);
  border-radius: 999px;
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--secondary-btn-hover); }

/* Keyboard-navigation focus ring — visible and on-brand (brass, not the
   browser's default blue) for anyone tabbing through forms/buttons,
   without adding a ring to every mouse click via :focus-visible. */
:focus-visible {
  outline: 2px solid var(--primary-purple);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Touch-input devices (phones, tablets, touch laptops) get roomier tap
   targets everywhere buttons appear; mouse/trackpad devices keep the
   tighter desktop sizing. Driven by the (pointer: coarse) feature query,
   not by device/OS name, so it applies correctly to any touchscreen. */
@media (pointer: coarse) {
  button, .btn, select, .close-sidebar-btn, .close-btn {
    min-height: 44px;
  }
  input[type="text"],
  input[type="password"],
  input[type="email"],
  input[type="number"],
  select,
  textarea {
    /* 16px stops iOS Safari from zooming the page in when a field is
       focused; below that threshold it zooms automatically. */
    font-size: 16px;
  }
}

button, .btn {
  touch-action: manipulation; /* removes the ~300ms tap delay & double-tap-zoom on touchscreens */
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--primary-purple);
  width: 15px;
  height: 15px;
}

/* AUTHENTICATION SCREEN */
.auth-screen {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: var(--app-height);
  height: 100dvh;
  background: radial-gradient(circle at 50% 40%, #241a0e 0%, var(--bg-dark) 65%);
  padding: calc(1rem + var(--safe-top)) calc(1rem + var(--safe-right)) calc(1rem + var(--safe-bottom)) calc(1rem + var(--safe-left));
}

.auth-card {
  background: linear-gradient(155deg, var(--panel-bg) 0%, var(--panel-bg-inset) 130%);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: 2.25rem 2rem;
  width: 100%;
  max-width: 400px;
  box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(245, 236, 214, 0.03);
  text-align: center;
}

.auth-title {
  font-size: 1.9rem;
  margin-bottom: 0.3rem;
  color: var(--text-bright);
  text-shadow: 0 0 24px rgba(var(--primary-purple-rgb), 0.25);
}

.auth-subtitle {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
}

.license-footer-note {
  color: var(--text-muted);
  font-size: 0.72rem;
  margin-top: 1rem;
}
.license-footer-note a {
  color: var(--text-muted);
  text-decoration: underline;
}
.license-footer-note a:hover {
  color: var(--text-main);
}
.license-footer-note-header {
  margin-top: 0;
  margin-left: 0.25rem;
}

.auth-tabs {
  display: flex;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--border-color);
}

.auth-tab {
  flex: 1;
  padding: 0.6rem;
  background: none;
  border: none;
  color: var(--text-muted);
  font-weight: 600;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}

.auth-tab.active {
  color: var(--primary-purple);
  border-bottom-color: var(--primary-purple);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  text-align: left;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.form-group label {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-weight: 600;
}

input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
select,
textarea {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  color: var(--text-main);
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  outline: none;
  width: 100%;
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

input:focus, select:focus, textarea:focus {
  border-color: var(--primary-purple);
  box-shadow: 0 0 0 3px rgba(201, 146, 47, 0.18);
}

/* APP CONTAINER & HEADER */
.app-container {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: var(--app-height);
  height: 100dvh;
  overflow: hidden;
}

/* CSS fallback for the Fullscreen button on browsers where the real
   Fullscreen API is unavailable or rejects the request — this is the
   normal case on every iPhone browser (Chrome, Safari, etc.), since iOS
   WebKit only supports the Fullscreen API on a <video> element, never on
   a page/div, regardless of which browser is asking. Hiding just the
   header (not the toolbar) frees most of the same vertical space real
   fullscreen would, while keeping the Fullscreen/Exit button — which
   lives in .story-toolbar, not the header — reachable to turn it back
   off. .workspace is flex:1 inside .app-container, so it expands into
   the freed space automatically with no extra height math needed. */
body.immersive-mode .app-header {
  display: none;
}

body.immersive-mode .story-toolbar {
  display: none;
}

.immersive-exit-btn {
  display: none;
  position: fixed;
  top: calc(0.6rem + var(--safe-top));
  right: calc(0.6rem + var(--safe-right));
  z-index: 50;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--border-color);
  background: rgba(24, 24, 32, 0.85);
  color: var(--text-main);
  font-size: 1.1rem;
  line-height: 1;
  backdrop-filter: blur(4px);
}

body.immersive-mode .immersive-exit-btn {
  display: block;
}

.a2hs-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 1rem;
  padding-left: calc(1rem + var(--safe-left));
  padding-right: calc(0.5rem + var(--safe-right));
  background: var(--primary-purple);
  color: #1a1206;
  font-size: 0.8rem;
  flex-shrink: 0;
}

.a2hs-banner[hidden] {
  display: none;
}

.a2hs-dismiss {
  background: transparent;
  border: none;
  color: #1a1206;
  font-size: 1.2rem;
  line-height: 1;
  padding: 0.2rem 0.5rem;
  flex-shrink: 0;
}

/* Immersive mode hides this too — it's about getting into a full-screen
   experience, so it shouldn't be taking up space once you're already
   using the CSS fallback for one. */
body.immersive-mode .a2hs-banner {
  display: none;
}

.app-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  padding-top: calc(0.75rem + var(--safe-top));
  padding-left: calc(1rem + var(--safe-left));
  padding-right: calc(1rem + var(--safe-right));
  background: linear-gradient(180deg, var(--panel-bg-alt) 0%, var(--panel-bg) 100%);
  border-bottom: 1px solid var(--border-color);
  box-shadow: var(--shadow-md);
  min-height: auto;
  box-sizing: border-box;
  flex-shrink: 0;
  flex-wrap: wrap;
  gap: 0.75rem;
  position: relative;
  z-index: 20;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  flex-wrap: wrap;
}

.header-controls {
  display: none;
  position: absolute;
  top: calc(100% + 0.45rem);
  right: 0;
  z-index: 30;
  flex-direction: column;
  align-items: stretch;
  min-width: 220px;
  max-height: min(70vh, 32rem);
  overflow-y: auto;
  gap: 0.5rem;
  padding: 0.65rem;
  background: var(--panel-bg);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}

.header-controls.is-open {
  display: flex;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  margin-left: auto;
  position: relative;
}

.header-primary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.header-more-toggle {
  display: inline-flex;
}

/* HEADER BUTTON STYLING (FILLED BACKGROUNDS) */
.app-header button,
.app-header .btn {
  background-color: var(--secondary-btn);
  color: var(--text-main);
  border: 1px solid var(--border-color);
  padding: 0.4rem 0.8rem;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  transition: background-color 0.15s var(--ease), transform 0.12s var(--ease);
  white-space: nowrap;
}

.app-header button:hover,
.app-header .btn:hover {
  background-color: var(--secondary-btn-hover);
  transform: translateY(-1px);
}

.app-header .btn-danger,
.app-header button.btn-danger {
  background-color: var(--danger-red);
  color: var(--text-bright);
}
.app-header .btn-danger:hover,
.app-header button.btn-danger:hover {
  background-color: var(--danger-red-hover);
}

.app-header .btn-primary,
.app-header button.btn-primary {
  background-color: var(--primary-purple);
  color: #1a1206;
}
.app-header .btn-primary:hover,
.app-header button.btn-primary:hover {
  background-color: var(--primary-purple-hover);
}

.logo {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-bright);
  white-space: nowrap;
  text-shadow: 0 0 18px rgba(var(--primary-purple-rgb), 0.35);
}

.user-badge {
  font-size: 0.8rem;
  color: var(--text-muted);
  background: var(--panel-bg-alt);
  padding: 0.2rem 0.5rem;
  border-radius: 20px;
  border: 1px solid var(--border-color);
  white-space: nowrap;
}

.slot-picker {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  white-space: nowrap;
}

/* WORKSPACE & SIDEBAR COLLAPSE */
.workspace {
  display: flex;
  flex: 1;
  /* .app-container is a column flexbox with a fixed dvh height, so
     flex:1 + min-height:0 lets this fill exactly what's left under the
     header — correct whether the header is one row (PC/tablet landscape)
     or wraps to several (small phones), instead of subtracting a
     hard-coded pixel amount that only matched one specific header height. */
  min-height: 0;
  width: 100vw;
  overflow: hidden;
  position: relative;
}

.sidebar {
  width: 320px;
  min-width: 320px;
  background-color: var(--panel-bg);
  background-image: linear-gradient(180deg, rgba(0,0,0,0.12) 0%, transparent 8%);
  padding: 1rem;
  overflow-y: auto;
  flex-shrink: 0;
  box-sizing: border-box;
  z-index: 10;
}

.sidebar-left { border-right: 1px solid var(--border-color); }
.sidebar-right { border-left: 1px solid var(--border-color); }
.sidebar.is-hidden { display: none !important; }

/* Tap-outside-to-close scrim for the sidebar drawers (see the
   "SIDEBAR DRAWERS" tablet/phone breakpoint below, and the drawer
   behavior script at the end of index.html). Hidden and inert on
   desktop, where the sidebars sit inline and never need a scrim. */
.sidebar-scrim {
  display: none;
}

.panel-header-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--border-accent);
  box-shadow: 0 1px 0 rgba(var(--primary-purple-rgb), 0.12);
  padding-bottom: 0.4rem;
  margin-bottom: 1rem;
}

.panel-header-title h2 {
  font-size: 1.1rem;
  color: var(--text-bright);
}

.close-sidebar-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 1.25rem;
  cursor: pointer;
  padding: 0 0.4rem;
  border-radius: 4px;
}

.close-sidebar-btn:hover {
  color: var(--text-bright);
  background: var(--panel-bg-alt);
}

.character-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.75rem;
}

.badge {
  background: var(--primary-purple);
  color: #1a1206;
  padding: 0.15rem 0.5rem;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  box-shadow: var(--glow-sm);
}

/* VITAL METERS */
.stat-group { margin-bottom: 0.75rem; }

.stat-header {
  display: flex;
  justify-content: space-between;
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 0.25rem;
}

.meter-bar {
  width: 100%;
  height: 8px;
  background: var(--panel-bg-inset);
  border-radius: 4px;
  overflow: hidden;
  border: 1px solid var(--border-color);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}

.meter-fill {
  height: 100%;
  transition: width 0.3s ease;
  background-image: linear-gradient(180deg, rgba(255,255,255,0.12) 0%, transparent 45%);
}

.health-fill { background-color: var(--health-green); }
/* Dying: unconscious and losing hp each round until stabilized or dead.
   Dead: locked at whatever fraction remained when the death threshold hit. */
.health-fill.hp-dying { background-color: #b45309; }
.health-fill.hp-dead { background-color: #4b5563; }
.resolve-fill { background-color: var(--resolve-blue); }

/* ABILITY SCORES GRID */
.section-title {
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 1.25rem 0 0.5rem 0;
  letter-spacing: 0.05em;
}

.section-title-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.label-row {
  margin-bottom: 0.35rem;
}

.label-row label {
  margin-bottom: 0;
}

.stats-roll-pool {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  padding: 0.6rem 0.7rem;
  margin-bottom: 0.6rem;
  background: var(--panel-bg-alt);
  border: 1px dashed var(--border-accent);
  border-radius: 8px;
}

.stats-roll-pool-hint {
  font-size: 0.75rem;
  color: var(--text-muted);
  width: 100%;
  margin-bottom: 0.15rem;
}

.roll-chip {
  border: 1px solid var(--border-accent);
  background: var(--panel-bg);
  color: var(--text-main);
  font-weight: 700;
  font-size: 0.9rem;
  padding: 0.4rem 0.75rem;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.roll-chip:hover:not(.is-used) {
  border-color: var(--primary-purple);
  transform: translateY(-1px);
}

.roll-chip.is-selected {
  border-color: var(--primary-purple);
  background: var(--primary-purple);
  color: #1a1206;
}

.roll-chip.is-used {
  opacity: 0.45;
  cursor: pointer;
  font-weight: 600;
}

.roll-chip .roll-chip-target {
  font-size: 0.7rem;
  text-transform: uppercase;
  opacity: 0.8;
  margin-left: 0.35rem;
}

.condition-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.75rem;
}

.condition-chip {
  border: 1px solid var(--border-accent);
  background: var(--panel-bg);
  color: var(--text-muted);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.condition-chip:hover {
  border-color: var(--primary-purple);
}

.condition-chip.is-active {
  border-color: var(--danger-red);
  background: var(--danger-red);
  color: white;
}

.form-group.stat-drop-target {
  outline: 2px solid var(--primary-purple);
  outline-offset: 2px;
  border-radius: 6px;
}

.class-hint {
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.4;
  margin: 0;
}

.ability-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.ability-box {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 0.5rem;
  text-align: center;
  display: flex;
  flex-direction: column;
}

.ability-box .lbl {
  font-size: 0.7rem;
  color: var(--text-muted);
  font-weight: bold;
}

.ability-box .val {
  font-size: 0.9rem;
  font-weight: bold;
  color: var(--text-bright);
}

.ability-box .val.val-clickable {
  cursor: pointer;
}

.ability-box .val.val-clickable:hover {
  text-decoration: underline;
  color: var(--accent-gold);
}

.char-subline {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-bottom: 0.75rem;
}

.combat-stats-grid { grid-template-columns: repeat(4, 1fr); }

.caster-info {
  font-size: 0.78rem;
  color: var(--accent-gold);
  margin-bottom: 0.5rem;
  line-height: 1.4;
}

/* SPELL SLOT TRACKER */
.spell-slot-tracker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
}

.spell-slot-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 0.25rem 0.55rem;
  font-size: 0.78rem;
}

.spell-slot-row.is-empty {
  border-color: var(--danger-red);
  opacity: 0.7;
}

.spell-slot-label {
  color: var(--text-muted);
  font-weight: 600;
}

.spell-slot-count {
  color: var(--text-bright);
  font-variant-numeric: tabular-nums;
}

.spell-slot-row.is-empty .spell-slot-count { color: var(--danger-red); }

.spell-slot-bonus {
  color: var(--accent-gold);
  font-size: 0.7rem;
}

/* Groups each caster class's slot rows on its own line when a character has
   more than one spellcasting class (e.g. Cleric 5/Wizard 5). */
.spell-slot-class-heading {
  flex-basis: 100%;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-top: 0.3rem;
}

.spell-slot-class-heading:first-child { margin-top: 0; }

.spell-rest-btn {
  margin-bottom: 0.75rem;
  width: 100%;
}

/* Known-spell list rows: name/level on the left, remaining slots + remove on the right */
.spell-li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.spell-li-text { flex: 1; }

.spell-li-slots {
  font-size: 0.75rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.spell-li-slots.is-empty {
  color: var(--danger-red);
}

.spell-selector-container {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.spell-selector-container select { flex: 1; }

.spell-form-card {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 0.9rem;
  margin-top: 1rem;
}

.spell-form-card h4 {
  color: var(--text-bright);
  font-size: 0.9rem;
  margin-bottom: 0.6rem;
}

/* SPELL & FEATURE PICKERS */
.spell-picker-card {
  background: var(--panel-bg-inset);
  padding: 0.85rem 1rem;
  border-radius: 8px;
  border: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.action-picker-group,
.action-feature-picker {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.action-feature-picker {
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
}

.spell-picker-label {
  font-weight: 600;
  color: var(--accent-gold);
  font-size: 0.8rem;
  display: block;
}

.spell-picker-row {
  display: flex;
  gap: 8px;
  align-items: center;
  width: 100%;
}

.spell-picker-row select {
  flex: 1;
  background: var(--panel-bg-inset);
  border-color: var(--border-color);
}

.feature-preview-card,
.spell-preview-card {
  width: 100%;
  box-sizing: border-box;
  margin-top: 0.35rem;
  padding: 0.75rem 0.9rem;
  background: var(--panel-bg-alt);
  border-left: 3px solid var(--accent-gold);
  border-radius: 6px;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.spell-preview-card h4,
.feature-preview-card h4 {
  margin-bottom: 0.3rem;
  color: var(--text-bright);
  font-size: 0.9rem;
}

.spell-preview-card p,
.feature-preview-card p {
  margin: 0.15rem 0;
  line-height: 1.35;
}

.spell-preview-desc,
.feature-preview-desc,
.feature-preview-gm {
  margin-top: 0.4rem;
  font-style: italic;
  color: var(--text-muted);
}

/* LISTS & ITEMS */
/* Equipment rows (renderEquipmentLists in app.js) — item name plus a strip
   of computed-stat "chips" sourced from parseEquipmentItem/describeItemStats,
   so equipped gear visibly shows what it's actually doing for the character. */
.equipment-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.4rem 0;
  border-bottom: 1px dashed var(--border-color);
}

.equipment-row:last-child { border-bottom: none; }

.item-info {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  flex: 1;
  min-width: 0;
}

.item-name {
  font-weight: 600;
}

.item-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  align-items: center;
}

.item-stats-none {
  font-size: 0.75rem;
  color: var(--text-muted, #999);
  font-style: italic;
}

.item-stats-inactive .stat-chip {
  opacity: 0.55;
}

.stat-chip {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 4px;
  padding: 0.1rem 0.4rem;
  font-size: 0.72rem;
  color: var(--accent-gold);
  white-space: nowrap;
}

.stat-chip-note {
  font-size: 0.72rem;
  color: var(--text-muted, #999);
  font-style: italic;
}

.btn-equip, .btn-unequip {
  flex-shrink: 0;
  align-self: flex-start;
}

.inventory-list, .spells-list, .quest-list, .faction-list, .npc-list {
  list-style: none;
  padding: 0;
}

.inventory-list li, .spells-list li, .quest-list li, .faction-list li, .npc-list li {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  padding: 0.5rem;
  border-radius: 6px;
  margin-bottom: 0.4rem;
  font-size: 0.85rem;
  line-height: 1.3;
}

/* Known NPCs (#npcList) renders .npc-card portraits (shared with the
   combat-only monster dock, .npc-dock-list) as direct children instead of
   <li> — only the "None met" empty state still uses a real <li>, styled
   by the shared rule above and spanning the full row via grid-column so
   it doesn't get squeezed into a single narrow card-width column. */
.npc-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 0.6rem;
}
.npc-list li { grid-column: 1 / -1; }

/* Known NPCs (#npcList) renders .npc-card portraits (shared with the
   combat-only monster dock, .npc-dock-list) as direct children instead of
   <li> — only the "None met" empty state still uses a real <li>, styled
   by the shared rule above. */
.npc-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 0.6rem;
}

.empty-hint {
  color: var(--text-muted);
  font-style: italic;
  font-size: 0.8rem;
}

.lore-item {
  display: flex;
  justify-content: space-between;
  padding: 0.4rem 0;
  border-bottom: 1px dashed var(--border-color);
  font-size: 0.85rem;
}

.lore-item .label { color: var(--text-muted); }
.lore-item .val { font-weight: 600; color: var(--text-bright); }

/* COMBAT TRACKER */
.enemy-list {
  list-style: none;
  padding: 0;
}

.enemy-item {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-accent);
  border-radius: 6px;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.4rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.enemy-item:not(.is-down):hover {
  border-color: var(--primary-purple);
}

.enemy-item.is-targeted {
  border-color: var(--danger-red);
  background: rgba(168, 53, 43, 0.08);
}

.enemy-item.is-down {
  opacity: 0.55;
  cursor: default;
}

.enemy-item-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.85rem;
  margin-bottom: 0.3rem;
}

.enemy-item-name {
  font-weight: 600;
  color: var(--text-bright);
}

.enemy-item-meta {
  color: var(--text-muted);
  font-size: 0.75rem;
}

.enemy-fill { background: var(--danger-red); }

/* ALLY TRACKER (mirrors the enemy tracker, distinct color) */
.combat-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.combat-count-badge {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
  white-space: nowrap;
}

.combat-subheading {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  margin: 0.5rem 0 0.3rem;
}

.ally-list {
  list-style: none;
  padding: 0;
}

.ally-item {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-accent);
  border-radius: 6px;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.4rem;
}

.ally-item.is-down {
  opacity: 0.55;
}

.ally-item-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.85rem;
  margin-bottom: 0.3rem;
}

.ally-item-name {
  font-weight: 600;
  color: var(--text-bright);
}

.ally-item-meta {
  color: var(--text-muted);
  font-size: 0.75rem;
}

.ally-fill { background: var(--health-green); }

/* BATTLE MAP GRID */
.combat-grid {
  display: grid;
  gap: 2px;
  margin-bottom: 0.5rem;
  overflow-x: auto;
  background: var(--bg-dark);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 4px;
}

.grid-cell {
  width: 40px;
  height: 40px;
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 3px;
  cursor: pointer;
  position: relative;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.grid-cell:hover {
  border-color: var(--primary-purple);
  background: var(--panel-bg);
}

.token {
  position: absolute;
  inset: 3px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-bright);
  pointer-events: none;
  user-select: none;
}

.token-player { background: var(--resolve-blue); box-shadow: 0 0 0 1px var(--accent-gold); }
.token-ally { background: var(--health-green); }
.token-enemy { background: var(--danger-red); }

/* AOE TARGETING PREVIEW — applied by CombatGrid.renderAoEPreview() while
   aoePicking.active is true. Origin gets its own class so it stays visibly
   distinct from the rest of the templated squares even once the shape's
   footprint is large. */
.aoe-preview-cell { background: rgba(224, 96, 40, 0.35); border-color: var(--danger-red); }
.aoe-origin-cell { box-shadow: inset 0 0 0 2px var(--accent-gold); }

/* INITIATIVE TRACKER */
.initiative-list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.initiative-item {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.initiative-item.is-current {
  border-color: var(--primary-purple);
  color: var(--text-bright);
  background: rgba(108, 141, 255, 0.12);
}

.initiative-item.is-next {
  border-color: var(--accent-gold);
}

.initiative-item.is-surprised {
  opacity: 0.55;
  font-style: italic;
}

.initiative-item.is-down {
  opacity: 0.4;
  text-decoration: line-through;
}

.initiative-item-name {
  font-weight: 600;
}

.initiative-item-marker {
  display: block;
  color: var(--accent-gold);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
}

.initiative-item-roll {
  color: var(--text-muted);
  font-size: 0.7rem;
}

.combat-turn-hint {
  margin: 0.35rem 0 0.5rem;
  color: var(--text-bright);
  font-size: 0.72rem;
}

.initiative-round-badge {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
  white-space: nowrap;
}

.attack-target-name {
  text-align: center;
  font-weight: 600;
  color: var(--text-bright);
  margin-bottom: 0.6rem;
  font-size: 0.95rem;
}

.attack-iterative-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
  margin-bottom: 0.8rem;
}

/* MIDDLE PANEL: STORY OUTPUT & ACTION CONSOLE */
.story-panel {
  flex: 1;
  display: grid;
  grid-template-rows: auto minmax(260px, 1fr) auto;  /* toolbar | story-body | action-bar — this 260px floor must stay equal to .story-container's own min-height below, or its content can overflow past this row's boundary and visually spill behind the action-bar row that follows it */
  background-color: var(--bg-dark);
  padding: 1rem;
  overflow-y: auto;   /* was overflow: hidden — a tall action-bar (lots of active skills/spells/feats) could need more room than toolbar+story leave it; scroll here instead of clipping it out of reach */
  overflow-x: hidden;
  min-height: 0;
  box-sizing: border-box;
  min-width: 0;
  gap: 0.75rem;
}

.story-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  flex-shrink: 0;
  position: sticky;   /* stays put if .story-panel above ends up scrolling */
  top: 0;
  z-index: 2;
  background-color: var(--bg-dark);
}

.toolbar-toggles, .toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.toggle-label {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.8rem;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}

.story-body {
  display: flex;       /* still flex internally, for story-container + npc-dock side by side */
  gap: 0.75rem;
  min-height: 0;        /* the grid row now owns this element's height */
}

/* Scene image is now its own visible panel ABOVE the story text, in
   normal document flow, instead of a dimmed full-bleed background sitting
   behind it — the old version kept the prose legible, but the art itself
   was barely visible under the readability overlay. Same element IDs,
   same JS-driven show/hide + src-swap logic (maybeGenerateSceneImage in
   story-ui.js) — purely re-styled. */
.scene-image-wrapper {
  position: relative;
  flex-shrink: 0; /* never let the flex column squeeze this to make room for text */
  width: 100%;
  height: clamp(70px, 14vh, 140px); /* shrinks on short windows instead of always claiming a fixed 140px */
  overflow: hidden;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  margin-bottom: 1rem;
  background: var(--bg-dark);
}

.image-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.6rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.85rem;
}

.scene-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover; /* this is only ever a cropped preview now — click "View full image" for the real thing */
  object-position: center;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.scene-image[src]:not([src=""]) {
  opacity: 1; /* fully visible now that it's not competing with text on top of it */
}

/* Light bottom fade so the image blends into the panel edge instead of
   ending on a hard line — much subtler than the old overlay since it no
   longer has to fight for text legibility. */
.scene-image-wrapper::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 35%;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(20, 17, 13, 0) 0%, rgba(20, 17, 13, 0.45) 100%);
}

/* Small pill button over the bottom-right corner of the thumbnail —
   hidden (via the `hidden` attribute) until story-ui.js reveals it once
   an actual image has loaded; there's nothing to expand while the
   placeholder text is showing. Opens #sceneImageModal. */
.scene-image-expand-btn {
  position: absolute;
  right: 0.5rem;
  bottom: 0.5rem;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--border-color);
  background: rgba(20, 17, 13, 0.72);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  color: var(--text-bright, #f2e9db);
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}

.scene-image-expand-btn:hover {
  background: rgba(20, 17, 13, 0.9);
}

.scene-image-expand-btn[hidden] {
  display: none;
}

/* Lightbox content — a wide, mostly-empty modal shell whose only job is
   to frame the full image; see .image-lightbox-body/-img below. */
.image-lightbox-content {
  max-width: min(92vw, 1100px);
}

.image-lightbox-body {
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-dark);
}

.image-lightbox-img {
  display: block;
  width: 100%;
  max-height: calc(90dvh - 3.5rem);
  max-height: calc(90vh - 3.5rem); /* modal-content's own 90dvh/90vh cap, minus the header's height */
  object-fit: contain; /* the whole point of the lightbox: nothing gets cropped here */
}

.story-text {
  overflow-y: auto;
  flex: 1;
  min-height: 140px; /* hard floor so this can never collapse toward zero when the image above it is also competing for the panel's height — see .scene-image-wrapper */
}

/* NPC / Monster portrait dock — persistent column beside the story text,
   independent of storyText's scroll position. Hidden entirely until
   story-ui.js (or whichever module owns combat/NPC state) populates it. */
.npc-dock {
  width: 168px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  overflow-y: auto;
}

.npc-dock[hidden] { display: none; }

.npc-dock-empty {
  color: var(--text-muted);
  font-size: 0.8rem;
  text-align: center;
  padding: 0.75rem 0.5rem;
}

.npc-dock-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.npc-card {
  background: var(--panel-bg);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md, 10px);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  flex-shrink: 0;
}

.npc-card-img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  display: block;
  background: var(--bg-dark);
}

.npc-card-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.75rem;
  text-align: center;
  padding: 0.4rem;
}

.npc-card-name {
  padding: 0.4rem 0.5rem;
  font-family: var(--font-display);
  font-size: 0.8rem;
  text-align: center;
  border-top: 1px solid var(--border-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 900px) {
  .story-body { flex-direction: column; }
  .npc-dock {
    width: 100%;
    flex-direction: row;
    overflow-x: auto;
    overflow-y: visible;
  }
  .npc-dock-list { flex-direction: row; }
  .npc-card { width: 110px; }
}

.story-container {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 260px; /* room for the responsive scene image (clamp, shrinks on short windows) + story-text's own 140px floor + padding — kept equal to .story-panel's grid row minimum above it, or content can overflow into the action bar */
  overflow: hidden; /* clips the background layer to the rounded corners; no longer the scroll container */
  background: linear-gradient(155deg, var(--panel-bg) 0%, var(--panel-bg-inset) 140%);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-md), inset 0 1px 0 rgba(245, 236, 214, 0.03);
}

.story-entry {
  margin-bottom: 1.25rem;
  line-height: 1.7;
  font-size: 0.95rem;
}

/* The single deliberate motion moment in the story feed: each new entry
   (player action or GM reply) eases in from below instead of just
   appearing, so the feed feels alive without animating on every scroll
   or hover. Respects the prefers-reduced-motion rule at the top of this
   file. */
.story-entry:last-child {
  animation: story-entry-in 0.25s var(--ease);
}

@keyframes story-entry-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

.story-entry.user {
  background: var(--panel-bg-alt);
  border-left: 3px solid var(--primary-purple);
  padding: 0.75rem 1rem;
  border-radius: 0 8px 8px 0;
  box-shadow: -1px 0 0 rgba(var(--primary-purple-rgb), 0.12);
}

.story-entry.gm {
  background: rgba(0, 0, 0, 0.18);
  border-left: 3px solid var(--accent-gold);
  padding: 0.75rem 1rem;
  border-radius: 0 8px 8px 0;
}

.entry-sender {
  font-weight: bold;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 0.3rem;
  color: var(--text-muted);
}

.story-entry.user .entry-sender { color: var(--primary-purple); }
.story-entry.gm .entry-sender { color: var(--accent-gold); }

/* ACTION BAR & SUGGESTED CHECKS
   Two parts now instead of one big scrolling blob: .action-bar-controls
   (suggested checks, skill/ability/attack rows, spell & feature pickers,
   OOC button) scrolls on its own and can be as tall as it needs to be;
   .input-group (the actionInput textarea + Send button) sits outside it
   as a pinned, always-visible row, so sending an action never requires
   scrolling past everything else first — see the wrapping div in
   index.html. */
.action-bar {
  display: flex;
  flex-grow: 1;
  flex-direction: column;
  gap: 0.65rem;
  min-height: 0;        /* the grid row now owns this element's height */
  max-height: 46vh;      /* capped so it can't crowd out the story area above it */
  overflow: hidden;       /* .action-bar-controls below does the actual scrolling */
  position: relative;
  z-index: 1;
  background: var(--panel-bg);
  padding: 0.85rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.2s var(--ease), border-color 0.2s var(--ease);
}

.action-bar:focus-within {
  border-color: var(--border-accent);
  box-shadow: var(--shadow-md), var(--glow-sm);
}

.action-bar-controls {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  flex: 1 1 auto;
  min-height: 0;    /* lets this shrink and scroll instead of pushing .input-group out */
  overflow-y: auto;
  overflow-x: hidden;
}

.action-bar .input-group {
  flex-shrink: 0;   /* always fully visible, never squeezed by .action-bar-controls above it */
}

.suggested-checks-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--panel-bg-inset);
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  border: 1px dashed rgba(212, 175, 55, 0.3);
  font-size: 0.82rem;
  flex-wrap: wrap;
}

.suggested-checks-label {
  color: var(--accent-gold);
  font-weight: 600;
  white-space: nowrap;
}

.suggested-checks-list {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.suggested-check-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
  max-width: 24rem;
}

.suggested-check-stakes,
.roll-stakes-summary {
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.35;
  white-space: normal;
}

.roll-stakes-summary {
  margin-top: 0.75rem;
  padding: 0.6rem 0.75rem;
  border-left: 3px solid var(--accent-gold);
  background: var(--panel-bg-inset);
  text-align: left;
}

.campaign-ledger {
  margin-top: 1rem;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  background: var(--panel-bg-inset);
}

.campaign-ledger summary {
  padding: 0.65rem 0.75rem;
  color: var(--accent-gold);
  cursor: pointer;
  font-weight: 600;
}

.campaign-ledger-content {
  padding: 0 0.75rem 0.75rem;
}

.campaign-ledger-heading {
  display: block;
  margin-top: 0.65rem;
}

.campaign-ledger-list {
  margin: 0.25rem 0 0;
  padding-left: 1.25rem;
}

.campaign-ledger-list li {
  margin-top: 0.25rem;
  overflow-wrap: anywhere;
  font-size: 0.85rem;
}

.campaign-journal-body {
  display: grid;
  gap: 1rem;
}

.campaign-journal-body section {
  min-width: 0;
}

.campaign-journal-body h3 {
  margin: 0 0 0.45rem;
  color: var(--accent-gold);
  font-size: 0.95rem;
}

.campaign-journal-summary {
  margin: 0;
  white-space: pre-wrap;
  line-height: 1.55;
}

.campaign-journal-events,
.campaign-journal-list,
.campaign-journal-ledger ul {
  margin: 0;
  padding-left: 1.25rem;
}

.campaign-journal-events li,
.campaign-journal-list li,
.campaign-journal-ledger li {
  margin: 0.35rem 0;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.campaign-dice-log-entry {
  display: grid;
  gap: 0.2rem;
}

.campaign-dice-log-entry pre {
  margin: 0;
  max-height: 12rem;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  font-size: 0.84rem;
}

.campaign-journal-ledger h4 {
  margin: 0.6rem 0 0.25rem;
  font-size: 0.82rem;
}

.skill-check-bar {
  display: flex;
  flex-grow: 1;
  flex-direction: column;
  gap: 0.4rem;
  background: var(--panel-bg-inset);
  padding: 0.6rem 0.75rem;
  border-radius: 6px;
  border: 1px solid var(--border-color);
}

.skill-check-manual-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.skill-check-manual-row select {
  max-width: 180px;
  padding: 0.35rem 0.5rem;
  font-size: 0.82rem;
  background: var(--panel-bg-inset);
}

.dc-badge {
  background: rgba(212, 175, 55, 0.15);
  color: var(--accent-gold);
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
  font-size: 0.78rem;
  font-weight: 600;
  border: 1px solid rgba(212, 175, 55, 0.3);
}

.pending-roll-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: rgba(95, 143, 78, 0.1);
  border: 1px solid rgba(95, 143, 78, 0.3);
  padding: 0.35rem 0.6rem;
  border-radius: 4px;
  font-size: 0.8rem;
  color: var(--health-green);
}

.btn-link {
  background: none;
  border: none;
  color: var(--text-muted);
  text-decoration: underline;
  cursor: pointer;
  font-size: 0.78rem;
}
.btn-link:hover { color: var(--text-bright); }

.ooc-row {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.combat-action-economy {
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-right: auto;
  font-size: 0.78rem;
}
.combat-action-economy label { color: var(--text-muted); }
.combat-action-economy select { max-width: 9rem; }
#combatActionBudget { color: var(--text-muted); }

.input-group {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}

textarea#actionInput {
  flex: 1;
  min-height: 52px;
  max-height: 100px;
  resize: vertical;
  background: var(--panel-bg-inset);
}

.btn-submit {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1.25rem;
  line-height: 1.2;
}

.btn-subtext {
  font-size: 0.65rem;
  opacity: 0.8;
  font-weight: normal;
}

/* BUTTONS & BADGES */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  border-radius: var(--radius-sm);
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: transform 0.12s var(--ease), box-shadow 0.15s var(--ease), background-color 0.15s var(--ease), filter 0.15s var(--ease);
  font-size: 0.85rem;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
}

/* Small lift-on-hover, press-down-on-click — the one bit of hover motion
   every clickable surface in the app gets, so it reads as consistently
   "alive" rather than each button reinventing its own feedback. Disabled
   buttons opt out entirely below. */
.btn:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.08); }
.btn:active:not(:disabled) { transform: translateY(0); filter: brightness(0.95); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; }

.btn-primary { background: var(--primary-purple); color: #1a1206; box-shadow: var(--shadow-sm), var(--glow-sm); }
.btn-primary:hover { background: var(--primary-purple-hover); box-shadow: var(--shadow-sm), var(--glow-md); }

.btn-secondary { background: var(--secondary-btn); color: var(--text-bright); }
.btn-secondary:hover { background: var(--secondary-btn-hover); }

.btn-danger { background: var(--danger-red); color: var(--text-bright); }
.btn-danger:hover { background: var(--danger-red-hover); }

.btn-block { width: 100%; }

/* Size modifiers — used throughout the header toolbar, spellbook, attack
   modal, feat/skill lists, and multiclass/prestige rows, but were never
   actually defined, so every one of those buttons silently rendered at
   full .btn size. */
.btn-sm { padding: 0.3rem 0.65rem; font-size: 0.75rem; }
.btn-xs { padding: 0.15rem 0.45rem; font-size: 0.68rem; }

/* Outline variant — self-contained coloring (transparent fill, bordered),
   used on its own rather than layered on .btn-primary/secondary/danger. */
.btn-outline {
  background: transparent;
  border: 1px solid var(--border-accent);
  color: var(--text-muted);
  box-shadow: none;
}
.btn-outline:hover {
  background: var(--panel-bg-alt);
  border-color: var(--primary-purple);
  color: var(--text-main);
}

.margin-top { margin-top: 1rem; }
.margin-top-sm { margin-top: 0.5rem; }

/* MODALS */
.modal {
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: var(--app-height);
  height: 100dvh;
  background: rgba(10, 8, 5, 0.75);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  z-index: 1000;
  padding: calc(1rem + var(--safe-top)) calc(1rem + var(--safe-right)) calc(1rem + var(--safe-bottom)) calc(1rem + var(--safe-left));
  overscroll-behavior: contain; /* stops the modal backdrop scroll from dragging the page behind it on iOS/Android */
  animation: modal-backdrop-in 0.15s var(--ease);
}

@keyframes modal-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes modal-content-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-content {
  background: var(--panel-bg);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 500px;
  max-height: 90dvh;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  animation: modal-content-in 0.18s var(--ease);
}

.modal-lg { max-width: 750px; }

.activation-details-content { max-width: 560px; }
.activation-details-content .modal-body { display: grid; gap: 0.75rem; }
.activation-details-prompt { color: var(--text-muted); margin: 0; }
.activation-details-fields { display: grid; gap: 0.85rem; }
.activation-details-field { display: grid; gap: 0.35rem; color: var(--text-bright); }
.activation-details-field[hidden] { display: none; }
.activation-details-field input,
.activation-details-field select,
.activation-details-field textarea { width: 100%; min-width: 0; }
.activation-details-field textarea { min-height: 5rem; resize: vertical; }
.admin-test-scenario-list { display: grid; gap: 0.75rem; }
.admin-test-scenario-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--panel-bg-alt);
}
.admin-test-scenario-card h3 { margin: 0 0 0.25rem; color: var(--text-bright); }
.admin-test-scenario-card p { margin: 0; color: var(--text-muted); }
.test-scenario-active-notice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding: 0.75rem;
  border: 1px solid var(--accent-gold);
  border-radius: var(--radius-md);
}
.test-scenario-active-notice[hidden] { display: none; }
.test-scenario-initiative {
  display: grid;
  gap: 0.65rem;
  padding: 1rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--panel-bg-alt);
}
.test-scenario-initiative[hidden] { display: none; }
.test-scenario-initiative h3,
.test-scenario-initiative p { margin: 0; }
.test-scenario-initiative input { width: 8rem; }
.test-scenario-initiative-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
@media (max-width: 600px) {
  .admin-test-scenario-card,
  .test-scenario-active-notice { align-items: stretch; flex-direction: column; }
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.25rem;
  background: linear-gradient(180deg, var(--panel-bg-alt) 0%, var(--panel-bg) 100%);
  border-bottom: 1px solid var(--border-color);
}

.modal-header h2 {
  font-size: 1.15rem;
  color: var(--text-bright);
}

.close-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.5rem;
  cursor: pointer;
  transition: color 0.15s var(--ease), transform 0.15s var(--ease);
}

.close-btn:hover { color: var(--text-bright); transform: rotate(90deg); }

.modal-body {
  padding: 1.25rem;
  overflow-y: auto;
  flex: 1;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--border-color);
  background: var(--panel-bg-alt);
}

.tab-row {
  display: flex;
  border-bottom: 1px solid var(--border-color);
  background: var(--panel-bg-alt);
}

.tab-btn {
  flex: 1;
  padding: 0.75rem;
  background: none;
  border: none;
  color: var(--text-muted);
  font-weight: 600;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  font-size: 0.85rem;
}

.tab-btn.active {
  color: var(--primary-purple);
  border-bottom-color: var(--primary-purple);
  background: var(--panel-bg);
}

.tab-panel { display: none; }
.tab-panel.active { display: block; }

.form-row {
  display: flex;
  gap: 1rem;
  margin-bottom: 1rem;
}

.flex-1 { flex: 1; min-width: 0; }
.flex-2 { flex: 2; min-width: 0; }

.character-level-badge {
  margin-bottom: 12px;
  font-weight: bold;
  color: var(--accent-gold);
}

.form-group-narrow { width: 90px; flex-shrink: 0; }
.form-group-align-end { align-self: flex-end; margin-bottom: 8px; flex-shrink: 0; }
.level-input-tiny { width: 56px; }
.level-input-narrow { width: 70px; }

.detail-block {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 0.75rem;
  font-size: 0.85rem;
  margin-top: 0.35rem;
}

.feature-item-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.3rem 0;
  border-bottom: 1px dashed var(--border-color);
}

.feature-item-row:last-child { border-bottom: none; }

.badge-feature {
  color: var(--accent-gold);
  font-weight: 600;
}

/* Class Features / Racial Traits panels (renderClassFeatures, renderRaceTraits
   in app.js build this markup; these classes previously had no rules). */
.features-list {
  font-size: 0.85rem;
}

.feature-group {
  margin-bottom: 0.25rem;
}

.feature-group h4 {
  margin: 0 0 0.35rem;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent-gold);
}

.feature-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.feature-list li {
  padding: 0.15rem 0;
}

/* Description text shown beneath a feature/feature-row name, populated from
   dnd35_srd.json via findSrdFeature() in app.js. */
.feature-desc {
  font-size: 0.78rem;
  font-weight: normal;
  color: var(--text-muted, #999);
  line-height: 1.35;
  margin-top: 0.15rem;
  padding-left: 1.1rem;
}

.feat-li-remove {
  background: transparent;
  border: none;
  color: var(--text-muted, #999);
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  padding: 0.15rem 0.3rem;
}

.feat-li-remove:hover {
  color: var(--danger-red);
}

.feat-picker-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.5rem;
}

.feat-picker-row .form-select {
  flex: 1;
}

/* Skills panel (renderSkillsList in app.js) — reuses .feature-item-row/
   .badge-feature/.feat-li-remove/.feat-picker-row from the Feats panel
   above, plus these skill-specific pieces for the ability/class tags, the
   rank input, and the points-remaining summary line.

   Layout: each row wraps onto two lines on narrow screens — the skill name
   + tags on their own full-width line, then the rank input/modifier/remove
   button right-aligned on the line below inside .skill-controls — rather
   than cramming everything into one row that only fit on a wide desktop
   viewport. */
.skill-points-summary {
  font-size: 0.8rem;
  color: var(--text-muted, #999);
  margin-bottom: 0.35rem;
}

.skill-item-row {
  flex-wrap: wrap;
  row-gap: 0.35rem;
}

.skill-item-row .badge-feature {
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.skill-controls {
  display: grid;
  grid-template-columns: 3.6rem 2.4rem 1.6rem;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

/* Both an element+attribute selector (input[type="number"], specificity
   0,1,1) AND nested under .skill-item-row so this reliably beats the global
   `input[type="number"] { width: 100% }` rule earlier in this file — a bare
   .skill-rank-input class (0,1,0) was losing that fight, which is why the
   rank field was rendering full-width instead of as a small box. Width is
   100% of its own grid cell (set by .skill-controls' grid-template-columns
   above) rather than a fixed rem value, so it lines up with every other
   row's input regardless of what's in the other two cells. */
.skill-item-row input.skill-rank-input[type="number"] {
  width: 100%;
  padding: 0.3rem 0.4rem;
  text-align: center;
}

.skill-mod {
  color: var(--text-muted, #999);
  font-size: 0.78rem;
  text-align: center;
}

.badge-skill-classtag,
.badge-skill-ability {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: 3px;
  padding: 0 0.25rem;
  flex-shrink: 0;
}

.badge-skill-classtag {
  color: var(--accent-gold);
  border: 1px solid var(--accent-gold);
}

.badge-skill-ability {
  color: var(--text-muted, #999);
  border: 1px solid var(--border-color);
}

.badge-skill-classtag.skill-points-overspent {
  color: var(--danger-red);
  border-color: var(--danger-red);
}

/* Sits in the same grid cell as .feat-li-remove within .skill-controls
   (see canRemove in renderSkillsList) so the rank input and modifier
   columns land in the exact same spot whether or not a given row has a
   remove button — an empty element of a different width there was
   throwing columns out of alignment by a few pixels. */
.skill-controls .feat-li-remove,
.skill-li-remove-spacer {
  width: 100%;
  text-align: center;
}

/* ROLL RESULT MODAL STYLES */
.roll-modal-content {
  max-width: 380px;
  text-align: center;
}

.text-center {
  text-align: center;
}

.roll-check-title {
  font-size: 1rem;
  color: var(--accent-gold);
  font-weight: bold;
  margin-bottom: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.dice-display-box {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1rem;
  margin-bottom: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  box-shadow: var(--shadow-sm);
}

.dice-animation-icon {
  font-size: 2.5rem;
  animation: pulseDice 1.5s infinite ease-in-out;
}

@keyframes pulseDice {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.1); }
}

.roll-breakdown {
  font-size: 1.1rem;
  color: var(--text-main);
}

.roll-dc-comparison {
  font-size: 0.95rem;
  color: var(--text-muted);
  margin-bottom: 1rem;
  background: rgba(255, 255, 255, 0.02);
  padding: 0.5rem;
  border-radius: 6px;
  border: 1px dashed var(--border-color);
}

.roll-outcome {
  font-size: 1.2rem;
  font-weight: bold;
  padding: 0.5rem;
  border-radius: 6px;
  margin-top: 0.5rem;
}

.roll-outcome.success {
  color: var(--health-green);
  background: rgba(95, 143, 78, 0.1);
  border: 1px solid rgba(95, 143, 78, 0.2);
}

.roll-outcome.failure {
  color: var(--danger-red);
  background: rgba(168, 53, 43, 0.1);
  border: 1px solid rgba(168, 53, 43, 0.2);
}

/* TWO-HANDED WIELD TOGGLE (attack modal) */
.two-handed-toggle-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-bottom: 0.6rem;
  cursor: pointer;
}

.two-handed-toggle-row input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--accent-gold);
}

.two-handed-toggle-row.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.two-handed-toggle-note {
  font-size: 0.75rem;
  color: var(--danger-red);
  margin-top: -0.35rem;
  margin-bottom: 0.6rem;
}

/* TOASTS */
.toast-stack {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  z-index: 2000;
  pointer-events: none;
}

.toast {
  background: linear-gradient(155deg, var(--panel-bg-alt) 0%, var(--panel-bg) 140%);
  color: var(--text-bright);
  border: 1px solid var(--border-accent);
  padding: 0.75rem 1.25rem;
  border-radius: 8px;
  font-size: 0.85rem;
  box-shadow: var(--shadow-md);
  animation: fadeInOut 3s ease forwards;
  pointer-events: auto;
}

.toast-with-action {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.toast-text { flex: 1; }

.toast-action-btn {
  background: transparent;
  color: var(--primary-purple);
  border: 1px solid var(--primary-purple);
  padding: 0.3rem 0.6rem;
  border-radius: 6px;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.toast-action-btn:hover {
  background: var(--primary-purple);
  color: #1a1206;
}

@keyframes fadeInOut {
  0% { opacity: 0; transform: translateY(1rem); }
  15% { opacity: 1; transform: translateY(0); }
  85% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-1rem); }
}

/* ==========================================================================
   DEVICE-ADAPTIVE BREAKPOINTS
   ----------------------------------------------------------------------
   These branch on available screen space and input type (via the
   pointer/hover media features), never on device or OS identity — the
   same iPad gets the "tablet" layout whether it's reporting itself as
   iPadOS or, as newer Safari versions do, as a desktop Mac UA. Ranges:
     > 1600px  ultra-wide desktop / large monitor  (light refinement)
     1025–1600 standard desktop / laptop            (default rules above)
      851–1024 tablet landscape / small laptop      (narrower inline sidebars)
       <= 850   tablet portrait, phone landscape,    (sidebar DRAWER mode)
                and phone portrait
       <= 480   phone portrait specifically          (full-bleed refinements)
   ========================================================================== */

/* ULTRA-WIDE DESKTOP: on large monitors two 320px sidebars leave the
   story panel comfortable, but let it breathe a little more and keep
   the sidebars from feeling cramped next to a huge center panel. */
@media screen and (min-width: 1600px) {
  .sidebar {
    width: 360px;
    min-width: 360px;
  }

  .story-container {
    padding: 1.5rem 2rem;
  }
}

/* SHORT DESKTOP WINDOWS (including 1366×768 laptops): keep the header
   compact and preserve usable space for both the story and action console. */
@media screen and (min-width: 1025px) and (max-height: 850px) {
  body:not(.immersive-mode) .header-left {
    flex: 1 1 auto;
    flex-wrap: nowrap;
  }

  body:not(.immersive-mode) .header-actions {
    flex: 0 0 auto;
  }

  body:not(.immersive-mode) .story-panel {
    grid-template-rows: auto minmax(180px, 1fr) minmax(220px, 44vh);
    padding: 0.65rem;
    gap: 0.5rem;
  }

  body:not(.immersive-mode) .story-container {
    min-height: 180px;
  }

  body:not(.immersive-mode) .story-text {
    min-height: 100px;
  }

  body:not(.immersive-mode) .action-bar {
    max-height: 44vh;
  }

  body:not(.immersive-mode) .action-bar-controls {
    min-height: 0;
  }
}

/* TABLET LANDSCAPE / SMALL LAPTOP
   Fills the gap between the full desktop layout (two 320px fixed
   sidebars) and drawer mode below, where two full-width sidebars would
   otherwise leave almost no room for the story panel in between. */
@media screen and (max-width: 1024px) and (min-width: 851px) {
  .sidebar {
    width: 240px;
    min-width: 240px;
    padding: 0.75rem;
  }

  .ability-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .combat-stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .modal-lg {
    max-width: 90vw;
  }

  .modal-content {
    max-width: 90vw;
  }
}

/* SIDEBAR DRAWERS — tablet portrait, phone landscape, phone portrait.
   Below 851px there isn't room to squeeze two sidebars in beside the
   story panel, so the Hero Sheet and World Lore panels become full-height
   overlay drawers that slide in over the content instead of shrinking it.
   app.js still just toggles `.is-hidden`; this only changes how a
   *visible* sidebar is positioned, plus the scrim (see index.html) that
   lets a tap outside the drawer close it. */
@media screen and (max-width: 850px) {
  .workspace {
    position: relative;
    overflow: visible;
  }

  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    width: min(85vw, 360px);
    min-width: 0;
    height: var(--app-height);
    height: 100dvh;
    z-index: 40;
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
    padding-top: calc(1rem + var(--safe-top));
    padding-bottom: calc(1rem + var(--safe-bottom));
  }

  .sidebar-left {
    left: 0;
    padding-left: calc(1rem + var(--safe-left));
  }

  .sidebar-right {
    right: 0;
    padding-right: calc(1rem + var(--safe-right));
  }

  .sidebar-scrim:not([hidden]) {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 30;
  }

  .ability-grid {
    grid-template-columns: repeat(3, 1fr);
  }

  .combat-stats-grid {
    grid-template-columns: repeat(4, 1fr);
  }

  .app-header {
    flex-direction: column;
    align-items: stretch;
    padding: 0.5rem;
    padding-top: calc(0.5rem + var(--safe-top));
    padding-left: calc(0.5rem + var(--safe-left));
    padding-right: calc(0.5rem + var(--safe-right));
  }

  .header-left {
    width: 100%;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: 0.4rem;
  }

  .header-actions {
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    margin-left: 0;
  }

  .header-primary {
    display: none;
  }

  .header-actions.is-open .header-primary {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }

  .header-more-toggle {
    align-self: flex-end;
  }

  .header-controls {
    position: static;
    width: 100%;
    max-height: min(60vh, 32rem);
    box-shadow: none;
  }

  .header-controls .slot-picker {
    justify-content: space-between;
  }

  .header-controls .license-footer-note-header {
    text-align: center;
    padding: 0.3rem 0;
  }

  /* Modals get almost the full screen so on-screen keyboards (entering
     text on a phone) don't squeeze form fields into a sliver of space. */
  .modal-content,
  .modal-lg {
    max-width: 100%;
    width: 100%;
  }

  /* .form-row (used throughout the Campaign & Character Setup modal) is a
     no-wrap flex row on desktop, sized for 2-4 fields side by side. On a
     phone-width screen there isn't room for that, and several of its
     children (the Level inputs, the character-level-badge) have a fixed
     pixel width rather than a flexible one — so instead of wrapping,
     they were getting squeezed down to near-zero width while their text
     still rendered at full size, visually overlapping the neighboring
     field entirely (e.g. "Hero Name" printing on top of "Level:").
     Stacking every field to full width top-to-bottom removes the
     squeeze entirely. */
  .form-row {
    flex-direction: column;
    gap: 0.75rem;
  }

  .form-row > .form-group,
  .form-row > .character-level-badge,
  .form-row > .form-group-narrow {
    width: 100%;
    flex: none;
  }

  .level-input-tiny,
  .level-input-narrow {
    width: 100%;
    max-width: 140px;
  }

  .form-group-align-end {
    align-self: stretch;
  }

  /* The prestige-class "Entry Requirements" panel used a fixed 2:1 flex
     ratio (.flex-2) intended for a side-by-side layout — on narrow
     screens that produced a squeezed sliver with its own inner
     horizontal scrollbar. Now that .form-row stacks, let it use the
     full stacked width like everything else instead. */
  .detail-block {
    max-height: 220px;
    overflow-y: auto;
  }

  /* The Tactical Mode/Adult Themes/Scene Images toggles and the
     Retry/Export/Fullscreen buttons were the second big chunk of vertical
     space above the actual story — tighten them up so more of the screen
     goes to the story panel and action bar underneath. */
  .story-panel {
    padding: 0.6rem;
    gap: 0.5rem;
  }

  .toolbar-toggles, .toolbar-actions {
    gap: 0.4rem;
  }

  .toggle-label {
    font-size: 0.75rem;
    gap: 0.25rem;
  }

  .story-toolbar .btn {
    padding: 0.3rem 0.5rem;
    font-size: 0.75rem;
  }
}

/* PHONE PORTRAIT — the tightest common case (iPhone SE/mini through most
   Android phones in portrait). Drawers go full-bleed and dense grids
   collapse to something legible on a ~360–430px wide screen. */
@media screen and (max-width: 480px) {
  html, body {
    font-size: 13px;
  }

  .logo {
    font-size: 0.9rem;
  }

  .sidebar {
    width: 100vw;
  }

  .scene-image-wrapper {
    height: 100px;
  }

  .story-container {
    min-height: 296px; /* 100px image + margin + story-text's 140px floor + padding — kept equal to .story-panel's grid row minimum below */
  }

  .story-panel {
    grid-template-rows: auto minmax(296px, 1fr) auto;
  }

  .action-bar {
    max-height: 38vh;
  }

  .ability-grid,
  .combat-stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .auth-card {
    padding: 1.5rem 1.25rem;
  }

  .modal {
    /* Full-bleed on the left/right (no hardware cutout there), but keep
       real safe-area padding on top/bottom — this got reset to a flat
       0 in an earlier pass, which is what let the modal header render
       underneath the iPhone status bar. env(safe-area-inset-top) tracks
       Safari's UI dynamically (it grows when Safari's own address bar
       auto-hides during scroll and the status bar is newly exposed), so
       this stays correct as that happens rather than needing JS. */
    padding: var(--safe-top) 0 var(--safe-bottom) 0;
  }

  .modal-content {
    height: 100%;
    max-height: none;
    border-radius: 0;
  }
}

/* PHONE / SMALL TABLET IN LANDSCAPE — short viewport height, not narrow
   width. Detected by height + orientation rather than width so it fires
   correctly regardless of the phone model. Trims vertical chrome so the
   story text and combat controls aren't squeezed under a shrunken
   on-screen area. */
@media screen and (max-height: 480px) and (orientation: landscape) {
  .app-header {
    flex-direction: row;
    padding-top: calc(0.4rem + var(--safe-top));
    padding-bottom: 0.4rem;
  }

  .modal-content {
    max-height: calc(100dvh - 1.5rem);
  }

  .auth-card {
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
  }
}

/* ==========================================================================
   ATTACK ROLL EXTENSIONS (ADD TO END OF STYLESHEET)
   ========================================================================== */

/* Spacing for multiple manual rows inside the skill check bar */
.skill-check-bar .skill-check-manual-row + .skill-check-manual-row {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border-color);
}

/* Specific styling for AC Target Badges (mirrors DC badge) */
.ac-badge {
  background: rgba(59, 130, 246, 0.15);
  color: var(--resolve-blue);
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
  font-size: 0.78rem;
  font-weight: 600;
  border: 1px solid rgba(59, 130, 246, 0.3);
}

/* Distinct styling for pending attack rolls (using danger/combat red tint or resolve blue) */
.pending-attack-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: rgba(59, 130, 246, 0.1);
  border: 1px solid rgba(59, 130, 246, 0.3);
  padding: 0.35rem 0.6rem;
  border-radius: 4px;
  font-size: 0.8rem;
  color: var(--resolve-blue);
  margin-top: 0.4rem;
}

/* NATURAL 20 & NATURAL 1 GLOW EFFECTS */
.dice-display-box.nat-20 {
  border-color: var(--health-green);
  box-shadow: 0 0 15px rgba(95, 143, 78, 0.35);
}

.dice-display-box.nat-20 #rollAttackD20Result {
  color: var(--health-green);
}

.dice-display-box.nat-1 {
  border-color: var(--danger-red);
  box-shadow: 0 0 15px rgba(168, 53, 43, 0.35);
}

.dice-display-box.nat-1 #rollAttackD20Result {
  color: var(--danger-red);
}

.text-muted { color: var(--text-muted); }

/* STUDY BUTTON (combat tracker) — attempt a Knowledge check to identify a
   still-living enemy; see openStudyRollModal / renderCombatantRow in app.js */
.enemy-study-btn {
  background: none;
  border: 1px solid var(--border-accent);
  color: var(--text-muted);
  border-radius: 4px;
  font-size: 0.7rem;
  padding: 0.05rem 0.4rem;
  margin-left: 0.5rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.enemy-study-btn:hover {
  border-color: var(--accent-gold);
  color: var(--accent-gold);
}

/* BESTIARY CODEX & DEFEATED FOE STAT BLOCK CARDS
   Shared by renderStatBlockCard() for both the Codex browser list and the
   defeated-foe pop-up (see app.js). */
.statblock-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.statblock-card {
  background: var(--panel-bg-alt);
  border: 1px solid var(--border-accent);
  border-radius: 8px;
  padding: 0.85rem 1rem;
}

.statblock-card h3 {
  font-size: 1rem;
  color: var(--text-bright);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.statblock-tier-badge {
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--border-color);
  color: var(--text-muted);
}

.statblock-tier-badge.tier-glimpse {
  color: var(--text-muted);
  border-color: var(--border-color);
}

.statblock-tier-badge.tier-partial {
  color: var(--resolve-blue);
  border-color: rgba(59, 130, 246, 0.4);
}

.statblock-tier-badge.tier-full {
  color: var(--health-green);
  border-color: rgba(95, 143, 78, 0.4);
}

.statblock-subtitle {
  color: var(--text-muted);
  font-size: 0.8rem;
  margin-top: 0.15rem;
}

.statblock-description {
  color: var(--text-main);
  font-size: 0.85rem;
  margin-top: 0.5rem;
  font-style: italic;
}

.statblock-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.35rem 1rem;
  margin-top: 0.6rem;
  font-size: 0.8rem;
}

.statblock-grid strong {
  color: var(--text-muted);
  font-weight: 600;
}

.statblock-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.6rem;
}

.statblock-tag {
  background: rgba(212, 175, 55, 0.1);
  border: 1px solid rgba(212, 175, 55, 0.3);
  color: var(--accent-gold);
  font-size: 0.72rem;
  border-radius: 999px;
  padding: 0.1rem 0.55rem;
}

/* ===== Compact action bar ===== */
.action-bar { max-height: 55vh; }              /* was 46vh / 38vh mobile */
.action-bar-controls { gap: 0.5rem; }

/* Roll rows: side by side, wrapping when narrow */
.skill-check-bar {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  align-content: flex-start;
  gap: 0.5rem 1.25rem;
}
.skill-check-bar .skill-check-manual-row,
.skill-check-bar .skill-check-manual-row + .skill-check-manual-row {
  flex: 1 1 520px;
  margin: 0 !important;      /* beats the inline margin-top: 8px */
  padding: 0;
  border-top: none;
  flex-wrap: nowrap;
}
.skill-check-bar .toggle-label {
  flex-shrink: 0;
  white-space: nowrap;
}
.skill-check-bar .skill-check-manual-row select {
  flex: 1 1 120px;
  min-width: 0;
  max-width: none;
}
.skill-check-bar .pending-roll-summary { flex: 1 1 100%; }

/* Spell / feature / feat pickers: columns instead of a stack */
.spell-picker-card {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 0.6rem 1rem;
  padding: 0.6rem 0.85rem;
}
.action-feature-picker { border-top: none; padding-top: 0; }
.spell-picker-card .spell-attack-row,
.spell-picker-card .pending-roll-summary,
.spell-picker-card .spell-preview-card { grid-column: 1 / -1; }
.spell-picker-card .spell-attack-row { order: 5; }
.spell-picker-card .pending-roll-summary { order: 6; }
.spell-picker-card .spell-preview-card { order: 7; }

.ooc-row { margin-bottom: 0; }
/* ---- Combat log & End Turn (engine-resolved combat) ---- */
.combat-log-list { list-style: none; margin: 0; padding: 0; max-height: 220px; overflow-y: auto; font-size: 0.8rem; }
.combat-log-list li { padding: 3px 6px; border-left: 3px solid var(--border-color, #555); margin-bottom: 3px; opacity: 0.9; }
.combat-log-list li.log-round { font-weight: 700; border-left-color: var(--accent-color, #c9a227); opacity: 1; margin-top: 6px; }
.combat-log-list li.log-hit { border-left-color: #c0392b; }
.combat-log-list li.log-miss { border-left-color: #7f8c8d; }
.combat-log-list li.log-flee { border-left-color: #2980b9; font-style: italic; }
#endTurnBtn.pulse { animation: endTurnPulse 1.4s ease-in-out infinite; }
@keyframes endTurnPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(201,162,39,0.0); } 50% { box-shadow: 0 0 0 5px rgba(201,162,39,0.45); } }