/* ── Zoho Puvi — self-hosted, 5 weights ── */
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 300; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Light.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Regular.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Medium.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Semibold.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Bold.ttf") format("truetype"); }
:root {
  /* ── Zoho brand identity (rarely used in product chrome) ── */
  --zoho-red: #e42527;
  --zoho-red-600: #c61f21;
  --zoho-red-700: #a41a1c;
  --zoho-green: #089949;
  --zoho-blue: #226db4;
  --zoho-yellow: #f9b21d;

  /* ── Typography ── */
  --font: "Zoho Puvi", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* ── Layout ── */
  /* ── Breakpoints (max-width thresholds) ──
     Two anchors, three zones:
       Mobile:  width ≤ --bp-mobile
       Tablet:  --bp-mobile < width ≤ --bp-tablet
       Desktop: width > --bp-tablet
     Touch-target sizing is governed by `@media (pointer: coarse)`,
     independent of width — a desktop touchscreen needs the same
     44px targets as a phone. */
  --bp-mobile: 640px;
  --bp-tablet: 1024px;

  --nav-height: 3.5rem;
  --sidebar-width: 240px;
  --sidebar-collapsed: 0px;
  /* Width of the sidebar's tablet/mobile drawer. Also the distance the
     COLLAPSED sidebar is parked off-canvas at every width — the two must agree,
     or crossing the drawer breakpoint animates the difference. */
  --sidebar-drawer-width: 280px;
  --context-panel-width: 360px;
  /* Width of the context panel's collapsed form (see ContextRail). */
  --context-rail-width: 48px;
  --content-max-width: 800px;
  /* Floor of the editor column (issue #164). Derived, not chosen: at 1025px —
     the narrowest width where the sidebar still claims its 240px track — no
     overflow requires 240 + --editor-min + 360 <= 1025, so the ceiling on this
     value is 425px. It must also fit a 320px mobile viewport, where both rails
     are zero-width tracks. 320px satisfies both and is the narrowest viewport
     the app supports, so the column never asks for more room than exists. */
  --editor-min: 320px;
  /* Horizontal padding inside .app-shell__content-inner. A token because the
     panel/rail threshold calculation has to subtract it (see context-fit.ts),
     and a literal here would silently disagree with the layout. */
  --content-padding-x: 24px;
  --status-bar-height: 38px;
  --mobile-tabbar-height: 56px;

  /* ── Surfaces (warm neutral, hue ~35) ──
     Page-bg is the "desk" beneath the floating editor surface. Paper-2 is
     the elevated chrome (sidebar) that sits above the desk. Paper is the
     document — the brightest reading surface. */
  --page-bg: #E9E7E2;            /* the desk: shows through gutters + status bar */
  --paper:   #FFFFFF;            /* the editor surface */
  --paper-2: #F7F6F3;            /* Paper Recessed: sidebar */
  --paper-3: #EBEBEA;            /* Paper Inset: inputs / code / containers */

  /* ── Borders ── */
  --line:   #E9E9E7;             /* Line Quiet */
  --line-2: #D9D9D6;             /* Line Firm */

  /* ── Text hierarchy (warm ink; AA-compliant on --paper and --paper-2) ── */
  --text:   #37352F;             /* Ink: primary text */
  --text-2: #706F6C;             /* Ink Secondary: 4.66:1 on paper-2, 5.03:1 on paper */
  --text-3: #9C9488;             /* Ink Tertiary: placeholders, inactive icons */
  --text-4: #8E8B84;             /* Ink Ghost: timestamps, kbd (AA on white at ≥14px) */

  /* ── Accent: Muted Dusty Blue ──
     Lower-chroma than a pure brand blue so it sits calmly inside the warm
     neutral palette (hue ~35). Still AA-compliant on every paper surface
     (5.0:1 on paper, 4.6:1 on paper-2, 4.2:1 on paper-3). */
  --accent:      #4F6FA6;        /* dusty blue */
  --accent-dim:  #3D5A8C;        /* pressed / hover-active */
  --accent-soft: #EBEEF6;        /* pale tint for chips, badges, hover bg */
  --accent-glow: rgba(79, 111, 166, 0.10);
  --accent-glow-strong: rgba(79, 111, 166, 0.20); /* hover/active glass tint */
  --on-accent:   #FFFFFF;

  /* ── Feedback signals ── */
  --ok:   #38A169;               /* Signal OK */
  --warn: #D9730D;               /* Signal Warn */
  --err:  #E53E3E;               /* Signal Err */

  /* ── Badge backgrounds ── */
  --badge-bg-ok:   rgba(56, 161, 105, 0.10);
  --badge-bg-warn: rgba(217, 115, 13, 0.10);
  --badge-bg-err:  rgba(229, 62, 62, 0.10);
  --badge-bg-mute: rgba(55, 53, 47, 0.06);

  /* ── Shadow (state-only; flat at rest) ── */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 0 0 1px var(--line);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.12), 0 0 0 1px var(--line);

  /* ── Radii ──
     Softer, more contemporary scale. Chips/kbd stay tight at 6, controls and
     list items get a noticeable 10, cards/popovers feel like floating
     surfaces at 14, and modals/palette read as generous at 20. */
  --rounded-sm: 6px;
  --rounded-md: 10px;
  --rounded-lg: 14px;
  --rounded-xl: 20px;
  --rounded-pill: 9999px;

  /* ── Transitions ── */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --duration-fast: 150ms;
  --duration-normal: 300ms;
  --duration-entrance: 280ms;
  --duration-entrance-content: 500ms;

  /* ── Design-system text extensions ── */
  --text-body: #706F6C;
  --text-muted: #9C9488;
  --text-dim: #8E8B84;
  --divider: #D9D9D6;
  --card-border: #D9D9D6;

  /* ── Surface accent ── */
  --surface-accent: var(--accent);
  --surface-accent-bg: rgba(79, 111, 166, 0.10);

  /* ── Margin annotation colors (warm brown, distinct from accent) ── */
  --margin-attr:      #8B7355;
  --margin-attr-soft: #F5F0EA;
  --margin-attr-glow: rgba(139, 115, 85, 0.10);

  /* ── Inline highlight mark (warm amber/yellow) ──
     Used by editor-chrome.css for the plain `<mark>` (Tiptap Highlight)
     tint. Deliberately amber/yellow so it reads as a highlighter stroke —
     distinct in hue from the comment-linked highlight, which is the
     `.comment-highlight` class in editor.css built on the dusty-blue
     --accent-glow (10% alpha). */
  --mark-highlight: rgba(250, 204, 21, 0.45);

  /* ── Semantic surfaces ── */
  --surface-sidebar: var(--paper-2);
  --surface-content: var(--paper);

  /* ── Scrollbar ── */
  --scrollbar-thumb: rgba(55, 53, 47, 0.16);
  --scrollbar-thumb-hover: rgba(55, 53, 47, 0.28);

  /* ── Liquid Glass ──
     Translucent surface + heavy blur + saturation boost for the sidebar and
     context panel. Apparent color stays within a few units of paper-2 so the
     warm-neutral palette is preserved; the blur picks up the ambient gradient
     mesh painted on .app-shell. */
  --surface-glass: rgba(247, 246, 243, 0.68);
  --glass-blur: blur(50px) saturate(200%);
  /* Subtle top-edge highlight that reads as "wet material" even when the
     backdrop is uniform. Apple Tahoe's signature glass tell. */
  --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  /* Richer shadow stack for chip-sized glass elements (status pill, commit
     button, mode toggle). Top highlight + bottom inset shadow + outer drop
     gives small surfaces the same refractive feel as the large panels. */
  --glass-chip-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    inset 0 -1px 0 rgba(55, 53, 47, 0.06),
    0 1px 2px rgba(55, 53, 47, 0.06);

  /* Traditional chip elevation: subtle drop + 1px hairline outline. Used by
     status-bar chrome (status pill, commit button, mode toggle) where glass
     reads as too showy for the calm status strip. */
  --chip-shadow:
    0 1px 2px rgba(55, 53, 47, 0.08),
    0 0 0 1px rgba(55, 53, 47, 0.06);
  --chip-shadow-hover:
    0 2px 6px rgba(55, 53, 47, 0.10),
    0 0 0 1px rgba(55, 53, 47, 0.08);

  /* ── Ambient gradient blobs ──
     Palette-aware via color-mix(paper, accent N%). For warm-neutral (the
     default), all 4 blobs derive from the dusty-blue accent — a single
     hue at varying intensities, not the mixed-cardinal hues we used to
     ship. The other 8 palettes override these with their recipe-derived
     hex values in tokens.palettes.generated.css. Cascading: :root values
     here fall through whenever no [data-theme-palette='X'] override
     applies. */
  --ambient-yellow: color-mix(in oklch, var(--paper), var(--accent) 12%); /* signature, strongest */
  --ambient-blue:   color-mix(in oklch, var(--paper), var(--accent) 10%);
  --ambient-red:    color-mix(in oklch, var(--paper), var(--accent) 8%);
  --ambient-green:  color-mix(in oklch, var(--paper), var(--accent) 11%);
}
[data-theme='dark'] {
  /* ── Surfaces (warm dark) ──
     Page-bg sits deepest. Document (paper) emerges from the desk as a darker
     reading surface; sidebar (paper-2) sits above the desk as the lightest
     chrome surface. */
  --page-bg: #161513;            /* the desk in dark mode */
  --paper:   #1F1E1B;            /* dark-paper */
  --paper-2: #23221F;            /* dark-paper-recessed */
  --paper-3: #2D2B27;            /* dark-paper-inset */

  /* ── Borders ── */
  --line:   #38362F;
  --line-2: #46443D;

  /* ── Text hierarchy (warm ink, dark) ── */
  --text:   #E6E1D8;             /* dark-ink */
  --text-2: #B0AAA0;
  --text-3: #908A80;
  --text-4: #9A9690;             /* AA-compliant on dark-paper */

  /* ── Accent: Muted Dusty Blue (dark) ──
     Lighter dusty blue tuned for dark warm-paper. AAA on dark paper. */
  --accent:      #9DAFD6;        /* muted dusty blue, dark */
  --accent-dim:  #7C92BD;
  --accent-soft: rgba(157, 175, 214, 0.14);
  --accent-glow: rgba(157, 175, 214, 0.10);
  --accent-glow-strong: rgba(157, 175, 214, 0.24); /* hover/active glass tint */
  --on-accent:   #1F1E1B;

  /* ── Feedback signals (dark-tuned) ── */
  --ok:   #4ECB85;
  --warn: #E89A3D;
  --err:  #EF6262;

  /* ── Badge backgrounds ── */
  --badge-bg-ok:   rgba(78, 203, 133, 0.14);
  --badge-bg-warn: rgba(232, 154, 61, 0.14);
  --badge-bg-err:  rgba(239, 98, 98, 0.14);
  --badge-bg-mute: rgba(230, 225, 216, 0.06);

  /* ── Shadow ── */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--line);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--line);
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--line);

  /* ── Design-system text extensions ── */
  --text-body: #B0AAA0;
  --text-muted: #908A80;
  --text-dim: #9A9690;
  --divider: #38362F;
  --card-border: #38362F;

  /* ── Surface accent ── */
  --surface-accent: var(--accent);
  --surface-accent-bg: rgba(157, 175, 214, 0.10);

  /* ── Margin annotation colors ── */
  --margin-attr:      #C4A882;
  --margin-attr-soft: #2A2620;
  --margin-attr-glow: rgba(196, 168, 130, 0.10);

  /* ── Inline highlight mark (warm amber/yellow, dark) ──
     Lower alpha than the light value so the highlighter tint stays legible
     (doesn't blow out) against the dark paper surface. Still amber/yellow,
     still distinct from --accent-glow (dusty blue) used by
     .comment-highlight in editor.css. */
  --mark-highlight: rgba(240, 179, 30, 0.22);

  /* ── Semantic surfaces ── */
  --surface-sidebar: var(--paper-2);
  --surface-content: var(--paper);

  /* ── Scrollbar ── */
  --scrollbar-thumb: rgba(230, 225, 216, 0.16);
  --scrollbar-thumb-hover: rgba(230, 225, 216, 0.28);

  /* ── Liquid Glass (dark) ── */
  --surface-glass: rgba(35, 34, 31, 0.70);
  --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --glass-chip-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 1px 2px rgba(0, 0, 0, 0.30);

  /* Traditional chip elevation (dark) */
  --chip-shadow:
    0 1px 2px rgba(0, 0, 0, 0.30),
    0 0 0 1px rgba(255, 255, 255, 0.06);
  --chip-shadow-hover:
    0 2px 6px rgba(0, 0, 0, 0.40),
    0 0 0 1px rgba(255, 255, 255, 0.10);

  /* ── Ambient gradient blobs (dark) ──
     Palette-aware via color-mix on paper + accent. For warm-neutral dark
     this resolves to dusty-blue-tinted ambients (matching the warm-neutral
     accent). Higher accent percentages than light mode to compensate for
     the eye needing larger deltas at low luminance (Weber-Fechner).
     Palette-specific overrides in tokens.palettes.generated.css take
     precedence for the other 8 palettes. */
  --ambient-yellow: color-mix(in oklch, var(--paper), var(--accent) 22%); /* signature, strongest */
  --ambient-blue:   color-mix(in oklch, var(--paper), var(--accent) 18%);
  --ambient-red:    color-mix(in oklch, var(--paper), var(--accent) 16%);
  --ambient-green:  color-mix(in oklch, var(--paper), var(--accent) 20%);
}
/* ── Alternative palettes ──
   The :root + [data-theme='dark'] blocks above define the default
   `warm-neutral` palette (light + dark). Alternative palettes live in
   `design-tokens/palettes/*.json` and are compiled by Style Dictionary
   into `src/styles/tokens.palettes.generated.css`. That file is imported
   alongside this one at the top of `global.css` — CSS spec requires
   `@import` rules to precede all other statements, so chaining the
   import from here would be invalid.

   To add a new palette:
     1. Add `design-tokens/palettes/<name>.light.json` and
        `<name>.dark.json` (start from an existing pair as a template)
     2. Run `npm run tokens:build`
     3. Add an entry to PALETTES in
        `src/components/settings/SettingsModal.tsx` so the user can pick it
     4. Add an entry to PALETTES in `src/pages/ThemePreviewPage.tsx`
        so the preview shows it
     5. Add an entry to PALETTES in `.ladle/config.mjs`
        so the Ladle toolbar dropdown shows it

   See docs/THEME-TOKENS.md for the contrast targets every palette
   must meet and the ring-by-ring guide on which tokens to override. */
/* AUTOGENERATED by design-tokens/build.mjs — do not edit by hand.
 * Source: design-tokens/palettes/*.json
 * Rebuild: npm run tokens:build
 */

/* ── cool-slate · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='cool-slate'] {
  --page-bg: #12161C;
  --paper: #1B1F26;
  --paper-2: #1F242C;
  --paper-3: #2A3038;
  --line: #2F3640;
  --line-2: #3E4750;
  --accent: #93AFDB;
  --accent-dim: #7090C2;
  --accent-soft: rgba(147, 175, 219, 0.14);
  --accent-glow: rgba(147, 175, 219, 0.10);
  --accent-glow-strong: rgba(147, 175, 219, 0.24);
  --on-accent: #1B1F26;
  --ambient-blue: #1A2235;
  --ambient-yellow: #20211B;
  --ambient-red: #221B19;
  --ambient-green: #1B2522;
  --surface-accent-bg: rgba(147, 175, 219, 0.10);
  --surface-glass: rgba(31, 36, 44, 0.70);
}

/* ── cool-slate · light ────────────────────────────────────────── */
[data-theme-palette='cool-slate'] {
  --page-bg: #E6E9ED;
  --paper: #FFFFFF;
  --paper-2: #F2F4F7;
  --paper-3: #E5E9EE;
  --line: #E1E5EA;
  --line-2: #CDD3DA;
  --accent: #3D6AAF;
  --accent-dim: #2E548F;
  --accent-soft: #E2EAF6;
  --accent-glow: rgba(61, 106, 175, 0.10);
  --accent-glow-strong: rgba(61, 106, 175, 0.20);
  --ambient-blue: #D9DEE6;
  --ambient-yellow: #E2DFD3;
  --ambient-red: #E5DBD9;
  --ambient-green: #D9E0DA;
  --surface-accent-bg: rgba(61, 106, 175, 0.10);
  --surface-glass: rgba(242, 244, 247, 0.68);
}

/* ── forest · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='forest'] {
  --page-bg: #14160F;
  --paper: #1D2017;
  --paper-2: #21241A;
  --paper-3: #2A2E22;
  --line: #353826;
  --line-2: #424733;
  --accent: #9DC09F;
  --accent-dim: #7BA37D;
  --accent-soft: rgba(157, 192, 159, 0.14);
  --accent-glow: rgba(157, 192, 159, 0.10);
  --accent-glow-strong: rgba(157, 192, 159, 0.24);
  --on-accent: #1D2017;
  --ambient-blue: #1A2228;
  --ambient-yellow: #22201A;
  --ambient-red: #221A18;
  --ambient-green: #1F2A1F;
  --surface-accent-bg: rgba(157, 192, 159, 0.10);
  --surface-glass: rgba(33, 36, 26, 0.70);
}

/* ── forest · light ────────────────────────────────────────── */
[data-theme-palette='forest'] {
  --page-bg: #E8E6D9;
  --paper: #FAF9F3;
  --paper-2: #F1EFE3;
  --paper-3: #E6E3D5;
  --line: #E2DFD2;
  --line-2: #CDC8B5;
  --accent: #4F7A55;
  --accent-dim: #3D5F42;
  --accent-soft: #E6EEE5;
  --accent-glow: rgba(79, 122, 85, 0.10);
  --accent-glow-strong: rgba(79, 122, 85, 0.20);
  --ambient-blue: #DEE3DC;
  --ambient-yellow: #E6E2CC;
  --ambient-red: #E2D9CF;
  --ambient-green: #D9E1CF;
  --surface-accent-bg: rgba(79, 122, 85, 0.10);
  --surface-glass: rgba(241, 239, 227, 0.68);
}

/* ── harbor · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='harbor'] {
  --page-bg: #020B0E;
  --paper: #0B1316;
  --paper-2: #101A1D;
  --paper-3: #17272B;
  --line: #1C292C;
  --line-2: #2C3E42;
  --accent: #9DB6B8;
  --accent-dim: #7B9698;
  --accent-soft: rgba(157, 182, 184, 0.14);
  --accent-glow: rgba(157, 182, 184, 0.1);
  --accent-glow-strong: rgba(157, 182, 184, 0.24);
  --on-accent: #0B1316;
  --ambient-blue: #071117;
  --ambient-yellow: #021014;
  --ambient-red: #030E0E;
  --ambient-green: #040F14;
  --surface-accent-bg: rgba(157, 182, 184, 0.1);
  --surface-glass: rgba(16, 26, 29, 0.6);
}

/* ── harbor · light ────────────────────────────────────────── */
[data-theme-palette='harbor'] {
  --page-bg: #CBDFE4;
  --paper: #F4F9FB;
  --paper-2: #E8F0F3;
  --paper-3: #CFE2E7;
  --line: #CDDADE;
  --line-2: #9CB6BD;
  --accent: #607274;
  --accent-dim: #445558;
  --accent-soft: #D6EDF2;
  --accent-glow: rgba(96, 114, 116, 0.1);
  --accent-glow-strong: rgba(96, 114, 116, 0.2);
  --ambient-blue: #D7E7F3;
  --ambient-yellow: #CDE7ED;
  --ambient-red: #CEE3E4;
  --ambient-green: #D0E5EE;
  --surface-accent-bg: rgba(96, 114, 116, 0.1);
  --surface-glass: rgba(232, 240, 243, 0.58);
}

/* ── ink · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='ink'] {
  --page-bg: #050911;
  --paper: #0C1217;
  --paper-2: #12191F;
  --paper-3: #1E242F;
  --line: #21272F;
  --line-2: #333B47;
  --accent: #6E94D4;
  --accent-dim: #4C73B5;
  --accent-soft: rgba(110, 148, 212, 0.14);
  --accent-glow: rgba(110, 148, 212, 0.1);
  --accent-glow-strong: rgba(110, 148, 212, 0.24);
  --on-accent: #0C1217;
  --ambient-blue: #0E0E18;
  --ambient-yellow: #070D18;
  --ambient-red: #050C13;
  --ambient-green: #0A0D17;
  --surface-accent-bg: rgba(110, 148, 212, 0.1);
  --surface-glass: rgba(18, 25, 31, 0.6);
}

/* ── ink · light ────────────────────────────────────────── */
[data-theme-palette='ink'] {
  --page-bg: #D2DBEA;
  --paper: #F8FCFF;
  --paper-2: #ECF3F8;
  --paper-3: #D6DFED;
  --line: #D2D8E2;
  --line-2: #A6B2C5;
  --accent: #0F2C59;
  --accent-dim: #081A38;
  --accent-soft: #DEE9F9;
  --accent-glow: rgba(15, 44, 89, 0.1);
  --accent-glow-strong: rgba(15, 44, 89, 0.2);
  --ambient-blue: #E2E3F4;
  --ambient-yellow: #D7E2F4;
  --ambient-red: #D2E0EC;
  --ambient-green: #DBE1F3;
  --surface-accent-bg: rgba(15, 44, 89, 0.1);
  --surface-glass: rgba(236, 243, 248, 0.58);
}

/* ── linen · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='linen'] {
  --page-bg: #0E0802;
  --paper: #15110B;
  --paper-2: #1D1710;
  --paper-3: #2B2218;
  --line: #2C251D;
  --line-2: #43392D;
  --accent: #C5B59C;
  --accent-dim: #A8987F;
  --accent-soft: rgba(197, 181, 156, 0.14);
  --accent-glow: rgba(197, 181, 156, 0.1);
  --accent-glow-strong: rgba(197, 181, 156, 0.24);
  --on-accent: #15110B;
  --ambient-blue: #120F06;
  --ambient-yellow: #140B03;
  --ambient-red: #110904;
  --ambient-green: #120C04;
  --surface-accent-bg: rgba(197, 181, 156, 0.1);
  --surface-glass: rgba(29, 23, 16, 0.6);
}

/* ── linen · light ────────────────────────────────────────── */
[data-theme-palette='linen'] {
  --page-bg: #E5D9CC;
  --paper: #FBF8F4;
  --paper-2: #F2EEE7;
  --paper-3: #E8DCCF;
  --line: #DED6CD;
  --line-2: #BEAE9D;
  --accent: #8C7456;
  --accent-dim: #6B5840;
  --accent-soft: #F3E5D6;
  --accent-glow: rgba(140, 116, 86, 0.1);
  --accent-glow-strong: rgba(140, 116, 86, 0.2);
  --ambient-blue: #E9E5D3;
  --ambient-yellow: #EEDFCE;
  --ambient-red: #EADBD0;
  --ambient-green: #EAE0CF;
  --surface-accent-bg: rgba(140, 116, 86, 0.1);
  --surface-glass: rgba(242, 238, 231, 0.58);
}

/* ── mauve · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='mauve'] {
  --page-bg: #0D070E;
  --paper: #16100B;
  --paper-2: #1E1711;
  --paper-3: #29212B;
  --line: #2A242C;
  --line-2: #403742;
  --accent: #C9B6CE;
  --accent-dim: #A697AC;
  --accent-soft: rgba(201, 182, 206, 0.14);
  --accent-glow: rgba(201, 182, 206, 0.1);
  --accent-glow-strong: rgba(201, 182, 206, 0.24);
  --on-accent: #16100B;
  --ambient-blue: #160C11;
  --ambient-yellow: #120A14;
  --ambient-red: #0D0912;
  --ambient-green: #130A12;
  --surface-accent-bg: rgba(201, 182, 206, 0.1);
  --surface-glass: rgba(30, 23, 17, 0.6);
}

/* ── mauve · light ────────────────────────────────────────── */
[data-theme-palette='mauve'] {
  --page-bg: #E1D6E4;
  --paper: #FCF8F4;
  --paper-2: #F4EDE8;
  --paper-3: #E5DAE7;
  --line: #DCD4DE;
  --line-2: #BAABBD;
  --accent: #7D6E83;
  --accent-dim: #524754;
  --accent-soft: #F0E3F3;
  --accent-glow: rgba(125, 110, 131, 0.1);
  --accent-glow-strong: rgba(125, 110, 131, 0.2);
  --ambient-blue: #F1DFE8;
  --ambient-yellow: #EADCED;
  --ambient-red: #E1DBEA;
  --ambient-green: #EBDCE9;
  --surface-accent-bg: rgba(125, 110, 131, 0.1);
  --surface-glass: rgba(244, 237, 232, 0.58);
}

/* ── sage · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='sage'] {
  --page-bg: #070B04;
  --paper: #13120B;
  --paper-2: #1B1810;
  --paper-3: #20261B;
  --line: #23281F;
  --line-2: #363D30;
  --accent: #BBCBB3;
  --accent-dim: #9AAB94;
  --accent-soft: rgba(187, 203, 179, 0.14);
  --accent-glow: rgba(187, 203, 179, 0.1);
  --accent-glow-strong: rgba(187, 203, 179, 0.24);
  --on-accent: #13120B;
  --ambient-blue: #08120B;
  --ambient-yellow: #0A0F05;
  --ambient-red: #0B0C04;
  --ambient-green: #081007;
  --surface-accent-bg: rgba(187, 203, 179, 0.1);
  --surface-glass: rgba(27, 24, 16, 0.6);
}

/* ── sage · light ────────────────────────────────────────── */
[data-theme-palette='sage'] {
  --page-bg: #D6DECF;
  --paper: #FAF8F4;
  --paper-2: #F0EEE7;
  --paper-3: #D9E1D2;
  --line: #D4DACF;
  --line-2: #ABB5A1;
  --accent: #6E7E68;
  --accent-dim: #4F5C4A;
  --accent-soft: #E2ECDA;
  --accent-glow: rgba(110, 126, 104, 0.1);
  --accent-glow-strong: rgba(110, 126, 104, 0.2);
  --ambient-blue: #D9EADE;
  --ambient-yellow: #DBE5D2;
  --ambient-red: #DDE0CF;
  --ambient-green: #D9E6D6;
  --surface-accent-bg: rgba(110, 126, 104, 0.1);
  --surface-glass: rgba(240, 238, 231, 0.58);
}

/* ── terracotta · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='terracotta'] {
  --page-bg: #100605;
  --paper: #17100C;
  --paper-2: #1E1611;
  --paper-3: #2E201D;
  --line: #2F2321;
  --line-2: #463632;
  --accent: #E89D8B;
  --accent-dim: #C77D6A;
  --accent-soft: rgba(232, 157, 139, 0.14);
  --accent-glow: rgba(232, 157, 139, 0.1);
  --accent-glow-strong: rgba(232, 157, 139, 0.24);
  --on-accent: #17100C;
  --ambient-blue: #160D07;
  --ambient-yellow: #160907;
  --ambient-red: #120808;
  --ambient-green: #150A06;
  --surface-accent-bg: rgba(232, 157, 139, 0.1);
  --surface-glass: rgba(30, 22, 17, 0.6);
}

/* ── terracotta · light ────────────────────────────────────────── */
[data-theme-palette='terracotta'] {
  --page-bg: #E9D6D1;
  --paper: #FCF7F5;
  --paper-2: #F4EDE8;
  --paper-3: #ECD9D4;
  --line: #E1D4D1;
  --line-2: #C3ABA4;
  --accent: #C5705D;
  --accent-dim: #9D5346;
  --accent-soft: #F8E2DD;
  --accent-glow: rgba(197, 112, 93, 0.1);
  --accent-glow-strong: rgba(197, 112, 93, 0.2);
  --ambient-blue: #F1E1D5;
  --ambient-yellow: #F3DBD5;
  --ambient-red: #EDD9D8;
  --ambient-green: #F1DDD4;
  --surface-accent-bg: rgba(197, 112, 93, 0.1);
  --surface-glass: rgba(244, 237, 232, 0.58);
}
/* Import order matters — @import must precede all other rules per the CSS
   spec, and tokens.css must come before the palette overrides so the
   `[data-theme-palette='X']` selectors in tokens.palettes.generated.css
   land later in cascade order than the :root defaults. */
/* ── Zoho Puvi — self-hosted, 5 weights ── */
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 300; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Light.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Regular.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Medium.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Semibold.ttf") format("truetype"); }
@font-face { font-family: "Zoho Puvi"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/zoho-puvi/ZohoPuvi-Bold.ttf") format("truetype"); }
:root {
  /* ── Zoho brand identity (rarely used in product chrome) ── */
  --zoho-red: #e42527;
  --zoho-red-600: #c61f21;
  --zoho-red-700: #a41a1c;
  --zoho-green: #089949;
  --zoho-blue: #226db4;
  --zoho-yellow: #f9b21d;

  /* ── Typography ── */
  --font: "Zoho Puvi", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* ── Layout ── */
  /* ── Breakpoints (max-width thresholds) ──
     Two anchors, three zones:
       Mobile:  width ≤ --bp-mobile
       Tablet:  --bp-mobile < width ≤ --bp-tablet
       Desktop: width > --bp-tablet
     Touch-target sizing is governed by `@media (pointer: coarse)`,
     independent of width — a desktop touchscreen needs the same
     44px targets as a phone. */
  --bp-mobile: 640px;
  --bp-tablet: 1024px;

  --nav-height: 3.5rem;
  --sidebar-width: 240px;
  --sidebar-collapsed: 0px;
  /* Width of the sidebar's tablet/mobile drawer. Also the distance the
     COLLAPSED sidebar is parked off-canvas at every width — the two must agree,
     or crossing the drawer breakpoint animates the difference. */
  --sidebar-drawer-width: 280px;
  --context-panel-width: 360px;
  /* Width of the context panel's collapsed form (see ContextRail). */
  --context-rail-width: 48px;
  --content-max-width: 800px;
  /* Floor of the editor column (issue #164). Derived, not chosen: at 1025px —
     the narrowest width where the sidebar still claims its 240px track — no
     overflow requires 240 + --editor-min + 360 <= 1025, so the ceiling on this
     value is 425px. It must also fit a 320px mobile viewport, where both rails
     are zero-width tracks. 320px satisfies both and is the narrowest viewport
     the app supports, so the column never asks for more room than exists. */
  --editor-min: 320px;
  /* Horizontal padding inside .app-shell__content-inner. A token because the
     panel/rail threshold calculation has to subtract it (see context-fit.ts),
     and a literal here would silently disagree with the layout. */
  --content-padding-x: 24px;
  --status-bar-height: 38px;
  --mobile-tabbar-height: 56px;

  /* ── Surfaces (warm neutral, hue ~35) ──
     Page-bg is the "desk" beneath the floating editor surface. Paper-2 is
     the elevated chrome (sidebar) that sits above the desk. Paper is the
     document — the brightest reading surface. */
  --page-bg: #E9E7E2;            /* the desk: shows through gutters + status bar */
  --paper:   #FFFFFF;            /* the editor surface */
  --paper-2: #F7F6F3;            /* Paper Recessed: sidebar */
  --paper-3: #EBEBEA;            /* Paper Inset: inputs / code / containers */

  /* ── Borders ── */
  --line:   #E9E9E7;             /* Line Quiet */
  --line-2: #D9D9D6;             /* Line Firm */

  /* ── Text hierarchy (warm ink; AA-compliant on --paper and --paper-2) ── */
  --text:   #37352F;             /* Ink: primary text */
  --text-2: #706F6C;             /* Ink Secondary: 4.66:1 on paper-2, 5.03:1 on paper */
  --text-3: #9C9488;             /* Ink Tertiary: placeholders, inactive icons */
  --text-4: #8E8B84;             /* Ink Ghost: timestamps, kbd (AA on white at ≥14px) */

  /* ── Accent: Muted Dusty Blue ──
     Lower-chroma than a pure brand blue so it sits calmly inside the warm
     neutral palette (hue ~35). Still AA-compliant on every paper surface
     (5.0:1 on paper, 4.6:1 on paper-2, 4.2:1 on paper-3). */
  --accent:      #4F6FA6;        /* dusty blue */
  --accent-dim:  #3D5A8C;        /* pressed / hover-active */
  --accent-soft: #EBEEF6;        /* pale tint for chips, badges, hover bg */
  --accent-glow: rgba(79, 111, 166, 0.10);
  --accent-glow-strong: rgba(79, 111, 166, 0.20); /* hover/active glass tint */
  --on-accent:   #FFFFFF;

  /* ── Feedback signals ── */
  --ok:   #38A169;               /* Signal OK */
  --warn: #D9730D;               /* Signal Warn */
  --err:  #E53E3E;               /* Signal Err */

  /* ── Badge backgrounds ── */
  --badge-bg-ok:   rgba(56, 161, 105, 0.10);
  --badge-bg-warn: rgba(217, 115, 13, 0.10);
  --badge-bg-err:  rgba(229, 62, 62, 0.10);
  --badge-bg-mute: rgba(55, 53, 47, 0.06);

  /* ── Shadow (state-only; flat at rest) ── */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 0 0 1px var(--line);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.12), 0 0 0 1px var(--line);

  /* ── Radii ──
     Softer, more contemporary scale. Chips/kbd stay tight at 6, controls and
     list items get a noticeable 10, cards/popovers feel like floating
     surfaces at 14, and modals/palette read as generous at 20. */
  --rounded-sm: 6px;
  --rounded-md: 10px;
  --rounded-lg: 14px;
  --rounded-xl: 20px;
  --rounded-pill: 9999px;

  /* ── Transitions ── */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --duration-fast: 150ms;
  --duration-normal: 300ms;
  --duration-entrance: 280ms;
  --duration-entrance-content: 500ms;

  /* ── Design-system text extensions ── */
  --text-body: #706F6C;
  --text-muted: #9C9488;
  --text-dim: #8E8B84;
  --divider: #D9D9D6;
  --card-border: #D9D9D6;

  /* ── Surface accent ── */
  --surface-accent: var(--accent);
  --surface-accent-bg: rgba(79, 111, 166, 0.10);

  /* ── Margin annotation colors (warm brown, distinct from accent) ── */
  --margin-attr:      #8B7355;
  --margin-attr-soft: #F5F0EA;
  --margin-attr-glow: rgba(139, 115, 85, 0.10);

  /* ── Inline highlight mark (warm amber/yellow) ──
     Used by editor-chrome.css for the plain `<mark>` (Tiptap Highlight)
     tint. Deliberately amber/yellow so it reads as a highlighter stroke —
     distinct in hue from the comment-linked highlight, which is the
     `.comment-highlight` class in editor.css built on the dusty-blue
     --accent-glow (10% alpha). */
  --mark-highlight: rgba(250, 204, 21, 0.45);

  /* ── Semantic surfaces ── */
  --surface-sidebar: var(--paper-2);
  --surface-content: var(--paper);

  /* ── Scrollbar ── */
  --scrollbar-thumb: rgba(55, 53, 47, 0.16);
  --scrollbar-thumb-hover: rgba(55, 53, 47, 0.28);

  /* ── Liquid Glass ──
     Translucent surface + heavy blur + saturation boost for the sidebar and
     context panel. Apparent color stays within a few units of paper-2 so the
     warm-neutral palette is preserved; the blur picks up the ambient gradient
     mesh painted on .app-shell. */
  --surface-glass: rgba(247, 246, 243, 0.68);
  --glass-blur: blur(50px) saturate(200%);
  /* Subtle top-edge highlight that reads as "wet material" even when the
     backdrop is uniform. Apple Tahoe's signature glass tell. */
  --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  /* Richer shadow stack for chip-sized glass elements (status pill, commit
     button, mode toggle). Top highlight + bottom inset shadow + outer drop
     gives small surfaces the same refractive feel as the large panels. */
  --glass-chip-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    inset 0 -1px 0 rgba(55, 53, 47, 0.06),
    0 1px 2px rgba(55, 53, 47, 0.06);

  /* Traditional chip elevation: subtle drop + 1px hairline outline. Used by
     status-bar chrome (status pill, commit button, mode toggle) where glass
     reads as too showy for the calm status strip. */
  --chip-shadow:
    0 1px 2px rgba(55, 53, 47, 0.08),
    0 0 0 1px rgba(55, 53, 47, 0.06);
  --chip-shadow-hover:
    0 2px 6px rgba(55, 53, 47, 0.10),
    0 0 0 1px rgba(55, 53, 47, 0.08);

  /* ── Ambient gradient blobs ──
     Palette-aware via color-mix(paper, accent N%). For warm-neutral (the
     default), all 4 blobs derive from the dusty-blue accent — a single
     hue at varying intensities, not the mixed-cardinal hues we used to
     ship. The other 8 palettes override these with their recipe-derived
     hex values in tokens.palettes.generated.css. Cascading: :root values
     here fall through whenever no [data-theme-palette='X'] override
     applies. */
  --ambient-yellow: color-mix(in oklch, var(--paper), var(--accent) 12%); /* signature, strongest */
  --ambient-blue:   color-mix(in oklch, var(--paper), var(--accent) 10%);
  --ambient-red:    color-mix(in oklch, var(--paper), var(--accent) 8%);
  --ambient-green:  color-mix(in oklch, var(--paper), var(--accent) 11%);
}
[data-theme='dark'] {
  /* ── Surfaces (warm dark) ──
     Page-bg sits deepest. Document (paper) emerges from the desk as a darker
     reading surface; sidebar (paper-2) sits above the desk as the lightest
     chrome surface. */
  --page-bg: #161513;            /* the desk in dark mode */
  --paper:   #1F1E1B;            /* dark-paper */
  --paper-2: #23221F;            /* dark-paper-recessed */
  --paper-3: #2D2B27;            /* dark-paper-inset */

  /* ── Borders ── */
  --line:   #38362F;
  --line-2: #46443D;

  /* ── Text hierarchy (warm ink, dark) ── */
  --text:   #E6E1D8;             /* dark-ink */
  --text-2: #B0AAA0;
  --text-3: #908A80;
  --text-4: #9A9690;             /* AA-compliant on dark-paper */

  /* ── Accent: Muted Dusty Blue (dark) ──
     Lighter dusty blue tuned for dark warm-paper. AAA on dark paper. */
  --accent:      #9DAFD6;        /* muted dusty blue, dark */
  --accent-dim:  #7C92BD;
  --accent-soft: rgba(157, 175, 214, 0.14);
  --accent-glow: rgba(157, 175, 214, 0.10);
  --accent-glow-strong: rgba(157, 175, 214, 0.24); /* hover/active glass tint */
  --on-accent:   #1F1E1B;

  /* ── Feedback signals (dark-tuned) ── */
  --ok:   #4ECB85;
  --warn: #E89A3D;
  --err:  #EF6262;

  /* ── Badge backgrounds ── */
  --badge-bg-ok:   rgba(78, 203, 133, 0.14);
  --badge-bg-warn: rgba(232, 154, 61, 0.14);
  --badge-bg-err:  rgba(239, 98, 98, 0.14);
  --badge-bg-mute: rgba(230, 225, 216, 0.06);

  /* ── Shadow ── */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--line);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--line);
  --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--line);

  /* ── Design-system text extensions ── */
  --text-body: #B0AAA0;
  --text-muted: #908A80;
  --text-dim: #9A9690;
  --divider: #38362F;
  --card-border: #38362F;

  /* ── Surface accent ── */
  --surface-accent: var(--accent);
  --surface-accent-bg: rgba(157, 175, 214, 0.10);

  /* ── Margin annotation colors ── */
  --margin-attr:      #C4A882;
  --margin-attr-soft: #2A2620;
  --margin-attr-glow: rgba(196, 168, 130, 0.10);

  /* ── Inline highlight mark (warm amber/yellow, dark) ──
     Lower alpha than the light value so the highlighter tint stays legible
     (doesn't blow out) against the dark paper surface. Still amber/yellow,
     still distinct from --accent-glow (dusty blue) used by
     .comment-highlight in editor.css. */
  --mark-highlight: rgba(240, 179, 30, 0.22);

  /* ── Semantic surfaces ── */
  --surface-sidebar: var(--paper-2);
  --surface-content: var(--paper);

  /* ── Scrollbar ── */
  --scrollbar-thumb: rgba(230, 225, 216, 0.16);
  --scrollbar-thumb-hover: rgba(230, 225, 216, 0.28);

  /* ── Liquid Glass (dark) ── */
  --surface-glass: rgba(35, 34, 31, 0.70);
  --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --glass-chip-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 1px 2px rgba(0, 0, 0, 0.30);

  /* Traditional chip elevation (dark) */
  --chip-shadow:
    0 1px 2px rgba(0, 0, 0, 0.30),
    0 0 0 1px rgba(255, 255, 255, 0.06);
  --chip-shadow-hover:
    0 2px 6px rgba(0, 0, 0, 0.40),
    0 0 0 1px rgba(255, 255, 255, 0.10);

  /* ── Ambient gradient blobs (dark) ──
     Palette-aware via color-mix on paper + accent. For warm-neutral dark
     this resolves to dusty-blue-tinted ambients (matching the warm-neutral
     accent). Higher accent percentages than light mode to compensate for
     the eye needing larger deltas at low luminance (Weber-Fechner).
     Palette-specific overrides in tokens.palettes.generated.css take
     precedence for the other 8 palettes. */
  --ambient-yellow: color-mix(in oklch, var(--paper), var(--accent) 22%); /* signature, strongest */
  --ambient-blue:   color-mix(in oklch, var(--paper), var(--accent) 18%);
  --ambient-red:    color-mix(in oklch, var(--paper), var(--accent) 16%);
  --ambient-green:  color-mix(in oklch, var(--paper), var(--accent) 20%);
}
/* ── Alternative palettes ──
   The :root + [data-theme='dark'] blocks above define the default
   `warm-neutral` palette (light + dark). Alternative palettes live in
   `design-tokens/palettes/*.json` and are compiled by Style Dictionary
   into `src/styles/tokens.palettes.generated.css`. That file is imported
   alongside this one at the top of `global.css` — CSS spec requires
   `@import` rules to precede all other statements, so chaining the
   import from here would be invalid.

   To add a new palette:
     1. Add `design-tokens/palettes/<name>.light.json` and
        `<name>.dark.json` (start from an existing pair as a template)
     2. Run `npm run tokens:build`
     3. Add an entry to PALETTES in
        `src/components/settings/SettingsModal.tsx` so the user can pick it
     4. Add an entry to PALETTES in `src/pages/ThemePreviewPage.tsx`
        so the preview shows it
     5. Add an entry to PALETTES in `.ladle/config.mjs`
        so the Ladle toolbar dropdown shows it

   See docs/THEME-TOKENS.md for the contrast targets every palette
   must meet and the ring-by-ring guide on which tokens to override. */
/* AUTOGENERATED by design-tokens/build.mjs — do not edit by hand.
 * Source: design-tokens/palettes/*.json
 * Rebuild: npm run tokens:build
 */
/* ── cool-slate · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='cool-slate'] {
  --page-bg: #12161C;
  --paper: #1B1F26;
  --paper-2: #1F242C;
  --paper-3: #2A3038;
  --line: #2F3640;
  --line-2: #3E4750;
  --accent: #93AFDB;
  --accent-dim: #7090C2;
  --accent-soft: rgba(147, 175, 219, 0.14);
  --accent-glow: rgba(147, 175, 219, 0.10);
  --accent-glow-strong: rgba(147, 175, 219, 0.24);
  --on-accent: #1B1F26;
  --ambient-blue: #1A2235;
  --ambient-yellow: #20211B;
  --ambient-red: #221B19;
  --ambient-green: #1B2522;
  --surface-accent-bg: rgba(147, 175, 219, 0.10);
  --surface-glass: rgba(31, 36, 44, 0.70);
}
/* ── cool-slate · light ────────────────────────────────────────── */
[data-theme-palette='cool-slate'] {
  --page-bg: #E6E9ED;
  --paper: #FFFFFF;
  --paper-2: #F2F4F7;
  --paper-3: #E5E9EE;
  --line: #E1E5EA;
  --line-2: #CDD3DA;
  --accent: #3D6AAF;
  --accent-dim: #2E548F;
  --accent-soft: #E2EAF6;
  --accent-glow: rgba(61, 106, 175, 0.10);
  --accent-glow-strong: rgba(61, 106, 175, 0.20);
  --ambient-blue: #D9DEE6;
  --ambient-yellow: #E2DFD3;
  --ambient-red: #E5DBD9;
  --ambient-green: #D9E0DA;
  --surface-accent-bg: rgba(61, 106, 175, 0.10);
  --surface-glass: rgba(242, 244, 247, 0.68);
}
/* ── forest · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='forest'] {
  --page-bg: #14160F;
  --paper: #1D2017;
  --paper-2: #21241A;
  --paper-3: #2A2E22;
  --line: #353826;
  --line-2: #424733;
  --accent: #9DC09F;
  --accent-dim: #7BA37D;
  --accent-soft: rgba(157, 192, 159, 0.14);
  --accent-glow: rgba(157, 192, 159, 0.10);
  --accent-glow-strong: rgba(157, 192, 159, 0.24);
  --on-accent: #1D2017;
  --ambient-blue: #1A2228;
  --ambient-yellow: #22201A;
  --ambient-red: #221A18;
  --ambient-green: #1F2A1F;
  --surface-accent-bg: rgba(157, 192, 159, 0.10);
  --surface-glass: rgba(33, 36, 26, 0.70);
}
/* ── forest · light ────────────────────────────────────────── */
[data-theme-palette='forest'] {
  --page-bg: #E8E6D9;
  --paper: #FAF9F3;
  --paper-2: #F1EFE3;
  --paper-3: #E6E3D5;
  --line: #E2DFD2;
  --line-2: #CDC8B5;
  --accent: #4F7A55;
  --accent-dim: #3D5F42;
  --accent-soft: #E6EEE5;
  --accent-glow: rgba(79, 122, 85, 0.10);
  --accent-glow-strong: rgba(79, 122, 85, 0.20);
  --ambient-blue: #DEE3DC;
  --ambient-yellow: #E6E2CC;
  --ambient-red: #E2D9CF;
  --ambient-green: #D9E1CF;
  --surface-accent-bg: rgba(79, 122, 85, 0.10);
  --surface-glass: rgba(241, 239, 227, 0.68);
}
/* ── harbor · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='harbor'] {
  --page-bg: #020B0E;
  --paper: #0B1316;
  --paper-2: #101A1D;
  --paper-3: #17272B;
  --line: #1C292C;
  --line-2: #2C3E42;
  --accent: #9DB6B8;
  --accent-dim: #7B9698;
  --accent-soft: rgba(157, 182, 184, 0.14);
  --accent-glow: rgba(157, 182, 184, 0.1);
  --accent-glow-strong: rgba(157, 182, 184, 0.24);
  --on-accent: #0B1316;
  --ambient-blue: #071117;
  --ambient-yellow: #021014;
  --ambient-red: #030E0E;
  --ambient-green: #040F14;
  --surface-accent-bg: rgba(157, 182, 184, 0.1);
  --surface-glass: rgba(16, 26, 29, 0.6);
}
/* ── harbor · light ────────────────────────────────────────── */
[data-theme-palette='harbor'] {
  --page-bg: #CBDFE4;
  --paper: #F4F9FB;
  --paper-2: #E8F0F3;
  --paper-3: #CFE2E7;
  --line: #CDDADE;
  --line-2: #9CB6BD;
  --accent: #607274;
  --accent-dim: #445558;
  --accent-soft: #D6EDF2;
  --accent-glow: rgba(96, 114, 116, 0.1);
  --accent-glow-strong: rgba(96, 114, 116, 0.2);
  --ambient-blue: #D7E7F3;
  --ambient-yellow: #CDE7ED;
  --ambient-red: #CEE3E4;
  --ambient-green: #D0E5EE;
  --surface-accent-bg: rgba(96, 114, 116, 0.1);
  --surface-glass: rgba(232, 240, 243, 0.58);
}
/* ── ink · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='ink'] {
  --page-bg: #050911;
  --paper: #0C1217;
  --paper-2: #12191F;
  --paper-3: #1E242F;
  --line: #21272F;
  --line-2: #333B47;
  --accent: #6E94D4;
  --accent-dim: #4C73B5;
  --accent-soft: rgba(110, 148, 212, 0.14);
  --accent-glow: rgba(110, 148, 212, 0.1);
  --accent-glow-strong: rgba(110, 148, 212, 0.24);
  --on-accent: #0C1217;
  --ambient-blue: #0E0E18;
  --ambient-yellow: #070D18;
  --ambient-red: #050C13;
  --ambient-green: #0A0D17;
  --surface-accent-bg: rgba(110, 148, 212, 0.1);
  --surface-glass: rgba(18, 25, 31, 0.6);
}
/* ── ink · light ────────────────────────────────────────── */
[data-theme-palette='ink'] {
  --page-bg: #D2DBEA;
  --paper: #F8FCFF;
  --paper-2: #ECF3F8;
  --paper-3: #D6DFED;
  --line: #D2D8E2;
  --line-2: #A6B2C5;
  --accent: #0F2C59;
  --accent-dim: #081A38;
  --accent-soft: #DEE9F9;
  --accent-glow: rgba(15, 44, 89, 0.1);
  --accent-glow-strong: rgba(15, 44, 89, 0.2);
  --ambient-blue: #E2E3F4;
  --ambient-yellow: #D7E2F4;
  --ambient-red: #D2E0EC;
  --ambient-green: #DBE1F3;
  --surface-accent-bg: rgba(15, 44, 89, 0.1);
  --surface-glass: rgba(236, 243, 248, 0.58);
}
/* ── linen · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='linen'] {
  --page-bg: #0E0802;
  --paper: #15110B;
  --paper-2: #1D1710;
  --paper-3: #2B2218;
  --line: #2C251D;
  --line-2: #43392D;
  --accent: #C5B59C;
  --accent-dim: #A8987F;
  --accent-soft: rgba(197, 181, 156, 0.14);
  --accent-glow: rgba(197, 181, 156, 0.1);
  --accent-glow-strong: rgba(197, 181, 156, 0.24);
  --on-accent: #15110B;
  --ambient-blue: #120F06;
  --ambient-yellow: #140B03;
  --ambient-red: #110904;
  --ambient-green: #120C04;
  --surface-accent-bg: rgba(197, 181, 156, 0.1);
  --surface-glass: rgba(29, 23, 16, 0.6);
}
/* ── linen · light ────────────────────────────────────────── */
[data-theme-palette='linen'] {
  --page-bg: #E5D9CC;
  --paper: #FBF8F4;
  --paper-2: #F2EEE7;
  --paper-3: #E8DCCF;
  --line: #DED6CD;
  --line-2: #BEAE9D;
  --accent: #8C7456;
  --accent-dim: #6B5840;
  --accent-soft: #F3E5D6;
  --accent-glow: rgba(140, 116, 86, 0.1);
  --accent-glow-strong: rgba(140, 116, 86, 0.2);
  --ambient-blue: #E9E5D3;
  --ambient-yellow: #EEDFCE;
  --ambient-red: #EADBD0;
  --ambient-green: #EAE0CF;
  --surface-accent-bg: rgba(140, 116, 86, 0.1);
  --surface-glass: rgba(242, 238, 231, 0.58);
}
/* ── mauve · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='mauve'] {
  --page-bg: #0D070E;
  --paper: #16100B;
  --paper-2: #1E1711;
  --paper-3: #29212B;
  --line: #2A242C;
  --line-2: #403742;
  --accent: #C9B6CE;
  --accent-dim: #A697AC;
  --accent-soft: rgba(201, 182, 206, 0.14);
  --accent-glow: rgba(201, 182, 206, 0.1);
  --accent-glow-strong: rgba(201, 182, 206, 0.24);
  --on-accent: #16100B;
  --ambient-blue: #160C11;
  --ambient-yellow: #120A14;
  --ambient-red: #0D0912;
  --ambient-green: #130A12;
  --surface-accent-bg: rgba(201, 182, 206, 0.1);
  --surface-glass: rgba(30, 23, 17, 0.6);
}
/* ── mauve · light ────────────────────────────────────────── */
[data-theme-palette='mauve'] {
  --page-bg: #E1D6E4;
  --paper: #FCF8F4;
  --paper-2: #F4EDE8;
  --paper-3: #E5DAE7;
  --line: #DCD4DE;
  --line-2: #BAABBD;
  --accent: #7D6E83;
  --accent-dim: #524754;
  --accent-soft: #F0E3F3;
  --accent-glow: rgba(125, 110, 131, 0.1);
  --accent-glow-strong: rgba(125, 110, 131, 0.2);
  --ambient-blue: #F1DFE8;
  --ambient-yellow: #EADCED;
  --ambient-red: #E1DBEA;
  --ambient-green: #EBDCE9;
  --surface-accent-bg: rgba(125, 110, 131, 0.1);
  --surface-glass: rgba(244, 237, 232, 0.58);
}
/* ── sage · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='sage'] {
  --page-bg: #070B04;
  --paper: #13120B;
  --paper-2: #1B1810;
  --paper-3: #20261B;
  --line: #23281F;
  --line-2: #363D30;
  --accent: #BBCBB3;
  --accent-dim: #9AAB94;
  --accent-soft: rgba(187, 203, 179, 0.14);
  --accent-glow: rgba(187, 203, 179, 0.1);
  --accent-glow-strong: rgba(187, 203, 179, 0.24);
  --on-accent: #13120B;
  --ambient-blue: #08120B;
  --ambient-yellow: #0A0F05;
  --ambient-red: #0B0C04;
  --ambient-green: #081007;
  --surface-accent-bg: rgba(187, 203, 179, 0.1);
  --surface-glass: rgba(27, 24, 16, 0.6);
}
/* ── sage · light ────────────────────────────────────────── */
[data-theme-palette='sage'] {
  --page-bg: #D6DECF;
  --paper: #FAF8F4;
  --paper-2: #F0EEE7;
  --paper-3: #D9E1D2;
  --line: #D4DACF;
  --line-2: #ABB5A1;
  --accent: #6E7E68;
  --accent-dim: #4F5C4A;
  --accent-soft: #E2ECDA;
  --accent-glow: rgba(110, 126, 104, 0.1);
  --accent-glow-strong: rgba(110, 126, 104, 0.2);
  --ambient-blue: #D9EADE;
  --ambient-yellow: #DBE5D2;
  --ambient-red: #DDE0CF;
  --ambient-green: #D9E6D6;
  --surface-accent-bg: rgba(110, 126, 104, 0.1);
  --surface-glass: rgba(240, 238, 231, 0.58);
}
/* ── terracotta · dark ────────────────────────────────────────── */
[data-theme='dark'][data-theme-palette='terracotta'] {
  --page-bg: #100605;
  --paper: #17100C;
  --paper-2: #1E1611;
  --paper-3: #2E201D;
  --line: #2F2321;
  --line-2: #463632;
  --accent: #E89D8B;
  --accent-dim: #C77D6A;
  --accent-soft: rgba(232, 157, 139, 0.14);
  --accent-glow: rgba(232, 157, 139, 0.1);
  --accent-glow-strong: rgba(232, 157, 139, 0.24);
  --on-accent: #17100C;
  --ambient-blue: #160D07;
  --ambient-yellow: #160907;
  --ambient-red: #120808;
  --ambient-green: #150A06;
  --surface-accent-bg: rgba(232, 157, 139, 0.1);
  --surface-glass: rgba(30, 22, 17, 0.6);
}
/* ── terracotta · light ────────────────────────────────────────── */
[data-theme-palette='terracotta'] {
  --page-bg: #E9D6D1;
  --paper: #FCF7F5;
  --paper-2: #F4EDE8;
  --paper-3: #ECD9D4;
  --line: #E1D4D1;
  --line-2: #C3ABA4;
  --accent: #C5705D;
  --accent-dim: #9D5346;
  --accent-soft: #F8E2DD;
  --accent-glow: rgba(197, 112, 93, 0.1);
  --accent-glow-strong: rgba(197, 112, 93, 0.2);
  --ambient-blue: #F1E1D5;
  --ambient-yellow: #F3DBD5;
  --ambient-red: #EDD9D8;
  --ambient-green: #F1DDD4;
  --surface-accent-bg: rgba(197, 112, 93, 0.1);
  --surface-glass: rgba(244, 237, 232, 0.58);
}
* { box-sizing: border-box; }
html { height: 100%; }
body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
#root {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
button {
  font-family: var(--font);
  cursor: pointer;
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* ── Animations ── */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes cardIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes popoverPop {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes slideInRight {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}
@keyframes slideInLeft {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}
/* ── Theme transition (skipped under reduced-motion via @media below) ── */
/* Theme swap transition. 220ms keeps the change perceptible (no jarring
   flash) without making the user wait. The previous 500ms felt like the
   chip click had a delay — it was the page slowly fading, not the click
   itself. */
html.theme-transition,
html.theme-transition *,
html.theme-transition *::before,
html.theme-transition *::after {
  transition: background-color 220ms var(--ease-out-expo),
              color 220ms var(--ease-out-expo),
              border-color 220ms var(--ease-out-expo),
              fill 220ms var(--ease-out-expo),
              stroke 220ms var(--ease-out-expo);
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  /* Explicit kill-switch for the responsive drawer/overlay/popover surfaces
     introduced in the Responsive phase. The universal rule above already
     compresses all motion to ~0ms; this block doubles down by also clearing
     `animation` and `transition` shorthands on the surfaces whose entrance
     motion is most noticeable (sidebar drawer, context panel overlay,
     mobile context overlay, popovers, tab bar tab feedback).
     Safe to set `animation: none` here because each popover's static
     resting state (opacity:1, transform:none) matches the popoverPop
     keyframe's `to` state — there's no risk of freezing an element
     mid-animation at an invisible scale. */
  .app-shell__sidebar,
  .app-shell__context,
  .app-shell__scrim,
  .mobile-context-overlay,
  .overflow-menu__popover,
  .repo-switcher__popover,
  .context-flyout,
  .context-tab-bar__tab {
    transition: none;
    animation: none;
  }

  /* The panel↔rail change animates on the shell grid, not on the panel. */
  .app-shell {
    transition: none;
  }
}
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 9999px; }
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
/* ── Focus ── */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ── Responsive visibility primitives ───────────────────────────────
   These classes coordinate which toolbar controls and which
   content surface are shown at each breakpoint. Defaults below
   give a clean desktop layout. The @media blocks immediately below
   invert them: toolbar wrappers flip at the tablet anchor (≤1024px,
   matching the sidebar/context overlay collapse), and the mobile
   context overlay becomes visible at the mobile anchor (≤640px). */

.editor-toolbar__desktop-only {
  display: contents;
}

.editor-toolbar__mobile-only {
  display: none;
}

.mobile-context-overlay {
  display: none;
}

/* Tablet+ mobile (≤1024): swap the desktop-only chrome out for the mobile-only
   trigger cluster. The 1024 threshold matches the tablet collapse of the
   sidebar + context panel into overlays above — at that point the toolbar
   needs to surface the hamburger + context trigger inline. */
@media (max-width: 1024px) {
  .editor-toolbar__desktop-only { display: none; }
  .editor-toolbar__mobile-only { display: inline-flex; }
}

/* Mobile-only (≤640): the context tab bar takes over the bottom strip, and
   the mobile context overlay floats over the editor between toolbar (top)
   and tab bar (bottom). Background matches the editor surface (--paper),
   not the warm app-shell desk, so the overlay reads as the editor's own
   second pane.
   Anchored to .app-shell__content (the editor card) rather than .app-shell
   so the overlay respects the card's gutter + rounded corners. The card
   already reserves --mobile-tabbar-height via its margin-bottom, so the
   overlay can run to bottom: 0 without overlapping the tab bar. */
@media (max-width: 640px) {
  .app-shell__content {
    position: relative;
  }
  .mobile-context-overlay {
    display: block;
    position: absolute;
    top: var(--toolbar-h);  /* clears .editor-toolbar */
    left: 0;
    right: 0;
    bottom: 0;  /* .app-shell__content already reserves tab bar height via margin-bottom */
    background: var(--paper);
    overflow-y: auto;
    z-index: 10;
    padding: 16px;
    border-radius: 0 0 var(--rounded-lg) var(--rounded-lg);
  }
}

/* ── App Shell ──
   The sidebar sits flush against the viewport's left edge. The editor and
   context panel float as rounded surfaces inside a tinted "desk" (page-bg)
   with a small gutter between them. The status bar is part of the desk,
   not a sibling chrome strip — color does the seam, no hairlines anywhere. */

.app-shell {
  --gutter: 12px;
  --sidebar-track: var(--sidebar-width);
  --context-track: var(--context-panel-width);
  display: grid;
  grid-template-rows: 1fr var(--status-bar-height);
  grid-template-columns: var(--sidebar-track) minmax(var(--editor-min), 1fr) var(--context-track);
  height: 100vh;
  overflow: hidden;
  position: relative;
  /* Ambient wash. Two slow-graduated blobs on a diagonal — one at top-left
     (the signature accent-hue blob), one at bottom-right (slight cool drift
     within the accent family). The blobs are oversized (80% × 70%) so the
     falloff is gradual; the eye reads "one warm desk" rather than "four
     painted spots." The remaining --ambient-red and --ambient-green tokens
     are retained for backward compatibility but no longer painted here. */
  background:
    radial-gradient(ellipse 80% 70% at 10% 0%,   var(--ambient-yellow), transparent 88%),
    radial-gradient(ellipse 80% 70% at 90% 100%, var(--ambient-blue),   transparent 88%),
    var(--page-bg);
  transition: grid-template-columns var(--duration-normal) var(--ease-out-expo);
}

.app-shell:has(.app-shell__sidebar--collapsed) {
  --sidebar-track: 0px;
}

/* Collapsed form: the context surface keeps a narrow track for its icon rail
   rather than disappearing. The grid's existing `transition:
   grid-template-columns` animates the panel↔rail change for free. */
.app-shell:has(.app-shell__context--rail) {
  --context-track: var(--context-rail-width);
}

/* No context panel rendered (welcome / decisions views) — collapse the track. */
.app-shell:not(:has(.app-shell__context)) {
  --context-track: 0px;
}

.app-shell__sidebar {
  /* Sidebar runs the full viewport height so the status bar sits beside it,
     not under it. Matches Linear / Cron / Notion: nav rail is always the
     leftmost continuous column.
     Liquid Glass: translucent paper-2 over the ambient gradient mesh painted
     on .app-shell. The blur + saturation boost picks up warmth from the
     backdrop without shifting the apparent color of the surface. The top
     highlight reads as "wet material" even when blur output is uniform. */
  grid-row: 1 / -1;
  grid-column: 1;
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-highlight);
  overflow-y: auto;
  overflow-x: hidden;
  min-width: 0;
}

/* Collapsed means "parked off-canvas", at EVERY width — not just below the
   drawer breakpoint where the drawer exists.

   This used to be `visibility: hidden` up here and `transform: translateX(-100%)`
   down in the @media block, i.e. two different mechanisms for the same state.
   Crossing 1024px while collapsed then swapped mechanisms: visibility flipped to
   visible while the transform went from `none` (0px) to -280px, with the drawer's
   transform transition now live — so the sidebar appeared at x=0, fully on
   screen, and slid away. It read as the drawer flying out and back for no reason.

   Both sides now resolve to the same translate, so crossing the breakpoint
   changes nothing there and the transition has nothing to animate. The distance
   is an absolute token rather than -100% on purpose: the element's own width
   changes across that same boundary (0px grid track above it, drawer width
   below), so a percentage would still resolve to two different pixel values. */
.app-shell__sidebar--collapsed {
  visibility: hidden;
  transform: translateX(calc(-1 * var(--sidebar-drawer-width)));
}

.app-shell__scrim {
  display: none;
}

.app-shell__content {
  grid-row: 1;
  grid-column: 2;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 0;
  /* `position: relative` makes this the containing block for the
     absolutely-positioned .editor-toolbar that floats over the scroll
     area. Without it, the toolbar would anchor to the next positioned
     ancestor (likely the app-shell grid container). */
  position: relative;
  margin: var(--gutter);
  background: var(--surface-content);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-sm);
}

/* Dark mode: replace the solid --line outline (too prominent at #38362F
   against the dark paper) with an alpha-white outline + soft drop + a
   subtle top inset highlight. Same elevation language as the context
   panel and the tab/chip indicators we just fixed. */
[data-theme='dark'] .app-shell__content {
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.04),
    0 0 0 1px rgba(255, 255, 255, 0.04);
}

.app-shell__scroll {
  flex: 1;
  overflow-y: auto;
  /* `overflow-y: auto` alone promotes overflow-x from visible → auto per the
     CSS spec, so any wide block inside (tables, code blocks with long URLs)
     painted a horizontal scrollbar on this container. The margin/review mode
     transform on .doc-view (translateX(-112px)) hid the overflow by accident,
     which is why the scrollbar only appeared in document-only mode. The outer
     scroll surface should never scroll horizontally — BlockNote tables and
     code blocks handle their own internal overflow. */
  overflow-x: hidden;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  position: relative;
}

/* Shorten the scrollbar track at both ends so the thumb stops running into the
   parent surface's rounded corners.
   
   .app-shell__content is rounded by --rounded-lg and clips with overflow:hidden,
   but the track here spans the scroller's FULL height — so its first and last
   pixels sit inside the corner arc and the thumb reads as overflowing the card.
   Insetting the track by the corner radius keeps it within the straight part of
   the edge. Same fix, same reason, as .doc-tree__scroll in sidebar.css. */
.app-shell__scroll::-webkit-scrollbar-track {
  margin-block: var(--rounded-lg);
}

.app-shell__content-inner {
  width: 100%;
  max-width: var(--content-max-width);
  padding: calc(var(--toolbar-h) + 20px) var(--content-padding-x) 48px;
  position: relative;
  z-index: 1;
}

/* Wide multi-column triage tables (Mentions, Decisions) opt out of the narrow
   reading column so their columns have room. */
.app-shell__content-inner--wide {
  max-width: 72rem;
}

.app-shell__context {
  /* Mirror the left sidebar: full-height flush wall, same liquid-glass
     material, no resting shadow (Flat-by-Default rule from DESIGN.md).
     Removes the visual asymmetry where the left rail read as architecture
     and the right read as a floating card. */
  grid-row: 1 / -1;
  grid-column: 3;
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-highlight);
  overflow-y: auto;
  overflow-x: hidden;
  min-width: 0;
}

/* No `--hidden` variant: the context surface is never fully absent above the
   mobile breakpoint. It is either the panel or the rail — collapsing all the
   way to nothing left the user with no route back to Comments except a toolbar
   button they had just pressed. */

.app-shell__status {
  /* Status bar spans only the editor's column (col 2), not the context
     column. This horizontally aligns the mode toggle, word count, and
     commit button to the document they describe — semantic alignment via
     Gestalt proximity. Below the context panel, col 3 row 2 is empty desk
     gradient, which integrates naturally with the gutter system. */
  grid-column: 2;
  grid-row: 2;

  /* Without this the bar's min-content (637px measured in the `modified`
     state) became the floor of the `1fr` track, so the grid demanded
     240 + 637 + 360 = 1237px before it fit and clipped the context panel
     below that. See issue #164. The column's floor is now DECLARED as
     --editor-min instead of inherited from however long a branch name
     happens to be. */
  min-width: 0;

  /* The ladder below keys off the bar's OWN width, which is exactly what a
     container query takes — so the degradation is pure CSS. No
     ResizeObserver, no React state, no chance of the text and the layout
     disagreeing for a frame. */
  container-type: inline-size;
  container-name: statusbar;
}

/* ── Responsive: tablet (≤1024) collapses the SIDEBAR to an overlay ──
   The context panel used to become a second sliding overlay here. It no longer
   does: it collapses to the icon rail instead, and it does so on measured fit
   (see context-fit.ts) rather than on a viewport breakpoint — the panel's
   room depends on whether the sidebar is open and whether review mode is
   reserving the margin column, neither of which a media query can see. */

@media (max-width: 1024px) {
  .app-shell {
    --sidebar-track: 0px;
  }

  /* Sidebar: drawer overlay with scrim. Visible even while collapsed, because
     the drawer animates in and out rather than blinking — the off-canvas
     position comes from .app-shell__sidebar--collapsed above, which now applies
     at every width. No transform here: an open drawer is simply untransformed. */
  .app-shell__sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: var(--sidebar-drawer-width);
    height: 100vh;
    z-index: 70;
    box-shadow: var(--shadow-lg);
    visibility: visible;
    transition: transform var(--duration-normal) var(--ease-out-expo);
  }
  .app-shell__scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 65;
    background: rgba(55, 53, 47, 0.32);
    border: none;
    cursor: pointer;
    animation: fadeIn var(--duration-fast) var(--ease-out-expo);
  }

}

/* ── Responsive: mobile (≤640) tightens drawer width and reserves bottom-tabbar space ── */

@media (max-width: 640px) {
  /* Retune the drawer through the token, so the collapsed sidebar's parked
     position moves with its width instead of drifting apart from it. */
  .app-shell {
    --sidebar-drawer-width: min(85vw, 320px);
  }

  /* The mobile ContextTabBar takes over on mobile. Hide the context surface —
     panel and rail alike — so the tab bar owns the bottom strip and the editor
     card stays unobscured. `display: none` does not remove the element from the
     DOM, so the `:has()` rule above would still hold the rail's track open;
     zero it here explicitly. */
  .app-shell {
    --context-track: 0px;
  }

  .app-shell__context {
    display: none;
  }

  .app-shell__content {
    margin-bottom: calc(var(--gutter) + var(--mobile-tabbar-height, 0px));
  }
}

/* ── Touch targets: coarse-pointer enlargement (independent of width) ── */

@media (pointer: coarse) {
  .editor-toolbar__btn {
    width: 40px;
    height: 40px;
  }
}


/* ── Editor Toolbar (replaces full-width header) ──
   Floats over the scroll container so body text passes UNDER the
   toolbar and gets blurred + tinted by backdrop-filter — same liquid-
   glass vocabulary as the sidebars. Removed from flex flow so the
   .app-shell__scroll below now fills the full content area; the
   existing padding-top inside .app-shell__content-inner (derived from
   --toolbar-h) keeps the doc's first element clear of the toolbar at
   scroll position 0.

   Two-row (chrome + formatting) variant: one glass surface, one
   backdrop-filter, taller. The formatting row is a child of this same
   element — never a second sticky/glass surface — so the blur stays
   seamless as content scrolls under both rows.

   --toolbar-h is declared at :root (44px default) and overridden on
   .app-shell__content--stacked-toolbar — the <main> ancestor shared by
   .editor-toolbar, .app-shell__content-inner, and .mobile-context-overlay
   — rather than on .editor-toolbar--stacked itself. Those three are
   siblings, not descendants of .editor-toolbar, so a custom-property
   override scoped to .editor-toolbar--stacked would never reach the
   content padding or the mobile overlay; setting it one level up lets
   the single value cascade to every consumer. */
:root {
  --toolbar-h: 44px;
}

.app-shell__content--stacked-toolbar {
  --toolbar-h: 84px;
}

/* Version-viewing mode stacks the version banner (52px) under the 44px chrome
   row. Set on <main> so the taller toolbar height cascades to the toolbar and
   to content-inner's padding-top clearance, exactly like --stacked-toolbar. */
.app-shell__content--version-toolbar {
  --toolbar-h: 96px;
}

.editor-toolbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  height: var(--toolbar-h);
  padding: 0;
  flex-shrink: 0;
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  /* Soft bottom scrim instead of the previous dashed line — reads as
     "this is where chrome ends and content begins" without competing
     with body type for attention. */
  box-shadow: 0 1px 0 color-mix(in srgb, var(--line), transparent 55%);
}

[data-theme='dark'] .editor-toolbar {
  /* Drop ambient hairline for the same reason the doc-header does in
     dark mode — paper-ring reads cleaner against dark surfaces. */
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04);
}

/* Bare variant (welcome view, no doc selected): no glass surface, no
   scrim. The welcome content has its own visual treatment. */
.editor-toolbar--bare {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}

.editor-toolbar__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.editor-toolbar__row--chrome {
  height: 44px;
  padding: 0 12px;
  flex-shrink: 0;
}

.editor-toolbar__row--format {
  height: 40px;
  padding: 0 4px;
  flex-shrink: 0;
  justify-content: center;
  border-top: 1px dashed color-mix(in srgb, var(--line), transparent 60%);
}

.editor-toolbar__left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.editor-toolbar__right {
  display: flex;
  align-items: center;
  gap: 4px;
}

.editor-toolbar__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  background: transparent;
  border: none;
  border-radius: var(--rounded-md);
  color: var(--text-3);
  /* Disable iOS Safari's double-tap-to-zoom on small icon-only buttons.
     Without this, tapping the review-mode or context-toggle button on a
     touch device fires the browser's zoom heuristic and the whole UI
     visually scales up. */
  touch-action: manipulation;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

.editor-toolbar__btn:hover:not(:disabled) {
  background: var(--accent-glow);
  color: var(--text);
}

/* Shown but inert, rather than removed: a control that vanishes as the window
   narrows reads as a bug, while a dimmed one says "not here, not now" and keeps
   the toolbar's shape stable. Used by the margin toggle below the width where
   the margin lane can be displayed at all. */
.editor-toolbar__btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.editor-toolbar__btn--active {
  color: var(--accent);
  background: var(--accent-glow);
}

.editor-toolbar__breadcrumbs {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  color: var(--text-3);
  /* Per-child motion stagger handles the entrance — see DocumentViewerPage.
     The nav itself has key={docPath} so React remounts the whole tree on
     doc switch, re-triggering the staggered children's initial animations. */
}

.editor-toolbar__crumb {
  transition: color var(--duration-fast) var(--ease-out-expo);
}

.editor-toolbar__crumb:hover {
  color: var(--text);
}

.editor-toolbar__crumb--active {
  color: var(--text-2);
  font-weight: 500;
}

.editor-toolbar__sep {
  color: var(--text-4);
  opacity: 0.5;
  flex-shrink: 0;
}

.editor-toolbar__sync {
  position: relative;
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 6px;
  border-radius: 50%;
  background: var(--ok);
  flex-shrink: 0;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.editor-toolbar__sync::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  background: currentColor;
  color: var(--ok);
  opacity: 0;
  pointer-events: none;
  animation: syncRing 600ms var(--ease-out-quart);
}

.editor-toolbar__sync--modified::after    { color: var(--warn); }
.editor-toolbar__sync--behind::after,
.editor-toolbar__sync--error::after       { color: var(--err); }
.editor-toolbar__sync--committing::after  { color: var(--warn); }
.editor-toolbar__sync--committed::after   { color: var(--accent); }

.editor-toolbar__sync--modified { background: var(--warn); }
.editor-toolbar__sync--behind   { background: var(--err); }
.editor-toolbar__sync--committing {
  background: var(--warn);
  animation: pulse 1s var(--ease-out-expo) infinite;
}
.editor-toolbar__sync--committed { background: var(--accent); }
.editor-toolbar__sync--error    { background: var(--err); }

/* ── Status Bar ── */

.status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* space-between is a distributor of FREE space, not a guarantee of
     separation: once the three groups add up to the bar's width there is no
     free space left and they butt directly against each other — the word count
     touching the mode toggle, the Discard chip touching it on the other side.
     That is what happens in the band where the sidebar and context rails both
     hold grid tracks and the editor column is squeezed. An explicit gap is the
     floor space-between can't provide; it also joins the bar's min-content
     width, so the groups stay 12px apart instead of collapsing onto each
     other. */
  gap: 12px;
  height: var(--status-bar-height);
  padding-inline: 16px;
  /* Visual centering of the chip in the combined bottom strip (editor gutter
     + status bar). The status bar background is transparent, so the user
     reads: desk above chip = gutter (12) + top padding (2) = 14, chip,
     desk below chip = bottom padding (14). True vertical balance. */
  padding-block: 2px 14px;
  /* Transparent so the ambient gradient on .app-shell shows through. The
     status bar visually IS the desk at that location — no color seam against
     the gutters above it. */
  background: transparent;
  font-size: 11px;
  color: var(--text-2);
}

.status-bar__left,
.status-bar__center,
.status-bar__right {
  display: flex;
  align-items: center;
  gap: 12px;
}

.status-bar__center {
  justify-content: center;
}

.status-bar__left,
.status-bar__right {
  flex: 1;
}

.status-bar__right {
  justify-content: flex-end;
}

.status-bar__item {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* ── The degradation ladder (issue #164) ──────────────────────────────────
   The bar gives things up in priority order as its column narrows, instead
   of propping the grid open and clipping the context panel.

   Thresholds are pinned to the bar's MEASURED segments in the worst-case
   `modified` state (Discard present): left 42 + center 153 + right 386 +
   padding 32 + gaps = 637px full.

   INVARIANT: Commit keeps its label at every tier. It is the only
   near-irreversible action in the bar, and an unlabelled circle-and-lines
   glyph is not something to make someone guess at. Pinned by
   status-bar-ladder.spec.ts. */

/* Tier 1 — Full (>= 640px). The short pill sentence waits its turn. */
.status-bar__status-text--short {
  display: none;
}

/* Tier 2 — Condensed status (560–639px).
   The pill sentence shortens: "Uncommitted changes on" -> "Uncommitted on".
   Nothing disappears yet; the sentence just stops taking the scenic route. */
@container statusbar (max-width: 639px) {
  .status-bar__status-text--full {
    display: none;
  }
  .status-bar__status-text--short {
    display: inline;
  }
}

/* Tier 3 — Labels off (440–559px).
   The word count goes first: it is ambient information, not an action, and
   it is the only thing here nobody navigates by. The mode toggle keeps its
   icons but drops its words — the active chip still reads as selected
   through the indicator, so the affordance survives the labels. */
@container statusbar (max-width: 559px) {
  .status-bar__left {
    display: none;
  }
  .mode-toggle__label {
    display: none;
  }
}

/* Tier 4 — Actions only (340–439px).
   Discard becomes its icon; it is destructive but confirmed by a popover, so
   a mis-click costs a dismissal rather than data. The pill drops its sentence
   and keeps the branch — at this width WHERE you are matters more than the
   prose describing it, and the full sentence is still on the tooltip. */
@container statusbar (max-width: 439px) {
  .status-bar__discard-btn .status-bar__btn-label {
    display: none;
  }
  .status-bar__status-text--short {
    display: none;
  }
}

/* Tier 5 — Floor (<= 339px).
   The branch name goes. State is carried by the pill's colour and icon, and
   the full sentence remains reachable via the tooltip (`title`), so nothing
   becomes unknowable — only less immediate. Measured floor lands near 282px,
   comfortably inside a 320px viewport. */
@container statusbar (max-width: 339px) {
  .status-bar__status-branch {
    display: none;
  }
}

/* ── Status pill (sibling to Commit button) ── */

.status-bar__status {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 22px;
  padding: 0 10px;
  box-sizing: border-box;
  font-size: 11px;
  font-weight: 500;
  font-family: var(--font);
  line-height: 1;
  border: 1px solid transparent;
  border-radius: var(--rounded-pill);
  color: var(--text-2);
  /* Traditional elevation: solid paper-2 base lifts visibly above the
     gradient desk; state modifiers below replace the bg with opaque tinted
     variants so every state stays lifted, not just the default. */
  background: var(--paper-2);
  box-shadow: var(--chip-shadow);
  transition:
    background var(--duration-normal) var(--ease-out-quart),
    color var(--duration-normal) var(--ease-out-quart),
    border-color var(--duration-normal) var(--ease-out-quart);
}

.status-bar__status-icon {
  flex-shrink: 0;
  opacity: 0.75;
}

.status-bar__status-text {
  white-space: nowrap;
}

.status-bar__status-branch {
  font-family: var(--mono);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.status-bar__status--modified {
  color: var(--warn);
  background: color-mix(in oklch, var(--paper-2), var(--warn) 12%);
}

.status-bar__status--behind {
  color: var(--err);
  background: color-mix(in oklch, var(--paper-2), var(--err) 12%);
}

.status-bar__status--committing {
  color: var(--warn);
  background: color-mix(in oklch, var(--paper-2), var(--warn) 12%);
}

.status-bar__status--committed {
  color: var(--accent);
  background: color-mix(in oklch, var(--paper-2), var(--accent) 10%);
}

.status-bar__status--error {
  color: var(--err);
  background: color-mix(in oklch, var(--paper-2), var(--err) 12%);
}

/* Expanding ring on state change — matches the toolbar sync dot pulse. */
.status-bar__status::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: var(--rounded-pill);
  border: 1px solid currentColor;
  opacity: 0;
  pointer-events: none;
  animation: statusRing 520ms var(--ease-out-quart);
}

@keyframes statusRing {
  0%   { transform: scale(1);    opacity: 0.45; }
  100% { transform: scale(1.18); opacity: 0;    }
}

.status-bar__dot--modified {
  background: var(--warn);
}

.status-bar__dot--behind {
  background: var(--err);
}

.status-bar__dot--committing {
  background: var(--warn);
  animation: pulse 1s var(--ease-out-expo) infinite;
}

.status-bar__dot--committed {
  background: var(--accent);
}

.status-bar__dot--error {
  background: var(--err);
}

@keyframes pulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

.status-bar__commit-wrap {
  position: relative;
}

.status-bar__commit-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 10px;
  box-sizing: border-box;
  font-size: 11px;
  font-weight: 500;
  font-family: var(--font);
  line-height: 1;
  color: var(--accent);
  /* Traditional elevation: solid accent-soft surface, lifted via chip-shadow.
     Same shadow stack as the status pill and mode toggle — one chrome
     language across the bottom bar. */
  background: var(--accent-soft);
  box-shadow: var(--chip-shadow);
  border: 1px solid transparent;
  border-radius: var(--rounded-pill);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo);
}

.status-bar__commit-btn:hover:not(:disabled) {
  /* Deeper accent surface + slightly stronger shadow stack. The chip rises
     a touch on hover (chip-shadow-hover has a larger drop) — reads as a
     traditional "lift on hover" interaction, not a hard outline. */
  background: color-mix(in oklch, var(--accent-soft), var(--accent) 22%);
  box-shadow: var(--chip-shadow-hover);
}

/* In dark mode --accent-soft is alpha-tinted (rgba), so layering accent over
   it stays translucent. The commit chip needs an OPAQUE accent-tinted
   surface that's lighter than --page-bg (so it reads as raised) and clearly
   accent-colored. Mixing paper-2 with --accent at increasing strength gives
   that for rest + hover, matching the opaque treatment of the other chips. */
[data-theme='dark'] .status-bar__commit-btn {
  background: color-mix(in oklch, var(--paper-2), var(--accent) 22%);
}

[data-theme='dark'] .status-bar__commit-btn:hover:not(:disabled) {
  background: color-mix(in oklch, var(--paper-2), var(--accent) 38%);
}

.status-bar__commit-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.status-bar__discard-wrap {
  position: relative;
}

/* Quiet sibling of the commit chip: neutral surface at rest, danger-tinted on
   hover so the destructive intent only surfaces on approach. */
.status-bar__discard-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 10px;
  box-sizing: border-box;
  font-size: 11px;
  font-weight: 500;
  font-family: var(--font);
  line-height: 1;
  color: var(--text-2);
  background: var(--paper-2);
  box-shadow: var(--chip-shadow);
  border: 1px solid transparent;
  border-radius: var(--rounded-pill);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo),
    box-shadow var(--duration-fast) var(--ease-out-expo);
}

.status-bar__discard-btn:hover {
  color: var(--warn);
  background: color-mix(in oklch, var(--paper-2), var(--warn) 14%);
  box-shadow: var(--chip-shadow-hover);
}

/* ── Mode Toggle (lives in the editor toolbar) ── */

.mode-toggle {
  display: inline-flex;
  /* 4px between segments so the active pill never visually touches the
     adjacent segment's icon or label. The indicator's translateX/scaleX
     math uses offsetLeft + offsetWidth, both of which already account for
     flex gap, so the slide animation stays correct. */
  gap: 4px;
  padding: 2px;
  /* Traditional elevated pill on the desk. Solid paper-2 background, same
     chip-shadow stack as the status pill and commit button. The 1px border
     gets dropped — the shadow's hairline outline does that job, and a
     literal border would double up at this scale. */
  background: var(--paper-2);
  border-radius: var(--rounded-md);
  border: none;
  box-shadow: var(--chip-shadow);
  position: relative;
}

.mode-toggle__indicator {
  position: absolute;
  top: 2px;
  left: 0;
  bottom: 2px;
  width: 80px; /* reference width; runtime scales via transform: scaleX() */
  transform-origin: left center;
  /* Concentric with the outer track: outer-radius minus the 2px outer padding. */
  border-radius: calc(var(--rounded-md) - 2px);
  background: var(--surface-content);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
  transition: transform var(--duration-entrance) var(--ease-out-expo);
  pointer-events: none;
  will-change: transform;
}

/* Dark mode: indicator needs to be LIGHTER than its container (paper-2) to
   read as raised. In dark mode --surface-content (paper) is darker than
   --paper-2, so we swap to --paper-3 and strengthen the elevation cues.
   Same pattern as .context-panel__tab-indicator's dark override. */
[data-theme='dark'] .mode-toggle__indicator {
  background: var(--paper-3);
  box-shadow:
    0 2px 6px rgba(0, 0, 0, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 0 0 1px rgba(255, 255, 255, 0.04);
}

.mode-toggle__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 3px 8px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.01em;
  border: none;
  /* Match the indicator beneath, which is offset by the 2px outer padding. */
  border-radius: calc(var(--rounded-md) - 2px);
  background: transparent;
  color: var(--text-4);
  cursor: pointer;
  white-space: nowrap;
  position: relative;
  z-index: 1;
  transition: color var(--duration-fast) var(--ease-out-expo);
}

.mode-toggle__btn:hover {
  color: var(--text-2);
}

.mode-toggle__btn--active {
  color: var(--accent);
  font-weight: 600;
}

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

.mode-toggle__btn svg {
  flex-shrink: 0;
}

.mode-toggle__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}


.status-bar__count {
  font-family: var(--mono);
}

.status-bar__kbd {
  font-family: var(--mono);
  font-size: 10px;
  padding: 1px 4px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  margin-right: 4px;
}

/* ── Status bar responsive ──
   At mobile width the status bar is hidden entirely — the bottom
   ContextTabBar takes over the bottom strip and the status bar's controls
   (mode toggle, commit, sync) become noise at read-and-scan intent.
   Collapse the grid row too so the tab bar can sit flush with the
   bottom of the viewport. */

@media (max-width: 640px) {
  .app-shell {
    grid-template-rows: 1fr;
  }
  .app-shell__status {
    display: none;
  }
  /* Per spec: "Mobile: ☰ · doc title (truncated) · ⋯ menu (everything
     secondary; mode toggle absent)". Review-mode button has no effect
     (margin column already hidden at ≤1024px); context-panel toggle has
     no effect (desktop context panel hidden at ≤640px — the mobile tab
     bar replaces it). */
  .editor-toolbar__btn--review,
  .editor-toolbar__btn--context-toggle {
    display: none;
  }
}
/* ── Sidebar ── */

.sidebar {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding-bottom: 0;
}

/* ── Workspace zone ──
   Now hosts the RepoSwitcher. Wrapper only sets the layout slot; the
   trigger button owns its own padding, background, hover, and focus. */

.sidebar__workspace {
  margin: 10px 8px 4px;
}

/* ── Repo Switcher ──
   Component styles live in src/components/repo-switcher/repo-switcher.css.
   This block intentionally left empty; the prior Phase 2 prototype's rules
   were removed to avoid colliding with the Phase 3 component classes. */

/* ── Actions Group ── */

.sidebar__actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 4px 0;
}

/* ── New Doc Zone ── */

.sidebar__new-doc-zone {
  padding: 0 8px;
}

.sidebar__new-doc-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  height: 30px;
  padding: 0 8px;
  border: 1px dashed var(--line-2);
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo);
}

.sidebar__new-doc-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-glow);
}

.new-doc-input {
  display: flex;
  flex-direction: column;
  gap: 4px;
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.new-doc-input__field {
  width: 100%;
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--accent);
  border-radius: var(--rounded-md);
  background: var(--paper);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text);
  outline: 2px solid var(--accent-glow);
  outline-offset: 0px;
}

.new-doc-input__field::placeholder {
  color: var(--text-4);
}

.new-doc-input__hint {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  padding: 0 2px;
  line-height: 1.4;
}

.new-doc-input__hint-sep {
  color: var(--text-4);
  margin: 0 1px;
}

.new-doc-input__hint-folder {
  color: var(--accent);
  font-weight: 600;
}

.new-doc-input__hint-file {
  color: var(--text-3);
}

.sidebar__search {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 8px;
  padding: 6px 10px;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  font-size: 12px;
  transition: border-color var(--duration-fast) var(--ease-out-expo);
}

.sidebar__search:hover {
  border-color: var(--text-3);
}

.sidebar__search svg {
  flex-shrink: 0;
  opacity: 0.7;
}

/* ── Nav Zone ── */

.sidebar__across-section {
  padding-top: 4px;
}

.sidebar__across-header {
  display: flex;
  align-items: center;
  padding: 8px 16px 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
}

.sidebar__nav-zone {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 8px 4px;
}

.sidebar__nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  text-align: left;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

.sidebar__nav-item:hover {
  background: var(--paper-3);
}

.sidebar__nav-item--warn:hover {
  background: var(--badge-bg-warn);
}

.sidebar__nav-item--ok:hover {
  background: var(--badge-bg-ok);
}

.sidebar__nav-item svg {
  flex-shrink: 0;
  opacity: 0.6;
}

.sidebar__nav-item--active svg {
  opacity: 1;
}

.sidebar__nav-item--active.sidebar__nav-item--warn {
  background: var(--badge-bg-warn);
  color: var(--warn);
}

.sidebar__nav-item--active.sidebar__nav-item--ok {
  background: var(--badge-bg-ok);
  color: var(--ok);
}

.sidebar__nav-label {
  flex: 1;
}

.sidebar__nav-count {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 0 5px;
  border-radius: var(--rounded-xl);
  line-height: 16px;
  min-width: 16px;
  text-align: center;
}

.sidebar__nav-count--warn {
  background: var(--badge-bg-warn);
  color: var(--warn);
}

.sidebar__nav-count--ok {
  background: var(--badge-bg-ok);
  color: var(--ok);
}

/* ── Scroll Area ── */

.sidebar__scroll {
  flex: 1;
  overflow-y: auto;
  /* Reserve scrollbar gutter space at all times. Without this, the scrollbar
     only takes space when the tree actually overflows, which shifts the
     section-header counts (Root, Directories — INSIDE this scroll
     container) left by the scrollbar width while the Triage nav counts
     above (OUTSIDE the scroll container) stay put. The visible result was
     misaligned right edges between the two counter sets. */
  scrollbar-gutter: stable;
  /* Soft scroll boundary. Without this, a tall doc tree appears to slide
     directly behind the Triage nav above and the user footer below — the
     overflow has no edge, so it reads as "content cut off" rather than
     "content scrolls in this region". The matching hairline lives on
     .sidebar__user; together they bracket the scroll surface. */
  border-top: 1px solid var(--line);
}

/* ── Sections ── */

.sidebar__section {
  margin-bottom: 8px;
  padding-top: 4px;
}

/* Content-label section headers (Root / Directories). Sentence-case, lower
   weight than the TRIAGE nav header above — content listings vs nav
   category, signaled by typography rather than by an extra hairline. */
.sidebar__section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px 6px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-3);
}

.sidebar__section:not(:first-child) .sidebar__section-header {
  margin-top: 8px;
}

.sidebar__section-count {
  font-family: var(--mono);
  font-size: 10px;
  /* Matches .sidebar__nav-count's weight. At 10px mono, a 500-vs-600 weight
     gap reads as a size difference next to the triage counter row above. */
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  background: var(--badge-bg-mute);
  padding: 0 5px;
  border-radius: var(--rounded-xl);
  line-height: 16px;
  min-width: 16px;
  text-align: center;
}

/* ── Doc Tree ── */

.doc-tree {
  list-style: none;
  margin: 0;
  padding: 0 4px;
}

.doc-tree__item {
  position: relative;
}

.doc-tree__row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px;
  border: none;
  background: transparent;
  text-align: left;
  font-size: 13px;
  color: var(--text-2);
  border-radius: var(--rounded-sm);
  transition:
    background var(--duration-normal) var(--ease-out-quart),
    color var(--duration-normal) var(--ease-out-quart);
  cursor: pointer;
}

.doc-tree__row:hover {
  background: var(--paper-3);
  color: var(--text);
}

.doc-tree__row--active {
  background: var(--accent-glow);
  color: var(--accent);
  font-weight: 600;
}

.doc-tree__row--active:hover {
  background: var(--accent-glow);
}

.doc-tree__row--folder {
  font-weight: 500;
  color: var(--text-3);
}

.doc-tree__row--folder:hover {
  color: var(--text-2);
}

.doc-tree__icon {
  flex-shrink: 0;
  color: var(--text-4);
  transition: color var(--duration-fast) var(--ease-out-expo);
}

.doc-tree__icon--doc .doc-tree__icon-body {
  fill: transparent;
  stroke: currentColor;
  transition:
    fill var(--duration-normal) var(--ease-out-quart),
    stroke var(--duration-normal) var(--ease-out-quart);
}

.doc-tree__icon--doc .doc-tree__icon-fold {
  fill: none;
  stroke: currentColor;
  transition:
    opacity var(--duration-normal) var(--ease-out-quart),
    stroke var(--duration-normal) var(--ease-out-quart);
}

.doc-tree__row--active .doc-tree__icon--doc .doc-tree__icon-body {
  fill: currentColor;
}

.doc-tree__row--active .doc-tree__icon--doc .doc-tree__icon-fold {
  opacity: 0;
}

.doc-tree__row--active .doc-tree__icon--doc {
  animation: docIconPop 360ms var(--ease-out-quart);
  transform-origin: center;
}

@keyframes docIconPop {
  0%   { transform: scale(0.85); }
  50%  { transform: scale(1.15); }
  100% { transform: scale(1); }
}

.doc-tree__row--active .doc-tree__icon,
.doc-tree__row--active .doc-tree__icon--doc .doc-tree__icon-body,
.doc-tree__row--active .doc-tree__icon--doc .doc-tree__icon-fold {
  color: var(--accent);
}

.doc-tree__label {
  /* Not flex:1 — the label must NOT grow to fill the row, otherwise the
     hover/active highlight stretches across the whole column instead of
     hugging the item (#11). min-width:0 keeps ellipsis truncation working
     when the (fit-content) row hits its max-width. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.doc-tree__badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 5px;
  border-radius: var(--rounded-sm);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.doc-tree__badge--draft {
  background: var(--badge-bg-warn);
  color: var(--warn);
}

.doc-tree__chevron {
  flex-shrink: 0;
  color: var(--text-4);
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.doc-tree__chevron--open {
  transform: rotate(90deg);
}

/* Nested levels — animated expand/collapse */
.doc-tree__collapse {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 350ms var(--ease-out-expo);
}

.doc-tree__collapse--open {
  grid-template-rows: 1fr;
}

.doc-tree__children {
  list-style: none;
  margin: 0;
  padding: 0;
  border: none;
  overflow: hidden;
  min-height: 0;
}

/* ── Focus ── */

.sidebar__search:focus-visible,
.sidebar__nav-item:focus-visible,
.doc-tree__row:focus-visible,
.sidebar__new-doc-btn:focus-visible,
.repo-switcher__trigger:focus-visible,
.repo-switcher__option:focus-visible,
.sidebar__user-settings:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ── User Footer ── */

.sidebar__user {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  flex-shrink: 0;
  /* Matches the hairline on .sidebar__scroll: brackets the scrollable doc
     tree so overflowing content has a visible bottom edge. */
  border-top: 1px solid var(--line);
}

.sidebar__user-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--paper-3);
  color: var(--text);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  /* grid+place-items centers mono initials optically (see .margin-glyph). */
  display: grid;
  place-items: center;
  flex-shrink: 0;
  border: 1px solid var(--line);
}

.sidebar__user-name {
  flex: 1;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar__user-settings {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-4);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

.sidebar__user-settings:hover {
  background: var(--paper-3);
  color: var(--text-2);
}

/* ── Skeleton loading ── */

@keyframes sidebar-shimmer {
  0% { opacity: 0.4; }
  50% { opacity: 0.15; }
  100% { opacity: 0.4; }
}

.sidebar__skeleton {
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.sidebar__skeleton-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
}

.sidebar__skeleton-icon {
  width: 14px;
  height: 14px;
  border-radius: var(--rounded-sm);
  background: var(--text);
  opacity: 0.12;
  animation: sidebar-shimmer 1.6s ease-in-out infinite;
  flex-shrink: 0;
}

.sidebar__skeleton-line {
  height: 10px;
  border-radius: var(--rounded-sm);
  background: var(--text);
  opacity: 0.12;
  animation: sidebar-shimmer 1.6s ease-in-out infinite;
  animation-delay: 0.15s;
}

.sidebar__skeleton-row:nth-child(2) .sidebar__skeleton-icon,
.sidebar__skeleton-row:nth-child(2) .sidebar__skeleton-line { animation-delay: 0.1s; }
.sidebar__skeleton-row:nth-child(3) .sidebar__skeleton-icon,
.sidebar__skeleton-row:nth-child(3) .sidebar__skeleton-line { animation-delay: 0.2s; }
.sidebar__skeleton-row:nth-child(4) .sidebar__skeleton-icon,
.sidebar__skeleton-row:nth-child(4) .sidebar__skeleton-line { animation-delay: 0.3s; }
.sidebar__skeleton-row:nth-child(5) .sidebar__skeleton-icon,
.sidebar__skeleton-row:nth-child(5) .sidebar__skeleton-line { animation-delay: 0.4s; }
.sidebar__skeleton-row:nth-child(6) .sidebar__skeleton-icon,
.sidebar__skeleton-row:nth-child(6) .sidebar__skeleton-line { animation-delay: 0.5s; }

/* ── Empty state ── */

.sidebar__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px 16px;
  color: var(--text-4);
  font-size: 12px;
  text-align: center;
}

.sidebar__empty svg {
  opacity: 0.35;
}

/* ── Multi-repo merge-mode groups ── */

.sidebar__repo-group {
  margin-bottom: 8px;
}

.sidebar__repo-group-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-3);
  cursor: pointer;
  border-radius: var(--rounded-md);
}

.sidebar__repo-group-summary:hover {
  background: var(--paper-3);
}

.sidebar__repo-group-name {
  text-transform: none;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text);
}

.sidebar__repo-group-content {
  padding-top: 4px;
}

/* ── Coarse-pointer touch targets ──
   Grow tappable rows + action affordances to comfortable touch sizes on
   touchscreens. Visual density on desktop (fine pointer) stays unchanged.
   40px floor matches the existing precedent in editor-toolbar and
   settings buttons. */

/* Virtualized doc tree (Phase 1 migration) */
/* 4px top breathing room. padding-top drops the rows 4px, but content still
   CLIPS at the container's top edge (level with the .sidebar__scroll divider) —
   so a scrolled tree disappears exactly at the divider, no gap. The native
   scrollbar would otherwise start at that top edge, 4px above the first row; the
   ::-webkit-scrollbar-track margin-top pushes the track (and thumb) down 4px so
   it lines up with the tree top. Scrollbars are webkit-styled app-wide (see
   global.css); Firefox falls back to its default, which is acceptable here. */
.doc-tree__scroll { height: 100%; min-height: 0; padding-top: 4px; }
.doc-tree__scroll::-webkit-scrollbar-track { margin-top: 4px; }
.doc-tree { position: relative; }
.doc-tree .doc-tree__row {
  height: 30px;            /* must equal ROW_HEIGHT in DocTreeView.tsx */
  /* fit-content so the hover/active highlight hugs the item (icon + label)
     rather than spanning the whole column (#11). The indent is applied as
     margin-left (inline, depth-aware) so it sits OUTSIDE the highlight, and a
     matching inline max-width caps the row short of the scrollbar and lets a
     long label truncate. */
  width: fit-content;
  display: flex;
  align-items: center;
  gap: 6px;
}

@media (pointer: coarse) {
  .sidebar__nav-item {
    min-height: 40px;
    padding: 10px 8px;
  }

  .sidebar__new-doc-btn,
  .new-doc-input__field,
  .sidebar__search {
    min-height: 40px;
  }

  .doc-tree__row {
    min-height: 40px;
    padding-top: 8px;
    padding-bottom: 8px;
  }

  .sidebar__repo-group-summary {
    min-height: 40px;
  }

  .sidebar__user-settings {
    width: 40px;
    height: 40px;
  }
}
/* ── Context Panel ── */

.context-panel {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.context-panel__tabs {
  display: grid;
  /* Column count comes from CONTEXT_SECTIONS.length, set as an inline custom
     property by ContextPanel — NOT a literal. It was `repeat(4, 1fr)` when a
     fifth section (Mentions) was registered, so the fifth tab wrapped onto a
     second row; that made the grid two rows tall, and the indicator (which
     spans top:8px to bottom:8px) grew to match. Deriving it means registering
     a sixth section cannot reintroduce this. The fallback keeps the panel
     sane if the property is ever missing. */
  grid-template-columns: repeat(var(--context-tab-count, 5), 1fr);
  gap: 0;
  padding: 8px 10px;
  /* Transparent so the right sidebar's liquid-glass material reads through
     consistently from top to bottom. A --paper-2 fill here made the top
     strip look like a different surface than the body of the panel — the
     left sidebar has no such overlay and was the reference. */
  background: transparent;
  position: relative;
}

.context-panel__tab-indicator {
  position: absolute;
  top: 8px;
  bottom: 8px;
  left: 10px;
  /* One tab wide. Must divide by the same count as the grid above: the
     indicator is positioned with translateX(index * 100%), so a mismatch
     lands it at a fraction of the wrong column — with the old `/ 4` the
     fifth tab's indicator sat past the end of the row entirely. */
  width: calc((100% - 20px) / var(--context-tab-count, 5));
  border-radius: var(--rounded-md);
  background: var(--surface-content);
  /* Single faint drop does the lift, no 1px ring. The ring was double-duty
     with the shadow and read as "stamped on." Lighter shadow lets the chip
     float softly on the liquid-glass surface. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: transform var(--duration-entrance) var(--ease-out-expo);
  pointer-events: none;
  will-change: transform;
}

/* Dark mode: 40% black drop was overpowering against the dim bar. 20%
   on the same paper-3 surface gives enough separation without the chip
   "popping." The inset top highlight stays for the material edge cue. */
[data-theme='dark'] .context-panel__tab-indicator {
  background: var(--paper-3);
  box-shadow:
    0 1px 4px rgba(0, 0, 0, 0.20),
    inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.context-panel__tab {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 7px 2px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  position: relative;
  z-index: 1;
  transition: color var(--duration-fast) var(--ease-out-expo);
  white-space: nowrap;
}

.context-panel__tab:hover {
  color: var(--text-2);
}

.context-panel__tab--active {
  color: var(--text);
  font-weight: 600;
}

.context-panel__meta {
  display: flex;
  align-items: center;
  padding: 8px 16px;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-2);
  border-bottom: 1px solid var(--line);
  transition: opacity var(--duration-fast) var(--ease-out-expo);
}

.context-panel__body {
  flex: 1;
  overflow-y: auto;
  padding: 14px 16px;
  background: var(--paper-2);
}

.context-panel__tab-content {
  animation: fadeIn var(--duration-entrance) var(--ease-out-expo);
}

/* ── Outline ── */

.outline {
  list-style: none;
  margin: 0;
  padding: 0;
  transition: opacity 150ms var(--ease-out-expo);
}

.outline--stale {
  opacity: 0.4;
  pointer-events: none;
}

.outline__empty {
  font-size: 12px;
  color: var(--text-4);
  padding: 8px;
  line-height: 1.5;
}

.outline__empty kbd {
  font-family: var(--mono);
  font-size: 10px;
  padding: 1px 4px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
}

.outline__item {
  padding: 5px 8px;
  border-radius: var(--rounded-sm);
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
  animation: cardIn 300ms var(--ease-out-expo) both;
  animation-delay: calc(min(var(--i, 0), 12) * 40ms);
}

.outline__item:hover {
  background: var(--paper-3);
  color: var(--text);
}

.outline__item--active {
  color: var(--accent);
  font-weight: 500;
  background: var(--accent-glow);
}

.outline__item--active:hover {
  background: var(--accent-glow);
}

.outline__item--h1 {
  padding-left: 8px;
  font-size: 13px;
  font-weight: 600;
}

.outline__item--h2 {
  padding-left: 20px;
  font-size: 12.5px;
}

.outline__item--h3 {
  padding-left: 32px;
  font-size: 11.5px;
  color: var(--text-2);
}

/* ── Thread Cards (shared by Comments + Decisions) ── */

.thread-cards {
  display: flex;
  flex-direction: column;
  gap: 8px;
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.thread-card {
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  background: var(--paper);
  transition: border-color var(--duration-fast) var(--ease-out-expo);
  animation: cardIn 350ms var(--ease-out-expo) both;
  animation-delay: calc(min(var(--i, 0), 8) * 60ms);
}

.thread-card:hover {
  border-color: var(--line-2);
}

/* ── Resolved / closed state ── */

.thread-card--resolved {
  opacity: 1;
}

.thread-card--resolved .thread-card__name {
  text-decoration: line-through;
  text-decoration-color: var(--text-4);
  color: var(--text-3);
}

.thread-card--resolved .thread-card__body {
  color: var(--text-4);
}

.thread-card--resolved .thread-card__quote {
  opacity: 0.5;
}

.thread-card--resolved .thread-card__footer {
  opacity: 0.6;
}

/* ── Header zone ── */

.thread-card__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px 0;
}

.thread-card__header > .thread-card__badge {
  margin-left: auto;
  flex-shrink: 0;
}

.thread-card__avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent-glow);
  /* grid+place-items centers mono initials optically (see .margin-glyph). */
  display: grid;
  place-items: center;
  font-size: 9px;
  font-weight: 600;
  color: var(--accent);
  flex-shrink: 0;
}

.thread-card__header-text {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.thread-card__name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.thread-card__time {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  flex-shrink: 0;
}

/* Quiet secondary affordance — provenance footnote, not a call to action.
   Stays out of the way of the author/time pair until hovered. */
.thread-card__origin {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 0;
  transition: color var(--duration-fast) var(--ease-out-expo);
}

.thread-card__origin:hover {
  color: var(--accent);
}

span.thread-card__origin {
  cursor: default;
}

span.thread-card__origin:hover {
  color: var(--text-3);
}

/* ── Content zone ── */

.thread-card__content {
  padding: 8px 12px 0;
}

.thread-card__quote-group {
  margin-bottom: 6px;
}

.thread-card__quote {
  /* Chrome only — background, padding, radius. Text styling + clamping
     live on the inner .thread-card__quote-text so the clamp doesn't have
     to coexist with the padding inside the same box. */
  padding: 5px 8px;
  background: var(--paper-3);
  border-radius: var(--rounded-sm);
}

.thread-card__quote-text {
  /* `max-height + overflow: hidden` is the SOLE truncation mechanism so
     toggling `--clamped` only changes one property and the height
     transition is the only visible effect. We previously toggled
     `-webkit-line-clamp` too, but line-clamp clips the rendered content
     instantly when added, snapping the visible height to 48px before
     max-height could animate — the shrink direction never animated.
     Truncation signal is carried by the "Show more" affordance below;
     the trailing `…` from line-clamp is not preserved. */
  overflow: hidden;
  font-size: 11px;
  /* Pixel line-height (not the 1.45 ratio) so 3 lines is exactly 48px,
     i.e. the clamped max-height clips on a clean line boundary. */
  line-height: 16px;
  color: var(--text-2);
  /* `interpolate-size: allow-keywords` (Chrome 129+) lets max-height
     transition between an explicit value (48px) and an intrinsic
     keyword (`max-content`, the actual content height), producing a
     smooth content-sized reveal in either direction. Browsers without
     support fall back to instant state changes. */
  interpolate-size: allow-keywords;
  max-height: max-content;
  transition: max-height var(--duration-normal) var(--ease-out-expo);
}

.thread-card__quote-text--clamped {
  /* No padding on this element, so max-height is unambiguously the content
     height: 3 × 16px line-height = 48px. The chrome wrapper supplies the
     5px vertical padding outside this clipping box, which is what visually
     separates the clipped text from the box edge. */
  max-height: 48px;
}

@media (prefers-reduced-motion: reduce) {
  .thread-card__quote-text { transition: none; }
}

.thread-card__expand-btn {
  display: inline-block;
  padding: 0;
  margin-top: 3px;
  border: none;
  background: none;
  font-size: 10px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  transition: opacity var(--duration-fast) var(--ease-out-expo);
}

.thread-card__expand-btn:hover {
  text-decoration: underline;
}

.thread-card__body {
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-2);
}

.thread-card__reasoning {
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-2);
  background: var(--paper-3);
  padding: 5px 8px;
  border-radius: var(--rounded-sm);
  margin-top: 6px;
}

/* ── Tags zone ── */

.thread-card__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 8px 12px 0;
}

.thread-card__badge {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  padding: 3px 6px;
  border-radius: var(--rounded-sm);
  border: none;
}

.thread-card__badge--decision {
  color: var(--accent);
  background: var(--accent-glow);
}

.thread-card__badge--count {
  color: var(--text-3);
  background: var(--badge-bg-mute);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

.thread-card__badge--count:hover {
  background: var(--paper-3);
  color: var(--text-2);
}

.thread-card__badge--status {
  /* Click affordance: chevron + tighter padding around the label so the
     row reads as a dropdown trigger, not a static state pill. */
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}

.thread-card__badge--status::after {
  /* Chevron rendered as a tiny SVG via background-image so it inherits
     the badge's color via currentColor on a mask. Using a mask keeps the
     glyph tinted to the status color (warn/ok/err/mute) automatically. */
  content: '';
  width: 8px;
  height: 8px;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>") center / contain no-repeat;
  background: currentColor;
  flex-shrink: 0;
  opacity: 0.7;
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.thread-card__badge--status:hover {
  filter: brightness(0.96);
  transform: translateY(-0.5px);
}

.thread-card__badge--status[aria-expanded='true']::after {
  transform: rotate(180deg);
}

.thread-card__badge--open {
  color: var(--warn);
  background: var(--badge-bg-warn);
}

.thread-card__badge--approved {
  color: var(--ok);
  background: var(--badge-bg-ok);
}

.thread-card__badge--rejected {
  color: var(--err);
  background: var(--badge-bg-err);
}

.thread-card__badge--deferred {
  color: var(--text-3);
  background: var(--badge-bg-mute);
}

/* ── Inline replies ──
   Collapser layer: an outer wrapper toggled via grid-template-rows lets
   the replies block animate open/closed even though its height is
   content-driven. The inner element keeps the visible chrome (padding,
   margin, border) so the wrapper itself stays purely structural. */

.thread-card__replies-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-normal) var(--ease-out-expo);
}

.thread-card__replies-wrap--open {
  grid-template-rows: 1fr;
}

.thread-card__replies-wrap > .thread-card__replies {
  overflow: hidden;
  min-height: 0;
}

@media (prefers-reduced-motion: reduce) {
  .thread-card__replies-wrap { transition: none; }
}

.thread-card__replies {
  padding: 0 12px;
}

/* Border + top gap only when the wrap is open. Borders are box-edge, not
   overflow content, so a `border-top: 1px` on the inner element renders
   even when the outer grid row is `0fr` — that produced a phantom
   hairline above the footer's own border, reading as a double divider.
   Bind the divider to the open state so it materializes with the
   reveal and disappears when the wrap collapses. */
.thread-card__replies-wrap--open > .thread-card__replies {
  margin-top: 8px;
  border-top: 1px solid var(--line);
}

.thread-card__reply {
  display: flex;
  gap: 8px;
  padding: 8px 0;
}

.thread-card__reply + .thread-card__reply {
  border-top: 1px solid var(--line);
}

.thread-card__reply-avatar {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--paper-3);
  display: grid;
  place-items: center;
  font-size: 8px;
  font-weight: 600;
  color: var(--text-3);
  flex-shrink: 0;
  margin-top: 1px;
}

.thread-card__reply-content {
  flex: 1;
  min-width: 0;
}

.thread-card__reply-header {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: 2px;
}

.thread-card__reply-name {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
}

.thread-card__reply-body {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-3);
}

/* ── Reply composer ── */

.reply-composer {
  padding: 10px 0 10px;
  position: relative;
}

.reply-composer__editor-wrap {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  transition: border-color var(--duration-fast) var(--ease-out-expo);
}

.reply-composer__editor-wrap:focus-within {
  border-color: var(--accent);
}

/* The focus indicator is the wrap's accent border (rounded to --rounded-sm).
   Suppress the contenteditable's default outline, which is a square ring whose
   corners don't match the input. */
.reply-composer__editor-wrap [contenteditable]:focus,
.reply-composer__editor-wrap [contenteditable]:focus-visible {
  outline: none;
}

/* Tiptap/EditorContent renders `<div class="tiptap ProseMirror" contenteditable>`
   here, same as the main document editor. editor-tiptap.css's `.tiptap` rule is
   GLOBAL (`padding: 0 72px 96px; font-size: 15px; line-height: 1.7`) — the
   full-page doc-reading surface. Override it for this ~320-360px chrome input:
   compact 12px/1.45 type, and symmetric `padding: 6px 8px` (matching the sibling
   .thread-card__promote-input). The `padding` shorthand overrides the global
   72px/96px on every side, and the equal top/bottom padding vertically centers a
   single line — placeholder and caret both sit at padding-top, balanced by the
   bottom padding — which is the compact-composer equivalent of the old BlockNote
   `.bn-block-content { align-items: center }`. Scoped to
   .reply-composer__editor-wrap so none of this touches the main editor. */
.reply-composer__editor-wrap .ProseMirror {
  font-size: 12px;
  line-height: 1.45;
  padding: 6px 8px;
}

/* The doc surface's `.tiptap p { margin: 0.5em 0 }` leaks in and would push the
   line off-center; zero it so a single line stays centered and multi-line
   replies stack tightly. */
.reply-composer__editor-wrap .ProseMirror p {
  margin: 0;
}

/* Send wrap collapses to true zero height when empty (grid-template-rows
   0fr) so there's no reserved dead space below the input. When the
   composer has content, it grows to 1fr (the button's natural height)
   and reveals the button. The button itself carries no margin so the
   composer's growth is exactly the button's pixel height — no extra
   padding/margin added on either side. The composer's own 10px
   padding-bottom remains the visible bottom edge in both states. */
.reply-composer__send-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-fast) var(--ease-out-expo);
}

.reply-composer__send-wrap--open {
  grid-template-rows: 1fr;
}

.reply-composer__send-wrap > .reply-composer__send-slot {
  overflow: hidden;
  min-height: 0;
  display: flex;
  justify-content: flex-end;
}

@media (prefers-reduced-motion: reduce) {
  .reply-composer__send-wrap { transition: none; }
}

.reply-composer__send {
  margin-top: 8px;
  padding: 3px 8px;
  border: none;
  border-radius: var(--rounded-sm);
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  opacity: 0;
  transform: scale(0.94);
  transition:
    opacity var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}

.reply-composer__send-wrap--open .reply-composer__send {
  opacity: 1;
  transform: scale(1);
}

.reply-composer__send:hover {
  opacity: 0.85;
}

.reply-composer__send-wrap--open .reply-composer__send:hover {
  opacity: 0.85;
}

/* ── @mention inline ── */

.mention {
  color: var(--accent);
  font-weight: 500;
  background: var(--accent-glow);
  padding: 0 2px;
  border-radius: 2px;
}

/* ── Status picker (decisions) ── */

/* Same grid-template-rows collapse pattern used by replies-wrap /
   promote-wrap / footer-wrap. Outer animates the row height; inner
   keeps the visible chrome (background, padding, borders) bound to
   the --open variant so the closed state is true zero with no
   phantom strip. Symmetrical animation in both directions. */
.thread-card__status-picker-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-normal) var(--ease-out-expo);
}

.thread-card__status-picker-wrap--open {
  grid-template-rows: 1fr;
}

.thread-card__status-picker-wrap > .thread-card__status-picker {
  overflow: hidden;
  min-height: 0;
}

.thread-card__status-picker-wrap:not(.thread-card__status-picker-wrap--open) > .thread-card__status-picker {
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  border-top-width: 0;
  border-bottom-width: 0;
}

@media (prefers-reduced-motion: reduce) {
  .thread-card__status-picker-wrap { transition: none; }
}

.thread-card__status-picker {
  display: flex;
  gap: 4px;
  /* Center the buttons within the picker — the previous left-aligned row
     left a large empty gutter on the right and read as unintentional. */
  justify-content: center;
  padding: 6px 12px;
  margin-top: 2px;
  background: var(--paper-2);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.thread-card__status-option {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  color: var(--text-3);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo),
              color var(--duration-fast) var(--ease-out-expo),
              border-color var(--duration-fast) var(--ease-out-expo);
}

.thread-card__status-option:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-glow);
}

.thread-card__status-option--approved:hover {
  border-color: var(--ok);
  color: var(--ok);
  background: var(--badge-bg-ok);
}

.thread-card__status-option--rejected:hover {
  border-color: var(--err);
  color: var(--err);
  background: var(--badge-bg-err);
}

/* ── Footer zone ──
   Wrap pattern mirrors .thread-card__replies-wrap: outer drives the
   grid-template-rows collapse so footer enter/exit animates; inner keeps
   the layout chrome (padding, flex). The divider + top gap live only in
   the --open state so a collapsed wrap doesn't render a phantom
   hairline above whatever sits below it. */

.thread-card__footer-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-normal) var(--ease-out-expo);
}

.thread-card__footer-wrap--open {
  grid-template-rows: 1fr;
}

.thread-card__footer-wrap > .thread-card__footer {
  overflow: hidden;
  min-height: 0;
}

/* Base rule keeps the visible chrome so standalone footers (e.g. the
   demote button in DecisionsTab, which is not wrapped) render with
   proper spacing. The collapsed wrap state explicitly zeros the vertical
   chrome so `grid-template-rows: 0fr` can clear it — with box-sizing:
   border-box an inner that carries padding-top/bottom is min-content >=
   its own padding, which would otherwise leak a phantom strip when the
   wrap collapses. Horizontal padding stays on base in both forms. */
.thread-card__footer-wrap:not(.thread-card__footer-wrap--open) > .thread-card__footer {
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  border-top: 0;
}

@media (prefers-reduced-motion: reduce) {
  .thread-card__footer-wrap { transition: none; }
}

.thread-card__footer {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px 8px;
  margin-top: 8px;
  border-top: 1px solid var(--line);
}

/* When the replies block is expanded, the inline composer already
   sits flush above the footer with its own 10px bottom padding; an
   extra 4px on the footer reads as drift. Sibling selector zeros it
   only in that case. */
.thread-card__replies-wrap--open ~ .thread-card__footer-wrap > .thread-card__footer {
  margin-top: 0;
}

.thread-card__action {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  font-size: 11px;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

.thread-card__action svg {
  flex-shrink: 0;
}

.thread-card__action:hover {
  background: var(--paper-3);
  color: var(--text);
}

.thread-card__action--accent {
  color: var(--accent);
}

.thread-card__action--accent:hover {
  background: var(--accent-glow);
  color: var(--accent);
}

/* ── Promote-to-decision inline composer ──
   Expanded state of the Promote action — replaces the footer until the
   user confirms or cancels. Lets the user attach optional reasoning at
   the moment of promotion, which then surfaces in the DecisionsTab. */

/* Same wrap pattern as footer + replies — outer collapses with grid,
   inner keeps chrome. Border + top gap bind to the --open state. */
.thread-card__promote-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-normal) var(--ease-out-expo);
}

.thread-card__promote-wrap--open {
  grid-template-rows: 1fr;
}

.thread-card__promote-wrap > .thread-card__promote {
  overflow: hidden;
  min-height: 0;
}

/* Same polarity as footer: base keeps chrome; collapsed wrap zeros it. */
.thread-card__promote-wrap:not(.thread-card__promote-wrap--open) > .thread-card__promote {
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  border-top: 0;
}

@media (prefers-reduced-motion: reduce) {
  .thread-card__promote-wrap { transition: none; }
}

.thread-card__promote {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 12px 10px;
  margin-top: 4px;
  border-top: 1px solid var(--line);
}

.thread-card__promote-input {
  width: 100%;
  resize: vertical;
  min-height: 44px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  color: var(--text);
  font-family: var(--font);
  font-size: 12px;
  line-height: 1.4;
  transition: border-color var(--duration-fast) var(--ease-out-expo);
}

.thread-card__promote-input::placeholder {
  color: var(--text-4);
}

.thread-card__promote-input:focus {
  outline: none;
  border-color: var(--accent);
}

.thread-card__promote-actions {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
}

/* ── Orphaned thread: anchor-lost badge + de-emphasised quote ── */

/* Small uppercase tracked pill — same typographic vocabulary as the Decision
   badge and the version-item tags. Uses --warn / --badge-bg-warn so the signal
   reads as "something needs attention" without the severity of an error. */
.thread-card__anchor-lost {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  padding: 3px 6px;
  border-radius: var(--rounded-sm);
  color: var(--warn);
  background: var(--badge-bg-warn);
  margin-left: auto;
  flex-shrink: 0;
}

/* De-emphasise the saved quote on orphaned threads: reduced opacity signals
   the text is historical context, not a live highlight. */
.thread-card__quote--orphan {
  opacity: 0.55;
}

/* ── Comments Empty State ── */

.comments-empty {
  padding: 8px;
  animation: fadeIn var(--duration-entrance) var(--ease-out-expo);
}

.comments-empty__title {
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: 4px;
}

.comments-empty__hint {
  font-size: 11px;
  color: var(--text-4);
  line-height: 1.5;
}

/* ── Version Timeline ── */

/* ── Versions Loader ── */

.versions-loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 48px 16px 32px;
}

.versions-loader__stack {
  position: relative;
  width: 44px;
  height: 52px;
}

.versions-loader__page {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 32px;
  height: 40px;
  margin-left: -16px;
  border-radius: var(--rounded-sm);
  border: 1.5px solid var(--text-4);
  background: var(--paper-3);
}

.versions-loader__page--back {
  z-index: 1;
  border-color: var(--line-2);
  background: var(--paper-2);
  transform-origin: bottom center;
}

.versions-loader__page--mid {
  z-index: 2;
  border-color: var(--text-4);
  background: var(--paper-3);
  transform-origin: bottom center;
}

.versions-loader__page--front {
  z-index: 3;
  border-color: var(--text-3);
  background: var(--paper);
  transform-origin: bottom left;
}

.versions-loader__page--front::after {
  content: '';
  position: absolute;
  top: 8px;
  left: 5px;
  right: 5px;
  height: 1.5px;
  background: var(--text-3);
  border-radius: 1px;
}

.versions-loader__page--front::before {
  content: '';
  position: absolute;
  top: 13px;
  left: 5px;
  width: 14px;
  height: 1.5px;
  background: var(--text-4);
  border-radius: 1px;
}

.versions-loader__text {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-4);
  letter-spacing: 0.02em;
}

/* ── Versions Empty ── */

.versions-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 48px 16px 32px;
  color: var(--text-4);
  text-align: center;
}

.versions-empty svg {
  color: var(--text-4);
  margin-bottom: 4px;
}

.versions-empty__title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-3);
  margin: 0;
}

.versions-empty__hint {
  font-size: 11px;
  color: var(--text-4);
  margin: 0;
  line-height: 1.5;
}

/* ── Version Timeline ── */

.version-timeline {
  --dot-size: 7px;
  --msg-line-height: 17px;
  --item-pad: 8px;
  --row-gap: 4px;
  --dot-top: calc(var(--item-pad) + (var(--msg-line-height) - var(--dot-size)) / 2);
  --line-x: calc(var(--item-pad) + var(--dot-size) / 2);
  --line-y: calc(var(--dot-top) + var(--dot-size) / 2);
  /* Inset where hover/active fills begin. Leaves the dot + connector line
     in a transparent gutter so the timeline thread stays readable in every
     state. ~4px past the dot's right edge. */
  --bg-inset: calc(var(--item-pad) + var(--dot-size) + 4px);
  list-style: none;
  margin: 0;
  padding: 0;
}

.version-item {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: var(--item-pad);
  border-radius: var(--rounded-sm);
  cursor: pointer;
  position: relative;
  margin-bottom: var(--row-gap);
  /* Trap the ::after fill inside this stacking context so its z-index: -1
     sits below the line/dot/text but doesn't slip behind the page. */
  isolation: isolate;
}

/* Hover/active fill. Lives on a positioned overlay so it can have its own
   border-radius (rounded on all four corners) while the connector line and
   dot remain in their own transparent gutter on the left. */
.version-item::after {
  content: '';
  position: absolute;
  inset: 0 0 0 var(--bg-inset);
  border-radius: var(--rounded-sm);
  background: transparent;
  transition: background var(--duration-fast) var(--ease-out-expo);
  pointer-events: none;
  z-index: -1;
}

.version-item:last-child {
  margin-bottom: 0;
}

/* Timeline connector. Extends into the inter-item gap so the line stays
   continuous even though the item backgrounds are separated. */
.version-item::before {
  content: '';
  position: absolute;
  left: var(--line-x);
  top: 0;
  bottom: calc(var(--row-gap) * -1);
  width: 1px;
  background: var(--line);
  pointer-events: none;
}

.version-item:first-child::before {
  top: var(--line-y);
}

.version-item:last-child::before {
  bottom: auto;
  height: var(--line-y);
}

.version-item:only-child::before {
  display: none;
}

.version-item:hover::after {
  background: var(--paper-3);
}

.version-item--active::after,
.version-item--active:hover::after {
  background: var(--accent-glow);
}

.version-item__dot {
  position: relative;
  z-index: 1;
  width: var(--dot-size);
  height: var(--dot-size);
  border-radius: 50%;
  background: var(--paper-3);
  border: 1.5px solid var(--line-2);
  flex-shrink: 0;
  margin-top: 5px;
}

.version-item--active .version-item__dot {
  background: var(--accent);
  border-color: var(--accent);
}

.version-item__content {
  flex: 1;
  min-width: 0;
  /* It's a button: strip the default chrome and inherit text/box from
     the timeline row, then add an accessible focus ring. */
  display: block;
  padding: 0;
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
  border-radius: var(--rounded-sm);
}

.version-item__content:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.version-item__message {
  font-size: 12px;
  font-weight: 500;
  line-height: 17px;
  color: var(--text);
  margin-bottom: 5px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.version-item__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 4px;
}

.version-item__tag {
  display: inline-block;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 1px 5px;
  border-radius: var(--rounded-sm);
  line-height: 16px;
  white-space: nowrap;
}

.version-item__tag--type {
  background: var(--accent-glow);
  color: var(--accent);
}

.version-item__tag--scope {
  background: var(--badge-bg-mute);
  color: var(--text-3);
}

.version-item__tag--sha {
  background: var(--badge-bg-mute);
  color: var(--text-4);
  font-weight: 500;
}

.version-item__meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  color: var(--text-4);
}

/* ── Focus ── */

.context-panel__tab:focus-visible,
.outline__item:focus-visible,
.thread-card__action:focus-visible,
.thread-card__badge--count:focus-visible,
.thread-card__badge--status:focus-visible,
.thread-card__status-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ── Context Rail ──
   The panel's collapsed form. Same liquid-glass material as .app-shell__context
   (which is still its container) so collapsing reads as the surface narrowing,
   not as a different chrome element appearing. Buttons follow the
   .editor-toolbar__btn recipe — this is the same class of icon control, and
   having them diverge visually would be arbitrary. */

.context-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 0;
  height: 100%;
}

.context-rail__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  background: transparent;
  border: none;
  border-radius: var(--rounded-md);
  color: var(--text-3);
  cursor: pointer;
  /* Same reason as .editor-toolbar__btn: stop iOS Safari's double-tap-to-zoom
     heuristic firing on a small icon-only target. */
  touch-action: manipulation;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.context-rail__btn:hover {
  background: var(--accent-glow);
  color: var(--text);
}

.context-rail__btn--active {
  color: var(--accent);
  background: var(--accent-glow);
}

.context-rail__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

@media (pointer: coarse) {
  .context-rail__btn {
    width: 40px;
    height: 40px;
  }
}

.context-rail__icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Same badge as the mobile tab bar's unread pip — one visual language for
   "this section has N things waiting". */
.context-rail__badge {
  position: absolute;
  top: -4px;
  right: -8px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 600;
  line-height: 14px;
  text-align: center;
}

/* ── Context Flyout ──
   A rail button's section, opened beside it. Portaled to <body> with fixed
   coordinates (see ContextFlyout.tsx) because the rail is a narrow scrolling
   column that would otherwise clip it. Shares the shell recipe used by every
   other popover in the app. */

.context-flyout {
  position: fixed;
  z-index: 800;
  display: flex;
  flex-direction: column;
  width: min(360px, calc(100vw - 16px));
  /* Bounded so a long thread list scrolls inside the flyout rather than
     running past the viewport; ContextFlyout clamps the top edge to match. */
  max-height: min(560px, calc(100vh - 2 * var(--gutter, 12px) - var(--status-bar-height)));
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: popoverPop 220ms var(--ease-out-expo);
}

.context-flyout__head {
  flex-shrink: 0;
  padding: 10px 14px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-3);
  border-bottom: 1px solid var(--line);
}

.context-flyout__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 16px;
  background: var(--paper-2);
}
/* ── Command Palette ── */

.palette-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 20vh;
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.palette-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
}

[data-theme='dark'] .palette-backdrop {
  background: rgba(0, 0, 0, 0.6);
}

.palette {
  position: relative;
  width: 560px;
  max-height: 420px;
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--rounded-xl);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: fadeUp var(--duration-entrance) var(--ease-out-expo);
}

.palette__input-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}

.palette__input-wrap svg {
  flex-shrink: 0;
  color: var(--text-3);
}

.palette__scope {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--rounded-sm);
  background: var(--accent-glow);
  color: var(--accent);
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
}

.palette__scope-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border: none;
  background: transparent;
  color: var(--accent);
  padding: 0;
  cursor: pointer;
}

.palette__input {
  flex: 1;
  border: none;
  background: transparent;
  font-family: var(--font);
  font-size: 15px;
  color: var(--text);
  outline: none;
}

.palette__input::placeholder {
  color: var(--text-4);
}

.palette__results {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
}

.palette__group {
  margin-bottom: 8px;
}

.palette__group-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 4px 8px 6px;
}

.palette__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--rounded-md);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.palette__item--selected {
  background: var(--accent-glow);
}

@media (hover: hover) {
  .palette__item:hover {
    background: var(--accent-glow);
  }
}

.palette__item-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--rounded-md);
  background: var(--paper-3);
  color: var(--text-3);
  flex-shrink: 0;
}

.palette__item--selected .palette__item-icon {
  background: var(--accent-glow);
  color: var(--accent);
}

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

.palette__item-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.palette__item-path {
  font-size: 11px;
  color: var(--text-4);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.palette__item-shortcut {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  flex-shrink: 0;
}

.palette__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
  color: var(--text-4);
  font-size: 13px;
}

/* ── Loading skeleton ──
   Matches the .palette__item shape (28×28 icon + stacked text), so the
   layout doesn't shift when results arrive. Each row pulses with a small
   staggered delay for a calm-but-clearly-loading rhythm. */

.palette__skeleton {
  display: flex;
  flex-direction: column;
}

.palette__skeleton-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--rounded-md);
  animation: skeletonPulse 1.6s var(--ease-out-quart) infinite;
}

.palette__skeleton-icon {
  width: 28px;
  height: 28px;
  border-radius: var(--rounded-md);
  background: var(--paper-3);
  flex-shrink: 0;
}

.palette__skeleton-line {
  height: 11px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
}

.palette__footer {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  color: var(--text-4);
}

.palette__footer kbd {
  font-family: var(--mono);
  font-size: 10px;
  padding: 1px 4px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
}

/* ── Mobile (≤640px) ──
   On narrow viewports the 560px-wide palette overflows. Drop the 20vh
   top padding (keeps the palette closer to the top of the viewport, just
   under any system status bar) and stretch the palette to fill the
   available width minus a small gutter. */
@media (max-width: 640px) {
  .palette-overlay {
    padding-top: env(safe-area-inset-top, 16px);
    padding-left: 8px;
    padding-right: 8px;
  }

  .palette {
    width: 100%;
    max-width: none;
    max-height: 70vh;
  }
}
/* ── BlockNote Editor Overrides ── */

.editor-wrapper {
  /* Page-level entrance is owned by motion.div in DocumentViewerPage.
     A CSS animation here would compose with motion's translateX and
     produce a diagonal "slide effect" instead of a clean horizontal one. */
}

/* TOC jumps scroll a block to the top of the scroll area via
   scrollIntoView({ block: 'start' }) (DocumentViewerPage). The liquid-glass
   action toolbar (~44px) floats over that top edge, so without a scroll
   margin the targeted heading lands half-hidden behind it. scroll-margin-top
   offsets the resting position clear of the toolbar. */
.editor-wrapper [data-id] {
  scroll-margin-top: 56px;
}

.editor-wrapper .bn-container,
.editor-wrapper .bn-mantine {
  --bn-font-family: var(--font);
  --bn-colors-editor-text: var(--text);
  /* Transparent so the editor blends into its parent surface + scribble
     texture instead of painting a solid color on top. */
  --bn-colors-editor-background: transparent;
  --bn-colors-menu-text: var(--text);
  --bn-colors-menu-background: var(--paper-2);
  --bn-colors-tooltip-text: var(--text);
  --bn-colors-tooltip-background: var(--paper-3);
  --bn-colors-hovered-text: var(--text);
  --bn-colors-hovered-background: var(--paper-3);
  --bn-colors-selected-text: var(--on-accent);
  --bn-colors-selected-background: var(--accent);
  --bn-colors-disabled-text: var(--text-4);
  --bn-colors-disabled-background: var(--paper-3);
  --bn-colors-shadow: rgba(0, 0, 0, 0.08);
  --bn-colors-border: var(--line);
  --bn-colors-side-menu: var(--text-4);
  --bn-colors-highlights-gray-text: #9b9a97;
  --bn-colors-highlights-gray-background: #e3e2e0;
  --bn-colors-highlights-brown-text: #64473a;
  --bn-colors-highlights-brown-background: #e9e5e3;
  --bn-colors-highlights-red-text: #e03e3e;
  --bn-colors-highlights-red-background: #fbe4e4;
  --bn-colors-highlights-orange-text: #d9730d;
  --bn-colors-highlights-orange-background: #f6e9d9;
  --bn-colors-highlights-yellow-text: #dfab01;
  --bn-colors-highlights-yellow-background: #fbf3db;
  --bn-colors-highlights-green-text: #0f7b6c;
  --bn-colors-highlights-green-background: #ddedea;
  --bn-colors-highlights-blue-text: #0b6e99;
  --bn-colors-highlights-blue-background: #ddebf1;
  --bn-colors-highlights-purple-text: #6940a5;
  --bn-colors-highlights-purple-background: #eae4f2;
  --bn-colors-highlights-pink-text: #ad1a72;
  --bn-colors-highlights-pink-background: #f4dfeb;
}

/* Direct background override on .bn-editor — the variable cascade above
   should handle this, but BlockNote sets the bg via the var in its own
   stylesheet, and the variable override wasn't taking effect in practice.
   Forcing it directly here. */
.editor-wrapper .bn-editor {
  background-color: transparent !important;
}

[data-theme='dark'] .editor-wrapper .bn-container,
[data-theme='dark'] .editor-wrapper .bn-mantine {
  --bn-colors-editor-text: var(--text);
  --bn-colors-editor-background: transparent;
  --bn-colors-menu-text: var(--text);
  --bn-colors-menu-background: var(--paper-2);
  --bn-colors-tooltip-text: var(--text);
  --bn-colors-tooltip-background: var(--paper-3);
  --bn-colors-hovered-text: var(--text);
  --bn-colors-hovered-background: var(--paper-3);
  --bn-colors-selected-text: var(--on-accent);
  --bn-colors-selected-background: var(--accent);
  --bn-colors-disabled-text: var(--text-4);
  --bn-colors-disabled-background: var(--paper-3);
  --bn-colors-shadow: rgba(0, 0, 0, 0.3);
  --bn-colors-border: var(--line);
  --bn-colors-side-menu: var(--text-4);
  --bn-colors-highlights-gray-text: #B8B8C0;
  --bn-colors-highlights-gray-background: #404048;
  --bn-colors-highlights-brown-text: #988080;
  --bn-colors-highlights-brown-background: #3A3236;
  --bn-colors-highlights-red-text: #ff6b6b;
  --bn-colors-highlights-red-background: #3A2A30;
  --bn-colors-highlights-orange-text: #e3790d;
  --bn-colors-highlights-orange-background: #38302E;
  --bn-colors-highlights-yellow-text: #dfab01;
  --bn-colors-highlights-yellow-background: #38362E;
  --bn-colors-highlights-green-text: #4dab9a;
  --bn-colors-highlights-green-background: #2A3A3C;
  --bn-colors-highlights-blue-text: #5facc6;
  --bn-colors-highlights-blue-background: #27343d;
  --bn-colors-highlights-purple-text: #9a7ad6;
  --bn-colors-highlights-purple-background: #332d3d;
  --bn-colors-highlights-pink-text: #d66ba8;
  --bn-colors-highlights-pink-background: #3A2A36;
}

.editor-wrapper .bn-editor {
  font-family: var(--font);
  color: var(--text);
  font-size: 15px;
  line-height: 1.7;
  background-color: var(--paper);
  /* Top padding intentionally 0: .doc-header sits directly above and supplies
     its own margin-bottom; .app-shell__content-inner already pads 64px above
     the doc-header itself. A 64px padding-top here stacked an extra block of
     whitespace between the title and the first block. */
  padding: 0 72px 96px;
}

/* ── Version viewer + banner ──
   When a past version is selected from VersionsTab the editor surface
   swaps to a read-only renderer. The banner above is a deliberately
   prominent "you are not in the live doc" affordance — accent-tinted
   so it reads as an active *mode*, not as a passive status strip.
   The doc body underneath sits on the regular paper surface but is
   slightly desaturated so the reader senses "this is a snapshot"
   without losing legibility. */

.doc-view--viewing-version .tiptap {
  /* Tiny saturation drop reads as "frozen, not live". Not enough to
     impair reading — the eye still parses text at full color, but the
     surface stops feeling like an active editor. */
  filter: saturate(0.92);
}

/* VersionViewer renders an isolated Tiptap instance (see
   VersionViewer.tsx) whose root is `.version-viewer .tiptap`, not
   `.editor-wrapper .tiptap` — so it doesn't inherit editor-tiptap.css's
   global `.tiptap` background/padding scoping tricks and must restate
   them here at higher specificity (`.version-viewer .tiptap` beats the
   bare `.tiptap` rule) to override that rule's `background: transparent`
   and `padding: 0 72px 96px`. */
.version-viewer .tiptap {
  font-family: var(--font);
  color: var(--text);
  font-size: 15px;
  line-height: 1.7;
  background-color: var(--paper);
  padding: 32px 72px 64px;
  /* Hide the editor's contenteditable caret affordance even though
     editable=false already suppresses interactions. Belt-and-braces. */
  caret-color: transparent;
}

.version-viewer__error {
  padding: 32px 72px;
  font-size: 13px;
  color: var(--accent);
}

/* Loading state: a prose-shaped skeleton that fully replaces the doc while a
   version loads, so switching between past versions never flashes the previous
   version's still-visible content behind a bare "Loading…" label. Reuses the
   app-wide skeletonPulse treatment (mentions / command-palette / decisions) for
   consistency; the global prefers-reduced-motion rule freezes the pulse. */
.version-viewer__skeleton {
  padding: 32px 72px 64px;
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.version-viewer__skeleton-title {
  height: 32px;
  width: 46%;
  margin-bottom: 28px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  animation: skeletonPulse 1.6s var(--ease-out-quart) infinite;
}

.version-viewer__skeleton-line {
  height: 15px;
  margin-bottom: 12px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  animation: skeletonPulse 1.6s var(--ease-out-quart) infinite;
}

/* The resolved doc fades in as it replaces the skeleton on each version→version
   swap, so content arrives softly instead of popping. */
.version-viewer__doc {
  animation: fadeIn var(--duration-normal) var(--ease-out-quart);
}

/* The version banner is a second row INSIDE .editor-toolbar (rendered in
   DocumentViewerPage), so it spans the full chrome width and sits flush beneath
   the chrome row by construction — no doc-column constraint, no negative-margin
   tricks. Its 52px height + the 44px chrome row = the 96px --toolbar-h set by
   .app-shell__content--version-toolbar, which content-inner's padding-top
   clears. 12px inline padding matches .editor-toolbar__row--chrome so the icon
   shares the toolbar's content lane; the accent-glow tint marks version mode,
   the top hairline divides it from the chrome row above. */
.version-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 52px;
  flex-shrink: 0;
  padding: 0 12px;
  background: var(--accent-glow);
  border-top: 1px solid color-mix(in srgb, var(--accent) 14%, transparent);
  font-family: var(--font);
  font-size: 12px;
  color: var(--text);
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.version-banner__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--paper);
  color: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent);
}

.version-banner__text {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  min-width: 0;
}

.version-banner__title {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  line-height: 1.2;
}

.version-banner__meta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-2);
  /* Single-line in the fixed-height toolbar row; truncates rather than wrapping
     (which would blow the row height) — same discipline as the breadcrumbs. */
  flex-wrap: nowrap;
  overflow: hidden;
  min-width: 0;
}

.version-banner__sha {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--text);
  padding: 1px 6px;
  background: var(--paper);
  border-radius: var(--rounded-sm);
  font-size: 10.5px;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent);
}

.version-banner__sep {
  color: var(--text-4);
}

.version-banner__author {
  color: var(--text-2);
}

.version-banner__time {
  color: var(--text-3);
}

.version-banner__exit {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-radius: var(--rounded-md);
  background: var(--paper);
  color: var(--accent);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  touch-action: manipulation;
  flex-shrink: 0;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}

.version-banner__exit svg {
  flex-shrink: 0;
  opacity: 0.85;
}

@media (hover: hover) {
  .version-banner__exit:hover {
    background: color-mix(in srgb, var(--accent) 6%, var(--paper));
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  }
}

.version-banner__exit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (pointer: coarse) {
  .version-banner__exit {
    height: 40px;
  }
}

@media (max-width: 640px) {
  .version-banner {
    /* Row model (fixed height, 12px lane) carries over from the base rule; just
       tighten the gap on the narrower surface. */
    gap: 10px;
  }
  .version-banner__title {
    font-size: 12px;
  }
  .version-viewer .tiptap {
    padding: 20px 24px 48px;
    font-size: 14px;
  }
  .version-viewer__error {
    padding: 24px 16px;
  }
  .version-viewer__skeleton {
    /* Match the mobile doc-body gutter above so skeleton→doc doesn't shift. */
    padding: 20px 24px 48px;
  }
}

.editor-wrapper .bn-comment-editor .bn-editor,
.editor-wrapper .bn-thread .bn-editor {
  background-color: var(--bn-colors-editor-background);
  min-height: auto;
  font-size: inherit;
  line-height: inherit;
}

.editor-wrapper .bn-editor [data-file-block] .bn-add-file-button {
  background-color: var(--paper-3);
  color: var(--text-3);
}

.editor-wrapper > .bn-editor > .bn-block-group {
  min-height: 60vh;
}

.editor-wrapper .bn-block-group .bn-block-outer {
  padding: 2px 0;
}

/* ── Doc Header ── */

.doc-header {
  /* Entrance owned by motion.div page transition. */
  padding: 0 72px; /* matches .bn-editor horizontal gutter so title + body align */
  /* Extra breathing room below the sticky formatting toolbar, on top of the
     +20px the .app-shell__content-inner padding already clears. Scoped to the
     editor's doc header rather than the shared content-inner. */
  margin-top: 24px;
  margin-bottom: 20px;
}

/* ── Doc title responsive sizing + doc-header padding ──
   On mobile, hide everything in the breadcrumb except the active doc title,
   and shrink the title so it fits between the ☰ trigger (left) and the ⋯
   overflow menu (right). The full truncation chain (.editor-toolbar__left →
   .editor-toolbar__breadcrumbs → .editor-toolbar__crumb--active) keeps long
   doc titles from overflowing the toolbar.

   .doc-header padding lives here too (was at 720px historically, briefly
   moved to 1024px). It belongs at 640px because that's where .bn-editor's
   horizontal padding stops scaling with the surrounding chrome — moving the
   doc-header compaction earlier breaks the invariant that the doc title and
   editor body share the same horizontal gutter at tablet widths. */
@media (max-width: 640px) {
  /* Editor body shrinks its horizontal gutter from 72px to 24px and its
     font-size from 15px to 14px so doc content has breathing room on a
     narrow viewport. BlockNote heading sizes scale relative to the body
     font, so the 1px body shrink also brings down H1/H2/H3 by the same
     ratio without per-heading overrides. Vertical padding also tightens
     since the editor sits inside a smaller card. */
  .editor-wrapper .bn-editor {
    font-size: 14px;
    /* Same rationale as desktop: zero top padding, .doc-header owns the gap. */
    padding: 0 24px 48px;
  }
  .doc-header {
    padding: 0 24px;
  }
  .editor-toolbar__left {
    flex: 1;
    min-width: 0;
  }
  .editor-toolbar__breadcrumbs {
    flex: 1;
    min-width: 0;
    overflow: hidden;
  }
  .editor-toolbar__crumb--active {
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }
  .editor-toolbar__breadcrumbs .editor-toolbar__crumb:not(.editor-toolbar__crumb--active),
  .editor-toolbar__breadcrumbs .editor-toolbar__sep {
    display: none;
  }
}

.doc-title-wrap {
  margin: 0;
  padding: 0;
  font-weight: 700;
}

.doc-title {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  /* Theme accent so the document title reads as distinct from body headings
     (which use --text). Theme-aware: --accent is the dusty blue in light and a
     lighter dusty blue in dark. */
  color: var(--accent);
  line-height: 1.2;
  border: none;
  outline: none;
  background: transparent;
  width: 100%;
  padding: 0;
  margin-bottom: 4px;
  font-family: var(--font);
  border-bottom: 2px solid transparent;
  transition: border-color var(--duration-normal) var(--ease-out-expo);
}

.doc-title:hover {
  border-bottom-color: var(--line);
}

.doc-title:focus {
  border-bottom-color: var(--accent);
}

.doc-title::placeholder {
  color: var(--text-3);
}

.doc-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-3);
  padding-bottom: 16px;
  margin-bottom: 4px;
  /* Reserve height so skeleton -> resolved doesn't push content down. */
  min-height: calc(20px + 16px); /* meta line + padding-bottom */
}

/* ── Where the document lives ──
   Its own row under the byline, shown only when more than one repo is in play.
   The byline is the document's metadata and degrades by width; this is a long
   string of a different kind, so it gets a line rather than a share of that one
   (#200). */
.doc-repo {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--accent);
  padding-bottom: 16px;
  margin-bottom: 4px;
}

/* The byline reserves the space beneath itself so the metadata skeleton
   resolving does not push the document down. When this row follows, that space
   belongs BETWEEN the two rows and the reservation moves with it — otherwise
   the pair would carry the gap twice. */
.doc-meta:has(+ .doc-repo) {
  padding-bottom: 4px;
  min-height: calc(20px + 4px);
}

.doc-meta__item {
  display: flex;
  align-items: center;
  gap: 4px;
}

.doc-meta__sha {
  font-family: var(--mono);
  font-size: 11px;
}

.doc-meta__sep {
  color: var(--text-3);
}

.doc-meta__item--placeholder {
  min-height: 20px;
  display: inline-flex;
  align-items: center;
}

/* Resolved-but-empty: the doc has no version history (new/uncommitted, or a
   path not present in the connected repo). Quiet, so it doesn't read as an
   error or a stuck loader. */
.doc-meta__item--empty {
  min-height: 20px;
  font-size: 12px;
  color: var(--text-4);
}

.doc-meta__resolved {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  animation: metaFadeIn 420ms var(--ease-out-quart);
}

@keyframes metaFadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.doc-meta__skeleton {
  display: inline-block;
  width: 220px;
  height: 12px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  animation: skeletonPulse 1.6s var(--ease-out-quart) infinite;
}

@keyframes skeletonPulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* ── Comment Highlights in Editor ── */

.comment-highlight {
  background: var(--accent-glow);
  border-bottom: 2px solid var(--accent);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.comment-highlight:hover {
  background: var(--accent-glow);
}

/* ── Diff Highlights ── */

/* ── Editor Error Fallback ── */

.editor-fallback {
  padding: 24px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  background: var(--paper-3);
  color: var(--text-3);
  font-size: 13px;
  line-height: 1.7;
}

.editor-fallback__title {
  font-weight: 600;
  color: var(--text);
  margin: 0 0 8px;
}

.editor-fallback__detail {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--err);
  margin: 0;
}

/* Diff highlighting uses background tint + leading glyph (set in markup),
   never a side-stripe border — see DESIGN.md §6. */
.diff-added,
.diff-removed,
.diff-modified {
  animation: diffIn 320ms var(--ease-out-quart);
}

@keyframes diffIn {
  from { background-color: transparent; }
}

.diff-added {
  background: color-mix(in oklch, var(--ok) 14%, transparent);
  border-radius: var(--rounded-sm, 4px);
  padding: 1px 8px;
}

.diff-removed {
  background: color-mix(in oklch, var(--err) 12%, transparent);
  border-radius: var(--rounded-sm, 4px);
  padding: 1px 8px;
  text-decoration: line-through;
  opacity: 0.7;
}

.diff-modified {
  background: color-mix(in oklch, var(--warn) 12%, transparent);
  border-radius: var(--rounded-sm, 4px);
  padding: 1px 8px;
}

/* ── Diff Stats ── */

.diff-stats {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 12px;
  border-radius: var(--rounded-md);
  background: var(--paper-3);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: 16px;
}

.diff-stats__added { color: var(--ok); }
.diff-stats__removed { color: var(--err); }
.diff-stats__modified { color: var(--warn); }

/* ── Editor formatting chrome (sticky bar, bubble bar, slash menu) ──
   Themed to app tokens. The sticky bar renders as row 2 of .editor-toolbar
   (see layout.css); this file styles the controls, not the glass surface. */

.fmt-bar {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 40px;
  padding: 0 8px;
  overflow-x: auto;
  scrollbar-width: none;
}
.fmt-bar::-webkit-scrollbar { display: none; }

.fmt-bar__group {
  display: flex;
  align-items: center;
  gap: 1px;
  flex-shrink: 0;
}

.fmt-bar__sep {
  width: 1px;
  height: 18px;
  margin: 0 6px;
  background: var(--line);
  flex-shrink: 0;
}

.fmt-bar__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--duration-fast, 150ms), color var(--duration-fast, 150ms);
}
.fmt-bar__btn:hover { background: var(--paper-3); color: var(--text); }
.fmt-bar__btn[data-active='true'] { background: var(--paper-3); color: var(--text); }
.fmt-bar__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

@media (pointer: coarse) {
  .fmt-bar__btn { width: 36px; height: 36px; }
}

/* Highlight mark tint (warm amber/yellow, defined in tokens.css). This is
   the plain Tiptap Highlight mark — distinct from the comment-linked
   highlight, which is the `.comment-highlight` class in editor.css built on
   the dusty-blue --accent/--accent-glow tokens (not a "--comment-highlight"
   variable; no such token exists). Phase 4's Tiptap `comment` mark will
   render via that same --accent-based treatment, staying visually distinct
   from --mark-highlight here. */
.tiptap mark {
  background: var(--mark-highlight);
  color: inherit;
  border-radius: 2px;
  padding: 0 1px;
  box-decoration-break: clone;
}

/* ── Floating (bubble) toolbar ── */
.bubble-bar {
  position: fixed;
  z-index: 50;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1px;
  max-width: 90vw;
  padding: 4px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-lg);
}
.bubble-bar__sep { width: 1px; height: 18px; margin: 0 3px; background: var(--line); flex-shrink: 0; }
.bubble-bar__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border: none; border-radius: var(--rounded-md);
  background: transparent; color: var(--text-3); cursor: pointer;
}
.bubble-bar__btn:hover { background: var(--paper-3); color: var(--text); }
.bubble-bar__btn[data-active='true'] { background: var(--paper-3); color: var(--text); }
.bubble-bar__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── Slash menu ── */
.slash-menu {
  display: flex;
  flex-direction: column;
  width: 260px;
  max-height: 320px;
  overflow-y: auto;
  padding: 6px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-lg);
}
.slash-menu--empty { padding: 12px; color: var(--text-3); font-size: 13px; }
.slash-menu__item {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  padding: 7px 10px; border: none; border-radius: var(--rounded-md);
  background: transparent; color: var(--text); text-align: left; cursor: pointer;
}
.slash-menu__item[data-active='true'] { background: var(--paper-3); }
.slash-menu__title { font-size: 14px; font-weight: 500; }
.slash-menu__desc { font-size: 12px; color: var(--text-3); }
/* ── BlockNote — Forge theme bridge ── */

.bn-root[data-forge][data-color-scheme] {
  --bn-colors-editor-text: var(--text);
  /* Transparent so the editor blends into its parent's off-white surface +
     scribble texture (set on .app-shell__content). Previously this was
     var(--paper) which painted pure white over the parent's bg. */
  --bn-colors-editor-background: transparent;
  --bn-colors-menu-text: var(--text);
  --bn-colors-menu-background: var(--paper);
  --bn-colors-tooltip-text: var(--on-accent);
  --bn-colors-tooltip-background: var(--accent);
  --bn-colors-hovered-text: var(--text);
  --bn-colors-hovered-background: var(--paper-3);
  --bn-colors-selected-text: var(--on-accent);
  --bn-colors-selected-background: var(--accent);
  --bn-colors-disabled-text: var(--text-4);
  --bn-colors-disabled-background: var(--paper-3);
  --bn-colors-shadow: var(--line);
  --bn-colors-border: var(--line);
  --bn-colors-side-menu: var(--text-4);
  --bn-font-family: var(--font);
  --bn-border-radius: var(--rounded-md);
}

/* ── Thread mark (inline text highlight) ── */

.bn-root[data-forge] .bn-thread-mark:not([data-orphan="true"]) {
  background: var(--accent-glow);
  border-bottom: 2px solid var(--accent);
  border-radius: 2px;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.bn-root[data-forge] .bn-thread-mark:not([data-orphan="true"]):hover {
  background: var(--accent-soft);
}

.bn-root[data-forge] .bn-thread-mark.bn-thread-mark-selected {
  background: var(--accent-soft);
}

/* ── Thread card root ── */

.bn-root[data-forge] .bn-thread {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-md);
  padding: 0;
  gap: 0;
  overflow: hidden;
}

/* ── Comment section ── */

.bn-root[data-forge] .bn-thread .bn-thread-comments {
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  gap: 12px;
}

.bn-root[data-forge] .bn-thread .bn-thread-comment {
  gap: 8px;
}

/* ── Avatar ── */

.bn-root[data-forge] .bn-thread .bn-thread-comment .mantine-Avatar-root {
  --avatar-size: 22px;
  width: 22px;
  height: 22px;
  min-width: 22px;
}

.bn-root[data-forge] .bn-thread .bn-thread-comment .mantine-Avatar-placeholder {
  background: var(--accent-glow);
  color: var(--accent);
  border: none;
  font-size: 9px;
}

.bn-root[data-forge] .bn-thread .bn-thread-comment .mantine-Avatar-placeholder svg {
  width: 11px;
  height: 11px;
}

/* ── Author name ── */

.bn-root[data-forge] .bn-thread .bn-thread-comment .mantine-Text-root {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.4;
}

/* ── Date ── */

.bn-root[data-forge] .bn-thread .bn-thread-comment .mantine-Text-root .mantine-Text-root {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  color: var(--text-2);
}

/* ── Comment body text ── */

.bn-root[data-forge] .bn-thread .bn-comment-editor .bn-editor {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-2);
}

.bn-root[data-forge] .bn-thread .bn-comment-editor .bn-editor .bn-inline-content {
  font-size: 13px;
  line-height: 1.5;
}

/* ── Composer section ── */

.bn-root[data-forge] .bn-thread .bn-thread-composer {
  padding: 10px 14px 6px;
  gap: 4px;
}

/* Composer input — compact single-line feel */
.bn-root[data-forge] .bn-thread .bn-thread-composer .bn-container > .bn-editor {
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  padding: 6px 10px;
  font-size: 13px;
  line-height: 1.5;
}

/* Reduce internal block padding for tighter input */
.bn-root[data-forge] .bn-thread .bn-thread-composer .bn-block-outer {
  padding: 0;
}

.bn-root[data-forge] .bn-thread .bn-thread-composer .bn-block-content {
  padding: 0;
}

/* ── Actions toolbar (wraps Save/Cancel buttons) ── */

.bn-root[data-forge] .bn-thread .bn-comment-actions-wrapper {
  padding: 0;
}

.bn-root[data-forge] .bn-thread .bn-comment-actions {
  background: transparent;
  border: none;
  padding: 0;
  gap: 4px;
  margin-left: auto;
}

/* ── Buttons ── */

.bn-root[data-forge] .bn-thread .bn-button {
  font-size: 11px;
  font-weight: 500;
  border-radius: var(--rounded-md);
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

.bn-root[data-forge] .bn-thread .bn-button[data-test="save"] {
  background: var(--accent);
  color: var(--on-accent);
}

.bn-root[data-forge] .bn-thread .bn-button[data-test="save"]:hover:not(:disabled) {
  background: var(--accent-dim);
}

.bn-root[data-forge] .bn-thread .bn-button[data-test="save"]:disabled {
  background: var(--paper-3);
  color: var(--text-4);
}

.bn-root[data-forge] .bn-thread .bn-button:not([data-test="save"]) {
  background: transparent;
  color: var(--text-3);
}

.bn-root[data-forge] .bn-thread .bn-button:not([data-test="save"]):hover {
  background: var(--paper-3);
  color: var(--text);
}

/* ── Placeholder text ── */

.bn-root[data-forge] .bn-thread [data-placeholder]::before {
  color: var(--text-4) !important;
  font-style: normal;
}

/* ── Focus ring suppression ── */

.bn-root[data-forge] .bn-thread .bn-comment-editor .bn-editor:focus-within {
  outline: none;
}

/* ── New comment card (no saved comments yet) ── */

.bn-root[data-forge] .bn-thread:not(:has(.bn-thread-comments)) {
  padding: 14px;
}

.bn-root[data-forge] .bn-thread:not(:has(.bn-thread-comments)) .bn-comment-editor .bn-editor {
  min-height: 48px;
}
/* ── Splash (editorial welcome surface) ── */

.splash {
  /* Full-width "page" so the spotlight + paper texture extend beyond the
     content column. Height fills the editor card's content area so vertical
     centering of the main content has room to breathe. --splash-col is the
     shared editorial column width (brand, meta, palettes, doc rows all align
     to it); wider than the original 640px so long doc titles stop truncating. */
  --splash-col: 860px;
  width: 100%;
  min-height: calc(100vh - 220px);
  padding: 56px 0 40px;
  display: flex;
  flex-direction: column;
  gap: 0;
  position: relative;
  /* Background paint (spotlight + paper texture) lives on .app-shell__content
     itself via the :has(.splash) rule below — moves the gradient further up
     the component tree so it fills the editor card, not just the 640px
     content column. */
}

/* Spotlight gradient + paper texture applied to the editor CARD when the
   welcome view is rendered. The :has() selector activates this paint only
   when .splash is a descendant — the regular document editor view keeps
   its plain --paper background. */
/* Off-white editor surface + pencil-scribble texture applied to EVERY
   editor card in light mode (welcome view AND document editor view).
   The scribble is the visual character: graphite-toned strokes lightly
   scattered across the page. Dark mode rules below restore the dark
   paper surface and hide the light scribble for the editor view; the
   welcome view in dark mode replaces the scribble with the grain +
   spotlight treatment via the :has(.splash) override further down. */
.app-shell__content {
  /* Was: hardcoded #FCFBF7 (warm cream) — locked all palettes into warm
     paper. Use --paper so cool palettes (Harbor, Ink) get their proper
     cool paper tone. */
  background-color: var(--paper);
  isolation: isolate;
}

.app-shell__content::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='turbulence' baseFrequency='0.92 0.18' numOctaves='2' stitchTiles='stitch' seed='2'/%3E%3CfeColorMatrix values='0 0 0 0 0.15  0 0 0 0 0.13  0 0 0 0 0.11  0 0 0 3.4 -2.2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E");
  background-size: 300px 300px;
  background-repeat: repeat;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 6%);
  mask-image: linear-gradient(to bottom, transparent 0%, black 6%);
}

/* Dark mode editor card (no welcome view): restore the dark paper surface
   and hide the light scribble. Dark mode welcome below replaces the
   texture with its grain + spotlight treatment via higher specificity. */
[data-theme='dark'] .app-shell__content {
  background-color: var(--paper);
}

[data-theme='dark'] .app-shell__content::before {
  display: none;
}

[data-theme='dark'] .app-shell__content:has(.splash) {
  /* Was: hardcoded warm-brown #1A1612 + amber spotlight. Now palette-aware:
     paper as the base, palette accent as the spotlight at low alpha. The
     spotlight tints the top of the welcome view in the active palette's
     hue — terracotta gets a warm orange wash, Harbor a cool teal wash,
     Ink a navy halo. */
  background-color: var(--paper);
  background-image: radial-gradient(
    ellipse 70% 60% at 50% -5%,
    var(--accent-glow-strong),
    var(--accent-glow) 32%,
    transparent 75%
  );
}

/* Dark-mode texture: white grain (against the dark surface) plus a mask
   matching the dark mode spotlight's wider/longer shape. display: block
   un-hides the pseudo from the dark mode editor base rule above. */
[data-theme='dark'] .app-shell__content:has(.splash)::before {
  display: block;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.18 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  -webkit-mask-image:
    radial-gradient(
      ellipse 70% 60% at 50% -5%,
      rgba(0, 0, 0, 0.9),
      rgba(0, 0, 0, 0.5) 28%,
      rgba(0, 0, 0, 0.18) 52%,
      transparent 75%
    ),
    linear-gradient(to bottom, transparent 0%, black 7%);
  mask-image:
    radial-gradient(
      ellipse 70% 60% at 50% -5%,
      rgba(0, 0, 0, 0.9),
      rgba(0, 0, 0, 0.5) 28%,
      rgba(0, 0, 0, 0.18) 52%,
      transparent 75%
    ),
    linear-gradient(to bottom, transparent 0%, black 7%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* ── Header ── */

.splash__header {
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo);
}

/* Brand anchored at the upper section of the page, max-width-constrained
   and horizontally centered within the wider .splash canvas. */
.splash__brand {
  max-width: var(--splash-col);
  margin: 0 auto;
  padding: 0 24px;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 24px;
  flex-shrink: 0;
  position: relative;
}

/* Main content area — title, sections, footer. Fills the remaining height
   below the brand and vertically centers its children. Same max-width as
   the brand for editorial consistency. */
.splash__main {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: var(--splash-col);
  margin: 0 auto;
  width: 100%;
  padding: 0 24px;
  gap: 40px;
}

/* Embossed editorial wordmark. Brand text + colored period + dashed rule
   all share the same emboss physics (white highlight below, dark shadow
   above) so they read as one carved composition on the paper-textured
   surface. The spotlight above provides the directional light source. */
.splash__brand-text {
  font-family: var(--font);
  font-size: 80px;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1;
  /* Optical kerning: the 'M' glyph has built-in left side-bearing
     whitespace that's invisible at small font sizes but visibly offsets
     the wordmark from the rest of the column at 80px. Negative text-indent
     pulls the first character back so the visible left edge of "M" lines
     up with the rest of the 640px content column (meta line, title,
     section labels, etc.). Value is in em so it scales with font-size at
     the responsive breakpoints (64px at ≤1024px, 48px at ≤640px). */
  text-indent: -0.04em;
  /* Padding-bottom extends the box just enough to include descenders
     (g, p, y, etc.). Without this, background-clip: text would clip the
     descender of "g" in "Margin" because the line-box (line-height × 1em)
     doesn't extend below the baseline far enough to cover them. */
  padding-bottom: 0.2em;
  flex-shrink: 0;
  /* Inset-text: dark accent-tinted ink fill + dimmer cream highlight
     softly washed in accent. The previous grey fill read ambiguously
     (the eye flipped between embossed and debossed) — locking the fill
     toward --accent-dim gives a definite "this is the carved interior,
     in the palette's deep ink" reading. The highlight layers a quiet
     cream + an accent-glow halo so paper light bouncing back up from
     the recess carries palette warmth, not just neutral cream. */
  background-color: color-mix(in oklch, var(--accent-dim), var(--text) 35%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Highlights stay subtle so the dark carved-interior fill carries the
     visual weight. Cream highlight at 0.10 alpha (was 0.22) and accent
     halo at the softer --accent-glow (0.10 alpha) instead of -strong
     (0.20). Together they hint at light bouncing off the inside of the
     recess without competing with the dark fill above. */
  text-shadow:
    rgba(255, 250, 240, 0.10) 0px 2px 2px,
    var(--accent-glow) 0px 3px 6px;
}

.splash__brand-period {
  /* The period is the one chromatic moment in the wordmark. Bumped from
     --accent-dim to --accent (full chroma) and given an accent-glow halo
     so it reads as a deliberate, vivid signature against the muted ink
     letters. Same downward highlight kept for emboss-physics consistency. */
  color: var(--accent);
  font-weight: 700;
  margin-left: 1px;
  text-shadow:
    0 0 28px var(--accent-glow-strong),
    rgba(255, 250, 240, 0.35) 0px 3px 3px;
}

/* Editorial baseline: 1px hairline in --line-2 that fades to transparent
   at the right edge. Previous version terminated in an accent dot, but
   the dot echoed the period of "Margin." — two accent dots in close
   proximity read as visual stuttering. Reserving the only chromatic
   moment for the period makes it more meaningful. The fade also lets
   the line feel like it continues past the visible end, not like it
   was capped. */
.splash__brand-rule {
  flex: 1;
  height: 1px;
  align-self: center;
  margin-left: 4px;
  background: linear-gradient(to right, var(--line-2), transparent);
}

/* Editorial metadata line — palette name + today's date. Mono uppercase
   tracked label, sits just below the brand row and aligns to the same
   640px column. The palette name takes the accent color (so the user's
   chosen palette is named in their chosen color); the date is the
   quieter mono tone. */
.splash__brand-meta {
  max-width: var(--splash-col);
  margin: 14px auto 0;
  padding: 0 24px;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-3);
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo);
}

.splash__brand-edition {
  color: var(--accent);
  font-weight: 700;
}

.splash__brand-divider {
  color: var(--text-4);
}

.splash__brand-date {
  font-family: var(--mono);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-2);
}

/* ── Palette taster row ──
   9 mini swatch cards under the metadata line. Each card is a tiny
   version of the SettingsModal dropdown card (paper stripe + surface
   block + accent dot), sized for "horizontal stamp row" not "settings
   UI grid." Click to switch palette inline without opening Settings.
   The current palette gets accent border + accent-glow background. */

.splash__palettes {
  max-width: var(--splash-col);
  margin: 16px auto 0;
  padding: 0 24px;
  width: 100%;
  display: flex;
  gap: 8px;
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo);
}

.splash__palette {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  width: 40px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  flex-shrink: 0;
  transition:
    transform var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    box-shadow var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .splash__palette:hover {
    transform: translateY(-1px);
    border-color: var(--line-2);
  }
}

.splash__palette:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.splash__palette--active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-glow);
}

.splash__palette-paper {
  display: block;
  height: 6px;
  flex-shrink: 0;
}

.splash__palette-surface {
  position: relative;
  flex: 1;
  display: block;
}

.splash__palette-accent {
  position: absolute;
  bottom: 3px;
  right: 3px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5);
}

/* Mobile: the row remains horizontal but allows horizontal scroll if it
   ever overflows on very narrow screens. Cards shrink slightly. */
@media (max-width: 640px) {
  .splash__palettes {
    overflow-x: auto;
    padding: 0 16px;
    scrollbar-width: none;
  }
  .splash__palettes::-webkit-scrollbar {
    display: none;
  }
  .splash__palette {
    width: 36px;
    height: 24px;
  }
}

/* Dark mode: unambiguous DEBOSSED (carved into the surface) treatment. In
   dark mode a transparent fill leaves the letter ambiguous — the eye can
   read the rim shadows as either a raised face being lit from above OR
   the inside of a carve. To force the debossed reading, the letter fill
   becomes slightly darker than the page (the carve interior catches less
   light than the outer surface), the top rim shadow goes much deeper (the
   recess wall is genuinely in shadow), and the bottom rim highlight
   brightens a touch (light bouncing off the inner bottom curve). */

[data-theme='dark'] .splash__brand-text {
  /* Dark mode fill: stays palette-agnostic at rgba(0,0,0,0.48). The
     palette character comes through the spotlight wash painted on the
     welcome surface behind the wordmark, not the fill itself.

     Bottom light rim: dropped 0.18 -> 0.04 alpha and softened with a
     1px blur. The previous hard 1px white rim at 0.10 alpha was still
     rendering as a visible outline that lifted the letters off the
     paper — the eye read "raised letterforms" instead of "carved
     interior." A blurred near-imperceptible rim is enough to suggest
     light bouncing back from the bottom of the carve without competing
     with the dark fill. The top dark rim stays sharp + strong (0.85)
     because the carve's top shadow IS the dominant depth cue. */
  /* Fill lightened from 0.48 -> 0.25 so the carved interior has enough
     contrast against the 0.85 top dark rim. At 0.48 the fill was
     essentially black-on-black with the rim, so the rim couldn't read.
     0.25 blends with paper to give a "darker paper" interior — visibly
     recessed but with enough room beneath it for the rim shadow to
     register as a distinct top-edge cliff.

     CRITICAL: reset the light-mode background-clip fill. In dark mode,
     --accent-dim is the LIFTED accent and --text is light cream, so the
     inherited color-mix produces a bright pink that bleeds through the
     semi-transparent color below — visible as "bright terracotta
     outlines" around the letter edges. Forcing background to none so
     only the dark `color` paints the glyph. */
  /* Canonical letterpress / engraved text recipe (verified via web
     research after several over-engineered attempts):
       1. text fill darker than paper — the carved-in interior
       2. light shadow 1px DOWN, 0 blur — light catching the bottom
          edge of the carve as it returns to surface level
       3. dark shadow 1-2px UP, 0 blur — the top cliff in shadow

     All three are hard (zero blur) — letterpress is a sharp-edged
     impression, not a soft glow. Earlier attempts with blur produced
     a halo that read as blurred letter edges (text-shadow blur always
     extends OUT of the glyph). Earlier attempts with a gradient fill
     killed the fill-vs-rim contrast that the eye needs to read depth. */
  /* DARK MODE WORDMARK — simple letterpress.
   *
   * Web research confirmed: combining background-clip:text with
   * text-shadow has fundamental rendering conflicts because the
   * text-shadow paints ON TOP of the clipped fill. The proper workaround
   * (pseudo-elements with duplicated text via data-text) is a structural
   * change too invasive for this iteration.
   *
   * So: ditch background-clip entirely. Use a plain solid color for the
   * fill, and two soft text-shadows for the depth cues. At 80px text, a
   * small 1px blur on the shadows is imperceptible as "blur" but smooths
   * the rim edges enough to read as atmospheric depth rather than hard
   * outlines.
   *
   * Three independent controls:
   *
   *   1. TEXT FILL    — `color`
   *      Currently:    paper + 22% white (~#4A4341 on Terracotta dark)
   *      To brighten:  raise the white mix percentage
   *      To darken:    lower it (or switch to black mix)
   *
   *   2. TOP INNER SHADOW — first text-shadow `0 -1px 1px ...`
   *      Currently:    1px dark band above letters at 60% black, 1px blur
   *      To deepen:    raise alpha
   *      To extend:    raise the Y offset (e.g., -2px)
   *
   *   3. BOTTOM HIGHLIGHT — second text-shadow `0 1px 1px ...`
   *      Currently:    1px light band below letters at 8% white, 1px blur
   *      To brighten:  raise alpha
   *      To dim:       lower alpha
   */
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  /* 1. TEXT FILL — lighter shade of paper, palette-tinted.
     Paper itself is near-zero chroma (a warm-leaning neutral), so just
     lifting its lightness produces lighter gray, not lighter terracotta.
     Mixing paper with the accent (which carries the actual palette hue)
     gives a lighter shade that retains the warm character — for
     Terracotta dark, this lands as a medium warm-brown, not gray. */
  /* Using --accent-dim (the deeper, more saturated palette variant) rather
     than --accent gives the fill more chromatic weight without losing the
     warm terracotta character. */
  color: color-mix(in oklch, var(--paper) 85%, var(--accent-dim) 15%);
  text-shadow:
    0 -1px 1px rgba(0, 0, 0, 0.6),          /* 2. TOP INNER SHADOW */
    0 1px 1px rgba(255, 255, 255, 0.08);    /* 3. BOTTOM HIGHLIGHT */
}

[data-theme='dark'] .splash__brand-period {
  /* Dark mode period: keep the accent color (now palette-aware via
     --accent) but the glow gets stronger since dark surfaces absorb more
     of the halo. The carve-emboss text-shadow rims stay so the period
     still reads as an inset letter, not a sticker. */
  color: var(--accent);
  text-shadow:
    0 0 36px var(--accent-glow-strong),
    0 1px 0 rgba(255, 255, 255, 0.18),
    0 -2px 0 rgba(0, 0, 0, 0.85);
}

[data-theme='dark'] .splash__brand-rule {
  /* Dark rule: white-alpha fade so the hairline reads against the dark
     paper without competing with the spotlight tint above. */
  background: linear-gradient(to right, rgba(255, 255, 255, 0.14), transparent);
}

.splash__title {
  font-size: 2rem;
  font-weight: 700;
  color: var(--text);
  margin: 0;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

/* ── Triage cards (Activity Preview) ──
   Three cards (Mentions / Drafts / Decisions) above "The story so far."
   Each card shows: count + label header, plus a 1-line preview of the
   most recent item in that category. Click anywhere → navigates to the
   triage view. Quiet variant when count is zero (preview becomes
   "All clear" / "Nothing pending" in dimmed text).

   Visual: 3-column grid, hairline-bordered cards. The card header is a
   single baseline-aligned row (count in mono, divider, label in caps).
   The preview is a 13px body line truncated with ellipsis. */
.splash__triage {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  /* Generous top gap separates the triage row from the palette taster
     above; the bottom margin stays tighter so the row reads as part of
     the actionable cluster with whatever follows below. */
  margin-top: 72px;
  margin-bottom: 36px;
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo);
}

.splash__triage-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  background: transparent;
  cursor: pointer;
  font-family: var(--font);
  text-align: left;
  min-width: 0;
  transition:
    border-color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .splash__triage-card:hover {
    border-color: var(--accent);
    background: var(--accent-glow);
    transform: translateY(-1px);
  }
  .splash__triage-card:hover .splash__triage-count {
    color: var(--accent);
  }
}

.splash__triage-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.splash__triage-head {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  line-height: 1;
}

.splash__triage-count {
  font-family: var(--mono);
  font-size: 17px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.01em;
  transition: color var(--duration-fast) var(--ease-out-expo);
}

.splash__triage-divider {
  color: var(--text-4);
  font-weight: 400;
}

.splash__triage-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
}

.splash__triage-preview {
  font-size: 13px;
  color: var(--text-2);
  line-height: 1.35;
  /* Truncate to a single line with ellipsis. The preview is meant to be
     a quick glance, not a wall of text. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Quiet variant: count = 0. Border softens, count/label dim to text-4,
   and the preview text reads as "All clear" in muted italic. Still
   clickable (user can navigate to the empty view) but visually recedes
   so populated cards carry the eye. */
.splash__triage-card--quiet {
  border-color: color-mix(in srgb, var(--line), transparent 40%);
}

.splash__triage-card--quiet .splash__triage-count,
.splash__triage-card--quiet .splash__triage-label {
  color: var(--text-4);
}

.splash__triage-card--quiet .splash__triage-preview {
  color: var(--text-4);
  font-style: italic;
}

/* Mobile: cards stack vertically. Single column means the count/label/
   preview hierarchy still reads but the row doesn't fight for width. */
@media (max-width: 640px) {
  .splash__triage {
    grid-template-columns: 1fr;
    gap: 8px;
    margin-bottom: 24px;
  }
}

/* ── Sections ── */

.splash__sections {
  display: flex;
  flex-direction: column;
  gap: 36px;
}

.splash__section {
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo) both;
}

.splash__section-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.splash__section-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0;
}

.splash__section-count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-4);
}

/* ── Doc Rows ── */

.splash__rows {
  display: grid;
  /* Fixed trailing tracks (not `auto`) so every section's grid resolves to the
     same column widths — otherwise each section sizes its columns to its own
     content and "Open comments" won't line up with "Recently published". */
  grid-template-columns: minmax(0, 1fr) 200px 64px 150px;
  gap: 0 24px;
}

.splash__row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
  padding: 10px 12px;
  margin: 0 -12px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  text-align: left;
  font-family: var(--font);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-expo);
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo) both;
}

@media (hover: hover) {
  .splash__row:hover {
    background: var(--accent-glow);
    transform: translateX(4px);
  }

  .splash__row:hover .splash__row-title {
    color: var(--accent);
  }
}

.splash__row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.splash__row-title {
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.splash__row-breadcrumb {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-4);
  white-space: nowrap;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.splash__row-time {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-4);
  white-space: nowrap;
  min-width: 56px;
  text-align: right;
}

.splash__row-author {
  /* The author cell is a <UserIdentity> (avatar + name). The row aligns cells
     on the text baseline, but an avatar has no baseline, so it sat visibly off.
     Lay the cell out as a centered inline-flex and center the whole cell in the
     row (align-self) so the avatar + name line up with the row's text. */
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  align-self: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-3);
  white-space: nowrap;
  min-width: 0;
  overflow: hidden;
}

/* ── Narrow viewport: wordmark scale-down ── */

@media (max-width: 1024px) {
  .splash__brand-text {
    font-size: 64px;
  }
}

/* ── Mobile: wordmark scale + card-stack list rows ──
   Each row becomes a small card: title on its own line, metadata
   (breadcrumb · time · author) reflows beneath as an inline-wrap
   block. The tabular grid is dissolved at this width because the
   four-column layout cannot fit without truncation. */

@media (max-width: 640px) {
  .splash {
    padding: 5vh 16px 32px;
    gap: 28px;
  }

  .splash__brand-text {
    font-size: 48px;
  }

  .splash__rows {
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .splash__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 8px;
    padding: 14px 12px;
    margin: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--line);
  }

  .splash__row:last-child {
    border-bottom: none;
  }

  .splash__row-title {
    flex-basis: 100%;
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
    white-space: normal;
  }

  .splash__row-breadcrumb,
  .splash__row-time,
  .splash__row-author {
    font-size: 12px;
    color: var(--text-2);
    min-width: 0;
    text-align: left;
    white-space: normal;
  }
}

/* ── Loading State ── */

.splash__loading {
  display: flex;
  flex-direction: column;
  gap: 14px;
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.splash__loading-bar {
  height: 12px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
}

/* ── Empty State ── */

.splash__empty {
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo) 0.1s both;
}

.splash__empty-text {
  font-size: 14px;
  color: var(--text-3);
  line-height: 1.7;
  margin: 0;
}

/* ── Footer ── */

.splash__footer {
  display: flex;
  flex-direction: column;
  gap: 16px;
  animation: fadeUp var(--duration-entrance-content) var(--ease-out-expo) 0.3s both;
}

.splash__search-cta {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  background: transparent;
  color: var(--text-3);
  font-family: var(--font);
  font-size: 13px;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), background var(--duration-fast) var(--ease-out-expo);
}

.splash__search-cta:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-glow);
}

.splash__search-cta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.splash__search-cta svg {
  flex-shrink: 0;
  opacity: 0.5;
}

.splash__search-cta .splash__kbd {
  margin-left: auto;
}

.splash__shortcuts {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 0 4px;
}

.splash__shortcut {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-4);
}

.splash__kbd {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  background: var(--paper-3);
  border: 1px solid var(--line);
  padding: 2px 6px;
  border-radius: var(--rounded-sm);
}

/* ── Doc View ──
   No animation on .doc-view itself — it owns the static review-mode
   translateX (see margin.css). An animation here that touches `transform`
   would override the static value during its runtime, then snap back at
   animation-end, producing a left-jutter on doc switch.
   The fade-up entrance is owned by the children (.doc-header and
   .editor-wrapper), each of which animates independently with the same
   duration and curve. */
/* ── Notifications ── */

.notif-wrap {
  position: relative;
}

.notif-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 14px;
  height: 14px;
  padding: 0 4px;
  border-radius: var(--rounded-md);
  background: var(--err);
  color: var(--on-accent);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  line-height: 14px;
  text-align: center;
  pointer-events: none;
  transform-origin: center;
  animation: badgePop 240ms var(--ease-out-quart);
}

@keyframes badgePop {
  from { opacity: 0; transform: scale(0.85); }
  to   { opacity: 1; transform: scale(1); }
}

/* ── Dropdown ── */

.notif-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: min(360px, calc(100vw - 16px));
  max-height: 440px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-lg);
  z-index: 200;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: fadeUp var(--duration-entrance) var(--ease-out-expo);
  transform-origin: top right;
}

.notif-dropdown__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.notif-dropdown__title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.notif-dropdown__mark-read {
  border: none;
  background: none;
  padding: 0;
  font-size: 11px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  transition: opacity var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .notif-dropdown__mark-read:hover {
    opacity: 0.75;
  }
}

.notif-dropdown__empty {
  padding: 28px 20px;
  font-size: 12px;
  color: var(--text-2);
  text-align: center;
  line-height: 1.5;
}

.notif-dropdown__error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 24px 20px;
}

.notif-dropdown__error-text {
  font-size: 12px;
  color: var(--err);
  text-align: center;
}

.notif-dropdown__retry {
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--line);
  padding: 5px 12px;
  border-radius: var(--rounded-sm);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out-expo), background var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .notif-dropdown__retry:hover {
    border-color: var(--accent);
    background: var(--accent-glow);
  }
}

.notif-dropdown__retry:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── List ── */

.notif-dropdown__list {
  flex: 1;
  overflow-y: auto;
  /* Reset the browser <ul> defaults — without this each <li> renders a disc
     bullet (a stray dot left of the card) and the list is indented 40px. Only
     visible once there's at least one notification. */
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ── Item ── */

.notif-item {
  display: flex;
  gap: 0;
  width: 100%;
  padding: 10px 14px;
  border: none;
  border-bottom: 1px solid var(--line);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.notif-item:last-child {
  border-bottom: none;
}

.notif-item--unread {
  background: var(--accent-soft);
}

@media (hover: hover) {
  .notif-item:hover {
    background: var(--paper-2);
  }

  .notif-item--unread:hover {
    background: var(--accent-glow);
  }
}

/* ── Dot column ── */

.notif-item__dot-col {
  width: 14px;
  flex-shrink: 0;
  padding-top: 5px;
}

.notif-item__dot {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* ── Content ── */

.notif-item__content {
  flex: 1;
  min-width: 0;
}

.notif-item__top {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 3px;
}

.notif-item__actor {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

.notif-item__time {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  margin-left: auto;
  flex-shrink: 0;
}

.notif-item__preview {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.notif-item__doc {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  margin-top: 3px;
}

/* Title truncates; the repo badge keeps its full width instead of being
   crushed by a long doc path (the two collided before — no gap, no flex). */
.notif-item__doc-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.notif-item__doc .repo-badge {
  flex-shrink: 0;
}

/* ── Type badge ── */

.notif__type {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  padding: 2px 5px;
  border-radius: var(--rounded-sm);
  flex-shrink: 0;
}

.notif__type--mention {
  color: var(--accent);
  background: var(--accent-glow);
}

.notif__type--comment {
  color: var(--text-3);
  background: var(--badge-bg-mute);
}

.notif__type--decision {
  color: var(--warn);
  background: var(--badge-bg-warn);
}

.notif__type--status {
  color: var(--ok);
  background: var(--badge-bg-ok);
}

/* ── Focus ── */

.notif-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.notif-dropdown__mark-read:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* ── Settings Modal ── */

.settings-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}

.settings-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}

[data-theme='dark'] .settings-backdrop {
  background: rgba(0, 0, 0, 0.6);
}

.settings-modal {
  position: relative;
  display: flex;
  width: 680px;
  max-width: calc(100vw - 48px);
  height: 500px;
  max-height: calc(100vh - 96px);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-xl);
  /* Drop tail (was `, 0 0 0 1px var(--line)`) removed — the 1px border above
     already draws the outline; doubling it read as a hard halo in dark mode. */
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  overflow: hidden;
  animation: fadeUp var(--duration-entrance) var(--ease-out-expo);
}

/* ── Modal Sidebar ── */

.settings-modal__sidebar {
  width: 180px;
  flex-shrink: 0;
  background: var(--paper-2);
  border-right: 1px solid var(--line);
  padding: 20px 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.settings-modal__title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 0 16px 12px;
}

.settings-modal__nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 0 8px;
}

.settings-modal__nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 8px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  text-align: left;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .settings-modal__nav-item:hover {
    background: var(--paper-3);
  }
}

.settings-modal__nav-item--active {
  background: var(--accent-glow);
  color: var(--accent);
}

@media (hover: hover) {
  .settings-modal__nav-item--active:hover {
    background: var(--accent-glow);
  }
}

.settings-modal__nav-item svg {
  flex-shrink: 0;
  opacity: 0.6;
}

.settings-modal__nav-item--active svg {
  opacity: 1;
}

/* ── Content Area ── */

.settings-modal__content {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.settings-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 24px 16px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.settings-modal__heading {
  font-size: 17px;
  font-weight: 600;
  color: var(--text);
  margin: 0;
}

.settings-modal__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .settings-modal__close:hover {
    background: var(--paper-3);
    color: var(--text);
  }
}

.settings-modal__close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.settings-modal__body {
  flex: 1;
  overflow-y: auto;
  padding: 16px 24px 24px;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

/* ── Settings Groups ── */

.settings-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.settings-group__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
  margin: 0 0 8px;
}

.settings-group__items {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  overflow: hidden;
}

/* ── Settings Row ── */

.settings-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 14px;
  background: var(--paper);
}

.settings-row + .settings-row {
  border-top: 1px solid var(--line);
}

.settings-row__text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.settings-row__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}

.settings-row__desc {
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.4;
}

.settings-row__control {
  flex-shrink: 0;
}

/* ── Toggle Switch ── */

.settings-toggle {
  position: relative;
  width: 36px;
  height: 20px;
  border: none;
  border-radius: var(--rounded-xl);
  background: var(--paper-3);
  cursor: pointer;
  padding: 0;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.settings-toggle--on {
  background: var(--accent);
}

.settings-toggle__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.settings-toggle--on .settings-toggle__thumb {
  transform: translateX(16px);
}

.settings-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Button ── */

.settings-btn {
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  background: var(--paper);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .settings-btn:hover {
    background: var(--paper-3);
    border-color: var(--line-2);
  }
}

.settings-btn--sync {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
  border-color: var(--accent);
}

@media (hover: hover) {
  .settings-btn--sync:hover {
    background: var(--accent-glow);
  }
}

.settings-btn--syncing {
  opacity: 0.7;
  cursor: wait;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.settings-btn--syncing svg {
  animation: spin 800ms linear infinite;
}

.settings-sync {
  display: flex;
  align-items: center;
  gap: 10px;
}

.settings-sync__time {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
  white-space: nowrap;
}

.settings-btn--danger {
  color: var(--err);
  border-color: var(--err);
}

@media (hover: hover) {
  .settings-btn--danger:hover {
    background: rgba(229, 62, 62, 0.08);
  }
}

/* ── Badge ── */

.settings-badge {
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: var(--rounded-sm);
}

.settings-badge--ok {
  color: var(--ok);
  background: var(--badge-bg-ok);
}

/* ── About ── */

.settings-about__version {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
  padding: 3px 8px;
  border-radius: var(--rounded-sm);
  background: var(--badge-bg-mute);
}

/* ── Account Profile ── */

.settings-account {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  background: var(--paper);
}

.settings-account__avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.settings-account__info {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.settings-account__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.settings-account__email {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
}

.settings-account + .settings-row {
  border-top: 1px solid var(--line);
}

/* ── Directories ── */

.settings-dirs__intro {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.settings-dirs__repo {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
}

.settings-dirs__repo svg {
  opacity: 0.5;
  flex-shrink: 0;
}

.settings-dirs__repo-name {
  font-weight: 600;
  color: var(--text);
}

.settings-dirs__repo-owner {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-4);
}

.settings-dirs__hint {
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.5;
  margin: 0;
}

.settings-dirs__summary {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
}

.settings-dirs__count {
  font-weight: 600;
  color: var(--accent);
}

.settings-dirs__tree {
  display: flex;
  flex-direction: column;
}

/* ── Dir Row ── */

.settings-dir {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .settings-dir:hover {
    background: var(--paper-2);
  }
}

.settings-dir__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: none;
  background: transparent;
  color: var(--text-4);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  border-radius: var(--rounded-sm);
  transition: color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .settings-dir__toggle:hover {
    color: var(--text-2);
  }
}

.settings-dir__toggle-spacer {
  width: 16px;
  flex-shrink: 0;
}

.settings-dir__chevron--open {
  transform: rotate(90deg);
}

.settings-dir__toggle svg {
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

/* ── Checkbox ── */

.settings-dir__check {
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo);
}

.settings-dir__check--checked {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.settings-dir__check--indeterminate {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  opacity: 0.7;
}

.settings-dir__check:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Folder icon ── */

.settings-dir__icon {
  flex-shrink: 0;
  color: var(--text-4);
}

/* ── Label ── */

.settings-dir__label {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  flex: 1;
}

.settings-dir__label--muted {
  color: var(--text-4);
}

/* ── Focus ── */

.settings-modal__nav-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ── Directories skeleton ── */

@keyframes settings-shimmer {
  0% { opacity: 0.4; }
  50% { opacity: 0.12; }
  100% { opacity: 0.4; }
}

.settings-dirs__skeleton {
  padding: 16px 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.settings-dirs__skeleton-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
}

.settings-dirs__skeleton-check {
  width: 16px;
  height: 16px;
  border-radius: var(--rounded-sm);
  background: var(--text);
  opacity: 0.1;
  animation: settings-shimmer 1.6s ease-in-out infinite;
  flex-shrink: 0;
}

.settings-dirs__skeleton-icon {
  width: 14px;
  height: 14px;
  border-radius: var(--rounded-sm);
  background: var(--text);
  opacity: 0.08;
  animation: settings-shimmer 1.6s ease-in-out infinite;
  animation-delay: 0.1s;
  flex-shrink: 0;
}

.settings-dirs__skeleton-line {
  height: 10px;
  border-radius: var(--rounded-sm);
  background: var(--text);
  opacity: 0.1;
  animation: settings-shimmer 1.6s ease-in-out infinite;
  animation-delay: 0.2s;
}

.settings-dirs__skeleton-row:nth-child(2) * { animation-delay: 0.12s; }
.settings-dirs__skeleton-row:nth-child(3) * { animation-delay: 0.24s; }
.settings-dirs__skeleton-row:nth-child(4) * { animation-delay: 0.36s; }
.settings-dirs__skeleton-row:nth-child(5) * { animation-delay: 0.48s; }

/* ── Directories empty state ── */

.settings-dirs__empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 48px 24px;
  text-align: center;
  color: var(--text-4);
}

.settings-dirs__empty-state svg {
  opacity: 0.3;
}

.settings-dirs__empty-state > span:first-of-type {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-3);
}

.settings-dirs__empty-hint {
  font-size: 12px;
  color: var(--text-4);
  max-width: 240px;
}

/* Segmented control (theme mode picker) */

/* Mirrors the footer .mode-toggle visual + animation pattern. paper-2 track
   with chip-shadow ring (no literal border — the shadow's hairline does
   that job, matching the footer chip bar). An absolute indicator pill
   slides via transform between active segments. Track + pill both use
   --rounded-pill (9999px); large radii self-cap at half the height so the
   container reads as a true capsule and the pill inside follows the same
   curvature. */
.settings-segmented {
  display: inline-flex;
  gap: 4px;
  padding: 2px;
  background: var(--paper-2);
  border-radius: var(--rounded-pill);
  border: none;
  box-shadow: var(--chip-shadow);
  position: relative;
}

.settings-segmented__indicator {
  position: absolute;
  top: 2px;
  left: 0;
  bottom: 2px;
  width: 80px;
  transform-origin: left center;
  border-radius: var(--rounded-pill);
  background: var(--surface-content);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
  transition: transform var(--duration-entrance) var(--ease-out-expo);
  pointer-events: none;
  will-change: transform;
}

/* Dark mode: indicator needs to be LIGHTER than its surrounding track to
   read as elevated. paper-3 sits above paper-2 in dark; same trick used
   by .mode-toggle__indicator and .context-panel__tab-indicator. Drop is
   lighter than the prior 40% (per the chip-shadow refinement). */
[data-theme='dark'] .settings-segmented__indicator {
  background: var(--paper-3);
  box-shadow:
    0 1px 4px rgba(0, 0, 0, 0.20),
    inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.settings-segmented__btn {
  border: none;
  background: transparent;
  color: var(--text-3);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  padding: 4px 12px;
  border-radius: var(--rounded-pill);
  cursor: pointer;
  position: relative;
  z-index: 1;
  transition: color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .settings-segmented__btn:hover {
    color: var(--text-2);
  }
}

.settings-segmented__btn--active {
  color: var(--accent);
  font-weight: 600;
}

/* ── Palette picker ──
   A dropdown of palette cards. The trigger shows the current palette as
   a card chip; the panel reveals a 3×3 grid of cards (one per palette).
   Cards show paper + surface tones as a tiny diorama with the accent as
   a dot, giving the user a real-ish preview of the surface depth range
   each palette produces. */

.palette-picker {
  position: relative;
  display: inline-block;
}

/* Trigger — a chip-style button that reads as one row in the settings UI.
   Same height/proportions as the other settings controls (toggles,
   selects) so the row stays balanced. */
.palette-picker__trigger {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px 6px 6px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-1);
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .palette-picker__trigger:hover {
    background: var(--paper-2);
    border-color: var(--line-2);
  }
}

.palette-picker__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.palette-picker__trigger--open {
  border-color: var(--accent);
  background: var(--accent-glow);
}

@media (hover: hover) {
  .palette-picker__trigger--open:hover {
    background: var(--accent-glow);
  }
}

.palette-picker__trigger-swatch {
  /* Mirror the .palette-card__swatch structure: flex column so the paper
     stripe + surface block stack with proper heights. Without flex, the
     surface child collapsed to 0px and the absolute-positioned accent dot
     got clipped at the swatch's bottom-right corner. */
  position: relative;
  display: inline-flex;
  flex-direction: column;
  width: 32px;
  height: 22px;
  border-radius: var(--rounded-sm);
  box-shadow: 0 0 0 1px var(--line);
  overflow: hidden;
}

/* Trigger swatch is ~2× smaller than the dropdown card swatch (32×22 vs
   64×38), so the accent dot needs to scale down proportionally. The
   default 9×9 dot read as oversized + too far from the bottom-right
   corner. 5×5 at bottom-3 right-3 mirrors the dropdown card's visual
   weight (14% of swatch width, anchored tight to the corner). */
.palette-picker__trigger-swatch .palette-card__accent {
  width: 5px;
  height: 5px;
  bottom: 3px;
  right: 3px;
}

.palette-picker__trigger-label {
  letter-spacing: 0.01em;
}

.palette-picker__chevron {
  display: inline-flex;
  color: var(--text-3);
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.palette-picker__chevron--open {
  transform: rotate(180deg);
}

/* Panel — portaled to <body>, positioned by JS (top/left set via inline
   style). Fixed positioning escapes the settings modal's overflow:hidden
   clip. Width is fixed so the 9 cards land in a clean 3×3 regardless of
   trigger width. Lifted on a multi-layer shadow stack so it reads as
   floating above the modal content beneath. */
.palette-picker__panel {
  position: fixed;
  /* Must beat .settings-overlay (z-index: 9999) since the panel portals
     to <body> while the modal lives in the same z-stack context. */
  z-index: 10001;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  width: 280px;
  padding: 10px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  /* Same border-softening treatment applied to the settings modal: the
     inset 1px ring duplicated the 1px border above, reading as a hard
     halo in dark mode. The 1px border alone is enough; the soft drop
     shadow does the elevation. */
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 12px 32px rgba(0, 0, 0, 0.16);
}

/* Each card in the panel is a button — focusable, selectable, with its
   own active state. Sizing keeps the swatch dominant (~64×38) so the
   palette reads at a glance before the label even registers. */
.palette-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 6px 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .palette-card:hover {
    background: var(--paper-2);
  }
}

.palette-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.palette-card--active {
  border-color: var(--accent);
  background: var(--accent-glow);
}

@media (hover: hover) {
  .palette-card--active:hover {
    background: var(--accent-glow);
  }
}

/* Swatch — paper stripe on top (15% of height) over the surface block
   (85%) with the accent dot pinned bottom-right of the surface. Reads
   as a tiny page on a desk. */
.palette-card__swatch {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 64px;
  height: 38px;
  border-radius: var(--rounded-sm);
  box-shadow: 0 0 0 1px var(--line);
  overflow: hidden;
}

.palette-card__paper {
  display: block;
  height: 6px;
  flex-shrink: 0;
}

.palette-card__surface {
  position: relative;
  flex: 1;
  display: block;
}

.palette-card__accent {
  position: absolute;
  bottom: 5px;
  right: 5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.55);
}

.palette-card__label {
  font-family: var(--font);
  font-size: 10.5px;
  font-weight: 500;
  color: var(--text-2);
  letter-spacing: 0.01em;
  line-height: 1.2;
  text-align: center;
}

.palette-card--active .palette-card__label {
  color: var(--accent);
  font-weight: 600;
}

/* Mobile — give the panel more breathing room and bigger cards. */
@media (pointer: coarse) {
  .palette-picker__panel {
    width: 320px;
    padding: 12px;
    gap: 8px;
  }
  .palette-card {
    padding: 10px 8px 8px;
  }
  .palette-card__swatch {
    width: 72px;
    height: 44px;
  }
}


/* Inline text input (display name) */

.settings-input {
  width: 200px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  background: var(--paper);
  color: var(--text);
  font-family: var(--font);
  font-size: 13px;
  padding: 6px 10px;
  transition: border-color var(--duration-fast) var(--ease-out-expo);
}

.settings-input:focus {
  outline: none;
  border-color: var(--accent);
}

.settings-input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Confirm modal (GitHub disconnect) */

.settings-confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.settings-confirm-modal {
  background: var(--surface-content);
  border-radius: var(--rounded-lg);
  padding: 24px;
  max-width: 360px;
  width: calc(100% - 32px);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}

.settings-confirm-modal h3 {
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 8px;
  color: var(--text);
}

.settings-confirm-modal p {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-2);
  margin: 0 0 20px;
}

.settings-confirm-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* ── Mobile: fullscreen sheet + horizontal nav tabs ── */

@media (max-width: 640px) {
  .settings-modal {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    max-height: 100vh;
    border-radius: 0;
    border: none;
    flex-direction: column;
  }

  .settings-modal__sidebar {
    width: 100%;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--line);
    padding: 12px 8px 0;
    flex-direction: column;
  }

  .settings-modal__title {
    padding: 0 12px 8px;
  }

  .settings-modal__nav {
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 0 8px 8px;
    gap: 4px;
    scrollbar-width: none;
    mask-image: linear-gradient(to right, black calc(100% - 24px), transparent);
    -webkit-mask-image: linear-gradient(to right, black calc(100% - 24px), transparent);
  }

  .settings-modal__nav::-webkit-scrollbar {
    display: none;
  }

  .settings-modal__nav-item {
    flex-shrink: 0;
    padding: 8px 12px;
    min-height: 44px;
  }

  .settings-modal__content {
    flex: 1;
    overflow-y: auto;
  }

  .settings-modal__header {
    padding: 14px 16px 12px;
  }

  .settings-modal__body {
    padding: 12px 16px 24px;
  }
}

/* Touch-device close button: scale to a real 44px target regardless of width. */

@media (pointer: coarse) {
  .settings-modal__close {
    width: 44px;
    height: 44px;
  }

  .settings-dir__toggle {
    width: 32px;
    height: 32px;
  }

  .settings-dir__check {
    width: 32px;
    height: 32px;
  }

  .settings-btn,
  .settings-btn--sync,
  .settings-btn--danger {
    min-height: 40px;
  }

  .settings-segmented__btn {
    min-height: 40px;
  }
}
/* src/app/styles/auth-gate.css
 * The unified auth gate (/welcome): one state-driven surface for the two
 * required steps — Sign in with Zoho, then Connect GitHub. Themed through the
 * app's semantic tokens; light/dark follow useTheme (system pre-auth). Shares
 * the login card's visual language (warm paper, accent period, corner theme
 * toggle, staggered entrance); the steps render as a numbered ledger where the
 * active row IS the call to action.
 */

.authgate {
  position: relative;
  min-height: 100vh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-color: var(--page-bg);
  color: var(--text);
  font-family: var(--font, "Zoho Puvi", system-ui, sans-serif);
}

/* Atmosphere: a single soft accent bloom behind the card. */
.authgate::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(720px, 120vw);
  height: min(720px, 120vw);
  transform: translate(-50%, -58%);
  background: radial-gradient(circle at center, var(--accent-glow) 0%, transparent 62%);
  pointer-events: none;
  z-index: 0;
}

/* ── Theme toggle ── */
.authgate__theme {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-pill);
  background: var(--paper);
  color: var(--text-2);
  cursor: pointer;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}
.authgate__theme:hover {
  color: var(--accent);
  border-color: var(--accent);
  transform: rotate(-18deg);
}
.authgate__theme:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Stage + card ── */
.authgate__stage {
  position: relative;
  z-index: 1;
  width: 100%;
  display: flex;
  justify-content: center;
  padding: 24px;
}

.authgate__card {
  width: 100%;
  max-width: 440px;
  padding: clamp(1.75rem, 1.25rem + 2.5vw, 2.75rem);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-xl);
  box-shadow: var(--shadow-lg);
  animation: authgate-card-in var(--duration-entrance) var(--ease-out-expo) both;
}

@keyframes authgate-card-in {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

.authgate__wordmark,
.authgate__steps,
.authgate__notice,
.authgate__footer,
.authgate__switch {
  animation: authgate-rise var(--duration-entrance-content) var(--ease-out-expo) both;
}
.authgate__wordmark { animation-delay: 90ms; }
.authgate__steps    { animation-delay: 170ms; }
.authgate__notice   { animation-delay: 240ms; }
.authgate__footer   { animation-delay: 300ms; }
.authgate__switch   { animation-delay: 340ms; }

@keyframes authgate-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ── Wordmark ── */
.authgate__wordmark {
  margin: 0 0 clamp(1.5rem, 1rem + 2vw, 2.25rem);
  text-align: center;
  font-weight: 700;
  font-size: clamp(2.25rem, 1.75rem + 3vw, 3rem);
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--text);
}
.authgate__dot {
  color: var(--accent);
  text-shadow: 0 0 22px var(--accent-glow-strong);
}

/* ── Step ledger ── */
.authgate__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.authgate__step {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 14px;
  border: 1px solid transparent;
  border-radius: var(--rounded-lg);
  text-align: left;
  font-family: inherit;
}

/* Active row — the call to action. */
button.authgate__step--active {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent), transparent 70%);
  color: var(--text);
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-out-expo),
    box-shadow var(--duration-normal) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}
button.authgate__step--active:hover {
  border-color: var(--accent);
  box-shadow: 0 6px 20px var(--accent-glow-strong);
  transform: translateY(-1px);
}
button.authgate__step--active:active {
  transform: translateY(0);
  box-shadow: none;
}
button.authgate__step--active:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Completed row — quiet, settled. */
.authgate__step--done {
  padding-top: 8px;
  padding-bottom: 8px;
}

/* Locked row — ghosted, waiting. */
.authgate__step--locked {
  opacity: 0.62;
}

/* ── Badges ── */
.authgate__badge {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--rounded-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.authgate__badge--done {
  background: var(--ok);
  color: #fff;
}
.authgate__badge--locked {
  background: transparent;
  color: var(--text-3);
  border: 1px solid var(--line-2);
}

/* ── Row body ── */
.authgate__step-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.authgate__step-label {
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
}
.authgate__step--locked .authgate__step-label {
  color: var(--text-3);
}
.authgate__step-sub {
  font-size: 12px;
  color: var(--text-3);
}
.authgate__step-meta {
  font-size: 12px;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.authgate__arrow {
  color: var(--accent);
  transition: transform var(--duration-fast) var(--ease-out-expo);
}
button.authgate__step--active:hover .authgate__arrow {
  transform: translateX(3px);
}

/* ── Notice ── */
.authgate__notice {
  margin: 14px 0 0;
  padding: 9px 12px;
  border-radius: var(--rounded-md);
  font-size: 13px;
  line-height: 1.45;
}
.authgate__notice--info {
  background: var(--accent-soft);
  color: var(--accent-dim);
}
.authgate__notice--error {
  background: var(--badge-bg-err);
  color: var(--err);
}

/* ── Footer ── */
.authgate__footer {
  margin: clamp(1.25rem, 1rem + 1vw, 1.75rem) 0 0;
  text-align: center;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.authgate__switch {
  display: block;
  margin: 14px auto 0;
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 12px;
  color: var(--text-3);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out-expo);
}
.authgate__switch:hover {
  color: var(--text-2);
  text-decoration: underline;
  text-underline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .authgate__card,
  .authgate__wordmark,
  .authgate__steps,
  .authgate__notice,
  .authgate__footer,
  .authgate__switch { animation: none; }
  .authgate__theme:hover { transform: none; }
  button.authgate__step--active:hover { transform: none; }
}
.drafts-view {
  max-width: 860px;
  margin: 0 auto;
  padding: 8vh 24px 48px;
  display: flex;
  flex-direction: column;
  gap: 32px;
  /* Page-level entrance handled by motion.div (see DocumentViewerPage). */
}

.drafts-view__clear-confirm {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.drafts-view__clear {
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  background: transparent;
  border: 1px solid var(--line);
  padding: 4px 10px;
  border-radius: var(--rounded-sm);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out-quart),
              color var(--duration-fast) var(--ease-out-quart);
}

@media (hover: hover) {
  .drafts-view__clear:hover {
    border-color: var(--line-2);
    color: var(--text);
  }
}

.drafts-view__clear:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.drafts-view__clear--danger {
  color: var(--warn);
  border-color: color-mix(in oklch, var(--warn), transparent 55%);
}

@media (hover: hover) {
  .drafts-view__clear--danger:hover {
    color: var(--warn);
    border-color: var(--warn);
    background: color-mix(in oklch, transparent, var(--warn) 8%);
  }
}

.drafts-view__empty {
  padding: 24px 0;
}

.drafts-view__empty-text {
  font-size: 14px;
  color: var(--text-2);
  margin: 0;
  line-height: 1.55;
}

.drafts-view__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.draft-row {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) minmax(0, 220px) minmax(0, 80px);
  align-items: baseline;
  gap: 0 16px;
  width: 100%;
  padding: 12px;
  margin: 0 -12px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  text-align: left;
  font-family: var(--font);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-quart);
}

/* When repo labels are shown they get their own column, inserted between the
   title and the breadcrumb (see DraftsView, gated on >1 connected repo). */
.draft-row--with-repo {
  grid-template-columns: 14px minmax(0, 1fr) minmax(0, 150px) minmax(0, 190px) minmax(0, 80px);
}

@media (hover: hover) {
  .draft-row:hover {
    background: var(--accent-glow);
  }
}

.draft-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.draft-row__status {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--warn);
  justify-self: center;
}

.draft-row__title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.draft-row__repo {
  display: flex;
  align-items: baseline;
  min-width: 0;
  overflow: hidden;
}

.draft-row__breadcrumb {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.draft-row__time {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  text-align: right;
}

/* ── Mobile: card-stack list rows ──
   Each row becomes a small card: status dot + title stack on top,
   metadata (breadcrumb · time) reflows beneath as an inline-wrap
   block. The tabular grid is dissolved at this width because the
   four-column layout cannot fit without truncation. */

@media (max-width: 640px) {
  .drafts-view {
    padding: 5vh 16px 32px;
    gap: 24px;
  }

  .draft-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 8px;
    padding: 14px 12px;
    margin: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--line);
    min-height: 64px;
  }

  .draft-row:last-child {
    border-bottom: none;
  }

  .draft-row__status {
    align-self: center;
    justify-self: start;
    flex: 0 0 auto;
  }

  .draft-row__title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .draft-row__repo {
    flex: 0 0 auto;
  }

  .draft-row__breadcrumb,
  .draft-row__time {
    font-size: 12px;
    color: var(--text-2);
    min-width: 0;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .draft-row__breadcrumb {
    flex: 1 1 auto;
  }

  .draft-row__time {
    flex: 0 0 auto;
  }
}

/* Row wrapper hosts the open-button and its discard action as siblings
   (the row itself is a button — nesting a second button is invalid). */
.draft-row-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.draft-row-wrap .draft-row {
  flex: 1 1 auto;
  /* Reserve room so the timestamp never sits under the action control. */
  padding-right: 44px;
}

.draft-row__discard,
.draft-row__confirm {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Persistent last-column action (not hover-revealed) — a quiet trashcan at
   rest that warms on hover. */
.draft-row__discard {
  padding: 6px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition:
    color var(--duration-fast) var(--ease-out-quart),
    background var(--duration-fast) var(--ease-out-quart);
}

.draft-row__discard:hover,
.draft-row__discard:focus-visible {
  color: var(--warn);
  background: color-mix(in oklch, transparent, var(--warn) 12%);
  outline: none;
}

.draft-row__confirm-btn {
  padding: 5px;
  border: 1px solid var(--paper-3);
  border-radius: var(--rounded-sm);
  background: var(--paper-2);
  cursor: pointer;
  display: inline-flex;
}

.draft-row__confirm-yes {
  color: var(--warn);
  border-color: color-mix(in oklch, var(--warn), transparent 55%);
}

.draft-row__confirm-cancel {
  color: var(--text-2);
}
.commit-popover {
  position: absolute;
  bottom: calc(100% + 6px);
  right: 0;
  width: 320px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-lg);
  z-index: 800;
  animation: commitPopIn 150ms var(--ease-out-expo) forwards;
}

@keyframes commitPopIn {
  from {
    opacity: 0;
    transform: translateY(4px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.commit-popover__head {
  padding: 10px 14px 8px;
  border-bottom: 1px solid var(--line);
}

.commit-popover__header {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.commit-popover__path {
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  direction: rtl;
  text-align: left;
}

.commit-popover__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 10.5px;
}

.commit-popover__summary-stat {
  color: var(--text-2);
}

.commit-popover__summary-stat[data-changes="true"] {
  color: var(--text);
}

.commit-popover__summary-toggle {
  font-family: var(--font);
  font-size: 10.5px;
  background: transparent;
  border: none;
  padding: 0;
  color: var(--accent);
  cursor: pointer;
}

.commit-popover__summary-toggle:hover {
  text-decoration: underline;
}

.commit-popover__diff-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-normal) var(--ease-out-quart);
}

.commit-popover__diff-wrap--open {
  grid-template-rows: 1fr;
}

.commit-popover__diff-wrap > .commit-popover__diff {
  min-height: 0;
  overflow-y: auto;
  border-bottom: 0;
  transition: border-bottom-color var(--duration-fast) var(--ease-out-quart);
}

.commit-popover__diff-wrap--open > .commit-popover__diff {
  border-bottom: 1px solid var(--line);
}

.commit-popover__diff {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0 14px;
  max-height: 180px;
  background: var(--paper-2);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  /* Render markdown literally — disable ligatures and contextual alternates
     so character sequences like *** or != aren't substituted with stylized
     glyphs. Same reason code editors disable them by default. */
  font-feature-settings: "liga" 0, "calt" 0;
  font-variant-ligatures: none;
}

.commit-popover__diff-wrap--open > .commit-popover__diff {
  padding: 6px 14px;
}

.commit-popover__diff-line {
  white-space: pre-wrap;
  word-break: break-word;
}

.commit-popover__diff-line--added {
  color: var(--ok);
}

.commit-popover__diff-line--removed {
  color: var(--err);
}

.commit-popover__diff-line--equal {
  color: var(--text-3);
}

.commit-popover__input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 8px 14px;
  margin: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
  resize: none;
}

.commit-popover__input::placeholder {
  color: var(--text-4);
}

.commit-popover__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 8px 10px;
  border-top: 1px solid var(--line);
}

.commit-popover__actions {
  display: flex;
  gap: 6px;
  align-items: center;
}

.commit-popover__shortcut {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: 8px;
  padding-left: 8px;
  border-left: 1px solid rgba(255, 255, 255, 0.18);
}

.commit-popover__kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.18);
  color: var(--on-accent);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  line-height: 1;
}

.commit-popover__btn--commit:disabled .commit-popover__shortcut {
  opacity: 0.6;
}

.commit-popover__btn {
  font-family: var(--font);
  font-size: 11px;
  font-weight: 500;
  padding: 4px 10px;
  border-radius: var(--rounded-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo);
}

.commit-popover__btn--cancel {
  background: transparent;
  color: var(--text-3);
}

.commit-popover__btn--cancel:hover {
  background: var(--paper-3);
  color: var(--text-2);
}

.commit-popover__btn--commit {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent-dim);
}

.commit-popover__btn--commit:hover:not(:disabled) {
  background: var(--accent-dim);
}

.commit-popover__btn--commit:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ── Discard confirm popover ──
   Reuses the .commit-popover shell (card border/shadow/width) but NOT its
   __head/__footer sections — those carry border dividers that read as noise
   on a short confirm dialog, so the discard layout is border-free. */
.discard-popover__title {
  padding: 12px 14px 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.discard-popover__body {
  margin: 0;
  padding: 8px 14px 4px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-2);
}

.discard-popover__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 6px 10px 10px;
}

/* Give Cancel a real border so it reads as a button, not a bare text label
   sitting next to the bordered Discard button. Scoped to the discard popover
   so CommitPopover's ghost Cancel is unchanged. */
.discard-popover .commit-popover__btn--cancel {
  border-color: var(--line);
}

.discard-popover .commit-popover__btn--cancel:hover {
  border-color: var(--text-3);
}

.discard-popover__confirm {
  background: var(--warn);
  color: var(--on-accent);
  border-color: color-mix(in oklch, var(--warn), black 12%);
}

.discard-popover__confirm:hover {
  background: color-mix(in oklch, var(--warn), black 10%);
}
/* ── Margin Layout Variables ── */

:root {
  /* Width of the annotation rail. Sized to its glyph cluster (at most one
     glyph per signal type, so three side by side) rather than to the expanded
     labels — those now float free of it. Must stay under the editor's 72px
     right gutter, which is the space it occupies.

     The sum, because stating the conclusion without it misled twice — once at
     18px glyphs, and once by forgetting the row's padding:

       4px .margin-row padding-left + 3 x 11px glyph + 2 x 3px gap = 43px

     i.e. the cluster's right edge lands 1px inside this 44px. Change any term
     and the geometry spec in e2e/margin-rail.authed.spec.ts fails, which is
     the point of it. */
  --margin-width: 44px;
  /* How far the rail is inset from the editor's right edge, and how far it
     slides from when it enters. With a 44px rail inside a 72px gutter there is
     28px of slack to spend; 12px here leaves 16px between the rail and the
     text, which reads as separation without pushing the rail off the edge. */
  --margin-gap: 12px;
  /* Whether the margin lane can be displayed at this width: 1 or 0, flipped by
     the same breakpoint that hides the column below. CSS owns this because CSS
     owns the breakpoint; duplicating it on the behaviour side is how the two
     drift apart. How WIDE the lane is stays derived from --margin-width and
     --margin-gap on the JS side, deliberately — a `calc()` here would come back
     from getPropertyValue as the literal string "calc(200px + 24px)", since an
     unregistered custom property computes to its token stream rather than a
     length. See useContextMode. */
  --margin-lane-available: 1;
  --margin-breakpoint: 1240px;
}

@media (max-width: 1024px) {
  .app-shell {
    --margin-lane-available: 0;
  }
}

.editor-review-wrapper {
  position: relative;
}

/* The document does NOT move for review mode.

   It used to slide left by half the lane's footprint, and the reading column
   gave up that footprint via a max-width clamp, so that the lane had somewhere
   to go. That cost a great deal: the shift could push text past the scroller's
   left edge, the clamp re-wrapped the document on every frame of any width
   change, and reserving the space could collapse the context panel to a rail.

   The lane now sits inside the editor's own right gutter — 72px of padding that
   text never enters — so nothing has to be given up and the document stays
   centred in its parent at every width, in both modes. */

/* ── Margin Column ── */

.margin-column {
  position: absolute;
  top: 0;
  /* Sits INSIDE the editor's right gutter rather than beyond the document's
     edge, which is what lets it appear without moving anything. --margin-width
     is the gutter inset; the rail itself is only as wide as its glyphs. */
  right: var(--margin-gap);
  width: var(--margin-width);
  /* Paints above the editor. The column is the wrapper's FIRST child and
     .tiptap is position: relative, so with z-index auto the two paint in DOM
     order and the editor's surface covers the rail — which swallowed the rows'
     hover entirely. It never mattered while the rail sat outside the editor's
     box; overlapping its gutter is what made the stacking load-bearing. */
  z-index: 5;
  /* Resting position: off the editor's right edge, so it slides in from
     off-screen rather than fading in place. */
  transform: translateX(calc(100% + var(--margin-gap)));
  /* Stays none even when active — see the --active rule. */
  pointer-events: none;
  opacity: 0;
  transition:
    transform var(--duration-normal) var(--ease-out-quart),
    opacity var(--duration-normal) var(--ease-out-quart);
}

/* When review mode is active, the column slides into its visible position.
   Note it does NOT take pointer events even then: the rail is a 44px strip
   running the full height of the document, and it now sits inside the editor's
   own gutter — a gutter ProseMirror uses to place the caret at the end of a
   line. Claiming the whole strip would make that column of the editor dead to
   clicks. Only the rows themselves are hit targets (`.margin-row` sets
   `pointer-events: auto`), so the gutter stays live everywhere between them. */
.editor-review-wrapper--active .margin-column {
  transform: translateX(0);
  opacity: 1;
}

@media (max-width: 1024px) {
  .margin-column {
    display: none;
  }
}

/* ── Margin Row ──
   Each row is a small bordered card pinned to a block's vertical position.
   Resting state: a left arrow (::before) tinted by the row's highest-
   priority signal, then a horizontal cluster of fixed-width glyphs.
   Hovering or focusing the row opens a floating card naming each signal in
   full. That card is portaled out of the rail rather than grown inside it: the
   rail is only as wide as its glyphs now, so anything expanding in place would
   be boxed in by it. At most one glyph per signal type, so a row is three
   glyphs wide at the very most. */

.margin-row {
  position: absolute;
  top: 0;
  left: 0;
  /* Hug the content: collapsed the tab wraps just the glyph cluster (no
     trailing dead space); expanded it grows to fit the labels. Width varies
     with signal count — uniformity is traded for a tight, intentional tag. */
  right: auto;
  width: fit-content;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  /* 4px on the left, not 6px: the cluster has to end inside the 44px rail, and
     with three glyphs every pixel of left padding comes straight off the glyph
     budget. */
  padding: 4px 8px 4px 4px;
  border-radius: 0 6px 6px 0;
  background: var(--paper-3);
  color: var(--text-3);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1;
  pointer-events: auto;
  cursor: default;
  /* No `overflow: hidden` here — the left arrow pointer is positioned at
     `right: 100%` (outside the box) and was getting clipped. The
     labels-wrap child handles its own clipping for the collapse. */
  transform-origin: left center;
  transition:
    transform var(--duration-normal) var(--ease-out-quart),
    background var(--duration-fast) var(--ease-out-expo),
    opacity 280ms var(--ease-out-quart);
}

@starting-style {
  /* Entrance applies only to rows flagged --enter (a block's first
     appearance). Rows that remount during the editor's two-pass initial
     settle mount without --enter, so they skip the entrance — no flicker. */
  .margin-row--enter {
    opacity: 0;
    transform: translateX(4px);
  }
}

.margin-row:hover {
  background: var(--paper-2);
}

/* ── Expanded state ──
   Only the trigger's own tint changes; the labels live in a floating card
   (see .margin-popover) so the rail never has to widen to hold them. */
.margin-row--expanded {
  background: var(--paper-2);
  z-index: 10;
}

.margin-popover .margin-row__label {
  /* Long labels wrap to a second line rather than ellipsis-clipping: the card
     is free-floating, so there is room. */
  white-space: normal;
  text-overflow: clip;
}

@media (prefers-reduced-motion: reduce) {
  .margin-row { transition: none; }
}

/* ── Editorial reference mark ──
   Replaces the generic triangle pointer with a print-typography device:
   a small filled dot pinned at the gutter side of the row, connected
   to the row's left edge by a thin hairline. Same vocabulary as a
   sidebar reference mark in editorial typography. Refined, on-brand
   for a doc-review tool, and the two-element split lets us animate
   the dot and the line independently. */

.margin-row::before {
  /* Connector line — sits in the gutter between block and row. */
  content: '';
  position: absolute;
  right: 100%;
  /* 4px row padding-top + 5.5px (half of the 11px glyph row) = 9.5px from row
     top to glyph centerline. Subtract half the 1px line height = 9. Visually
     centered against the glyph row. */
  top: 9px;
  width: 10px;
  height: 1px;
  background: var(--text-4);
  transform-origin: right center;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
  animation: marginConnectorIn 240ms var(--ease-out-quart) 60ms backwards;
}

.margin-row::after {
  /* Reference dot — pinned at the far (gutter) end of the connector. */
  content: '';
  position: absolute;
  /* connector ends at right: 100% + 10px; the dot sits at the same
     anchor with center aligned to the line — 9.5px glyph centerline less half
     the dot's 5px height. */
  right: calc(100% + 8px);
  top: 7px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-3);
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo),
    box-shadow var(--duration-fast) var(--ease-out-expo);
  animation: marginDotIn 240ms var(--ease-out-quart) backwards;
  transform-origin: center;
}

/* Tint both connector and dot by the row's priority. Decision/comment take the
   accent; a mention takes the violet mention hue, so a row addressed to you
   reads as yours from the connector alone. */
.margin-row--decision::before,
.margin-row--comment::before {
  background: color-mix(in srgb, var(--accent) 45%, transparent);
}
.margin-row--decision::after,
.margin-row--comment::after {
  background: var(--accent);
}
.margin-row--mention::before {
  background: color-mix(in srgb, var(--signal-mention) 45%, transparent);
}
.margin-row--mention::after {
  background: var(--signal-mention);
}

/* Refined hover: dot lifts subtly (small scale up + a soft glow ring),
   line stays steady so the connector feels anchored. */
.margin-row:hover::after,
.margin-row--expanded::after {
  transform: scale(1.15);
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 12%, transparent);
}

@keyframes marginConnectorIn {
  from { transform: scaleX(0); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}

@keyframes marginDotIn {
  from { transform: scale(0); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .margin-row::before,
  .margin-row::after { animation: none; transition: none; }
}

/* Resting glyph cluster. Fixed-width tokens stack horizontally without
   competing for variable text room. */
.margin-row__glyphs {
  display: inline-flex;
  align-items: center;
  /* 3px: three 11px glyphs plus two gaps is 39px, and the row's 4px left
     padding puts the cluster's right edge 1px inside the 44px rail (measured,
     not computed — 832 -> 871 against a rail ending at 872). A 4px gap spends
     that pixel and 2px it does not have. */
  gap: 3px;
}

/* ── Floating annotation card ──
   Portaled to <body> with fixed coordinates (see MarginRowPopover), so it is
   free of the rail's width entirely — the rail is 44px and the card is not.
   Same shell recipe as the app's other popovers. */
.margin-popover {
  position: fixed;
  z-index: 800;
  width: max-content;
  max-width: min(280px, calc(100vw - 16px));
  padding: 6px;
  background: var(--paper-2);
  border-radius: 8px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1;
  box-shadow:
    0 10px 24px -8px rgba(0, 0, 0, 0.18),
    0 2px 4px -1px rgba(0, 0, 0, 0.08);
  animation: popoverPop 150ms var(--ease-out-expo);
}

[data-theme='dark'] .margin-popover {
  box-shadow:
    0 12px 28px -10px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.06);
}

.margin-popover__labels {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 240px;
  overflow-y: auto;
}

@media (prefers-reduced-motion: reduce) {
  .margin-popover { animation: none; }
}

/* The clickable line item: a reset button laid out as two lanes — the text
   hugs the left, a trailing arrow pins to the right. Because every label
   stretches to the row width, the arrows form a single right-hand lane across
   stacked rows. (Glyphs are non-interactive; these rows carry the action.) */
.margin-row__label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  border: 0;
  margin: 0;
  padding: 2px 6px;
  background: transparent;
  border-radius: 5px;
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

/* Hover tint is hue-derived (currentColor = the row's signal color), so a
   comment row glows accent, a decision row amber, etc. */
.margin-row__label:hover {
  background: color-mix(in srgb, currentColor 12%, transparent);
}

.margin-row__label:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 1px;
}

.margin-row__label-text {
  /* No truncation — the expanded card grows to fit (see
     .margin-row--expanded width: max-content), so labels show in full. */
  white-space: nowrap;
}

/* Trailing affordance arrow. Inherits the row's text color so it reads as part
   of the label, and nudges right on hover/focus to signal "go to thread". */
.margin-row__label-arrow {
  flex-shrink: 0;
  transition: transform var(--duration-fast) var(--ease-out-quart);
}

.margin-row__label:hover .margin-row__label-arrow,
.margin-row__label:focus-visible .margin-row__label-arrow {
  transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
  .margin-row__label-arrow { transition: none; }
}

/* Per-signal accent hues. Comment follows the active palette accent; decision
   and mention carry fixed signature colors so the three signal types stay
   distinguishable across every palette. Tuned for legibility on both the light
   paper ground and the warm-dark surface. Each glyph and its row text share the
   same hue. Defined on :root (not scoped to .margin-row) because non-row
   surfaces — the Mentions tab, for one — consume these tokens too. */
:root {
  --signal-decision: oklch(56% 0.13 66);   /* amber — judgment */
  --signal-mention: oklch(52% 0.16 300);   /* violet — @mention */
}
[data-theme='dark'] {
  --signal-decision: oklch(79% 0.11 78);
  --signal-mention: oklch(76% 0.12 300);
}

.margin-row__label--comment  { color: var(--accent); }
.margin-row__label--decision { color: var(--signal-decision); font-weight: 600; }
.margin-row__label--mention  { color: var(--signal-mention); }

/* ── Glyphs ──
   Every glyph is the same 18×18 circle regardless of type. Different
   contents (initials, count, icon) sit centered inside the same
   footprint so a row of mixed signals reads as a row of tokens, not
   a row of mismatched chips. Buttons (comment + decision) carry
   pointer-events; attribution is informational and stays a plain span
   with `cursor: default`. */

.margin-glyph {
  /* Grid centering is more reliable than inline-flex for tiny mono text
     in a tight circular container: flexbox's `align-items: center`
     centers the LINE BOX, which leaves the baseline-anchored glyphs
     sitting visually low because mono fonts have asymmetric ascender/
     descender metrics. `place-items: center` aligns the actual rendered
     content centroid. */
  display: inline-grid;
  place-items: center;
  position: relative;
  /* 11px, not 18px. Three glyphs is the row's ceiling, and at 18px the cluster
     was 62px inside a 44px rail — the row ran 20px past the editor's own right
     edge. 11px with a 3px gap puts the cluster at 39px, which clears the rail's
     inner edge with room to spare (see the geometry spec in
     e2e/margin-rail.authed.spec.ts, which pins it). */
  width: 11px;
  height: 11px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--paper-2);
  color: var(--text-2);
  /* Hue-matched ring so the circle edge reads on BOTH grounds. currentColor
     resolves to the variant's signal color (accent / amber / violet / muted),
     so the ring is the glyph's own hue at ~32% — darker than the light paper,
     lighter than the dark surface — instead of the near-white / invisible
     faint-fill edge it had before. Applies to every variant. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 32%, transparent);
  font-family: var(--mono);
  /* line-height: 1 collapses the line-box to the actual glyph cap-height
     so the grid centering sees a tight bounding box, not a tall line. */
  line-height: 1;
  cursor: default;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo),
    transform var(--duration-fast) var(--ease-out-expo);
}

.margin-glyph svg {
  display: block;
}

/* Glyphs are non-interactive indicators — no cursor/hover affordance (the
   expanded line items carry the click). Each just tints its circle by hue. */
.margin-glyph--comment {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}

.margin-glyph--decision {
  background: color-mix(in srgb, var(--signal-decision) 16%, transparent);
  color: var(--signal-decision);
}

.margin-glyph--mention {
  background: color-mix(in srgb, var(--signal-mention) 16%, transparent);
  color: var(--signal-mention);
}

.margin-glyph--resolved {
  background: var(--paper-2);
  color: var(--text-4);
}

.margin-glyph:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Hover Connection: pulse inline mark ── */

.bn-thread-mark--margin-hover {
  background: var(--accent-glow) !important;
  transition: background 200ms var(--ease-out-expo);
  animation: markHoverPulse 460ms var(--ease-out-quart);
}

@keyframes markHoverPulse {
  0%   { box-shadow: 0 0 0 0 transparent; }
  35%  { box-shadow: 0 0 0 4px var(--accent-glow); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ── Review Mode Toggle Button ── */

.editor-toolbar__btn--review {
  position: relative;
}
/* ── Theme preview page ──
   Documentation surface, not application chrome. Uses the same tokens
   it documents — so any palette switch updates everything live. */

.theme-preview {
  min-height: 100vh;
  padding: 32px clamp(16px, 4vw, 48px) 96px;
  background: var(--page-bg);
  color: var(--text);
  font-family: var(--font);
}

.theme-preview__header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.theme-preview__title {
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 4px;
  color: var(--text);
}

.theme-preview__subtitle {
  font-size: 13px;
  color: var(--text-2);
  margin: 0;
}

.theme-preview__subtitle code {
  font-family: var(--mono);
  font-size: 12px;
  padding: 1px 5px;
  background: var(--paper-3);
  border-radius: var(--rounded-sm);
}

.theme-preview__controls {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

.theme-preview__control-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.theme-preview__control-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
}

.theme-preview__seg {
  display: inline-flex;
  background: var(--paper-3);
  border-radius: var(--rounded-md);
  padding: 2px;
  gap: 2px;
}

.theme-preview__seg-btn {
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 500;
  font-family: var(--font);
  color: var(--text-2);
  background: transparent;
  border: none;
  border-radius: calc(var(--rounded-md) - 2px);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.theme-preview__seg-btn:hover {
  color: var(--text);
}

.theme-preview__seg-btn--active {
  background: var(--paper);
  color: var(--accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}

/* ── Sections ── */

.theme-preview__main {
  display: flex;
  flex-direction: column;
  gap: 40px;
  margin-top: 32px;
}

.theme-preview__section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.theme-preview__section-header {
  display: flex;
  align-items: center;
  gap: 12px;
}

.theme-preview__section-title {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  color: var(--text);
}

.theme-preview__ring {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: var(--rounded-pill);
  white-space: nowrap;
}

.theme-preview__ring--palette-strong {
  background: var(--accent-glow);
  color: var(--accent);
}

.theme-preview__ring--palette-stable {
  background: var(--paper-3);
  color: var(--text-2);
}

.theme-preview__ring--palette-immune {
  background: var(--badge-bg-mute);
  color: var(--text-3);
}

.theme-preview__section-intent {
  font-size: 12.5px;
  color: var(--text-2);
  margin: 0 0 8px;
  max-width: 72ch;
  line-height: 1.5;
}

/* ── Context figure ──
   A cropped screenshot of the real app UI placed above each group's swatches,
   showing where the tokens actually appear in production chrome. Captioned
   so the reader understands what they're looking at without having to map
   geography from memory. */

.theme-preview__context {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 12px;
  padding: 12px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  max-width: 720px;
}

.theme-preview__context-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--rounded-sm);
  /* The crops were captured at 2x device pixel ratio; subtle outline keeps
     them visually grounded against the card background. */
  box-shadow: 0 0 0 1px var(--line-2);
}

.theme-preview__context-caption {
  font-size: 11.5px;
  color: var(--text-2);
  line-height: 1.45;
  margin: 0;
}

/* ── Grid + cards ── */

.theme-preview__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}

.theme-preview__card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
}

.theme-preview__sample {
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  overflow: hidden;
  position: relative;
}

.theme-preview__token {
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text);
  word-break: break-all;
}

.theme-preview__desc {
  font-size: 11px;
  color: var(--text-2);
  line-height: 1.45;
}

/* ── Sample variants ── */

.theme-preview__surface {
  width: 80%;
  height: 38px;
  border-radius: var(--rounded-sm);
  box-shadow: 0 0 0 1px var(--line);
}

.theme-preview__text {
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
}

.theme-preview__border {
  width: 80%;
  height: 38px;
  border-radius: var(--rounded-sm);
  border: 2px solid var(--line);
  background: var(--paper);
}

.theme-preview__accent-stack {
  display: flex;
  align-items: center;
  gap: 12px;
}

.theme-preview__accent-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
}

.theme-preview__accent-text {
  font-family: var(--font);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.theme-preview__feedback-dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
}

.theme-preview__badge {
  width: 56px;
  height: 22px;
  border-radius: var(--rounded-pill);
}

.theme-preview__ambient {
  width: 80%;
  height: 38px;
  border-radius: 50%;
  filter: blur(2px);
}

.theme-preview__shadow {
  width: 70%;
  height: 38px;
  background: var(--paper);
  border-radius: var(--rounded-sm);
}

.theme-preview__glass {
  width: 80%;
  height: 38px;
  border-radius: var(--rounded-sm);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

.theme-preview__code {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
  background: transparent;
  padding: 0;
}

/* ── Responsive ── */

@media (max-width: 640px) {
  .theme-preview {
    padding: 20px 16px 64px;
  }
  .theme-preview__header {
    flex-direction: column;
    align-items: flex-start;
  }
  .theme-preview__title {
    font-size: 22px;
  }
  .theme-preview__grid {
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 10px;
  }
}
.decisions-view {
  /* Fills the widened triage container (.app-shell__content-inner--wide). */
  margin: 0 auto;
  padding: 8vh 0 48px;
  display: flex;
  flex-direction: column;
  gap: 32px;
  /* Page-level entrance is handled by motion.div in DocumentViewerPage.
     Adding a CSS animation here would compose with motion's transform
     and produce a compound (non-linear) slide. */
}

.decisions-view__skeleton {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.decisions-view__skeleton-row {
  height: 18px;
  display: flex;
  align-items: center;
}

.decisions-view__skeleton-bar {
  height: 10px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

.decisions-view__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.decisions-view__group-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.decisions-view__group-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
  margin: 0;
}

.decisions-view__group-count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
}

.decisions-view__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Fixed tracks (not minmax) so the repo-tag / time lanes align down the list. */
.decisions-row {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr) 150px 150px 80px;
  gap: 0 24px;
  align-items: baseline;
  /* +24px width against the -12px side margins so the hover highlight bleeds
     symmetrically (width:100% here would shift the whole box 12px left). */
  width: calc(100% + 24px);
  padding: 12px 12px;
  margin: 0 -12px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  text-align: left;
  font-family: var(--font);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .decisions-row:hover {
    background: var(--accent-glow);
  }
}

.decisions-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.decisions-row__status {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 3px 7px;
  border-radius: var(--rounded-sm);
  justify-self: start;
}

.decisions-row__status--open {
  color: var(--warn);
  background: var(--badge-bg-warn);
}

.decisions-row__status--deferred {
  color: var(--text-2);
  background: var(--badge-bg-mute);
}

.decisions-row__status--approved {
  color: var(--ok);
  background: var(--badge-bg-ok);
}

.decisions-row__status--rejected {
  color: var(--err);
  background: var(--badge-bg-err);
}

.decisions-row__preview {
  font-size: 14px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.decisions-row__doc {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.decisions-row__repo {
  display: flex;
  align-items: center;
  align-self: center;
  min-width: 0;
}

.decisions-row__time {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  text-align: right;
}

.decisions-view__empty {
  font-size: 14px;
  color: var(--text-3);
  line-height: 1.55;
  margin: 0;
}

/* ── Mobile: card-stack list rows ──
   Each row becomes a small card: status pill + preview title stack on
   top, metadata (doc · time) reflows beneath as an inline-wrap block.
   The tabular grid is dissolved at this width because the four-column
   layout cannot fit without truncation. */

@media (max-width: 640px) {
  .decisions-view {
    padding: 6vh 16px 32px;
    gap: 24px;
  }

  .decisions-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 8px;
    padding: 14px 12px;
    margin: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--line);
    min-height: 64px;
  }

  .decisions-row:last-child {
    border-bottom: none;
  }

  .decisions-row__status {
    align-self: center;
    justify-self: start;
    flex: 0 0 auto;
  }

  .decisions-row__preview {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.4;
  }

  .decisions-row__doc,
  .decisions-row__repo,
  .decisions-row__time {
    font-size: 12px;
    color: var(--text-2);
    min-width: 0;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .decisions-row__doc {
    flex: 1 1 auto;
  }

  .decisions-row__repo,
  .decisions-row__time {
    flex: 0 0 auto;
  }
}
.mentions-view {
  /* Fills the widened triage container (.app-shell__content-inner--wide). */
  margin: 0 auto;
  padding: 8vh 0 48px;
  display: flex;
  flex-direction: column;
  gap: 32px;
  /* Page-level entrance handled by motion.div (see DocumentViewerPage). */
}

.mentions-view__skeleton {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.mentions-view__skeleton-bar {
  height: 12px;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  animation: skeletonPulse 1.6s var(--ease-out-quart) infinite;
}

.mentions-view__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.mentions-view__group-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.mentions-view__group-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
  margin: 0;
}

.mentions-view__group-count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
}

.mentions-view__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Fixed tracks (not minmax) so every row resolves the same column widths and
   the repo-tag / time lanes line up down the list. */
.mention-row {
  display: grid;
  grid-template-columns: 16px 120px minmax(0, 1fr) 150px 150px 64px;
  align-items: baseline;
  gap: 0 16px;
  /* +24px width against the -12px side margins so the hover highlight bleeds
     symmetrically (width:100% here would shift the whole box 12px left). */
  width: calc(100% + 24px);
  padding: 12px;
  margin: 0 -12px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  text-align: left;
  font-family: var(--font);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-quart);
}

@media (hover: hover) {
  .mention-row:hover {
    background: var(--accent-glow);
  }
}

.mention-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Lead column — unread dot or (once read) a check. Centered so the icon sits
   on the row's optical midline rather than the text baseline. */
.mention-row__lead {
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
}

.mention-row__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

.mention-row__check {
  color: var(--text-3);
}

.mention-row__actor {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mention-row--read .mention-row__actor {
  font-weight: 500;
  color: var(--text-2);
}

.mention-row__preview {
  font-size: 14px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mention-row--read .mention-row__preview {
  color: var(--text-2);
}

.mention-row__doc {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mention-row__repo {
  display: flex;
  align-items: center;
  align-self: center;
  min-width: 0;
}

.mention-row__time {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  text-align: right;
}

/* ── Mobile: card-stack list rows ──
   Each row becomes a small card: actor/preview stack on top, metadata
   (doc · repo · time) reflows beneath as an inline-wrap block. The tabular
   grid is dissolved at this width because the six-column layout cannot fit
   without truncation. */

@media (max-width: 640px) {
  .mentions-view {
    padding: 5vh 16px 32px;
    gap: 24px;
  }

  .mention-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 8px;
    padding: 14px 12px;
    margin: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--line);
    min-height: 64px;
  }

  .mention-row:last-child {
    border-bottom: none;
  }

  .mention-row__lead {
    align-self: center;
    flex: 0 0 auto;
  }

  .mention-row__actor {
    font-size: 14px;
    font-weight: 500;
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .mention-row--read .mention-row__actor {
    color: var(--text-2);
  }

  .mention-row__preview {
    flex-basis: 100%;
    font-size: 14px;
    font-weight: 500;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.4;
  }

  .mention-row--read .mention-row__preview {
    color: var(--text-2);
  }

  .mention-row__doc,
  .mention-row__repo,
  .mention-row__time {
    font-size: 12px;
    color: var(--text-2);
    min-width: 0;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .mention-row__doc {
    flex: 1 1 auto;
  }

  .mention-row__repo,
  .mention-row__time {
    flex: 0 0 auto;
  }
}

/* ── Mentions tab (context panel) ── */

.mentions-tab {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
}

.mentions-tab--empty {
  padding: 24px 16px;
}

.mentions-tab__empty-text {
  margin: 0;
  font-size: 13px;
  color: var(--text-3);
}

.mentions-tab__row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 10px;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: var(--rounded-md);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.mentions-tab__row:hover {
  background: var(--paper-2);
}

/* Unread rows carry a faint mention-hued wash so "which of these haven't I
   read" reads at a glance — subtle enough to sit behind the unread dot
   rather than compete with it. On hover the wash is mixed with --paper-2,
   not replaced by it, so hovering an unread row doesn't erase the very
   signal that means "still unread". */
.mentions-tab__row--unread {
  background: color-mix(in srgb, var(--signal-mention) 7%, transparent);
}

.mentions-tab__row--unread:hover {
  background: color-mix(in srgb, var(--signal-mention) 10%, var(--paper-2));
}

.mentions-tab__row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.mentions-tab__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border-radius: var(--rounded-pill);
  background: color-mix(in srgb, var(--signal-mention) 16%, transparent);
  color: var(--signal-mention);
}

.mentions-tab__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.mentions-tab__actor {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.mentions-tab__preview {
  font-size: 12px;
  line-height: 17px;
  color: var(--text-2);
  overflow-wrap: anywhere;
}

.mentions-tab__time {
  font-size: 11px;
  color: var(--text-4);
}

/* Unread dot sits in its own fixed-width lane so the actor names across rows
   stay on one vertical line whether or not a row is unread. */
.mentions-tab__unread {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  margin-top: 8px;
  border-radius: var(--rounded-pill);
  background: var(--signal-mention);
}
/* ── Workspace — team task board over GitHub Issues ──────────────────────
   Lives inside .app-shell__content. Calm lanes on the desk, floating paper
   cards that stay flat at rest and lift on hover (matching the system's
   state-only shadow rule). All colour comes from tokens, so light/dark and
   the alternate palettes are handled automatically. */

.ws-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100%;
  /* The board has no context panel — span into that empty grid track so the
     lanes use the full width instead of leaving dead space on the right. */
  grid-column: 2 / -1;
}

/* ── Header ──────────────────────────────────────────────────────────── */
.ws-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

/* In list view the sticky glass accordion header provides the separation, so the
   page header's own hairline reads as a redundant double line ("gap") — drop it. */
.ws-main--list .ws-header {
  border-bottom: none;
}

.ws-header__titles {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  margin-right: auto;
  min-width: 0;
}

.ws-header__title {
  margin: 0;
  font-family: var(--font);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}

.ws-header__sub {
  font-size: 0.75rem;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.ws-header__icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart),
    border-color var(--duration-fast) var(--ease-out-quart);
}

.ws-header__icon-btn:hover {
  background: var(--paper-3);
  color: var(--text);
}

.ws-header__icon-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ── Board filter bar ────────────────────────────────────────────────── */
/* A calm horizontal strip between the page header and the board scroll
   region. Uses the same glass surface as the list accordion headers so it
   reads as part of the same design vocabulary. The bar slides in via a
   max-height + opacity transition whenever it mounts (board view only). */

.ws-filterbar {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.4375rem 1.25rem;
  border-bottom: 1px solid var(--line);
  background: color-mix(in oklch, var(--surface-glass), transparent 20%);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-highlight);
  flex-shrink: 0;
  /* Slide-in entrance when the bar mounts (board view first shown) */
  animation: ws-filterbar-in var(--duration-normal, 200ms) var(--ease-out-quart, ease-out)
    backwards;
}

@keyframes ws-filterbar-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* When any filter is active, add a very subtle left accent rail. */
.ws-filterbar--active {
  box-shadow:
    var(--glass-highlight),
    inset 3px 0 0 color-mix(in oklch, var(--accent), transparent 60%);
}

/* "Filter" label — icon + word; muted so it doesn't compete with the controls */
.ws-filterbar__label {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  flex-shrink: 0;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-4);
  user-select: none;
}

/* Controls row — wraps selects. Uses gap so adding/removing the Repo select
   doesn't disturb spacing. */
.ws-filterbar__controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
}

/* Each select in the filter bar: fixed compact width; does NOT stretch to
   fill (unlike the list's per-column header cells which go full-width). */
.ws-filterbar__select {
  width: 9rem;
}

/* Labels multiselect needs a touch more room for the "2 labels" summary text. */
.ws-filterbar__controls .ws-multiselect {
  width: 10rem;
}

/* Clear — plain text button; fades in when active, using accent colour. */
.ws-filterbar__clear {
  margin-left: auto;
  flex-shrink: 0;
  padding: 0.1875rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--accent);
  font-family: var(--font);
  font-size: 0.6875rem;
  font-weight: 500;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    border-color var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart);
}

.ws-filterbar__clear:hover {
  background: var(--accent-soft);
  border-color: color-mix(in oklch, var(--accent), transparent 60%);
}

.ws-filterbar__clear:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ── Reduced motion — filter bar ─────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ws-filterbar {
    animation: none;
  }
}

/* ── Narrow viewport — filter bar ────────────────────────────────────── */
@media (max-width: 640px) {
  .ws-filterbar {
    padding: 0.375rem 0.875rem;
    gap: 0.5rem;
    flex-wrap: wrap;
  }

  .ws-filterbar__label {
    display: none; /* Reclaim space on small screens */
  }

  .ws-filterbar__controls {
    flex-wrap: wrap;
    gap: 0.375rem;
  }

  .ws-filterbar__select,
  .ws-filterbar__controls .ws-multiselect {
    width: 8rem;
  }
}

/* ── Board scroll region ─────────────────────────────────────────────── */
.ws-scroll {
  position: relative; /* bounding box for the absolutely-filled list scroller */
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}

/* Lanes share the available width and each scrolls vertically on its own, so
   the board never overflows horizontally on desktop. */
.ws-board {
  display: flex;
  align-items: stretch;
  gap: 1rem;
  height: 100%;
  padding: 1.25rem;
  min-width: 0;
}

/* ── Column / lane ───────────────────────────────────────────────────── */
/* flex:1 1 0 + min-width:0 lets the five lanes share the width without
   overflowing. The lane clips to its radius; the OverlayScrollbars viewport
   inside does the scrolling. */
.ws-col {
  --ws-head-h: 2.75rem;
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-width: 0;
  max-height: 100%;
  overflow: hidden;
  background: color-mix(in oklch, var(--paper-2), transparent 35%);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
}

/* OverlayScrollbars viewport — themed to Margin's scrollbar tokens, with the
   vertical bar inset below the sticky header so it never overlaps it. */
.ws-col__os {
  flex: 1;
  min-height: 0;
  --os-handle-bg: var(--scrollbar-thumb);
  --os-handle-bg-hover: var(--scrollbar-thumb-hover);
  --os-handle-bg-active: var(--scrollbar-thumb-hover);
  --os-handle-border-radius: var(--rounded-pill);
  /* Match the sidebar doc-tree scrollbar: a thin (~4px), light thumb. The 8px
     track minus 2px perpendicular padding on each side leaves a ~4px handle. */
  --os-size: 8px;
  --os-padding-perpendicular: 2px;
}

/* Inset the vertical bar below the sticky header. OS marks a vertical-only bar
   `.os-scrollbar-cornerless` (top:0; bottom:0 at specificity 0,3,0), so this
   needs !important to win rather than a deeper selector. */
.ws-col__os .os-scrollbar-vertical {
  top: var(--ws-head-h) !important;
}

/* Liquid glass header — sticky so the cards scroll beneath it; the heavy blur
   then has real content to frost, matching the sidebar/context-panel glass. */
.ws-col__head {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  height: var(--ws-head-h);
  padding: 0 0.875rem;
  /* Own top radius: backdrop-filter paints a square region that the parent's
     border-radius does NOT clip, so without this the glass corners stick out. */
  border-radius: var(--rounded-lg) var(--rounded-lg) 0 0;
  border-bottom: 1px solid var(--line);
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-highlight);
}

.ws-col__title {
  font-family: var(--font);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
}

.ws-col__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.375rem;
  border-radius: var(--rounded-pill);
  background: var(--badge-bg-mute);
  color: var(--text-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
}

.ws-col__cards {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: 0.625rem;
}

.ws-col__empty {
  padding: 0.625rem 0.25rem;
  font-size: 0.75rem;
  color: var(--text-4);
  text-align: center;
}

/* ── Issue card ──────────────────────────────────────────────────────── */
.ws-card {
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 0.5rem;
  padding: 0.6875rem 0.75rem;
  background: var(--paper);
  border: 1px solid var(--card-border);
  border-radius: var(--rounded-md);
  text-decoration: none;
  transition: transform var(--duration-fast) var(--ease-out-quart),
    box-shadow var(--duration-fast) var(--ease-out-quart),
    border-color var(--duration-fast) var(--ease-out-quart);
  /* Staggered entrance — `backwards` holds the from-state during the delay. */
  animation: ws-rise var(--duration-entrance, 280ms) var(--ease-out-quart) backwards;
  animation-delay: calc(var(--ws-i, 0) * 22ms);
}

.ws-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--line-2);
}

/* The title is the link; its ::after stretches over the whole card so a click
   anywhere (except the grip) opens the issue. */
.ws-card__link {
  text-decoration: none;
  cursor: pointer;
}

.ws-card__link::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.ws-card:has(.ws-card__link:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Drag handle / drag + drop states ────────────────────────────────── */
/* Content column to the right of the grip rail. */
.ws-card__body {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  flex: 1 1 auto;
  min-width: 0;
}

/* Drag handle — a single rounded pill rail (per the Paper design). Two pills are
   stacked: a grey base that's always visible, and the blue gradient on top that
   fades in and gently pulses on hover, together with the grab cursor, to signal
   the card is interactive. */
.ws-card__grip {
  position: relative;
  z-index: 2; /* above the stretched link's ::after */
  align-self: stretch;
  flex-shrink: 0;
  width: 12px; /* comfortable hit area; the visible pill is narrower */
  margin-left: -2px;
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
}

.ws-card__grip::before,
.ws-card__grip::after {
  content: '';
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 50%;
  width: 5px;
  transform: translateX(-50%);
  border-radius: var(--rounded-pill);
}

/* Grey base pill — theme-safe equivalent of the design's #DDDDDD. */
.ws-card__grip::before {
  background: var(--scrollbar-thumb);
}

/* Blue gradient pill (exact values from the Paper design), faded out at rest. */
.ws-card__grip::after {
  background-image: linear-gradient(
    in oklch 180deg,
    oklch(75.4% 0.06 265.5) 0%,
    oklch(60.3% 0.06 265.5) 100%
  );
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-out-quart);
}

.ws-card__grip:hover::after,
.ws-card__grip:focus-visible::after {
  opacity: 1;
  animation: ws-grip-pulse 1.6s ease-in-out infinite;
}

/* Subtle "alive" pulse — a soft glow that breathes around the gradient pill. */
@keyframes ws-grip-pulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
  50% {
    box-shadow: 0 0 7px 0 oklch(67% 0.12 265.5 / 0.55);
  }
}

.ws-card__grip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: var(--rounded-pill);
}

.ws-card__grip:active {
  cursor: grabbing;
}

/* Source card while its overlay clone is being dragged. */
.ws-card--dragging {
  opacity: 0.4;
}

.ws-card--dragging:hover {
  transform: none;
  box-shadow: none;
}

/* The floating clone under the cursor / keyboard focus. */
.ws-card--overlay {
  cursor: grabbing;
  box-shadow: var(--shadow-lg, var(--shadow-md));
  border-color: var(--line-2);
  animation: none;
  rotate: 1.5deg;
}

/* Lane highlight when a card hovers over it as a drop target. */
.ws-col--over {
  border-color: var(--accent);
  background: color-mix(in oklch, var(--accent-soft), transparent 55%);
}

@keyframes ws-rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.ws-card__top {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ws-card__repo {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ws-card__num {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--text-4);
  flex-shrink: 0;
}

.ws-card__title {
  font-family: var(--font);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Labels ──────────────────────────────────────────────────────────── */
.ws-card__labels {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3125rem;
}

.ws-label {
  padding: 0.0625rem 0.4375rem;
  border-radius: var(--rounded-pill);
  background: var(--badge-bg-mute);
  color: var(--text-2);
  font-size: 0.6875rem;
  line-height: 1.45;
  white-space: nowrap;
}

.ws-label--status {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 500;
}

.ws-label--more {
  color: var(--text-4);
}

/* ── Assignee ────────────────────────────────────────────────────────── */
.ws-card__assignee {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
}

/* The assignee renders via the shared <UserIdentity>, whose .user-identity__name
   sets no font-size — scope it back to the compact card label size. */
.ws-card__assignee .user-identity__name {
  font-size: 0.6875rem;
  color: var(--text-3);
}

/* ── States (loading / error / empty) ───────────────────────────────── */
.ws-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 100%;
  padding: 2rem;
  text-align: center;
}

.ws-state__title {
  margin: 0;
  font-family: var(--font);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text);
}

.ws-state__sub {
  margin: 0;
  max-width: 28rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--text-3);
}

.ws-state__btn {
  margin-top: 0.5rem;
  padding: 0.4375rem 0.875rem;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-quart);
}

.ws-state__btn:hover {
  background: var(--paper-3);
}

/* ── Skeleton ────────────────────────────────────────────────────────── */
.ws-card--skel {
  pointer-events: none;
  animation: none;
}

.ws-skel {
  display: block;
  height: 0.6875rem;
  border-radius: var(--rounded-sm);
  background: linear-gradient(
    90deg,
    var(--paper-3) 0%,
    color-mix(in oklch, var(--paper-3), var(--paper) 50%) 50%,
    var(--paper-3) 100%
  );
  background-size: 200% 100%;
  animation: ws-shimmer 1.4s ease-in-out infinite;
}

.ws-skel--title {
  width: 45%;
  height: 0.8125rem;
}

.ws-skel--line {
  height: 0.625rem;
}

@keyframes ws-shimmer {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

/* ── Refresh spinner ─────────────────────────────────────────────────── */
.ws-spin {
  animation: ws-rotate 0.8s linear infinite;
}

@keyframes ws-rotate {
  to {
    transform: rotate(360deg);
  }
}

/* ── Quick-add — column footer affordance ────────────────────────────── */
/* Always mounted; class-toggled for smooth CSS transitions. Sits after the
   card list so it anchors to the column bottom. */
/* Header "+" button — always visible, right-justified in the lane header, so
   quick-add is never buried at the bottom of a long column. */
.ws-col__add {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  flex-shrink: 0;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart),
    transform var(--duration-normal) var(--ease-out-quart);
}

.ws-col__add:hover {
  background: var(--paper-3);
  color: var(--text);
}

.ws-col__add:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Open: rotate the "+" into an "×" so it reads as a cancel/close toggle. */
.ws-col__add--open {
  transform: rotate(45deg);
  color: var(--text-2);
}

/* Form — drops in directly under the sticky header, animating from height 0.
   Lives between the header and the cards, so it's always at the top in view. */
.ws-quickadd__form {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 0 0.625rem;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition:
    max-height var(--duration-normal) var(--ease-out-quart),
    padding var(--duration-normal) var(--ease-out-quart),
    opacity var(--duration-fast) var(--ease-out-quart);
}

.ws-quickadd__form--open {
  max-height: 9rem; /* generously larger than the tallest expanded state */
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
  opacity: 1;
}

/* Input row: text field + submit button side by side. */
.ws-quickadd__row {
  display: flex;
  align-items: center;
  gap: 0.3125rem;
}

.ws-quickadd__input {
  flex: 1;
  min-width: 0;
  height: 2rem;
  padding: 0 0.5rem;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.8125rem;
  outline: none;
  transition:
    border-color var(--duration-fast) var(--ease-out-quart),
    box-shadow var(--duration-fast) var(--ease-out-quart);
}

.ws-quickadd__input::placeholder {
  color: var(--text-4);
}

.ws-quickadd__input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-glow-strong);
}

.ws-quickadd__input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Submit button — compact icon-only affordance. */
.ws-quickadd__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    opacity var(--duration-fast) var(--ease-out-quart);
}

.ws-quickadd__submit:hover:not(:disabled) {
  background: var(--accent-dim);
}

.ws-quickadd__submit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.ws-quickadd__submit:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ── WsSelect — reusable single-select dropdown ──────────────────────── */
/* A generic custom dropdown used anywhere a native <select> can't style its
   open option list. Mirrors the sidebar repo switcher's popover recipe:
   (var(--paper) surface, --line border, --shadow-md, popoverPop, accent-glow
   active row) for visual consistency across the app. The popover is portaled
   to <body> with fixed coords, so it escapes overflow:hidden ancestors. */
.ws-select {
  position: relative;
}

.ws-select__trigger {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  width: 100%;
  height: 1.875rem;
  padding: 0 0.5rem 0 0.4375rem;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  color: var(--text-3);
  font-family: var(--mono);
  font-size: 0.6875rem;
  text-align: left;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart);
}

.ws-select__trigger:hover:not(:disabled),
.ws-select__trigger--open {
  border-color: var(--line-2);
  color: var(--text-2);
}

.ws-select__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.ws-select__trigger:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.ws-select__value {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ws-select__chevron {
  flex-shrink: 0;
  color: var(--text-4);
  transition: transform var(--duration-fast) var(--ease-out-quart);
}

.ws-select__chevron--open {
  transform: rotate(180deg);
}

/* Popover — same recipe as .repo-switcher__popover in the sidebar. Portaled to
   <body> with fixed coords (position/left/top/width set inline), so it escapes
   any overflow:hidden ancestor. z-index sits above app chrome. */
.ws-select__popover {
  z-index: 1000;
  padding: 4px;
  /* Clip the inner scroll list to the rounded corners so the scrollbar can't
     poke past the radius. The scroll lives on .ws-select__list inside. */
  overflow: hidden;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-md);
  animation: popoverPop 220ms var(--ease-out-expo);
  transform-origin: top center;
}

/* Inner scroll list — sits inside the popover's 4px padding, so its scrollbar
   stays clear of the rounded corners. */
.ws-select__list {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 12rem;
  overflow-y: auto;
}

/* Inset the scrollbar thumb vertically (track margin = corner radius) so it
   stops before the rounded corners begin — without displacing the options. */
.ws-select__list::-webkit-scrollbar-track {
  margin: var(--rounded-lg) 0;
}

.ws-select__option {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart);
}

.ws-select__option:hover {
  background: var(--paper-3);
  color: var(--text);
}

.ws-select__option--active,
.ws-select__option--active:hover {
  background: var(--accent-glow);
  color: var(--accent);
  font-weight: 600;
  cursor: default;
}

/* ── WsMultiSelect — multi-value variant of WsSelect ────────────────── */
/* Inherits all .ws-select* base styles. Only adds multi-select specifics:
   the circle/check glyph per option row, and active-trigger tinting. */

/* Active trigger: tint the border/text when one or more values are selected,
   so the user can tell a filter is applied without opening the menu. */
.ws-select__trigger--active {
  border-color: var(--accent);
  color: var(--text);
}

.ws-select__trigger--active:hover:not(:disabled) {
  border-color: var(--accent-dim, var(--accent));
}

/* Multi-select popover: slightly wider min-width for the glyph column. */
.ws-multiselect__popover {
  min-width: 160px;
}

/* Each option row in the multi-select: glyph + label side by side. */
.ws-multiselect__option {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

/* The circle / check glyph — fixed width so labels stay aligned. */
.ws-multiselect__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 14px;
  color: var(--text-4);
}

/* When the row is selected, the glyph picks up the accent colour. */
.ws-select__option--active .ws-multiselect__glyph {
  color: var(--accent);
}

/* Label text — grows to fill remaining width; may truncate. */
.ws-multiselect__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Inline error message — small, role=alert, accent-err. */
.ws-quickadd__error {
  margin: 0;
  padding: 0.25rem 0.25rem;
  font-size: 0.6875rem;
  color: var(--err);
  line-height: 1.4;
}

/* ── Reduced motion ──────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ws-card,
  .ws-skel,
  .ws-spin {
    animation: none;
  }
  .ws-card:hover {
    transform: none;
  }
  /* Keep the hover color, drop the pulse. */
  .ws-card__grip:hover::after,
  .ws-card__grip:focus-visible::after {
    animation: none;
  }
  .ws-quickadd__form {
    transition: opacity var(--duration-fast) var(--ease-out-quart);
  }
}

/* ── Narrow viewports ────────────────────────────────────────────────── */
@media (max-width: 640px) {
  .ws-header {
    padding: 0.625rem 0.875rem;
  }
  /* Too narrow to share five lanes — switch to fixed-width lanes that scroll
     horizontally instead. */
  .ws-scroll {
    overflow-x: auto;
  }
  .ws-board {
    padding: 0.875rem;
    gap: 0.75rem;
    width: max-content;
  }
  .ws-col {
    flex: 0 0 84vw;
  }
}

/* ── Sidebar: Workspace section "coming soon" affordance ─────────────── */
.sidebar__nav-item:disabled {
  opacity: 0.5;
  cursor: default;
}

.sidebar__nav-soon {
  margin-left: auto;
  padding: 0.0625rem 0.375rem;
  border-radius: var(--rounded-pill);
  background: var(--badge-bg-mute);
  color: var(--text-3);
  font-family: var(--font);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* ══ Daily Digest — editorial "morning read" ═══════════════════════════
   A dated standfirst over hairline-separated departments. Ink hierarchy and
   rules do the work; the accent appears only on counts, links, and unread. */
.digest {
  height: 100%;
  overflow-y: auto;
}

.digest__inner {
  max-width: 54rem;
  margin: 0 auto;
  padding: 2.5rem 2.5rem 4rem;
}

/* ── Masthead ─────────────────────────────────────────────────────────── */
.digest__masthead {
  margin-bottom: 2.5rem;
}

.digest__dateline {
  margin: 0 0 0.625rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
}

.digest__title {
  margin: 0 0 0.625rem;
  font-family: var(--font);
  font-size: 2.125rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--accent);
}

/* Standfirst — a stat line: one accent count per present section, dot-joined. */
.digest__standfirst {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem;
  font-family: var(--font);
  font-size: 1.125rem;
  line-height: 1.4;
  color: var(--text);
}

.digest__stat {
  display: inline-flex;
  align-items: baseline;
  gap: 0.375rem;
}

.digest__stat-num {
  font-family: var(--font);
  font-weight: 600;
  color: var(--accent);
}

.digest__stat-sep {
  color: var(--line-2);
}

.digest__standfirst-quiet {
  margin: 0;
  font-size: 1.125rem;
  color: var(--text-2);
}

/* ── Groups (Documents / Issues) ──────────────────────────────────────── */
.digest__group + .digest__group {
  margin-top: 2.75rem;
}

.digest__group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.75rem;
  margin-bottom: 1.25rem;
  /* Was 1.5px solid var(--text) — a black rule, heavier than anything else on
     the page and reading as imported rather than designed. The hierarchy is
     carried by the title's weight and size; the rule only has to close the
     header, so it takes the firm line token. */
  border-bottom: 1px solid var(--line-2);
}

.digest__group-title {
  margin: 0;
  font-family: var(--font);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
}

.digest__group-scope {
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  color: var(--text-3);
}

/* ── Sections (stacked within a group; no per-section rule) ───────────── */
.digest__depts {
  display: flex;
  flex-direction: column;
}

.digest__dept {
  min-width: 0;
}

.digest__dept + .digest__dept {
  /* Whitespace alone did not separate sections — with single-line rows the
     stack reads as one long list, and the kicker is small enough to be missed
     as a boundary. A quiet rule does the work the space could not. */
  margin-top: 1.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--line);
}

.digest__kicker {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}

.digest__kicker-label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.digest__kicker-count {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text);
}

.digest__viewall {
  border: none;
  background: none;
  padding: 0;
  font-family: var(--font);
  font-size: 0.75rem;
  color: var(--text-3);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out-quart);
}

.digest__viewall::after {
  content: ' →';
}

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

/* ── Entries (editorial list rows, not cards) ─────────────────────────── */
.digest__rows {
  /* The GRID lives on the list, not the row, so every row shares one set of
     lanes. Per-row flex sized each row independently and the repo badges landed
     at a different x on every line — a single-line list that does not align is
     harder to scan than the stacked rows it replaced.
     title | meta | attribution | time */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: baseline;
}

.digest__entry {
  /* subgrid is what makes the lanes SHARED; without it each row would resolve
     its own track sizes again and the alignment would be back to per-row. */
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  position: relative;
  align-items: baseline;
  column-gap: 0.875rem;
  width: calc(100% + 1.25rem);
  margin: 0 -0.625rem;
  padding: 0.4375rem 0.625rem;
  border: none;
  border-radius: var(--rounded-md);
  background: none;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-quart);
}

.digest__entry:hover {
  background: var(--accent-soft);
}

.digest__entry:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Subtle divider between rows — the adjacent-sibling selector keeps it off the
   first row (no rule above) and the last (no rule below).
   
   Drawn as a positioned pseudo-element, NOT as border-top: the row is rounded
   so the hover background can be, and a real border follows that radius —
   which curled the divider's ends inward and left a visible gap at each end.
   A pseudo-element is a straight line regardless of the corner. It is also
   inset to the row's text box so it aligns with the content rather than with
   the negative-margin hover bleed. */
.digest__entry + .digest__entry::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0.625rem;
  right: 0.625rem;
  height: 1px;
  background: var(--line);
}

.digest__entry-head {
  grid-column: 1;
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  /* min-width:0 is what lets the title truncate instead of forcing the lane
     wider than its track. */
  min-width: 0;
}

.digest__entry-title {
  flex: 1;
  min-width: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.digest__entry-time {
  /* Explicitly the LAST lane, so a row with no meta or no attribution still
     lands its time on the same right edge as every other row. */
  grid-column: 4;
  /* Fixed lane so a longer value ("113d ago") doesn't shove the repo tag left —
     keeps every row's tag on the same vertical line. */
  min-width: 3.75rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  text-align: right;
  color: var(--text-4);
}

.digest__entry-head .repo-badge {
  flex-shrink: 0;
}

/* Meta — path (docs) or "#number · tag" (issues). Inline since the row went
   single-line; allowed to shrink and truncate before the title does, and capped
   so a deep path cannot crowd out the title it belongs to. */
.digest__entry-meta {
  grid-column: 2;
  min-width: 0;
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--text-4);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Attribution — avatar + name (or Unassigned), plus an optional note. Holds its
   natural width: a name that truncates to nothing is worse than a slightly
   shorter title, and the avatar must not squash. */
.digest__entry-attr {
  grid-column: 3;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: 0.75rem;
  color: var(--text-2);
}

.digest__entry-attr .user-identity__name {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-2);
}

.digest__attr-none {
  color: var(--text-3);
}

.digest__attr-note {
  color: var(--text-4);
}

/* Hollow avatar placeholder for unassigned issues. */
.digest__avatar-empty {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: var(--rounded-pill);
  border: 1px dashed var(--line-2);
}

/* Decision preview — was a clamped two-line block below the title. Now inline,
   truncated to one line like every other row part, so the Decisions section
   matches the rest of the digest instead of being the one that wraps. */
.digest__entry-preview {
  /* Shares the attribution lane — a decision row has one or the other. */
  grid-column: 3;
  min-width: 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.digest__dot {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: var(--rounded-pill);
  background: var(--accent);
}

/* Decision status chip */
.digest__status {
  flex-shrink: 0;
  padding: 0.0625rem 0.375rem;
  border-radius: var(--rounded-pill);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.digest__status--open { background: var(--badge-bg-warn); color: var(--warn); }
.digest__status--approved { background: var(--badge-bg-ok); color: var(--ok); }
.digest__status--rejected { background: var(--badge-bg-err); color: var(--err); }
.digest__status--deferred { background: var(--badge-bg-mute); color: var(--text-3); }

.digest__empty {
  margin: 0;
  padding: 0.25rem 0;
  font-size: 0.8125rem;
  color: var(--text-4);
}

/* Show more / less — a subtle text toggle at the foot of a section. */
.digest__showmore {
  display: inline-flex;
  margin-top: 0.625rem;
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font);
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-3);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out-quart);
}

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

.digest__showmore:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--rounded-sm);
}

@media (max-width: 640px) {
  .digest__inner {
    padding: 1.5rem 1.25rem 3rem;
  }
  .digest__title {
    font-size: 1.75rem;
  }
  .digest__standfirst {
    font-size: 1rem;
  }
}

/* ── View-mode toggle (Board | List) ─────────────────────────────────── */
/* Segmented pill: two buttons share a single rounded container.
   The active segment gets a filled "track" background (--paper) with a
   subtle inset shadow so it reads as a raised selector, not a flat highlight. */
.ws-viewtoggle {
  display: inline-flex;
  align-items: center;
  padding: 2px;
  gap: 1px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-sm);
  background: var(--paper-2);
}

.ws-viewtoggle__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: calc(var(--rounded-sm) - 2px);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart),
    box-shadow var(--duration-fast) var(--ease-out-quart);
}

.ws-viewtoggle__btn:hover {
  color: var(--text-2);
}

.ws-viewtoggle__btn[aria-pressed='true'] {
  background: var(--paper);
  color: var(--text);
  box-shadow: 0 1px 2px oklch(0% 0 0 / 0.08), 0 0 0 1px var(--line);
}

.ws-viewtoggle__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ── List view — accordion ───────────────────────────────────────────── */
/* The outer scroll region is ws-scroll (defined above). The list fills it.
   OverlayScrollbarsComponent replaces the native scroll so the overlay
   scrollbar floats over content (no clip/overlap) and the sticky glass
   group headers work inside the OS viewport just like the board column
   headers. overflow-y:auto is removed — OS manages scrolling. */
/* Shell absolute-fills the (position:relative) .ws-scroll, then lays out as a
   flex column. The OverlayScrollbars host is flex:1; min-height:0 inside it —
   the SAME flex-bounded recipe as the board's .ws-col / .ws-col__os, which is
   what actually gives OS a definite height to scroll within. (Percentage height
   on the host didn't resolve, so OS grew and .ws-scroll clipped it.) */
.ws-list-shell {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}

.ws-list {
  flex: 1;
  min-height: 0;
  /* Sticky-zone heights (group header + filter row) — used for the filter-row
     sticky offset and the scrollbar inset. */
  --ws-list-head-h: 2.75rem;
  --ws-list-filter-h: 2.25rem;
  /* Column grid template — defined here (not on the panel) so BOTH the lifted
     sticky filter row and the rows inside the panel inherit it and stay aligned.
     The Repo track collapses to 0px unless multi-repo. */
  --ws-repo-col: 0px;
  --ws-list-grid:
    minmax(0, 1fr)       /* Issue */
    var(--ws-repo-col)   /* Repo  */
    150px                /* Assignee */
    190px                /* Labels */
    96px                 /* Updated */
    40px;                /* Move */
  /* OverlayScrollbars theming — mirrors .ws-col__os for visual consistency */
  --os-handle-bg: var(--scrollbar-thumb);
  --os-handle-bg-hover: var(--scrollbar-thumb-hover);
  --os-handle-bg-active: var(--scrollbar-thumb-hover);
  --os-handle-border-radius: var(--rounded-pill);
  --os-size: 8px;
  --os-padding-perpendicular: 2px;
}

/* Remove any default OS-viewport padding so the first group header sits
   flush against the ws-header above (task C). The OS viewport wrapper has
   no margin/padding of its own; this targets the inner content element. */
.ws-list .os-viewport {
  padding-top: 0 !important;
}

/* Inset the vertical scrollbar so it starts below the sticky header + filter
   row zone (mirrors the board's .ws-col__os scrollbar inset). */
.ws-list .os-scrollbar-vertical {
  top: calc(var(--ws-list-head-h) + var(--ws-list-filter-h)) !important;
}

/* Ensure the first group has no top margin/border leaking a visual gap. */
.ws-list__group:first-child {
  border-top: none;
}

/* ── Grid template ───────────────────────────────────────────────────── */
/* The grid template + --ws-repo-col live on `.ws-list` (see above) so both the
   lifted sticky filter row and the panel rows inherit them. Multi-repo expands
   the Repo track. */
.ws-list--multi-repo {
  --ws-repo-col: 120px;
}

/* ── Accordion group (one per column) ───────────────────────────────── */
/* No separators between groups — only the last group closes with a hairline. */
.ws-list__group:last-child {
  border-bottom: 1px solid var(--line);
}

/* Accordion header — full-bleed clickable row. Sticky liquid glass so the issue
   rows scroll *behind* it. After switching to OverlayScrollbars the sticky
   element resolves inside the OS viewport, exactly like .ws-col__head where
   glass works. Aligned property-for-property with .ws-col__head so the same
   backdrop-filter effect renders (z-index, background, blur, highlight). */
.ws-list__header {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  padding: 0 1.25rem;
  height: var(--ws-list-head-h);
  border: none;
  /* backdrop-filter requires a stacking context; isolation:isolate ensures
     the element forms one without fighting the parent's z-index tree. */
  isolation: isolate;
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-highlight);
  color: var(--text);
  font-family: var(--font);
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-quart);
}

.ws-list__header:hover {
  background: color-mix(in oklch, var(--surface-glass), var(--text) 4%);
}

.ws-list__header:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Open group — faint accent-tinted glass so the open state reads (the rotated,
   accent-colored chevron is the primary cue). */
.ws-list__group--open > .ws-list__header {
  background: color-mix(in oklch, var(--surface-glass), var(--accent) 8%);
  color: var(--text);
  /* Only an OPEN header gets a hairline — to separate it from its rows below. */
  border-bottom: 1px solid var(--line);
}

.ws-list__group--open > .ws-list__header:hover {
  background: color-mix(in oklch, var(--surface-glass), var(--accent) 14%);
}

/* Chevron — rotates 90° when the panel is open. */
.ws-list__chevron {
  flex-shrink: 0;
  color: var(--text-3);
  transition:
    transform var(--duration-normal) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart);
}

.ws-list__chevron--open {
  transform: rotate(90deg);
  color: var(--accent);
}

/* Column title — grows to fill space between chevron and count badge. */
.ws-list__col-title {
  flex: 1;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* ── Collapsible panel ───────────────────────────────────────────────── */
/* Always mounted; class-toggled so the CSS transition runs.
   max-height: 0 → a generous ceiling is enough — actual height is shorter.
   No per-row transform stagger: that caused the virtualiser row-overlap bug. */
.ws-list__panel {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition:
    max-height 320ms var(--ease-out-quart),
    opacity var(--duration-fast) var(--ease-out-quart);
}

.ws-list__panel--open {
  /* 9999px cap is intentional: actual panel height is always shorter.
     The transition still runs; it just never reaches the ceiling value. */
  max-height: 9999px;
  opacity: 1;
}

/* ── Filter / column-header row ──────────────────────────────────────── */
/* A single CSS grid row shared with all issue rows — guarantees that
   filter controls sit above their corresponding data column.              */
.ws-list__filter-row {
  /* Sticky just below the group header (which sticks at top:0). Glass so the
     issue rows scroll behind it. Lives outside the clipped panel (see JSX). */
  position: sticky;
  top: var(--ws-list-head-h);
  z-index: 4;
  display: grid;
  grid-template-columns: var(--ws-list-grid);
  align-items: center;
  gap: 0;
  padding: 0 1.25rem;
  height: var(--ws-list-filter-h);
  border-bottom: 1px solid var(--line);
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

/* Column headers / filter cells */
.ws-list__fh {
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 0 0.25rem;
}

/* "Issue" label — plain text, no control */
.ws-list__fh--issue {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-4);
  padding-left: 0;
}

/* Repo col — hide when single-repo (width is 0) */
.ws-list__fh--repo {
  overflow: hidden;
}

/* Assignee, Labels — hold a WsSelect / WsMultiSelect; full-width trigger. */
.ws-list__fh--assignee .ws-select,
.ws-list__fh--labels .ws-select,
.ws-list__fh--repo .ws-select {
  width: 100%;
}

/* "Updated" label — right-ish alignment, plain text */
.ws-list__fh--updated {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-4);
  justify-content: flex-end;
}

/* Move — empty spacer */
.ws-list__fh--move {
  /* intentionally empty */
}

/* ── Issue rows ──────────────────────────────────────────────────────── */
.ws-list__rows {
  display: flex;
  flex-direction: column;
}

/* Row — CSS grid div (NOT an <a>) so the Move button is a valid child.
   The left accent bar uses ::before; only the title cell contains an <a>. */
.ws-list__row {
  position: relative;
  display: grid;
  grid-template-columns: var(--ws-list-grid);
  align-items: center;
  gap: 0;
  min-height: 2.75rem;
  padding: 0.25rem 1.25rem;
  border-bottom: 1px solid var(--line);
  color: inherit;
  transition: background var(--duration-fast) var(--ease-out-quart);
}

.ws-list__row:last-child {
  border-bottom: none;
}

/* Left grip pill — mirrors the kanban card drag handle (.ws-card__grip): a grey
   base pill always present, with the blue oklch gradient fading in + pulsing on
   hover/focus. Two stacked pseudo-elements, same recipe as the card grip. */
.ws-list__row::before,
.ws-list__row::after {
  content: '';
  position: absolute;
  left: 6px;
  top: 8px;
  bottom: 8px;
  width: 5px;
  border-radius: var(--rounded-pill);
}

/* Grey base pill — theme-safe equivalent of the design's #DDDDDD. */
.ws-list__row::before {
  background: var(--scrollbar-thumb);
}

/* Blue gradient pill (same values as the card grip), faded out at rest. */
.ws-list__row::after {
  background-image: linear-gradient(
    in oklch 180deg,
    oklch(75.4% 0.06 265.5) 0%,
    oklch(60.3% 0.06 265.5) 100%
  );
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-out-quart);
}

.ws-list__row:hover {
  background: var(--paper-2);
}

.ws-list__row:hover::after,
.ws-list__row:focus-within::after {
  opacity: 1;
  animation: ws-grip-pulse 1.6s ease-in-out infinite;
}

/* ── Grid cells ──────────────────────────────────────────────────────── */
.ws-list__cell {
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 0 0.25rem;
  height: 100%;
}

.ws-list__cell--issue {
  padding-left: 0;
}

.ws-list__cell--repo {
  overflow: hidden;
}

.ws-list__cell--updated {
  justify-content: flex-end;
}

.ws-list__cell--move {
  justify-content: center;
}

/* ── Issue cell contents ─────────────────────────────────────────────── */
/* The title link: stacked block with title + secondary meta line below. */
.ws-list__title-link {
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}

.ws-list__title-link:focus-visible {
  outline: 2px solid var(--accent);
  border-radius: var(--rounded-sm);
}

/* Primary title — truncated single line. */
.ws-list__title {
  font-family: var(--font);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.35;
}

.ws-list__title-link:hover .ws-list__title {
  color: var(--accent);
}

/* Secondary meta — repo short + number beneath the title. */
.ws-list__meta {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-width: 0;
}

.ws-list__repo {
  font-family: var(--mono);
  font-size: 0.625rem;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ws-list__num {
  font-family: var(--mono);
  font-size: 0.625rem;
  color: var(--text-4);
  flex-shrink: 0;
}

/* ── Repo chip (multi-repo column) ──────────────────────────────────── */
.ws-list__repo-chip {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Assignee cell ───────────────────────────────────────────────────── */
/* Show avatar + compact name so the column has readable content. */
.ws-list__cell--assignee .user-identity {
  display: flex;
  align-items: center;
  gap: 0.3125rem;
  min-width: 0;
}

.ws-list__cell--assignee .user-identity__name {
  font-size: 0.6875rem;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Label chips — fit-or-count (task E) ─────────────────────────────── */
/* The LabelCell component measures available width via ResizeObserver and
   only renders chips that FULLY FIT. No chip is ever truncated (no
   text-overflow: ellipsis). Any chip that won't fit is counted into "+X". */
.ws-list__label-chips {
  position: relative; /* positioning context for the off-flow measurer */
  display: flex;
  flex-wrap: nowrap;
  gap: 0.25rem;
  min-width: 0;
  overflow: hidden;
  /* Cell is itself the reference width for measurement */
  width: 100%;
}

/* Off-flow measurer holding ALL chips at natural width — visibility:hidden keeps
   layout (offsetWidth readable); position:absolute keeps it out of the visible
   flow so it never shifts the real chips. */
.ws-list__label-measurer {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  gap: 0.25rem;
  white-space: nowrap;
  visibility: hidden;
  pointer-events: none;
}

/* Every chip is rigid — never shrinks, never truncates. Measurement ensures
   only whole chips appear. */
.ws-list__label-chip {
  flex: 0 0 auto;
  white-space: nowrap;
  /* NO overflow:hidden / text-overflow:ellipsis — chips are never truncated */
}

/* The "+X" overflow pill stays whole. */
.ws-list__cell--labels .ws-label--more {
  flex-shrink: 0;
}

/* ── Updated cell ────────────────────────────────────────────────────── */
.ws-list__updated {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--text-4);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── Muted placeholder dash ──────────────────────────────────────────── */
.ws-list__none {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--text-4);
}

/* Empty hint inside an expanded but filtered/empty column panel. */
.ws-list__empty {
  margin: 0;
  padding: 0.875rem 1.25rem;
  font-size: 0.75rem;
  color: var(--text-4);
}

/* ── Move button + popover ───────────────────────────────────────────── */
.ws-move {
  position: relative;
}

.ws-move__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.625rem;
  height: 1.625rem;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-4);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart),
    border-color var(--duration-fast) var(--ease-out-quart);
  /* Hide by default; appear on row hover / focus-within */
  opacity: 0;
  pointer-events: none;
}

.ws-list__row:hover .ws-move__trigger,
.ws-list__row:focus-within .ws-move__trigger {
  opacity: 1;
  pointer-events: auto;
}

.ws-move__trigger:hover,
.ws-move__trigger--open {
  background: var(--paper-3);
  color: var(--text-2);
  border-color: var(--line);
}

.ws-move__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  opacity: 1;
  pointer-events: auto;
}

/* Popover — mirrors .ws-select__popover recipe for visual consistency. */
.ws-move__popover {
  z-index: 1000;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-md);
  animation: popoverPop 220ms var(--ease-out-expo);
  transform-origin: top right;
}

.ws-move__option {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-2);
  font-family: var(--mono);
  font-size: 0.6875rem;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background var(--duration-fast) var(--ease-out-quart),
    color var(--duration-fast) var(--ease-out-quart);
}

.ws-move__option:hover {
  background: var(--paper-3);
  color: var(--text);
}

.ws-move__option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-radius: var(--rounded-md);
}

/* ── Reduced motion — list ───────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ws-list__panel {
    transition: opacity var(--duration-fast) var(--ease-out-quart);
  }
  .ws-list__chevron {
    transition: color var(--duration-fast) var(--ease-out-quart);
  }
  /* Keep the grip pill's hover color, drop the breathing pulse. */
  .ws-list__row:hover::after,
  .ws-list__row:focus-within::after {
    animation: none;
  }
  .ws-viewtoggle__btn {
    transition: background var(--duration-fast) var(--ease-out-quart);
  }
  .ws-move__trigger {
    transition: background var(--duration-fast) var(--ease-out-quart);
  }
  .ws-move__popover {
    animation: none;
  }
  /* Multi-select popover: skip the pop animation. */
  .ws-multiselect__popover {
    animation: none;
  }
}

/* ── Narrow viewport — list ──────────────────────────────────────────── */
@media (max-width: 640px) {
  .ws-list__header {
    gap: 0.5rem;
    padding: 0 0.875rem;
  }

  .ws-list__filter-row,
  .ws-list__row {
    padding: 0 0.875rem;
  }

  /* On small screens collapse to: Issue | Assignee | Move.
     Override the grid template entirely. */
  .ws-list {
    --ws-list-grid:
      minmax(0, 1fr)  /* Issue */
      0px             /* Repo  — hidden */
      0px             /* Assignee — hidden (too narrow) */
      0px             /* Labels — hidden */
      0px             /* Updated — hidden */
      36px;           /* Move */
  }

  .ws-list--multi-repo {
    --ws-list-grid:
      minmax(0, 1fr)
      0px
      0px
      0px
      0px
      36px;
  }

  /* Hide cells that are collapsed to 0px on narrow viewports. */
  .ws-list__fh--repo,
  .ws-list__fh--assignee,
  .ws-list__fh--labels,
  .ws-list__fh--updated,
  .ws-list__cell--repo,
  .ws-list__cell--assignee,
  .ws-list__cell--labels,
  .ws-list__cell--updated {
    overflow: hidden;
    visibility: hidden;
  }
}

/* ══ Task board — read-only people lanes ══════════════════════════════════
   Replaces the status-column board. See
   docs/superpowers/specs/2026-08-10-task-board-design.md.

   The header takes its OWN token pair rather than a fixed colour, because
   "visually distinct from the content" inverts between themes: in warm
   neutral the header SINKS below the card; in dark it must LIFT above it,
   since dark surfaces run --page-bg -> --paper -> --paper-3. A fixed hex
   renders a near-white bar in dark mode, and --text is worse (it is #E6E1D8
   there). Same problem .mode-toggle__indicator already solved once.

   Every theme-coupled value below derives from this pair rather than being
   separately invented. */
:root {
  --board-header-bg: var(--text);
  --board-header-fg: var(--paper);
  --board-header-meta: rgba(255, 255, 255, 0.62);
  --board-chip-bg: rgba(255, 255, 255, 0.12);
  --board-chip-border: rgba(255, 255, 255, 0.20);
  --board-issue-accent: var(--accent);
}

[data-theme='dark'] {
  /* Lift, don't sink: paper-3 sits above paper in dark. */
  --board-header-bg: var(--paper-3);
  --board-header-fg: var(--text);
  --board-header-meta: var(--text-2);
  --board-chip-bg: rgba(255, 255, 255, 0.06);
  --board-chip-border: rgba(255, 255, 255, 0.12);
  /* The dusty blue is too dark to read on a dark surface. */
  --board-issue-accent: #9DAFD6;
}

.lane-board {
  display: flex;
  flex-direction: column;
  /* Fit content, but never outgrow the card. */
  max-height: 100%;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  background: var(--paper);
  overflow: hidden;
}

.lane-board__header,
.lane-board__unassigned {
  /* Chrome: always visible, never scrolled away. */
  flex: 0 0 auto;
}

.lane-board--empty {
  padding: 48px 24px;
  text-align: center;
  color: var(--text-2);
}

.lane-board__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  background: var(--board-header-bg);
  color: var(--board-header-fg);
}

.lane-board__title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.lane-board__meta {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--board-header-meta);
}

.lane-board__chip {
  flex-shrink: 0;
  padding: 4px 10px;
  border: 1px solid var(--board-chip-border);
  border-radius: var(--rounded-pill);
  background: var(--board-chip-bg);
  font-size: 11px;
  white-space: nowrap;
}

.lane-board__lanes {
  display: flex;
  flex-direction: column;
  /* min-height:0 is what lets the children actually shrink inside a flex
     column; without it they keep their content height and overflow instead. */
  min-height: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.lane {
  display: flex;
  flex-direction: column;
  /* Size to content, yield only when the board is out of room. */
  flex: 0 1 auto;
  min-height: 0;
}

.lane + .lane {
  border-top: 1px solid var(--line);
}

.lane__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px 8px;
  /* The edge the lane's list scrolls under. Dashed rather than solid so it
     reads as a fold within one person's row, not as a divider between two
     people — that separation is .lane + .lane's solid line. */
  border-bottom: 1px dashed var(--line);
}

.lane__name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.lane__handle {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
}

.lane__counts {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
}

.lane__issues {
  /* The one part that scrolls: a lane's own list. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0;
  padding: 0 0 4px;
  list-style: none;
}

.lane__issues--quiet {
  /* Disclosed backlog reads as secondary to the active list above it. */
  background: var(--paper-2);
  border-top: 1px dashed var(--line);
}

.lane__issues--quiet .lane__issue-title {
  color: var(--text-2);
}

.lane__issue-link {
  display: grid;
  /* Fixed slot for the repo so names form a column instead of ragging. */
  grid-template-columns: 96px 52px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 5px 18px 5px 44px;
  color: inherit;
  text-decoration: none;
  font-size: 12px;
}

.lane__issue-link:hover {
  background: var(--paper-3);
}

.lane__issue-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.lane__repo {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
}

.lane__number {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--board-issue-accent);
}

.lane__issue-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}

.lane__issue-time {
  font-size: 11px;
  color: var(--text-4);
  white-space: nowrap;
}

.lane__more,
.lane__quiet {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 6px 18px 10px 44px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11px;
  color: var(--text-3);
  text-align: left;
  cursor: pointer;
}

.lane__more:hover,
.lane__quiet:hover {
  color: var(--text-2);
  background: var(--paper-3);
}

.lane__more:focus-visible,
.lane__quiet:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* "N more active" continues the list above it, so it reads as part of that
   list rather than as a second footer alongside the quiet disclosure. */
.lane__more {
  flex: 0 0 auto;
  /* Closes the list the way the quiet disclosure opens one. Rendered only
     when hiddenCount > 0, so the border appears exactly when there is more
     to reveal — never as a stray line under a complete list. */
  border-top: 1px dashed var(--line);
  color: var(--board-issue-accent);
  font-weight: 500;
}

.lane__quiet {
  flex: 0 0 auto;
}

.lane__quiet-chevron {
  font-size: 12px;
}

.lane-board__unassigned {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--line);
  /* Recessed in light, raised in dark — the same inversion the header makes. */
  background: var(--paper-2);
  font-size: 12px;
}

[data-theme='dark'] .lane-board__unassigned {
  background: var(--paper-3);
}

.lane-board__unassigned-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--rounded-pill);
  background: var(--badge-bg-mute);
  color: var(--text-3);
  font-size: 12px;
}

.lane-board__muted {
  color: var(--text-3);
}

.lane-board__unassigned-count {
  margin-left: auto;
  color: var(--text-2);
  white-space: nowrap;
}

/* The lane board is ONE tall document, not a row of independently-scrolling
   columns. `.ws-scroll` is `overflow: hidden` because the column board handled
   its own scrolling per column — so without this the lanes simply overflowed
   the viewport with no way to reach them. Scoped to the board view rather than
   changing `.ws-scroll` itself, which the list view fills absolutely. */
.ws-scroll--lanes {
  /* The board sizes to its content and stops at the card's boundary — it does
     NOT scroll the page. Past that point each lane scrolls its own list, so
     every person's header stays on screen instead of being scrolled away by
     whoever happens to have the most work. */
  display: flex;
  /* Without this the board stretches to the container's full height (flex
     items default to align-items: stretch) and leaves dead space below the
     unassigned row. Starting it at the top lets it size to its content, while
     max-height on .lane-board still stops it at the card's boundary. */
  align-items: flex-start;
  padding: 1.25rem;
  overflow: hidden;
}

.lane-board--skeleton {
  pointer-events: none;
}

.lane-board--skeleton .lane-board__header {
  /* The header's own token pair would show a solid ink bar before any content
     exists; a neutral surface reads as "loading" rather than as chrome. */
  background: var(--paper-3);
}

/* Widths live here, not as inline styles: an inline style object is a mutant
   the skeleton has no way to assert away.

   The row placeholders sit in .lane__issue-link's real grid
   (96px | 52px | 1fr | auto), so they fill whatever width the board has
   instead of a fixed percentage that emptied out as the screen widened. */
.lane-board--skeleton .lane__issue-link > .ws-skel {
  width: 100%;
}

.lane-board--skeleton .ws-skel--time {
  width: 40px;
}

/* Ragged title lengths so the block reads as text, not as a bar chart. Done
   with nth-child rather than per-row inline styles, which would be untestable
   mutants — and these stay proportional at any width. */
.lane-board--skeleton .lane__issue:nth-child(3n + 1) .ws-skel--title-line {
  width: 92%;
}

.lane-board--skeleton .lane__issue:nth-child(3n + 2) .ws-skel--title-line {
  width: 64%;
}

.lane-board--skeleton .lane__issue:nth-child(3n + 3) .ws-skel--title-line {
  width: 78%;
}

.lane-board--skeleton .ws-skel--name {
  width: 140px;
}

.ws-skel--avatar {
  width: 28px;
  height: 28px;
  border-radius: var(--rounded-pill);
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .ws-scroll--lanes {
    padding: 0.875rem;
  }
}
.toast-container {
  position: fixed;
  bottom: calc(var(--status-bar-height) + 12px);
  right: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 900;
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: var(--rounded-md);
  font-size: 12px;
  font-weight: 500;
  font-family: var(--font);
  color: var(--text);
  background: var(--paper-2);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-md);
  pointer-events: auto;
  animation: toastIn 200ms var(--ease-out-expo) forwards;
}

.toast--exit {
  animation: toastOut 200ms var(--ease-out-expo) forwards;
}

.toast--success {
  border-color: var(--accent);
}

.toast--error {
  border-color: var(--err);
}

.toast__text {
  line-height: 1.4;
}

.toast__action {
  margin-left: 4px;
  padding: 2px 8px;
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  background: transparent;
  border: none;
  border-radius: var(--rounded-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

@media (hover: hover) {
  .toast__action:hover {
    background: var(--accent-glow);
  }
}

.toast__action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@keyframes toastIn {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes toastOut {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(8px) scale(0.96);
  }
}

/* ── Mobile (≤640px) ──
   Stretch the toast container to span the viewport with a comfortable
   gutter, and lift it above the mobile tab bar so toasts aren't hidden
   behind it. */
@media (max-width: 640px) {
  .toast-container {
    left: 16px;
    right: 16px;
    bottom: calc(var(--status-bar-height) + var(--mobile-tabbar-height) + env(safe-area-inset-bottom, 0px) + 16px);
    align-items: stretch;
  }

  .toast {
    width: 100%;
  }
}
.repo-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
  background: var(--paper-3);
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  padding: 2px 6px;
  white-space: nowrap;
  font-family: var(--font);
}

.repo-badge svg {
  opacity: 0.6;
}
/* ── Repo Switcher ──
   Sidebar trigger and hierarchy dropdown. Active state uses the project's
   nav-row recipe (Review Blue Wash background + Review Blue text +
   weight 600). No colored stripes. Accent appears only on the currently
   active row inside the popover — nothing else. */

.repo-switcher {
  position: relative;
}

.repo-switcher__trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 9px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  user-select: none;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.repo-switcher__trigger:hover {
  background: var(--paper-3);
}

.repo-switcher__trigger--open {
  background: var(--paper-3);
}

.repo-switcher__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.repo-switcher__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-3);
}

.repo-switcher__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.2;
  font-weight: 600;
  color: var(--text);
}

.repo-switcher__title--placeholder {
  color: var(--text-3);
  font-weight: 400;
}

.repo-switcher__chevron {
  flex-shrink: 0;
  color: var(--text-4);
  opacity: 0.7;
  display: inline-flex;
  transition:
    transform var(--duration-fast) var(--ease-out-expo),
    opacity var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.repo-switcher__trigger:hover .repo-switcher__chevron,
.repo-switcher__trigger--open .repo-switcher__chevron {
  opacity: 1;
  color: var(--text-2);
}

.repo-switcher__chevron--open {
  transform: rotate(180deg);
}

/* ── Empty state ─────────────────────────────────────────────────── */

.repo-switcher__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 8px 10px;
  border: 1px dashed var(--line-2);
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo);
}

.repo-switcher__empty:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-glow);
}

/* ── Popover ─────────────────────────────────────────────────────── */

.repo-switcher__popover {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 100;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-md);
  padding: 4px;
  overflow: hidden;
  animation: popoverPop 220ms var(--ease-out-expo);
  transform-origin: top center;
}

/* ── Group (owner > repos hierarchy) ─────────────────────────────── */

.repo-switcher__group + .repo-switcher__group {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--line);
}

.repo-switcher__group-header {
  padding: 7px 10px 5px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
}

.repo-switcher__list {
  list-style: none;
  margin: 0;
  padding: 0 0 2px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* ── Row ─────────────────────────────────────────────────────────── */

.repo-switcher__row {
  display: flex;
  align-items: stretch;
  border-radius: var(--rounded-md);
  overflow: hidden;
}

.repo-switcher__pick {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border: none;
  background: transparent;
  color: var(--text-2);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.repo-switcher__pick:hover {
  background: var(--paper-3);
  color: var(--text);
}

/* Active row uses the project's nav-row-active recipe. No left stripe. */
.repo-switcher__row--active .repo-switcher__pick,
.repo-switcher__row--active .repo-switcher__pick:hover {
  background: var(--accent-glow);
  color: var(--accent);
  font-weight: 600;
  cursor: default;
}

.repo-switcher__pick-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.repo-switcher__merged-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-3);
  flex-shrink: 0;
}

/* ── Merge toggle ─────────────────────────────────────────────────── */

.repo-switcher__merge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  border: none;
  background: transparent;
  color: var(--text-4);
  cursor: pointer;
  opacity: 0;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo),
    opacity var(--duration-fast) var(--ease-out-expo);
}

.repo-switcher__row:hover .repo-switcher__merge,
.repo-switcher__merge--on,
.repo-switcher__merge:focus-visible {
  opacity: 1;
}

.repo-switcher__merge:hover {
  color: var(--text);
  background: var(--paper-3);
}

.repo-switcher__merge--on {
  color: var(--text-2);
}

/* ── Footer actions ──────────────────────────────────────────────── */

.repo-switcher__footer {
  display: flex;
  gap: 4px;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
}

.repo-switcher__action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  padding: 6px 8px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-2);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.repo-switcher__action:hover {
  background: var(--paper-3);
  color: var(--text);
}

.repo-switcher__action--ghost {
  flex: 0 0 auto;
  justify-content: center;
  width: 28px;
  padding: 0;
  color: var(--text-3);
}

.repo-switcher__action svg {
  flex-shrink: 0;
}
.overflow-menu {
  position: relative;
}

.overflow-menu__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.overflow-menu__trigger:hover {
  background: var(--accent-glow);
  color: var(--text);
}

.overflow-menu__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (pointer: coarse) {
  .overflow-menu__trigger {
    width: 40px;
    height: 40px;
  }
}

.overflow-menu__popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 100;
  min-width: 200px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-md);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  animation: popoverPop 220ms var(--ease-out-expo);
  transform-origin: top right;
}

.overflow-menu__item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.overflow-menu__item:hover:not(:disabled) {
  background: var(--paper-3);
}

.overflow-menu__item:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.overflow-menu__icon {
  display: inline-flex;
  color: var(--text-3);
  flex-shrink: 0;
}

@media (pointer: coarse) {
  .overflow-menu__item {
    min-height: 44px;
  }
}
.context-tab-bar {
  position: fixed;
  /* Sit flush with the viewport bottom — the status bar is hidden at
     mobile width (the tab bar replaces it), so no reserve is needed. */
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 55;
  display: none;
  grid-template-columns: repeat(6, 1fr);
  gap: 2px;
  /* Symmetric vertical padding so the active-tab highlight sits with equal
     gap above and below. On safe-area devices the bottom extends to clear
     the home indicator; min 4px keeps parity with the top. */
  padding: 4px 4px max(4px, env(safe-area-inset-bottom, 0px));
  background: var(--surface-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--line);
  box-shadow: var(--glass-highlight);
  /* min-height so safe-area padding adds to the bar instead of cropping tabs.
     --mobile-tabbar-height is declared on :root in tokens.css so consumers in
     other DOM scopes (e.g. .app-shell__content margin-bottom) read the same
     value through the cascade. */
  min-height: calc(var(--mobile-tabbar-height) + env(safe-area-inset-bottom, 0px));
  font-family: var(--font);
}

@media (max-width: 640px) {
  .context-tab-bar {
    display: grid;
  }
}

.context-tab-bar__tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 4px;
  border: none;
  border-radius: var(--rounded-md);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  touch-action: manipulation;
  font-family: inherit;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo);
  min-height: 44px;
}

.context-tab-bar__tab:hover {
  background: var(--accent-glow);
}

/* Project's nav-row-active recipe: Review Blue Wash + Review Blue text + weight 600. */
.context-tab-bar__tab--active {
  background: var(--accent-glow);
  color: var(--accent);
  font-weight: 600;
}

.context-tab-bar__tab--active:hover {
  background: var(--accent-glow);
}

.context-tab-bar__tab--active .context-tab-bar__icon {
  color: var(--accent);
}

.context-tab-bar__icon {
  position: relative;
  display: inline-flex;
  color: var(--text-3);
}

.context-tab-bar__label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
}

.context-tab-bar__badge {
  position: absolute;
  top: -4px;
  right: -8px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 600;
  line-height: 14px;
  text-align: center;
}
.write-target-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1100;
}

.write-target-modal {
  background: var(--surface-content);
  border-radius: var(--rounded-lg);
  padding: 24px;
  max-width: 420px;
  width: calc(100% - 32px);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}

.write-target-modal h3 {
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 16px;
  color: var(--text);
}

.write-target-options {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 20px;
}

.write-target-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--rounded-md);
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}

@media (hover: hover) {
  .write-target-option:hover {
    background: var(--paper-3);
  }
}

/* The path being created, under the question. Monospace because it is a
   literal string the author typed, not prose. */
.write-target-path {
  margin: -8px 0 16px;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12px;
  color: var(--text-2);
  word-break: break-all;
}

/* A repo that already holds this path. Marked, never disabled: opening the
   existing file is a legitimate thing to want, it just must not happen by
   accident. The note carries the meaning; the row is only tinted enough to
   draw the eye. */
.write-target-option--collides {
  background: var(--accent-soft);
}

.write-target-option__note {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
}

.write-target-warning {
  margin: -12px 0 16px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-2);
}

.write-target-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

.settings-btn--primary {
  background: var(--accent);
  color: white;
}

/* ── Touch input ──
   Match the codebase precedent set by overflow-menu items: 44px min
   target height when the primary pointer is coarse, independent of
   viewport width. Keeps tap targets ergonomic on tablets in landscape
   too, not just narrow phones. */
@media (pointer: coarse) {
  .write-target-option {
    min-height: 44px;
    padding: 10px 12px;
  }
}
/* ── Repo Settings (Settings → Repos tab) ──
   Warm-neutral throughout. Review Blue only on the active connected row
   (nav-row-active recipe) and on hover/focus interactive feedback. No
   side-stripe borders. Disconnect is always visible. */

.repo-settings__section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

.repo-settings__section-header .settings-group__label {
  margin: 0;
}

.repo-settings__icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__icon-btn:hover {
  color: var(--text);
  background: var(--paper-3);
}

.repo-settings__icon-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ── Empty state ─────────────────────────────────────────────────── */

.repo-settings__empty {
  padding: 18px 14px;
  text-align: center;
  font-size: 12.5px;
  color: var(--text-3);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
}

/* ── Available repos: per-install cards ──────────────────────────── */

.repo-settings__installs {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.repo-settings__install {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  overflow: hidden;
}

.repo-settings__install-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.repo-settings__install-id {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.repo-settings__install-id svg {
  color: var(--text-2);
  flex-shrink: 0;
}

.repo-settings__install-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
}

.repo-settings__install-eyebrow {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  background: var(--badge-bg-mute);
  border-radius: var(--rounded-sm);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
}

.repo-settings__install-manage {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  height: 26px;
  padding: 0 11px;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  font-family: var(--font);
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1;
  color: var(--text-2);
  text-decoration: none;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__install-manage svg {
  flex-shrink: 0;
  opacity: 0.7;
  display: block;
}

.repo-settings__install-manage:hover {
  background: var(--paper-3);
  border-color: var(--text-3);
  color: var(--text);
}

.repo-settings__install-empty {
  padding: 14px;
  margin: 0;
  font-size: 12.5px;
  color: var(--text-3);
  line-height: 1.5;
  text-align: center;
}

.repo-settings__install-empty strong {
  font-weight: 600;
  color: var(--text-2);
}

.repo-settings__repo-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.repo-settings__repo {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  font-size: 13px;
  color: var(--text);
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__repo + .repo-settings__repo {
  border-top: 1px solid var(--line);
}

.repo-settings__repo:hover {
  background: var(--paper-2);
}

.repo-settings__repo-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}

.repo-settings__repo-add {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  font-family: var(--font);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__repo-add:hover {
  background: var(--accent-glow);
  border-color: var(--accent);
  color: var(--accent);
}

.repo-settings__repo-added {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
}

.repo-settings__repo-added svg {
  color: var(--ok);
}

/* Personal-install nudge — single line, low weight. */
.repo-settings__nudge {
  margin: 10px 2px 0;
  font-size: 11.5px;
  color: var(--text-3);
  line-height: 1.5;
}

.repo-settings__nudge a {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
}

.repo-settings__nudge a:hover {
  text-decoration: underline;
}

/* ── Connected repos rows ────────────────────────────────────────── */

.repo-settings__rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  overflow: hidden;
}

.repo-settings__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px 8px 14px;
  font-size: 13px;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__row + .repo-settings__row {
  border-top: 1px solid var(--line);
}

.repo-settings__row:hover {
  background: var(--paper-2);
}

.repo-settings__row--active {
  background: var(--accent-glow);
}

.repo-settings__row--active:hover {
  background: var(--accent-glow);
}

.repo-settings__row-pick {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 0;
  border: none;
  background: transparent;
  color: var(--text-2);
  font-family: var(--font);
  font-size: 13px;
  cursor: pointer;
  text-align: left;
  transition: color var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__row-pick:hover {
  color: var(--text);
}

.repo-settings__row--active .repo-settings__row-pick,
.repo-settings__row--active .repo-settings__row-pick:hover {
  color: var(--accent);
  cursor: default;
}

.repo-settings__row-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}

.repo-settings__row--active .repo-settings__row-name {
  font-weight: 600;
}

.repo-settings__row-owner {
  color: var(--text-3);
  font-weight: 400;
}

.repo-settings__row--active .repo-settings__row-owner {
  color: var(--accent);
  opacity: 0.7;
}

.repo-settings__row-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.repo-settings__row-state {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: var(--rounded-pill);
  box-shadow: 0 1px 2px rgba(61, 90, 140, 0.18);
}

.repo-settings__row-merge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  font-family: var(--font);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-3);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo),
    color var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__row-merge:hover {
  color: var(--text);
  border-color: var(--text-3);
}

.repo-settings__row-merge--on {
  color: var(--text);
  border-color: var(--text-3);
}

.repo-settings__row-merge svg {
  flex-shrink: 0;
}

/* Disconnect — always visible, neutral at rest, err on hover. */
.repo-settings__row-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__row-remove:hover {
  color: var(--err);
  background: var(--badge-bg-err);
}

.repo-settings__row-remove:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ── Inline disconnect confirmation ──────────────────────────────── */

.repo-settings__row--confirm {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  padding: 12px 14px;
  background: var(--paper-2);
}

.repo-settings__row-confirm-text {
  font-size: 12.5px;
  color: var(--text-2);
  line-height: 1.5;
}

.repo-settings__row-confirm-text strong {
  color: var(--text);
  font-weight: 600;
}

.repo-settings__row-confirm-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

.repo-settings__confirm-btn {
  padding: 5px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: var(--paper);
  font-family: var(--font);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out-expo),
    border-color var(--duration-fast) var(--ease-out-expo);
}

.repo-settings__confirm-btn:hover {
  background: var(--paper-3);
  border-color: var(--text-3);
}

.repo-settings__confirm-btn--danger {
  color: var(--err);
  border-color: rgba(229, 62, 62, 0.4);
}

.repo-settings__confirm-btn--danger:hover {
  background: var(--badge-bg-err);
  border-color: var(--err);
}

/* ── Counter ─────────────────────────────────────────────────────── */

.repo-settings__counter {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-size: 11px;
  color: var(--text-3);
}

.repo-settings__counter-n {
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
