/* ── 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;
  /* Horizontal gutter inside the document surface: the space between the
     reading column and the edge of its container.

     A TOKEN, not a repeated literal, because five rules have to agree on it and
     the reasons are all invisible from any one of them. The doc header uses it
     so the title lines up with the body; the markdown surface uses it so
     toggling between the two does not shift the text; the version viewer and
     its loading skeleton use it so opening a version does not either. It was
     72px in all five, written out five times.

     88px, and the 16px it grew by is the annotation rail's (#240). The design
     draws an 18px marker circle, and three of those plus the connector need
     83px of gutter; 88 gives them that with room. --content-max-width below
     grows by the same 32px so the reading column does not pay for it.

     Narrows back to 72px at 1024px and below, which is where margin.css turns
     the lane off (--margin-lane-available). Nothing should pay reading width
     for a rail it does not get. Narrower still at 640px, where the body drops
     to a 24px gutter.

     THE COST, stated because it is real: --content-max-width holds the measure
     at 608px only where the column actually reaches that width, which needs
     roughly a 1480px viewport with both panels open. Between there and 1025px
     the extra 16px a side comes out of the text. At a 1280px viewport that is
     464px of measure before, 432px after: 59 characters per line down to 55,
     both inside the classic 45 to 75 and well under the WCAG 1.4.8 ceiling of
     80. Shorter lines, not worse ones. */
  --doc-gutter-x: 88px;
  /* 832px, and the number is derived rather than chosen. The reading measure is
     what matters and it is 608px:

       832 column - 2 x 24px shell padding - 2 x 88px gutter = 608px

     which is 78 characters at 17px Zoho Puvi, under the 80 that WCAG 1.4.8
     (AAA) sets as its ceiling. See the note on .tiptap's font-size in
     editor-tiptap.css, which measured it.

     It was 800px against a 72px gutter, giving the same 608px. Both terms moved
     together on purpose: widening the gutter alone would have taken 32px out of
     the reading column and pushed the line back over the ceiling. */
  --content-max-width: 832px;
  /* The wide measure, for the triage tables and for a document whose author has
     asked for it (#234). 72rem was already the triage figure; naming it puts
     both on one number rather than two that happen to match.

     It gives 1152 - 2 x 24px shell padding - 2 x 88px gutter = 928px of measure,
     against the 608px above. That is past the WCAG 1.4.8 (AAA) ceiling of 80
     characters for prose, which is the trade the reader is choosing to make and
     the reason this is a control rather than a rule — #230 applied it by
     inference and #233 reverted that.

     Nothing about the annotation rail has to move for it. The rail is anchored
     inside the editor's own right gutter (--doc-gutter-x, see margin.css), so
     widening the column moves the editor's box and takes the rail with it; the
     18px between the text and the glyphs is the same at either measure. That is
     what a table BLEEDING out of the column could not have said — #188. */
  --content-max-width-wide: 72rem;
  /* 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;
  /* Horizontal gutter inside the document surface: the space between the
     reading column and the edge of its container.

     A TOKEN, not a repeated literal, because five rules have to agree on it and
     the reasons are all invisible from any one of them. The doc header uses it
     so the title lines up with the body; the markdown surface uses it so
     toggling between the two does not shift the text; the version viewer and
     its loading skeleton use it so opening a version does not either. It was
     72px in all five, written out five times.

     88px, and the 16px it grew by is the annotation rail's (#240). The design
     draws an 18px marker circle, and three of those plus the connector need
     83px of gutter; 88 gives them that with room. --content-max-width below
     grows by the same 32px so the reading column does not pay for it.

     Narrows back to 72px at 1024px and below, which is where margin.css turns
     the lane off (--margin-lane-available). Nothing should pay reading width
     for a rail it does not get. Narrower still at 640px, where the body drops
     to a 24px gutter.

     THE COST, stated because it is real: --content-max-width holds the measure
     at 608px only where the column actually reaches that width, which needs
     roughly a 1480px viewport with both panels open. Between there and 1025px
     the extra 16px a side comes out of the text. At a 1280px viewport that is
     464px of measure before, 432px after: 59 characters per line down to 55,
     both inside the classic 45 to 75 and well under the WCAG 1.4.8 ceiling of
     80. Shorter lines, not worse ones. */
  --doc-gutter-x: 88px;
  /* 832px, and the number is derived rather than chosen. The reading measure is
     what matters and it is 608px:

       832 column - 2 x 24px shell padding - 2 x 88px gutter = 608px

     which is 78 characters at 17px Zoho Puvi, under the 80 that WCAG 1.4.8
     (AAA) sets as its ceiling. See the note on .tiptap's font-size in
     editor-tiptap.css, which measured it.

     It was 800px against a 72px gutter, giving the same 608px. Both terms moved
     together on purpose: widening the gutter alone would have taken 32px out of
     the reading column and pushed the line back over the ceiling. */
  --content-max-width: 832px;
  /* The wide measure, for the triage tables and for a document whose author has
     asked for it (#234). 72rem was already the triage figure; naming it puts
     both on one number rather than two that happen to match.

     It gives 1152 - 2 x 24px shell padding - 2 x 88px gutter = 928px of measure,
     against the 608px above. That is past the WCAG 1.4.8 (AAA) ceiling of 80
     characters for prose, which is the trade the reader is choosing to make and
     the reason this is a control rather than a rule — #230 applied it by
     inference and #233 reverted that.

     Nothing about the annotation rail has to move for it. The rail is anchored
     inside the editor's own right gutter (--doc-gutter-x, see margin.css), so
     widening the column moves the editor's box and takes the rail with it; the
     18px between the text and the glyphs is the same at either measure. That is
     what a table BLEEDING out of the column could not have said — #188. */
  --content-max-width-wide: 72rem;
  /* 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); }
/* Where a vertical and a horizontal scrollbar meet. Unstyled, Chrome paints it
   white once ::-webkit-scrollbar is styled at all, which showed as a small
   square in the corner of an expanded table that scrolls both ways. */
::-webkit-scrollbar-corner { background: transparent; }
/* ── 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, and so does a document whose
   reader has asked for the full width (#234).

   A class on the container rather than an override of --content-max-width,
   deliberately: context-fit.ts reads that token to decide whether the context
   panel still fits, and a document going wide must not be able to collapse the
   panel to a rail underneath it. */
.app-shell__content-inner--wide {
  max-width: var(--content-max-width-wide);
}

.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;
}

/* AND ONCE MORE ONE LEVEL UP, for the fixed-position chrome that is a SIBLING
   of <main> rather than a descendant of it — the toast container is the one
   that exists today (toast.css). Inheritance cannot carry the override above
   to a sibling, so left to that rule alone such an element reads the :root
   44px while the real toolbar is 84px tall and lands under it. `.app-shell` is
   the nearest ancestor shared by the toolbar and by anything fixed beside it.
   It lives here, next to the value it overrides, rather than in the stylesheet
   of whichever component happened to need it first (#302). */
:root:has(.app-shell__content--stacked-toolbar) {
  --toolbar-h: 84px;
}


.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;
}

/* Row 2 — the surface row.
   Holds the Rich/Markdown toggle always, and the formatting bar beside it only
   on the rich surface. It was the formatting bar's own row until #256, which is
   why the toggle sat up in the chrome row among app-global controls: there was
   nowhere else that survived the markdown surface.

   `1fr auto 1fr` with the third track left EMPTY, which is the status bar's
   arrangement written as a grid: there, .status-bar__left and __right are
   `flex: 1` and the centre group is auto-sized between them. Equal side tracks
   plus equal gaps put the bar on the row's true centre line, not the centre of
   whatever the toggle left over.

   It degrades rather than collides. A `1fr` track's floor is its content's
   min-content width, so the first track never resolves narrower than the
   toggle; when the row runs short the bar's `auto` track gives way instead
   (its own automatic minimum is 0, because .fmt-bar is a scroll container) and
   the bar drifts off centre before it ever overlaps. */
.editor-toolbar__row--surface {
  height: 40px;
  padding: 0 8px;
  flex-shrink: 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  border-top: 1px dashed color-mix(in srgb, var(--line), transparent 60%);
}

/* Grid items stretch by default, and the toggle's width is auto, so without
   this it would span the whole first track instead of sitting at its start. */
.editor-toolbar__row--surface .surface-toggle {
  justify-self: start;
}

/* Named rather than left to auto-placement: the row has two children and three
   tracks, so the bar has to be told it belongs in the middle one. */
.editor-toolbar__row--surface .fmt-bar {
  grid-column: 2;
}

/* Same weight as the Review/Edit toggle on purpose: one control shape that
   always means "pick one of two". They never compete for the eye — that one is
   at the bottom of the viewport, this one at the top of the document.

   Its words go first when the row runs out of room, ahead of anything in the
   formatting bar. The active chip still reads as selected through the
   indicator, so the affordance survives the labels; a formatting button that
   scrolls out of reach does not. Same trade the status bar makes at its own
   tier 3, and the ResizeObserver in SegmentedToggle is what keeps the indicator
   sized to the chip across it. */
@media (max-width: 559px) {
  .surface-toggle .mode-toggle__label {
    display: none;
  }
}

.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,
  .status-bar__upstream-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;
}

/* The shared chip shape for Discard and the upstream action; each is coloured
   below. */
.status-bar__discard-btn,
.status-bar__upstream-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);
}

/* A chip is one line at its fixed 22px. The merge chip carries the
   committer's name, which is any length: without this it wrapped inside the
   chip (#350, "Merge Ana Test's changes" on five lines). */
.status-bar__discard-btn,
.status-bar__upstream-btn {
  white-space: nowrap;
}

/* The merge chip in the ladder (#350). The tiers above were measured before
   the chip existed, and it costs 38px bare plus its label, which carries a
   name of any length. With the chip bare, the full bar needs 665px: padding
   32 + words 42 + toggle 131 + chip 38 + Discard 78 + Commit 80 + pill 204
   + five 12px gaps. Measured in e2e/merge.authed.spec.ts.

   While a merge is on offer the bar makes room in the ladder's own order:
   below 900px the word count goes (tier 3's first casualty) and the pill
   takes its short sentence (tier 2's), which brings the need to 564px. The
   label gets what is left, up to 28ch, and below 64px of room it goes and
   the chip keeps its icon; the full label is on its aria-label and title. */
.status-bar__upstream-btn .status-bar__btn-label {
  max-width: min(28ch, max(0px, 100cqw - 665px));
  overflow: hidden;
  text-overflow: ellipsis;
}

@container statusbar (max-width: 899px) {
  .status-bar:has(.status-bar__upstream-btn) .status-bar__left {
    display: none;
  }
  .status-bar:has(.status-bar__upstream-btn) .status-bar__upstream-btn .status-bar__btn-label {
    max-width: min(28ch, max(0px, 100cqw - 564px));
  }
}

@container statusbar (min-width: 440px) and (max-width: 899px) {
  .status-bar:has(.status-bar__upstream-btn) .status-bar__status-text--full {
    display: none;
  }
  .status-bar:has(.status-bar__upstream-btn) .status-bar__status-text--short {
    display: inline;
  }
}

@container statusbar (max-width: 627px) {
  .status-bar__upstream-btn .status-bar__btn-label {
    display: none;
  }
}

/* Constructive, not destructive: the accent where Discard warns. */
.status-bar__upstream-btn:hover {
  color: var(--accent);
  background: color-mix(in oklch, var(--paper-2), var(--accent) 14%);
}

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

/* Destructive, so red at rest, not only on approach (user decision on #350):
   the error signal over the chip surface, opaque in both themes. */
.status-bar__discard-btn {
  color: var(--err);
  background: color-mix(in oklch, var(--paper-2), var(--err) 10%);
}

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

/* The merge chip is the way forward once someone else has committed: a commit
   now would be refused. So it is the one filled chip in the bar, in the accent
   that marks their lines on the merge screen, with Commit's --on-accent text. */
.status-bar__upstream-btn--merge,
.status-bar__upstream-btn--merge:hover {
  color: var(--on-accent);
}

.status-bar__upstream-btn--merge {
  background: var(--accent);
}

.status-bar__upstream-btn--merge:hover {
  background: var(--accent-dim);
  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;
  }
}

/* ── What stands in for a document that has not arrived (#317) ──

   The editor is `display: none` behind this, not unmounted: it is the thing
   doing the seeding, so removing it would remove what the gate is waiting for.

   NO ENTRANCE, and no delay before it appears. The usual reason to hold a
   loader back for a second is to stop it flashing over content that is already
   on screen — and this only goes up when there is none, so there is nothing to
   flash over and a blank panel would read as a broken page. A fade would say
   "something is arriving" one beat after the moment it is worth saying. */
.seed-gate {
  display: flex;
  flex-direction: column;
  gap: 13px;
  /* The shared doc gutter, as .doc-header has it, so the bars start under
     the title rather than a gutter to its left. */
  padding: 26px var(--doc-gutter-x) 30px;
}

/* The bars are decorative; this is what a screen reader is given instead. */
.seed-gate__reading {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.seed-gate__line {
  height: 13px;
  border-radius: 4px;
  background: var(--paper-3);
}

/* Three lengths rather than one, and descending rather than uniform: equal
   bars read as a table, and a paragraph does not end flush. The widths are the
   mock's — item 10 on the Editor page. */
.seed-gate__line--a { width: 68%; }
.seed-gate__line--b { width: 80%; background: color-mix(in srgb, var(--paper-3) 70%, var(--paper)); }
.seed-gate__line--c { width: 56%; background: color-mix(in srgb, var(--paper-3) 45%, var(--paper)); }

/* ── And the same panel when it never arrived ── */
.seed-gate--failed {
  align-items: center;
  gap: 10px;
  padding: 54px 28px 58px;
  text-align: center;
}

.seed-gate__title {
  font-family: var(--font);
  font-size: 17px;
  font-weight: 600;
  line-height: 26px;
  color: var(--text);
}

.seed-gate__body {
  max-width: 410px;
  font-family: var(--font);
  font-size: 14px;
  line-height: 21px;
  color: var(--text-2);
}

.seed-gate__actions {
  display: flex;
  gap: 10px;
  padding-top: 12px;
}

.seed-gate__retry,
.seed-gate__back {
  font-family: var(--font);
  font-size: 13px;
  line-height: 19px;
  border-radius: var(--rounded-sm);
  padding: 8px 16px;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

/* Try again is the primary: it is what the reader came back for, and it costs
   nothing to press — the message above it says nothing has been changed. */
.seed-gate__retry {
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: none;
}

.seed-gate__back {
  font-weight: 500;
  color: var(--text-2);
  background: var(--paper);
  border: 1px solid var(--line-2);
}

@media (hover: hover) {
  .seed-gate__retry:hover { background: var(--accent-dim); }
  .seed-gate__back:hover { background: var(--paper-2); }
}

.seed-gate__retry:focus-visible,
.seed-gate__back:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* ── 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;
  /* No top hairline of its own since #379: the one above the Documents
     heading (.sidebar__tree-header) starts the tree's section, and the
     heading, always present now, is the edge the list scrolls under. The
     bottom edge is the hairline on .sidebar__user. */
}

/* ── 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;
  /* No side padding here: each row's li is absolutely positioned against this
     ul's padding box, so padding would not inset it. The scroller carries it. */
  padding: 0; margin: 0;
}

.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 {
  /* flex:1 since #379: the label fills the row so the trailing chevron lane
     sits at the right edge. min-width:0 keeps ellipsis truncation working. */
  flex: 1;
  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);
}

/* ── 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;
  /* With the hairline above the Documents heading, brackets the tree's
     section, 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;
}

.sidebar__empty-title {
  color: var(--text-2);
  font-weight: 500;
}

/* ── 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. */
/* overflow: auto moved here from the old inline style (#379): the virtualizer
   scrolls this element, and without it nothing past the pane's height shows. */
.doc-tree__scroll { height: 100%; min-height: 0; padding-top: 4px; padding-inline: 8px; overflow: auto; }
.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 */
  /* Full width since #379: one folder per list, no indentation, and folders
     need a fixed trailing lane for the chevron. Reverses the label-hugging
     highlight from #11. */
  width: 100%;
  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;
  }
}

/* ── Document tree header (#217) ──
   Sits above .sidebar__scroll rather than inside it, so the header stays put
   while two hundred documents scroll past it.

   Set like .sidebar__across-header, which names the Workspace and Triage
   groups, because this names a group in the same way. Unlike those, it opens
   with a hairline and more room above: the groups above are navigation and
   this is the repository's content, and without a break the list read as more
   Triage rows (#379). Always present; inside a folder the path row sits below
   it (DocTreeNav). */
.sidebar__tree-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 8px;
  border-top: 1px solid var(--line);
  padding: 14px 10px 6px 16px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
}

/* ── Folder path row (#379) ──
   Below the Documents heading, only inside a folder: back, then the path. */
.doc-tree-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 30px;
  padding: 0 10px 0 9px;
  min-width: 0;
}
/* Home, at the right of the Documents heading: one press back to the top
   level from any depth. Only rendered inside a folder (DocTreeNav). */
.doc-tree-nav__home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin: -4px -2px -4px 0;
  border: none;
  border-radius: var(--rounded-sm);
  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);
}

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

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

.doc-tree-nav__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: var(--rounded-sm);
  background: var(--paper-3);
  color: var(--text);
  cursor: pointer;
}
/* Fills the row, so the 70% below is of the room there is, not of the path's
   own width (which capped a lone crumb at 70% of itself). */
.doc-tree-nav__path { flex: 1; min-width: 0; overflow: hidden; }
.doc-tree-nav__path ol {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
  font-size: 12px;
  line-height: 16px;
}
.doc-tree-nav__path li { display: flex; align-items: center; gap: 4px; min-width: 0; }
/* The current folder keeps its name; the ancestors take the squeeze, and it
   only truncates past 70% of the path, so they always keep some room. Alone
   (a repo's root, when repos are merged) it has the whole path. */
.doc-tree-nav__path li:last-child { flex-shrink: 0; max-width: 70%; }
.doc-tree-nav__path li:only-child { max-width: 100%; }
.doc-tree-nav__path li:has(> .doc-tree-nav__more) { flex-shrink: 0; }
.doc-tree-nav__sep { color: var(--text-4); opacity: 0.7; }
.doc-tree-nav__crumb,
.doc-tree-nav__more {
  border: none;
  background: transparent;
  padding: 2px 4px;
  border-radius: 5px;
  font: inherit;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
}
/* A long ancestor name ends in an ellipsis instead of pushing the path out of the header. */
.doc-tree-nav__crumb {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc-tree-nav__crumb:hover,
.doc-tree-nav__more:hover,
.doc-tree-nav__more:popover-open { background: var(--paper-3); color: var(--text); }
.doc-tree-nav__current {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc-tree-nav__menu {
  position: fixed;
  inset: auto;
  margin: 0;
  min-width: 168px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--rounded-md);
  background: var(--paper);
  box-shadow: var(--shadow-md);
}
.doc-tree-nav__menu-item {
  display: flex;
  align-items: center;
  width: 100%;
  height: 28px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  font-size: 13px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.doc-tree-nav__menu-item:hover { background: var(--paper-3); }
.doc-tree-nav__back:focus-visible,
.doc-tree-nav__crumb:focus-visible,
.doc-tree-nav__more:focus-visible,
.doc-tree-nav__menu-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* One folder at a time (#379): the viewport clips the slide, each pane is a
   folder, and the pane's own scroller is what the virtualizer measures. */
/* clip, not hidden: a hidden box is still programmatically scrollable, so focusing
   a row mid-slide could leave the viewport shifted sideways. */
.doc-tree__viewport { position: relative; height: 100%; overflow: clip; }
.doc-tree__pane { position: absolute; inset: 0; }
.doc-tree__trail {
  display: inline-flex;
  flex-shrink: 0;
  width: 12px;
  height: 12px;
  color: var(--text-4);
  opacity: 0.7;
}
.doc-tree__holder {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.doc-tree li { list-style: none; }
/* ── 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;
}

/* ICON AND COUNT (#385). The section's name is the tab's text, visually hidden
   and shown as a tooltip; the number is drawn here from `data-count`, so it is
   never part of that name. Orange is something waiting on you (unresolved
   threads, unread mentions); muted is only a total. */
.context-panel__tab {
  gap: 6px;
  color: var(--text-3);
}

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

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

.context-panel__tab svg {
  flex-shrink: 0;
}

.context-panel__tab[data-count]::after {
  content: attr(data-count);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
}

.context-panel__tab[data-tone='warn']::after {
  color: var(--warn);
  font-weight: 700;
}

.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;
  /* flex-start, not center: the byline is two lines on a thread that carries a
     version note and one line on a thread that does not, and centring against a
     block whose height varies moves the avatar between the two. */
  align-items: flex-start;
  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;
}

/* The byline stacks the identity row over the provenance note. Two lines
   because they cannot share one: see .thread-card__origin below for the
   measurement (#241). DecisionsTab renders .thread-card__header-text WITHOUT a
   byline around it and has no provenance note, so the row rule stays a row. */
.thread-card__byline {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 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.

   On its own line since #241. Measured beside the name at a 326px card: the
   note is 146px and the name 130px, against 294px of usable row once the avatar
   and gaps are taken — 62px short. This element carries flex-shrink: 0 and the
   name does not, so the name absorbed the entire shortfall and rendered as
   "Mig...". A footnote cannot win a space fight against the identity it
   annotates. */
.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;
  /* Shrink-wrapped rather than stretched, so the button's hit area is the text
     and not the card's full width; capped so a long note still ellipsises
     rather than widening the panel. `text-align` because a <button> centres by
     default, which was invisible while it was shrink-wrapped in a row. */
  align-self: flex-start;
  max-width: 100%;
  text-align: left;
  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);
}

/* Only you can see this (#239). The decision hue rather than the accent: it is
   the same "this is a state, not a status" signal the Decision badge carries,
   and the two never appear together — a private note cannot be promoted, since
   a decision is a team artefact. */
.thread-card__badge--private {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--signal-decision);
  background: color-mix(in srgb, var(--signal-decision) 14%, transparent);
}

/* Waiting for a commit (#239). The muted tone rather than a signal hue: this is
   a state that resolves itself the moment the author commits, not something
   asking to be dealt with. */
.thread-card__badge--pending {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--text-3);
  background: var(--paper-3);
}

.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;
  /* space-between rather than flex-end: the audience control sits at the left
     of the row and Send at the right, so the two read as separate decisions
     rather than a pair of buttons. With no audience control the single child
     still lands right, because it is the only one. */
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

/* Who the comment is for (#239). Quiet by default — the team is the ordinary
   case and does not need announcing — and tinted only once it is a private
   note, which is the state worth noticing before you commit to it. */
.reply-composer__audience {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  padding: 3px 8px;
  border: 1px solid var(--line-2);
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  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);
}

.reply-composer__audience:hover {
  color: var(--text-2);
  border-color: var(--text-4);
}

.reply-composer__audience--private {
  color: var(--signal-decision);
  border-color: color-mix(in srgb, var(--signal-decision) 45%, transparent);
  background: color-mix(in srgb, var(--signal-decision) 12%, transparent);
}

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

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

/* CANCEL, the quietest thing in the row (#306).

   Three controls share this slot now and they are three different weights on
   purpose: Send is filled with the accent because it is what the reader came
   here to do; the audience toggle is outlined because it is a choice about that
   send rather than a rival to it; Cancel carries no fill and no border at all.

   That order is the argument. Cancel exists because a press outside no longer
   throws a comment away, so it is the only way to abandon one — which makes it
   necessary AND makes it the control a stray press must not find first. A
   tertiary text button is reachable when looked for and invisible when not,
   which is the right trade for something that destroys writing.

   `margin-left: auto` rather than a wrapper element around the two right-hand
   buttons. The slot is `justify-content: space-between`, sized for the audience
   toggle at the left and Send at the right; a third child would spread all
   three evenly and put Cancel in the middle of the row. An auto margin takes
   the free space ahead of it instead, so the pair sits together at the right
   with the slot's own 8px between them, and the panel's reply composer — which
   passes no onCancel and therefore renders no Cancel — keeps the two-child
   layout this rule was written for, untouched.

   It rides the same entrance as Send, because they arrive together: the slot
   opens the moment there is text, and a Cancel that faded in on a different
   curve would read as a second event. */
.reply-composer__cancel {
  margin-top: 8px;
  margin-left: auto;
  padding: 3px 8px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  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),
    color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo);
}

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

@media (hover: hover) {
  .reply-composer__send-wrap--open .reply-composer__cancel:hover {
    color: var(--text);
    background: var(--paper-2);
  }
}

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

.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: the "Text gone" badge + de-emphasised quote ── */

/* A comment whose text is gone (#212). --warn / --badge-bg-warn so it reads as
   "something needs attention" without the severity of an error: nothing is
   lost, the comment and its saved quote are both still here.

   A modifier on .thread-card__badge rather than a class of its own. It used to
   restate every one of that rule's declarations, so the badges drifted apart by
   default — this one had no icon and no title while the two beside it had
   both. */
.thread-card__badge--orphan {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  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;
}

/* The document-level controls, above the list rather than in a card, because
   they are about the document rather than any one conversation. Comment on
   document sits on the left; the mute stays right-aligned, away from the cards'
   own left-aligned content. */
.thread-cards__doc-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 4px;
  padding: 0 8px 6px;
}

/* The card-footer actions sit in a tight footer row, so 2px of vertical
   padding is enough there. Up here they stand alone, and the hover fill
   needs the same height as the pill beside it. */
.thread-cards__doc-actions .thread-card__action {
  padding: 4px 8px;
}

/* The one entry point for a whole-document review: a tinted pill, as in the
   Paper mock ("Doc comments 1"), so it reads as the panel's primary action
   rather than one more footer link. */
.thread-cards__doc-comment {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  border: none;
  border-radius: var(--rounded-sm);
  background: var(--accent-soft);
  font-size: 11px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.thread-cards__doc-comment svg {
  flex-shrink: 0;
}

.thread-cards__doc-comment:hover {
  background: color-mix(in oklch, var(--accent-soft), var(--accent) 12%);
}

/* A mute that is ON is a state, not an action, so it reads at the same weight
   as the other card badges rather than as a button waiting to be pressed. */
.thread-card__action--muted {
  color: var(--text-3);
}

/* ── Comments Empty State ── */

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

/* The empty state now sits inside the list wrapper, so the document-level mute
   renders above it as it does above a populated list (#269). The wrapper fades
   itself in, and two fades over one element read as a stutter. */
.thread-cards--empty .comments-empty {
  animation: none;
}

.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 ──
   A rail down the left: a filled dot per version, a ring on the one in the
   editor. The line is drawn in two pieces per row, above and below the dot,
   so it never shows through the ring (the panel is glass, so the ring cannot
   hide it with an opaque fill). */

.version-timeline {
  --rail: 12px;
  --dot-cy: 12px;                /* dot centre, on the first line of text */
  list-style: none;
  margin: 0;
  padding: 4px 0;
}

.version-item {
  --dot-r: 4px;
  position: relative;
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  column-gap: 4px;
  padding-bottom: 10px;
}

.version-item--active {
  --dot-r: 6px;
}

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

.version-item::before,
.version-item::after {
  content: '';
  position: absolute;
  left: calc(var(--rail) / 2 - 0.5px);
  width: 1px;
  background: var(--line-2);
  pointer-events: none;
}

.version-item::before {
  top: 0;
  height: calc(var(--dot-cy) - var(--dot-r));
}

.version-item::after {
  top: calc(var(--dot-cy) + var(--dot-r));
  bottom: 0;
}

.version-item:first-child::before,
.version-item:last-child::after {
  display: none;
}

.version-item__dot {
  justify-self: center;
  width: calc(var(--dot-r) * 2);
  height: calc(var(--dot-r) * 2);
  margin-top: calc(var(--dot-cy) - var(--dot-r));
  border-radius: 50%;
  background: color-mix(in srgb, var(--text-3) 70%, transparent);
  box-sizing: border-box;
}

.version-item--active .version-item__dot {
  background: transparent;
  border: 2px solid var(--accent);
}

.version-item__content {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  /* It's a button: strip the default chrome, then add a focus ring. */
  padding: 4px 8px 6px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

.version-item__content:hover {
  background: var(--badge-bg-mute);
}

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

.version-item__eyebrow {
  font-size: 10px;
  font-weight: 600;
  line-height: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.version-item__headline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 6px;
}

.version-item__message {
  min-width: 0;
  font-size: 13px;
  line-height: 19px;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.version-item--current .version-item__message,
.version-item--active .version-item__message {
  color: var(--text);
}

.version-item__chip {
  padding: 0 6px;
  border-radius: 5px;
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  white-space: nowrap;
}

.version-item__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0 6px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 15px;
  color: var(--text-4);
}

.version-item--current .version-item__meta,
.version-item--active .version-item__meta {
  color: var(--text-2);
}

/* ── Focus ── */

.context-panel__tab:focus-visible,
.outline__item:focus-visible,
.thread-card__action:focus-visible,
.thread-cards__doc-comment: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);
}

/* ── Orphans, under the version they were written against (#212) ──

   A comment whose quote can no longer be found is not deleted and is not drawn
   in the document — there is nowhere honest to put it. It goes here, named for
   the commit it WAS about, which is a true thing to say about it.

   `<details>`, not a hand-rolled disclosure. It comes with the open/closed
   state, the keyboard, and the right role for free, and this is the shape those
   were built for. Collapsed by default: history is here to be reachable, not to
   be in the way of the conversation above it. */
.orphan-section {
  border-top: 1px solid var(--line);
}

.orphan-section__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 18px;
  background: var(--paper-2);
  cursor: pointer;
  list-style: none;
  font-family: var(--font);
  font-size: 12px;
  line-height: 16px;
  color: var(--text-2);
}

/* Safari draws its own triangle through ::-webkit-details-marker, which sits
   outside the flex row and breaks the lane the three labels share. */
.orphan-section__head::-webkit-details-marker { display: none; }

/* The disclosure triangle, drawn rather than inherited so it can be placed in
   the row like everything else. Rotates instead of swapping glyphs, so the
   open and closed states are one element and cannot drift apart. */
.orphan-section__head::before {
  content: "";
  width: 0;
  height: 0;
  flex-shrink: 0;
  border-left: 5px solid var(--text-3);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.orphan-section[open] > .orphan-section__head::before {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
  .orphan-section__head::before { transition: none; }
}

.orphan-section__when {
  font-weight: 600;
}

/* Monospace, because a sha is read character by character when it is read at
   all — and it is the half somebody copies. */
.orphan-section__sha {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
}

.orphan-section__count {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-3);
}

@media (hover: hover) {
  .orphan-section__head:hover { background: var(--paper-3); }
}

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

/* ── Document review modal (#384) ──
   The writing surface for a whole-document review or a reply to one
   (DocumentCommentModal.tsx). Above popovers (1000) and below the tooltip tier
   (1100), so its own toolbar labels still show over it. */

.doc-review-modal__backdrop {
  position: fixed;
  inset: 0;
  z-index: 1050;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.5);
  animation: fadeIn var(--duration-fast) var(--ease-out-expo);
}

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

/* The mention dropdown mounts at <body> with the document's z-index (100),
   which would put it under this modal. While the modal is open it rises above
   it — and stays under the tooltips. */
body:has(.doc-review-modal) .mention-suggestion {
  z-index: 1075;
}

.doc-review-modal {
  display: flex;
  flex-direction: column;
  width: min(680px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  font-family: var(--font);
  color: var(--text);
  animation: fadeUp var(--duration-entrance) var(--ease-out-expo);
}

.doc-review-modal__header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}

.doc-review-modal__heading-text {
  flex: 1;
  min-width: 0;
}

.doc-review-modal__heading-text > div {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.doc-review-modal__title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
}

.doc-review-modal__status,
.doc-review-modal__subtitle {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
}

.doc-review-modal__subtitle {
  margin: 2px 0 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.doc-review-modal__close,
.doc-review-modal__tool,
.doc-review-modal__heading-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
}

.doc-review-modal__close {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

.doc-review-modal__toolbar {
  position: relative;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px;
  padding: 6px 16px;
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.doc-review-modal__tool {
  width: 28px;
  height: 26px;
}

.doc-review-modal__heading-trigger {
  gap: 4px;
  height: 26px;
  padding: 0 8px;
  font-family: var(--font);
  font-size: 12px;
  color: var(--text-2);
}

.doc-review-modal__tool[aria-pressed='true'],
.doc-review-modal__heading-trigger[aria-expanded='true'] {
  background: var(--accent-soft);
  color: var(--accent);
}

@media (hover: hover) {
  .doc-review-modal__close:hover,
  .doc-review-modal__tool:hover,
  .doc-review-modal__heading-trigger:hover,
  .doc-review-modal__heading-item:hover {
    background: var(--paper-3);
    color: var(--text);
  }
}

.doc-review-modal__close:focus-visible,
.doc-review-modal__tool:focus-visible,
.doc-review-modal__heading-trigger:focus-visible,
.doc-review-modal__heading-item:focus-visible,
.doc-review-modal__btn:focus-visible,
.doc-review-modal__link-field:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.doc-review-modal__divider {
  width: 1px;
  height: 16px;
  margin: 0 6px;
  background: var(--line);
}

.doc-review-modal__format {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-3);
}

.doc-review-modal__heading {
  position: relative;
}

.doc-review-modal__heading-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  min-width: 140px;
  padding: 4px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--rounded-lg);
  box-shadow: var(--shadow-md);
}

.doc-review-modal__heading-item {
  padding: 6px 8px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-2);
  font-family: var(--font);
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.doc-review-modal__heading-item[aria-checked='true'] {
  color: var(--accent);
  font-weight: 600;
}

.doc-review-modal__link-field {
  width: 200px;
  height: 26px;
  margin-left: 6px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--paper);
  color: var(--text);
  font-family: var(--mono);
  font-size: 12px;
}

.doc-review-modal__link-field[aria-invalid='true'] {
  border-color: var(--err);
}

/* The editor: a fixed writing area that scrolls, set in the reading
   renderer's typography (.markdown-body below) so a review looks the same
   written as read. */
.doc-review-modal__body {
  height: 440px;
  max-height: calc(100vh - 280px);
  overflow-y: auto;
  padding: 22px 28px;
}

.doc-review-modal__body .ProseMirror {
  min-height: 100%;
  outline: none;
}

.doc-review-modal__body .ProseMirror p.is-editor-empty:first-child::before {
  content: attr(data-placeholder);
  float: left;
  height: 0;
  color: var(--text-4);
  pointer-events: none;
}

.doc-review-modal__error {
  margin: 0 20px 12px;
  padding: 8px 12px;
  border-radius: var(--rounded-md);
  background: var(--badge-bg-err);
  color: var(--err);
  font-size: 12px;
}

.doc-review-modal__footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 20px;
  border-top: 1px solid var(--line);
}

.doc-review-modal__count {
  margin-right: auto;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
}

.doc-review-modal__count--over {
  color: var(--err);
}

.doc-review-modal__confirm {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  font-size: 13px;
}

.doc-review-modal__confirm > span {
  margin-right: auto;
  font-weight: 600;
}

.doc-review-modal__btn {
  padding: 6px 12px;
  border: 1px solid transparent;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-3);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}

@media (hover: hover) {
  .doc-review-modal__btn:hover:not(:disabled) {
    background: var(--paper-3);
    color: var(--text-2);
  }
}

.doc-review-modal__btn--primary {
  background: var(--accent);
  border-color: var(--accent-dim);
  color: var(--on-accent);
}

@media (hover: hover) {
  .doc-review-modal__btn--primary:hover:not(:disabled) {
    background: var(--accent-dim);
    color: var(--on-accent);
  }
}

.doc-review-modal__btn--primary:disabled {
  opacity: 0.5;
  cursor: default;
}

.doc-review-modal__btn--danger {
  border-color: var(--err);
  color: var(--err);
}

/* ── Markdown body (#384) ──
   Shared by the reading renderer (MarkdownBody) and the writing modal's
   editor, so a review reads the way it was written. */

.markdown-body {
  font-size: 15px;
  line-height: 24px;
  color: var(--text);
  overflow-wrap: anywhere;
}

.markdown-body > :first-child,
.markdown-body .ProseMirror > :first-child {
  margin-top: 0;
}

.markdown-body p,
.markdown-body ul,
.markdown-body ol,
.markdown-body blockquote,
.markdown-body pre {
  margin: 0 0 12px;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3 {
  margin: 18px 0 8px;
  font-weight: 600;
  line-height: 1.35;
}

.markdown-body h1 { font-size: 18px; }
.markdown-body h2 { font-size: 16px; }
.markdown-body h3 { font-size: 15px; }

.markdown-body ul,
.markdown-body ol {
  padding-left: 22px;
}

.markdown-body li > p {
  margin: 0;
}

.markdown-body blockquote {
  padding-left: 12px;
  border-left: 3px solid var(--line-2);
  color: var(--text-2);
}

.markdown-body code {
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--badge-bg-mute);
  font-family: var(--mono);
  font-size: 0.9em;
}

.markdown-body pre {
  padding: 10px 12px;
  border-radius: var(--rounded-md);
  background: var(--paper-3);
  overflow-x: auto;
}

.markdown-body pre code {
  padding: 0;
  background: none;
}

.markdown-body a {
  color: var(--accent);
  text-decoration: underline;
}

/* ── Document reviews (#384) ──
   The Comments tab's "On the whole document" section, the review card's body
   (DocumentReviewCard.tsx) and the reading modal (ReviewReadingModal.tsx). */

.thread-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The Versions tab's eyebrow, in the muted ink: a label, not a state. */
.thread-section__title {
  margin: 4px 8px 0;
  font-size: 10px;
  font-weight: 600;
  line-height: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* The panel's reading size, smaller than the modal's: a card is a summary. */
.doc-review .markdown-body {
  font-size: 13px;
  line-height: 19px;
  color: var(--text-2);
}

.doc-review .markdown-body h1 { font-size: 15px; }
.doc-review .markdown-body h2 { font-size: 14px; }
.doc-review .markdown-body h3 { font-size: 13px; }

.doc-review .markdown-body h1,
.doc-review .markdown-body h2,
.doc-review .markdown-body h3 {
  margin: 10px 0 4px;
}

.doc-review .markdown-body p,
.doc-review .markdown-body ul,
.doc-review .markdown-body ol,
.doc-review .markdown-body blockquote,
.doc-review .markdown-body pre {
  margin-bottom: 8px;
}

.doc-review .markdown-body ul,
.doc-review .markdown-body ol {
  padding-left: 18px;
}

.doc-review .markdown-body blockquote {
  padding-left: 10px;
  border-left-width: 2px;
}

.thread-card--resolved .doc-review .markdown-body {
  color: var(--text-4);
}

/* Six lines, and the line box is fixed above so six lines is a known height. */
.doc-review__body--clamped {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  line-clamp: 6;
  max-height: calc(19px * 6);
  overflow: hidden;
}

.doc-review__toggle {
  margin-top: 4px;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
}

.doc-review__toggle:hover {
  text-decoration: underline;
}

.doc-review__open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  align-self: center;
  width: 18px;
  height: 18px;
  /* The browser's default button padding, 6px a side, would leave the icon
     6px of the 18 and squash it to that. */
  padding: 0;
  /* The name line's last item, pushed to the card's right edge. */
  margin-left: auto;
  border: none;
  border-radius: var(--rounded-sm);
  background: var(--accent-soft);
  color: var(--accent);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out-expo);
}

/* The byline takes the header's free width, so the name line reaches the right
   edge for the button and the version line gets the full width beneath it. */
.thread-card__byline:has(.doc-review__open) {
  flex: 1;
}

.doc-review__open:hover {
  background: color-mix(in oklch, var(--accent-soft), var(--accent) 12%);
}

.doc-review__toggle:focus-visible,
.doc-review__open:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.doc-review__status {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 6px 0 0;
  font-size: 11px;
  color: var(--text-3);
}

.doc-review__status--error {
  color: var(--err);
}

/* The writing modal's frame (.doc-review-modal), with a body that scrolls. */
.review-reading-modal__scroll {
  height: 440px;
  max-height: calc(100vh - 180px);
  overflow-y: auto;
  padding: 22px 28px;
}

.review-reading-modal__replies {
  margin: 20px 0 0;
  padding: 16px 0 0;
  border-top: 1px solid var(--line);
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.review-reading-modal__reply {
  display: flex;
  gap: 10px;
}

.review-reading-modal__reply-content {
  flex: 1;
  min-width: 0;
}

.review-reading-modal__reply .markdown-body {
  font-size: 13px;
  line-height: 19px;
}

/* "via <tool>" on a review an AI tool posted. Only the tool name is tagged;
   "via" stays plain label text. The tag uses the avatar's own pair
   (--accent-soft / --accent), so the two match in light and dark. */
.agent-mark {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
}

.agent-mark__via {
  font-size: 11px;
  color: var(--text-3);
  flex-shrink: 0;
}

/* Plain text, not a chip: the avatar mark already says a tool posted this.
   The name is whatever the tool called itself, so it gives way first. */
.agent-mark__tag {
  min-width: 0;
  font-size: 11px;
  font-weight: 500;
  color: var(--accent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The second line of a card's byline: the tool that posted it, then the
   version it was written on. */
.thread-card__meta {
  display: flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
}

.thread-card__meta .thread-card__origin {
  flex-shrink: 0;
}

.thread-card__meta-sep {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
  flex-shrink: 0;
}

/* A small mark on the author's avatar for a review a tool posted. */
.agent-avatar {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.agent-avatar__badge {
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 22%, var(--paper));
  border: 2px solid var(--paper);
}
/* ── 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 var(--doc-gutter-x) 96px;
}






}

.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 var(--doc-gutter-x); /* the shared doc 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 --doc-gutter-x 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 ──

   This class used to be load-bearing for dismissing the expanded-table overlay,
   and a note here said so. It is not any more (#291): the overlay's dismiss was
   hand-rolled because its own chrome was portalled to <body> and therefore
   OUTSIDE the popover, and the fix was to move the chrome inside it rather than
   to keep a list of chrome that counts as inside. `popover="auto"` does the
   dismissing now, and a bar that must be usable over an expanded table goes in
   the pop-out — see components/editor/chrome/chrome-host.ts — rather than being
   named in a selector. */
.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);
  /* 9px and 6px below, both LITERALS rather than tokens, and deliberately so.
     The floating chrome was reading as a chip: --rounded-lg (14px) on a 38px
     bar is most of a pill, and it wants to read as a toolbar. The approved mock
     tightens the family to 9/6, which is between --rounded-sm (6) and
     --rounded-md (10) and so cannot be spelled with the tokens as they stand.
     Editing the tokens instead would move every genuine chip, pill and badge in
     the app, which are the components those two values were chosen for. A third
     token for one family of two rules would be a token nothing else could ever
     use. */
  border-radius: 9px;
  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: 6px;
  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;
  }
}

/* The filter definitions WelcomeView renders for the wordmark: no box. */
.splash__filters {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

[data-theme='dark'] .splash__brand-text {
  /* Pressed into the paper, chosen 2026-10-30 from a Chrome render of every
     palette. The fill is light mode's ink (the accent with 35% of the text
     colour), so the word reads on every dark palette; the SVG filter in
     PressedInFilter.tsx then cuts it in, lit from the spotlight above. The
     previous fill was 85% paper and all but vanished. The period is inside
     this element, so it is cut in too. */
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: color-mix(in oklch, var(--accent-dim), var(--text) 35%);
  text-shadow: none;
  filter: url(#splash-pressed-in);
}

[data-theme='dark'] .splash__brand-period {
  /* The accent, cut in like the word. No glow: the filter works from the
     glyph's shape, and a halo around it would be cut in as well. */
  color: var(--accent);
  text-shadow: none;
}

[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);
  }
}

/* ── Desktop notifications status (Settings › Notifications) ──
   What the browser allows, as a quiet chip where the toggle would be: there is
   nothing to switch once permission is given or refused. */
.settings-status {
  padding: 3px 10px;
  border-radius: var(--rounded-pill);
  background: var(--paper-2);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  white-space: nowrap;
}

.settings-status--granted {
  color: var(--ok);
  background: color-mix(in oklch, var(--paper-2), var(--ok) 12%);
}

.settings-status--denied {
  color: var(--err);
  background: color-mix(in oklch, var(--paper-2), var(--err) 10%);
}

.settings-btn:disabled {
  opacity: 0.55;
  cursor: default;
}

/* ── Developer tab ──
   One padded column inside the group's card: what the tool does, its buttons,
   and its answer. The answer is a callout INSIDE that padding: the card clips
   its children to its rounded border, so a rail drawn at its edge was cut off. */
.settings-dev {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  background: var(--paper);
}

.settings-dev__text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-width: 62ch;
}

.settings-dev__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.settings-dev__result {
  margin: 0;
  padding: 8px 12px;
  border-left: 2px solid var(--settings-dev-tone, var(--ok));
  border-radius: 0 var(--rounded-sm) var(--rounded-sm) 0;
  background: color-mix(in oklch, var(--paper), var(--settings-dev-tone, var(--ok)) 8%);
  font-size: 12.5px;
  line-height: 18px;
  color: var(--text);
}

.settings-dev__result--warn { --settings-dev-tone: var(--warn); }
.settings-dev__result--error { --settings-dev-tone: var(--err); }

/* ── 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;
  }
}

/* ── AI tools (Settings › AI tools) ── */

.settings-ai__desc {
  margin: 0;
  max-width: 62ch;
  font-size: 13px;
  line-height: 20px;
  color: var(--text-2);
}

.settings-ai__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A tool names itself, so a long name wraps inside its column, never under the button. */
.settings-ai__name {
  line-height: 18px;
  overflow-wrap: anywhere;
}

/* Fixed, so every row's button lines up whatever the name's length. */
.settings-ai__slot {
  display: flex;
  justify-content: flex-end;
  width: 96px;
  flex-shrink: 0;
}

.settings-ai__disconnect {
  height: 28px;
  padding: 0 12px;
  line-height: 16px;
  color: var(--text);
}

.settings-ai__disconnect:disabled {
  opacity: 0.6;
  cursor: default;
}

.settings-ai__empty {
  margin: 0;
  padding: 14px;
  border: 1px dashed var(--line);
  border-radius: var(--rounded-lg);
  background: var(--paper);
  font-size: 12px;
  line-height: 18px;
  color: var(--text-2);
}

.settings-ai__empty code {
  font-family: var(--mono);
  color: var(--text);
  overflow-wrap: anywhere;
}
/* 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; }
}

/* ── Not on the team (/not-authorized) ──
   The same card; a title and a message instead of the step ledger, one
   full-width action and a quiet text link. */
.authgate--refused .authgate__wordmark {
  margin-bottom: 20px;
  font-size: 1.75rem;
}
.authgate__refused-title {
  margin: 0;
  text-align: center;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
}
.authgate__refused-message {
  margin: 10px 0 0;
  text-align: center;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-2);
}
.authgate__refused-message strong {
  font-weight: 600;
  color: var(--text);
}
.authgate__refused-divider {
  height: 0;
  margin: 20px 0;
  border: none;
  border-top: 1px solid var(--line);
}
.authgate__refused-action {
  display: block;
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--rounded-lg);
  background: var(--accent);
  color: var(--on-accent);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: box-shadow var(--duration-normal) var(--ease-out-expo);
}
.authgate__refused-action:hover { box-shadow: 0 6px 20px var(--accent-glow-strong); }
.authgate__refused-action:focus-visible,
.authgate__refused-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.authgate__refused-link {
  display: block;
  margin: 14px auto 0;
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 12px;
  color: var(--accent);
  cursor: pointer;
}
.authgate__refused-link:hover { text-decoration: underline; text-underline-offset: 2px; }

/* ── Boot splash (components/BootSplash.tsx) ──
   The sign-in card before it knows its content, drawn by the server so the
   first paint is never an empty page. Fixed over the app, which mounts and
   loads underneath it; the first page with something to show takes it down. */
.boot-splash {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

.boot-splash__card {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.boot-splash .authgate__wordmark {
  margin-bottom: 28px;
}

.boot-splash__track {
  position: relative;
  width: 120px;
  height: 3px;
  overflow: hidden;
  border-radius: 9999px;
  background: var(--accent-soft);
}

.boot-splash__sweep {
  position: absolute;
  inset: 0 auto 0 0;
  width: 44px;
  border-radius: inherit;
  background: var(--accent);
  animation: boot-splash-sweep 1.4s var(--ease-out-expo, ease-in-out) infinite;
}

@keyframes boot-splash-sweep {
  from { transform: translateX(-44px); }
  to   { transform: translateX(120px); }
}

/* Hidden, not absent: it is in the server's markup and shown by CSS alone,
   three seconds in, so a slow start explains itself before any JavaScript has
   run. A warm start never sees it. It takes no room until then, so the card
   is only the wordmark and the bar, and grows to fit the line as it fades in. */
.boot-splash__caption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  max-height: 0;
  padding-top: 0;
  overflow: hidden;
  text-align: center;
  opacity: 0;
  animation: boot-splash-caption 400ms var(--ease-out-expo, ease-out) 3s forwards;
}

@keyframes boot-splash-caption {
  to { max-height: 80px; padding-top: 22px; opacity: 1; }
}

.boot-splash__caption-title {
  margin: 0;
  font-size: 14px;
  font-weight: 500;
  line-height: 20px;
  color: var(--text);
}

.boot-splash__caption-body {
  margin: 0;
  font-size: 13px;
  line-height: 18px;
  color: var(--text-body);
}

@media (prefers-reduced-motion: reduce) {
  .boot-splash__sweep {
    animation: none;
    transform: translateX(38px);
  }
  /* Still appears at three seconds; it just does not fade. */
  .boot-splash__caption {
    animation-duration: 1ms;
  }
}

/* The sign-in card taking over from the boot splash (WelcomePage): the same
   card is already on screen, so nothing in it animates in again. */
.authgate--settled .authgate__card,
.authgate--settled .authgate__wordmark,
.authgate--settled .authgate__steps,
.authgate--settled .authgate__notice,
.authgate--settled .authgate__footer,
.authgate--settled .authgate__switch {
  animation: 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;
}

/* One grid cell holding the title and, when there is one, the gone badge.
   The badge used to be a grid item of its own, which the row's templates have
   no track for — it stretched to fill the next column and pushed the timestamp
   onto an implicit second row. */
.draft-row__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.draft-row__title {
  min-width: 0;
  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__head {
    flex: 1 1 auto;
    min-width: 0;
  }

  .draft-row__title {
    flex: 0 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);
}

/* A draft whose document the repository no longer has (#271). Renames made
   with Margin closed leave the row pointing at a path GitHub has moved.

   The row keeps working: the text lives in the editor's own storage under the
   old path, so opening it is how somebody gets their words back. What changes
   is that it stops pretending nothing happened. */
.draft-row__gone {
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: var(--rounded-sm);
  /* The repo's own warn signal, at a tint that sits under text rather than
     shouting: this is information, not an error the reader has to act on. */
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  color: var(--warn);
  font-size: 10.5px;
  white-space: nowrap;
}

/* The title dims rather than the whole row: the time and the breadcrumb are
   still true, and dimming everything would read as disabled — which it is not. */
.draft-row--gone .draft-row__title {
  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 by
     forgetting the row's padding, and once by treating this number as fixed and
     shrinking the glyphs to fit it, which is what produced #240:

       6px .margin-row padding-left + 3 x 18px glyph + 2 x 2px gap = 64px

     i.e. the cluster's right edge lands exactly on this 64px. Change any term
     and the geometry spec in e2e/margin-rail.authed.spec.ts fails, which is
     the point of it.

     18px is the design's figure (Paper, "Margin Tabs") and the whole budget is
     built around holding it:

       separation + row padding + 3 glyphs + 2 gaps + row padding <= --doc-gutter-x
       13 + 6 + 54 + 4 + 6 = 83px

     which is why --doc-gutter-x is 88px rather than the 72px it was. Two
     earlier attempts took that 72px as fixed and shrank the glyph into it: to
     11px, where the icon no longer fitted its own container (#240), and then to
     16px, which fitted but was still not the design. The gutter was the term
     that had to move, and --content-max-width moved with it so the reading
     column kept its measure.

     What must not give is the ratio. These are SQUARE icons in ROUND
     containers, so the container has to hold the icon's DIAGONAL, not its side:
     an 11px icon needs 7.8px of radius against the 9px an 18px circle gives,
     and a 12px icon needs 8.5px. The 11px circle this was squeezed to gave
     4.5px, so the corners crossed the edge by construction and no padding value
     could have moved them. */
  --margin-width: 64px;
  /* How far the rail is inset from the editor's right edge, and how far it
     slides from when it enters. It has to be at least the row's own padding, or
     the pill's right edge lands outside the editor — the overhang the geometry
     spec exists to catch. 6px is that minimum now the row pads 6px, and it
     leaves 18px between the text and the pill, where the connector needs 13. */
  --margin-gap: 6px;
  /* 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;
    /* The gutter's extra 16px a side exists to hold the marker cluster (see
       --margin-width). With no lane there is nothing to hold, so the reading
       column takes the width back. Set here rather than in tokens.css so the
       breakpoint that hides the lane and the breakpoint that narrows the gutter
       are the same line, and cannot drift apart. */
    --doc-gutter-x: 72px;
  }
}

.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;
  /* Symmetric, and 6px, per the design (Paper, "Margin Tabs"). It used to be
     `4px 8px 4px 4px`, short on the left because every pixel of left padding
     came off a glyph budget that no longer binds — the rail is sized to the
     cluster now, and the gutter to the rail. */
  padding: 4px 6px;
  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 + 9px (half of the 18px glyph row) = 13px from row top
     to glyph centerline. Subtract half the 1px line height, which rounds to 13.
     Visually centered against the glyph row. */
  top: 13px;
  /* 10px of line, with the dot beyond it, inside the 18px between the text and
     the pill (see --margin-gap). */
  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 — 13px glyph centerline less half the dot's 5px
     height. */
  right: calc(100% + 8px);
  top: 10.5px;
  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;
  /* 2px, per the design (Paper, "Margin Tabs"). Three 18px glyphs plus two
     gaps is 58px, and the row's 6px left padding puts the cluster's right edge
     on the 64px rail exactly. */
  gap: 2px;
}

/* ── 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`. */

/* An 18px circle holding an 11px or 12px icon, per the design (Paper, "Margin
   Tabs").

   The size is load-bearing rather than decorative: these are SQUARE icons in
   ROUND containers, so the circle has to hold the icon's DIAGONAL, not its
   side. 18px gives 9px of radius against the 7.8px an 11px icon needs and the
   8.5px a 12px one does. It was squeezed to an 11px circle with a 9px icon to
   fit a 44px rail, and at that size the corners sat 5.2px out against 4.5px of
   radius: they crossed the edge by construction, which is what #240 reported.
   See --margin-width for what had to move to hold 18px. */
.margin-glyph {
  /* Grid centering rather than inline-flex: flexbox's `align-items: center`
     centers the LINE BOX, which leaves the icon sitting visually low, because
     the box carries the font's asymmetric ascender and descender metrics.
     `place-items: center` aligns the rendered content centroid. */
  display: inline-grid;
  place-items: center;
  position: relative;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--text-2);
  font-family: var(--mono);
  /* line-height: 1 collapses the line-box to the actual 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 tints its circle by hue, at the
   16% the design specifies (#9DAFD629 and friends are that alpha over the
   signal colour). No ring: at 18px the fill carries the edge on its own, and
   the ring the 11px version needed was what the icon's corners collided with. */
.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);
}

/* Resolved drops the hue from both the fill and the mark: the plain ground
   where an open thread carries its signal tint. */
.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;
  }
}
/* BOTTOM-RIGHT OF THE VIEWPORT, 16px in, on every surface.

   It used to hang under the top-right header of whichever surface was showing,
   which took a hand-measured header height per surface (#302) and still landed
   the toast inside the right-hand panel, over its cards. The bottom-right
   corner has one thing to clear: the phone's context tab bar, which declares
   `--app-bottom-inset` itself (context-tab-bar.css). Nothing else up or down
   the app needs to know a toast exists.

   Note: e2e/toast-placement.authed.spec.ts measures the corner on each surface
   and the gap above the tab bar on a phone. */
.toast-container {
  position: fixed;
  bottom: calc(var(--app-bottom-inset, 0px) + 16px);
  right: 16px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  z-index: 900;
  pointer-events: none;
}

/* ONE hairline. It used to be a 1px border AND --shadow-md, whose last layer is
   a 1px ring of its own, so every toast was outlined twice, and a success or
   error toast drew its coloured border inside a grey ring. The kind is now the
   glyph's job, not the outline's. */
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 8px 9px 12px;
  border-radius: var(--rounded-md);
  font-family: var(--font);
  font-size: 13px;
  color: var(--text);
  background: var(--paper);
  border: 1px solid color-mix(in oklab, var(--text) 9%, transparent);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
  pointer-events: auto;
  animation: toastIn 200ms var(--ease-out-expo) forwards;
}

/* Lifted off the dark page rather than cut into it: --paper is the darkest
   surface in the dark themes, so the card sits between the two raised ones. */
[data-theme='dark'] .toast {
  background: color-mix(in oklab, var(--paper-2), var(--paper-3));
  border-color: color-mix(in oklab, var(--text) 7%, transparent);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}

.toast--exit {
  animation: toastOut 200ms var(--ease-out-expo) forwards;
}

.toast__icon {
  flex-shrink: 0;
}

.toast__icon--success { color: var(--ok); }
.toast__icon--error   { color: var(--err); }
.toast__icon--info    { color: var(--text-3); }

.toast__text {
  line-height: 18px;
}

.toast__action {
  padding: 2px 6px;
  font-family: var(--font);
  font-size: 12px;
  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;
}

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

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

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

@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) ──
   Full width, with the same 16px gutter. Above the context tab bar where there
   is one: the bar sets --app-bottom-inset, safe-area inset included. */
@media (max-width: 640px) {
  .toast-container {
    left: 16px;
    right: 16px;
    bottom: calc(var(--app-bottom-inset, env(safe-area-inset-bottom, 0px)) + 12px);
    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;
}

/* ── Show / hide toggle ───────────────────────────────────────────────
   An eye, always shown, so a row says whether its repo is in view without
   being hovered: crossed out and quiet when hidden, open and in the accent
   when shown. It was a layers glyph that only appeared on hover. */

.repo-switcher__merge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  border: none;
  border-radius: var(--rounded-sm);
  background: transparent;
  color: var(--text-4);
  cursor: pointer;
  transition:
    color var(--duration-fast) var(--ease-out-expo),
    background var(--duration-fast) var(--ease-out-expo);
}

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

.repo-switcher__merge--on {
  color: var(--accent);
}

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

/* ── 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) {
  /* What a toast stays above on a phone (toast.css). Declared here, by the
     thing in the way, so the toast never has to know this bar exists. */
  :root:has(.context-tab-bar) {
    --app-bottom-inset: calc(var(--mobile-tabbar-height) + env(safe-area-inset-bottom, 0px));
  }

  .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;
}
