/* ── Reset & base ─────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Custom scrollbars ───────────────────────────────────────────────────── */
::-webkit-scrollbar              { width: 6px; height: 6px; }
::-webkit-scrollbar-track        { background: transparent; }
::-webkit-scrollbar-thumb        { background: var(--color-text-tertiary); border-radius: var(--radius-full); }
::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-thumb:focus-visible  { background: var(--color-text-secondary); }
/* Firefox */
*                                { scrollbar-width: thin; scrollbar-color: var(--color-text-tertiary) transparent; }

/* ── Threadfall Design Tokens ────────────────────────────────────────────── */
:root {
  /* ── Light palette ── */
  --color-bg:               #F8FAFC;
  --color-surface:          #F1F5F9;
  --color-surface-elevated: #E2E8F0;
  --color-border:           #CBD5E1;
  --color-text-primary:     #0F172A;
  --color-text-secondary:   #475569;
  /* v800: bumped from #64748B (slate-500, ~4.6:1) to #475569 (slate-600,
     ~7.5:1). User direction: light-grey on dark = dark-grey on light, ~66%
     black on white. Collapses with --color-text-secondary by design — the
     eyebrow / placeholder / muted-chip hierarchy now relies on font-weight,
     letter-spacing, and italic to differentiate, not colour intensity. */
  --color-text-tertiary:    #475569;

  /* ── Brand & semantic ── */
  --color-brand:   #2F7C85;
  --color-success: #16A34A;
  --color-danger:  #DC2626;
  --color-warning: #C26A2E;       /* ember-based: non-destructive attention */
  --color-info:    #2563EB;
  --color-purple:  #7C3AED;
  --color-gold:    #CA8A04;
  --color-ember:   #C26A2E;       /* EMBER_BASE — dim firelight, weathered copper */
  --color-copper:  #C2763D;       /* Artifact rarity — distinct from ember (Item) */
  --color-muted-violet: #7C6FA0;  /* NPC / faction accent — softer than purple  */
  --color-white:        #FFFFFF;
  --color-discord:      #5865F2;
  --color-discord-hover:#4752C4;

  /* ── Typography scale ── */
  --font-size-xs:   0.68rem;   /* badges, chips, tiny labels       */
  --font-size-sm:   0.75rem;   /* secondary body, small buttons    */
  --font-size-base: 0.875rem;  /* default body / inputs            */
  --font-size-md:   0.9rem;    /* card body, log entries           */
  --font-size-lg:   1rem;      /* prominent labels, titles         */
  --font-size-xl:   1.1rem;    /* modal headers                    */
  --font-size-2xl:  1.5rem;    /* page-level headings (setup/login)*/

  --font-weight-normal:    400;
  --font-weight-medium:    500;
  --font-weight-semibold:  600;
  --font-weight-bold:      700;
  --font-weight-extrabold: 800;

  /* ── Spacing scale (base 4px / 0.25rem) ── */
  --header-height: 52px;

  --space-1:  0.25rem;   /*  4px — icon gaps, micro nudges      */
  --space-2:  0.5rem;    /*  8px — tight inline spacing         */
  --space-3:  0.75rem;   /* 12px — list items, form rows        */
  --space-4:  1rem;      /* 16px — card padding, section gaps   */
  --space-5:  1.25rem;   /* 20px — entity form padding          */
  --space-6:  1.5rem;    /* 24px — large card padding           */
  --space-8:  2rem;      /* 32px — modal / setup padding        */
  --space-10: 2.5rem;    /* 40px — setup card max padding       */

  /* ── Layout: chrome heights ──
     The two top bars stack on top of every .tab-panel. Hard-coding
     52/41 in `calc(100vh - var(--chrome-height))` was fragile — bumping either
     bar's padding meant grepping every panel rule. Centralised here so
     every panel can reference the same source of truth. */
  --nav-height:           52px;   /* main nav (#tab-nav) */
  --tab-strip-height:     41px;   /* per-tab title + pill row */
  --chrome-height:        calc(var(--nav-height) + var(--tab-strip-height));

  /* ── Border radius scale ── */
  --radius-sm:   4px;    /* tight controls (close btn, session num) */
  --radius-md:   6px;    /* inputs, small buttons                   */
  --radius-lg:   8px;    /* standard — cards, buttons, dropdowns    */
  --radius-xl:   10px;   /* session cards, panels                   */
  --radius-2xl:  12px;   /* modals, chat bubbles                    */
  --radius-3xl:  16px;   /* setup card, profile modal               */
  --radius-full: 999px;  /* pills, badges, avatars                  */

  /* ── Shadow / elevation scale ── */
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);   /* subtle (status dots, toasts)     */
  --shadow-md: 0 4px 12px rgba(0,0,0,0.15);  /* hover states, floating buttons   */
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.20);  /* modals, popovers                 */
  --shadow-xl: 0 20px 60px rgba(0,0,0,0.25); /* large modals (profile)           */

  /* ── Legacy aliases (keep existing code working) ── */
  --bg:     var(--color-bg);
  --bg-2:   var(--color-surface);
  --bg-3:   var(--color-surface-elevated);
  --text:   var(--color-text-primary);
  --text-1: var(--color-text-primary);
  --text-2: var(--color-text-secondary);
  --text-3: var(--color-text-tertiary);
  --border: var(--color-border);
  --surface: var(--color-surface);
  --bg-1:    var(--color-bg);
  --accent:  var(--color-brand);
  --shadow:  var(--shadow-sm);

  /* ── Accent aliases ── */
  --accent-teal:   var(--color-brand);
  --accent-amber:  var(--color-warning);
  --accent-red:    var(--color-danger);
  --accent-purple: var(--color-purple);
  --accent-green:  var(--color-success);
  --accent-blue:   var(--color-info);
  --accent-gold:   var(--color-gold);
  --accent-ember:  var(--color-ember);

  /* ── Teal system (TEAL_BASE = #2F7C85 — mineral teal, Threadfall mountain tones) ── */
  --teal-base:      #2F7C85;
  --teal-strong:    #25656C;
  --teal-soft:      #4F9AA3;
  --teal-muted:     #6FAFB6;
  --teal-text:      #25656C;  /* darker for light mode text */
  --teal-bg:        rgba(47, 124, 133, 0.14);
  --teal-border:    rgba(47, 124, 133, 0.35);
  --teal-glow:      rgba(47, 124, 133, 0.22);
  --teal-muted-bg:  rgba(47, 124, 133, 0.08);

  /* ── Ember system (EMBER_BASE = #C26A2E — dim firelight / weathered copper) ── */
  --ember-base:    #C26A2E;
  --ember-soft:    #A55724;
  --ember-muted:   #7A3F1A;
  --ember-bg:      rgba(194, 106, 46, 0.12);
  --ember-border:  rgba(194, 106, 46, 0.35);
  --ember-glow:    rgba(194, 106, 46, 0.18);
  --ember-text-on: #F3D6C2;

  /* ── Split motif ── */
  --split-color:  color-mix(in srgb, #2F7C85 40%, transparent);
  --split-strong: color-mix(in srgb, #2F7C85 60%, transparent);

  /* ── Additional border ── */
  --color-border-strong: #94A3B8;

  /* ── Motion system ── */
  --ease:          cubic-bezier(0.4, 0, 0.2, 1);
  --duration-fast: 80ms;
  --duration-base: 140ms;
  --duration-slow: 200ms;
  /* legacy aliases */
  --t-hover: var(--duration-base);
  --t-press: var(--duration-fast);
  --t-trans: var(--duration-slow);

  color-scheme: light;
}

[data-theme="dark"] {
  /* ── Dark palette — cooler/bluer slate ── */
  --color-bg:               #080C12;
  --color-surface:          #0F1924;
  --color-surface-elevated: #182438;
  --color-border:           #1E3050;
  --color-border-strong:    #2E4A6A;
  --color-text-primary:     #E5E7EB;
  --color-text-secondary:   #9CA3AF;
  --color-text-tertiary:    #6B7280;

  /* ── Brand & semantic (brighter for dark backgrounds) ── */
  --color-brand:   #4F9AA3;
  --color-success: #22C55E;
  --color-danger:  #EF4444;
  --color-warning: #D4753A;       /* ember-based: brightened for dark bg legibility */
  --color-info:    #3B82F6;
  --color-purple:  #8B5CF6;
  --color-gold:    #FBBF24;
  --color-ember:   #D4753A;       /* EMBER_BASE brightened for dark background legibility */
  --color-copper:  #E8A77A;       /* Artifact rarity (lighter on dark to read clearly) */
  --color-muted-violet: #9B8EC4;  /* NPC / faction accent — softer on dark     */

  /* ── Teal system — mineral teal scaled for dark backgrounds ── */
  --teal-base:      #2F7C85;
  --teal-strong:    #2F7C85;
  --teal-soft:      #4F9AA3;
  --teal-muted:     #6FAFB6;
  --teal-text:      #6FAFB6;  /* lighter for dark mode text */
  --teal-bg:        rgba(47, 124, 133, 0.14);
  --teal-border:    rgba(47, 124, 133, 0.35);
  --teal-glow:      rgba(47, 124, 133, 0.22);
  --teal-muted-bg:  rgba(47, 124, 133, 0.08);

  /* ── Ember system (dark mode — brightened for legibility on dark bg) ── */
  --ember-base:    #D4753A;
  --ember-soft:    #B8612E;
  --ember-muted:   #8A4522;
  --ember-bg:      rgba(212, 117, 58, 0.12);
  --ember-border:  rgba(212, 117, 58, 0.35);
  --ember-glow:    rgba(212, 117, 58, 0.18);
  --ember-text-on: #F3D6C2;

  /* ── Split motif ── */
  --split-color:  color-mix(in srgb, #4F9AA3 35%, transparent);
  --split-strong: color-mix(in srgb, #4F9AA3 55%, transparent);

  /* ── Shadows are stronger on dark backgrounds ── */
  --shadow-sm: 0 1px 4px rgba(0,0,0,0.50);
  --shadow-md: 0 4px 12px rgba(0,0,0,0.35);
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.45);
  --shadow-xl: 0 20px 60px rgba(0,0,0,0.55);

  /* ── Legacy aliases ── */
  --bg:     var(--color-bg);
  --bg-2:   var(--color-surface);
  --bg-3:   var(--color-surface-elevated);
  --text:   var(--color-text-primary);
  --text-1: var(--color-text-primary);
  --text-2: var(--color-text-secondary);
  --text-3: var(--color-text-tertiary);
  --border: var(--color-border);
  --surface: var(--color-surface);
  --bg-1:    var(--color-bg);
  --accent:  var(--color-brand);
  --shadow:  var(--shadow-sm);

  /* ── Accent aliases ── */
  --accent-teal:   var(--color-brand);
  --accent-amber:  var(--color-warning);
  --accent-red:    var(--color-danger);
  --accent-purple: var(--color-purple);
  --accent-green:  var(--color-success);
  --accent-blue:   var(--color-info);
  --accent-gold:   var(--color-gold);
  --accent-ember:  var(--color-ember);

  color-scheme: dark;
}

/* ── Brand motif: vertical seam / horizontal section divider ─────────────── */
.tf-split-divider {
  border: none;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent 0%,
    var(--split-color) 20%,
    var(--split-strong) 50%,
    var(--split-color) 80%,
    transparent 100%
  );
  margin: var(--space-4) 0;
}

/* ── Site background image ───────────────────────────────────────────────────
   bg.png is fixed behind all content. Light mode uses a pale overlay so the
   texture shows through softly without clashing with light-surface panels.
   Dark mode overrides this further down with its own layers.
   Raise the rgba alpha toward 1.0 to mute more; lower toward 0.80 for more texture. */
body {
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
  /* WebP preferred (~326 KB), PNG fallback (~6.2 MB) for browsers without WebP support. */
  background-image:
    linear-gradient(rgba(241, 245, 249, 0.70), rgba(241, 245, 249, 0.70)),
    image-set(url('/static/bg.webp') type('image/webp'), url('/static/bg.png') type('image/png'));
  background-size: auto, cover;
  background-position: center, center;
  background-attachment: fixed, fixed;
  background-color: var(--bg); /* fallback if image fails to load */
  color: var(--text);
  min-height: 100vh;
  transition: background-color var(--t-trans) ease, color var(--t-trans) ease;
}

/* ── Header ──────────────────────────────────────────────────────────────── */
#app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.5rem;
  height: var(--header-height);
  background: var(--bg-2);
  position: sticky;
  top: 0;
  z-index: 100;
}

#campaign-name {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: var(--font-size-xl);
  font-weight: 600;
  color: var(--text);
}

#header-actions { display: flex; gap: 0.5rem; align-items: center; }

#theme-toggle {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 4px 8px;
  cursor: pointer;
  color: var(--text-2);
  font-size: var(--font-size-lg);
  transition: background var(--t-hover);
}
#theme-toggle:hover, #theme-toggle:focus-visible { background: var(--bg-3); }

/* ── Screens ─────────────────────────────────────────────────────────────── */
.screen { min-height: calc(100vh - var(--nav-height)); }
.hidden { display: none !important; } /* must beat component display:flex/block rules */

/* ── Setup ───────────────────────────────────────────────────────────────── */
#setup-screen {
  display: flex;
  align-items: center;
  justify-content: center;
}

.setup-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xl);
  padding: 2.5rem;
  width: 100%;
  max-width: 420px;
  box-shadow: var(--shadow);
}

.setup-card h1 {
  font-family: Georgia, serif;
  font-size: var(--font-size-2xl);
  margin-bottom: 0.5rem;
}

.setup-card p {
  color: var(--text-2);
  margin-bottom: 1.5rem;
  font-size: var(--font-size-md);
}

.setup-card form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* ── Inputs & buttons ────────────────────────────────────────────────────── */
input[type="text"], input[type="date"], textarea, select {
  width: 100%;
  padding: 0.55rem 0.75rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--text);
  font-family: inherit;
  font-size: var(--font-size-md);
  outline: none;
  transition: border-color var(--t-hover);
}
input[type="text"]:focus, textarea:focus, select:focus {
  border-color: var(--accent-teal);
}

/* Legacy aliases — both `button[type="submit"]` (unclassed) and `.btn-primary`
   now compose the same ghost-pill recipe as `.btn-new`. Pre-v813 these rendered
   in legacy teal; user retired teal across the app. The dual-selector keeps
   submit-button specificity (button[type="submit"] = 0,0,1,1) above .btn-new. */
button[type="submit"]:not(.btn-new):not(.btn-danger), .btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 1rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  color: var(--text-1);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease);
}
button[type="submit"]:not(.btn-new):not(.btn-danger):hover,
button[type="submit"]:not(.btn-new):not(.btn-danger):focus-visible,
.btn-primary:hover, .btn-primary:focus-visible {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 18%, transparent);
  transform: translateY(-1px);
}
button[type="submit"]:not(.btn-new):not(.btn-danger):active,
.btn-primary:active {
  transform: translateY(0);
  transition-duration: var(--t-press);
}
[data-theme="dark"] button[type="submit"]:not(.btn-new):not(.btn-danger),
[data-theme="dark"] .btn-primary {
  color: rgba(255,255,255,0.92);
  background: rgba(255,255,255,0.05);
  border-color: rgba(255,255,255,0.32);
}
[data-theme="dark"] button[type="submit"]:not(.btn-new):not(.btn-danger):hover,
[data-theme="dark"] .btn-primary:hover {
  color: #fff;
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 14px 2px rgba(255,255,255,0.18);
}

/* Canonical "secondary action" recipe — used for every Cancel / Close / Skip /
   nav-arrow / passive button in the app. v813: tightened to a single style
   the user signed off on — transparent + squared + teal-tinted border + grey
   text at rest, text brightens to full white and border to full teal on hover.
   Single source of truth: do NOT add per-surface .btn-ghost overrides that
   re-tint background or border, or the design language drifts. */
.btn-ghost {
  padding: 0.5rem 1rem;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--accent-teal) 35%, transparent);
  border-radius: 0;
  color: var(--text-3);
  font-family: inherit;
  font-size: var(--font-size-base);
  cursor: pointer;
  transition:
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease);
}
.btn-ghost:hover, .btn-ghost:focus-visible {
  color: var(--text-1);
  border-color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 7%, transparent);
  transform: translateY(-1px);
}
.btn-ghost:active {
  transform: translateY(0);
  transition-duration: var(--t-press);
}
.btn-ghost:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}
/* `.btn-ghost--danger` recipe lives in the canonical destructive-action
   block (~line 487) — composed with .btn-ghost so a Cancel/Delete pair in
   a modal share silhouette and only differ by color. */

/* Small inline action button — replaces style="font-size:0.78rem;padding:2px 8px" */
.btn-sm {
  padding: 0.25rem 0.65rem;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  border-radius: 0;
}
/* Extra-small — tight inline micro-actions (party card, roster, admin table) */
.btn-xs {
  padding: 2px 8px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  border-radius: 0;
}
/* Compact — modal-row inline actions (Copy / Save / Choose-image button clusters
   inside settings dialogs). Bigger than .btn-sm, smaller than default .btn-primary. */
.btn-compact {
  padding: 0.55rem 0.9rem;
  font-size: var(--font-size-sm);
  white-space: nowrap;
}
/* Canonical "destructive action" recipe — used for every Delete / Remove /
   warning button across the app. v813: legacy `.btn-danger` (filled red on
   hover), `.btn-ghost--danger`, and pill `.btn-new--danger` collapsed onto
   one squared red-ghost silhouette so deletes look identical everywhere.
   Mirrors `.btn-ghost`'s shape so a Cancel / Delete pair in the same modal
   reads as a unit, distinguished only by color. */
.btn-danger,
.btn-new.btn-new--danger,
.btn-ghost.btn-ghost--danger {
  padding: 0.5rem 1rem;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--accent-red) 35%, transparent);
  border-radius: 0;
  color: color-mix(in srgb, var(--accent-red) 75%, var(--text-3));
  font-family: inherit;
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  letter-spacing: normal;
  cursor: pointer;
  white-space: nowrap;
  transition:
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease);
}
.btn-danger:hover, .btn-danger:focus-visible,
.btn-new.btn-new--danger:hover, .btn-new.btn-new--danger:focus-visible,
.btn-ghost.btn-ghost--danger:hover, .btn-ghost.btn-ghost--danger:focus-visible {
  color: var(--accent-red);
  border-color: var(--accent-red);
  background: color-mix(in srgb, var(--accent-red) 8%, transparent);
  box-shadow: none;
  transform: translateY(-1px);
}
.btn-danger:active,
.btn-new.btn-new--danger:active,
.btn-ghost.btn-ghost--danger:active {
  transform: translateY(0);
  transition-duration: var(--t-press);
}
.btn-danger:disabled,
.btn-new.btn-new--danger:disabled,
.btn-ghost.btn-ghost--danger:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* Campaign danger zone */
.camp-danger-zone {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
}
.camp-delete-warning {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  margin-bottom: var(--space-4);
  line-height: 1.5;
}

/* Uniform checkbox + radio styling */
input[type="checkbox"],
input[type="radio"] {
  width: 1rem; height: 1rem;
  accent-color: var(--accent-teal);
  cursor: pointer; flex-shrink: 0;
}

.error { color: var(--accent-red); font-size: var(--font-size-base); }

/* ── Tab nav ─────────────────────────────────────────────────────────────── */
#tab-nav {
  display: flex;
  gap: 0;
  align-items: flex-end;   /* anchor children to bottom so tab underlines sit on the border */
  padding-top: 4px;        /* small top breathing room */
  background: var(--bg-2);
  overflow: hidden;        /* clip the scroll area's scrollbar track */
}

/* Left cluster: profile avatar + back button (chevron + logo) */
.nav-left {
  display: flex;
  align-items: center;
  gap: 0;
  flex-shrink: 0;
  padding-right: 0.25rem;
}

/* Profile button — sits inside tab-scroll-area, immediately after last tab.
   Mirrors the campaign-select #cs-profile-btn treatment (card border, lift,
   teal hover) at the smaller 28px tab-bar scale. */
#tab-nav .nav-profile-btn,
#profile-nav-btn {
  margin: 0 0.25rem 0 0.5rem;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-2);
  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--t-hover) ease,
              box-shadow   var(--t-hover) ease,
              transform    var(--t-hover) ease;
}
#profile-nav-btn:hover, #profile-nav-btn:focus-visible {
  border-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 10px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}
/* Inner avatar fills the button — the button now owns the border. */
#profile-nav-btn .nav-avatar-img {
  width: 100%; height: 100%;
  border: none;
  box-shadow: none;
}
#profile-nav-btn:hover .nav-avatar-img,
#profile-nav-btn:focus-visible .nav-avatar-img { border: none; box-shadow: none; }

/* Back button wraps chevron + logo as one click target */
/* v798: bumped resting from --text-3 to --text-2 — primary nav affordance
   needs to read clearly on light theme (--text-3 was 2.4:1 against --bg). */
.nav-back-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.4rem 0.2rem 0.4rem 0.25rem;
  border-radius: var(--radius-md);
  transition: background var(--t-hover) var(--ease);
  color: var(--text-2);
}
.nav-back-btn:hover, .nav-back-btn:focus-visible { background: var(--bg-3); color: var(--text-1); }
.nav-back-btn:hover .nav-logo, .nav-back-btn:focus-visible .nav-logo { opacity: 1; }

.nav-back-chevron {
  font-size: 1.3rem;
  line-height: 1;
  font-weight: 300;
  color: var(--text-2);
  transition: color var(--t-hover) var(--ease), transform var(--t-hover) var(--ease);
  display: inline-block;
  margin-right: 0.1rem;
}
.nav-back-btn:hover .nav-back-chevron, .nav-back-btn:focus-visible .nav-back-chevron {
  color: var(--text-1);
  transform: translateX(-2px);
}

/* Scrollable wrapper for tabs + profile — takes all remaining space, Live stays pinned right */
#tab-scroll-wrap {
  flex: 1;
  min-width: 0;            /* allow shrinking inside the flex nav */
  position: relative;      /* anchor for the fade pseudo-elements */
  overflow: hidden;
  display: flex;
  align-items: stretch;
}
#tab-scroll-area {
  display: flex;
  align-items: center;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;   /* Firefox */
}
#tab-scroll-area::-webkit-scrollbar { display: none; }  /* Chrome/Safari */

/* Fade-out right edge only — hints tabs are scrollable, left has no overflow */
#tab-scroll-wrap::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: 1.5rem;
  pointer-events: none;
  z-index: 1;
  background: linear-gradient(to left, var(--bg-2), transparent);
}
.nav-logo {
  height: 28px;
  width: auto;
  opacity: 0.9;
  flex-shrink: 0;
  filter: drop-shadow(0 0 1px rgba(20,20,20,0.90)) drop-shadow(0 0 3px rgba(20,20,20,0.55));
  transition: opacity var(--t-hover) var(--ease);
}

/* ── Right utility cluster (gear + profile) ──────────────────────────────
   Sits between #tab-scroll-wrap and .nav-right. Right-justified, always
   visible, doesn't scroll with the tab strip. v777. */
.nav-utility-cluster {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
  padding: 0 var(--space-2);
  align-self: stretch;
}
/* v798: theme-correct base + dark override (mirrors .btn-new refactor).
   Round 28px utility button — sits in the right utility cluster of the
   in-campaign nav. Pre-v798 used rgba(255,255,255,*) which made the gear
   button invisible on light theme. */
.nav-utility-btn {
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  color: var(--text-2);
  font-size: var(--font-size-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform    var(--t-hover) ease,
              background   var(--t-hover) ease,
              border-color var(--t-hover) ease,
              color        var(--t-hover) ease,
              box-shadow   var(--t-hover) ease;
}
.nav-utility-btn:hover, .nav-utility-btn:focus-visible {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 10%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 18%, transparent);
  transform: translateY(-1px);
}
.nav-utility-btn.active {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 60%, transparent);
}
.nav-utility-btn:active {
  transform: translateY(0);
  transition-duration: var(--t-press);
}
[data-theme="dark"] .nav-utility-btn {
  border-color: rgba(255,255,255,0.32);
  background: rgba(255,255,255,0.05);
  color: rgba(255,255,255,0.92);
}
[data-theme="dark"] .nav-utility-btn:hover,
[data-theme="dark"] .nav-utility-btn:focus-visible {
  color: #fff;
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 14px 2px rgba(255,255,255,0.18);
}
[data-theme="dark"] .nav-utility-btn.active {
  color: #fff;
  background: rgba(255,255,255,0.14);
  border-color: rgba(255,255,255,0.6);
}

.tab-btn {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 0.55rem 1rem 0.5rem;
  font-family: inherit;
  font-size: var(--font-size-base);
  color: var(--text-2);
  cursor: pointer;
  transition: color var(--t-hover), border-color var(--t-hover);
  margin-bottom: -1px;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.tab-btn:hover, .tab-btn:focus-visible { color: var(--text); }
.tab-btn.active { color: var(--accent-teal); border-bottom-color: transparent; }

/* Threadfall mark in tabs */
.tf-tab-mark {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  opacity: 0.5;
  transition: opacity var(--t-hover);
}
.tab-btn.active .tf-tab-mark,
.tab-btn:hover .tf-tab-mark, .tab-btn:focus-visible .tf-tab-mark { opacity: 1; }
.live-nav-btn { flex-shrink: 0; color: var(--color-gold); font-weight: 700; }

/* ── Live nav button — premium action pill ───────────────────────────── */
#live-nav-btn {
  border: 1px solid color-mix(in srgb, var(--color-gold) 55%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--color-gold) 55%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--color-gold) 18%, transparent);
  margin-bottom: 0;
  margin-left: 0.5rem;
  margin-right: 1.25rem;
  padding: 0.38rem 1rem;
  animation: live-btn-flicker 3.6s ease-in-out infinite;
  transition: background var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease),
              box-shadow var(--t-hover) var(--ease);
}
#live-nav-btn .tf-tab-mark { opacity: 0.85; }
#live-nav-btn:hover, #live-nav-btn:focus-visible {
  color: var(--color-gold);
  background: color-mix(in srgb, var(--color-gold) 26%, transparent);
  border-color: color-mix(in srgb, var(--color-gold) 75%, transparent);
  box-shadow: 0 0 14px 3px color-mix(in srgb, var(--color-gold) 25%, transparent);
}
#live-nav-btn:hover .tf-tab-mark, #live-nav-btn:focus-visible .tf-tab-mark { opacity: 1; }
/* Candle-flicker: irregular brief dips + a steady mid-cycle glow. The
   asymmetric keyframe times prevent it from reading as a smooth pulse. */
@keyframes live-btn-flicker {
  0%, 100% {
    box-shadow: 0 0 0 0 transparent;
    background: color-mix(in srgb, var(--color-gold) 18%, transparent);
  }
  17% { background: color-mix(in srgb, var(--color-gold) 9%, transparent); }
  20% { background: color-mix(in srgb, var(--color-gold) 24%, transparent); }
  50% {
    box-shadow: 0 0 12px 3px color-mix(in srgb, var(--color-gold) 24%, transparent);
    background: color-mix(in srgb, var(--color-gold) 20%, transparent);
  }
  73% { background: color-mix(in srgb, var(--color-gold) 11%, transparent); }
  77% { background: color-mix(in srgb, var(--color-gold) 22%, transparent); }
}
/* .nav-auth-btns removed — profile moved to nav-left, switch-campaign removed */
.nav-gm-btn {
  padding: 0.2rem 0.55rem;
  font-size: var(--font-size-sm); font-weight: 600; cursor: pointer;
  border-radius: var(--radius-md); border: 1px solid;
  border-color: var(--accent-teal); color: var(--accent-teal);
  background: none;
  transition: background var(--t-hover), color var(--t-hover);
}
.nav-gm-btn:hover, .nav-gm-btn:focus-visible { background: var(--accent-teal); color: #fff; }

/* ── Nav profile button ──────────────────────────────────────────────────── */
.nav-profile-btn {
  background: none; border: none; cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
.nav-avatar-img {
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--font-size-xs); font-weight: 700; letter-spacing: 0.02em;
  overflow: hidden; flex-shrink: 0;
  border: 2px solid var(--accent-teal);
  background: var(--bg-3); color: var(--text-2);
  transition: border-color var(--t-hover), box-shadow var(--t-hover);
}
.nav-avatar-img img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.nav-profile-btn:hover .nav-avatar-img, .nav-profile-btn:focus-visible .nav-avatar-img { border-color: var(--accent-teal); box-shadow: 0 0 0 2px rgba(47,124,133,0.25); }

/* ── Campaign selector profile button — fixed top-right, mirrors admin pulse layout ── */
/* Outer button gets the campaign-card border & hover treatment so it reads as the
   visual sibling of the cards (instead of a tiny floating avatar). */
#cs-profile-btn {
  position: fixed;
  top: 1.5rem;
  right: 1.5rem;
  z-index: 10003;
  width: 40px;
  height: 40px;
  padding: 0;
  margin: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-2);
  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-hover) ease;
}
#cs-profile-btn:hover, #cs-profile-btn:focus-visible {
  border-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}
/* Inner avatar fills the button — the button now owns the border. */
#cs-profile-btn .nav-avatar-img {
  width: 100%; height: 100%;
  border: none;
  box-shadow: none;
}
#cs-profile-btn:hover .nav-avatar-img,
#cs-profile-btn:focus-visible .nav-avatar-img { border: none; box-shadow: none; }

/* ── Profile modal ────────────────────────────────────────────────────────── */
.profile-modal-box {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-3xl);
  padding: 2rem; width: 360px; max-width: 94vw;
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 1.5rem;
  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
  animation: modal-in 0.18s ease;
}
.profile-modal-close {
  position: absolute; top: 1rem; right: 1rem;
}
.profile-avatar-section {
  display: flex; flex-direction: column; align-items: center; gap: 0.6rem;
}
/* v791: drop the teal accent + outer glow so the profile avatar matches the
   neutral image-frame treatment used elsewhere (e.g., .ef-img-preview). */
.profile-avatar-lg {
  width: 88px; height: 88px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 2rem; font-weight: 700; letter-spacing: 0.02em;
  overflow: hidden; border: 1px solid var(--border);
  background: var(--bg-3); color: var(--text-2);
}
.profile-avatar-lg img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.profile-avatar-actions {
  display: flex; gap: 0.4rem; align-items: center;
}
.profile-avatar-edit-btn {
  background: none; border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 0.25rem 0.65rem; font-size: var(--font-size-sm); cursor: pointer;
  color: var(--text-2); transition: border-color var(--t-hover), color var(--t-hover);
}
.profile-avatar-edit-btn:hover, .profile-avatar-edit-btn:focus-visible { border-color: var(--accent-teal); color: var(--accent-teal); }
.profile-discord-btn {
  display: inline-flex; align-items: center; gap: 0.3rem;
  background: var(--color-discord); border: none; border-radius: var(--radius-md);
  padding: 0.25rem 0.65rem; font-size: var(--font-size-sm); font-weight: 600;
  cursor: pointer; color: #fff;
  transition: background var(--t-hover), opacity var(--t-hover);
}
.profile-discord-btn:hover, .profile-discord-btn:focus-visible { background: var(--color-discord-hover); }
.profile-discord-btn:disabled { opacity: 0.5; cursor: default; }
.profile-fields {
  width: 100%; display: flex; flex-direction: column; gap: 0.75rem;
}
.profile-field-row {
  display: flex; flex-direction: column; gap: 0.25rem;
}
.profile-label {
  font-size: var(--font-size-xs); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-3);
}
.profile-input {
  width: 100%; padding: 0.5rem 0.65rem; border-radius: var(--radius-lg);
  border: 1.5px solid var(--border); background: var(--bg-2);
  color: var(--text); font-size: var(--font-size-md); font-family: inherit;
  transition: border-color var(--t-hover);
}
.profile-input:focus { outline: none; border-color: var(--accent-teal); }
.profile-email-ro {
  font-size: var(--font-size-base); color: var(--text-3); padding: 0.35rem 0;
}
.profile-role-badge {
  display: inline-flex; align-items: center; padding: 0.2rem 0.55rem;
  border-radius: var(--radius-full); font-size: var(--font-size-sm); font-weight: 700;
  background: rgba(47,124,133,0.12); color: var(--accent-teal);
  text-transform: capitalize;
}
.profile-section-divider {
  width: 100%; height: 1px; background: var(--border); margin: 0.75rem 0;
}
.profile-section-label {
  font-size: var(--font-size-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--color-text-tertiary); margin-bottom: 0.5rem;
}
.profile-change-pw-section {
  width: 100%; display: flex; flex-direction: column; gap: 0.5rem;
}
.profile-footer {
  width: 100%; display: flex; gap: 0.5rem; align-items: center;
}
.profile-footer .btn-primary { flex: 1; padding: 0.6rem; font-size: var(--font-size-md); }
.btn-danger-outline {
  padding: 0.55rem 1rem; border-radius: 0;
  border: 1px solid var(--color-danger); color: var(--color-danger);
  background: none; font-size: var(--font-size-sm); font-weight: 500;
  cursor: pointer; transition: background var(--t-hover);
  white-space: nowrap;
}
.btn-danger-outline:hover, .btn-danger-outline:focus-visible {
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
}
.profile-delete-account-row {
  width: 100%; text-align: center; padding-top: 0.25rem;
}
.profile-delete-account-btn {
  background: none; border: none; cursor: pointer;
  font-size: var(--font-size-xs); color: var(--color-text-tertiary);
  text-decoration: underline; padding: 0.25rem;
  transition: color var(--t-hover);
}
.profile-delete-account-btn:hover, .profile-delete-account-btn:focus-visible { color: var(--color-danger); }

.theme-pill-group {
  display: flex; gap: 0.35rem;
}
.theme-pill {
  padding: 0.25rem 0.65rem; border-radius: var(--radius-full);
  border: 1px solid var(--border); background: none;
  color: var(--text-2); font-size: var(--font-size-xs); font-weight: 600;
  cursor: pointer; transition: background var(--t-hover), color var(--t-hover), border-color var(--t-hover);
}
.theme-pill:hover, .theme-pill:focus-visible { background: var(--bg-3); }
.theme-pill.active {
  background: var(--accent-teal); border-color: var(--accent-teal);
  color: #fff;
}

/* ── Preference toggle (profile modal) ───────────────────────────────────── */
.pref-toggle {
  display: flex; align-items: center; gap: 0.5rem; cursor: pointer; user-select: none;
}
.pref-toggle input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; }
.pref-toggle-track {
  position: relative; width: 34px; height: 18px;
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--radius-full); transition: background var(--t-hover), border-color var(--t-hover); flex-shrink: 0;
}
.pref-toggle input:checked + .pref-toggle-track {
  background: var(--accent-teal); border-color: var(--accent-teal);
}
.pref-toggle-thumb {
  position: absolute; top: 2px; left: 2px;
  width: 12px; height: 12px; border-radius: 50%;
  background: #fff; transition: transform var(--t-hover);
}
.pref-toggle input:checked + .pref-toggle-track .pref-toggle-thumb { transform: translateX(16px); }
.pref-toggle-label { font-size: var(--font-size-xs); color: var(--text-2); font-weight: 500; min-width: 2rem; }

/* ── Log entry author display ──────────────────────────────────────────────── */
.le-author-row {
  display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.25rem;
}
.le-author-avatar {
  width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.58rem; font-weight: 700; overflow: hidden;
  background: var(--bg-3); color: var(--text-3);
  border: 1.5px solid var(--border);
}
.le-author-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.le-author-name {
  font-size: var(--font-size-xs); font-weight: 600; color: var(--text-2);
}

/* ── Tab panels ──────────────────────────────────────────────────────────── */
.tab-panel {
  padding: 1.5rem;
  overflow-y: auto;
  height: calc(100vh - var(--chrome-height)); /* viewport minus nav + tab strip (--chrome-height) */
}
/* Journal tab fills edge-to-edge; sessions column scrolls, chronicle sticks */
#tab-sessions.tab-panel {
  padding: 0;
  overflow-y: auto;
}
/* v787: align Codex eyebrow to canonical top-left spacing. The default
   .tab-panel padding (1.5rem all around) was making the Codex eyebrow
   sit further from the corner than every other tab's. */
#tab-compendium.tab-panel {
  padding: var(--space-5) var(--space-4);
}
/* v788: zero the OUTER #tab-party padding so the inner .party-tab-panel's
   var(--space-5)/var(--space-4) is the only spacing from the tab edge.
   Without this override the base .tab-panel { padding: 1.5rem } stacked
   on top of the inner padding, producing 2.5rem left/top — no other tab
   was double-padded. */
#tab-party.tab-panel {
  padding: 0;
}
/* v906 RCCA: align Fates eyebrows (My Fates / Weave / Threads) to the
   canonical top-left spacing. The tab was inheriting the base
   `.tab-panel { padding: 1.5rem }` (24px), which sat the eyebrows
   4-8px right and below where every other single-column tab places
   them. Match Codex's canonical pattern so 🎭 / 🧶 / 🧵 align with
   📜 / 📖 / ⚔ / 🗺 across the app. */
#tab-threads.tab-panel {
  padding: var(--space-5) var(--space-4);
}
/* .map-tab-panel layout lives in the "Map shared" block below — single rule. */

/* v965: canonical empty/hint placeholder — small italic dim, left-aligned.
   Pattern reference: the Weave Fates "No threads selected yet…" line.
   Used for ALL inline helpers, empty-list bodies, and "select something"
   detail-panel placeholders. Bespoke per-tab classes (.chat-empty,
   .admin-empty, .feed-empty, .map-placeholder, .quest-detail-placeholder,
   .fates-saved-detail-placeholder, etc.) compose this and override only
   layout (flex containers); they MUST NOT re-set font-size, text-align,
   or font-style. Highlight action words inside placeholders with
   <strong> — the rule below tints + un-italicises them automatically. */
.placeholder {
  color: var(--text-3);
  font-size: var(--font-size-sm);
  font-style: italic;
}
.placeholder strong {
  color: var(--color-text-secondary);
  font-style: normal;
  font-weight: var(--font-weight-bold);
}
/* Padded variant — used as the empty / loading state inside list bodies
   (codex, party, quotes). Replaces inline style="padding:1.5rem" that
   recurred 6× across app.js render templates. */
.placeholder--padded {
  padding: var(--space-6);
}
/* v967: inline variant — sits at the canonical "in-tab inline hint"
   position (small horizontal nudge off the absolute tab edge for
   breathing room, no vertical padding). Used for any short hint that
   sits directly under a section eyebrow rather than inside a card body
   — Weave Fates / Saved Fates / Loose Threads empty states all compose
   this so they line up at exactly the same x. */
.placeholder--inline {
  padding: var(--space-2);
}

/* ── Compendium ──────────────────────────────────────────────────────────── */
/* Section header — "CODEX" eyebrow on the left, "+ Add" CTA group on the
   right. Mirrors .sessions-toolbar rhythm so all three primary tabs lead
   with the same eyebrow + paired-CTA grammar. */
.compendium-section-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.compendium-section-actions {
  display: flex;
  gap: var(--space-2);
}
.compendium-toolbar {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}
.compendium-toolbar input[type="text"] { flex: 1; min-width: 180px; }

#type-filters, #hooks-type-filters { display: flex; gap: 0.45rem; flex-wrap: wrap; }
/* Codex type-filter pills mirror the .btn-new pill metrics. The resting pill
   is the same white-outlined ghost; the .active pill is the teal counterpart
   (same kinetics, teal-tinted fill + glow). */
/* v798: theme-correct base + dark override (mirrors .btn-new refactor). */
.type-filter-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 1rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  color: var(--text-1);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease);
}
.type-filter-btn:hover, .type-filter-btn:focus-visible {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 18%, transparent);
  transform: translateY(-1px);
}
.type-filter-btn:active { transform: translateY(0); transition-duration: var(--t-press); }
[data-theme="dark"] .type-filter-btn {
  color: rgba(255,255,255,0.92);
  background: rgba(255,255,255,0.05);
  border-color: rgba(255,255,255,0.32);
}
[data-theme="dark"] .type-filter-btn:hover, [data-theme="dark"] .type-filter-btn:focus-visible {
  color: #fff;
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 14px 2px rgba(255,255,255,0.18);
}
/* Active teal is theme-driven via var(--accent-teal) so it adapts to
   light/dark themes; the previous literal rgb(79,154,163) was the
   dark-mode --color-brand value, hard-coded. */
.type-filter-btn.active {
  color: #e6f6f8;
  background: color-mix(in srgb, var(--accent-teal) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 65%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 22%, transparent);
}
.type-filter-btn.active:hover, .type-filter-btn.active:focus-visible {
  color: #fff;
  background: color-mix(in srgb, var(--accent-teal) 28%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 85%, transparent);
  box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent-teal) 30%, transparent);
  transform: translateY(-1px);
}
/* Threads filter pills (#hooks-type-filters) selected state — instead of the
   teal .active treatment used by Codex, mirror the white-ghost hover state of
   the sibling unselected pills. */
#hooks-type-filters .type-filter-btn.active {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 18%, transparent);
}
#hooks-type-filters .type-filter-btn.active:hover,
#hooks-type-filters .type-filter-btn.active:focus-visible {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 18%, transparent);
  transform: translateY(-1px);
}
[data-theme="dark"] #hooks-type-filters .type-filter-btn.active {
  color: #fff;
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 14px 2px rgba(255,255,255,0.18);
}
[data-theme="dark"] #hooks-type-filters .type-filter-btn.active:hover,
[data-theme="dark"] #hooks-type-filters .type-filter-btn.active:focus-visible {
  color: #fff;
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 14px 2px rgba(255,255,255,0.18);
  transform: translateY(-1px);
}

.entity-list {
  overflow: hidden;
}

/* ── Encyclopaedia A–Z layout ───────────────────────────────────────────────── */
/* Typography (uppercase, weight, letter-spacing, color, font-size) inherits
   from .form-section-label via composition; .alpha-letter-hdr only contributes
   the per-letter spacing. */
.alpha-letter-hdr {
  padding: 0.6rem 0.25rem 0.25rem;
  margin-top: 0.75rem;
}
.alpha-letter-hdr:first-child { margin-top: 0; }

/* Gap-separated cards (parity with .session-list) — was a stitched table
   grid with shared 1px borders. Each entity row now stands as its own
   bordered card with rounded corners and the canonical card hover. */
.alpha-entries-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-3);
}

.entity-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--bg-2);
  min-width: 0;
  overflow: hidden;
}

/* Expanded row spans all grid columns */
.entity-row.expanded {
  grid-column: 1 / -1;
}

.entity-row-hdr {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem 1rem;
  cursor: pointer;
  transition: background var(--t-hover);
}
.entity-row-hdr:hover, .entity-row-hdr:focus-visible { background: var(--bg-3); }

.entity-icon {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-lg);
  flex-shrink: 0;
  overflow: hidden;
}
.entity-icon img { width: 2.25rem; height: 2.25rem; object-fit: cover; object-position: top center; }

/* ── Entity icon tint — accepts --entity-color CSS var set inline ─────── */
/* Replaces inline style="background:${color}22;color:${color}" pattern    */
.entity-icon-tinted {
  background: color-mix(in srgb, var(--entity-color, var(--color-brand)) 13%, transparent);
  color: var(--entity-color, var(--color-brand));
}
/* Sibling utility — same --entity-color injection pattern, but for tinted
   text-only badges (entity type label, merge-search type). Replaces
   inline style="color:${cfg.color}" / style="color:${cfg.color || '#888'}". */
.entity-badge-tinted {
  color: var(--entity-color, var(--text-3));
}

.entity-meta { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.entity-name { font-size: var(--font-size-md); font-weight: 500; color: var(--text); }
.entity-badge { font-size: var(--font-size-xs); font-weight: 500; }
.entity-chevron { color: var(--text-3); font-size: var(--font-size-xs); }

/* One visual split at the expanded-body boundary — background change only.
   The previous border-top + background combo doubled up the affordance;
   .session-card uses border-top alone, .entity-row uses background alone.
   Either reads cleanly; doubling does not. */
.entity-detail {
  padding: 1rem 1rem 1.25rem 1rem;
  background: var(--bg);
}
.entity-full-img {
  float: right;
  width: 12.5rem;
  height: 12.5rem;
  object-fit: cover;
  object-position: top center;
  border-radius: var(--radius-lg);
  margin: 0 0 0.75rem 1rem;
}
.entity-detail-body {
  overflow: visible;
}
.entity-detail-body::after {
  content: '';
  display: table;
  clear: both;
}
.entity-desc { font-size: var(--font-size-base); color: var(--text-2); line-height: 1.6; margin-bottom: 0.75rem; }
/* Empty-state italic shown inside .entity-desc when an entity has no
   description yet. Replaces inline style="color:var(--text-3)" on <em>. */
.entity-desc-empty { color: var(--text-3); font-style: italic; }
.entity-detail-actions { display: flex; gap: 0.5rem; }

/* ── Codex mode toggle ───────────────────────────────────────────────────── */
/* The codex-mode-btn now uses the global .btn-filter pattern (defined in
   the Sort/Filter icon-button block below). We keep the id selector solely
   to ensure white-space stays nowrap when the label flips between
   "All Entries" and "In Play". */
#codex-mode-btn { white-space: nowrap; }

/* ── Reason badges ───────────────────────────────────────────────────────── */
/* Used in the expanded-entity context header's "Why Now" row. (The Active
   Context Layer that previously wrapped these on the codex front page was
   removed — In-Play mode now filters the normal entity grid directly.) */
.codex-reason-badge {
  display: inline-block;
  font-size: 0.6rem;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: var(--radius-full);
  background: var(--bg-3);
  color: var(--text-3);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.codex-reason-quest       { background: color-mix(in srgb, var(--color-purple) 12%, transparent); color: var(--accent-purple); border-color: var(--accent-purple); }
.codex-reason-thissession { background: color-mix(in srgb, var(--color-brand)  12%, transparent); color: var(--accent-teal);   border-color: var(--accent-teal); }
.codex-reason-recent      { background: color-mix(in srgb, var(--color-info)   10%, transparent); color: var(--accent-blue);   border-color: var(--accent-blue); }
.codex-reason-prevsession { background: var(--bg-3); color: var(--text-3); border-color: var(--border); }

/* ── Entity Context Header (inside expanded entity) ─────────────────────── */
.entity-ctx-header {
  margin-bottom: 0.75rem;
  padding: 0.5rem 0.65rem;
  background: color-mix(in srgb, var(--color-brand) 6%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--color-brand) 20%, var(--border));
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
}
/* Typography inherits from .form-section-label (composed via markup); the
   teal color is the only divergence — context-header eyebrow is teal so it
   reads as an "active" callout vs the neutral-tertiary canonical eyebrow. */
.entity-ctx-label {
  color: var(--accent-teal);
  margin-bottom: 0.4rem;
}
.ech-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
  line-height: 1.45;
}
.ech-row:last-child { margin-bottom: 0; }
.ech-label {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  min-width: 70px;
}
.ech-value { color: var(--text-2); font-size: var(--font-size-sm); }
.ech-quest { color: var(--accent-purple); }
.ech-also  { display: flex; flex-wrap: wrap; gap: 4px; }
.ech-entity-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1px 6px;
  font-size: var(--font-size-xs);
  color: var(--accent-teal);
  cursor: pointer;
  transition: background var(--t-hover);
}
.ech-entity-btn:hover, .ech-entity-btn:focus-visible { background: var(--bg-3); }

/* ── Inline Entity Preview Drawer ───────────────────────────────────────── */
.entity-preview-drawer {
  position: fixed;
  top: var(--header-height); /* below nav */
  right: 0;
  width: 320px;
  max-width: 90vw;
  height: calc(100vh - var(--nav-height));
  background: var(--bg-2);
  border-left: 1px solid var(--border);
  box-shadow: -4px 0 24px rgba(0,0,0,0.20);
  z-index: 10005;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.entity-preview-drawer.hidden { display: none; }
.epd-inner { display: flex; flex-direction: column; height: 100%; }
.epd-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.65rem 0.75rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.epd-icon { flex-shrink: 0; }
.epd-titlebox { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.epd-name  { font-size: var(--font-size-md); font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.epd-badge { font-size: var(--font-size-xs); font-weight: 500; }
.epd-close-btn {
  background: none; border: none; cursor: pointer;
  color: var(--text-3); font-size: var(--font-size-lg); padding: 2px 4px;
  border-radius: var(--radius-sm); transition: color var(--t-hover);
  flex-shrink: 0;
}
.epd-close-btn:hover, .epd-close-btn:focus-visible { color: var(--text); }
.epd-content {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem;
}
.epd-portrait {
  width: 100%; max-height: 160px;
  object-fit: cover; object-position: top center;
  border-radius: var(--radius-md);
  margin-bottom: 0.65rem;
}
.epd-footer {
  padding: 0.6rem 0.75rem;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

/* ── Map shared ──────────────────────────────────────────────────────────── */
/* Single canonical layout rule — the duplicate `.map-tab-panel { height: …;
   overflow: hidden }` declaration that used to live up near the
   #tab-sessions block was retired in v774; layout + overflow live here. */
.map-tab-panel {
  padding: 0;
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--chrome-height));
  overflow: hidden;
}

/* Leaflet container inherits app theme */
.leaflet-container {
  background: var(--bg) !important;
  font-family: inherit;
}

/* Zoom controls */
.leaflet-control-zoom {
  border: none !important;
  box-shadow: 0 2px 10px color-mix(in srgb, #000 35%, transparent) !important;
  border-radius: var(--radius-lg) !important;
  overflow: hidden;
}
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
  background: var(--bg-2) !important;
  color: var(--text-2) !important;
  border: none !important;
  border-bottom: 1px solid var(--border) !important;
  /* 30px is Leaflet's default zoom button size — preserved literally so
     the +/- glyphs sit centred without re-tuning padding. */
  width: 30px !important;
  height: 30px !important;
  line-height: 30px !important;
  font-size: var(--font-size-md) !important;
  transition: background var(--t-hover), color var(--t-hover) !important;
}
.leaflet-control-zoom-in:hover, .leaflet-control-zoom-in:focus-visible,
.leaflet-control-zoom-out:hover, .leaflet-control-zoom-out:focus-visible {
  background: var(--accent-teal) !important;
  color: var(--color-white) !important;
}
.leaflet-attribution-flag { display: none !important; }
.leaflet-control-attribution { display: none !important; }

/* Popup chrome */
.leaflet-popup-content-wrapper {
  background: var(--bg-2) !important;
  border: 1px solid var(--border) !important;
  border-radius: var(--radius-xl) !important;
  box-shadow: 0 8px 32px color-mix(in srgb, #000 55%, transparent) !important;
  color: var(--text) !important;
  padding: 0 !important;
}
.leaflet-popup-content { margin: 0 !important; }
.leaflet-popup-tip { background: var(--bg-2) !important; }
.leaflet-popup-close-button {
  color: var(--text-3) !important;
  font-size: var(--font-size-lg) !important;
  padding: var(--space-1) var(--space-2) !important;
  top: 2px !important;
  right: 2px !important;
  line-height: 1 !important;
}
.leaflet-popup-close-button:hover, .leaflet-popup-close-button:focus-visible { color: var(--text) !important; }

/* ── Map pin (Atlas + Live, unified) ─────────────────────────────────────── */
/* Per-instance border tint via --pin-color (matches the --entity-color /
   --qbadge-color / --lead-color injection pattern documented in CLAUDE.md). */
.map-pin {
  width: 34px;
  height: 34px;
  border-radius: 50% 50% 50% 0;
  border: 2.5px solid var(--pin-color, currentColor);
  background: var(--bg-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-md);
  box-shadow:
    0 3px 10px color-mix(in srgb, #000 50%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent);
  transform: rotate(-45deg);
  overflow: hidden;
  transition: box-shadow var(--t-hover);
}
.map-pin:hover, .map-pin:focus-visible {
  box-shadow:
    0 5px 18px color-mix(in srgb, #000 65%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 10%, transparent);
}
.map-pin > * { transform: rotate(45deg); }
/* Thumb image inside a pin — replaces inline style="width:100%;..." that
   shipped on every <img> rendered by makePin(). */
.map-pin-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}
.map-pin.locked {
  border-color: var(--text-3) !important;
  opacity: 0.4;
  filter: grayscale(0.85);
  cursor: default;
}

/* ── Party beacon (Live map) ─────────────────────────────────────────────── */
.party-beacon {
  position: relative;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.party-beacon-dot {
  width: 13px;
  height: 13px;
  background: var(--accent-teal);
  border: 2.5px solid #fff;
  border-radius: 50%;
  position: relative;
  z-index: 2;
  box-shadow: 0 0 8px rgba(47,124,133,0.7);
}
.party-beacon-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(47,124,133,0.3);
  animation: beacon-pulse 2.2s ease-out infinite;
}
@keyframes beacon-pulse {
  0%   { transform: scale(0.6); opacity: 0.9; }
  100% { transform: scale(2.4); opacity: 0; }
}

/* Pinned log entry markers on the live map */
.live-entry-pin {
  width: 14px; height: 14px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
}
.live-entry-pin.note {
  background: rgba(140,140,140,0.75);
  border: 1.5px solid rgba(255,255,255,0.5);
}
.live-entry-pin.key-event {
  background: none; border: none;
  font-size: 13px; line-height: 1;
}

/* ── Popup content (Atlas + Live, unified) ───────────────────────────────── */
.map-popup-inner {
  padding: var(--space-3);
  min-width: 175px;
}
.map-popup-name {
  font-size: var(--font-size-base);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0;
}
/* Thin divider between the location name and the GM rename controls.
   Composes the canonical 1px border-top against --border; only the local
   margin (asymmetric to keep the rename label tight under it) is bespoke. */
.map-popup-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: var(--space-2) 0 var(--space-1);
}
.map-popup-label {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  margin-bottom: var(--space-1);
}
.map-popup-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-bottom: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-3);
  color: var(--text);
  font-size: var(--font-size-sm);
  font-family: inherit;
}
.map-popup-input:focus { outline: none; border-color: var(--accent-teal); }
/* Save / Remove buttons inside the popup — composes .btn-primary +
   .btn-ghost--danger; this class only adds the full-width block layout
   and the tight inter-button spacing the popup needs. v773 retired the
   bespoke .map-popup-save-btn / .map-popup-remove-btn rules. */
.map-popup-btn {
  display: block;
  width: 100%;
  margin-bottom: var(--space-1);
  font-size: var(--font-size-sm);
}
.map-popup-btn:last-child { margin-bottom: 0; }

/* ── Map hover card tooltip ──────────────────────────────────────────────── */
.map-hover-tooltip {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  padding: 0 !important;
}
.map-hover-tooltip::before { display: none !important; }
.map-hover-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-teal);
  border-radius: var(--radius-lg);
  padding: 0.5rem 0.7rem;
  max-width: 240px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.55);
  pointer-events: none;
}
.map-hover-card-name {
  font-size: var(--font-size-base);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.2rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.map-hover-card-desc {
  font-size: var(--font-size-xs);
  color: var(--text-2);
  line-height: 1.45;
  white-space: normal;
}

/* ── Atlas toolbar ───────────────────────────────────────────────────────── */
/* Eyebrow (.form-section-label "Atlas") + optional map switcher on the LEFT,
   GM controls cluster on the RIGHT. Mirrors .quest-nav-toolbar /
   .sessions-toolbar / .compendium-section-hdr rhythm — no border-bottom,
   just an indent + space-between. The divider is implicit via #map-container
   below, which sits flush against the toolbar. */
.map-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  /* v787: canonical eyebrow top-left spacing — matches all other tabs. */
  padding: var(--space-5) var(--space-4) var(--space-2);
  flex-shrink: 0;
}
.map-toolbar-left,
.map-toolbar-right {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.map-toolbar .btn-ghost { font-size: var(--font-size-sm); }
.map-place-select {
  padding: 0.28rem 0.6rem;
  font-size: var(--font-size-base);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  width: auto;
  max-width: 220px;
}
.map-hint { color: var(--text-3); font-size: var(--font-size-sm); font-style: italic; }

#map-container { flex: 1; background: var(--bg-3); }

/* v968: stripped flex-center recipe — empty map state now sits at the
   canonical inline-hint x (matches every other empty state app-wide).
   Hint above, upload button below, both left-aligned, small gap. */
.map-placeholder {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  font-style: italic;
}
/* Inline-button-shaped <label> wrapping the hidden file input in the empty
   state. Composes .btn-primary; this class only sets cursor + sized type. */
.map-upload-label {
  cursor: pointer;
  font-size: var(--font-size-sm);
}
/* Re-extract modal location list — taller than the default checklist
   so users can scan ~20 candidates without paging. */
.reextract-checklist { max-height: 300px; }
/* Warn variant of .form-helper-text — amber-tinted, used by the re-extract
   "this will replace N existing locations" line. */
.form-helper-text--warn {
  margin-top: 3px;
  margin-bottom: 0;
  color: var(--accent-amber);
  font-size: var(--font-size-sm);
}

/* ── Entity modal ────────────────────────────────────────────────────────── */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 1rem;
}
/* Heavier scrim for blocking moments (legal consent, irreversible flows) */
.modal-overlay--blocking { background: rgba(0,0,0,0.82); z-index: 9000; }
/* Confirmation dialogs always stack ABOVE the modal that triggered them.
   v918 fix: previously #confirm-modal got the same z-index: 1000 as every
   other overlay; since #fates-weave-modal sits later in the DOM, the
   workspace covered the confirm dialog when "Delete this Fate?" fired
   from inside the workspace — invisible confirm, double-click → double-
   delete → 404 on the second request. This rule keeps every confirmation
   dialog (current or future) above the surface it interrupts. */
#confirm-modal { z-index: 1100; }
.modal-box {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xl);
  width: 100%;
  max-width: 480px;          /* default — md */
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 8px 32px rgba(0,0,0,0.2);
}
.modal-box--sm { max-width: 420px; }
/* v987: stacked choice buttons for the first-session-choice modal.
   Primary recommended CTA on top, escape-hatch ghost button below.
   Both stretch full width of the modal body for thumb-target sizing. */
.first-session-choice-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.first-session-choice-actions > button { width: 100%; justify-content: center; }
.modal-box--lg { max-width: 560px; }
.modal-box--xl { max-width: 720px; }
/* xxl — for the Brainstorm Workspace which needs three-column horizontal
   room. Sprint A only uses the center column; left rail (pinning, Sprint C)
   and right rail (saves, Sprint D) reuse this size. */
.modal-box--xxl { max-width: 1080px; }
/* Generic body wrapper for non-form modal content (paragraphs, lists, status text). */
.modal-body { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
.modal-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5) var(--space-3);
  border-bottom: 1px solid var(--border);
}
/* v792: modal title typography aligned to the eyebrow recipe (uppercase,
   weight 700, letter-spacing 0.06em) — just larger and white instead of
   --text-3. Drops the Georgia serif for typographic consistency app-wide. */
.modal-hdr h2 {
  font-family: inherit;
  font-size: var(--font-size-lg);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
  margin: 0;
}
.modal-close-btn {
  background: none;
  border: none;
  font-size: var(--font-size-xl);
  color: var(--text-3);
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
}
.modal-close-btn:hover, .modal-close-btn:focus-visible { background: var(--bg-3); color: var(--text); }

.entity-form { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
.form-row { display: flex; flex-direction: column; gap: 0.3rem; }
.form-row label { font-size: var(--font-size-sm); font-weight: 500; color: var(--text-2); }
.date-parts { display: flex; align-items: center; gap: 0.2rem; }
.date-part { width: 3ch; text-align: center; padding: 0.4rem 0.3rem; }
.date-sep { color: var(--text-3); font-weight: 600; }
.form-actions { display: flex; gap: 0.75rem; margin-top: 0.25rem; }

.ef-img-preview {
  width: 80px;
  height: 80px;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  margin-bottom: 0.4rem;
}
/* Multi-image gallery strip in the entity modal — horizontal scroller of
   thumbnail tiles with per-tile actions (★ make primary, × remove). */
.ef-image-gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.4rem 0;
}
.ef-gallery-tile {
  position: relative;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  overflow: hidden;
  background: var(--bg-1);
  flex-shrink: 0;
}
.ef-gallery-tile--primary {
  border-color: var(--accent-gold, #d4a574);
  box-shadow: 0 0 0 1px var(--accent-gold, #d4a574);
}
.ef-gallery-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.ef-gallery-primary-badge {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  color: var(--accent-gold, #d4a574);
  background: rgba(0,0,0,0.55);
  border-radius: 50%;
  pointer-events: none;
}
.ef-gallery-btn {
  position: absolute;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  background: rgba(0,0,0,0.55);
  color: var(--text);
  border: none;
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  line-height: 1;
  transition: background var(--t-hover) ease, color var(--t-hover) ease;
}
.ef-gallery-btn:hover,
.ef-gallery-btn:focus-visible {
  background: rgba(0,0,0,0.85);
  outline: none;
}
.ef-gallery-btn--star { top: 2px; left: 2px; color: var(--accent-gold, #d4a574); }
.ef-gallery-btn--remove { top: 2px; right: 2px; color: var(--accent-red, #d4724f); }

/* Image-cycler host + pagination dots. The host wraps each multi-image
   <img data-img-cycle="..."> in a positioned span so the dots can absolutely-
   position at the bottom-inside of the image. fit-content sizing means the
   wrapper hugs the image dimensions without disturbing the surrounding flow. */
.img-cycle-host {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  line-height: 0;  /* removes inline-image gap below */
}
/* When the wrapped image was sized via width:100% (full-width grid card etc.),
   the host needs to span the same width so the absolute-positioned dots align
   with the image's actual rendered bounds. */
.img-cycle-host > img { max-width: 100%; }

/* Context overrides: when the wrapped image previously carried layout
   responsibilities (float-right with text-wrap, 100%-fill of a sized parent),
   the host has to adopt those properties — otherwise wrapping in a
   `display: block; width: fit-content` span breaks the surrounding layout.
   :has() hoists float/dims to the host and resets the img to fill it. */
.img-cycle-host:has(> .entity-full-img) {
  float: right;
  width: 12.5rem;
  height: 12.5rem;
  margin: 0 0 0.75rem 1rem;
}
.img-cycle-host > .entity-full-img {
  float: none;
  margin: 0;
  width: 100%;
  height: 100%;
}
/* Party / live PC portrait: image fills the parent's sized box (160px square
   on Party tab, 140px tall × 100% wide in the live PC sidebar). */
.party-pc-portrait > .img-cycle-host {
  display: block;
  width: 100%;
  height: 100%;
}
/* Codex sidebar pane (#epd-content): image is full-width with max-height. */
#epd-content > .img-cycle-host {
  display: block;
  width: 100%;
}
.img-cycle-dots {
  position: absolute;
  bottom: 6px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 0.35rem;
  align-items: center;
  padding: 3px 6px;
  border-radius: var(--radius-full);
  background: rgba(0,0,0,0.45);
  backdrop-filter: blur(2px);
  pointer-events: auto;
}
.img-cycle-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: none;
  background: rgba(255,255,255,0.55);
  cursor: pointer;
  padding: 0;
  transition: background var(--t-trans), transform var(--t-trans);
}
.img-cycle-dot:hover,
.img-cycle-dot:focus-visible {
  background: rgba(255,255,255,0.8);
  outline: none;
}
.img-cycle-dot--active {
  background: #ffffff;
  transform: scale(1.3);
}

/* Search-field clear button. Wraps each <input data-clearable> with a
   positioning span and overlays a × button on the right. Hidden when the
   field is empty — an empty search field renders identical to before. The
   wrapper sizes to its content by default so it doesn't disturb existing
   toolbar layouts; specific contexts (e.g. .compendium-toolbar) re-apply
   the original input's flex sizing to the wrapper below. */
.search-clear-host {
  position: relative;
  display: inline-flex;
  align-items: stretch;
}
.search-clear-host > input {
  flex: 1;
  min-width: 0;
  /* Reserve room on the right for the × button so typed text doesn't run
     under it. The number matches .search-clear-btn's right offset + width. */
  padding-right: 2rem;
}
/* Codex toolbar: input had `flex: 1; min-width: 180px` — now that the input
   is wrapped, redirect that flex sizing to the wrapper so the toolbar layout
   matches the pre-wrap behaviour. */
.compendium-toolbar .search-clear-host { flex: 1; min-width: 180px; }
/* Fates entity picker: input was width:100% in a vertical flex column —
   the inline-flex host would otherwise collapse to content width and
   break the picker's full-width search field. */
.fates-weave-entity-picker .search-clear-host { display: flex; width: 100%; }
.search-clear-btn {
  position: absolute;
  right: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.4rem;
  height: 1.4rem;
  display: none;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: 50%;
  color: var(--text-3);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  transition: background var(--t-hover) ease, color var(--t-hover) ease;
}
.search-clear-btn--visible { display: inline-flex; }
.search-clear-btn:hover,
.search-clear-btn:focus-visible {
  background: color-mix(in srgb, var(--text-1) 10%, transparent);
  color: var(--text);
  outline: none;
}

/* ── Setup wizard ────────────────────────────────────────────────────────── */
.setup-step-indicator {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.75rem;
}

.drop-zone {
  border: 2px dashed var(--border);
  border-radius: var(--radius-xl);
  padding: 2rem 1rem;
  text-align: center;
  cursor: pointer;
  color: var(--text-3);
  font-size: var(--font-size-base);
  transition: border-color var(--t-hover), background var(--t-hover);
  margin-bottom: 0.75rem;
}
.drop-zone:hover, .drop-zone:focus-visible, .drop-zone.drag-over {
  border-color: var(--accent-teal);
  background: var(--bg-3);
}

.map-preview {
  width: 100%;
  max-height: 180px;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  margin-bottom: 0.75rem;
}

.status-msg {
  font-size: var(--font-size-base);
  color: var(--text-2);
  margin-bottom: 0.5rem;
}
/* Tertiary helper line that sits *under* a form field — smaller + dimmer
   than .status-msg, slight margin-top so it tucks beneath the input.
   Replaces inline style="margin-top:3px;color:var(--text-3);font-size:0.78rem". */
.form-helper-text {
  margin-top: 3px;
  margin-bottom: 0;
  color: var(--text-3);
  font-size: var(--font-size-xs);
}
/* Inline-with-label variant — sits next to the label text inside the same
   <label> element, normal weight + tertiary colour. Replaces inline
   style="color:var(--text-3);font-weight:400" hints used after labels. */
.form-helper-inline {
  color: var(--text-3);
  font-weight: 400;
}

.setup-actions {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  margin-top: 0.75rem;
}

/* Location checklist */
.location-checklist {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin-bottom: 1rem;
}
.location-check-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
  font-size: var(--font-size-base);
}
.location-check-row:last-child { border-bottom: none; }
.location-check-row:hover, .location-check-row:focus-visible { background: var(--bg-3); }
.loc-name { flex: 1; color: var(--text); }
.loc-coords { color: var(--text-3); font-size: var(--font-size-sm); font-variant-numeric: tabular-nums; }

/* Loading spinner */
.loading-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--text-2);
  font-size: var(--font-size-base);
  padding: 1rem 0;
}
/* Centered variant — replaces inline style="justify-content:center" on the
   roster loading spinner. */
.loading-row--center {
  justify-content: center;
}
.spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid var(--border);
  border-top-color: var(--accent-teal);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── DB status ───────────────────────────────────────────────────────────── */
#db-status {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  font-size: var(--font-size-sm);
  padding: 4px 10px;
  border-radius: var(--radius-full);
  background: var(--bg-3);
  color: var(--text-2);
  border: 1px solid var(--border);
  opacity: 0;
  transition: opacity var(--t-trans);
}
#db-status.visible { opacity: 1; }
#db-status.ok { color: var(--accent-green); }
#db-status.err { color: var(--accent-red); }

/* ── Journey tab layout ──────────────────────────────────────────────────── */
.journey-layout {
  display: flex;
  align-items: flex-start; /* columns don't stretch to match each other */
  min-height: 100%;
}
.journey-sessions-col {
  flex: 0 0 42%;
  min-width: 320px;
  border-right: none;
  position: relative;
}
.journey-sessions-col::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 1px;
  background: linear-gradient(to bottom, transparent 0%, color-mix(in srgb, var(--border) 50%, transparent) 15%, var(--border) 50%, color-mix(in srgb, var(--border) 50%, transparent) 85%, transparent 100%);
}
.journey-summary-col {
  flex: 1;
  min-width: 0;
  padding: var(--space-5) var(--space-6);
  position: sticky;
  top: 0;
  max-height: 100vh;
  overflow-y: auto;
  box-sizing: border-box;
}
.journey-summary-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: var(--space-5) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-height: calc(100vh - 3.5rem);
}
.journey-summary-hdr {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.6rem;
}
.journey-summary-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.02em;
  /* Take the available space between the leading badge icon and the
     trailing meta-actions cluster so meta-actions stay right-justified. */
  flex: 1;
  min-width: 0;
}
.journey-summary-body {
  flex: 1;
  overflow-y: auto;
  font-size: var(--font-size-base);
  line-height: 1.7;
  color: var(--text-2);
}
.chronicle-entry { padding: var(--space-3) 0; border-bottom: 1px solid var(--border); }
.chronicle-entry:last-child { border-bottom: none; }
/* Typography is now inherited from .form-section-label (composed via the
   markup); .chronicle-entry-hdr just provides the bullet-list spacing. */
.chronicle-entry-hdr { margin: 0 0 0.35rem; }
.chronicle-bullets { margin: 0; padding-left: 1.1rem; list-style: disc; }
.chronicle-bullets li { margin-bottom: 0.2rem; line-height: 1.5; }
.chronicle-entry--zero { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); margin-bottom: var(--space-2); }
.chronicle-entry--zero .chronicle-bullets { list-style: none; padding-left: 0; }
.chronicle-entry--zero .chronicle-bullets li { font-style: italic; color: var(--text-2); line-height: 1.7; margin-bottom: 0; }
.chronicle-entry--zero .chronicle-entry-hdr { color: var(--accent-teal); }

/* GM-only pencil to inline-edit the session_blurb that feeds this chronicle
   entry. Same visual recipe as .session-title-edit-btn so the affordance
   reads identically across surfaces. */
.chronicle-tldr-edit-btn {
  background: none;
  border: none;
  color: var(--text-3);
  cursor: pointer;
  padding: 0 var(--space-1);
  margin-left: var(--space-2);
  font-size: var(--font-size-sm);
  line-height: 1;
  border-radius: var(--radius-sm);
  opacity: 0.45;
  transition: opacity var(--t-hover) ease, color var(--t-hover) ease;
}
.chronicle-entry:hover .chronicle-tldr-edit-btn,
.chronicle-tldr-edit-btn:hover,
.chronicle-tldr-edit-btn:focus-visible {
  opacity: 1;
  color: var(--accent-teal);
}
.chronicle-tldr-edit-box {
  width: 100%;
  font: inherit;
  color: var(--text-1);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  resize: vertical;
}
.chronicle-tldr-edit-box:focus {
  outline: none;
  border-color: var(--accent-teal);
}
.journey-summary-body strong, .journey-summary-body b {
  color: var(--text);
  font-weight: 600;
}
.journey-summary-empty {
  color: var(--text-3);
  font-style: italic;
}
.chronicle-backstory {
  font-style: italic;
  color: var(--text-2);
  border-left: 3px solid var(--accent-teal);
  padding: 0.4rem 0.75rem;
  margin-bottom: 1rem;
  font-size: var(--font-size-md);
}
@media (max-width: 900px) {
  /* On mobile we collapse to a single vertical flow: session list on top,
     chronicle on bottom, ONE scroll context (the page). The desktop layout
     created three nested scroll regions (col, card, body) which felt jarring
     on phones — scrolling inside one didn't move the others. */
  .journey-layout { flex-direction: column; }
  .journey-sessions-col {
    flex: none;
    border-right: none;
    max-height: none;
  }
  /* Divider rotates from a vertical line between columns to a horizontal
     gradient under the session list, matching other section dividers. */
  .journey-sessions-col::after {
    top: auto; right: 0; bottom: 0; left: 0;
    width: auto; height: 1px;
    background: linear-gradient(to right, transparent 0%, color-mix(in srgb, var(--border) 50%, transparent) 15%, var(--border) 50%, color-mix(in srgb, var(--border) 50%, transparent) 85%, transparent 100%);
  }
  .journey-summary-col {
    padding: var(--space-4);
    position: static;       /* override desktop sticky */
    max-height: none;       /* let content flow */
    overflow-y: visible;
    margin-top: var(--space-4);
  }
  .journey-summary-card {
    max-height: none;       /* override desktop max-height */
  }
  .journey-summary-body {
    overflow-y: visible;    /* parent (page) handles scroll */
    max-height: none;
  }
}

/* ── Sessions tab ────────────────────────────────────────────────────────── */
/* "SESSIONS" eyebrow on the left, "+ New Session" CTA on the right —
   matches the campaign-page section header rhythm. The horizontal divider
   was removed so the toolbar reads as a section title rather than a
   chrome strip. Vertical padding tuned so the gap from label → first
   session card mirrors the Chronicle column's eyebrow → first-entry
   rhythm (≈0.5rem, matching .form-section-label's inline margin-bottom). */
/* v787: canonical eyebrow padding for ALL primary tabs is
   var(--space-5) top, var(--space-4) horizontal. Chronicle is the
   reference; Codex / Party / Atlas / Quests / Manage all align here. */
.sessions-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-4) var(--space-2);
  flex-shrink: 0;
}

.session-list {
  padding: 0 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.session-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  /* Shadowless at rest — mirrors .campaign-card. Hover/focus block below
     adds the lift + teal-tinted border + drop shadow in one motion. */
}

/* Header is the click target for expand/collapse. Position relative so the
   session-num and session-date can corner-anchor (mirrors .campaign-card-role
   / .campaign-live-badge), leaving the title to claim the full main row. */
.session-card-hdr {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1.85rem 1rem 0.85rem 1rem;
  cursor: pointer;
  user-select: none;
  position: relative;
}
.session-card-hdr:hover, .session-card-hdr:focus-visible { background: var(--bg-3); }

/* Top-left corner badge — the canonical "where am I in the campaign"
   marker, parallel to .campaign-card-role's role chip. The previous
   `var(--accent-teal)22` was invalid CSS (you can't suffix-concatenate
   alpha hex onto a CSS variable); switched to color-mix so the chip
   actually renders the intended ~13%-teal fill. */
.session-num {
  position: absolute;
  top: 0.6rem;
  left: 0.7rem;
  z-index: 1;
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 13%, transparent);
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.session-title {
  font-weight: 600;
  color: var(--text-1);
}
/* v783: wraps title + pencil + input so the inline-edit affordance shares
   the title's flex slot. flex:1 moved here from .session-title so the
   wrap (and the input inside it on rename) takes the same space. */
.session-title-wrap {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex: 1;
  min-width: 0;
}
/* Pencil affordance — subtle at rest, brightens on card hover or focus.
   stopPropagation in the click handler keeps it from toggling the card. */
.session-title-edit-btn {
  background: none;
  border: none;
  color: var(--text-3);
  cursor: pointer;
  padding: 0 var(--space-1);
  font-size: var(--font-size-sm);
  line-height: 1;
  border-radius: var(--radius-sm);
  opacity: 0.45;
  transition: opacity var(--t-hover) ease, color var(--t-hover) ease;
}
.session-card:hover .session-title-edit-btn,
.session-title-edit-btn:hover,
.session-title-edit-btn:focus-visible {
  opacity: 1;
  color: var(--accent-teal);
}
/* Inline rename input — inherits typography from .session-title so the
   swap is visually seamless. Only the box treatment + minimal padding
   are local. */
.session-title-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-weight: 600;
  color: var(--text-1);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
}
.session-title-input:focus {
  outline: none;
  border-color: var(--accent-teal);
}

/* Top-right corner — plain meta text (not chipped) so it reads as a
   secondary timestamp rather than competing with the session-num chip. */
.session-date {
  position: absolute;
  top: 0.65rem;
  right: 0.7rem;
  z-index: 1;
  font-size: var(--font-size-xs);
  color: var(--text-3);
  white-space: nowrap;
}

.session-chevron {
  color: var(--text-3);
  font-size: var(--font-size-sm);
  margin-left: 0.25rem;
}

.session-body {
  border-top: 1px solid var(--border);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.session-summary {
  color: var(--text-2);
  font-size: var(--font-size-md);
  line-height: 1.65;
  white-space: pre-wrap;
}
/* Inline recap edit — pencil button overlays the top-right of the wrapper,
   textarea swap mirrors the session-title rename pattern. */
.session-summary-wrap { position: relative; }
.session-summary-edit-btn {
  position: absolute;
  top: 0;
  right: 0;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-2);
  border-radius: var(--radius-md);
  padding: 0.15rem 0.5rem;
  cursor: pointer;
  font-size: var(--font-size-sm);
  line-height: 1.2;
  transition: color var(--t-hover) ease, border-color var(--t-hover) ease;
}
.session-summary-edit-btn:hover,
.session-summary-edit-btn:focus-visible {
  color: var(--text);
  border-color: rgba(47,154,163,0.45);
}
.session-summary-textarea {
  width: 100%;
  min-height: 20rem;
  font-family: inherit;
  font-size: var(--font-size-md);
  line-height: 1.55;
  background: var(--bg-1);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
  resize: vertical;
}
.session-summary-edit-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.session-beat-header {
  font-size: var(--font-size-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent-teal);
  margin: 1rem 0 0;
  white-space: normal;
}

.session-no-summary {
  color: var(--text-3);
  font-style: italic;
  font-size: var(--font-size-base);
}

.session-transcript-toggle {
  font-size: var(--font-size-sm);
  color: var(--accent-teal);
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  text-align: left;
}
.session-transcript-toggle:hover, .session-transcript-toggle:focus-visible { text-decoration: underline; }

.session-transcript-box {
  width: 100%;
  min-height: 120px;
  max-height: 260px;
  resize: vertical;
  font-size: var(--font-size-sm);
  font-family: monospace;
  background: var(--bg-3);
  color: var(--text-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.6rem;
  box-sizing: border-box;
}

.session-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}
/* Inline AI status text next to "Generate Recap" — small + tertiary. */
.recap-status {
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
/* Edit pushes itself + Delete to the right edge of the action bar; the
   recap-button + status sit at the left. Replaces inline margin-left:auto. */
.session-actions .edit-session-btn { margin-left: auto; }
/* (v784) Bespoke .delete-session-btn red-tint rules retired — destructive
   colouring now flows through .btn-new--danger composed in markup. */

#toast {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%) translateY(1rem);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.6rem 1.1rem;
  font-size: var(--font-size-base);
  color: var(--text);
  box-shadow: var(--shadow);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-trans), transform var(--t-trans);
  z-index: 10001;
}
#toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
.toast-copy-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-2);
  cursor: pointer;
  font-size: var(--font-size-xs);
  line-height: 1;
  margin-left: 0.6rem;
  padding: 0.15rem 0.4rem;
  vertical-align: middle;
  transition: background var(--t-hover) var(--ease), color var(--t-hover) var(--ease);
}
.toast-copy-btn:hover, .toast-copy-btn:focus-visible { background: var(--bg-3); color: var(--text); }
.entity-new-badge {
  display: inline-flex; align-items: center;
  font-size: var(--font-size-xs); font-weight: 700; letter-spacing: 0.03em;
  background: color-mix(in srgb, var(--color-success) 15%, transparent);
  color: var(--color-success);
  border: 1px solid color-mix(in srgb, var(--color-success) 35%, transparent);
  border-radius: var(--radius-full);
  padding: 0.1rem 0.45rem;
  margin-left: 0.4rem;
}
.entity-updated-badge {
  display: inline-flex; align-items: center;
  font-size: var(--font-size-xs); font-weight: 700; letter-spacing: 0.03em;
  background: color-mix(in srgb, var(--color-warning) 15%, transparent);
  color: var(--color-warning);
  border: 1px solid color-mix(in srgb, var(--color-warning) 35%, transparent);
  border-radius: var(--radius-full);
  padding: 0.1rem 0.45rem;
  margin-left: 0.4rem;
}

.entity-link {
  text-decoration: underline dotted;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.entity-link[data-type="pc"]       { color: var(--accent-blue); }
.entity-link[data-type="location"] { color: var(--accent-teal); }
.entity-link[data-type="npc"]      { color: var(--accent-amber); }
.entity-link[data-type="item"]     { color: var(--accent-red); }
.entity-link[data-type="faction"]  { color: var(--accent-purple); }
.entity-link[data-type="deity"]    { color: var(--accent-gold); }
.entity-link:hover, .entity-link:focus-visible { text-decoration: underline; }
.entity-link:focus { outline: 2px solid currentColor; outline-offset: 1px; }

.session-recap-result {
  /* var(--accent-teal)11 / 44 was invalid CSS (you can't suffix-concat alpha
     hex onto a CSS variable). Resolved to undefined → bg/border weren't
     rendering. color-mix gives the intended ~7%/27% teal tint. */
  background: color-mix(in srgb, var(--accent-teal) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 27%, transparent);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.85rem;
  font-size: var(--font-size-base);
  color: var(--accent-teal);
}

.session-section {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.session-section-toggle {
  background: none;
  border: none;
  color: var(--text-2);
  font-size: var(--font-size-base);
  padding: 0.25rem 0;
  cursor: pointer;
  text-align: left;
  width: fit-content;
}
.session-section-toggle:hover, .session-section-toggle:focus-visible { color: var(--text); }

.section-empty { color: var(--text-3); }

.session-section-box {
  width: 100%;
  min-height: 120px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.6rem;
  font-family: monospace;
  font-size: var(--font-size-base);
  color: var(--text);
  resize: vertical;
}
.session-section-pre {
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-teal);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.75rem;
  font-family: monospace;
  font-size: var(--font-size-sm);
  color: var(--text-2);
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
  max-height: 280px;
  overflow-y: auto;
}

/* Live transcript variant — greyed out, larger max-height for timestamped lines */
.session-live-transcript {
  color: var(--text-3);
  border-left-color: var(--accent-purple);
  max-height: 400px;
}

/* ── Speaker re-labeling bar ──────────────────────────────────────────────── */
.spk-bar {
  display: flex; flex-direction: column; align-items: stretch; gap: 0.45rem;
  background: var(--bg-2); border: 1px solid var(--border);
  border-bottom: none; border-radius: var(--radius-md) 6px 0 0;
  padding: 0.55rem 0.75rem;
  font-size: var(--font-size-sm);
}
.spk-bar-hdr {
  font-weight: 600; color: var(--text-2); white-space: nowrap; flex-shrink: 0;
  margin-bottom: 0.15rem;
}
.spk-row {
  display: flex; flex-direction: column; gap: 0.2rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bg) 40%, transparent);
}
.spk-row-top {
  display: flex; align-items: center; gap: 0.35rem;
}
.spk-row-sample {
  display: flex; align-items: flex-start; gap: 0.35rem;
  padding-left: 0.15rem;
  font-size: var(--font-size-sm);
  color: var(--text-2);
  font-style: italic;
  line-height: 1.45;
}
.spk-quote-text { flex: 1; min-width: 0; }
.spk-quote-more {
  margin-left: 0.35rem;
  font-size: 0.65rem; font-weight: 600; font-style: normal;
  color: var(--color-purple);
  background: color-mix(in srgb, var(--color-purple) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-purple) 28%, transparent);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.35rem;
  white-space: nowrap;
}
.spk-quote-dismiss {
  background: none; border: none;
  color: var(--text-3); cursor: pointer;
  font-size: var(--font-size-base); line-height: 1;
  padding: 0 0.3rem;
  opacity: 0.65;
  transition: opacity var(--t-hover), color var(--t-hover);
  flex-shrink: 0;
}
.spk-quote-dismiss:hover, .spk-quote-dismiss:focus-visible { opacity: 1; color: var(--color-danger); }
.spk-token-chip {
  font-family: monospace; font-size: var(--font-size-sm); font-weight: 700;
  color: var(--accent-purple);
  background: color-mix(in srgb, var(--accent-purple) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-purple) 30%, transparent);
  border-radius: var(--radius-sm); padding: 1px 5px;
  white-space: nowrap; max-width: 80px; overflow: hidden; text-overflow: ellipsis;
}
.spk-arrow { color: var(--text-3); font-size: var(--font-size-sm); }
.spk-avatar-preview {
  width: 24px; height: 24px; border-radius: 50%; overflow: hidden; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.spk-avatar-preview img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.spk-select {
  font-size: var(--font-size-sm); padding: 2px 4px;
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text); cursor: pointer;
  max-width: 150px;
}
/* Round off the transcript pre when speaker bar is above it */
.spk-bar + .session-live-transcript {
  border-radius: 0 0 6px 6px;
  border-top: none;
}

/* ── Live Session Entries section ─────────────────────────────────────────── */
.le-panel {
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-teal);
  border-radius: var(--radius-md);
  background: var(--bg-2);
  overflow: hidden;
}
.le-list { display: flex; flex-direction: column; }
.le-row {
  display: grid;
  grid-template-columns: 4.5rem 1.4rem 1fr auto;
  gap: 0.4rem;
  align-items: baseline;
  padding: 0.35rem 0.65rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--font-size-base);
}
.le-row:last-child { border-bottom: none; }
.le-ts {
  font-family: monospace;
  font-size: var(--font-size-sm);
  color: var(--text-3);
  white-space: nowrap;
}
.le-tag { font-size: var(--font-size-md); text-align: center; }
.le-content { color: var(--text-1); word-break: break-word; line-height: 1.45; }
.le-key .le-content { font-weight: 600; }
.le-count { font-size: var(--font-size-sm); color: var(--text-3); font-weight: 400; }
/* Auto-generated live beats — tint the tag column for visual grouping */
.le-story   .le-tag { color: var(--color-brand); }
.le-combat  .le-tag { color: var(--color-danger); }
.le-context .le-tag { color: var(--color-muted-violet, #7C6FA0); }
/* Row actions — always visible so the affordance is obvious on touch + desktop */
.le-actions {
  display: inline-flex;
  gap: 0.15rem;
  align-self: start;
  opacity: 0.55;
  transition: opacity var(--t-hover) ease;
}
.le-row:hover .le-actions,
.le-row:focus-within .le-actions { opacity: 1; }
.le-actions .btn-xs { padding: 0 0.35rem; line-height: 1.3; }
.le-del-btn { color: var(--color-danger); }
.le-edit-box {
  width: 100%;
  min-height: 3rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  line-height: 1.45;
  padding: 0.35rem 0.5rem;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--color-brand);
  border-radius: var(--radius-sm);
  resize: vertical;
}
.le-empty {
  padding: 0.6rem 0.75rem;
  font-size: var(--font-size-sm);
  color: var(--text-3);
  margin: 0;
}

.session-zero .session-card-hdr { border-left: 3px solid var(--color-text-secondary); }

.session-zero-badge {
  color: var(--color-text-secondary);
}

.session-entries-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.entry-item {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.6rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.entry-item.entry-new {
  border-style: dashed;
}

.entry-pc-row { margin-bottom: 0.3rem; }
.entry-pc-row select {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: var(--font-size-base);
  padding: 0.25rem 0.4rem;
  cursor: pointer;
}

.entry-box, .entry-new-box {
  width: 100%;
  min-height: 80px;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 0.3rem 0.2rem;
  font-family: monospace;
  font-size: var(--font-size-base);
  color: var(--text);
  resize: vertical;
}
.entry-box:focus, .entry-new-box:focus { outline: none; border-bottom-color: var(--accent-teal); }

.entry-item-actions {
  display: flex;
  gap: 0.4rem;
  justify-content: flex-end;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 500;
  cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

/* ── Chat floating widget ────────────────────────────────────────────────── */
#chat-widget {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.75rem;
}
#chat-widget.hidden { display: none; }

/* Floating action button — glass-translucent treatment that mirrors the
   admin-pulse panel (bottom-left mirror of this bottom-right button). The
   solid full-saturation teal background was visually loud; a teal-stroked
   icon on a frosted-surface background reads as "AI assistant available"
   without dominating the corner. Size + position unchanged. */
#chat-fab {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-surface) 18%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--accent-teal);
  border: 1px solid color-mix(in srgb, var(--color-border) 30%, transparent);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 20px rgba(0,0,0,0.10);
  transition:
    background       var(--t-hover),
    border-color     var(--t-hover),
    backdrop-filter  var(--t-hover),
    box-shadow       var(--t-hover),
    transform        var(--t-hover),
    color            var(--t-hover);
  flex-shrink: 0;
}
#chat-fab:hover, #chat-fab:focus-visible {
  background: color-mix(in srgb, var(--color-surface) 75%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-color: color-mix(in srgb, var(--color-border) 60%, transparent);
  box-shadow: 0 4px 20px rgba(0,0,0,0.18);
  transform: translateY(-1px);
}
/* Open state — chat panel is visible; tone the icon down so it doesn't
   compete with the popout's content. */
#chat-fab.chat-fab-open { color: var(--text-3); }

/* Popout panel */
.chat-popout {
  width: 340px;
  height: 480px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xl);
  box-shadow: 0 8px 32px rgba(0,0,0,0.22);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: chat-pop-in 0.18s ease;
}
.chat-popout.hidden { display: none; }
@keyframes chat-pop-in {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* v822: teal backdrop + bold title retired in favor of the canonical
   eyebrow header recipe — uppercase / 700 / --font-size-xs / --text-3 /
   0.06em — so the chat popout reads as a panel header consistent with
   modal-hdr eyebrows app-wide. */
.chat-popout-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 0.85rem;
  background: transparent;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.chat-popout-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: inherit;
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-3);
}
.chat-popout-hdr-actions { display: flex; gap: 0.25rem; }
.chat-hdr-btn {
  background: transparent;
  border: none;
  color: var(--text-3);
  cursor: pointer;
  padding: 0.2rem 0.4rem;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-base);
  line-height: 1;
  transition: background var(--t-hover), color var(--t-hover);
}
.chat-hdr-btn:hover, .chat-hdr-btn:focus-visible { background: var(--bg-3); color: var(--text-1); }

.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
/* v965: .chat-empty inherits canonical .placeholder typography (sm,
   italic, dim, left-aligned). Only padding remains — chat empty needs
   a little vertical breathing room inside its column. */
.chat-empty {
  padding: var(--space-5) var(--space-4);
  line-height: 1.6;
}
.chat-msg { display: flex; }
.chat-msg-user      { justify-content: flex-end; }
.chat-msg-assistant { justify-content: flex-start; }

.chat-bubble {
  max-width: 82%;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-2xl);
  font-size: var(--font-size-base);
  line-height: 1.5;
  word-break: break-word;
}
/* v823: user bubble retired the filled-teal recipe in favor of a teal
   outline + default text color — reads as a quoted message rather than a
   loud chat-app bubble, and matches the rest of the app's ghost idiom. */
.chat-msg-user .chat-bubble {
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--accent-teal) 50%, transparent);
  color: var(--text-1);
  border-bottom-right-radius: 3px;
}
.chat-msg-assistant .chat-bubble {
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-bottom-left-radius: 3px;
}

/* Typing indicator */
.chat-typing {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 0.6rem 0.75rem;
}
.chat-typing span {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-3);
  animation: chat-bounce 1.2s infinite;
}
.chat-typing span:nth-child(2) { animation-delay: var(--t-trans); }
.chat-typing span:nth-child(3) { animation-delay: var(--t-trans); }
@keyframes chat-bounce {
  0%, 80%, 100% { transform: translateY(0); }
  40% { transform: translateY(-5px); }
}

.chat-input-bar {
  display: flex;
  gap: 0.4rem;
  padding: 0.6rem 0.7rem;
  border-top: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
}
.chat-input-bar textarea {
  flex: 1;
  resize: none;
  border-radius: var(--radius-lg);
  padding: 0.45rem 0.6rem;
  font-size: var(--font-size-base);
  line-height: 1.4;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
}
.chat-input-bar textarea:focus { outline: none; border-color: var(--accent-teal); }
/* v822: drop the font-size + padding override so the Send button inherits the
   canonical .btn-new pill recipe (0.5rem 1rem, --font-size-sm) — matches every
   other primary CTA across the app. Layout-only properties remain. */
.chat-input-bar .btn-new { align-self: flex-end; white-space: nowrap; }

/* v827: tools-used audit footer inside each assistant bubble. A dim,
   monospaced line under a dashed separator showing which tools the
   Bardic Companion called to compose the answer — lets the user verify
   the model actually consulted the records and gives a spot-check
   surface when an answer feels off. */
.chat-tools-used {
  display: block;
  margin-top: 0.5rem;
  padding-top: 0.4rem;
  border-top: 1px dashed color-mix(in srgb, var(--text-3) 35%, transparent);
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: 0.68rem;
  line-height: 1.4;
  color: var(--text-3);
  opacity: 0.75;
  word-break: break-all;
}
.chat-tools-used-icon {
  margin-right: 0.25rem;
  opacity: 0.85;
}

/* ── Quest Log — two-panel layout ───────────────────────────────────────── */
#tab-quests.tab-panel {
  padding: 0;
  overflow-y: auto;
}

/* ── Manage tab (combined GM panel: settings + roster + feed) ──────────────
   Same split-scroll grammar as Chronicle (.journey-layout) and Quests
   (.quest-layout): wrapper scrolls, left col flows, right col sticks
   so the feed stays visible while the GM works through settings. v777. */
#tab-manage.tab-panel {
  padding: 0;
  overflow-y: auto;
}
.manage-layout {
  display: flex;
  align-items: flex-start;
  min-height: 100%;
}
.manage-nav-col {
  flex: 0 0 56%;
  min-width: 360px;
  position: relative;
  /* v787: canonical eyebrow top-left spacing — matches every other tab. */
  padding: var(--space-5) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  box-sizing: border-box;
}
.manage-nav-col::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 1px;
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--border) 50%, transparent) 15%,
    var(--border) 50%,
    color-mix(in srgb, var(--border) 50%, transparent) 85%,
    transparent 100%);
}
.manage-feed-col {
  flex: 1;
  min-width: 0;
  position: sticky;
  top: 0;
  max-height: 100vh;
  overflow: hidden;
  box-sizing: border-box;
}
.manage-feed-col .feed-layout { height: 100%; }
/* Each section in the left col — campaign settings, roster, etc. Stack
   gap is provided by .manage-nav-col's `gap`, not bottom-margins. */
.manage-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Section header — eyebrow LEFT, optional CTA RIGHT. Mirrors the canonical
   toolbar rhythm without re-rolling typography (.form-section-label is
   composed in the markup). */
.manage-section-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
/* Form rows inside the Manage tab — gap-driven layout instead of the
   per-row margin-bottom the modal used. Replaces inline
   style="margin-top:1.25rem;margin-bottom:0" on the cover-image row. */
.manage-form-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.manage-form-row > label { font-size: var(--font-size-sm); font-weight: 500; color: var(--text-2); }
/* Inline rows for invite-code controls (input + Copy + Save) and cover
   controls (Choose / Remove / status). Replace inline
   style="display:flex;gap:0.5rem;align-items:center" plus the wrap variant. */
.camp-invite-row,
.camp-cover-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}
.camp-invite-inp {
  margin-bottom: 0;
  flex: 1;
  min-width: 200px;
}
/* Tighter danger zone in the manage layout — the modal needed bigger
   margins because it was a tall modal; here the section gap already
   provides breathing room. */
.manage-danger-zone {
  margin-top: var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
}
/* Success-tinted helper line — green text, used by the "Saved!" message
   after invite-code save. Replaces inline
   style="font-size:0.8rem;color:var(--color-success);margin-top:0.3rem". */
.form-helper-text--success {
  margin-top: 3px;
  margin-bottom: 0;
  color: var(--color-success);
  font-size: var(--font-size-sm);
}


.quest-layout {
  display: flex;
  align-items: flex-start;
  min-height: 100%;
}

/* ── Left nav column — mirrors .journey-sessions-col (free-floating, no box) */
.quest-nav-col {
  flex: 0 0 42%;
  min-width: 320px;
  position: relative;
}

/* Subtle fade divider between columns (same as session panel) */
.quest-nav-col::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 1px;
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--border) 50%, transparent) 15%,
    var(--border) 50%,
    color-mix(in srgb, var(--border) 50%, transparent) 85%,
    transparent 100%);
}

/* Eyebrow LEFT (with inline hide-completed toggle), "+ New Quest" CTA
   RIGHT. The horizontal divider was removed in v761 — canonical pattern
   is no border-bottom under section headers. */
.quest-nav-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  padding: var(--space-5) var(--space-4) var(--space-2);
  flex-shrink: 0;
  gap: var(--space-2);
}
.quest-nav-toolbar-left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* (Retired in v764) .quest-hide-label / #quest-hide-completed went away
   when the explicit Hide-Completed toggle was removed. Completed and
   failed quests now stay in the nav permanently, visually greyed via
   .quest-nav-row.is-completed (defined alongside .quest-nav-row below). */

.quest-nav-list {
  padding: 0 var(--space-4) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* v968: .quest-nav-empty retired — Quests empty state now composes the
   canonical .placeholder.placeholder--inline like every other empty
   placeholder app-wide. */

/* (Retired in v762) The .quest-group-hdr / -chevron / -label / -sublabel /
   -count block re-implemented the .form-section-label eyebrow recipe and
   was never referenced by the JS — renderQuestNav() emits a flat sorted
   list. Two related dark-mode rules at lines ~6052 and ~6215 were also
   removed. The Leads count chip retained as .quest-leads-count below. */

/* ── Quest nav rows — share the canonical card recipe with .session-card,
   .campaign-card, .entity-row, .party-pc-card, .quotes-pc-section.
   Shadowless at rest, hover lifts with translateY + teal-tinted border
   + drop shadow. Selected state keeps the teal border tint without
   extra background fill (the hover lift already carries the affordance). */
.quest-nav-row {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  transition:
    border-color var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
.quest-nav-row:hover, .quest-nav-row:focus-within {
  border-color: rgba(47,154,163,0.45);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.quest-nav-row.selected {
  border-color: rgba(47,154,163,0.65);
  background-color: color-mix(in srgb, var(--color-brand) 7%, var(--bg-2));
}
/* Completed / failed quests stay in the list permanently but read as
   "settled / past tense" — opacity dimmed and slightly desaturated so
   they don't compete with active quests for attention. Hover/focus
   restores full opacity so they remain easy to inspect on demand. */
.quest-nav-row.is-completed {
  opacity: 0.55;
  filter: grayscale(35%);
}
.quest-nav-row.is-completed:hover,
.quest-nav-row.is-completed:focus-within,
.quest-nav-row.is-completed.selected {
  opacity: 1;
  filter: none;
}

.quest-nav-hdr {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.85rem 1rem;
  cursor: pointer;
  user-select: none;
}
.quest-nav-hdr:hover, .quest-nav-hdr:focus-visible { background: var(--bg-3); }
.quest-nav-row.selected .quest-nav-hdr { background: transparent; }

/* Status badge: same font as .session-num — no pill, just colored text */
.quest-nav-badge {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--qbadge-color, var(--accent-teal));
  white-space: nowrap;
  flex-shrink: 0;
}
/* Icon variant — single-character status indicator (□ Active, □ purple
   Background, ✓ Complete, ✗ Failed). Sized up so the glyph reads at a
   glance next to the title text; .quest-nav-row.is-completed handles
   the dimming/desaturation. */
.quest-nav-badge--icon {
  font-size: var(--font-size-md);
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.1em;
}
/* Purple PC-name suffix for Background quests — sits inline after the
   quest title in the nav row; same accent-purple as the badge square. */
.quest-nav-pc {
  color: var(--accent-purple);
  font-size: var(--font-size-sm);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 1;
  min-width: 0;
}
/* (Retired in v772) .quest-pc-link was the inline detail-header crosslink
   to the PC. The detail header is now title-only; PC linkage shows as a
   purple suffix in the nav row via .quest-nav-pc above. */

/* Quest title: identical to .session-title */
.quest-nav-title {
  font-weight: 600;
  flex: 1;
  color: var(--text-1);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Right detail column — sticky like .journey-summary-col ── */
.quest-detail-col {
  flex: 1;
  min-width: 0;
  padding: var(--space-5) var(--space-6);
  position: sticky;
  top: 0;
  max-height: 100vh;
  overflow-y: auto;
  box-sizing: border-box;
}

.quest-detail-panel {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}

/* v976: .quest-detail-placeholder retired. Detail-pane placeholders now
   compose the canonical .placeholder.placeholder--inline directly so they
   sit at the same x as every other in-tab hint and don't carry a bespoke
   vertical padding override. */

/* Quest detail header — meta info + actions cluster on the right side
   of .journey-summary-hdr (badge + session label + Edit + Delete). The
   parallel .quest-detail-inner / -hdr / -title / -meta / -badge / -divider
   block (which never made it into the JS markup — renderQuestDetail uses
   .journey-summary-card directly) was dead code; retired in v761. */
.quest-detail-meta-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-left: auto;
}
.quest-detail-session {
  font-size: var(--font-size-sm);
  color: var(--text-3);
}

/* Objectives section */
.quest-detail-objectives { margin-bottom: 0; }

/* Typography is now inherited from .form-section-label (composed via the
   markup); .quest-obj-header just contributes the bottom spacing before
   the objective/lead row list. */
.quest-obj-header { margin-bottom: 0.6rem; }

.quest-obj-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.quest-obj-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.quest-obj-diamond {
  color: var(--accent-teal);
  font-size: var(--font-size-xs);
  margin-top: 0.32rem;
  flex-shrink: 0;
  opacity: 0.75;
}

.quest-obj-text {
  font-size: var(--font-size-md);
  color: var(--text-2);
  line-height: 1.65;
}

.quest-obj-empty {
  font-size: var(--font-size-base);
  color: var(--text-3);
  font-style: italic;
}

/* ── Investigation leads (next-step threads on a quest) ──────────────────── */
/* Dashed border-top retired in v763 — only dashed divider in the app, and
   the section-label rhythm ("Leads N open") + extra margin-top already
   reads as a separate sub-section. */
.quest-detail-leads {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
/* Lead count chip + lead toggles inherit colour from the parent quest's
   identity via --lead-color (set inline on .quest-detail-leads):
   green for active/party-shared, purple for background/PC-personal.
   Visual hierarchy: green = "in progress / actionable",
   purple = "background / personal", muted/red = "settled / closed." */
.quest-leads-count {
  margin-left: 0.4rem;
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--lead-color, var(--color-success));
  background: color-mix(in srgb, var(--lead-color, var(--color-success)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--lead-color, var(--color-success)) 28%, transparent);
  border-radius: var(--radius-full);
  padding: 0.08rem 0.45rem;
  letter-spacing: 0.03em;
  text-transform: none;
}
.quest-lead-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.35rem 0.1rem;
  border-radius: var(--radius-md);
  transition: background var(--t-hover);
}
.quest-lead-row:hover, .quest-lead-row:focus-visible { background: color-mix(in srgb, var(--color-border) 25%, transparent); }
.quest-lead-toggle {
  background: none;
  border: none;
  color: var(--lead-color, var(--color-success));
  font-size: var(--font-size-lg);
  line-height: 1;
  cursor: pointer;
  padding: 0.1rem 0.15rem;
  flex-shrink: 0;
}
.quest-lead-toggle:hover, .quest-lead-toggle:focus-visible { color: var(--color-brand); }
.quest-lead-text {
  flex: 1;
  font-size: var(--font-size-md);
  color: var(--text-2);
  line-height: 1.55;
}
.quest-lead-resolved .quest-lead-text {
  color: var(--text-3);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--text-3) 60%, transparent);
}
.quest-lead-edit,
.quest-lead-delete {
  background: none;
  border: none;
  color: var(--text-3);
  cursor: pointer;
  padding: 0.1rem 0.3rem;
  font-size: var(--font-size-sm);
  opacity: 0;
  transition: opacity var(--t-hover), color var(--t-hover);
}
.quest-lead-row:hover .quest-lead-edit, .quest-lead-row:focus-within .quest-lead-edit,
.quest-lead-row:hover .quest-lead-delete, .quest-lead-row:focus-within .quest-lead-delete { opacity: 1; }
.quest-lead-edit:hover, .quest-lead-edit:focus-visible { color: var(--color-brand); }
.quest-lead-delete:hover, .quest-lead-delete:focus-visible { color: var(--color-danger); }
.quest-leads-resolved-wrap {
  margin-top: 0.5rem;
  font-size: var(--font-size-sm);
}
/* Typography inherits from .form-section-label (composed in markup);
   summary only adds the cursor + click padding. */
.quest-leads-resolved-wrap summary {
  cursor: pointer;
  padding: 0.25rem 0;
}
.quest-lead-edit-form {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 1;
}
.quest-lead-edit-form .quest-lead-input { flex: 1; }
.quest-lead-add-row {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* bg / border / radius / colour / focus inherit from the global
   input[type="text"] rule (line ~342). Local overrides only adjust
   size for the inline lead-add affordance. */
.quest-lead-add-row .quest-lead-input {
  flex: 1;
  padding: 0.4rem 0.6rem;
  font-size: var(--font-size-sm);
}
.quest-lead-add-btn {
  font-size: var(--font-size-xs);
  padding: 0.35rem 0.75rem;
  white-space: nowrap;
}

/* Detail action row — match session-actions. The previous margin-top:auto
   pushed actions to the bottom of the panel, orphaning them from the
   leads section on tall viewports. Now they sit naturally below the leads
   block with the standard top-padded divider. */
.quest-detail-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  margin-top: var(--space-3);
}

/* (Retired in v763) .quest-pc-tag was orphan CSS — Tier 2 routed PC
   linkage on background quests through .quest-nav-badge + .quest-pc-link
   crosslinks instead. The dark-mode override at line ~7719 was also
   removed. */

/* Responsive */
@media (max-width: 700px) {
  .quest-layout { flex-direction: column; }
  .quest-nav-col { flex: none; min-width: 0; width: 100%; }
  .quest-nav-col::after { top: auto; right: 0; bottom: 0; left: 0; width: auto; height: 1px; background: linear-gradient(to right, transparent 0%, color-mix(in srgb, var(--border) 50%, transparent) 15%, var(--border) 50%, color-mix(in srgb, var(--border) 50%, transparent) 85%, transparent 100%); }
  .quest-detail-col { position: static; max-height: none; padding: 1rem; }
  .quest-detail-inner { padding: 1rem 1.25rem; }
}

/* ── Alias chips ─────────────────────────────────────────────────────────── */
.alias-input-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.alias-input-row input { flex: 1; }

.alias-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.4rem;
}

.alias-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: var(--accent-teal);
  color: #fff;
  font-size: var(--font-size-sm);
  padding: 0.2rem 0.5rem;
  border-radius: var(--radius-full);
}
.alias-chip button {
  background: none;
  border: none;
  color: rgba(255,255,255,0.8);
  cursor: pointer;
  font-size: var(--font-size-base);
  line-height: 1;
  padding: 0;
}
.alias-chip button:hover, .alias-chip button:focus-visible { color: #fff; }

/* Read-only alias chips in compendium card */
.alias-chip-ro {
  display: inline-flex;
  align-items: center;
  background: var(--bg-3);
  color: var(--text-2);
  font-size: var(--font-size-xs);
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
}
/* Read-only chip-row spacing — separates the alias row from the description
   below. Replaces inline style="margin-bottom:0.5rem". */
.alias-chips--ro { margin-bottom: var(--space-2); }

/* ── D&D Beyond PC header ────────────────────────────────────────────────── */
.ddb-url-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.ddb-url-row input { flex: 1; }

.ddb-header {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  background: var(--bg-2);
  border: 1px solid var(--accent-blue);
  border-radius: var(--radius-md);
  margin-bottom: 0.75rem;
  font-size: var(--font-size-base);
  overflow: hidden;
}
.ddb-portrait {
  flex-shrink: 0;
  overflow: hidden;
  /* width and height are set by JS to header.offsetHeight for a true 1:1 square */
}
.ddb-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--portrait-x, 50%) center;
  display: block;
}
.ddb-content {
  flex: 1;
  padding: 0.75rem 1rem;
  min-width: 0;
}
.ddb-header-empty {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
}
.ddb-header-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 0.15rem;
}
.ddb-title {
  font-weight: 600;
  /* Secondary text colour — was --accent-blue, which made "Level X Species"
     compete with the PC name. Now reads as supporting metadata. */
  color: var(--text-2);
  font-size: var(--font-size-md);
}
.ddb-background {
  margin-left: 0.5rem;
  font-size: var(--font-size-sm);
  color: var(--text-3);
  font-style: italic;
}
.ddb-class-line {
  color: var(--text-2);
  font-size: var(--font-size-base);
  margin-bottom: 0.5rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.pc-role-badge {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: 600;
  padding: 1px 7px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--accent-teal) 15%, transparent);
  border: 1px solid var(--accent-teal);
  color: var(--accent-teal);
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.pc-noncombat-badge {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: 600;
  padding: 1px 7px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--accent-purple) 15%, transparent);
  border: 1px solid var(--accent-purple);
  color: var(--accent-purple);
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.pc-ai-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--accent-gold) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-gold) 50%, transparent);
  color: var(--accent-gold);
  letter-spacing: 0.03em;
  white-space: nowrap;
  opacity: 0.85;
}
.pc-ai-banner {
  width: 100%;
  padding: 0.3rem 0.6rem;
  margin: 0.25rem 0 0.1rem;
  background: color-mix(in srgb, var(--accent-amber) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-amber) 45%, transparent);
  border-radius: var(--radius-md);
  color: var(--accent-amber);
  font-size: var(--font-size-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-align: center;
}
.pc-player-name {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  margin-left: 0.35rem;
  font-style: italic;
}
.pc-pronouns {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 0 0.45rem;
  margin-left: 0.35rem;
  font-style: normal;
  vertical-align: middle;
}

/* Combat quick-reference row */
.ddb-combat-row {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  flex-wrap: wrap;
}
.ddb-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3.2rem;
  background: var(--bg-3);
  border-radius: var(--radius-sm);
  padding: 0.2rem 0.5rem;
  font-weight: 600;
  font-size: var(--font-size-md);
}
.ddb-stat-label {
  font-size: 0.62rem;
  font-weight: 400;
  text-transform: uppercase;
  color: var(--text-3);
  letter-spacing: 0.04em;
  margin-bottom: 1px;
  white-space: nowrap;
}

/* Ability scores grid */
.ddb-ability-grid {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0.5rem;
  flex-wrap: wrap;
}
.ddb-ab-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--bg-3);
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  padding: 0.25rem 0.4rem;
  min-width: 2.8rem;
  flex: 1;
  transition: background var(--t-hover), border-color var(--t-hover);
}
.ddb-ab-label {
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  color: var(--text-3);
  letter-spacing: 0.04em;
}
.ddb-ab-mod {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--text);
  line-height: 1.2;
}
.ddb-ab-score {
  font-size: var(--font-size-xs);
  color: var(--text-3);
}

/* Best or tied for best — dark green. Token-driven palette mirrors the
   .pleg-swatch--best / --near legend swatches so the highlights and the
   legend stay locked together. */
.ddb-ab-best, .ddb-ab-tied-best {
  background: color-mix(in srgb, var(--accent-green) 18%, transparent);
  border-color: var(--accent-green);
}
.ddb-ab-best .ddb-ab-mod,      .ddb-ab-tied-best .ddb-ab-mod  { color: var(--accent-green); }
.ddb-ab-best .ddb-ab-label,    .ddb-ab-tied-best .ddb-ab-label { color: color-mix(in srgb, var(--accent-green) 80%, white); }

/* Within 1 of best — yellow-green. */
.ddb-ab-near-best {
  background: color-mix(in srgb, var(--accent-green) 30%, var(--accent-amber) 12%);
  border-color: color-mix(in srgb, var(--accent-green) 60%, var(--accent-amber));
}
.ddb-ab-near-best .ddb-ab-mod  { color: color-mix(in srgb, var(--accent-green) 50%, var(--accent-amber)); }
.ddb-ab-near-best .ddb-ab-label { color: color-mix(in srgb, var(--accent-green) 60%, var(--accent-amber)); }

.ddb-row {
  color: var(--text-2);
  font-size: var(--font-size-sm);
  margin-top: 0.18rem;
  line-height: 1.4;
}
.ddb-row-label {
  font-weight: 600;
  color: var(--text);
  margin-right: 0.35rem;
}
.ddb-strength-label { color: var(--accent-green); }
.ddb-gap-label      { color: var(--accent-red); }

/* ── Party Assessment Box ────────────────────────────────────────────────── */
.party-box {
  background: var(--bg-2);
  border: 1px solid var(--border);
  /* radius bumped from --lg to --xl in v748 to match every other card
     type in the app (.campaign-card / .session-card / .entity-row /
     .party-pc-card / .quotes-pc-section). */
  border-radius: var(--radius-xl);
  padding: 1rem 1.1rem;
  /* v932: dropped the var(--space-4) margin-top that previously
     simulated grid-gap. The party-section-label above it already
     supplies var(--space-2) margin-bottom; stacking another 16px
     on top gave Party Assessment a 24px gap below its eyebrow vs
     the ~8px every other tab uses (Quests / Codex / Sessions).
     Total now matches the rest of the app. */
  margin-bottom: 1rem;
  font-size: var(--font-size-base);
}
/* Empty-state variant — solid border (matches the rest of the app's
   "empty card" treatment); subtler text. The previous dashed-border was
   the only place in the app using a dashed-card empty state. */
.party-box-empty {
  color: var(--text-2);
}
.party-box-loading {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.party-box-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}
.party-box-title {
  font-weight: 700;
  font-size: var(--font-size-md);
  color: var(--text);
}
.party-box-spinner {
  color: var(--text-3);
  font-style: italic;
  font-size: var(--font-size-base);
  animation: pulse 1.4s ease-in-out infinite;
}

.pa-summary {
  color: var(--text-2);
  margin-bottom: 0.75rem;
  line-height: 1.5;
  font-size: var(--font-size-base);
}
.pa-tldr {
  color: var(--text);
  margin-bottom: 0.6rem;
  line-height: 1.5;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-medium);
  border-left: 3px solid var(--color-brand);
  padding: 0.2rem 0 0.2rem 0.7rem;
}

/* Three-stage visibility: 1=TLDR · 2=Summary · 3=Full */
.party-box[data-stage="1"] .pa-summary,
.party-box[data-stage="1"] .pa-grid,
.party-box[data-stage="1"] .pa-tip { display: none; }

.party-box[data-stage="2"] .pa-tldr,
.party-box[data-stage="2"] .pa-detail { display: none; }

.party-box[data-stage="3"] .pa-tldr,
.party-box[data-stage="3"] .pa-chips { display: none; }
.pa-date {
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
.pa-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
  margin-bottom: 0.7rem;
}
@media (max-width: 700px) { .pa-grid { grid-template-columns: 1fr; } }

.pa-section {
  background: var(--bg-3);
  border-radius: var(--radius-md);
  padding: 0.55rem 0.7rem;
  border-left: 3px solid transparent;
}
.pa-strengths  { border-color: var(--accent-green); }
.pa-gaps       { border-color: var(--accent-red); }
.pa-synergies  { border-color: var(--accent-blue); }
.pa-watchouts  { border-color: var(--accent-amber); }

.pa-section-label {
  font-weight: 700;
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.4rem;
}
.pa-strengths  .pa-section-label { color: var(--accent-green); }
.pa-gaps       .pa-section-label { color: var(--accent-red); }
.pa-synergies  .pa-section-label { color: var(--accent-blue); }
.pa-watchouts  .pa-section-label { color: var(--accent-amber); }

.pa-count {
  font-size: var(--font-size-xs);
  font-weight: 400;
  color: var(--text-3);
  margin-left: 2px;
}

/* Collapsed chip row */
.pa-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 0.25rem;
}
.pa-chip {
  font-size: var(--font-size-xs);
  padding: 2px 7px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  white-space: nowrap;
  color: var(--text-2);
  background: var(--bg);
}
.pa-chip-strengths { border-color: color-mix(in srgb, var(--accent-green) 40%, transparent); color: var(--accent-green); }
.pa-chip-gaps      { border-color: color-mix(in srgb, var(--accent-red)   40%, transparent); color: var(--accent-red); }
.pa-chip-synergies { border-color: color-mix(in srgb, var(--accent-blue)  40%, transparent); color: var(--accent-blue); }
.pa-chip-watchouts { border-color: color-mix(in srgb, var(--accent-amber) 40%, transparent); color: var(--accent-amber); }

/* Full detail list */
.pa-list {
  margin: 0.3rem 0 0 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  color: var(--text-2);
  line-height: 1.45;
}

/* Stage-nav row (Less · Label · More) */
.pa-expand-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.6rem;
}
.pa-stage-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  min-width: 80px;
  text-align: center;
}
.pa-stage-btn[disabled] {
  opacity: 0.35;
  cursor: not-allowed;
}

.pa-tip {
  background: linear-gradient(90deg, #ca8a0415, transparent);
  border-left: 3px solid var(--accent-gold);
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.7rem;
  color: var(--text);
  line-height: 1.5;
}

/* ── Live Session Layout ────────────────────────────────────────────────────── */
.screen { position: fixed; inset: 0; z-index: 200; display: flex; flex-direction: column; }
.screen.hidden { display: none; }

.live-header {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.4rem 0.75rem;
  background: var(--bg);
  border-bottom: 1px solid color-mix(in srgb, var(--color-gold) 30%, var(--color-border));
  flex-shrink: 0;
}
/* (v795) .live-header-back and .live-title retired — both were CSS-only
   orphans (no markup references). The Live header uses .nav-back-btn
   instead, and the live-session-controls dropdown replaced any title
   element this class targeted. */

/* Session switcher in live header */
.live-session-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
}
.live-session-select {
  flex: 1;
  min-width: 0;
  max-width: 360px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-family: inherit;
  font-size: var(--font-size-md);
  font-weight: 600;
  padding: 0.3rem 0.6rem;
  cursor: pointer;
  transition: border-color var(--t-hover) var(--ease);
}
.live-session-select:focus {
  outline: none;
  border-color: var(--accent-teal);
  box-shadow: 0 0 0 3px rgba(47,124,133,0.12);
}
.live-new-session-btn {
  white-space: nowrap;
  font-size: var(--font-size-base);
  padding: 0.3rem 0.75rem;
}


.live-body {
  display: flex; flex: 1; overflow: hidden;
}

/* Left column: map on top, quests below */
.live-left {
  flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0;
}

.live-map-panel {
  display: flex; flex-direction: column;
  flex: 1; min-height: 0; background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.live-map-panel.collapsed { flex: none; }
.live-map-panel-hdr {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.3rem 0.6rem;
  border-bottom: 1px solid var(--border);
  background: var(--bg); flex-shrink: 0;
}
/* v795: typography moved to .form-section-label composed in markup;
   this rule keeps only the layout (flex grow). */
.live-map-panel-title { flex: 1; }
.live-map-panel.collapsed .live-map-collapse-btn { transform: rotate(-90deg); }
.live-map-body {
  flex: 1; position: relative; min-height: 0;
}
.live-map-panel.collapsed .live-map-body { display: none; }
#live-map-container { width: 100%; height: 100%; }
#live-map-hint {
  position: absolute; top: 0.75rem; left: 50%; transform: translateX(-50%);
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--accent-teal);
  font-size: var(--font-size-sm); font-weight: 500;
  padding: 0.3rem 0.9rem; border-radius: var(--radius-full);
  pointer-events: none; z-index: 500; white-space: nowrap;
  box-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
/* Layout-only — typography flows through .placeholder + .placeholder--padded
   composed in markup. v780 retired the bespoke font-size/colour/italic recipe. */
.live-map-empty {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; min-height: 80px;
}

/* ── Atlas map switcher ───────────────────────────────────────────────────── */
.atlas-map-switcher {
  display: flex; align-items: center; gap: var(--space-1); flex-shrink: 0;
}
.atlas-map-nav-btn {
  font-size: var(--font-size-xs); padding: 2px 7px; color: var(--text-2);
}
.atlas-map-nav-btn:hover, .atlas-map-nav-btn:focus-visible { color: var(--accent-teal); }
.atlas-map-title {
  font-size: var(--font-size-sm); font-weight: 600;
  color: var(--text); white-space: nowrap;
  max-width: 160px; overflow: hidden; text-overflow: ellipsis;
  padding: 0 var(--space-1);
}

/* Live map nav buttons in live-map-panel-hdr */
.live-map-nav-btn {
  font-size: 0.65rem; padding: 1px 5px; flex-shrink: 0;
  color: var(--text-2);
}
.live-map-nav-btn:hover, .live-map-nav-btn:focus-visible { color: var(--accent-teal); }
#live-map-title-label {
  flex: 1; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Live map manager button + dropdown */
.live-map-manage-btn {
  font-size: var(--font-size-xs); padding: 1px 5px; flex-shrink: 0;
  color: var(--text-3);
}
.live-map-manage-btn:hover, .live-map-manage-btn:focus-visible { color: var(--accent-teal); }
.live-map-manager-dropdown {
  position: absolute; top: 100%; right: 0; z-index: 200;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  padding: 0.35rem 0; min-width: 180px; max-width: 240px;
}
.live-map-mgr-item {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.35rem 0.75rem; cursor: pointer;
  font-size: var(--font-size-sm); color: var(--text);
}
.live-map-mgr-item:hover, .live-map-mgr-item:focus-visible { background: var(--bg-3); }
.live-map-mgr-item input[type="checkbox"] { flex-shrink: 0; accent-color: var(--accent-teal); }
.live-map-mgr-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* (v780) .live-map-mgr-empty retired — replaced by .placeholder.placeholder--padded
   composed in markup in renderLiveMapManagerDropdown. */

/* ── Map management modal ─────────────────────────────────────────────────── */
/* Sizing now flows through .modal-box--lg (width/max-width). This class
   only carries the column layout + max-height the manager needs to keep
   the long map list scrollable. v773 retired the literal width: 560px. */
.map-manage-box {
  max-height: 80vh;
  display: flex; flex-direction: column;
}
.map-manage-body {
  flex: 1; overflow-y: auto; padding: var(--space-4) var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-4);
}
.map-manage-upload-section {
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--space-4);
}
.map-manage-upload-row {
  display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap;
}
.map-manage-title-input {
  flex: 1; min-width: 160px;
  padding: 0.45rem 0.65rem; border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font-size: var(--font-size-base);
  font-family: inherit;
}
.map-manage-title-input:focus { outline: none; border-color: var(--accent-teal); }
.map-manage-upload-btn { cursor: pointer; font-size: var(--font-size-sm); }
.map-manage-status {
  margin-top: var(--space-2); font-size: var(--font-size-sm);
  color: var(--text-2);
}
.map-manage-list {
  display: flex; flex-direction: column; gap: var(--space-3);
}
/* Map row card — composes the canonical card hover (matches .entity-row,
   .session-card, .party-pc-card, .quest-nav-row) so all repeated-row
   surfaces lift identically on hover. */
.map-manage-item {
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4);
  display: flex; gap: var(--space-3); align-items: flex-start;
  transition: border-color var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-hover) ease;
}
.map-manage-item:hover, .map-manage-item:focus-visible {
  border-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}
.map-manage-item-thumb {
  width: 64px; height: 48px; flex-shrink: 0;
  border-radius: var(--radius-sm); object-fit: cover;
  border: 1px solid var(--border);
}
.map-manage-item-info {
  flex: 1; min-width: 0;
}
.map-manage-item-title-row {
  display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2);
}
.map-manage-item-title-inp {
  flex: 1; font-size: var(--font-size-sm); font-weight: 600;
  padding: 0.2rem 0.45rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font-family: inherit;
}
.map-manage-item-title-inp:focus { outline: none; border-color: var(--accent-teal); }
.map-manage-vis-row {
  display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap;
}
.map-manage-vis-label {
  display: flex; align-items: center; gap: var(--space-1);
  font-size: var(--font-size-xs); font-weight: 600; color: var(--text-2);
  cursor: pointer; user-select: none;
}
.map-manage-vis-label input { accent-color: var(--accent-teal); }
.map-manage-item-actions {
  display: flex; flex-direction: column; gap: var(--space-2); flex-shrink: 0;
}
/* Inline "Save" button in the title-edit row — composes .btn-ghost; this
   class only resizes the button to fit alongside the title input. Replaces
   inline style="font-size:0.72rem;padding:2px 8px". */
.map-manage-save-btn {
  font-size: var(--font-size-xs);
  padding: 2px 8px;
}
/* Delete button — composes .btn-ghost.btn-ghost--danger; this class only
   resizes for the actions column. v773 retired the bespoke colour recipe. */
.map-manage-delete-btn {
  font-size: var(--font-size-xs);
  padding: 2px 8px;
}

.live-sidebar {
  width: 340px; flex-shrink: 0;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--border);
  background: var(--bg);
  overflow: hidden;
}
/* v911 GM Notebook commit 3: chat textarea + log relocated INTO the
   sidebar. Without explicit flex shares, transcript-panel (flex:1)
   and live-log (flex:1) would each demand all remaining space and
   the layout would collapse one or the other to nothing. Engineering
   audit specifically flagged this as the chat-relocation gotcha.

   Transcript gets ~60% of remaining space (it's the focus during
   live), log gets ~30% (enough to scroll the last few entries).
   Both have min-height: 0 so they actually shrink within the
   sidebar's overflow:hidden context. Entry form is intrinsic-sized
   via its existing flex-shrink: 0. */
.live-sidebar > .live-transcript-panel { flex: 1 1 60%; min-height: 0; }
.live-sidebar > .live-log { flex: 1 1 30%; min-height: 0; }

.live-entry-form {
  padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
/* Textarea wrapper with GIF icon overlay */
.live-text-wrap {
  position: relative; margin-bottom: 0.4rem;
}
.live-text-wrap #live-entry-text {
  margin-bottom: 0; padding-right: 2rem;
}
.live-gif-icon {
  position: absolute; top: 6px; right: 6px;
  background: none; border: none; cursor: pointer;
  font-size: var(--font-size-lg); line-height: 1; opacity: 0.55;
  transition: opacity var(--t-hover);
  padding: 0; z-index: 1;
}
.live-gif-icon:hover, .live-gif-icon:focus-visible { opacity: 1; }

/* Key event checkbox row */

#live-entry-text {
  width: 100%; min-height: 48px; resize: vertical;
  padding: 0.5rem; border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text);
  font-size: var(--font-size-base); font-family: inherit; margin-bottom: 0.3rem;
}
#live-entry-text:focus { outline: none; border-color: var(--accent-teal); }

#live-set-pos-btn {
  padding: 0.4rem 0.7rem; background: none;
  border: 1.5px solid var(--border); border-radius: 0;
  font-size: var(--font-size-base); cursor: pointer; color: var(--text-2);
  transition: border-color var(--t-hover), background var(--t-hover);
}
#live-set-pos-btn.active { border-color: var(--accent-teal); background: var(--accent-teal); color: #fff; }
#live-set-pos-btn:hover:not(.active), #live-set-pos-btn:focus-visible:not(.active) { background: var(--bg-3); }

/* ── Session Preamble panel ──────────────────────────────────────────────── */
.live-preamble {
  flex-shrink: 0;
  margin: 0 0.5rem 0.5rem;
  border: 1px solid var(--border);
  border-top: 2px solid var(--split-color);
  border-radius: var(--radius-lg);
  background-color: color-mix(in srgb, var(--color-gold) 5%, var(--bg-2));
  overflow: hidden;
  max-height: 280px;
  display: flex;
  flex-direction: column;
}
.live-preamble-hdr {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.28rem 0.6rem;
  background: color-mix(in srgb, var(--color-gold) 10%, var(--bg-3));
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.live-preamble-title {
  /* Composes .form-section-label for typography (uppercase, weight 700,
     letter-spacing) — only override the color so the recap eyebrow
     reads gold instead of the canonical tertiary-text. v920: dropped
     the ::before ◆ glyph because the 📍 emoji in the text now serves
     as the marker. */
  color: var(--color-gold);
  flex: 1;
}
.live-preamble-session-label {
  font-size: 0.66rem; color: var(--text-3); font-style: italic; white-space: nowrap;
}
.live-preamble-body {
  overflow-y: auto; padding: 0.45rem 0.6rem;
  flex: 1; display: flex; flex-direction: column; gap: 0.5rem;
}
.live-preamble-section-hdr {
  font-size: 0.66rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-3); margin-bottom: 0.28rem;
}
/* "What Comes Next" section header — ember tint to signal forward pressure */
.live-preamble-section-hdr:last-of-type,
.live-preamble-next .live-preamble-section-hdr {
  color: var(--ember-text);
  border-left: 2px solid var(--color-ember);
  padding-left: var(--space-2);
}
.live-preamble-bullets {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.22rem;
}
.live-preamble-bullet {
  font-size: var(--font-size-sm); color: var(--text-2); line-height: 1.45;
  padding-left: 0.85rem; position: relative;
}
.live-preamble-bullet::before {
  content: '◆'; position: absolute; left: 0;
  color: var(--split-color); font-size: 0.5rem; top: 0.25em;
}
.live-preamble-next {
  display: flex; flex-direction: column; gap: 0.2rem;
}
.live-preamble-next-line {
  font-size: var(--font-size-sm); color: color-mix(in srgb, var(--color-ember) 55%, var(--color-text-secondary)); line-height: 1.45; margin: 0;
}
.live-preamble-next-coda {
  color: var(--text-3); font-style: italic;
}

/* Live transcript preview panel — flex:1 so it splits 50/50 with the log */
.live-transcript-panel {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-1);
  margin: 0 0.5rem 0.5rem;
  overflow: hidden;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.live-transcript-box {
  flex: 1; overflow-y: auto;
  padding: 0.4rem 0.6rem;
  min-height: 60px;
  font-size: var(--font-size-sm); line-height: 1.55;
  color: var(--text-2);
  word-break: break-word;
}
.live-transcript-panel.hidden { display: none; }
/* ── Unified collapse/expand arrow button — used on all Live section headers ── */
.live-collapse-btn {
  background: none; border: none; cursor: pointer;
  color: var(--text-3); font-size: var(--font-size-sm); line-height: 1;
  padding: 2px 5px; flex-shrink: 0;
  transition: transform var(--t-hover) var(--ease), color var(--t-hover) var(--ease);
  font-family: inherit;
}
.live-collapse-btn:hover, .live-collapse-btn:focus-visible { color: var(--text); }
/* ✕ dismiss buttons in live panels (preamble close, etc.) keep their own style */
.live-panel-toggle {
  background: none; border: none; cursor: pointer;
  color: var(--text-3); font-size: var(--font-size-md); line-height: 1;
  padding: 0 0.15rem; flex-shrink: 0;
  transition: color var(--t-hover);
}
.live-panel-toggle:hover, .live-panel-toggle:focus-visible { color: var(--text-1); }
/* Transcript body wrapper — fills remaining height, enables scroll chain */
.live-transcript-body { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
.live-transcript-panel.transcript-collapsed .live-transcript-body { display: none; }
/* Shrink panel to header-only when collapsed — frees space for entry form + log */
.live-transcript-panel.transcript-collapsed { flex: none; }

/* Transcript / Speakers tab bar */
.live-trans-tabs { display: flex; gap: 2px; flex: 1; align-items: center; }
.live-trans-tab {
  background: none; border: none; cursor: pointer;
  font-size: var(--font-size-xs); font-weight: 600; padding: 0.18rem 0.55rem;
  border-radius: 0; color: var(--text-3);
  transition: color var(--t-hover), background var(--t-hover);
}
.live-trans-tab:hover, .live-trans-tab:focus-visible { background: var(--bg-3); color: var(--text-1); }
.live-trans-tab.active {
  background: color-mix(in srgb, var(--accent-teal) 12%, transparent);
  color: var(--accent-teal);
}
.live-transcript-hdr {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.3rem 0.6rem;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  font-size: var(--font-size-xs);
}
.live-transcript-label {
  display: flex; align-items: center; gap: 0.35rem;
  font-weight: 600; color: var(--accent-red);
}
.live-transcript-label .rec-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent-red);
  animation: rec-blink 0.9s ease-in-out infinite;
  flex-shrink: 0;
}
.live-transcript-wc { color: var(--text-3); font-size: var(--font-size-xs); }
/* .live-transcript-box defined above with max-height */
.transcript-interim { color: var(--text-3); font-style: italic; }
/* Live speaker indicator badge in transcript header */
.live-spk-indicator {
  font-size: var(--font-size-xs); font-weight: 700;
  padding: 0.1rem 0.45rem; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent-teal) 20%, transparent);
  color: var(--accent-teal);
  animation: spk-pulse 1.2s ease-in-out infinite;
}
@keyframes spk-pulse { 0%,100%{opacity:1} 50%{opacity:0.55} }


/* Speaker labeling panel — now a full tab, not an embedded strip */
.live-spk-bar-wrap {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  overflow: hidden; background: var(--bg);
  padding: 0.5rem 0.6rem;
}
/* Body rows — fills available height, scrollable */
.live-spk-body {
  flex: 1; overflow-y: auto;
  scroll-behavior: smooth;
  padding-bottom: 0.3rem;
  display: flex; flex-direction: column; gap: 0.4rem;
}
/* (v780) .live-spk-empty retired — replaced by .placeholder.placeholder--padded
   composed in markup in _refreshLiveSpkBar. */
.live-spk-bar-wrap .spk-bar { border: none; padding: 0; background: none; }

/* Grouped speaker layout */
.live-spk-grouped {
  display: flex; flex-direction: column; gap: 0.25rem;
}
/* v795: typography moved to .form-section-label composed in markup. */
.live-spk-grouped .spk-bar-hdr { margin-bottom: var(--space-1); }
.live-spk-group {
  display: flex; align-items: center; gap: 0.45rem;
}
.live-spk-group-name {
  font-size: var(--font-size-sm); font-weight: 600; color: var(--text);
  min-width: 4rem;
}
.live-spk-group-name--unlabeled {
  color: var(--text-3); font-style: italic; font-weight: 400;
}
.live-spk-chip-row { display: flex; flex-wrap: wrap; gap: 3px; }
.live-spk-chip {
  font-size: var(--font-size-xs); font-weight: 700;
  padding: 0.1rem 0.35rem; border-radius: var(--radius-sm); cursor: pointer;
  background: color-mix(in srgb, var(--accent-teal) 18%, transparent);
  color: var(--accent-teal);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 35%, transparent);
  transition: background var(--t-hover);
}
.live-spk-chip:hover, .live-spk-chip:focus-visible { background: color-mix(in srgb, var(--accent-teal) 32%, transparent); }
.live-spk-chip--unlabeled {
  background: color-mix(in srgb, var(--text-3) 12%, transparent);
  color: var(--text-3);
  border-color: color-mix(in srgb, var(--text-3) 25%, transparent);
}
.live-spk-quote-row {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.15rem 0;
}
.live-spk-quote-text {
  flex: 1; min-width: 0;
  font-size: var(--font-size-sm); color: var(--text-2);
  font-style: italic;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.4;
}
.live-spk-quote-more {
  display: inline-block;
  margin-left: 0.3rem;
  font-size: 0.6rem; font-weight: 600; font-style: normal;
  color: var(--color-purple);
  background: color-mix(in srgb, var(--color-purple) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-purple) 28%, transparent);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.3rem;
  vertical-align: middle;
}
.live-spk-dismiss-btn {
  flex-shrink: 0;
  background: none; border: none; padding: 0 0.2rem;
  font-size: 0.65rem; color: var(--text-3); cursor: pointer;
  line-height: 1; opacity: 0.6; transition: opacity var(--t-hover), color var(--t-hover);
}
.live-spk-dismiss-btn:hover, .live-spk-dismiss-btn:focus-visible { opacity: 1; color: var(--accent-red); }
.live-spk-inline-select {
  font-size: var(--font-size-sm); padding: 0.1rem 0.2rem;
  border-radius: var(--radius-sm); border: 1px solid var(--accent-teal);
  background: var(--bg-2); color: var(--text);
  max-width: 120px;
}

.transcript-turn {
  display: flex; gap: 0.5rem; align-items: baseline;
  padding: 0.25rem 0; border-bottom: 1px solid var(--border);
}
.transcript-turn--active { opacity: 0.85; }
.transcript-speaker {
  flex-shrink: 0;
  min-width: 1.9rem;                  /* fits 2-char labels like "Be", "GM" */
  max-width: 3.75rem;                 /* fits 4-char extended labels like "Grub" */
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: var(--font-size-xs); font-weight: 700; letter-spacing: 0.02em;
  color: var(--accent-teal); background: color-mix(in srgb, var(--accent-teal) 15%, transparent);
  border-radius: var(--radius-sm); padding: 0.1rem 0.35rem; line-height: 1.4;
  cursor: default;
}
/* When the chip is a button (committed segments), make it interactive. */
button.transcript-speaker--clickable {
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
  transition: background var(--t-hover) ease, border-color var(--t-hover) ease;
}
button.transcript-speaker--clickable:hover,
button.transcript-speaker--clickable:focus-visible {
  background: color-mix(in srgb, var(--accent-teal) 28%, transparent);
  border-color: rgba(47,154,163,0.45);
  outline: none;
}
/* Manually-overridden chip — subtle ember tint so the GM can see at a glance
   which segments they corrected vs. which are still on AAI's original cluster. */
.transcript-speaker--override {
  color: var(--color-ember, #d4753a);
  background: color-mix(in srgb, var(--color-ember, #d4753a) 18%, transparent);
}
button.transcript-speaker--override:hover,
button.transcript-speaker--override:focus-visible {
  background: color-mix(in srgb, var(--color-ember, #d4753a) 30%, transparent);
}
/* Reassign popover anchored beside a clicked speaker chip. */
.spk-reassign-pop {
  position: absolute;
  z-index: 10000;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
  padding: 0.6rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 16rem;
  font-size: var(--font-size-sm);
}
.spk-reassign-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.spk-reassign-row label {
  color: var(--text-2);
  font-weight: var(--font-weight-semibold);
}
.spk-reassign-select {
  flex: 1;
  background: var(--bg-1);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.25rem 0.4rem;
  font: inherit;
}
.spk-reassign-checkbox {
  font-size: var(--font-size-xs);
  color: var(--text-2);
  font-weight: var(--font-weight-regular);
  align-items: flex-start;
  gap: 0.4rem;
}
.spk-reassign-checkbox input { margin-top: 2px; }
.spk-reassign-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.25rem;
}
.spk-reassign-actions .btn-new { padding: 0.25rem 0.75rem; font-size: var(--font-size-sm); }
.transcript-text { flex: 1; font-size: var(--font-size-base); line-height: 1.55; color: var(--text-1); }
.transcript-empty { color: var(--text-3); font-style: italic; font-size: var(--font-size-base); }
.transcript-divider {
  text-align: center; font-size: var(--font-size-xs); color: var(--text-3);
  padding: 0.4rem 0; letter-spacing: 0.05em;
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
  margin: 0.25rem 0;
}

.live-log {
  flex: 1; overflow-y: auto; padding: 0.5rem 0.6rem;
}
@keyframes live-log-ping {
  0%   { box-shadow: inset 3px 0 0 0 var(--accent-teal), inset 0 0 18px 0 color-mix(in srgb, var(--color-brand) 22%, transparent); }
  60%  { box-shadow: inset 3px 0 0 0 color-mix(in srgb, var(--accent-teal) 40%, transparent), inset 0 0 8px 0 color-mix(in srgb, var(--color-brand) 10%, transparent); }
  100% { box-shadow: none; }
}
.live-log--ping { animation: live-log-ping 0.7s ease-out forwards; }
.live-log-entry {
  /* Left-border colour stays load-bearing for at-a-glance tag categorisation
     (premium signal — preserved per project rule). Other rest/hover state
     follows the canonical card-hover recipe. */
  border-left: 3px solid var(--split-color);
  border: 1px solid transparent;
  border-left-width: 3px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin-bottom: var(--space-1);
  padding: var(--space-1) var(--space-2);
  background: none;
  transition: border-color var(--t-hover) ease,
              box-shadow   var(--t-hover) ease,
              transform    var(--t-hover) ease,
              background   var(--t-hover) ease;
}
.live-log-entry[data-tag="note"]      { border-left-color: var(--text-3); }
.live-log-entry[data-tag="key_event"] { border-left-color: var(--accent-amber); }
.live-log-entry[data-tag="travel"]    { border-left-color: var(--accent-teal); }
/* Canonical card hover — translateY + tinted border + drop shadow.
   Left-border-color (data-tag) stays untouched; only the other three
   border edges and the shadow respond. */
.live-log-entry:hover, .live-log-entry:focus-visible {
  background: var(--bg-2);
  border-top-color: rgba(47,154,163,0.45);
  border-right-color: rgba(47,154,163,0.45);
  border-bottom-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}
.live-log-entry-hdr {
  display: flex; align-items: center; gap: var(--space-1); margin-bottom: 2px;
}
.live-log-time {
  font-family: monospace; font-size: var(--font-size-xs); color: var(--text-3);
}
/* Per-instance tint via --tag-color CSS var (set inline by renderLiveLog).
   Replaces inline style="color:${cfg.color}" — matches the
   --entity-color / --pin-color / --lead-color injection pattern. */
.live-log-tag {
  font-size: var(--font-size-xs); font-weight: 600; flex: 1;
  color: var(--tag-color, var(--text-2));
}
.live-log-delete {
  background: none; border: none; cursor: pointer;
  color: var(--text-3); font-size: var(--font-size-lg); line-height: 1; padding: 0 0.2rem;
  transition: color var(--t-hover);
}
.live-log-delete:hover, .live-log-delete:focus-visible { color: var(--accent-red); }
.live-log-content {
  font-size: var(--font-size-base); color: var(--text); line-height: 1.4; white-space: pre-wrap;
}
/* GIF picker */
.live-gif-picker {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.5rem;
  margin-bottom: 0.5rem;
}
.live-gif-search {
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.35rem 0.5rem;
  font-size: var(--font-size-base);
  color: var(--text);
  outline: none;
  box-sizing: border-box;
}
.live-gif-search:focus { border-color: var(--accent-teal); }
.live-gif-results {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 4px;
  max-height: 180px;
  overflow-y: auto;
  margin-top: 0.4rem;
}
.live-gif-thumb {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: opacity var(--t-hover), transform var(--t-hover);
}
.live-gif-thumb:hover, .live-gif-thumb:focus-visible { opacity: 0.85; transform: scale(1.03); }
.live-gif-attribution {
  font-size: 0.65rem;
  color: var(--text-3);
  text-align: right;
  margin: 0.25rem 0 0;
}

.live-log-gif {
  max-width: 100%;
  max-height: 200px;
  border-radius: var(--radius-md);
  display: block;
  margin-top: 0.25rem;
}
.le-reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 0.35rem;
}
.le-reaction-btn {
  background: var(--surface-2, var(--bg-2));
  border: 1px solid var(--border);
  border-radius: var(--radius-2xl);
  padding: 0.1rem 0.4rem;
  font-size: var(--font-size-sm);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.2rem;
  color: var(--text-2);
  transition: background var(--t-hover);
}
.le-reaction-btn:hover, .le-reaction-btn:focus-visible, .le-reaction-btn.mine {
  background: var(--surface-3, rgba(255,255,255,0.08));
  border-color: var(--accent-teal);
}
.le-react-add {
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: var(--radius-2xl);
  padding: 0.1rem 0.35rem;
  font-size: var(--font-size-sm);
  cursor: pointer;
  color: var(--text-3);
  opacity: 0.7;
}
.le-react-add:hover, .le-react-add:focus-visible { opacity: 1; }
.le-emoji-picker {
  position: absolute;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  max-width: 200px;
  z-index: 100;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.le-emoji-picker button {
  background: none;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
  padding: 0.2rem;
  border-radius: 0;
  transition: background var(--t-hover);
}
.le-emoji-picker button:hover, .le-emoji-picker button:focus-visible { background: var(--surface-2); }

/* (v795) .live-launch-btn / .live-mic-btn retired — CSS-only orphans (no
   markup references). Recording controls use .rec-start-btn instead. */

/* Recording status bar */
.live-rec-status {
  font-size: var(--font-size-sm); font-weight: 500;
  padding: 0.35rem 0.6rem; border-radius: var(--radius-md);
  margin-bottom: 0.4rem; display: flex; align-items: center; gap: 0.4rem;
}
.live-rec-status.hidden { display: none; }
.live-rec-listening {
  background: rgba(220,38,38,0.12); color: var(--accent-red);
  border: 1px solid rgba(220,38,38,0.25);
}
.live-rec-saved {
  background: rgba(22,163,74,0.12); color: var(--accent-green);
  border: 1px solid rgba(22,163,74,0.25);
}
.live-rec-error {
  background: var(--ember-bg); color: var(--accent-ember);
  border: 1px solid var(--ember-border);
}
.rec-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
  animation: rec-blink 0.9s ease-in-out infinite;
}
.rec-dot.capture-lost    { background: var(--accent-amber); animation: none; }
.rec-dot.recovering      { background: var(--accent-blue);  animation: rec-blink 0.5s ease-in-out infinite; }
.rec-dot.recovery-failed { background: var(--accent-red);   animation: none; }
.rec-word-count { opacity: 0.7; font-weight: 400; }

/* ── Capture health alert banner ──────────────────────────────────────────── */
.capture-health-banner {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-sm);
  font-weight: 600;
  border-bottom: 1px solid transparent;
  animation: tf-slide-up var(--t-trans) ease reverse;
}
.capture-health-banner.hidden { display: none; }

/* Severity variants */
.capture-health-banner.error {
  background: color-mix(in srgb, var(--color-danger) 15%, var(--color-bg));
  border-color: color-mix(in srgb, var(--color-danger) 35%, transparent);
  color: var(--color-danger);
}
.capture-health-banner.warning {
  background: color-mix(in srgb, var(--color-warning) 15%, var(--color-bg));
  border-color: color-mix(in srgb, var(--color-warning) 35%, transparent);
  color: var(--color-warning);
}
.capture-health-banner.info {
  background: color-mix(in srgb, var(--color-info) 15%, var(--color-bg));
  border-color: color-mix(in srgb, var(--color-info) 35%, transparent);
  color: var(--color-info);
}
.capture-health-icon { font-size: var(--font-size-lg); flex-shrink: 0; }
.capture-health-msg  { flex: 1; }
.capture-health-detail {
  font-size: var(--font-size-xs);
  font-weight: 400;
  opacity: 0.75;
  white-space: nowrap;
}
.capture-health-dismiss {
  background: none; border: none; cursor: pointer;
  color: inherit; opacity: 0.6; font-size: var(--font-size-lg); padding: 0 0.25rem;
  flex-shrink: 0;
}
.capture-health-dismiss:hover, .capture-health-dismiss:focus-visible { opacity: 1; }

/* ── Login screen ────────────────────────────────────────────────────────── */
.login-screen {
  position: fixed; inset: 0; z-index: 1000;
  background: var(--bg);
  display: grid;
  grid-template-columns: 1fr 420px;
  align-items: center;
  overflow: hidden;
}
.login-screen.hidden { display: none; }
.login-card {
  background: var(--bg-2); border: 1px solid var(--border);
  border-top: 2px solid var(--split-strong);
  border-radius: var(--radius-3xl); padding: 2.5rem 2rem;
  width: 100%; max-width: 360px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.12);
  text-align: center;
}
.login-title {
  font-size: var(--font-size-2xl); font-weight: 700; margin-bottom: 0.25rem;
  color: var(--text);
}
.login-logo {
  max-width: 260px;
  width: 80%;
  margin: 0 auto 1.25rem;
  display: block;
}
.login-subtitle {
  font-size: var(--font-size-base); color: var(--text-2); margin-bottom: 1.25rem;
}
.login-input {
  width: 100%; padding: 0.6rem 0.8rem;
  border: 1.5px solid var(--border); border-radius: var(--radius-lg);
  background: var(--bg); color: var(--text);
  font-size: var(--font-size-md); font-family: inherit;
  margin-bottom: 0.75rem;
  box-sizing: border-box;
}
.login-input:focus { outline: none; border-color: var(--accent-teal); }
.login-btn {
  width: 100%; padding: 0.65rem;
  background: var(--accent-teal); color: #fff;
  border: none; border-radius: var(--radius-md);
  font-size: var(--font-size-md); font-weight: 600; cursor: pointer;
  margin-bottom: 0.75rem; transition: opacity var(--t-hover);
}
.login-btn:hover, .login-btn:focus-visible { opacity: 0.88; }
.login-error {
  font-size: var(--font-size-sm); color: var(--accent-red);
  margin-bottom: 0.5rem;
}
.login-gm-link {
  background: none; border: none; cursor: pointer;
  font-size: var(--font-size-base); color: var(--accent);
  text-decoration: underline; padding: 0.4rem 0;
  display: block; margin-top: 0.5rem;
}
.login-gm-link:hover, .login-gm-link:focus-visible { opacity: 0.75; }
.auth-links {
  display: flex; justify-content: space-between; gap: 0.5rem;
  margin-top: 0.25rem;
}
.auth-links .login-gm-link { margin-top: 0; }
.login-tos-note {
  font-size: var(--font-size-xs); color: var(--color-text-tertiary);
  text-align: center; margin: 0.25rem 0 0;
}
/* v980: marketing opt-in row on the signup card. Layout mirrors the
   Extended-content checkbox grammar (checkbox left, label flow right)
   so signup feels like the rest of the app. Font-size dimmer than the
   inputs — the checkbox is a secondary choice, not a primary one. */
.login-opt-in {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0.25rem 0;
  padding: 0.5rem 0.1rem;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: 1.45;
  cursor: pointer;
}
.login-opt-in input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--accent-teal);
  flex-shrink: 0;
  margin-top: 0.15rem;
}
.login-opt-in strong { color: var(--text); font-weight: var(--font-weight-semibold); }
.pc-quote-divider {
  border: none; border-top: 1px solid var(--color-border);
  margin: 0.75rem 0 0.5rem;
}
.pc-best-quote {
  font-size: var(--font-size-md); color: var(--color-text-secondary);
  line-height: 1.5; padding: 0 0.5rem 0.25rem;
  text-align: center;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.pc-best-quote-ctx {
  display: block; font-size: var(--font-size-xs); color: var(--color-text-tertiary);
  text-align: center; margin-top: 0.2rem; font-style: normal;
}

/* ── Codex Quotes panel ─────────────────────────────────────────────────── */
/* Each entity (or the synthetic "Party") renders as the canonical .entity-row
   shape — collapse-by-default, click to expand. The Quotes panel itself just
   provides the column layout + group-header rhythm; cards inherit border /
   radius / bg / hover from the shared .entity-row rules. */
.quotes-panel {
  padding: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}
/* Group headers (Active Party / NPCs / Fallen and Departed Party) — composed
   with .form-section-label; .quotes-group-hdr only adds the spacing above. */
.quotes-group-hdr {
  display: block;
  margin-top: var(--space-3);
  margin-bottom: 0;
}
.quotes-group-hdr:first-child { margin-top: 0; }
/* Fallen and Departed group — desaturated and slightly faded so it reads
   as historical, sits at the bottom. */
.quotes-fallen-group {
  display: flex; flex-direction: column; gap: var(--space-3);
  filter: grayscale(70%);
  opacity: 0.7;
}
.quotes-fallen-group .entity-row {
  border-color: var(--text-3);
}
/* Count chip — sits between entity-meta and chevron in the .entity-row-hdr.
   Matches the .session-num corner-badge recipe (teal-tinted color-mix,
   small radius, xs font). Single source of truth for "count chip". */
.quotes-count-badge {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 13%, transparent);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.5rem;
  white-space: nowrap;
  flex-shrink: 0;
}
/* Quote list inside an expanded entity-row.entity-detail — subtle internal
   dividers (the outer .entity-row border handles overall enclosure). */
.quotes-list { display: flex; flex-direction: column; gap: 0; }
.quotes-item {
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
  position: relative;
}
.quotes-item:first-child {
  border-top: none;
  padding-top: 0;
}
.quotes-item-text {
  font-size: var(--font-size-md);
  color: var(--text);
  line-height: 1.5;
}
.quotes-item-text em { font-style: italic; }
.quotes-item-ctx {
  font-size: var(--font-size-xs);
  color: var(--text-2);
  margin-top: 0.2rem;
}
.quotes-item-session {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  margin-top: 0.15rem;
  font-style: italic;
}

.archived-campaign-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.5rem 0.75rem; border-radius: var(--radius-lg);
  border: 1px solid var(--color-border); background: var(--color-surface);
  margin-bottom: 0.35rem; gap: 0.75rem;
}
.archived-campaign-name { font-size: var(--font-size-base); color: var(--color-text-secondary); flex: 1; }
.archived-campaign-meta { font-size: var(--font-size-xs); color: var(--color-text-tertiary); }

/* Campaign role badges & member rows */
.camp-role-badge {
  display: inline-block; padding: 0.15rem 0.45rem;
  border-radius: var(--radius-sm); font-size: var(--font-size-xs); font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  margin-left: 0.4rem;
}
.camp-role-gm     { background: var(--ember-bg); border: 1px solid var(--ember-border); color: var(--ember-base); }
.camp-role-co-gm  { background: var(--ember-bg); border: 1px solid var(--ember-border); color: var(--ember-base); }
/* v798: theme-correct base + dark override. Pre-v798 white-rgba was
   invisible on light theme. */
.camp-role-member { background: var(--bg-3); border: 1px solid var(--border); color: var(--text-1); }
.camp-role-admin  { background: var(--bg-3); border: 1px solid var(--border); color: var(--text-2); }
[data-theme="dark"] .camp-role-member { background: rgba(255,255,255,0.07); border-color: rgba(255,255,255,0.18); color: var(--color-text-primary); }
[data-theme="dark"] .camp-role-admin  { background: rgba(148,163,184,0.10); border-color: rgba(148,163,184,0.20); color: var(--color-text-tertiary); }
.camp-member-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.5rem 0.75rem; border-radius: var(--radius-lg);
  background: var(--bg-2); border: 1px solid var(--border);
  gap: 0.5rem;
}
.camp-member-info { display: flex; align-items: center; flex: 1; min-width: 0; }
.camp-member-email { font-size: var(--font-size-base); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.camp-member-actions { display: flex; gap: 0.3rem; flex-shrink: 0; }
.gm-escalate-btn {
  padding: 0.25rem 0.6rem;
  background: none; border: 1px solid var(--accent);
  border-radius: var(--radius-md); color: var(--accent);
  font-size: var(--font-size-sm); cursor: pointer;
  transition: background var(--t-hover), color var(--t-hover);
}
.gm-escalate-btn:hover, .gm-escalate-btn:focus-visible { background: var(--accent); color: #fff; }
.gm-escalate-overlay {
  position: fixed; inset: 0; z-index: 900;
  background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
}
.gm-escalate-overlay.hidden { display: none; }
.gm-escalate-card {
  background: var(--bg-1); border: 1px solid var(--border);
  border-radius: var(--radius-2xl); padding: 1.6rem 2rem;
  width: min(340px, 90vw); display: flex; flex-direction: column; gap: 0.8rem;
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.gm-escalate-title {
  font-size: var(--font-size-xl); font-weight: 700; color: var(--text-1);
  margin: 0; text-align: center;
}
.gm-escalate-btns { display: flex; gap: 0.6rem; }
.logout-btn {
  padding: 0.25rem 0.6rem;
  background: none; border: 1px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-3);
  font-size: var(--font-size-sm); cursor: pointer;
  transition: color var(--t-hover), border-color var(--t-hover);
}
.logout-btn:hover, .logout-btn:focus-visible { color: var(--accent-red); border-color: var(--accent-red); }

/* ── Recorder Widget ─────────────────────────────────────────────────────────── */
.live-recorder {
  padding: 0.3rem 0.6rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.live-recorder.hidden { display: none; }
/* v796: eyebrow row above the recorder panel. Tight padding so it doesn't
   eat the limited Live-sidebar real estate. */
.live-recorder-hdr {
  display: flex;
  align-items: center;
  padding: var(--space-1) 0 var(--space-2);
}
.rec-panel { display: flex; flex-direction: column; gap: 0.22rem; }
.rec-panel.hidden { display: none; }

/* System audio toggle */
.rec-sys-audio-label {
  display: flex; align-items: center; gap: 0.35rem;
  font-size: var(--font-size-sm); color: var(--text-2); cursor: pointer;
  user-select: none;
}
.rec-sys-audio-label input[type="checkbox"] { accent-color: var(--accent-teal); cursor: pointer; }

/* Ready state */
.rec-start-btn {
  width: 100%; padding: 0.3rem;
  font-size: var(--font-size-sm); font-weight: 700; letter-spacing: 0.03em;
  border: 2px solid var(--accent-red); border-radius: var(--radius-md);
  background: none; color: var(--accent-red); cursor: pointer;
  transition: background var(--t-hover), color var(--t-hover);
}
.rec-start-btn:hover, .rec-start-btn:focus-visible { background: var(--accent-red); color: #fff; }

/* Active / paused state */
.rec-top-row {
  display: flex; align-items: center; gap: 0.5rem;
}
.rec-indicator {
  width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
}
.rec-indicator.recording {
  background: var(--accent-red);
  animation: rec-pulse 1.2s ease-in-out infinite;
}
.rec-indicator.paused { background: var(--accent-amber); animation: none; }
.rec-state-lbl {
  font-size: var(--font-size-xs); font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
}
.rec-state-lbl.recording { color: var(--accent-red); }
.rec-state-lbl.paused { color: var(--accent-amber); }
.rec-clock {
  font-family: monospace; font-size: var(--font-size-base); font-weight: 700;
  color: var(--text); margin-left: auto; letter-spacing: 0.05em;
}

/* ── Mic input level meter (v780) ─────────────────────────────────────────
   Segmented LED bar driven by liveAnalyser. 14 segments split into three
   zones — green (1-9), amber (10-12), red (13-14). Lit segments glow with
   their zone colour; unlit segments stay tinted-but-dim so the bar's
   silhouette is always readable. Premium signal: visible feedback that
   the mic is actually capturing audio, especially during in-room sessions. */
.rec-level-meter {
  display: flex;
  gap: 2px;
  align-items: stretch;
  width: 100%;
  height: 8px;
  margin-top: var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--bg-3);
  border: 1px solid var(--border);
  padding: 1px;
  box-sizing: border-box;
  overflow: hidden;
}
.rec-level-seg {
  flex: 1;
  border-radius: 1px;
  background: color-mix(in srgb, var(--text-3) 25%, transparent);
  transition: background 60ms linear, box-shadow 60ms linear, opacity 60ms linear;
  opacity: 0.55;
}
.rec-level-seg.is-on { opacity: 1; }
.rec-level-seg.zone-green.is-on {
  background: var(--color-success);
  box-shadow: 0 0 4px color-mix(in srgb, var(--color-success) 60%, transparent);
}
.rec-level-seg.zone-amber.is-on {
  background: var(--accent-amber);
  box-shadow: 0 0 4px color-mix(in srgb, var(--accent-amber) 60%, transparent);
}
.rec-level-seg.zone-red.is-on {
  background: var(--accent-red);
  box-shadow: 0 0 5px color-mix(in srgb, var(--accent-red) 70%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .rec-level-seg { transition: none; }
}
.rec-btn-row { display: flex; gap: 0.4rem; }
.rec-btn {
  flex: 1; padding: 0.22rem 0.4rem;
  font-size: var(--font-size-xs); font-weight: 600; white-space: nowrap;
  border: 1.5px solid var(--border); border-radius: var(--radius-md);
  background: var(--bg-2); color: var(--text-1); cursor: pointer;
  transition: background var(--t-hover), border-color var(--t-hover), color var(--t-hover);
}
.rec-btn:hover, .rec-btn:focus-visible { background: var(--bg-3); }
.rec-btn-finish { border-color: var(--accent); color: var(--accent); }
.rec-btn-finish:hover, .rec-btn-finish:focus-visible { background: var(--accent); color: #fff; }
.rec-discard-row {
  display: flex; justify-content: center; gap: 1rem;
}
.rec-discard-link {
  background: none; border: none; padding: 0.1rem 0;
  font-size: var(--font-size-xs); color: var(--text-3); cursor: pointer;
  transition: color var(--t-hover);
}
.rec-discard-link:hover, .rec-discard-link:focus-visible { color: var(--accent-red); }
.rec-member-notice {
  font-size: var(--font-size-sm); color: var(--text-3); text-align: center;
  padding: 0.4rem 0; font-style: italic;
}
.rec-chime-notice {
  font-size: var(--font-size-xs); color: var(--text-3); text-align: center;
  margin-top: 0; opacity: 0.7;
}
.rec-bt-note {
  font-size: var(--font-size-xs); color: var(--color-warning);
  margin-top: 0.35rem; line-height: 1.35; cursor: help;
  padding: 0.3rem 0.5rem;
  background: color-mix(in srgb, var(--color-warning) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-warning) 25%, transparent);
  border-radius: var(--radius-md);
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
}
.rec-bt-note-text {
  flex: 1 1 auto;
  text-align: center;
}
.rec-bt-note-close {
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: inherit;
  font-size: var(--font-size-sm);
  line-height: 1;
  padding: 0 0.2rem;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--t-hover) var(--ease);
}
.rec-bt-note-close:hover,
.rec-bt-note-close:focus-visible {
  opacity: 1;
  outline: none;
}

/* Quest panel — below map on the left */
/* v795: removed border-top — the project rule explicitly forbids
   border-top/border-bottom dividers under section headers. */
.live-quest-panel {
  padding: var(--space-2) var(--space-3);
  flex-shrink: 0; max-height: 220px; overflow-y: auto;
  background: var(--surface-1);
}
.live-quest-panel-hdr {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 0.4rem;
}
/* v795: typography moved to .form-section-label composed in markup. */
.live-quest-panel-body { display: flex; flex-direction: column; gap: 0.3rem; }
/* (v780) .lqp-empty retired — replaced by .placeholder.placeholder--padded
   composed in markup in renderLiveQuestPanel. */
.lqo-item {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 0.3rem 0.4rem; border-radius: var(--radius-md); background: var(--bg-2);
}
.lqo-main { flex: 1; min-width: 0; }
.lqo-title {
  font-size: var(--font-size-sm); color: var(--text-1); display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lqo-desc {
  font-size: var(--font-size-xs); color: var(--text-3); margin-top: 2px;
  line-height: 1.35; white-space: pre-wrap; word-break: break-word;
}
.lqo-actions { display: flex; gap: 0.25rem; flex-shrink: 0; align-items: center; }
.lqo-btn {
  padding: 1px 6px; border-radius: 0; font-size: var(--font-size-sm); font-weight: 700;
  border: 1.5px solid var(--border); background: none; cursor: pointer; line-height: 1.5;
  transition: background var(--t-hover), color var(--t-hover), border-color var(--t-hover);
}
.lqo-complete { color: var(--accent-teal); border-color: var(--accent-teal); }
.lqo-complete:hover, .lqo-complete:focus-visible { background: var(--accent-teal); color: #fff; }
.lqo-fail { color: var(--accent-red); border-color: var(--accent-red); }
.lqo-fail:hover, .lqo-fail:focus-visible { background: var(--accent-red); color: #fff; }

/* no-map: left column always visible; map panel shows empty state placeholder */
/* (kept as hook for JS in case future layout overrides are needed) */

/* Quest log collapsed state */
.live-quest-panel.collapsed { max-height: none; overflow: visible; }
.live-quest-panel.collapsed .live-quest-panel-body { display: none; }
.live-quest-panel.collapsed .live-quest-panel-hdr  { margin-bottom: 0; }
.live-quest-panel.collapsed .live-quest-collapse-btn { transform: rotate(-90deg); }

/* ── Live screen PC character sidebar ───────────────────────────── */
.live-pc-sidebar-expander {
  width: 28px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-right: 1px solid var(--border);
  background: var(--bg-2);
  cursor: pointer;
  user-select: none;
  transition: background var(--t-hover), color var(--t-hover);
}
.live-pc-sidebar-expander:hover, .live-pc-sidebar-expander:focus-visible { background: var(--bg-3); color: var(--accent-blue); }
.live-pc-sidebar-expander span {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--font-size-xs); font-weight: 600;
  color: var(--text-2);
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  max-height: 200px;
  text-overflow: ellipsis;
}
.live-pc-sidebar-expander:hover span, .live-pc-sidebar-expander:focus-visible span { color: var(--accent-blue); }
.live-pc-sidebar {
  width: 300px; flex-shrink: 0;
  display: flex; flex-direction: column;
  border-right: 1px solid var(--border);
  background: var(--bg);
  overflow: hidden;
}
.live-pc-sidebar-hdr {
  display: flex; align-items: center; gap: 0.25rem;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  font-size: var(--font-size-base); font-weight: 600; color: var(--text);
}
/* v795: typography moved to .form-section-label composed in markup. */
#live-pc-sidebar-hdr-title {
  flex: 1; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.live-pc-nav-btn {
  font-size: var(--font-size-xs); padding: 2px 7px; flex-shrink: 0;
  color: var(--text-2);
}
.live-pc-nav-btn:hover, .live-pc-nav-btn:focus-visible { color: var(--accent-teal); }
/* v795: shared --subtle modifier for the small ▾/◁ collapse buttons.
   Used by .live-pc-collapse-btn and .live-ai-collapse-btn — same recipe,
   one source of truth. */
.live-collapse-btn--subtle,
.live-pc-collapse-btn,
.live-ai-collapse-btn { margin-left: auto; font-size: 0.6rem; opacity: 0.6; }
.live-collapse-btn--subtle:hover, .live-collapse-btn--subtle:focus-visible,
.live-pc-collapse-btn:hover, .live-pc-collapse-btn:focus-visible,
.live-ai-collapse-btn:hover, .live-ai-collapse-btn:focus-visible { opacity: 1; }
.live-pc-sidebar-body {
  flex: 1; overflow-y: auto; padding: 0.6rem;
}

/* ── Live Chat Drawer (right side, mirrors PC sidebar) ─────────────────── */
.live-ai-drawer {
  width: 420px; flex-shrink: 0;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--border);
  background: var(--bg);
  overflow: hidden;
}
.live-ai-drawer-hdr {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
/* v795: typography moved to .form-section-label composed in markup. */
.live-ai-drawer-title { flex: 1; }
/* (v795) .live-ai-collapse-btn rules consolidated into the
   .live-collapse-btn--subtle group above. */
.live-ai-drawer-body {
  flex: 1;
  display: flex; flex-direction: column;
  overflow-y: auto;       /* drawer scrolls as a whole — sections stack vertically */
  min-height: 0;
}

/* GM Notebook section pattern (commit 4 of 7).
   Each section: header (eyebrow + count badge + chevron) + body.
   Click anywhere on the header to toggle .ln-collapsed.
   Compose .form-section-label / .quotes-count-badge / .live-collapse-btn
   from existing rules — no new chrome typography. */
.live-notebook-section {
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
}
.live-notebook-section:last-child { border-bottom: none; }
.live-notebook-section-hdr {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: var(--space-3) var(--space-3);
  cursor: pointer;
  user-select: none;
  background: transparent;
  transition: background var(--t-hover) var(--ease);
}
.live-notebook-section-hdr:hover {
  background: color-mix(in srgb, var(--accent-teal) 5%, transparent);
}
.live-notebook-section-hdr .form-section-label {
  flex: 1 1 auto;
  /* let the label truncate before pushing the chevron off-canvas */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Count badge hides when zero so an empty section reads cleanly. */
.live-notebook-count[data-count="0"],
.live-notebook-count:empty { display: none; }
.live-notebook-collapse {
  transition: transform var(--t-hover) var(--ease);
}
.live-notebook-section.ln-collapsed .live-notebook-collapse {
  transform: rotate(-90deg);
}
.live-notebook-body {
  padding: 0 var(--space-3) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.live-notebook-section.ln-collapsed .live-notebook-body {
  display: none;
}
/* Empty-state body text inside a notebook section — used by the
   v912 placeholders that render until commits 5-7 wire real data. */
.live-notebook-empty {
  margin: 0;
  font-style: italic;
  color: var(--text-3);
  font-size: var(--font-size-sm);
}

/* Fates section card (Notebook column) — collapsed header is a compact
   row of: type-chip + title + pending-count + ↗ pop-out. Click anywhere
   on the header (except the pop-out) toggles expand. Expand reveals the
   full scene rendered inline (compact density tuned to ~360px width).
   v919: switched from "click row → open modal" to "click row → expand
   inline" per user direction; modal is now opt-in via the ↗ icon. */
.ln-fate-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--font-size-sm);
  overflow: hidden;
  transition: border-color var(--t-hover) var(--ease);
}
.ln-fate-card--expanded {
  border-color: rgba(47, 154, 163, 0.45);
}
.ln-fate-card-hdr {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.55rem;
  cursor: pointer;
  user-select: none;
  transition: background var(--t-hover) var(--ease);
}
.ln-fate-card-hdr:hover {
  background: color-mix(in srgb, var(--accent-teal) 5%, transparent);
}
.ln-fate-card--expanded .ln-fate-card-hdr {
  background: color-mix(in srgb, var(--accent-teal) 6%, transparent);
}
.ln-fate-row-title {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--font-weight-bold);
}
.ln-fate-row-count {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  color: var(--text-3);
  font-weight: 500;
}
.ln-fate-popout {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-3);
  font-size: var(--font-size-base);
  line-height: 1;
  padding: 0.15rem 0.35rem;
  cursor: pointer;
  opacity: 0;
  transition:
    opacity      var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}
.ln-fate-card-hdr:hover .ln-fate-popout,
.ln-fate-card-hdr:focus-within .ln-fate-popout,
.ln-fate-card--expanded .ln-fate-popout {
  opacity: 0.7;
}
.ln-fate-popout:hover,
.ln-fate-popout:focus-visible {
  opacity: 1;
  color: var(--accent-teal);
  border-color: color-mix(in srgb, var(--accent-teal) 40%, var(--border));
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  outline: none;
}
/* Inline-expanded scene body — uses the same .fates-scene-section /
   .form-section-label / .fates-scene-premise rules as the modal render
   so the typography is consistent. Just wrapped with tighter padding
   for the narrow drawer column. */
.ln-fate-expanded {
  padding: var(--space-3) var(--space-3) var(--space-3);
  border-top: 1px dashed color-mix(in srgb, var(--accent-teal) 25%, var(--border));
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.ln-fate-expanded .fates-scene-premise {
  margin: 0;
}
.ln-fate-expanded .fates-scene-section p {
  /* Slightly tighter line-height in the narrow column for scannability. */
  line-height: 1.45;
}

/* Foretelling row inside the Notebook — checkbox + placement chip on
   left, seed text + cover + parent-Fate footnote middle, hover-revealed
   ✕ delete on the right (v942 — GM-only seed delete). */
.ln-foretelling-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0.6rem;
  align-items: start;
  padding: 0.5rem 0.55rem;
  background: color-mix(in srgb, var(--accent-teal) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 18%, var(--border));
  border-radius: var(--radius-md);
  transition: opacity var(--t-hover) var(--ease);
}
/* Planted state — strikethrough + dim, mirrors the modal's inline view. */
.ln-foretelling-row--planted { opacity: 0.55; }
.ln-foretelling-row--planted .ln-foretelling-text {
  text-decoration: line-through;
  text-decoration-color: var(--text-3);
}
/* v942: hover-revealed ✕ on each foreshadow row. Same pattern as
   .fates-row-action-btn--snip / .fates-edit-npc-remove (rest opacity 0,
   row-hover reveals to 0.7, button hover to 1 + danger tint). */
.fates-foreshadow-row-delete {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-3);
  font-size: var(--font-size-sm);
  line-height: 1;
  padding: 0.15rem 0.35rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: 0;
  transition:
    opacity      var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
  align-self: start;
}
.ln-foretelling-row:hover .fates-foreshadow-row-delete,
.ln-foretelling-row:focus-within .fates-foreshadow-row-delete { opacity: 0.7; }
.fates-foreshadow-row-delete:hover,
.fates-foreshadow-row-delete:focus-visible {
  opacity: 1;
  color: var(--accent-red);
  border-color: color-mix(in srgb, var(--accent-red) 35%, var(--border));
  background: color-mix(in srgb, var(--accent-red) 8%, transparent);
  outline: none;
}
/* v942: empty-state CTA when a Fate has no foreshadow yet. Stack a
   helper line + a single Generate button. .fates-foreshadow-regen is
   the same idiom for the populated state — small ghost button under
   the seed list that re-runs generation. */
.fates-foreshadow-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  padding: 0.4rem 0;
}
.fates-foreshadow-empty .form-helper-text { margin: 0; }
.fates-foreshadow-regen {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.4rem;
}

/* Foreshadow section embedded inside an expanded Fate (v921) — list
   of seed rows stacked vertically with tight spacing. */
.ln-fate-foreshadow-section .ln-fate-foreshadow-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 0.25rem;
}
.ln-foretelling-check {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  user-select: none;
}
.ln-foretelling-check input {
  cursor: pointer;
  accent-color: var(--accent-teal);
}
.ln-foretelling-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.ln-foretelling-text {
  margin: 0;
  font-size: var(--font-size-sm);
  line-height: 1.4;
  color: var(--text);
}
.ln-foretelling-cover {
  margin: 0;
  font-size: var(--font-size-xs);
  font-style: italic;
  color: var(--text-3);
  line-height: 1.35;
}
.ln-foretelling-cover-label {
  font-style: normal;
  font-weight: 500;
  color: var(--text-2);
  margin-right: 0.25rem;
}
.ln-foretelling-fate {
  margin: 0;
  font-size: var(--font-size-xs);
  color: color-mix(in srgb, var(--accent-teal) 70%, var(--text-3));
}

/* Thread row inside the Notebook — status glyph + title + heat score.
   Compact list style, no card chrome (the Threads section can have
   up to 7 visible — keeping rows tight prevents the drawer from
   becoming a wall). */
.ln-thread-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.4rem;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  color: var(--text);
  cursor: default;
  transition: background var(--t-hover) var(--ease);
}
.ln-thread-row:hover {
  background: color-mix(in srgb, var(--accent-teal) 5%, transparent);
}
.ln-thread-status {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  line-height: 1;
}
.ln-thread-status--hot  { color: var(--accent-ember); }
.ln-thread-status--warm { color: var(--accent-gold); }
.ln-thread-status--cold { color: var(--text-3); }
.ln-thread-title {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ln-thread-heat {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
}
.ln-thread-overflow {
  margin-top: 0.3rem;
  text-align: center;
}

/* v922: in-section expand/fewer button for the Threads cap.
   Mirrors the Fates-tab entity-rail expander recipe (dashed teal
   border, ▾ glyph, hover lifts subtle teal wash). The "Show fewer"
   variant flips the chevron via .ln-thread-expand-glyph--up. */
.ln-thread-expand-btn {
  display: block;
  width: 100%;
  margin-top: var(--space-2);
  padding: 0.4rem 0.5rem;
  background: transparent;
  border: 1px dashed color-mix(in srgb, var(--accent-teal) 28%, var(--border));
  border-radius: var(--radius-md);
  color: var(--text-3);
  font-family: inherit;
  font-size: var(--font-size-xs);
  text-align: left;
  cursor: pointer;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease);
}
.ln-thread-expand-btn:hover,
.ln-thread-expand-btn:focus-visible {
  background: color-mix(in srgb, var(--accent-teal) 6%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 45%, var(--border));
  color: var(--text-2);
  outline: none;
}
.ln-thread-expand-glyph {
  display: inline-block;
  margin-right: 0.3rem;
  color: color-mix(in srgb, var(--accent-teal) 70%, var(--text-3));
  transition: transform var(--t-hover) var(--ease);
}
.ln-thread-expand-glyph--up { transform: rotate(180deg); }

/* Loose Threads — collapsible card pattern (v919). Header row is the
   compact summary; expand reveals the thread context (linkified to
   Codex entities) right in the column. */
.ln-thread-card {
  border-radius: var(--radius-sm);
  transition: background var(--t-hover) var(--ease);
}
.ln-thread-card .ln-thread-row {
  cursor: pointer;
}
.ln-thread-card--expanded {
  background: color-mix(in srgb, var(--accent-teal) 4%, transparent);
}
.ln-thread-card--expanded .ln-thread-row {
  background: color-mix(in srgb, var(--accent-teal) 5%, transparent);
}
.ln-thread-expanded {
  padding: 0.4rem 0.5rem 0.5rem;
}
.ln-thread-context {
  margin: 0;
  font-size: var(--font-size-xs);
  line-height: 1.45;
  color: var(--text-2);
}
.ln-thread-context--empty {
  font-style: italic;
  color: var(--text-3);
}

/* Live Context cards — NPCs / Factions / Locations mentioned ≥2× in
   this session's transcript (PCs / spells / items filtered out per
   v954). Horizontal layout: portrait LEFT, body RIGHT. Portrait is
   the click target → opens the canonical entity-preview side drawer.
   Codex snippet is always visible inline; the pre-v954 expand-on-
   card-click behavior + per-mention timestamp quotes are retired
   (the table is at the live session — they all heard it). */
.ln-context-card {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 0.55rem;
  padding: 0.5rem 0.6rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}
.ln-context-card:hover {
  background: color-mix(in srgb, var(--accent-teal) 5%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent-teal) 35%, var(--border));
}
.ln-context-portrait {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--text-1) 8%, transparent);
  background: var(--bg-3);
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    border-color var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
.ln-context-portrait:hover,
.ln-context-portrait:focus-visible {
  outline: none;
  border-color: color-mix(in srgb, var(--accent-teal) 55%, var(--border));
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.ln-context-portrait--icon {
  font-size: var(--font-size-lg);
  appearance: none;
  padding: 0;
}
.ln-context-card-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.ln-context-card-hdr {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.ln-context-card-hdr .fates-warp-head {
  /* compact — name pill takes available width with ellipsis */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ln-context-mentions {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  font-weight: 500;
}
.ln-context-dismiss {
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: var(--text-3);
  font-size: var(--font-size-sm);
  line-height: 1;
  padding: 0 0.25rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease), color var(--t-hover) var(--ease);
}
.ln-context-card:hover .ln-context-dismiss,
.ln-context-card:focus-within .ln-context-dismiss {
  opacity: 0.7;
}
.ln-context-dismiss:hover {
  opacity: 1;
  color: var(--color-danger);
}
.ln-context-snippet {
  margin: 0;
  font-size: var(--font-size-xs);
  color: var(--text-2);
  line-height: 1.4;
  /* v954: codex blurb wraps to 2 lines max; ellipsis after that.
     Italic dropped — this is descriptive prose now, not a quote. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ln-context-expanded {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-top: 0.3rem;
  padding-top: 0.4rem;
  border-top: 1px dashed color-mix(in srgb, var(--accent-teal) 25%, var(--border));
}
.ln-context-quote {
  margin: 0;
  font-size: var(--font-size-xs);
  line-height: 1.4;
  color: var(--text-2);
}
.ln-context-quote-time {
  font-weight: 600;
  color: var(--accent-teal);
  font-variant-numeric: tabular-nums;
}
.ln-context-quote-text {
  font-style: italic;
}
.ln-context-codex {
  margin: 0;
  font-size: var(--font-size-xs);
  color: var(--text-3);
  line-height: 1.35;
  padding-top: 0.3rem;
  border-top: 1px dashed color-mix(in srgb, var(--border) 60%, transparent);
}
/* v910: hoisted off .live-ai-drawer-body parent so the rules survive
   when chat moves under the transcript (commit 3 of GM Notebook).
   Behavior unchanged in the drawer; same rules now also apply when
   .live-log + .live-entry-form mount under .live-sidebar. */
/* Log fills remaining space in its parent flex container */
.live-log { flex: 1; overflow-y: auto; }
/* Entry form doesn't scroll */
.live-entry-form { flex-shrink: 0; }
/* Preamble (Where We Left Off) — stays in the drawer as the Notebook's
   first section, so this rule keeps its drawer-context selector. */
.live-ai-drawer-body .live-preamble {
  max-height: none;
  border-bottom: none;
  flex-shrink: 0;
}

.live-ai-expander {
  width: 28px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-left: 1px solid var(--border);
  background: var(--bg-2);
  cursor: pointer;
  user-select: none;
  transition: background var(--t-hover), color var(--t-hover);
}
.live-ai-expander:hover, .live-ai-expander:focus-visible { background: var(--bg-3); color: var(--accent-teal); }
.live-ai-expander span {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--font-size-xs); font-weight: 600;
  color: var(--text-2);
  overflow: hidden;
  max-height: 160px;
  text-overflow: ellipsis;
}
.live-ai-expander:hover span, .live-ai-expander:focus-visible span { color: var(--accent-teal); }

/* ── Live viewers bar (GM-only — shows who's in the session) ─────────── */
/* Viewers bar — inline chips in the live header */
.live-viewers-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1);
  flex-shrink: 0;
}
.live-viewers-bar.hidden { display: none; }
.live-viewer-chip {
  display: inline-flex; align-items: center;
  font-size: var(--font-size-xs); font-weight: 600;
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-success) 28%, transparent);
  border-radius: var(--radius-full);
  padding: 0.1rem 0.45rem;
}

/* "played by X" is redundant when viewing your own card */
.live-pc-sidebar .pc-player-name { display: none; }
/* Stack portrait above stats inside the narrow sidebar */
.live-pc-sidebar .party-pc-card { flex-direction: column; }
.live-pc-sidebar .party-pc-portrait { width: 100%; aspect-ratio: unset; height: 140px; }
/* Live sidebar uses a tall slim 140px crop where the head/face usually sits
   near the top of the source art. Default `--portrait-y: 0%` anchors the top
   edge to the panel's top edge; the crop tool on this surface is 2D so GMs
   can pull the face into frame and save a per-PC offset. Other surfaces
   keep `center` for vertical and ignore `--portrait-y`. */
.live-pc-sidebar .party-pc-portrait img { object-position: var(--portrait-x, 50%) var(--portrait-y, 0%); }
.live-pc-sidebar .party-pc-stats { padding: 0.55rem 0.65rem; }
/* Name row: stack name/metadata above buttons so both get full width (no squeeze) */
.live-pc-sidebar .party-pc-name-row {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
}
.live-pc-sidebar .party-pc-name-row > div:last-child {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}
/* Ability grid: 3 columns per row so 6 stats wrap as 3+3 */
.live-pc-sidebar .ddb-ability-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.35rem;
}
/* Toggle buttons for multiple assigned PCs */
.live-pc-toggle-row {
  display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 0.6rem;
}
.live-pc-toggle-btn.active {
  background: var(--accent-blue); color: #fff; border-color: var(--accent-blue);
}

/* Subtle recording dot in the live header */
.live-header-rec-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent-red);
  animation: rec-pulse 1.2s ease-in-out infinite;
  flex-shrink: 0;
}
.live-header-rec-dot.hidden { display: none; }

/* ── Party tab ───────────────────────────────────────────────────────────── */
/* v787: canonical eyebrow spacing — matches .sessions-toolbar / Codex /
   .quest-nav-toolbar / .map-toolbar / .manage-nav-col. */
.party-tab-panel {
  padding: var(--space-5) var(--space-4);
  overflow-y: auto;
  height: calc(100vh - var(--chrome-height));
}
/* Action bar above the legend (New PC button, etc.) */
.party-tab-actions {
  display: flex; align-items: center; justify-content: flex-end;
  padding-bottom: 0.5rem;
}

/* Portrait upload affordance (GM only) */
.party-pc-portrait--editable {
  cursor: pointer;
  position: relative;
}
.party-pc-portrait--editable::after {
  content: '📷';
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.5);
  color: var(--color-white);
  font-size: 1.4rem;
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity var(--t-hover);
  pointer-events: none;
}
.party-pc-portrait--editable:hover::after, .party-pc-portrait--editable:focus-within::after { opacity: 1; }
.party-legend {
  display: flex; gap: 1.25rem; align-items: center;
  padding: 0.35rem 0.75rem;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-md);
  font-size: var(--font-size-xs); color: var(--text-2);
  margin-bottom: 0; /* gap comes from party-cards-grid margin-top */
}
/* Inline variant — sits in the section header row between the label and
   the action buttons. Drops the chip-like card chrome (bg/border/radius)
   so the swatches read as inline metadata, not a separate panel. Fills
   the middle space and wraps gracefully on narrow widths. */
.party-legend-inline {
  flex: 1 1 auto;
  flex-wrap: wrap;
  background: none;
  border: none;
  padding: 0;
  gap: var(--space-3);
  margin: 0;
}
.pleg-actions { display: flex; gap: 0.4rem; margin-left: auto; align-items: center; }
.pleg-actions .btn-xs { white-space: nowrap; }
.pleg-item { display: flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.pleg-swatch {
  width: 11px; height: 11px; border-radius: var(--radius-sm);
  border: 2px solid transparent; flex-shrink: 0;
}
/* Swatches share a single semantic palette with the ability cells below
   (.ddb-ab-best / .ddb-ab-near-best) so the legend is a 1:1 visual key.
   Token-driven via color-mix; no raw hex. */
.pleg-swatch--best   { border-color: var(--accent-green);  background: color-mix(in srgb, var(--accent-green)  18%, transparent); }
.pleg-swatch--near   { border-color: color-mix(in srgb, var(--accent-green) 60%, var(--accent-amber)); background: color-mix(in srgb, var(--accent-green) 30%, var(--accent-amber) 12%); }
.pleg-swatch--unique { border-color: var(--color-gold);    background: color-mix(in srgb, var(--color-gold)    14%, transparent); }
.pleg-swatch--fav    { border-color: var(--accent-purple); background: color-mix(in srgb, var(--accent-purple) 14%, transparent); }
.pleg-swatch--adv {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-blue); color: var(--color-white);
  border-radius: var(--radius-sm); font-size: 0.55rem; font-weight: 700;
  width: 11px; height: 11px; border: none; flex-shrink: 0;
}
.party-cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(520px, 1fr));
  gap: 1rem;
  margin-top: 1rem;
}
.party-pc-card {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  transition:
    border-color var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
/* Canonical card hover — same recipe as .campaign-card / .session-card /
   .entity-row / .quotes-pc-section. Treasury and Campaign-To-Date cards
   inherit because they currently share the .party-pc-card base. */
.party-pc-card:hover, .party-pc-card:focus-within {
  border-color: rgba(47,154,163,0.45);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
/* Fallen PC — fully greyed out */
.party-pc-card--dead {
  border-color: var(--text-3);
  filter: grayscale(100%);
  opacity: 0.5;
}
/* Departed PC — half greyed out */
.party-pc-card--departed {
  border-color: var(--text-3);
  filter: grayscale(55%);
  opacity: 0.7;
}
/* Party-tab section header rows. Two flavours, both compose with
   .form-section-label for typography (canonical eyebrow):
     • .party-section-label — standalone label, full grid width
     • .party-section-hdr   — eyebrow LEFT + paired CTA RIGHT
   The previous .fallen-section-hdr re-implemented the eyebrow recipe and
   added a forbidden border-top divider; both retired in v743. */
/* v789: flex + min-height matches the effective vertical extent of canonical
   button-row eyebrow toolbars (.sessions-toolbar / .compendium-section-hdr /
   .quest-nav-toolbar / .map-toolbar). Without this, a bare standalone
   .party-section-label sits ~0.5rem higher than every other tab's eyebrow,
   because peers center their eyebrow text in a flex row sized by the button
   companions — bare spans don't get that lift. */
.party-section-label {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  min-height: 2rem;
  margin-top: var(--space-3);
  margin-bottom: var(--space-2);
}
.party-section-label:first-child { margin-top: 0; }
.party-section-hdr {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
  margin-bottom: var(--space-2);
}
.party-section-hdr:first-child { margin-top: 0; }
/* Inline-edit affordance for the party name (v925) — same recipe
   as .session-title-edit-btn / .session-title-input. ✎ swaps to
   input; blur or Enter saves; Esc cancels. */
/* v945: rename-party affordance lives on the Party Assessment eyebrow.
   .party-section-hdr-row is the flex row holding the eyebrow LEFT + the
   rename trigger RIGHT. Two visual states for the trigger: when the
   campaign uses the default party name, render a full text-labeled
   ghost-pill ("✎ Rename Party") for discoverability; once the GM has
   set a custom name, collapse to a tiny pencil-icon circle button via
   .party-rename-btn--icon. */
.party-section-hdr-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* margin-bottom matches the previous standalone .party-section-label
     so the row → content gap is unchanged. */
  margin-bottom: var(--space-2);
}
.party-section-hdr-row .party-section-label { margin: 0; }
.party-rename-btn--icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-3);
  font-size: var(--font-size-sm);
  line-height: 1;
  cursor: pointer;
  transition:
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease);
}
.party-rename-btn--icon:hover,
.party-rename-btn--icon:focus-visible {
  color: var(--accent-teal);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, var(--border));
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  outline: none;
  transform: translateY(-1px);
}
.party-rename-btn--icon:active {
  transform: translateY(0);
  transition-duration: var(--t-press);
}
/* Edit state — input + Save + Cancel inline. The "{Name} Assessment"
   eyebrow text is replaced with just "⚔️" + input so the input has
   room to breathe. */
.party-section-hdr-row--editing {
  flex-wrap: wrap;
}
.party-name-input {
  font: inherit;
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-1);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  min-width: 8rem;
  flex: 1 1 auto;
}
.party-name-input:focus {
  outline: none;
  border-color: var(--accent-teal);
}
.party-section-hdr-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}
.party-section-hdr-actions .btn-xs { white-space: nowrap; }
/* PC card top-right action button cluster — Refresh / AI / Import / Edit /
   skull/leave/rejoin / Treasury controls. Replaces a verbatim inline
   flex-stack that used to repeat 4× across renderPartyCard,
   renderPartyInventoryCard, and the no-DDB-data branch. Children get
   white-space: nowrap so multi-word labels never break mid-button. */
.party-pc-actions {
  display: flex;
  gap: 4px;
  align-items: flex-start;
  flex-shrink: 0;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.party-pc-actions > * { white-space: nowrap; }
/* Right-side cluster of the main nav: campaign name + Live pill share one
   flex row so they're vertically centered relative to each other (and to the
   nav as a whole), regardless of how many lines the campaign name takes. */
.nav-right {
  display: flex;
  align-items: center;
  align-self: stretch;
  flex-shrink: 0;
  margin-bottom: -1px; /* match the tab strip's bottom-border collapse */
  padding: 0 0.45rem;
}
/* With a cover image, the cluster takes a fixed card-sized width and paints the
   cover via background-image so it never affects nav height. Same dark gradient
   tint as campaign cards keeps the name and Live pill readable. */
.nav-right.has-cover {
  width: clamp(240px, 28vw, 360px);
  justify-content: flex-end;
  background-image:
    linear-gradient(180deg, rgba(11,15,20,0.78) 0%, rgba(11,15,20,0.55) 50%, rgba(11,15,20,0.82) 100%),
    var(--cover-img, linear-gradient(transparent, transparent));
  background-size: auto, cover;
  background-repeat: no-repeat, no-repeat;
  background-position: center, center;
}
/* Brighter campaign-name on the cover so it pops against the image */
.nav-right.has-cover .nav-campaign-name { color: rgba(255,255,255,0.92); }
/* v801: light-theme inverse — same parchment wash as .campaign-card--has-cover
   so the nav banner reads consistently with the campaign cards. White text
   is invisible against the bright wash, so flip nav-campaign-name to ink. */
html:not([data-theme="dark"]) .nav-right.has-cover {
  background-image:
    linear-gradient(180deg, rgba(252,247,237,0.70) 0%, rgba(252,247,237,0.48) 50%, rgba(252,247,237,0.74) 100%),
    var(--cover-img, linear-gradient(transparent, transparent));
}
html:not([data-theme="dark"]) .nav-right.has-cover .nav-campaign-name {
  color: var(--text);
}
/* Campaign name to the left of the Live pill. Mirrors .form-section-label
   eyebrow typography. Wraps to a second line if needed; only truncates past two. */
.nav-campaign-name {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-3);
  line-height: 1.2;
  text-align: right;
  margin: 0 0.6rem 0 0.4rem;
}
.nav-campaign-name .ncn-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-width: 22ch;
  word-break: break-word;
}
.nav-campaign-name:has(.ncn-text:empty) { display: none; }
/* (Retired in v743) .party-section-hdr-standalone existed to undo the
   forbidden border-top + top margin that the old .fallen-section-hdr
   class applied. Now that headers compose .form-section-label directly,
   no override is needed — first-child margin reset lives on
   .party-section-hdr / .party-section-label. */
/* ── Party Treasury Card ────────────────────────────────────────────────── */
.party-treasury-card { border-color: var(--border); }
.party-treasury-portrait { cursor: default; }
.party-treasury-portrait::after { display: none; }

.party-currency-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem 0.6rem;
  margin: 0.25rem 0;
}
.coin-group { display: inline-flex; align-items: baseline; gap: 0.2rem; }
.coin-badge {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); font-weight: 700;
  font-size: var(--font-size-sm); padding: 0.1rem 0.45rem;
}
.coin-label { font-size: var(--font-size-xs); font-weight: 600; color: var(--text-3); }
.coin-pp { background: color-mix(in srgb, #e2e8f0 18%, transparent); border: 1px solid #94a3b8; color: #94a3b8; }
.coin-gp { background: color-mix(in srgb, #9B855A 12%, transparent); border: 1px solid #9B855A; color: #9B855A; }
.coin-ep { background: color-mix(in srgb, var(--teal-soft) 15%, transparent); border: 1px solid var(--teal-soft); color: var(--teal-soft); }
.coin-sp { background: color-mix(in srgb, #64748b 18%, transparent); border: 1px solid #64748b; color: #94a3b8; }
.coin-cp { background: color-mix(in srgb, #c2763d 15%, transparent); border: 1px solid #c2763d; color: #c2763d; }
/* v798: coin chip colors are tuned for dark bg; on light theme darker
   variants preserve readability without breaking the dark treatment. */
html:not([data-theme="dark"]) .coin-pp { color: #475569; border-color: #94a3b8; }
html:not([data-theme="dark"]) .coin-gp { color: #6B5C3D; border-color: #6B5C3D; }
html:not([data-theme="dark"]) .coin-ep { color: var(--teal-text); border-color: var(--teal-text); }
html:not([data-theme="dark"]) .coin-sp { color: #475569; border-color: #64748b; }
html:not([data-theme="dark"]) .coin-cp { color: #8B5226; border-color: #8B5226; }
.coin-total {
  display: inline-flex; align-items: baseline; gap: 0.2rem;
  margin-left: 0.4rem; padding-left: 0.6rem;
  border-left: 1px solid var(--color-border);
}

/* Dead PC — compendium row */
.entity-row--dead .entity-name {
  color: var(--text-3);
  text-decoration: line-through;
  text-decoration-color: var(--text-3);
}
.entity-icon--dead {
  filter: grayscale(100%) brightness(0.45);
}
.entity-full-img--dead {
  filter: grayscale(100%) brightness(0.45);
}
.pc-dead-badge {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--text-3);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
  margin-left: 4px;
  vertical-align: middle;
}
.entity-row--inactive .entity-name {
  color: var(--text-3);
  font-style: italic;
}
.pc-left-badge {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--accent-amber);
  background: color-mix(in srgb, var(--accent-amber) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-amber) 40%, transparent);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
  margin-left: 4px;
  vertical-align: middle;
}
.party-pc-portrait {
  flex-shrink: 0;
  width: 160px;
  aspect-ratio: 1 / 1;
  overflow: hidden;
}
.party-pc-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--portrait-x, 50%) center;
  display: block;
}

/* ✂ Crop button — appears on GM hover, bottom-right */
.portrait-crop-btn {
  position: absolute;
  bottom: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-md);
  background: rgba(0,0,0,0.65);
  color: #fff;
  border: 1px solid rgba(255,255,255,0.2);
  font-size: var(--font-size-sm);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-hover), background var(--t-hover);
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
}
.party-pc-portrait--editable:hover .portrait-crop-btn, .party-pc-portrait--editable:focus-within .portrait-crop-btn { opacity: 1; }
.portrait-crop-btn:hover, .portrait-crop-btn:focus-visible { background: rgba(0,0,0,0.85); }

/* Crop mode — suppress camera overlay, change cursor */
.party-pc-portrait.portrait-cropping::after { opacity: 0 !important; pointer-events: none; }
.party-pc-portrait.portrait-cropping .portrait-crop-btn { display: none; }
.party-pc-portrait.portrait-cropping img {
  cursor: ew-resize;
  will-change: object-position;
  user-select: none;
  -webkit-user-drag: none;
}
.party-pc-portrait.portrait-cropping {
  outline: 2px solid var(--color-brand);
  outline-offset: -2px;
  touch-action: none;
}

/* Confirm / cancel bar shown during crop mode */
.portrait-crop-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px;
  background: rgba(0,0,0,0.72);
  z-index: 6;
}
.portrait-crop-bar.hidden { display: none; }
.portrait-crop-bar-hint {
  flex: 1;
  font-size: 0.62rem;
  color: rgba(255,255,255,0.65);
  white-space: nowrap;
}
.portrait-crop-confirm-btn,
.portrait-crop-cancel-btn {
  background: none;
  border: none;
  color: #fff;
  font-size: var(--font-size-md);
  line-height: 1;
  cursor: pointer;
  padding: 2px 5px;
  border-radius: var(--radius-sm);
  transition: background var(--t-hover);
}
.portrait-crop-confirm-btn:hover, .portrait-crop-confirm-btn:focus-visible { background: rgba(34,197,94,0.4); }
.portrait-crop-cancel-btn:hover, .portrait-crop-cancel-btn:focus-visible  { background: rgba(239,68,68,0.4); }
.party-pc-portrait-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 3rem;
  background: var(--bg-3);
  /* Class-icon color injected via inline `style="--entity-color:..."`
     (canonical CSS-var injection pattern, replaces `style="color:..."`
     direct colour). Falls back to secondary text colour. */
  color: var(--entity-color, var(--text-2));
}
/* v966: empty-grid CTA — left-aligned to match canonical inline-hint
   position (matches the Weave Fates "No threads selected yet…" line
   and every other empty/hint placeholder app-wide). Padding nudges
   the hint off the absolute tab edge for breathing room; buttons sit
   below the hint with the same left alignment. No centering. */
.party-empty-cta {
  padding: var(--space-2);
}
.party-empty-cta-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}
.party-pc-stats {
  flex: 1;
  padding: 0.75rem 1rem;
  min-width: 0;
  overflow: hidden;
}
.party-pc-name-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 0.15rem;
  gap: 0.5rem;
}
.party-pc-name-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--accent-blue);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-align: left;
}
.party-pc-name-btn:hover, .party-pc-name-btn:focus-visible { color: var(--text); }
.party-pc-no-data {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
  color: var(--text-3);
  font-size: var(--font-size-base);
}

/* ── Party tag pills (senses, languages, proficiencies, tools) ── */
/* Two-column table layout: fixed label col | flex pill col */
.party-tags-row {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  column-gap: 0.45rem;
  align-items: start;
}
/* Label cell — right-aligned, section-label style, | seam as right border */
.party-tags-row > .ddb-row-label {
  text-align: right;
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-3);
  white-space: nowrap;
  margin-right: 0;
  padding-top: 3px;         /* nudge to align with pill cap-height */
  padding-right: 0.3rem;
  border-right: 1px solid var(--split-color);
}
/* Pill cell */
.party-tags-wrap { display: flex; flex-wrap: wrap; gap: 3px; }
.pill-section-gap {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0.45rem 0;
  pointer-events: none;
  user-select: none;
}
.pill-section-gap::after {
  content: '';
  display: block;
  width: 45%;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    var(--border, rgba(255,255,255,0.12)) 20%,
    var(--border, rgba(255,255,255,0.12)) 80%,
    transparent
  );
}
.party-trait-tag,
.party-unique-tag {
  display: inline-block;
  font-size: var(--font-size-xs);
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid;
  line-height: 1.5;
  white-space: nowrap;
}
/* Normal tag — subtle */
.party-trait-tag {
  background: var(--bg-3);
  border-color: var(--border);
  color: var(--text-2);
}
/* Unique tag — amber/gold highlight */
.party-unique-tag {
  background: #ca8a0420;
  border-color: #ca8a04;
  color: var(--accent-gold);
  font-weight: 600;
}
/* Skill pills grid — strict 3 × 6 layout */
.skill-pills-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 3px;
  margin-bottom: 0.25rem;
}
.skill-pills-grid .skill-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--bg-3);
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  padding: 0.18rem 0.25rem;
  font-size: 0.67rem;
  line-height: 1.3;
  text-align: center;
  transition: background var(--t-hover), border-color var(--t-hover);
  white-space: nowrap;
}
.skill-pill .sp-name {
  font-size: 0.6rem;
  font-weight: 400;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.skill-pill .sp-val {
  font-weight: 700;
  color: var(--text);
  font-size: var(--font-size-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
/* Advantage badge on skill pills and initiative */
.sp-adv {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-blue);
  color: var(--color-white);
  border-radius: var(--radius-sm);
  font-size: 0.5rem;
  font-weight: 700;
  padding: 0 2px;
  line-height: 1.4;
  flex-shrink: 0;
}
/* Best or tied for best — dark green. Same token-driven palette as the
   .ddb-ab-best ability cells and the .pleg-swatch--best legend chip. */
.skill-pill.sp-best, .skill-pill.sp-tied {
  background: color-mix(in srgb, var(--accent-green) 18%, transparent);
  border-color: var(--accent-green);
}
.skill-pill.sp-best .sp-name, .skill-pill.sp-tied .sp-name { color: color-mix(in srgb, var(--accent-green) 80%, white); }
.skill-pill.sp-best .sp-val,  .skill-pill.sp-tied .sp-val  { color: var(--accent-green); }
/* Within 1 of best — yellow-green. */
.skill-pill.sp-near {
  background: color-mix(in srgb, var(--accent-green) 30%, var(--accent-amber) 12%);
  border-color: color-mix(in srgb, var(--accent-green) 60%, var(--accent-amber));
}
.skill-pill.sp-near .sp-name { color: color-mix(in srgb, var(--accent-green) 60%, var(--accent-amber)); }
.skill-pill.sp-near .sp-val  { color: color-mix(in srgb, var(--accent-green) 50%, var(--accent-amber)); }

/* ── Key Spells ──────────────────────────────────────────────────────────── */
.key-spells-section {
  margin-top: 0.4rem;
}
.key-spells-header {
  margin-bottom: 0.25rem;
}
.key-spells-body {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.key-spell-pill {
  display: inline-flex; align-items: center; gap: 3px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1px 6px;
  font-size: var(--font-size-xs);
  color: var(--text-2);
  white-space: nowrap;
}
.key-spell-fav {
  border-color: #7c3aed;
  background: #4c1d9518;
  color: #7c3aed;
  font-weight: 600;
}
.spell-use-count {
  display: inline-flex; align-items: center; justify-content: center;
  background: #7c3aed; color: #fff;
  border-radius: var(--radius-lg); font-size: 0.58rem; font-weight: 700;
  padding: 0 4px; min-width: 14px; height: 13px;
}

/* ── Spell metadata pills ──────────────────────────────────────────────── */
.spell-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
  margin-bottom: var(--space-2);
}
.spell-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: 600;
  border: 1px solid transparent;
}
.spell-pill--level {
  background: color-mix(in srgb, var(--color-text-tertiary) 12%, transparent);
  border-color: color-mix(in srgb, var(--color-text-tertiary) 25%, transparent);
  color: var(--color-text-secondary);
}
.spell-pill--school {
  background: color-mix(in srgb, var(--spell-school-color) 12%, transparent);
  border-color: color-mix(in srgb, var(--spell-school-color) 30%, transparent);
  color: var(--spell-school-color);
}
.spell-pill--conc {
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
  color: var(--color-warning);
}
.spell-pill--ritual {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
  color: var(--color-success);
}

/* ── Item rarity pills ─────────────────────────────────────────────────── */
.item-pills { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: var(--space-2); }
.item-pill {
  display: inline-flex; align-items: center;
  font-size: var(--font-size-xs); font-weight: 600;
  border-radius: var(--radius-full); padding: 0.12rem 0.5rem;
  border: 1px solid transparent;
}
.item-pill--common     { background: color-mix(in srgb, var(--color-text-tertiary) 12%, transparent); border-color: color-mix(in srgb, var(--color-text-tertiary) 25%, transparent); color: var(--color-text-secondary); }
.item-pill--uncommon   { background: color-mix(in srgb, var(--color-success) 12%, transparent);       border-color: color-mix(in srgb, var(--color-success) 30%, transparent);       color: var(--color-success); }
.item-pill--rare       { background: color-mix(in srgb, var(--color-info) 12%, transparent);          border-color: color-mix(in srgb, var(--color-info) 30%, transparent);          color: var(--color-info); }
.item-pill--very-rare  { background: color-mix(in srgb, var(--color-purple) 12%, transparent);        border-color: color-mix(in srgb, var(--color-purple) 30%, transparent);        color: var(--color-purple); }
.item-pill--legendary  { background: color-mix(in srgb, var(--color-gold) 12%, transparent);          border-color: color-mix(in srgb, var(--color-gold) 30%, transparent);          color: var(--color-gold); }
.item-pill--artifact   { background: color-mix(in srgb, var(--color-copper) 12%, transparent);        border-color: color-mix(in srgb, var(--color-copper) 30%, transparent);        color: var(--color-copper); }

/* ── Item rarity colours (text-only) ──────────────────────────────────────
   Applied alongside any element to tint just its text by the standard D&D
   rarity palette. Used by:
     • Codex item rows — adds a "Rare" / "Very Rare" / etc. badge next to
       the "Item" type tag (item type stays its canonical ember colour).
     • Party tab inventory tags — colours the item NAME inside the trait
       tag without changing the pill's background or border.
   Common / Mundane intentionally omitted (no tint applied). */
.item-rarity-uncommon  { color: var(--color-success); }
.item-rarity-rare      { color: var(--color-info); }
.item-rarity-very-rare { color: var(--color-purple); }
.item-rarity-legendary { color: var(--color-gold); }
.item-rarity-artifact  { color: var(--color-copper); }

/* PC Chronicle (.pc-chronicle-*) classes removed 2026-05-06: the
   collapsible per-session-blurbs UI was retired in favor of the always-
   comprehensive PC description (regenerated each recap from the same
   blurbs source). The .pc-quote-* family below stays — quotes still
   render in the entity-quotes panel via a separate component. */

/* Section headers inside PC entity descriptions ("## Header" lines from
   the chronicle prompt). Composes .session-beat-header for the typography
   (uppercase, weight 700, teal); this rule tightens the margin to fit
   inside the entity-card context. The session-beat version is sized for
   full-page session summaries with more breathing room. */
.entity-section-header {
  margin: 1rem 0 0.35rem;
}
.entity-desc .entity-section-header:first-child {
  margin-top: 0.25rem;
}

/* Quotes inside PC entity rows (kept) */
.pc-quote-row {
  margin-top: 0.35rem; padding-left: 0.6rem;
  border-left: 2px solid var(--border);
}
.pc-quote-text {
  font-size: var(--font-size-sm); font-style: italic; color: var(--text-2);
  margin: 0; quotes: none;
}
.pc-quote-ctx {
  display: block; font-size: var(--font-size-xs); color: var(--text-3);
  margin-top: 0.1rem;
}
/* Reassign quote button + inline picker */
.pc-quote-reassign-btn {
  font-size: var(--font-size-xs); padding: 1px 6px;
  margin-top: 3px; color: var(--text-3);
}
.pc-quote-reassign-btn:hover, .pc-quote-reassign-btn:focus-visible { color: var(--accent-teal); }
.pc-quote-reassign-picker {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 3px;
}
.pc-quote-reassign-sel {
  font-size: var(--font-size-xs);
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 1px 4px;
}
.pc-quote-reassign-ok, .pc-quote-reassign-cancel {
  font-size: var(--font-size-xs); padding: 1px 6px;
}
.pc-quote-reassign-ok:not(:disabled) { color: var(--accent-teal); }
/* Quote action button group (edit / reassign / delete) */
.pc-quote-actions {
  display: inline-flex; align-items: center; gap: 2px; margin-top: 3px;
}
.pc-quote-edit-btn, .pc-quote-delete-btn {
  font-size: var(--font-size-xs); padding: 1px 5px; color: var(--text-3);
}
.pc-quote-edit-btn:hover, .pc-quote-edit-btn:focus-visible  { color: var(--accent-teal); }
.pc-quote-delete-btn:hover, .pc-quote-delete-btn:focus-visible { color: var(--accent-red); }
/* Inline quote edit form */
.pc-quote-edit-form {
  display: flex; flex-direction: column; gap: 4px; margin-top: 4px;
}
.pc-quote-edit-text {
  font-size: var(--font-size-sm); font-style: italic;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 4px 6px;
  color: var(--text); resize: vertical; width: 100%; box-sizing: border-box;
}
.pc-quote-edit-ctx {
  font-size: var(--font-size-xs); background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 3px 6px; color: var(--text-2); width: 100%; box-sizing: border-box;
}
.pc-quote-edit-actions {
  display: flex; gap: 4px;
}
.pc-quote-edit-save  { font-size: var(--font-size-xs); padding: 1px 6px; color: var(--accent-teal); }
.pc-quote-edit-cancel { font-size: var(--font-size-xs); padding: 1px 6px; }


/* ── Companion section (party cards & compendium) ────────────────────────── */
.pc-companions-section {
  margin-top: 0.55rem;
  border-top: 1px solid var(--border);
  padding-top: 0.45rem;
}
.pc-companions-hdr {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 0.3rem;
}
.companion-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.companion-chip-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-xs);
  padding: 2px 8px;
  background: var(--bg-3);
  border: 1px solid var(--accent-teal);
  border-radius: var(--radius-2xl);
  color: var(--accent-teal);
  cursor: pointer;
  white-space: nowrap;
}
.companion-chip-btn:hover, .companion-chip-btn:focus-visible {
  background: color-mix(in srgb, var(--accent-teal) 13%, transparent);
}
.companion-chip-img {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  object-fit: cover;
}
.companion-none {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  font-style: italic;
}
.compendium-companions-section {
  margin: 0.4rem 0 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.pc-companion-of-badge {
  font-size: 0.65rem;
  font-weight: 600;
  background: color-mix(in srgb, var(--accent-teal) 13%, transparent);
  color: var(--accent-teal);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
  margin-left: 4px;
  white-space: nowrap;
}

/* ── Merge modal ─────────────────────────────────────────────────────────── */
.merge-pair {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.25rem;
}
.merge-card {
  flex: 1;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.6rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.merge-card-a { border-color: var(--accent-teal); }
.merge-card-b { border-color: var(--border); }
.merge-card-label {
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
}
.merge-card-name {
  font-size: var(--font-size-md);
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.merge-card-type { font-size: var(--font-size-xs); color: var(--text-3); }
.merge-card-placeholder { font-size: var(--font-size-base); color: var(--text-3); font-style: italic; }
.merge-arrow { font-size: 1.2rem; color: var(--text-3); flex-shrink: 0; user-select: none; }
.merge-search-results {
  /* Inline flow (not absolute) so the modal's overflow-y: auto doesn't clip it */
  position: relative;
  z-index: 10;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin-top: 0.25rem;
  max-height: 13.75rem;
  overflow-y: auto;
  box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.merge-search-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
  gap: 0.5rem;
  transition: background var(--t-hover);
}
.merge-search-item:hover, .merge-search-item:focus-visible { background: var(--bg-2); }
.merge-search-name { font-size: var(--font-size-base); color: var(--text); }
.merge-search-type { font-size: var(--font-size-xs); font-weight: 500; flex-shrink: 0; }
/* v965: .merge-search-empty inherits canonical .placeholder typography. */
.merge-search-empty { padding: 0.5rem 0.75rem; }
.merge-survivor-opts {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.25rem;
}
.merge-survivor-opt {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  font-size: var(--font-size-base);
  color: var(--text);
  transition: border-color var(--t-hover), background var(--t-hover);
  flex: 1;
}
.merge-survivor-opt:has(input:checked) {
  border-color: var(--accent-teal);
  background: rgba(47,124,133,0.08);
}
.merge-survivor-opt input { accent-color: var(--accent-teal); }
.merge-survivor-hint { font-size: var(--font-size-sm); color: var(--text-3); margin-top: 0.35rem; }
/* Merge modal layout — replaces inline style="margin-top:..." patches that
   sprinkled raw rems through the markup. All distances snap to --space-*. */
.merge-modal-intro {
  color: var(--text-2);
  font-size: var(--font-size-sm);
  margin-bottom: var(--space-4);
}
.merge-search-row    { margin-top: var(--space-4); position: relative; }
.merge-survivor-row  { margin-top: var(--space-3); }
.merge-error         { margin-top: var(--space-2); }
.merge-actions       { margin-top: var(--space-4); }
.merge-loading       { justify-content: center; padding: var(--space-4) 0; }

/* ── Campaign selector ──────────────────────────────────────────────────────── */
#campaign-selector { overflow-y: auto; }

.campaign-selector-wrap {
  max-width: 960px;
  margin: 0 auto;
  padding: 0.35rem 1.25rem 2rem;
}
/* Bump container width at the 3-col breakpoint so each card has a sensible
   16:9 footprint instead of being squashed into 1/3 of 960px. */
@media (min-width: 1200px) {
  .campaign-selector-wrap { max-width: 1400px; }
}
.cs-actions-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
/* "+ New" pill — used on every tab toolbar's primary "create new thing" CTA
   (campaign, session, quest, PC, codex entity). White-outlined, calm at rest,
   lifts and glows on hover with the same kinetics as the campaign cards.
   Reserved for create-NEW actions; modal Save/Confirm buttons keep .btn-primary. */
/* v791: full-width modifier for the join-campaign Join button. Replaces
   inline style="width:100%;margin-top:0.25rem". */
.join-camp-submit-btn {
  display: flex;
  width: 100%;
  margin-top: 0.25rem;
  justify-content: center;
}

/* v798: .btn-new now follows the project rule — base rule is light-correct,
   [data-theme="dark"] adds the white-rgba ghost recipe. Pre-v798 the base
   used rgba(255,255,255,*) which made every Save / Add / Send button across
   the app invisible in light mode (~25 surfaces). Dual-selector list keeps
   submit-button specificity (button[type="submit"] = 0,0,1,1). */
.btn-new,
button.btn-new {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 1rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  color: var(--text-1);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease);
}
.btn-new:hover, .btn-new:focus-visible,
button.btn-new:hover, button.btn-new:focus-visible {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 50%, transparent);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--accent-teal) 18%, transparent);
  transform: translateY(-1px);
}
.btn-new:active,
button.btn-new:active {
  transform: translateY(0);
  transition-duration: var(--t-press);
}
/* Dark theme restores the original white-rgba ghost-pill recipe */
[data-theme="dark"] .btn-new,
[data-theme="dark"] button.btn-new {
  color: rgba(255,255,255,0.92);
  background: rgba(255,255,255,0.05);
  border-color: rgba(255,255,255,0.32);
}
[data-theme="dark"] .btn-new:hover, [data-theme="dark"] .btn-new:focus-visible,
[data-theme="dark"] button.btn-new:hover, [data-theme="dark"] button.btn-new:focus-visible {
  color: #fff;
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 14px 2px rgba(255,255,255,0.18);
}
/* v813: legacy `.btn-new--danger` (pill, fills red on hover) retired —
   canonical squared red-ghost recipe lives in the early btn block (~line 487).
   `.btn-new.btn-new--danger` now renders identical to `.btn-ghost.btn-ghost--danger`
   so every Delete / Remove / warning button across card actions, modals,
   roster, and map popups looks the same. */

/* ── Global Sort / Filter icon-buttons ────────────────────────────────────
   One canonical look for "sort" and "filter" triggers across the whole app.
   Same shape (rectangular ghost, mirroring the campaign page's "Sort:" pill
   in its original .btn-ghost.btn-sm form) and same kinetics — only the
   leading symbol differs.
     • .btn-sort   → Campaign sort, Codex sort
     • .btn-filter → Codex In-Play / All-Entries toggle
   The icon is rendered via a mask-image ::before so it inherits currentColor
   for free (active states, hover states, role variants all just work). */
.btn-sort,
.btn-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.65rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--text-2);
  background: none;
  border: 1px solid var(--border);
  border-radius: 0;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease);
}
.btn-sort:hover, .btn-sort:focus-visible,
.btn-filter:hover, .btn-filter:focus-visible {
  background: var(--bg-3);
  color: var(--text-1);
}
.btn-sort::before,
.btn-filter::before {
  content: '';
  display: inline-block;
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
  flex-shrink: 0;
}
/* Sort icon — three descending horizontal lines (sort hierarchy) */
.btn-sort::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'><line x1='3' y1='6' x2='21' y2='6'/><line x1='6' y1='12' x2='18' y2='12'/><line x1='9' y1='18' x2='15' y2='18'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'><line x1='3' y1='6' x2='21' y2='6'/><line x1='6' y1='12' x2='18' y2='12'/><line x1='9' y1='18' x2='15' y2='18'/></svg>");
}
/* Filter icon — funnel/hopper */
.btn-filter::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round' stroke-linecap='round'><polygon points='22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round' stroke-linecap='round'><polygon points='22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3'/></svg>");
}
/* Active variants — teal counterpart of the rectangular ghost. Subtle so
   it reads as "this filter is on" without competing with the page content.
   Theme-driven via var(--accent-teal) (matches .type-filter-btn.active). */
.btn-sort.active,
.btn-filter.active {
  color: #e6f6f8;
  background: color-mix(in srgb, var(--accent-teal) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 65%, transparent);
}
.btn-sort.active:hover, .btn-sort.active:focus-visible,
.btn-filter.active:hover, .btn-filter.active:focus-visible {
  color: #fff;
  background: color-mix(in srgb, var(--accent-teal) 28%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 85%, transparent);
}

.campaign-selector-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.6rem;
  gap: 1rem;
  flex-wrap: wrap;
}
.campaign-selector-logo {
  display: block;
  width: 100%;
  max-width: 300px;
  height: auto;
  /* In 2-col mode, nudge left so the Thread|fall wordmark lines up with the
     50% column divider. In 3-col mode (≥1200px) we use 2 dividers at 33% / 66%,
     so the brand-mark alignment trick no longer applies — center geometrically. */
  transform: translateX(-27px);
  margin: 0 auto;
  filter: drop-shadow(0 0 1px rgba(20,20,20,0.95)) drop-shadow(0 0 2px rgba(20,20,20,0.80)) drop-shadow(0 0 4px rgba(20,20,20,0.50));
}
@media (min-width: 1200px) {
  .campaign-selector-logo { transform: none; }
}
/* (v787) .campaign-selector-title retired — eyebrow now composes
   .form-section-label like every in-tab eyebrow. Text changed from
   "Your Campaigns" to "Select Campaign" to match the verb-led pattern. */


/* Wrapper for the 2-col divider line — ::before on a grid container
   would be treated as a grid item, so this wrapper owns the pseudo-element */
.campaign-grid-wrap {
  position: relative;
}
/* The | dividers between columns. 2-col: one at 50% (matches Thread|fall brand mark).
   3-col (≥1200px): two at 33.33% and 66.66%. Both lines use the same gradient
   for consistency across breakpoints. The gradient fades top/bottom so the lines
   feel atmospheric rather than ruled. */
.campaign-grid-wrap::before,
.campaign-grid-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(
    to bottom,
    transparent        0%,
    rgba(210,225,248,0.10)  6%,
    rgba(210,225,248,0.32) 25%,
    rgba(210,225,248,0.42) 50%,
    rgba(210,225,248,0.32) 75%,
    rgba(210,225,248,0.10) 94%,
    transparent       100%
  );
  pointer-events: none;
  z-index: 0;
}
.campaign-grid-wrap::before { left: calc(50% - 0.5px); }
/* ::after only used at the 3-col breakpoint */
.campaign-grid-wrap::after { display: none; }
@media (min-width: 1200px) {
  /* Gap-aware positioning: with 3 equal columns and a 1.5rem (24px) gap, the
     true gap centers are NOT at 33.333% / 66.666% — they're offset 4px toward
     each other (gap-width / 6 ≈ 0.25rem). Without this nudge the dividers
     sit slightly inside the middle column instead of cleanly between columns. */
  .campaign-grid-wrap::before { left: calc(33.333% - 4.5px); }
  .campaign-grid-wrap::after  { display: block; left: calc(66.666% + 3.5px); }
}
.campaign-list-screen {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem 1.5rem;
}
/* Wide-screen density: at ≥1200px viewport the selector container has room for
   3 cards per row without making them too small to read. */
@media (min-width: 1200px) {
  .campaign-list-screen { grid-template-columns: repeat(3, 1fr); }
}
/* Group eyebrow: spans the full grid row so cards below it flow underneath the
   header naturally. Only emitted when sorting by Last Session AND both buckets
   have cards — see _renderCampaignCards. */
.cs-group-eyebrow {
  grid-column: 1 / -1;
  margin-top: 0.5rem;
}
.cs-group-eyebrow:first-child { margin-top: 0; }
/* Skeleton placeholder card — same dimensions as real cards, shimmer animation. */
.campaign-card--skeleton {
  cursor: default;
  pointer-events: none;
  background: linear-gradient(90deg, var(--bg-2) 0%, var(--bg-3) 50%, var(--bg-2) 100%);
  background-size: 200% 100%;
  animation: skel-shimmer 1.4s ease-in-out infinite;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.9rem;
}
.campaign-card--skeleton::before, .campaign-card--skeleton::after { content: none; }
.campaign-card--skeleton .skel-line {
  display: block;
  height: 0.7rem;
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.08);
}
.campaign-card--skeleton .skel-line--name { width: 60%; height: 0.95rem; }
.campaign-card--skeleton .skel-line--gm   { width: 45%; height: 0.7rem; }
.campaign-card--skeleton .skel-line--meta { width: 70%; height: 0.6rem; }
@keyframes skel-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.campaign-card {
  display: flex;
  flex-direction: column;
  background-color: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 0.7rem 0.9rem;
  gap: 0.3rem;
  transition: border-color var(--t-hover) ease, box-shadow var(--t-hover) ease, transform var(--t-hover) ease;
  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
  /* Match the 16:9 aspect ratio of cover images so the art fills without cropping. */
  aspect-ratio: 16 / 9;
  /* Whole card is clickable — wired in JS. Without this affordance users only
     discover the small Enter button. */
  cursor: pointer;
  /* Footer + role-badge are absolute-positioned children — establish containing block. */
  position: relative;
}
.campaign-card:hover, .campaign-card:focus-within {
  border-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}
.campaign-card-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  /* Reserve space at the bottom for the absolute-positioned footer (chevron + gear). */
  padding-right: 60px;
  padding-bottom: 28px;
}
.campaign-card-name {
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--text);
  /* Allow up to 2 lines so long campaign names don't truncate to "Phandelver…". */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.25;
  word-break: break-word;
}
.campaign-card-gm {
  font-size: var(--font-size-sm);
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.campaign-card-meta {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin-top: 0.5rem;
  width: fit-content;
}
.campaign-card-sessions {
  color: var(--text-2);
  font-weight: var(--font-weight-semibold);
}
/* PC name chips — 3-col grid, 2 rows for ≤6 PCs, 3 rows when more. Names that
   overflow a cell truncate with ellipsis. Constrained width keeps the right
   center of the cover image visible. */
.campaign-card-pcs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  margin-top: 2px;
  /* 2/3 of the card-info content area — leaves the right side of the cover open. */
  width: 66%;
}
/* v798: theme-correct base + dark override. */
.campaign-card-pc {
  font-size: var(--font-size-xs);
  color: var(--text-2);
  background: color-mix(in srgb, var(--text-1) 5%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  padding: 1px 0.5rem;
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
}
.campaign-card-pc--more {
  color: var(--text-3);
  background: transparent;
  border-color: var(--border);
  font-weight: var(--font-weight-semibold);
}
[data-theme="dark"] .campaign-card-pc {
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.10);
}
[data-theme="dark"] .campaign-card-pc--more { border-color: rgba(255,255,255,0.18); }
/* v799: footer container overlays the 70×70 watermark sigil at the
   bottom-right of the card. The chevron sits flex-centered inside, which
   places it on top of the diamond (the sigil's central path).
   Watermark is at `right 6px bottom 6px` size 70×70 (line ~8014); we
   match those coordinates exactly so the chevron tracks the diamond.
   pointer-events: none lets card-level clicks fall through to the
   card's own handler — the chevron is purely a visual cue. */
.campaign-card-footer {
  position: absolute;
  bottom: 6px;
  right: 6px;
  width: 70px;
  height: 70px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
/* Role badge anchored to the top-right corner — an at-a-glance signal of "what am I in this campaign?" */
.campaign-card-role {
  position: absolute;
  top: 0.6rem;
  right: 0.7rem;
  margin-left: 0;
  z-index: 1;
}
/* Live Now badge — promoted to top-left when present so it reads as a status
   chip rather than another line of meta text inside the info stack. */
/* v786: live badge moved to the LOWER-left so it never overlaps the title
   (the top-left placement collided with .campaign-card-name's first line
   on most cards). The bottom-left slot sits in the same horizontal band
   as the chevron (bottom-right) inside the .campaign-card-info's reserved
   `padding-bottom: 28px`, so it doesn't fight any other content. */
.campaign-card .campaign-live-badge {
  position: absolute;
  bottom: 0.55rem;
  left: 0.8rem;
  margin-top: 0;
  background: rgba(11,15,20,0.55);
  border: 1px solid color-mix(in srgb, var(--color-gold) 38%, transparent);
  border-radius: var(--radius-full);
  padding: 2px 0.55rem;
  z-index: 1;
}
/* v799: chevron is now sized to fill the watermark's diamond path. At rest
   it's muted gold so it reads as part of the sigil; on hover it lights up
   (full saturation + halo glow), scales up, and slides right — the same
   "telegraph the click target" intent, dramatized to match the new size. */
.campaign-enter-chevron {
  flex-shrink: 0;
  font-size: 3.6rem;
  line-height: 1;
  color: var(--color-gold);
  opacity: 0.55;
  /* The `›` glyph (U+203A) carries asymmetric side-bearings in the system
     sans stack — the visible apex sits in the right half of its advance
     box, so flex-centering alone parks the visual mass right of the
     diamond's true center. Pull LEFT a hair to land the apex on (50,50)
     of the watermark sigil. The translateY(-1px) compensates for the
     glyph's baseline-relative vertical bias (sits a touch low at line-height: 1). */
  transform: translate(14px, -3px);
  transition:
    color var(--t-hover) var(--ease),
    opacity var(--t-hover) var(--ease),
    transform var(--t-hover) var(--ease),
    text-shadow var(--t-hover) var(--ease),
    filter var(--t-hover) var(--ease);
  user-select: none;
}
.campaign-card:hover .campaign-enter-chevron,
.campaign-card:focus-within .campaign-enter-chevron {
  opacity: 1;
  color: var(--color-gold);
  /* Hover shift kept inside the diamond's right vertex — a 5px slide from
     the rest position telegraphs the click without the chevron escaping
     the sigil. Scale stays gentle for the same reason. */
  transform: translate(19px, -3px) scale(1.1);
  text-shadow:
    0 0 12px color-mix(in srgb, var(--color-gold) 60%, transparent),
    0 0 24px color-mix(in srgb, var(--color-gold) 30%, transparent);
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.25));
}
/* Light theme — slightly less translucent at rest so the chevron reads
   against the bright card surface without losing its "part of sigil" feel. */
html:not([data-theme="dark"]) .campaign-enter-chevron { opacity: 0.7; }
/* Settings gear — pinned to the top-right corner directly underneath the
   role badge, so it sits in the corner-stack with role badge above and is
   never crowded by long PC chip rows along the bottom. Explicit 28×28
   touch target keeps it reliably hittable on mobile. */
.camp-settings-btn {
  position: absolute;
  top: 2.1rem;
  right: 0.7rem;
  z-index: 1;
  width: 28px;
  height: 28px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* ── Compendium AI Sync button ───────────────────────────────────────────────── */
.compendium-progress {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  margin-top: 3px;
}
.compendium-progress.hidden { display: none; }

.entry-compendium-btn {
  color: var(--color-warning);
  border-color: var(--color-warning);
  background: linear-gradient(
    to right,
    color-mix(in srgb, var(--color-warning) 22%, transparent) var(--fill-pct, 0%),
    transparent var(--fill-pct, 0%)
  );
  transition: background var(--t-trans) ease, color var(--t-trans), border-color var(--t-trans);
}
.compendium-btn-done {
  color: var(--color-success);
  border-color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
}

/* ═══════════════════════════════════════════════════════════════════════════
   THREADFALL MOTION SYSTEM
   Consistent timing, hover lift, press feedback, focus states, entry anim.
   All rules here override earlier specifics — do not edit inline transitions.
═══════════════════════════════════════════════════════════════════════════ */

/* ── Entry keyframes ──────────────────────────────────────────────────── */
@keyframes tf-fade-up {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0);    }
}
@keyframes tf-modal-enter {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0)    scale(1);    }
}
@keyframes tf-slide-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0);   }
}

/* ── Refined live-mode pulses (gentler than originals) ────────────────── */
@keyframes rec-blink  { 0%,100%{ opacity:1 } 50%{ opacity:0.35 } }
@keyframes pulse       { 0%,100%{ opacity:1 } 50%{ opacity:0.55 } }
@keyframes rec-pulse   {
  0%,100%{ opacity:1; transform:scale(1)    }
  50%    { opacity:.7; transform:scale(1.04)}
}
/* (v795) @keyframes mic-pulse retired — only consumer was .live-mic-btn.recording, now orphan. */

/* ── Tab panel & screen entry ─────────────────────────────────────────── */
.tf-entering {
  animation: tf-fade-up var(--t-trans) var(--ease) both;
}
/* Fade-in whenever a tab panel becomes visible */
.tab-panel:not(.hidden) {
  animation: tf-fade-up var(--t-trans) var(--ease) both;
}
#live-session-screen:not(.hidden) {
  animation: tf-fade-up var(--t-trans) var(--ease) both;
}

/* ── Collapse chevron — universal rotation system ─────────────────────── */
/* Transcript panel */
.live-transcript-panel.transcript-collapsed #live-transcript-toggle { transform: rotate(-90deg); }
/* Speaker bar */
.live-spk-bar-wrap.spk-collapsed .live-spk-toggle { transform: rotate(-90deg); }
/* (Quest group header chevron rotation retired in v762 along with the
   .quest-group-* block — the JS no longer renders quest sub-groups.) */
/* (PC chronicle chevron rules retired 2026-05-06 — see PC chronicle CSS
   block above for context.) */

/* ── Modal entry ──────────────────────────────────────────────────────── */
.modal-overlay:not(.hidden) .modal-box {
  animation: tf-modal-enter var(--t-trans) var(--ease) both;
}

/* ── Toast slide ──────────────────────────────────────────────────────── */
#toast {
  transition:
    opacity  var(--t-trans) var(--ease),
    transform var(--t-trans) var(--ease);
}

/* ── Unified button transitions ───────────────────────────────────────── */
.btn-primary,
button[type="submit"],
.btn-ghost,
.btn-danger {
  transition:
    transform    var(--t-hover) var(--ease),
    opacity      var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}

/* v813: legacy teal recipes for `.btn-primary` / unclassed `button[type="submit"]`
   retired — ghost-pill recipe lives in the early btn block (~line 363).
   Canonical `.btn-ghost` hover + active lives next to its base rule and is no
   longer overridden here. */

/* ── Focus states (keyboard navigation) ──────────────────────────────── */
:focus-visible {
  outline: 2px solid var(--accent-teal);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(47,124,133,0.12);
  border-radius: var(--radius-sm);
}
/* Inputs get a tighter inline focus ring */
input[type="text"]:focus,
input[type="date"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
  outline: none;
  border-color: var(--accent-teal);
  box-shadow: 0 0 0 3px rgba(47,124,133,0.12);
  transition:
    border-color var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}

/* ── Tab buttons ──────────────────────────────────────────────────────── */
.tab-btn {
  transition:
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}

/* ── Filter pills ─────────────────────────────────────────────────────── */
/* .type-filter-btn defines its own richer transition (matches .btn-new)
   in the Codex toolbar block above; only .quest-filter-btn lives here. */
.quest-filter-btn {
  transition:
    background   var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}

/* ── Session cards ────────────────────────────────────────────────────── */
.session-card {
  transition:
    border-color var(--t-hover) ease,
    transform    var(--t-hover) ease,
    box-shadow   var(--t-hover) ease;
}
.session-card:hover, .session-card:focus-within {
  /* Pure parity with .campaign-card hover — teal-tinted border, gentle
     translateY lift, modest drop shadow. The old border-left color flip
     was removed so the hover state reads as one coherent motion. */
  border-color: rgba(47,154,163,0.45);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.session-card-hdr {
  transition: background var(--t-hover) var(--ease);
}

/* ── Campaign cards ───────────────────────────────────────────────────── */
.campaign-card {
  transition:
    border-color var(--t-hover) ease,
    transform    var(--t-hover) ease,
    box-shadow   var(--t-hover) ease;
}
.campaign-card:hover, .campaign-card:focus-within {
  border-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}

/* ── Entity / compendium rows ─────────────────────────────────────────── */
/* Canonical card hover — same recipe as .campaign-card and .session-card:
   shadowless at rest, lifts with translateY + teal-tinted border + drop
   shadow on hover/focus. */
.entity-row {
  transition:
    border-color var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
.entity-row:hover, .entity-row:focus-within {
  border-color: rgba(47,154,163,0.45);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}

/* ── Quest nav ────────────────────────────────────────────────────────── */
.quest-nav-row {
  transition:
    border-color var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
.quest-nav-hdr {
  transition: background var(--t-hover) var(--ease);
}
/* ── Login button ─────────────────────────────────────────────────────── */
.login-btn {
  transition:
    transform    var(--t-hover) var(--ease),
    opacity      var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
.login-btn:hover, .login-btn:focus-visible  { transform: translateY(-1px); box-shadow: 0 3px 10px rgba(0,0,0,0.28); }
.login-btn:active { transform: scale(0.98); transition-duration: var(--t-press); }

/* ── Merge survivor options ───────────────────────────────────────────── */
.merge-survivor-opt {
  transition:
    border-color var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease);
}

/* ── DDB party card cells ─────────────────────────────────────────────── */
.ddb-ab-cell,
.skill-pills-grid .skill-pill {
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}

/* ── Compendium AI sync button ────────────────────────────────────────── */
.entry-compendium-btn {
  transition:
    background   var(--t-trans) var(--ease),
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease) !important;
}

/* ── Live mode: subtle status pulse ──────────────────────────────────── */
/* Recording dot — already uses rec-blink, now gentler via keyframe above */
/* Active session indicator in live tab */
.live-active-indicator {
  animation: pulse 2s var(--ease) infinite;
}

/* ── Nav auth buttons ─────────────────────────────────────────────────── */
.nav-gm-btn,
.nav-logout-btn {
  transition:
    background   var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}

/* ── Theme toggle ─────────────────────────────────────────────────────── */
#theme-toggle {
  transition:
    background   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease);
}
#theme-toggle:hover, #theme-toggle:focus-visible { transform: rotate(15deg); }

/* ── Admin Panel ──────────────────────────────────────────────────────── */
/* Admin screen is controlled via style.display (not .hidden class) for reliability */
#admin-screen {
  position: fixed !important;
  inset: 0 !important;
  z-index: 10000 !important;
  background: var(--bg) !important;
  display: none;
  flex-direction: column;
  overflow-y: auto;
}
.admin-wrap {
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
}
.admin-sticky-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg-2);
}
.admin-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.5rem;
  border-bottom: 1px solid var(--border);
}
.admin-badge {
  font-size: var(--font-size-base);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 30%, transparent);
  border-radius: var(--radius-md);
  padding: 0.2rem 0.55rem;
}
.admin-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
  padding: 0 1.5rem;
  overflow-x: auto;
}
.admin-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-2);
  cursor: pointer;
  font-size: var(--font-size-base);
  font-weight: 500;
  padding: 0.65rem 1rem;
  white-space: nowrap;
  transition: color var(--t-hover), border-color var(--t-hover);
}
.admin-tab:hover, .admin-tab:focus-visible { color: var(--text); }
.admin-tab.active { color: var(--accent-teal); border-bottom-color: var(--accent-teal); }
.admin-tab-panel { padding: 1.5rem; flex: 1; }
.admin-actions-bar {
  display: flex; gap: 0.5rem; flex-wrap: wrap;
  margin-bottom: 1rem;
}
.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.admin-stat-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 1rem 1.25rem;
}
.admin-stat-card--accent {
  border-color: color-mix(in srgb, var(--accent-teal) 35%, transparent);
  background: color-mix(in srgb, var(--accent-teal) 5%, var(--bg-2));
}
.admin-stat-label { font-size: var(--font-size-xs); color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 0.35rem; }
.admin-stat-value { font-size: 1.6rem; font-weight: 700; color: var(--text); line-height: 1; }
.admin-stat-sub   { font-size: var(--font-size-sm); color: var(--text-3); margin-top: 0.3rem; }
.admin-table-wrap { overflow-x: auto; }
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-base);
}
.admin-table th {
  text-align: left;
  padding: 0.5rem 0.75rem;
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.admin-table td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
}
.admin-table tr:last-child td { border-bottom: none; }
.admin-table tr:hover td, .admin-table tr:focus-within td { background: var(--bg-2); }
/* ── Admin view-as bar ───────────────────────────────────────────────────── */
/* Admin panel version — compact row inside the pulse footer */
.admin-view-as {
  display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap;
  padding: 0.4rem 0.65rem 0.35rem;
  border-bottom: 1px solid color-mix(in srgb, var(--color-border) 50%, transparent);
  background: color-mix(in srgb, #5b21b6 18%, transparent);
  font-size: var(--font-size-xs);
}
.admin-va-label { font-weight: 600; color: #c4b5fd; letter-spacing: 0.02em; white-space: nowrap; }
#view-as-label { font-weight: 600; letter-spacing: 0.02em; }
.view-as-toggle {
  display: flex; border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--color-white) 30%, transparent);
}
.view-as-btn {
  background: transparent; border: none;
  border-right: 1px solid color-mix(in srgb, var(--color-white) 20%, transparent);
  color: color-mix(in srgb, var(--color-white) 65%, transparent);
  padding: 0.15rem 0.65rem;
  cursor: pointer; font-size: var(--font-size-xs); transition: background var(--t-hover);
}
.view-as-btn:last-child { border-right: none; }
.view-as-btn:hover, .view-as-btn:focus-visible { background: color-mix(in srgb, var(--color-white) 10%, transparent); color: var(--color-white); }
.view-as-btn.active { background: color-mix(in srgb, var(--color-white) 22%, transparent); color: var(--color-white); font-weight: 700; }

.admin-pill {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: 600;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-sm);
  line-height: 1.4;
}
.admin-pill--admin  { background: color-mix(in srgb, var(--accent-teal) 18%, transparent); color: var(--accent-teal); }
.admin-pill--ok     { background: color-mix(in srgb, #4ade80 15%, transparent); color: #4ade80; }
.admin-pill--warn    { background: color-mix(in srgb, #facc15 15%, transparent); color: #ca8a04; }
.admin-pill--blocked { background: color-mix(in srgb, var(--accent-red) 15%, transparent); color: var(--accent-red); }
.admin-pill--danger { background: color-mix(in srgb, var(--accent-red) 15%, transparent); color: var(--accent-red); }
.admin-section-hdr  { font-size: var(--font-size-sm); font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.06em; margin: 1.5rem 0 0.5rem; }
/* AI Usage tab two-column layout */
.admin-ai-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 0.5rem; }
.admin-ai-grid .admin-section-hdr:first-child { margin-top: 0; }
@media (max-width: 900px) { .admin-ai-grid { grid-template-columns: 1fr; } }
.admin-alert-card {
  background: var(--bg-2);
  border: 1px solid color-mix(in srgb, var(--accent-red) 30%, transparent);
  border-radius: var(--radius-lg);
  padding: 0.75rem 1rem;
  margin-bottom: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}
.admin-alert-card--info { border-color: var(--border); }
/* v965: .admin-empty inherits canonical .placeholder typography. Only
   padding survives — admin tables need vertical breathing room. */
.admin-empty { padding: var(--space-5) 0; }

/* ── Recording consent modal ─────────────────────────────────────────────── */
/* Uses the standard .modal-overlay + .modal-box hierarchy now. The only
   bespoke piece that remains is .modal-overlay--blocking for the heavier
   "you can't dismiss this casually" scrim — defined alongside .modal-overlay.
   Below: only the inner-content rules unique to consent UX (body, actions, badge). */

.consent-body {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}
.consent-body p {
  margin: 0;
  font-size: var(--font-size-base);
  line-height: 1.55;
  color: var(--text-2);
}
.consent-version {
  margin: 0;
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
.consent-actions {
  display: flex;
  gap: var(--space-3);
}
.consent-accept-btn {
  flex: 1;
  padding: 0.6rem 1rem;
  font-size: var(--font-size-md);
}
.consent-leave-btn {
  padding: 0.6rem 1rem;
  font-size: var(--font-size-md);
  color: var(--text-3);
}
.consent-leave-btn:hover, .consent-leave-btn:focus-visible { color: var(--text-1); }

/* Persistent badge shown after consent is given */
.consent-accepted-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--font-size-xs);
  color: var(--text-3);
  background: color-mix(in srgb, var(--text-1) 5%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  padding: 0.18rem 0.65rem;
  white-space: nowrap;
  user-select: none;
}
.consent-accepted-badge.hidden { display: none; }

/* ── Live presence indicators ────────────────────────────────────────────── */
@keyframes presence-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.35; }
}

/* Live tab — active state (other users are in Live) */
#live-nav-btn.live-active {
  color: var(--accent-green);
}
#live-nav-btn.live-active svg.tf-tab-mark use {
  /* inherit the green so the diamond marker also turns green */
  color: inherit;
}

/* Badge inside the Live tab button */
.live-presence-badge {
  display: inline-flex;
  align-items: center;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  background: color-mix(in srgb, var(--accent-green) 14%, transparent);
  color: var(--accent-green);
  border: 1px solid color-mix(in srgb, var(--accent-green) 30%, transparent);
  border-radius: var(--radius-full);
  padding: 0.06rem 0.45rem 0.06rem 0.3rem;
  margin-left: 0.3rem;
  gap: 0.2rem;
  white-space: nowrap;
  animation: presence-pulse 2.2s ease-in-out infinite;
}
.live-presence-badge::before {
  content: '';
  display: inline-block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.live-presence-badge.hidden { display: none; }

/* Campaign selector — live badge on card */
.campaign-live-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--accent-green);
  margin-top: 0.2rem;
}
.campaign-live-badge.hidden { display: none; }
/* Admin "browsing only" state — no pulsing, muted color */
.campaign-live-badge.pres-badge--browsing { color: var(--color-text-tertiary); }
.campaign-live-badge.pres-badge--browsing .camp-live-dot {
  background: var(--color-text-tertiary);
  animation: none;
}
.camp-live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-green);
  flex-shrink: 0;
  animation: presence-pulse 2.2s ease-in-out infinite;
}


/* ── Live log real-time sync ─────────────────────────────────────────────── */
@keyframes le-arrive {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: translateY(0); }
}
.live-log-entry--remote {
  animation: le-arrive 0.28s ease-out;
  border-left: 2px solid var(--accent-teal);
  padding-left: calc(0.65rem - 2px); /* compensate for border so content doesn't shift */
}
.live-log-entry[data-tag="system_event"] {
  border-left-color: var(--accent-teal);
  background: color-mix(in srgb, var(--color-brand) 7%, transparent);
  font-style: italic;
}
.live-log-entry--scan {
  animation: le-arrive 0.28s ease-out, le-scan-pulse 2.2s ease-out 0.28s forwards;
  border-left: 2px solid var(--accent-teal);
  padding-left: calc(0.65rem - 2px);
}
@keyframes le-scan-pulse {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--color-brand) 50%, transparent); }
  40%  { box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-brand) 20%, transparent); }
  100% { box-shadow: 0 0 0 0   transparent; }
}

/* ── Roster panel ────────────────────────────────────────────────────────── */
.roster-wrap {
  max-width: 860px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-5) var(--space-10);
}
.roster-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-5);
  gap: var(--space-4);
}
/* .roster-title rule retired in v775 — the eyebrow recipe now flows through
   .form-section-label composed in markup, no bespoke heading typography. */
.roster-new-btn { flex-shrink: 0; }

.roster-new-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
}
.roster-link-select {
  font-size: var(--font-size-base);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text-1);
  max-width: 220px;
}
.roster-inline-input {
  flex: 1;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-1);
  padding: var(--space-2) var(--space-2);
  font-size: var(--font-size-md);
  min-width: 0;
}
.roster-inline-input:focus { outline: none; border-color: var(--accent-teal); }
/* Compact override on the inline ✓/✕ buttons in the new-PC row. Composes
   .btn-primary / .btn-ghost; this class only tightens the padding so the
   buttons sit flush at the same height as the input. */
.roster-inline-btn {
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-size-base);
  flex-shrink: 0;
}

.roster-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-base);
}
.roster-table th {
  text-align: left;
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-3);
  padding: 0 var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--border);
}
.roster-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  vertical-align: middle;
}
.roster-table tr:last-child td { border-bottom: none; }
.roster-actions-col { width: 60px; text-align: center; }
/* Member-row actions cell — replaces inline style="white-space:nowrap" on
   the per-member <td> in renderRoster(). */
.roster-actions-td { white-space: nowrap; }
/* Member email tag inside the "Played By" label — replaces inline
   style="color:var(--text-3);font-size:0.8rem". */
.roster-member-email {
  color: var(--text-3);
  font-size: var(--font-size-sm);
}

.roster-name-display {
  cursor: pointer;
  color: var(--text-1);
  border-bottom: 1px dashed transparent;
  transition: border-color var(--t-hover);
}
.roster-name-display:hover, .roster-name-display:focus-visible { border-bottom-color: var(--accent-teal); }

.roster-assign-select {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-1);
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-size-base);
  max-width: 240px;
}
.roster-assign-select:focus { outline: none; border-color: var(--accent-teal); }

/* Compact delete-icon button in the actions column. Composes .btn-ghost;
   this class only resizes the icon and tints it tertiary by default,
   accent-red on hover (which would otherwise need .btn-ghost--danger). */
.roster-del-btn {
  font-size: var(--font-size-lg);
  padding: var(--space-1) var(--space-1);
  color: var(--text-3);
}
.roster-del-btn:hover, .roster-del-btn:focus-visible { color: var(--accent-red); }
.roster-del-locked { color: var(--text-3); font-size: var(--font-size-md); cursor: default; }

/* PC name in live log author row */
.le-author-pc {
  font-size: var(--font-size-sm);
  color: var(--accent-teal);
  font-weight: 400;
  margin-left: 0.15rem;
}

/* PC badge in profile modal */
.profile-pc-badge {
  display: inline-flex;
  align-items: center;
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: var(--color-success);
  border: 1px solid color-mix(in srgb, var(--color-success) 30%, transparent);
  border-radius: var(--radius-full);
  font-size: var(--font-size-sm);
  font-weight: 600;
  padding: 0.25rem 0.75rem;
  margin-bottom: 0.75rem;
}

/* Read-only player name display in entity edit form */
.ef-player-name-display {
  font-size: var(--font-size-base);
  color: var(--text-2);
  padding: 0.35rem 0;
}
/* (v779) .roster-members-hdr retired — the separate "Manage Content
   Editors" sub-table folded into the unified member-row table, so a
   second eyebrow is no longer needed. */

/* ── Combined member-row table cells (v779) ──────────────────────────────
   One row per campaign member; columns: Member | PC chips | Role. */
.roster-pcs-cell { vertical-align: middle; }
.roster-role-cell { vertical-align: middle; white-space: nowrap; }
.roster-role-actions {
  display: inline-flex;
  gap: var(--space-1);
  margin-left: var(--space-2);
}
.roster-member-cell { vertical-align: middle; }

/* PC chip — compact pill showing one PC assigned to a member. The × button
   inside unassigns the PC (PC stays in the campaign as orphan). Neutral
   tinting (bg-3 + border) keeps chips quiet alongside the role badges
   and white-pill action buttons in the same row. */
.pc-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  align-items: center;
}
.pc-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 0.15rem 0.35rem 0.15rem 0.55rem;
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: 600;
  white-space: nowrap;
}
.pc-chip-name { line-height: 1.2; }
.pc-chip-remove {
  background: none;
  border: none;
  color: var(--text-3);
  font-size: var(--font-size-md);
  line-height: 1;
  padding: 0 var(--space-1);
  margin-right: -0.15rem;
  cursor: pointer;
  border-radius: 50%;
  transition: color var(--t-hover), background var(--t-hover);
}
.pc-chip-remove:hover, .pc-chip-remove:focus-visible {
  color: var(--accent-red);
  background: color-mix(in srgb, var(--accent-red) 12%, transparent);
}

/* "+ Add" inline picker — composes .btn-new + .btn-xs for the canonical
   white-pill aesthetic at compact size. Click → reveals .pc-add-select. */
.pc-add-btn { font-size: var(--font-size-xs); padding: 2px 8px; }
.pc-add-select {
  font-size: var(--font-size-xs);
  padding: 2px 6px;
  max-width: 160px;
}

/* ── Party Treasury Modal ─────────────────────────────────────────────────── */
.treasury-currency-grid {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.treasury-coin-label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-3);
}
.treasury-coin-input {
  width: 72px;
  text-align: center;
}
.treasury-item-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.treasury-qty-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  padding: 0.2rem 0.4rem;
  font-size: var(--font-size-sm);
}

/* ── Treasury Transaction Log ─────────────────────────────────────────────── */
/* Loot splitter layout */
.ls-coin-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.ls-coin-label {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
}
.ls-coin-badge { font-size: var(--font-size-sm); padding: 0.2rem 0.55rem; }
.ls-coin-input { text-align: center; padding: 0.45rem 0.3rem; width: 100%; }
.ls-players-row {
  display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-4);
}
.ls-players-label { font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text-secondary); white-space: nowrap; }
.ls-players-input { width: 72px; text-align: center; }
.ls-calc-btn { padding: 0.45rem 1rem; white-space: nowrap; margin-left: auto; }

/* Loot splitter result */
.loot-split-result {
  background: color-mix(in srgb, var(--color-gold) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-gold) 30%, transparent);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
}
.ls-section-label {
  font-size: var(--font-size-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-text-tertiary); margin-bottom: 0.35rem;
}
.ls-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem; padding: 0.25rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--color-border) 50%, transparent);
}
.ls-row:last-child { border-bottom: none; }
.ls-row--treasury .ls-coins { color: var(--color-gold); }
.ls-count { font-size: var(--font-size-sm); color: var(--color-text-secondary); white-space: nowrap; }
.ls-coins { font-size: var(--font-size-sm); font-weight: 700; color: var(--color-text-primary); text-align: right; }

.treasury-log-entry {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-size: var(--font-size-xs);
  background: var(--bg-3);
  border-radius: var(--radius-md);
  padding: 0.3rem 0.5rem;
  flex-wrap: wrap;
}
.treasury-log-actor {
  font-weight: 700;
  color: var(--color-brand);
  white-space: nowrap;
}
.treasury-log-summary {
  flex: 1;
  color: var(--text-2);
  min-width: 0;
  word-break: break-word;
}
.treasury-log-time {
  color: var(--text-3);
  white-space: nowrap;
  font-size: 0.65rem;
}

/* ── Admin Service Health Cards ──────────────────────────────────────────── */
/* ── Session Feed tab ────────────────────────────────────────────────────────  */
@keyframes feed-flash {
  0%   { background: color-mix(in srgb, var(--color-brand) 18%, transparent); }
  60%  { background: color-mix(in srgb, var(--color-brand) 18%, transparent); }
  100% { background: transparent; }
}
.feed-item--new { animation: feed-flash 2.4s ease-out forwards; }

/* ── GM-only indicator pip ─────────────────────────────────────────────── */
.gm-pip {
  display: inline-flex;
  align-items: center;
  font-size: 0.52rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-ember);
  background: color-mix(in srgb, var(--color-ember) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-ember) 32%, transparent);
  border-radius: var(--radius-full);
  padding: 0.08rem 0.3rem;
  margin-right: 0.28rem;
  margin-left: 0;
  line-height: 1.2;
  vertical-align: middle;
  flex-shrink: 0;
}
/* On solid-color buttons (teal primary), invert pip to white so it's visible */
.btn-primary .gm-pip {
  color: rgba(255,255,255,0.92);
  background: rgba(255,255,255,0.18);
  border-color: rgba(255,255,255,0.38);
}

/* Admin pip — same shape as .gm-pip but purple, for admin-only features */
.admin-pip {
  display: inline-flex;
  align-items: center;
  font-size: 0.52rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-purple);
  background: color-mix(in srgb, var(--color-purple) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-purple) 32%, transparent);
  border-radius: var(--radius-full);
  padding: 0.08rem 0.3rem;
  margin-right: 0.28rem;
  margin-left: 0;
  line-height: 1.2;
  vertical-align: middle;
  flex-shrink: 0;
}
.btn-primary .admin-pip {
  color: rgba(255,255,255,0.92);
  background: rgba(255,255,255,0.18);
  border-color: rgba(255,255,255,0.38);
}

/* ── GM tabs — same color as normal tabs, distinguished by the GM pip only ── */
.tab-btn.gm-tab { color: var(--text-2); }
.tab-btn.gm-tab:hover, .tab-btn.gm-tab:focus-visible { color: var(--text); }
.tab-btn.gm-tab.active {
  color: var(--accent-teal);
  border-bottom-color: transparent;
}


.feed-layout {
  display: flex; flex-direction: column;
  height: 100%; overflow: hidden;
}
/* Eyebrow LEFT (composed via .form-section-label in markup), live-indicator
   pill RIGHT — mirrors .sessions-toolbar / .compendium-section-hdr /
   .quest-nav-toolbar / .map-toolbar. v775 retired the bg + border-bottom
   that made Feed the visual outlier across all primary tabs. */
.feed-toolbar {
  display: flex; align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-2);
  flex-shrink: 0;
}
/* .feed-toolbar-title rule retired in v775 — eyebrow recipe now flows
   through .form-section-label composed in markup. */
.feed-live-indicator {
  display: inline-flex; align-items: center; gap: var(--space-1);
  font-size: var(--font-size-xs); font-weight: 600; color: var(--color-success);
}
/* .feed-live-indicator.hidden { display:none } retired in v775 — the
   global .hidden { display:none !important } rule beats this anyway. */

.feed-list {
  flex: 1; overflow-y: auto;
  display: flex; flex-direction: column;
  padding: var(--space-1) 0;
}
/* Empty + error markers — typography flows through .placeholder +
   .placeholder--padded composed in markup; this class only contributes
   the centred alignment specific to the feed. .error class (red tint)
   added to the markup for the error variant. */
/* v965: .feed-empty inherits canonical .placeholder typography — no
   text-align override (was centered, now left-aligned to match the
   Weave Fates / Sessions / Codex empty-state grammar). */
.feed-item {
  display: flex; align-items: baseline; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  transition: background var(--t-hover) ease,
              border-color var(--t-hover) ease,
              transform var(--t-hover) ease,
              box-shadow var(--t-hover) ease;
  font-size: var(--font-size-sm);
}
.feed-item--link { cursor: pointer; }
/* Canonical card-hover recipe — matches .campaign-card / .session-card /
   .entity-row / .party-pc-card / .quest-nav-row / .map-manage-item. */
.feed-item--link:hover, .feed-item--link:focus-visible {
  background: var(--bg-2);
  border-color: rgba(47,154,163,0.45);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}
.feed-item-icon { font-size: var(--font-size-lg); flex-shrink: 0; line-height: 1.3; }
.feed-item-body { flex: 1; color: var(--text); line-height: 1.45; }
.feed-item-body strong { font-weight: 600; }
.feed-item-time {
  color: var(--text-3); font-size: var(--font-size-xs);
  white-space: nowrap; flex-shrink: 0;
}
.feed-type-pill {
  display: inline-block;
  font-size: var(--font-size-xs); font-weight: 600;
  padding: 0.05rem var(--space-1); border-radius: var(--radius-full);
  background: var(--bg-3); color: var(--text-2);
  text-transform: capitalize; margin-left: var(--space-1);
}

/* Feed item type colouring */
.feed-item--death  { border-left: 3px solid var(--color-danger); }
.feed-item--departure { border-left: 3px solid var(--color-warning); }
.feed-item--complete { border-left: 3px solid var(--color-success); }
.feed-item--failed { border-left: 3px solid var(--color-danger); }
.feed-item--quest  { border-left: 3px solid var(--color-purple); }
.feed-item--session { border-left: 3px solid var(--color-brand); font-weight: 600; }
.feed-item--key    { border-left: 3px solid var(--color-gold); }
.feed-item--entity { border-left: 3px solid var(--color-info); }


/* ── Live session background bar (minimized live session) ───────────────── */
.live-record-bar {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.35rem 1rem;
  background: color-mix(in srgb, var(--color-danger) 10%, var(--bg-2));
  border-bottom: 1px solid color-mix(in srgb, var(--color-danger) 25%, var(--border));
  font-size: var(--font-size-sm);
  font-weight: 500;
}
.live-record-bar.hidden { display: none; }

.lrb-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--color-text-tertiary);
}
.lrb-dot--rec {
  background: var(--color-danger);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-danger) 30%, transparent);
  animation: rec-blink 1.4s ease-in-out infinite;
}
.lrb-dot--idle { background: var(--color-success); }

.lrb-label { color: var(--color-text-secondary); }
.lrb-timer { font-variant-numeric: tabular-nums; color: var(--color-danger); font-weight: 600; min-width: 4.5rem; }

.lrb-actions { display: flex; gap: 0.4rem; margin-left: auto; }
.lrb-btn {
  font-size: var(--font-size-xs);
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: background var(--t-hover), color var(--t-hover);
}
.lrb-btn:hover, .lrb-btn:focus-visible { background: var(--color-surface-elevated); color: var(--color-text-primary); }
.lrb-end-btn { color: var(--color-danger); border-color: color-mix(in srgb, var(--color-danger) 40%, var(--border)); }
.lrb-end-btn:hover, .lrb-end-btn:focus-visible { background: color-mix(in srgb, var(--color-danger) 10%, transparent); }
.lrb-end-btn.hidden { display: none; }

/* ── Live broadcast banner (shown to members when GM broadcasts) ──────────── */
.live-broadcast-banner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.75rem;
  background: color-mix(in srgb, var(--color-success) 10%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--color-success) 30%, transparent);
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-success);
}
.live-broadcast-banner.hidden { display: none; }
.broadcast-live-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-success);
  animation: presence-pulse 1.4s ease-in-out infinite;
  flex-shrink: 0;
}
.broadcast-live-dot.small { width: 6px; height: 6px; }
.broadcast-banner-label { flex: 1; }
.broadcast-watch-btn {
  background: none;
  border: 1px solid var(--color-success);
  border-radius: var(--radius-full);
  color: var(--color-success);
  font-size: var(--font-size-xs);
  font-weight: 600;
  padding: 0.1rem 0.55rem;
  cursor: pointer;
  transition: background var(--t-hover);
}
.broadcast-watch-btn:hover, .broadcast-watch-btn:focus-visible { background: color-mix(in srgb, var(--color-success) 15%, transparent); }

/* ── Watcher panel (collapsible transcript drawer for members) ────────────── */
.live-watcher-panel {
  border-bottom: 1px solid var(--color-border);
  background: var(--bg-1);
  max-height: 280px;
  display: flex;
  flex-direction: column;
}
.live-watcher-panel.hidden { display: none; }
.watcher-hdr {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--bg-2);
  font-size: var(--font-size-xs);
}
.watcher-hdr-title { font-weight: 600; color: var(--color-success); flex: 1; display: flex; align-items: center; gap: 0.35rem; }
.watcher-wordcount { color: var(--color-text-tertiary); font-size: var(--font-size-xs); }
.watcher-box {
  flex: 1;
  overflow-y: auto;
  padding: 0.4rem 0.75rem;
  max-height: 220px;
  font-size: var(--font-size-sm);
  line-height: 1.55;
}
.watcher-turn {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}
.watcher-speaker {
  font-weight: 700;
  font-size: var(--font-size-xs);
  color: var(--color-brand);
  min-width: 1.4rem;
  text-align: center;
  padding-top: 0.15rem;
  flex-shrink: 0;
}
.watcher-text { color: var(--color-text-primary); }
.watcher-empty { color: var(--color-text-tertiary); font-style: italic; }

.admin-services-list {
  display: flex; flex-direction: column; gap: 0.75rem; margin-top: 0.5rem;
}

/* ── Admin Pulse Panel (floating, bottom-left) ────────────────── */
#admin-pulse-panel {
  position: fixed;
  bottom: 1.5rem;
  left: 1.5rem;
  z-index: 10003; /* above admin-screen (10000) so expand buttons receive clicks */
  width: 210px;
  background: color-mix(in srgb, var(--color-surface) 18%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid color-mix(in srgb, var(--color-border) 30%, transparent);
  border-radius: var(--radius-xl);
  box-shadow: 0 4px 20px rgba(0,0,0,0.10);
  font-size: var(--font-size-xs);
  overflow: hidden;
  transition: background var(--t-hover), border-color var(--t-hover), backdrop-filter var(--t-hover), box-shadow var(--t-hover);
}
#admin-pulse-panel:hover, #admin-pulse-panel:focus-visible {
  background: color-mix(in srgb, var(--color-surface) 75%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-color: color-mix(in srgb, var(--color-border) 60%, transparent);
  box-shadow: 0 4px 20px rgba(0,0,0,0.18);
}
.admin-pulse-hdr {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.45rem 0.65rem 0.45rem 0.75rem;
  background: color-mix(in srgb, var(--color-brand) 10%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent);
}
.admin-pulse-title {
  font-weight: 700; font-size: var(--font-size-xs);
  color: var(--color-brand); letter-spacing: 0.04em; text-transform: uppercase;
}
.admin-pulse-hdr-actions { display: flex; align-items: center; gap: 0.4rem; }
.admin-pulse-ts { color: var(--color-text-tertiary); font-size: 0.65rem; }
.admin-pulse-collapse-btn {
  background: none; border: none; cursor: pointer; color: var(--color-text-tertiary);
  font-size: var(--font-size-md); line-height: 1; padding: 0 2px;
}
.admin-pulse-collapse-btn:hover, .admin-pulse-collapse-btn:focus-visible { color: var(--color-text-primary); }

/* ── Admin pulse three-state collapse ─────────────────────────────────── */
/* State 1: header only */
#admin-pulse-panel[data-state="1"] #admin-pulse-body,
#admin-pulse-panel[data-state="1"] .admin-pulse-footer { display: none; }
#admin-pulse-panel[data-state="1"] .admin-pulse-collapse-btn::after { content: '●'; }
#admin-pulse-panel[data-state="1"] .admin-pulse-collapse-btn { font-size: 0.65rem; }

/* State 2: minimized — just the shield icon */
#admin-pulse-panel[data-state="2"] { width: auto; min-width: 0; border-radius: var(--radius-full); padding: 2px; }
#admin-pulse-panel[data-state="2"] .admin-pulse-hdr,
#admin-pulse-panel[data-state="2"] #admin-pulse-body,
#admin-pulse-panel[data-state="2"] .admin-pulse-footer { display: none; }
.admin-pulse-mini-btn {
  display: none;
  align-items: center; justify-content: center;
  width: 34px; height: 34px;
  background: none; border: none; cursor: pointer;
  font-size: var(--font-size-lg); line-height: 1;
  color: var(--color-brand);
}
.admin-pulse-mini-btn:hover, .admin-pulse-mini-btn:focus-visible { color: var(--color-text-primary); }
#admin-pulse-panel[data-state="2"] .admin-pulse-mini-btn { display: flex; }
.admin-pulse-body { padding: 0.25rem 0 0.25rem; display: flex; flex-direction: column; gap: 0; }
.admin-pulse-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.admin-pulse-label { color: var(--color-text-secondary); flex: 1; }
.admin-pulse-val {
  font-weight: 600; color: var(--color-text-primary);
  font-variant-numeric: tabular-nums; min-width: 2rem; text-align: right;
}
.admin-pulse-val--hot { color: var(--color-success); }
.admin-pulse-divider {
  height: 1px; background: var(--color-border);
  margin: 0.2rem 0;
}
.ap-expand-btn {
  background: none; border: none; cursor: pointer;
  color: var(--color-text-tertiary); font-size: var(--font-size-xs);
  line-height: 1; padding: 0 1px; margin-left: 2px;
  flex-shrink: 0; transition: color var(--t-hover);
}
.ap-expand-btn:hover, .ap-expand-btn:focus-visible, .ap-expand-btn--open { color: var(--color-brand); }
.ap-detail {
  margin: 0.15rem 0 0.35rem 0;
  border-left: 2px solid var(--color-border);
  padding-left: 0.5rem;
  display: flex; flex-direction: column; gap: 0.25rem;
}
.ap-detail-item { display: flex; flex-direction: column; gap: 0.05rem; }
.ap-di-name {
  font-size: 0.65rem; font-weight: 600;
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ap-di-meta {
  font-size: 0.6rem; color: var(--color-text-tertiary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ap-detail-empty {
  font-size: 0.62rem; color: var(--color-text-tertiary); font-style: italic;
}

/* Admin presence detail — campaign → tabs breakdown */
.ap-pres-campaign {
  display: flex; flex-direction: column; gap: 0.1rem;
  margin-bottom: 0.3rem;
}
.ap-pres-camp-hdr {
  font-size: 0.65rem; font-weight: 700;
  color: var(--color-text-primary);
  display: flex; align-items: center; gap: 0.3rem;
}
.ap-pres-count {
  font-size: 0.58rem; font-weight: 700;
  background: color-mix(in srgb, var(--color-brand) 14%, transparent);
  color: var(--color-brand);
  border-radius: var(--radius-full);
  padding: 0.05rem 0.32rem;
}
.ap-pres-tab-row {
  display: flex; gap: 0.35rem; align-items: baseline;
  padding-left: 0.5rem;
}
.ap-pres-tab-name {
  font-size: 0.6rem; font-weight: 600; text-transform: capitalize;
  color: var(--color-gold); flex-shrink: 0;
  min-width: 3.5rem;
}
.ap-pres-names {
  font-size: 0.6rem; color: var(--color-text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Admin "Who's Online" panel — who's-where list ────────────── */
.ap-campaign-section { padding: 0.35rem 0.65rem; }
.ap-campaign-section + .ap-campaign-section { border-top: 1px solid var(--color-border); }
.ap-campaign-name {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  margin-bottom: 0.25rem;
}
.ap-user-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0;
  font-size: var(--font-size-xs);
}
.ap-user-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--color-success);
  flex-shrink: 0;
}
.ap-user-dot--rec { background: var(--color-danger); }
.ap-user-name {
  color: var(--color-text-primary); font-weight: 500;
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-user-tab { color: var(--color-text-tertiary); font-size: 0.65rem; }
.ap-rec-tag {
  font-size: 0.6rem; font-weight: 700;
  color: var(--color-danger);
  border: 1px solid color-mix(in srgb, var(--color-danger) 40%, transparent);
  border-radius: var(--radius-sm);
  padding: 0 0.25rem;
  letter-spacing: 0.04em;
}
.ap-empty {
  padding: 0.6rem 0.65rem;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  font-style: italic;
}
.admin-pulse-footer { padding: 0.35rem 0.65rem 0.5rem; }

/* Campaign presence pill (in main app header) */
.camp-presence-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-success) 25%, transparent);
  border-radius: var(--radius-full);
  padding: 0.1rem 0.5rem;
  cursor: default;
}
.camp-presence-pill::before {
  content: '';
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--color-success);
  flex-shrink: 0;
}

/* Campaign selector badge — recording state */
.campaign-live-badge.camp-badge--recording .camp-live-dot {
  background: var(--color-danger);
  animation: rec-blink 1.2s ease-in-out infinite;
}
.campaign-live-badge.camp-badge--recording .camp-live-label {
  color: var(--color-danger);
}

/* Live viewers bar — recording tag */
.live-viewer-rec {
  font-size: 0.6rem; font-weight: 700;
  color: var(--color-danger);
  vertical-align: middle;
}

/* ── Admin: section wrapper (shared by Services + Costs tabs) ─── */
.admin-section { margin-bottom: 2rem; }
.admin-section-label {
  font-size: var(--font-size-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-text-tertiary); margin-bottom: 0.75rem;
}

/* ── Cost / Pricing Calculator ───────────────────────────────────── */
.cost-calc-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.cost-calc-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.75rem;
}
.cost-calc-card-lbl { font-size: var(--font-size-xs); color: var(--text-3); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.25rem; }
.cost-calc-card-val { font-size: var(--font-size-lg); font-weight: 700; color: var(--text); line-height: 1.1; }
.cost-calc-card-sub { font-size: var(--font-size-xs); color: var(--text-3); margin-top: 0.25rem; }
.cost-calc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
@media (max-width: 720px) { .cost-calc-grid { grid-template-columns: 1fr; } }
.cost-calc-block {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.75rem 0.85rem;
  margin-bottom: 1rem;
}
.cost-calc-block-hdr { font-size: var(--font-size-sm); font-weight: 600; color: var(--text-2); margin-bottom: 0.5rem; }
.cost-calc-block-sub { font-size: var(--font-size-xs); color: var(--text-3); font-weight: 400; }
.cost-calc-fixed-grid { display: grid; gap: 0.4rem; }
.cost-calc-fixed-row { display: grid; grid-template-columns: 1fr auto 7rem auto; gap: 0.4rem; align-items: center; }
.cost-calc-fixed-label { font-size: var(--font-size-sm); color: var(--text-2); }
.cost-calc-fixed-prefix, .cost-calc-fixed-suffix { color: var(--text-3); font-size: var(--font-size-xs); }
.cost-calc-fixed-input, .cost-calc-margin-input, .cost-calc-addon-input, .cost-calc-target-input {
  background: var(--bg-1); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.2rem 0.4rem; font: inherit;
}
.cost-calc-fixed-input  { width: 7rem; text-align: right; }
.cost-calc-margin-input { width: 4rem; text-align: right; }
.cost-calc-addon-input  { width: 5rem; text-align: right; }
.cost-calc-target-input { width: 6rem; text-align: right; }

/* Primary editable controls in the summary cards — make the value itself
   the input so the affordance is unmissable. The `--input` modifier swaps
   the static number for an inline editor sized to the same big-number scale. */
.cost-calc-card--editable { background: color-mix(in srgb, var(--accent-teal) 4%, var(--bg-2)); }
.cost-calc-card-val--input {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: var(--font-size-lg);
}
.cost-calc-bignum {
  display: inline-flex;
  align-items: baseline;
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--text);
  line-height: 1.1;
}
.cost-calc-bignum-input {
  background: transparent;
  color: var(--text);
  border: none;
  border-bottom: 2px dashed color-mix(in srgb, var(--accent-teal) 60%, transparent);
  padding: 0 0.15rem;
  font: inherit;
  font-weight: 700;
  width: 4rem;
  text-align: right;
  cursor: text;
}
.cost-calc-bignum-input:focus {
  outline: none;
  border-bottom-color: var(--accent-teal);
  border-bottom-style: solid;
}
.cost-calc-margin-range {
  width: 100%;
  accent-color: var(--accent-teal);
  height: 1.25rem;
  cursor: pointer;
}
.cost-calc-bucket-table { width: 100%; border-collapse: collapse; }
.cost-calc-bucket-table td { padding: 0.25rem 0.4rem; font-size: var(--font-size-sm); border-bottom: 1px dashed var(--border); }
.cost-calc-bucket-table tr:last-child td { border-bottom: none; }
.cost-calc-bucket-name { color: var(--text-2); text-transform: capitalize; }
.cost-calc-tier-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); }
.cost-calc-tier-table th, .cost-calc-tier-table td { padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); }
.cost-calc-tier-table th { font-weight: 600; color: var(--text-3); text-align: left; font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.04em; }
.cost-calc-tier-table td { color: var(--text); }
.cost-calc-tier-name { color: var(--text); }
.cost-calc-num { text-align: right; font-variant-numeric: tabular-nums; }
.cost-calc-price { color: var(--accent-teal, #2f9aa3); }
.cost-calc-arch-col { color: var(--text-2); font-size: var(--font-size-xs); }
.cost-calc-arch em { color: var(--text-3); font-style: normal; }
.cost-calc-capped {
  display: inline-block;
  margin-left: 0.3rem;
  padding: 1px 0.4rem;
  font-size: 0.65rem;
  font-weight: 700;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--color-ember, #d4753a) 18%, transparent);
  color: var(--color-ember, #d4753a);
  text-transform: uppercase;
}
.cost-calc-capped--mono {
  background: color-mix(in srgb, var(--accent-teal, #2f9aa3) 18%, transparent);
  color: var(--accent-teal, #2f9aa3);
}
.cost-calc-addon-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.6rem;
  margin-top: 0.6rem;
  border-top: 1px dashed var(--border);
  flex-wrap: wrap;
  font-size: var(--font-size-sm);
}
.cost-calc-sanity {
  padding: 0.6rem 0.85rem;
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  border: 1px solid var(--border);
}
.cost-calc-sanity--ok   { background: color-mix(in srgb, var(--color-success, #2da84a) 12%, transparent); border-color: color-mix(in srgb, var(--color-success, #2da84a) 35%, transparent); }
.cost-calc-sanity--warn { background: color-mix(in srgb, var(--color-ember, #d4753a) 12%, transparent); border-color: color-mix(in srgb, var(--color-ember, #d4753a) 35%, transparent); }
.cost-calc-sanity--bad  { background: color-mix(in srgb, var(--accent-red, #d4724f) 14%, transparent); border-color: color-mix(in srgb, var(--accent-red, #d4724f) 40%, transparent); }

/* ── Admin: Costs tab ─────────────────────────────────────────── */
.costs-table {
  width: 100%; border-collapse: collapse;
  font-size: var(--font-size-sm);
}
.costs-th {
  text-align: left; font-size: var(--font-size-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--color-text-tertiary);
  padding: 0.4rem 0.75rem; border-bottom: 1px solid var(--color-border);
}
.costs-th--num { text-align: right; }
.costs-td {
  padding: 0.55rem 0.75rem; border-bottom: 1px solid color-mix(in srgb, var(--color-border) 50%, transparent);
  color: var(--color-text-primary);
}
.costs-td--service { font-weight: 600; }
.costs-td--num { text-align: right; font-variant-numeric: tabular-nums; }
.costs-td--note { color: var(--color-text-tertiary); font-size: var(--font-size-xs); }
.costs-free { color: var(--color-success); font-weight: 600; }
.costs-total-row td {
  border-top: 2px solid var(--color-border); border-bottom: none;
  padding-top: 0.65rem;
}

/* Anthropic consumption bar */
.costs-ai-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-4); }
.costs-ai-row { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.6rem; flex-wrap: wrap; }
.costs-ai-label { font-size: var(--font-size-sm); color: var(--color-text-secondary); }
.costs-ai-val { font-size: 1.15rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.costs-ai-val--spent { color: var(--color-brand); }
.costs-progress-track { height: 8px; background: var(--color-surface-elevated); border-radius: var(--radius-full); overflow: hidden; margin-bottom: 0.4rem; }
.costs-progress-bar { height: 100%; background: var(--color-brand); border-radius: var(--radius-full); transition: width var(--t-trans) ease; }
.costs-progress-bar--warn { background: var(--color-warning); }
.costs-ai-pct { font-size: var(--font-size-xs); color: var(--color-text-tertiary); }

/* Burn total cards */
.costs-burn-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.75rem; }
.costs-burn-card {
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); padding: 0.75rem 1.25rem; text-align: center; min-width: 110px;
}
.costs-burn-card--total { border-color: var(--color-brand); background: color-mix(in srgb, var(--color-brand) 8%, var(--color-surface)); }
.costs-burn-label { font-size: var(--font-size-xs); color: var(--color-text-tertiary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.3rem; }
.costs-burn-val { font-size: 1.2rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--color-text-primary); }
.costs-burn-sep { font-size: 1.25rem; color: var(--color-text-tertiary); font-weight: 300; }
.costs-note { font-size: var(--font-size-xs); color: var(--color-text-tertiary); margin: 0; }
.svc-card {
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); padding: var(--space-4); position: relative;
}
.svc-card::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px; border-radius: var(--radius-lg) 0 0 var(--radius-lg);
}
.svc-status-ok::before    { background: var(--color-success); }
.svc-status-warn::before  { background: var(--color-warning); }
.svc-status-danger::before{ background: var(--color-danger); }
.svc-card-header {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 0.75rem;
}
.svc-card-title { font-size: var(--font-size-base); font-weight: 600; color: var(--color-text-primary); }
.svc-stats-row {
  display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem;
}
.svc-stat-val {
  font-size: var(--font-size-lg); font-weight: 600; color: var(--color-text-primary);
}
.svc-stat-lbl {
  font-size: var(--font-size-xs); color: var(--color-text-tertiary); margin-top: 0.1rem;
}
.text-danger { color: var(--color-danger) !important; }
.svc-quota-group { margin-bottom: 0.75rem; display: flex; flex-direction: column; gap: 0.35rem; }
.svc-quota-row { display: flex; align-items: center; gap: 0.5rem; }
.svc-quota-label { font-size: var(--font-size-xs); color: var(--color-text-secondary); min-width: 140px; }
.svc-quota-bar {
  flex: 1; height: 6px; background: var(--color-surface-elevated);
  border-radius: var(--radius-full); overflow: hidden;
}
.svc-quota-fill {
  height: 100%; background: var(--color-brand);
  border-radius: var(--radius-full); transition: width var(--t-trans);
}
.svc-quota-fill.svc-quota-warn { background: var(--color-warning); }
.svc-quota-pct { font-size: var(--font-size-xs); color: var(--color-text-tertiary); min-width: 32px; text-align: right; }
.svc-history {
  display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem;
}
.svc-hist-item {
  display: flex; flex-direction: column; align-items: center;
  background: var(--color-surface-elevated); border-radius: var(--radius-md);
  padding: 0.2rem 0.5rem;
}
.svc-hist-month { font-size: var(--font-size-xs); color: var(--color-text-tertiary); }
.svc-hist-cost  { font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text-primary); }
.svc-update-row {
  display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem;
  padding-top: 0.75rem; border-top: 1px solid var(--color-border);
}
.svc-balance-input, .svc-threshold-input {
  width: 110px; padding: 0.3rem 0.5rem; font-size: var(--font-size-sm);
  background: var(--color-bg); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); color: var(--color-text-primary);
}
.svc-error-note { font-size: var(--font-size-xs); color: var(--color-danger); margin: 0.25rem 0; }


/* ═══════════════════════════════════════════════════════════════════════════
   ARCANE IDENTITY — Modern Campaign Command Center
   Layered over the design system base. All values use design tokens.
   Goal: premium dark fantasy companion, not a medieval prop.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Page atmosphere (dark mode only — subtle teal bloom at top) ─── */
[data-theme="dark"] body {
  /* Must use background-image (not shorthand) to preserve the bg image from the base body rule.
     Shorthand 'background:' resets all layers and wipes the bg url. */
  background-image:
    radial-gradient(ellipse 160% 40% at 50% -5%,
      color-mix(in srgb, var(--color-brand) 9%, transparent) 0%,
      transparent 100%),
    linear-gradient(rgba(4, 8, 14, 0.88), rgba(4, 8, 14, 0.88)),
    image-set(url('/static/bg.webp') type('image/webp'), url('/static/bg.png') type('image/png'));
  background-size: auto, auto, cover;
  background-position: center, center, center;
  background-attachment: fixed, fixed, fixed;
}

/* Dark mode: cancel the light-mode drop-shadow on PNG logos */
[data-theme="dark"] .nav-logo,
[data-theme="dark"] .campaign-selector-logo { filter: none; }

/* ── Active tab — arcane glow on the underline accent ──────────────────── */
[data-theme="dark"] .tab-btn.active {
  text-shadow: 0 0 18px color-mix(in srgb, var(--color-brand) 75%, transparent);
}

/* ── Campaign cards — arcane presence ─────────────────────────────────── */
/* Watermark via background-image with opacity baked into the SVG.
   Background-image renders behind all content automatically — no z-index needed. */
.campaign-card {
  background-image: url("data:image/svg+xml,%3Csvg opacity='0.18' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='none' stroke='%232F7C85' stroke-width='2.2'/%3E%3Ccircle cx='50' cy='50' r='34' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Ccircle cx='50' cy='50' r='19' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Cline x1='50' y1='4' x2='50' y2='96' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='4' y1='50' x2='96' y2='50' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='18' y1='18' x2='82' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cline x1='82' y1='18' x2='18' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cpath d='M50 16 L84 50 L50 84 L16 50 Z' fill='none' stroke='%232F7C85' stroke-width='1.2'/%3E%3C/svg%3E");
  background-size: 70px 70px;
  background-repeat: no-repeat;
  background-position: right 6px bottom 6px;
}

/* Cover image variant: layer (front → back) watermark over a dark gradient over the image.
   Watermark uses the same teal stroke as cards without a cover (#2F7C85) for visual consistency.
   Doubled selector (.campaign-card.campaign-card--has-cover) to win the cascade against the
   later .campaign-card rule that also sets background-image. */
.campaign-card.campaign-card--has-cover {
  background-image:
    url("data:image/svg+xml,%3Csvg opacity='0.18' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='none' stroke='%232F7C85' stroke-width='2.2'/%3E%3Ccircle cx='50' cy='50' r='34' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Ccircle cx='50' cy='50' r='19' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Cline x1='50' y1='4' x2='50' y2='96' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='4' y1='50' x2='96' y2='50' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='18' y1='18' x2='82' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cline x1='82' y1='18' x2='18' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cpath d='M50 16 L84 50 L50 84 L16 50 Z' fill='none' stroke='%232F7C85' stroke-width='1.2'/%3E%3C/svg%3E"),
    var(--cover-tint, linear-gradient(transparent, transparent)),
    linear-gradient(180deg, rgba(11,15,20,0.68) 0%, rgba(11,15,20,0.45) 50%, rgba(11,15,20,0.72) 100%),
    var(--cover-img, linear-gradient(transparent, transparent));
  background-size: 70px 70px, auto, auto, cover;
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
  background-position: right 6px bottom 6px, center, center, center top;
}
.campaign-card.campaign-card--has-cover:hover, .campaign-card.campaign-card--has-cover:focus-visible { border-color: rgba(47,154,163,0.6); }
/* Slightly brighter meta text on covered cards — the default --text-3 reads too dim against the image */
.campaign-card.campaign-card--has-cover .campaign-card-meta { color: color-mix(in srgb, var(--text-3) 55%, #ffffff 45%); }

/* Settings-modal preview: same layering as the card so the GM sees what players will see */
.camp-cover-preview {
  height: 120px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  margin-bottom: 0.55rem;
  background-image:
    linear-gradient(180deg, rgba(11,15,20,0.45) 0%, rgba(11,15,20,0.25) 50%, rgba(11,15,20,0.55) 100%),
    var(--cover-img, none);
  background-size: auto, cover;
  background-repeat: no-repeat;
  background-position: center, center top;
}
.camp-cover-preview.hidden { display: none; }

[data-theme="dark"] .campaign-card {
  transition: border-color var(--t-hover) ease, box-shadow var(--t-hover) ease, transform var(--t-hover) ease;
}

/* Hover — grounded lift, no glow (dark mode) */
[data-theme="dark"] .campaign-card:hover, [data-theme="dark"] .campaign-card:focus-visible {
  border-color: rgba(79,154,163,0.35);
  box-shadow: 0 6px 16px rgba(0,0,0,0.45);
  transform: translateY(-1px);
}

/* ── GM badge — ember insignia (canonical rule is in .camp-role-gm base above) ── */

/* GM tabs dark-mode glow removed — they now match normal tab active style */

/* ── Live indicators — gold ──────────────────────────────────────────── */
.campaign-live-badge             { color: var(--color-gold); }
.camp-live-dot                   { background: var(--color-gold);
                                   animation: live-dot-pulse 2.6s ease-in-out infinite; }
#live-nav-btn.live-active {
  color: var(--color-gold);
  background: color-mix(in srgb, var(--color-gold) 13%, transparent);
  border-color: color-mix(in srgb, var(--color-gold) 60%, transparent);
  animation: live-btn-breathe-active 2.4s ease-in-out infinite;
}
@keyframes live-btn-breathe-active {
  0%, 100% { box-shadow: 0 0 4px 0   color-mix(in srgb, var(--color-gold) 25%, transparent); }
  50%       { box-shadow: 0 0 18px 4px color-mix(in srgb, var(--color-gold) 32%, transparent); }
}
.live-presence-badge             {
  background:  color-mix(in srgb, var(--color-gold) 13%, transparent);
  color:       var(--color-gold);
  border-color: color-mix(in srgb, var(--color-gold) 28%, transparent);
}

/* v808: Light-theme Live nav pill — INVERTED. Solid gold fill, white
   letters, gentle opacity pulse so it breathes on the page without
   shifting the background colour. Replaces the dark-theme `live-btn-flicker`
   animation (which mutates background through transparent color-mix steps —
   that would re-render our solid fill as semi-transparent gold mid-cycle).
   The pulse-light keyframes only touch opacity, so the gold pill stays
   gold and the white text rides along. Same treatment carries to hover,
   .live-active, and the .live-presence-badge count chip beside it. */
html:not([data-theme="dark"]) #live-nav-btn {
  background: var(--color-gold);
  border-color: var(--color-gold);
  color: #ffffff;
  animation: live-btn-pulse-light 2.8s ease-in-out infinite;
  box-shadow: 0 1px 2px rgba(202,138,4,0.25);
}
html:not([data-theme="dark"]) #live-nav-btn .tf-tab-mark { opacity: 1; }
html:not([data-theme="dark"]) #live-nav-btn:hover,
html:not([data-theme="dark"]) #live-nav-btn:focus-visible {
  background: var(--color-gold);
  color: #ffffff;
  border-color: var(--color-gold);
  filter: brightness(1.08);
  box-shadow: 0 0 14px 3px color-mix(in srgb, var(--color-gold) 40%, transparent);
}
html:not([data-theme="dark"]) #live-nav-btn.live-active {
  background: var(--color-gold);
  border-color: var(--color-gold);
  color: #ffffff;
}
html:not([data-theme="dark"]) .live-presence-badge {
  background: var(--color-gold);
  color: #ffffff;
  border-color: var(--color-gold);
}
@keyframes live-btn-pulse-light {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.78; }
}

/* v810: Profile button — the rest state (border + drop) is fine, but
   .nav-avatar-img DEFAULT carries `border: 2px solid var(--accent-teal)`
   (= #2F7C85 on light = mineral teal). Inside #cs-profile-btn /
   #profile-nav-btn we already null that with `border: none`, but on hover
   the canonical .nav-profile-btn:hover rule adds an outer
   `box-shadow: 0 0 0 2px rgba(47,124,133,0.25)` halo — that's the "ultra
   bright thick circle" on the parchment wash. The dark-theme reading is
   "elegant teal accent ring"; on light it's an angry blue-green halo.
   Quiet it to a soft ink ring that matches the rest of the light-theme
   premium parity treatment. */
html:not([data-theme="dark"]) .nav-profile-btn:hover .nav-avatar-img,
html:not([data-theme="dark"]) .nav-profile-btn:focus-visible .nav-avatar-img {
  border-color: var(--color-border-strong);
  box-shadow: 0 0 0 1px rgba(15,23,42,0.10);
}
/* And drop the .nav-avatar-img default 2px teal ring on light — at the
   sizes it's used (28-40px round) the saturated teal reads as a halo.
   Use a light slate ring so the avatar reads as a framed portrait. */
html:not([data-theme="dark"]) .nav-avatar-img {
  border-color: var(--color-border-strong);
}

/* Campaign card live badge — base uses `rgba(11,15,20,0.55)` dark fill so
   gold text reads against the dark card. On the parchment wash that's a
   dark hole pill on a bright card. Flip the FILL to warm cream and let
   the gold border + gold dot/label do the signaling — same recipe shape
   as the dark version, just inverted for light surfaces. Recording
   variant follows: cream fill, red border + red dot/label preserved. */
html:not([data-theme="dark"]) .campaign-card .campaign-live-badge {
  background: rgba(252,247,237,0.92);
  border-color: var(--color-gold);
  box-shadow: 0 1px 2px rgba(202,138,4,0.18);
}
html:not([data-theme="dark"]) .campaign-card .campaign-live-badge.camp-badge--recording {
  border-color: var(--color-danger);
  box-shadow: 0 1px 2px rgba(220,38,38,0.18);
}
/* Camp-presence-pill (main app header — "N online") — base 10% green fill
   on transparent gives a faint green stain on parchment. Bump to a real
   light-mint pill with an ink-leaning green text so the count reads. */
html:not([data-theme="dark"]) .camp-presence-pill {
  background: color-mix(in srgb, var(--color-success) 14%, #ffffff);
  border-color: var(--color-success);
  color: #14532D;
}
html:not([data-theme="dark"]) .camp-presence-pill::before {
  background: var(--color-success);
}

/* Header live dot (recording active) stays red — recording = red convention */
/* Session-active dot in nav uses gold */

@keyframes live-dot-pulse {
  0%, 100% { opacity: 1;    box-shadow: 0 0 0 0   color-mix(in srgb, var(--color-gold) 55%, transparent); }
  50%       { opacity: 0.75; box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-gold)  0%, transparent); }
}

/* ── Live session screen — arcane atmosphere (both modes) ─────────────── */
#live-session-screen:not(.hidden) {
  border-top: 2px solid color-mix(in srgb, var(--color-gold) 35%, transparent);
  background:
    radial-gradient(ellipse 70% 18% at 50% 0%,
      color-mix(in srgb, var(--color-gold) 5%, transparent) 0%,
      transparent 100%);
}
[data-theme="dark"] #live-session-screen:not(.hidden) {
  background:
    radial-gradient(ellipse 80% 22% at 50% 0%,
      color-mix(in srgb, var(--color-gold) 7%, transparent) 0%,
      transparent 100%);
}

/* Live header — very faint gold warmth when session is running */
[data-theme="dark"] .live-header {
  background: linear-gradient(
    to right,
    var(--color-bg),
    color-mix(in srgb, var(--color-gold) 3%, var(--color-bg)) 50%,
    var(--color-bg)
  );
}

/* ── AI story beats (system_event) — gold ────────────────────────────── */
.live-log-entry[data-tag="system_event"] {
  border-left-color: var(--color-gold);
  background: color-mix(in srgb, var(--color-gold) 5%, transparent);
}

/* ── AI combat beats (combat_event) — amber ──────────────────────────── */
.live-log-entry[data-tag="combat_event"] {
  border-left-color: var(--color-warning);
  background: color-mix(in srgb, var(--color-warning) 5%, transparent);
}

/* ── AI context beats (context_event) — muted violet, italic ─────────── */
.live-log-entry[data-tag="context_event"] {
  border-left-color: var(--color-muted-violet);
  background: color-mix(in srgb, var(--color-muted-violet) 6%, transparent);
  font-style: italic;
  color: var(--color-text-secondary);
}

/* Scan-pulse ripple uses gold */
.live-log-entry--scan {
  border-left-color: var(--color-gold);
  animation: le-arrive 0.28s ease-out, le-scan-pulse-gold 2.2s ease-out 0.28s forwards;
}
@keyframes le-scan-pulse-gold {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--color-gold) 55%, transparent); }
  40%  { box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-gold) 18%, transparent); }
  100% { box-shadow: 0 0 0 0   transparent; }
}

/* ── Live sidebar + chat drawer — faint teal depth gradient ────────────── */
[data-theme="dark"] .live-sidebar,
[data-theme="dark"] .live-ai-drawer {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--color-brand) 3%, var(--color-bg)) 0%,
    var(--color-bg) 14%);
}

/* ── Session cards — grounded lift hover (dark mode) ──────────────────── */
[data-theme="dark"] .session-card {
  transition: border-color var(--t-hover) ease, box-shadow var(--t-hover) ease, transform var(--t-hover) ease;
}
[data-theme="dark"] .session-card:hover, [data-theme="dark"] .session-card:focus-visible {
  border-color: rgba(79,154,163,0.35);
  box-shadow: 0 6px 16px rgba(0,0,0,0.45);
  transform: translateY(-1px);
}

/* ── Live preamble header — gold title, split bullet diamonds ─────────── */
.live-preamble-title   { color: var(--color-gold); }
.live-preamble-bullet::before { color: var(--split-color); }


/* ── Arcane watermark — applied consistently across all primary card types ── */
/* Same SVG sigil (opacity baked in), repositioned per card shape.            */
.session-card-hdr,
.party-pc-card,
.entity-row-hdr,
.quest-nav-hdr,
.fates-saved-card-hdr,
.live-preamble {
  background-image: url("data:image/svg+xml,%3Csvg opacity='0.18' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='none' stroke='%232F7C85' stroke-width='2.2'/%3E%3Ccircle cx='50' cy='50' r='34' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Ccircle cx='50' cy='50' r='19' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Cline x1='50' y1='4' x2='50' y2='96' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='4' y1='50' x2='96' y2='50' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='18' y1='18' x2='82' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cline x1='82' y1='18' x2='18' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cpath d='M50 16 L84 50 L50 84 L16 50 Z' fill='none' stroke='%232F7C85' stroke-width='1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
}

/* Session card header — sigil sits in bottom-right of the header row */
.session-card-hdr {
  background-size: 100px 100px;
  background-position: right -6px bottom -6px;
}

/* Party PC card — sigil in bottom-right of the card body */
.party-pc-card {
  background-size: 110px 110px;
  background-position: right -8px bottom -8px;
}

/* Codex entity row header — smaller sigil, bottom-right bleed */
.entity-row-hdr {
  background-size: 80px 80px;
  background-position: right -4px bottom -4px;
}

/* Quest nav card header — same as session-card-hdr: bottom-right bleed */
.quest-nav-hdr {
  background-size: 100px 100px;
  background-position: right -6px bottom -6px;
}

/* Fates saved-card header — same recipe as .quest-nav-hdr verbatim
   (v929). User direction: "DUPLICATE the way the quest card is
   formatted." */
.fates-saved-card-hdr {
  background-size: 100px 100px;
  background-position: right -6px bottom -6px;
}

/* Live preamble body — sigil bleeds from bottom-right */
.live-preamble {
  background-size: 110px 110px;
  background-position: right -6px bottom -6px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   VISUAL REFINEMENT PASS — teal + ember gold hierarchy, arcane atmosphere
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Entity crosslinks — standardized palette ────────────────────────── */
.entity-link[data-type="npc"]     { color: var(--color-muted-violet); }
.entity-link[data-type="item"]    { color: var(--color-ember); }
.entity-link[data-type="faction"] { color: var(--color-muted-violet); }
/* PCs stay blue but desaturated slightly */
.entity-link[data-type="pc"]      { color: color-mix(in srgb, var(--accent-blue) 75%, var(--color-text-secondary)); }

/* ── View-as bar — replace purple with dark indigo/teal + ember top edge */
/* v798: light-correct default (parchment-with-ember-edge), dark gets the
   indigo gradient. Pre-v798 the dark gradient was hardcoded for both
   themes — black bar across the top of light theme. */
#view-as-bar {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--color-ember) 4%, var(--bg)) 0%,
    color-mix(in srgb, var(--color-ember) 8%, var(--bg)) 55%,
    color-mix(in srgb, var(--color-ember) 4%, var(--bg)) 100%);
  border-top: 2px solid color-mix(in srgb, var(--color-ember) 50%, transparent);
  border-bottom: 1px solid var(--border);
}
[data-theme="dark"] #view-as-bar {
  background: linear-gradient(90deg, #181430 0%, #0c2233 55%, #0a1e2e 100%);
  border-top: 2px solid color-mix(in srgb, var(--color-ember) 38%, transparent);
  border-bottom: 1px solid rgba(0,0,0,0.45);
}

/* ── Chronicle — session beat headers: split motif left accent ─────────── */
.session-beat-header {
  font-size: var(--font-size-base);
  padding-left: 0.6rem;
  border-left: 2px solid var(--split-strong);
  margin-top: 1.1rem;
}

/* ── Campaign cards — radial teal glow layered behind sigil ──────────── */
.campaign-card {
  background-image:
    url("data:image/svg+xml,%3Csvg opacity='0.18' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='none' stroke='%232F7C85' stroke-width='2.2'/%3E%3Ccircle cx='50' cy='50' r='34' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Ccircle cx='50' cy='50' r='19' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Cline x1='50' y1='4' x2='50' y2='96' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='4' y1='50' x2='96' y2='50' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='18' y1='18' x2='82' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cline x1='82' y1='18' x2='18' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cpath d='M50 16 L84 50 L50 84 L16 50 Z' fill='none' stroke='%232F7C85' stroke-width='1.2'/%3E%3C/svg%3E"),
    radial-gradient(circle at 88% 88%, color-mix(in srgb, var(--color-brand) 7%, transparent) 0%, transparent 52%);
  background-repeat: no-repeat;
  background-size: 70px 70px, 100%;
  background-position: right 6px bottom 6px, center;
}

/* Chevron — ember-gold tint on hover so it picks up the brand "Live" feel. */
.campaign-card:hover .campaign-enter-chevron,
.campaign-card:focus-within .campaign-enter-chevron {
  color: color-mix(in srgb, var(--color-ember) 60%, var(--color-gold));
}

/* ── Codex rows — softer grid, taller ─────────────────────────────────
   Hover treatment moved to the canonical .entity-row block (gap-separated
   cards, translateY + teal border + drop shadow). What remains here is
   resting border tint + the expanded row's elevated surface. The old
   2px left-border affordance + inset shadow on row-hdr were dropped — they
   re-implemented the very pattern we removed from .session-card last round. */
.entity-row {
  border-color: color-mix(in srgb, var(--color-border) 55%, transparent);
}
.entity-row.expanded {
  background: var(--color-surface-elevated);
}
.entity-row-hdr {
  min-height: 54px;
}

/* Decorative halo ring removed — campaign / session / party card icons
   don't carry one, and the standalone-card refactor of entity rows made
   the halo read as visual noise next to the new card border. */

/* ── Quest sidebar — selected row handled in main quest block above */

/* (Retired in v763) Intra-row hairlines were competing chrome — the
   .quest-obj-list `gap: 0.5rem` already separates rows cleanly. Other
   list patterns in the app (.session-list, .alpha-entries-grid,
   .party-cards-grid) rely on gap alone for the same reason. */

/* (v784) Quest action button hover glow retired — Mark Completed / Mark
   Failed / Reopen now use .btn-new which carries its own white-glow lift. */

/* ── Party PC cards — portrait vignette + frame glow on hover ─────────── */
.party-pc-portrait {
  position: relative;
}
[data-theme="dark"] .party-pc-portrait::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    transparent 60%,
    color-mix(in srgb, var(--color-surface) 55%, transparent) 100%
  );
  pointer-events: none;
}
/* Dark-mode hover override removed in v743 — canonical hover now lives on
   .party-pc-card itself (mirrors .campaign-card / .session-card / .entity-row).
   The previous full-saturation teal border + outer ring + inset glow was
   visually louder than the rest of the app's card hovers. */

/* ── Recording start button — idle gold glow pulse ──────────────────── */
.rec-start-btn {
  animation: rec-idle-gold 3.5s ease-in-out infinite;
}
.rec-start-btn:hover, .rec-start-btn:focus-visible { animation: none; }
@keyframes rec-idle-gold {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-gold) 0%, transparent); }
  50%       { box-shadow: 0 0 10px 2px color-mix(in srgb, var(--color-gold) 28%, transparent); }
}

/* ── Live indicator — breathing glow ─────────────────────────────────── */
.camp-live-dot {
  animation: live-breath 3s ease-in-out infinite;
}
@keyframes live-breath {
  0%, 100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--color-gold) 55%, transparent); opacity: 1; }
  50%       { box-shadow: 0 0 7px 2px color-mix(in srgb, var(--color-gold) 20%, transparent); opacity: 0.8; }
}

/* ── Live preamble "Where We Left Off" — split underline ─────────────── */
.live-preamble-section-hdr {
  border-bottom: 1px solid var(--split-color);
  padding-bottom: 0.3rem;
  margin-bottom: 0.4rem;
}

/* ── Active tab — restore correct token (uses ember for glow) ─────────── */
[data-theme="dark"] .tab-btn.active {
  text-shadow: 0 0 16px color-mix(in srgb, var(--color-brand) 65%, transparent);
}


/* ═══════════════════════════════════════════════════════════════════════════
   ORNATE FANTASY LAYER — 20% intensity
   Target: "forged steel + ember" — etched edges, metallic depth, no noise.
   Rule: if it draws attention → halve it. If it hurts readability → remove.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Dark mode card grounding — slate surface, flat border, clean shadow ── */
[data-theme="dark"] .campaign-card {
  background-color: color-mix(in srgb, var(--color-bg) 22%, var(--color-surface));
  border: 1px solid rgba(255,255,255,0.06);
  box-shadow: 0 4px 12px rgba(0,0,0,0.35);
  border-radius: var(--radius-lg);
}

[data-theme="dark"] .session-card {
  background: color-mix(in srgb, var(--color-bg) 22%, var(--color-surface));
  border: 1px solid rgba(255,255,255,0.06);
  box-shadow: 0 4px 12px rgba(0,0,0,0.35);
}

[data-theme="dark"] .party-pc-card {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    0 3px 10px rgba(0,0,0,0.25);
}


/* Active TF mark — faint teal sigil glow */
[data-theme="dark"] .tab-btn.active .tf-tab-mark {
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--color-brand) 65%, transparent));
}

/* ── Live header — etched divider feel ────────────────────────────────── */
[data-theme="dark"] .live-header {
  box-shadow:
    inset 0 -1px 0 rgba(0,0,0,0.45),
    inset 0 1px 0 rgba(255,255,255,0.025);
}

/* ── Live sidebar + chat drawer — inner teal glow (very diffuse) ────────── */
[data-theme="dark"] .live-sidebar,
[data-theme="dark"] .live-ai-drawer {
  box-shadow: inset 0 0 70px color-mix(in srgb, var(--color-brand) 4%, transparent);
}

/* ── Live preamble — inner engraved feel ─────────────────────────────── */
[data-theme="dark"] .live-preamble {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 0 -1px 0 rgba(0,0,0,0.28);
}

/* ── Live recorder border — gold-tinged at rest ──────────────────────── */
[data-theme="dark"] .live-recorder {
  border-bottom: 1px solid color-mix(in srgb, var(--color-gold) 14%, var(--color-border));
}

/* ── Arcane compass rose — map panel corner (pointer-events off) ───────── */
/* Tiny ember gold geometry, barely visible, adds depth behind the map */
[data-theme="dark"] .live-map-panel::after {
  content: '';
  position: absolute;
  bottom: 10px; right: 10px;
  width: 72px; height: 72px;
  background-image: url("data:image/svg+xml,%3Csvg opacity='0.09' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='none' stroke='%23D4A843' stroke-width='1.8'/%3E%3Cpath d='M50 4 L96 50 L50 96 L4 50 Z' fill='none' stroke='%23D4A843' stroke-width='1.8'/%3E%3Cpath d='M50 26 L74 50 L50 74 L26 50 Z' fill='none' stroke='%23D4A843' stroke-width='1.2'/%3E%3Cline x1='50' y1='4' x2='50' y2='96' stroke='%23D4A843' stroke-width='0.9'/%3E%3Cline x1='4' y1='50' x2='96' y2='50' stroke='%23D4A843' stroke-width='0.9'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  pointer-events: none;
  z-index: 5;
}



/* ── Entity preview drawer — inner engraved edge ─────────────────────── */
[data-theme="dark"] .entity-preview-drawer {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.04),
    inset 1px 0 0 rgba(255,255,255,0.02),
    0 0 50px rgba(0,0,0,0.55);
}

/* Session card border is uniform — no etched-edge effect */

/* ── Chronicle panel — very faint teal inner glow ────────────────────── */
[data-theme="dark"] #sessions-panel {
  background: radial-gradient(ellipse 80% 30% at 50% 0%,
    color-mix(in srgb, var(--color-brand) 3%, transparent) 0%,
    transparent 100%);
}

/* ── Mobile second tab bar ─────────────────────────────────────────────
   On narrow screens the 7 content tabs break out of the main nav row
   into a full-width swipeable strip directly below it.
   Desktop / tablet layouts are completely unaffected.
──────────────────────────────────────────────────────────────────────── */
/* Campaign grid collapses to 1 column on narrow viewports; divider hidden */
@media (max-width: 540px) {
  .campaign-list-screen {
    grid-template-columns: 1fr;
    gap: 0.75rem;
  }
  .campaign-grid-wrap::before { display: none; }
}

@media (max-width: 600px) {
  /* Allow the nav to wrap — first row: logo + Live + auth; second row: tabs */
  #tab-nav {
    flex-wrap: wrap;
    overflow: visible;   /* let the second row extend below without clipping */
    padding: 0 0.5rem 0 0.75rem;
  }

  /* Push the tab strip to a second line, full width */
  #tab-scroll-wrap {
    order: 10;
    width: 100%;
    flex: none;
    min-width: unset;
    border-top: 1px solid var(--border);
    /* Fades still work — position:relative is inherited from the base rule */
  }

  /* Slightly more compact tabs on the mobile strip */
  #tab-scroll-area {
    padding: 0 0.25rem;
  }
  #tab-scroll-area .tab-btn {
    padding: 0.45rem 0.8rem;
    font-size: var(--font-size-sm);
    white-space: nowrap;
  }
}

/* ── PC Sheet Import / Stats Edit shared ─────────────────────────────────── */
.form-section-label {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
}
.pc-stats-field-lbl {
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
}
.form-input {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  padding: 0.55rem 0.75rem;
  width: 100%;
  box-sizing: border-box;
  transition: border-color var(--t-hover);
}
.form-input:focus {
  outline: none;
  border-color: var(--color-brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 12%, transparent);
}

/* ── PC Stats Edit modal layout ───────────────────────────────────────────── */
/* Width comes from .modal-box--lg (560px) — the max-width override was
   removed in v748 along with the duplicate sizing logic. This class now
   only contributes the inner flex-column layout that the body needs. */
.pse-modal-box {
  max-height: 92vh;
  display: flex;
  flex-direction: column;
}
.pse-body {
  overflow-y: auto;
  flex: 1;
  padding: var(--space-4) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.pse-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: 0.75rem var(--space-5);
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}
.pse-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}
.pse-section:last-of-type { border-bottom: none; padding-bottom: 0; }
.pse-label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.pse-label--center { align-items: center; }
.pse-grid-2 { display: grid; grid-template-columns: 1fr 1fr;            gap: var(--space-3); }
.pse-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr);     gap: var(--space-3); }
.pse-grid-5 { display: grid; grid-template-columns: repeat(5, 1fr);     gap: var(--space-2); }
.pse-grid-6 { display: grid; grid-template-columns: repeat(6, 1fr);     gap: var(--space-2); }
.pse-score-input { text-align: center; padding: 0.4rem 0.25rem; }

/* Saving throws */
.pse-saves-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.pse-save-cb {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
  cursor: pointer;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  transition: background var(--t-hover), border-color var(--t-hover);
}
.pse-save-cb:hover, .pse-save-cb:focus-visible { background: var(--color-surface); }
.pse-save-cb input { accent-color: var(--color-brand); }

/* Skills grid */
.pse-skills-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}
.pse-skill-cell {
  display: grid;
  grid-template-columns: 3rem 1fr auto;
  align-items: center;
  gap: 0.3rem;
}
.pse-skill-name {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-text-secondary);
  white-space: nowrap;
}
.pse-skill-input {
  padding: 0.25rem 0.4rem;
  text-align: center;
  font-size: var(--font-size-sm);
  min-width: 0;
}
.pse-skill-adv {
  display: flex;
  align-items: center;
  gap: 2px;
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--color-text-tertiary);
  cursor: pointer;
  white-space: nowrap;
}
.pse-skill-adv input { accent-color: var(--color-brand); }

/* ── Character Import Pipeline ───────────────────────────────────────────── */
.import-review-box {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xl);
  box-shadow: 0 8px 32px rgba(0,0,0,0.20);
  width: min(96vw, 960px);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.import-step { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }

/* ── Step 1: Pick ── */
.import-pick-body {
  flex: 1;
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  overflow-y: auto;
}
.import-reimport-warn {
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-warning) 50%, transparent);
  border-radius: var(--radius-lg);
  padding: 0.65rem 0.9rem;
  font-size: var(--font-size-sm);
  color: var(--color-text-primary);
  line-height: 1.5;
}
.import-pick-desc {
  font-size: var(--font-size-base);
  color: var(--color-text-secondary);
}
.import-pick-options {
  display: flex;
  gap: var(--space-4);
}
.import-pick-option {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-4);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  cursor: pointer;
  transition: background var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease), box-shadow var(--t-hover) var(--ease);
  text-align: center;
  user-select: none;
}
.import-pick-option:hover, .import-pick-option:focus-visible {
  background: var(--bg-3);
  border-color: var(--color-brand);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.import-pick-icon { font-size: 2rem; line-height: 1; }
.import-pick-label { font-size: var(--font-size-base); font-weight: 600; color: var(--color-text-primary); }
.import-pick-hint  { font-size: var(--font-size-sm); color: var(--color-text-tertiary); }
.import-pick-footer {
  display: flex;
  justify-content: flex-end;
  padding: 0.75rem var(--space-6);
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

/* ── Step 2: Parsing ── */
.import-parsing-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-8);
}
.import-parsing-animation {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.import-parsing-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-brand);
  animation: pulse 1.2s ease-in-out infinite;
}
.import-parsing-dot:nth-child(2) { animation-delay: var(--t-trans); }
.import-parsing-dot:nth-child(3) { animation-delay: var(--t-trans); }
.import-parsing-label { font-size: var(--font-size-lg); font-weight: 600; color: var(--color-text-primary); }
.import-parsing-hint  { font-size: var(--font-size-sm); color: var(--color-text-tertiary); }

/* ── Step 3: Review ── */
.import-review-layout {
  flex: 1;
  min-height: 0;
  display: flex;
  overflow: hidden;
}
.import-review-preview-pane {
  width: 320px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  background: var(--bg);
  overflow-y: auto;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.import-preview-thumb {
  width: 100%;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  display: block;
}
.import-preview-label {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  text-align: center;
}
.import-preview-no-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  gap: var(--space-3);
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
  font-style: italic;
  min-height: 160px;
}
.import-review-form-pane {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-5);
}
.import-section {
  margin-bottom: var(--space-6);
}
.import-section-title {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border);
}
.import-fields-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-3);
}
.import-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.import-field-label {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-text-secondary);
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.import-field input,
.import-field textarea,
.import-field select {
  font-size: var(--font-size-sm);
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--color-text-primary);
  width: 100%;
  box-sizing: border-box;
}
.import-field textarea { resize: vertical; min-height: 64px; }
.import-field input:focus,
.import-field textarea:focus {
  border-color: var(--color-brand);
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-brand) 12%, transparent);
}

/* Confidence indicators */
.import-field.conf-low input,
.import-field.conf-low textarea {
  border-color: color-mix(in srgb, var(--color-warning) 60%, transparent);
  background: color-mix(in srgb, var(--color-warning) 6%, var(--bg));
}
.import-field.conf-missing input,
.import-field.conf-missing textarea {
  border-color: color-mix(in srgb, var(--color-danger) 40%, transparent);
  background: color-mix(in srgb, var(--color-danger) 5%, var(--bg));
}
.conf-badge {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 5px;
  border-radius: var(--radius-full);
}
.conf-badge--high    { background: color-mix(in srgb, var(--color-success) 18%, transparent); color: var(--color-success); }
.conf-badge--medium  { background: color-mix(in srgb, var(--color-info) 18%, transparent);    color: var(--color-info); }
.conf-badge--low     { background: color-mix(in srgb, var(--color-warning) 18%, transparent); color: var(--color-warning); }
.conf-badge--missing { background: color-mix(in srgb, var(--color-danger) 15%, transparent);  color: var(--color-danger); }

/* Ability scores mini-grid */
.import-ability-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-2);
}
.import-ability-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.import-ability-cell label {
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}
.import-ability-cell input {
  width: 100%;
  text-align: center;
  font-size: var(--font-size-sm);
  padding: 0.3rem 0.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--color-text-primary);
  box-sizing: border-box;
}
.import-ability-cell input:focus {
  border-color: var(--color-brand);
  outline: none;
}
.import-ability-mod {
  font-size: 0.65rem;
  color: var(--color-text-tertiary);
  text-align: center;
}

.import-review-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem var(--space-5);
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

/* Mobile responsive */
@media (max-width: 700px) {
  .import-review-box { width: 100vw; max-height: 100dvh; border-radius: 0; }
  .import-review-layout { flex-direction: column; }
  .import-review-preview-pane { width: 100%; border-right: none; border-bottom: 1px solid var(--border); max-height: 180px; }
  .import-pick-options { flex-direction: column; }
  .import-ability-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ── Feature Showcase Panels ─────────────────────────────────────────────── */

/* ── Login: hero column ───────────────────────────────────────────────────── */
.login-hero {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 3rem 3.5rem 3rem 5%;
  gap: 1.75rem;
  height: 100%;
  overflow: hidden;
  border-right: 1px solid var(--border);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--color-brand) 5%, transparent) 0%,
    transparent 60%);
}
.login-form-col {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  overflow-y: auto;
  padding: 2rem 1.5rem;
}
.login-hero-eyebrow {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--color-brand);
}
.login-hero-headline {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: clamp(1.5rem, 3vw, 2.2rem);
  font-weight: 700;
  color: var(--color-text-primary);
  line-height: 1.2;
  margin: 0;
}
.login-hero-sub {
  font-size: var(--font-size-md);
  color: var(--color-text-secondary);
  line-height: 1.5;
  margin: 0;
}
.login-hero-features {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.lhf-item {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}
.lhf-icon {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6rem;
  margin-top: 2px;
}
.lhf-icon--teal { background: color-mix(in srgb, var(--color-brand) 15%, transparent); color: var(--color-brand); }
.lhf-icon--violet { background: color-mix(in srgb, var(--color-purple) 15%, transparent); color: var(--color-purple); }
.lhf-icon--ember { background: color-mix(in srgb, var(--color-ember) 15%, transparent); color: var(--color-ember); }
.lhf-title {
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--color-text-primary);
  margin-bottom: 0.15rem;
}
.lhf-desc {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: 1.45;
}

/* Login hero — CSS app mockup */
.login-hero-mockup {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  overflow: hidden;
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.lh-mock-bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.75rem;
  background: var(--color-surface-elevated);
  border-bottom: 1px solid var(--color-border);
}
.lh-mock-tab {
  font-size: 0.65rem;
  color: var(--color-text-tertiary);
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-sm);
}
.lh-mock-tab--active {
  color: var(--color-brand);
  background: color-mix(in srgb, var(--color-brand) 12%, transparent);
}
.lh-mock-live-dot {
  margin-left: auto;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--color-success);
  animation: presence-pulse 2s ease-in-out infinite;
}
.lh-mock-session-list {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.lh-mock-session {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.25rem 0.4rem;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  font-size: 0.65rem;
}
.lh-mock-session--active {
  background: color-mix(in srgb, var(--color-brand) 10%, transparent);
  color: var(--color-text-primary);
  border-left: 2px solid var(--color-ember);
}
.lh-mock-date { color: var(--color-text-tertiary); font-size: 0.6rem; }
.lh-mock-recap {
  padding: 0.6rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.lh-mock-beat {
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-ember);
  margin-top: 0.2rem;
}
.lh-mock-text {
  font-size: 0.67rem;
  color: var(--color-text-secondary);
  line-height: 1.5;
}
.lh-mock-link {
  color: var(--color-brand);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--color-brand) 40%, transparent);
  cursor: default;
}

/* v957: campaign-selector empty state — first-time GMs (zero campaigns).
   Replaces the bare "No campaigns yet" placeholder with a welcome card
   that pairs the primary "create your campaign" CTA with the GM Discord
   invite. Both surfaced together while the user is in "exploring" mode. */
.cs-empty-state {
  margin: var(--space-6) auto;
  max-width: 640px;
  padding: var(--space-6) var(--space-5);
  text-align: center;
  background: color-mix(in srgb, var(--accent-teal) 5%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--accent-teal) 22%, var(--border));
  border-radius: var(--radius-xl);
}
.cs-empty-title {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--text);
  letter-spacing: 0.01em;
}
.cs-empty-body {
  margin: 0 0 var(--space-4);
  font-size: var(--font-size-base);
  line-height: 1.55;
  color: var(--color-text-secondary);
}
.cs-empty-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.cs-empty-cta-secondary {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* v957: returning-GM Discord footer — subtle, dismissable. Sits below
   the campaign list, above the showcase + archives. Single line, low
   visual weight: meant to be ignorable for engaged users but
   discoverable for users who haven't found the community yet. */
.cs-discord-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
  border-top: 1px solid color-mix(in srgb, var(--accent-teal) 18%, transparent);
}
.cs-discord-footer-text { line-height: 1.5; }
.cs-discord-footer-link {
  color: var(--accent-teal);
  text-decoration: none;
  font-weight: var(--font-weight-semibold);
  transition: color var(--t-hover) var(--ease);
}
.cs-discord-footer-link:hover,
.cs-discord-footer-link:focus-visible {
  color: var(--text);
  text-decoration: underline;
  outline: none;
}
.cs-discord-footer-dismiss {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-3);
  font-size: var(--font-size-xs);
  line-height: 1;
  padding: 0.2rem 0.45rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: 0.55;
  transition:
    opacity      var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}
.cs-discord-footer-dismiss:hover,
.cs-discord-footer-dismiss:focus-visible {
  opacity: 1;
  color: var(--text-1);
  border-color: var(--border);
  background: var(--bg-3);
  outline: none;
}

/* ── Campaign selector: showcase strip ──────────────────────────────────── */
/* Outer spacing now lives on #cs-showcase-section (mirrors the
   "Recently Archived" rhythm). Local margins reset to 0 so the wrapper
   is the single source of truth for vertical spacing. */
.cs-showcase {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  margin-top: 0;
  margin-bottom: 0;
  overflow: hidden;
  transition: opacity var(--t-trans) var(--ease);
}
.cs-showcase.cs-showcase--dismissed { display: none; }
/* When the showcase is dismissed, hide the wrapping section too — otherwise
   the "Thread|fall Features" label would float alone above the archived list. */
#cs-showcase-section:has(.cs-showcase--dismissed) { display: none; }
/* All slides occupy the same grid cell so the container's height is fixed at
   the tallest slide. Without this, slides with mockups vs. without flicker the
   showcase height when cycling, which (combined with the 16:9 campaign cards
   above) causes the cards to visibly resize as the scrollbar toggles. */
.cs-showcase-slides {
  display: grid;
  grid-template-areas: "stack";
}
.cs-showcase-slide {
  grid-area: stack;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 1.5rem;
  padding: 1.25rem 1.5rem 1.1rem;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.cs-showcase-slide--active {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
.css-body { flex: 1 1 auto; min-width: 0; }
.css-icon {
  flex-shrink: 0;
  width: 36px; height: 36px;
  border-radius: var(--radius-lg);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--font-size-sm);
  margin-top: 2px;
}
.css-icon--teal { background: color-mix(in srgb, var(--color-brand) 15%, transparent); color: var(--color-brand); }
.css-icon--violet { background: color-mix(in srgb, var(--color-purple) 15%, transparent); color: var(--color-purple); }
.css-icon--ember { background: color-mix(in srgb, var(--color-ember) 15%, transparent); color: var(--color-ember); }
.css-eyebrow {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  margin-bottom: 0.2rem;
}
.css-headline {
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-text-primary);
  margin-bottom: 0.25rem;
}
.css-desc {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: 1.5;
}
.cs-showcase-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 1.25rem 0.65rem;
  border-top: 1px solid var(--color-border);
}
.cs-showcase-dots {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
.cs-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  border: none;
  background: var(--color-border);
  cursor: pointer;
  padding: 0;
  transition: background var(--t-trans), transform var(--t-trans);
}
.cs-dot--active {
  background: var(--color-brand);
  transform: scale(1.3);
}
.cs-showcase-dismiss {
  background: none;
  border: none;
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
  cursor: pointer;
  padding: 0.2rem 0.4rem;
  border-radius: var(--radius-sm);
  line-height: 1;
  transition: color var(--t-hover);
}
.cs-showcase-dismiss:hover, .cs-showcase-dismiss:focus-visible { color: var(--color-text-secondary); }

/* ── Responsive: collapse login hero on small screens ──────────────────── */
@media (max-width: 860px) {
  .login-screen {
    grid-template-columns: 1fr;
    align-items: flex-start;
    overflow-y: auto;
  }
  .login-hero { display: none; }
  .login-form-col {
    min-height: 100%;
    padding: 2rem 1rem;
  }
}
@media (max-width: 768px) {
  .cs-showcase-slide { flex-direction: column; gap: 0.6rem; }
}

/* ── Login hero carousel ─────────────────────────────────────────────────── */
.login-hero { gap: 1rem; }  /* tighten gap since carousel replaces feature list */

.lh-carousel {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  overflow: hidden;
}
/* Feature accordion */
.lh-accordion {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.lha-item {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--t-hover) var(--ease);
}
.lha-item--active {
  border-color: var(--color-brand);
}
.lha-header {
  display: flex;
  flex-direction: column;
  gap: 0.18rem;
  width: 100%;
  padding: 0.45rem 0.7rem;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}
.lha-title-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
  transition: color var(--t-hover) var(--ease);
}
.lha-item--active .lha-title-row { color: var(--color-text-primary); }
.lha-item:hover:not(.lha-item--active) .lha-title-row, .lha-item:focus-within:not(.lha-item--active) .lha-title-row { color: var(--color-text-primary); }
.lha-desc {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  line-height: 1.35;
  transition: color var(--t-hover) var(--ease);
}
.lha-item--active .lha-desc { color: var(--color-text-secondary); }
.lha-dot { font-size: 0.45rem; }
.lha-dot--teal   { color: var(--color-brand); }
.lha-dot--violet { color: var(--color-purple); }
.lha-dot--ember  { color: var(--color-ember); }
.lha-dot--gold   { color: var(--color-gold); }
.lha-panel {
  display: none;
  padding: 0 0.5rem 0.5rem;
}
.lha-item--active .lha-panel { display: block; }

/* Shared frame for all slide mockups */
.lh-mock-frame {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  overflow: hidden;
  font-size: var(--font-size-xs);
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}

/* ── Codex mockup (slide 2) ─────────────────────────────────────────── */
.lh-mock-codex-filters {
  display: flex; gap: 0.3rem; align-items: center;
  margin-left: auto;
}
.lh-mock-pill {
  font-size: 0.6rem; padding: 0.1rem 0.4rem;
  border-radius: var(--radius-full);
  background: var(--color-surface-elevated);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
}
.lh-mock-pill--active {
  background: color-mix(in srgb, var(--color-brand) 15%, transparent);
  color: var(--color-brand);
  border-color: color-mix(in srgb, var(--color-brand) 30%, transparent);
}
.lh-mock-codex-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 0; padding: 0.5rem 0.6rem;
}
.lh-mock-entity {
  display: flex; flex-direction: column; align-items: flex-start;
  padding: 0.35rem 0.4rem;
  border-bottom: 1px solid var(--color-border);
  gap: 0.15rem;
}
.lh-mock-entity:nth-last-child(-n+3) { border-bottom: none; }
.lh-mock-entity-icon { font-size: var(--font-size-sm); }
.lh-mock-entity-name { font-size: 0.65rem; font-weight: 600; color: var(--color-text-primary); }
.lh-mock-entity-type { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.lh-mock-entity-type--npc   { color: var(--color-muted-violet, #7C6FA0); }
.lh-mock-entity-type--loc   { color: var(--color-brand); }
.lh-mock-entity-type--fac   { color: var(--color-purple); }
.lh-mock-entity-type--item  { color: var(--color-ember); }

/* ── Live capture mockup (slide 3) ──────────────────────────────────── */
.lh-mock-rec-indicator {
  margin-left: auto;
  display: flex; align-items: center; gap: 0.25rem;
  font-size: 0.6rem; font-weight: 700;
  color: var(--color-danger);
}
.lh-mock-rec-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--color-danger);
  animation: rec-blink 1.2s ease-in-out infinite;
}
.lh-mock-live-log { padding: 0.5rem 0.6rem; display: flex; flex-direction: column; gap: 0.3rem; }
.lh-mock-log-row { display: flex; align-items: baseline; gap: 0.35rem; }
.lh-mock-log-time { color: var(--color-text-tertiary); font-size: 0.6rem; flex-shrink: 0; }
.lh-mock-log-tag {
  font-size: 0.55rem; font-weight: 700; text-transform: uppercase;
  padding: 0.1rem 0.35rem; border-radius: var(--radius-full);
  flex-shrink: 0;
}
.lh-mock-log-tag--story  { background: color-mix(in srgb, var(--color-brand) 15%, transparent); color: var(--color-brand); }
.lh-mock-log-tag--combat { background: color-mix(in srgb, var(--color-danger) 15%, transparent); color: var(--color-danger); }
.lh-mock-log-text { font-size: 0.65rem; color: var(--color-text-secondary); line-height: 1.4; }
.lh-mock-log-row--new .lh-mock-log-text { color: var(--color-text-primary); }

/* ── Party assessment mockup (slide 4) ──────────────────────────────── */
.lh-mock-party-summary {
  padding: 0.5rem 0.65rem;
  font-size: 0.65rem; color: var(--color-text-secondary); line-height: 1.45;
  border-bottom: 1px solid var(--color-border);
}
.lh-mock-party-cols {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0; padding: 0.5rem 0.6rem;
}
.lh-mock-party-col { display: flex; flex-direction: column; align-items: flex-start; gap: 0.25rem; }
.lh-mock-party-col:first-child { padding-right: 0.5rem; border-right: 1px solid var(--color-border); }
.lh-mock-party-col:last-child  { padding-left: 0.5rem; }
.lh-mock-party-heading {
  font-size: 0.58rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; margin-bottom: 0.15rem;
}
.lh-mock-party-heading--str { color: var(--color-success); }
.lh-mock-party-heading--gap { color: var(--color-danger); }
.lh-mock-chip {
  display: inline-block;
  font-size: 0.6rem; font-weight: 600;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-full);
  margin-bottom: 0.15rem;
}
.lh-mock-chip--green {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: var(--color-success);
}
.lh-mock-chip--red {
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

/* ── Campaign selector: mockup column ────────────────────────────────── */
.css-mockup {
  flex-shrink: 0;
  width: clamp(240px, 32vw, 360px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-elevated);
  padding: 0.75rem 0.9rem;
  font-size: 0.72rem;
  display: flex; flex-direction: column; gap: 0.4rem;
}
.css-mock-beat {
  font-size: 0.56rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--color-ember);
  margin-top: 0.2rem;
}
.css-mock-text { color: var(--color-text-secondary); line-height: 1.45; }
.css-mock-link { color: var(--color-brand); text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--color-brand) 40%, transparent); }

.css-mock-entity-row { display: flex; align-items: center; gap: 0.35rem; padding: 0.2rem 0; border-bottom: 1px solid var(--color-border); }
.css-mock-entity-row:last-child { border-bottom: none; }
.css-mock-icon-npc, .css-mock-icon-loc, .css-mock-icon-fac, .css-mock-icon-item { font-size: var(--font-size-xs); }
.css-mock-ename { flex: 1; font-weight: 600; color: var(--color-text-primary); }
.css-mock-etype {
  font-size: 0.55rem; font-weight: 700; text-transform: uppercase;
  padding: 0.1rem 0.3rem; border-radius: var(--radius-full);
}
.css-mock-etype--npc  { background: color-mix(in srgb, var(--color-purple) 15%, transparent); color: var(--color-purple); }
.css-mock-etype--loc  { background: color-mix(in srgb, var(--color-brand) 15%, transparent); color: var(--color-brand); }
.css-mock-etype--fac  { background: color-mix(in srgb, var(--color-purple) 15%, transparent); color: var(--color-muted-violet, #7C6FA0); }
.css-mock-etype--item { background: color-mix(in srgb, var(--color-ember) 15%, transparent); color: var(--color-ember); }

.css-mock-log-row { display: flex; align-items: baseline; gap: 0.3rem; }
.css-mock-time { color: var(--color-text-tertiary); font-size: 0.58rem; flex-shrink: 0; }
.css-mock-tag {
  font-size: 0.52rem; font-weight: 700; text-transform: uppercase;
  padding: 0.1rem 0.3rem; border-radius: var(--radius-full); flex-shrink: 0;
}
.css-mock-tag--story  { background: color-mix(in srgb, var(--color-brand) 15%, transparent); color: var(--color-brand); }
.css-mock-tag--combat { background: color-mix(in srgb, var(--color-danger) 15%, transparent); color: var(--color-danger); }
.css-mock-log-text { color: var(--color-text-secondary); line-height: 1.4; }

@media (max-width: 960px) { .css-mockup { display: none; } }

/* ── Session tally editor (Chronicle page) ─────────────────────────────── */
.tally-panel {
  border: 1px solid var(--border);
  border-left: 3px solid var(--color-brand);
  border-radius: var(--radius-md);
  background: var(--bg-2);
  padding: 0.6rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}
.tally-subsection { display: flex; flex-direction: column; gap: 0.35rem; }
.tally-subhdr {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--text-2);
  letter-spacing: 0.02em;
}
.tally-subcount { color: var(--text-3); font-weight: var(--font-weight-normal); margin-left: 0.25rem; }
.tally-rows { display: flex; flex-direction: column; gap: 0.25rem; }
.tally-row {
  display: grid;
  grid-template-columns: minmax(120px, 1.4fr) 60px minmax(160px, 2fr) auto;
  gap: 0.35rem;
  align-items: center;
}
.tally-row input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.45rem;
  font-size: var(--font-size-sm);
  color: var(--text);
  font-family: inherit;
  min-width: 0;
}
.tally-row input:focus { outline: 2px solid var(--color-brand); outline-offset: -1px; border-color: var(--color-brand); }
.tally-count { text-align: right; }
.tally-del { color: var(--color-danger); line-height: 1; padding: 0.25rem 0.4rem; }
.tally-add-row { align-self: flex-start; margin-top: 0.15rem; }
.tally-row-ro {
  padding: 0.25rem 0;
  font-size: var(--font-size-sm);
  color: var(--text);
  line-height: 1.5;
  border-bottom: 1px dashed var(--border);
  display: grid;
  grid-template-columns: 52px 1fr;
  align-items: baseline;
  column-gap: 0.5rem;
}
.tally-row-ro:last-child { border-bottom: none; }
.tally-count-ro {
  color: var(--color-brand);
  font-weight: var(--font-weight-semibold);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.tally-note-ro { color: var(--text-3); font-style: italic; }
.tally-empty {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  padding: 0.2rem 0;
  font-style: italic;
}
.tally-hint {
  font-size: var(--font-size-sm);
  color: var(--text-2);
  padding: 0.5rem 0.75rem;
  background: var(--bg-2);
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  margin: 0;
}
.tally-save-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.25rem;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.tally-meta { font-size: var(--font-size-xs); color: var(--text-3); margin-left: auto; }
.tally-dirty-badge {
  display: inline-block;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning);
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-full);
  margin-left: 0.4rem;
}

@media (max-width: 600px) {
  .tally-row {
    grid-template-columns: 1fr 60px auto;
    grid-template-rows: auto auto;
  }
  .tally-note { grid-column: 1 / -1; }
}

/* ── Campaign To-Date card (Party tab) ─────────────────────────────────── */
.ktd-card {
  grid-column: 1 / -1;
  flex-direction: column;
  padding: 0.9rem 1rem;
  gap: 0.75rem;
}
.ktd-summary {
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
  padding: 0.4rem 0.25rem 0.65rem;
  border-bottom: 1px solid var(--border);
}
.ktd-summary-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 70px;
}
.ktd-summary-num {
  font-size: 1.4rem;
  font-weight: var(--font-weight-bold);
  color: var(--text);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.ktd-summary-lbl {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: 0.1rem;
}
.ktd-sections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.9rem;
}
.ktd-section { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
.ktd-section-hdr {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--text-2);
  letter-spacing: 0.02em;
  padding-bottom: 0.15rem;
  border-bottom: 1px solid var(--border);
}
.ktd-section-count { color: var(--text-3); font-weight: var(--font-weight-normal); }
.ktd-scroll {
  max-height: 260px;
  overflow-y: auto;
  padding-right: 0.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.ktd-row {
  font-size: var(--font-size-sm);
  color: var(--text);
  padding: 0.25rem 0;
  border-bottom: 1px dashed color-mix(in srgb, var(--border) 70%, transparent);
  display: grid;
  /* minmax(0, ...) on the flexible tracks prevents long content from blowing
     out the name column into char-by-char wrapping. */
  grid-template-columns: 52px minmax(0, 1fr) minmax(0, 180px);
  align-items: baseline;
  column-gap: 0.5rem;
}
.ktd-row:last-child { border-bottom: none; }
.ktd-row-event { grid-template-columns: 1fr; row-gap: 0.15rem; }
.ktd-row-top {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr) minmax(0, 180px);
  align-items: baseline;
  column-gap: 0.5rem;
}
.ktd-name {
  min-width: 0;
  color: var(--text);
  /* break on word boundaries first; only hyphenate-in-the-middle for tokens
     that genuinely have no spaces (e.g. urls, single-word compounds). */
  overflow-wrap: break-word;
  word-break: break-word;
}
.ktd-count {
  color: var(--color-brand);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.ktd-session {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  text-align: right;
}
.ktd-note {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  font-style: italic;
  padding-left: calc(52px + 0.5rem);
  line-height: 1.4;
}
.ktd-empty {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  font-style: italic;
  padding: 0.2rem 0;
}
.ktd-sub { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: 0.4rem; }
.ktd-sub:last-child { margin-bottom: 0; }
.ktd-sub-hdr {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.2rem 0 0.1rem;
}
.ktd-sub-count { color: var(--text-3); font-weight: var(--font-weight-normal); }

/* ═══════════════════════════════════════════════════════════════════════════
   LIGHT-THEME PREMIUM PARITY — v798
   Dark theme had a polished "TTRPG by candlelight" treatment via
   [data-theme="dark"] overrides; light theme inherited bare base rules with
   no equivalent premium signal, leaving it feeling like an engineering
   placeholder. The block below adds light-only counterparts using a
   parchment / ink / gold-leaf idiom that matches the dark version's
   perceived depth + hierarchy without inverting the palette.

   Convention: `html:not([data-theme="dark"]) .x { ... }` — same
   specificity as `[data-theme="dark"] .x` so neither steals from the
   other. Keep rules in this block scoped to that selector ONLY.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Body atmosphere — top-anchor brand bloom + warm corner vignette so the
   page doesn't feel like an unstyled dump on light. */
html:not([data-theme="dark"]) body {
  background-image:
    radial-gradient(1200px 600px at 50% -120px,
      color-mix(in srgb, var(--color-brand) 7%, transparent) 0%,
      transparent 70%),
    radial-gradient(800px 500px at 100% 110%,
      color-mix(in srgb, var(--color-ember) 5%, transparent) 0%,
      transparent 60%),
    linear-gradient(rgba(241,245,249,0.70), rgba(241,245,249,0.70)),
    url('/static/bg.png');
  background-blend-mode: normal, normal, normal, normal;
}

/* Active tab — light-mode glow signal (replaces the dark text-shadow). */
html:not([data-theme="dark"]) .tab-btn.active {
  text-shadow: 0 0 6px color-mix(in srgb, var(--color-brand) 35%, transparent);
}
html:not([data-theme="dark"]) .tab-btn.active .tf-tab-mark {
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--color-brand) 55%, transparent));
}

/* Card hover shadow — light theme needs more depth than rgba(0,0,0,0.12)
   which is barely perceptible on near-white. Bump opacity + add subtle
   inset highlight for lifted-vellum feel. */
html:not([data-theme="dark"]) .campaign-card:hover,
html:not([data-theme="dark"]) .campaign-card:focus-visible,
html:not([data-theme="dark"]) .session-card:hover,
html:not([data-theme="dark"]) .session-card:focus-visible,
html:not([data-theme="dark"]) .entity-row:hover,
html:not([data-theme="dark"]) .entity-row:focus-visible,
html:not([data-theme="dark"]) .party-pc-card:hover,
html:not([data-theme="dark"]) .party-pc-card:focus-visible,
html:not([data-theme="dark"]) .quest-nav-row:hover,
html:not([data-theme="dark"]) .quest-nav-row:focus-visible,
html:not([data-theme="dark"]) .map-manage-item:hover,
html:not([data-theme="dark"]) .map-manage-item:focus-visible,
html:not([data-theme="dark"]) .quotes-pc-section:hover,
html:not([data-theme="dark"]) .quotes-pc-section:focus-visible {
  box-shadow:
    0 6px 20px rgba(15,23,42,0.18),
    inset 0 1px 0 rgba(255,255,255,0.7);
}

/* Resting card shadows — dark gets `0 4px 12px rgba(0,0,0,0.35)`; give light
   a softer drop-shadow so cards feel grounded instead of pasted. */
html:not([data-theme="dark"]) .session-card,
html:not([data-theme="dark"]) .party-pc-card {
  box-shadow: 0 1px 3px rgba(15,23,42,0.06);
}

/* Logo drop-shadows — heavy dark outline on light theme so the
   gold/cream "Thread|fall" wordmark inside the PNG reads against the
   near-white page (the bitmap can't be re-coloured per theme; stacked
   dark drop-shadows create an outline that makes the cream text legible
   as inked letters on vellum). v800. */
html:not([data-theme="dark"]) .login-logo,
html:not([data-theme="dark"]) .nav-logo,
html:not([data-theme="dark"]) .campaign-selector-logo {
  filter:
    drop-shadow(0 0 1px rgba(15,23,42,0.95))
    drop-shadow(0 0 2px rgba(15,23,42,0.65))
    drop-shadow(0 1px 2px rgba(15,23,42,0.35));
}

/* Live header — light parchment gradient + warm gold border-bottom mirrors
   the dark version's gold-tint + linear-gradient. Without this the Live
   screen looks identical to the rest of the app on light theme. */
html:not([data-theme="dark"]) .live-header {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--color-gold) 5%, var(--bg)) 0%,
    var(--bg) 50%,
    color-mix(in srgb, var(--color-gold) 5%, var(--bg)) 100%);
  border-bottom: 2px solid color-mix(in srgb, var(--color-gold) 35%, transparent);
  box-shadow: inset 0 -1px 0 rgba(15,23,42,0.05);
}

/* Live sidebar / drawer — warm cream inset glow mirrors the dark theme's
   teal glow. Quiet, not distracting; same premium "lit room" effect. */
html:not([data-theme="dark"]) .live-sidebar,
html:not([data-theme="dark"]) .live-ai-drawer {
  box-shadow: inset 0 0 50px color-mix(in srgb, var(--color-gold) 4%, transparent);
}

/* Live preamble — engraved inset on light. */
html:not([data-theme="dark"]) .live-preamble {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    inset 0 -1px 0 rgba(15,23,42,0.06);
}

/* Sessions panel — soft daytime mist mirroring dark's faint teal radial. */
html:not([data-theme="dark"]) #sessions-panel {
  background: radial-gradient(circle at 50% 0%,
    color-mix(in srgb, var(--color-brand) 4%, transparent) 0%,
    transparent 60%);
}

/* Party PC portrait vignette — light counterpart to dark's right-side
   gradient. Soft ink shadow at bottom-right gives depth on light too. */
html:not([data-theme="dark"]) .party-pc-portrait::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg,
    transparent 60%,
    rgba(15,23,42,0.06) 88%,
    rgba(15,23,42,0.10) 100%);
}

/* Login hero — warm ivory→teal-tinted gradient gives the form column the
   same atmospheric weight dark gets from its 5%-brand wash. */
html:not([data-theme="dark"]) .login-hero {
  background: linear-gradient(135deg,
    #FAF6EE 0%,
    color-mix(in srgb, var(--color-brand) 7%, var(--bg)) 100%);
}

/* Map hover-card / popup shadow — 55% black is correct on dark map but
   bruises on light Atlas tiles. Soften. */
html:not([data-theme="dark"]) .map-hover-card,
html:not([data-theme="dark"]) .leaflet-popup-content-wrapper {
  box-shadow: 0 8px 28px rgba(15,23,42,0.20) !important;
}

/* Map pin — pale-gray on light map = washed-out silhouette. Use warm
   ivory so the teardrop reads against terrain art. */
html:not([data-theme="dark"]) .map-pin {
  background: #FAF6EE;
}

/* v801: cover-image cards on light theme — INVERSE of the dark-mode tint.
   Dark theme uses a black wash (rgba(11,15,20,0.78→0.55→0.82)) so white
   text reads against bright cover art. v800 first attempt thinned that
   black wash on light theme — but the title is var(--text) (dark on light),
   so a thin dark wash + busy art still ate the text. The correct inverse
   is a WHITE/parchment wash at the same opacity progression, which mutes
   the art to a bright surface that dark text reads against cleanly.
   Slight warm tint (252,247,237) keeps it sympathetic to the parchment
   atmosphere rather than a clinical pure-white. */
html:not([data-theme="dark"]) .campaign-card.campaign-card--has-cover {
  background-image:
    url("data:image/svg+xml,%3Csvg opacity='0.18' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='none' stroke='%232F7C85' stroke-width='2.2'/%3E%3Ccircle cx='50' cy='50' r='34' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Ccircle cx='50' cy='50' r='19' fill='none' stroke='%232F7C85' stroke-width='1.4'/%3E%3Cline x1='50' y1='4' x2='50' y2='96' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='4' y1='50' x2='96' y2='50' stroke='%232F7C85' stroke-width='1.2'/%3E%3Cline x1='18' y1='18' x2='82' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cline x1='82' y1='18' x2='18' y2='82' stroke='%232F7C85' stroke-width='0.9'/%3E%3Cpath d='M50 16 L84 50 L50 84 L16 50 Z' fill='none' stroke='%232F7C85' stroke-width='1.2'/%3E%3C/svg%3E"),
    var(--cover-tint, linear-gradient(transparent, transparent)),
    linear-gradient(180deg, rgba(252,247,237,0.70) 0%, rgba(252,247,237,0.48) 50%, rgba(252,247,237,0.74) 100%),
    var(--cover-img, linear-gradient(transparent, transparent));
}
/* Cover-card meta text — the dark-mode rule mixes white into --text-3 to
   keep the muted line readable against the dark wash. On light theme that
   washes the meta into the parchment wash. Restore the canonical --text-3
   reading by mixing toward INK instead of white. */
html:not([data-theme="dark"]) .campaign-card.campaign-card--has-cover .campaign-card-meta {
  color: color-mix(in srgb, var(--text-3) 55%, var(--text) 45%);
}

/* ── Threads tab (Phase 1) ─────────────────────────────────────────────────
   Loose-thread cards mirror .session-card / .entity-row recipe exactly:
   shadowless rest, hover lift + teal-tinted border + drop shadow. Status
   pill colors compose existing semantic tokens — no new color values added.
   The 4-state status enum is class-keyed (.thread-status-pill--hot etc.)
   rather than CSS-var-keyed because the colors are shared across all
   instances of a given status, not per-row.
   Tab layout composes .compendium-section-hdr for the header (eyebrow
   LEFT + CTA RIGHT, no padding) — .tab-panel default 1.5rem handles
   outer spacing. Don't add padding here or it'll double-pad.
*/
.threads-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* F1: thread cards are raw material for weaves now, not the feature.
   Compact: ~40% less vertical real estate per card so the GM can scan a
   dozen threads without scrolling. Context truncates to 2 lines via
   line-clamp; full text on hover via title attribute (added in JS). */
.thread-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.55rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  transition: transform var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease), box-shadow var(--t-hover) var(--ease);
}
.thread-card:hover {
  transform: translateY(-1px);
  border-color: rgba(47, 154, 163, 0.45);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10);
}
/* v935: muted "ghost pill" recipe — same pattern as .camp-role-member
   (bg-3 + neutral border on light; rgba(255,255,255,0.07) + 0.18 on
   dark). No shadow halo; just a quiet tonal lift so the selected
   row reads "active" without shouting. */
.thread-card--selected {
  background: var(--bg-3);
  border-color: var(--border);
  box-shadow: none;
}
[data-theme="dark"] .thread-card--selected {
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.18);
}

/* ── Fates F1.5: unified loom-table for Possible / Player / Threads ─────── */

/* Junction-thread callout — single line above the Possible Weaves
   carousel naming the thread(s) that connect 2+ weaves. Surfaces the
   campaign's structural connector once instead of letting it appear
   implicitly by repetition across cards. */
.fates-junction-callout {
  margin: 0.4rem 0 0.7rem;
  padding: 0.45rem 0.7rem;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  letter-spacing: 0.02em;
  border-left: 2px solid color-mix(in srgb, var(--accent-teal) 50%, transparent);
  background: color-mix(in srgb, var(--accent-teal) 7%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.fates-junction-callout strong {
  color: var(--color-text-secondary);
  font-weight: var(--font-weight-bold);
}

/* Section wrapper — Possible Weaves carousel + Player Weave Surface
   share this container shape. The threads-list section uses the
   existing .compendium-section-hdr above #threads-body, so it doesn't
   need this. */
.fates-section {
  margin-bottom: var(--space-4);
}
.fates-section-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  flex-wrap: wrap;
}
.fates-section-actions {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
/* .fates-section-meta removed — was an unused re-implementation of
   the .form-section-label eyebrow recipe. Compose .form-section-label
   if a meta line is needed here later. */

/* Suggestions modal — opened from the GM Weave header's See Suggestions
   trigger. Body composes the existing loom-table + a synthesis line
   below + nav/actions. Carousel prev/next buttons are small chevrons. */
.fates-suggestions-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.fates-suggestions-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.fates-suggestions-counter {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}
.fates-suggestions-nav {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
.fates-carousel-btn {
  min-width: 2rem;
  padding: 0.2rem 0.6rem;
  font-size: var(--font-size-md);
  line-height: 1;
}
.fates-carousel-btn:disabled {
  opacity: 0.4;
  cursor: default;
}
.fates-suggestion-meta {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  letter-spacing: 0.01em;
}
/* Synthesis area — forward-looking narrative one-liner from
   /fates/weave-synthesis. Lazy-fetched on cursor change inside the
   modal. Eyebrow label "Possible scene" + the one-liner body. */
.fates-suggestion-synthesis {
  padding: 0.7rem 0.85rem;
  background: color-mix(in srgb, var(--accent-teal) 8%, var(--bg-2));
  border-left: 2px solid color-mix(in srgb, var(--accent-teal) 55%, transparent);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-height: 3.4rem;
}
.fates-suggestion-synthesis:empty {
  display: none;
}
.fates-suggestion-synthesis-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}
.fates-suggestion-synthesis-label--error {
  color: var(--color-danger);
}
.fates-suggestion-synthesis-text {
  margin: 0;
  font-size: var(--font-size-md);
  color: var(--text);
  line-height: 1.4;
}
.fates-suggestion-synthesis-text--loading {
  color: var(--color-text-tertiary);
  font-style: italic;
}
.fates-suggestion-synthesis-text--error {
  color: var(--color-danger);
}
.fates-suggestions-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: 0.2rem;
}
.fates-suggestions-add {
  /* The primary CTA inside the modal — a touch heavier than the rest. */
  background: color-mix(in srgb, var(--accent-teal) 18%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent-teal) 55%, var(--border));
  color: var(--text);
}
.fates-suggestions-add:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-teal) 28%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent-teal) 75%, var(--border));
}
.fates-suggestions-add:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Thread description tooltip — shared across the threads list +
   GM Weave thread rail. Side-positioned by JS so it never sits on
   top of the cursor's read; a leader line (CSS ::before, drawn per
   data-placement attribute) connects the tooltip to the hovered
   row. Solid opaque background with strong elevation so it reads
   cleanly against busy backgrounds (codex tab, the loom-table,
   the GM Weave SVG ribbons). pointer-events:none so the cursor
   never accidentally hovers the tooltip and snags. */
.fates-thread-tooltip {
  position: fixed;
  /* Sit above everything — modals (~1000), the suggestions overlay,
     and the GM Weave SVG ribbons. The earlier z-index of 100 was
     beaten by the SVG paths' compositing layer in some browsers,
     leaving the tooltip text behind the ribbons and dots. */
  z-index: 10000;
  width: max-content;
  max-width: min(26rem, calc(100vw - 4rem));
  padding: 0.7rem 0.9rem;
  /* Layered background: hardcoded near-black fallback first, theme
     token on top. Defends against any token going semi-transparent
     in a future theme — the tooltip stays modal-grade opaque. */
  background-color: #131823;
  background: var(--bg-2, #131823);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 50%, var(--border));
  border-radius: var(--radius-md);
  font-size: 0.84rem;
  line-height: 1.45;
  color: var(--text);
  /* Two-layer shadow for clean separation from busy backgrounds. */
  box-shadow:
    0 12px 32px rgba(0, 0, 0, 0.55),
    0 2px 6px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  opacity: 1;
  transition: opacity var(--t-hover) var(--ease);
}
.fates-thread-tooltip.hidden {
  /* Override the global .hidden { display:none } so we can fade in/
     out smoothly. JS still flips the class, but visibility is the
     opacity transition rather than display:none → display:block. */
  display: block;
  opacity: 0;
  pointer-events: none;
  /* Move offscreen when hidden so it doesn't intercept anything
     during the fade. */
  left: -9999px;
  top: -9999px;
}
.fates-thread-tooltip-title {
  font-weight: var(--font-weight-bold);
  font-size: 0.88rem;
  color: var(--text);
  margin-bottom: 0.35rem;
  line-height: 1.3;
}
.fates-thread-tooltip-context {
  color: var(--color-text-secondary);
  white-space: pre-wrap;
}

/* Leader line — drawn as a CSS pseudo on the tooltip, positioned by
   the data-placement attribute that JS sets. The line points from
   the tooltip's edge back toward the hovered row's center; the
   row's center y-coord (or x, for below/above placements) is
   threaded in via --leader-y / --leader-x as a CSS variable. */
.fates-thread-tooltip[data-placement="right"]::before,
.fates-thread-tooltip[data-placement="left"]::before {
  content: '';
  position: absolute;
  top: var(--leader-y, 50%);
  width: 28px;
  height: 1.5px;
  background: color-mix(in srgb, var(--accent-teal) 65%, transparent);
  transform: translateY(-50%);
  pointer-events: none;
}
.fates-thread-tooltip[data-placement="right"]::before { left: -28px; }
.fates-thread-tooltip[data-placement="left"]::before  { right: -28px; }
.fates-thread-tooltip[data-placement="below"]::before,
.fates-thread-tooltip[data-placement="above"]::before {
  content: '';
  position: absolute;
  left: var(--leader-x, 50%);
  width: 1.5px;
  height: 28px;
  background: color-mix(in srgb, var(--accent-teal) 65%, transparent);
  transform: translateX(-50%);
  pointer-events: none;
}
.fates-thread-tooltip[data-placement="below"]::before { top: -28px; }
.fates-thread-tooltip[data-placement="above"]::before { bottom: -28px; }
/* Small tipped end cap on the tooltip side of the leader — a tiny
   teal dot where line meets box. Reads as a callout endpoint. */
.fates-thread-tooltip::after {
  content: '';
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent-teal) 80%, transparent);
  pointer-events: none;
}
.fates-thread-tooltip[data-placement="right"]::after {
  left: -3px;
  top: var(--leader-y, 50%);
  transform: translateY(-50%);
}
.fates-thread-tooltip[data-placement="left"]::after {
  right: -3px;
  top: var(--leader-y, 50%);
  transform: translateY(-50%);
}
.fates-thread-tooltip[data-placement="below"]::after {
  top: -3px;
  left: var(--leader-x, 50%);
  transform: translateX(-50%);
}
.fates-thread-tooltip[data-placement="above"]::after {
  bottom: -3px;
  left: var(--leader-x, 50%);
  transform: translateX(-50%);
}

/* GM Weave empty hint — visible only when the section header is
   present but no threads are selected. The whole section stays
   visible so the See Suggestions trigger is reachable from the
   default view. */
/* v967: .fates-player-empty retired — typography flows from canonical
   .placeholder, padding from .placeholder--inline, strong-tag styling
   from the global .placeholder strong rule. The ID #fates-player-empty
   is preserved on the element for the existing JS show/hide handler. */

/* Drag-link discoverability hint — shown below the Weave graph
   whenever it's populated. Quiet typography so it sits as guidance,
   not a banner. */
.fates-player-drag-hint {
  margin: 0.5rem 0 0;
  padding: 0.45rem 0.7rem;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  letter-spacing: 0.01em;
  border-left: 2px solid color-mix(in srgb, var(--accent-teal) 40%, transparent);
  background: color-mix(in srgb, var(--accent-teal) 7%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.fates-player-drag-hint strong {
  color: var(--color-text-secondary);
  font-weight: var(--font-weight-bold);
}

/* ── F1.7 GM Weave relationship-map graph ──
   Bipartite ribbon viz. Threads stack as labeled rows on the left
   rail; entities stack as type-colored pills on the right rail;
   colored ribbons curve between them per thread→entity touchpoint.
   Ribbons are colored by the thread's status pill, so when two
   threads pass through the same entity their ribbons converge and
   cross — that's the visual knot. SVG handles the ribbon paths;
   the rails are HTML for truncation + click-target ergonomics. */
.fates-weave-graph {
  position: relative;
  display: grid;
  /* 4-column layout (F1.7 v2):
       1: thread rail
       2: ribbon SVG gap
       3: entity rail (auto-touched + manually-added entities)
       4: entity picker sidebar (full codex search/checkbox list) */
  grid-template-columns:
    minmax(11rem, 18rem)
    minmax(5rem, 1fr)
    minmax(8rem, 14rem)
    minmax(11rem, 14rem);
  align-items: stretch;
  min-height: 6rem;
  /* Hard ceiling so the entity picker (which can carry hundreds of
     rows in a mature campaign) doesn't stretch the graph row to the
     full codex height. Inside the picker, the list scrolls. Any
     thread/entity rail exceeding 36rem also scrolls via overflow on
     each rail. */
  max-height: 36rem;
  padding: 0.6rem 0;
  background: color-mix(in srgb, var(--accent-teal) 7%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--accent-teal) 18%, var(--border));
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.fates-weave-graph-rail {
  /* Allow each rail to scroll independently if it exceeds the
     graph's max-height (rare but possible for big weaves). */
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--accent-teal) 30%, transparent) transparent;
}

/* Ribbons SVG — overlays the entire graph, painted behind the rail
   rows. pointer-events:auto on the ribbons themselves (set per-path)
   so hover wiring picks them up. */
.fates-weave-graph-ribbons {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
}
.fates-weave-graph-ribbons .fates-weave-ribbon {
  pointer-events: stroke;
}

/* Rails — HTML stack of rows. Threads on the left, entities on the
   right. Both rails sit ABOVE the SVG (z-index 2) so their text and
   pills are crisp and clickable. */
.fates-weave-graph-rail {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.4rem 0.7rem;
  min-width: 0;
}
.fates-weave-graph-rail--threads {
  grid-column: 1;
  grid-row: 1;
  align-items: stretch;
}
.fates-weave-graph-rail--entities {
  grid-column: 3;
  grid-row: 1;
  align-items: flex-start;
  padding-left: 0.4rem;
}

/* Thread row on the left rail — status pill + truncated title +
   heat + hover-revealed snip ×. Background tint on hover so the
   row reads as the active anchor for any ribbons that originate
   from it. */
.fates-weave-thread-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bg-1) 70%, transparent);
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  font-size: var(--font-size-base);
  cursor: default;
  min-height: 2.1rem;
  transition: background var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.fates-weave-thread-row:hover {
  background: color-mix(in srgb, var(--accent-teal) 8%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent-teal) 45%, var(--border));
}
.weave-thread-title {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--font-weight-bold);
  color: var(--text);
}
.weave-thread-heat {
  flex-shrink: 0;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-tertiary);
}
.fates-weave-thread-row .fates-row-action-btn--snip {
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease);
}
.fates-weave-thread-row:hover .fates-row-action-btn--snip,
.fates-weave-thread-row:focus-within .fates-row-action-btn--snip {
  opacity: 1;
}

/* Entity row on the right rail — single pill aligned to its
   ribbon's endpoint. Hover bumps the pill so it reads as the
   highlighted anchor, and reveals the × snip button on the right
   edge so the GM can drop the entity from this weave's view. */
.fates-weave-entity-row {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.45rem;
  border-radius: var(--radius-md);
  min-height: 2.1rem;
  cursor: default;
  transition: background var(--t-hover) var(--ease);
}
.fates-weave-entity-row:hover {
  background: color-mix(in srgb, var(--accent-teal) 5%, transparent);
}
.fates-weave-entity-row .fates-warp-head {
  /* Stretch the pill so a longer entity name has more room to
     breathe; flex:1 hands the leftover row width to the pill so
     the × sits flush right. */
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fates-weave-entity-row .fates-row-action-btn--snip {
  flex-shrink: 0;
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease);
}
.fates-weave-entity-row:hover .fates-row-action-btn--snip,
.fates-weave-entity-row:focus-within .fates-row-action-btn--snip {
  opacity: 1;
}

/* Per-row visual subordination so the GM can read the rail at a
   glance and know which entities anchor multiple selected threads
   (load-bearing) vs. which only touch one (supplementary) vs.
   which they added by hand (intentional outliers).

   Anchors get the default treatment — full pill saturation.
   Singletons soften slightly so the eye lands on anchors first
   without losing the type-color cue. Added entities sit at full
   presence (the GM put them there on purpose) and get a small
   "+" mark on the left so it's obvious at a glance they're not
   thread-derived. */
.fates-weave-entity-row--singleton .fates-warp-head {
  opacity: 0.78;
}
.fates-weave-entity-row--singleton:hover .fates-warp-head,
.fates-weave-entity-row--singleton:focus-within .fates-warp-head {
  opacity: 1;
}
.fates-weave-entity-added {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.85rem;
  height: 0.85rem;
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--accent-teal);
  opacity: 0.72;
  cursor: help;
}

/* Expander row — ghost button at the bottom of the entity rail.
   Surfaces low-commonality singletons that were collapsed under
   "+N more touched" so the GM can pull them into the AI input
   without leaving the workspace. Full-width, low-key, sits flush
   with the rail's vertical rhythm. */
.fates-weave-entity-expander {
  display: block;
  width: 100%;
  margin-top: var(--space-1);
  padding: 0.4rem 0.5rem;
  background: transparent;
  border: 1px dashed color-mix(in srgb, var(--accent-teal) 28%, var(--border));
  border-radius: var(--radius-md);
  color: var(--text-3);
  font-size: var(--font-size-xs);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--t-hover) var(--ease), color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.fates-weave-entity-expander:hover,
.fates-weave-entity-expander:focus-visible {
  background: color-mix(in srgb, var(--accent-teal) 6%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 45%, var(--border));
  color: var(--text-2);
  outline: none;
}
.fates-weave-entity-expander-glyph {
  display: inline-block;
  margin-right: 0.3rem;
  color: color-mix(in srgb, var(--accent-teal) 70%, var(--text-3));
  transition: transform var(--t-hover) var(--ease);
}
.fates-weave-entity-expander-glyph--up { transform: rotate(180deg); }

/* Entity picker sidebar — 4th column of the Weave graph. Search
   input at top + scrollable grouped checkbox list below. */
.fates-weave-entity-picker {
  grid-column: 4;
  grid-row: 1;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.4rem 0.55rem 0.4rem 0.65rem;
  border-left: 1px solid color-mix(in srgb, var(--accent-teal) 16%, var(--border));
  min-width: 0;
  max-height: 100%;
  overflow: hidden;
}
.fates-weave-entity-picker-search {
  width: 100%;
  padding: 0.35rem 0.55rem;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: 0.8rem;
  color: var(--text);
  font-family: inherit;
}
.fates-weave-entity-picker-search:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent-teal) 60%, var(--border));
}
.fates-weave-entity-picker-search::placeholder {
  color: var(--color-text-tertiary);
}
.fates-weave-entity-picker-list {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-right: 0.15rem;
  /* Scrollbar styling — keeps the inner panel from looking like
     it's punched through to the page chrome. */
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--accent-teal) 30%, transparent) transparent;
}
.fates-weave-entity-picker-list::-webkit-scrollbar { width: 6px; }
.fates-weave-entity-picker-list::-webkit-scrollbar-track { background: transparent; }
.fates-weave-entity-picker-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--accent-teal) 30%, transparent);
  border-radius: 3px;
}

.fates-entity-picker-placeholder {
  margin: 0.5rem 0;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}

.fates-entity-picker-group {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}
.fates-entity-picker-eyebrow {
  margin: 0 0 0.2rem;
  /* form-section-label already gives uppercase + letter-spacing */
}

.fates-entity-picker-row {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.2rem 0.35rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-sm);
  line-height: 1.25;
  transition: background 100ms ease;
  user-select: none;
}
.fates-entity-picker-row:hover {
  background: color-mix(in srgb, var(--accent-teal) 6%, transparent);
}
.fates-entity-picker-row input[type="checkbox"] {
  flex-shrink: 0;
  width: 0.95rem;
  height: 0.95rem;
  accent-color: var(--accent-teal);
  cursor: pointer;
  margin: 0;
}
.fates-entity-picker-name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--font-weight-medium, 500);
}

/* Color-code the name by entity type — same canon as warp pills.
   Tinted text only (no background) so the picker stays compact and
   the eyebrow groups still read as the primary structure. */
.fates-entity-picker-row--pc        .fates-entity-picker-name { color: var(--accent-blue); }
.fates-entity-picker-row--companion .fates-entity-picker-name { color: var(--accent-teal); }
.fates-entity-picker-row--deity     .fates-entity-picker-name { color: var(--accent-gold); }
.fates-entity-picker-row--location  .fates-entity-picker-name { color: var(--accent-teal); }
.fates-entity-picker-row--npc       .fates-entity-picker-name { color: var(--color-muted-violet); }
.fates-entity-picker-row--item      .fates-entity-picker-name { color: var(--accent-ember); }
.fates-entity-picker-row--faction   .fates-entity-picker-name { color: var(--color-muted-violet); }
.fates-entity-picker-row--lore      .fates-entity-picker-name { color: var(--accent-green); }
.fates-entity-picker-row--spell     .fates-entity-picker-name { color: var(--accent-purple); }

/* Drag-and-drop: entity rows show a grab cursor; thread rows highlight
   when a draggable entity is hovering them as a valid drop target. */
.fates-weave-entity-row[draggable="true"],
.fates-entity-picker-row[draggable="true"] {
  cursor: grab;
}
.fates-weave-entity-row[draggable="true"]:active,
.fates-entity-picker-row[draggable="true"]:active {
  cursor: grabbing;
}
body.is-fates-dragging .fates-weave-thread-row {
  /* While a drag is in flight, prep the threads visually as
     potential targets (subtle border tint). The actual hovered
     row gets a stronger highlight via .is-drop-target below. */
  outline: 1px dashed color-mix(in srgb, var(--accent-teal) 25%, transparent);
  outline-offset: -1px;
}
.fates-weave-thread-row.is-drop-target {
  background: color-mix(in srgb, var(--accent-teal) 18%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent-teal) 70%, var(--border));
  outline: 1px solid color-mix(in srgb, var(--accent-teal) 80%, transparent);
}

/* ── F2: Saved Fates strip + Weave Fate workspace modal ────────────────── */

/* v947 first-touch intro banner — canonical dismissible per-tab tip.
   Subtle teal-tinted card with leading emoji + title + body. The Fates
   tab adds a workflow chart inside the body via .fates-workflow-* (kept
   Fates-specific). v978: class renamed from .fates-intro-* → .tab-intro-*
   when the pattern was extended to every top-level tab. Dismissable via
   ✕ in the corner; persistent per tab via tf_{tab}_intro_dismissed_v1
   localStorage flag — bump _vN when copy changes meaningfully so
   returning GMs see the updated intro once. */
.tab-intro-banner {
  position: relative;
  /* v985b: OUTER card max-width now matches the INNER .tab-intro-content
     max-width (56rem ≈ 896px). Previously OUTER stretched up to 904/1344px
     while INNER stayed at 56rem, leaving the card visibly wider than its
     own text on every tab EXCEPT Atlas — Atlas's flex-column parent
     happened to constrain OUTER to INNER's intrinsic size, which made it
     look "right". Locking OUTER to 56rem gives every tab that same
     content-hugging width without depending on parent layout quirks.
     Top margin stays load-bearing for split-layout tabs (Sessions /
     Party / Quests / Atlas / Manage have padding:0 on their tab-panel);
     Codex + Threads panels carry their own padding-top, so they override
     margin-top: 0 below. */
  max-width: 56rem;
  margin: var(--space-5) auto var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: color-mix(in srgb, var(--accent-teal) 5%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--accent-teal) 22%, var(--border));
  border-left: 3px solid color-mix(in srgb, var(--accent-teal) 55%, transparent);
  border-radius: var(--radius-lg);
  text-align: center;
}
/* Codex + Threads panels already have padding-top from the canonical
   single-column eyebrow pattern (var(--space-5) var(--space-4)). The
   banner's own margin-top would double the gap there, so suppress it. */
#tab-compendium > .tab-intro-banner,
#tab-threads > .tab-intro-banner {
  margin-top: 0;
}
/* v965b: pegged-width centered wrapper. Used by the Fates banner whose
   body contains a workflow chart — the pure inline-block trick doesn't
   work there because the body paragraph's max-content (single-line
   width with no wrapping) is much wider than the chart's natural
   width, so the inline-block grew to the body's preferred size and
   the chart sat left-aligned in a wide container. The explicit
   max-width caps the wrapper at the chart's typical row width
   (~40rem covers the four cards + three arrows + gaps with a small
   buffer); the body wraps inside that, the chart fills that, the
   wrapper centers as a unit via banner's text-align: center.
   Plain-text banners (no chart) compose this same wrapper for centered
   tip text with a sane line-length cap. */
.tab-intro-content {
  display: inline-block;
  text-align: left;
  max-width: 56rem;
  width: 100%;
  vertical-align: top;
}
.tab-intro-dismiss {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-3);
  font-size: var(--font-size-sm);
  line-height: 1;
  padding: 0.2rem 0.45rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: 0.55;
  transition:
    opacity      var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}
.tab-intro-dismiss:hover,
.tab-intro-dismiss:focus-visible {
  opacity: 1;
  color: var(--text-1);
  border-color: var(--border);
  background: var(--bg-3);
  outline: none;
}
.tab-intro-title {
  margin: 0 0 var(--space-2);
  padding-right: 2rem;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  color: var(--text);
  letter-spacing: 0.01em;
}
.tab-intro-body {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-sm);
  line-height: 1.55;
  color: var(--color-text-secondary);
}
.tab-intro-body em {
  font-style: normal;
  color: var(--accent-teal);
  font-weight: var(--font-weight-semibold);
}
/* v974: small caption above the chart — primes it as a process
   explainer ("here's how a Fate gets built") rather than a page-scroll
   map. Composes .form-section-label for the canonical eyebrow recipe;
   only adds margin so it doesn't crash into the chart. */
.fates-workflow-caption {
  margin: 0 0 var(--space-2);
}
/* Workflow chart — four step cards in a horizontal flex row, joined
   by ─→ arrows. Each step: emoji top, label, one-line subtitle. The
   row stacks vertically on narrow viewports with ↓ arrows. */
.fates-workflow-chart {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}
.fates-workflow-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.2rem;
  padding: 0.55rem 0.85rem;
  min-width: 6.5rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.fates-workflow-step-glyph {
  font-size: var(--font-size-lg);
  line-height: 1;
}
.fates-workflow-step-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
}
.fates-workflow-step-sub {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  line-height: 1.3;
}
.fates-workflow-step-sub em {
  font-style: normal;
  color: var(--accent-teal);
  font-weight: var(--font-weight-semibold);
}
/* v977: terminal LIVE card echoes the gold "Live" pill in the nav so
   the chart's last step reads as a forward pointer to that already-
   learned landmark (encoding-specificity: same retrieval cue across
   two surfaces collapses two lookup tasks into one). Subtle gold tint
   on the card surface, full --color-gold on the label + glyph. No
   flicker animation — the chart card is informational, not a control. */
.fates-workflow-step--live {
  border-color: color-mix(in srgb, var(--color-gold) 55%, transparent);
  background: color-mix(in srgb, var(--color-gold) 8%, var(--bg-2));
}
.fates-workflow-step--live .fates-workflow-step-label,
.fates-workflow-step--live .fates-workflow-step-glyph {
  color: var(--color-gold);
}
/* v977: equivalence connector (Fate = Live) instead of the arrow used
   between build steps. Tinted gold to reinforce the visual link to the
   gold LIVE card on the right. Slight font-weight bump so the equals
   reads at the same visual weight as the wider → glyph. */
.fates-workflow-arrow--equiv {
  color: color-mix(in srgb, var(--color-gold) 70%, var(--text-3));
  font-weight: var(--font-weight-bold);
  opacity: 0.85;
}
.fates-workflow-arrow {
  font-size: var(--font-size-lg);
  color: color-mix(in srgb, var(--accent-teal) 55%, var(--text-3));
  font-weight: var(--font-weight-regular);
  line-height: 1;
  user-select: none;
  padding: 0 0.15rem;
  opacity: 0.75;
}
@media (max-width: 720px) {
  /* Stack vertically with ↓ arrows on narrow viewports. */
  .fates-workflow-chart { flex-direction: column; align-items: stretch; }
  .fates-workflow-step { min-width: 0; }
  .fates-workflow-arrow { transform: rotate(90deg); align-self: center; padding: 0.1rem 0; }
}

/* My Fates strip — horizontal scrollable list of saved Fate cards above
   the Weave section. Hidden when zero saved Fates. Each card: small
   premise-type pill + title; click opens the workspace in 'saved' mode. */
.fates-saved-section {
  margin-bottom: var(--space-4);
}
.fates-saved-hdr {
  margin-bottom: var(--space-2);
}
/* v965: .fates-saved-empty retired — replaced by canonical
   .placeholder.placeholder--padded composition in the JS render. */
/* v924: quest-style split layout — left nav of compact rows, right
   detail panel. Mirrors .quest-layout / .quest-nav-col / .quest-
   detail-col patterns. */
.fates-saved-layout {
  display: flex;
  align-items: flex-start;
  min-height: 100%;
  gap: var(--space-4);
}
.fates-saved-nav-col {
  /* v930: matches .quest-nav-col verbatim — 42% width, 320px min. */
  flex: 0 0 42%;
  min-width: 320px;
  position: relative;
}
/* Fade divider between columns — same recipe as .quest-nav-col::after */
.fates-saved-nav-col::after {
  content: '';
  position: absolute;
  top: 0; right: calc(var(--space-4) * -0.5); bottom: 0;
  width: 1px;
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--border) 50%, transparent) 15%,
    var(--border) 50%,
    color-mix(in srgb, var(--border) 50%, transparent) 85%,
    transparent 100%);
}
/* v941: matches .quest-detail-col verbatim — column owns the outer
   padding, the inner .journey-summary-card owns the surface bg + border
   + radius + internal padding. JS renders the .journey-summary-card
   chassis directly so Fates and Quests share one recipe instead of two
   parallel ones. */
.fates-saved-detail-col {
  flex: 1;
  min-width: 0;
  padding: var(--space-5) var(--space-6);
  position: sticky;
  top: 0;
  max-height: 100vh;
  overflow-y: auto;
  box-sizing: border-box;
}
.fates-saved-detail-panel {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
/* v976: .fates-saved-detail-placeholder retired (see quest sibling). */
/* Detail-header target suffix — same purple chip the list rows show,
   sized up slightly since the detail header has more breathing room. */
.fates-saved-detail-pc {
  font-size: var(--font-size-base);
  flex-shrink: 0;
}
.fates-saved-list {
  /* v931: gap matches .quest-nav-list. NO horizontal padding —
     unlike Quest (whose #tab-quests is padding:0, so the list
     provides the 16px inset), our #tab-threads already pads
     var(--space-4) horizontally per the v906 RCCA, so adding
     padding here doubled the left edge to 32px and made Fate
     rows look pushed right vs Quest rows. */
  padding: 0 0 var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Stack to single column on narrow viewports — Quests does the same
   via a media query. */
@media (max-width: 720px) {
  .fates-saved-layout { flex-direction: column; }
  .fates-saved-nav-col { flex: 0 0 auto; min-width: 0; width: 100%; }
  .fates-saved-nav-col::after { display: none; }
  /* Mobile padding matches .quest-detail-col mobile (~line 3648). */
  .fates-saved-detail-col { position: static; max-height: none; width: 100%; padding: 1rem; }
}

/* Foretelling — Phase 2 Seeds-to-Plant panel. Sits between My Fates
   and Weave; surfaces every unplanted seed across every saved Fate
   so the GM sees them during routine prep, not just when they
   open the parent Fate card. Hidden when zero seeds pending. */
.fates-seeds-section {
  margin-bottom: var(--space-4);
}
.fates-seeds-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.fates-seeds-group {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-left: 3px solid color-mix(in srgb, var(--accent-green) 50%, var(--border));
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.fates-seeds-group-hdr {
  display: flex;
  align-items: center;
  margin-bottom: var(--space-2);
}
.fates-seeds-group-title {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.5rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--text);
  font: inherit;
  font-weight: var(--font-weight-bold);
  cursor: pointer;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}
.fates-seeds-group-title:hover,
.fates-seeds-group-title:focus-visible {
  background: color-mix(in srgb, var(--accent-teal) 6%, transparent);
  border-color: rgba(47, 154, 163, 0.45);
  outline: none;
}
.fates-seeds-group-name { flex: 0 1 auto; }
.fates-seeds-group-count {
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--text-3);
  letter-spacing: 0.04em;
}

/* Foretelling seed list — used in BOTH the inline scene render and
   the Seeds-to-Plant panel. Each row: checkbox + placement chip on
   the left, seed text + plausible-cover footnote on the right. The
   --planted state strikes through and dims so the eye scans for
   what's still pending. */
.fates-foretelling-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.fates-foretelling-seed {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--accent-teal) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 18%, var(--border));
  border-radius: var(--radius-md);
  transition: opacity var(--t-hover) var(--ease);
}
.fates-foretelling-seed--planted {
  opacity: 0.55;
}
.fates-foretelling-seed--planted .fates-foretelling-seed-text {
  text-decoration: line-through;
  text-decoration-color: var(--text-3);
}
.fates-foretelling-check {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  user-select: none;
}
.fates-foretelling-check input {
  cursor: pointer;
  accent-color: var(--accent-teal);
}
.fates-foretelling-placement {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  white-space: nowrap;
}
/* Placement urgency colors — same_session = ember (do this NOW),
   late = gold, mid = teal, early = muted. */
.fates-foretelling-placement--same_session {
  color: var(--accent-ember);
  border-color: color-mix(in srgb, var(--accent-ember) 35%, var(--border));
  background: color-mix(in srgb, var(--accent-ember) 10%, transparent);
}
.fates-foretelling-placement--late {
  color: var(--accent-gold);
  border-color: color-mix(in srgb, var(--accent-gold) 35%, var(--border));
  background: color-mix(in srgb, var(--accent-gold) 10%, transparent);
}
.fates-foretelling-placement--mid {
  color: var(--accent-teal);
  border-color: color-mix(in srgb, var(--accent-teal) 35%, var(--border));
  background: color-mix(in srgb, var(--accent-teal) 10%, transparent);
}
.fates-foretelling-placement--early {
  color: var(--text-3);
  border-color: var(--border);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
}
.fates-foretelling-body {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
}
.fates-foretelling-seed-text {
  margin: 0;
  font-size: var(--font-size-md);
  line-height: 1.45;
  color: var(--text);
}
.fates-foretelling-cover {
  margin: 0;
  font-size: var(--font-size-sm);
  font-style: italic;
  color: var(--text-3);
  line-height: 1.4;
}
.fates-foretelling-cover-label {
  font-style: normal;
  font-weight: 500;
  color: var(--text-2);
  margin-right: 0.3rem;
}
/* Inline-only — collapsed footnote when applicability=none. */
.fates-scene-section--foretelling-skipped p {
  margin-top: 0.25rem;
}

/* Edit-form — Foretelling fieldset borrows the npc_moves grid pattern
   but with a 3-column header (placement + remove × stretching to row
   right) above the two textareas (seed + plausible_cover). */
.fates-edit-foretelling-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* v942: footer of the in-edit foretelling fieldset — flex row of two
   ghost-pill actions (+ Add foreshadow / 🪄 Generate) sitting under
   the seed list. Hidden when applicability=`none`. */
.fates-edit-foretelling-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-2);
}
/* v947: Quick Save tip line in the manual edit form — sits under the
   premise field, signals to free-tier GMs that they don't need to fill
   every field before saving. Subdued teal-tinted helper line. */
.fates-quick-save-hint {
  margin: -0.2rem 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--accent-teal) 6%, transparent);
  border-left: 2px solid color-mix(in srgb, var(--accent-teal) 50%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: 1.4;
}
.fates-edit-foretelling-row {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: var(--space-3);
  background: color-mix(in srgb, var(--accent-teal) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 18%, var(--border));
  border-radius: var(--radius-md);
}
.fates-edit-foretelling-row-hdr {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.fates-edit-foretelling-placement {
  flex: 0 0 auto;
  width: 9rem;
}
.fates-edit-foretelling-row-hdr .fates-edit-npc-remove {
  margin-left: auto;
}
/* Saved Fate row — v925: dimensions match .quest-nav-row exactly so
   the Fates list and Quests list read as siblings (radius-xl,
   0.85rem 1rem inner padding, gap 0.75rem, 600-weight title). The
   v924 button-with-direct-padding shape was tighter than Quests; the
   v925 outer-card + inner-hdr split mirrors Quests' chrome. */
.fates-saved-card {
  display: block;
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--text);
  padding: 0;   /* inner .fates-saved-card-hdr owns the padding */
  transition:
    border-color var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease);
}
.fates-saved-card:hover, .fates-saved-card:focus-within, .fates-saved-card:focus-visible {
  border-color: rgba(47, 154, 163, 0.45);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  outline: none;
}
.fates-saved-card.selected {
  border-color: rgba(47, 154, 163, 0.65);
  background-color: color-mix(in srgb, var(--color-brand) 7%, var(--bg-2));
}
/* Selected card clears the arcane sigil watermark on its header — same
   pattern as `.quest-nav-row.selected .quest-nav-hdr` so the active row
   reads as "this is the focused thing" without watermark noise. The
   watermark itself is set on `.fates-saved-card-hdr` via the shared
   selector list (lines ~9434-9442); `background: transparent` on the
   header shorthand wipes the background-image. */
.fates-saved-card.selected .fates-saved-card-hdr { background: transparent; }
.fates-saved-card-hdr {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.85rem 1rem;
  user-select: none;
  /* Arcane sigil watermark added below via the shared selector list
     alongside .session-card-hdr / .quest-nav-hdr. v929: dropped the
     v928 per-type colored glyph in favor of the shared sigil — that
     IS the watermark Quests use, and the user wants Fates to
     duplicate the Quest formatting. */
}
.fates-saved-card-title {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
  color: var(--text-1);
}
/* Purple target suffix — mirrors .quest-nav-pc verbatim. PC name
   when one was detected in the premise; otherwise the configured
   party label. */
.fates-saved-card-pc {
  color: var(--accent-purple);
  font-size: var(--font-size-sm);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 1;
  min-width: 0;
}
.fates-saved-card-count {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  color: var(--text-3);
  font-weight: 500;
  white-space: nowrap;
}
.fates-saved-card-popout {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-3);
  font-size: var(--font-size-base);
  line-height: 1;
  padding: 0.15rem 0.4rem;
  cursor: pointer;
  transition:
    opacity      var(--t-hover) var(--ease),
    color        var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease);
}
.fates-saved-card-popout:hover,
.fates-saved-card-popout:focus-visible {
  color: var(--accent-teal);
  border-color: color-mix(in srgb, var(--accent-teal) 40%, var(--border));
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
  outline: none;
}
/* Brief teal pulse on the just-saved card so the GM sees their save
   land in the list without having to scan for it. Class is removed
   after 1.8s by the save handler. */
@keyframes fates-saved-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-teal) 60%, transparent); }
  60%  { box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent-teal) 0%,  transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-teal) 0%,  transparent); }
}
.fates-saved-card--just-saved {
  border-color: color-mix(in srgb, var(--accent-teal) 70%, var(--border));
  animation: fates-saved-pulse 1.8s ease-out;
}
.fates-saved-card-type {
  flex-shrink: 0;
  padding: 0.05rem 0.45rem;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
}
.fates-saved-card-type--collision   { color: var(--accent-ember);  border-color: color-mix(in srgb, var(--accent-ember)  35%, var(--border)); background: color-mix(in srgb, var(--accent-ember)  10%, transparent); }
.fates-saved-card-type--convergence { color: var(--accent-purple); border-color: color-mix(in srgb, var(--accent-purple) 35%, var(--border)); background: color-mix(in srgb, var(--accent-purple) 10%, transparent); }
.fates-saved-card-type--catalyst    { color: var(--accent-gold);   border-color: color-mix(in srgb, var(--accent-gold)   35%, var(--border)); background: color-mix(in srgb, var(--accent-gold)   10%, transparent); }
.fates-saved-card-type--custom      { color: var(--color-text-secondary); border-color: var(--border); background: color-mix(in srgb, var(--text-1) 4%, transparent); }
.fates-saved-card-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--font-weight-bold);
}

/* Workspace modal — wider than the suggestions modal because the scene
   render needs room for multiple sections + an action bar. */
.fates-workspace-box {
  /* modal-box--xl already gives the size; this hook is for any
     workspace-specific tweaks. */
}
.fates-workspace-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-height: 80vh;
  overflow-y: auto;
}
.fates-workspace-stage {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Stage 1: premise picker */
.fates-premise-status,
.fates-scene-status {
  min-height: 1.4rem;
}
.fates-premise-status .placeholder.error,
.fates-scene-status   .placeholder.error {
  color: var(--color-danger);
}
/* "Edits applied — click Save" inline status — subtle teal tint so it
   reads as a successful state-change cue, not an error. Auto-clears
   from JS after 4s. */
.fates-scene-status--applied {
  color: var(--accent-teal);
  font-style: normal;
}
/* "Saved Fate · read-only" eyebrow — surfaces the read-only contract
   at the top of the saved-Fate render so the GM doesn't hunt for an
   Edit button that isn't there. Composes .form-section-label for
   typography; just adds spacing + the subtle teal accent. */
.fates-scene-saved-eyebrow {
  display: block;
  margin-bottom: var(--space-2);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid color-mix(in srgb, var(--accent-teal) 18%, var(--border));
  color: var(--accent-teal);
}
.fates-premise-cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.55rem;
}

/* Loading-state skeleton — three faded shimmering placeholders
   that mirror the real premise card structure (left-border type
   color + title bar + body lines). The 8-second AI call previously
   showed a static "Proposing premises…" line; now the structure
   fills in as the model returns. Same animation drives the scene-
   stage skeleton below. */
@keyframes fates-skeleton-shimmer {
  0%   { background-position: -200% 0; }
  100% { background-position:  200% 0; }
}
.fates-skeleton-card,
.fates-skeleton-scene {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.85rem 1rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.fates-skeleton-card {
  border-left: 3px solid var(--border);
  margin-bottom: 0.55rem;
}
.fates-skeleton-card--collision   { border-left-color: color-mix(in srgb, var(--accent-ember) 40%, var(--border)); }
.fates-skeleton-card--convergence { border-left-color: color-mix(in srgb, var(--accent-purple) 40%, var(--border)); }
.fates-skeleton-card--catalyst    { border-left-color: color-mix(in srgb, var(--accent-gold) 40%, var(--border)); }
.fates-skeleton-line {
  height: 0.7rem;
  border-radius: var(--radius-sm);
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--text-1) 6%, transparent) 0%,
    color-mix(in srgb, var(--text-1) 12%, transparent) 50%,
    color-mix(in srgb, var(--text-1) 6%, transparent) 100%
  );
  background-size: 200% 100%;
  animation: fates-skeleton-shimmer 1.6s linear infinite;
}
.fates-skeleton-line--title { height: 1rem; width: 50%; }
.fates-skeleton-line--short { width: 70%; }
.fates-skeleton-section {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.4rem;
}
.fates-premise-card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.85rem 1rem;
  font: inherit;
  text-align: left;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  cursor: pointer;
  transition:
    background   var(--t-hover) var(--ease),
    border-color var(--t-hover) var(--ease),
    box-shadow   var(--t-hover) var(--ease),
    transform    var(--t-hover) var(--ease);
}
/* Canonical card-hover recipe — picking a premise is the marquee
   click in the Weave workspace and should feel as expensive as
   picking a quest or opening a session. Unavailable + disabled
   variants stay flat (no cursor, no lift, no shadow). */
.fates-premise-card:hover:not(.fates-premise-card--unavailable):not(:disabled) {
  background: color-mix(in srgb, var(--accent-teal) 6%, var(--bg-2));
  border-color: rgba(47, 154, 163, 0.45);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.fates-premise-card--collision   { border-left-color: var(--accent-ember); }
.fates-premise-card--convergence { border-left-color: var(--accent-purple); }
.fates-premise-card--catalyst    { border-left-color: var(--accent-gold); }
.fates-premise-card--unavailable {
  cursor: default;
  opacity: 0.55;
}
.fates-premise-card-hdr {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.fates-premise-card-type {
  flex-shrink: 0;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-tertiary);
}
.fates-premise-card--collision   .fates-premise-card-type { color: var(--accent-ember); }
.fates-premise-card--convergence .fates-premise-card-type { color: var(--accent-purple); }
.fates-premise-card--catalyst    .fates-premise-card-type { color: var(--accent-gold); }
.fates-premise-card-title {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-md);
}
.fates-premise-card-text {
  margin: 0;
  font-size: var(--font-size-md);
  line-height: 1.4;
  color: var(--text);
}
.fates-premise-card-tension {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
  font-style: italic;
}

.fates-premise-custom {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.7rem 0.85rem;
  background: color-mix(in srgb, var(--text-1) 3%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.fates-premise-custom-input {
  width: 100%;
  padding: 0.5rem 0.65rem;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--font-size-sm);
  color: var(--text);
  resize: vertical;
}
.fates-premise-custom-input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent-teal) 60%, var(--border));
}
.fates-premise-custom-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Stage 2: scene render */
.fates-scene-render {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.fates-scene-hdr {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid color-mix(in srgb, var(--accent-teal) 18%, var(--border));
}
.fates-scene-type {
  display: inline-block;
  align-self: flex-start;
  padding: 0.1rem 0.55rem;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
}
.fates-scene-type--collision   { color: var(--accent-ember);  border-color: color-mix(in srgb, var(--accent-ember)  40%, var(--border)); background: color-mix(in srgb, var(--accent-ember)  10%, transparent); }
.fates-scene-type--convergence { color: var(--accent-purple); border-color: color-mix(in srgb, var(--accent-purple) 40%, var(--border)); background: color-mix(in srgb, var(--accent-purple) 10%, transparent); }
.fates-scene-type--catalyst    { color: var(--accent-gold);   border-color: color-mix(in srgb, var(--accent-gold)   40%, var(--border)); background: color-mix(in srgb, var(--accent-gold)   10%, transparent); }
.fates-scene-type--custom      { color: var(--color-text-secondary); border-color: var(--border); background: color-mix(in srgb, var(--text-1) 4%, transparent); }
.fates-scene-title {
  margin: 0;
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
}
.fates-scene-premise {
  margin: 0;
  padding: 0.4rem 0.7rem;
  font-style: italic;
  color: var(--color-text-secondary);
  border-left: 2px solid color-mix(in srgb, var(--accent-teal) 50%, transparent);
  background: color-mix(in srgb, var(--accent-teal) 7%, transparent);
}
/* Eyebrow on each section composes .form-section-label per CLAUDE.md
   hard rule — typography lives there. We only override the small
   gap so the label hugs the body paragraph. */
.fates-scene-section .form-section-label {
  margin-bottom: 0.25rem;
}
.fates-scene-section p {
  margin: 0;
  font-size: var(--font-size-md);
  line-height: 1.5;
  color: var(--text);
}
.fates-scene-section--secret p {
  /* GM-only intel — slightly tinted so the GM can spot it as
     player-invisible at a glance. */
  color: color-mix(in srgb, var(--accent-gold) 25%, var(--text));
}
.fates-scene-section--secret .form-section-label {
  color: var(--accent-gold);
}
.fates-scene-npc {
  margin: 0;
  padding-left: 1.2rem;
  list-style: disc;
}
.fates-scene-npc li {
  margin-bottom: 0.25rem;
  font-size: var(--font-size-md);
  line-height: 1.45;
  color: var(--text);
}
.fates-scene-npc strong { color: var(--accent-teal); }
/* npc_moves presence badge — small label after the actor name showing
   whether they're in-scene, incoming, or offstage. F2 v901+. */
.fates-scene-npc-presence {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.65rem;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  vertical-align: middle;
}
.fates-scene-npc-presence--in-scene {
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 10%, transparent);
  border-color: color-mix(in srgb, var(--accent-teal) 35%, var(--border));
}
.fates-scene-npc-presence--incoming {
  color: var(--accent-ember);
  background: color-mix(in srgb, var(--accent-ember) 10%, transparent);
  border-color: color-mix(in srgb, var(--accent-ember) 35%, var(--border));
}
.fates-scene-npc-presence--offstage {
  color: var(--color-text-tertiary);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  border-color: var(--border);
}
.fates-scene-npc-sep { color: var(--color-text-tertiary); }

.fates-scene-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.fates-scene-actions-spacer { flex: 1 1 auto; }

/* Edit-before-save form — replaces the read-only scene render when
   the GM clicks Edit. Reuses .form-row for labels + controls so the
   typography matches the rest of the app's edit forms (entity, session,
   quest). NPC moves are a dynamic list with a 3-cell row (who / presence
   / what) + remove button. */
.fates-scene-edit-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* Subtle visual frame so the GM perceives "I'm in edit mode" without
     a heavy chrome change. Left rule + faint teal wash mirrors the
     inline-edit pattern used elsewhere in the app. */
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid color-mix(in srgb, var(--accent-teal) 55%, transparent);
  background: color-mix(in srgb, var(--accent-teal) 4%, transparent);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.fates-scene-edit-form .form-row label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.fates-edit-input {
  width: 100%;
  padding: 0.4rem 0.55rem;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-1);
  font-family: inherit;
  font-size: var(--font-size-sm);
  line-height: 1.45;
  transition: border-color var(--t-hover) var(--ease), background var(--t-hover) var(--ease);
}
.fates-edit-input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent-teal) 60%, var(--border));
  background: color-mix(in srgb, var(--accent-teal) 6%, var(--bg-1));
}
.fates-edit-textarea {
  resize: vertical;
  min-height: 3rem;
}
.fates-edit-row-meta {
  align-self: flex-end;
  max-width: 14rem;
}
.fates-edit-fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-3) var(--space-2);
  margin: 0;
}
.fates-edit-fieldset legend {
  padding: 0 0.4rem;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--text-2);
}
.fates-edit-npc-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.fates-edit-npc-row {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) minmax(6rem, 8rem) 1fr auto;
  gap: 0.5rem;
  align-items: start;
}
.fates-edit-npc-remove {
  align-self: stretch;
  padding: 0 0.55rem;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-3);
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: background var(--t-hover) var(--ease), color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.fates-edit-npc-remove:hover,
.fates-edit-npc-remove:focus-visible {
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-danger) 35%, var(--border));
  color: var(--color-danger);
  outline: none;
}
.fates-edit-add-npc-btn {
  margin-top: var(--space-2);
  align-self: flex-start;
  font-size: var(--font-size-sm);
}
/* On narrow viewports the 4-cell NPC row gets cramped; collapse to a
   stacked layout so each control gets full width. */
@media (max-width: 720px) {
  .fates-edit-npc-row {
    grid-template-columns: 1fr;
  }
  .fates-edit-npc-remove {
    align-self: flex-end;
    width: auto;
  }
}
.fates-scene-copy {
  background: color-mix(in srgb, var(--accent-teal) 18%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent-teal) 55%, var(--border));
}
.fates-scene-copy:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-teal) 28%, var(--bg-2));
}

/* Manual-link modal — small modal-box, textarea + GM-only warning.
   Note input gets enough room for a couple of sentences without
   feeling cramped. */
.fates-manual-link-note {
  width: 100%;
  padding: 0.55rem 0.7rem;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--font-size-sm);
  color: var(--text);
  resize: vertical;
  min-height: 5rem;
  margin-top: 0.5rem;
}
.fates-manual-link-note:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent-teal) 60%, var(--border));
}
.fates-manual-link-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* Ribbon paths — the actual weave. Two visual signals encode the
   data:
     COLOR  = entity type (codex-canonical palette — same hue as
              the entity's pill on the right rail and the entity's
              row in the Codex tab). Reads consistently across the
              app — a Tiamat ribbon is gold whether the thread it
              comes from is hot or cold.
     WEIGHT = how many SELECTED threads the destination entity
              binds. An entity in 4 selected threads gets the
              heaviest stroke (it's the load-bearing weave bone);
              an entity in 1 thread gets the thinnest. The visual
              hierarchy reads at a glance: heavy ribbons converging
              on one entity = a real anchor; sparse thin ribbons
              flying past = context. */
.fates-weave-ribbon {
  fill: none;
  stroke-width: 2.2;          /* default — overridden by tier modifier */
  stroke-linecap: round;
  opacity: 0.75;
  transition: opacity var(--t-hover) var(--ease), stroke-width var(--t-hover) var(--ease);
}
/* Weight tiers — anchor count of the destination entity drives this. */
.fates-weave-ribbon--thin   { stroke-width: 1.6; opacity: 0.6;  }
.fates-weave-ribbon--medium { stroke-width: 2.4; opacity: 0.78; }
.fates-weave-ribbon--bold   { stroke-width: 3.2; opacity: 0.86; }
.fates-weave-ribbon--heavy  { stroke-width: 4.0; opacity: 0.92; }

/* Codex-canonical type palette — mirrors ENTITY_CFG in static/app.js
   and the .fates-warp-head--{type} variants on the entity-rail pills.
   A ribbon's stroke matches the hue of the pill it lands on. */
.fates-weave-ribbon--pc        { stroke: var(--accent-blue); }
.fates-weave-ribbon--companion { stroke: var(--accent-teal); }
.fates-weave-ribbon--deity     { stroke: var(--accent-gold); }
.fates-weave-ribbon--location  { stroke: var(--accent-teal); }
.fates-weave-ribbon--npc       { stroke: var(--color-muted-violet); }
.fates-weave-ribbon--item      { stroke: var(--accent-ember); }
.fates-weave-ribbon--faction   { stroke: var(--color-muted-violet); }
.fates-weave-ribbon--lore      { stroke: var(--accent-green); }
.fates-weave-ribbon--spell     { stroke: var(--accent-purple); }

/* Highlight state — when the GM hovers a thread row, an entity row,
   or a ribbon path, all matching ribbons go .is-active and the rest
   go .is-dim. Active stroke bumps one tier heavier; dim drops to
   ghost opacity so the overall shape stays legible. */
.fates-weave-graph.is-highlighting .fates-weave-ribbon.is-dim {
  opacity: 0.14;
}
.fates-weave-graph.is-highlighting .fates-weave-ribbon.is-active {
  opacity: 1;
  /* Bump stroke beyond the tier — the active ribbon should always
     read as the focal point regardless of weight tier. */
  stroke-width: calc(var(--ribbon-stroke, 2.4px) + 1.2px);
}
.fates-weave-ribbon--thin.is-active   { stroke-width: 2.8; }
.fates-weave-ribbon--medium.is-active { stroke-width: 3.6; }
.fates-weave-ribbon--bold.is-active   { stroke-width: 4.4; }
.fates-weave-ribbon--heavy.is-active  { stroke-width: 5.2; }
.fates-weave-ribbon:hover { opacity: 1; }

/* ── Loom-table — the unifying visual across all three Fates surfaces ──
   2-column CSS grid:
      col 1: thread-info  (checkbox + status pill + title + heat + actions)
      col 2: warp/weft bar (entity pills as headers; knots as data)
   All cells emit flat into the parent grid (no row wrappers) so column
   widths stay aligned across header + every data row. The warp-bar
   (col 2 row 1) and each weft-bar (col 2 rows 2..N) both use flex
   space-around with the same N items, so an entity pill's centerline
   matches its knot's centerline column-by-column. */
.fates-loom-table {
  display: grid;
  grid-template-columns: clamp(14rem, 32%, 22rem) minmax(0, 1fr);
  row-gap: 0.35rem;
  column-gap: 0.7rem;
  align-items: center;
}

/* Info column — checkbox + status pill + title + heat badge + actions */
.fates-loom-info {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.45rem;
  border-radius: var(--radius-md);
  min-width: 0;       /* allow title to truncate */
  font-size: var(--font-size-base);
}
.fates-loom-info--head {
  /* empty corner cell above the row stack — keeps col 1 aligned */
  padding: 0;
  min-height: 0;
}
.fates-loom-info--compact {
  padding: 0.2rem 0.35rem;
  font-size: 0.8rem;
}

/* Title — single line truncate. Tooltip on hover (set in JS via title=)
   restores the full string for long names. */
.fates-loom-title {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--font-weight-bold);
  color: var(--text);
}

/* Action icons — dismiss / mark resolved. Small, low-key, right-aligned. */
.fates-loom-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  margin-left: auto;
  flex-shrink: 0;
}
.fates-row-action-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  padding: 0.05rem 0.4rem;
  font-size: var(--font-size-base);
  line-height: 1;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition: background var(--t-hover) var(--ease), color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.fates-row-action-btn:hover {
  background: color-mix(in srgb, var(--text-1) 6%, transparent);
  color: var(--text);
  border-color: var(--border);
}

/* "+N" shares-with-weave badge — only rendered on threads-list rows
   when GM Weave is non-empty AND the row is not already in the
   weave. Tells the GM "this thread is adjacent to your working
   weave by N shared anchor entities" — surfaces near-misses without
   forcing the GM to scan column-by-column. Sits inline with the
   title, before the heat score. */
.fates-weave-adj-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: 0.05rem 0.4rem;
  font-size: 0.7rem;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  border-radius: var(--radius-full);
  color: var(--accent-teal);
  background: color-mix(in srgb, var(--accent-teal) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-teal) 35%, var(--border));
  cursor: help;
}

/* Snip × on Your Weave rows — hover-revealed only. The working surface
   should read as quiet at rest; the × appears when the GM declares
   intent (hovering the row pair). On its own hover the icon tints
   toward --color-danger so the destructive read is unambiguous. */
.fates-loom-actions--snip {
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease);
}
.fates-loom-info:hover .fates-loom-actions--snip,
.fates-loom-info:has(+ .fates-loom-bar:hover) .fates-loom-actions--snip,
.fates-loom-info:focus-within .fates-loom-actions--snip {
  opacity: 1;
}
.fates-row-action-btn--snip:hover {
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-danger) 30%, var(--border));
}

/* Bar column — both warp (header) and weft (per-row) live in col 2 */
.fates-loom-bar {
  position: relative;
  display: flex;
  justify-content: space-around;
  align-items: center;
  gap: 0.3rem;
  min-height: 1.4rem;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-md);
}
.fates-loom-bar--warp {
  align-items: flex-end;
  padding-bottom: 0.45rem;
  border-bottom: 1px dashed color-mix(in srgb, var(--accent-teal) 28%, transparent);
}
/* Weft rail — drawn behind the knots; ::before is a thin teal line
   spanning the bar's full width. The knots sit on top, masked by the
   bg-2 ring so they look like beads on a string. */
.fates-loom-bar--weft::before {
  content: '';
  position: absolute;
  left: 0.45rem;
  right: 0.45rem;
  top: 50%;
  height: 1.5px;
  background: color-mix(in srgb, var(--accent-teal) 40%, transparent);
  z-index: 1;
  pointer-events: none;
}

/* Hover the row pair — both info and bar light up together. :has()
   lets us style the info cell when its sibling bar is hovered;
   adjacent + handles the reverse. */
.fates-loom-info:hover,
.fates-loom-info:has(+ .fates-loom-bar:hover),
.fates-loom-info:hover + .fates-loom-bar,
.fates-loom-info + .fates-loom-bar:hover {
  background: color-mix(in srgb, var(--accent-teal) 5%, transparent);
}
/* Selected — checkbox checked. Slightly heavier teal across the pair. */
.fates-loom-info--selected,
.fates-loom-bar--selected {
  background: color-mix(in srgb, var(--accent-teal) 8%, transparent);
}
/* Selected weft rail: muted teal track flips to a luminous line so the
   connection between the active loose thread and its entity columns reads
   as live linkage. The pulse is a quiet breathing of the .type-filter-btn
   hover halo (~0.18 white alpha at rest), not a throb — same family of
   light, just slower. White on dark; saturated ink-teal on light (white
   wouldn't survive against the parchment surface). */
.fates-loom-bar--weft.fates-loom-bar--selected::before {
  background: var(--accent-teal);
  height: 1.5px;
  box-shadow: 0 0 8px 1px color-mix(in srgb, var(--accent-teal) 18%, transparent);
  animation: tf-weft-pulse 2.4s ease-in-out infinite;
}
[data-theme="dark"] .fates-loom-bar--weft.fates-loom-bar--selected::before {
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 8px 1px rgba(255, 255, 255, 0.18);
  animation: tf-weft-pulse-dark 2.4s ease-in-out infinite;
}
@keyframes tf-weft-pulse {
  0%, 100% { box-shadow: 0 0 8px 1px color-mix(in srgb, var(--accent-teal) 16%, transparent); }
  50%      { box-shadow: 0 0 12px 2px color-mix(in srgb, var(--accent-teal) 26%, transparent); }
}
@keyframes tf-weft-pulse-dark {
  0%, 100% { box-shadow: 0 0 8px 1px rgba(255, 255, 255, 0.16); }
  50%      { box-shadow: 0 0 12px 2px rgba(255, 255, 255, 0.26); }
}
@media (prefers-reduced-motion: reduce) {
  .fates-loom-bar--weft.fates-loom-bar--selected::before { animation: none; }
}

/* Warp head pill — entity name above its column.
   Per-type --type-color drives the tint; Codex-canonical palette
   (matches ENTITY_CFG in static/app.js). Hubs get an outlined treatment
   so the GM can spot "this column is everywhere — probably setting." */
.fates-warp-head {
  --type-color: var(--accent-teal);
  display: inline-block;
  padding: 0.08rem 0.45rem;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  border-radius: var(--radius-full);
  white-space: nowrap;
  color: var(--type-color);
  background: color-mix(in srgb, var(--type-color) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--type-color) 45%, var(--border));
  max-width: 8rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fates-warp-head--hub {
  /* Hubs (>40% of threads) — visually muted so they read as
     "background context, not signal." Earlier we used a dashed
     border, but UX agent's note: dashed reads as "incomplete /
     disabled" before it reads as "ubiquitous." Subdued opacity +
     desaturated tint communicates "this column is everywhere"
     without looking like an error state. JS sets a tooltip naming
     the actual count. */
  opacity: 0.55;
  filter: saturate(0.55);
}

/* Suggestions modal — × snip on each warp pill. Replaces the old
   "Mark setting" button trigger. Click the × on an entity that's
   driving suggestions you don't want, and the entity gets added
   to the campaign blocklist (excluded from affinity scoring) and
   the suggestions list refreshes. The pill stays its full size;
   the × sits flush to the right edge of the pill, hover-revealed
   so the rest state stays clean. */
.fates-warp-head--snip {
  /* Reserve a hair of space on the right so the × doesn't crowd
     the entity name when it appears on hover. */
  padding-right: 0.3rem;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}
.fates-warp-head-name {
  /* Lets the name truncate via the existing max-width / overflow
     rules on .fates-warp-head, while the × stays full-size. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fates-warp-snip {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  padding: 0;
  border: none;
  background: transparent;
  color: inherit;
  opacity: 0;
  font-size: var(--font-size-base);
  line-height: 1;
  cursor: pointer;
  border-radius: 50%;
  transition: opacity var(--t-hover) var(--ease), background var(--t-hover) var(--ease), color var(--t-hover) var(--ease);
}
.fates-warp-head--snip:hover .fates-warp-snip,
.fates-warp-head--snip:focus-within .fates-warp-snip {
  opacity: 0.85;
}
.fates-warp-snip:hover {
  opacity: 1 !important;
  background: color-mix(in srgb, var(--color-danger) 15%, transparent);
  color: var(--color-danger);
}

/* Suggestions modal hint — small explanatory line above the loom
   table. Tells the GM the × on each pill is the way to mark setting
   noise, replacing the old Mark Setting button. Quiet typography so
   it doesn't compete with the loom or the synthesis text. */
.fates-suggestions-hint {
  margin: 0;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  font-style: italic;
}
.fates-warp-head--pc        { --type-color: var(--accent-blue); }
.fates-warp-head--companion { --type-color: var(--accent-teal); }
.fates-warp-head--deity     { --type-color: var(--accent-gold); }
.fates-warp-head--location  { --type-color: var(--accent-teal); }
.fates-warp-head--npc       { --type-color: var(--color-muted-violet); }
.fates-warp-head--item      { --type-color: var(--accent-ember); }
.fates-warp-head--faction   { --type-color: var(--color-muted-violet); }
.fates-warp-head--lore      { --type-color: var(--accent-green); }
.fates-warp-head--spell     { --type-color: var(--accent-purple); }

/* Knot — colored disc where a thread crosses an entity column.
   The bg-2 shadow ring masks the rail line so the knot reads as a
   bead. Empty knots are transparent placeholders that preserve grid
   spacing without drawing anything. */
.fates-knot {
  --type-color: var(--accent-teal);
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--type-color);
  box-shadow: 0 0 0 2px var(--bg-2);
  position: relative;
  z-index: 2;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  flex-shrink: 0;
}
.fates-knot--empty {
  width: 0.7rem;
  height: 0.7rem;
  background: transparent;
  box-shadow: none;
  z-index: 0;
  flex-shrink: 0;
}
.fates-knot--pc        { --type-color: var(--accent-blue); }
.fates-knot--companion { --type-color: var(--accent-teal); }
.fates-knot--deity     { --type-color: var(--accent-gold); }
.fates-knot--location  { --type-color: var(--accent-teal); }
.fates-knot--npc       { --type-color: var(--color-muted-violet); }
.fates-knot--item      { --type-color: var(--accent-ember); }
.fates-knot--faction   { --type-color: var(--color-muted-violet); }
.fates-knot--lore      { --type-color: var(--accent-green); }
.fates-knot--spell     { --type-color: var(--accent-purple); }

/* When the row pair is hovered, knots bloom — same affordance as the
   v875 weave card. Hovering an entity column suggests "this column is
   what binds these threads." */
.fates-loom-info:hover + .fates-loom-bar .fates-knot:not(.fates-knot--empty),
.fates-loom-bar:hover .fates-knot:not(.fates-knot--empty),
.fates-loom-promote:hover .fates-knot:not(.fates-knot--empty),
.fates-loom-promote:focus-visible .fates-knot:not(.fates-knot--empty) {
  transform: scale(1.25);
  box-shadow:
    0 0 0 2px var(--bg-2),
    0 0 0 4px color-mix(in srgb, var(--type-color) 38%, transparent);
}

/* Multi-select checkbox on each thread card */
.thread-select {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  margin-right: 0.35rem;
}
/* v933: custom checkbox — kills the OS-default grey backdrop (which
   read as visual noise on the thread row). Transparent at rest with
   a subtle border; teal fill + white check on selected. */
.thread-select-cb {
  appearance: none;
  -webkit-appearance: none;
  width: 1.05rem;
  height: 1.05rem;
  cursor: pointer;
  background: transparent;
  border: 1.5px solid color-mix(in srgb, var(--text-3) 60%, transparent);
  border-radius: var(--radius-sm);
  position: relative;
  flex-shrink: 0;
  transition:
    border-color var(--t-hover) var(--ease),
    background   var(--t-hover) var(--ease);
}
.thread-select-cb:hover {
  border-color: var(--accent-teal);
}
.thread-select-cb:checked {
  background: var(--accent-teal);
  border-color: var(--accent-teal);
}
.thread-select-cb:checked::after {
  content: '✓';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1;
}
.thread-select-cb:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent-teal) 60%, transparent);
  outline-offset: 2px;
}

/* "shares: X, Y" inline tag row */
.thread-shared-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem;
  margin-top: 0.1rem;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
.thread-shared-label {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: var(--font-weight-bold);
}
.thread-shared-tag {
  padding: 0.1rem 0.5rem;
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  color: var(--color-text-secondary);
  font-size: var(--font-size-xs);
  white-space: nowrap;
}

/* Blocklist editor — list of toggleable rows */
.fates-blocklist-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.fates-blocklist-row:hover {
  background: color-mix(in srgb, var(--text-1) 3%, transparent);
}
.fates-blocklist-row .fates-blocklist-cb {
  flex-shrink: 0;
  width: 1.05rem;
  height: 1.05rem;
  accent-color: var(--accent-teal);
}
.fates-blocklist-type {
  flex-shrink: 0;
  width: 4.5rem;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.05em;
  color: var(--color-text-tertiary);
}
.fates-blocklist-name {
  flex: 1;
  color: var(--text);
}
.fates-blocklist-meta {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  font-variant-numeric: tabular-nums;
}

.thread-card-hdr {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
}

.thread-card-title {
  font-weight: 600;
  font-size: var(--font-size-sm);
  color: var(--text);
  flex: 1;
  min-width: 0;
}

.thread-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border: 1px solid currentColor;
  background: transparent;
}
.thread-status-pill--hot      { color: var(--color-danger); }
.thread-status-pill--warm     { color: var(--color-warning); }
.thread-status-pill--cold     { color: var(--color-info); }
.thread-status-pill--dormant  { color: var(--color-text-tertiary); }
.thread-status-pill--resolved { color: var(--color-success); }

.thread-heat-score {
  font-size: 0.7rem;
  color: var(--color-text-tertiary);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* F1: context truncates to 2 lines via line-clamp. GMs scanning a dozen
   threads benefit from density; full title attribute on the card surfaces
   the rest on hover. Reduced font + tighter line-height. */
.thread-context {
  margin: 0;
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Provenance line — single breadcrumb under every hook for trust calibration.
   Pattern: "Surface › Entity · last seen Session N"  (e.g., "Codex › Roth ·
   last seen Session 22"). Tertiary-text register so it sits below the title
   visually but stays scannable. The entity portion is a clickable link
   (.entity-link recipe). Separators sit at low contrast so the eye lands on
   the entity, not the punctuation. */
.thread-provenance {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.05rem;
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  line-height: 1.45;
}
.thread-prov-surface {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  color: var(--color-text-secondary);
}
.thread-prov-sep {
  color: var(--color-text-tertiary);
  opacity: 0.55;
}
.thread-prov-entity {
  /* Composes .entity-link visually but renders as a button — keep it
     inline-baseline so it tracks alongside the surrounding text. */
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent-teal);
  cursor: pointer;
  text-decoration: none;
}
.thread-prov-entity:hover, .thread-prov-entity:focus-visible {
  text-decoration: underline;
}
.thread-prov-ref {
  color: var(--color-text-tertiary);
  font-variant-numeric: tabular-nums;
}

.thread-entity-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.thread-entity-chip {
  font-size: var(--font-size-xs);
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--border);
}

.thread-card-actions {
  display: flex;
  gap: 0.3rem;
  flex-wrap: wrap;
  margin-top: 0.15rem;
}
.thread-card-actions .btn-new {
  /* Smaller buttons for the now-compact card. Match the .btn-sort/.btn-filter
     visual register used in toolbars. */
  padding: 0.2rem 0.6rem;
  font-size: 0.72rem;
}

/* Locked preview cards (hobby/standard tier). Blur + mute interaction;
   pointer-events:none so the upgrade CTA is the only viable target. */
.thread-card--locked {
  filter: blur(4px);
  opacity: 0.55;
  pointer-events: none;
  user-select: none;
}

.thread-upgrade-banner {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 1.25rem 1.25rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}
.thread-upgrade-body {
  margin: 0;
  color: var(--text);
  font-size: var(--font-size-sm);
  line-height: 1.45;
}
.thread-upgrade-banner .btn-new {
  align-self: flex-start;
  margin-top: 0.4rem;
}

.thread-dismissed-link {
  margin: 0.75rem 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
}
.thread-dismissed-link a {
  color: var(--color-info);
  cursor: pointer;
  text-decoration: underline;
}

/* Spinner used in the "Detecting hooks…" inline state. Single-character
   rotating glyph — keeps the message lightweight without pulling in an
   icon system. The form-section-label container provides positioning. */
.threads-spinner {
  display: inline-block;
  animation: tf-threads-spin 1.2s linear infinite;
  margin-right: 0.25rem;
}
@keyframes tf-threads-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ── Brainstorm Workspace (Hooks Phase 2 Sprint A) ────────────────────────
   Sprint A renders only the center column (conversation + composer);
   Sprint C adds the left rail for pinned context and Sprint D adds the
   right rail for save-to-campaign drafts. The .brainstorm-box modifier
   layered on top of .modal-box--xxl tunes the internal flex layout. */
.brainstorm-box {
  display: flex;
  flex-direction: column;
  height: min(85vh, 800px);
}
.brainstorm-hdr {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.brainstorm-hook-title {
  margin: 0;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  color: var(--text);
}
.brainstorm-hdr-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.brainstorm-refresh-btn[disabled] {
  cursor: progress;
  opacity: 0.6;
}
.brainstorm-mobile-banner {
  display: none;  /* desktop default — flipped on at 900px breakpoint below */
  margin: 0 var(--space-5);
  padding: 0.6rem 0.85rem;
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
  background: color-mix(in srgb, var(--color-info) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-info) 30%, transparent);
  border-radius: var(--radius-md);
}
.brainstorm-conversation {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  min-height: 240px;
}
.brainstorm-msg {
  /* Per-message tint flows through --brainstorm-mode-color so mode-specific
     coloring is one CSS var instead of inline style — matches the
     per-instance-color hard rule. */
  padding: 0.75rem 1rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--bg-2);
  color: var(--text);
  font-size: var(--font-size-sm);
  line-height: 1.55;
  white-space: pre-wrap;
}
.brainstorm-msg--user {
  align-self: flex-end;
  max-width: 78%;
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
}
.brainstorm-msg--assistant {
  align-self: flex-start;
  max-width: 88%;
  border-color: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 50%, var(--border));
  border-left: 3px solid var(--brainstorm-mode-color, var(--accent-teal));
}
.brainstorm-msg--surface { --brainstorm-mode-color: var(--color-info); }
.brainstorm-msg--suggest { --brainstorm-mode-color: var(--color-warning); }
.brainstorm-msg-meta {
  display: block;
  margin-bottom: 0.35rem;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--brainstorm-mode-color, var(--color-text-tertiary));
}
.brainstorm-msg-body {
  white-space: pre-wrap;
  /* Body inherits the message font/color from .brainstorm-msg above. */
}

/* Follow-up chips — render at the bottom of every assistant message.
   Click populates the textarea; GM keeps editorial control. Numbered
   prefix matches the model's "1. 2. 3. 4." output convention. */
.brainstorm-followups {
  margin-top: 0.85rem;
  padding-top: 0.7rem;
  border-top: 1px dashed color-mix(in srgb, var(--brainstorm-mode-color, var(--border)) 35%, var(--border));
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.brainstorm-followups-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin-bottom: 0.15rem;
}
.brainstorm-followup-chip {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.55rem 0.85rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  text-align: left;
  color: var(--text);
  background: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 28%, var(--border));
  border-radius: var(--radius-md);
  cursor: pointer;
  line-height: 1.45;
  transition: background var(--t-hover), border-color var(--t-hover), transform var(--t-hover);
}
.brainstorm-followup-chip:hover, .brainstorm-followup-chip:focus-visible {
  background: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 14%, transparent);
  border-color: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 55%, var(--border));
  transform: translateY(-1px);
}
.brainstorm-followup-num {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  color: var(--brainstorm-mode-color, var(--accent-teal));
  background: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 18%, transparent);
}
.brainstorm-followup-text {
  flex: 1;
}
/* Free-entry chip — same shape as model chips so the row reads as one
   list of options, but dashed border + italic + tertiary text color
   signal "this one is blank, you write the prompt." */
.brainstorm-followup-chip--custom {
  border-style: dashed;
  background: transparent;
}
.brainstorm-followup-chip--custom .brainstorm-followup-text {
  color: var(--color-text-tertiary);
  font-style: italic;
}
/* Expanded state for the free-entry chip 4 — fills the chip body with an
   inline input + Send button. Same outer shape as the collapsed chip so
   the chip-row layout doesn't reflow when the GM clicks "Write your own…". */
.brainstorm-followup-chip--expanded {
  border-style: solid;
  background: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 8%, transparent);
  cursor: text;
}
.brainstorm-followup-chip--expanded:hover {
  transform: none;
}
.brainstorm-custom-input {
  flex: 1;
  min-width: 0;
  padding: 0.35rem 0.55rem;
  font-family: inherit;
  font-size: var(--font-size-sm);
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.brainstorm-custom-input:focus {
  outline: 2px solid color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 50%, transparent);
  outline-offset: 1px;
}
.brainstorm-custom-send {
  flex-shrink: 0;
  padding: 0.35rem 0.85rem;
  font-family: inherit;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  color: var(--brainstorm-mode-color, var(--accent-teal));
  background: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 55%, var(--border));
  border-radius: var(--radius-md);
  cursor: pointer;
}
.brainstorm-custom-send:hover, .brainstorm-custom-send:focus-visible {
  background: color-mix(in srgb, var(--brainstorm-mode-color, var(--accent-teal)) 28%, transparent);
}

/* Mobile cutoff per HOOKS_PHASE_2_FRAMING.md Q2: read-only history with
   "best on desktop" banner. All input affordances hidden — desktop only. */
@media (max-width: 900px) {
  .brainstorm-box { height: min(92vh, 600px); }
  .brainstorm-mobile-banner { display: block; }
  .brainstorm-followups { display: none; }
}
