/* ============================================================
   ROC Literary — Foundation styles
   Design system ported from mockup passes 1, 3, 4.
   Screens (book list, person detail, kanban, etc.) not yet
   wired up — class names are reserved here and will be applied
   when those views are built in Step 2+.
   ============================================================ */

:root {
  /* Slate Ink — warm paper + warm slate-grey ink. Pencil graphite
     on cream. The lowest-commitment palette that still has warmth
     and depth; reads slightly blue but stays neutral. The whole UI
     lives in this single warm-and-slate family; only ROYGBIV
     pipeline stage colors remain outside it (separate semantic
     system). */

  --bg: #f4f1e9;
  --bg-rule: #e4ddca;
  --bg-card: #faf7ee;
  --bg-hover: #ebe3d0;
  --bg-selected: #ddd4bd;
  /* `--memo-paper` — the .memo-render document background for
     this theme. Used anywhere a control wants to read as "part
     of the memo paper" rather than part of the page chrome
     (e.g., A7 note-toggle's checked-state pill). Default (Light)
     theme uses the base .memo-render #ffffff; each theme block
     below carries its own override matching the paper color
     pinned in the MEMO-RENDER section at the bottom of this
     file. */
  --memo-paper: #ffffff;

  --ink: #1f242c;          /* deep slate-black, warm */
  --ink-soft: #3e4854;
  --ink-muted: #76736a;
  --ink-faint: #aba599;

  --accent: #3a4754;       /* warm slate — primary accent */
  --accent-hover: #29333d; /* darker slate for hover/pressed */
  --accent-soft: #5b6776;  /* italic emphasis, ampersand, soft links */
  --accent-bg: #dee1e5;    /* tinted surface for active rows */

  --client: #4a5664;       /* slightly brighter slate — same family */

  --status-active:    #757265;
  --status-hot:       #c44536;
  --status-submitted: #d68c3a;
  --status-offer:     #d4a93a;
  --status-sold:      #6b8e4e;
  --status-dormant:   #aaa595;

  --stage-pitched:    #c44536;
  --stage-reading:    #d68c3a;
  --stage-offer:      #d4a93a;
  --stage-acquired:   #6b8e4e;
  --stage-optioned:   #4a7c8a;
  --stage-passed:     #6e5a8a;

  /* Stage-prefix label color used in the rendered memo entry title
     (e.g. "OFFERS", "INTEREST"). Warm terracotta — reads urgent
     against the cream paper without fighting the slate ink. */
  --memo-stage-accent: #a85539;

  /* Red text — the rich-text "Red" toolbar button wraps a selection
     in <span class="memo-red">. Same value the Gmail (inline color)
     and PDF (C.textRed) exporters use, so red reads identically on
     every surface. */
  --memo-red: #c8102e;

  --serif: 'EB Garamond', Georgia, 'Times New Roman', serif;
  --sans:  'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  /* Logo font — Playfair Display italic for the Galley wordmark.
     High-contrast, editorial, slightly playful. Used only on the
     brand wordmark in the auth screen + side nav. */
  --logo:  'Playfair Display', 'EB Garamond', Georgia, serif;

  --col-width: 340px;
  --col-detail-min: 500px;

  /* The "page header zone" — a fixed 80px-tall shelf running across
     the very top of the app. The Galley brand wordmark (in the
     side nav) and each primary view's page title (Memos, Library,
     People, Companies) both sit vertically centered inside this
     shelf, so their text baselines align horizontally across the
     row. Entity-detail views (book / person / company / memo) reuse
     this height too so their headers don't visually compete with
     the primary-page chrome. */
  --page-header-height: 80px;
  --nav-width: 180px;
  --nav-collapsed: 56px;
}

/* ============================================================
   DARK MODE
   Inverts background/ink ramps to a warm-paper-on-deep-slate
   palette. Status / stage colors are nudged a touch brighter so
   they still read against the darker surfaces. Activated via
   `<html data-theme="dark">` from theme.js (system pref or the
   user's explicit setting in the Profile page).
   ============================================================ */
[data-theme="dark"] {
  --bg: #1a1d22;
  --bg-rule: #2a2f37;
  --bg-card: #21252b;
  --bg-hover: #2a2f37;
  --bg-selected: #353c47;
  --memo-paper: #e3cea7;   /* aged manila — matches .memo-render override */

  --ink: #f0eadb;          /* warm paper */
  --ink-soft: #cfc8b6;
  --ink-muted: #8b8678;
  --ink-faint: #5c5950;

  --accent: #8aa1bc;
  --accent-hover: #a9bbd1;
  --accent-soft: #6f8298;
  --accent-bg: #2c343f;

  --client: #8aa1bc;

  --status-active:    #8b8678;
  --status-hot:       #e5705e;
  --status-submitted: #e0a25a;
  --status-offer:     #e3c062;
  --status-sold:      #8eb175;
  --status-dormant:   #6e6a60;

  --stage-pitched:    #e5705e;
  --stage-reading:    #e0a25a;
  --stage-offer:      #e3c062;
  --stage-acquired:   #8eb175;
  --stage-optioned:   #76a4b3;
  --stage-passed:     #9483b3;

  /* The stage-prefix label only ever appears inside .memo-render,
     which stays on warm manila paper in dark mode (see the
     memo-render override block at the bottom of this file). So
     the accent is intentionally the same warm terracotta as the
     light theme — no dark-theme override needed. */

  color-scheme: dark;
}

/* ============================================================
   EXTRA THEMES — Sepia, Forest, Oxblood, Midnight.
   Opt-in only via Settings → Appearance (system mode still
   flips between light and dark only). Each block redefines
   the same ~25 tokens the dark block does, so every surface
   that uses var(--bg) / var(--ink) / var(--accent) etc.
   adapts automatically. Status + stage colors are split
   between light-theme tones (sepia) and dark-theme tones
   (forest / oxblood / midnight) so semantic colors stay
   readable on each surface.

   The memo-render card itself stays paper-toned across every
   theme (Approach 3: "document on a desk") — paper tones are
   set in the memo-render override block further down in this
   file. The default --memo-stage-accent terracotta carries
   through every theme since it reads well on paper-tone
   surfaces; no per-theme override needed.
   ============================================================ */

/* SEPIA — manila pages, walnut ink, brass-lamp warmth. Bridges
   light and dark; reads as "fresh document on aged desk." */
[data-theme="sepia"] {
  --bg: #ebdcc1;
  --bg-rule: #c8b896;
  --bg-card: #f3e8d1;
  --bg-hover: #ddcdb1;
  --bg-selected: #c4b08a;
  --memo-paper: #faf3e3;   /* pale ivory — matches .memo-render override */

  --ink: #3a2818;
  --ink-soft: #5d4427;
  --ink-muted: #84684a;
  --ink-faint: #a99776;

  --accent: #8b4513;
  --accent-hover: #6b3411;
  --accent-soft: #a06940;
  --accent-bg: #e0d0b5;

  --client: #964820;

  /* Sepia is a light theme — keep the warm status palette. */
  --status-active:    #757265;
  --status-hot:       #c44536;
  --status-submitted: #d68c3a;
  --status-offer:     #d4a93a;
  --status-sold:      #6b8e4e;
  --status-dormant:   #aaa595;

  --stage-pitched:    #c44536;
  --stage-reading:    #d68c3a;
  --stage-offer:      #d4a93a;
  --stage-acquired:   #6b8e4e;
  --stage-optioned:   #4a7c8a;
  --stage-passed:     #6e5a8a;

  color-scheme: light;
}

/* FOREST — leather library, brass lamp, wood paneling. */
[data-theme="forest"] {
  --bg: #1f2a25;
  --bg-rule: #2d3a32;
  --bg-card: #263530;
  --bg-hover: #2d3a32;
  --memo-paper: #efe4ca;   /* warm cream — matches .memo-render override */
  --bg-selected: #3a4d42;

  --ink: #e8dec3;
  --ink-soft: #c5b894;
  --ink-muted: #948872;
  --ink-faint: #65604f;

  --accent: #b8956a;
  --accent-hover: #d4b186;
  --accent-soft: #957658;
  --accent-bg: #2c3a32;

  --client: #b8956a;

  /* Dark status tones — brighter for dark surfaces. */
  --status-active:    #8b8678;
  --status-hot:       #e5705e;
  --status-submitted: #e0a25a;
  --status-offer:     #e3c062;
  --status-sold:      #a8b074;
  --status-dormant:   #6e6a60;

  --stage-pitched:    #e5705e;
  --stage-reading:    #e0a25a;
  --stage-offer:      #e3c062;
  --stage-acquired:   #a8b074;
  --stage-optioned:   #76a4b3;
  --stage-passed:     #9483b3;

  color-scheme: dark;
}

/* OXBLOOD — cracked red leather, gentleman's-club study. */
[data-theme="oxblood"] {
  --bg: #1f1612;
  --bg-rule: #2e2018;
  --bg-card: #26181a;
  --bg-hover: #2e2018;
  --memo-paper: #e8d8b7;   /* aged parchment — matches .memo-render override */
  --bg-selected: #3d2520;

  --ink: #f0ddc4;
  --ink-soft: #c9b196;
  --ink-muted: #948065;
  --ink-faint: #6b5a45;

  --accent: #b8513c;
  --accent-hover: #d3614a;
  --accent-soft: #9c4232;
  --accent-bg: #3d2520;

  --client: #d3614a;

  --status-active:    #8b8678;
  --status-hot:       #e58263;
  --status-submitted: #e0a25a;
  --status-offer:     #e3c062;
  --status-sold:      #9aaa68;
  --status-dormant:   #6e6a60;

  --stage-pitched:    #e58263;
  --stage-reading:    #e0a25a;
  --stage-offer:      #e3c062;
  --stage-acquired:   #9aaa68;
  --stage-optioned:   #76a4b3;
  --stage-passed:     #9483b3;

  color-scheme: dark;
}

/* MIDNIGHT — late-night desk, brass lamplight, indigo paper. */
[data-theme="midnight"] {
  --bg: #161a26;
  --bg-rule: #232838;
  --bg-card: #1d2230;
  --bg-hover: #232838;
  --memo-paper: #e3cea7;   /* aged manila — shares with Dark theme's .memo-render override */
  --bg-selected: #2e3548;

  --ink: #e8e3d0;
  --ink-soft: #c5bca5;
  --ink-muted: #918a73;
  --ink-faint: #5c5644;

  --accent: #c8a878;
  --accent-hover: #dcbb8a;
  --accent-soft: #a48a62;
  --accent-bg: #2e3548;

  --client: #c8a878;

  --status-active:    #8b8678;
  --status-hot:       #e09a78;
  --status-submitted: #e0a25a;
  --status-offer:     #e3c062;
  --status-sold:      #a4b078;
  --status-dormant:   #6e6a60;

  --stage-pitched:    #e5705e;
  --stage-reading:    #e0a25a;
  --stage-offer:      #e3c062;
  --stage-acquired:   #a4b078;
  --stage-optioned:   #76a4b3;
  --stage-passed:     #9483b3;

  color-scheme: dark;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input  { font-family: inherit; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ============================================================
   SIGN-IN / AUTH SCREENS
   ============================================================ */

.auth-screen {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  padding: 24px;
  z-index: 1000;
}

.auth-card {
  width: 100%;
  max-width: 400px;
  text-align: center;
  padding: 48px 32px;
}

/* The wordmark is now an <img src="/galley-logo.svg">. The SVG carries
   its own underline and glyph paths, so we don't need font-family,
   weight, ::after pseudo, or anything else here. Just size it. */
/* Rendered via mask-image (not <img>) so the logo color is
   `background-color: var(--ink)` — automatically matching each
   theme's body-text color. The wordmark SVG is a pure 2-color
   asset (dark fill on transparent), so it makes a clean mask
   silhouette. Explicit height is required because mask-image
   doesn't establish intrinsic dimensions. */
.auth-brand {
  display: block;
  width: 220px;
  height: 80px;        /* matches wordmark aspect ratio 2889:1051 (~2.749:1) */
  margin: 0 auto 14px;
  background-color: var(--ink);
  mask-image: url(/galley-logo.svg);
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-image: url(/galley-logo.svg);
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}

.auth-tagline {
  font-family: var(--serif);
  font-size: 16px;
  font-style: italic;
  color: var(--ink-muted);
  margin-bottom: 40px;
  letter-spacing: 0.005em;
}

.auth-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 22px;
  background: var(--accent);
  color: var(--bg-card);
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  transition: background 120ms ease;
  min-width: 220px;
}
.auth-button:hover { background: var(--accent-hover); }
.auth-button:disabled { opacity: 0.6; cursor: not-allowed; }

.auth-button.secondary {
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--bg-rule);
}
.auth-button.secondary:hover { background: var(--bg-hover); color: var(--ink); }

.auth-google-mark {
  width: 16px;
  height: 16px;
  display: inline-block;
}

.auth-status {
  margin-top: 28px;
  padding: 16px 20px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  text-align: left;
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.5;
}

.auth-status .label {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 6px;
}

.auth-status .email {
  font-family: var(--serif);
  font-size: 15px;
  color: var(--ink);
  font-style: italic;
  margin-bottom: 4px;
  word-break: break-all;
}

.auth-error {
  margin-top: 16px;
  font-size: 12px;
  color: var(--accent);
  font-style: italic;
  font-family: var(--serif);
}

.auth-actions {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
}

.auth-loading {
  font-size: 12px;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 500;
}

/* ============================================================
   APP SHELL
   ============================================================ */

.app {
  display: flex;
  height: 100vh;
  width: 100vw;
}

.app[hidden] { display: none; }

/* ============================================================
   SIDE NAV
   ============================================================ */

.nav {
  width: var(--nav-width);
  flex-shrink: 0;
  background: var(--bg);
  border-right: 1px solid var(--bg-rule);
  display: flex;
  flex-direction: column;
  /* No top padding — the brand wordmark's own 80px shelf
     (see .nav-brand) IS the page-header zone, and we want
     its bottom edge to sit at exactly 80px from the viewport
     top so the first nav item's highlight box aligns with the
     content column's search field on the right. No bottom
     padding either; the user block pins itself with
     margin-top: auto and any extra padding here would leave
     a dead strip under it. */
  padding: 0;
}

/* Nav-side wordmark — mirrors the auth-screen brand styling so the
   logo reads the same in both places. Lives inside an 80px shelf
   at the top of the side nav, vertically centered, so its text
   baseline lines up horizontally with the page title in the
   content area. The text + underline live in an inner span (.nav-
   brand-text) so the underline tracks the text baseline rather
   than the shelf bottom. */
.nav-brand {
  display: flex;
  align-items: center;
  align-self: stretch;
  min-height: var(--page-header-height);
  padding: 0 20px;
  margin: 0;
}
/* Wordmark variant — shown at the default expanded nav width.
   Mask-image so `background-color: var(--ink)` colors the logo
   to match each theme's body-text color exactly. */
.nav-brand-text {
  display: block;
  width: 140px;
  height: 51px;        /* matches wordmark aspect ratio (~2.749:1) */
  background-color: var(--ink);
  mask-image: url(/galley-logo.svg);
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-image: url(/galley-logo.svg);
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}
/* Glyph variant — shown only when the nav collapses to icon-only
   (≤800px viewport, see the @media block further down). Default
   is hidden; the responsive rule flips display. Without the
   explicit `display: none`, the base `display: block` would
   override the element's `hidden` attribute. Uses mask-image
   with the favicon glyph SVG so its color also tracks --ink. */
.nav-brand-glyph {
  display: none;
  width: 32px;
  height: 32px;
  margin: 0 auto;
  background-color: var(--ink);
  mask-image: url(/galley-glyph.svg);
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-image: url(/galley-glyph.svg);
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}
/* The dark-mode filter-invert rule was retired — mask-image +
   `background-color: var(--ink)` handles per-theme coloring
   exactly across every theme, including Sepia (deep coffee
   brown on manila), Forest (warm cream on green), Oxblood
   (warm bone on wine), Midnight (warm cream on indigo). */

/* ============================================================
   Dev environment brand override.

   When the page is served from a dev hostname (localhost,
   127.0.0.1, *.local, or galley-dev-rocliterary.*), the inline
   <script> at the top of <head> in index.html adds the
   `is-dev` class to <html>. These rules swap the SVG-masked
   "Galley" wordmark for a CSS-rendered "DEV" wordmark in
   matching Playfair italic typography — at-a-glance visual
   distinction so the developer never mistakes the dev tab for
   the prod tab.

   The rules clear the mask-image background and render the
   text via a ::before pseudo. Underline mimicked via
   border-bottom so the silhouette still reads "branded
   wordmark with underline" — just the letters differ.

   Aesthetic overrides apply to all three brand slots, but
   `display` is NOT set in the shared rule so the existing
   visibility behavior is preserved:
     • .auth-brand     — always visible (gets display: flex below)
     • .nav-brand-text — always visible at default viewport (gets display: flex below)
     • .nav-brand-glyph — hidden until @media (max-width: 800px) kicks in,
       at which point we re-assert display:flex !important to defeat the
       hidden attribute + match the existing prod-mode reveal pattern.

   Has zero effect on prod (galley.rocliterary.com): the
   hostname check never adds .is-dev there.
   ============================================================ */
html.is-dev .auth-brand,
html.is-dev .nav-brand-text,
html.is-dev .nav-brand-glyph {
  background-color: transparent;
  mask-image: none;
  -webkit-mask-image: none;
  align-items: center;
  justify-content: center;
  font-family: 'Playfair Display', serif;
  font-style: italic;
  font-weight: 700;
  color: var(--ink);
  line-height: 1;
}
html.is-dev .auth-brand,
html.is-dev .nav-brand-text {
  display: flex;
}
html.is-dev .auth-brand::before {
  content: 'DEV';
  font-size: 96px;
  border-bottom: 5px solid var(--ink);
  padding-bottom: 8px;
  letter-spacing: 0.02em;
}
html.is-dev .nav-brand-text::before {
  content: 'DEV';
  font-size: 56px;
  border-bottom: 3px solid var(--ink);
  padding-bottom: 4px;
  letter-spacing: 0.02em;
}
html.is-dev .nav-brand-glyph::before {
  content: 'D';
  font-size: 26px;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 1px;
}

.nav-build-memo {
  margin: 0 12px 24px;
  padding: 12px 14px;
  background: var(--accent);
  color: var(--bg-card);
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-align: left;
  display: flex;
  align-items: center;
  gap: 10px;
  transition: background 120ms ease;
  width: calc(100% - 24px);
}
.nav-build-memo:hover { background: var(--accent-hover); }
.nav-build-memo .star { font-size: 14px; }
.nav-build-memo .badge {
  margin-left: auto;
  background: rgba(251, 249, 244, 0.18);
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 600;
}
.nav-build-memo .badge[hidden] { display: none; }

.nav-section-label {
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  padding: 8px 20px 6px;
  font-weight: 600;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 20px;
  font-size: 13px;
  color: var(--ink-soft);
  cursor: pointer;
  border-left: 2px solid transparent;
  transition: background 100ms ease, color 100ms ease;
  width: 100%;
  text-align: left;
}
.nav-item:hover {
  background: var(--bg-hover);
  color: var(--ink);
}
.nav-item.active {
  background: var(--bg-hover);
  color: var(--ink);
  border-left-color: var(--accent);
  font-weight: 500;
}
.nav-item .icon {
  /* Container for the SVG icon. width is fixed so the icon slot
     stays a stable column (so labels align across nav items
     even as icons differ in horizontal extent). */
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-muted);
  flex-shrink: 0;
}
.nav-item .icon svg {
  display: block;
}
.nav-item.active .icon { color: var(--accent); }

.nav-divider {
  height: 1px;
  background: var(--bg-rule);
  margin: 16px 16px;
}

.nav-item.search { margin-top: auto; }

.nav-search-shortcut {
  margin-left: auto;
  font-size: 10px;
  color: var(--ink-faint);
  background: var(--bg-rule);
  padding: 2px 6px;
  border-radius: 3px;
  font-family: var(--sans);
}

/* User block — pinned to the bottom of the nav (margin-top: auto
   eats whatever vertical space is left after the nav items) and
   acts as a button that routes to #settings when clicked. */
.nav-user {
  margin-top: auto;
  padding: 12px 20px;
  border-top: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  gap: 10px;
  position: relative;
  /* Reset button defaults so the row reads like the other nav-items. */
  appearance: none;
  background: transparent;
  border: none;
  cursor: pointer;
  width: 100%;
  text-align: left;
  font-family: inherit;
  transition: background 100ms;
}
.nav-user:hover { background: var(--bg-hover); }
.nav-user.active { background: var(--bg-selected); }

.avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg-card);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  flex-shrink: 0;
  background-size: cover;
  background-position: center;
}

/* Tinted avatar variants — all live in the warm-paper + slate-ink
   family. Apply one of these classes alongside .avatar to give a
   list-row avatar a subtle, distinct color. */
.avatar.tint-slate { background: #dee1e5; color: #3a4754; }
.avatar.tint-tan   { background: #e8e2d4; color: #6b5418; }
.avatar.tint-grey  { background: #d8d4cc; color: #3e4854; }
.avatar.tint-clay  { background: #e8dac8; color: #5c4a32; }
.avatar.tint-light { background: #d4d8de; color: #4a5664; }

.nav-user-name {
  font-size: 12px;
  color: var(--ink-soft);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-user-menu-btn {
  font-size: 14px;
  color: var(--ink-muted);
  padding: 2px 6px;
  border-radius: 3px;
}
.nav-user-menu-btn:hover {
  background: var(--bg-hover);
  color: var(--ink);
}

.nav-user-menu {
  position: absolute;
  bottom: calc(100% - 4px);
  left: 12px;
  right: 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
  padding: 6px;
  z-index: 50;
}
.nav-user-menu[hidden] { display: none; }

.nav-user-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  font-size: 12px;
  color: var(--ink-soft);
  border-radius: 4px;
}
.nav-user-menu-item:hover {
  background: var(--bg-hover);
  color: var(--ink);
}

/* ============================================================
   COLUMNS AREA — CORE DRILL UI
   ============================================================ */

.columns-wrap {
  flex: 1;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

/* Wrapper around the column stack ONLY — the .cw-footer is
   a sibling of this element, NOT a descendant. This split
   exists so the profile-launched Memo Builder drawer can
   mount inside `.cw-columns-area` with `position: absolute;
   inset: 0` and overlay the column stack without ever
   reaching the footer. Mirrors the memo-section pattern
   where `.memo-shell-drawer` lives inside
   `.memo-section-content-row` and `.memo-section-footer` is
   the row's sibling — drawer physically cannot reach the
   footer because the footer isn't in the drawer's
   positioned ancestor.

   `display: flex; flex-direction: column` so `.columns`
   inside continues to receive its `flex: 1` growth the same
   way it did when it was a direct flex child of
   `.columns-wrap` — adding this wrapper is layout-neutral
   for everything except the drawer overlay. */
.cw-columns-area {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

/* Column-stack footer — three fixed zones at the bottom of
   .columns-wrap. Left zone holds back/forward nav buttons (always
   visible, faded when inactive). Centre swaps between metadata
   stats and the breadcrumb path based on `.columns-wrap.is-drilled`.
   Right zone holds list-view trailing buttons (Archive). */
.cw-footer {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  /* Pinned at exactly 52px — same hard clamp as
     `.section-list-footer` and `.memo-section-footer`. With
     box-sizing: border-box, 52px = 1px border + 10+10 padding +
     31px content area. The 32px `.cw-nav-btn` is taller than the
     content area by 1px; `align-items: center` distributes that
     0.5px above + 0.5px below into the padding region, and
     `overflow: visible` (default) keeps it from clipping —
     visually imperceptible, box stays at 52. */
  height: 52px;
  padding: 10px 24px;
  border-top: 1px solid var(--bg-rule);
  background: var(--bg);
  gap: 16px;
  box-sizing: border-box;
}
.cw-footer-left {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.cw-footer-center {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  /* Left-align centre content (metadata + breadcrumb) just to the
     right of the nav buttons. Earlier centred layout pushed the
     metadata to the optical centre of the work area, which felt
     disconnected from the rest of the footer chrome. */
  justify-content: flex-start;
}
.cw-footer-right {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  /* Reserve a min-width so the centre stays optically centred
     even when the list view hasn't supplied a trailing button
     yet (e.g., during the brief mount window). */
  min-width: 0;
}
/* Centre zone — only one child visible at a time (metadata vs
   breadcrumb), swapped by `.is-drilled`. Both share the same
   typographic register so the swap is invisible chrome-wise.
   Single-line: nowrap + ellipsis on overflow keeps the footer at
   a fixed 52px height regardless of window width. The full text
   is exposed via a `title` attribute set in JS for hover tooltip. */
.cw-center-metadata,
.cw-center-breadcrumb {
  display: none;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.columns-wrap:not(.is-drilled) .cw-center-metadata { display: block; }
.columns-wrap.is-drilled .cw-center-breadcrumb { display: block; }

/* Memo section is the architectural outlier: it's a single
   col-wide column with its own internal panes + footer (Copy /
   PDF / Send buttons). The column-stack footer doesn't apply,
   so columns.js tags the wrap with `.is-memos-section` while
   the memo-section column is mounted, and we hide .cw-footer
   here. The memo column's own internal footer takes over. */
.columns-wrap.is-memos-section .cw-footer { display: none; }

/* Nav buttons in the left zone — circular, sized to match the
   Archive button's height (border + 7+7 padding + ~18px line ≈ 32px).
   Slate-soft icon, transparent fill until hover, fade to ~0.4 when
   inactive (no click handler fires while faded — see columns.js). */
.cw-nav-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 50%;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 100ms, border-color 100ms, background 100ms, opacity 100ms;
  flex-shrink: 0;
}
.cw-nav-btn:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--bg-hover);
}
.cw-nav-btn.is-faded {
  opacity: 0.4;
  cursor: default;
}
.cw-nav-btn.is-faded:hover {
  color: var(--ink-soft);
  border-color: var(--bg-rule);
  background: transparent;
}

/* Breadcrumb crumb styling — typography matches the metadata
   strip exactly (11px uppercase slate-soft small caps). The only
   per-segment liberties are: hover-state on past segments for
   click affordance, and a faintly-darker tone on the current
   segment to signal "you are here" (no bold, no size shift). */
.crumb {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  font-size: 11px;
  font-weight: inherit;
  color: var(--ink-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  flex-shrink: 0;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color 100ms ease;
}
.crumb:hover {
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.crumb.current {
  color: var(--ink-soft);
  cursor: default;
}
.crumb.current:hover { text-decoration: none; }
.crumb-sep {
  color: var(--ink-faint);
  margin: 0 6px;
}
.crumb-overflow {
  color: var(--ink-faint);
  cursor: default;
  padding: 0 2px;
}

.columns {
  flex: 1;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-behavior: smooth;
  /* Snap scroll to column starts so manual horizontal scrolling
     lines up cleanly. `proximity` is gentler than `mandatory` —
     small drags don't get yanked to the next column. */
  scroll-snap-type: x proximity;
}
.col {
  scroll-snap-align: start;
}

.col {
  height: 100%;
  border-right: 1px solid var(--bg-rule);
  overflow-y: auto;
  overflow-x: hidden;
  /* Reserve space for the scrollbar even when content fits without
     scrolling, so a column with one item lines up pixel-for-pixel
     with a column that overflows (no horizontal jitter when more
     rows arrive). */
  scrollbar-gutter: stable;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  position: relative;
}
/* `.is-focused` is still applied by columns.js to the rightmost
   column, but its visual treatment was removed: the prior 2px
   accent inset at the top read as a stray stripe (especially at
   the top of detail pages) and the breadcrumb's current-segment
   already provides the "you are here" cue. The class is kept as a
   hook in case future styling wants to target the focused column. */

/* ============================================================
   LIBRARY FILTER ROW (Catalog browse view)
   Two-axis filter — Media type + Genre — both multi-select via
   typeahead popovers. Sits between the existing "All / Queued /
   Recent" chip row and the result list. Chips render in groups
   by axis, each with × to remove; "+ Media ▾" / "+ Genre ▾"
   buttons open the popovers; "Clear all" appears when any filter
   is applied. Result count rendered just below.
   ============================================================ */
.lib-filter-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  /* Equal vertical padding so the dividers above and below sit
     equidistant from the row content. Horizontal alignment stays
     left-flush — chips are added left-to-right as the user picks
     genres. */
  padding: 8px 20px;
  border-bottom: 1px solid var(--bg-rule);
}

/* Chrome wrapper that pins the header + filter + sort rows to
   the top of the list column during scroll. The wrapper is the
   sticky element; its children (.col-header, .lib-filter-row,
   .lib-filter-row--sort) keep their original styling and
   positioning untouched — so the visual treatment matches
   exactly what shipped before, just no longer scrolls away. */
.col-chrome {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--bg);
}
.lib-filter-eyebrow {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-right: 4px;
}
.lib-filter-divider {
  flex-basis: 100%;
  height: 0;
  margin: 0;
  /* Force a wrap break between Media and Genre chip groups so each
     axis reads as its own line on narrow columns; on wider screens
     the line wraps gracefully via flex-wrap. */
}
.lib-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  height: 22px;
  padding: 0 4px 0 8px;
  background: var(--bg-selected);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-size: 11px;
  line-height: 1;
  color: var(--ink);
}
.lib-filter-chip-label { padding-right: 2px; }
.lib-filter-chip-remove {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 12px;
  padding: 0 4px;
  height: 16px;
  border-radius: 2px;
  transition: background 100ms, color 100ms;
}
.lib-filter-chip-remove:hover {
  color: var(--ink);
  background: rgba(0, 0, 0, 0.06);
}
.lib-filter-add {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 8px;
  background: transparent;
  border: 1px dashed var(--ink-faint);
  border-radius: 3px;
  font-size: 11px;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
  transition: all 100ms;
}
.lib-filter-add:hover {
  border-style: solid;
  border-color: var(--ink-soft);
  color: var(--ink);
  background: var(--bg-hover);
}
.lib-filter-add .caret {
  font-size: 9px;
  opacity: 0.85;
}
/* A2 — Sort-pill row. Lives as a second .lib-filter-row beneath
   the existing genre filter row. Visually distinct from the
   genre filter pills:
     • single-select (exactly one pill active at a time)
     • no × on chips (a sort isn't "removed", just swapped)
     • a direction arrow on the ACTIVE pill only, drawn via
       ::after using the data-sort-dir attribute
   The row itself inherits .lib-filter-row's chrome (padding,
   gap, bottom hairline) so it reads as a sibling section beneath
   the filter row. The --sort modifier is a hook for any per-
   axis tweaks down the road; today it's just a marker class. */
.lib-filter-row--sort {
  /* No layout tweaks needed beyond the base row. The modifier
     stays so future fine-tuning doesn't have to discover an
     existing selector to graft onto. */
}
.lib-sort-chip {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 10px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
  transition: background 100ms, border-color 100ms, color 100ms;
}
.lib-sort-chip:hover {
  border-color: var(--ink-faint);
  color: var(--ink);
  background: var(--bg-hover);
}
.lib-sort-chip--active {
  background: var(--bg-selected);
  border-color: var(--bg-rule);
  color: var(--ink);
}
/* Direction arrow on the active pill only — drawn via ::after
   so the label text in the button stays clean. The arrow is
   keyed to data-sort-flipped, NOT the literal asc/desc dir:
     • default direction (the axis's "natural top" — A at top
       for alphabetical, newest at top for time) → ▼
     • reversed → ▲
   This way every pill shows ▼ on first activation regardless
   of whether the axis's default is asc or desc underneath. */
.lib-sort-chip--active::after {
  content: '▼';
  font-size: 9px;
  margin-left: 2px;
  opacity: 0.85;
}
.lib-sort-chip--active[data-sort-flipped="1"]::after {
  content: '▲';
}

/* A2 — Year-divider heading for the Library's grouped Year
   sort. Mirrors the eyebrow treatment used elsewhere (small,
   uppercase, slate-faint) so the divider reads as section
   chrome rather than as a row. The .--undated variant is a
   slight italicization to signal "the catch-all bucket". */
.list-year-divider {
  padding: 16px 20px 4px;
  font-family: var(--sans);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 600;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--bg-rule);
}
.list-year-divider--undated {
  font-style: italic;
  letter-spacing: 0.08em;
}

.lib-filter-clear-all {
  margin-left: auto;
  appearance: none;
  background: transparent;
  border: none;
  font-size: 11px;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 4px 6px;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color 100ms, color 100ms;
}
.lib-filter-clear-all:hover {
  color: var(--ink);
  text-decoration-color: var(--ink);
}
.lib-result-count {
  padding: 6px 20px 8px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
  border-bottom: 1px solid var(--bg-rule);
}

/* List columns are fixed-width — they're row scanners, scaling them
   wider doesn't help the user. */
.col.col-list   { flex: 0 0 var(--col-width); }
/* Detail columns flex to absorb the remaining horizontal space, with
   a sane min so the detail body never gets squeezed below readable.
   When two detail columns coexist (e.g. book + person after a cross-
   reference drill) they split the available space proportionally. */
.col.col-detail { flex: 1 1 var(--col-detail-min); min-width: var(--col-detail-min); }
.col.col-wide   { flex: 1 1 0; min-width: 500px; }

.col.col-empty .col-stub {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 16px;
  color: var(--ink-faint);
  line-height: 1.5;
}

.col-stub-eyebrow {
  display: block;
  font-family: var(--sans);
  font-style: normal;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 10px;
}

.col-stub-body em { color: var(--accent); font-style: italic; }

/* Column header. The very first row (.col-header-row) is sized
   to the 80px page-header shelf so the page title aligns with
   the side-nav brand wordmark across the top of the app. We
   strip the wrapper's top padding — the title row owns its own
   vertical space — and keep horizontal + bottom padding for the
   subtitle / search / chips that flow below. */
.col-header {
  padding: 0 20px 12px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 2;
}
.col-title {
  font-family: var(--serif);
  font-size: 28px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.15;
}
.col-subtitle {
  font-size: 11px;
  color: var(--ink-muted);
  margin-top: 2px;
}
.col-search {
  /* No top margin — the search field sits flush against the
     bottom of the 80px page-header shelf, which puts its top
     edge at exactly 80px from the viewport top (matching the
     side-nav's first nav-item highlight on the left). */
  margin-top: 0;
  width: 100%;
  padding: 6px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 12px;
  color: var(--ink);
  outline: none;
  transition: border-color 100ms;
}
.col-search:focus { border-color: var(--accent); }
.col-search::placeholder { color: var(--ink-faint); }

.col-filters {
  display: flex;
  gap: 4px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.filter-chip {
  padding: 3px 9px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-size: 11px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: all 100ms;
}
.filter-chip:hover { background: var(--bg-hover); }
.filter-chip.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}

/* "+" affordance on the Person detail role-chip strip. Reads as
   a quiet call-to-action: same chip footprint as the role chips
   it lives next to, but borderless + dashed-on-hover so it feels
   like an add button rather than a value chip. */
.filter-chip-add {
  border-style: dashed;
  border-color: var(--bg-rule);
  color: var(--ink-faint);
  font-weight: 500;
  padding: 3px 10px;
  min-width: 28px;
}
.filter-chip-add:hover {
  border-color: var(--ink-soft);
  color: var(--ink-soft);
  background: transparent;
}

/* Role popover — opened by the "+" chip. Lists unset roles,
   click to add. Shares its visual register with the Person
   page's other lightweight popovers (reports-to, photo-url):
   slate-soft border, low shadow, single-column action list. */
/* Brought into the same chrome as .classify-popover so the
   ad-hoc inline pickers (Person page "+ Role" chip, company
   imprint picker) match the rest of the app's dropdown register.
   Same bg-card fill, 4px radius, soft shadow; items share the
   8×12 padding + 13px sans + hairline divider with the canonical
   .classify-popover-item rows. */
.role-popover {
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07);
  min-width: 140px;
  max-height: 280px;
  overflow: hidden auto;
}
.role-popover-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bg-rule);
  padding: 8px 12px;
  font-family: var(--sans);
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
}
.role-popover-item:last-child { border-bottom: 0; }
.role-popover-item:hover { background: var(--bg-hover); }

/* ============================================================
   LIST ROWS (books, clients, people, etc.)
   ============================================================ */

.book-row, .client-row, .person-row {
  padding: 14px 20px;
  border-bottom: 1px solid var(--bg-rule);
  cursor: pointer;
  transition: background 100ms;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
}
/* Book rows are horizontal — small cover on the left, stacked text
   to the right. The cover keeps the row scannable as a "shelf". */
.book-row {
  flex-direction: row;
  align-items: flex-start;
  gap: 12px;
}
.book-row .book-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.book-row-cover {
  flex-shrink: 0;
  width: 38px;
  height: 56px;
  border-radius: 2px;
  background-color: var(--bg-card);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid var(--bg-rule);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
  /* Centered glyph for the empty placeholder. */
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 18px;
  color: var(--ink-faint);
}
.book-row-cover.empty { letter-spacing: 0; }
/* Mouse-mode hover paints the standard --bg-hover sand tint so
   a user scanning with the cursor still gets feedback over the
   row they're about to click. The two-rows-selected confusion
   that pushed me to remove this earlier is now handled by the
   `body.keyboard-nav` suppression below — when arrow keys move
   selection while the mouse sits still, hover bg disappears
   instead of competing with the selected row. */
.book-row:hover:not(.selected),
.client-row:hover:not(.selected),
.person-row:hover:not(.selected) {
  background: var(--bg-hover);
}
.book-row.selected, .client-row.selected, .person-row.selected {
  background: var(--bg-selected);
  box-shadow: inset 3px 0 0 var(--accent);
}
/* Keyboard-nav mode (set by attachListArrowNav, cleared on next
   mousemove) — suppress hover bg on non-selected rows so the
   originally-clicked row stops looking "still selected" while
   the user arrows around. The .selected row keeps its bg via
   the rule above. */
body.keyboard-nav .book-row:hover:not(.selected),
body.keyboard-nav .client-row:hover:not(.selected),
body.keyboard-nav .person-row:hover:not(.selected) {
  background: transparent;
}

/* Hover-only quick delete button on list rows. */
.row-delete-btn {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  color: var(--ink-muted);
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  opacity: 0;
  transition: opacity 100ms, color 100ms, border-color 100ms;
  cursor: pointer;
  z-index: 1;
}
.book-row:hover .row-delete-btn,
.person-row:hover .row-delete-btn,
.client-row:hover .row-delete-btn,
/* Also reveal on the keyboard-selected row so a user who's
   navigating with arrow keys can still see the delete affordance
   without having to mouse over the row. */
.book-row.selected .row-delete-btn,
.person-row.selected .row-delete-btn,
.client-row.selected .row-delete-btn {
  opacity: 1;
}
/* While the user is keyboard-navigating (body.keyboard-nav set
   by attachListArrowNav, cleared on next mousemove), suppress
   the hover-only × on non-selected rows. Otherwise the cursor
   sits over the originally-clicked row and keeps painting its
   × even after the keyboard moved selection — the "ghost X"
   effect. The selected row's × stays visible via the rule
   above, which has equal specificity but isn't suppressed. */
body.keyboard-nav .book-row:hover:not(.selected) .row-delete-btn,
body.keyboard-nav .person-row:hover:not(.selected) .row-delete-btn,
body.keyboard-nav .client-row:hover:not(.selected) .row-delete-btn {
  opacity: 0;
}
.row-delete-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-bg);
}

.book-row-head, .client-row-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  justify-content: space-between;
}

.book-row-title {
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
  flex: 1;
}
/* B10/B11 — article/podcast title link. Inherits the row title's
   typography and color so the byline reads as a normal title at
   rest; only an underline-on-hover signals it's an external
   link. (The row's drill-into-profile is the more common click;
   the underline-on-hover lets users find the link when they
   actually want it.) */
.book-row-title-link {
  color: inherit;
  text-decoration: none;
}
.book-row-title-link:hover,
.book-row-title-link:focus {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 3px;
}

/* Star next to the title when this work is queued for the next memo. */
.book-row-title-star {
  display: inline-block;
  margin-left: 6px;
  color: var(--accent);
  font-size: 13px;
  vertical-align: 1px;
}

.book-row-status {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--status-active);
  margin-top: 6px;
}
.book-row-status.hot       { background: var(--status-hot); }
.book-row-status.submitted { background: var(--status-submitted); }
.book-row-status.offer     { background: var(--status-offer); }
.book-row-status.sold      { background: var(--status-sold); }
.book-row-status.dormant   { background: var(--status-dormant); }

.book-row-subtitle {
  font-family: var(--serif);
  font-size: 12px;
  font-style: italic;
  color: var(--ink-muted);
  line-height: 1.35;
  margin-top: 2px;
}

.book-row-author {
  font-size: 12px;
  color: var(--ink-muted);
  font-style: italic;
  font-family: var(--serif);
}

.book-row-meta {
  display: flex;
  gap: 6px;
  font-size: 11px;
  color: var(--ink-faint);
  margin-top: 4px;
  /* Keep the meta line on one row so heavily-tagged works don't push
     the row taller than its neighbours. The genre summary (capped at
     2 genres + "+N" in JS) is the long item; let it ellipsis if even
     that overflows on a narrow column. */
  flex-wrap: nowrap;
  min-width: 0;
  overflow: hidden;
}
.book-row-meta > span {
  flex-shrink: 0;
}
.book-row-meta .book-row-meta-genres {
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.book-row-meta .star { color: var(--accent); }

.client-row-name {
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.client-row-status {
  flex-shrink: 0;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-muted);
  font-weight: 600;
}
.client-row-status.active      { color: var(--status-sold); }
.client-row-status.dormant     { color: var(--ink-faint); }
.client-row-status.prospective { color: var(--status-submitted); }
.client-row-territory {
  font-size: 11px;
  color: var(--ink-muted);
  margin-top: 3px;
  font-style: italic;
  font-family: var(--serif);
}
.client-row-stats {
  font-size: 10px;
  color: var(--ink-faint);
  margin-top: 4px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 500;
}
.client-row-stats .num { color: var(--ink-soft); font-weight: 600; }

/* ============================================================
   DETAIL VIEWS
   ============================================================ */

.detail, .book-detail {
  padding: 32px 36px 80px;
  max-width: none;
}
/* Modifier for detail views whose first element is a
   .detail-page-header shelf (currently company-detail + person-
   detail). Drops the wrapper's 32px top padding so the shelf
   itself sits at y=0, putting the chip row immediately below it
   at exactly y=80 — aligned with the side-nav nav-items area
   and the list column's search field on the left. */
.book-detail.book-detail-shelf {
  padding-top: 0;
}

.detail-eyebrow {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 11px;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 8px;
}

/* The 80px "page-header shelf" used at the top of detail views
   (company / person) so the first row of content below the title
   (chip rows, etc.) starts at exactly y=80px from the viewport
   top — aligning with the side-nav's first nav-item highlight
   and the list column's search field on the left. The title
   itself is vertically centered inside the shelf. */
.detail-page-header {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: var(--page-header-height);
}
.detail-page-header .detail-title {
  /* Inside the shelf the title's bottom margin is unnecessary;
     the shelf's own height provides the gap to the next element. */
  margin-bottom: 0;
}

/* Primary affiliation: "at [Company] · [Title]" — flows inline
   after the Person's name on the same baseline. Same serif as the
   title (so the eye reads them as one phrase), smaller, fully
   italic. The company name still acts as a click target into the
   Company detail column. */
.detail-affiliation-line {
  font-family: var(--serif);
  /* 16px paired with the 28px title — editorial ~4:7 ratio,
     reads as a proper sub-line rather than a metadata footnote. */
  font-size: 16px;
  font-style: italic;
  color: var(--ink-muted);
}
/* Single-tone byline: every span (prefix words, clickable names,
   trailing fields) inherits --ink-muted from .detail-affiliation-line.
   The italic register + hover-underline on links is enough visual
   affordance — no color shifts needed. */
.detail-affiliation-prefix {
  color: inherit;
}
.detail-affiliation-company {
  color: inherit;
  cursor: pointer;
  font-style: italic;
  border-bottom: 1px solid transparent;
  transition: border-color 100ms;
}
.detail-affiliation-company:hover {
  /* Underline darker than the text so the hover state is visible
     against the muted body color. */
  border-bottom-color: var(--ink);
}
.detail-affiliation-title {
  color: inherit;
  font-style: italic;
}

/* Entity title — work / person / company / memo names. Matches
   the page title at 28px (no longer demoted to 22px) because the
   detail pane is a peer surface to the list pane, not a child:
   each column has its own "page title" and they read as co-equal
   headers across the shared 80px shelf. The eyebrow + byline
   below provide the "this is a specific entity, not a page name"
   distinction without needing a size step. */
.detail-title {
  font-family: var(--serif);
  font-size: 28px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.15;
  margin-bottom: 8px;
}

.detail-byline, .detail-subhead {
  font-family: var(--serif);
  font-size: 17px;
  font-style: italic;
  color: var(--ink-soft);
  margin-bottom: 16px;
}

.detail-byline .name {
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 3px;
  transition: text-decoration-color 120ms;
}
.detail-byline .name:hover { text-decoration-color: var(--accent); }

/* B7c — empty-state byline ("No author linked." etc.) is itself
   a click target that opens the contributor picker. The cursor +
   subtle hover background advertises the click affordance so an
   unset byline can be resolved inline (otherwise the empty
   message reads as a passive label). */
.byline-empty-trigger {
  cursor: pointer;
  border-radius: 3px;
  padding: 1px 4px;
  margin: -1px -4px;
  transition: background 100ms;
}
.byline-empty-trigger:hover,
.byline-empty-trigger:focus-visible {
  background: var(--bg-hover);
  outline: none;
}

.detail-status-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 0;
  border-top: 1px solid var(--bg-rule);
  /* No bottom border here — the vital-stats block right below has
     its own top rule, which would otherwise produce a doubled-up
     divider after the PUBLISHING / FILM-TV star toggles. */
  margin-bottom: 0;
  font-size: 12px;
  color: var(--ink-soft);
}

.detail-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: var(--bg-card);
  border-radius: 4px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: var(--ink-soft);
  border: 1px solid var(--bg-rule);
}
.detail-status-pill .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--status-hot);
}

.detail-star-toggle {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 11px;
  color: var(--ink-soft);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: all 100ms;
}
.detail-star-toggle.active {
  background: var(--accent-bg);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
.detail-star-toggle:hover { background: var(--bg-hover); }

/* ============================================================
   CLASSIFICATION ROW (work-detail) — single compact row of chips
   replacing the previous two-row taxonomy block. Sits just under
   the title-block, above the star toggles.

   Layout: media-type chip first, then genre chips, then a subtler
   "+ Add genre" button on the right. Wraps onto multiple lines if
   needed; alignment hugs the start so chips read in order.
   ============================================================ */
.classification-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 4px 0 14px;
}

/* Base chip — used by media-type AND applied genres so they share
   the same slate-ink visual. Explicit height keeps both variants
   pixel-identical regardless of inner content (caret vs × button). */
.classify-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 10px;
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 4px;
  font-size: 11px;
  line-height: 1;
  color: var(--bg);
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: default;
}
.classify-chip.media-type {
  cursor: pointer;
  transition: background 100ms;
}
.classify-chip.media-type:hover { background: var(--ink-soft); border-color: var(--ink-soft); }
.classify-chip .caret {
  font-size: 9px;
  margin-left: 2px;
  opacity: 0.85;
}
/* Genre chips read as tags, not as the work's identity — a warmer,
   more saturated cream background pulls them out from the page bg
   without competing with the dark media-type chip. Same height/shape
   so the row baseline holds. */
.classify-chip.genre {
  padding-right: 4px;
  background: var(--bg-selected);
  border-color: var(--bg-rule);
  color: var(--ink);
  font-weight: 400;
}
.genre-chip-remove {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-muted);
  opacity: 0.7;
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 0 4px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 2px;
  border-radius: 2px;
  transition: opacity 100ms, background 100ms, color 100ms;
}
.genre-chip-remove:hover {
  opacity: 1;
  color: var(--ink);
  background: rgba(0, 0, 0, 0.06);
}

/* + Add genre / + Add agent — outline-only chip-row trigger
   that sits inline with `.classify-chip` filled chips. Reads
   as "do this" not "you've picked this". Same 24px chip height
   so the row keeps a clean baseline; uses the canonical 11px /
   weight 500 / 0.02em register so the typography matches the
   rest of the small-button family. */
.classify-add-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  background: transparent;
  border: 1px dashed var(--ink-faint);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  cursor: pointer;
  transition: background 100ms, border-color 100ms, color 100ms;
}
.classify-add-btn:hover {
  border-color: var(--ink-soft);
  border-style: solid;
  color: var(--ink);
  background: var(--bg-hover);
}

/* ----- Floating popovers (media-type + add-genre) -----
   Mounted into <body>; positioned-fixed by JS. Both share a
   common shell with elevation + cream background. */
.classify-popover {
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  /* 4px radius matches .field-input + .field-picker-trigger, so a
     trigger button and its popover read as one continuous shape
     when the picker opens. A softer single-shadow lifts the panel
     just enough to separate it from the surface beneath without
     the previous heavier double-shadow, which felt overdesigned
     against the warm-paper palette. */
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07);
  font-family: var(--sans);
  min-width: 200px;
  max-width: 320px;
  overflow: hidden;
}
.classify-popover.media-type-popover { padding: 4px 0; }
.classify-popover.add-genre-popover { width: 280px; }

/* Multi-section pickers (agent picker has Person + Company,
   publisher picker has Company + pub-date). Each section is a
   .classify-popover-section-block wrapping its label, search
   input, and list. Separator between blocks for scanning. */
.classify-popover.agent-picker-popover,
.classify-popover.publisher-picker-popover,
.classify-popover.author-picker-popover {
  /* Explicit width so the panel doesn't reflow on subtle content
     changes (e.g. the rename pencil's opacity 0↔1 hover transition
     causing the SVG to re-measure and the auto-sized panel to
     visibly jump wider). Matches the publisher picker's width
     so single-section Person/Company pickers read at the same
     scale across the app. */
  width: 280px;
}
.classify-popover-section-block + .classify-popover-section-block {
  border-top: 1px solid var(--bg-rule);
}
/* AP15 — when the previous section block is hidden (e.g., the
   agent picker's Selected pill collapses to nothing when there
   are no fresh picks or orphans), don't paint the seam above
   the next section. The `+` combinator doesn't honor visibility
   on its own; this rule cancels the border in the hidden case
   via specificity. */
.classify-popover-section-block.is-hidden + .classify-popover-section-block {
  border-top: none;
}
.classify-popover-section-block .classify-popover-list {
  max-height: 180px;
}
/* Make the .create-new top-of-list item not paint a duplicate
   border when it's the first child of a section list. */
.classify-popover-section-block .classify-popover-item.create-new {
  border-top: none;
}
/* Multi-select agent picker: list of currently-linked
   {personId, agencyId} pairs above the add-new sections. Each
   row shows the resolved "Person / Agency" string with an × to
   remove that specific pair. The Person + Agency sections
   below are in "add new agent" mode — picking both halves
   appends a new pair to the array. */
.agent-picker-current-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 12px;
  color: var(--ink);
}
.agent-picker-current-row + .agent-picker-current-row {
  border-top: 1px solid var(--bg-rule);
}
.agent-picker-current-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agent-picker-current-remove {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 14px;
  padding: 0 4px;
  height: 18px;
  line-height: 1;
  border-radius: 2px;
  transition: background 100ms, color 100ms;
}
.agent-picker-current-remove:hover {
  color: #c8102e;
  background: rgba(0, 0, 0, 0.05);
}

/* Multi-batch agent picker: each agency-group is a stacked card
   inside the Selected pill. Header row = agency name + × to drop
   the whole group; person rows = name + × to drop just that
   agent. The Orphans card (agents picked without a resolvable
   agency) uses the same shape but with a prompt header. */
.agent-picker-group-card {
  border-top: 1px solid var(--bg-rule);
  padding: 4px 0;
}
.agent-picker-group-card:first-of-type {
  border-top: none;
}
.agent-picker-group-header-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
}
.agent-picker-group-header {
  flex: 1;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-muted);
}
.agent-picker-orphans-header {
  color: var(--accent-soft);
  font-style: italic;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  padding: 4px 12px;
}
/* Active agency — subtle tint so the user sees where the next
   ambiguous pick will land. */
.agent-picker-group-card.is-active {
  background: rgba(0, 0, 0, 0.025);
}
/* Brief flash when the picker opens with `focusAgencyId` and
   scrolls a specific group's card into view. */
.agent-picker-group-card.is-flash {
  background: var(--accent-bg);
  transition: background 600ms ease-out;
}

/* "+ Set agency" button on the Orphans card in the Selected
   pill — the orphan-resolution affordance in the agents-only
   model. Click opens a small attached popover with a single
   agency picker (search + create). Inherits the .lib-filter-add
   shape (dashed slate border, small label + ▾ caret) so it
   reads as the same "+ pick something" register the app uses
   elsewhere. */
.agent-picker-set-agency-btn {
  margin: 6px 12px 4px;
}

/* Muted tag rendered on agents with no resolvable
   currentCompanyId — surfaces the orphan state inline in the
   agent list so the user can identify which agents will land
   in the Orphans card once picked. Same shape as the
   affiliation tag, lighter color + italic so it reads as a
   placeholder state rather than a real affiliation. */
.classify-popover-item .agent-picker-no-agency-tag {
  padding: 1px 6px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 10px;
  color: var(--ink-faint);
  background: transparent;
  border: none;
}

/* Hide a picker section entirely (header + search + list) when
   the caller toggles .is-hidden on the section block. Used by
   the agent picker to drop the Company section once a company
   is picked — only one belongs to a batch, so keeping the
   search + list visible after the pick would be dead UI. */
.classify-popover-section-block.is-hidden {
  display: none;
}

/* AP14 — `.classify-popover-divider` and `.classify-popover-recent-tag`
   are gone. The Recent vs. rest distinction is now expressed via row
   dimming on `.classify-popover-item.is-recent` (see below) — no
   divider element, no per-row italic "Recent" eyebrow, just a quiet
   opacity drop on Recent rows so the eye lands first on the
   alphabetical rest of the list. */
.classify-popover-item.is-recent {
  opacity: 0.55;
}
.classify-popover-item.is-recent:hover,
.classify-popover-item.is-recent.highlighted {
  opacity: 1;            /* hover/keyboard-focus restores full opacity */
}
.classify-popover-truncation-hint {
  padding: 6px 12px 8px;
  font-size: 11px;
  color: var(--ink-muted);
  font-style: italic;
}
/* Keyboard-highlighted item — same visual register as :hover so
   mouse and arrow-key driving feel like one navigation surface.
   The mousemove handler in buildPickerSection.render keeps these
   in sync so there's never two highlights on screen. */
.classify-popover-item.highlighted:not(.selected) {
  background: var(--bg-hover);
}

/* People rows for agents already affiliated with the currently-
   selected agency. Pinned to the top of the list (via the
   recordPinPriority sort tier in buildPickerSection) and given a
   subtle paper-tint background plus a small slate-italic tag
   that names the affiliation, so the user sees at a glance
   which agents Galley already has on file at this agency vs.
   which they'd be linking fresh. The tag sits between the name
   and the ✓ selected mark so selection state still reads as the
   primary signal. */
.classify-popover-item.is-affiliated {
  background: var(--bg-card);
}
.classify-popover-item.is-affiliated:hover,
.classify-popover-item.is-affiliated.highlighted {
  background: var(--bg-hover);
}
/* Auxiliary-tag wrapper inside picker rows. Holds the Recent tag
   and/or the affiliation tag, right-aligned via margin-left:auto.
   Wrapper handles the alignment so individual tags don't each
   absorb free space (which would create awkward gaps when both
   show on the same row). The ✓ selected mark sits OUTSIDE this
   wrapper so it stays in its established slot at the far right. */
.classify-popover-aux-tags {
  margin-left: auto;
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  /* Reserve room for the ✓ that may follow. */
  margin-right: 6px;
}
.classify-popover-item .agent-picker-affiliation-tag {
  /* margin-left: auto removed — the parent .classify-popover-aux-tags
     wrapper now handles right-alignment so this tag composes cleanly
     with the Recent tag on rows that have both. */
  padding: 1px 6px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 10px;
  color: var(--accent-soft);
  background: transparent;
  border: none;
  letter-spacing: 0;
  /* Truncate long agency names so an "at Marlowe & Quinn Literary"
     doesn't push the ✓ off the right edge of a narrow popover. */
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Path B — "Agency" badge on Company results in the unified agent
   picker, so an agency row reads distinctly from an agent row. A
   small bordered pill (vs the agent affiliation's borderless tint)
   so the kind-distinction is unmistakable at a glance. */
.classify-popover-item .agent-picker-agency-tag {
  padding: 1px 6px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 10px;
  color: var(--ink-soft);
  background: var(--bg-selected);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  letter-spacing: 0;
  white-space: nowrap;
}

/* Path B — the "as agent" / "as agency" suffix on the disambiguated
   create rows. Muted + small so the typed name stays the focus. */
.agent-picker-create-row .agent-picker-create-kind {
  margin-left: 6px;
  font-style: normal;
  font-size: 11px;
  color: var(--ink-soft);
}

/* Path B — muted note inside a Selected-block agency-only card
   (an agency picked with no specific agent). */
.agent-picker-agency-only-note {
  font-style: italic;
  color: var(--ink-faint);
  font-size: 11px;
}

/* "Add Agents" commit button at the bottom of the agent picker —
   sticky-feeling primary action that reads as the explicit close-
   the-batch step. Disabled until both an agency and ≥ 1 person
   are selected; ready state lifts the bg to the warm slate accent
   so it visually pops from the muted picker chrome above. */
/* AP1 — agent-picker panel becomes a flex column so the commit
   button below sits in a non-scrolling footer row beneath a
   flex-grow scrollable body. Without this restructure the commit
   button got pushed below the panel's clamped viewport when the
   SELECTED block grew tall (and double-scrollbar showed up — see
   the inner-list override two rules below). */
.classify-popover.agent-picker-popover {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.agent-picker-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
/* AP7 — bound the Agents list to ~280px with its OWN internal
   scroller so the dropdown stays comfortable even on agencies
   with dozens of agents. (Before AP7 this rule was max-height:
   none + overflow visible — see AP1 history — which let the
   list grow unbounded inside the outer body scroller. Robby's
   feedback: "the dropdown menu is way too long.") The Selected
   block above the list isn't capped here — it's small now that
   AP7 hides committed rows from staging, and orphan-resolution
   needs to be fully visible. */
.classify-popover.agent-picker-popover .classify-popover-section-block .classify-popover-list {
  max-height: 280px;
  overflow-y: auto;
  overflow-x: hidden;
}

/* AP7 — dim already-committed agents in the main list so the
   eye lands on un-picked rows first. The .selected state still
   has its background + bold weight + ✓ from the base
   classify-popover-item rules, plus a Selected-block visibility
   distinction outside the list. The opacity drop is the visual
   "already added" cue Robby asked for. */
.classify-popover.agent-picker-popover .classify-popover-item.selected {
  opacity: 0.55;
}
.classify-popover.agent-picker-popover .classify-popover-item.selected:hover,
.classify-popover.agent-picker-popover .classify-popover-item.selected.highlighted {
  opacity: 1;        /* hover/keyboard-focus restores full opacity */
}

.agent-picker-commit-btn {
  appearance: none;
  display: block;
  width: calc(100% - 16px);
  margin: 8px 8px 8px;
  padding: 8px 12px;
  flex-shrink: 0;
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  background: var(--bg);
  color: var(--ink-faint);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: not-allowed;
  transition: background 100ms, color 100ms, border-color 100ms;
}
.agent-picker-commit-btn.is-ready {
  background: var(--accent);
  color: var(--bg-card);
  border-color: var(--accent);
  cursor: pointer;
}
.agent-picker-commit-btn.is-ready:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* Media-type list rows + genre typeahead rows share base styling.
   Slightly airier than the previous tight 12px register so the
   list reads with the same elegant cadence the Wikidata results
   dropdown has — 13px sans, 8×12 padding, hairline divider
   between consecutive items so each pick has its own row. The
   last item's divider is suppressed so the bottom doesn't draw
   a double-rule against the popover's own border. */
.classify-popover-item {
  appearance: none;
  display: flex;
  align-items: center;
  width: 100%;
  padding: 8px 12px;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--bg-rule);
  text-align: left;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  transition: background 80ms;
}
.classify-popover-item:last-child { border-bottom: 0; }
.classify-popover-item:hover,
.classify-popover-item.highlighted {
  background: var(--bg-hover);
}
.classify-popover-item.selected {
  background: var(--bg-selected);
  font-weight: 500;
}
.classify-popover-item.from-import { font-style: italic; }
.classify-popover-item.from-import .tag {
  margin-left: auto;
  font-style: normal;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  border: 1px dashed var(--ink-faint);
  border-radius: 2px;
  padding: 1px 5px;
}
.classify-popover-item.create-new {
  font-size: 11px;
  color: var(--ink-soft);
  border-top: 1px solid var(--bg-rule);
}
.classify-popover-item.create-new em {
  color: var(--ink);
  font-style: italic;
  margin-left: 2px;
}

/* Shared search input for every picker. Flat/ambient treatment
   (the audit's Option 2): no border, blends into the panel
   background, subtle hover/focus tint as the only chrome. The
   placeholder text ("Search …") carries the "type here"
   affordance.

   `background: var(--bg-card)` matches the panel's own
   background — visually transparent at rest. The explicit color
   (not `transparent`) is intentional: combined with
   `position: sticky`, it keeps scrolled list rows from showing
   through when the input pins to the top of its container.

   `position: sticky; top: 0` is the belt-and-suspenders guard
   for Problem A (agents-clipped-under-search-bar). If the
   ancestor panel scrolls (B5's clamp+flip can produce that),
   the input pins at the top of its containing block; list rows
   scroll cleanly beneath it. Sticky is a no-op in pickers
   where the panel itself doesn't scroll, so it doesn't disturb
   hand-rolled pickers (genre, etc.). */
.classify-popover-input {
  width: 100%;
  padding: 8px 12px;
  border: none;
  /* var(--bg-card) matches the panel's own background exactly,
     so the sticky input is visually invisible at rest while
     still being opaque enough to keep scrolled list rows from
     bleeding through when the panel scrolls (B5 clamp case). */
  background: var(--bg-card);
  /* `appearance: none` overrides browser UA defaults (some
     engines apply a tinted/system-color background to inputs
     that fights our explicit declaration). */
  appearance: none;
  font-family: inherit;
  font-size: 12px;
  color: var(--ink);
  outline: none;
  position: sticky;
  top: 0;
  z-index: 1;
}
.classify-popover-input::placeholder { color: var(--ink-faint); }
/* Focus tint is `var(--bg)` (#f4f1e9) — the page background,
   which is only ~5-6 RGB points darker than the panel's
   `--bg-card` (#faf7ee). Subtle interaction feedback that
   barely registers visually but is enough to confirm focus.
   The previous `--bg-hover` (#ebe3d0) was the row-hover token —
   15-30 points darker than the panel — and read as a heavy
   tan/sepia band on the picker auto-focused on open. */
.classify-popover-input:focus { background: var(--bg); }
.classify-popover-list {
  max-height: 280px;
  overflow-y: auto;
}
.classify-popover-section {
  padding: 6px 12px 2px;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
  background: var(--bg-card);
}
.classify-popover-empty {
  padding: 14px 12px;
  font-size: 11px;
  color: var(--ink-faint);
  font-style: italic;
  text-align: center;
}

/* ----- Legacy work-chip rows (kept for any stray references) ----- */
.work-chips-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 14px;
}
.work-chips-row .work-chips-eyebrow {
  flex-shrink: 0;
  width: 88px;
  padding-top: 5px;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-weight: 600;
  line-height: 1.4;
}
.work-chips-row .work-chips-list {
  flex: 1;
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  align-items: center;
}
/* "+ More" / "Show less" affordance — chip-shaped but ghosted so it
   reads as a control rather than a value. */
.work-chips-row .chip-toggle {
  padding: 3px 9px;
  background: transparent;
  border: 1px dashed var(--bg-rule);
  border-radius: 3px;
  font-size: 11px;
  color: var(--ink-muted);
  cursor: pointer;
  transition: all 100ms;
}
.work-chips-row .chip-toggle:hover {
  background: var(--bg-hover);
  color: var(--ink-soft);
  border-color: var(--ink-faint);
}
/* Inline "+ Add genre" input — same height/feel as a chip so the row
   doesn't reflow when expanded. */
.work-chips-row .chip-add {
  padding: 3px 9px;
  background: transparent;
  border: 1px dashed var(--bg-rule);
  border-radius: 3px;
  font-size: 11px;
  color: var(--ink);
  outline: none;
  min-width: 110px;
  font-family: inherit;
  transition: border-color 100ms;
}
.work-chips-row .chip-add::placeholder { color: var(--ink-faint); }
.work-chips-row .chip-add:focus { border-color: var(--ink-muted); border-style: solid; }

/* Stack the main library chip list above an "API suggestions" sub-row
   when there are unmatched API categories worth surfacing. */
.work-chips-stack {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.work-chips-sub {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  align-items: center;
}
.work-chips-sub .sub-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  margin-right: 4px;
}
/* Ghost chip — visually clear that it's an "add me" affordance, not a
   live selection. Same height/shape as a filter chip so the row reads
   as a continuous group. */
.work-chips-sub .chip-suggested {
  padding: 3px 9px;
  background: transparent;
  border: 1px dashed var(--ink-faint);
  border-radius: 3px;
  font-size: 11px;
  color: var(--ink-muted);
  cursor: pointer;
  transition: all 100ms;
}
.work-chips-sub .chip-suggested:hover {
  border-color: var(--ink-soft);
  color: var(--ink);
  background: var(--bg-hover);
}
.work-chips-sub .chip-suggested:disabled {
  opacity: 0.6;
  cursor: default;
}

.detail-section { margin-bottom: 28px; }

/* .detail-section-former — modifier for any section whose
   contents represent a past / no-longer-current relationship
   (Former Employees on company pages, and any future "alumni"
   surfaces). Drops tile content to the same 0.4 opacity used by
   the work-experience past-card dim and former-colleague chips,
   keeping the visual language of "background reference" consistent
   across surfaces. Each tile restores to full opacity on its own
   hover so the alumni stay readable when actually scanned. The
   section label itself stays at full opacity — only the tile
   grid dims, since the label is informational and the dim is a
   signal about the content. */
.detail-section-former .profile-tile,
.detail-section-former .profile-tile-block {
  opacity: 0.4;
  transition: opacity 100ms;
}
.detail-section-former .profile-tile:hover,
.detail-section-former .profile-tile-block:hover {
  opacity: 1;
}

.detail-section-label {
  font-family: var(--sans);
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 8px;
  display: flex;
  justify-content: space-between;
}
.detail-section-label .count {
  color: var(--ink-muted);
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
}

.detail-logline {
  font-family: var(--serif);
  font-size: 17px;
  font-style: italic;
  color: var(--ink);
  line-height: 1.4;
  border-left: 2px solid var(--accent);
  padding-left: 16px;
}

.detail-synopsis {
  font-family: var(--serif);
  font-size: 15px;
  color: var(--ink);
  line-height: 1.55;
}
.detail-synopsis p + p { margin-top: 0.6em; }

/* Memo-prose fields (Notes, Reader Notes) — Notes that become
   memo content. Visually persistent textarea: slate-soft border
   + subtle padding so the field always READS as editable, not
   as a display surface that requires a click to discover.
   Both view and edit states get the frame; the focus state
   tightens the border. The richText helper applies viewClass
   to both modes, so this rule covers both. */
.memo-prose-field {
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 12px 14px;
  min-height: 96px;
  background: var(--bg);
  transition: border-color 100ms;
}
.memo-prose-field:hover {
  border-color: var(--ink-faint);
}
.memo-prose-field:focus,
.memo-prose-field:focus-within {
  border-color: var(--ink-muted);
  outline: none;
}
.memo-prose-field.empty {
  /* The richText helper adds .empty when value is empty — gives
     a placeholder render. Don't restyle the placeholder text;
     just keep the frame consistent. */
}

/* Single-line prose fields (Pitch Tag, Logline) share the
   memo-prose-field's frame + auto-save behavior but typically
   carry only a sentence or phrase. Override the multi-paragraph
   sizing so the field collapses to its content height instead
   of forcing a 96px-tall block of empty space. */
.detail-pitch-tag.memo-prose-field,
.detail-logline.memo-prose-field {
  min-height: 0;
  padding: 8px 12px;
}

/* When .inline-edit-view / .inline-edit-input is also
   .memo-prose-field — currently only the Book Detail Pitch Tag,
   which became inlineText in the B2 fix — suppress the default
   inline-edit padding/margin/hover-background so .memo-prose-
   field's own frame (border, padding, focus state) takes over
   cleanly. The paired-class selectors outrank the late-defined
   `.inline-edit-input` border default, so edit-mode matches
   view-mode's frame. Mirrors the .identity-line-input pattern
   below. Without these rules the pitch-tag field would inherit
   .inline-edit-view's negative margin (offset 1px up + 4px left
   from siblings) and .inline-edit-input would draw its own
   accent-soft border that doesn't match the memo-prose-field
   frame in view mode. */
.inline-edit-view.memo-prose-field,
.inline-edit-input.memo-prose-field {
  margin: 0;
  background: transparent;
}
.inline-edit-view.memo-prose-field:hover {
  background: transparent;
}
.inline-edit-input.memo-prose-field {
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
}
.inline-edit-input.memo-prose-field:focus {
  border-color: var(--ink-muted);
  outline: none;
}

/* Reader Notes attribution line: "Writing as [Name]" sits above
   the field in muted small caps so the writer knows their voice
   is what gets attributed in the memo output. */
.memo-prose-attribution {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}

/* ============================================================
   IDENTITY-PROSE EDIT AFFORDANCE — Logline + Synopsis.
   Visual gradient: subtler than the .memo-prose-field treatment
   used by Notes / Reader Notes. Identity prose reads as
   "stable reference content" — the affordance signals
   editability without competing with the active writing
   surfaces below.

   Two modifiers:
     .identity-line-input  → single-line: bottom underline only
     .identity-block-input → multi-line:  full thin border
   Both apply to BOTH view and edit modes (via inlineText's
   viewClass + inputClass), so the field looks identical
   whether it's been clicked or not.
   ============================================================ */
/* The combined selectors below intentionally double up
   .inline-edit-view and .inline-edit-input — that bumps
   specificity above the bare .inline-edit-input rule defined
   later in this file (which sets a different border + padding
   default for inline-text inputs). */
.inline-edit-view.identity-line-input,
.inline-edit-input.identity-line-input {
  border: none;
  border-bottom: 1px solid var(--bg-rule);
  border-radius: 0;
  padding: 4px 0;
  margin: 0;
  background: transparent;
  transition: border-color 100ms;
  width: auto;
  display: block;
}
.inline-edit-view.identity-line-input:hover,
.inline-edit-input.identity-line-input:hover {
  border-bottom-color: var(--ink-faint);
}
.inline-edit-input.identity-line-input:focus {
  border-bottom-color: var(--ink-muted);
  outline: none;
}

.inline-edit-view.identity-block-input,
.inline-edit-input.identity-block-input {
  /* Subtle full-frame border for the multi-line synopsis. Same
     padding shape as .memo-prose-field but using --bg-rule (a
     hair lighter) so the visual hierarchy reads as "subtler
     than the active writing surfaces below". */
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 12px 14px;
  min-height: 96px;
  margin: 0;
  background: transparent;
  transition: border-color 100ms;
}
.inline-edit-view.identity-block-input:hover,
.inline-edit-input.identity-block-input:hover {
  border-color: var(--ink-faint);
}
.inline-edit-input.identity-block-input:focus {
  border-color: var(--ink-muted);
  outline: none;
}

.meta-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px 24px;
  padding: 16px 0;
  border-top: 1px solid var(--bg-rule);
  border-bottom: 1px solid var(--bg-rule);
}

.meta-item { display: flex; flex-direction: column; gap: 2px; }
.meta-label {
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
}
.meta-value { font-size: 13px; color: var(--ink); }
.meta-value.linked { color: var(--accent); cursor: pointer; }
.meta-value.linked:hover { text-decoration: underline; }

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.tag {
  display: inline-block;
  padding: 3px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 12px;
  font-size: 11px;
  color: var(--ink-soft);
  font-family: var(--serif);
  font-style: italic;
}
.tag.warn {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-bg);
  font-style: normal;
}

/* Rights, galleys, pipeline rows */
.rights-list, .galley-list { display: flex; flex-direction: column; gap: 6px; }

.rights-row, .galley-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 13px;
}

.rights-territory {
  font-weight: 600;
  color: var(--ink);
  width: 110px;
  flex-shrink: 0;
  font-size: 12px;
}
.rights-status { flex: 1; color: var(--ink-soft); }
.rights-status .dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--status-active);
  margin-right: 6px;
  vertical-align: middle;
}
.rights-status .dot.available { background: var(--ink-faint); }
.rights-status .dot.offer     { background: var(--stage-offer); }
.rights-status .dot.sold      { background: var(--stage-acquired); }
.rights-status .dot.optioned  { background: var(--stage-optioned); }
.rights-date { color: var(--ink-muted); font-size: 11px; }

.galley-icon { color: var(--ink-muted); font-size: 12px; }
.galley-label { flex: 1; color: var(--ink); }
.galley-action {
  font-size: 11px;
  color: var(--accent);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
}

.pipeline-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  margin-bottom: 6px;
  cursor: pointer;
  transition: border-color 100ms;
}
.pipeline-row:hover { border-color: var(--ink-muted); }
.pipeline-client { flex: 1; font-size: 13px; color: var(--ink); }
.pipeline-date { font-size: 11px; color: var(--ink-muted); flex-shrink: 0; }

.pipeline-stage {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 12px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: var(--bg-card);
}
.pipeline-stage.pitched   { background: var(--stage-pitched); }
.pipeline-stage.reading   { background: var(--stage-reading); }
.pipeline-stage.offer     { background: var(--stage-offer); color: var(--ink); }
.pipeline-stage.acquired  { background: var(--stage-acquired); }
.pipeline-stage.optioned  { background: var(--stage-optioned); }
.pipeline-stage.passed    { background: var(--stage-passed); }

.comp-titles {
  font-family: var(--serif);
  font-size: 15px;
  color: var(--ink);
  line-height: 1.5;
  font-style: italic;
}
.comp-titles .linked-book {
  color: var(--accent);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  transition: border-color 120ms;
  font-style: italic;
}
.comp-titles .linked-book:hover { border-bottom-color: var(--accent); }

/* ============================================================
   NOTES THREAD
   ============================================================ */

.notes-thread { display: flex; flex-direction: column; gap: 12px; }

.note {
  padding: 12px 14px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  position: relative;
}
.note-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
  font-size: 11px;
}
.note-author { font-weight: 600; color: var(--ink); }
.note-time {
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.note-body {
  /* Sans-serif (chat-thread register), distinct from the serif
     prose fields above (Notes / Reader Notes / Reader Report).
     14px is compact for an internal-team thread vs the 15px
     serif of memo-prose fields. */
  font-family: var(--sans);
  font-size: 14px;
  color: var(--ink);
  line-height: 1.5;
  white-space: pre-wrap;
}

/* Hover-revealed Edit / Delete row in the note header. Keeps the
   resting state clean — actions only appear when the user mouses
   over the note. */
.note-actions {
  margin-left: auto;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 100ms;
}
.note:hover .note-actions { opacity: 1; }
.note-actions:focus-within { opacity: 1; }
.note-action {
  appearance: none;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  padding: 2px 8px;
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  cursor: pointer;
  transition: all 100ms;
}
.note-action:hover {
  border-color: var(--ink-soft);
  color: var(--ink);
  background: var(--bg-hover);
}
.note-action.danger:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-bg);
}

/* In-place edit textarea + button bar. */
.note-edit-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--ink-faint);
  border-radius: 3px;
  font-family: var(--serif);
  font-size: 14px;
  color: var(--ink);
  line-height: 1.5;
  background: var(--bg);
  outline: none;
  resize: vertical;
  min-height: 60px;
}
.note-edit-input:focus { border-color: var(--accent); }
.note-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 6px;
}
.btn-small {
  /* Small variant of `.btn` — see the BUTTON SYSTEM doc block
     above. Used for inline +/Add buttons and per-row actions
     that need to fit in tighter chrome than the toolbar register. */
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  border-radius: 3px;
}
.note-body .mention {
  color: var(--accent);
  cursor: pointer;
}
.note-body .mention:hover { text-decoration: underline; }

.note-add {
  margin-top: 12px;
  padding: 10px 14px;
  border: 1px dashed var(--bg-rule);
  border-radius: 4px;
  font-family: var(--serif);
  font-size: 14px;
  font-style: italic;
  color: var(--ink-faint);
  cursor: text;
}

/* ============================================================
   LANDING VIEW
   ============================================================ */

.landing {
  flex: 1;
  overflow-y: auto;
  padding: 48px 56px 80px;
  max-width: 920px;
  margin: 0 auto;
  width: 100%;
}

.landing-greeting {
  font-family: var(--serif);
  font-size: 38px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.02em;
  margin-bottom: 4px;
  line-height: 1.1;
}
.landing-greeting em { font-style: italic; color: var(--accent); }

.landing-date {
  font-size: 11px;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin-bottom: 40px;
  font-weight: 500;
}

.landing-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 28px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 8px;
  margin-bottom: 44px;
  cursor: pointer;
  transition: border-color 120ms, box-shadow 120ms;
  position: relative;
  overflow: hidden;
}
.landing-cta::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--accent);
}
.landing-cta:hover {
  border-color: var(--accent);
  box-shadow: 0 4px 12px rgba(0,0,0,0.04);
}
.landing-cta-text {
  font-family: var(--serif);
  font-size: 20px;
  color: var(--ink);
  line-height: 1.4;
}
.landing-cta-text em {
  font-style: italic;
  color: var(--accent);
  font-weight: 600;
  margin-right: 4px;
}
.landing-cta-arrow { color: var(--accent); font-size: 22px; }

.landing-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 40px;
}

.landing-section-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--bg-rule);
}
.landing-section-title .see-all {
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: none;
  color: var(--ink-muted);
  cursor: pointer;
}
.landing-section-title .see-all:hover { color: var(--accent); }

/* (Removed: `.landing-empty`. Home empty states now use
   `.empty-state empty-state--section`.) */

/* (Removed: `.recent-memo*` block. Home's "Recent memos" rows
   switched to the shared `.rollup-row` markup in v1.1.0 via
   `buildMemoHistoryRow`; the old class was orphaned.) */

/* Activity feed rows. */
.activity-item {
  display: flex;
  gap: 12px;
  padding: 8px 0;
  font-size: 13px;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--bg-rule);
  cursor: pointer;
  transition: color 120ms;
}
.activity-item:last-child { border-bottom: none; }
.activity-item:hover .activity-text .ent { color: var(--accent-hover); }

.activity-time {
  color: var(--ink-faint);
  font-size: 11px;
  flex-shrink: 0;
  width: 80px;
  padding-top: 2px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.activity-text strong {
  font-weight: 600;
  color: var(--ink);
}
.activity-text .ent {
  color: var(--accent);
  font-style: italic;
  font-family: var(--serif);
  font-size: 14px;
  transition: color 120ms;
}

/* ============================================================
   INLINE EDIT — Notion-style click-to-edit
   The .inline-edit wrapper holds either a view node (.inline-edit-view)
   or an input/textarea (.inline-edit-input). Surrounding structures
   (meta-grid, detail-section, detail-title, etc.) keep their look
   because the view node carries those classes itself.
   ============================================================ */

.inline-edit {
  position: relative;
  width: 100%;
}

.inline-edit-view {
  cursor: text;
  border-radius: 3px;
  padding: 1px 4px;
  margin: -1px -4px;
  transition: background 100ms;
  white-space: pre-wrap;
  min-height: 1em;
}
.inline-edit-view:hover { background: var(--bg-hover); }
.inline-edit-view.empty {
  color: var(--ink-faint);
  font-style: italic;
}

/* Edit pencil — appears (only) when the rendered value is a
   link, since link clicks navigate rather than entering edit
   mode. Sits inline to the right of the link with a small gap;
   reserves its 18px footprint at all times so the layout
   doesn't jump when hovering, then fades in on view hover or
   keyboard focus-within. The reserved space lives outside the
   link itself so the pencil and the link never overlap. */
.inline-edit-pencil {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 8px;
  vertical-align: middle;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 3px;
  color: var(--ink-faint);
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, color 100ms, background 100ms;
}
.inline-edit-view:hover .inline-edit-pencil,
.inline-edit-view:focus-within .inline-edit-pencil,
.inline-edit-pencil:focus {
  opacity: 1;
}
.inline-edit-pencil:hover {
  color: var(--ink);
  background: var(--bg-hover);
}

/* B7b — picker-row rename pencil. Sits on the SELECTED (✓) row
   of a buildPickerSection list when the caller opts in via
   onRowRename. Hover-revealed on the row itself (the row is a
   button; hovering or keyboard-highlighting it surfaces the
   pencil). The picker-row context is its own opacity-reveal
   trigger because .classify-popover-item isn't an
   .inline-edit-view (different chrome / cursor / hover bg). */
.classify-popover-item:hover .inline-edit-pencil,
.classify-popover-item.highlighted .inline-edit-pencil,
.classify-popover-item:focus-within .inline-edit-pencil {
  opacity: 1;
}
.picker-row-pencil {
  /* Default override of the .inline-edit-pencil 8px gap —
     picker rows are denser and the pencil typically sits next
     to the trailing ✓. The right-edge alignment is set below
     via auto-margin selectors. */
  margin-right: 2px;
}

/* B7b — selected-row check glyph. Replaces the pill-style
   .tag '✓' previously used in classify-popover rows so the
   visual vocabulary matches the rename pencil — both are 12px
   currentColor line art at the same stroke weight. */
.selected-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 12px;
  height: 12px;
  color: var(--ink-soft);
  flex-shrink: 0;
}

/* Right-pin the rename pencil + selected check inside picker
   rows. Whichever right-edge element comes FIRST claims
   margin-left: auto (which consumes the row's free space and
   pushes everything that follows to the right edge); subsequent
   right-edge elements get a small fixed gap so the group reads
   as one cluster. The .classify-popover-aux-tags wrapper
   (Recent / affiliation) already has margin-left: auto of its
   own, so when present it's the anchor and the pencil + check
   follow with fixed gaps. */
.classify-popover-item > .picker-row-pencil,
.classify-popover-item > .selected-check {
  margin-left: auto;
}
.classify-popover-item > .picker-row-pencil + .selected-check,
.classify-popover-item > .classify-popover-aux-tags + .picker-row-pencil,
.classify-popover-item > .classify-popover-aux-tags + .selected-check {
  margin-left: 4px;
}

/* B7b — byline rename pencil container. Used by
   buildBookAuthorLine + buildByline to wrap each contributor
   span with a personId. The wrapper is the opacity-reveal hook
   (hovering the contributor name surfaces the pencil) but adds
   NO other visual chrome — no padding, no background, no
   cursor change — so the byline reads exactly as it does
   without the pencil. */
.person-rename-wrap {
  display: inline-flex;
  align-items: baseline;
}
.person-rename-wrap:hover .inline-edit-pencil,
.person-rename-wrap:focus-within .inline-edit-pencil {
  opacity: 1;
}
.byline-rename-pencil {
  /* Tighter spacing than the default 8px — book-profile bylines
     are dense and the pencil should sit close to the name it
     edits without crowding the trailing comma / & separator. */
  margin-left: 4px;
}

/* M-series — non-primary contributor chips on the work-profile
   byline. Wraps a name span + a × delete button. The wrapper is
   inline-flex so the × sits on the baseline alongside the name;
   no padding / background — the byline reads as a plain "by A,
   B & C" run, the × only appears on hover/focus to keep the
   default state quiet. */
.detail-byline-coauthor-wrap {
  display: inline-flex;
  align-items: baseline;
  position: relative;
}
.detail-byline-coauthor-remove {
  background: none;
  border: 0;
  padding: 0 0 0 4px;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  opacity: 0;
  transition: opacity 100ms, color 100ms;
}
.detail-byline-coauthor-wrap:hover .detail-byline-coauthor-remove,
.detail-byline-coauthor-wrap:focus-within .detail-byline-coauthor-remove {
  opacity: 1;
}
.detail-byline-coauthor-remove:hover {
  color: var(--accent);
}

/* M-series — "+ Add co-author ▾" affordance row beneath the
   byline. Reuses .lib-filter-add for the dashed slate-soft
   chrome (same look as the empty-state "+ Add author ▾"
   affordance), wrapped in a flex row so multiple role buttons
   wrap gracefully when the work-type allowlist surfaces several
   (co-author, illustrator, translator, editor for books). */
.detail-byline-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.detail-byline-add-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.detail-byline-add-affordance {
  /* Slightly tighter font + padding than the empty-state
     affordance — these sit beneath an already-populated byline,
     so they should read as secondary actions, not a primary
     CTA. */
  font-size: 11px;
  padding: 2px 8px;
}

/* B7b — inline rename input (the <input> that replaces the
   name span during edit). Reuses .classify-popover-input
   styling (border, radius, focus tint) but constrains the
   width to its content via field-sizing so the input doesn't
   stretch to fill the parent row. The .person-rename-input
   class is added by enterPersonRenameMode. */
.person-rename-input {
  field-sizing: content;
  min-width: 8ch;
  max-width: 100%;
}

/* Base inline-edit input — quiet-frame default. The previous
   default (1px var(--accent-soft) border + var(--bg-card) fill +
   width: 100%) made every click-into-edit "jump": text rendered
   inside a bordered, filled, full-width box that the view-mode
   element didn't occupy. The Memo Builder neutralized that under
   three composer anchors; this base flip generalizes the
   neutralization so every inlineText surface — Work/Person/Company
   profile titles, vital-stats cells — gets the same seamless
   continuity.

   The flip:
   • `width: auto` so the input sizes to content rather than
     stretching to its container.
   • `field-sizing: content` so the rendered glyph width drives
     the input width (eliminates the leading-character clip
     under text-transform: uppercase + bold + underline +
     letter-spacing — the issue the Memo Builder's previous
     three-anchor override addressed).
   • `min-width: 4ch` so an empty input still has a clickable
     target. JS's `sizeSlack` (inline-edit.js) remains the
     fallback for browsers without field-sizing.
   • `border: 0` + `background: transparent` so the input box
     matches the view-mode element's chrome (none).
   • Outline-on-focus is the sole edit signal (rule below) —
     keeps NO reflow on focus.

   Multiline inputs (.multiline below) explicitly keep the
   bordered writing-area frame: prose-editing surfaces (Synopsis,
   Reader Notes, person/company About) want the visible canvas
   the quiet frame would erase. Single-line inputs go quiet.

   Three explicit-frame opt-in classes — .memo-prose-field,
   .identity-line-input, .identity-block-input — remain at higher
   specificity (rules at styles.css ~2476/~2525/~2546) and keep
   working unchanged. */
.inline-edit-input {
  width: auto;
  field-sizing: content;
  min-width: 4ch;
  padding: 1px 4px;
  margin: -1px -4px;
  border: 0;
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  /* No `font: inherit` here on purpose. The shorthand `font`
     expands to all font longhand properties (family/size/weight/
     style/variant/line-height) — at specificity 0,1,0 it WINS
     against the call-site typography class (`.detail-title`,
     `.detail-subtitle-inline`, etc., also 0,1,0) because this
     rule appears LATER in source order. The result was that
     clicking into edit on a profile title swapped the bold
     36px serif for the inherited body sans — the typography
     "jump" the user reported. Dropping `font: inherit` lets
     each call site's typography class own the input's font
     directly, so view-mode and edit-mode look identical.

     Memo Builder typography (`.composer-title`, `.composer-date`,
     etc.) uses paired selectors at specificity 0,2,0 and is
     unaffected. The multiline textarea rule below DOES re-add
     `font: inherit` because multiline call sites don't pass
     an inputClass and need to pick up the inherited page font
     instead of the browser's UA system control font. */
  outline: none;
  resize: vertical;
}
.inline-edit-input:focus {
  /* Quiet outline rather than a bordered/filled box — no
     reflow, matches the Memo Builder's prior outline-on-focus
     treatment that this flip generalizes app-wide. */
  outline: 1px solid var(--ink-faint);
  outline-offset: 2px;
  border-radius: 2px;
}
.inline-edit-input.multiline {
  /* Multiline = prose writing area (Synopsis, Reader Notes,
     person/company About). KEEP a visible canvas-style frame —
     the writing surface is itself the editing affordance, and
     the quiet base would erase the canvas. Border + fill +
     full width are explicitly restored here.

     `font: inherit` here (removed from base — see comment
     above): multiline call sites don't pass inputClass, so
     without an explicit `font: inherit` the textarea would
     fall back to the browser's UA system control font. */
  font: inherit;
  width: 100%;
  border: 1px solid var(--bg-rule);
  background: var(--bg-card);
  border-radius: 3px;
  /* Drop the fixed min-height — JS auto-sizes to scrollHeight on
     mount and on every keystroke so the textarea always shows the
     full body. We keep a small floor for empty inputs so a brand-new
     synopsis still has a visible click target. */
  min-height: 36px;
  line-height: 1.5;
  padding: 4px 6px;
  /* Hide overflow inside the textarea since JS keeps height === scrollHeight. */
  overflow-y: hidden;
  /* field-sizing: content doesn't apply usefully to multiline
     prose; reset to default so the JS autosize path drives
     height. */
  field-sizing: auto;
}

.inline-edit-saving {
  position: absolute;
  right: 4px;
  top: 4px;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  pointer-events: none;
}
.inline-edit-error {
  position: absolute;
  right: 4px;
  top: 4px;
  font-size: 11px;
  color: var(--accent);
  pointer-events: none;
}

/* Book detail header — cover thumbnail + text block side by side. */
.detail-header-row {
  display: flex;
  gap: 18px;
  align-items: flex-start;
  margin-bottom: 16px;
}

.detail-header-text {
  flex: 1;
  min-width: 0;
}

.detail-cover {
  /* 96 × 144 — width matches the 96px avatar so the avatar and
     book cover read as the same "primary visual" slot, while
     the 2:3 aspect-ratio gives the cover its natural book
     proportion. Works gallery cards below use the same 96px
     width so the page reads as one coherent system. */
  width: 96px;
  height: 144px;
  flex-shrink: 0;
  border-radius: 4px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}

.detail-cover.empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 26px;
  color: var(--ink-faint);
  background: var(--bg);
}

/* Modal back-button — counterpart to .modal-close that lives on
   the LEFT side of the header. Used by inline view-swap modals
   (New Work + Add Galley) that fold the Drive browser into the
   parent modal as a navigable sub-view. */
.modal-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-right: 8px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 50%;
  color: var(--ink-soft);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background 100ms, color 100ms;
}
.modal-back:hover {
  background: var(--bg-hover);
  color: var(--ink);
}
.modal-back[hidden] { display: none; }

/* Drive search/browse view — rendered inline as a modal-body
   variant when the parent modal swaps to its drive sub-view.
   The `[hidden]` rules below override the `display: flex` so the
   HTML hidden attribute actually hides the element (UA style
   loses to author CSS without an explicit override). */
.modal-body-drive {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  padding: 16px 20px 20px;
}
.modal-body-drive[hidden] { display: none; }
.drive-search-body {
  display: flex;
  flex-direction: column;
  /* 14px matches `.attach-pane`'s gap between top-level children
     across every other tab in New Work (Manual's Title-field →
     Author-field gap, Search's input → results gap, etc.).
     Previously 10px, which made the Drive tab's section spacing
     (input → "2 FOLDERS" label → list) feel tighter than the
     equivalent section spacing on Manual (input → next label). */
  gap: 14px;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
.drive-search-body[hidden] { display: none; }
/* Folder breadcrumb above the search input. Styled to match
   `.field-label` (10px uppercase, 0.12em tracking, 600 weight,
   --ink-faint) — same visual register as "SEARCH SOURCES" on
   the Search tab and "SOURCE URL" on the Article tab. The
   crumb segments inside inherit these properties and add the
   interactive hover affordance.

   No `min-height` or `align-items: center` — those used to
   reserve an 18px tall row to prevent layout jumps during the
   first load, but they also made the breadcrumb sit 6px taller
   than a plain .field-label, breaking the inline alignment
   with SOURCE URL. Now the breadcrumb settles at its content's
   natural text height, exactly like a label. */
.drive-search-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.4;
}
.drive-search-breadcrumb.is-search {
  font-style: italic;
  text-transform: none;
  letter-spacing: 0;
}
.drive-search-crumb {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  /* Inherit typography AND color from the .drive-search-breadcrumb
     parent so every crumb segment + separator renders in the same
     --ink-faint tone as the SOURCE URL / SEARCH SOURCES labels on
     other tabs. The previous two-tone treatment (ink-muted for
     parent crumbs, ink-soft for the current crumb, ink-faint for
     the separator) made the breadcrumb read as navigation chrome
     instead of a section title. Hover provides the only color
     differentiation, marking the click affordance. */
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  line-height: inherit;
  cursor: pointer;
  transition: color 100ms;
}
.drive-search-crumb:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.drive-search-crumb.current {
  cursor: default;
}
.drive-search-crumb.current:hover {
  /* The current crumb is not clickable; suppress the hover
     affordance so it doesn't suggest interactivity. */
  color: inherit;
  text-decoration: none;
}
.drive-search-breadcrumb .crumb-sep { color: inherit; }

/* Spacing between the breadcrumb and the search input is now
   provided by the .field wrapper around them (gap: 4px),
   exactly like every other label/input pair in the modal —
   no sibling-margin hack needed anymore. */

/* Folder row variant — the icon is a filled folder silhouette
   (sepia outline + lighter sepia fill) sitting directly on the
   row background, no tile chrome. On hover the fill + stroke
   both deepen so the navigable affordance reads clearly. File
   rows keep the tile chrome since they hold a text type-badge
   (PDF / DOCX / EPUB) that needs the framed container. */
.drive-search-row.is-folder .drive-search-row-icon {
  background: transparent;
  border: none;
}
.drive-search-row.is-folder .drive-search-row-icon svg {
  width: 34px;
  height: 34px;
  fill: var(--bg-rule);
  stroke: var(--ink-faint);
  stroke-width: 1.5;
  transition: fill 120ms, stroke 120ms;
}
.drive-search-row.is-folder:hover .drive-search-row-icon svg,
.drive-search-row.is-folder.selected .drive-search-row-icon svg {
  fill: var(--bg-selected);
  stroke: var(--ink-muted);
}
/* Matches .field-input exactly — same padding, background, font,
   border, radius — so the Drive tab's search input is visually
   indistinguishable from the search inputs on every other tab
   (Book, Graphic Novel, Article URL, Manual Title/Author). */
.drive-search-input {
  width: 100%;
  padding: 8px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 13px;
  color: var(--ink);
  outline: none;
  font-family: var(--sans);
  transition: border-color 100ms;
}
.drive-search-input:focus { border-color: var(--accent); }
.drive-search-status {
  /* Matches .field-label typography exactly (10px uppercase,
     0.12em tracking, 600 weight, --ink-faint) so the "2 FOLDERS"
     status reads as a peer label to "ROC LITERARY" above it on
     the Drive tab — same visual register, just different content. */
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.drive-search-list {
  /* Fixed height = exactly two folder rows + outer borders.
     Each row: 36px icon + 10px top + 10px bottom padding = 56px.
     Two rows = 112px. Plus 1px top + 1px bottom border = 114px.
     115px gives one pixel of breathing room without dropping
     into "extra empty space at the bottom" territory.

     The list is ALWAYS this height — clicking into a subfolder
     doesn't resize the rectangle, only its scroll content.
     `flex: 0 0 auto` overrides any inherited flex behavior
     from the parent so the explicit `height` is the source of
     truth. */
  flex: 0 0 auto;
  height: 115px;
  overflow-y: auto;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  background: var(--bg);
}
.drive-search-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--bg-rule);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 80ms;
}
.drive-search-row:last-child { border-bottom: none; }
.drive-search-row:hover,
.drive-search-row.selected {
  background: var(--bg-hover);
}
.drive-search-row-icon {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-family: var(--sans);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
.drive-search-row-text {
  flex: 1;
  min-width: 0;
}
.drive-search-row-name {
  font-family: var(--serif);
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.drive-search-row-meta {
  font-family: var(--sans);
  font-size: 11px;
  color: var(--ink-faint);
  margin-top: 2px;
}

/* Wrapper that lets the Read pill sit on top of the cover
   without intercepting cover clicks (the button is a sibling
   of .detail-cover, not a child). Inline-block so it doesn't
   stretch to the column width — the cover itself sets the size.
   The pill's own styling is shared with .work-card-galley-pill
   via a comma-selector elsewhere in this file, so one rule
   drives the look on every cover in the app, big or small. */
.detail-cover-wrap {
  position: relative;
  display: inline-block;
  flex-shrink: 0;
}

/* Title / subtitle / series block in book detail header */
.detail-subtitle {
  font-family: var(--serif);
  font-size: 18px;
  color: var(--ink-soft);
  margin-top: -2px;
  margin-bottom: 8px;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

/* Book detail header — title + subtitle on the same line, no
   separator. Block container so `text-wrap: balance` can spread
   the wrap evenly across both runs (otherwise a long subtitle
   tends to wrap as a whole and leave the title alone on the
   first line). The inline-edit children below are coerced to
   inline so the balance algorithm sees one text flow. */
.book-title-line {
  text-wrap: balance;
  line-height: 1.15;
  margin-bottom: 4px;
}
.book-title-line > .inline-edit,
.book-title-line > .inline-edit > .inline-edit-view {
  display: inline;
  width: auto;
}
.book-title-line > .inline-edit > .inline-edit-input {
  display: inline-block;
  width: auto;
}

/* Subtitle inline next to the title. Lighter weight, smaller,
   italic, slate-soft — typography alone signals the title /
   subtitle relationship without a colon or em-dash. The 0.4em
   left margin gives the eye a slightly larger gap than a regular
   word space so the boundary reads cleanly. */
.detail-subtitle-inline {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 400;
  font-style: italic;
  color: var(--accent-soft);
  letter-spacing: -0.005em;
  margin-left: 0.4em;
}
/* Empty state: shows the word "Subtitle" in the SAME typography
   ramp as the real subtitle and the input's placeholder (22px
   italic serif). Only the color differs — `var(--ink-faint)`
   reads as placeholder-fade rather than as a real subtitle in
   `var(--accent-soft)`. Same vertical baseline as the populated
   subtitle so the title line doesn't shift when a subtitle is
   added or cleared. */
.book-title-line .inline-edit > .detail-subtitle-inline.empty {
  color: var(--ink-faint);
}

.detail-series {
  display: inline-block;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  border: 1px solid var(--bg-rule);
  padding: 2px 8px;
  border-radius: 3px;
  margin-bottom: 8px;
}

.detail-pitch-tag {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  margin-bottom: 6px;
}

.detail-material-rr {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  font-size: 13px;
  color: var(--ink);
}
.detail-material-rr .rr {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
}
.detail-material-rr .rr.positive       { color: var(--status-sold); border-color: var(--status-sold); }
.detail-material-rr .rr.mixedPositive  { color: var(--status-submitted); border-color: var(--status-submitted); }
.detail-material-rr .rr.mixed          { color: var(--ink-muted); }
.detail-material-rr .rr.pass           { color: var(--ink-faint); }
.detail-material-rr .rr.negative       { color: var(--status-hot); border-color: var(--status-hot); }

/* Status pill wrapped in inline-edit needs to size to content,
   not full row width. */
.detail-status-row .inline-edit { width: auto; flex-shrink: 0; }
.detail-status-row .inline-edit-view { display: inline-block; }

/* Star toggles container — sits inline with the status pill (left-
   aligned alongside it, not pushed to the right). */
.detail-star-toggles {
  display: flex;
  gap: 6px;
}
.detail-star-toggle.active.smaller,
.detail-star-toggles .detail-star-toggle {
  font-size: 10px;
  padding: 4px 8px;
}

/* Refetch-from-API button — same height/shape as the star toggles
   so it groups visually with the other per-work controls. Dashed
   border marks it as a recovery affordance, not a primary action. */
/* ↻ Fetch from API — ink-faint dashed-outline secondary action
   that sits next to the genre row when the user has not yet
   pulled API categories. Inherits canonical small-variant
   dimensions; distinct uppercase 0.08em letter-spacing + ink-
   faint dashed border marks it as a "recovery" affordance, not
   a primary or accent action. */
.detail-refetch-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 4px 10px;
  background: transparent;
  border: 1px dashed var(--ink-faint);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  cursor: pointer;
  transition: background 100ms, border-color 100ms, color 100ms;
}
.detail-refetch-btn:hover {
  border-color: var(--ink-soft);
  color: var(--ink);
  background: var(--bg-hover);
}
.detail-refetch-btn:disabled {
  opacity: 0.55;
  cursor: default;
}

/* ============================================================
   CLIENT MARKER — universal blue ★ next to names of clients,
   plus the toggle button used on Person + Company detail headers.
   ============================================================ */

.client-star {
  display: inline-block;
  color: var(--client);
  font-size: 0.85em;
  margin-left: 0.35em;
  vertical-align: baseline;
  line-height: 1;
}
.client-star.lg { font-size: 0.7em; margin-left: 0.5em; vertical-align: 0.15em; }

/* Chips dressed in client blue. Used by every "starred chip" in
   the app — the ★ Clients filter (People + Companies), the
   ★ Client toggle on detail pages, and the ★ Queued filter in
   Library. They all share the same visual contract: faint-gray
   star off, solid-blue chip with white star on. */
.filter-chip-clients .star,
.filter-chip-queued  .star,
.filter-chip-galleys .galley-icon {
  color: var(--ink-faint);
  margin-right: 2px;
  transition: color 100ms;
}
.filter-chip-galleys .galley-icon {
  display: inline-flex;
  align-items: center;
  vertical-align: -1px;
}
.filter-chip-clients:hover,
.filter-chip-queued:hover,
.filter-chip-galleys:hover { border-color: var(--client); }
.filter-chip-clients:hover .star,
.filter-chip-queued:hover  .star,
.filter-chip-galleys:hover .galley-icon { color: var(--client); }
.filter-chip-clients.active,
.filter-chip-queued.active,
.filter-chip-galleys.active {
  background: var(--client);
  border-color: var(--client);
  color: var(--bg);
}
.filter-chip-clients.active .star,
.filter-chip-queued.active  .star,
.filter-chip-galleys.active .galley-icon { color: var(--bg); }
.filter-chip-clients.active:hover,
.filter-chip-queued.active:hover,
.filter-chip-galleys.active:hover { background: var(--client); }

/* ============================================================
   LEGACY CLIENT-STATUS PILL — kept for back-compat with rows
   that may still surface clientStatus before migration runs.
   ============================================================ */

.client-status-pill {
  display: inline-block;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 3px;
  border: 1px solid;
}
.client-status-pill.active      { color: var(--status-sold);  border-color: var(--status-sold);  background: var(--bg-card); }
.client-status-pill.dormant     { color: var(--ink-faint);    border-color: var(--bg-rule);      background: var(--bg-card); }
.client-status-pill.prospective { color: var(--status-submitted); border-color: var(--status-submitted); background: var(--bg-card); }
.client-status-pill.former      { color: var(--ink-muted);    border-color: var(--bg-rule);      background: var(--bg); opacity: 0.7; }

/* Tag list rendering for genres / territories / formats */
.client-tag-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px 0;
}
.client-tag {
  font-size: 11px;
  padding: 3px 9px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 12px;
  color: var(--ink-soft);
  font-family: var(--serif);
  font-style: italic;
}

/* ============================================================
   PIPELINE KANBAN BOARD
   ============================================================ */

.pipeline-board-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 16px 20px;
}

.pipeline-board-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 14px;
}
.pipeline-board-title {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.pipeline-board-subtitle {
  font-size: 11px;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 500;
}

.pipeline-board {
  flex: 1;
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 12px;
}

.pipeline-stage-col {
  width: 240px;
  min-width: 240px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  overflow: hidden;
  height: 100%;
}

.pipeline-stage-header {
  padding: 10px 12px;
  background: var(--bg);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 600;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  gap: 8px;
  border-bottom: 2px solid transparent;
}
.pipeline-stage-header .dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
}
.pipeline-stage-header .label { flex: 1; }
.pipeline-stage-header .count { font-size: 10px; color: var(--ink-muted); }

.pipeline-stage-col[data-stage="pitched"]  .pipeline-stage-header { border-bottom-color: var(--stage-pitched); }
.pipeline-stage-col[data-stage="pitched"]  .pipeline-stage-header .dot { background: var(--stage-pitched); }
.pipeline-stage-col[data-stage="reading"]  .pipeline-stage-header { border-bottom-color: var(--stage-reading); }
.pipeline-stage-col[data-stage="reading"]  .pipeline-stage-header .dot { background: var(--stage-reading); }
.pipeline-stage-col[data-stage="offer"]    .pipeline-stage-header { border-bottom-color: var(--stage-offer); }
.pipeline-stage-col[data-stage="offer"]    .pipeline-stage-header .dot { background: var(--stage-offer); }
.pipeline-stage-col[data-stage="acquired"] .pipeline-stage-header { border-bottom-color: var(--stage-acquired); }
.pipeline-stage-col[data-stage="acquired"] .pipeline-stage-header .dot { background: var(--stage-acquired); }
.pipeline-stage-col[data-stage="optioned"] .pipeline-stage-header { border-bottom-color: var(--stage-optioned); }
.pipeline-stage-col[data-stage="optioned"] .pipeline-stage-header .dot { background: var(--stage-optioned); }
.pipeline-stage-col[data-stage="passed"]   .pipeline-stage-header { border-bottom-color: var(--stage-passed); }
.pipeline-stage-col[data-stage="passed"]   .pipeline-stage-header .dot { background: var(--stage-passed); }

.pipeline-stage-body {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pipeline-stage-body .empty {
  font-family: var(--serif);
  font-style: italic;
  font-size: 12px;
  color: var(--ink-faint);
  padding: 16px 8px;
  text-align: center;
}

.pipeline-card {
  padding: 10px;
  background: var(--bg);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  cursor: pointer;
  transition: border-color 100ms, box-shadow 100ms;
}
.pipeline-card:hover {
  border-color: var(--accent);
  box-shadow: 0 2px 6px rgba(0,0,0,0.04);
}

.pipeline-card-title {
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
}
.pipeline-card-author {
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  margin-top: 2px;
}
.pipeline-card-client {
  font-size: 11px;
  color: var(--accent);
  margin-top: 4px;
}
.pipeline-card-date {
  font-size: 10px;
  color: var(--ink-faint);
  margin-top: 4px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ============================================================
   COMPANY HIERARCHY + TYPE BADGE
   ============================================================ */

.detail-hierarchy {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 6px;
  font-size: 11px;
  color: var(--ink-muted);
}

.detail-hierarchy-link {
  color: var(--accent);
  cursor: pointer;
  text-decoration-color: transparent;
}
.detail-hierarchy-link:hover { text-decoration: underline; }

.detail-hierarchy-sep {
  color: var(--ink-faint);
  padding: 0 2px;
}

.detail-type-row {
  margin: -4px 0 14px;
}

.detail-type-pill {
  display: inline-block;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-bg);
  border: 1px solid var(--accent-soft);
  padding: 3px 9px;
  border-radius: 3px;
}

/* ============================================================
   VITAL STATS BLOCK — compact 2-column key/value grid placed at
   the top of the work-detail (just below the classification +
   star rows). Surfaces the four facts Robby tracks most often:
   Lit Agent, Film/TV Agent, Publisher, Pages, Year. Less-used
   metadata (series, word count, pub date, language) stays in
   the Particulars block lower on the page.

   Layout: fixed-width label column on the left (~110px), value
   column flexes. Each row is one line; values can be chips,
   inline editors, or plain text — the row min-height matches a
   24px chip so chip rows and text rows align cleanly.
   ============================================================ */
.work-stats {
  display: grid;
  grid-template-columns: 110px 1fr;
  row-gap: 6px;
  column-gap: 16px;
  padding: 12px 0;
  margin: 0 0 22px;
  border-top: 1px solid var(--bg-rule);
  border-bottom: 1px solid var(--bg-rule);
}
.work-stats-row {
  display: contents; /* let the two (or four) children participate in the grid */
}
/* In-stats divider — 1px slate rule that spans every grid
   column (works for both the 2-track default and the 4-track
   .work-stats-quad). Used to break the agents block from the
   publisher / particulars block inside the Vital Stats list.
   Tighter vertical padding than the page-level .detail-divider
   since it lives inside a denser context. */
.work-stats-divider {
  grid-column: 1 / -1;
  height: 1px;
  background: var(--bg-rule);
  margin: 6px 0;
}
/* Books-only 4-track variant: two side-by-side label/value
   pairs (deal info on the left, structural particulars on the
   right). Agents above the divider span all 4 cols via the
   .work-stats-value-full modifier on the value cell. */
.work-stats-quad {
  grid-template-columns: 110px 1fr 110px 1fr;
}

/* Detail-page variant: drops the top border so About flows
   directly into the stats block's first row without a divider
   between them. Keeps the bottom border (which separates the
   stats block from whatever section follows) and any soft inner
   work-stats-divider rows. Used on Person and Company detail
   pages where Contact + Particulars are merged into a single
   block under the About prose; book-detail's vital stats block
   keeps both top + bottom borders. */
.work-stats.detail-stats-block {
  border-top: none;
  /* Compensate for the missing top border + 12px top padding so
     the contact rows don't sit right against the About prose;
     32px matches the .detail-divider section-break rhythm. */
  padding-top: 0;
  margin-top: 32px;
}
.work-stats-value-full {
  grid-column: 2 / -1;
}
.work-stats-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
  align-self: center;
  padding: 2px 0;
}
.work-stats-value {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-height: 24px;
  font-size: 12px;
  color: var(--ink);
}
/* Inline link in the stats grid (e.g. Apple Podcasts URL, RSS Feed,
   article URL) — same height/baseline as inline-text rows. */
.work-stats-link {
  font-size: 12px;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 100ms;
}
.work-stats-link:hover { border-bottom-color: var(--accent); }

/* Recent episodes — compact list rendered below the stats block
   on podcast works. Each row: small play button + (title + pub-date). */
.recent-episodes-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.recent-episode-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--bg-rule);
}
.recent-episode-row:last-child { border-bottom: none; }
.recent-episode-play {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  width: 36px;
}
.recent-episode-text {
  display: flex;
  flex: 1;
  min-width: 0;
  align-items: baseline;
  gap: 12px;
}
.recent-episode-title {
  flex: 1;
  font-family: var(--serif);
  font-size: 13px;
  color: var(--ink);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.recent-episode-date {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Audio player — slate-ink palette, sits inline below the row that
   triggered playback. Singleton: only one panel can be playing at a
   time; the audio-player module enforces that. */
.audio-play-btn {
  appearance: none;
  background: var(--accent, #3a4754);
  color: var(--ink-inverse, #f4f1e9);
  border: 0;
  border-radius: 999px;
  width: 28px;
  height: 28px;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0 0 1px;
  transition: background 80ms ease, transform 80ms ease;
}
.audio-play-btn:hover { transform: scale(1.06); }
.audio-play-btn.is-playing {
  background: var(--ink, #1f242c);
  padding: 0;
}
.audio-fallback-link {
  font-size: 11px;
  color: var(--accent, #3a4754);
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
}
.audio-fallback-link:hover { text-decoration: none; border-bottom-color: var(--accent); }

.audio-panel {
  margin: 4px 0 8px 48px; /* line up with the title column */
  padding: 10px 12px;
  background: var(--bg-card, #faf7f0);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  font-size: 12px;
  color: var(--ink);
}
.audio-panel-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.audio-ctrl {
  appearance: none;
  background: transparent;
  color: var(--ink, #1f242c);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 11px;
  cursor: pointer;
  line-height: 1;
}
.audio-ctrl:hover { border-color: var(--accent, #3a4754); color: var(--accent, #3a4754); }
.audio-ctrl-play { min-width: 36px; font-weight: 600; }
.audio-ctrl-skip { font-variant-numeric: tabular-nums; }
.audio-ctrl-speed { min-width: 40px; font-variant-numeric: tabular-nums; }
.audio-time {
  font-size: 11px;
  color: var(--ink-muted, #677);
  font-variant-numeric: tabular-nums;
  min-width: 78px;
  text-align: center;
}
.audio-scrubber {
  flex: 1 1 140px;
  min-width: 100px;
  accent-color: var(--accent, #3a4754);
}
.audio-volume {
  width: 70px;
  accent-color: var(--accent, #3a4754);
}
.audio-error {
  margin-top: 6px;
  font-size: 11px;
  color: var(--danger, #b94e4e);
}
.audio-error a { color: var(--accent, #3a4754); margin-left: 4px; }

/* Section header with an inline action (e.g. Refresh from RSS). */
.detail-section-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.detail-section-label-row .detail-section-label { margin-bottom: 0; }
.detail-section-action {
  appearance: none;
  background: transparent;
  border: 1px solid var(--bg-rule);
  color: var(--ink-muted, #677);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 80ms ease, color 80ms ease;
}
.detail-section-action:hover {
  border-color: var(--accent, #3a4754);
  color: var(--accent, #3a4754);
}
.detail-section-action[disabled] { opacity: 0.6; cursor: default; }
/* (Removed: `.detail-section-empty`. Section-level empty states
   now use `.empty-state empty-state--section`.) */

/* Secondary byline line for podcasts/articles (e.g. "in The New
   Yorker", "a Ringer production"). */
.detail-byline-secondary {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14px;
  color: var(--ink-muted);
  margin-top: 2px;
}

.work-stats-agents,
.work-stats-material {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
/* Material chip uses the same warm-cream agent-chip treatment but
   with a slightly tighter left padding since its content tends to
   be shorter (e.g. "MS" vs full names). */
.material-chip { padding-left: 8px; }
/* Custom-text input inside the material popover sits below the
   "Custom" section header, with a top border separating it from the
   list above. */
.classify-popover.material-type-popover .classify-popover-input {
  /* The base .classify-popover-input is now borderless app-wide
     (the "Option 2 flat" treatment from the dropdown-consistency
     audit), so the `border-bottom: none` override that used to
     live here is redundant. Padding is kept slightly tighter
     (6/8 vs the base 8/8) because this input sits at the bottom
     of the Material picker after the preset list — the smaller
     vertical padding keeps the panel from feeling bottom-heavy. */
  padding: 6px 12px 8px;
}

/* Material popover — Counts section at the top. Each row is a
   compact label + numeric input on a single line, sized like a
   classify-popover-item so the visual rhythm matches the material-
   type list below. The input strips its own border (the row border
   provides separation) and stays small to keep the popover compact. */
.classify-popover.material-type-popover .classify-popover-count-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 12px;
  cursor: text;
}
.classify-popover.material-type-popover .classify-popover-count-row:hover {
  background: var(--bg-hover);
}
.classify-popover.material-type-popover .classify-popover-count-label {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--ink);
  min-width: 76px;
}
.classify-popover.material-type-popover .classify-popover-count-input {
  flex: 1 1 auto;
  width: auto;
  padding: 2px 6px;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  background: var(--bg);
  font-size: 12px;
  text-align: right;
}
.classify-popover.material-type-popover .classify-popover-count-input:focus {
  border-color: var(--accent-soft);
  background: var(--bg-card);
}
/* Hide the spinner buttons that browsers render on number inputs —
   they cramp the right edge and don't add value when users almost
   always type counts directly. */
.classify-popover.material-type-popover .classify-popover-count-input::-webkit-outer-spin-button,
.classify-popover.material-type-popover .classify-popover-count-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.classify-popover.material-type-popover .classify-popover-count-input {
  -moz-appearance: textfield;
}

/* Reader Report row — chip + "+ Add"/"+ Change" trigger styled to
   match the +Add Right affordance. Color-coded chip text + popover
   item text per rating mirrors the old inline-pill colors. */
.reader-report-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.rr-chip { padding-left: 10px; font-weight: 500; }
.rr-chip.rr-positive       { color: var(--status-sold);     border-color: var(--status-sold); }
.rr-chip.rr-mixedPositive  { color: var(--status-submitted); border-color: var(--status-submitted); }
.rr-chip.rr-mixed          { color: var(--ink-muted); }
.rr-chip.rr-pass           { color: var(--ink-faint); }
.rr-chip.rr-negative       { color: var(--status-hot);      border-color: var(--status-hot); }
/* Color the × to match its chip. */
.rr-chip .agent-chip-remove { color: inherit; opacity: 0.55; }
.rr-chip .agent-chip-remove:hover { opacity: 1; }
/* Popover item color cues match the chip palette so the user can
   see the rating tone before clicking. */
.classify-popover-item.rr-positive       { color: var(--status-sold); }
.classify-popover-item.rr-mixedPositive  { color: var(--status-submitted); }
.classify-popover-item.rr-mixed          { color: var(--ink-muted); }
.classify-popover-item.rr-pass           { color: var(--ink-faint); }
.classify-popover-item.rr-negative       { color: var(--status-hot); }
/* Keep inline editors flush with chips on the same row. */
.work-stats-value .inline-edit { width: auto; }
.work-stats-value .inline-edit-view { display: inline-block; }

/* Legacy class kept for any stray references; no longer rendered. */
.agents-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 18px; }
.agents-group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.agents-group-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-right: 2px;
}

.agent-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 4px 0 10px;
  background: var(--bg-selected);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 11px;
  line-height: 1;
  color: var(--ink);
  font-weight: 400;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.agent-chip-person,
.agent-chip-company {
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color 100ms;
}
/* Agency-only chip (set the agency, no specific agent) — a dashed
   border + italic company name signals "agency, no agent known yet"
   at a glance, so a bare "CAA" chip doesn't read as a half-finished
   agent. Same size/shape as a normal agent chip otherwise. */
.agent-chip--agency-only {
  border-style: dashed;
}
.agent-chip--agency-only .agent-chip-company {
  font-style: italic;
}
.agent-chip-person:hover,
.agent-chip-company:hover { text-decoration-color: var(--ink-soft); }
.agent-chip-sep {
  color: var(--ink-faint);
  font-weight: 300;
}
.agent-chip-remove {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-muted);
  opacity: 0.7;
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 0 4px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 2px;
  border-radius: 2px;
  transition: opacity 100ms, background 100ms, color 100ms;
}
.agent-chip-remove:hover {
  opacity: 1;
  color: var(--ink);
  background: rgba(0, 0, 0, 0.06);
}

/* Reuse the +-Add Genre button style for the agent group's trigger
   so the row reads as one continuous control set. The .classify-add-btn
   already handles dimensions; agents-add-btn is just a hook for any
   row-specific tweaks later. */
.agents-add-btn { font-size: 10px; }

/* ============================================================
   AGENTS, PUBLISHERS, GALLEYS, OPTION HISTORY — list rows
   ============================================================ */

.relation-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 13px;
  color: var(--ink);
  margin-bottom: 4px;
}

.relation-row .name,
.relation-row .company-name {
  color: var(--accent);
  cursor: pointer;
}
.relation-row .name:hover,
.relation-row .company-name:hover { text-decoration: underline; }

.relation-row .sep { color: var(--ink-faint); padding: 0 2px; }

.relation-row .meta {
  font-size: 11px;
  color: var(--ink-muted);
  margin-left: 6px;
}

.relation-row .actions {
  margin-left: auto;
  display: flex;
  gap: 4px;
  opacity: 0.4;
  transition: opacity 100ms;
}
.relation-row:hover .actions { opacity: 1; }

.relation-row-action {
  font-size: 11px;
  color: var(--ink-muted);
  padding: 2px 6px;
  border-radius: 3px;
  border: 1px solid transparent;
}
.relation-row-action:hover {
  background: var(--bg-hover);
  color: var(--ink);
}
.relation-row-action.danger:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* + Add right / + Add option deal / + Add galley / + Add
   affiliation — inline add buttons that sit below their
   respective list. Inherits canonical small-variant dimensions
   matching `.col-action-btn` so the column-header "+ New" and
   the per-section "+ Add..." buttons read as siblings. Dashed
   outline + accent text distinguishes the "add to this list"
   semantic from the "create new" semantic of `.col-action-btn`. */
.list-add-btn {
  /* Hug the content rather than stretching to the column width when
     the button lives inside a flex-column parent (rights/galley lists).
     Option History never had a column-flex parent so it always sized
     naturally; this brings the others into parity. */
  align-self: flex-start;
  margin-top: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 4px 10px;
  background: transparent;
  border: 1px dashed var(--bg-rule);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--accent);
  cursor: pointer;
  transition: background 100ms, border-color 100ms, color 100ms;
}
.list-add-btn:hover {
  border-color: var(--accent);
  background: var(--accent-bg);
}

/* Option history rows render multi-line */
.option-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  margin-bottom: 6px;
  font-size: 13px;
}
.option-row-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.option-row-head .company-name {
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}
.option-row-head .company-name:hover { text-decoration: underline; }
.option-row-head .type {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
  font-weight: 600;
  background: var(--bg);
  padding: 2px 7px;
  border-radius: 3px;
}
.option-row-head .dates {
  font-size: 11px;
  color: var(--ink-muted);
  margin-left: auto;
}
.option-row-head .lapsed-badge {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  font-weight: 600;
}

.option-row-talent {
  font-size: 12px;
  color: var(--ink-muted);
  font-style: italic;
  font-family: var(--serif);
}
.option-row-notes {
  font-size: 12px;
  color: var(--ink-soft);
  font-style: italic;
  font-family: var(--serif);
  line-height: 1.45;
}

.option-row .actions {
  margin-top: 4px;
  display: flex;
  gap: 4px;
  justify-content: flex-end;
  opacity: 0.4;
  transition: opacity 100ms;
}
.option-row:hover .actions { opacity: 1; }

/* C.V. entries on the Person detail page render as cards
   matching the people-card visual treatment (avatar + name +
   subtitle + hover-revealed Edit/Delete) but stacked one per
   row and 1.5× the people-card width. The wider footprint
   accommodates the role + date-range subtitle without
   truncation; stacked layout reads top-to-bottom like a
   traditional resume rather than wrapping into a grid. */
.affiliations-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  margin-top: 4px;
}

/* ============================================================
   Work experience — compact LinkedIn-style cards on the People
   detail page (non-authors). One card per affiliation:
     40px company logo on the left
     one-line metadata strip on the right: Title · Company · Dates
     colleague chip row below the strip: [avatar][name] pairs
   Cards use --bg-card as a subtle "contained surface" tint with
   no borders. Past jobs dim their inner content (logo + text
   column) so the × delete affordance stays at full opacity on
   hover.
   ============================================================ */
.work-experience-list {
  display: flex;
  flex-direction: column;
  margin-top: 8px;
}

.work-experience-section {
  position: relative;
  display: flex;
  gap: 14px;
  align-items: flex-start;
  background: var(--bg-card);
  border-radius: 6px;
  padding: 14px 18px;
  margin-bottom: 10px;
  transition: background-color 100ms;
}

/* Card-level hover-highlight — matches the app-wide convention
   (`background: var(--bg-hover)` for "this region is under
   attention," used on nav items, list rows, inline-edit views,
   the Add card, modal close buttons, etc.). The cursor stays at
   the browser default here: the card body itself isn't a click
   target, but its sub-elements (logo, company link, colleague
   chips, pencil, ×) are — and those each carry their own
   `cursor: pointer`. The bg swap reinforces the hover state that
   already reveals the pencil + × pair, so the whole card reads
   as one cohesive "focused" surface. The Add card inherits this
   rule too — no need for a separate hover override there. */
.work-experience-section:hover {
  background: var(--bg-hover);
}

/* When a card has no colleague row (no resolved companyId, or no
   loose-match colleagues found), the text column collapses to a
   single metadata strip and top-align leaves the strip floating
   near the top of the card while the 72px logo extends well below
   it — visually unbalanced. Switch to center alignment so the
   strip sits at the logo's vertical midpoint. JS adds this class
   in buildWorkExperienceRow when renderedColleagues is false. */
.work-experience-section-empty {
  align-items: center;
}

/* Past-job dim is applied to the inner content (logo + text-
   column) so the × delete affordance, which lives at the
   section level, can reach full opacity on hover. CSS opacity
   on the parent would clamp child max-opacity to the parent's
   value — the per-child rule sidesteps that. Hover restores
   both to full opacity for clean scanning.

   Matches the former-colleague chip dim (0.4) so the "no
   longer current" signal reads consistently whether it's a
   whole job or a single colleague within an active job. */
.work-experience-section.past .work-experience-logo,
.work-experience-section.past .work-experience-text-column {
  opacity: 0.4;
}
.work-experience-section.past:hover .work-experience-logo,
.work-experience-section.past:hover .work-experience-text-column {
  opacity: 1;
}

/* Company logo — same circular-profile vocabulary as the
   standard .person-avatar / buildCompanyAvatar (--bg fill,
   --bg-rule border, serif initials in --ink-muted). Sized at
   72px (75% of the standard 96px) so the card has a clear
   anchor without dominating the strip. Font size scales to the
   same 1:2.67 ratio as the larger page-header avatars. */
.work-experience-logo {
  flex-shrink: 0;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background-color: var(--bg);
  background-size: cover;
  background-position: center;
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 27px;
  font-weight: 500;
  color: var(--ink-muted);
  line-height: 1;
  letter-spacing: -0.01em;
  overflow: hidden;
  transition: opacity 100ms, box-shadow 100ms, transform 100ms,
              border-color 100ms;
}
.work-experience-logo.empty {
  background-color: var(--bg-card);
}
/* Logo carries a click affordance when the affiliation has a
   resolved companyId — same navigation as clicking the company
   name. Cursor + subtle hover lift signal that it's interactive. */
.work-experience-logo.clickable {
  cursor: pointer;
}
.work-experience-logo.clickable:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  transform: translateY(-1px);
  border-color: var(--ink-faint);
}

/* Text column to the right of the logo. */
.work-experience-text-column {
  flex: 1;
  min-width: 0;
  transition: opacity 100ms;
}

/* Metadata strip — Title · Company · Dates on one line, wraps
   onto two lines if needed. Interpuncts stay attached to the
   preceding text via flex wrapping (each item is its own flex
   child).

   No margin-bottom: spacing to the colleague row is owned by the
   colleague row itself (it carries `margin-top: 10px` + a hairline
   `border-top` divider). Putting the spacing on both sides would
   double up to ~20px, which read too airy. The colleague row also
   gates conditionally on having ≥1 chip, so when it doesn't render
   we don't need any bottom margin here either — the card collapses
   cleanly to just the strip. */
.work-experience-metadata {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

/* Metadata strip — Company · Title · Dates, all serif so the
   line reads as one editorial register. Company leads (bold,
   primary ink); title sits in the middle at normal weight in
   --ink-soft; dates close in italic --ink-muted. */

.work-experience-company {
  font-family: var(--serif);
  font-size: 19px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
  letter-spacing: -0.005em;
}
.work-experience-company .company-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 100ms;
}
.work-experience-company .company-link:hover {
  border-bottom-color: var(--ink-muted);
}
.work-experience-company.empty {
  color: var(--ink-faint) !important;
  font-style: normal !important;
  font-weight: 400 !important;
  font-size: 16px !important;
}

.work-experience-title {
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 400;
  color: var(--ink-soft);
  line-height: 1.3;
}
.work-experience-sep {
  font-family: var(--serif);
  color: var(--ink-muted);
  font-size: 15px;
  line-height: 1.3;
}

/* Date range — read-only span containing the derived display
   string ("2018 – Present", "Present", "– 2024", etc.). Italic
   serif in --ink-muted to close the metadata strip. */
.work-experience-dates {
  font-family: var(--serif);
  font-size: 15px;
  color: var(--ink-muted);
  font-style: italic;
  line-height: 1.3;
  white-space: nowrap;
}

/* Colleague chip row — [28px avatar][name] pairs in a wrapping
   flex layout. No subtitles, no italic role labels.

   A hairline divider sits above the row to separate the "what
   this job was" metadata strip from the "who else was there"
   chips, indented to start at the text column's left edge (not
   the card's left edge) so it reads as a sub-section of the
   text content rather than a band across the whole card.

   Indent math: 72px logo + 14px section flex gap = 86px from the
   text column's parent edge. The row is a child of
   .work-experience-text-column, whose left edge already starts
   86px in from the card's padding edge, so a 0 left-margin would
   already align — but the divider lives on this same element, so
   no extra margin-left is needed. (Earlier draft put 86px here;
   that double-indented past the text column. Removed.)

   The whole row gates conditionally in JS (`if currentColleagues
   + formerColleagues > 0`), so when there are no chips, the
   border-top + padding-top never render — no orphan divider. */
.work-experience-colleagues {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  align-items: center;
  border-top: 0.5px solid var(--bg-rule);
  padding-top: 10px;
  margin-top: 10px;
}
.work-experience-colleague {
  display: flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
/* Force-off the global `a:hover { text-decoration: underline }` —
   otherwise it strikes through every text descendant of the chip,
   including the initials sitting inside the avatar circle. */
.work-experience-colleague:hover {
  text-decoration: none;
}
.work-experience-colleague.former {
  /* Per-chip dim — flows together with the current chips in the
     same row; opacity is the only signal of "no longer here".
     Set well below the past-card content dim (0.65) so former
     colleagues read as background presence rather than peers
     of the current set — they're there for reference, not the
     focal information of the row. */
  opacity: 0.4;
}
.work-experience-colleague:hover .work-experience-colleague-name {
  color: var(--ink);
}
/* Colleague avatar — same standard circular-profile treatment as
   .person-avatar / .work-experience-logo (--bg fill, --bg-rule
   border, serif initials in --ink-muted). Just sized smaller for
   chip use. The 1px border gives the edge against the card's
   --bg-card background even when an empty avatar's fill is also
   --bg-card. */
.work-experience-colleague-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background-color: var(--bg);
  background-size: cover;
  background-position: center;
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-muted);
  line-height: 1;
  letter-spacing: -0.01em;
  flex-shrink: 0;
  overflow: hidden;
}
.work-experience-colleague-avatar.empty {
  background-color: var(--bg-card);
}
.work-experience-colleague-name {
  font-family: var(--serif);
  font-size: 14px;
  color: var(--ink-soft);
  white-space: nowrap;
  line-height: 1.3;
  transition: color 100ms;
}

/* Hover-revealed action pair at top-right: pencil (edit) on the
   left, × (delete) on the right. Both are plain --ink-faint
   glyphs, no background or border; hover lifts to --ink-muted.
   The pair stays at full opacity on past cards even though the
   rest of the card dims, because past dimming is applied to
   .logo / .text-column only — not the section root. */
.work-experience-section-edit,
.work-experience-section-delete {
  position: absolute;
  top: 10px;
  background: transparent;
  border: none;
  padding: 4px 6px;
  color: var(--ink-faint);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, color 100ms;
  display: flex;
  align-items: center;
  justify-content: center;
}
.work-experience-section-edit    { right: 38px; }
.work-experience-section-delete  { right: 12px; }
.work-experience-section-edit svg,
.work-experience-section-delete svg { display: block; }
.work-experience-section:hover .work-experience-section-edit,
.work-experience-section:hover .work-experience-section-delete {
  opacity: 1;
}
.work-experience-section-edit:hover,
.work-experience-section-delete:hover {
  color: var(--ink-muted);
}

/* Add-work-experience card — uses the same .work-experience-section
   shell, just with quieter typography and a dashed + glyph in the
   logo slot to signal "this is the add affordance." The
   background-swap on hover and the `transition` are inherited
   from the base `.work-experience-section` rule (the same hover
   treatment populated cards get); only the `cursor: pointer` is
   Add-card-specific, since on this variant the whole card body
   opens the affiliation modal whereas populated cards have no
   body-level click action. */
.work-experience-section-add {
  cursor: pointer;
}
.work-experience-section-add:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
}

/* + glyph circle — matches the 72px logo dimensions of populated
   workplace cards but uses a dashed border + faint + glyph so it
   reads as "empty, click to populate." Hover deepens the colors.

   The "+" character sits on the typographic math axis, which in
   serif faces falls a few pixels below the em-box geometric
   center. Flex `align-items: center` centers the line-box, not
   the glyph, so the + drifts visually low. `padding-bottom: 5px`
   + `box-sizing: border-box` shrinks the content area from the
   bottom while keeping the outer dimensions fixed at 72×72,
   pulling the centered line-box (and the + with it) up to the
   geometric center of the circle. */
.work-experience-logo-add {
  background-color: var(--bg);
  border: 1px dashed var(--bg-rule);
  color: var(--ink-faint);
  font-family: var(--serif);
  font-weight: 400;
  font-size: 34px;
  padding-bottom: 5px;
  box-sizing: border-box;
}
.work-experience-section-add:hover .work-experience-logo-add {
  border-color: var(--ink-faint);
  color: var(--ink-muted);
}

/* "Add work experience" label sits in the company-name slot at
   the metadata strip. Matches the bold weight (600) of a real
   company name so the affordance reads with the same typographic
   confidence, but recolored to the warm-slate accent so it still
   reads as an action rather than a populated entry. Hover deepens
   to --accent-hover. */
.work-experience-section-add .work-experience-company {
  color: var(--accent);
}
.work-experience-section-add:hover .work-experience-company {
  color: var(--accent-hover);
}

/* Profile-tile vocabulary — clickable 96px circular profile +
   serif name beneath. Used by:
     • Publishers section on Author detail (`.publishers-list`
       + `.publisher-badge*`)
     • Current Employees, Authors, Former Employees sections on
       Company detail (`.profile-tile-grid` + `.profile-tile*`)
   Single set of rules below covers both via comma selectors so
   the two contexts stay pixel-identical and any future tweak
   propagates everywhere.

   Circle dimensions + initials sizing match the standard
   .person-avatar / buildCompanyAvatar at the page header so a
   row of tiles reads as a roster of full-strength profile
   chips. Name typography matches .work-card-title — the same
   serif 13px/500 used for book-cover titles in the Works grid
   above, so the page reads as one continuous gallery: covers
   on top, profile circles on the same visual register below. */
.publishers-list,
.profile-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 96px);
  justify-content: start;
  gap: 14px 12px;
  margin-top: 4px;
}
.publisher-badge,
.profile-tile {
  appearance: none;
  background: transparent;
  border: none;
  padding: 0;
  font-family: inherit;
  color: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.publisher-badge-avatar,
.profile-tile-avatar {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  background-color: var(--bg);
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: box-shadow 100ms, transform 100ms;
}
.publisher-badge-avatar.empty,
.profile-tile-avatar.empty {
  background-color: var(--bg-card);
}
.publisher-badge:hover .publisher-badge-avatar,
.profile-tile:hover .profile-tile-avatar {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  transform: translateY(-1px);
}
.publisher-badge-initial,
.profile-tile-initial {
  font-family: var(--serif);
  /* Same 36px on 96px as .person-avatar-initial — keeps the
     optical weight balanced inside the circle. */
  font-size: 36px;
  font-weight: 500;
  color: var(--ink-muted);
  line-height: 1;
  letter-spacing: -0.01em;
}
.publisher-badge-name,
.profile-tile-name {
  /* Matches .work-card-title: serif 13px/500 ink, 1.25 line
     height, 2-line clamp. The text-align center is the only
     deviation (work titles are left-aligned under their covers;
     names under circles read better centered). */
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.25;
  text-align: center;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

/* Add-person tile — same .profile-tile geometry as a populated
   tile (96px circle + label beneath) but with a dashed border,
   centered serif "+" glyph, and the bold-accent label
   convention used by the Work Experience "Add" card on person
   pages. Lives at the end of the Current Employees grid; both
   .profile-tile-grid and .profile-tile-blocks place it as the
   last flex/grid item without special-casing. */
.profile-tile-avatar-add {
  border: 1px dashed var(--bg-rule);
  background: var(--bg);
  color: var(--ink-faint);
  font-family: var(--serif);
  font-weight: 400;
  font-size: 48px;
  /* Same math-axis correction trick the work-experience add
     circle uses: the "+" glyph in serif faces sits a few px
     below the line-box midpoint, so a flex-centered glyph
     drifts low. padding-bottom + border-box pulls it up to the
     geometric center while preserving the 96×96 outer dim. */
  padding-bottom: 8px;
  box-sizing: border-box;
  line-height: 1;
}
.profile-tile-add:hover .profile-tile-avatar-add {
  border-color: var(--ink-faint);
  color: var(--ink-muted);
  /* Suppress the standard tile's shadow-lift on hover so the
     dashed-border affordance doesn't read as a populated tile
     under attention. */
  box-shadow: none;
  transform: none;
}
.profile-tile-add .profile-tile-name {
  /* Bold + warm-slate accent, same treatment as the Work
     Experience "Add" card's label — visually classes this as
     "an action" rather than "a populated record". */
  color: var(--accent);
  font-weight: 600;
  transition: color 100ms;
}
.profile-tile-add:hover .profile-tile-name {
  color: var(--accent-hover);
}

/* Small italic subtitle below the name — job title / role line
   on company-detail's Current Employees tiles. Matches the
   .work-card-author + .employee-card-subtitle register so the
   page reads as one typographic system. Single line, ellipsizes
   past the tile's 96px max width. */
.profile-tile-subtitle {
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  line-height: 1.2;
  text-align: center;
  margin-top: -2px;
  max-width: 96px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ============================================================
   CLIENT HISTORY — work-profile section that lists clients
   (People and/or Companies) associated with this work. Reuses
   the .profile-tile vocabulary above for visual parity with
   the Current Employees / Authors grids on company-detail and
   person-detail.

   The polymorphic mix is signaled per tile by a small kind pill
   ("Person" / "Company") below the name. Each tile also gets
   a hover-revealed × in its top-right corner for unlinking.
   ============================================================ */
.client-history-grid {
  /* Inherit .profile-tile-grid base spacing; nothing to override
     today — kept as a selector hook for future scoped tweaks
     (e.g., dense layout when the list grows long). */
}
.client-history-tile {
  /* Anchor the × overlay against the tile box. */
  position: relative;
}
.client-history-kind-pill {
  font-family: var(--serif);
  font-style: italic;
  font-size: 10px;
  color: var(--ink-faint);
  line-height: 1;
  margin-top: -2px;
  letter-spacing: 0.02em;
}
.client-history-tile-remove {
  appearance: none;
  position: absolute;
  top: 0;
  right: 0;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: var(--bg-card);
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  /* Hidden until tile hover — same affordance pattern as the
     × on agent-picker chips and material chips. */
  opacity: 0;
  transition: opacity 100ms, color 100ms, background 100ms;
}
.client-history-tile:hover .client-history-tile-remove {
  opacity: 0.85;
}
.client-history-tile-remove:hover {
  opacity: 1;
  color: #c8102e;
  background: var(--bg);
}

/* Picker modal — minimal chrome on top of the shared .modal /
   .modal-overlay shell. The body is just the buildPickerSection
   block, so the panel has no padding to clip the input/list
   layout. */
.client-picker-modal-body {
  padding: 0;
}
/* DONE button — sits at the modal foot as a primary close
   affordance. The picker no longer auto-closes after each pick
   (so the user can add multiple recipients in one session),
   so DONE is the explicit "I'm finished" gesture. Same accent
   chrome the agent picker's commit button uses (AP6) so the
   two close affordances read as siblings. */
.client-picker-done-btn {
  appearance: none;
  display: block;
  width: calc(100% - 16px);
  margin: 8px 8px 8px;
  padding: 8px 12px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--accent);
  color: var(--bg-card);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background 100ms, border-color 100ms;
}
.client-picker-done-btn:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}
/* Cross-collection "Person" / "Company" tag on each picker row.
   Mirrors the .classify-popover-aux-tags + ::before pattern used
   by the agent picker's affiliation tag, but content-only (no
   paper-tint background). Inline-block so it sits inline with
   the name without absorbing free space. */
.client-picker-kind-tag {
  margin-left: auto;
  margin-right: 6px;
  padding: 1px 6px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 10px;
  color: var(--ink-muted);
  letter-spacing: 0.02em;
  border: 1px solid var(--bg-rule);
  border-radius: 999px;
  background: var(--bg);
}

/* Nested-tile blocks (Current Employees on Company detail).
   Each top-level person occupies a block: their tile, plus
   (when they have direct reports) an indented children
   container below tied back with a connector line — the
   family-tree equivalent of the old .employee-blocks layout,
   re-rendered as profile tiles. Top-level blocks wrap in a
   flex row; tall blocks don't stretch their neighbors. */
.profile-tile-blocks {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 24px 20px;
  margin-top: 4px;
}
.profile-tile-block {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.profile-tile-block > .profile-tile {
  /* Top-level (boss) tile inside a block sits flush-left so
     children can indent off its left edge. */
  align-self: flex-start;
}
.profile-tile-block-children {
  margin-left: 24px;
  padding-left: 24px;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Each child tile renders relative so the per-child pseudo-
   elements can anchor a connector segment. */
.profile-tile-block-children > .profile-tile {
  position: relative;
  align-self: flex-start;
}
/* Horizontal stub from the vertical line to the child's avatar
   center (96px circle → 48px from the tile's top). */
.profile-tile-block-children > .profile-tile::before {
  content: '';
  position: absolute;
  left: -24px;
  top: 48px;
  width: 24px;
  height: 1px;
  background: var(--bg-rule);
}
/* Vertical line — each child draws its own segment. For all
   children except the last, the line spans -12px (into the gap
   above, so consecutive segments meet continuously) to the
   bottom of the tile. The last child caps its segment at the
   avatar's vertical center so there's no hanging thread. */
.profile-tile-block-children > .profile-tile::after {
  content: '';
  position: absolute;
  left: -24px;
  top: -12px;
  bottom: 0;
  width: 1px;
  background: var(--bg-rule);
}
.profile-tile-block-children > .profile-tile:last-child::after {
  bottom: calc(100% - 48px);
}
.affiliation-card {
  flex: 0 0 auto;
  width: 300px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  position: relative;
  transition: background 100ms, box-shadow 100ms, transform 100ms;
}
.affiliation-card:hover {
  background: var(--bg-hover);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
  transform: translateY(-1px);
}
.affiliation-card-avatar {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background-color: var(--bg);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-muted);
}
.affiliation-card-text {
  flex: 1;
  min-width: 0;
}
.affiliation-card-name {
  font-family: var(--serif);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  transition: border-color 100ms;
}
.affiliation-card-name:hover {
  border-bottom-color: var(--ink-muted);
}
.affiliation-card-subtitle {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
}
/* Inline CURRENT pill — matches the slate-accent treatment
   used by similar status indicators elsewhere in the app. */
.affiliation-card-current {
  font-family: var(--sans);
  font-style: normal;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 2px;
  padding: 1px 6px;
}
.affiliation-card-actions {
  /* Lives inside .affiliation-card-subtitle (which is a flex row),
     so margin-left:auto pushes the actions to the right edge —
     inline with the dates rather than on their own line. The 11px
     font on .relation-row-action already matches the subtitle, so
     it reads as a quiet trailing pair. opacity-fades on hover. */
  margin-left: auto;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 100ms;
}
.affiliation-card:hover .affiliation-card-actions {
  opacity: 1;
}

/* Pipeline + memo history (read-only rollups) */
.rollup-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  margin-bottom: 4px;
  font-size: 13px;
  cursor: pointer;
  transition: border-color 100ms;
}
.rollup-row:hover { border-color: var(--ink-muted); }

.rollup-title { flex: 1; color: var(--ink); }
.rollup-meta {
  font-size: 11px;
  color: var(--ink-muted);
  flex-shrink: 0;
}
/* Archived rollup row — soft-deleted records (memos in the Archive)
   surface in Memo History rollups for historical context. Muted but
   still clickable; the memo-detail column applies its own muted
   treatment once opened. */
.rollup-row-archived {
  opacity: 0.55;
}
.rollup-row-archived .rollup-title { font-style: italic; }
.rollup-row-archived:hover { opacity: 0.8; }
/* Sent memo rollup row — mirrors the Memos-section rolodex row
   treatment further down in this file (~line 8318). Title fades
   to 0.55 at rest, hover restores to full opacity. The row
   border + background stay at full strength so the row chrome
   itself reads as crisply as any other; only the title text
   dims, the same way it does on the rolodex side.
   `data-memo-status="sent"` is the same attribute the rolodex
   + preview pane use, set in buildMemoHistoryRow when
   memo.status === 'sent'. */
.rollup-row[data-memo-status="sent"] .rollup-title {
  opacity: 0.55;
  transition: opacity 100ms;
}
.rollup-row[data-memo-status="sent"]:hover .rollup-title {
  opacity: 1;
}

/* Status pill inside a memo-history rollup row. Reuses the
   `.memo-row-status-pill` chrome (border, font, color) from the
   rolodex but overrides its absolute positioning — in the
   rollup-row context the pill is a flow flex child sitting to
   the right of the title, not an absolutely-pinned chip in the
   row's lower-right corner. */
.rollup-row .memo-row-status-pill {
  position: static;
  flex-shrink: 0;
}
/* Sent-row dim extends to the pill so it fades along with the
   title at rest, restoring on hover — matches the rolodex
   treatment exactly. */
.rollup-row[data-memo-status="sent"] .memo-row-status-pill {
  opacity: 0.55;
  transition: opacity 100ms;
}
.rollup-row[data-memo-status="sent"]:hover .memo-row-status-pill {
  opacity: 1;
}
/* Selected rollup target — flipped by the global swap-target
   sync listener (views/_util.js) whenever a column matching
   the row's stamped data-attrs is open in the stack. The class
   is toggled on multiple row shapes (.rollup-row bars but also
   .work-card grids, .publisher-badge buttons, .work-experience-
   logo circles, colleague chips, etc.), so the visual needs to
   read as "this is the open one" across all of them.

   Strategy:
     - Rectangular .rollup-row bars: warm fill + inset accent
       stripe on the left, matching .book-row.selected.
     - Everything else: a 2px accent outline drawn inside the
       element's bounds, so cards / circles / chips all gain a
       clear ring without their layout shifting.
*/
.rollup-row.rollup-row-selected,
.rollup-row.rollup-row-selected:hover {
  background: var(--bg-selected);
  border-color: var(--ink-muted);
  box-shadow: inset 3px 0 0 var(--accent);
}
.rollup-row-selected:not(.rollup-row) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* When a row is BOTH archived and selected, keep the selected
   treatment fully opaque — the user just clicked it, so dimming
   the row they're viewing reads as a bug. */
.rollup-row-archived.rollup-row-selected { opacity: 1; }
.rollup-row-archived.rollup-row-selected .rollup-title { font-style: italic; }

/* Delete link in book detail */
.detail-danger-zone {
  margin-top: 56px;
  padding-top: 20px;
  border-top: 1px dashed var(--bg-rule);
  display: flex;
  justify-content: flex-end;
}
.detail-delete-btn {
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.04em;
  padding: 4px 10px;
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: pointer;
  transition: all 100ms;
}
.detail-delete-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-bg);
}

.modal.danger .modal-title { color: var(--accent); }
.btn.btn-danger {
  background: var(--accent);
  color: var(--bg-card);
  border-color: var(--accent);
}
.btn.btn-danger:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

/* ============================================================
   COL-HEADER ACTIONS (e.g. "+ New" buttons)
   ============================================================ */

.col-header-row {
  display: flex;
  align-items: center;       /* center title in the 80px shelf */
  justify-content: space-between;
  gap: 10px;
  /* The title row is the page-header shelf — same height the
     side-nav brand uses, so titles read as on a continuous row
     across the app. */
  min-height: var(--page-header-height);
  /* Cancel the col-header's bottom padding here so the title
     row's bottom edge sits flush at 80px from the top; the
     col-header still pads the subtitle / search / chips below. */
}

/* + New / + Build button at the top of each list column.
   Inherits the canonical small-variant dimensions (4px 10px /
   11px / weight 500) so it sits at the same register as the
   Restore / + Add / ↻ Fetch family. Distinct hover treatment:
   accent text + accent border + accent-bg fill — reads as a
   call-to-action in contrast to the slate `.btn-secondary`. */
.col-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 4px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
  transition: background 100ms, border-color 100ms, color 100ms;
}
.col-action-btn:hover {
  border-color: var(--accent);
  background: var(--accent-bg);
}

/* ============================================================
   LIST STATES (loading, empty, error)
   ============================================================ */

/* ============================================================
   EMPTY STATES — unified vocabulary

   Single base class (.empty-state) gives every empty / loading
   / "no longer exists" surface in the app the same typographic
   register: italic 14px EB Garamond at --ink-faint, line-height
   1.5. Three modifiers handle the padding contexts:

     .empty-state--inline   no padding, flows with section content
                            ("No hosts linked.", "No author linked.",
                            byline empties on book detail)
     .empty-state--section  8px vertical padding, gentle breathing
                            room for section-level placeholders
                            ("No episodes captured yet…",
                            "Quiet so far.")
     .empty-state--page     40/24 padding + centered, full-column
                            empty states ("This work no longer
                            exists.", "Loading memos…", filtered-
                            results empties)

   Earlier vocabulary had four near-identical classes
   (.list-state, .detail-empty-hint, .detail-section-empty,
   .landing-empty) that varied only in padding. Collapsed into
   this single base + modifier set in v1.1.7.
   ============================================================ */
.empty-state {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14px;
  color: var(--ink-faint);
  line-height: 1.5;
}
.empty-state--inline  { padding: 0; }
.empty-state--section { padding: 8px 0; }
.empty-state--page    { padding: 40px 24px; text-align: center; }

/* ============================================================
   DETAIL — empty placeholders & inline edits
   ============================================================ */

.note-add-input {
  width: 100%;
  margin-top: 12px;
  padding: 10px 14px;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-family: var(--serif);
  font-size: 14px;
  color: var(--ink);
  background: var(--bg-card);
  outline: none;
  resize: vertical;
  min-height: 60px;
  line-height: 1.5;
  transition: border-color 100ms;
}
.note-add-input:focus { border-color: var(--accent); }
.note-add-input::placeholder { color: var(--ink-faint); font-style: italic; }

.note-add-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
}

/* ============================================================
   FORM CONTROLS
   ============================================================ */

.field { display: flex; flex-direction: column; gap: 4px; }

.field-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
}

.field-input,
.field-select,
.field-textarea {
  width: 100%;
  padding: 8px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 13px;
  color: var(--ink);
  outline: none;
  font-family: var(--sans);
  transition: border-color 100ms;
}
.field-input:focus,
.field-select:focus,
.field-textarea:focus { border-color: var(--accent); }
.field-input::placeholder { color: var(--ink-faint); }

/* Picker-trigger button — used by form-modal `custom` fields
   that open a buildPickerSection popover on click. Styled to
   match .field-input's geometry so the modal reads as one
   coherent column of input controls (border / padding / radius
   / typography all keyed off the same tokens), with a caret on
   the right that signals "dropdown" + a placeholder-tinted
   label when nothing's picked yet.

   The whole button is clickable; when a value is picked, an
   inline × clear renders between the label and the caret (see
   `.field-picker-trigger-clear` below). This matches the ×
   convention every other picker uses on populated chips; the
   popover no longer renders a "(none)" row. */
.field-picker-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 13px;
  font-family: var(--sans);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  outline: none;
  transition: border-color 100ms, background-color 100ms;
}
.field-picker-trigger:hover { border-color: var(--ink-muted); }
.field-picker-trigger:focus { border-color: var(--accent); }
.field-picker-trigger.is-empty .field-picker-trigger-label {
  color: var(--ink-faint);
}
.field-picker-trigger-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.field-picker-trigger-caret {
  flex-shrink: 0;
  font-size: 10px;
  color: var(--ink-muted);
  line-height: 1;
}
/* × clear affordance — rendered only when a value is picked, sits
   between the label and the caret. Its click handler stops
   propagation so it clears the value without opening the picker.
   This is the canonical clear path now that the field-modal
   pickers (Company in New Person, Parent Company in New Company,
   Company in the affiliation-edit modal) no longer render a
   "(none)" row in the popover. */
.field-picker-trigger-clear {
  flex-shrink: 0;
  padding: 0 4px;
  background: transparent;
  border: 0;
  border-radius: 2px;
  font-size: 14px;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color 100ms, background 100ms;
}
.field-picker-trigger-clear:hover {
  color: #c8102e;
  background: rgba(0, 0, 0, 0.05);
}

/* Work-detail Stats rows — Network / Venue / Publication / Publisher
   fields use .linked-company-clear (a .field-picker-trigger-clear
   variant) as their × clear button. On a profile page the rest of
   the row chrome is hover-revealed (pencil, etc.); the × stays
   always-visible by default, which made the row read busy. Match
   the pencil's hover-reveal pattern so the row is calm at rest and
   surfaces both edit affordances together on hover/keyboard focus.

   Scoped to the work-detail variant only — .field-picker-trigger-
   clear used inside form-modal triggers stays always-visible since
   those live in focused dialog surfaces where the chrome is already
   the user's attention. */
.linked-company-clear {
  opacity: 0;
  transition: opacity 100ms, color 100ms, background 100ms;
}
.linked-company-view:hover .linked-company-clear,
.linked-company-view:focus-within .linked-company-clear,
.linked-company-clear:focus {
  opacity: 1;
}

/* Picker-popover variant for form-modal triggers. Slightly wider
   than the genre popover (companies have longer display names)
   and keyed off the same chrome as the publisher/agent pickers
   so it visually rhymes with them. The list's default 280px
   max-height is fine here — the popover floats over the modal,
   so its size doesn't reflow modal geometry. */
.classify-popover.field-picker-popover {
  width: 320px;
}

.field-textarea {
  font-family: var(--serif);
  font-size: 14px;
  line-height: 1.5;
  resize: vertical;
  min-height: 80px;
}

.field-help {
  font-size: 11px;
  color: var(--ink-muted);
  font-style: italic;
}

.field-error {
  font-size: 11px;
  color: var(--accent);
  font-style: italic;
  font-family: var(--serif);
}

/* Wikidata search-as-you-type dropdown — used by the New Person
   and New Company modals to surface match suggestions inline
   below the Name field. Each row shows the canonical label +
   short Wikidata description; clicking stamps the QID so a
   create writes the rich record (bio / photo / Wikipedia link
   for people, founded / HQ / website for companies). The hint
   line appears below the dropdown after a pick to confirm what
   will be imported. */
/* Wikidata suggest popover — floats below the Name field via
   attachPopover (the same shell the company picker uses), so
   the modal body doesn't reflow as the user types. Width is
   wider than the 320px field-picker-popover because Wikidata
   result rows pair a person name with a (sometimes long) one-
   line description and we don't want descriptions wrapping
   for the common case. Max-height keeps it within view when
   a search returns the full 5-row cap. Base .classify-popover
   rules supply bg-card + border + radius + shadow + overflow
   clipping so all dropdowns in the app share one chrome. */
.classify-popover.wd-suggest-popover {
  width: 400px;
  max-height: 280px;
  overflow-y: auto;
}
.classify-popover.wd-suggest-popover[hidden] { display: none; }
/* Suggest rows — same padding/divider/hover register as
   .classify-popover-item now (the two are visually unified), but
   the row holds two stacked text lines (canonical name + italic
   description) rather than a single label. Each row is a real
   <button> for keyboard semantics + focus rings. */
.new-modal-suggest-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--bg-rule);
  padding: 8px 12px;
  font-family: inherit;
  cursor: pointer;
  transition: background 100ms;
}
.new-modal-suggest-item:last-child { border-bottom: 0; }
.new-modal-suggest-item:hover { background: var(--bg-hover); }
.new-modal-suggest-name {
  font-family: var(--sans);
  font-size: 13px;
  color: var(--ink);
  margin-bottom: 2px;
}
.new-modal-suggest-desc {
  font-family: var(--sans);
  font-size: 11px;
  color: var(--ink-muted);
  font-style: italic;
}
/* `.new-modal-suggest-hint` was retired 2026-05-13 — both
   new-person-modal and new-company-modal's Wikidata-pick
   confirmation hint now use the canonical `.modal-helper-text`
   class (serif italic, 13px, ink-faint), matching the
   subtitle treatment in the New Work modal. */

/* ============================================================
   BUTTON SYSTEM — canonical sizes for action buttons across
   the app. Pick a base size (`.btn` standard or `.btn .btn-small`)
   and stack a color variant (.btn-primary / .btn-secondary /
   .btn-danger).

   STANDARD SIZE (`.btn`) — every memo-section toolbar button
   (Copy, PDF, Send, Open Builder), every modal action button
   (Cancel, Confirm, Delete), and the Library / People /
   Companies / Memos archive toggles:

       padding: 7px 14px   ·   font-size: 12px / weight 500
       letter-spacing: 0.02em   ·   border-radius: 4px

   SMALL SIZE (`.btn-small`) — inline +/Add buttons + per-row
   actions that live inside denser chrome (rights/galley lists,
   genre chip row, archive-mode Restore button, ↻ Fetch from
   API):

       padding: 4px 10px   ·   font-size: 11px / weight 500
       letter-spacing: 0.02em   ·   border-radius: 3px

   VARIANTS:
     .btn-primary    — accent-fill CTA (Send, Open Builder)
     .btn-secondary  — outlined slate (Copy, PDF, Cancel)
     .btn-danger     — accent-red destructive (Delete, Discard)
     .archive-link   — outlined slate with .active pressed state
                        (Library / People / Companies / Memos
                         archive toggle)

   One-off classes (`.col-action-btn`, `.list-add-btn`,
   `.detail-refetch-btn`, `.classify-add-btn`, `.archive-row-
   restore`) inherit the canonical dimensions so the size
   register stays consistent; their unique selectors carry the
   distinct hover / accent / dashed-outline treatments that
   distinguish "+ New" / "+ Add" / "↻ Fetch" / per-row Restore.
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* 9px vertical padding so the standard button height (~34px)
     matches `.field-input` height (8px padding + 13px font + 2px
     border ≈ 34px). Form rows mixing buttons + inputs (e.g. the
     URL paste row in New Work, "Extract from URL" next to the
     URL input) now have visually aligned heights. The previous
     7px padding gave ~30px height — 4px shorter than the input
     it sat beside, which read as a sloppy mismatch.

     `.btn-small` stays at its tighter 4px padding for the
     inline +/Add affordances that need to fit in compact chrome. */
  padding: 9px 14px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 100ms, border-color 100ms, color 100ms;
}

.btn-primary {
  background: var(--accent);
  color: var(--bg-card);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:disabled {
  background: var(--ink-faint);
  cursor: not-allowed;
}

.btn-secondary {
  background: transparent;
  color: var(--ink-soft);
  border-color: var(--bg-rule);
}
.btn-secondary:hover {
  background: var(--bg-hover);
  color: var(--ink);
}

/* ============================================================
   MEMO TYPE PICKER (when Build a memo opens with no memoId)
   ============================================================ */

.memo-type-picker {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 40px;
  text-align: center;
}
.memo-type-picker h2 {
  font-family: var(--serif);
  font-size: 32px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.02em;
  margin-bottom: 8px;
}
.memo-type-picker .lede {
  font-family: var(--serif);
  font-style: italic;
  font-size: 16px;
  color: var(--ink-muted);
  margin-bottom: 36px;
  max-width: 540px;
}
.memo-type-choices {
  display: flex;
  gap: 16px;
  max-width: 760px;
  width: 100%;
}
.memo-type-btn {
  flex: 1;
  padding: 22px 24px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 8px;
  cursor: pointer;
  transition: border-color 120ms, box-shadow 120ms;
  text-align: left;
}
.memo-type-btn:hover {
  border-color: var(--accent);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
}
.memo-type-btn-title {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  margin-bottom: 6px;
}
.memo-type-btn-desc {
  font-size: 12px;
  color: var(--ink-muted);
  line-height: 1.5;
}

/* ============================================================
   MEMO COMPOSER — 3-pane (Library / Builder / Preview)
   ============================================================ */

.composer {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.composer-header {
  padding: 18px 24px 12px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
}
.composer-title.inline-edit-view,
.composer-title.inline-edit-input {
  font-family: var(--serif);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.composer-meta {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: nowrap;
  margin-top: 6px;
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  min-width: 0;
}
/* Each meta child keeps its content on one line — DRAFT, the date,
   and the save state should never split across lines. */
.composer-meta > * { white-space: nowrap; }
.composer-save-state {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  font-family: var(--sans);
  /* "Saved · just now" contains whitespace inside a single text run.
     Without explicit nowrap the browser breaks it across two lines
     when the column is tight, which produces a stacked DRAFT / SAVED
     layout. Keep the whole indicator on one line; if the meta row
     can't fit, it overflows to the right rather than wrapping. */
  white-space: nowrap;
  flex-shrink: 0;
}
.composer-save-state.saving { color: var(--ink-muted); }
.composer-save-state.saving::before {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink-muted);
  animation: pulse 1s ease-in-out infinite;
}
.composer-save-state.saved::before {
  content: '✓';
  color: var(--accent);
  font-size: 12px;
}
@keyframes pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

/* Version history modal — list of past memo states */
.version-list {
  display: flex;
  flex-direction: column;
  max-height: 60vh;
  overflow-y: auto;
  margin: -4px 0;
}
.version-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--bg-rule);
}
.version-row:last-child { border-bottom: none; }
.version-row-text { flex: 1; min-width: 0; }
.version-row-time {
  font-family: var(--serif);
  font-size: 14px;
  color: var(--ink);
  font-weight: 500;
}
.version-row-summary {
  font-size: 11px;
  color: var(--ink-muted);
  margin-top: 2px;
}
.version-row-by {
  font-size: 10px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: 3px;
}
.version-empty {
  padding: 18px 0;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  color: var(--ink-faint);
}
.composer-type-pill {
  background: var(--accent-bg);
  color: var(--accent);
  padding: 2px 8px;
  border-radius: 3px;
  font-weight: 600;
}
.composer-meta .sep { color: var(--ink-faint); }
.composer-date.inline-edit-view,
.composer-date.inline-edit-input {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.composer-body {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}

.composer-library,
.composer-builder,
.composer-preview {
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* Library + Builder share a single fixed width so the two left
   columns read as a paired set, with the Preview taking everything
   that's left. Tightened from 260/320 → 240 each since their content
   (small library cards / section cards with title + by-line) reads
   fine at this narrower size. */
.composer-library {
  width: 240px;
  flex-shrink: 0;
  border-right: 1px solid var(--bg-rule);
}
.composer-builder {
  width: 240px;
  flex-shrink: 0;
  border-right: 1px solid var(--bg-rule);
  background: var(--bg-card);
}
.composer-preview {
  flex: 1;
  min-width: 460px;
  background: var(--bg-card);
  overflow-y: auto;
}

.composer-pane-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-weight: 600;
  padding: 12px 16px 8px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 1;
}
.composer-builder .composer-pane-label,
.composer-preview .composer-pane-label { background: var(--bg-card); }

/* Library */
.composer-library-search {
  margin: 8px 14px 6px;
  width: calc(100% - 28px);
  padding: 6px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 12px;
  color: var(--ink);
  outline: none;
}
.composer-library-search:focus { border-color: var(--accent); }

.composer-library-filters {
  display: flex;
  gap: 4px;
  padding: 0 14px 8px;
  flex-wrap: wrap;
}
/* Genre row reads as its own section with hairline rules above
   and below it — flanks the chip cluster so it visually sits
   apart from the All/Queued/Builder row above and the book list
   below. */
.composer-library-filters-genre {
  padding: 8px 14px;
  border-top: 1px solid var(--bg-rule);
  border-bottom: 1px solid var(--bg-rule);
  margin-bottom: 4px;
}

/* Composer Library sort row. Inherits .lib-filter-row chrome (gap,
   bottom hairline) but uses the composer pane's 14px gutter so it
   aligns with the search input + filter chips above. The bottom
   hairline doubles as the divider between the filter chrome and
   the book list. */
.composer-library-filters-sort {
  padding: 4px 14px 8px;
  margin-bottom: 4px;
}

.composer-library-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0;
}
/* Library card chrome mirrors the Builder entry card exactly so a
   work looks identical whether it lives in the Library or in the
   Builder — same border, padding, background, shadow. Dragging
   between columns reads as a single object moving rather than
   transforming. */
.composer-library-card {
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 8px 10px 10px;
  /* Horizontal margin sized to land at the same column inset as
     a Builder entry: 10px section-card margin + 1px section
     border + 8px section-body padding = 19px each side. With
     both columns at 240px (.composer-library / .composer-builder),
     library cards and builder entries both occupy 202px of width
     — so dragging from Library to Builder doesn't visually
     resize the chip. */
  margin: 0 19px 6px;
  position: relative;
  display: flex;
  gap: 10px;
  align-items: flex-start;
  cursor: grab;
  transition: opacity 120ms, box-shadow 100ms;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
}
/* When a library card moves into a section card body mid-drag,
   reset its horizontal margins to 0 so the inset is provided by
   the section body's 8px padding alone — same way a real
   .composer-builder-entry sits. Without this the 19px library
   margin would compound with the section body's padding and the
   dragged chip would shrink as it crossed columns. */
.composer-section-card-body .composer-library-card {
  margin-left: 0;
  margin-right: 0;
}
/* Symmetric: when a builder entry moves into the library list
   mid-drag (the user pulling a book OUT of the memo), adopt the
   library card's 19px horizontal gutter so its width matches the
   surrounding library cards. Without this the entry sits flush at
   0px while neighbours sit at 19px — SortableJS's FLIP animation
   sees the width mismatch, recalculates positions, and the other
   library cards visibly jump left to align with the new element's
   wider footprint. */
.composer-library-list .composer-builder-entry {
  margin-left: 19px;
  margin-right: 19px;
}
.composer-library-card:hover  { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06); }
.composer-library-card:active { cursor: grabbing; }
.composer-library-card.dragging { opacity: 0.4; }
.composer-library-card.in-memo {
  /* Only visible when the user explicitly clicks the Builder
     filter — otherwise in-memo books are hidden from the list.
     Keep them slightly recessed when surfaced so they read as
     "already placed" reference cards. */
  opacity: 0.55;
  cursor: default;
}
.composer-library-card.in-memo:hover { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); }

/* Library list still accepts entry-card drops (drop = remove
   from Builder), but we don't surface a visual cue for it — the
   drag itself is enough affordance, and the dashed outline read
   as too alarming on what's a benign undo. */
.composer-library-card-thumb {
  width: 28px;
  height: 40px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 2px;
  flex-shrink: 0;
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 14px;
  color: var(--ink-faint);
}
.composer-library-card-text { flex: 1; min-width: 0; }
.composer-library-card-title {
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.composer-library-card-author {
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.composer-library-card .check {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  color: var(--accent);
  font-size: 14px;
}

/* Builder — section card stack. The column is a fixed 240px so
   cards just fill it; their own `margin: 0 10px 10px` provides the
   side gutters. Earlier centering / max-width hooks were for when
   the column was user-resizable; with that gone, plain block flow
   gives the right padding. */
.composer-builder-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0;
}

/* Section cards — top-level draggable container in the Builder.
   Header strip carries drag handle / inline-edit name / × delete.
   Body holds entry cards or an "empty section" placeholder. */
.composer-section-card {
  background: var(--bg);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  margin: 0 10px 10px;
  overflow: hidden;
  transition: opacity 120ms, border-color 100ms;
}
/* SortableJS chosen-class on the card the user is currently
   dragging — slight desaturation so the original-position
   placeholder visually recedes behind the floating clone. */
.composer-section-card.dragging { opacity: 0.4; }

.composer-section-card-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
  background: var(--bg-hover);
  border-bottom: 1px solid var(--bg-rule);
  /* Whole header is the Sortable drag handle — show the grab
     cursor so the affordance is discoverable. The inline-edit
     text and × button override with their own cursors. */
  cursor: grab;
}
.composer-section-card-header:active { cursor: grabbing; }

.composer-section-header.inline-edit-view,
.composer-section-header.inline-edit-input,
.composer-section-header-static {
  font-family: var(--sans);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink);
  font-weight: 600;
  flex: 1;
}

.composer-section-card-actions {
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 100ms;
}
.composer-section-card:hover .composer-section-card-actions { opacity: 1; }

.composer-section-remove {
  background: none;
  border: 1px solid transparent;
  border-radius: 3px;
  padding: 2px 6px;
  font-size: 12px;
  color: var(--ink-faint);
  cursor: pointer;
  line-height: 1;
}
.composer-section-remove:hover { color: var(--accent); border-color: var(--accent); }

.composer-section-card-body {
  padding: 8px;
  min-height: 36px;
}

.composer-builder-empty {
  font-family: var(--serif);
  font-style: italic;
  font-size: 12px;
  color: var(--ink-faint);
  padding: 6px 4px;
}

/* Drag handle — six dots, hover-only visible. Used on both
   section cards and entry cards. */
.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 16px;
  cursor: grab;
  color: var(--ink-faint);
  font-size: 13px;
  line-height: 1;
  letter-spacing: -3px;
  user-select: none;
  flex-shrink: 0;
  opacity: 0.25;
  transition: opacity 100ms, color 100ms;
  background: none;
  border: none;
  padding: 0;
}
.drag-handle::before { content: '⋮⋮'; }
.composer-section-card:hover .drag-handle,
.composer-builder-entry:hover .drag-handle { opacity: 1; }
.drag-handle:hover { color: var(--ink-soft); }
.drag-handle:active { cursor: grabbing; }
/* Smaller, slightly more recessed handle on entry cards so the
   visual hierarchy reads section > entry. */
.composer-builder-entry .drag-handle { font-size: 11px; }

/* Entry cards — slightly inset under section body. */
.composer-builder-entry {
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 8px 10px 10px;
  margin-bottom: 6px;
  position: relative;
  transition: opacity 120ms, box-shadow 100ms;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
  /* Whole card is grabbable. SortableJS handles the actual drag
     activation; we just signal the affordance with the cursor. */
  cursor: grab;
}
.composer-builder-entry:hover { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06); }
.composer-builder-entry:active { cursor: grabbing; }
/* SortableJS chosenClass on the source card (stays in place,
   recedes); the floating clone is what the user actually drags. */
.composer-builder-entry.dragging { opacity: 0.4; }
/* SortableJS ghostClass on the placeholder occupying the future
   landing slot. We let it lay out at full size (so neighbouring
   cards animate around it via Sortable's `animation: 150ms`),
   but render it transparent so visually only the floating clone
   reads as live. */
.composer-builder-entry.drag-ghost {
  opacity: 0;
}
/* Library cards share the floating-clone treatment when dragged
   into a section, plus the same ghost-placeholder rule. */
.composer-library-card.dragging  { opacity: 0.4; }
.composer-library-card.drag-ghost { opacity: 0; }

/* Top row of an entry mirrors the Library card visual rhythm:
   small cover thumb on the left, title + author stacked on the
   right, × pinned to the corner. The scout note sits below in
   its own row. */
.composer-builder-entry-top {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  position: relative;
}
.composer-builder-entry-top .composer-library-card-text { padding-right: 22px; }

.composer-entry-stage.inline-edit-view,
.composer-entry-stage.inline-edit-input {
  font-family: var(--sans);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
}
.composer-entry-title {
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
}
.composer-entry-author {
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  margin-top: 2px;
}
.composer-entry-actions {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 100ms;
}
.composer-builder-entry:hover .composer-entry-actions { opacity: 1; }

/* The remove (×) button now lives inside the entry top-row directly
   (no .composer-entry-actions wrapper). Position it in the corner
   so it doesn't push the title block down. */
.composer-builder-entry-top > .composer-entry-action {
  position: absolute;
  top: 0;
  right: 0;
  opacity: 0;
  transition: opacity 100ms;
}
.composer-builder-entry:hover .composer-builder-entry-top > .composer-entry-action { opacity: 1; }
.composer-entry-action {
  background: var(--bg);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  width: 20px;
  height: 20px;
  font-size: 10px;
  color: var(--ink-muted);
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.composer-entry-action:hover { color: var(--ink); border-color: var(--ink-muted); }
.composer-entry-action.danger:hover { color: var(--accent); border-color: var(--accent); }

.composer-entry-scout.inline-edit-view,
.composer-entry-scout.inline-edit-input {
  margin-top: 6px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 11px;
  color: var(--ink-soft);
  line-height: 1.4;
}

.composer-add-section {
  margin: 8px 14px 14px;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--accent);
  padding: 5px 10px;
  border: 1px dashed var(--bg-rule);
  border-radius: 3px;
  background: transparent;
  cursor: pointer;
  transition: all 100ms;
  width: calc(100% - 28px);
}
.composer-add-section:hover {
  border-color: var(--accent);
  background: var(--accent-bg);
}

/* A18 — "+ Add Unworthy" sits underneath "+ Add section" with a
   softer treatment: the unworthy section is a personal-history
   shelf, not a regular section. Slightly tighter top margin so
   the two add-buttons read as a paired stack. */
.composer-add-section--unworthy {
  margin-top: 0;
  color: var(--ink-soft);
  border-style: dashed;
  border-color: var(--bg-rule-soft, var(--bg-rule));
}
.composer-add-section--unworthy:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--accent-bg);
}

/* A18 — Unworthy section card in the Builder. The treatment is
   muted/recessed (NOT a warning) — its contents are deliberately
   set aside, not in error. We push the card and its header strip
   toward neutral grey via `color-mix` against the theme's cream
   surface tokens, plus a touch of opacity for a recessed feel.
   The mix preserves enough theme character that the card still
   feels native, but desaturates enough that the difference
   against active section cards is unmistakable at a glance.

   No accent / red / orange — those would signal "warning" which
   is the wrong semantics here. The active section cards stay
   exactly as they are; only the `--unworthy` modifier alters
   their treatment. Works uniformly across light + dark themes
   because color-mix shifts the theme's own bg tokens, not a
   hard-coded color.

   Browser support note: color-mix in CSS is supported in every
   evergreen browser at this point (Chrome 111+, Safari 16.4+,
   Firefox 113+). Galley targets modern browsers; no fallback
   required. */
.composer-section-card--unworthy {
  background: color-mix(in srgb, var(--bg) 65%, #888 35%);
  border-color: var(--bg-rule);
  opacity: 0.82;
}
.composer-section-card-header--unworthy {
  /* No drag handle inside; tighten left padding so the inline-edit
     name lines up with regular sections (where the drag handle
     takes its own gutter). Header strip steps back a notch
     further than the card body via a slightly heavier grey mix. */
  padding-left: 14px;
  background: color-mix(in srgb, var(--bg-hover) 60%, #888 40%);
  border-bottom-color: var(--bg-rule);
}
.composer-section-header--unworthy,
.composer-section-header.composer-section-header--unworthy {
  color: var(--ink-soft);
  font-style: italic;
}

/* A18 — Unworthy section header in the memo PREVIEW. Matches the
   Builder card's muted register so the preview reads
   "this won't be sent" without breaking the black-border boxed
   header rhythm. The export renderers (Gmail HTML / PDF /
   plain-text) drop this section entirely, so this styling only
   ever ships to Robby's own view. */
.memo-render-section-header--unworthy {
  color: var(--ink-soft);
  font-style: italic;
  border-color: var(--bg-rule) !important;
  background: color-mix(in srgb, var(--memo-paper, #fff) 65%, #888 35%);
}

/* ============================================================
   MEMO PREVIEW — matches mockups/gmail-body-exact.html so the
   in-app preview shows exactly what recipients will see when
   the memo is pasted into Gmail. Sans-serif (Calibri / Helvetica
   Neue / Arial) on white, boxed black-bordered section headers,
   red italic stage prefixes, bold-underlined ALL-CAPS book
   titles, dense scanning rhythm. The elevated EB Garamond look
   lives only in the PDF attachment renderer.
   ============================================================ */
.composer-preview-pad {
  padding: 18px;
}
.memo-render {
  padding: 36px 44px 44px;
  font-family: Calibri, 'Helvetica Neue', Arial, sans-serif;
  color: #000;
  /* 14.5px ≈ 11pt — bumped from 13.5px (≈ 10pt) per Robby's ask.
     Kept in lock-step with GB_BODY in views/memo-section.js so this
     on-screen preview stays a 1:1 WYSIWYG of the pasted Gmail body. */
  font-size: 14.5px;
  line-height: 1.42;
  background: #ffffff;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  max-width: 720px;
  /* Center the memo card within whatever pane is hosting it
     (the standalone Preview view, the composer's preview column,
     or the read-only memo-shell preview). */
  margin: 0 auto;
}
.memo-render * { color: inherit; }
.memo-render-header {
  /* Doc title + date both centered, no margin between, 18px gap
     before the first section. Matches the mockup's masthead. */
  margin-bottom: 0;
}
.memo-render-title-line {
  font-weight: 700;
  font-size: 16px;
  text-align: center;
  margin: 0;
}
/* Custom-memo inline-edit field nested inside the title line.
   The shared inline-edit-input quiet-frame already strips border
   and background; here we make sure the input also inherits the
   bold + 16px size + centered alignment of the title line, so the
   view-mode "ROC Literary <title>" reads as a single uninterrupted
   centered heading (no font drop, no left-align jump on focus).

   Inline-flow override across all three layers — wrapper, view,
   input — so "ROC Literary " (text node) + the editable suffix sit
   on the same line together. The base .inline-edit is a div
   (display: block) with width:100%; .inline-edit-view is also a
   div (display: block). Without all three flipped to inline-block,
   the wrapper or view forces the suffix onto its own line in
   view mode. */
.memo-render-title-line .inline-edit,
.memo-render-title-line .memo-render-title-editable.inline-edit-view,
.memo-render-title-line .memo-render-title-editable.inline-edit-input {
  display: inline-block;
  width: auto;
  vertical-align: baseline;
}
.memo-render-title-line .memo-render-title-editable.inline-edit-view,
.memo-render-title-line .memo-render-title-editable.inline-edit-input {
  font-weight: 700;
  font-size: 16px;
}
.memo-render-date {
  color: #000;
  font-weight: 700;
  font-size: 16px;
  text-align: center;
  margin: 0 0 18px;
}
/* Empty-state "+ add date" affordance — only shown in Builder
   mode (readonly=false) when memo.date is blank. Matches the
   "+ add subtitle" register: small sans-serif, slate-faint, no
   bold/uppercase, so it reads as an editor affordance, not as
   the rendered heading the populated date paints as. */
.memo-render-date.memo-render-editable.empty {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 11px;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  text-transform: none;
}
/* Builder-mode editable date — the wrapper is a block element
   that fills the memo-render-header width and centers its inline
   children. Inside: a styled <label> (inline-block, looks like
   the formatted date "May 13, 2026") + a native <input type=
   "date"> overlaid absolutely on top of the label. Default state
   shows the label; focusing the input (via label-forwarding or
   tab) fades the label out and reveals the segmented MM/DD/YYYY
   input + calendar dropdown. Blurring flips back. */
.memo-render-date-wrap {
  position: relative;
  display: block;
  width: 100%;
  margin: 0 0 18px;
  text-align: center;
}

.memo-render-date-wrap .memo-render-date {
  /* Label: visually identical to the original display-only
     .memo-render-date — same font, weight, size, color (via
     .memo-render-date base rule above). Inline-block + parent's
     `text-align: center` is what actually horizontally centers
     it inside the wrap. The earlier inline-grid attempt made the
     wrap itself inline, which dropped centering. */
  display: inline-block;
  margin: 0;
  padding: 1px 6px;
  border-radius: 3px;
  cursor: pointer;
  transition: background 100ms, opacity 100ms;
}
.memo-render-date-wrap .memo-render-date:hover {
  background: rgba(0, 0, 0, 0.05);
}

/* Input: absolutely positioned, centered horizontally over the
   label via left:50% + translateX(-50%). Out of normal flow so
   it doesn't affect the wrap's size — the wrap sizes to the
   label, and the input overlays where the label sits. Hidden
   by default; revealed on :focus-within. */
.memo-render-date-input {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  font-family: Calibri, 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: #000;
  text-align: center;
  background: var(--bg-card);
  border: 1px solid var(--accent);
  border-radius: 3px;
  padding: 0 4px;
  margin: 0;
  outline: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 100ms;
  width: auto;
  min-width: 11ch;
}
.memo-render-date-wrap:focus-within .memo-render-date-input {
  opacity: 1;
  pointer-events: auto;
}
.memo-render-date-wrap:focus-within .memo-render-date {
  opacity: 0;
  pointer-events: none;
}
.memo-render-section-header {
  font-style: italic;
  font-weight: 700;
  /* 14.5px ≈ 11pt — matched to body so report typography is uniform.
     Kept in lock-step with GB_SECTION in views/memo-section.js so the
     on-screen preview stays a 1:1 WYSIWYG of the pasted Gmail body. */
  font-size: 14.5px;
  text-transform: uppercase;
  /* Boxed black border on all four sides — 1.5px solid #000,
     tight 4/10 padding, no fill. Replaces the previous slate-
     tinted shelf treatment. */
  border: 1.5px solid #000;
  background: transparent;
  padding: 4px 10px;
  margin: 18px 0 16px;
  letter-spacing: 0;
}
.memo-render-entry { margin-bottom: 22px; }
.memo-render-entry-title { font-weight: 700; margin: 0; }
.memo-render-entry-title .title-caps,
.memo-render-entry-title .subtitle,
.memo-render-entry-title .series-tag { font-weight: 700; }

/* Empty-state placeholder for the subtitle. Reads as a muted
   preview of what the field will look like once filled — bold
   serif (inherited from .memo-render-entry-title + .subtitle)
   in the faint ink color, NOT italic. Matches the edit-mode
   input's placeholder appearance so view ↔ edit transitions
   don't visually re-shape the field. (Earlier this rule forced
   sans 11px non-bold italic to read as a small affordance
   button; the user feedback was that the affordance and the
   edit-mode input looked too different on click-in.) */
.memo-render-entry-title .memo-render-editable.subtitle.empty {
  color: var(--ink-faint);
  font-style: normal;
}
/* Book title — UPPERCASE + bold + underline. Both view AND
   input carry `.title-caps` (via matching viewClass + inputClass
   on the inlineText call in memo-composer.js), so this rule
   fires for both modes and the edit input renders identically
   to the view. `text-transform: uppercase` is the visual layer:
   stored `book.title` keeps the user's typed casing, CSS paints
   it uppercase on render.

   A14 — the bold + underline now extends across the whole
   byline ("TITLE by Author") as a single continuous run.
   Adjacent inline spans with `text-decoration: underline`
   render as one visually continuous underline (the underline
   is drawn per-span but the spans share a baseline, so the
   result is unbroken across the line). Each piece carries
   its own underline declaration below so the merge holds
   even when the title is wrapped in an `<a>` (the link still
   draws its own underline; the byline's continues alongside). */
.memo-render-entry-title .title-caps {
  /* R-S7 — uppercasing happens in JS (entryTitleCaps) for the VIEW
     span / read-only link span so "fka" can stay lowercase, which a
     CSS text-transform can't do per-word. The edit INPUT keeps CSS
     uppercase below (.inline-edit-input.title-caps) for WYSIWYG. */
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
/* Subtitle — same uppercase + bold + underline treatment as
   the title so "TITLE: SUBTITLE" reads as one continuous
   heading unit (matches Gmail / PDF / plain-text exports).
   font-weight is already 700 via the grouped rule above; this
   adds the text-transform + text-decoration. */
.memo-render-entry-title .subtitle {
  /* R-S7 — uppercasing in JS (entryTitleCaps); edit input keeps CSS
     uppercase below. */
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
/* Series notation — "[NEW WORLD TRILOGY SERIES BOOK 1]". Carries the
   SAME underline as the title / subtitle / byline so the heading reads
   as ONE continuous underlined run ("TITLE [SERIES] by Author"),
   matching the Gmail HTML (series rides in the GB_BOOKT run) and PDF.
   Previously had only font-weight, leaving a bare un-underlined gap
   mid-heading in the Builder preview. */
.memo-render-entry-title .series-tag {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
/* R-S7 — the edit INPUT keeps CSS uppercase so typing is WYSIWYG; the
   view + read-only link spans are JS-cased (entryTitleCaps) so "fka"
   survives as a lowercase connector. */
.memo-render-entry-title .inline-edit-input.title-caps,
.memo-render-entry-title .inline-edit-input.subtitle {
  text-transform: uppercase;
}
/* Empty-state subtitle (no value yet) — strip the uppercase +
   underline so the editor's "+ subtitle" / "subtitle"
   placeholder hint reads as an affordance, not as a missing
   piece of rendered output. Pairs with the muted color rule
   further down. */
.memo-render-entry-title .memo-render-editable.subtitle.empty {
  text-transform: none;
  text-decoration: none;
}
/* Colon + space separator between title and subtitle — wrapped
   in its own span so it inherits the heading row's bold and
   carries the same underline as the title/subtitle on either
   side, producing one continuous "TITLE: SUBTITLE" underline.
   Mirrors the `.by` separator below. */
.memo-render-entry-title .memo-render-entry-title-subtitle-sep {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
/* A14 — the connector word and the author chip now match the
   title's bold + underline so the whole heading reads as one
   unit. Was font-weight: 400 / no decoration prior to A14. */
.memo-render-entry-title .by {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

/* Stage prefix chip — editor affordance shown inline before
   the entry title in the live Preview. The populated chip
   mirrors the rendered output's red italic bold ALL CAPS
   appearance (so what the user sees while editing is what
   recipients will read), with chip chrome (thin border, ×
   on hover) communicating that it's interactive. The chrome
   is preview-only — clipboard + PDF renderers work from data
   and produce plain typography with no chrome. */
.memo-render-stage-add {
  /* Empty state: dashed slate-soft "+ Stage" — sized down so it
     reads as an editor affordance, not part of the document
     scale. Once the user picks a stage prefix the chip flips
     to its populated form (.memo-render-stage-chip) which DOES
     match the title's font-size, so the user sees the actual
     output scale the moment something is selected. The "add"
     affordance staying small keeps the empty entry-title row
     visually quiet. */
  vertical-align: 1px;
  height: 16px;
  padding: 0 6px;
  font-size: 10px;
  letter-spacing: 0.04em;
  line-height: 1;
}
.memo-render-stage-add .caret { font-size: 8px; }
.memo-render-stage-chip {
  display: inline-flex;
  align-items: baseline;
  vertical-align: baseline;
  padding: 0 2px 0 4px;
  background: transparent;
  border: 1px solid rgba(200, 16, 46, 0.35);
  border-radius: 2px;
  line-height: 1.2;
  margin-right: 1px;
  /* Inherit font-size from the entry title row (13.5px via
     .memo-render) instead of .lib-filter-chip's 11px chrome
     baseline. Height auto so the chip sizes to its label. */
  font-size: inherit;
  height: auto;
}
.memo-render-stage-chip .memo-render-stage-chip-label {
  /* Mirror the output's appearance: red italic bold ALL CAPS at
     the entry title's font-size (font-size inherits via
     .memo-render's base 14.5px). formatStagePrefixDisplay already
     uppercases the text. */
  font-style: italic;
  font-weight: 700;
  color: #c8102e;
  letter-spacing: 0.02em;
  cursor: pointer;
  padding: 0 2px;
}
.memo-render-stage-chip .lib-filter-chip-remove {
  /* X is hidden by default; appears on chip hover so the title
     line stays clean under normal scanning. */
  opacity: 0;
  font-size: 12px;
  height: 14px;
  padding: 0 3px;
  transition: opacity 100ms;
}
.memo-render-stage-chip:hover .lib-filter-chip-remove { opacity: 0.75; }
.memo-render-stage-chip:hover .lib-filter-chip-remove:hover { opacity: 1; }
/* Separator between stage chips — matches the chip label's
   red italic bold so the cluster reads as one continuous
   "STAGE / STAGE" prefix in the editor preview, not two
   bracketed pills with a default-ink slash. */
.memo-render-stage-sep {
  font-style: italic;
  font-weight: 700;
  color: #c8102e;
  letter-spacing: 0.02em;
}

/* Stage-prefix popover Done button (A5) — appears at the bottom
   of the multi-select picker as an explicit commit + close
   affordance. Click-away also commits via the same code path;
   the button is for mouse users who'd rather end the interaction
   explicitly than click outside the panel. Visually a quiet
   slate footer row — flush with the popover edges, full width,
   thin top border to separate it from the list above. */
.classify-popover-done {
  appearance: none;
  display: block;
  width: 100%;
  padding: 8px 12px;
  background: var(--bg-card);
  border: none;
  border-top: 1px solid var(--bg-rule);
  text-align: center;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  cursor: pointer;
  transition: background 80ms ease, color 80ms ease;
}
.classify-popover-done:hover {
  background: var(--bg-hover);
  color: var(--ink);
}

/* Read-only preview (memo section's right pane when a sent
   memo is selected) — hide the empty "+ Stage" affordance
   entirely and disable interactions on a populated chip. The
   populated chip's label is real rendered content, so it stays
   visible; only its click affordance is suppressed. */
.memo-render-host-readonly .memo-render-stage-add { display: none; }
.memo-render-host-readonly .memo-render-stage-chip,
.memo-render-host-readonly .memo-render-stage-chip * {
  pointer-events: none;
  cursor: default;
}
.memo-render-host-readonly .memo-render-stage-chip {
  border-color: transparent;
  padding-left: 0;
}
.memo-render-host-readonly .memo-render-stage-chip .lib-filter-chip-remove {
  display: none;
}
.memo-render-entry-meta {
  /* Tight meta-line spacing within an entry — matches the dense
     scanning rhythm of Robby's existing Word-doc memos. */
  margin: 0;
}
.memo-render-entry-meta strong { font-weight: 700; }
.memo-render-synopsis {
  margin: 0 0 12px;
}
.memo-render-synopsis .pitch-tag {
  font-weight: 700;
  text-transform: uppercase;
  font-style: normal;
  color: inherit;
}
.memo-render-notes,
.memo-render-reader-notes {
  margin: 0 0 12px;
}
.memo-render-notes strong,
.memo-render-reader-notes strong { font-weight: 700; }

/* A7 — collapse / expand caret button used inline before each
   note field's label in the Builder editor preview. Mirrors
   the column-stack `.cw-nav-btn` visual vocabulary (transparent
   fill, 1px rule border, slate-soft glyph, hover lifts to ink +
   bg-hover fill) at a smaller entry-level size. The glyph is a
   Unicode triangle (▾ expanded / ▸ collapsed); the same
   character is used everywhere — only the JS swaps it on click.

   Disclosure pattern: ▾ = expanded (field drafts into memo),
   ▸ = collapsed (field hidden; a single-line stub renders to
   the right of the caret as a Builder-only affordance). */
.memo-render-collapse-caret {
  appearance: none;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 50%;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 100ms, border-color 100ms, background 100ms;
}
.memo-render-collapse-caret:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--bg-hover);
}

/* Editor-only: hang the caret off the row's left edge into the
   existing `.memo-render` 44px horizontal padding, so the
   "Notes:" / "Reader Notes:" labels stay aligned with the rest
   of the entry content (Pitch tag, synopsis, etc.) and the
   caret lives in the marginal whitespace to the left — same
   positioning the prior pill toggle used.

   Scoped via the OUTER `.composer-preview` because that's where
   `.memo-render-host-readonly` is added by memo-section.js +
   memo-detail.js (the inner `.memo-render` doesn't carry the
   class). Read-only preview surfaces never render a caret, so
   the positioning rule never fires there either. */
.composer-preview:not(.memo-render-host-readonly) .memo-render-notes,
.composer-preview:not(.memo-render-host-readonly) .memo-render-reader-notes,
/* Collapse carats also hang off the metadata rows (Lit Agent,
   Film/TV Agent, Publisher, Material, Reader Report) and the
   synopsis row (its caret toggles the Pitch Tag lead-in) — same
   per-entry caret affordance, same left-margin position. */
.composer-preview:not(.memo-render-host-readonly) .memo-render-entry-meta,
.composer-preview:not(.memo-render-host-readonly) .memo-render-synopsis {
  position: relative;
}
.composer-preview:not(.memo-render-host-readonly) .memo-render-notes > .memo-render-collapse-caret,
.composer-preview:not(.memo-render-host-readonly) .memo-render-reader-notes > .memo-render-collapse-caret,
.composer-preview:not(.memo-render-host-readonly) .memo-render-entry-meta > .memo-render-collapse-caret,
.composer-preview:not(.memo-render-host-readonly) .memo-render-synopsis > .memo-render-collapse-caret {
  position: absolute;
  /* -(18px caret + 8px breathing gap) — the caret's right edge
     sits ~8px to the left of the row's content, with the rest
     hanging into the `.memo-render` 44px left padding. */
  left: -26px;
  top: 1px;
}

/* A15 — per-heading bold-override button. Same circular
   chrome as the A7 collapse caret above: 18px circle,
   transparent fill, 1px `--bg-rule` border, hover lifts to
   ink + bg-hover. The glyph is a serif letter ("A" / "B" /
   "b") that reflects the current override state. Click
   cycles the state (null → 'bold' → 'unbold' → null) and
   the JS swaps the glyph + class on re-render. */
.memo-render-heading-bold-override {
  appearance: none;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 50%;
  color: var(--ink-soft);
  font-family: var(--serif);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 100ms, border-color 100ms, background 100ms;
}
.memo-render-heading-bold-override:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--bg-hover);
}
.memo-render-heading-bold-override.is-forced-bold {
  color: var(--ink);
  font-weight: 700;
}
.memo-render-heading-bold-override.is-forced-unbold {
  color: var(--ink);
  font-weight: 300;
}
.memo-render-heading-bold-override.is-auto {
  color: var(--ink-soft);
  font-weight: 400;
}

/* Editor-only: hang the override button in the entry-heading
   row's left margin, same way the collapse caret hangs in the
   Notes / Reader Notes row margins above. */
.composer-preview:not(.memo-render-host-readonly) .memo-render-entry-title {
  position: relative;
}
.composer-preview:not(.memo-render-host-readonly) .memo-render-entry-title > .memo-render-heading-bold-override {
  position: absolute;
  left: -26px;
  top: 1px;
}

/* Collapsed stub — the inline "— hidden, click to expand"
   marker that replaces the rich-text editor when the field
   is collapsed. Muted italic so it reads as an editor
   annotation, not part of the rendered memo. Never appears
   in any export path (all four render sites skip a disabled
   field entirely; the stub is Builder-only chrome). */
.memo-render-field-stub {
  color: var(--ink-muted);
  font-style: italic;
  font-size: 0.92em;
}

/* When collapsed, dim the field's strong label too so the
   user can see at a glance which fields are suppressed from
   the exported memo. */
.memo-render-field-collapsed strong {
  opacity: 0.55;
}

/* Collapsed Pitch Tag preview — the pitch tag (or a "Pitch tag"
   placeholder) shown greyed in place of a blank stub, so a
   collapsed pitch-tag/synopsis row reads like the other collapsed
   fields (whose labels survive). Matches their font color exactly:
   inherited ink at 0.55 opacity, same as `.memo-render-field-collapsed
   strong` (the dimmed "Reader Report:" label). The .pitch-tag class
   already supplies bold + uppercase + inherit color. Editor-only
   chrome — never exported. */
.memo-render-pitch-collapsed-preview {
  opacity: 0.55;
}
.memo-render-scout-note {
  margin: 0 0 12px;
  font-style: italic;
  color: #555;
}
.memo-render-empty {
  font-style: italic;
  color: #777;
  padding: 6px 0;
}

/* ----- Custom sections (per-entry freeform rich-text blocks) -----
   No label, no border, no chrome — the content renders as prose,
   same register as Notes/synopsis. Editor mode adds a hover × at the
   block's top-right (parallel to the chip × delete pattern) and a
   gutter "+" add affordance below (parallel to the field carats). */
.memo-render-custom-section {
  position: relative;
  /* Same vertical rhythm as Notes / Reader Notes / synopsis / scout
     note: no top margin (hug the block above, or the title when this
     is the only block), 12px below. */
  margin: 0 0 12px;
}
/* Editor-only: hang the per-section collapse caret off the block's
   left edge into the `.memo-render` left padding — same gutter the
   field carats and the "+" add button use. The block is already
   position:relative for the × button. */
.composer-preview:not(.memo-render-host-readonly) .memo-render-custom-section > .memo-render-collapse-caret {
  position: absolute;
  left: -26px;
  top: 1px;
}
/* Reserve a right gutter so the hover × (top-right, absolute) sits to
   the right of the text instead of overlapping it once the editor
   wraps to full width. Editor-only — the readonly render hides the ×
   and keeps the content full width. */
.composer-preview:not(.memo-render-host-readonly) .memo-render-custom-section {
  padding-right: 20px;
}
/* Collapsed custom section — a greyed snippet of its content (or
   "Custom section") shown in place of the editor, dimmed to the same
   0.55 tone as the other collapsed labels. */
.memo-render-custom-collapsed-preview {
  opacity: 0.55;
}
.memo-render-custom-section-remove {
  position: absolute;
  top: 0;
  right: 0;
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-faint);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, color 100ms;
}
.memo-render-custom-section:hover .memo-render-custom-section-remove { opacity: 1; }
.memo-render-custom-section-remove:hover { color: #c8102e; }
/* Read-only surfaces never render the × (defensive — not built there). */
.memo-render-host-readonly .memo-render-custom-section-remove { display: none; }

.memo-render-custom-add-row {
  position: relative;
  margin: 12px 0 0;
  min-height: 18px;
  display: flex;
  align-items: center;
}
/* "+" circle — same chrome as the collapse carats. */
.memo-render-add-custom-section {
  appearance: none;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 50%;
  color: var(--ink-soft);
  /* Sans font: the serif "+" glyph sits low/left in its em box, so it
     reads off-center inside the circle. The sans "+" centers cleanly
     with the flex centering below. */
  font-family: var(--sans);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 100ms, border-color 100ms, background 100ms;
}
.memo-render-add-custom-section:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--bg-hover);
}
.memo-render-custom-add-hint {
  font-size: 11px;
  color: var(--ink-faint);
}
/* Editor-only: hang the "+" off the row's left edge into the
   `.memo-render` left padding, the same gutter the carats use; the
   "Add section" hint then sits at the content-left edge. */
.composer-preview:not(.memo-render-host-readonly) .memo-render-custom-add-row {
  padding-left: 0;
}
.composer-preview:not(.memo-render-host-readonly) .memo-render-custom-add-row > .memo-render-add-custom-section {
  position: absolute;
  left: -26px;
  top: 0;
}

/* Inline-edit overrides for the trade-doc preview. The hover
   affordance is very subtle so the preview still reads as a
   clean memo. */
.memo-render .inline-edit { display: inline; width: auto; }
.memo-render .inline-edit-view.memo-render-editable {
  display: inline;
  padding: 1px 3px;
  margin: -1px -3px;
  border-radius: 2px;
  cursor: text;
  background: transparent;
  color: inherit;
  white-space: pre-wrap;
  transition: background 100ms;
}
.memo-render .inline-edit-view.memo-render-editable:hover {
  background: rgba(0, 0, 0, 0.05);
}
/* B7d note: the previous three-anchor override block that lived
   here — `.composer-header / .composer-builder / .memo-render
   .inline-edit-input` setting `border:0; background:transparent;
   width:auto; field-sizing:content; min-width:4ch; border-radius:
   2px` — has been DELETED. The base `.inline-edit-input` rule
   (styles.css ~line 3125) now carries the same quiet-frame
   defaults app-wide, so this composer-scoped duplicate is
   redundant. Same outcome inside the Memo Builder, plus the
   same outcome on Work/Person/Company profile pages. */

/* In `.memo-render` the view-mode element's padding/margin are
   overridden to `1px 3px / -1px -3px` (see
   `.memo-render .inline-edit-view.memo-render-editable` rule
   above — same rule that makes the richText editor padding 1/3).
   The input needs to match that padding exactly so the box
   occupied by the input is identical to the box occupied by the
   view, AND so the outline-on-focus around the input is the same
   size as the outline around the richText editors (Synopsis,
   Notes, Reader Notes) right next to it in the same row. Base
   padding `1px 4px` would make this input's outline 2px wider on
   each side than the synopsis editor's — visibly "heavier" in
   side-by-side comparison.

   `display: inline` + `box-sizing: content-box` keep the input
   flowing inline with surrounding text (so it sits where the
   view-mode span sat) rather than as a block. These were
   inherited from the pre-existing `.memo-render .inline-edit-
   input` rule we consolidated below at line ~6590; merged here
   to avoid a duplicate selector with conflicting border/bg. */
.memo-render .inline-edit-input {
  padding: 1px 3px;
  margin: -1px -3px;
  display: inline;
  box-sizing: content-box;
}

/* Unified edit-mode affordance for every text-editing field in
   the Memo Builder — richText editors AND inlineText inputs.
   One quiet signal around the whole field, no layout shift, no
   per-line fragmentation. Targets:
     • richText editors via `.memo-render .memo-render-editable`
       (the contenteditable DIV is the sole focusable element
       carrying that class — view-mode divs aren't focusable, and
       inlineText inputs don't carry the class).
     • inlineText inputs via `.composer-header .inline-edit-input`,
       `.composer-builder .inline-edit-input`, and
       `.memo-render .inline-edit-input` (their `<input>` elements
       receive focus directly; the suppression rule above clears
       the default accent-soft frame so the outline reads as the
       sole signal).

   Why `outline` rather than background/border/box-shadow. The
   richText editor is display:inline by design here (so prose
   flows after the pitch tag and the "Notes:" prefix without
   breaking the row). Background, box-shadow, and border all
   fragment across each wrapped line box on inline elements —
   that produced the stacked-cells look in earlier attempts. We
   tried switching to display:block on focus and that fixed
   fragmentation but caused a visible reflow jump each time the
   user clicked into / out of a field. `outline` is the one CSS
   property that (a) doesn't participate in the box model so it
   causes NO reflow, and (b) in modern Chrome/Firefox/Safari
   draws ONE outline around the union of an inline element's
   line boxes rather than stacking a rectangle per line. The
   combination — no layout shift, one continuous signal — is the
   right fit for both editor types in the Memo Builder.

   `outline-offset: 2px` keeps the outline off the text glyphs.
   `border-radius: 2px` softens the outline's corners (modern
   browsers respect border-radius on outline).

   Out of scope (intentionally untouched by this rule):
     • `.memo-render-date-input` — the builder-mode native date
       input keeps its own `--accent` border + reveal pattern.
     • `.composer-library-search` — filter input, has its own
       `--accent` focus border.
     • Popover-triggered fields (Publisher, Material, Reader
       Report, Lit Agent, Film/TV Agent, Stage prefix) — their
       triggers aren't text editors and they commit via popover.
     • inlineText `<input>` focus — covered by the base
       `.inline-edit-input:focus` rule (styles.css ~line 3158)
       app-wide as of B7d; the previous composer-scoped branches
       were folded into that base. */
.memo-render .memo-render-editable:focus,
.memo-render .memo-render-editable:focus-within {
  outline: 1px solid var(--ink-faint);
  outline-offset: 2px;
  border-radius: 2px;
}
/* Author byline — primary author's name is inline-editable
   in the title line. The literal " by " prefix stays static
   (rendered before this span by buildPreviewEntry). The title
   line is bold by default; force regular weight on the author
   to match the rendered output. */
/* A14 — author chip now bold + underlined so the entry heading
   reads as a single bold + underlined run across "TITLE by
   Author". Was font-weight: 400 / no decoration prior to A14. */
.memo-render-entry-title .memo-render-author {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

/* A15 — when an entry heading is NOT on its work's first
   appearance (and isn't force-bold via the manual override),
   the whole row drops to font-weight: 400 — underline-only. The
   underlines on .title-caps / .subtitle / .subtitle-sep / .by /
   .memo-render-author stay in place (text-decoration is
   independent of font-weight), so the heading still reads as
   one continuous underlined unit, just without the heavy
   weight. */
.memo-render-entry-title--unbold,
.memo-render-entry-title--unbold .title-caps,
.memo-render-entry-title--unbold .subtitle,
.memo-render-entry-title--unbold .memo-render-entry-title-subtitle-sep,
.memo-render-entry-title--unbold .series-tag,
.memo-render-entry-title--unbold .by,
.memo-render-entry-title--unbold .memo-render-author {
  font-weight: 400;
}
/* Picker-value spans for Lit Agent / Film/TV Agent / Publisher
   — the value text itself is the click target that opens the
   picker dropdown. Hover affordance comes from the shared
   .memo-render-editable rules above. The wrap groups the value
   with the × delete affordance so they share a single hover
   state — moving the cursor over the value reveals the X
   without it leaking into other meta-line content. */
.memo-render .memo-render-picker-value-wrap {
  /* `inline` (not inline-block) so a long value — e.g. "John Smith / UTA
     and Jane Doe / CAA" on the Lit/Film Agent line — wraps mid-text
     after the bold label rather than orphaning the entire value to its
     own line. The hover state still works: the hover rect of an inline
     span spans whatever line(s) the content occupies, so the × delete
     affordance still reveals when you hover anywhere over the value. */
  display: inline;
}
/* Multi-material chips in the Builder — each token renders as
   its own .memo-render-picker-value-wrap. A comma + space
   between chips matches the export format ("MS, Proposal,
   Sample"). Generated via ::before on every chip except the
   first so the visible separator persists between chips
   without needing JS-injected text nodes. */
.memo-render .memo-render-entry-meta .memo-render-picker-value-wrap + .memo-render-picker-value-wrap::before {
  content: ', ';
  color: var(--ink-soft);
}
/* R-S6 — the Material row injects its OWN Oxford comma + ampersand
   separators as JS spans (buildMaterialMetaRow), because the row's
   <strong> "Material:" label and trailing "+ Material" button shift
   :first-child/:last-child off the real chips, so a CSS ::before
   can't special-case the first/last token. Here we just CANCEL the
   base comma above for material chips (more specific → wins) so the
   JS spans are the only separators, and tint those spans to match
   the muted ::before look. Agent rows ("; ") are a different marker
   and unaffected. */
.memo-render .memo-render-entry-meta-material .memo-render-picker-value-wrap + .memo-render-picker-value-wrap::before {
  content: none;
}
.memo-render .memo-render-material-sep {
  color: var(--ink-soft);
}
/* AP11 — agents row overrides the Material comma with "; "
   between agency-batch chips. Same selector pattern (adjacent
   .memo-render-picker-value-wrap), scoped by the agents-row
   marker class so Material rows keep their comma. Matches the
   Gmail / PDF cross-agency separator (joinAgents in memo-section
   + pdf-renderer) so the live preview reads the same as the sent
   memo. */
.memo-render .memo-render-entry-meta-agents .memo-render-picker-value-wrap + .memo-render-picker-value-wrap::before {
  content: '; ';
  color: var(--ink-soft);
}
.memo-render .memo-render-picker-value {
  cursor: pointer;
}
.memo-render .memo-render-picker-remove {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 12px;
  padding: 0 4px;
  margin-left: 1px;
  height: 14px;
  line-height: 1;
  vertical-align: 1px;
  opacity: 0;
  transition: opacity 100ms, color 100ms;
}
.memo-render .memo-render-picker-value-wrap:hover .memo-render-picker-remove {
  opacity: 0.6;
}
.memo-render .memo-render-picker-remove:hover {
  opacity: 1;
  color: #c8102e;
}
.memo-render-host-readonly .memo-render-picker-remove { display: none; }
/* "+ Add" affordance shown when an editable meta line has no
   value — sits inline where the value would be, sized down so
   it reads as an editor control rather than rendered content.
   Reuses .lib-filter-add for the dashed slate-soft chrome. */
.memo-render .memo-render-add-affordance {
  vertical-align: 1px;
  height: 16px;
  padding: 0 6px;
  font-size: 10px;
  letter-spacing: 0.04em;
  line-height: 1;
}
.memo-render .memo-render-add-affordance .caret { font-size: 8px; }
/* General empty-state placeholder for any inline-editable view
   in the memo preview that doesn't have its own .empty rule.
   Reads as a muted preview of the field's actual typography —
   the bold/uppercase/underlined styling of `.pitch-tag` /
   `.title-caps` / `.subtitle` still applies; this rule only
   recolors the text to the faint ink. (Earlier this rule
   forced font-style: italic + font-weight: normal so empty
   placeholders read as "small italic affordance buttons," but
   that made view-mode and edit-mode look distractingly
   different on click-in. `font-style: normal` is explicit so
   we override the base `.inline-edit-view.empty { font-style:
   italic }` cleanly.) */
.memo-render .inline-edit-view.memo-render-editable.empty {
  color: var(--ink-faint);
  font-style: normal;
}
/* `.memo-render .inline-edit-input` was previously defined here
   with a visible gray border + white background. That rule has
   been merged into the unified Memo Builder inlineText override
   above (search for "Memo Builder inlineText inputs") so a
   single rule governs both visual and geometric properties.
   Keeping the multiline variant below — its block layout for
   multi-line textareas isn't part of the unified focus story. */
.memo-render .inline-edit-input.multiline {
  display: block;
  width: 100%;
  margin: 4px 0;
  min-height: 80px;
  resize: vertical;
}
.memo-render-signature {
  margin-top: 32px;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 14px;
  color: #1a1a1a;
  white-space: pre;
  background: none;
  border: none;
  padding: 0;
}

/* Bottom action bar */
.composer-actions {
  padding: 10px 24px;
  border-top: 1px solid var(--bg-rule);
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  background: var(--bg);
  flex-shrink: 0;
}
.composer-actions .left {
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.composer-actions .btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* ============================================================
   GOOGLE BOOKS IMPORT — search results in New Book modal
   ============================================================ */

/* ============================================================
   NEW WORK MODAL — type-tile selector + per-type import body
   ============================================================ */
/* New-Work modal stays a fixed size regardless of which media tile
   is active — the import body's content can vary substantially
   (book search has results streaming in, podcast has an iTunes list,
   article is just a URL field, "other" has nothing). The body
   becomes the scroll container so the overall modal frame doesn't
   jump around as the user clicks through tiles. */
/* Shared "+ New" entity-modal width — 540px. Lands between the
   default `.modal` width (460px, right for confirms / image-edits /
   small forms) and the previous 640px New-Work-only width. The
   four "+ New" modals (Work, Person, Company, Memo) read as a
   family at the same width. */
.modal.new-work-modal,
.modal.new-person-modal,
.modal.new-company-modal,
.modal.new-memo-modal {
  width: 540px;
  max-width: 92vw;
}

/* When the memo-type tiles render inside the New Memo modal,
   the original .memo-type-btn chrome (designed for the full
   Builder drawer's 760px-wide centered layout: 22×24px padding,
   22px serif title) is too spacious for the 540px modal. These
   overrides scope-down the tiles for the modal context only;
   the drawer's URL-typed fallback (#memos/new/edit) still uses
   the original sizing. The padding + title size + line-height
   ratios stay proportional — the tile just renders ~35% smaller. */
.modal.new-memo-modal .memo-type-choices {
  /* Drop the max-width constraint that lived for the drawer's
     larger viewport — inside the modal the choices container is
     just the body width. */
  max-width: none;
}
.modal.new-memo-modal .memo-type-btn {
  padding: 16px 18px;
  border-radius: 6px;
}
.modal.new-memo-modal .memo-type-btn-title {
  font-size: 17px;
  margin-bottom: 4px;
}
.modal.new-memo-modal .memo-type-btn-desc {
  font-size: 11px;
  line-height: 1.45;
}

/* New Work additionally needs its tabbed-content layout (tabs +
   fixed-height content panel + scrolling banners) which the other
   two "+ New" modals don't have. These extras stay scoped to
   .new-work-modal. */
.modal.new-work-modal {
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
.modal.new-work-modal .modal-header { flex-shrink: 0; }
.modal.new-work-modal .modal-footer { flex-shrink: 0; }
.modal.new-work-modal .modal-body {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

/* Type-tile selector + chip sizing match `.col-filters` / `.filter-chip`
   so the workType selector here reads identically to the Roles
   chips in New Person and the Type chips in New Company. The
   container retains its border-bottom divider since this modal
   has tabbed content below the chip row (acts as a "step 1 → 2"
   separator); the chips themselves are the same shape and weight
   as their Person/Company cousins. */
.new-work-type-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--bg-rule);
}
.new-work-type-tile {
  appearance: none;
  padding: 3px 9px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: all 100ms;
}
.new-work-type-tile:hover { background: var(--bg-hover); }
.new-work-type-tile.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}

.new-work-import-body { margin-bottom: 12px; }

.import-search {
  position: relative;
}
.import-search .field-input { padding-right: 28px; }
.import-search-spinner {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 11px;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  pointer-events: none;
}

/* Per-source loading state, rendered above the results list. */
.import-sources {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 6px;
  font-family: var(--sans);
}
.import-source-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  color: var(--ink-faint);
  font-weight: 600;
}
.import-source-pill.idle    { opacity: 0.5; }
.import-source-pill.loading { color: var(--ink-muted); }
.import-source-pill.loading::before { content: '…'; margin-right: 2px; }
.import-source-pill.done    { color: var(--status-sold); border-color: var(--status-sold); }
.import-source-pill.done::before { content: '✓'; margin-right: 2px; }
.import-source-pill.empty   { color: var(--ink-faint); }
.import-source-pill.empty::before { content: '∅'; margin-right: 2px; }
.import-source-pill.error   { color: var(--accent); border-color: var(--accent); opacity: 0.7; }
.import-source-pill.error::before { content: '✕'; margin-right: 2px; }

.import-results {
  display: flex;
  flex-direction: column;
  max-height: 280px;
  overflow-y: auto;
  margin-top: 8px;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  background: var(--bg-card);
}
.import-results[hidden] { display: none; }

/* Per-row source tags ("Google Books · Open Library") */
.import-result-tags {
  display: flex;
  gap: 4px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.import-result-tag {
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 1px 5px;
  border-radius: 2px;
  background: var(--bg);
  border: 1px solid var(--bg-rule);
  color: var(--ink-muted);
  font-family: var(--sans);
  font-weight: 600;
}
.import-result-row.multi-source .import-result-tag {
  border-color: var(--accent-soft);
  color: var(--accent);
  background: var(--accent-bg);
}

.import-result-row {
  display: flex;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--bg-rule);
  cursor: pointer;
  transition: background 100ms;
  align-items: flex-start;
}
.import-result-row:last-child { border-bottom: none; }
.import-result-row:hover { background: var(--bg-hover); }

.import-result-thumb {
  width: 36px;
  height: 50px;
  flex-shrink: 0;
  background: var(--bg-rule);
  border-radius: 2px;
  background-size: cover;
  background-position: center;
}
.import-result-thumb.empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  color: var(--ink-faint);
  font-family: var(--serif);
  font-style: italic;
}

.import-result-text { flex: 1; min-width: 0; }
.import-result-title {
  font-family: var(--serif);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
}
.import-result-author {
  font-family: var(--serif);
  font-size: 12px;
  font-style: italic;
  color: var(--ink-muted);
  margin-top: 2px;
}
.import-result-meta {
  font-size: 11px;
  color: var(--ink-faint);
  margin-top: 2px;
}

.import-state-msg {
  /* Default (info) variant: small grey italic line, in-flow. */
  padding: 8px 12px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 13px;
  color: var(--ink-faint);
  align-items: center;
  gap: 8px;
}
.import-state-msg.is-info {
  display: flex;
}
/* Error variant: prominent red banner with icon. The user has
   reported missing extraction failures because the message was too
   subtle — make this loud enough that it can't be overlooked. */
.import-state-msg.is-error {
  display: flex;
  margin: 8px 0;
  padding: 10px 12px;
  background: rgba(185, 78, 78, 0.10);
  border: 1px solid var(--danger, #b94e4e);
  border-left: 4px solid var(--danger, #b94e4e);
  border-radius: 4px;
  color: var(--danger, #b94e4e);
  font-style: normal;
  font-weight: 600;
  font-size: 14px;
  line-height: 1.35;
}
.import-state-msg .import-state-icon {
  flex-shrink: 0;
  font-size: 16px;
  line-height: 1;
}
.import-state-msg .import-state-text {
  flex: 1;
  min-width: 0;
}
/* Legacy class kept so older callsites that use .error still work. */
.import-state-msg.error { color: var(--danger, #b94e4e); font-weight: 600; }

.import-or-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 0 4px;
  color: var(--ink-faint);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

/* Canonical helper-text style for modal tab subtitles. Italic
   serif at 13px / --ink-faint matches Galley's editorial voice
   (italic byline, italic memo prose) and reads as a writerly
   aside rather than a UI status line. Use under the input/control
   in modal tab panels (Search, Drive, Manual). The dropzone hint
   under the Upload tab keeps its sibling style — it sits beneath
   a tinted dropzone box, a different visual context.

   The old `.new-work-section` / `.new-work-section-body` /
   `.new-work-section-hr` / `.new-work-section-wrap` primitives
   that used to live here were retired when the New Work modal
   collapsed from a section-list layout into the four-tab
   layout. Removed 2026-05-13. */
.modal-helper-text {
  /* Single subtitle/helper-text style used everywhere a hint
     sits below a control (every Search tab, the Manual tab,
     the Drive tab, the no-search hint for Play/Other types).
     13px to match the in-flow `.import-state-msg.is-info`
     font-size (also serif italic) — keeps "subtitle" and
     "loading/state" text in the same visual register, just
     with different padding (state messages are callouts and
     get padding; helpers are inline below an input and don't). */
  font-family: var(--serif);
  font-size: 13px;
  font-style: italic;
  color: var(--ink-faint);
  line-height: 1.4;
}
.new-work-drive-btn { align-self: flex-start; }
.import-or-divider::before,
.import-or-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--bg-rule);
}

.import-applied-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 12px;
  background: var(--accent-bg);
  border: 1px solid var(--accent);
  border-radius: 4px;
  font-size: 12px;
  color: var(--ink);
  margin-bottom: 6px;
}
/* The HTML `hidden` attribute is equivalent to `display: none`,
   but `display: flex` above wins specificity, leaving an empty
   blue bar visible. Override explicitly. */
.import-applied-banner[hidden] { display: none; }
.import-applied-banner .check { color: var(--accent); font-weight: 600; }
.import-applied-banner .clear-link {
  margin-left: auto;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  font-size: 11px;
}
/* Second-line note under the source list, e.g.
   "Genres matched: Thriller, Mystery". Forces a wrap to its own row
   inside the flex banner. */
.import-applied-banner .import-applied-genres {
  flex-basis: 100%;
  font-size: 11px;
  color: var(--ink-soft);
  padding-top: 4px;
  border-top: 1px solid var(--bg-rule);
  margin-top: 2px;
}
.import-applied-banner .import-applied-genres.muted {
  color: var(--ink-faint);
  font-style: italic;
}
/* When a cover image came back from extraction, show it as a small
   thumbnail on the left of the banner. The banner is still flex/wrap;
   the cover stays pinned to the leading edge while text fills the row. */
.import-applied-banner .import-banner-cover {
  flex-shrink: 0;
  width: 48px;
  height: 64px;
  border-radius: 3px;
  background-size: cover;
  background-position: center;
  background-color: var(--bg-rule);
  border: 1px solid var(--bg-rule);
}
.import-applied-banner .import-banner-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.import-applied-banner .import-banner-head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.import-applied-banner .import-banner-sub {
  font-size: 11px;
  color: var(--ink-faint);
  font-style: italic;
}

/* ============================================================
   MODAL
   ============================================================ */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(26, 24, 20, 0.32);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 100;
  animation: modal-fade-in 120ms ease-out;
}

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

.modal {
  width: 100%;
  max-width: 460px;
  background: var(--bg);
  border: 1px solid var(--bg-rule);
  border-radius: 8px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.16);
  overflow: hidden;
  animation: modal-slide-in 160ms ease-out;
}

@keyframes modal-slide-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.modal-header {
  padding: 20px 24px 12px;
  border-bottom: 1px solid var(--bg-rule);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.modal-title {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.modal-close {
  font-size: 18px;
  color: var(--ink-muted);
  padding: 2px 8px;
  border-radius: 3px;
  line-height: 1;
}
.modal-close:hover { background: var(--bg-hover); color: var(--ink); }

.modal-body {
  padding: 18px 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Author CSS `display: flex` above wins over the UA stylesheet's
   `[hidden] { display: none }`, so the HTML hidden attribute
   would otherwise be a no-op. Restore expected semantics. */
.modal-body[hidden] { display: none; }

.modal-footer {
  padding: 12px 24px 18px;
  border-top: 1px solid var(--bg-rule);
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.modal-footer[hidden] { display: none; }

/* ============================================================
   SCROLLBARS
   ============================================================ */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--bg-rule);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: content-box;
}
/* No hover-darkening on the thumb. The prior rule swapped in
   `--ink-faint` (#aba599) on cursor-hover, which made revealed-on-
   hover scrollbars (`.col:hover` below) read as dark slate the
   moment the cursor approached the gutter. Keeping the thumb at
   the resting beige in all states matches the horizontal scrollbar
   at the bottom of `.columns`. */

/* Notion-style column scrollbar: invisible by default, revealed
   in soft beige on column hover.

   Important: we don't `display: none` the webkit scrollbar. If
   we did, macOS Safari would supplement the now-unstyled
   scrollable area with its own NATIVE overlay scrollbar (the dark
   slate-grey thumb), which is exactly the artifact we set out to
   eliminate. Keeping the webkit scrollbar in the rendering
   pipeline (just with a transparent thumb) prevents the OS from
   stepping in. `scrollbar-gutter: stable` on `.col` (line 712)
   keeps the gutter reserved regardless of thumb visibility, so
   toggling never shifts content. Memo view uses internal
   `overflow: hidden` panes and doesn't hit this rule. */
.col::-webkit-scrollbar-thumb {
  background: transparent;
  transition: background 180ms ease;
}
.col:hover::-webkit-scrollbar-thumb {
  background: var(--bg-rule);
}
/* Firefox equivalent: transparent track + thumb by default,
   beige thumb on hover. Firefox respects scrollbar-color directly. */
.col { scrollbar-color: transparent transparent; }
.col:hover { scrollbar-color: var(--bg-rule) transparent; }

/* Horizontal scrollbar on `.columns` keeps the global beige
   thumb (always visible when overflow exists). Tried multiple
   hide-and-reveal approaches — pure CSS `:hover`, parent-hover,
   JS-toggled class — and none of them reliably surfaced the
   webkit-scrollbar thumb in Safari for this element. The horizontal
   bar is small, lives at the bottom edge, and matches Finder /
   Mail / Notes default behavior. */

/* ============================================================
   RESPONSIVE
   The columns module also enforces a max visible count via JS:
   4 → 3 → 2 → 1 as width drops.
   ============================================================ */
@media (max-width: 1100px) {
  :root { --col-width: 300px; }
}

@media (max-width: 800px) {
  .nav { width: var(--nav-collapsed); padding: 16px 0; }
  .nav-build-memo span:not(.star),
  .nav-build-memo .badge,
  .nav-section-label,
  .nav-item span:not(.icon),
  .nav-search-shortcut,
  .nav-user-name,
  .nav-user-menu-btn { display: none; }
  /* Swap wordmark → glyph in the brand slot. The slot itself
     stays visible (it still anchors the 80px header shelf that
     the rest of the app aligns against), but its contents
     collapse to the italic G + underline. */
  .nav-brand {
    padding: 0;
    justify-content: center;
  }
  .nav-brand-text { display: none; }
  .nav-brand-glyph {
    /* `hidden` attribute on the <img> normally wins specificity
       over display:block from .nav-brand-glyph above. Force-
       reveal here at the collapsed breakpoint. */
    display: block !important;
  }
  /* Dev override: when the glyph is revealed at the collapsed
     breakpoint, switch to flex so the "D" ::before centers
     vertically inside the 32×32 slot. !important matches the
     prod rule above so we win the specificity contest cleanly. */
  html.is-dev .nav-brand-glyph {
    display: flex !important;
  }
  .nav-build-memo {
    margin: 0 8px 16px;
    padding: 10px 0;
    justify-content: center;
    width: calc(100% - 16px);
  }
  .nav-item { justify-content: center; padding: 12px 0; }
  .nav-user { justify-content: center; padding: 12px 0; }
}

@media (max-width: 600px) {
  :root { --col-width: 100vw; }
}

/* ============================================================
   APP MODES — list / preview / builder
   The body class records the current mode; CSS keys off it for
   any mode-specific tweaks. Side nav is visible in every mode —
   the Builder drawer overlays the columns area only, leaving
   the nav untouched.
   ============================================================ */

/* ============================================================
   BUILDER DRAWER — overlays the columns area, slides up from
   the bottom in Builder mode (#memos/:id/edit). The underlying
   [memos-list, memo-preview] stack stays mounted underneath so
   the close-animation reveal is instant.
   ============================================================ */
.columns-wrap { position: relative; }
.builder-drawer {
  position: absolute;
  inset: 0;
  background: var(--bg);
  z-index: 50;
  transform: translateY(100%);
  transition: transform 240ms cubic-bezier(0.32, 0.72, 0.24, 1);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 -12px 28px rgba(0, 0, 0, 0.10);
}
.builder-drawer.open { transform: translateY(0); }
.builder-drawer[hidden] { display: none; }
.builder-drawer-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Profile-launched Memo Builder drawer. Mounts inside
   `.cw-columns-area`, reuses the `.memo-shell-drawer` CSS
   (positioned absolute, slides up via translateY) — same
   class, same animation, same z-index as the in-section
   drawer. `.memo-shell-drawer-profile` is a marker class
   the host adds in `drawer-only` mode so we can apply
   profile-specific tweaks.

   Height matches the Memos-section drawer's `applyDrawerHeight`
   math at full snap: the drawer's top edge sits flush with
   the bottom of the column-header shelf (the 80px
   `.col-header-row` that runs across the top of every column).
   The `-1px` is the same 1px overlap the inline drawer uses
   so the drawer's top border paints exactly on top of the
   column header's bottom border — without it the two adjacent
   1px borders would stack into a visible 2px double-line.

   80px is the canonical page-header-shelf height; same value
   `.col-header-row` uses (see styles.css around line 5544). */
.memo-shell-drawer.memo-shell-drawer-profile {
  height: calc(100% - 79px);
}

/* ============================================================
   MEMO SECTION — unified Memos list + Preview + Builder view.
   Routed at #memos, #memos/:id, and #memos/:id/edit. The list
   stays mounted at all times; the right pane swaps between an
   empty state (no memo selected), the preview, or the preview
   with the Builder drawer open. The footer spans the full width
   below both panes.
   ============================================================ */
.memo-section-col { padding: 0; }
.memo-section {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  height: 100%;
}
.memo-section-content-row {
  flex: 1;
  display: flex;
  min-height: 0;
  /* Position-anchor for the Builder drawer — the drawer overlays
     this entire row (both list and right pane) so it slides up
     from the bottom over both, leaving only the footer below. */
  position: relative;
  overflow: hidden;
}
.memo-section-list-pane {
  flex-shrink: 0;
  /* Mirror the global --col-width variable so the memo rolodex
     stays the same width as the standalone list columns (Library,
     People, Companies, Archive). Previously this was hardcoded at
     320px and drifted from the variable. */
  width: var(--col-width);
  border-right: 1px solid var(--bg-rule);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  scrollbar-gutter: stable;
  background: var(--bg);
}
/* The .people-list inside the memo-section list pane is the
   scrollable rows container. The pane itself is overflow:hidden so
   the sticky .col-chrome (filter + sort rows) pins above; the rows
   list below gets flex:1 + its own overflow-y:auto so it scrolls
   independently of the right-pane preview. (In the standalone
   /memos column the .col parent owns the scroll, so .people-list
   stays unstyled there — this rule is scoped to the memo-section
   pane only.) */
.memo-section-list-pane > .people-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}
.memo-section-right-pane {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

/* Empty state — shown in the right pane when no memo is
   selected. Just a flex container that centers a child
   `.empty-state` text node; no glyph, no hero copy, no
   sub-classes. Quieter and consistent with empty states
   everywhere else in the app. */
.memo-section-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 24px;
  text-align: center;
}

/* Footer — spans both list and right panes. */
.memo-section-footer {
  flex-shrink: 0;
  /* Pinned at exactly 52px — same hard clamp as `.cw-footer`
     and `.section-list-footer`. With box-sizing: border-box,
     52px = 1px border + 9+9 padding + 33px content area. The
     34px `.btn` is taller than the content area by 1px;
     `align-items: center` distributes that 0.5px above + 0.5px
     below into the padding region, and `overflow: visible`
     (default) keeps it from clipping. Visual: button is
     centered in the 52px box with effectively 8.5px breathing
     room above and below. */
  height: 52px;
  padding: 9px 24px;
  border-top: 1px solid var(--bg-rule);
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
  background: var(--bg);
  box-sizing: border-box;
}
.memo-section-footer .left {
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.memo-section-footer .left.muted { color: var(--ink-faint); }
.memo-section-footer .btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
/* The primary slot toggles between "Open Builder", "Close
   Builder", and "Duplicate" depending on memo + drawer state.
   Lock a min-width so all labels render in a button of
   identical size — no shimmy when the drawer opens or closes,
   and no width difference between the draft and sent primary
   slots. Same rule applies to the cw-footer-right primary on
   the profile path (memo-detail action bar) so the silhouette
   matches across both surfaces. */
.memo-section-footer .btn-primary,
.cw-footer-right .btn-primary { min-width: 132px; }

/* The right pane reuses memo-shell-* classes for the header,
   preview body, and drawer — defined below in the legacy block. */

/* ============================================================
   MEMO SHELL (legacy class names — now scoped to inside the
   memo-section-right-pane). Header, preview, drawer.
   ============================================================ */
.memo-shell {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  height: 100%;
}

/* ----- HEADER (stable across modes) -----
   Same 80px page-header shelf used by the side-nav brand and the
   list-column titles, so the right pane's header sits on the same
   horizontal row as everything else across the top of the app. */
.memo-shell-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
  min-height: var(--page-header-height);
}
/* No memo selected → collapse the header to just the rule line so
   the empty-state below feels centered, not pushed down. */
.memo-shell-header.empty {
  padding: 0;
  min-height: 0;
  border-bottom: none;
}
.memo-shell-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--ink-muted);
  text-decoration: none;
  padding: 4px 8px;
  border-radius: 3px;
  transition: background 100ms, color 100ms;
}
.memo-shell-back:hover { background: var(--bg-hover); color: var(--ink); }
.memo-shell-back .arrow { font-size: 14px; }
.memo-shell-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.memo-shell-title-text {
  /* Entity title — same scale as page title (28px) so the memo's
     header reads as a peer to the column page titles across the
     80px shelf. line-height 1.3 gives descenders (e.g. lowercase
     g/p in "Publishing Report") room inside the overflow:hidden
     box that's required for the ellipsis. */
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--serif);
  font-size: 28px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.memo-shell-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  font-size: 11px;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
/* Custom-memo shell title — inline-editable suffix nested inside
   .memo-shell-title-text. The suffix input inherits the parent's
   28px serif so view-mode + edit-mode render at the same visual
   weight; the static "<date> — " prefix flows in front of it as a
   text node.

   Inline-flow override across all three layers — wrapper, view,
   input — so the "<date> — " text node and the editable suffix
   sit together as one continuous heading line. The base
   .inline-edit is a div (display: block); its inner
   .inline-edit-view is also a div (display: block). Without all
   three flipped to inline-block, the suffix wraps onto its own
   line beneath the date prefix in view mode. */
.memo-shell-title-text .inline-edit,
.memo-shell-title-text .memo-shell-title-suffix.inline-edit-view,
.memo-shell-title-text .memo-shell-title-suffix.inline-edit-input {
  display: inline-block;
  width: auto;
  vertical-align: baseline;
}
.memo-shell-title-text .memo-shell-title-suffix.inline-edit-view,
.memo-shell-title-text .memo-shell-title-suffix.inline-edit-input {
  font-family: var(--serif);
  font-size: 28px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.3;
}
/* Type pill — styled to match `.col-action-btn` ("+ Build",
   "+ New", etc. at the top of each column) so the report-type
   toggle reads as a peer-level action. Sentence-case text,
   accent color, soft pill shape. Reset the user-agent button
   defaults so a <button> renders identical to the equivalent
   <span>-with-class would. */
.memo-shell-type-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 4px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  /* `text-transform: none` breaks the inherited `uppercase` from
     the parent `.memo-shell-meta` — we want sentence-case
     ("Publishing Report") to match `.col-action-btn`, not the
     all-caps look of the parent meta line. */
  text-transform: none;
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
  transition: background 100ms, border-color 100ms, color 100ms;
}
.memo-shell-type-pill:hover {
  border-color: var(--accent);
  background: var(--accent-bg);
}
/* Locked state — applied when the memo is sent (`disabled` attr
   set in memo-section.js). The pill still reads the report type
   but no longer accepts the toggle click. Cursor signals locked;
   the disabled hover override prevents the accent treatment. */
.memo-shell-type-pill:disabled {
  cursor: not-allowed;
  color: var(--ink-muted);
  border-color: var(--bg-rule);
  background: var(--bg-card);
}
.memo-shell-type-pill:disabled:hover {
  border-color: var(--bg-rule);
  background: var(--bg-card);
  color: var(--ink-muted);
}

/* Status pill — historical class kept for legacy compatibility.
   The draft/sent status pill in this region was moved to each
   rolodex row in memos-list.js → buildRow; the class is unused
   in the current preview-pane header. If the surface is ever
   reintroduced, the original all-caps mini-pill chrome is here. */
.memo-shell-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 6px;
  border-radius: 2px;
  font-size: 10px;
  letter-spacing: 0.1em;
  border: 1px solid var(--bg-rule);
  background: var(--bg-card);
  color: inherit;
  font-family: inherit;
  cursor: pointer;
  text-transform: uppercase;
  transition: background-color 80ms ease, border-color 80ms ease;
}
.memo-shell-status-pill:hover {
  background: var(--bg-hover, var(--bg-rule));
  border-color: var(--accent);
}
.memo-shell-status-pill.sent {
  border-color: var(--status-sold);
  color: var(--status-sold);
}

/* ============================================================
   UNLOCK MODAL — opened from the Sent status pill. Two large
   primary-weight action tiles in a row (Update / Preserve)
   plus a Cancel in the footer. The two tiles are visually
   equivalent — the user is picking a path, not approving vs
   canceling — so neither one gets the .btn-primary slate fill.
   Both render as bordered cards that lift on hover.
   ============================================================ */
.unlock-modal {
  /* Slightly wider than the base modal so the two action
     tiles can sit side-by-side without their captions
     wrapping awkwardly. */
  max-width: 560px;
}
.unlock-modal-desc {
  font-family: var(--serif);
  font-size: 14px;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0 0 18px;
}
.unlock-modal-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.unlock-modal-action {
  appearance: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  text-align: left;
  padding: 14px 16px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
  transition: border-color 100ms, background-color 100ms,
              transform 100ms, box-shadow 100ms;
}
.unlock-modal-action:hover {
  border-color: var(--accent);
  background: var(--bg-hover);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
}
.unlock-modal-action:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
}
.unlock-modal-action:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.unlock-modal-action-title {
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.2;
}
.unlock-modal-action-caption {
  font-family: var(--serif);
  font-size: 12px;
  font-style: italic;
  color: var(--ink-muted);
  line-height: 1.4;
}
/* Stack vertically on narrow viewports so captions stay
   readable rather than wrapping to two cramped lines. */
@media (max-width: 600px) {
  .unlock-modal-actions { grid-template-columns: 1fr; }
}

/* ----- CONTENT (preview + drawer overlay) ----- */
.memo-shell-content {
  flex: 1;
  position: relative;
  overflow: hidden;
  background: var(--bg-card);
}
.memo-preview-pane {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  scroll-padding-bottom: 0;
  transition: scroll-padding-bottom 250ms ease-out;
}

/* ----- DRAWER (slides up from the bottom of the content area) ----- */
.memo-shell-drawer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  transform: translateY(100%);
  transition: transform 250ms ease-out, box-shadow 250ms ease-out;
  background: var(--bg);
  /* Drawer's own top rule — spans the full content-row width,
     covering both the list pane and the right pane (the right
     pane's header has its own border-bottom, but the list pane
     side needs this for the line to read continuously across). */
  border-top: 1px solid var(--bg-rule);
  /* Shadow is gated on `.open` so it doesn't bleed when the drawer
     is hidden under the closed transform. */
  box-shadow: none;
  display: flex;
  flex-direction: column;
  z-index: 5;
}
.memo-shell-drawer.open {
  transform: translateY(0);
  box-shadow: 0 -10px 24px rgba(0, 0, 0, 0.10);
}
.memo-shell-drawer.no-anim { transition: none !important; }
.memo-shell-drawer-handle {
  flex-shrink: 0;
  height: 14px;
  cursor: ns-resize;
  display: flex;
  justify-content: center;
  align-items: center;
  background: var(--bg);
  border-bottom: 1px solid var(--bg-rule);
  user-select: none;
  position: relative;
}
.memo-shell-drawer-handle::before {
  content: '';
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--ink-faint);
  transition: background 100ms;
}
.memo-shell-drawer-handle:hover::before { background: var(--ink-muted); }
.memo-shell-drawer-body {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}
/* When the user is mid-drag, suppress text selection app-wide and
   keep the drag cursor regardless of which element is under the
   pointer. */
body.drawer-resizing { cursor: ns-resize !important; }
body.drawer-resizing * { user-select: none !important; }

/* Inside the embedded composer the wrapping .composer fills the
   drawer body; .composer-body-embedded is a 2-column flex (Library
   + Builder, no Preview pane). */
.composer-body-embedded { /* hook for any drawer-only tweaks */ }

/* ----- FOOTER (stable across modes) ----- */
.memo-shell-footer {
  flex-shrink: 0;
  padding: 10px 24px;
  border-top: 1px solid var(--bg-rule);
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
  background: var(--bg);
}
.memo-shell-footer .left {
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.memo-shell-footer .btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* ============================================================
   LEGACY MEMO-PREVIEW selectors — kept for trade-doc readonly
   styling that's still keyed off .memo-render-host-readonly.
   ============================================================ */
.memo-preview-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 24px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 4;
}
.memo-preview-header-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.memo-preview-header-title-text {
  font-family: var(--serif);
  font-size: 18px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.memo-preview-header-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.memo-preview-type-pill,
.memo-preview-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 6px;
  border-radius: 2px;
  font-size: 10px;
  letter-spacing: 0.1em;
  border: 1px solid var(--bg-rule);
  background: var(--bg-card);
}
.memo-preview-status-pill.sent {
  border-color: var(--status-sold);
  color: var(--status-sold);
}

/* The bottom action bar in Preview reuses .composer-actions styling
   so the strip looks identical to the builder's footer. The
   memo-preview-footer modifier is currently a no-op hook in case
   we want to diverge later. */
.memo-preview-footer { /* hook for future preview-only tweaks */ }

/* Trade-doc render inside Preview is read-only — disable the
   inline-edit click/hover affordances inherited from the shared
   builder so it reads as a finished doc, not a composer pane.
   Pointer-events stays auto on the host so scrolling works. */
.memo-render-host-readonly {
  flex: 1;
  overflow-y: auto;
  padding: 28px 32px 80px;
  background: var(--bg-card);
}
.memo-render-host-readonly .inline-edit-view,
.memo-render-host-readonly .memo-render-editable,
.memo-render-host-readonly button {
  pointer-events: none;
  cursor: default;
}
/* Anchors inside the read-only preview ARE meant to be
   clickable — opening source URLs is the only interactive
   affordance the read-only surface offers. The pointer-events
   block above kills clicks on everything else (so click-to-
   edit doesn't fire in readonly), but anchors need to escape
   that. Applies to the A13 article/podcast title link
   (which carries `.memo-render-editable title-caps` classes
   for visual consistency) and to any user-authored body
   links inside Notes / Reader Notes / Synopsis. */
.memo-render-host-readonly a[href] {
  pointer-events: auto;
  cursor: pointer;
}

/* Sent memos fade their content body to signal "locked" — matches
   the 0.4 dim used elsewhere for past / no-longer-current surfaces
   (past workplace cards, former colleagues, former employees) so
   the lock signal reads consistently across the app.

   Scoped to the .memo-render content inside the preview pane —
   the header (title + type + Sent pill), footer (Copy / PDF /
   Send / Duplicate-as-Draft), and status pill all stay at full
   opacity so interactive controls don't fade with the document
   body. */
.memo-preview-pane[data-memo-status="sent"] .memo-render {
  opacity: 0.55;
}

/* Sent rows in the memos list — same lock dim (0.4) so the
   list visual matches the open-memo preview. We fade the row's
   text content (title + meta) instead of the row itself, so
   the status pill in the top-right stays at full opacity —
   otherwise it'd be unreadable at rest and only visible on
   hover, which defeats its purpose as a state indicator and
   unlock affordance.

   Hover restores text to full opacity since the row is
   interactive (click to open) and the user wants comfortable
   scanning when actively engaging with the row.
   `[data-memo-status="sent"]` is unique to memo rows (no other
   entity uses that attribute), so this rule is naturally
   scoped — no memo-specific class needed on the selector. */
.person-row[data-memo-status="sent"] .book-row-head,
.person-row[data-memo-status="sent"] .book-row-meta {
  opacity: 0.55;
  transition: opacity 100ms;
}
.person-row[data-memo-status="sent"]:hover .book-row-head,
.person-row[data-memo-status="sent"]:hover .book-row-meta {
  opacity: 1;
}

/* Memo-row × delete — uses the standard `.row-delete-btn`
   chrome (22×22 box, 14px glyph, 3px radius, hover-revealed
   via the opacity:0→1 rule) so it matches the × on book /
   person / company rows. Only the `right` inset is overridden:
   the default × sits at `right: 10px`, but the status pill
   below this × sits at `right: 12px`, so we nudge the × right
   by 2px to keep the two chips vertically flush. */
.memo-row .row-delete-btn {
  right: 12px;
}

/* Status pill on each memo row — lower-right corner, sharing
   the same right inset (12px) as the × above so both chips
   are flush-right. Same pill chrome as the formerly-in-header
   pill; the visual register matches the × by design.

   Always at full opacity even when the row is dimmed (sent
   rows fade title + meta only, scoped earlier in this file)
   so it reads as a crisp, always-visible state indicator. */
.memo-row-status-pill {
  position: absolute;
  bottom: 12px;
  right: 12px;
  appearance: none;
  display: inline-flex;
  align-items: center;
  /* Fixed min-width + centered text so DRAFT (5 chars) and SENT
     (4 chars) take exactly the same horizontal footprint. Without
     this the pill silhouette flickers between two widths as rows
     get re-rendered (and the right edge of SENT wouldn't line up
     visually with DRAFT pills above/below it in the list). */
  min-width: 56px;
  justify-content: center;
  padding: 2px 6px;
  border-radius: 2px;
  border: 1px solid var(--bg-rule);
  background: var(--bg-card);
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--ink-muted);
  cursor: pointer;
  text-transform: uppercase;
  transition: background-color 80ms ease, border-color 80ms ease,
              color 80ms ease, opacity 100ms;
  z-index: 1;
}
.memo-row-status-pill:hover {
  background: var(--bg-hover);
  border-color: var(--accent);
  color: var(--ink);
}
.memo-row-status-pill.sent {
  border-color: var(--status-sold);
  color: var(--status-sold);
}
/* Sent row fade extends to the status pill — it now dims to 0.4
   at rest along with the title + meta, lifting to full opacity
   on row hover. Consistent with the rest of the row: the locked
   signal is a uniform 0.4 wash, and engaging with the row
   surfaces everything for interaction. */
.person-row[data-memo-status="sent"] .memo-row-status-pill {
  opacity: 0.55;
}
.person-row[data-memo-status="sent"]:hover .memo-row-status-pill {
  opacity: 1;
}

/* ============================================================
   A23 — Clients button + row-actions wrapper.

   The status pill was previously positioned absolutely against
   the row's bottom-right corner. With the new Clients button it
   needs a sibling, so wrap both in `.memo-row-actions` (a small
   flex cluster) and shift the absolute positioning up to the
   wrapper. Inside the wrapper both pills are static-positioned;
   the wrapper itself sits at the row's bottom-right just like
   the lone status pill used to.
   ============================================================ */
.memo-row-actions {
  position: absolute;
  bottom: 12px;
  right: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
  z-index: 1;
}
/* Re-statify pills inside the wrapper — their inherited
   `position: absolute; bottom: 12px; right: 12px` would
   double-anchor them and break the flex layout. */
.memo-row-actions .memo-row-status-pill,
.memo-row-actions .memo-row-clients-btn {
  position: static;
  bottom: auto;
  right: auto;
}
/* Clients button — sibling pill style to .memo-row-status-pill,
   same chrome (border, fixed min-width for stable horizontal
   footprint regardless of count, uppercase + letterspaced
   label), so the row-actions cluster reads as a coherent
   pair. The pill is interactive on BOTH draft and sent memos. */
.memo-row-clients-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Same 56px min-width as .memo-row-status-pill so the two
     pills in the row-actions cluster are visually identical
     in footprint. The count lives in the metadata line below
     the title — no point duplicating it here. */
  min-width: 56px;
  padding: 2px 6px;
  border-radius: 2px;
  border: 1px solid var(--bg-rule);
  background: var(--bg-card);
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--ink-muted);
  cursor: pointer;
  text-transform: uppercase;
  transition: background-color 80ms ease, border-color 80ms ease,
              color 80ms ease, opacity 100ms;
}
.memo-row-clients-btn:hover {
  background: var(--bg-hover);
  border-color: var(--accent);
  color: var(--ink);
}
/* Sent rows dim the whole actions cluster like they used to
   dim the lone status pill. */
.person-row[data-memo-status="sent"] .memo-row-clients-btn {
  opacity: 0.55;
}
.person-row[data-memo-status="sent"]:hover .memo-row-clients-btn {
  opacity: 1;
}

/* "Sent to" marker on Memo History rollup rows. Marks rows
   that surface because the memo's sentToClients includes this
   entity (reason === 'sent' or 'both'). A small paper-plane
   glyph with a `title` tooltip — NOT a text pill. The pill
   form (formerly .memo-history-row-tag-sent) was visually a
   sibling of the DRAFT/SENT status pill, but the two are
   different kinds of things: the pill is the memo's status,
   this is an annotation explaining why the row is here. The
   icon reads as a glyph (annotation), the pill as a chip
   (state); the row's flex gap gives natural separation.

   Sized to match the existing 14px icon vocabulary used by
   the inline-edit pencil. Pointer-events stay enabled so the
   browser's native title-attr tooltip fires on hover; the
   icon doesn't have its own click handler, so clicks pass
   through to the row's onClick naturally. */
.memo-history-row-sent-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-muted);
  flex-shrink: 0;
  /* No background, no border, no padding — annotation glyph
     only. */
}
.memo-history-row-sent-icon svg {
  display: block;
}
/* Sent-row dim extends to the icon so it fades along with the
   title / status pill at rest, restoring on hover. */
.rollup-row[data-memo-status="sent"] .memo-history-row-sent-icon {
  opacity: 0.55;
  transition: opacity 100ms;
}
.rollup-row[data-memo-status="sent"]:hover .memo-history-row-sent-icon {
  opacity: 1;
}
.memo-render-host-readonly .memo-render-editable:hover,
.memo-render-host-readonly .inline-edit-view:hover {
  background: transparent;
}
.memo-render-host-readonly .composer-pane-label { display: none; }
/* Empty-state meta lines are editor affordances ("+ Add lit
   agent", "+ Add film/TV agent") — hide them in the read-only
   preview so sent memos render cleanly without placeholder
   chrome. The populated-state lines render as plain text. */
.memo-render-host-readonly .memo-render-meta-empty { display: none; }
/* Inline "+ Material" affordance for stacking multi-material lines.
   It lives inside a populated meta row (so the row itself isn't
   .memo-render-meta-empty), but the button is editor chrome — hide
   it in Preview view alongside the other empty-state affordances. */
.memo-render-host-readonly .memo-render-add-more { display: none; }
/* "+ add subtitle" is an editor affordance — let the user re-add a
   subtitle inline from Builder Preview without bouncing to Book
   detail. In a read-only preview (memo-section right pane,
   memo-detail column, printed PDF) it's noise — the entry title
   line should read "TITLE by Author" without the dangling button. */
.memo-render-host-readonly .memo-render-entry-title .memo-render-editable.subtitle.empty {
  display: none;
}

/* ============================================================
   MEMO DETAIL COLUMN — read-only memo view opened from Memo
   History rollups on book / person / company detail pages.

   The column is a standard .col-detail (width handled by the
   shared variable) with a fixed header on top and a scrolling
   body that hosts buildPreview()'s render. No editor chrome:
   no type pill, no status pill, no Send/PDF/Copy footer — just
   the memo's title and the preview body. The editor lives at
   #memos/:id; this column is intentionally a reader.
   ============================================================ */
/* Override the standard .col-detail min-width (500px) — the memo
   render's max-width is 720px, so anything tighter than that
   forces the trade-doc card to shrink below its natural width.
   Targeting the column root (which has .col, .col-detail, AND
   .memo-detail-col) — the extra class gives us enough specificity
   to beat .col.col-detail's flex shorthand. */
.col.col-detail.memo-detail-col {
  flex: 1 1 720px;
  min-width: 720px;
}
.memo-detail-pane {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  background: var(--bg-card);
  /* Anchors the absolutely-positioned find panel to the pane's
     upper-right corner (the find panel mounts as a direct child
     of .memo-detail-pane, not inside .memo-detail-body which is
     wiped + rebuilt on every memo snapshot update). */
  position: relative;
}
/* Header sits on the same 80px page-header shelf used by every
   other column's top rule, so the memo title aligns horizontally
   with the book / person / company titles in the columns to its
   left. */
.memo-detail-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  padding: 0 24px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
  min-height: var(--page-header-height);
}
.memo-detail-title {
  flex: 1;
  min-width: 0;
  font-family: var(--serif);
  font-size: 24px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.3;
  /* Two-line clamp — memo titles can be long (date + report
     type), and the .col-detail width is narrower than the
     memo-section's right pane. Truncate cleanly rather than
     pushing the body down. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Archived-state note — italic muted line above the body. */
.memo-detail-archived-note {
  flex-shrink: 0;
  padding: 12px 24px;
  font-size: 12px;
  font-style: italic;
  color: var(--ink-muted);
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
}
/* Body — fills the rest of the column and scrolls. buildPreview
   returns a .composer-preview wrapper; we let it occupy the
   body's full height and inherit its own internal padding +
   scroll. */
.memo-detail-body {
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: hidden;
  background: var(--bg-card);
}
.memo-detail-body > .composer-preview {
  position: absolute;
  inset: 0;
  min-width: 0; /* override the editor-pane min-width:460px so
                   the preview can shrink inside .col-detail */
}
/* Archived body — dim the preview but keep it readable. */
.memo-detail-body.memo-detail-archived > .composer-preview {
  opacity: 0.55;
}
/* Sent body — same 0.55 dim as the Memos-section preview pane
   applies via `.memo-preview-pane[data-memo-status="sent"]
   .memo-render`. Profile-launched memo previews go through
   .memo-detail-body, so the matching selector lives here. */
.memo-detail-body.memo-detail-sent > .composer-preview {
  opacity: 0.55;
}
/* Empty / loading / deleted state — same neutral placeholder
   style used by other detail columns' list-state spinners. */
.memo-detail-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  font-size: 13px;
  color: var(--ink-faint);
  text-align: center;
}

/* ============================================================
   FIND-IN-MEMO panel — floating Cmd+F search affordance for the
   memo-section preview pane and the memo-detail column. The
   panel mounts as an absolutely-positioned child of whatever
   container the view passes to attachMemoFind, anchored to the
   top-right corner with a 12px / 16px inset. Sits above the
   memo body (z-index 5) but below modal overlays.
   ============================================================ */
.memo-find-panel {
  position: absolute;
  top: 12px;
  right: 16px;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 6px 6px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
  font-family: var(--sans);
}
.memo-find-panel-closed { display: none; }

.memo-find-input {
  flex: 1;
  min-width: 180px;
  padding: 6px 8px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-size: 13px;
  color: var(--ink);
  outline: none;
  font-family: var(--sans);
  transition: border-color 100ms;
}
.memo-find-input:focus { border-color: var(--accent); }
.memo-find-input::placeholder { color: var(--ink-faint); }

.memo-find-counter {
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.04em;
  white-space: nowrap;
  padding: 0 4px;
  min-width: 56px;
  text-align: right;
}

.memo-find-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 3px;
  color: var(--ink-muted);
  cursor: pointer;
  transition: background 100ms, color 100ms;
}
.memo-find-btn:hover:not(:disabled) {
  background: var(--bg-hover);
  color: var(--ink);
}
.memo-find-btn:disabled {
  opacity: 0.35;
  cursor: default;
}
.memo-find-close {
  font-size: 14px;
  line-height: 1;
}

/* Match highlights — soft sepia tint over matched runs. The
   active match (the one arrow/Enter is stepping to) gets a
   darker fill + a thin accent outline so it stands out from
   the other inactive matches as the user tabs through. The
   <mark> wrapper sits around text only, so any clickable
   <strong>/<em>/<a>/<button> inside an entry keeps its
   interaction. */
mark.memo-find-hl {
  background: var(--bg-selected);
  color: inherit;
  border-radius: 2px;
  padding: 0 1px;
  /* Inherit font/style so highlighted text inside <strong> stays
     bold and inside <em> stays italic — the tint is the only
     change applied. */
}
mark.memo-find-hl-active {
  background: var(--accent);
  color: #fff;
  box-shadow: 0 0 0 1px var(--accent);
}

/* ============================================================
   COMPOSER SLIM HEADER (Builder mode)
   40px top strip replacing the bigger composer-header when the
   composer is rendered with data.fullWindow === true.
   ============================================================ */
.composer-header.composer-header-slim {
  height: 44px;
  padding: 0 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  border-bottom: 1px solid var(--bg-rule);
  background: var(--bg);
}
.composer-back-link {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--ink-muted);
  text-decoration: none;
  padding: 4px 8px;
  border-radius: 3px;
  transition: background 100ms, color 100ms;
}
.composer-back-link:hover { background: var(--bg-hover); color: var(--ink); }
.composer-back-link .arrow { font-size: 14px; }

.composer-title-slim.inline-edit-view,
.composer-title-slim.inline-edit-input {
  flex: 1;
  min-width: 0;
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.composer-header-slim-right {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.composer-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 6px;
  border-radius: 2px;
  font-size: 10px;
  letter-spacing: 0.1em;
  border: 1px solid var(--bg-rule);
  background: var(--bg-card);
  color: var(--ink-muted);
}
.composer-status-pill.sent {
  border-color: var(--status-sold);
  color: var(--status-sold);
}

/* ============================================================
   PERSON WORKS LIBRARY — grid of cover cards at the top of a
   person detail. Combines every work the person is affiliated
   with (author / co-author / editor / translator / illustrator /
   lit agent / film-tv agent). Click a card to open the work;
   hover any card for a tooltip with that person's roles on it.
   ============================================================ */
.person-works-section { margin-bottom: 28px; }
/* Fixed-track grid — cards at 96px wide match the detail-page
   header cover and avatar widths so the gallery reads as one
   continuous system with the page header. Extra horizontal
   space falls off the right (justify-content: start). */
.person-works-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 96px);
  justify-content: start;
  gap: 14px 12px;
  margin-top: 4px;
}

/* (Removed: `.work-card-add*` block. The "+ Add work" tile was
   pulled from Person + Company profile pages in v1.1.4; work
   creation flows through the global "+ New" affordance instead.
   The class was orphaned.) */

/* Horizontal section divider used to separate major content
   blocks on detail pages: the Books + Adaptations block from
   About on Person pages, and the Identity → Editorial →
   Tracking sections on Book pages. 1px slate rule with extra
   vertical room (32px) so the section break reads as a hard
   beat rather than a between-fields gap (which uses ~16px). */
.detail-divider {
  border: none;
  border-top: 1px solid var(--bg-rule);
  margin: 32px 0;
}
.work-card {
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Hover: only the cover scales (slightly), and the title/author
   stay anchored. No shadow — keeps it understated. */
.work-card-cover {
  width: 100%;
  aspect-ratio: 2 / 3;
  background-color: var(--bg-card);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 28px;
  color: var(--ink-faint);
  transition: transform 120ms;
}
.work-card:hover .work-card-cover {
  transform: scale(1.04);
}

/* Empty-cover SVG placeholder for adaptations without poster.
   currentColor inherits the cover's `color: var(--ink-faint)`,
   so the icon reads as a soft slate silhouette. The 32px sizing
   is hard-coded inside the SVG width/height attrs to match the
   visual weight of the books'-fleuron empty state. */
.work-card-cover.empty-media {
  color: var(--ink-faint);
}
.work-card-cover.empty-media svg {
  display: block;
}

/* Hover-revealed X — sits in the top-right corner of a work-card
   cover so the user can delete a book or adaptation from a
   Person / Company page without navigating into it. The cover
   has `position: relative` (already set elsewhere); we layer on
   top with `position: absolute`. opacity: 0 by default so the
   ornament only appears on hover; `pointer-events: auto` keeps
   the button clickable inside an otherwise-clickable card.

   The card's `onClick` opens the work; the button's own click
   handler stops propagation, so clicking the X doesn't
   accidentally navigate. */
.work-card-cover {
  position: relative;
}
.work-card-delete {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, background-color 100ms;
}
.work-card-delete svg {
  display: block;
}
.work-card:hover .work-card-delete {
  opacity: 1;
}
.work-card-delete:hover {
  background: rgba(180, 30, 30, 0.85);
}

/* ============================================================
   CLIENT HISTORY (reverse lookup) — section on person and
   company profiles listing every work whose `book.clients`
   array points to this entity. Renders as the standard
   .person-works-grid + .work-card vocabulary; the only extra
   chrome is a non-destructive UNLINK × and an empty "+ Add Work"
   rectangle tile at the end of the grid.
   ============================================================ */

/* UNLINK × — same hover-revealed pattern as .work-card-delete
   above, but semantically distinct: removes THIS entity from
   the work's `clients` array. Visually identical chrome
   (top-right corner, dark circle, white glyph) so the
   affordance reads as a familiar "remove from this view"
   action. The destructive-delete × stays the property of
   .work-card-delete, which the existing buildWorkCard callers
   continue to use unchanged. */
.work-card-unlink {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, background-color 100ms;
}
.work-card-unlink svg { display: block; }
.work-card:hover .work-card-unlink {
  opacity: 1;
}
.work-card-unlink:hover {
  background: rgba(180, 30, 30, 0.85);
}

/* "+ Add Work" tile — blank rectangle the size of a work poster,
   dashed border instead of cover image, centered serif "+",
   "Add Work" label. Mirrors company-detail's buildAddPersonTile
   vocabulary (circle + dashed border + serif "+") in rectangle
   form so the two affordances read as siblings across the app.
   Sits as the last item in the Client History grid. */
.work-card-add {
  appearance: none;
  background: transparent;
  border: none;
  padding: 0;
  font-family: inherit;
  color: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.work-card-cover-add {
  width: 100%;
  aspect-ratio: 2 / 3;
  background: var(--bg);
  border: 1px dashed var(--bg-rule);
  border-radius: 3px;
  /* Suppress the standard .work-card-cover shadow + box-shadow
     so the dashed-border affordance doesn't read as a populated
     cover under attention. */
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  /* Larger "+" to match the geometric weight of a 2:3 rectangle
     (the company-detail "+ Add person" circle uses 48px; the
     larger poster footprint gets a touch more weight). */
  font-size: 56px;
  font-weight: 400;
  color: var(--ink-faint);
  /* Glyph axis correction — serif "+" sits a few px below the
     line-box midpoint; padding-bottom nudges it into the
     geometric center. */
  padding-bottom: 10px;
  box-sizing: border-box;
  line-height: 1;
  transition: border-color 120ms, color 120ms;
}
.work-card-add:hover .work-card-cover-add {
  border-color: var(--ink-faint);
  color: var(--ink-muted);
  /* Suppress the .work-card:hover .work-card-cover scale: the
     dashed tile is an action, not a populated cover. */
  transform: none;
}
.work-card-title-add {
  /* Bold + warm-slate accent, matching company-detail's
     buildAddPersonTile label treatment — classes this as an
     action rather than a populated record. */
  color: var(--accent);
  font-weight: 600;
  text-align: center;
  transition: color 120ms;
}
.work-card-add:hover .work-card-title-add {
  color: var(--accent-hover);
}

/* Read pill — unified affordance used on every work cover, big
   or small, indicating the work has either a galley attached
   (.work-card-galley-pill, label "Read galley") or a sourceUrl
   set (.work-card-source-pill, label "Read source"). Same rules
   power the book-detail primary cover's pill via the comma-
   selector below, so a 96px grid card and a 200px detail cover
   render the pill at the exact same absolute size — the pill
   reads as a consistent object across the app, just placed on
   covers of different sizes.

   Sits INSIDE the cover at the bottom (the original
   .detail-cover-read-btn position), overlapping the cover image
   rather than overhanging its edge. */
.work-card-galley-pill,
.detail-cover-read-btn {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 5px 10px;
  border: none;
  border-radius: 3px;
  background: rgba(58, 71, 84, 0.92);
  color: #f4f1e9;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
  transition: background 100ms;
  z-index: 2;
  /* Uniform width across all three labels so READ GALLEY,
     READ SOURCE, and LISTEN render at the exact same
     dimensions. Without min-width LISTEN (6 chars) was
     visibly narrower than READ SOURCE / READ GALLEY (11
     chars each); border-box + center alignment makes
     shorter labels expand to the floor instead of hugging
     their natural text width.
     Floor is 90px — fits inside the 96px work-detail hero
     cover (.detail-cover width) with a small gutter on
     either side, so the pill never overhangs. */
  box-sizing: border-box;
  min-width: 90px;
  text-align: center;
}
.work-card-galley-pill:hover,
.detail-cover-read-btn:hover {
  background: var(--accent-hover);
}
.work-card-galley-pill:focus-visible,
.detail-cover-read-btn:focus-visible {
  outline: 2px solid var(--client);
  outline-offset: 2px;
}
.work-card-title {
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.work-card-author {
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Reports to section (Person detail, assistants only) — a card
   grid where each card is a boss the assistant reports to. The
   grid reuses the .employee-blocks layout so cards have the
   same width / spacing as Colleagues. Each card wrap gets a
   hover-revealed × in its corner for unlinking. The trailing
   "+ Add reports-to" trigger sits inline with the cards so the
   row reads as one continuous control surface. */
.reports-to-card-wrap {
  position: relative;
}
.reports-to-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, color 100ms, border-color 100ms;
}
.reports-to-card-wrap:hover .reports-to-remove,
.reports-to-remove:hover {
  opacity: 1;
}
.reports-to-remove:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* Add-reports-to trigger — tile-shaped (96px dashed circle +
   "Add reports-to" label beneath) so it sits in the .profile-
   tile-blocks row with the same footprint as the surrounding
   boss tiles. Echoes the dashed-tile vocabulary the legacy
   work-card-add tile used to share (now removed). */
.reports-to-add-trigger {
  appearance: none;
  background: transparent;
  border: none;
  padding: 0;
  font-family: inherit;
  color: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.reports-to-add-glyph {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 1px dashed var(--bg-rule);
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--logo);
  font-style: italic;
  font-weight: 700;
  font-size: 36px;
  line-height: 1;
  color: var(--accent-soft);
  transition: border-color 100ms, background 100ms, transform 100ms,
              box-shadow 100ms;
}
.reports-to-add-trigger:hover .reports-to-add-glyph {
  border-color: var(--accent-soft);
  background: var(--bg-hover);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
}
.reports-to-add-label {
  font-family: var(--serif);
  font-size: 13px;
  font-style: italic;
  color: var(--ink-soft);
  line-height: 1.25;
  text-align: center;
}

/* Drop-zone visual feedback while a colleague card is being
   dragged over the Reports to grid. Sortable adds class
   `sortable-drag` to the dragged item; we don't have a global
   "is-dragging" state on the drop target, but we can highlight
   the grid via :has() while it contains a sortable-ghost — the
   placeholder Sortable inserts at the drop position. */
.reports-to-cards:has(.sortable-ghost) {
  outline: 2px dashed var(--accent);
  outline-offset: 4px;
  border-radius: 4px;
}
.employee-card.sortable-ghost,
.employee-block.sortable-ghost {
  opacity: 0.4;
}

/* ============================================================
   EMPLOYEE CARDS / FAMILY-TREE TREATMENT (Company detail)
   Compact name+role cards in a wrapping grid; assistants whose
   reportsToPersonId matches another current employee get nested
   below their boss's card with a single connector line drawn
   via CSS pseudo-elements. Top-level cards (no boss linked or
   boss is outside this company) flow alphabetically across rows.
   ============================================================ */
.employee-blocks {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: flex-start;
  margin-top: 6px;
}
.employee-block {
  flex: 0 0 300px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.employee-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  cursor: pointer;
  transition: background 100ms, box-shadow 100ms, transform 100ms;
}
.employee-card:hover {
  background: var(--bg-hover);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
  transform: translateY(-1px);
}
.employee-card-avatar {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background-color: var(--bg);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-muted);
}
.employee-card-text {
  flex: 1;
  min-width: 0;
}
.employee-card-name {
  font-family: var(--serif);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.employee-card-subtitle {
  font-family: var(--serif);
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Family-tree connector — each child card draws its own segment
   of the vertical line. The default segment covers the full
   "from above me to past my bottom" range (so middle children
   render a passthrough and a non-last first child connects down
   to the next sibling). The last child's segment is capped at
   its midpoint via `:last-child` override, which terminates the
   tree without a hanging thread. The horizontal stub (::before)
   anchors each child to the line at its vertical midpoint. */
.employee-block-children {
  margin-left: 18px;
  padding-left: 18px;
  margin-top: 8px;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.employee-block-children > .employee-card {
  position: relative;
}
.employee-block-children > .employee-card::before {
  content: '';
  position: absolute;
  left: -18px;
  top: 50%;
  width: 18px;
  height: 1px;
  background: var(--bg-rule);
}
.employee-block-children > .employee-card::after {
  content: '';
  position: absolute;
  left: -18px;
  width: 1px;
  background: var(--bg-rule);
  /* `top: -8px` reaches into the 8px gap above the children
     container (matches `.employee-block-children { margin-top: 8px }`),
     visually anchoring the line to the agent's card above.
     `bottom: -7px` covers the 6px inter-child gap with 1px of
     overlap into the next card so the segments meet cleanly. */
  top: -8px;
  bottom: -7px;
}
.employee-block-children > .employee-card:last-child::after {
  bottom: 50%;
}

/* ============================================================
   SETTINGS / PROFILE PAGE
   Full-width single column. Sections separated by hairline rules.
   Theme tiles live in a 3-up grid with light/dark/system swatches
   so the user can preview each before picking.
   ============================================================ */
.settings-page {
  max-width: 640px;
  /* Left-aligned, not centered — settings shouldn't drift to the
     middle of a wide screen. The max-width still keeps lines short
     enough to read; the column itself can be wider. */
  margin: 0;
  padding: 32px 28px 80px;
  font-family: var(--sans);
  color: var(--ink);
}
.settings-header { margin-bottom: 28px; }
.settings-title {
  font-family: var(--serif);
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.settings-section {
  padding: 22px 0;
  border-top: 1px solid var(--bg-rule);
}
.settings-section:first-of-type { border-top: none; padding-top: 0; }
.settings-section-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 12px;
}
.settings-section-help {
  font-size: 12px;
  color: var(--ink-muted);
  margin-bottom: 14px;
  line-height: 1.5;
}

/* Version footer — uses .settings-section / .settings-section-title /
   .settings-section-help directly so the typography exactly matches
   the other Settings sections ("APPEARANCE", "MEMO", etc.). The
   .settings-version class itself is a no-op styling hook kept around
   in case the footer needs to differentiate from a normal section
   later (e.g., a colophon flourish, a right-aligned date, etc.). */

/* Identity row — avatar + name/email */
.settings-identity {
  display: flex;
  align-items: center;
  gap: 18px;
  border-top: none;
  padding-top: 0;
}
.settings-avatar {
  width: 72px;
  height: 72px;
  flex-shrink: 0;
  border-radius: 50%;
  background-color: var(--accent);
  color: var(--bg-card);
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid var(--bg-rule);
}
.settings-identity-name {
  font-family: var(--serif);
  font-size: 22px;
  color: var(--ink);
  line-height: 1.2;
  font-weight: 500;
}
.settings-identity-email {
  font-size: 13px;
  color: var(--ink-muted);
  margin-top: 4px;
}

/* Theme tiles — 3 buttons per row, big swatch + label. Grid
   width matches the surrounding settings sections (the memo
   distribution list textarea above, the team list below) by
   filling the section's content area — no `max-width` cap.
   Each tile gets ~33 % of that width via repeat(3, 1fr). */
.settings-theme-tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.settings-theme-tile {
  appearance: none;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  padding: 10px;
  cursor: pointer;
  transition: all 100ms;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: stretch;
  font-family: inherit;
  color: var(--ink);
}
.settings-theme-tile:hover { border-color: var(--ink-faint); }
.settings-theme-tile.active {
  border-color: var(--ink);
  box-shadow: 0 0 0 1px var(--ink) inset;
}
.settings-theme-swatch {
  height: 56px;
  border-radius: 4px;
  border: 1px solid var(--bg-rule);
  background-size: cover;
  background-position: center;
}
.settings-theme-swatch.theme-swatch-light {
  /* Mini preview of the cream paper + slate ink. */
  background:
    linear-gradient(135deg, transparent 50%, rgba(31,36,44,0.10) 50%),
    #f4f1e9;
  border-color: #e4ddca;
}
.settings-theme-swatch.theme-swatch-dark {
  background:
    linear-gradient(135deg, transparent 50%, rgba(240,234,219,0.12) 50%),
    #1a1d22;
  border-color: #2a2f37;
}
.settings-theme-swatch.theme-swatch-system {
  /* Diagonal split — half light, half dark — to show "follows OS". */
  background:
    linear-gradient(135deg, #f4f1e9 0%, #f4f1e9 50%, #1a1d22 50%, #1a1d22 100%);
  border-color: var(--bg-rule);
}
/* Extras — each swatch shows the theme's page bg + an ink-on-bg
   wedge in the upper-right corner so the contrast direction is
   immediately legible. Matches the .theme-swatch-light /
   .theme-swatch-dark pattern. */
.settings-theme-swatch.theme-swatch-sepia {
  background:
    linear-gradient(135deg, transparent 50%, rgba(58, 40, 24, 0.18) 50%),
    #ebdcc1;
  border-color: #c8b896;
}
.settings-theme-swatch.theme-swatch-forest {
  background:
    linear-gradient(135deg, transparent 50%, rgba(232, 222, 195, 0.16) 50%),
    #1f2a25;
  border-color: #2d3a32;
}
.settings-theme-swatch.theme-swatch-oxblood {
  background:
    linear-gradient(135deg, transparent 50%, rgba(240, 221, 196, 0.14) 50%),
    #1f1612;
  border-color: #2e2018;
}
.settings-theme-swatch.theme-swatch-midnight {
  background:
    linear-gradient(135deg, transparent 50%, rgba(232, 227, 208, 0.14) 50%),
    #161a26;
  border-color: #232838;
}
.settings-theme-label {
  font-size: 12px;
  font-weight: 500;
  text-align: center;
  color: var(--ink);
}

.settings-signout-btn { min-width: 120px; }

/* ============================================================
   PRINT — Export PDF flow
   The memo-section's Export PDF button calls window.print() with
   `body.printing` set. The rules below hide every UI surface
   except the read-only trade-doc render, then position it at the
   page's top-left so the print preview shows the memo as a clean
   document. Save-as-PDF from the browser dialog finishes the job.
   ============================================================ */
@media print {
  /* Strip page margins so the memo render fills the printable area
     (the .memo-render card has its own internal padding). */
  @page { margin: 0.5in; }

  body.printing { background: #fff; }

  /* Hide everything by default; selectively show the memo render. */
  body.printing .nav,
  body.printing .memo-section-list-pane,
  body.printing .memo-section-footer,
  body.printing .memo-shell-header,
  body.printing .memo-shell-drawer,
  body.printing .breadcrumb,
  body.printing .columns-wrap > .builder-drawer { display: none !important; }

  /* The read-only render from the section's preview pane is what
     prints — pull it out of its scroll container and pin to the
     top-left of the page. */
  body.printing .memo-render-host-readonly {
    position: static !important;
    overflow: visible !important;
    padding: 0 !important;
    background: #fff !important;
  }
  body.printing .memo-render {
    position: static !important;
    margin: 0 auto !important;
    border: none !important;
    box-shadow: none !important;
    max-width: none !important;
  }

  /* Suppress the composer's preview pane render (it's also a
     .memo-render — would otherwise print as a duplicate when the
     drawer is open). */
  body.printing .composer-preview { display: none !important; }
}

/* ============================================================
   SECTION-LIST-FOOTER — small-caps metadata strip that sits at
   the bottom of the .columns-wrap shell, spanning the FULL work
   area (list column + any detail columns to its right). Mirrors
   how the memo-section's footer spans across its list and preview
   panes. Each list view (Books, People, Companies) appends its
   own instance on mount and removes it on teardown.

   We deliberately mount this at the wrap level rather than inside
   the column — that way the footer's tally always reads as a
   section-wide overview regardless of which detail panes happen
   to be open to its right. */
/* Multiple list views can mount footers (e.g. People list + Person
   detail both want their own stats strip). Only the LAST mounted
   footer is shown, so a person detail's footer effectively
   overrides the list's while open; when the detail unmounts,
   its footer's `dispose` removes it and the list footer becomes
   :last-of-type again. */
.section-list-footer:not(:last-of-type) {
  display: none;
}
.section-list-footer {
  flex-shrink: 0;
  /* Pinned at exactly 52px — same hard clamp as `.cw-footer`
     and `.memo-section-footer`. Text content (font-size 11px,
     line-height 1.3 ≈ 14px) fits comfortably inside the 31px
     content area; no overflow. */
  height: 52px;
  padding: 10px 24px;
  border-top: 1px solid var(--bg-rule);
  background: var(--bg);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
  box-sizing: border-box;
}
.section-list-footer .stat,
.cw-center-metadata .stat {
  white-space: nowrap;
}
/* Numbers render flush with the labels — same muted color, same
   weight. The <strong> tag stays for semantic emphasis (helps
   screen readers identify the count) but loses all visual
   distinction. */
.section-list-footer .stat strong,
.cw-center-metadata .stat strong {
  color: inherit;
  font-weight: inherit;
}
.section-list-footer .stat-sep,
.cw-center-metadata .stat-sep {
  color: var(--ink-faint);
}
/* Replace the lost flex `gap: 10px` with margin on the separators —
   matches the prior visual spacing now that the metadata container
   is block-layout (for ellipsis truncation). The .section-list-footer
   variant still uses inline-flex with gap, so doesn't need this. */
.cw-center-metadata .stat-sep {
  margin: 0 6px;
}
.section-list-footer.muted,
.cw-center-metadata.muted {
  color: var(--ink-faint);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0;
}

/* `has-trailing` flips the footer to a 2-slot layout: stats
   span pinned to the LEFT, trailing content (e.g. archive
   button) pinned to the RIGHT. Plain stats-only mode keeps the
   historical centered strip. */
.section-list-footer.has-trailing {
  justify-content: space-between;
}
.section-list-footer-stats {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
}
.section-list-footer-trailing {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Archive button — toggle button surfaced in the right slot of
   each list footer + in the memo-section footer next to Copy /
   PDF / Send. Sized to match the canonical `.btn .btn-secondary`
   action button so it sits cleanly alongside Copy/PDF/Send in
   the memo-section toolbar without the size mismatch the old
   uppercase 11px pill produced. Active (pressed) state inverts
   to slate-fill so the toggle reads as "on" without changing
   the label. */
.archive-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 7px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  /* Explicit override — `.section-list-footer` sets uppercase
     for its stat strip (11px small-caps metadata), and the
     button would otherwise inherit that. We want title-case
     "Archive" / "Archive (3)" labels matching the .btn
     toolbar siblings (Copy / PDF / Send). */
  text-transform: none;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 100ms, border-color 100ms, background 100ms;
}
.archive-link:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--bg-hover);
}
.archive-link.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}
.archive-link.active:hover {
  background: var(--ink-soft);
  border-color: var(--ink-soft);
}

/* Archive banner — pinned at the top of the list area when the
   user toggles into archive mode. Left side: "Showing N archived
   books" (muted small-caps); right side: "Show active" link.
   Sits inside the list scroll area above the rows so it scrolls
   with content rather than floating fixed. */
.archive-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 24px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--bg-rule);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.archive-banner-text { color: var(--ink); }
.archive-banner-sep  { color: var(--ink-faint); }
.archive-banner-back {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--ink-soft);
  cursor: pointer;
  letter-spacing: inherit;
  text-transform: inherit;
  border-bottom: 1px solid transparent;
  transition: color 100ms, border-color 100ms;
}
.archive-banner-back:hover {
  color: var(--ink);
  border-bottom-color: var(--ink-faint);
}

/* Per-row attribution line in archive mode — compact "Deleted
   by [name] · [time]" appended below the row's normal content.
   Italic + ink-faint so it's read as metadata rather than
   primary content. */
.archive-row-meta {
  display: block;
  margin-top: 4px;
  font-style: italic;
  color: var(--ink-faint);
  font-size: 11px;
  letter-spacing: 0.04em;
}

/* Restore button — small inline button at the right edge of
   each archive-mode row, plus the centered button on the
   detail-view "this record is archived" placeholder. Uses the
   canonical small-variant dimensions (4px 10px / 11px / weight
   500 / 0.02em / 3px radius) so it sits at the same register
   as `.list-add-btn`, `.col-action-btn`, `.detail-refetch-btn`. */
.archive-row-restore {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 100ms, border-color 100ms, background 100ms;
}
.archive-row-restore:hover {
  color: var(--ink);
  border-color: var(--ink-soft);
  background: var(--bg-hover);
}

/* "Delete forever" — destructive sibling of Restore. Same small
   register as Restore but lights up to the accent (red) color
   on hover so the destructive intent is unambiguous. Sits to
   the right of Restore in archive-mode rows. */
.archive-row-delete-forever {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color 100ms, border-color 100ms, background 100ms;
}
.archive-row-delete-forever:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-bg);
}
/* Right-pinned action group containing Restore + Delete forever
   on each archive-mode row. Same edge alignment as the previous
   single-button layout (which positioned via flex on the row's
   parent), now wrapped so the two buttons sit together with a
   small gap. */
.archive-row-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Detail-view placeholder shown when an open record is in the
   archived state. Centered card with calm slate-soft chrome —
   reads as "this is paused, here's how to bring it back" rather
   than as a hard error. */
.detail-archived-placeholder {
  margin: 64px auto;
  max-width: 480px;
  padding: 32px 28px;
  text-align: center;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
}
.detail-archived-heading {
  font-family: var(--serif);
  font-size: 18px;
  color: var(--ink);
  margin-bottom: 8px;
}
.detail-archived-body {
  font-size: 13px;
  color: var(--ink-muted);
  line-height: 1.5;
  margin-bottom: 18px;
}
/* Hero Restore button on the detail-view placeholder reads as
   a primary recovery action — bump it from the per-row small
   register up to the canonical `.btn` standard register so it
   matches `.btn-secondary` siblings elsewhere on the site. */
.detail-archived-placeholder .archive-row-restore {
  padding: 7px 14px;
  font-size: 12px;
}

/* ============================================================
   PERSON DETAIL — title row + avatar + auto-import button
   ============================================================ */

/* New title-area layout: avatar stacked above name. The 80px
   page-header shelf alignment doesn't apply here — a proper
   profile avatar needs more vertical room — so the chip row
   lands wherever the stacked title content ends rather than
   at y=80. */
.person-title-area {
  margin-bottom: 14px;
}
.person-title-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 14px;
  margin-bottom: 10px;
}
/* Name + "at [Company]" inline. Baseline alignment so the smaller
   italic affiliation sits flush with the title's text baseline.
   Wraps cleanly on narrow columns; the .detail-title's own
   margin-bottom provides the gap to the chip row below. */
.person-title-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
}
/* The inline-edit wrapper around the name defaults to width:100%,
   which would push the affiliation onto its own line. Inside the
   title row we want it to shrink to its text so the affiliation
   can sit alongside it. */
.person-title-row > .inline-edit {
  width: auto;
  flex: 0 0 auto;
}
.person-title-row .detail-affiliation-line {
  /* In-flow trailing line: no bottom margin (the .detail-title
     supplies its own), and we let the row's gap do the spacing. */
  margin: 0;
}
/* (Was: .book-author-line — block-level layout for an
   author/host line BELOW the title row. Removed: byline now
   renders inline with the title via the same .person-title-row
   path used by Person and Company headers, so no per-class
   spacing override is needed.) */
.person-avatar {
  flex-shrink: 0;
  /* 96 × 96 — ~3.4× the 28px title, sits as a balanced primary
     identity marker without dominating the column. Standard
     professional-app sizing (LinkedIn-compact / Notion). */
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  background-color: var(--bg);
  border: 1px solid var(--bg-rule);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: box-shadow 100ms, transform 100ms;
}
.person-avatar:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  transform: translateY(-1px);
}
.person-avatar.empty {
  background-color: var(--bg-card);
}
.person-avatar-initial {
  font-family: var(--serif);
  /* Scaled with the 96px avatar — same 1:2.67 ratio keeps the
     initials' optical weight balanced inside the circle. */
  font-size: 36px;
  font-weight: 500;
  color: var(--ink-muted);
  line-height: 1;
  letter-spacing: -0.01em;
}
/* (`.auto-import-btn` was removed — no remaining JS callers
   apply this class. The auto-import wand modal uses the
   canonical `.btn .btn-primary` for its action footer.) */

/* ============================================================
   IMAGE-EDIT MODAL — unified Edit photo / Edit logo / Edit
   cover surface for People, Companies, and Books. Centered
   modal with a large preview + stacked action buttons.
   ============================================================ */
.modal-image-edit { width: 360px; }
.modal-image-edit-body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 14px;
  padding: 22px 22px 16px;
}
.image-edit-preview {
  align-self: center;
  width: 144px;
  height: 144px;
  border-radius: 50%;
  background: var(--bg-card);
  background-size: cover;
  background-position: center;
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 48px;
  color: var(--ink-muted);
}
/* For Book covers, swap to a portrait rectangle — covers don't
   crop well into a circle. The aspect matches the existing
   .detail-cover thumbnail (96×144). */
.modal-image-edit[data-entity="book"] .image-edit-preview,
.image-edit-preview.book-cover {
  width: 120px;
  height: 180px;
  border-radius: 4px;
}
.image-edit-preview.empty { background-color: var(--bg-card); }
.image-edit-preview-fallback {
  user-select: none;
  pointer-events: none;
}
.image-edit-caption {
  align-self: center;
  font-family: var(--sans);
  font-size: 11px;
  color: var(--ink-faint);
  font-style: italic;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: -6px;
}
.image-edit-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
}
.image-edit-action {
  /* Stacked full-width — Upload / Search / Pull / Remove all
     read as peer choices. Inherits canonical .btn dimensions. */
  width: 100%;
  justify-content: center;
}
.image-edit-error {
  font-family: var(--sans);
  font-size: 12px;
  color: var(--accent);
  text-align: center;
  padding: 6px 8px;
  background: var(--accent-bg);
  border-radius: 3px;
}
.image-edit-busy {
  font-family: var(--sans);
  font-size: 12px;
  color: var(--ink-muted);
  font-style: italic;
  text-align: center;
}

/* Search-mode (book cover grid). 3-up at 96x144 each, reads
   like the auto-import gallery. Click a card to set as cover. */
.image-edit-search-header {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  text-align: center;
  margin-bottom: 4px;
}
.image-edit-search-grid {
  display: grid;
  grid-template-columns: repeat(3, 96px);
  justify-content: center;
  gap: 12px;
  margin: 6px 0;
}
.image-edit-search-card {
  width: 96px;
  height: 144px;
  background: var(--bg-card);
  background-size: cover;
  background-position: center;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  cursor: pointer;
  padding: 0;
  transition: border-color 100ms, transform 100ms;
}
.image-edit-search-card:hover {
  border-color: var(--ink-soft);
  transform: scale(1.03);
}
.image-edit-empty {
  font-family: var(--sans);
  font-size: 12px;
  color: var(--ink-muted);
  font-style: italic;
  text-align: center;
  padding: 24px 0;
}

/* ============================================================
   CROP MODE — pan + zoom adjust UI inside the image-edit modal.
   The frame is a fixed-size overflow:hidden box; the source
   image is positioned inside via translate(-50%, -50%) so its
   center anchors to the frame center, then user drag /
   zoom apply additional translate + width/height scaling.
   Circular mask for person/company; rectangle for book.
   ============================================================ */
.image-edit-crop-header {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 500;
  text-align: center;
}
.image-edit-crop-frame {
  align-self: center;
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  overflow: hidden;
  cursor: grab;
  user-select: none;
  touch-action: none;     /* prevent scroll while dragging on touch */
}
.image-edit-crop-frame:active { cursor: grabbing; }
.image-edit-crop-frame.circle { border-radius: 50%; }
.image-edit-crop-frame.book   { border-radius: 4px; }
.image-edit-crop-img {
  position: absolute;
  left: 50%;
  top: 50%;
  /* JS sets the actual width / height + adds an additional
     translate; the base translate(-50%, -50%) anchors the
     center. Display is non-block so the user can't accidentally
     drag-select the image. */
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  max-width: none;
}
.image-edit-crop-slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 4px;
}
.image-edit-crop-slider-label {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  min-width: 36px;
}
.image-edit-crop-slider {
  flex: 1;
  /* Native range input; lightly themed to match the slate
     palette without trying to skin it cross-browser. */
  accent-color: var(--ink-soft);
}

/* ENRICHMENT WAND — floating circular trigger that opens the
   auto-import / find-associated-info modal. Sticky-top inside
   the detail column wrap so it pins to the upper-right while
   the user scrolls. The anchor is a 0-height flex row with its
   button pushed to the right edge via justify-content: flex-end.
   Negative bottom margin prevents the anchor from displacing
   real content below it — the button overlays the title shelf. */
.enrich-wand-anchor {
  position: sticky;
  top: 16px;
  display: flex;
  justify-content: flex-end;
  width: 100%;
  height: 0;
  margin-bottom: 0;
  padding-right: 4px;
  z-index: 5;
  /* The anchor's empty space shouldn't capture clicks — only the
     inner button does — so clicks elsewhere fall through to the
     underlying content (e.g. inline-edit on the title). */
  pointer-events: none;
}
.enrich-wand {
  pointer-events: auto;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  border: none;
  background: var(--ink);
  color: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  transition: transform 120ms, background 120ms, box-shadow 120ms;
}
.enrich-wand:hover:not(:disabled) {
  transform: scale(1.06);
  background: var(--ink-soft, #2a2a2a);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.24);
}
.enrich-wand:disabled {
  cursor: default;
}
.enrich-wand.is-busy {
  /* While enrichment is fetching, the wand spins to signal work
     in progress. Click is gated separately (the JS toggles
     `disabled` on busy). */
  animation: enrich-wand-spin 900ms linear infinite;
}
@keyframes enrich-wand-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Photo-URL popover — drop-or-paste image picker. Hosts a
   drop-zone, an "or paste a URL" divider with the URL field
   below, a status line for upload progress / errors, and an
   actions row. Wider than other classify popovers because it
   needs to fit the drop-zone target. */
.classify-popover.photo-url-popover {
  width: 360px;
  padding: 14px;
}
.classify-popover.photo-url-popover .classify-popover-input {
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  padding: 8px 10px;
}
.classify-popover.photo-url-popover .classify-popover-input:focus {
  border-color: var(--ink-muted);
}
.photo-url-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 22px 12px;
  background: var(--bg);
  border: 1px dashed var(--bg-rule);
  border-radius: 6px;
  cursor: pointer;
  transition: background 100ms, border-color 100ms;
  text-align: center;
}
.photo-url-drop:hover,
.photo-url-drop.dragging {
  background: var(--bg-hover);
  border-color: var(--ink-muted);
  border-style: solid;
}
.photo-url-drop-icon {
  font-size: 20px;
  color: var(--ink-muted);
  line-height: 1;
}
.photo-url-drop-text {
  font-size: 12px;
  color: var(--ink);
  font-weight: 500;
}
.photo-url-drop-hint {
  font-size: 10px;
  color: var(--ink-faint);
}
.photo-url-divider {
  text-align: center;
  font-size: 10px;
  color: var(--ink-faint);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 12px 0 6px;
}
.photo-url-status {
  font-size: 11px;
  color: var(--ink-muted);
  margin-top: 8px;
  min-height: 14px;
  line-height: 1.4;
}
.photo-url-status.error {
  color: var(--accent);
}
.photo-url-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 8px;
}

/* Cropper UI — replaces the drop-zone + URL-input view inside
   the photo-url popover after a file is picked. Circular
   preview frame (matches the avatar shape) over an
   absolute-positioned background-image layer that the user
   pans by dragging the frame and zooms via the slider. The
   visible region is what gets exported on Save. */
.photo-crop-frame {
  width: 256px;
  height: 256px;
  margin: 4px auto 12px;
  position: relative;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--bg-rule);
  background: var(--bg);
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}
.photo-crop-frame:active { cursor: grabbing; }
.photo-crop-img {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  pointer-events: none;
}
.photo-crop-zoom {
  width: 100%;
  margin: 0;
  accent-color: var(--accent);
}
.photo-crop-hint {
  font-size: 11px;
  color: var(--ink-muted);
  margin-top: 6px;
  text-align: center;
  letter-spacing: 0.02em;
}

/* Match the Books grid exactly — fixed 96px tracks so adaptation
   cards are pixel-identical to book covers, and the two galleries
   read as paired surfaces. */
.person-adaptations-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 96px);
  justify-content: start;
  gap: 14px 12px;
  margin-top: 8px;
}

/* ============================================================
   AUTO-IMPORT MODAL — large centered modal with sectioned
   checkbox rows.
   ============================================================ */

.modal.modal-auto-import {
  width: 720px;
  max-width: 92vw;
  height: 720px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
}
.modal-auto-import .modal-header {
  flex-shrink: 0;
  padding: 18px 22px 12px;
  border-bottom: 1px solid var(--bg-rule);
}
.modal-auto-import .modal-eyebrow {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}
.modal-auto-import .modal-title {
  font-family: var(--serif);
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.25;
}
.modal-auto-import .modal-subtitle {
  font-size: 12px;
  color: var(--ink-muted);
  margin-top: 6px;
  line-height: 1.4;
}
.modal-auto-import .modal-body {
  flex: 1;
  overflow-y: auto;
  padding: 14px 22px;
}
.auto-import-loading {
  font-size: 13px;
  color: var(--ink-muted);
  font-style: italic;
  padding: 24px 0;
  text-align: center;
}
.auto-import-empty {
  font-size: 13px;
  color: var(--ink-faint);
  font-style: italic;
  padding: 24px 0;
  text-align: center;
}
.auto-import-section {
  margin-bottom: 12px; /* sections sit close — separator line carries the visual break */
}
/* Section header row: small-caps label on the left, "Select all"
   toggle on the right, with a 1px slate-rule line below them.
   The flexbox row keeps the eyebrow and toggle on a shared
   baseline so they read as a paired header. */
.auto-import-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 4px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--bg-rule);
}
.auto-import-section-eyebrow {
  font-size: 11px;
  letter-spacing: 0.14em;
  font-weight: 600;
  color: var(--ink-soft, var(--ink-muted));
  text-transform: uppercase;
}
.auto-import-select-all {
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.04em;
  padding: 0;
  transition: color 80ms;
}
.auto-import-select-all:hover {
  color: var(--ink);
  text-decoration: underline;
}
/* Low-confidence flag — appears after a low-confidence item's
   title in muted ink so it reads as informational, not alarming. */
.auto-import-low-conf {
  font-size: 11px;
  font-style: italic;
  color: var(--ink-faint);
  margin-left: 4px;
}

/* GALLERY layout — used by the BOOKS section in the wand modal.
   Fixed-track grid (mirroring the person-works-grid pattern) so
   covers stay a stable size as the modal width changes. The
   whole card is the click target; selection paints a check
   overlay onto the top-right corner of the cover. */
.auto-import-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, 98px);
  justify-content: start;
  gap: 14px 12px;
  margin-top: 8px;
}
.auto-import-gallery-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  cursor: pointer;
}
.auto-import-gallery-card.is-existing {
  cursor: default;
  opacity: 0.5;
}
.auto-import-gallery-cover {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 3;
  background-color: var(--bg-card);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid var(--bg-rule);
  border-radius: 3px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: 22px;
  color: var(--ink-faint);
  transition: transform 120ms, border-color 120ms;
}
.auto-import-gallery-card:hover .auto-import-gallery-cover {
  transform: scale(1.04);
}
.auto-import-gallery-card.is-checked .auto-import-gallery-cover {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}
/* SVG icon rendering inside an empty cover. The cover already
   centers via flex, but we also force display: block on the SVG
   so it doesn't get inline-baseline weirdness, and explicitly
   set its color so currentColor (stroke="currentColor" on every
   icon path) resolves to the muted ink. */
.auto-import-gallery-cover.empty-media {
  color: var(--ink-faint);
}
.auto-import-gallery-cover.empty-media svg {
  display: block;
}
/* Check overlay sits in the top-right of the cover and only
   paints when the card is selected. Pointer-events off so the
   click goes to the card, not the overlay. */
.auto-import-gallery-check {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg);
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms;
}
.auto-import-gallery-card.is-checked .auto-import-gallery-check {
  opacity: 1;
}
.auto-import-gallery-title {
  font-family: var(--serif);
  font-size: 12px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.auto-import-gallery-sub {
  font-family: var(--serif);
  font-size: 10px;
  font-style: italic;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* AWARDS section on Person pages — chips with optional year
   prefix + parenthetical work, plus a hover-revealed × remove. */
.awards-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.awards-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 3px 8px 3px 8px;
  border: 1px solid var(--bg-rule);
  border-radius: 12px;
  background: var(--bg);
  font-family: var(--serif);
  font-size: 12px;
  color: var(--ink);
  position: relative;
  transition: border-color 80ms;
}
.awards-chip:hover {
  border-color: var(--ink-faint);
}
.awards-chip-year {
  font-size: 10px;
  font-family: var(--sans);
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  font-weight: 600;
  margin-right: 2px;
}
.awards-chip-work {
  font-style: italic;
  color: var(--ink-muted);
  font-size: 11px;
}
.awards-chip-remove {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 0 0 2px;
  margin-left: 2px;
  font-size: 14px;
  line-height: 1;
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity 80ms, color 80ms;
}
.awards-chip:hover .awards-chip-remove {
  opacity: 1;
}
.awards-chip-remove:hover {
  color: var(--ink);
}
.auto-import-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 8px 4px;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 80ms ease;
}
.auto-import-row:hover { background-color: var(--bg-card, rgba(0, 0, 0, 0.02)); }
.auto-import-row.is-existing {
  cursor: default;
  opacity: 0.55;
}
.auto-import-row.is-existing:hover { background-color: transparent; }
.auto-import-checkbox {
  flex-shrink: 0;
  margin-top: 4px;
  accent-color: var(--accent, #3a4754);
  cursor: pointer;
}
.auto-import-thumb {
  flex-shrink: 0;
  width: 44px;
  height: 64px;
  border-radius: 3px;
  background-size: cover;
  background-position: center;
  background-color: var(--bg-rule);
  border: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  color: var(--ink-faint);
}
.auto-import-thumb.empty { font-style: italic; }
.auto-import-text {
  flex: 1;
  min-width: 0;
}
.auto-import-row-title {
  font-family: var(--serif);
  font-size: 14px;
  color: var(--ink);
  line-height: 1.3;
  word-break: break-word;
}
.auto-import-row-sub {
  font-size: 11px;
  color: var(--ink-muted);
  margin-top: 2px;
}
.auto-import-existing-badge {
  display: inline-block;
  margin-top: 4px;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: var(--bg-rule);
  padding: 2px 8px;
  border-radius: 999px;
}
.modal-auto-import .modal-footer {
  flex-shrink: 0;
  padding: 12px 22px;
  border-top: 1px solid var(--bg-rule);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.modal-auto-import .modal-footer-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}
.modal-auto-import .modal-error {
  flex: 1;
  font-size: 12px;
  color: var(--danger, #b94e4e);
}

/* ============================================================
   ADAPTATION DETAIL — minimal book-detail variant for film/tv
   ============================================================ */
.book-detail-minimal .detail-eyebrow {
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  margin-bottom: 4px;
}
.adaptation-source-card {
  max-width: 220px;
}

/* ============================================================
   MEMO-RENDER — Approach 3 (paper on theme).

   The memo card is the only printed-document surface in the
   app and stays paper-toned across every theme. Its specific
   paper tone subtly shifts to harmonize with the surrounding
   chrome — bright white against cool cream chrome (Light),
   pale ivory against warm manila (Sepia), aged manila against
   slate / indigo (Dark, Midnight), warm cream against deep
   pine (Forest), aged parchment against deep wine (Oxblood) —
   but the document always reads as "a document on a desk"
   rather than "part of the room."

   Body text stays warm near-black on every paper variant so
   prose remains comfortably legible at length. The
   `--memo-stage-accent` warm terracotta carries through every
   theme since it reads well on paper-tone surfaces; no per-
   theme override needed.
   ============================================================ */

/* Light: keeps the base rule (.memo-render's #ffffff + #000).
   No override here — listed for completeness. */

/* Dark + Midnight: aged manila against cool chrome. */
[data-theme="dark"] .memo-render,
[data-theme="midnight"] .memo-render {
  background: #e3cea7;
  color: #2f2517;
  border-color: rgba(0, 0, 0, 0.22);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}

/* Sepia: pale ivory — fine writing paper rather than copy
   paper, with a whisper of warmth tying it to the manila
   page chrome around it. */
[data-theme="sepia"] .memo-render {
  background: #faf3e3;
  color: #1a1a1a;
  border-color: rgba(0, 0, 0, 0.14);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
}

/* Forest: warm cream — less yellow than manila so it
   harmonizes with the green-and-brass palette. */
[data-theme="forest"] .memo-render {
  background: #efe4ca;
  color: #2f2517;
  border-color: rgba(0, 0, 0, 0.20);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}

/* Oxblood: aged parchment — slightly deeper than manila,
   rhymes with the burgundy-leather aesthetic. */
[data-theme="oxblood"] .memo-render {
  background: #e8d8b7;
  color: #2f2517;
  border-color: rgba(0, 0, 0, 0.22);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}

/* Internal muted text (date, scout note, empty-state italic).
   Base rule uses #000/#555/#777/#999 (paper-mode dark greys);
   for paper cards under the four non-light themes we want
   warm-brown muted tones that read as "ink-on-paper at lower
   intensity" rather than cool grey. */
[data-theme="dark"]     .memo-render-date,
[data-theme="sepia"]    .memo-render-date,
[data-theme="forest"]   .memo-render-date,
[data-theme="oxblood"]  .memo-render-date,
[data-theme="midnight"] .memo-render-date {
  color: #2f2517;
}
[data-theme="dark"]     .memo-render-scout-note,
[data-theme="forest"]   .memo-render-scout-note,
[data-theme="oxblood"]  .memo-render-scout-note,
[data-theme="midnight"] .memo-render-scout-note {
  color: #7a5d3a;
}
[data-theme="sepia"] .memo-render-scout-note {
  color: #5d4427;
}
[data-theme="dark"]     .memo-render-empty,
[data-theme="forest"]   .memo-render-empty,
[data-theme="oxblood"]  .memo-render-empty,
[data-theme="midnight"] .memo-render-empty {
  color: #93785a;
}
[data-theme="sepia"] .memo-render-empty {
  color: #84684a;
}

/* Section header shelf — every paper-card theme uses the same
   softer ink-on-paper treatment so the section divider feels
   "stamped on" rather than the harsh 1.5px black border the
   base rule applies (which only reads well on stark white). */
[data-theme="dark"]     .memo-render-section-header,
[data-theme="sepia"]    .memo-render-section-header,
[data-theme="forest"]   .memo-render-section-header,
[data-theme="oxblood"]  .memo-render-section-header,
[data-theme="midnight"] .memo-render-section-header {
  background: rgba(0, 0, 0, 0.07);
  border-top-color:    rgba(0, 0, 0, 0.20);
  border-bottom-color: rgba(0, 0, 0, 0.20);
}

/* ============================================================
   TOAST — single-slot bottom-center notification used by the
   Gmail action (and available to anything else that calls
   showToast). Fades in/out via opacity transition; the JS
   timer adds/removes .visible.
   ============================================================ */
.roc-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  max-width: min(560px, calc(100vw - 40px));
  padding: 10px 16px;
  border-radius: 6px;
  background: var(--ink);
  color: var(--bg-card);
  font-size: 13px;
  letter-spacing: 0.01em;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}
.roc-toast.visible { opacity: 1; }

/* ============================================================
   SEND-INSTRUCTIONS MODAL — appears after the user clicks Send
   to walk them through the three manual steps in Gmail compose.
   Light overlay + centered card; dismisses on Escape, click on
   the overlay (not the card itself), or the "Got it" button.
   ============================================================ */
.send-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.42);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1200;
  padding: 24px;
  animation: send-modal-fade 140ms ease-out;
}
@keyframes send-modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.send-modal {
  background: var(--bg-card);
  color: var(--ink);
  border: 1px solid var(--bg-rule);
  border-radius: 8px;
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.22);
  padding: 28px 30px 24px;
  max-width: 460px;
  width: 100%;
  font-family: var(--sans);
}
.send-modal-title {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
  color: var(--ink);
}
.send-modal-status {
  font-size: 13px;
  color: var(--ink-muted);
  margin: 0 0 22px;
  line-height: 1.5;
}
.send-modal-steps-label {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 8px;
  font-weight: 600;
}
.send-modal-steps {
  margin: 0 0 22px;
  padding-left: 22px;
  font-size: 13px;
  line-height: 1.65;
  color: var(--ink);
}
.send-modal-steps li {
  margin-bottom: 6px;
}
.send-modal-steps li:last-child { margin-bottom: 0; }
.send-modal-btn {
  display: block;
  margin-left: auto;
  min-width: 92px;
}

/* ============================================================
   SETTINGS — memo distribution list textarea. Shares the
   settings-section visuals with Appearance + Account.
   ============================================================ */
.settings-textarea {
  width: 100%;
  min-height: 80px;
  padding: 8px 10px;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  font-family: var(--sans);
  font-size: 13px;
  color: var(--ink);
  outline: none;
  resize: vertical;
  transition: border-color 100ms;
}
.settings-textarea:focus { border-color: var(--accent); }
.settings-save-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
}
.settings-save-status {
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.04em;
}

/* ============================================================
   RICH TEXT — minimal contenteditable editor for memo prose
   fields (notes / readerNotes / synopsis / scoutNote). View
   mode renders sanitized HTML and inherits memo-render-editable
   styling. Edit mode swaps to a contenteditable; the floating
   toolbar / link input / link popup are body-level singletons.
   ============================================================ */
.rich-text { display: inline; }
.rich-text-view {
  /* Inherits typography from the host context. The empty
     placeholder sets ink-faint italic to match inlineText. */
  display: inline;
}
.rich-text-view.empty {
  color: var(--ink-faint);
  font-style: italic;
}
.rich-text-view a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* About sections on detail pages (Person.bio + Company.description)
   use richText with the .detail-synopsis viewClass. The default
   .rich-text-view is inline (so it can flow within memo prose),
   but the About surface needs block layout so <p> tags render
   with the proper .detail-synopsis paragraph spacing. */
.rich-text-view.detail-synopsis,
.rich-text-edit.detail-synopsis {
  display: block;
  min-height: 1.4em;
}
/* Same fix for the .memo-prose-field hosts (Pitch Tag, Logline,
   Synopsis, Notes, Reader Notes). Without this, the view-mode
   div renders inline while the edit-mode div renders block —
   clicking in/out shifts the layout because the display type
   switches. Forcing block on both keeps the field anchored. */
.rich-text-view.memo-prose-field,
.rich-text-edit.memo-prose-field {
  display: block;
}
.rich-text-edit {
  display: block;
  outline: none;
  /* The host class (e.g. memo-render-editable) provides the
     interior background + border on hover; in edit mode we keep
     the same skin so swapping in/out doesn't shift layout. */
  min-height: 1.4em;
  /* Avoid contenteditable's tendency to add inline styles when
     the user pastes — we strip in sanitize anyway, but keeping
     the local rendering clean helps. */
  white-space: pre-wrap;
}
.rich-text-edit:empty::before {
  content: attr(data-placeholder);
  color: var(--ink-faint);
  font-style: italic;
  pointer-events: none;
}
.rich-text-edit a {
  color: var(--accent);
  text-decoration: underline;
  cursor: text;        /* edit mode — caret, not pointer */
}

/* Inside the trade-doc memo render, both view and edit modes
   need to flow inline with surrounding prose so swapping into
   edit mode doesn't push the synopsis to a new line. Mirrors
   the .inline-edit overrides defined elsewhere in this file. */
.memo-render .rich-text { display: inline; }
.memo-render .rich-text-view,
.memo-render .rich-text-edit {
  display: inline;
  min-height: 0;
  /* Match the subtle padding/margin on .inline-edit-view in this
     context so the edit-mode click target sits in the same
     position as the read-mode text — no layout shift. */
  padding: 1px 3px;
  margin: -1px -3px;
  border-radius: 2px;
}
/* `.memo-render .rich-text-edit` previously carried a
   `background: rgba(0, 0, 0, 0.04)` as a "caret-friendly" tint
   to signal edit mode. That treatment predated the outline-only
   focus affordance and is now redundant — worse, because the
   editor is `display: inline` here (so prose flows after the
   pitch tag and the "Notes:" prefix), a background fragments
   per wrapped line into stacked grey boxes. Rule deleted; the
   `--ink-faint` outline above is the sole edit-mode signal. */

/* In read-only memo render hosts, links inside rich-text views
   need to remain clickable even though pointer-events:none is
   applied to the surrounding editable surface. */
.memo-render-host-readonly .rich-text-view {
  pointer-events: none;
  cursor: default;
}
.memo-render-host-readonly .rich-text-view a {
  pointer-events: auto;
  cursor: pointer;
}

/* Floating selection toolbar / inline link input / existing-link
   popup. Body-level absolute elements, only one of each in
   flight. The shell uses the app's paper palette so it matches
   light mode in light mode and dark in dark — a tinted card on
   the page surface, not a high-contrast tooltip. */
.rich-text-toolbar,
.rich-text-link-input,
.rich-text-link-popup {
  position: absolute;
  display: none;
  z-index: 1100;     /* above modals + drawer */
  background: var(--bg-card);
  color: var(--ink);
  border: 1px solid var(--bg-rule);
  border-radius: 5px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.10);
  padding: 3px;
  font-family: var(--sans);
  font-size: 12px;
}
.rich-text-toolbar.visible,
.rich-text-link-input.open,
.rich-text-link-popup.visible {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.rich-text-tb-btn {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  width: 28px;
  height: 28px;
  border-radius: 3px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 80ms, color 80ms;
}
.rich-text-tb-btn:hover {
  background: var(--bg-hover);
  color: var(--ink);
}
.rich-text-tb-btn.active {
  background: var(--accent-bg);
  color: var(--accent);
}
.rich-text-tb-btn[data-cmd="I"] { font-style: italic; font-family: var(--serif); }
.rich-text-tb-btn[data-cmd="U"] { text-decoration: underline; text-underline-offset: 2px; }
.rich-text-tb-btn[data-cmd="CAPS"] {
  /* "AA" cap label sized down so it reads as a small-cap cue,
     not a full-height pair of letters competing with B / I / U. */
  font-size: 11px;
  letter-spacing: 0.04em;
}
.rich-text-tb-btn[data-cmd="red"] {
  /* "A" rendered in the memo red so the button telegraphs what it
     does; active state keeps the red but tints the cell. */
  color: var(--memo-red);
  font-weight: 700;
}
.rich-text-tb-btn[data-cmd="red"].active {
  color: var(--memo-red);
}
.rich-text-tb-btn[data-cmd="link"] {
  /* "↗" — outbound-link arrow, matches the slate palette and
     reads as a link icon without the colored emoji baggage. */
  font-size: 15px;
  line-height: 1;
  font-weight: 500;
}

/* Red text produced by the rich-text "Red" button. Applies in the
   editor, the Builder preview, and the readonly memo render alike —
   they all share this DOM. Gmail/PDF carry their own red (inline
   color / C.textRed). */
.memo-red { color: var(--memo-red); }

/* Inline URL input — same shell as the toolbar so it visually
   replaces it. */
.rich-text-link-input {
  padding: 4px 8px;
}
.rich-text-link-input-field {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--ink);
  font-family: inherit;
  font-size: 12px;
  outline: none;
  width: 240px;
}
.rich-text-link-input-field::placeholder { color: var(--ink-faint); }

/* Existing-link popup — URL display + Edit / Remove. */
.rich-text-link-popup {
  padding: 4px 8px;
  gap: 8px;
}
.rich-text-link-popup-url {
  color: var(--accent);
  text-decoration: underline;
  font-size: 11px;
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rich-text-link-popup-btn {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
  padding: 3px 7px;
  border-radius: 3px;
  transition: background 80ms, color 80ms, border-color 80ms;
}
.rich-text-link-popup-btn:hover {
  background: var(--bg-hover);
  color: var(--ink);
  border-color: var(--bg-rule);
}

/* Memo render — links inside the rendered preview use classic
   hyperlink coloring (#1155cc blue + underline) rather than the
   theme `--accent`, so a link reads unambiguously as a link in
   every theme and the on-screen preview matches the Gmail HTML
   export (the recipient's inbox uses the same blue per the
   visual contract at the top of memoToGmailBodyHtml in
   memo-section.js). Applies to article/podcast title links
   (A13) and to user-authored body links inside Notes, Reader
   Notes, and Synopsis rich-text fields. */
.memo-render a,
.memo-render-notes a,
.memo-render-reader-notes a,
.memo-render-synopsis a {
  color: #1155cc;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ============================================================
   SETTINGS — TEAM section. Admin-only; manages pending /
   active / declined users with approve, decline, admin toggle,
   and revoke-access affordances. Visual density matches the
   surrounding Profile page — calm, scannable.
   ============================================================ */
.settings-team-loading,
.settings-team-empty {
  font-size: 12px;
  color: var(--ink-muted);
  padding: 10px 0;
}
.settings-team-subheader {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
  font-weight: 600;
  margin: 22px 0 8px;
}
.settings-team-subheader:first-child { margin-top: 0; }
.settings-team-subheader-accent { color: var(--accent); }

.settings-team-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 0;
  min-height: 60px;
}
.settings-team-row + .settings-team-row {
  border-top: 1px solid var(--bg-rule);
}
.settings-team-avatar {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  background-color: var(--accent);
  color: var(--bg-card);
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid var(--bg-rule);
}
.settings-team-text {
  flex: 1;
  min-width: 0;
}
.settings-team-name {
  font-size: 14px;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 8px;
}
.settings-team-email {
  font-size: 12px;
  color: var(--ink-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.settings-team-owner-badge {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  border: 1px solid var(--accent);
  padding: 1px 5px;
  border-radius: 3px;
  font-weight: 600;
}
.settings-team-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.settings-team-revoke {
  background: none;
  border: none;
  color: var(--accent-soft);
  font-size: 11px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 3px;
  font-family: inherit;
  transition: color 100ms, background 100ms;
}
.settings-team-revoke:hover {
  color: var(--ink);
  background: var(--bg-hover);
}
.settings-team-show-declined {
  background: none;
  border: none;
  color: var(--accent-soft);
  font-size: 12px;
  cursor: pointer;
  padding: 8px 0;
  margin-top: 14px;
  font-family: inherit;
  display: inline-block;
}
.settings-team-show-declined:hover { color: var(--ink); }

/* Pill toggle switch — hidden checkbox + styled track. The
   ::after on the track is the slider knob; it slides 14px right
   when input is :checked. */
.settings-team-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  font-size: 12px;
  color: var(--ink-muted);
}
.settings-team-toggle input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.settings-team-toggle-track {
  width: 32px;
  height: 18px;
  border-radius: 999px;
  background: var(--bg-rule);
  position: relative;
  transition: background 120ms;
  flex-shrink: 0;
}
.settings-team-toggle-track::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--bg-card);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: transform 120ms;
}
.settings-team-toggle input:checked + .settings-team-toggle-track {
  background: var(--accent);
}
.settings-team-toggle input:checked + .settings-team-toggle-track::after {
  transform: translateX(14px);
}
.settings-team-toggle.disabled {
  cursor: not-allowed;
}
.settings-team-toggle.disabled .settings-team-toggle-track {
  opacity: 0.55;
}


/* ============================================================
   GALLEYS (Book Detail page) — horizontal card row + Add tile +
   drag-drop overlay + Add modal + fullscreen Reader. v1 ships
   with Drive integration and an iframe-based Reader; PDF.js /
   mammoth / epub.js are deferred to v2.
   ============================================================ */

/* Outer wrap holds the row + the drag-drop overlay layered on
   top. position: relative so the overlay can pin to it. */
.galleys-row-wrap {
  position: relative;
  margin-top: 8px;
}

/* The cards row. Horizontal scroll if it overflows the column;
   the spec implies the row keeps growing as galleys are added,
   so the existing column width is the natural constraint. */
.galleys-row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: stretch;
}

.galleys-empty-hint {
  /* Replaces the bare "No galleys yet." text when there's
     nothing to show. The Add tile sits next to it.  */
  font-size: 12px;
  color: var(--ink-muted);
  font-style: italic;
  align-self: center;
  padding: 8px 0;
}

/* ----- Galley card ----- */
.galley-card {
  position: relative;
  width: 168px;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  overflow: hidden;
  transition: box-shadow 120ms, transform 120ms;
}
.galley-card:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
  transform: translateY(-1px);
}

.galley-card-thumb {
  height: 200px;
  background: var(--bg);
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid var(--bg-rule);
}
.galley-card-thumb.has-image {
  /* Drive thumbnails are roughly first-page renders for PDFs.
     For DOCX/EPUB Drive returns nothing useful, so we stay on
     the glyph fallback below. */
}
.galley-card-thumb-glyph {
  font-family: var(--logo);
  font-style: italic;
  font-weight: 700;
  font-size: 22px;
  color: var(--ink-muted);
  letter-spacing: 0.04em;
}

.galley-card-meta {
  padding: 10px 12px 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.galley-card-filename {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink);
  /* Single-line ellipsis — full name available via title attr. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.galley-card-added {
  font-size: 10px;
  color: var(--ink-muted);
  letter-spacing: 0.01em;
}

.galley-card-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 12px 12px;
  margin-top: auto;
}
.galley-card-actions .btn {
  width: 100%;
  font-size: 11px;
  padding: 6px 8px;
  text-align: center;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* × remove button — top-right corner, hidden until card hover. */
.galley-card-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border: none;
  background: rgba(255, 255, 255, 0.85);
  border-radius: 10px;
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms, color 100ms, background 100ms;
}
.galley-card:hover .galley-card-remove {
  opacity: 1;
}
.galley-card-remove:hover {
  color: #c8102e;
  background: white;
}

/* ----- Add Galley tile ----- */
.galley-card-add {
  width: 168px;
  height: auto;
  min-height: 290px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px 16px;
  /* Sepia/cream brand background per spec — matches --bg, the
     paper color of the auth screen. The card stands out from
     populated cards (which use --bg-card, the lighter cream). */
  background: var(--bg);
  border: 1px dashed var(--bg-rule);
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  transition: background 120ms, border-color 120ms, transform 120ms,
              box-shadow 120ms;
  appearance: none;
  color: var(--ink);
}
.galley-card-add:hover {
  background: var(--bg-hover);
  border-color: var(--accent-soft);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
}
.galley-card-add-plus {
  font-family: var(--logo);
  font-style: italic;
  font-weight: 700;
  font-size: 56px;
  line-height: 1;
  color: var(--accent-soft);
}
.galley-card-add-label {
  font-family: var(--logo);
  font-style: italic;
  font-size: 16px;
  color: var(--ink-muted);
}

/* ----- Drag-drop overlay on the section ----- */
.galleys-drop-overlay {
  position: absolute;
  inset: -8px;
  border: 2px dashed var(--accent-soft);
  border-radius: 8px;
  background: rgba(244, 241, 233, 0.85); /* sepia-tinted veil */
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms;
  z-index: 10;
}
.galleys-row-wrap.is-drop-active .galleys-drop-overlay {
  opacity: 1;
}
.galleys-drop-overlay-msg {
  font-family: var(--logo);
  font-style: italic;
  font-size: 22px;
  color: var(--accent);
  letter-spacing: 0.01em;
}

/* ============================================================
   ADD GALLEY MODAL — three stacked panels (Upload / Drive search
   / Paste URL). Wider than the standard modal so the Drive
   search results have room.
   ============================================================ */
.galley-add-overlay .modal {
  max-width: 560px;
  /* Belt-and-suspenders viewport cap. Panel heights inside are
     already sized to fit comfortably, but if anything inside
     grows unexpectedly (long picker breadcrumb, etc.) the modal
     won't push past the viewport — its body becomes the scroll
     container instead of the page. */
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
.galley-add-overlay .modal .modal-header,
.galley-add-overlay .modal .modal-footer {
  flex-shrink: 0;
}
.galley-add-overlay .modal .modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.galley-add-body {
  gap: 0;
  padding: 0 0;
}
.galley-add-panel {
  padding: 20px 22px;
}
.galley-add-divider {
  height: 1px;
  background: var(--bg-rule);
  margin: 0;
}
.galley-add-panel-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 10px;
}
.galley-add-panel-hint {
  font-size: 11px;
  color: var(--ink-muted);
  font-style: italic;
  margin-bottom: 10px;
}

/* ----- Upload panel ----- */
.galley-upload-dropzone {
  border: 2px dashed var(--bg-rule);
  border-radius: 8px;
  padding: 32px 24px;
  text-align: center;
  cursor: pointer;
  background: var(--bg);
  transition: border-color 120ms, background 120ms;
  /* Fill the full height of the Upload pane (which is the
     parent .attach-pane, a flex column). The dropzone is
     the visually dominant affordance — letting it stretch keeps
     the cursor target generous and the dashed border meaningful
     instead of a short stripe at the top of the pane. */
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 0;
}
.galley-upload-dropzone:hover,
.galley-upload-dropzone.is-active {
  border-color: var(--accent-soft);
  background: var(--bg-hover);
}
.galley-upload-dropzone.is-busy {
  opacity: 0.6;
  pointer-events: none;
}
.galley-upload-icon {
  font-size: 28px;
  color: var(--accent-soft);
  margin-bottom: 8px;
}
.galley-upload-text {
  font-size: 13px;
  color: var(--ink);
  margin-bottom: 6px;
}
.galley-upload-hint {
  font-size: 11px;
  color: var(--ink-muted);
  font-style: italic;
}
.galley-upload-progress {
  margin-top: 12px;
  position: relative;
  height: 18px;
  border-radius: 9px;
  background: var(--bg-rule);
  overflow: hidden;
}
.galley-upload-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: var(--accent);
  transition: width 100ms;
}
.galley-upload-progress-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: white;
  font-weight: 600;
  mix-blend-mode: difference;
}

/* ----- Browse Drive panel (Picker entry button) -----
   Content-width, left-aligned to match the New Work modal's
   "Browse Google Drive" button. (`.galley-add-panel` is a plain
   block container, so a button without explicit width defaults
   to content-width / inline flow.) */
.galley-add-browse-btn {
  margin-top: 4px;
}
.galley-add-browse-btn:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* ----- Drive search panel (legacy — kept for any non-Picker
   callers; Picker is now the primary "find existing in Drive"
   flow). ----- */
.galley-add-search-input {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg-card);
  outline: none;
}
.galley-add-search-input:focus {
  border-color: var(--accent);
}
.galley-add-search-results {
  margin-top: 10px;
  max-height: 280px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.galley-add-search-status {
  margin-top: 8px;
  font-size: 11px;
  color: var(--ink-muted);
  font-style: italic;
}
.galley-add-search-row {
  appearance: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  background: var(--bg-card);
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  transition: background 100ms, border-color 100ms;
}
.galley-add-search-row:hover {
  background: var(--bg-hover);
  border-color: var(--accent-soft);
}
.galley-add-search-row-thumb {
  flex: 0 0 auto;
  width: 36px;
  height: 48px;
  background: var(--bg);
  border: 1px solid var(--bg-rule);
  border-radius: 4px;
  background-size: cover;
  background-position: center top;
  display: flex;
  align-items: center;
  justify-content: center;
}
.galley-add-search-row-meta {
  flex: 1 1 auto;
  min-width: 0;
}
.galley-add-search-row-name {
  font-size: 12px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.galley-add-search-row-date {
  font-size: 10px;
  color: var(--ink-muted);
  margin-top: 2px;
}

/* ----- URL panel ----- */
.galley-add-url-label,
.galley-add-url-input {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg-card);
  outline: none;
  margin-bottom: 8px;
}
.galley-add-url-label:focus,
.galley-add-url-input:focus {
  border-color: var(--accent-soft);
}
.galley-add-url-btn {
  /* Content-width, left-aligned (no width:100%) — matches the
     Browse Drive button. */
}

/* ============================================================
   GALLEY READER (fullscreen iframe overlay)
   ============================================================ */
.galley-reader-overlay {
  position: fixed;
  inset: 0;
  background: #1a1d22;
  z-index: 9000;
  display: flex;
  flex-direction: column;
}
.galley-reader-header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  background: #1f242c;
  color: #f0eadb;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  flex: 0 0 auto;
}
.galley-reader-titles {
  flex: 1 1 auto;
  min-width: 0;
}
.galley-reader-book-title {
  font-family: var(--logo);
  font-style: italic;
  font-weight: 700;
  font-size: 18px;
  letter-spacing: 0.01em;
  color: #f0eadb;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.galley-reader-filename {
  font-size: 11px;
  color: #aba599;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}
.galley-reader-drive-link {
  flex: 0 0 auto;
  font-size: 12px;
  color: #aba599;
  text-decoration: none;
}
.galley-reader-drive-link:hover {
  color: #f0eadb;
  text-decoration: underline;
}
.galley-reader-close {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 16px;
  color: #f0eadb;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: background 100ms, border-color 100ms;
  /* Sit above the iframe — the iframe lives in a sibling block,
     but some browsers' fullscreen-style iframes can grab pointer
     events that drift out of bounds. Explicit z-index keeps the
     close button reliably clickable. */
  position: relative;
  z-index: 1;
}
.galley-reader-close:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.4);
}
.galley-reader-body {
  flex: 1 1 auto;
  min-height: 0;
  background: #1a1d22;
}
.galley-reader-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: white;
}
.galley-reader-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #aba599;
}
.galley-reader-fallback {
  max-width: 480px;
  margin: 80px auto;
  padding: 32px;
  background: var(--bg-card);
  border-radius: 8px;
  text-align: center;
  color: var(--ink);
}
.galley-reader-fallback-title {
  font-family: var(--logo);
  font-style: italic;
  font-size: 22px;
  margin-bottom: 12px;
}
.galley-reader-fallback-msg {
  font-size: 13px;
  color: var(--ink-muted);
  margin-bottom: 16px;
  line-height: 1.5;
}
.galley-reader-fallback-btn {
  text-decoration: none;
}

/* ============================================================
   Tabbed sections in modals — folder-tab metaphor.

   Used by:
     • DriveAttachSection (Upload new / Pick existing) in the
       New Work + Add Galley modals.
     • The Metadata section (Search / Enter manually) in the
       New Work modal — same `.attach-tab` markup, so it
       picks up these styles for free.

   Visual:
     ┌───────────┬──────────┐
     │ active    │ inactive │   ← tabs sit ON TOP of the panel.
     ├───────────┘──────────┤   ← active tab merges (no bottom
     │                      │     border); inactive tab's bottom
     │   panel surface      │     border continues the panel's
     │                      │     top edge across the gap.
     └──────────────────────┘

   Active tab fills with --bg-selected (warm sepia tan), text
   stays --ink. Inactive tabs are transparent on the cream modal
   surface with --ink-soft text. The panel itself is --bg-card,
   one half-step lighter than the modal's --bg, with side +
   bottom borders only — no top border, because the tab row
   visually closes that edge.

   Other "selected = dark fill" surfaces in the app (filter
   chips, work-type chips, archive toggle, FAB, toast) are NOT
   tabs — they keep the --ink fill treatment. This sepia fill is
   reserved for tabs-as-folders.
   ============================================================ */
.attach-tabs {
  display: flex;
  gap: 4px;
  /* No bottom border on the row itself; the inactive tabs' bottom
     borders + the panel's top edge do that work. Z-index so the
     active tab paints on top of the panel border below. */
  position: relative;
  z-index: 1;
}
.attach-tab {
  /* flex:1 + min-width:0 → tabs split the row width 50/50.
     min-width keeps long labels from blowing the layout out. */
  flex: 1 1 0;
  min-width: 0;
  appearance: none;
  padding: 10px 14px;
  background: transparent;
  border: 1px solid var(--bg-rule);
  border-radius: 4px 4px 0 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 100ms, color 100ms, border-color 100ms;
  position: relative;
  /* Drop the tab 1px so its bottom edge overlaps with the panel's
     top edge. For the active tab (no bottom border), the panel's
     surface flows continuously up into the tab's fill area. For
     inactive tabs, this puts the bottom border exactly on the line
     that would be the panel's top border — same y, same color. */
  margin-bottom: -1px;
}
.attach-tab:hover {
  background: var(--bg-hover);
  color: var(--ink-soft);
}
.attach-tab.active {
  /* Front edge of the folder. Sepia fill is the sibling of the
     panel's --bg-card surface — both warm-paper-family, distinct
     but harmonious. Bottom border transparent (not absent) so the
     1px overlap above doesn't gain a visible line; on a 1x screen
     this collapses cleanly into the panel's top edge. */
  background: var(--bg-selected);
  border-bottom-color: transparent;
  color: var(--ink);
  font-weight: 500;
}

/* Folder body — the panel below the tabs. Side + bottom borders,
   rounded only at the bottom; no top border (closed by tabs). */
.attach-content {
  background: var(--bg-card);
  border: 1px solid var(--bg-rule);
  border-top: none;
  border-radius: 0 0 4px 4px;
  padding: 14px;
  position: relative;
  z-index: 0;
}

/* Tabbed content panel — fits the tallest tab variant (the Drive
   file picker), so all four tabs in New Work (Upload / Drive /
   Search / Manual) and both tabs in Add Galley (Upload / Drive)
   share the same locked size. Shorter tabs (Upload's drop zone,
   Manual's two-field form, an empty Search input) top-align and
   leave whitespace below — switching tabs never reflows the
   modal. Drive's file list overflows internally past ~5 rows. */
.attach-content {
  height: 280px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Collapse the modal-body's flex gap (14px) between the tab row
   and the panel that immediately follows it, so the active tab's
   bottom edge merges into the panel without a cream-colored
   seam. Scoped to direct children of .modal-body — the Add Galley
   panel wraps tabs+content in .galley-add-panel (block flow, no
   gap) so this rule doesn't affect it. */
.modal-body > .attach-tabs + .attach-content {
  margin-top: -14px;
}
.attach-content .attach-pane {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Vertical rhythm between stacked children (field groups,
     dropzones, picker, helper text). Matches the panel's outer
     14px padding so the spacing reads as one consistent grid. */
  gap: 14px;
}
.attach-content .attach-pane[hidden] { display: none; }

/* Embedded picker is now content-height (was flex: 1 1 auto,
   which made it stretch to fill the pane and pushed the helper
   text far below the list). The list itself has its own fixed
   height (.drive-search-list { height: 110px }) and its own
   internal scroll, so the pick-host doesn't need to claim
   extra vertical space. */
.attach-pick-host {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* Tighten the gap between the pick-host's last visible child
   (the fixed-height list) and the modal helper text that
   follows it. Parent .attach-pane uses gap: 14px between
   children; this margin reduces the effective gap to ~4px,
   matching the Search tab's helper-below-input spacing. */
.attach-pick-host + .modal-helper-text {
  margin-top: -10px;
}
.attach-pick-host .drive-search-body { height: 100%; }
.attach-pick-helper {
  font-size: 12px;
  color: var(--ink-faint);
  margin-top: 8px;
}

/* (The earlier .new-work-meta-content / .new-work-meta-helper /
   .new-work-section-eyebrow rules were retired when the New Work
   modal collapsed to a single 4-tab row above one .attach-content
   panel. Both modals now use .attach-content alone for the panel;
   the height-lock above sizes it to fit the Drive tab's file
   picker, the tallest variant.

   Class names were renamed from .drive-attach-* → .attach-*
   on 2026-05-13 since both New Work and Add Galley modals share
   the same tab system; the "drive-" prefix had become misleading.) */

/* ============================================================
   Folder picker — overlay layered above the calling modal so
   the user can pick an upload destination without losing the
   underlying form. Higher z-index than .modal-overlay (100)
   lets it sit on top when stacked. The picker modal is taller
   than the default 460px width since it's a file browser, not
   a one-off form.
   ============================================================ */
.folder-picker-overlay {
  z-index: 200;
}
.folder-picker-modal {
  max-width: 540px;
  display: flex;
  flex-direction: column;
  max-height: 80vh;
}
.folder-picker-modal .modal-body {
  flex: 1 1 auto;
  min-height: 320px;
}

/* Folder-mode action row: Save here + New folder + inline new-
   folder form. Sits above the breadcrumb so it stays visible
   while the user navigates. */
.drive-folder-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding: 4px 0 8px;
  border-bottom: 1px solid var(--bg-rule);
  margin-bottom: 4px;
}
.drive-folder-save-btn,
.drive-folder-new-btn {
  flex: 0 0 auto;
}
.drive-folder-save-btn:disabled,
.drive-folder-new-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.drive-folder-new-form {
  display: flex;
  gap: 6px;
  align-items: center;
  flex: 1 1 100%;
  flex-wrap: wrap;
}
.drive-folder-new-form[hidden] { display: none; }
.drive-folder-new-input {
  flex: 1 1 200px;
  padding: 6px 10px;
  font-size: 13px;
  background: var(--bg-soft, #fff);
  border: 1px solid var(--bg-rule);
  border-radius: 6px;
}
.drive-folder-new-input:focus {
  outline: none;
  border-color: var(--ink, #1a1814);
}
.drive-folder-new-error {
  flex: 1 1 100%;
  font-size: 12px;
  color: #b03a2e;
}
.drive-folder-new-error[hidden] { display: none; }

