/* ============================================================
 * Routario design tokens (v0.2)
 *
 * Canonical names live in docs/PRIMITIVES.md § Tokens. Mirrored
 * verbatim from docs/facelift-mocks/shell.css. Do not invent new
 * tokens here — extend PRIMITIVES.md first.
 *
 * The trailing alias block exists so existing CSS rules referencing
 * old token names keep rendering visually until Steps 2-7 of the
 * facelift migration replace them. Aliases are dead code as soon as
 * their last consumer is migrated; remove them in a cleanup pass.
 * ============================================================ */

:root {
    /* Paper + surfaces */
    --bg-page: #f2eee6;
    --bg-surface: #ffffff;          /* CARDS sit on paper, not the same gray */
    --bg-subtle: #f9f6f0;           /* hover, zebra, ghost-button bg */
    --bg-elevated: #ffffff;
    --bg-sidebar: #e7dfd2;          /* warmer than before, paper-adjacent */
    --bg-sidebar-active: #ffffff;   /* active nav item floats on paper */

    /* Ink */
    --text-primary: #16202a;        /* matches design system "Ink" */
    --text-secondary: #586977;
    --text-muted: #8a96a3;
    --text-inverse: #ffffff;

    /* Borders */
    --border: rgba(22, 32, 42, 0.08);
    --border-subtle: rgba(22, 32, 42, 0.06);
    --border-strong: rgba(22, 32, 42, 0.16);
    --border-input: rgba(22, 32, 42, 0.14);

    /* Brand color register — terracotta is the primary action */
    --accent: #b95b37;
    --accent-hover: #a44e2d;
    --accent-soft: rgba(185, 91, 55, 0.08);
    --accent-strong: #16202a;       /* ink panel CTAs */
    --accent-ember: #de9e47;        /* gold, used in mark gradient */

    /* Steel-blue is structural only — sidebar active indicator, focus ring */
    --steel: #6e8798;
    --steel-soft: rgba(110, 135, 152, 0.12);

    --forest: #2f6b57;              /* success, secondary accent */
    --forest-soft: rgba(47, 107, 87, 0.10);

    /* AI / LLM register — used sparingly */
    --violet: #7c3aed;
    --violet-soft: rgba(124, 58, 237, 0.08);
    --violet-strong: rgba(124, 58, 237, 0.18);

    /* Status */
    --success: #2f6b57;
    --warning: #c98810;
    --error: #b3261e;

    /* Type — Manrope loaded via <link> in base.html / auth_layout.html */
    --font-sans: "Manrope", ui-sans-serif, system-ui, sans-serif;
    --font-display: "Fraunces", Georgia, serif;     /* not used in product, present for slides */
    --font-mono: ui-monospace, "JetBrains Mono", monospace;

    /* Radii — product side: tight */
    --radius-sm: 6px;
    --radius: 8px;
    --radius-md: 10px;
    --radius-lg: 14px;

    /* Shadows */
    --shadow-card: 0 1px 2px rgba(22, 32, 42, 0.04), 0 2px 8px rgba(22, 32, 42, 0.04);
    --shadow-pop: 0 8px 24px rgba(22, 32, 42, 0.08), 0 2px 6px rgba(22, 32, 42, 0.06);
    --shadow-modal: 0 24px 60px rgba(22, 32, 42, 0.18);

    /* Spacing scale (4 baseline) */
    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 20px;
    --sp-6: 24px;
    --sp-8: 32px;
    --sp-10: 40px;
    --sp-12: 48px;

    /* Density — compact is the default. Comfortable opt-in via .density-comfortable */
    --row-h: 28px;
    --row-pad-y: 4px;
    --row-pad-x: 10px;
    --card-pad-y: 10px;
    --card-pad-x: 14px;
    --section-gap: 12px;
    --type-step: -1px;

    /* ── Backward-compat aliases (Steps 2-7 retire these progressively) ── */
    --ok: var(--success);
    --color-ok: var(--success);
    --color-warn: var(--warning);
    --color-err: var(--error);
    --color-danger: var(--error);
    --ai-accent: var(--violet);
    --ai-accent-soft: var(--violet-soft);
    --ai-accent-strong: var(--violet-strong);
    --surface-1: var(--bg-elevated);
    --surface-2: var(--bg-subtle);
    --bg-input: var(--bg-elevated);
    /* Backfilled from template usage — implemented by claude code, to be reviewed by claude design */
    --surface-secondary: var(--bg-subtle);
    --bg-code: #1a1a2e;                          /* no new equivalent — keep literal */
    --border-default: var(--border-strong);
}

.density-comfortable {
    --row-h: 36px;
    --row-pad-y: 8px;
    --row-pad-x: 12px;
    --card-pad-y: 14px;
    --card-pad-x: 18px;
    --section-gap: 20px;
    --type-step: 0;
}

/* ---------- Density: comfortable per-component overrides --------------
 * Compact density is the default. Comfortable mode is opt-in by adding
 * `.density-comfortable` to a page or container. The token cascade
 * above only re-scales rows/cards/sections; these rules also re-scale
 * the inbox row, KPI strip, greeting block, section heads, datagrid
 * header, page padding, and table headers. Lifted verbatim from
 * `docs/facelift-mocks/v0.2/shell.css:121-135`. */
.density-comfortable .panel__bd { padding: 16px; }
.density-comfortable .panel__hd { padding: 12px 18px; }
.density-comfortable .inbox-item { padding: 14px 18px; }
.density-comfortable .inbox-item.is-ai { padding-left: 16px; }
.density-comfortable .inbox-item__sub { -webkit-line-clamp: 2; line-clamp: 2; font-size: 13px; }
.density-comfortable .inbox-item__title { font-size: 14px; }
.density-comfortable .kpi { padding: 18px 20px; }
.density-comfortable .kpi__value { font-size: 26px; }
.density-comfortable .kpi-grid { gap: 14px; margin-bottom: 20px; }
.density-comfortable .greet { font-size: 26px; margin-bottom: 6px; }
.density-comfortable .greet-sub { margin-bottom: 20px; }
.density-comfortable .section-head { margin: 24px 0 10px; }
.density-comfortable .app-page { padding: 28px 40px 80px; }
.density-comfortable .ds-tbl thead th { padding: 10px var(--row-pad-x); font-size: 11px; }

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
[hidden]{display:none!important}
/* scrollbar-gutter lives on the actual scroll container, not on html:
   .ds-main__scroll owns it on desktop, body owns it on mobile (≤1280px,
   set in the @media block below). Keeping it on html previously cost
   8px on the right of the topline because the gutter was reserved even
   though html never scrolls on desktop. */
html{font-size:16px;scroll-behavior:smooth}
/* Reset the no-CSS fallback styles from base.html / auth_layout.html that
   set body{max-width:880px;margin:1.5rem auto;padding:0 1rem} so the page
   doesn't render in a narrow centred column with a top gap when style.css
   loads successfully (regression from #544 — inline fallback persisted
   because nothing here re-set it). */
body{font-family:var(--font-sans);font-size:.938rem;color:var(--text-primary);background:var(--bg-page);line-height:1.6;max-width:none;margin:0;padding:0}
/* ── Links: single accent color, no visited-state differentiation. Modern
   app convention; the "purple-after-click" pattern reads as web-1.0.
   Underline only on hover for explicit affordance. Buttons styled as links
   opt out via .btn / .btn-link classes. */
a{color:var(--steel);text-decoration:none;cursor:pointer}
a:hover{text-decoration:underline}
a:visited{color:var(--steel)}
a:focus-visible{outline:2px solid var(--steel);outline-offset:2px;border-radius:2px}
/* Row-link pattern: when an entire <tr class="row-link"> is the click
   target, the inner <a> stays in the markup for keyboard nav + ⌘+click
   in new tab, but visually inherits the row's text color so the row reads
   as one click surface (not "a link inside a row"). */
.ds-tbl tr.row-link{cursor:pointer}
.ds-tbl tr.row-link:hover td{background:var(--bg-subtle)}
.ds-tbl tr.row-link a,
.ds-tbl tr.row-link a:visited{color:inherit;text-decoration:none}
.ds-tbl tr.row-link a:hover{text-decoration:none}
.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}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:var(--bg-page)}
::-webkit-scrollbar-thumb{background:var(--bg-surface);border-radius:4px}
::-webkit-scrollbar-thumb:hover{background:var(--text-secondary)}

/* TOP BAR */
.ds-topbar{display:none}
.ds-topbar__t{font-size:1.1rem;font-weight:700;color:var(--text-primary)}
.ds-topbar__t span{color:var(--steel);margin-right:8px}
.ds-topbar__v{font-size:.813rem;color:var(--text-secondary);padding:2px 8px;background:var(--bg-surface);border-radius:4px}

/* SHELL GRID — sidebar + main in two columns. On desktop (>1280px) the
   shell is locked to viewport height and the .ds-main__scroll wrapper
   inside .ds-main owns the vertical scrollbar, so the topline above it
   spans the full column width with no scrollbar adjacent to the bell.
   Mobile (≤1280px) overrides this in the @media block below — body
   scrolls naturally and topline is position:fixed at viewport top. */
.app-shell{display:grid;grid-template-columns:240px 1fr;height:100vh}
.app-shell--collapsed{grid-template-columns:88px 1fr}

/* LEFT SIDEBAR */
.ds-sb{position:sticky;top:0;height:100vh;width:240px;background:var(--bg-sidebar);border-right:1px solid var(--border-subtle);overflow-y:hidden;z-index:1000;padding:0;display:flex;flex-direction:column;flex-shrink:0}
.ds-sb__nav{flex:1;overflow-y:auto;padding-bottom:8px}
.ds-sb__footer{flex-shrink:0;border-top:1px solid var(--border-subtle);padding:8px 0}
.ds-sb__brand{height:56px;display:flex;align-items:center;padding:0 1.5rem 0 19px;flex-shrink:0}.ds-sb__brand .ds-sb__brand-link{display:flex;align-items:center;gap:11px;text-decoration:none;margin:0;padding:0}.ds-sb__mark{width:28px;height:28px;flex-shrink:0}.ds-sb__t{font-size:14px;font-weight:700;letter-spacing:-0.01em;color:var(--text-primary);margin:0!important;line-height:1}.ds-sb__sub{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);margin-top:.25rem;padding:12px 12px 4px}
.ds-sb a{display:flex;align-items:center;gap:10px;padding:6px 12px;color:var(--text-secondary);text-decoration:none;font-size:13px;font-weight:500;border-radius:6px;margin:1px 4px;transition:all .2s ease}
.ds-sb a:hover{background:rgba(255,255,255,0.55);color:var(--text-primary)}
.ds-sb a.active{background:var(--bg-sidebar-active);color:var(--text-primary);font-weight:600;box-shadow:0 0 0 1px var(--border);border-radius:6px}
.ds-sb a[data-tooltip]{position:relative}
.app-shell--collapsed .ds-sb a.active{border-radius:6px}
.app-nav__icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;border-radius:.7rem;background:transparent;color:var(--text-muted);flex-shrink:0}
.lucide-svg{width:1rem;height:1rem;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
.app-nav__glyph{width:1rem;height:1rem}
/* Inbox sidebar card — focal-point treatment matching v0.2 mock (docs/facelift-mocks/v0.2/shell.css). White card, thin neutral border, accent ring on active. The icon sits in a solid 28x28 terracotta tile with a white inbox glyph inside. */
.ds-sb__inbox-card{display:flex;align-items:center;gap:10px;margin:8px 12px 4px;padding:10px 12px;background:#fff;border:1px solid var(--border);border-radius:var(--radius);color:var(--text-primary);text-decoration:none;cursor:pointer;transition:border-color 120ms}
.ds-sb__inbox-card:hover{border-color:var(--border-strong);color:var(--text-primary)}
.ds-sb__inbox-card.is-active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
.ds-sb__inbox-card__icon{width:28px;height:28px;border-radius:7px;background:var(--accent);color:#fff;display:grid;place-items:center;flex-shrink:0}
.ds-sb__inbox-card__icon svg{width:16px;height:16px;color:#fff}
.ds-sb__inbox-card__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.ds-sb__inbox-card__label{font-size:13px;font-weight:600;color:var(--text-primary);line-height:1.3}
.ds-sb__inbox-card__meta{font-size:11px;color:var(--text-secondary);margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ds-sb__inbox-card.is-zero .ds-sb__inbox-card__meta{color:var(--text-muted)}
.ds-sb__inbox-card__badge{margin-left:auto;background:var(--accent);color:#fff;font-size:11px;font-weight:700;padding:1px 8px;border-radius:999px;line-height:1.5;flex-shrink:0}
/* Collapsed shell: hide the text body and the count text; demote the badge to a small overlay dot anchored on the icon tile's top-right corner so the count is still visible at a glance. */
.app-shell--collapsed .ds-sb__inbox-card{position:relative;justify-content:center;padding:10px}
.app-shell--collapsed .ds-sb__inbox-card__body{display:none}
.app-shell--collapsed .ds-sb__inbox-card__badge{position:absolute;top:4px;right:6px;margin-left:0;min-width:16px;height:16px;padding:0 4px;font-size:10px;line-height:1;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 0 0 2px var(--bg-sidebar)}

/* Sidebar nav-item live badge — generic LiveBus consumer slot.
   Subtle by design: Inbox card is the focal point, nav badges are quiet
   informational counts. The dot appears next to the count when overdue>0
   to flag urgency without changing the visual weight of the count itself. */
.ds-sb__nav-badge{margin-left:auto;display:inline-flex;align-items:center;gap:4px;padding:2px 8px;background:rgba(22,32,42,.06);color:var(--text-muted);font-size:11px;font-weight:600;line-height:1;border-radius:999px;flex-shrink:0}
.ds-sb__nav-badge[hidden]{display:none}
.ds-sb a.active .ds-sb__nav-badge{background:rgba(22,32,42,.1);color:var(--text-primary)}
.ds-sb__nav-badge__dot{width:6px;height:6px;border-radius:50%;background:var(--accent);flex-shrink:0}
.ds-sb__nav-badge__dot[hidden]{display:none}
.app-shell--collapsed .ds-sb__nav-badge{position:absolute;top:4px;right:4px;margin-left:0;padding:0 4px;min-width:16px;height:16px;font-size:10px;justify-content:center;box-shadow:0 0 0 2px var(--bg-sidebar)}
.app-shell--collapsed .ds-sb__nav-badge__count{display:inline}
.app-shell--collapsed .ds-sb__nav-badge__dot{position:absolute;top:-2px;right:-2px;width:6px;height:6px;box-shadow:0 0 0 2px var(--bg-sidebar)}

/* Sidebar project switcher — sits between brand band and first nav section */
.ds-sb__project-switch{flex-shrink:0;padding:10px 16px 12px;border-bottom:1px solid var(--border-subtle)}
.ds-sb__proj-wrap{position:relative}
.ds-sb__proj-btn{display:flex;align-items:center;width:100%;padding:8px 10px 8px 12px;font-family:var(--font-sans);font-size:.875rem;font-weight:600;color:var(--text-primary);background:none;border:1px solid transparent;border-radius:8px;cursor:pointer;text-align:left;gap:6px;transition:background-color .15s ease,border-color .15s ease}
.ds-sb__proj-btn:hover{background-color:rgba(110,135,152,.1)}
.ds-sb__proj-btn:focus{border-color:var(--steel);box-shadow:0 0 0 2px rgba(110,135,152,.2);outline:none}
.ds-sb__proj-btn--static{cursor:default}
.ds-sb__proj-btn--static:hover{background:none}
.ds-sb__proj-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.ds-sb__proj-chevron{width:14px;height:14px;flex-shrink:0;transition:transform .15s ease}
.ds-sb__proj-btn[aria-expanded=true] .ds-sb__proj-chevron{transform:rotate(180deg)}
.ds-sb__proj-check{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;flex-shrink:0}
.ds-sb__proj-menu{min-width:0;z-index:1500}
.ds-sb__project-single{padding:8px 12px;font-size:.875rem;font-weight:600;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.app-shell--collapsed .ds-sb__project-switch{display:none}

/* MAIN CONTENT — grid handles offset, no margin-left needed.
   .ds-main is a vertical flex column on desktop: topline at top
   (flex-shrink:0, no longer sticky), .ds-main__scroll below it owns
   the vertical scrollbar. The topline therefore spans the full column
   width with the scrollbar living strictly below it. Mobile breakpoint
   resets this — body scrolls and topline is position:fixed at top.

   overflow:visible (not :hidden) because .app-sidebar-toggle docks at
   the sidebar/main seam via position:absolute; left:0; transform:
   translate(-50%) — its left half therefore extends ~16px past
   .ds-main's left edge into the sidebar column. overflow:hidden would
   clip that half (the bug fixed here). Vertical containment is still
   safe: heights are fully constrained by flex (topline 56px +
   .ds-main__scroll flex:1 with min-height:0 + overflow-y:auto), so
   there is no actual overflow inside .ds-main to leak. */
.ds-main{padding:0;min-width:0;display:flex;flex-direction:column;height:100vh;overflow:visible}
.ds-main__scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}
.app-page{max-width:1120px;padding:24px 32px 40px;box-sizing:border-box}
.app-page--wide{max-width:1440px}
.app-page--full{max-width:none}
/* Topline: lives at the top of .ds-main's flex column. Used to be
   position:sticky inside the body scroll; that put the scrollbar gutter
   visually adjacent to the bell (notification badge looked squeezed
   when the page overflowed). Now the scrollbar is on .ds-main__scroll
   below — topline spans the column edge-to-edge.

   position:relative is required so it stays the containing block for
   absolutely-positioned children — specifically .app-sidebar-toggle,
   which uses left:0; top:50% to dock on the sidebar/main seam. Without
   it the toggle button positions against <body> and floats away. */
.app-topline{position:relative;flex-shrink:0;background:var(--bg-page);display:flex;align-items:center;justify-content:space-between;gap:16px;height:56px;padding:0 24px;border-bottom:1px solid var(--border)}
.app-topline__start{display:flex;align-items:center;gap:12px;min-width:0}
.app-topline__meta{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-secondary)}
.app-topline__aux{font-size:.813rem;color:var(--text-secondary);display:flex;align-items:center;gap:8px;flex:1;justify-content:flex-end}
.app-topline__account{display:flex;align-items:center;gap:8px;margin-left:auto}
/* Account chip (avatar+name trigger with dropdown) */
.app-account-chip{position:relative}
.app-account-chip__trigger{display:inline-flex;align-items:center;gap:8px;padding:4px 8px 4px 4px;background:transparent;border:1px solid transparent;border-radius:999px;color:var(--text-primary);font-family:var(--font-sans);font-size:.875rem;font-weight:500;cursor:pointer;transition:background .15s ease,border-color .15s ease}
.app-account-chip__trigger:hover{background:rgba(255,255,255,.5);color:var(--text-primary)}
.app-account-chip__trigger:focus-visible{outline:2px solid var(--steel);outline-offset:2px}
.app-account-chip[data-open="true"] .app-account-chip__trigger{background:rgba(255,255,255,.6);border-color:var(--border)}
.app-account-chip__avatar{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:rgba(110,135,152,.2);color:var(--steel);font-size:.75rem;font-weight:700;line-height:1;flex-shrink:0}
.app-account-chip__name{max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-account-chip__chevron{display:inline-flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:transform .15s ease}
.app-account-chip__chevron .lucide-svg{width:14px;height:14px}
.app-account-chip[data-open="true"] .app-account-chip__chevron{transform:rotate(180deg)}
.app-account-chip__menu{position:absolute;top:calc(100% + 8px);right:0;min-width:220px;background:#fff;border:1px solid var(--bg-surface);border-radius:12px;box-shadow:0 12px 48px rgba(0,0,0,.12);z-index:2000;padding:6px;display:flex;flex-direction:column}
.app-account-chip__eyebrow{padding:8px 12px 6px;display:flex;flex-direction:column;gap:2px;cursor:default}
.app-account-chip__eyebrow-name{font-size:.875rem;font-weight:600;color:var(--text-primary);line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-account-chip__eyebrow-email{font-size:.75rem;color:var(--text-secondary);line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-account-chip__sep{height:1px;background:var(--bg-surface);margin:4px 0}
.app-account-chip__item{display:flex;align-items:center;gap:10px;width:100%;padding:8px 12px;background:transparent;border:none;border-radius:8px;color:var(--text-primary);font-family:var(--font-sans);font-size:.875rem;font-weight:500;text-align:left;text-decoration:none;cursor:pointer;transition:background .15s ease}
.app-account-chip__item:hover,.app-account-chip__item:focus{background:rgba(228,221,209,.65);color:var(--text-primary);outline:none}
.app-account-chip__item:focus-visible{outline:2px solid var(--steel);outline-offset:-2px}
.app-account-chip__item .btn__icon{display:inline-flex;color:var(--steel);flex-shrink:0}
.app-account-chip__menu form{margin:0}
.app-alert-stack{display:grid;gap:12px;margin-bottom:24px}
.app-mobile-menu-toggle{display:none!important}
.app-mobile-overlay{display:none}
.app-sidebar-toggle.app-sidebar-toggle{position:absolute;left:0;top:50%;transform:translate(-50%,-50%);z-index:1002;flex-shrink:0;background:#fff;border:1px solid rgba(228,221,209,.9);color:var(--text-secondary);box-shadow:0 6px 16px rgba(0,0,0,.08);display:inline-flex;align-items:center;justify-content:center;line-height:1;padding:0;box-sizing:border-box}
.app-sidebar-toggle.app-sidebar-toggle:hover:not(:disabled){background:#fff;color:var(--text-primary)}
.app-sidebar-toggle.app-sidebar-toggle .btn__icon{display:inline-flex;align-items:center;justify-content:center;line-height:0}
.app-page{display:flex;flex-direction:column;gap:24px}
.app-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}
.app-page__eyebrow{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-bottom:6px}
/* Page-chrome contract — scope slot. Lives directly above the H1; uses the
   .ds-breadcrumb primitive (normal-case, 13px, chevron separators). The
   section-root anchor inside (Rule 2) is not clickable, so it reads slightly
   lighter than the linked ancestors. */
.app-page__breadcrumb{margin-bottom:6px}
.app-page__breadcrumb .ds-breadcrumb__item--anchor{color:var(--text-muted);cursor:default}
/* Builder chrome — the 56px strip that replaces the standard page header on
   canvas-style editors (workflow / agent / module-spec). See Rule 13 of the
   page-chrome contract (docs/BUILDER_CHROME.md). Replaces the standard
   ``.app-page__header`` band; the canvas owns the rest of the viewport. */
.app-builder-strip{display:flex;align-items:center;gap:16px;height:56px;margin:0 -48px 24px;padding:0 48px;background:var(--bg-page);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:5}
.app-builder-strip__back{display:inline-flex;align-items:center;gap:6px;font-size:.813rem;font-weight:600;color:var(--text-secondary);text-decoration:none;flex:0 0 auto;padding:6px 10px;margin-left:-10px;border-radius:6px;transition:background-color 120ms}
.app-builder-strip__back:hover{background:var(--surface-hover);color:var(--text-primary)}
.app-builder-strip__back-glyph{width:16px;height:16px}
.app-builder-strip__back-label{white-space:nowrap}
.app-builder-strip__title-wrap{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:8px}
.app-builder-strip__title{margin:0;font-size:18px;font-weight:700;line-height:1.3;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;padding-right:2px}
.app-builder-strip__title--placeholder{color:var(--text-muted);opacity:.6;font-weight:500}
.app-builder-strip__dirty-dot{display:none;width:6px;height:6px;border-radius:50%;background:var(--accent);flex-shrink:0;margin-left:2px}
.app-builder-strip.is-dirty .app-builder-strip__dirty-dot{display:inline-block}
.app-builder-strip__actions{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.app-builder-strip__ai .app-builder-strip__ai-glyph{color:#7c3aed}
.app-builder-strip__ai:hover{background:rgba(124,58,237,.08)}
.app-builder-strip__save{min-width:80px}
@media (max-width: 768px){
  .app-builder-strip{margin:0 -16px 16px;padding:0 16px;gap:8px}
  .app-builder-strip__back-label{display:none}
}
.app-page__title{font-size:22px;line-height:1.2;letter-spacing:-0.01em;margin:0}
.app-page__subtitle{margin:8px 0 0;color:var(--text-secondary);max-width:760px}
.app-page__meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px;color:var(--text-secondary);font-size:.875rem}
.app-page__meta:empty{display:none}
.app-page__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
/* Multi-element groups inside .app-page__actions (e.g. a filter form with
   input + select + submit) must be inline-flex with the same gap so children
   align with sibling buttons and wrapping happens at the toolbar level — not
   inside the group. Codified in design_system.html section 19. */
.app-page__actions form{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
.app-page__actions form .ds-input{width:auto;min-width:200px}
.app-page__actions form select.ds-input{min-width:140px}
.app-tabs{display:flex;gap:8px;flex-wrap:wrap;margin-top:-8px}
.app-tab{display:inline-flex;align-items:center;gap:8px;padding:8px 12px;border-radius:999px;background:rgba(228,221,209,.65);color:var(--text-secondary);text-decoration:none;font-size:.875rem;font-weight:600;transition:all .15s ease}
.app-tab:hover{color:var(--text-primary);background:#fff}
.app-tab.active{background:#fff;color:var(--text-primary);box-shadow:0 1px 2px rgba(0,0,0,.05)}
.app-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}
.app-card-link{display:block;padding:18px;border:1px solid var(--bg-surface);border-radius:16px;background:#fff;color:inherit;text-decoration:none;box-shadow:0 1px 2px rgba(0,0,0,.04);transition:transform .15s ease,box-shadow .15s ease}
.app-card-link:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.08)}
.app-card-link__head{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.app-card-link__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border-radius:.9rem;background:rgba(110,135,152,.14);color:var(--steel);flex-shrink:0}
.app-card-link__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--steel);margin-bottom:6px}
.app-card-link__title{font-size:1rem;font-weight:700;margin-bottom:4px}
.app-card-link__desc{font-size:.875rem;color:var(--text-secondary)}
.app-meta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}
.app-meta{padding:14px 16px;background:#fff;border:1px solid var(--bg-surface);border-radius:12px}
.app-meta__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-secondary)}
.app-meta__value{margin-top:6px;font-family:var(--font-mono);font-size:.875rem;word-break:break-word}
.app-placeholder{padding:28px;background:#fff;border:1px solid var(--bg-surface);border-radius:16px;max-width:760px}
.app-placeholder__badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:999px;background:rgba(110,135,152,.15);color:var(--steel);font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:12px}
.app-placeholder__title{margin:0 0 8px;font-size:1.5rem}
.app-placeholder__desc{margin:0;color:var(--text-secondary);max-width:48rem}
.app-placeholder__list{margin-top:16px;padding-left:18px;color:var(--text-secondary)}
.app-frame{width:100%;height:min(78vh,980px);border:1px solid var(--bg-surface);border-radius:16px;background:#fff}
.ds-section{margin-bottom:48px;padding-top:12px;scroll-margin-top:72px}
.ds-section__title{font-size:1.25rem;font-weight:700;color:var(--text-primary);margin-bottom:24px}
.ds-section__desc{color:var(--text-secondary);margin-bottom:24px;max-width:640px;line-height:1.7}
h1,h2,h3,h4{color:var(--text-primary);line-height:1.3;margin-bottom:12px}
h3{font-size:1.5rem;font-weight:700}
h4{font-size:1.25rem;font-weight:700}
p{margin-bottom:12px}
.mono{font-family:var(--font-mono);font-size:.813rem}
.small{font-size:.813rem;color:var(--text-secondary)}
.label{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-secondary)}
hr{border:none;border-top:1px solid var(--bg-surface);margin:24px 0}
.ds-row{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start}
.ds-col{display:flex;flex-direction:column;gap:12px}
.ds-mt-3{margin-top:12px}.ds-mt-4{margin-top:16px}.ds-mt-5{margin-top:24px}
.ds-mb-2{margin-bottom:8px}.ds-mb-3{margin-bottom:12px}.ds-mb-4{margin-bottom:16px}.ds-mb-5{margin-bottom:24px}
/* ---------- Type utilities (v0.2 — for inline color shortcuts) -----
 * Replaces ad-hoc `style="color:var(--text-secondary)"` declarations
 * with a canonical class. Composes with existing classes (.small,
 * .text-sm, etc.) — they only set font properties, not color. */
.text-secondary { color: var(--text-secondary); }
/* Text utilities — implemented by claude code, to be reviewed by claude design */
.text-muted { color: var(--text-secondary); }
.text-sm { font-size: .875rem; }

/* ---------- Design-system primitives — defined to match prior fabricated
              usage in templates; canonical from here on. -------------- */

/* .ds-link — inline link with the brand text color, underline on hover.
   Used inside tables, row-clickable surfaces, and other places where a
   raw browser-default blue link would clash with the surrounding type.
   Stay subtle by default; rely on the parent row's hover affordance. */
.ds-link {
    color: var(--text-primary);
    text-decoration: none;
}
.ds-link:hover,
.ds-link:focus-visible {
    color: var(--accent);
    text-decoration: underline;
}

/* .ds-form-hint — muted descriptive text under a form label or input.
   Single source of truth for the "no items configured — add them in
   Settings" pattern that recurred across property_cases templates. */
.ds-form-hint {
    margin: 4px 0 0;
    color: var(--text-secondary);
    font-size: .8125rem;
    line-height: 1.5;
}

/* ---------- Buttons (v0.2 — from PRIMITIVES.md § Atoms) -------------- */

.btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border-radius: var(--radius);
    border: 1px solid transparent;
    background: transparent;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.4;
    cursor: pointer;
    color: var(--text-primary);
    transition: background 100ms, border-color 100ms;
    white-space: nowrap;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn svg { width: 14px; height: 14px; }

.btn-main {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}
.btn-main:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn-secondary {
    background: #fff;
    color: var(--text-primary);
    border-color: var(--border-strong);
}
.btn-secondary:hover:not(:disabled) { background: var(--bg-subtle); }

.btn-ghost {
    background: transparent;
    color: var(--text-secondary);
}
.btn-ghost:hover:not(:disabled) { background: rgba(22, 32, 42, 0.05); color: var(--text-primary); }

.btn-danger { background: var(--error); color: #fff; border-color: var(--error); }
.btn-danger:hover:not(:disabled) { filter: brightness(0.95); }

.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-lg { padding: 10px 16px; font-size: 14px; }
.btn-icon { padding: 6px; border-radius: var(--radius); justify-content: center; }
.btn-icon svg { width: 16px; height: 16px; }

/* AI register: a button that signals an LLM action */
.btn-ai {
    background: var(--violet-soft);
    color: var(--violet);
    border-color: var(--violet-strong);
}
.btn-ai:hover:not(:disabled) { background: var(--violet-strong); }

/* ── Buttons: legacy compat (retire as templates migrate) ──
 *   .btn-icon--sm/--md → reserved for chrome controls (sidebar toggle, bell, mobile menu)
 *                        until those templates pick up the new .btn-icon directly
 *   .btn-loading        → preserved as-is (JS-driven spinner, no token surface)
 */
.btn-icon .btn__icon { display: inline-flex; align-items: center; justify-content: center; }
.btn-icon--sm { width: 2rem; height: 2rem; padding: 0; }
.btn-icon--md { width: 2.5rem; height: 2.5rem; padding: 0; }
.app-shell-toggle__glyph { width: 1rem; height: 1rem; }
.btn-loading { position: relative; color: transparent !important; pointer-events: none; }
.btn-loading::after { content: ""; position: absolute; width: 16px; height: 16px; border: 2px solid rgba(255, 255, 255, 0.3); border-top-color: #fff; border-radius: 50%; animation: ds-spin 0.6s linear infinite; }
@keyframes ds-spin { to { transform: rotate(360deg); } }

/* When .btn-* variants are applied to <a> elements (e.g. nav-styled
 * primary/secondary actions), the global a:visited rule overrides the
 * variant's color. Restore the variant's intended foreground for visited
 * links so brand-action buttons don't go gray after first click. */
a.btn-main, a.btn-main:visited,
a.btn-danger, a.btn-danger:visited { color: #fff; }
a.btn-secondary, a.btn-secondary:visited { color: var(--text-primary); }
a.btn-ghost, a.btn-ghost:visited { color: var(--text-secondary); }
a.btn-ai, a.btn-ai:visited { color: var(--violet); }

/* ---------- Badges (v0.2 — from PRIMITIVES.md § Atoms) --------------- */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 10px 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    background: var(--bg-subtle);
    color: var(--text-secondary);
    border: 1px solid var(--border);
    line-height: 1.5;
    white-space: nowrap;
}
.badge-ok { background: var(--forest-soft); color: var(--forest); border-color: rgba(47,107,87,0.2); }
.badge-warn { background: rgba(201,136,16,0.10); color: var(--warning); border-color: rgba(201,136,16,0.2); }
.badge-err { background: rgba(179,38,30,0.08); color: var(--error); border-color: rgba(179,38,30,0.2); }
.badge-ai { background: var(--violet-soft); color: var(--violet); border-color: var(--violet-strong); }
.badge-accent { background: var(--accent-soft); color: var(--accent); border-color: rgba(185,91,55,0.25); }

.badge-dot-pre {
    width: 6px; height: 6px; border-radius: 50%; background: currentColor;
    display: inline-block;
}

/* ── Badges: legacy compat (retire as templates migrate) ──
 *   .badge-primary  → use .badge-accent (terracotta) when intent is "highlight",
 *                     or leave unstyled (default badge is muted) otherwise.
 *                     Aliased to the steel-soft chrome it had before so existing
 *                     "running" workflow chips don't change tone in this PR.
 *   .badge-muted    → identical to the new default .badge — kept as an explicit
 *                     selector since templates use it as a deliberate marker.
 */
/* .badge-primary block deleted — every consumer migrated in Phase D5b
   (PR after #417). Mapping: highlight intent → .badge-accent;
   in-progress → .badge-warn; provider/source/type labels → plain .badge.
   See PRIMITIVES.md: the four semantic variants (.badge-ok/warn/err/ai)
   plus .badge-accent cover ~95% of uses. */
.badge-muted   { background: var(--bg-subtle); color: var(--text-muted); border-color: var(--border-subtle); }

/* ---------- .ds-entity-chip — canonical entity-reference chip ------- *
 *
 * Used everywhere an entity_id needs to be rendered as a clickable
 * reference: Relations panel on /memory/entity/<id>, mentions strip on
 * /memory/document/<id>, future linked-entity cells across modules.
 * Visually similar to .badge but with:
 *   - explicit hover affordance (deeper bg + ink color)
 *   - small entity-type swatch on the left (terracotta=person,
 *     forest=location, steel=company, violet=time, paper=document)
 *   - data-entity-id attribute so JS can wire hover-peek
 *
 * Pairs with the .ds-entity-peek popover below. Hover for 400ms (or
 * keyboard-focus, for a11y) and a small card appears with type + 2-3
 * substrate facts + "Open →".  */
.ds-entity-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px 3px 6px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    background: var(--bg-subtle);
    color: var(--text-secondary);
    border: 1px solid var(--border);
    text-decoration: none;
    line-height: 1.4;
    cursor: pointer;
    white-space: nowrap;
    max-width: 240px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ds-entity-chip:hover,
.ds-entity-chip:focus-visible {
    background: var(--bg-surface);
    border-color: var(--border-strong);
    color: var(--text-primary);
    text-decoration: none;
    outline: none;
}
.ds-entity-chip__type {
    width: 16px; height: 16px;
    border-radius: 4px;
    background: var(--bg-surface);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 10px; font-weight: 700;
    color: var(--text-muted);
    flex-shrink: 0;
    text-transform: uppercase;
    letter-spacing: 0;
}
.ds-entity-chip__type.is-person   { background: var(--accent-soft); color: var(--accent); }
.ds-entity-chip__type.is-location { background: rgba(47,107,87,0.10); color: var(--forest); }
.ds-entity-chip__type.is-company  { background: rgba(110,135,152,0.15); color: var(--steel); }
.ds-entity-chip__type.is-time     { background: var(--violet-soft); color: var(--violet); }
.ds-entity-chip__type.is-document { background: var(--bg-subtle); color: var(--text-muted); }
.ds-entity-chip__type.is-product  { background: rgba(201,136,16,0.10); color: var(--warning); }
.ds-entity-chip__type.is-task     { background: rgba(47,107,87,0.10); color: var(--forest); }
.ds-entity-chip__type.is-deal     { background: var(--accent-soft); color: var(--accent); }
.ds-entity-chip__type.is-email_address { background: var(--bg-subtle); color: var(--text-muted); }
.ds-entity-chip__type.is-phone_number  { background: var(--bg-subtle); color: var(--text-muted); }
.ds-entity-chip__icon {
    width: 12px;
    height: 12px;
    stroke-width: 2;
}
.ds-entity-chip__name { overflow: hidden; text-overflow: ellipsis; }

/* Hover-peek popover — appears 400ms after pointer enters a chip,
 * dismisses on pointer leave / Esc / scroll. Body is fetched lazily
 * from /memory/entity/<id>/peek and cached per chip in JS. */
.ds-entity-peek {
    position: absolute;
    z-index: 100;
    width: 280px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: 0 6px 24px rgba(22,32,42,0.12), 0 2px 6px rgba(22,32,42,0.06);
    padding: 12px 14px;
    font-size: 12px;
    color: var(--text-primary);
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 80ms ease-out, transform 80ms ease-out;
    pointer-events: none;
}
.ds-entity-peek.is-open {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
.ds-entity-peek__title {
    font-size: 13px;
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 6px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ds-entity-peek__meta {
    font-size: 11px;
    color: var(--text-muted);
    margin-bottom: 8px;
}
.ds-entity-peek__facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 10px;
    margin: 0 0 8px 0;
}
.ds-entity-peek__facts dt { color: var(--text-muted); font-weight: 500; }
.ds-entity-peek__facts dd { margin: 0; color: var(--text-primary); font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Pin BOTH :link and :visited to var(--accent). Without the :visited
 * rule the browser default (purple/indigo) takes over once the user
 * has opened an entity, so the same peek popover renders in two
 * different colours depending on history. */
.ds-entity-peek__open,
.ds-entity-peek__open:link,
.ds-entity-peek__open:visited {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
}
.ds-entity-peek__open:hover { text-decoration: underline; }

/* ---------- .ds-inline-stamp — per-fact module attribution ---------- *
 *
 * Small chip rendered next to a substrate annotation value (channels
 * strip, Known Facts dd) by modules contributing via the inline slot
 * of the inject primitive. Tooltip-shaped, sized to sit unobtrusively
 * beside a one-line value — distinct from .rrcard (self-standing
 * infobox) and .memory-inject-banner (loud strip).
 *
 * Visual rules:
 *  - Must include its own icon/marker so the chip never reads as
 *    substrate fact (module's render() includes a lucide check/flag/
 *    etc. inside body_html).
 *  - Background slightly off-substrate to signal "from somewhere else".
 *  - Inline-flex so it flows next to the value text without breaking
 *    the line awkwardly.
 *
 * Substrate rules (enforced by contract, not CSS):
 *  - Read-only (no edit affordance)
 *  - Additive-only (cannot override substrate values)
 *  - Module-tag visually (the icon inside body_html serves this) */
.ds-inline-stamp {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 8px 1px 6px;
    margin-left: 6px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 500;
    background: var(--bg-subtle);
    color: var(--text-secondary);
    border: 1px solid var(--border-subtle);
    line-height: 1.4;
    white-space: nowrap;
    vertical-align: middle;
}
/* When the chip body is a link (the common case — Contacts deep-links to
   the contact row), the <a> is the sole flex item of .ds-inline-stamp,
   which means the svg + label inside the <a> fall back to inline flow
   and the svg's default vertical-align: baseline lifts the glyph ~1.7px
   above the line centre. Make the link itself an inline-flex container
   so svg + text become flex items and inherit the chip's gap + centering. */
.ds-inline-stamp a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: inherit;
    text-decoration: none;
}
.ds-inline-stamp a:hover { color: var(--text-primary); text-decoration: underline; }
.ds-inline-stamp svg {
    width: 11px; height: 11px;
    opacity: 0.7;
    flex-shrink: 0;
}
/* Affirmative stamp tint — used by Contacts "confirmed" today; other
 * modules can opt in with data-stamp-kind="confirmed" if useful. */
.ds-inline-stamp[data-stamp-kind="confirmed"] {
    background: var(--forest-soft);
    color: var(--forest);
    border-color: rgba(47,107,87,0.18);
}

/* ---------- Entity graph thumbnail (Slice 7) ------------------------ *
 *
 * Compact 320×260 SVG depth-1 graph at the bottom of /memory/entity/<id>.
 * Lives inside a collapsed <details> accordion — power users open it,
 * novices never see it. Reuses the same per-type color tints as
 * .ds-entity-chip__type for visual consistency. */
.entity-graph {
    width: 100%;
    max-width: 360px;
    margin: 0 auto;
    background: var(--bg-subtle);
    border-radius: var(--radius-lg);
    border: 1px solid var(--border-subtle);
    padding: 8px;
    position: relative;
}
.entity-graph__svg {
    display: block;
    width: 100%;
    /* Height is pinned via the inline `height="260"` attribute on the
       <svg> element (matches the viewBox dimension). We used to rely on
       `aspect-ratio: 320/260` but that gets clobbered by flex/grid
       ancestors in some layouts, collapsing the SVG to ~150px and
       rendering the radial graph as a tiny smudge. Explicit height in
       the markup is robust against parent display-mode quirks. */
    overflow: visible;
}
.entity-graph__empty {
    padding: 24px 16px;
    text-align: center;
    font-size: 12px;
    color: var(--text-muted);
}
/* SVG node circles — coloured by data-entity-type set by the renderer.
 * The default `fill` here is the neutral fallback for entity types that
 * don't yet have a per-type tint registered (e.g. `product`, `vendor`).
 * Without it SVG paints unrecognized circles solid black. New types
 * should add an explicit tint rule below; the fallback is the safety
 * net, not an excuse to skip the new rule. */
.entity-graph__node circle {
    fill: var(--bg-surface);
    stroke: var(--border-strong);
    stroke-width: 1.5;
    transition: stroke-width 80ms ease-out, transform 80ms ease-out;
    cursor: pointer;
}
/* Suppress the browser-default focus ring on focusable <g> nodes; the
 * hover/match strokes already communicate state. Keyboard users still
 * see a thicker accent stroke via :focus-visible below. */
.entity-graph__node:focus { outline: none; }
.entity-graph__node:focus-visible circle { stroke: var(--accent); stroke-width: 3; }
.entity-graph__node:hover circle { stroke-width: 2.5; }
.entity-graph__node[data-entity-type="person"]   circle { fill: var(--accent-soft); stroke: var(--accent); }
.entity-graph__node[data-entity-type="location"] circle { fill: rgba(47,107,87,0.12); stroke: var(--forest); }
.entity-graph__node[data-entity-type="company"]  circle { fill: rgba(110,135,152,0.18); stroke: var(--steel); }
.entity-graph__node[data-entity-type="time"]     circle { fill: var(--violet-soft); stroke: var(--violet); }
.entity-graph__node[data-entity-type="document"] circle { fill: var(--bg-surface); stroke: var(--text-muted); }
.entity-graph__node[data-entity-type="product"]  circle { fill: rgba(201,136,16,0.10); stroke: var(--warning); }
.entity-graph__node--centre circle {
    stroke-width: 2.5;
    /* Centre node gets a subtle inner ring */
    filter: drop-shadow(0 0 0 var(--bg-surface));
}
/* Overflow bubble: occupies the last ring slot when more neighbours
 * exist than the thumbnail can render. Muted fill + dashed stroke so
 * it visibly differs from real-entity nodes — same convention as the
 * AvatarStack +N chip (var(--bg-subtle) / var(--text-secondary)). */
.entity-graph__node--overflow circle {
    fill: var(--bg-subtle);
    stroke: var(--text-muted);
    stroke-dasharray: 3 2;
}
.entity-graph__node--overflow:hover circle { stroke-width: 2.5; }
.entity-graph__overflow-mark {
    font-size: 11px;
    font-weight: 700;
    fill: var(--text-secondary);
}
.entity-graph__label {
    font-size: 10px;
    font-weight: 600;
    fill: var(--text-primary);
    pointer-events: none;
}
.entity-graph__node--centre .entity-graph__label { fill: var(--text-primary); }
/* Lucide sprite-icon mark on value-type entity nodes (email_address,
 * phone_number). The sprite symbols carry no presentation attributes;
 * stroke styling is supplied here. Matches Lucide's default
 * stroke-width=2, round joins. */
.entity-graph__node-icon {
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
}
.entity-graph__edge {
    stroke: var(--text-muted);
    stroke-width: 1;
    opacity: 0.5;
}
.entity-graph__edge-label {
    font-size: 9px;
    fill: var(--text-muted);
    pointer-events: none;
    font-style: italic;
}

/* ---------- .graph-explorer — full-page /memory/graph surface --------
 * Reuses .entity-graph__* node + edge styles from the thumbnail above.
 * Adds the full-page chrome: toolbar (filters + depth slider + search),
 * canvas-wrap (the d3-force-laid SVG), and a 280px side panel for the
 * selected node card. Below 960px the side panel stacks under the
 * canvas. */
.graph-explorer {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-height: 70vh;
}
.graph-explorer__toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 16px;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.graph-explorer__filters {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 18px;
    align-items: center;
    min-width: 0;
}
.graph-explorer__filter-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.graph-explorer__filter-label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-right: 4px;
}
.graph-explorer__filter-icon {
    width: 12px;
    height: 12px;
    opacity: 0.7;
}
.graph-explorer__controls {
    display: flex;
    align-items: center;
    gap: 12px;
}
.graph-explorer__depth {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--text-secondary);
}
/* Match the styleguide select-chevron pattern (the same SVG that
 * `select.ds-input` uses) instead of leaving the OS-default arrow.
 * The compact toolbar can't take the heavier `.ds-input` base styling,
 * so the chevron treatment is applied here locally. */
.graph-explorer__depth select {
    appearance: none;
    -webkit-appearance: none;
    height: 28px;
    padding: 0 26px 0 8px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-subtle);
    background-color: var(--bg-surface);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23697784' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 7px center;
    background-size: 14px;
    color: var(--text-primary);
    font-size: 13px;
}
.graph-explorer__search input {
    height: 28px;
    padding: 0 10px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-subtle);
    background: var(--bg-surface);
    color: var(--text-primary);
    font-size: 13px;
    min-width: 180px;
}
/* Filter chips: each chip is an independent on/off toggle. Default = all
 * chips active (URL has no `type_off=` / `relation_off=` entries).
 * Clicking a chip flips ONLY its own id in/out of the URL — others stay
 * untouched. `.is-active` = visible in the graph; plain `.graph-chip`
 * (no is-active) = dimmed, currently hidden. */
.graph-chip {
    text-decoration: none;
    cursor: pointer;
    transition: background-color 80ms ease, color 80ms ease, opacity 80ms ease;
    opacity: 0.55;
}
.graph-chip:hover { opacity: 0.85; }
.graph-chip.is-active {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent);
    opacity: 1;
}
.graph-chip__count {
    display: inline-block;
    margin-left: 4px;
    padding: 0 5px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.5;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.05);
    color: inherit;
    opacity: 0.85;
}
.graph-chip.is-active .graph-chip__count {
    background: rgba(185, 91, 55, 0.15);
}
.graph-explorer__notice {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    background: rgba(201, 136, 16, 0.10);
    color: var(--warning);
    font-size: 13px;
}
.graph-explorer__body {
    /* Canvas is the only column. The previous 280px detail rail was
     * dropped when single-click started pinning the EntityChip popover
     * (close to the clicked node, no fixed-position duplicate). */
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
    flex: 1;
    min-height: 0;
}
.graph-explorer__canvas-wrap {
    position: relative;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    min-height: 60vh;
    overflow: hidden;
}
.graph-explorer__canvas {
    display: block;
    width: 100%;
    height: 100%;
    user-select: none;
}
.graph-explorer__zoom-controls {
    position: absolute;
    top: 12px;
    right: 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    background: var(--bg-surface);
    padding: 4px;
    border-radius: var(--radius-sm);
    box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.graph-explorer__zoom-controls .btn { min-width: 32px; padding: 2px 8px; }
/* Search dim/highlight: dim non-matching nodes to half opacity, leave
 * matches at full saturation. Pure CSS — JS just flips the classes. */
.entity-graph__node.is-dim { opacity: 0.18; }
.entity-graph__node.is-match circle { stroke-width: 3; }

/* ── Temporal strip — full-width row below .graph-explorer__body.
 * Hidden until JS detects ≥2 distinct timestamps in the rendered graph.
 * Two date <input> bookends frame a horizontal scrubber: a 2 px rail,
 * a tinted window between the handles, and two draggable thumbs. The
 * Play button on the left animates the To handle forward. No
 * histogram — kept deliberately spare; density sparkline is a follow-up. */
.graph-explorer__time {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 14px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.graph-explorer__time[hidden] { display: none; }
.graph-explorer__time-input {
    height: 28px;
    padding: 0 8px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-subtle);
    background: var(--bg-surface);
    color: var(--text-primary);
    font-size: 12px;
    /* Native picker honours dark mode + locale formatting. */
    color-scheme: light dark;
}
.graph-explorer__time-strip {
    position: relative;
    flex: 1;
    height: 40px;
    cursor: pointer;
    user-select: none;
    /* Side margin so handles at the extremes (0%/100%) don't get clipped
     * by neighbouring inputs. */
    margin: 0 8px;
}
/* Density sparkline — a single SVG layer at the bottom of the strip
 * showing event count per bin across the full data range. Bars sit
 * below the rail so they don't compete with the handles for attention.
 * The sparkline is informational, not interactive: pointer-events
 * disabled so drag/click still reaches the handles above. */
.graph-explorer__time-sparkline {
    position: absolute;
    left: 0;
    right: 0;
    top: 24px;
    height: 16px;
    width: 100%;
    pointer-events: none;
    overflow: visible;
}
.graph-explorer__time-sparkline-bar {
    fill: var(--accent);
    opacity: 0.28;
}
/* Rail + handles sit in the top portion of the strip so the sparkline
 * underneath has room to breathe. With the strip at 40px and the
 * sparkline occupying y=24..40 (16px), centering the rail at y=12
 * puts the handles at y=2..22 — no overlap with the sparkline area. */
.graph-explorer__time-track {
    position: absolute;
    left: 0;
    right: 0;
    top: 12px;
    height: 2px;
    background: var(--border-subtle);
    border-radius: 1px;
}
.graph-explorer__time-window {
    position: absolute;
    top: 10px;
    height: 6px;
    background: var(--accent-soft);
    border-top: 1px solid var(--accent);
    border-bottom: 1px solid var(--accent);
    pointer-events: none;
    border-radius: 2px;
}
.graph-explorer__time-handle {
    position: absolute;
    top: 2px;
    width: 12px;
    height: 20px;
    margin-left: -6px;
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 3px;
    padding: 0;
    cursor: ew-resize;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
    touch-action: none;
}
.graph-explorer__time-handle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
/* Floating date label that sits above each handle and tracks its
 * position. Updates live during drag and Play so the user can read
 * the current window without looking down at the date inputs. Width
 * is fixed so the label doesn't reflow as the date string length
 * changes between locales; the handle's own .left value drives the
 * label's horizontal position via inheritance. The label is also
 * the source of truth for what date the user sees — the native date
 * inputs below are a keyboard-precision fallback. */
.graph-explorer__time-handle-label {
    position: absolute;
    left: 50%;
    bottom: 100%;
    transform: translateX(-50%);
    margin-bottom: 6px;
    padding: 2px 6px;
    font-size: 11px;
    font-weight: 500;
    line-height: 1.4;
    white-space: nowrap;
    color: var(--text-primary);
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: 3px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.08);
    pointer-events: none;
}
.graph-explorer__time-play.is-playing {
    background: var(--accent-soft);
    color: var(--accent);
}

/* ── Badge semantic variants — Proposals workcell (promoted from inline styles)
   .badge--archived      : archived requirement label — subdued, neutral
   .badge--gate-mandatory: knockout/mandatory gate — error register
   .badge--gate-min-score: minimum-score gate — warning register
*/
.badge--archived      { background: var(--bg-subtle); color: var(--text-secondary); border-color: var(--border-subtle); }
.badge--gate-mandatory { background: rgba(179,38,30,0.08); color: var(--error); border-color: rgba(179,38,30,0.2); }
.badge--gate-min-score { background: rgba(201,136,16,0.10); color: var(--warning); border-color: rgba(201,136,16,0.2); }

/* ---------- StatusSelect (data-attribute pattern) ----------
   A <select> whose visual style reflects its own chosen value.
   Apply [data-status="<value>"] to the <select> element.
   Documented in PRIMITIVES.md § Atoms as "StatusSelect".
   Consumers: Proposals vendors table (invitation status).
   NOT a global form-input style — only use where status drives the colour.
*/
[data-status] { font-size: .813rem; border-radius: var(--radius-sm); }
[data-status="pending"]   { background: var(--bg-subtle); color: var(--text-secondary); }
[data-status="sent"]      { background: rgba(110,135,152,0.12); color: var(--steel); }
[data-status="received"]  { background: var(--forest-soft); color: var(--forest); }
[data-status="declined"]  { background: rgba(179,38,30,0.08); color: var(--error); }
[data-status="withdrawn"] { background: var(--bg-subtle); color: var(--text-muted); }

/* ---------- Draggable rows ----------
   Generic primitive for row-level drag-to-reorder in a <table>.
   Canonical consumer: Proposals Brief → requirements table.
   Future consumers compose this pattern; do NOT invent new drag classes.
   Documented in PRIMITIVES.md § Atoms as "DraggableRow".
   JS entry point: see proposals_brief.js initDrag() for the reference
   implementation. A future generic DraggableRows.mount(table, onReorder)
   helper may be extracted to shell.js when a second consumer lands.
   CSS lives here (shell.css); not in proposals_brief.css.
*/
.draggable-row__handle-cell {
    width: 28px;
    padding: 0 4px !important;
    vertical-align: middle;
}
.draggable-row__handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary, #697784);
    opacity: 0.35;
    cursor: grab;
    transition: opacity .15s ease;
    width: 20px;
    height: 20px;
}
tr[data-brief-drag-req]:hover .draggable-row__handle { opacity: 0.75; }
tr[data-brief-drag-req].draggable-row.is-dragging .draggable-row__handle {
    opacity: 1;
    cursor: grabbing;
}
.draggable-row__handle-icon {
    width: 14px;
    height: 14px;
    stroke: currentColor;
    fill: none;
}
/* Ghost row while dragging */
tr.draggable-row.is-dragging { opacity: 0.4; }
/* Drop-line indicator — above a row */
tr.draggable-row.is-drop-before td,
tr[data-brief-drop-group].is-drop-before td {
    border-top: 2px solid var(--text-primary, #384652) !important;
}
/* Drop-line indicator — below a row */
tr.draggable-row.is-drop-after td {
    border-bottom: 2px solid var(--text-primary, #384652) !important;
}
/* Group-header target highlight */
tr[data-brief-drop-group].is-drop-target td {
    background: rgba(110,135,152,.12) !important;
}

/* ── Module-specific helpers (Proposals workcell) —————————————————————————
   These are NOT primitives. They live here because style.css is the
   single "everything that isn't shell-level" file, and these classes are
   small enough not to warrant their own file.
   Do NOT promote them to other modules without first proposing a primitive
   in PRIMITIVES.md.
*/

/* .proposals-summary — body text for an RFP overview paragraph.
   Module-specific; not a primitive. */
.proposals-summary { color: var(--text-primary); line-height: 1.55; }

/* .items-cell--leader — cheapest-available cell on the items × vendors grid
   (Proposals → Items tab). Soft success tint + a 3px left-edge bar so the
   "winner per row" is scannable without staring. Computed in the route via
   `winning_per_item` (cheapest unit_price among available=true responses).
   Module-specific; not a primitive. */
td.items-cell--leader {
    background: rgba(47, 107, 87, 0.08);
    position: relative;
}
td.items-cell--leader::before {
    content: '';
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--success);
    pointer-events: none;
}
.items-leader-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    margin-left: 4px;
    color: var(--success);
    font-size: .8rem;
    line-height: 1;
}

/* .suggestion-row — one vendor dimension suggestion row in the compare tab.
   Module-specific; not a primitive. JS-generated so no template class swap needed. */
/* .suggestion-row is styled inline in _compare.html JS — see that file. */

/* Verbalized confidence pills (see core.web.filters.verbalize_confidence).
   Used wherever a 0-1 confidence is shown to a non-developer; the raw
   percentage stays as the title= tooltip for power users. Tiers map to
   subdued tones from the existing token palette so harsh red is avoided. */
.conf-pill{display:inline-flex;align-items:center;padding:1px 8px;font-size:.72rem;font-weight:600;border-radius:999px;line-height:1.5;text-transform:lowercase;letter-spacing:.01em;border:1px solid transparent}
.conf-pill--high{background:rgba(62,142,65,.18);color:var(--success);border-color:rgba(62,142,65,.32)}
.conf-pill--likely{background:rgba(110,135,152,.18);color:var(--steel);border-color:rgba(110,135,152,.34)}
.conf-pill--unsure{background:rgba(230,162,60,.2);color:var(--warning);border-color:rgba(230,162,60,.34)}
.conf-pill--low{background:rgba(217,48,37,.14);color:var(--error);border-color:rgba(217,48,37,.28)}
.conf-pill--unknown{background:var(--surface-2);color:var(--text-muted);border-color:var(--border-subtle)}

/* Per-user project chips on /settings/users */
.user-project-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.user-project-chip{display:inline-flex;align-items:center;padding:2px 8px;font-size:.75rem;font-weight:500;border-radius:999px;line-height:1.6;background:rgba(110,135,152,.1);color:var(--steel);border:1px solid var(--border-subtle)}
.user-project-chip--archived{background:var(--surface-2);color:var(--text-muted);text-decoration:line-through}
.user-project-chip--all{background:rgba(62,142,65,.12);color:var(--success);border:1px solid rgba(62,142,65,.3);font-weight:600}
.user-project-chip-add{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:999px;border:1px dashed var(--border-default);background:transparent;color:var(--text-muted);cursor:pointer;padding:0}
.user-project-chip-add:hover:not(:disabled){border-style:solid;color:var(--accent);border-color:var(--accent)}
.user-project-chip-add:disabled{opacity:.4;cursor:not-allowed}
.user-project-chip-add svg{width:14px;height:14px}

/* Minimal modal dialog (native <dialog>) */
.ds-modal{border:none;border-radius:12px;padding:0;box-shadow:0 16px 48px rgba(0,0,0,.18);background:var(--bg-elevated);max-width:480px;width:calc(100% - 32px);margin:auto}
.ds-modal::backdrop{background:rgba(15,20,25,.35);backdrop-filter:blur(2px)}
.ds-modal__hd{position:relative;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 20px;border-bottom:1px solid var(--border-subtle)}
.ds-modal__hd h3,.ds-modal__hd h4{margin:0;font-size:1rem}
.ds-modal__hd h3{font-size:1.05rem}
.ds-modal__bd{padding:20px;display:flex;flex-direction:column;gap:14px;overflow-wrap:anywhere}
.ds-modal__ft{padding:12px 20px;border-top:1px solid var(--border-subtle);display:flex;justify-content:flex-end;gap:8px}
/* Standard X close button for modal headers */
.ds-modal__close{position:absolute;top:8px;right:8px;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--text-secondary);border-radius:6px;cursor:pointer;transition:background .15s,color .15s}
.ds-modal__close:hover{background:var(--bg-subtle);color:var(--text-primary)}
.ds-modal__close:focus-visible{outline:2px solid var(--steel);outline-offset:2px}
.ds-modal__close .ds-icon{width:18px;height:18px}

/* ---------- Modal (v0.2 — from PRIMITIVES.md § Detail surfaces) -----
 * The canonical Modal primitive. Use only for: destructive confirms,
 * single-task creation when there is no useful list-context behind it,
 * and auth challenges. If a modal scrolls, it should have been a
 * Drawer or Page (Rule 0.3 — smaller wins).
 *
 * Live .ds-modal* uses the native <dialog> element with ::backdrop and
 * stays unchanged — templates currently relying on it keep working.
 * The .modal* + .modal-scrim primitive below is for new (non-<dialog>)
 * markup; JS hook lives in core/web/static/modal.js (to be created
 * with first .modal consumer in Steps 4-5). */

.modal-scrim {
    position: fixed;
    inset: 0;
    background: rgba(22, 32, 42, 0.45);
    display: grid;
    place-items: center;
    z-index: 1100;
    backdrop-filter: blur(2px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 180ms ease;
}
.modal-scrim.is-open { opacity: 1; pointer-events: auto; }

.modal {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-modal);
    width: 480px;
    max-width: calc(100% - 32px);
    display: flex;
    flex-direction: column;
    transform: translateY(8px) scale(0.98);
    transition: transform 180ms cubic-bezier(.2,.8,.2,1);
}
.modal-scrim.is-open .modal { transform: translateY(0) scale(1); }

.modal__hd {
    padding: 16px 20px;
    border-bottom: 1px solid var(--border-subtle);
    display: flex;
    align-items: center;
    gap: 10px;
}
.modal__hd h3 {
    margin: 0;
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
    flex: 1;
}
.modal__close {
    background: transparent;
    border: none;
    width: 32px; height: 32px;
    border-radius: 6px;
    display: grid; place-items: center;
    cursor: pointer;
    color: var(--text-secondary);
}
.modal__close:hover { background: rgba(22,32,42,0.06); color: var(--text-primary); }
.modal__close svg { width: 16px; height: 16px; }

.modal__bd {
    padding: 20px;
    color: var(--text-primary);
    font-size: 13px;
}
.modal__bd > * + * { margin-top: 12px; }

.modal__ft {
    padding: 12px 20px;
    border-top: 1px solid var(--border-subtle);
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}
.modal__ft .spacer { flex: 1; }

/* Destructive confirm — red focus ring on the primary action.
 * Use class="modal modal--danger" on .modal when the primary CTA is
 * .btn-danger (delete, merge into, revoke). */
.modal--danger { border-color: rgba(179,38,30,0.16); }

body.ds-drawer-open{overflow:hidden}

/* ---------- Drawer (v0.2 — from PRIMITIVES.md § Detail surfaces) ----
 * Canonical Drawer primitive. Two siblings: <div class="drawer-scrim"> +
 * <aside class="drawer">. Both get .is-open toggled in lockstep by the
 * Drawer module in core/web/static/ds_primitives.js. */

.drawer-scrim {
    position: fixed;
    inset: 0;
    background: rgba(22, 32, 42, 0.32);
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease;
    z-index: 1100;
}
.drawer-scrim.is-open { opacity: 1; pointer-events: auto; }

.drawer {
    position: fixed;
    /* Drawer is full-height and sits ABOVE the topline (z-index 1001). It
       owns its own header so the underlying topline must not bleed through
       its first 56px. */
    top: 0; right: 0; bottom: 0;
    width: min(560px, 92vw);
    background: var(--bg-elevated);
    border-left: 1px solid var(--border);
    box-shadow: -16px 0 40px rgba(22, 32, 42, 0.10);
    transform: translateX(100%);
    transition: transform 220ms cubic-bezier(.2,.8,.2,1);
    z-index: 1101;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.drawer.is-open { transform: translateX(0); }
.drawer.drawer--sm { width: min(360px, 92vw); }
.drawer.drawer--lg { width: min(640px, 96vw); }
.drawer.drawer--wide { width: min(720px, 96vw); }

.drawer__hd {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-subtle);
    flex-shrink: 0;
}
.drawer__crumb {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}
.drawer__title {
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
    letter-spacing: -0.005em;
    margin: 0;
}
.drawer__hd-meta { color: var(--text-secondary); font-size: 12px; }
/* Back affordance — appears in the header when a drawer is opened as part
 * of a "Drilling between records" replace flow (see style_guide.html).
 * Sits left of the title, color-matches secondary text, no border. */
.drawer__back {
    color: var(--text-secondary);
    font-weight: 500;
    padding: 4px 8px;
    flex-shrink: 0;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.drawer__back:hover { color: var(--text-primary); }
.drawer__close {
    background: transparent;
    border: none;
    width: 32px; height: 32px;
    border-radius: 6px;
    display: grid; place-items: center;
    cursor: pointer;
    color: var(--text-secondary);
}
.drawer__close:hover { background: rgba(22,32,42,0.06); color: var(--text-primary); }
.drawer__close svg { width: 16px; height: 16px; }

.drawer__bd {
    flex: 1;
    overflow-y: auto;
    padding: 18px;
}
.drawer__bd > * + * { margin-top: 14px; }

.drawer__ft {
    flex-shrink: 0;
    padding: 12px 18px;
    border-top: 1px solid var(--border-subtle);
    background: var(--bg-subtle);
    display: flex;
    gap: 8px;
    align-items: center;
}
.drawer__ft .spacer { flex: 1; }

/* Definition list inside drawers */
.drawer-dl {
    display: grid;
    grid-template-columns: 130px 1fr;
    gap: 6px 12px;
    margin: 0;
    font-size: 13px;
}
.drawer-dl dt {
    color: var(--text-muted);
    font-weight: 500;
    font-size: 12px;
    padding-top: 1px;
}
.drawer-dl dd {
    margin: 0;
    color: var(--text-primary);
    font-weight: 600;
}
.drawer-dl dd .muted { color: var(--text-muted); font-weight: 500; }

/* Section blocks inside drawers (e.g. tasks/_task_drawer.html groupings). */
.drawer__section { padding: 14px 0; border-bottom: 1px solid var(--border-subtle); }
.drawer__section:last-child { border-bottom: none; }
.drawer__section-title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    font-weight: 700;
    margin: 0 0 8px;
}

/* Drawer header keeps an h4 in some legacy templates. Style it like
 * .drawer__title so the migration doesn't require renaming the element. */
.drawer__hd h4 {
    margin: 0;
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
    letter-spacing: -0.005em;
}

/* INLINE EDIT (see docs/design_system.html §14c) */
.ds-inline-edit{display:inline-flex;align-items:center;gap:6px;cursor:text;position:relative;max-width:100%}
.ds-inline-edit__text{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Pencil hint is visible at rest (faded) so users see the field is editable
 * without having to hover. Full opacity on hover/focus keeps the original
 * affordance. Reported as a discoverability bug on the company detail page
 * (May 2026): people were missing the "click to edit" cue because the hint
 * was opacity:0 at rest. */
.ds-inline-edit__hint{display:inline-flex;align-items:center;opacity:.35;transition:opacity .15s ease;color:var(--text-secondary);flex-shrink:0}
.ds-inline-edit:hover .ds-inline-edit__hint,
.ds-inline-edit:focus-within .ds-inline-edit__hint{opacity:1}
.ds-inline-edit__input{display:none;font-family:var(--font-sans);font-size:.938rem;color:var(--text-primary);background:var(--bg-elevated,#fff);border:1px solid var(--border-input);border-radius:8px;padding:8px 12px;outline:none;width:100%;min-width:200px;box-sizing:border-box;transition:border-color .15s ease,box-shadow .15s ease}
.ds-inline-edit__input:focus{border-color:var(--steel);box-shadow:0 0 0 2px rgba(110,135,152,.2)}
/* Selects: hide the native arrow and paint a consistent chevron (same SVG
 * the .ds-input rule uses) — without this, the OS arrow and our custom one
 * both render, doubling the chevron. Pattern mirrors `select.ds-input`. */
select.ds-inline-edit__input{appearance:none;-webkit-appearance:none;padding-right:36px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23697784' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 10px center;background-size:16px}
.ds-inline-edit.is-editing .ds-inline-edit__text,
.ds-inline-edit.is-editing .ds-inline-edit__hint{display:none}
.ds-inline-edit.is-editing .ds-inline-edit__input{display:block}

/* Inline-edit validation error.
 *   Surfaced by InlineEdit.showError() in ds_primitives.js when the
 *   server returns 400. Shown below the cell as a small red message,
 *   fades out after ~4 s. Auto-removed by the JS helper.
 *   role="alert" is set on the span so screen readers announce it. */
.ds-inline-edit__err {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 2px;
    font-size: 11px;
    line-height: 1.3;
    color: var(--error);
    background: rgba(179, 38, 30, 0.08);
    border: 1px solid rgba(179, 38, 30, 0.25);
    border-radius: 4px;
    padding: 2px 6px;
    max-width: 320px;
    white-space: normal;
    pointer-events: none;
    z-index: 10;
    animation: ds-ie-err-in 0.18s ease, ds-ie-err-out 0.4s ease 3.6s forwards;
}
@keyframes ds-ie-err-in { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
@keyframes ds-ie-err-out { to { opacity: 0; transform: translateY(-2px); } }

/* ── Toast primitive (Linear/Notion model) ─────────────────────────────
 *   Bottom-right stack of small auto-dismissing cards. Replaces the
 *   v0.1 inline alert-stack at the top of the page (which pushed
 *   content down and made every flash feel important). Toasts here
 *   are ephemeral feedback ("Saved", "Created", "Failed") — they
 *   slide in, sit for ~4 s, slide out. No content jank.
 *
 *   Wired by Toast.show({type, title?, body, ttl?}) in ds_primitives.js.
 *   Server-side flash messages are picked up automatically on page load
 *   by the bootstrap script in base.html. */
.ds-toast-host {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 5000;
    display: flex;
    flex-direction: column-reverse;  /* newest appears at the bottom */
    gap: 8px;
    pointer-events: none;            /* host doesn't block clicks */
    max-width: calc(100vw - 40px);
}
.ds-toast {
    pointer-events: auto;
    min-width: 280px;
    max-width: 360px;
    padding: 10px 36px 10px 14px;
    background: var(--bg-elevated, #fff);
    border: 1px solid var(--border);
    border-left: 3px solid var(--steel);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(15, 20, 25, 0.12), 0 1px 2px rgba(15, 20, 25, 0.04);
    font-size: 13px;
    line-height: 1.4;
    color: var(--text-primary);
    position: relative;
    animation: ds-toast-in 0.22s ease-out;
}
.ds-toast.is-leaving { animation: ds-toast-out 0.28s ease-in forwards; }
.ds-toast--ok   { border-left-color: var(--forest, #2f6b57); }
.ds-toast--err  { border-left-color: var(--error,  #b3261e); }
.ds-toast--warn { border-left-color: var(--warning,#c98810); }
.ds-toast--info { border-left-color: var(--steel,  #6e8798); }
.ds-toast__title {
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 2px;
}
.ds-toast__body { color: var(--text-secondary); }
.ds-toast__dismiss {
    position: absolute;
    top: 6px; right: 6px;
    width: 22px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 18px; line-height: 1;
    border-radius: 4px;
}
.ds-toast__dismiss:hover { background: var(--bg-subtle); color: var(--text-primary); }
.ds-toast__dismiss svg { width: 14px; height: 14px; display: block; }
/* Inline action button (Linear-style "Undo"). Sits between body and ×;
 * the toast keeps the right padding for × and the action sits in normal
 * flow under the body for predictable layout regardless of body length.
 * Slightly heavier weight + accent color so it reads as the primary
 * affordance of the toast. */
.ds-toast__action {
    display: inline-flex;
    align-items: center;
    margin-top: 6px;
    padding: 4px 10px;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    line-height: 1.2;
}
.ds-toast__action:hover {
    background: var(--bg-subtle);
    border-color: var(--text-muted);
}
.ds-toast__action:focus-visible {
    outline: 2px solid var(--accent, #6366f1);
    outline-offset: 1px;
}
/* Countdown bar -- a thin progress line at the bottom of an action toast
 * that shrinks from full width to zero over the toast's TTL. Conveys
 * "you have ~N seconds left to click Undo" without a literal countdown
 * number, which would just add visual noise. Color matches the toast's
 * tone (forest/red/amber/steel for ok/err/warn/info). The animation
 * duration is set inline by Toast.show via style.animationDuration so
 * the bar always matches the actual ttl. */
.ds-toast__countdown {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: 100%;
    background: var(--steel, #6e8798);
    transform-origin: left center;
    border-bottom-left-radius: 8px;
    border-bottom-right-radius: 8px;
    animation-name: ds-toast-countdown;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
    /* Sensible fallback if no inline style is set; Toast.show overrides. */
    animation-duration: 4000ms;
    opacity: 0.55;
}
.ds-toast--ok   .ds-toast__countdown { background: var(--forest, #2f6b57); }
.ds-toast--err  .ds-toast__countdown { background: var(--error,  #b3261e); }
.ds-toast--warn .ds-toast__countdown { background: var(--warning,#c98810); }
.ds-toast--info .ds-toast__countdown { background: var(--steel,  #6e8798); }
@keyframes ds-toast-in  { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } }
@keyframes ds-toast-out { to   { opacity: 0; transform: translateX(16px); } }
@keyframes ds-toast-countdown { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) {
    .ds-toast { animation: none; }
    .ds-toast.is-leaving { animation: none; }
    /* Hide the countdown rather than freezing it -- a static "full" bar
     * misleads about remaining time; the toast still auto-dismisses on TTL. */
    .ds-toast__countdown { display: none; }
}

/* ALERTS / INLINE BANNERS (legacy — flash messages now use the toast
 * primitive above; .ds-alert is still used by static info/warn banners
 * inside specific surfaces, e.g. memory invariant violations). */
.ds-alert{position:relative;padding:12px 48px 12px 16px;border-radius:8px;border-left:4px solid;font-size:.813rem;margin-bottom:12px;display:flex;align-items:flex-start;gap:12px;animation:ds-alert-in .3s ease}
@keyframes ds-alert-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
.ds-alert__icon{flex-shrink:0;font-size:1.15rem;line-height:1.4}
.ds-alert__body{flex:1}
.ds-alert__body strong{display:block;margin-bottom:2px;color:currentColor;font-size:.938rem}
.ds-alert__dismiss{position:absolute;top:8px;right:8px;background:none;border:none;color:inherit;opacity:.5;cursor:pointer;font-size:1rem;padding:4px;border-radius:4px;line-height:1}
.ds-alert__dismiss:hover{opacity:1}
.alert-solid.ds-alert--ok{background:var(--success);color:#fff;box-shadow:0 8px 32px rgba(62,142,65,.2);border-left:none;font-weight:500}
.alert-solid.ds-alert--warn{background:var(--warning);color:#fff;box-shadow:0 8px 32px rgba(230,162,60,.2);border-left:none;font-weight:500}
.ds-alert--err{background:rgba(217,48,37,.12);border-color:var(--error);color:var(--error)}
.ds-alert--info{background:rgba(110,135,152,.12);border-color:var(--steel);color:var(--steel)}
.ds-alert.dismissing{animation:ds-alert-out .3s ease forwards}
@keyframes ds-alert-out{to{opacity:0;transform:translateX(20px);max-height:0;margin:0;padding:0;overflow:hidden}}

/* PAGINATION */
.ds-pag{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.ds-pag__info{font-size:.813rem;color:var(--text-secondary)}
.ds-pager{display:flex;align-items:center;gap:2px}
.ds-pager button{display:flex;align-items:center;justify-content:center;min-width:34px;height:34px;padding:0 8px;font-family:var(--font-sans);font-size:.813rem;color:var(--text-secondary);background:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer;transition:all .15s ease}
.ds-pager button:hover:not(:disabled):not(.active){background:var(--bg-surface);border-color:var(--bg-surface);color:var(--text-primary)}
.ds-pager button.active{background:var(--steel);color:#fff;border-color:var(--steel);font-weight:600}
.ds-pager button:disabled{opacity:.3;cursor:not-allowed}
.ds-pager .dots{padding:0 4px;color:var(--text-secondary);border:1px solid transparent;min-width:34px;text-align:center}
.ds-pag__sz{font-size:.813rem;color:var(--text-secondary);display:flex;align-items:center;gap:8px}
.ds-pager__nav{display:inline-flex;align-items:center;gap:4px}
.ds-pager__glyph{width:14px;height:14px;flex-shrink:0}
.ds-pag__sz select{background:var(--bg-surface);color:var(--text-primary);border:1px solid var(--bg-surface);border-radius:4px;padding:2px 8px;font-size:.813rem}

/* NAV SIDEBAR COMPONENT */
.ds-nav{background:var(--bg-surface);border:1px solid var(--bg-surface);border-radius:12px;overflow:hidden;max-width:260px}
.ds-nav__hd{padding:12px 16px;border-bottom:1px solid var(--bg-surface);font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-secondary)}
.ds-nav a{display:flex;align-items:center;gap:12px;padding:8px 16px;color:var(--text-secondary);text-decoration:none;font-size:.938rem;transition:all .15s ease;border-left:3px solid transparent}
.ds-nav a:hover{background:rgba(110,135,152,.15);color:var(--text-primary);text-decoration:none}
.ds-nav a.active{color:var(--steel);background:rgba(110,135,152,.15);border-left-color:var(--steel);font-weight:600}
.ds-nav a .ni{font-size:1rem;width:20px;text-align:center;flex-shrink:0}
.ds-nav a .nr{margin-left:auto;font-size:.75rem}

/* SPINNER */
.ds-spinner{display:inline-block;border-radius:50%;border:3px solid var(--bg-surface);border-top-color:var(--steel);animation:ds-spin .8s linear infinite}
.ds-spinner--sm{width:20px;height:20px;border-width:2px}
.ds-spinner--lg{width:48px;height:48px;border-width:3px}
.ld-overlay{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px;background:var(--bg-surface);border:1px solid var(--bg-surface);border-radius:12px}
.ld-overlay .small{color:var(--text-secondary)}

/* SKELETON */
.sk-line{height:14px;background:var(--bg-surface);border-radius:4px;position:relative;overflow:hidden;margin-bottom:12px}
.sk-line::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.06) 50%,transparent 100%);animation:sk-sh 1.8s ease infinite}
.sk-line.w75{width:75%}.sk-line.w50{width:50%}.sk-line.w25{width:25%}.sk-line.w100{width:100%}
.sk-block{height:100px;background:var(--bg-surface);border-radius:8px;position:relative;overflow:hidden;margin-bottom:12px}
.sk-block::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.06) 50%,transparent 100%);animation:sk-sh 1.8s ease infinite}
.sk-circle{width:48px;height:48px;background:var(--bg-surface);border-radius:50%;position:relative;overflow:hidden;display:inline-block}
.sk-circle::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.06) 50%,transparent 100%);animation:sk-sh 1.8s ease infinite}
.sk-avrow{display:flex;align-items:center;gap:12px;margin-bottom:16px}
.sk-lcol{display:flex;flex-direction:column;gap:12px}
.sk-tbl{width:100%;border-collapse:collapse}
.sk-tbl td{padding:8px;border-bottom:1px solid var(--bg-surface)}
.sk-tbl td .sk-line{margin-bottom:0}
.sk-group{background:var(--bg-surface);border:1px solid var(--bg-surface);border-radius:12px;padding:16px}
@keyframes sk-sh{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}

/* EMPTY STATE */
/* .ds-empty / .ds-empty--sm — legacy alias for .empty-state.
   PRIMITIVES.md § Atoms says canonical EmptyState is .empty-state
   with .empty-state__icon / __title / __sub / __action. Older code
   and a handful of tests still reference .ds-empty class strings;
   we keep those classes alive but visually identical to the canonical
   primitive so future template migrations are no-op visually.
   Migrate gradually; do not introduce new .ds-empty consumers. */
.ds-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 48px 24px;
    color: var(--text-secondary);
}
.ds-empty__icon {
    width: 40px;
    height: 40px;
    color: var(--text-muted);
    margin-bottom: 12px;
}
.ds-empty__icon svg { width: 100%; height: 100%; }
.ds-empty__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 4px;
}
.ds-empty__desc {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 0 0 16px;
    max-width: 420px;
    line-height: 1.5;
}
.ds-empty--sm { padding: 24px 16px; }

/* Record title — canonical 16px in-content title with inline rename.
 * Used on workflow canvas, contact card, RFP brief, etc. Click pencil
 * (or the text itself) → swap to input; Enter saves, Esc reverts, blur
 * saves. Server contract: POST with redirect_to=ajax → 204 on success,
 * 400 + JSON {"error": "..."} on validation failure. Full spec at
 * /style-guide#recordtitle. */
.ds-record-title {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    max-width: 100%;
}
.ds-record-title__text {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.3;
    cursor: text;
}
.ds-record-title__input {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    border: 1px solid #c8c0ad;
    border-radius: 6px;
    padding: 2px 8px;
    background: #ffffff;
    line-height: 1.3;
    width: min(420px, 60vw);
}
.ds-record-title__input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(185, 91, 55, 0.16);
}
.ds-record-title__input.is-error {
    border-color: #dc2626;
    box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12);
}
.ds-record-title__edit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: 4px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s ease, color .12s ease, background .12s ease;
}
.ds-record-title:hover .ds-record-title__edit,
.ds-record-title__edit:focus-visible {
    opacity: 1;
}
.ds-record-title__edit:hover {
    color: var(--text-primary);
    background: #f0ece2;
}
.ds-record-title__edit svg {
    width: 14px;
    height: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.ds-record-title__error {
    display: none;
    font-size: 11px;
    color: #b91c1c;
    margin-left: 6px;
}
.ds-record-title.is-editing .ds-record-title__text,
.ds-record-title.is-editing .ds-record-title__edit { display: none; }
.ds-record-title:not(.is-editing) .ds-record-title__input { display: none; }
.ds-record-title.is-editing.has-error .ds-record-title__error { display: inline; }

/* CARD */
.panel{background:#fff;border-radius:12px;border:1px solid var(--bg-surface);box-shadow:0 1px 2px rgba(0,0,0,.05);overflow:visible}
.section-hd{padding:12px 24px;background:rgba(228,221,209,.3);border-bottom:1px solid var(--bg-surface);border-radius:12px 12px 0 0}
.panel-hd{padding:12px 24px;background:rgba(228,221,209,.3);border-bottom:1px solid var(--bg-surface);border-radius:12px 12px 0 0}
.panel-hd h3,.panel-hd h4{margin:0}
/* implemented by claude code, to be reviewed by claude design */
.panel-hd--flex { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.panel-hd__title { margin: 0; font-size: 1rem; font-weight: 600; }
.panel-hd__actions { display: inline-flex; align-items: center; gap: 8px; }
.section-bd{padding:24px}
.section-ft{padding:16px 24px;background:rgba(228,221,209,.3);border-top:1px solid var(--bg-surface);display:flex;justify-content:flex-end;border-radius:0 0 12px 12px}
.panel-bd{padding:24px}
.panel-bd--flush{padding:0;overflow:hidden;border-radius:0 0 12px 12px}
.panel-bd--flush>.ds-tbl-wrap{border:none;border-radius:0}
.panel-ft{padding:16px 24px;background:rgba(228,221,209,.3);border-top:1px solid var(--bg-surface);display:flex;justify-content:flex-end;border-radius:0 0 12px 12px}

/* MODAL — v1 legacy overlay: scoped to .ds-modal-bg so it cannot bleed into native <dialog class="ds-modal"> */
.ds-modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:1000;opacity:0;visibility:hidden;transition:opacity .25s ease,visibility .25s ease}
.ds-modal-bg.visible{opacity:1;visibility:visible}
.ds-modal-bg .ds-modal{background:#fff;border-radius:12px;width:100%;max-width:400px;box-shadow:0 12px 40px rgba(0,0,0,.2);transform:translateY(12px);transition:transform .2s ease}
.ds-modal-bg.visible .ds-modal{transform:translateY(0) scale(1)}
.ds-modal-bg .ds-modal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--bg-surface)}
.ds-modal-bg .ds-modal__bd{padding:16px}
.ds-modal-bg .ds-modal__ft{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:12px 16px;border-top:1px solid var(--bg-surface)}
.ds-modal__x{background:none;border:none;color:var(--text-secondary);font-size:1.5rem;cursor:pointer;line-height:1;padding:0 4px;border-radius:4px}
.ds-modal__x:hover{color:var(--text-primary);background:var(--bg-surface)}
.ds-modal-bg .ds-modal--danger .ds-modal__ft .btn-ok{background:var(--error);color:#fff;border-color:var(--error)}
.ds-tabs{display:flex;border-bottom:1px solid var(--bg-surface);gap:4px;margin-bottom:16px}
.ds-tabs__tab{padding:8px 12px;font-weight:500;color:var(--text-secondary);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;font-size:.875rem;text-decoration:none;display:inline-flex;align-items:center;gap:6px}
.ds-tabs__tab:hover{color:var(--text-primary);text-decoration:none}
.ds-tabs__tab.is-active{color:var(--text-primary);border-bottom-color:var(--accent)}
.ds-tabs__tab.is-disabled{color:var(--text-muted);cursor:not-allowed;opacity:.55;pointer-events:auto}
.ds-tabs__tab.is-disabled:hover{background:none;color:var(--text-muted)}
.ds-tab{padding:8px 12px;font-weight:500;color:var(--text-secondary);background:none;border:1px solid transparent;border-bottom-color:transparent;border-radius:4px 4px 0 0;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:6px}
.ds-tab:hover{background:var(--bg-surface);color:var(--text-primary);text-decoration:none}
.ds-tab.active,.ds-tab--active{color:var(--text-primary);border-color:var(--bg-surface);border-bottom-color:var(--bg-page);border-bottom:2px solid var(--accent);background:var(--bg-surface)}
.ds-tab.is-disabled{color:var(--text-muted);cursor:not-allowed;opacity:.55;pointer-events:auto}
.ds-tab.is-disabled:hover{background:none;color:var(--text-muted)}
/* Saved-view group: tab + inline close button rendered by ds_grid_saved_views.html.
 * The close button is hidden by default and revealed on hover or when the group
 * holds the active tab — same affordance pattern as browser tabs. The wrapper
 * inherits the tab's bottom-border so the underline still draws cleanly under
 * the combined unit. */
.ds-tab__group{display:inline-flex;align-items:center;gap:0;position:relative}
.ds-tab__group .ds-tab{padding-right:6px}
.ds-tab__close{display:inline-grid;place-items:center;width:18px;height:18px;margin:0 6px 0 -2px;padding:0;border:0;border-radius:50%;background:none;color:var(--text-muted);cursor:pointer;opacity:0;transition:opacity .12s,background .12s,color .12s}
.ds-tab__close svg{width:12px;height:12px;display:block}
.ds-tab__group:hover .ds-tab__close,.ds-tab__group.is-active .ds-tab__close,.ds-tab__close:focus-visible{opacity:.7}
.ds-tab__close:hover,.ds-tab__close:focus-visible{opacity:1;color:var(--text-primary);background:rgba(0,0,0,.06)}
.ds-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 6px;font-size:.7rem;font-weight:600;line-height:1;color:#fff;background:var(--steel);border-radius:9px;font-variant-numeric:tabular-nums}
.ds-tab-badge--dot{min-width:8px;width:8px;height:8px;padding:0;border-radius:50%}
.ds-tab-panel{display:none}
.ds-anav{display:flex;gap:0;position:sticky;top:0;z-index:10;background:var(--bg-page);border-bottom:1px solid var(--border)}
.ds-ai-section{scroll-margin-top:51px}
.ds-anav__item{padding:10px 0;margin-right:24px;font-size:.875rem;font-weight:500;color:var(--text-secondary);text-decoration:none;border-bottom:2px solid transparent;transition:color .15s,border-color .15s}
.ds-anav__item:last-child{margin-right:0}
.ds-anav__item:hover{color:var(--text-primary)}
.ds-anav__item.ds-anav-active{color:var(--text-primary);border-bottom-color:var(--steel)}
.ds-tab-panel.visible{display:flex;flex-direction:column;gap:24px}
.ds-page-stack{display:flex;flex-direction:column;gap:24px}
/* ---------- Datagrid (v0.2 — from PRIMITIVES.md § Data display) ----- */

.ds-grid {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
}

.ds-grid__toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border-subtle);
    flex-wrap: wrap;
}
.ds-grid__search {
    display: flex;
    align-items: center;
    gap: 6px;
    background: var(--bg-subtle);
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 4px 10px;
    width: 280px;
    color: var(--text-secondary);
}
.ds-grid__search:focus-within { border-color: var(--steel); background: #fff; }
.ds-grid__search svg { width: 14px; height: 14px; }
.ds-grid__search input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    font: inherit;
    color: var(--text-primary);
    padding: 2px 0;
}

.ds-grid__spacer { flex: 1; }

.ds-grid__view-tabs {
    display: flex;
    align-items: center;
    gap: 0;
    padding: 6px 12px;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-subtle);
}
.ds-grid__view-tab {
    padding: 4px 10px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    border-radius: var(--radius-sm);
    background: transparent;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
}
.ds-grid__view-tab:hover { color: var(--text-primary); background: rgba(255,255,255,0.6); }
.ds-grid__view-tab.is-active {
    color: var(--text-primary);
    background: #fff;
    box-shadow: 0 0 0 1px var(--border);
}
.ds-grid__view-tab .count {
    font-size: 11px;
    color: var(--text-muted);
    background: transparent;
}
.ds-grid__view-tab.is-active .count { color: var(--text-secondary); }

/* Table */
.ds-tbl-wrap { overflow-x: auto; overflow-y: visible; border: 1px solid var(--border); border-radius: var(--radius); }
.ds-tbl {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.ds-tbl thead th {
    text-align: left;
    font-weight: 600;
    color: var(--text-secondary);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 8px var(--row-pad-x);
    background: var(--bg-subtle);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
    user-select: none;
}
.ds-tbl thead th .sort-ind {
    display: inline-block;
    margin-left: 4px;
    opacity: 0.5;
    font-size: 10px;
}
.ds-tbl thead th.is-sorted { color: var(--text-primary); }
.ds-tbl thead th.is-sorted .sort-ind { opacity: 1; color: var(--text-primary); }
.ds-tbl tbody td {
    padding: var(--row-pad-y) var(--row-pad-x);
    border-bottom: 1px solid var(--border-subtle);
    height: var(--row-h);
    vertical-align: middle;
}
.ds-tbl tbody tr { transition: background 80ms; }
.ds-tbl tbody tr:hover { background: var(--bg-subtle); cursor: pointer; }
.ds-tbl tbody tr.is-selected { background: rgba(22, 32, 42, 0.04); }
.ds-tbl tbody tr:last-child td { border-bottom: none; }

.ds-tbl .col-pri { font-weight: 600; color: var(--text-primary); }
.ds-tbl .col-mut { color: var(--text-secondary); }
.ds-tbl .col-num { font-variant-numeric: tabular-nums; text-align: right; }
/* Single-line cell that ellipsises overflow. Pair with title="<full value>"
 * on the cell so hover reveals the rest. max-width:0 + table-layout:fixed
 * is the only reliable pattern for ellipsis inside <td>; without max-width:0
 * the cell expands to fit content and overflow never triggers. */
.ds-tbl .col-trunc { max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Source-affix chip — provenance token raised at a filename's top-right.
 * The flex lives on the .ds-affix wrapper, NEVER the <td> (a flex td breaks
 * table row-height equalisation — see style_guide.html · Datagrid pitfalls).
 * .ds-affix__name keeps the ellipsis; .ds-src is non-shrinking so a long
 * name truncates without ever clipping the chip. */
.ds-affix { display: flex; align-items: flex-start; min-width: 0; }
.ds-affix__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ds-src {
    flex: 0 0 auto;
    margin-left: 5px;
    transform: translateY(-0.12em);
    font-size: .625rem;
    font-weight: 600;
    line-height: 1.5;
    letter-spacing: .02em;
    /* Pale blue — matches the .ds-chip--blue tag variant (not grey). */
    color: #2563eb;
    background: rgba(59, 130, 246, .12);
    border: 1px solid rgba(59, 130, 246, .3);
    border-radius: 4px;
    padding: 0 4px;
    white-space: nowrap;
    text-transform: lowercase;
}

/* Selection checkbox column.
 * .ds-check is the COLUMN class (sets width). The actual checkbox is either
 * a real <input type="checkbox"> child, or a decorative .ds-check__box span. */
.ds-tbl .ds-check {
    width: 32px;
    text-align: center;
    padding-left: 10px;
    padding-right: 4px;
}
.ds-tbl .ds-check input[type="checkbox"] {
    width: 14px; height: 14px;
    accent-color: var(--accent);
    cursor: pointer;
    margin: 0;
    vertical-align: middle;
}
.ds-tbl .ds-check__box {
    width: 14px; height: 14px;
    border-radius: 3px;
    border: 1.5px solid var(--border-strong);
    background: #fff;
    display: inline-block;
    vertical-align: middle;
    cursor: pointer;
    position: relative;
}
.ds-tbl tr.is-selected .ds-check__box {
    background: var(--accent);
    border-color: var(--accent);
}
.ds-tbl tr.is-selected .ds-check__box::after {
    content: "";
    position: absolute;
    left: 3px; top: 0px;
    width: 4px; height: 8px;
    border-right: 2px solid #fff;
    border-bottom: 2px solid #fff;
    transform: rotate(45deg);
}

/* ── Datagrid: tight modifier ──
 *   .ds-tbl.is-tight → slightly tighter rows for unified browse grids
 *   where the identity-icon cell already adds a visual line. Used by
 *   the canonical Contacts datagrid (mixed companies + people). */
.ds-tbl.is-tight tbody td { padding-top: 8px; padding-bottom: 8px; }

/* ── Sort header button ──
 *   <th><button class="sort-btn">Label <span class="sort-ind">↓</span></button></th>
 *   Reset for sortable column headers — keyboard-focusable, looks
 *   identical to a non-sortable <th>. The is-sorted state lives on the
 *   parent <th>; the .sort-ind arrow inherits the rule above. */
.ds-tbl thead th .sort-btn {
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; color: inherit; letter-spacing: inherit;
    text-transform: inherit; text-align: left;
    cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
}
.ds-tbl thead th .sort-btn:hover { color: var(--text-primary); }
.ds-tbl thead th .sort-btn:focus-visible { outline: 2px solid var(--steel); outline-offset: 2px; border-radius: 3px; }

/* ── Tag-cell wrapper ──
 *   Inline group of <span class="badge"> chips inside a single <td>.
 *   Wraps gracefully on narrow viewports. */
.cell-tags { display: inline-flex; flex-wrap: wrap; gap: 4px; align-items: center; }

/* ── Datagrid footer ──
 *   <div class="ds-grid__footer">…</div> at the bottom of a .ds-grid.
 *   Row count on the left, paginator on the right. The .ds-grid__count
 *   span is the visible "11 z 799 zobrazeno" line; .ds-grid__spacer
 *   pushes paginator buttons to the right. */
.ds-grid__footer {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 12px;
    border-top: 1px solid var(--border-subtle);
    background: var(--bg-subtle);
    font-size: 12px; color: var(--text-muted);
}
.ds-grid__count { font-weight: 500; }

/* ── Toolbar separator ──
 *   <span class="ds-grid__sep"></span> — 1×18px vertical divider
 *   between groups inside .ds-grid__toolbar. */
.ds-grid__sep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }

/* ── Chip count badge ──
 *   <span class="ds-chip__count">799</span> — small muted count tucked
 *   inside a kind chip ("Vše 799"). Inverts on .is-active so it remains
 *   readable on the dark active fill. */
.ds-chip__count {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
    margin-left: 2px;
}
.ds-chip.is-active .ds-chip__count { color: rgba(255, 255, 255, 0.7); }

/* ── Datagrid: legacy compat ──
 *   .ds-tbl--relaxed  → killed in PR following #568. Was a no-op (Rule
 *                       0.4 forbids comfortable density on datagrids);
 *                       all template references stripped.
 *   .ds-grid__hd      → killed in v0.2 alignment pass (#564). The
 *                       canonical toolbar class is .ds-grid__toolbar.
 */

/* Disabled table row — use .is-disabled on <tr>. Text becomes muted, row
   fades to ~55% opacity, and the hover highlight is suppressed so it is
   clear the row is not interactive. The actions cell opts out of the
   opacity fade so action buttons (incl. Enable) stay fully legible. */
.ds-tbl tr.is-disabled td{color:var(--text-muted);opacity:.55}
.ds-tbl tr.is-disabled td:has(.ds-ctx),
.ds-tbl tr.is-disabled td.actions-cell{opacity:1}
.ds-tbl tr.is-disabled:hover td{background:transparent}
.ds-ctx{position:absolute;right:0;top:calc(100% + 4px);background:#fff;border:1px solid var(--bg-surface);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.12);z-index:500;min-width:160px;display:none;padding:4px 0}
.ds-ctx.visible{display:block}
.ds-ctx form{display:block;margin:0}
.ds-ctx .item{padding:7px 12px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:.875rem;color:var(--text-primary);text-decoration:none;width:100%;border:0;background:none;text-align:left;border-radius:0}
.ds-ctx .item:hover,
.ds-ctx .item:focus{background:rgba(228,221,209,.65);outline:none}
.ds-ctx .item:disabled{opacity:.4;cursor:default;pointer-events:none}
.ds-ctx .item.danger{color:var(--error)}
.ds-ctx__sep{height:1px;background:var(--bg-surface);margin:4px 0}
.ds-ctx__grp{padding:4px 12px 2px;font-size:.68rem;font-weight:600;text-transform:uppercase;color:var(--text-secondary);letter-spacing:.04em}
.ds-ctx__flyout-wrap{position:relative}
.ds-ctx__flyout{display:none;position:absolute;left:100%;top:-4px;min-width:180px;max-height:320px;overflow-y:auto}
.ds-ctx__flyout-wrap:hover>.ds-ctx__flyout{display:block}
/* Viewport-aware flip: when the parent menu is pinned near the right edge,
   JS adds .open-left to the outer .ds-ctx so the flyout opens to the left
   instead of the right and never clips off-screen. */
.ds-ctx.open-left .ds-ctx__flyout{left:auto;right:100%}
.ds-ctx__flyout.open-up{top:auto;bottom:-4px}
.ds-ctx__flyout-trigger{justify-content:space-between}
.ds-ctx-trig{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:1.1rem;padding:4px}
.ds-ctx-trig:hover{color:var(--text-primary)}

/* Disabled kebab item — class variant for <a> and <button> alike. The
   existing `:disabled` selector only matches form controls; class-based
   `.is-disabled` lets disabled <a> render the same way and lets a
   reason tooltip (`.ds-tip` + `data-tip="…"`) hover-trigger on top.
   Pointer-events are kept enabled so the tooltip can fire; clicks land
   on a no-op (the renderer emits `<span>` for disabled items rather
   than a clickable element). See ds_grid_actions macro. */
.ds-ctx .item.is-disabled{opacity:.45;cursor:not-allowed}
.ds-ctx .item.is-disabled:hover,
.ds-ctx .item.is-disabled:focus{background:transparent}
/* Tooltip needs to outrank the menu's stacking context. */
.ds-ctx .item.ds-tip::after,
.ds-ctx .item.cmp-tip::after{z-index:600}

/* Datagrid mass-action chrome — see /style-guide § Datagrid · Mass
   actions and docs/PRIMITIVES.md § GridActions. Three pieces:
     1. Selected-row highlight (visual feedback for the .ds-check
        checkbox column).
     2. Select-all-matching strip — sits between the toolbar and the
        table, offering to upgrade visible-selection → cohort-selection
        when total > current page. JS toggles `.is-visible`.
     3. Sticky bar (existing `.ds-sticky-bar` family; see § Settings
        memory entities for the original consumer). */
.ds-tbl tbody tr:has(input[data-grid-select]:checked){background:var(--bg-subtle)}
.ds-grid__select-all-strip{
    display:none;
    align-items:center;
    gap:10px;
    padding:8px 16px;
    background:var(--bg-subtle);
    border-bottom:1px solid var(--border-subtle);
    font-size:12px;
    color:var(--text-secondary);
}
.ds-grid__select-all-strip.is-visible{display:flex}
.ds-grid__select-all-strip__text{flex:1;min-width:0}
.ds-grid__select-all-strip__text strong{color:var(--text-primary);font-weight:600}

/* Typed-confirm dialog body — composes `.ds-modal`. The input is
   typographically distinct from a regular ds-input: bigger, centred,
   tabular numerals so the user reads "type the count" not "type the
   id". Hint sits below the input in muted secondary text. */
.ds-typed-confirm__body{margin:12px 0}
.ds-typed-confirm__input{
    display:block;
    width:100%;
    max-width:180px;
    margin:12px auto 0;
    padding:8px 12px;
    font-family:var(--font-sans);
    font-size:1.125rem;
    font-variant-numeric:tabular-nums;
    text-align:center;
    letter-spacing:.06em;
    color:var(--text-primary);
    background:#fff;
    border:1px solid var(--border-input);
    border-radius:var(--radius);
    outline:none;
}
.ds-typed-confirm__input:focus{border-color:var(--steel);box-shadow:0 0 0 2px rgba(110,135,152,.2)}
.ds-typed-confirm__hint{
    display:block;
    text-align:center;
    font-size:.75rem;
    color:var(--text-muted);
    margin-top:6px;
}
.ds-typed-confirm__body p{margin:0;color:var(--text-secondary);font-size:.938rem}

.msb-row-menu{position:relative;display:inline-flex;align-items:center;justify-content:flex-end}
.msb-row-current td:first-child{font-weight:600}
.msb-current-dot{display:inline-block;width:8px;height:8px;border-radius:999px;background:var(--steel);margin-right:8px;vertical-align:middle}
.ds-footer{margin-top:8rem;padding-top:32px;border-top:1px solid var(--bg-surface);text-align:center}.ds-footer p{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.16em;color:var(--text-secondary)}
.ds-footer{margin-top:8rem;padding-top:32px;border-top:1px solid var(--bg-surface);text-align:center}.ds-footer p{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.16em;color:var(--text-secondary)}
.demo-box{background:var(--bg-page);border:1px solid var(--bg-surface);border-radius:12px;padding:24px;margin-bottom:32px}
/* Page-form layout: stacked groups with even vertical rhythm.  The form
   is the ONLY parent that sets group-to-group spacing — `.ds-form-group`
   is intentionally label↔input only (gap:4px), so consumers must wrap
   in `.ds-form` (or another flex parent with gap) to avoid collapsed
   forms where the actions row lands flush against the last input. */
.ds-form{display:flex;flex-direction:column;gap:16px}
.ds-form-actions{display:flex;align-items:center;gap:12px}
.ds-form-group{display:flex;flex-direction:column;gap:4px}
.ds-form-row{display:flex;align-items:center;gap:12px}
.ds-form-row .ds-form-group{flex:1}
.ds-modal__bd .ds-form-row{flex-direction:column;align-items:stretch;gap:6px}
.drawer__bd .ds-form-row{flex-direction:column;align-items:stretch;gap:6px}
.ds-label{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-secondary)}
.ds-label .req{color:var(--error);margin-left:4px}
.ds-input{display:block;width:100%;padding:10px 16px;font-family:var(--font-sans);font-size:.938rem;color:var(--text-primary);background:#fff;border:1px solid var(--border-input);border-radius:var(--radius);transition:border-color .2s ease,box-shadow .2s ease;outline:none}
.ds-input::placeholder{color:var(--text-secondary);opacity:.6}
.ds-input:focus{border-color:var(--steel);box-shadow:0 0 0 2px rgba(110,135,152,.2)}
.ds-input:disabled{background:var(--border-subtle);border-color:var(--border-subtle);cursor:not-allowed;opacity:.6}
.ds-input-pw{position:relative;display:block}
.ds-input-pw>.ds-input{padding-right:42px}
.ds-input-pw__toggle{position:absolute;top:50%;right:6px;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;background:transparent;border:0;border-radius:6px;color:var(--text-secondary);cursor:pointer;transition:color .15s ease,background .15s ease}
.ds-input-pw__toggle:hover{color:var(--text-primary);background:rgba(0,0,0,.04)}
.ds-input-pw__toggle:focus-visible{outline:2px solid var(--steel);outline-offset:1px;color:var(--text-primary)}
.ds-input-pw__toggle .icon{width:18px;height:18px}
.ds-field-err{font-size:.813rem;color:var(--error);margin-top:2px}
.ds-field-hint{font-size:.813rem;color:var(--text-secondary);margin-top:2px}
.ds-input[aria-invalid="true"],.ds-textarea[aria-invalid="true"],.ds-select[aria-invalid="true"]{border-color:var(--error)}
.ds-input[aria-invalid="true"]:focus,.ds-textarea[aria-invalid="true"]:focus,.ds-select[aria-invalid="true"]:focus{border-color:var(--error);box-shadow:0 0 0 2px rgba(179,38,30,.18)}
.ds-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:24px 16px;border:2px dashed var(--bg-surface);border-radius:12px;cursor:pointer;text-align:center;transition:border-color .2s,background .2s;background:#fff}
.ds-dropzone:hover,.ds-dropzone.is-active{border-color:var(--steel);background:rgba(110,135,152,.05)}
.ds-dropzone__icon{font-size:1.5rem;color:var(--text-secondary);line-height:1}
.ds-dropzone__hint{font-size:.875rem;color:var(--text-secondary)}
.ds-dropzone.has-file .ds-dropzone__hint{display:none}
.ds-dropzone__selected{font-size:.875rem;color:var(--text-primary);font-weight:500;word-break:break-all}
.ds-textarea{display:block;width:100%;min-height:80px;padding:12px;font-family:var(--font-sans);font-size:.938rem;color:var(--text-primary);background:#fff;border:1px solid var(--bg-surface);border-radius:var(--radius);resize:vertical;outline:none}
.ds-textarea:focus{border-color:var(--steel);box-shadow:0 0 0 2px rgba(110,135,152,.2)}
.ds-select{display:block;width:100%;padding:10px 16px;font-family:var(--font-sans);font-size:.938rem;color:var(--text-primary);background:#fff;border:1px solid var(--border-input);border-radius:var(--radius);outline:none;cursor:pointer}
.ds-select:focus{border-color:var(--steel);box-shadow:0 0 0 2px rgba(110,135,152,.2)}
.ds-cb-group{display:flex;align-items:flex-start;gap:8px;cursor:pointer;user-select:none}
.ds-cb{appearance:none;-webkit-appearance:none;width:18px;height:18px;border:1px solid var(--bg-surface);border-radius:4px;background:var(--bg-surface);cursor:pointer;position:relative;flex-shrink:0;margin-top:2px;transition:all .15s ease}
.ds-cb:checked{background:var(--steel);border-color:var(--steel)}
.ds-cb:checked::after{content:"";position:absolute;left:5px;top:1px;width:5px;height:10px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}
.ds-cb-label{font-size:.938rem;color:var(--text-primary)}
.ds-toggle{position:relative;display:inline-block}
.ds-toggle input{position:absolute;opacity:0;width:0;height:0}
.ds-toggle-track{display:block;width:44px;height:24px;background:var(--bg-surface);border-radius:999px;cursor:pointer;position:relative;transition:background .25s ease}
.ds-toggle-track::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;background:#fff;border-radius:50%;transition:transform .25s ease;box-shadow:0 1px 3px rgba(0,0,0,.3)}
.ds-toggle input:checked+.ds-toggle-track{background:var(--steel)}
.ds-toggle input:checked+.ds-toggle-track::after{transform:translateX(20px)}

.auth-shell{min-height:100vh;display:flex;flex-direction:column;background:var(--bg-page)}
.auth-shell__main{flex:1;display:flex;flex-direction:column;align-items:center;padding:32px 16px;width:100%;box-sizing:border-box}
.auth-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:32px}
.auth-shell__brand-link{display:flex;align-items:center;gap:11px;text-decoration:none;color:var(--text-primary)}
.auth-shell__mark{width:32px;height:32px;flex-shrink:0}
.auth-shell__workspace{font-size:1.25rem;font-weight:700;letter-spacing:-.025em;color:var(--text-primary);line-height:1}
.auth-shell__page{width:100%;max-width:460px;display:flex;flex-direction:column;gap:24px}
.auth-shell__header{text-align:center}
.auth-shell__title{font-size:1.5rem;font-weight:700;color:var(--text-primary);margin:0 0 8px}
.auth-shell__header .app-page__eyebrow{justify-content:center;display:inline-block;font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.16em;color:var(--text-secondary);margin-bottom:8px}
.auth-shell__header .app-page__subtitle{margin:8px 0 0;color:var(--text-secondary);font-size:.938rem}
.auth-shell__footer{margin-top:48px;font-size:.75rem;color:var(--text-secondary);text-align:center}

.auth-card{max-width:420px;margin:0 auto}
.auth-card--wide{max-width:460px}
.auth-form{display:grid;gap:16px}
.auth-form__actions{margin-top:8px}
.auth-card__support{margin-top:16px;text-align:center;font-size:.813rem;color:var(--text-secondary)}
.auth-card__support p:last-child{margin-bottom:0}
.auth-profile-grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.auth-profile-value{min-height:46px;padding:10px 16px;background:#fff;border:1px solid var(--bg-surface);border-radius:12px;display:flex;align-items:center}
/* Document-level identity form is empty (fields reference it via form=). */
.auth-profile-edit{display:none}
.auth-profile-identity{display:flex;gap:28px;align-items:flex-start;flex-wrap:wrap}
.auth-profile-identity > .auth-profile-grid{flex:1;min-width:260px}
.auth-profile-debug{margin-top:16px;color:var(--text-secondary);font-size:.875rem}
.auth-profile-debug > summary{cursor:pointer;padding:6px 0;font-weight:500}
.auth-avatar-block{display:flex;flex-direction:column;align-items:flex-start;gap:12px;min-width:160px}
.auth-avatar-preview{width:128px;height:128px;border-radius:50%;overflow:hidden;background:var(--bg-surface);border:1px solid var(--border-subtle);display:flex;align-items:center;justify-content:center}
.auth-avatar-preview__img{width:100%;height:100%;object-fit:cover;display:block}
.auth-avatar-actions{display:flex;flex-direction:column;gap:6px;align-items:flex-start}
.app-account-chip__avatar{overflow:hidden;padding:0;width:28px;height:28px}
.app-account-chip__avatar-img{width:100%;height:100%;object-fit:cover;display:block;border-radius:50%}
.auth-avatar-dialog{max-width:540px}
.auth-avatar-dialog__form{display:flex;flex-direction:column;min-height:0}
.auth-avatar-dialog__bd{gap:16px}
.auth-avatar-picker{display:flex;flex-direction:column;align-items:center;gap:10px;padding:32px 16px;border:1px dashed var(--border-subtle);border-radius:12px;background:var(--bg-surface)}
.auth-avatar-crop{display:flex;flex-direction:column;gap:12px}
.auth-avatar-crop__stage{width:100%;height:320px;background:#111;border-radius:12px;overflow:hidden}
.auth-avatar-crop__stage img{display:block;max-width:100%}
/* WYSIWYG cropper hint — the crop overlay echoes the destination shape.
   .cropper-view-box is the lit window over the crop area; .cropper-face is
   the click target inside it. The vendor CSS leaves both square; we round
   them so the user sees the final avatar shape while choosing the crop. */
.auth-avatar-crop__stage--circle .cropper-view-box,
.auth-avatar-crop__stage--circle .cropper-face{border-radius:50%}
.auth-avatar-crop__stage--square .cropper-view-box,
.auth-avatar-crop__stage--square .cropper-face{border-radius:var(--radius)}
.auth-avatar-crop__controls{display:flex;align-items:center;gap:12px}
.auth-avatar-crop__controls input[type="range"]{flex:1}
.auth-avatar-error{margin:0}
.auth-security-status{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin:0}
.auth-security-status > div{display:flex;flex-direction:column;gap:4px}
.auth-security-status dt{font-size:.8125rem;color:var(--text-secondary);font-weight:500}
.auth-security-status dd{margin:0;font-weight:500}
.phone-picker-row{align-items:flex-end}

.add-user-mode{display:flex;gap:2px;background:var(--bg-surface);border-radius:8px;padding:3px;width:fit-content;margin-bottom:20px}
.add-user-mode__btn{padding:6px 16px;font-size:.8125rem;font-weight:500;border:none;background:none;border-radius:6px;cursor:pointer;color:var(--text-secondary);transition:all .15s ease;white-space:nowrap}
.add-user-mode__btn--active{background:#fff;color:var(--text-primary);box-shadow:0 1px 4px rgba(0,0,0,.1)}
.add-user-mode__btn:focus{outline:2px solid var(--steel);outline-offset:1px}
.admin-users__grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.admin-users__filter-chip{display:inline-flex;align-items:center;gap:6px}
.admin-users__filter-icon{width:14px;height:14px;flex-shrink:0}
.admin-users__resend-failed{display:inline-flex;margin:0}
.admin-users__resend-failed-btn{color:var(--error);background:rgba(179,38,30,0.08);border:1px solid rgba(179,38,30,0.18)}
.admin-users__resend-failed-btn:hover{background:rgba(179,38,30,0.16);color:var(--error)}
.admin-users__phone-row{align-items:flex-end;grid-column:span 2}
.admin-users__phone-country{flex:0 0 auto;width:110px}
.phone-group{display:flex;flex-direction:column;gap:4px}
.phone-group__row{align-items:center;gap:8px}
.phone-group__country{flex:0 0 auto;width:120px}
.phone-group__number{flex:1;min-width:0}
.phone-picker{position:relative}
.phone-picker__btn{display:flex;align-items:center;justify-content:space-between;gap:6px;width:100%;text-align:left;cursor:pointer;padding-right:10px;font-size:.875rem}
.phone-picker__sel{display:flex;align-items:center;gap:6px;min-width:0;overflow:hidden}
.phone-picker__caret{color:var(--text-secondary);flex-shrink:0;margin-left:auto;display:block;width:16px;height:16px}
.phone-picker__list{position:fixed;z-index:400;background:var(--bg-elevated,#fff);border:1px solid rgba(228,221,209,.9);border-radius:10px;box-shadow:0 6px 20px rgba(0,0,0,.1);min-width:240px;max-height:280px;overflow-y:auto;list-style:none;padding:4px 0;margin:0}
.phone-picker__item{display:flex;align-items:center;gap:10px;padding:8px 14px;cursor:pointer;font-size:.875rem;white-space:nowrap}
.phone-picker__item:hover{background:rgba(110,135,152,.08)}
.phone-picker__item[aria-selected=true]{background:rgba(110,135,152,.12)}
.phone-picker__item-code{margin-left:auto;color:var(--text-secondary);font-size:.8rem;padding-left:12px}
.admin-users__table{table-layout:fixed;width:100%}
.admin-users__table th,.admin-users__table td{padding-left:8px;padding-right:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.admin-users__table th:first-child,.admin-users__table td:first-child{padding-left:12px}
.admin-users__table th:last-child,.admin-users__table td:last-child{padding-right:12px;white-space:normal}
.admin-users__table col.c-user{width:9%}
.admin-users__table col.c-role{width:6%}
.admin-users__table col.c-projects{width:22%}
.admin-users__table col.c-name{width:10%}
.admin-users__table col.c-email{width:14%}
.admin-users__table col.c-phone{width:9%}
.admin-users__table col.c-astate{width:13%}
.admin-users__table col.c-2fa{width:5%}
.admin-users__table col.c-actions{width:5%}
.admin-users__table td:nth-child(3){white-space:normal}
.admin-users__toggle-form{display:inline-flex;margin:0}
.user-project-chips--muted{opacity:.4;pointer-events:none}
.user-project-chips--muted .user-project-chip:not(.user-project-chip--all){filter:grayscale(1)}

/* Collapsed: grid-template-columns already set on .app-shell--collapsed above;
   sidebar just shrinks in place, no margin-left / left overrides needed. */
.app-shell--collapsed .ds-sb{width:88px}
/* Centre the brand link (28px mark, text hidden) inside the 88px rail. */
.app-shell--collapsed .ds-sb__brand{padding:0;justify-content:center}
.app-shell--collapsed .ds-sb__sub{padding-left:0!important;padding-right:0!important;text-align:center;font-size:.56rem}
.app-shell--collapsed .ds-sb a{justify-content:center;padding-left:10px;padding-right:10px}
/* Collapsed: hide the nav label by class, NOT by :last-child. The label is
   no longer guaranteed to be the last child since live_topic items append a
   .ds-sb__nav-badge sibling (see base.html). A positional selector would
   silently break for those items. */
.app-shell--collapsed .ds-sb a .app-nav__label,
.app-shell--collapsed .ds-sb__brand .ds-sb__sub{display:none}
.app-shell--collapsed .ds-sb__brand-link{justify-content:center}
.app-shell--collapsed .ds-sb__brand-link .ds-sb__t{display:none}
.app-shell--collapsed .app-nav__icon{min-width:2.25rem}
/* Collapsed-state tooltip: rendered via JS into body as .app-nav-tooltip to escape
   sidebar's overflow container. See the tooltip script near the end of base.html. */
.app-nav-tooltip{position:fixed;padding:8px 10px;border-radius:10px;background:var(--text-primary);color:#fff;font-size:.75rem;font-weight:600;letter-spacing:.02em;white-space:nowrap;pointer-events:none;box-shadow:0 10px 24px rgba(0,0,0,.18);z-index:1100;opacity:0;transform:translate(-4px,-50%);transition:opacity .16s ease,transform .16s ease}
.app-nav-tooltip--visible{opacity:1;transform:translate(0,-50%)}
.app-nav-tooltip::before{content:"";position:absolute;right:100%;top:50%;transform:translateY(-50%);border-width:6px;border-style:solid;border-color:transparent var(--text-primary) transparent transparent}
/* ---- platform tooltip primitive (.ds-tip) ----
   Style-guide § Atoms · Tooltip: 120ms show delay, 280px max width with
   wrap, 12ms fade. Apply class="ds-tip" + data-tip="..." + matching
   aria-label. Used by the datagrid action manifest (disabled kebab items),
   the comparison matrix, brief tab, and any other surface that needs a
   styled, fast tooltip. Avoid native title= for anything richer than a
   one-word fallback.

   `.cmp-tip` is kept as a legacy alias (the class was originally named
   after its first consumer, the comparison matrix). New code uses
   `.ds-tip`. The alias may be removed once all consumers migrate. */
.ds-tip, .cmp-tip{position:relative}
.ds-tip::after, .cmp-tip::after{
  content:attr(data-tip);
  position:absolute;
  bottom:calc(100% + 6px);
  left:50%;
  transform:translateX(-50%);
  max-width:280px;
  width:max-content;
  white-space:normal;
  text-align:center;
  padding:5px 9px;
  background:var(--text-primary,#384652);
  color:#fff;
  font-size:.75rem;
  font-weight:500;
  line-height:1.35;
  border-radius:5px;
  box-shadow:0 4px 10px rgba(0,0,0,.16);
  pointer-events:none;
  opacity:0;
  transition:opacity .12s ease;
  transition-delay:120ms;
  z-index:30;
}
.ds-tip:hover::after, .cmp-tip:hover::after{opacity:1}

/* Collapsed sidebar: suppress horizontal overflow that used to be created by
   CSS tooltip pseudo-elements. Tooltips are now rendered outside the sidebar. */
.app-shell--collapsed .ds-sb__nav,
.app-shell--collapsed .ds-sb__footer{overflow-x:hidden}
.app-shell--collapsed .app-sidebar-toggle{box-shadow:none;background:var(--bg-surface);border-color:transparent}

@media (max-width: 1280px){
  /* Below 1280px (tablet + mobile) the sidebar is an overlay drawer, not a
     grid column. The desktop collapse/expand toggle button disappears at
     this width and the hamburger is the only sidebar control.
     Also reverts the desktop scroll-container layout: body scrolls
     naturally, .ds-main is a normal block, .ds-main__scroll is
     transparent, topline is position:fixed at viewport top. */
  .app-shell,.app-shell--collapsed{display:block;grid-template-columns:none;height:auto;min-height:100vh}
  .ds-sb{position:fixed;top:0;left:0;bottom:0;height:auto;width:min(280px,calc(100vw - 3rem));background:var(--bg-sidebar);border-right:1px solid var(--border-subtle);overflow-y:hidden;overflow-x:hidden;z-index:1100;padding:0;transform:translateX(-100%);transition:transform .3s ease-out;box-shadow:none}
  .app-shell--mobile-menu-open .ds-sb{transform:translateX(0);box-shadow:0 18px 48px rgba(0,0,0,.18)}
  /* Topline is position:fixed below 1280px so it floats out of flow — ds-main
     compensates with padding-top equal to the topline's 56px height. */
  .ds-main{margin-left:0;padding:56px 0 0;display:block;height:auto;overflow:visible}
  .ds-main__scroll{flex:initial;min-height:0;overflow:visible;scrollbar-gutter:auto}
  /* Body owns the scrollbar on mobile/tablet — keep its gutter stable so
     a tall→short content change doesn't shift the page horizontally. */
  body{scrollbar-gutter:stable}
  .app-topline{position:fixed;top:0;left:0;right:0;z-index:1050;flex-direction:row;align-items:center;padding:0 16px;justify-content:space-between;gap:8px}
  .app-topline__start{width:auto;justify-content:flex-start;flex:1;min-width:0}
  .app-topline__aux{width:auto}
  .app-topline__account{gap:4px;flex-shrink:0}
  .app-account-chip__name{display:none}
  .app-account-chip__trigger{padding:4px}
  .app-account-chip__chevron{display:none}
  /* Double-class selector to outscore the desktop rule's 0,2,0 specificity. */
  .app-sidebar-toggle.app-sidebar-toggle{display:none}
  .app-mobile-menu-toggle{display:inline-flex!important;order:-1;flex-shrink:0;box-shadow:none;background:transparent;border-color:transparent;color:var(--text-secondary)}
  .app-mobile-menu-toggle:hover:not(:disabled){background:rgba(255,255,255,.5);color:var(--text-primary)}
  .app-mobile-overlay{display:block;position:fixed;inset:0;border:none;padding:0;background:rgba(56,70,82,.32);opacity:0;pointer-events:none;transition:opacity .2s ease;z-index:1099}
  .app-shell--mobile-menu-open .app-mobile-overlay{opacity:1;pointer-events:auto}
  .app-shell--mobile .ds-sb,
  .app-shell--mobile.app-shell--collapsed .ds-sb{width:min(240px,calc(100vw - 3rem));padding:1.5rem 0}
  .app-shell--mobile .ds-main,
  .app-shell--mobile.app-shell--collapsed .ds-main{margin-left:0}
  .app-shell--mobile .ds-sb__brand,
  .app-shell--mobile.app-shell--collapsed .ds-sb__brand{height:56px;display:flex;align-items:center;padding:0 1.5rem}
  .app-shell--mobile .ds-sb__t,
  .app-shell--mobile.app-shell--collapsed .ds-sb__t{font-size:1.25rem;text-align:left}
  .app-shell--mobile .ds-sb__sub,
  .app-shell--mobile.app-shell--collapsed .ds-sb__sub{padding-left:24px!important;padding-right:24px!important;text-align:left;font-size:11px}
  .app-shell--mobile .ds-sb a,
  .app-shell--mobile.app-shell--collapsed .ds-sb a{justify-content:flex-start;padding-left:12px;padding-right:12px}
  .app-shell--mobile .ds-sb a .app-nav__label,
  .app-shell--mobile .ds-sb__brand .ds-sb__sub,
  .app-shell--mobile.app-shell--collapsed .ds-sb a .app-nav__label,
  .app-shell--mobile.app-shell--collapsed .ds-sb__brand .ds-sb__sub{display:inline}
  .app-shell--mobile .app-nav__icon,
  .app-shell--mobile.app-shell--collapsed .app-nav__icon{min-width:2rem}
  .app-shell--mobile .ds-sb a[data-tooltip]::after,
  .app-shell--mobile .ds-sb a[data-tooltip]::before,
  .app-shell--mobile.app-shell--collapsed .ds-sb a[data-tooltip]::after,
  .app-shell--mobile.app-shell--collapsed .ds-sb a[data-tooltip]::before{display:none}
  .admin-users__phone-row{grid-column:1 / -1;flex-direction:column;align-items:stretch}
  .admin-users__phone-country{width:auto;flex:1}
  .phone-group__row{flex-direction:column;align-items:stretch}
  .phone-group__country{width:auto;flex:1}
}

/* ── FORM FIELD HINTS ───────────────────────────────────────────────────── */
.ds-hint{font-size:.75rem;color:var(--text-secondary);opacity:.65;margin-top:3px;line-height:1.4}

/* ── SELECT CHEVRON (consistent cross-browser) ──────────────────────────── */
select.ds-input{appearance:none;-webkit-appearance:none;padding-right:36px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23697784' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 10px center;background-size:16px}

/* ── CLICK-TO-COPY ──────────────────────────────────────────────────────── */
.ds-copyable{cursor:pointer;border-radius:4px;transition:background .15s}
.ds-copyable:hover{background:rgba(110,135,152,.1)}
.ds-overflow-tag{display:inline-block;background:var(--bg-surface);border-radius:4px;padding:1px 6px;margin:2px 2px 2px 0;font-family:monospace}
.ds-overflow-item{display:none}.ds-overflow-item.ds-visible{display:inline-block}
.ds-overflow-toggle{background:none;border:none;padding:0;cursor:pointer;font-size:inherit;color:var(--text-secondary);font-family:inherit;line-height:inherit;text-decoration:none}
.ds-overflow-toggle:hover{text-decoration:underline}


/* FILTER GROUP */
.ds-filter-group{display:flex;gap:4px;align-items:center}
.ds-filter-group .btn.active{background:var(--steel);color:#fff;border-color:var(--steel)}
.ds-filter-group .btn.active:hover{opacity:.88}

/* STEP PROGRESS */
.ds-steps{display:flex;flex-direction:column;gap:2px}
.ds-steps__item{display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:6px}
.ds-steps__item--active{background:rgba(110,135,152,.1);font-weight:500}
.ds-steps__item--failed{background:rgba(217,48,37,.05)}
.ds-steps__marker{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;font-size:.7rem;font-weight:700;flex-shrink:0;background:var(--bg-surface);color:var(--text-secondary)}
.ds-steps__item--done .ds-steps__marker{background:var(--success);color:#fff}
.ds-steps__item--active .ds-steps__marker{background:var(--steel);color:#fff}
.ds-steps__item--failed .ds-steps__marker{background:var(--error);color:#fff}
.ds-steps__label{font-size:.875rem}
.ds-steps__item--failed .ds-steps__label{color:var(--error)}
.ds-steps__item:not(.ds-steps__item--done):not(.ds-steps__item--active):not(.ds-steps__item--failed) .ds-steps__label{color:var(--text-secondary)}

.ds-copy-toast{position:fixed;bottom:20px;right:20px;background:var(--text-primary);color:#fff;padding:7px 14px;border-radius:8px;font-size:.8rem;font-weight:500;z-index:9999;pointer-events:none;opacity:0;transform:translateY(6px);transition:opacity .15s ease,transform .15s ease}
.ds-copy-toast.show{opacity:1;transform:translateY(0)}

/* STATUS ICONS — reusable across platform */
.ds-status-icon{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;font-size:.7rem;font-weight:700;flex-shrink:0;line-height:1}
.ds-status-icon--ok{background:var(--success);color:#fff}
.ds-status-icon--warn{background:var(--warning);color:#fff}
.ds-status-icon--err{background:var(--error);color:#fff}
.ds-status-icon--muted{background:var(--bg-surface);color:var(--text-secondary)}
/* COPY TARGET — click to copy + cursor hint */
.ds-copy-target{font-family:monospace;font-size:.8em;color:var(--text-secondary);cursor:pointer;border-radius:4px;padding:1px 4px;transition:background .15s}
.ds-copy-target:hover{background:rgba(110,135,152,.1)}

/* WORKFLOW RUN DETAIL */
.wf-run-meta-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:.813rem}
.wf-run-meta-sep{width:1px;height:14px;background:var(--bg-surface);flex-shrink:0}

/* Timeline */
.wf-timeline{display:flex;flex-direction:column;gap:0}
.wf-tl-node{display:flex;align-items:center;gap:10px;padding:8px 0;position:relative}
.wf-tl-node:not(:last-child)::after{content:'';position:absolute;left:10px;top:calc(50% + 11px);bottom:-1px;width:2px;background:var(--bg-surface)}
.wf-tl-node--sys{opacity:.7}
.wf-tl-icon{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;font-size:.7rem;font-weight:700;flex-shrink:0;background:var(--bg-surface);color:var(--text-secondary);z-index:1;line-height:1}
.wf-tl-icon--ok{background:var(--success);color:#fff}
.wf-tl-icon--warn{background:var(--warning);color:#fff}
.wf-tl-icon--err{background:var(--error);color:#fff}
.wf-tl-glyph{width:12px;height:12px;stroke:currentColor;stroke-width:2.5;fill:none;stroke-linecap:round;stroke-linejoin:round}
.wf-tl-label{font-size:.875rem;font-weight:500}
.wf-tl-type{font-size:.7rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.03em}
.wf-tl-time{margin-left:auto;font-size:.75rem;color:var(--text-secondary);font-variant-numeric:tabular-nums}
.wf-tl-detail{padding:0 0 6px 32px;font-size:.75rem}
.wf-tl-summary{color:var(--text-secondary);cursor:pointer;user-select:none;font-size:.72rem}
.wf-tl-pre{font-size:.7rem;background:var(--bg-surface);border-radius:4px;padding:8px 10px;overflow-x:auto;margin-top:4px;max-height:200px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}

/* WORKFLOW EDITOR */
.wf-step-card{border:1px solid var(--bg-surface);border-radius:8px;background:#fff}
.wf-step-card+.wf-step-card{margin-top:6px}
.wf-step-card__row{display:flex;align-items:center;gap:10px;padding:8px 10px;min-height:44px;cursor:pointer}
.wf-step-card__row:hover{background:rgba(228,221,209,.18)}
.wf-step-card--collapsed .wf-step-card__row{border-bottom:0}
.wf-step-card__body{border-top:1px solid var(--border-subtle);transition:none}
.wf-step-card--collapsed .wf-step-card__body{display:none}
.wf-step-card__body>.wf-step-card__sub{border-top:0}
/* Accordion header chrome */
.wf-step-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;flex-shrink:0;color:var(--text-secondary)}
.wf-step-icon .lucide-svg,.wf-step-icon__svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.wf-step-card__summary{font-size:.813rem;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
.wf-step-card__summary--empty{flex:1}
.wf-step-card:not(.wf-step-card--collapsed) .wf-step-card__summary{display:none}
.wf-step-card:not(.wf-step-card--collapsed) .wf-step-card__fields{display:flex}
.wf-step-card--collapsed .wf-step-card__fields{display:none}
.wf-step-card__chev{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;flex-shrink:0;color:var(--text-secondary);transition:transform .15s ease;transform:rotate(0deg)}
.wf-step-card__chev svg{width:1rem;height:1rem;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.wf-step-card--collapsed .wf-step-card__chev{transform:rotate(-90deg)}
.wf-step-card__num{width:22px;height:22px;border-radius:50%;background:var(--steel);color:#fff;font-size:.65rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}
.wf-step-card__type{font-size:.68rem;font-weight:700;text-transform:uppercase;color:var(--text-secondary);min-width:80px;flex-shrink:0;letter-spacing:.03em;white-space:nowrap}
.wf-step-label{font-family:inherit;font-size:.78rem;font-weight:500;color:var(--text-primary);background:transparent;border:0;border-bottom:1px dotted rgba(110,135,152,.45);border-radius:0;padding:1px 2px;max-width:160px;min-width:60px;transition:background-color .15s,border-color .15s;flex-shrink:0;cursor:text}
.wf-step-label:hover{border-bottom-color:var(--steel);background:rgba(110,135,152,.06)}
.wf-step-label:focus{outline:none;border-bottom:1px solid var(--steel);background:rgba(110,135,152,.1)}
.wf-step-label--conflict{border-bottom-color:var(--error)!important;background:rgba(239,68,68,.06)}
.wf-step-label::placeholder{color:var(--text-secondary);font-style:italic;opacity:.7}
.wf-step-alias{display:inline-flex;align-items:center;font-family:var(--font-mono);font-size:.75rem;font-variant-numeric:tabular-nums;padding:2px 6px;border-radius:4px;background:var(--bg-surface);color:var(--text-secondary);flex-shrink:0;white-space:nowrap;line-height:1.4}
/* Variable picker */
.wf-tpl-wrap{display:flex;gap:4px;align-items:flex-start;grid-column:3;margin-top:2px;position:relative}
/* Lift the host wrap above sibling wraps while its Jinja picker is open, so
 * the dropdown isn't punched through by the next field's {x} button. JS
 * toggles the class around the picker lifecycle (template_builder.js). */
.wf-tpl-wrap--picker-open{z-index:220}
.wf-tpl-wrap .wf-input-literal{flex:1;grid-column:unset;margin-top:0}
.wf-tpl-btn{font-family:monospace;font-size:.75rem;padding:2px 6px;flex-shrink:0;color:var(--text-secondary)}
.wf-var-picker{position:absolute;right:0;top:100%;bottom:auto;z-index:200;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.1);min-width:260px;max-width:340px;max-height:320px;overflow-y:auto;overflow-x:hidden;padding:6px 0}
.wf-var-picker.open-up{top:auto;bottom:100%}
.wf-var-picker__group{font-size:.7rem;font-weight:600;color:var(--text-secondary);padding:6px 12px 2px;text-transform:uppercase;letter-spacing:.03em}
.wf-var-picker__item{display:block;width:100%;text-align:left;padding:4px 12px;font-size:.75rem;font-family:monospace;cursor:pointer;border:none;background:none;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wf-var-picker__item:hover{background:var(--bg-surface)}
.wf-step-card__fields{flex:1;display:flex;gap:8px;align-items:center;min-width:0}
.wf-step-card__sub{padding:0 10px 10px 52px;display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--border-subtle)}
.wf-step-card__sub-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
/* Chip input */
.ds-chip-input{display:flex;flex-wrap:wrap;gap:4px;align-items:center;padding:3px 8px;border:1px solid var(--border);border-radius:6px;background:#fff;min-height:36px;cursor:text}
.ds-chip-input:focus-within{outline:2px solid var(--steel);outline-offset:-1px}
.ds-chip-input__text{border:none;outline:none;background:transparent;font-size:.813rem;min-width:80px;flex:1;padding:2px 0;line-height:1.5;font-family:var(--font-sans)}
/* Chip atom — used for filter chips on datagrid headers AND tag chips
 * inside .ds-chip-input.  Color variants below override base bg/border/color.
 *
 * Interactivity model: chips are STATIC by default (cursor:default, no hover
 * reaction).  A chip is treated as interactive — pointer cursor + hover
 * background — only when it is one of:
 *   - a native <button class="ds-chip">       (canonical filter/toolbar chip)
 *   - a native <a class="ds-chip">             (memory filter chips etc.)
 *   - a <span class="ds-chip" role="button">  (JS-built interactive chip)
 * FYI / structural chips (workload count, "Main", "Primary", "Default") render
 * as <span class="ds-chip"> and stay flat — they communicate state, not an
 * affordance, so a hover flash would lie about what clicking does. */
.ds-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 8px 3px 10px;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 12px;
    font-weight: 500;
    color: var(--text-primary);
    cursor: default;
}
button.ds-chip,
a.ds-chip,
.ds-chip[role="button"] {
    cursor: pointer;
}
button.ds-chip:not(.is-active):hover,
a.ds-chip:not(.is-active):hover,
.ds-chip[role="button"]:not(.is-active):hover {
    background: #fff;
    border-color: var(--border-strong);
}
.ds-chip svg { width: 12px; height: 12px; opacity: 0.6; }
.ds-chip.is-active {
    background: var(--text-primary);
    border-color: var(--text-primary);
    color: #fff;
}
/* Hover-on-active variant: the base hover rule above used to swap background
 * to #fff on every chip, which inverted .is-active chips to white-on-white
 * (the label disappeared). The :not(.is-active) guard scopes the base
 * hover; this rule gives active chips a subtle lighter-dark cue so the
 * hover affordance survives without erasing the label. Caught in the
 * cron-builder Daily-mode day chips — same bug applied everywhere
 * .ds-chip + .is-active was used as a button. */
button.ds-chip.is-active:hover,
a.ds-chip.is-active:hover,
.ds-chip.is-active[role="button"]:hover {
    background: var(--text-primary);
    border-color: var(--text-primary);
    color: #fff;
    filter: brightness(1.15);
}
.ds-chip.is-active svg { opacity: 1; }
.ds-chip__close {
    width: 14px; height: 14px;
    border-radius: 50%;
    display: grid; place-items: center;
    margin-left: 2px;
    cursor: pointer;
    opacity: 0.6;
}
.ds-chip__close:hover { opacity: 1; background: rgba(0,0,0,0.06); }
/* Lucide-x SVG inside chip close/dismiss — override .ds-chip svg's
 * generic 0.6 (let the button wrapper own the fade) and force display:block
 * so the SVG has no inline-baseline gap. The wrapper's grid place-items
 * does the actual centering. */
.ds-chip__close svg,
.ds-chip__dismiss svg {
    width: 12px;
    height: 12px;
    opacity: 1;
    display: block;
}

/* When .ds-chip__close is a <button> (interactive remove from a tag picker
 * or filter strip) strip the default UA chrome so the canonical 14×14 grid
 * styling above shows through. The span-form close (server-rendered inside
 * ds_grid_filter.html) doesn't need this and the rule is benign there. */
button.ds-chip__close { background: none; border: 0; padding: 0; }
/* Tag color variants — applied as .ds-chip--slate, .ds-chip--blue, etc. */
.ds-chip--slate{background:rgba(110,135,152,.14);border-color:rgba(110,135,152,.3);color:var(--steel)}
.ds-chip--blue{background:rgba(59,130,246,.12);border-color:rgba(59,130,246,.3);color:#2563eb}
.ds-chip--green{background:rgba(62,142,65,.12);border-color:rgba(62,142,65,.3);color:var(--success)}
.ds-chip--amber{background:rgba(230,162,60,.14);border-color:rgba(230,162,60,.35);color:#b45309}
.ds-chip--rose{background:rgba(244,63,94,.12);border-color:rgba(244,63,94,.3);color:#be123c}
.ds-chip--violet{background:var(--ai-accent-soft);border-color:var(--ai-accent-strong);color:var(--ai-accent)}
.ds-chip--orange{background:rgba(249,115,22,.12);border-color:rgba(249,115,22,.3);color:#c2410c}
.ds-chip--teal{background:rgba(20,184,166,.12);border-color:rgba(20,184,166,.3);color:#0f766e}
/* Aliases used by workflow maturity chips */
.ds-chip--warn{background:rgba(230,162,60,.14);border-color:rgba(230,162,60,.35);color:#b45309}
.ds-chip--grey{background:rgba(110,135,152,.12);border-color:rgba(110,135,152,.25);color:var(--text-secondary)}
/* Tag picker popover */
.tag-picker{position:fixed;z-index:9999;min-width:200px;background:#fff;border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.1);padding:4px;display:none}
.tag-picker.open{display:block}
.tag-picker__search{width:100%;box-sizing:border-box;border:none;border-bottom:1px solid var(--border);padding:6px 8px;font-size:.813rem;font-family:var(--font-sans);outline:none;border-radius:4px 4px 0 0;background:transparent}
.tag-picker__list{max-height:200px;overflow-y:auto;padding:4px 0}
.tag-picker__item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;cursor:pointer;font-size:.813rem;user-select:none}
.tag-picker__item:hover,.tag-picker__item.focused{background:rgba(110,135,152,.1)}
.tag-picker__item--create{color:var(--steel);font-style:italic}
.tag-picker__dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.tag-picker__dot--slate{background:var(--steel)}.tag-picker__dot--blue{background:#3b82f6}.tag-picker__dot--green{background:var(--success)}.tag-picker__dot--amber{background:#f59e0b}.tag-picker__dot--rose{background:#f43f5e}.tag-picker__dot--violet{background:#8b5cf6}.tag-picker__dot--orange{background:#f97316}.tag-picker__dot--teal{background:#14b8a6}
/* Tag filter bar */
.tag-filter-bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.tag-filter-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 10px 2px 8px;border-radius:999px;font-size:.75rem;font-weight:600;cursor:pointer;border:1px solid transparent;transition:opacity .15s}
.tag-filter-chip:hover{opacity:.8}
/* Compose field blocks */
.wf-compose-field{border:1px solid var(--border);border-radius:8px;padding:12px;background:#fff;display:flex;flex-direction:column;gap:8px}
.wf-compose-field__hd{display:flex;align-items:center;gap:8px}
.wf-compose-field__label{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.03em;color:var(--text-secondary);white-space:nowrap}
.wf-compose-area{font-family:var(--font-mono,monospace);font-size:.813rem;line-height:1.6;resize:vertical;min-height:120px;padding:10px 12px}
.wf-compose-field .wf-tpl-wrap{flex-direction:column;align-items:stretch}
.wf-compose-field .wf-tpl-btn{position:absolute;top:6px;right:6px}
.wf-compose-field .wf-tpl-wrap{position:relative}
/* Trigger row — pinned row-0 of the Steps list */
.wf-step-card--trigger{border-left:3px solid var(--steel);background:rgba(110,135,152,.04)}
.wf-step-card--trigger+.wf-step-card{margin-top:10px}
.wf-step-card__num--trigger{background:var(--success);color:#fff}
.wf-step-card__num--trigger svg{width:10px;height:10px;stroke:currentColor;fill:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-left:1px}
.wf-step-icon--trigger{color:var(--steel)}
.wf-step-card__menu-slot{width:2rem;height:2rem;flex-shrink:0}
.wf-step-card--trigger .wf-step-card__body>.wf-step-card__sub{padding:14px 16px 16px}
/* Map step — de-emphasised secondary card */
.wf-step-card--map{border:1px dashed rgba(228,221,209,.9);background:rgba(228,221,209,.08);margin-top:0!important;margin-bottom:0!important}
.wf-step-card--map+.wf-step-card,.wf-step-card+.wf-step-card--map{margin-top:2px}
.wf-step-card__row--map{padding:5px 10px;min-height:32px}
.wf-step-card__type--map{font-size:.63rem;color:rgba(110,135,152,.6);width:40px}
/* UseStep input mapping sub-section */
.wf-step-card__sub--inputs{padding:6px 10px 8px 52px;gap:4px;border-top:1px solid rgba(228,221,209,.4)}
.wf-input-row{display:grid;grid-template-columns:120px 48px 1fr;gap:6px;align-items:center;min-height:28px}
.wf-input-row__name{font-size:.75rem;font-weight:500;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wf-input-row__type{font-size:.65rem;padding:1px 4px;text-align:center}
.wf-input-literal{grid-column:3;margin-top:2px}
.wf-input-section{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary);padding:6px 0 2px;border-top:1px solid var(--border-subtle);margin-top:4px}
.wf-input-section:first-child{border-top:0;padding-top:0;margin-top:0}
.wf-deliver-card{border:1px solid rgba(228,221,209,.7);border-radius:8px;margin-bottom:8px;overflow:hidden}
.wf-deliver-card__hd{display:flex;align-items:center;padding:6px 10px;background:rgba(228,221,209,.25)}
.wf-deliver-card__label{font-size:.813rem;font-weight:600;color:var(--text-primary);flex:1}
.wf-deliver-card__fields{padding:8px 10px;display:flex;flex-direction:column;gap:6px}
.wf-deliver-field{display:flex;align-items:center;gap:6px;min-height:28px}
.wf-deliver-field__name{font-size:.75rem;font-weight:500;color:var(--text-primary);min-width:80px;white-space:nowrap}

/* ── NOTIFICATION CENTER ──────────────────────────────────────────────── */
.ntf-bell-wrap{position:relative}
.ntf-bell{position:relative}
.ntf-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--error);color:#fff;font-size:.6rem;font-weight:700;line-height:16px;text-align:center;pointer-events:none;z-index:1}
.ntf-panel{display:none;position:absolute;top:calc(100% + 8px);right:0;width:min(380px,calc(100vw - 32px));max-height:480px;background:#fff;border:1px solid var(--bg-surface);border-radius:12px;box-shadow:0 12px 48px rgba(0,0,0,.12);z-index:2000;flex-direction:column;overflow:hidden}
.ntf-panel--open{display:flex}
.ntf-panel__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid rgba(228,221,209,.6);flex-shrink:0}
.ntf-panel__title{font-size:.875rem;font-weight:700;color:var(--text-primary)}
.ntf-panel__actions{display:flex;align-items:center;gap:2px}
.ntf-panel__body{flex:1;overflow-y:auto;padding:4px 0}
.ntf-loading{display:flex;align-items:center;justify-content:center;padding:32px}
.ntf-item{padding:10px 16px;border-bottom:1px solid rgba(228,221,209,.4);transition:opacity .25s ease,transform .25s ease}
.ntf-item--unread{background:rgba(110,135,152,.06);border-left:3px solid var(--steel);padding-left:13px}
.ntf-item__hd{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:2px}
.ntf-item__title{font-size:.813rem;font-weight:600;color:var(--text-primary);line-height:1.3}
.ntf-item__time{font-size:.68rem;color:var(--text-secondary);white-space:nowrap;flex-shrink:0;margin-top:1px}
.ntf-item__body{font-size:.75rem;color:var(--text-secondary);line-height:1.5;margin-bottom:4px}
.ntf-item__action{display:flex;gap:6px;flex-wrap:wrap;margin:6px 0 4px}
.ntf-item__action .ds-input--sm{padding:4px 8px;font-size:.75rem;flex:1;min-width:100px}
.ntf-item__responded{margin:4px 0}
.ntf-respond-form{display:flex;gap:6px;flex:1}
.ntf-respond-form .ntf-respond-input{flex:1;padding:4px 8px;font-size:.75rem;border:1px solid var(--border);border-radius:6px}
.ntf-item__foot{display:flex;gap:6px;margin-top:2px}
.ntf-item__foot .btn{font-size:.68rem;padding:2px 6px;color:var(--text-secondary)}
.ntf-item:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.ntf-load-more{display:block;width:100%;padding:10px 16px;background:none;border:none;color:var(--text-secondary);font-size:.813rem;text-align:center;cursor:pointer;transition:background .15s}
.ntf-load-more:hover{background:var(--bg-subtle);color:var(--text-primary)}
.ntf-load-more:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* ── RECENTS DROPDOWN ─────────────────────────────────────────────────── */
.rec-trigger-wrap{position:relative;display:inline-flex;align-items:center}
.rec-trigger{position:relative}
.rec-panel{display:none;position:absolute;top:calc(100% + 8px);right:0;width:min(360px,calc(100vw - 32px));max-height:480px;background:#fff;border:1px solid var(--bg-surface);border-radius:12px;box-shadow:0 12px 48px rgba(0,0,0,.12);z-index:2000;flex-direction:column;overflow:hidden}
.rec-panel--open{display:flex}
.rec-panel__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid rgba(228,221,209,.6);flex-shrink:0}
.rec-panel__title{font-size:.875rem;font-weight:700;color:var(--text-primary)}
.rec-panel__kbd{font-size:.7rem;color:var(--text-secondary);padding:2px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg-subtle)}
.rec-panel__body{flex:1;overflow-y:auto;padding:4px 0}
.rec-loading{display:flex;align-items:center;justify-content:center;padding:32px}
.rec-empty{padding:24px 16px;text-align:center;color:var(--text-secondary);font-size:.813rem}
.rec-item{display:flex;align-items:center;gap:10px;padding:8px 16px;font-size:.813rem;color:var(--text-primary);text-decoration:none;border-bottom:1px solid rgba(228,221,209,.4)}
.rec-item:last-child{border-bottom:none}
.rec-item:hover,.rec-item:focus-visible{background:rgba(110,135,152,.06);outline:none}
.rec-item:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.rec-item__icon{width:16px;height:16px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;color:var(--text-secondary)}
.rec-item__label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rec-item__time{font-size:.68rem;color:var(--text-secondary);white-space:nowrap;flex-shrink:0}
@media (max-width:520px){.rec-panel{max-width:calc(100vw - 16px);right:8px}}

/* ── Routario mark animations ─────────────────────────────────────────── */
@keyframes rm-spin{to{transform:rotate(360deg)}}
@keyframes rm-pulse{0%,100%{opacity:.3;transform:scale(.65)}50%{opacity:1;transform:scale(1.4)}}
@keyframes rm-star-flash{0%{opacity:0;transform:scale(.3)}18%{opacity:1;transform:scale(1.25);filter:drop-shadow(0 0 5px rgba(247,244,238,.9))}60%{opacity:1;transform:scale(1);filter:drop-shadow(0 0 2px rgba(247,244,238,.5))}100%{opacity:0;transform:scale(.55);filter:none}}
#rm-ai.active{opacity:1!important;transform-box:fill-box;transform-origin:center;animation:rm-spin 3.2s linear infinite}
#rm-ai.active .rm-ai-dot{transform-box:fill-box;transform-origin:center;animation:rm-pulse .9s ease-in-out infinite}
#rm-ai.active .rm-ai-dot:nth-child(2){animation-delay:.225s}
#rm-ai.active .rm-ai-dot:nth-child(3){animation-delay:.45s}
#rm-ai.active .rm-ai-dot:nth-child(4){animation-delay:.675s}
#rm-star.flash{transform-box:fill-box;transform-origin:center;animation:rm-star-flash 1.4s ease-out forwards}

/* ── TEMPLATE BUILDER (wizard + highlight) ───────────────────────────── */
/* IF / FOR wizard dropdown */
.tpl-wiz{position:absolute;left:0;top:100%;bottom:auto;z-index:200;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.1);padding:8px 10px;min-width:280px}
.tpl-wiz.open-up{top:auto;bottom:100%}
.tpl-wiz__row{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin-bottom:6px}
.tpl-wiz__expr{flex:1;font-size:.75rem;padding:3px 6px;font-family:var(--font-mono,monospace);min-width:120px}
.tpl-wiz__var{font-size:.72rem;padding:2px 6px;max-width:60px;font-family:var(--font-mono,monospace)}
.tpl-wiz__kw{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text-secondary)}
.tpl-wiz__kw--blue{color:#3b82f6}
.tpl-wiz__kw--purple{color:#8b5cf6}
.tpl-wiz__else-label{font-size:.72rem;color:var(--text-secondary);display:flex;align-items:center;gap:4px;cursor:pointer}
/* Typed IF condition builder */
.tpl-wiz__field{flex:1;font-size:.75rem;padding:3px 6px;font-family:var(--font-mono,monospace);min-width:120px}
.tpl-wiz__cond{flex-wrap:nowrap}
.tpl-wiz__op{font-size:.72rem;padding:3px 6px;max-width:46%}
.tpl-wiz__value-slot{display:flex;flex:1;gap:4px;align-items:center;min-width:0}
.tpl-wiz__value{flex:1;font-size:.75rem;padding:3px 6px;font-family:var(--font-mono,monospace);min-width:0}
.tpl-wiz__and{font-size:.7rem;color:var(--text-secondary)}
.tpl-wiz__preview{font-size:.72rem;font-family:var(--font-mono,monospace);color:var(--text-primary);background:var(--bg-subtle,#f9f6f0);border-radius:4px;padding:4px 6px;margin-bottom:6px;word-break:break-all}
.tpl-wiz__preview.is-empty{color:var(--text-secondary);font-family:inherit;font-style:italic;background:none;padding:2px 0}
/* Mini picker (inside wizards) */
.tpl-mini-picker{position:absolute;left:0;top:100%;bottom:auto;z-index:210;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.1);min-width:200px;max-height:260px;overflow-y:auto;padding:4px 0}
.tpl-mini-picker.open-up{top:auto;bottom:100%}
.tpl-mini-picker__group{font-size:.65rem;font-weight:600;color:var(--text-secondary);padding:5px 10px 2px;text-transform:uppercase;letter-spacing:.03em}
.tpl-mini-picker__item{display:block;width:100%;text-align:left;padding:6px 10px;font-size:.72rem;font-family:var(--font-mono,monospace);cursor:pointer;border:none;background:none;color:var(--text-primary)}
.tpl-mini-picker__item:hover,.tpl-mini-picker__item.is-active{background:var(--bg-hover,#f3f4f6)}
.tpl-mini-picker__item.is-active{box-shadow:inset 2px 0 0 var(--accent)}
.tpl-mini-picker__search{position:sticky;top:0;z-index:1;background:#fff;padding:6px 8px;border-bottom:1px solid var(--border)}
.tpl-mini-picker__search-input{width:100%;box-sizing:border-box;padding:5px 8px;font-size:.78rem;font-family:inherit;color:var(--text-primary);background:#fff;border:1px solid var(--border);border-radius:5px}
.tpl-mini-picker__search-input:focus{outline:none;border-color:var(--accent)}
.tpl-mini-picker__empty{padding:8px 10px;font-size:.72rem;color:var(--text-secondary)}
.tpl-wiz__actions{display:flex;justify-content:flex-end;padding-top:6px;border-top:1px solid var(--border);margin-top:2px}
.tpl-wiz__insert{font-size:.72rem;padding:3px 14px;background:var(--steel);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:600}
.tpl-wiz__insert:hover{opacity:.9}
.tpl-wiz__insert:disabled{opacity:.35;cursor:not-allowed}
/* Highlight overlay (textarea backdrop) */
.tpl-hl-backdrop{position:absolute;top:0;left:0;z-index:0;overflow:hidden;pointer-events:none;white-space:pre-wrap;word-wrap:break-word;color:transparent;box-sizing:border-box}
.tpl-hl--var{background:rgba(59,130,246,.12);border-radius:2px;color:transparent}
.tpl-hl--tag{background:var(--ai-accent-soft);border-radius:2px;color:transparent}
/* Module picker rows — main label (click = insert) + wand button (click = insert + autofill) */
.ds-ctx .item.wf-picker-row{display:flex;align-items:stretch;padding:0;gap:0}
.ds-ctx .item.wf-picker-row:hover,
.ds-ctx .item.wf-picker-row:focus-within{background:rgba(228,221,209,.65)}
.wf-picker-row__main{flex:1;display:flex;align-items:center;gap:8px;background:none;border:0;padding:7px 12px;text-align:left;color:inherit;cursor:pointer;font:inherit;min-width:0}
.wf-picker-row__wand{display:inline-flex;align-items:center;justify-content:center;width:2rem;flex-shrink:0;background:none;border:0;border-left:1px solid var(--bg-surface);padding:0;cursor:pointer;color:var(--text-secondary);opacity:0;transition:opacity .12s ease,color .12s ease,background .12s ease}
.wf-picker-row:hover .wf-picker-row__wand,
.wf-picker-row:focus-within .wf-picker-row__wand{opacity:1}
.wf-picker-row__wand:hover{color:var(--brand,#f97316);background:rgba(0,0,0,.04)}
.wf-picker-row__wand svg{width:1rem;height:1rem;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}

/* "+ Add step" picker — multi-column flyout with search box. Replaces the
   long vertical list that overflowed the viewport. Panel width targets four
   160px columns, but the grid uses auto-fit so it collapses gracefully on
   narrower viewports. */
/* --wf-step-picker-max-h is set by _wfAddMenuToggle based on the trigger's
   position so the flyout never overflows the viewport. Fallback keeps it
   usable if the JS hasn't run yet (initial paint, tests). */
.ds-ctx.wf-step-picker{width:640px;max-width:calc(100vw - 24px);max-height:var(--wf-step-picker-max-h,calc(100vh - 120px));display:none;padding:0;overflow:hidden}
.ds-ctx.wf-step-picker.visible{display:flex;flex-direction:column}
/* When a group spans 2 tracks we need room for 5 tracks total (1 wide group
   + up to 3 single-track groups) so no column wraps to a new row. */
.ds-ctx.wf-step-picker:has(.wf-step-picker__col--wide){width:760px}
.wf-step-picker__search{padding:10px 12px;border-bottom:1px solid var(--bg-surface);flex-shrink:0}
.wf-step-picker__search input{width:100%}
.wf-step-picker__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:0;overflow-y:auto;flex:1;min-height:0}
.wf-step-picker__col{border-right:1px solid var(--bg-surface);min-width:0;padding:6px 0}
.wf-step-picker__col:last-child{border-right:0}
/* A "wide" group (>~8 items) spans 2 grid tracks and lays its items out in
   2 sub-columns so long lists do not need to scroll vertically inside the
   flyout. Falls back gracefully when the parent grid has already collapsed
   to a single track on narrow viewports. */
.wf-step-picker__col--wide{grid-column:span 2}
.wf-step-picker__col--wide .wf-step-picker__col-body{display:grid;grid-template-columns:1fr 1fr;column-gap:0}
.wf-step-picker__col-hd{padding:6px 12px 4px;font-size:.68rem;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em}
.wf-step-picker__col-body{display:flex;flex-direction:column}
.wf-step-picker__item{display:flex;align-items:stretch;min-width:0}
.wf-step-picker__item:hover,
.wf-step-picker__item:focus-within{background:rgba(228,221,209,.65)}
.wf-step-picker__item--disabled{opacity:.45;cursor:not-allowed;padding:6px 12px;font-size:.875rem;color:var(--text-primary);display:flex;align-items:flex-start;min-width:0}
.wf-step-picker__item--disabled:hover{background:transparent}
/* Row is flex-wrap so the maturity chip stays fully visible even in the
   narrow 2-sub-column layout of a wide group — if the label is too long to
   share a line with the chip, the chip drops to a second line instead of
   being clipped. Label text wraps naturally (no ellipsis). */
.wf-step-picker__main{flex:1;display:flex;flex-wrap:wrap;align-items:center;gap:2px 6px;background:none;border:0;padding:6px 12px;text-align:left;color:inherit;cursor:pointer;font:inherit;font-size:.875rem;min-width:0}
.wf-step-picker__main-text{min-width:0;overflow-wrap:anywhere;word-break:break-word}
.wf-step-picker__wand{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;flex-shrink:0;background:none;border:0;border-left:1px solid var(--bg-surface);padding:0;cursor:pointer;color:var(--text-secondary);opacity:0;transition:opacity .12s ease,color .12s ease,background .12s ease}
.wf-step-picker__item:hover .wf-step-picker__wand,
.wf-step-picker__item:focus-within .wf-step-picker__wand{opacity:1}
.wf-step-picker__wand:hover{color:var(--brand,#f97316);background:rgba(0,0,0,.04)}
.wf-step-picker__wand svg{width:.9rem;height:.9rem;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.wf-step-picker__chip{font-size:.6rem;padding:1px 4px;margin-left:0;flex-shrink:0}
.wf-step-picker__label{display:flex;flex-wrap:wrap;align-items:center;gap:2px 6px;padding:0;min-width:0}
.wf-step-picker__label-text{min-width:0;overflow-wrap:anywhere;word-break:break-word}
.wf-step-picker__empty{padding:20px 12px;text-align:center;color:var(--text-secondary);font-size:.813rem;border-top:1px solid var(--bg-surface)}
/* Break-glass support session banner */
.app-break-glass-banner{display:flex;align-items:center;gap:8px;padding:10px 24px;background:#7f1d1d;color:#fef2f2;font-size:.813rem;z-index:200;flex-wrap:wrap}
.app-break-glass-banner__glyph{width:1rem;height:1rem;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.app-break-glass-banner__icon{display:flex;align-items:center;flex-shrink:0}
.app-break-glass-banner__end{color:#fef2f2;border-color:#fca5a5;margin-left:auto}
.app-break-glass-banner code{background:rgba(255,255,255,.15);padding:1px 6px;border-radius:3px;font-size:.75rem}

/* AI-unconfigured banner — admin-only, sticky at top of every shell page.
   Surfaces the silent-failure mode where annotations / mbox imports /
   classification fire LLM calls against a missing or unreachable route. */
.app-ai-unconfigured-banner{display:flex;align-items:center;gap:8px;padding:10px 24px;background:#b91c1c;color:#fef2f2;font-size:.813rem;z-index:200;flex-wrap:wrap}
.app-ai-unconfigured-banner__glyph{width:1rem;height:1rem;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.app-ai-unconfigured-banner__icon{display:flex;align-items:center;flex-shrink:0}
.app-ai-unconfigured-banner__end{color:#fef2f2;border-color:#fca5a5;margin-left:auto;text-decoration:none}
.app-ai-unconfigured-banner__end:hover{background:rgba(255,255,255,.12)}

/* Small form input modifier — shared for inline search bars. */
.ds-input--sm{padding:6px 10px;font-size:.813rem;border-radius:8px}

/* Contacts row helpers — clickable contact / company rows on /contacts.
   (Legacy `.crm-row` class renamed in 2026-05-18 — CRM = Contacts was
   pre-rename naming; the `/crm` blueprint today owns deals/pipeline.) */
.contacts-row{cursor:pointer}
.contacts-row:hover{background:rgba(228,221,209,.35)}
.contacts-row:focus{outline:2px solid var(--steel);outline-offset:-2px}
.text-faint{color:var(--text-secondary);opacity:.7}
.contacts-name-cell{display:flex;align-items:center;gap:8px}
.contacts-avatar__img{border-radius:50%;object-fit:cover;flex-shrink:0;display:block;background:var(--bg-surface)}
.contacts-avatar__trigger{border:none;background:none;padding:0;cursor:pointer;border-radius:50%;overflow:hidden;flex-shrink:0;display:block}
.contacts-avatar__trigger:hover .contacts-avatar__img{opacity:.8}
/* Square variant — companies (and other org-shaped entities) get a rounded
   square instead of a circle. The wrapping <button> clips its child <img>
   to the same shape via overflow:hidden + matching border-radius, and the
   server-side initials_svg fallback uses shape="square" so the SVG corners
   match the host wrapper. */
.contacts-avatar__trigger--square{border-radius:var(--radius)}
.contacts-avatar__trigger--square .contacts-avatar__img,
.contacts-avatar__trigger--square .contacts-row-avatar{border-radius:var(--radius)}
.contacts-avatar__trigger--square:hover .contacts-row-avatar{opacity:.8}
.contacts-row-avatar{object-fit:cover;display:block;background:var(--bg-surface)}
.contacts-mono{font-family:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:.875rem}
.crm-empty{padding:48px 24px;text-align:center}
.crm-empty h4{margin:0 0 8px;font-size:1rem;font-weight:600}
.crm-empty p{margin:0 0 16px;color:var(--text-secondary);font-size:.938rem}
.contacts-note-placeholder{margin-top:16px;font-style:italic;font-size:.813rem}
.contacts-search-form{display:inline-flex;align-items:center;gap:8px}
.contacts-search-form .ds-input{width:auto;min-width:220px}

/* Notes: drawer-embedded list + composer, reused by the global feed.
 * Section wrapper is the shared `.ds-drawer-section` primitive (same one
 * tasks use) — see "Drawer sub-sections" in style_guide.html. The Notes-
 * specific rules below only style the individual note cards and composer. */
.contacts-note-composer{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
.contacts-note-composer__body{min-height:56px}
.contacts-note-composer__ft{display:flex;justify-content:flex-end}
.contacts-notes__list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}
.contacts-notes__empty{margin:0;font-size:.813rem;font-style:italic}
.contacts-note{background:var(--bg-page);border:1px solid var(--bg-surface);border-radius:8px;padding:10px 12px}
.contacts-note__meta{display:flex;align-items:center;gap:8px;font-size:.75rem;margin-bottom:4px}
.contacts-note__stamp{flex-shrink:0}
.contacts-note__target{display:inline-flex;align-items:center;gap:6px;color:var(--text-primary);text-decoration:none}
.contacts-note__target:hover{text-decoration:underline}
.contacts-note__delete{margin-left:auto;display:inline-flex}
.contacts-note__body{font-size:.875rem;color:var(--text-primary);white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.45}
.contacts-notes__feed .contacts-note{padding:14px 16px}

/* Small chip modifier used by the notes feed entity-type badge. */
.ds-chip--sm{font-size:.688rem;padding:0 5px;border-radius:3px}

/* ---------- Kanban primitive ---------- */
.kanban{display:flex;gap:12px;overflow-x:auto;padding:4px 24px 24px 4px;align-items:flex-start}
.kanban__col{flex:0 0 280px;width:280px;background:var(--bg-subtle);border:1px solid var(--border);border-radius:var(--radius-lg);padding:12px;display:flex;flex-direction:column;gap:8px;min-height:240px;transition:background .18s ease,border-color .18s ease,box-shadow .18s ease}
.kanban__col.is-drop-target{background:color-mix(in oklab,var(--accent) 6%,var(--bg-subtle));box-shadow:inset 0 0 0 1.5px color-mix(in oklab,var(--accent) 45%,transparent)}
.kanban__col-hd{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:2px 2px 4px}
.kanban__col-name{font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-secondary)}
.kanban__col-count{background:var(--border);color:var(--text-secondary)}
.kanban__cards{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px;min-height:48px}
.kanban__card{background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:10px 12px;cursor:grab;display:flex;flex-direction:column;gap:4px;transition:transform 220ms cubic-bezier(.34,1.56,.64,1),box-shadow 220ms cubic-bezier(.34,1.56,.64,1),border-color .18s ease,opacity .18s ease;will-change:transform}
.kanban__card:hover{border-color:var(--border-strong);box-shadow:0 1px 3px rgba(22,32,42,.06)}
.kanban__card:focus{outline:2px solid var(--accent);outline-offset:-2px}
.kanban__card:active{cursor:grabbing}
.kanban__card.is-dragging{transform:scale(1.04) rotate(-.6deg);box-shadow:0 1px 2px rgba(22,32,42,.04),0 12px 28px -8px rgba(22,32,42,.22),0 24px 48px -16px rgba(22,32,42,.18);border-color:var(--border-strong);cursor:grabbing;z-index:5;position:relative}
.kanban__col.is-source .kanban__card:not(.is-dragging){opacity:.55}
.kanban__card.is-settling{animation:kanbanSettle 380ms cubic-bezier(.22,1,.36,1)}
/* Server rejected a status change — snap the card back to source with a
   tight ease (no celebration). Applied for ~240ms by tasks.js after revert. */
.kanban__card.is-reverting{animation:kanbanCardRevert 240ms cubic-bezier(.4,0,.2,1)}
@keyframes kanbanCardRevert{0%{transform:translateX(0);box-shadow:0 0 0 2px rgba(220,38,38,.35)}40%{transform:translateX(-4px)}70%{transform:translateX(2px)}100%{transform:translateX(0);box-shadow:none}}
.kanban__card-title{font-size:.8125rem;font-weight:600;line-height:1.3;color:var(--text-primary)}
.kanban__card-meta{font-size:.6875rem;color:var(--text-muted)}
.kanban__empty{font-size:.8125rem;color:var(--text-muted);padding:8px 2px}
.kanban__placeholder{height:0;margin-bottom:0;background:transparent;border:1.5px dashed color-mix(in oklab,var(--accent) 50%,transparent);border-radius:var(--radius);transition:height 180ms cubic-bezier(.22,1,.36,1),opacity 180ms ease;opacity:0}
.kanban__placeholder.is-active{height:44px;opacity:1}
@keyframes kanbanSettle{0%{transform:scale(1.06) rotate(-.4deg);box-shadow:0 16px 32px -10px rgba(22,32,42,.25)}55%{transform:scale(.98) rotate(0deg);box-shadow:0 4px 10px -2px rgba(22,32,42,.10)}100%{transform:scale(1) rotate(0deg);box-shadow:0 1px 2px rgba(22,32,42,.04)}}
@media (prefers-reduced-motion:reduce){.kanban__card,.kanban__col,.kanban__placeholder{transition:none}.kanban__card.is-settling,.kanban__card.is-reverting{animation:none}.kanban__card.is-dragging{transform:none}}

/* ---------- CRM Kanban board ---------- */
.crm-board{display:flex;gap:12px;overflow-x:auto;padding:4px 4px 24px;align-items:flex-start}
.crm-board__col{flex:1 1 160px;min-width:160px;max-width:260px;background:var(--bg-page);border:1px solid var(--bg-surface);border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:10px;min-height:240px;transition:background .12s,border-color .12s}
.crm-board__col.is-drop-target{background:rgba(99,102,241,.06);border-color:var(--steel)}
.crm-board__col--won{border-top:3px solid #16a34a}
.crm-board__col--lost{border-top:3px solid #dc2626}
.crm-board__col-hd{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.crm-board__col-title{display:inline-flex;align-items:center;gap:8px;font-weight:600;font-size:.875rem;min-width:0}
.crm-board__col-count{background:var(--bg-surface);color:var(--text-secondary)}
.crm-board__col-sum{font-size:.75rem;font-variant-numeric:tabular-nums;margin-left:auto}
.crm-board__cards{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;min-height:80px}
.crm-card{background:var(--bg-surface);border:1px solid transparent;border-radius:8px;padding:10px 12px;cursor:grab;transition:border-color .12s,box-shadow .12s,transform .06s;display:flex;flex-direction:column;gap:4px}
.crm-card:hover{border-color:var(--steel);box-shadow:0 1px 2px rgba(0,0,0,.04)}
.crm-card:focus{outline:2px solid var(--steel);outline-offset:-2px}
.crm-card:active{cursor:grabbing}
.crm-card.is-dragging{opacity:.5;transform:rotate(-1deg)}
/* Server rejected a stage change — snap the card back to source with a
   tight ease (no celebration). Applied for ~240ms by crm.js after revert. */
.crm-card.is-reverting{animation:crmCardRevert 240ms cubic-bezier(.4,0,.2,1)}
@keyframes crmCardRevert{0%{transform:translateX(0);box-shadow:0 0 0 2px rgba(220,38,38,.35)}40%{transform:translateX(-4px)}70%{transform:translateX(2px)}100%{transform:translateX(0);box-shadow:none}}
@media (prefers-reduced-motion:reduce){.crm-card.is-reverting{animation:none}}
.crm-card__name{font-weight:600;font-size:.875rem;line-height:1.3}
.crm-card__company{font-size:.75rem}
.crm-card__meta{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:.75rem;margin-top:2px}
.crm-card__value{font-weight:600;font-variant-numeric:tabular-nums}
.crm-card__date{font-variant-numeric:tabular-nums}
.crm-card__owner{display:flex;justify-content:flex-end;margin-top:4px}

/* Shared owner-avatar primitive (deals card, task card, future RFP/claim).
   The macro at templates/_owner_avatar.html renders an <img> with this class;
   the `crm-card__owner` and `kanban__card-owner` wrappers position it. */
.owner-avatar{border-radius:50%;object-fit:cover;display:block}
.kanban__card-owner{display:flex;justify-content:flex-end;margin-top:4px}
.deal-owner-pill,.task-owner-pill{display:inline-flex;align-items:center;gap:6px}
.deal-owner-pill .owner-avatar,.task-owner-pill .owner-avatar{flex:none}

/* Drawer linked-entity sections (contacts / deals inside company drawer). */
/* Drawer sub-section primitive — the shared frame for Notes, Tasks, and any
 * future drawer-embedded sub-block. Visual contract documented in
 * style_guide.html under "Drawer sub-sections". */
.ds-drawer-section{margin-top:18px;padding-top:18px;border-top:1px solid var(--bg-surface)}
.ds-drawer-section__hd{font-size:.8125rem;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:8px;margin:0 0 12px}
.ds-drawer-section__empty{font-size:.8125rem;margin:0 0 10px}
.ds-drawer-section__hd-actions{margin-left:auto}
/* Linked-list item — flat one-liner. Add `.is-clickable` to turn the row
 * into a drawer/detail trigger; it gains a hover background, a pointer,
 * and a keyboard-focus outline. */
.ds-linked-list{list-style:none;margin:0 0 10px;padding:0;display:flex;flex-direction:column;gap:2px}
.ds-linked-list li{display:flex;align-items:center;gap:8px;font-size:.8125rem;padding:6px 8px;margin:0 -8px;border-radius:6px}
.ds-linked-list li.is-clickable{cursor:pointer;transition:background .12s ease}
.ds-linked-list li.is-clickable:hover{background:rgba(228,221,209,.35)}
.ds-linked-list li.is-clickable:focus-visible{outline:2px solid var(--steel);outline-offset:-2px;background:rgba(228,221,209,.35)}
.ds-linked-list__name{font-weight:500;color:var(--text-primary);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.contacts-detail-avatar__img:hover{opacity:.85}

/* Phone input — dial-code select + number field fused into one visual unit. */
.ds-phone-input{display:flex}
.ds-phone-input__code{min-width:82px;border-radius:var(--radius,6px) 0 0 var(--radius,6px);border:1px solid var(--border-input,#d1d5db);border-right:none;background:var(--bg-surface,#f9fafb);color:var(--text-primary);font-size:.875rem;padding:0 8px;cursor:pointer;appearance:none;-webkit-appearance:none;text-align:center}
.ds-phone-input__code:focus{outline:2px solid var(--focus-ring,#6366f1);outline-offset:-1px;z-index:1}
.ds-phone-input__number{border-radius:0 var(--radius,6px) var(--radius,6px) 0;flex:1;min-width:0}
.ds-phone-input__number.ds-input{border-radius:0 var(--radius,6px) var(--radius,6px) 0}

/* Company / entity combobox — autocomplete typeahead. */
.ds-combobox{position:relative}
.ds-combobox__list{position:absolute;top:calc(100% + 2px);left:0;right:0;background:var(--bg-elevated,#fff);border:1px solid var(--border-subtle,#e5e7eb);border-radius:var(--radius,6px);box-shadow:0 4px 16px rgba(0,0,0,.1);z-index:300;max-height:200px;overflow-y:auto;padding:4px 0;display:none}
.ds-combobox__list.is-open{display:block}
.ds-combobox__item{padding:7px 12px;cursor:pointer;font-size:.875rem;color:var(--text-primary)}
.ds-combobox__item:hover,.ds-combobox__item.is-active{background:var(--bg-hover,#f3f4f6)}
.ds-combobox__empty{padding:7px 12px;font-size:.8125rem;color:var(--text-secondary)}

/* ds-form-group row modifier — two inputs side-by-side (value + currency). */
.ds-form-group--row{display:grid;grid-template-columns:2fr 1fr;gap:12px}

/* Memory index — concept-group chips in the table column wrap with horizontal +
   vertical gap. Previously `display:flex` was applied directly to the `<td>`,
   which removed the cell from table-cell layout: it stopped participating in
   row-height equalisation, so its bottom edge sat ~2px above the row's bottom
   and the row's bottom border visibly stepped at the column boundary. Keeping
   the td as `display:table-cell` and giving chips margin-based spacing makes
   the row divider continuous across every column. Chips still wrap to
   subsequent lines naturally because `<span class="badge">` is inline-flex. */
[data-doc-concepts-cell] > .badge { margin: 0 0 2px 4px; }
[data-doc-concepts-cell] > .badge:first-child { margin-left: 0; }

/* Memory index — two-column grid that snaps to single column when the
   right "Browse by entity" panel can't fit cleanly. The threshold is wider
   than typical because the panel needs ~280 px and the recent-documents
   table needs ~700 px before the sidebar; collapsing below ~1180 px avoids
   half-clipping the panel which the layout otherwise can't avoid. */
.memory-layout{display:grid;grid-template-columns:minmax(0,2fr) minmax(280px,1fr);gap:24px;align-items:start}
@media(max-width:1180px){.memory-layout{grid-template-columns:1fr}}

/* Memory-fabric references — annotation values that resolve to substrate
 * entities. Single-ref renders the value as a plain <a> (inherits steel +
 * hover-underline from the global anchor styles — no special CSS needed).
 * Multi-ref renders superscript chips after the value, each linking to its
 * candidate entity. Honest about ambiguity. See docs/MEMORY_FABRIC.md and
 * core/web/templates/memory/_macros.html. */
.ann-ref-sup{margin-left:1px;font-size:.7em;line-height:0;vertical-align:super}
.ann-ref-sup a{padding:0 2px;color:var(--steel);text-decoration:none;font-weight:600}
.ann-ref-sup a:hover{color:var(--accent);text-decoration:underline}
.ann-ref-sup + .ann-ref-sup{margin-left:1px}

/* ---------- AI mark + AI working states (v0.2 — from PRIMITIVES.md
 * § AI surfaces) ---------------------------------------------------
 *
 * .ai-mark is the violet "AI" chip that ALWAYS accompanies AI-derived
 * content (Rule 0.5 — there is no quiet AI mode in product). Sizes:
 *   .ai-mark--sm (default, inline)
 *   .ai-mark--md (card headers)
 *   .ai-mark--lg (page headers, sparingly)
 *
 * Three working-state primitives govern "AI is busy" surfaces, with one
 * rule: at most ONE animated AI surface visible per viewport. If parallel
 * work is happening on a page, hoist a single .ai-activity to the page
 * header and use uniform .ai-skeleton placeholders.
 *
 *   .ai-mark--working   — the chip itself shimmers (slow white sweep)
 *   .ai-skeleton        — a value/cell being computed (violet shimmer)
 *   .ai-activity        — page-level "N items being processed" pill
 *
 * All three respect prefers-reduced-motion (animations disabled, marks
 * stay visible — they're trust signals, not animations). */

.ai-mark {
    background: var(--violet);
    color: #fff;
    display: inline-grid;
    place-items: center;
    border-radius: 4px;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.04em;
    line-height: 1;
    padding: 2px 4px;
    min-width: 14px;
    text-align: center;
    vertical-align: middle;
}
.ai-mark--sm { font-size: 9px;  padding: 2px 4px; min-width: 14px; }
.ai-mark--md { font-size: 10px; padding: 3px 6px; min-width: 18px; }
.ai-mark--lg { font-size: 11px; padding: 4px 8px; min-width: 22px; }

.ai-mark--working {
    position: relative;
    overflow: hidden;
}
.ai-mark--working::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(110deg, transparent 35%, rgba(255,255,255,0.55) 50%, transparent 65%);
    animation: aiMarkSweep 1.6s linear infinite;
}
@keyframes aiMarkSweep {
    0%   { transform: translateX(-110%); }
    100% { transform: translateX(110%);  }
}

.ai-skeleton {
    display: inline-block;
    height: 1em;
    min-width: 80px;
    border-radius: 4px;
    background: linear-gradient(90deg,
        var(--violet-soft) 0%,
        color-mix(in oklab, var(--violet) 18%, var(--bg-subtle)) 50%,
        var(--violet-soft) 100%);
    background-size: 200% 100%;
    animation: aiSkeletonShimmer 1.8s linear infinite;
    vertical-align: middle;
}
.ai-skeleton--value { width: 120px; }
.ai-skeleton--text  { width: 60%; }
.ai-skeleton--block { display: block; height: 14px; width: 100%; margin: 4px 0; }
@keyframes aiSkeletonShimmer {
    0%   { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}

.ai-activity {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 4px 10px 4px 8px;
    background: var(--violet-soft);
    border: 1px solid color-mix(in oklab, var(--violet) 30%, transparent);
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--violet);
    font-family: var(--font-mono);
}
.ai-activity__dot {
    width: 6px; height: 6px;
    border-radius: 999px;
    background: var(--violet);
    animation: aiActivityPulse 1.4s ease-in-out infinite;
}
.ai-activity__count {
    font-variant-numeric: tabular-nums;
    background: var(--violet);
    color: #fff;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 10.5px;
    line-height: 1.4;
}
@keyframes aiActivityPulse {
    0%, 100% { opacity: 0.4; transform: scale(0.85); }
    50%      { opacity: 1;   transform: scale(1.1);  }
}

@media (prefers-reduced-motion: reduce) {
    .ai-mark--working::after,
    .ai-skeleton,
    .ai-activity__dot { animation: none; }
}

/* ---------- Filter popover (v0.2 — from PRIMITIVES.md § Filter & sort)
 * Lifted from docs/facelift-mocks/styleguide.html's inline <style> block
 * (Step 7 of HANDOFF.md forbids inline <style> blocks; this section
 * is the canonical home).
 *
 * Two parts:
 *   (a) .ds-chip--filter / --add — chip variants used in the datagrid
 *       toolbar (terracotta-tinted active filter chip, dashed "+ Filtr"
 *       chip). Promoted to standalone in the v0.2 alignment pass — they
 *       were previously scoped to .ds-grid__hd, which was killed.
 *   (b) .filter-popover + sub-elements — the popover itself, anchored
 *       under the "+ Filtr" chip via JS (core/web/static/filter_popover.js,
 *       to be created with first consumer in Step 5 / Companies). */

.ds-chip--filter {
    background: rgba(185, 91, 55, 0.08);
    color: var(--accent);
    border-color: rgba(185, 91, 55, 0.25);
    font-weight: 500;
}
.ds-chip--filter .ds-chip__close { color: var(--accent); }
.ds-chip--add {
    background: transparent;
    border: 1px dashed var(--border-strong);
    color: var(--text-secondary);
    font-weight: 500;
}
.ds-chip--add:hover {
    background: var(--bg-subtle);
    color: var(--text-primary);
    border-style: solid;
}

/* Suggestion chip (PR 14) — dashed border + lighter terracotta tint,
 * source-agnostic. Reads as "not yet applied, here for your
 * consideration". No violet, no AI mark — see Suggestion docstring in
 * core/web/grid_filters.py for rationale.
 *
 * Composition: <span class="ds-chip ds-chip--suggestion"> wraps an
 * accept-link <a> (clicking applies the suggestion) AND a dismiss
 * <button> (clicking hides + sessionStorage-remembers). The wrapper
 * carries the visual chip styling. */
.ds-chip--suggestion {
    background: rgba(185, 91, 55, 0.04);
    color: var(--text-primary);
    border: 1px dashed var(--border-strong);
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 4px 2px 10px;
}
.ds-chip--suggestion:hover {
    background: rgba(185, 91, 55, 0.08);
    border-color: var(--accent);
}
.ds-chip--suggestion .ds-chip__hint {
    font-style: italic;
    color: var(--text-secondary);
    font-size: 11px;
    font-weight: 400;
}
.ds-chip--suggestion .ds-chip__label {
    color: var(--accent);
}
.ds-chip--suggestion .ds-chip__dismiss:hover {
    color: var(--text-primary);
}

.filter-popover {
    position: absolute;       /* anchored to a chip via JS; mock used relative */
    width: 320px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 6px 24px -8px rgba(22, 32, 42, 0.18), 0 1px 2px rgba(22, 32, 42, 0.04);
    overflow: hidden;
    font-size: 12.5px;
    z-index: 1200;
}
/* Popover header — panel title pattern (matches .ds-modal__hd h4).
 * Previously rendered as an uppercase 11px label which visually competed
 * with the IDENTITA group label below, making the popover purpose
 * ambiguous (Jakub feedback 2026-05-18). */
.filter-popover__hd {
    padding: 12px 14px 10px;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-subtle);
}
.filter-popover__search {
    padding: 8px 10px;
    border-bottom: 1px solid var(--border);
}
.filter-popover__search input { width: 100%; }
.filter-popover__list {
    padding: 6px 0;
    max-height: 260px;
    overflow: auto;
}
.filter-popover__group {
    padding: 8px 12px 4px;
    font-size: 10px; font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase; letter-spacing: 0.08em;
}
.filter-popover__item {
    display: flex; align-items: center; gap: 10px;
    width: 100%;
    padding: 6px 12px;
    background: transparent; border: 0;
    text-align: left;
    font: inherit; color: var(--text-primary);
    cursor: pointer;
}
.filter-popover__item:hover,
.filter-popover__item:focus-visible {
    background: var(--bg-subtle);
    outline: none;
}
.filter-popover__icon {
    width: 22px; flex-shrink: 0;
    font-family: var(--font-mono); font-size: 10.5px;
    color: var(--text-muted);
    text-align: center;
}
.filter-popover__hint {
    margin-left: auto;
    font-size: 11px; color: var(--text-muted);
    font-family: var(--font-mono);
}
.filter-popover__ft {
    padding: 8px 12px;
    border-top: 1px solid var(--border);
    background: var(--bg-subtle);
}

/* ---------- KPI strip (v0.2 — from PRIMITIVES.md § Data display ---
 * "Up to 4 stat cards in a row at top of a page. Maximum 4." */
.kpi { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 14px 16px; box-shadow: var(--shadow-card); }
.kpi__label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); font-weight: 700; }
.kpi__value { font-size: 24px; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; color: var(--text-primary); margin-top: 2px; }
.kpi__delta { font-size: 11px; color: var(--forest); font-weight: 600; margin-top: 2px; }
.kpi__delta.is-down { color: var(--error); }
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
.kpi-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }

/* .kpi.is-attention — applied when a KPI crosses a soft threshold (e.g.
 * inbox-pending count > 0). Stays calm — just tightens the border without
 * painting the card or the value accent-coloured. */
.kpi.is-attention { border-color: var(--border-strong); }

/* ---------- Confidence pill (v0.2 — from PRIMITIVES.md § Atoms) -----
 * Verbal/percentage confidence next to AI-derived values. Violet for
 * high confidence, amber for low. */
.conf {
    display: inline-flex; align-items: center;
    background: var(--violet-soft); color: var(--violet);
    font-size: 10px; font-weight: 700;
    padding: 1px 6px; border-radius: 999px;
    font-family: var(--font-mono);
}
.conf.low { background: rgba(201, 136, 16, 0.10); color: var(--warning); }

/* ---------- Avatar stack (v0.2 — from PRIMITIVES.md § Atoms) -------
 * Up to 3 avatars + +N overflow. Steel-soft circle, ink-on-paper
 * borders, slight overlap. */
.avatar-stack { display: inline-flex; }
.avatar-stack > * {
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--steel-soft); color: var(--steel);
    border: 2px solid #fff;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 10px; font-weight: 700;
    margin-left: -6px;
}
.avatar-stack > *:first-child { margin-left: 0; }
.avatar-stack__more { background: var(--bg-subtle); color: var(--text-secondary); }

/* ---------- ProposalCard (v0.2 — from PRIMITIVES.md § AI surfaces) -
 * The unifying human-in-the-loop block. Violet AI mark + verb +
 * confidence + one-sentence proposal + Because reasoning + actions. */
.proposal {
    background: linear-gradient(180deg, rgba(124, 58, 237, 0.04), rgba(124, 58, 237, 0.01));
    border: 1px solid var(--violet-strong);
    border-radius: var(--radius-md);
    padding: 14px 16px;
}
.proposal__hd { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.proposal__verb { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--violet); font-weight: 700; }
.proposal__t { font-size: 14px; font-weight: 600; color: var(--text-primary); margin: 0 0 6px; line-height: 1.4; }
.proposal__because { font-size: 12.5px; color: var(--text-secondary); padding-left: 14px; position: relative; line-height: 1.5; }
.proposal__because::before { content: '▸'; position: absolute; left: 0; color: var(--violet); }
.proposal__actions { display: flex; gap: 6px; margin-top: 10px; }

/* ---------- AskActor (v0.2 — from PRIMITIVES.md § AI surfaces) -----
 * Yellow-bordered card from a paused workflow. Header (eyebrow) + question
 * + answer actions. Inline (in-page) variant; the notification-channel
 * version is delivered via the bell panel. */
.askactor {
    background: rgba(201, 136, 16, 0.06);
    border: 1px solid rgba(201, 136, 16, 0.30);
    border-radius: var(--radius-md);
    padding: 14px 16px;
}
.askactor__eyebrow { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--warning); font-weight: 700; margin-bottom: 4px; }
.askactor__q { font-size: 14px; font-weight: 600; color: var(--text-primary); margin: 0 0 10px; }
.askactor__actions { display: flex; gap: 6px; }

/* ---------- ExtractedFacts (v0.2) ----------------------------------
 * Violet-highlighted fact list with confidence pills. Used wherever AI
 * extracted structured data from unstructured input. PRIMITIVES.md §
 * AI surfaces describes the canonical composition (drawer-dl + ai-mark
 * + conf pills); .xfact-list / .xfact is the standalone block variant
 * for when the facts aren't inside a drawer. */
.xfact-list { display: flex; flex-direction: column; gap: 8px; }
.xfact {
    display: grid; grid-template-columns: 130px 1fr auto; gap: 12px; align-items: center;
    padding: 8px 10px;
    background: rgba(124, 58, 237, 0.03);
    border: 1px solid var(--violet-soft);
    border-radius: var(--radius);
}
.xfact__l { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; }
.xfact__v { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.xfact__src { font-size: 10px; color: var(--text-muted); margin-top: 8px; padding: 0 10px; font-family: var(--font-mono); }

/* ---------- Memory Ask · Machine B — fact-card + cited-span ----------
 * The "fact + citation" answer object (docs/MEMORY_ASK_SURFACE.md). A
 * fact-card leads with the answer value + the canonical .conf-pill, an
 * optional normalized form, and a .cited-span: the source quote
 * highlighted inside ±100 chars of surrounding context, with a deep-link
 * to the source document at the matching offset (#c<start>[-<end>]).
 * Rendered client-side from the POST /memory/ask `fact` shape by
 * core/web/static/cited_span.js (window.MemoryAsk.renderFact). The
 * highlight mark (.mem-hl) is shared with the document OCR view's
 * scroll-to-highlight so the proof reads identically in both places. */
.fact-card { display: flex; flex-direction: column; gap: 12px; }
.fact-card__head { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.fact-card__answer {
    margin: 0;
    flex: 1 1 auto;
    min-width: 0;
    font-size: 21px; font-weight: 700; line-height: 1.3;
    color: var(--text-primary);
    word-break: break-word;
}
.fact-card__head .conf-pill { margin-top: 5px; flex-shrink: 0; }
.fact-card__norm {
    font-size: 12px; color: var(--text-muted);
    font-family: var(--font-mono); word-break: break-word;
}
.fact-card__norm b { color: var(--text-secondary); font-weight: 600; }

.cited-span {
    border: 1px solid var(--border-default);
    border-radius: var(--radius);
    background: var(--bg-subtle);
    overflow: hidden;
}
.cited-span__context {
    margin: 0; padding: 12px 14px;
    font-size: 13px; line-height: 1.6;
    color: var(--text-secondary);
    white-space: pre-wrap;        /* keep OCR line breaks inside the excerpt */
    word-break: break-word;
    max-height: 220px; overflow-y: auto;
}
.cited-span__src {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 14px;
    border-top: 1px solid var(--border-subtle);
    background: var(--bg-surface);
    font-size: 12px;
}
.cited-span__file {
    display: inline-flex; align-items: center; gap: 6px;
    min-width: 0; overflow: hidden;
    color: var(--text-muted); font-family: var(--font-mono); font-size: 11px;
    white-space: nowrap; text-overflow: ellipsis;
}
.cited-span__file svg { width: 13px; height: 13px; flex-shrink: 0; }
.cited-span__file-name { overflow: hidden; text-overflow: ellipsis; }
.cited-span__open {
    margin-left: auto; flex-shrink: 0;
    font-weight: 600; color: var(--accent); text-decoration: none; white-space: nowrap;
}
.cited-span__open:hover { text-decoration: underline; }

/* The highlighted quote — shared by the cited-span and the source-doc
 * OCR view (scroll-to-highlight). Highlighter-gold over full ink so the
 * cited words pop against the dimmed context. */
mark.mem-hl {
    background: rgba(222, 158, 71, 0.34);   /* --accent-ember, soft */
    color: var(--text-primary); font-weight: 600;
    border-radius: 3px; padding: 0.5px 1px;
    box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.mem-hl--flash { animation: memHlFlash 1.6s ease-out 1; }
@keyframes memHlFlash {
    0%   { background: rgba(222, 158, 71, 0.85); }
    100% { background: rgba(222, 158, 71, 0.34); }
}
@media (prefers-reduced-motion: reduce) {
    .mem-hl--flash { animation: none; }
}

/* ---------- CompanyMark + name cell (v0.2 — PRIMITIVES.md § Atoms) -
 * Two-letter monogram next to a name, used in datagrid identity cells.
 * Steel-soft chip on paper, with a name + secondary line. */
.cell-co { display: flex; align-items: center; gap: 8px; }
.cell-co__mark {
    width: 26px; height: 26px;
    background: var(--steel-soft); color: var(--steel);
    border-radius: var(--radius-sm);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 10px; font-weight: 700; letter-spacing: 0.02em;
}
.cell-co__name { font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
.cell-co__sub { font-size: 11px; color: var(--text-muted); }

/* Misc utility used across the styleguide and a handful of inline rows */
.row-flex { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ---------- Identity-icon cell (.cell-id) — unified browse atom ---
 *
 * Used by the unified Contacts browse (mixed companies + people in
 * one Datagrid). Mono square shows two letters: company initials on
 * a neutral ground; person initials in a terracotta-soft pill. The
 * shape (square vs round) + color separates kinds at scan speed
 * without hunting for a column header.
 *
 * Generalises .cell-co (kept as legacy) by adding the .is-person
 * variant and a sub-line slot for secondary identity (email/phone
 * for a person, locale/IČO for a company). */

.cell-id {
    display: flex;
    align-items: center;
    gap: 10px;
}
.cell-id__mark {
    width: 30px; height: 30px;
    border-radius: var(--radius);
    display: grid; place-items: center;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.02em;
    flex-shrink: 0;
    border: 1px solid transparent;
    /* When the mark is rendered as <img> (avatar URL → uploaded JPEG or
       initials SVG fallback), keep the same 30×30 footprint and crop any
       overflow. The is-person modifier below still rounds it to a circle. */
    object-fit: cover;
    background: var(--bg-subtle);
}
.cell-id__mark.is-co {
    background: #dde6ec;
    color: #16202a;
}
.cell-id__mark.is-person {
    background: var(--accent-soft);
    color: var(--accent);
    border-radius: 999px;
    border-color: rgba(185, 91, 55, 0.18);
}
.cell-id__name {
    font-weight: 600;
    color: var(--text-primary);
    font-size: 13px;
}
.cell-id__sub {
    font-size: 11.5px;
    color: var(--text-muted);
    margin-top: 1px;
}
.cell-id__sub a {
    color: var(--text-muted);
    text-decoration: none;
}
.cell-id__sub a:hover { color: var(--accent); }

/* Kind tag in its own datagrid column — small caps "Person" / "Company".
 * Quiet by design; the .cell-id__mark shape and color carry most of
 * the signal already. */
.cell-kind {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
    color: var(--text-muted);
}
.cell-kind.is-person { color: var(--accent); }

/* ---------- EmptyState (v0.2 — from PRIMITIVES.md § Data display) ---
 * Centered block for "nothing here yet" surfaces. Always paired with
 * a Datagrid that has zero rows (Rule: never a blank table). One small
 * muted icon, one-line heading, one-line explanation, one primary action. */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 48px 24px;
    color: var(--text-secondary);
}
.empty-state__icon {
    width: 40px;
    height: 40px;
    color: var(--text-muted);
    margin-bottom: 12px;
}
.empty-state__icon svg { width: 100%; height: 100%; }
.empty-state__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 4px;
}
.empty-state__sub {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 0 0 16px;
    max-width: 420px;
    line-height: 1.5;
}
.empty-state__action { margin-top: 4px; }

/* ---------- Breadcrumb (v0.2 — for DetailPage eyebrow) ----------------
 * Topline path used on detail surfaces (Proposals RFP, Memory Document,
 * Memory Concept, …). Anchored items are links to ancestor pages; the
 * `--current` modifier marks the leaf (no link, slightly heavier weight).
 * Separators are muted chevrons. Sits in the page eyebrow slot. */
.ds-breadcrumb {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 13px;
    line-height: 1.4;
    color: var(--text-secondary);
}
.ds-breadcrumb__item {
    color: var(--text-secondary);
    text-decoration: none;
}
a.ds-breadcrumb__item:hover {
    color: var(--text-primary);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.ds-breadcrumb__sep {
    color: var(--text-muted);
    user-select: none;
}
.ds-breadcrumb__item--current {
    color: var(--text-primary);
    font-weight: 500;
}

/* ---------- PdfDrawer (v0.2 — platform primitive promoted from
 *            comparison_matrix.css)
 * Side-by-side viewer for source documents: full-size iframe on the
 * left, excerpt + page-nav rail on the right. Used by the comparison
 * matrix to show the PDF excerpt that supports an extracted score.
 * Implemented as a native <dialog> with an open/close CSS-only
 * fade-overlay. Class names retained as `.ip-pdf-*` until a
 * coordinated rename across CSS + JS + templates lands; the
 * primitive name "PdfDrawer" applies to the design contract.
 * Today PDF-only; if/when other document formats are added, rename
 * to DocPanel and rev the primitive. */

/* native <dialog> */
.ip-pdf-dialog {
  border: none;
  border-radius: 14px;
  padding: 0;
  background: transparent;
  box-shadow: 0 24px 80px rgba(0,0,0,.28);
  max-width: min(92vw, 900px);
  width: min(92vw, 900px);
  max-height: 90vh;
  overflow: hidden;
  margin: auto;
}

.ip-pdf-dialog::backdrop {
  background: rgba(0,0,0,.55);
}

.ip-pdf-dialog[open] {
  display: flex;
  flex-direction: column;
}

/* polyfill fallback: if browser doesn't support <dialog>, we show a manual overlay */
.ip-pdf-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.55);
  z-index: 9000;
  align-items: center;
  justify-content: center;
}

.ip-pdf-overlay.open {
  display: flex;
}

/* shared inner container used by both the native dialog and the polyfill */
.ip-pdf-inner {
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 14px;
  overflow: hidden;
  width: 100%;
  max-width: min(92vw, 900px);
  max-height: 90vh;
}

/* ---- header ---- */
.ip-pdf-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border, rgba(228,221,209,.9));
  background: var(--bg-subtle, #f9f8f6);
  flex-shrink: 0;
  gap: 12px;
}

.ip-pdf-hd__title {
  font-size: .938rem;
  font-weight: 600;
  color: var(--text-primary, #384652);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}

.ip-pdf-hd__page {
  font-size: .813rem;
  color: var(--text-secondary, #697784);
  white-space: nowrap;
}

.ip-pdf-hd__close {
  background: none;
  border: none;
  color: var(--text-secondary, #697784);
  cursor: pointer;
  font-size: 1.4rem;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 4px;
  flex-shrink: 0;
}

.ip-pdf-hd__close:hover {
  color: var(--text-primary, #384652);
  background: var(--bg-surface, #E4DDD1);
}

/* ---- body — two-pane ---- */
.ip-pdf-body {
  display: flex;
  flex: 1;
  overflow: hidden;
  min-height: 0;
}

.ip-pdf-viewer {
  flex: 1;
  overflow: auto;
  background: #525659;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 16px;
}

.ip-pdf-viewer iframe {
  border: none;
  background: #fff;
  min-height: 500px;
  width: 100%;
  border-radius: 2px;
  box-shadow: 0 2px 12px rgba(0,0,0,.3);
}

/* ---- excerpt sidebar ---- */
.ip-pdf-excerpt {
  width: 240px;
  flex-shrink: 0;
  border-left: 1px solid var(--border, rgba(228,221,209,.9));
  background: var(--bg-subtle, #f9f8f6);
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ip-pdf-excerpt__label {
  font-size: .688rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-secondary, #697784);
}

.ip-pdf-excerpt__text {
  font-size: .875rem;
  color: var(--text-primary, #384652);
  line-height: 1.6;
  font-style: italic;
  border-left: 3px solid var(--steel, #6E8798);
  padding-left: 10px;
}

/* ---- footer nav ---- */
.ip-pdf-ft {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 10px 20px;
  border-top: 1px solid var(--border, rgba(228,221,209,.9));
  background: var(--bg-subtle, #f9f8f6);
  flex-shrink: 0;
}

.ip-pdf-ft__page-label {
  font-size: .875rem;
  color: var(--text-secondary, #697784);
  min-width: 80px;
  text-align: center;
}

/* ---------- RightRail + RightRailCard (v0.2 design pass) -------------
 *
 * From docs/facelift-mocks/v0.2/PRIMITIVES.md § Detail surfaces:
 * "RightRailCard is the canonical primitive for every right-rail block.
 *  Memory, Affiliation, Deals, future module-contributed cards — all
 *  compose this. There are no separate MemoryContext / Affiliation /
 *  Deals primitives."
 *
 * v1 rules:
 * - Rail scrolls independently of the main column.
 * - Memory is always pinned at the top of the stack.
 * - No overflow disclosure — if the rail grows past 3-4 cards, scroll.
 *
 * Module-contributed cards carry a quiet .rrcard__module-tag (small
 * uppercase mono, neutral grey, top-right of header — "FROM SALES").
 * Trust signal that this section comes from a module, not core. */

.rrail {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 320px;
    flex-shrink: 0;
}
.rrail--narrow { width: 280px; }
.rrail--wide   { width: 340px; }

/* ---------- Memory inject banner (main-column slot) ----------------- *
 *
 * Banner slot for module injects on memory's read surfaces (entity
 * detail, document detail). Loud enough to claim attention when a
 * module has open work referencing this artefact — "there's an open
 * claim on this doc" — quiet enough that it doesn't overwhelm the
 * substrate cargo when no one's contributing. See
 * core/memory/injects.py::memory_banner. */
.memory-inject-banner {
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-lg);
    padding: 12px 16px;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    box-shadow: var(--shadow-card);
}
.memory-inject-banner .rrcard__module-tag {
    margin-top: 2px;
    flex-shrink: 0;
}
.memory-inject-banner__body {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-primary);
}
.memory-inject-banner__body a {
    color: var(--accent);
    font-weight: 600;
    text-decoration: none;
}
.memory-inject-banner__body a:hover { text-decoration: underline; }

.rrcard {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
}

.rrcard__hd {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-subtle);
}
.rrcard__hd-title {
    font-size: 12px;
    font-weight: 700;
    color: var(--text-primary);
    letter-spacing: -0.005em;
    flex: 1;
    min-width: 0;
}
.rrcard__hd-title .ai-mark { margin-right: 4px; vertical-align: 1px; }

/* "See all →" trailing link in a card header — quiet, accent-coloured. */
.rrcard__hd-link {
    font-size: 12px;
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
    flex-shrink: 0;
    letter-spacing: -0.005em;
}
.rrcard__hd-link:hover { text-decoration: underline; }

/* Quiet, non-interactive trust signal. Same typographic family as .ai-mark
 * (uppercase, mono, ~9-10px) but neutral grey, no fill, no background. */
.rrcard__module-tag {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    flex-shrink: 0;
    user-select: none;
}

/* Optional inline action in the header — e.g. "+" to add a note. Tiny,
 * ghost, never draws focus from the card content. */
.rrcard__hd-action {
    background: transparent;
    border: none;
    width: 24px; height: 24px;
    border-radius: 5px;
    display: grid; place-items: center;
    cursor: pointer;
    color: var(--text-secondary);
    flex-shrink: 0;
    margin-left: 2px;
}
.rrcard__hd-action:hover { background: rgba(22,32,42,0.06); color: var(--text-primary); }
.rrcard__hd-action svg { width: 13px; height: 13px; }

.rrcard__bd { padding: 12px 14px; }
.rrcard__bd--flush { padding: 0; }    /* lists, full-bleed content */
.rrcard__bd--tight { padding: 8px 14px; }

/* Inline icon used by the Contacts rail identity card (employments,
 * emails, phones) — sized to sit at x-height alongside the value. */
.contacts-rail__icon {
    width: 12px; height: 12px;
    stroke-width: 2;
    color: var(--text-muted);
    flex-shrink: 0;
}

.rrcard__ft {
    border-top: 1px solid var(--border-subtle);
    padding: 8px 14px;
    background: var(--bg-subtle);
}
.rrcard__ft a, .rrcard__ft button.btn-link {
    font-size: 12px;
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
    background: transparent; border: none; padding: 0; cursor: pointer;
    font-family: inherit;
}
.rrcard__ft a:hover, .rrcard__ft button.btn-link:hover { text-decoration: underline; }

/* Body content patterns — these are *configurations* of rrcard__bd,
 * not separate primitives. */

/* Chip strip — used by Memory card to surface top-N items. */
.rrcard__chips {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.rrcard__chip {
    display: flex;
    gap: 8px;
    align-items: baseline;
    padding: 6px 8px;
    border-radius: var(--radius-sm);
    font-size: 12.5px;
    color: var(--text-primary);
    text-decoration: none;
    line-height: 1.35;
    cursor: pointer;
    border: none;
    background: transparent;
    text-align: left;
    font-family: inherit;
}
.rrcard__chip:hover { background: var(--bg-subtle); }
.rrcard__chip-kind {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    flex-shrink: 0;
    width: 44px;
    padding-top: 2px;
}
.rrcard__chip-kind.is-note { color: var(--accent); }
.rrcard__chip-kind.is-fact { color: var(--violet); }
.rrcard__chip-kind.is-quote { color: var(--steel); }
.rrcard__chip-body { flex: 1; min-width: 0; }
.rrcard__chip-body strong { font-weight: 600; color: var(--text-primary); }
.rrcard__chip-meta {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 1px;
}

/* Key-value list — used by Affiliation card. */
.rrcard__kv {
    display: grid;
    grid-template-columns: 88px 1fr;
    gap: 4px 10px;
    margin: 0;
    font-size: 12.5px;
}
.rrcard__kv dt { color: var(--text-muted); font-weight: 500; font-size: 11.5px; padding-top: 1px; }
.rrcard__kv dd { margin: 0; color: var(--text-primary); font-weight: 600; }
.rrcard__kv dd .muted { color: var(--text-muted); font-weight: 500; }

/* Compact line list — used by Deals card. */
.rrcard__lines {
    list-style: none;
    margin: 0; padding: 0;
}
.rrcard__lines li {
    padding: 8px 14px;
    border-bottom: 1px solid var(--border-subtle);
    font-size: 12.5px;
    display: flex;
    gap: 8px;
    align-items: center;
}
.rrcard__lines li:last-child { border-bottom: none; }
.rrcard__lines strong { font-weight: 600; color: var(--text-primary); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rrcard__lines .num { font-variant-numeric: tabular-nums; color: var(--text-secondary); font-size: 12px; }

/* Empty state inside a card — neutral, never errored. */
.rrcard__empty {
    padding: 14px 14px 16px;
    text-align: center;
    font-size: 12.5px;
    color: var(--text-muted);
    line-height: 1.5;
}
.rrcard__empty strong { color: var(--text-secondary); font-weight: 600; display: block; margin-bottom: 2px; }

/* Loading state — uses ai-skeleton blocks per the AI working-states rule.
 * Per-row skeletons inside one card are fine; multiple shimmering cards
 * across the rail at once is drift — hoist to a page-level .ai-activity. */
.rrcard__skeleton {
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.rrcard__skeleton .ai-skeleton { border-radius: 5px; }

/* ---------- Note composer (Memory tab) ------------------------------- *
 *
 * From docs/facelift-mocks/v0.2/PRIMITIVES.md § Detail surfaces:
 * "The only way humans add to Memory. A note is a memory item with
 *  kind=note, source=manual, author=current_actor."
 *
 * Idle: single-line collapsed input ("Add a note about this contact…").
 * Focused: expands to textarea + Cancel/Save bar. Lives at the top of
 * the Memory tab on every detail page. The Memory RightRailCard "+"
 * affordance focuses-and-expands this same composer (it does not open
 * its own).
 *
 * Keyboard: ⌘+Enter saves, Esc cancels. Do not auto-save on blur. */

.note-composer {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    transition: border-color 160ms ease, box-shadow 160ms ease;
}
.note-composer.is-expanded {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-card);
}
.note-composer__collapsed {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    cursor: text;
    color: var(--text-muted);
    font-size: 13px;
}
.note-composer__collapsed svg { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.7; }
.note-composer.is-expanded .note-composer__collapsed { display: none; }

.note-composer__expanded { display: none; }
.note-composer.is-expanded .note-composer__expanded { display: block; }

.note-composer__textarea {
    width: 100%;
    border: none;
    background: transparent;
    padding: 12px 14px 6px;
    font: inherit;
    font-size: 13.5px;
    color: var(--text-primary);
    resize: vertical;
    min-height: 72px;
    outline: none;
    font-family: inherit;
    line-height: 1.5;
}
.note-composer__bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 8px;
    border-top: 1px solid var(--border-subtle);
}
.note-composer__bar .spacer { flex: 1; }
.note-composer__hint {
    font-size: 11px;
    color: var(--text-muted);
}
.note-composer__hint kbd {
    font-family: var(--font-sans);
    font-size: 10px;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0 4px;
    color: var(--text-secondary);
    font-weight: 600;
}

/* ---------- Memory list (interleaved, on Memory tab) ----------------- *
 *
 * Notes (kind=note, source=manual) interleave with extracted facts,
 * summaries, quotes, etc. Same store, same primitive, sorted by
 * recency + relevance.
 *
 * System events DO NOT enter Memory — they live in Activity. Memory
 * is curated knowledge; Activity is the immutable event log. Three
 * lenses, one entity: Overview / Memory / Activity. */

.memlist { list-style: none; margin: 0; padding: 0; }
.memlist__item {
    display: grid;
    grid-template-columns: 60px 1fr auto;
    gap: 14px;
    padding: 14px 0;
    border-bottom: 1px solid var(--border-subtle);
    align-items: baseline;
}
.memlist__item:last-child { border-bottom: none; }
.memlist__kind {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    padding-top: 2px;
}
.memlist__kind.is-note { color: var(--accent); }
.memlist__kind.is-fact { color: var(--violet); }
.memlist__kind.is-quote { color: var(--steel); }
.memlist__kind.is-summary { color: var(--forest); }
.memlist__kind.is-extracted { color: var(--violet); }

.memlist__body { font-size: 14px; line-height: 1.55; color: var(--text-primary); min-width: 0; }
.memlist__body strong { font-weight: 600; }
.memlist__body em.q { font-style: normal; color: var(--text-secondary); border-left: 2px solid var(--steel); padding-left: 10px; display: block; margin-top: 2px; }
.memlist__meta {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 6px;
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
}
.memlist__meta .ai-mark { vertical-align: 0; }
.memlist__meta .sep { color: rgba(22,32,42,0.18); }

.memlist__side {
    display: flex;
    align-items: center;
    gap: 4px;
    align-self: start;
    padding-top: 2px;
}
.memlist__side button {
    background: transparent;
    border: none;
    width: 24px; height: 24px;
    border-radius: 5px;
    display: grid; place-items: center;
    cursor: pointer;
    color: var(--text-muted);
    opacity: 0;
    transition: opacity 120ms ease, background 120ms ease;
}
.memlist__item:hover .memlist__side button { opacity: 1; }
.memlist__side button:hover { background: rgba(22,32,42,0.06); color: var(--text-primary); }
.memlist__side button svg { width: 13px; height: 13px; }
/* Pin action affordances on the row currently being edited so users can
 * still see the Cancel target without hunting for it. */
.memlist__item.is-editing .memlist__side button { opacity: 1; }

/* Inline edit affordance for memory notes: lightweight surface that reuses
 * the .note-composer__textarea + bar primitives so the edit experience
 * matches the create experience. */
.memlist__editor {
    margin-top: 4px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}
.memlist__editor .note-composer__textarea { padding: 8px 10px; min-height: 56px; }
.memlist__editor .note-composer__bar { padding: 4px 8px 6px; }

/* ---------- Person/Company detail header (DetailPage atom) ---------- *
 *
 * Identity row at the top of every Person/Company DetailPage:
 * avatar/monogram (56×56) + name + role chip + key channels (email/phone).
 * Sits inside the Page.Header before the TabBar. */

.detail-id {
    display: flex;
    align-items: center;
    gap: 14px;
}
.detail-id__mono {
    width: 56px; height: 56px;
    border-radius: var(--radius-lg);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    display: grid; place-items: center;
    font-size: 18px;
    font-weight: 700;
    color: var(--text-secondary);
    letter-spacing: 0.02em;
    flex-shrink: 0;
}
.detail-id__mono.is-person {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: rgba(185,91,55,0.20);
}
/* Click-to-change avatar — wraps the same 56px footprint as .detail-id__mono
   so the layout doesn't shift, but presents an <img> with hover affordance. */
.detail-id__avatar-btn {
    width: 56px; height: 56px;
    padding: 0; border: none; background: none;
    border-radius: 50%;
    overflow: hidden;
    cursor: pointer;
    flex-shrink: 0;
    display: block;
}
.detail-id__avatar-img {
    width: 100%; height: 100%;
    object-fit: cover;
    border-radius: 50%;
    background: var(--bg-surface);
    display: block;
}
/* Square variant — companies (and other org-shaped entities) get a rounded
   square instead of a circle. Mirrors the .contacts-avatar__trigger--square
   pattern used by the list views; the server-side initials fallback already
   renders with shape="square" so the SVG corners match the wrapper. */
.detail-id__avatar-btn--square,
.detail-id__avatar-btn--square .detail-id__avatar-img { border-radius: var(--radius); }

/* ----- Avatar edit affordance (pencil overlay) --------------------- *
 *
 * Shared by .detail-id__avatar-btn (56px on /contacts/<id>) and
 * .drawer-avatar-btn (36px on the per-row drawer in /contacts). The
 * overlay is absolutely positioned, inherits border-radius from its
 * host button so it clips to the avatar shape (circle for people,
 * rounded-square for companies), and fades in on hover/focus.
 *
 * Why an overlay instead of a corner badge: scales cleanly to any avatar
 * size, no badge-positioning math, and the pencil is centred so it reads
 * as "edit this image" at 36px just as well as at 56px. */
.detail-id__avatar-btn,
.drawer-avatar-btn { position: relative; }
.avatar-edit-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    background: rgba(0, 0, 0, 0.42);
    opacity: 0;
    transition: opacity .14s ease;
    border-radius: inherit;
    pointer-events: none;
}
.avatar-edit-overlay svg { width: 40%; height: 40%; stroke-width: 2; }
.detail-id__avatar-btn:hover .avatar-edit-overlay,
.detail-id__avatar-btn:focus-visible .avatar-edit-overlay,
.drawer-avatar-btn:hover .avatar-edit-overlay,
.drawer-avatar-btn:focus-visible .avatar-edit-overlay {
    opacity: 1;
}

/* Contacts-drawer avatar button — wraps the 36×36 .cell-id__mark img
   from _browse_row_drawer.html so the avatar becomes a click target
   that opens the cropper dialog. Footprint and rounding match the
   inner img's kind-aware shape (.is-person → circle, .is-co → square)
   so the overlay clips correctly to the avatar silhouette. */
.drawer-avatar-btn {
    width: 36px; height: 36px;
    padding: 0; border: none; background: none;
    cursor: pointer;
    overflow: hidden;
    flex-shrink: 0;
    display: block;
}
.drawer-avatar-btn.is-person { border-radius: 999px; }
.drawer-avatar-btn.is-co { border-radius: var(--radius); }
/* `.cell-id__mark` is sized 30×30 in the table-row context (its primary
   home). Inside the 36×36 drawer button it needs to fill the wrapper
   so the avatar silhouette matches the overlay's clip path — without
   this the img sits in the top-left corner of the button and the
   pencil hint floats over empty pixels. Same shape as the detail-page
   `.detail-id__avatar-img { width:100%; height:100% }` rule. */
.drawer-avatar-btn .cell-id__mark {
    display: block;
    width: 100%;
    height: 100%;
}
.detail-id__channels {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
    font-size: 13px;
    color: var(--text-secondary);
    margin-top: 4px;
}
.detail-id__channels a { color: var(--text-secondary); text-decoration: none; }
.detail-id__channels a:hover { color: var(--accent); }
/* Scope to the direct mail/phone glyph on the channel link, NOT any descendant
   svg — otherwise inline-stamp chips rendered next to the value (e.g. Contacts
   "✓ Kontakty" via core/contacts/inject.py) inherit margin/size/opacity and
   the pill spacing breaks. The .ds-inline-stamp's own svg rule keeps its
   intended 11×11 / opacity .7 inside the chip. */
.detail-id__channels > a > svg { width: 12px; height: 12px; vertical-align: -2px; opacity: 0.6; margin-right: 4px; }
/* Per-fact copy affordance — a quiet glyph button after the channel value that
   reuses the global [data-copy] handler (base.html) + .ds-copy-toast. It is a
   <button> (never the <a>), so the carefully-scoped "> a > svg" glyph rule
   above never styles its icon. */
.detail-id__copy { margin-left: -6px; background: none; border: 0; padding: 2px; cursor: pointer; line-height: 0; color: var(--text-secondary); opacity: 0.5; border-radius: 4px; display: inline-flex; align-items: center; transition: opacity .15s ease, color .15s ease, background .15s ease; }
.detail-id__copy:hover { opacity: 1; color: var(--accent); background: rgba(110, 135, 152, .12); }
.detail-id__copy:focus-visible { opacity: 1; color: var(--accent); outline: 2px solid var(--accent); outline-offset: 1px; }
.detail-id__copy svg { width: 13px; height: 13px; }

/* ---------- Page body with rail (Page + RightRail combined) --------- *
 *
 * The shape used by detail pages: main column (1fr) + sticky right rail
 * (auto width via .rrail). Rail scrolls independently of main column.
 * Below 1280px the layout collapses to single column, rail static. */

.page-with-rail {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 32px;
    align-items: start;
}
.page-with-rail__main { min-width: 0; }
.page-with-rail__rail { position: sticky; top: 24px; }
@media (max-width: 1280px) {
    .page-with-rail { grid-template-columns: 1fr; }
    .page-with-rail__rail { position: static; }
    .rrail { width: 100%; }
}


/* ---------- Home / Overview tool strip ---------------------------------
 *
 * Lifted from the inline <style> block in templates/home.html.
 * Used by the optional bottom-of-page link strip (Style guide, etc.) on
 * /home. The tool buttons are quiet by default and lift to ink on hover.
 */
.health-tools {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid var(--border-subtle);
}
.health-tool-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text-secondary);
}
.health-tool-btn:hover { color: var(--text-primary); }
.health-tool-btn .app-lucide { width: 14px; height: 14px; }

/* ---------- Inbox primitive (Phase E aggregator surface) ---------------
 *
 * Used by /home (the new inbox surface) and any future filtered slice.
 * The .inbox-item row is built around a 3-col layout:
 *   .inbox-item__src  | .inbox-item__body                 | .inbox-item__meta
 *   icon column       | title + badges + subtitle + actions| time + category badge
 *
 * .is-ai modifier lifts the row with an AI accent (used when the underlying
 * memory item / proposal / draft was AI-extracted).
 */
.inbox-list {
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.inbox-item {
    display: grid;
    grid-template-columns: 24px 1fr auto;
    gap: 14px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border-subtle);
    align-items: start;
}
.inbox-item:last-child { border-bottom: none; }
/* AI-derived items get a 3px violet stripe down the left edge instead of
 * a full gradient background — the v0.2 mock cue is a vertical accent,
 * not a tinted row. The stripe is laid in via box-shadow inset so it
 * doesn't shift content (no border-width to compensate for) and works
 * cleanly with the row's existing grid layout. */
.inbox-item.is-ai { box-shadow: inset 3px 0 0 0 var(--violet); }

.inbox-item__src {
    width: 24px;
    height: 24px;
    border-radius: 6px;
    background: var(--bg-subtle);
    color: var(--text-secondary);
    display: grid;
    place-items: center;
    flex-shrink: 0;
}
.inbox-item__src svg { width: 14px; height: 14px; }
/* Source-class colors. Both the legacy short names (src-ai, src-mail,
 * src-timer) and the runtime kind values emitted by sources (src-ai-proposal,
 * src-email, src-cron, src-scheduled, src-ask-actor, src-notification) are
 * aliased to the same palette so adapters can pick whichever name reads
 * naturally without fighting the template's `src-{kind}` formatter. */
.inbox-item__src.src-ai,
.inbox-item__src.src-ai-proposal { background: var(--violet-soft); color: var(--violet); }
.inbox-item__src.src-mail,
.inbox-item__src.src-email { background: var(--forest-soft); color: var(--forest); }
.inbox-item__src.src-whatsapp { background: rgba(47,107,87,0.1); color: var(--forest); }
.inbox-item__src.src-timer,
.inbox-item__src.src-cron,
.inbox-item__src.src-scheduled { background: rgba(201,136,16,0.1); color: var(--warning); }
.inbox-item__src.src-ask-actor,
.inbox-item__src.src-notification { background: var(--bg-subtle); color: var(--text-secondary); }

.inbox-item__body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.inbox-item__head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.inbox-item__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    line-height: 1.35;
}
.inbox-item__sub {
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.5;
}
.inbox-item__sub strong { color: var(--text-primary); font-weight: 600; }
.inbox-item__sub .muted { color: var(--text-muted); }
.inbox-item__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
    opacity: 0;
    transition: opacity 100ms;
}
.inbox-item:hover .inbox-item__actions,
.inbox-item.is-active .inbox-item__actions { opacity: 1; }

/* Bundle rows: actions are the primary affordance (Open / Accept all /
 * Dismiss all), not a hover-revealed extra. Override the opacity gate so
 * they're always visible. The .is-bundle modifier is reserved for any
 * future visual treatment that distinguishes a bundle from a singleton —
 * today the row chrome is identical. */
.inbox-item__actions--bundle { opacity: 1; }

/* AI · N count chip on bundle rows. Slightly stronger than the regular
 * .badge-ai so the count reads as "this row stands in for N items" at
 * a glance — otherwise the number gets lost inside the title text. */
.inbox-item__count-chip {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.inbox-item__meta {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    font-size: 11px;
    color: var(--text-muted);
    flex-shrink: 0;
}

/* Inbox-zero — celebration when the feed is empty. Larger than .empty-state
 * because /home with no inbox items is a meaningful achievement. */
.inbox-zero {
    padding: 60px 24px;
    text-align: center;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}
.inbox-zero__mark {
    width: 56px;
    height: 56px;
    margin: 0 auto 16px;
    border-radius: 14px;
    background: var(--bg-subtle);
    display: grid;
    place-items: center;
    color: var(--text-muted);
}
.inbox-zero__mark svg { width: 28px; height: 28px; }
.inbox-zero__title {
    font-size: 16px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 0 0 6px;
}
.inbox-zero__sub {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 0;
}

/* Greeting line on /home (above the inbox) */
.greet {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.2;
    margin: 0;
}
.greet-name { color: var(--text-primary); }
.greet-tail { color: var(--text-muted); font-weight: 500; }
.greet-sub {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 6px 0 24px;
}

/* Section heading above an inbox slice. Matches the .ds-tabs scale. */
.section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin: 0 0 12px;
}
.section-head h2 {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    font-weight: 700;
    margin: 0;
}
.section-head .meta { font-size: 12px; color: var(--text-muted); }

/* ---------- Inbox layout (two-column: main + right rail) -----------------
 *
 * Used by /inbox. Main column takes remaining space; right rail is fixed
 * at 320px (the canonical .rrail width). Below 1100px the two columns
 * collapse to a single column with the rail below the feed.
 *
 * Classes:
 *   .inbox-layout        — outer grid container
 *   .inbox-layout__main  — primary column (feed + chips)
 *   .inbox-layout__rail  — right rail (composes .rrail)
 */
.inbox-layout {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 20px;
    align-items: start;
}
.inbox-layout__main { min-width: 0; }
.inbox-layout__rail { position: sticky; top: 24px; }
@media (max-width: 1100px) {
    .inbox-layout { grid-template-columns: 1fr; }
    .inbox-layout__rail { position: static; }
    .inbox-layout__rail.rrail { width: 100%; }
}

/* ---------- Inbox filter chips (tabbed row above the list) ---------------
 *
 * Five source-filter buttons rendered as a segmented control. The active
 * chip has a filled background; disabled/empty chips are muted and not
 * clickable. JS sets data-filter on .inbox-list; CSS hides non-matching rows.
 *
 * CSS filter rules — show/hide rows by kind:
 *   .inbox-list[data-filter="email"]       → show only data-kind="email"
 *   .inbox-list[data-filter="whatsapp"]    → show only data-kind="whatsapp"
 *   .inbox-list[data-filter="ai_proposal"] → show only data-kind="ai_proposal"
 *   .inbox-list[data-filter="scheduled"]   → show only data-kind="scheduled" + ask_actor
 *   .inbox-list[data-filter="all"]         → show all (no hiding)
 */
/* The .inbox-filters row wraps the chip strip + spacer + mark-all-read
 * button so the trailing action stays right-aligned even on narrow rails. */
.inbox-filters {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}
.inbox-mark-all-read { margin-left: auto; flex-shrink: 0; }
.inbox-tabs {
    display: flex;
    align-items: center;
    gap: 3px;
    flex-wrap: wrap;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 3px;
    box-shadow: var(--shadow-card);
    width: fit-content;
}
.inbox-tab {
    background: transparent;
    border: none;
    padding: 4px 10px;
    border-radius: 5px;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
    transition: background 100ms ease, color 100ms ease;
}
.inbox-tab:hover:not(:disabled) { color: var(--text-primary); background: var(--bg-subtle); }
.inbox-tab.is-active {
    background: var(--bg-page);
    color: var(--text-primary);
    box-shadow: 0 0 0 1px var(--border);
}
.inbox-tab.is-empty,
.inbox-tab:disabled {
    opacity: 0.45;
    cursor: default;
}
.inbox-tab__count {
    font-size: 11px;
    color: var(--text-muted);
    font-weight: 500;
}
.inbox-tab.is-active .inbox-tab__count { color: var(--text-secondary); }
/* Inline source icon: 12px, currentColor — picks up the chip's own foreground
 * so the icon shifts from muted (idle) to text-primary (hover/active). */
.inbox-tab__icon {
    width: 12px; height: 12px;
    flex-shrink: 0;
}
/* AI-mark sits inside the AI-proposals chip and stays violet regardless of
 * chip activeness — the brand mark is the recognition signal. */
.inbox-tab .ai-mark { vertical-align: 1px; }

/* CSS filter — hide rows that don't match the active tab.
 * "all" shows everything; each other value hides rows whose data-kind
 * doesn't match. Scheduled maps to 'ask_actor' source items triggered
 * by cron — we match both 'scheduled' and 'ask_actor' for now. */
.inbox-list[data-filter="email"] .inbox-item:not([data-kind="email"]) { display: none; }
.inbox-list[data-filter="whatsapp"] .inbox-item:not([data-kind="whatsapp"]) { display: none; }
.inbox-list[data-filter="ai_proposal"] .inbox-item:not([data-kind="ai_proposal"]) { display: none; }
.inbox-list[data-filter="scheduled"] .inbox-item:not([data-kind="scheduled"]):not([data-kind="ask_actor"]) { display: none; }

/* ---------- Right-rail run list (Right now card) -------------------------
 *
 * A compact list of currently running workflow runs with a pulsing dot
 * (green = RUNNING, steel/static = WAITING). Composes inside .rrcard__bd.
 */
.inbox-rail-runs {
    list-style: none;
    margin: 0; padding: 0;
}
.inbox-rail-run {
    border-bottom: 1px solid var(--border-subtle);
    font-size: 12px;
}
.inbox-rail-run:last-child { border-bottom: none; }
/* The whole row is a link — clicking jumps to the workflow run detail.
 * For WAITING runs that's the respond surface, the most common ask. */
.inbox-rail-run__link {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 14px;
    color: inherit;
    text-decoration: none;
    transition: background-color .12s ease;
}
.inbox-rail-run__link:hover { background: var(--accent-soft); }
.inbox-rail-run__link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.inbox-rail-run__body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.inbox-rail-run__body strong {
    font-weight: 600;
    color: var(--text-primary);
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.inbox-rail-run__meta {
    font-size: 11px;
    color: var(--text-muted);
}
.inbox-rail-run__ts {
    font-size: 11px;
    color: var(--text-muted);
    flex-shrink: 0;
    white-space: nowrap;
}
/* Pulsing dot — green when RUNNING, steel when WAITING */
.inbox-rail-run__dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 4px;
    background: var(--forest);
    box-shadow: 0 0 0 0 rgba(47, 107, 87, 0.4);
    animation: inbox-rail-pulse 1.6s infinite;
}
.inbox-rail-run__dot.is-wait {
    background: var(--steel);
    animation: none;
    box-shadow: none;
}
@keyframes inbox-rail-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(47, 107, 87, 0.4); }
    70%  { box-shadow: 0 0 0 6px rgba(47, 107, 87, 0); }
    100% { box-shadow: 0 0 0 0 rgba(47, 107, 87, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .inbox-rail-run__dot { animation: none; }
}

/* ---------- Right-rail activity timeline (What just happened card) --------
 *
 * A minimal timeline — dot + name + timestamp — for recent completed
 * workflow runs. Dot color: green = COMPLETED, red = FAILED.
 */
.inbox-rail-timeline {
    list-style: none;
    margin: 0; padding: 0;
}
.inbox-rail-tl {
    border-bottom: 1px solid var(--border-subtle);
    font-size: 12px;
    color: var(--text-secondary);
}
.inbox-rail-tl:last-child { border-bottom: none; }
/* Whole row links to /flows/run/<id> so the operator can drill into the
 * recent activity item directly from the inbox. */
.inbox-rail-tl__link {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 14px;
    color: inherit;
    text-decoration: none;
    transition: background-color .12s ease;
}
.inbox-rail-tl__link:hover { background: var(--accent-soft); }
.inbox-rail-tl__link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.inbox-rail-tl__dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 5px;
    background: var(--text-muted);
}
.inbox-rail-tl__dot.is-ok  { background: var(--forest); }
.inbox-rail-tl__dot.is-err { background: var(--error); }
.inbox-rail-tl__dot.is-ai  { background: var(--violet); }
.inbox-rail-tl__body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.inbox-rail-tl__body strong {
    font-weight: 600;
    color: var(--text-primary);
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.inbox-rail-tl__ts {
    font-size: 11px;
    color: var(--text-muted);
    display: block;
}

/* ---------- Admin Capabilities (templates/admin/capabilities.html) ------
 *
 * Lifted from the inline <style> block in templates/admin/capabilities.html.
 * Used by the /admin/capabilities cheatsheet page — a grid of cap-cards
 * grouped by type (Triggers, Flow, Send, Actions, CRM, AI).
 */
.cap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.cap-group-hd {
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  padding: 24px 0 6px;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 4px;
}
.cap-group-hd:first-child { padding-top: 0; }
.cap-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.cap-card--dim { opacity: .55; }
.cap-card__hd {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.cap-card__label {
  font-weight: 600;
  font-size: .875rem;
}
.cap-card__id {
  margin-left: auto;
  font-size: .7rem;
  color: var(--color-text-muted);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 1px 5px;
  font-family: var(--font-mono, monospace);
  white-space: nowrap;
}
.cap-card__summary {
  font-size: .8125rem;
  color: var(--color-text-secondary);
  margin: 0;
  line-height: 1.45;
}
.cap-card__desc {
  font-size: .8125rem;
  color: var(--color-text);
  margin: 0;
  line-height: 1.55;
  padding-top: 4px;
  border-top: 1px solid var(--color-border);
}
.cap-card__meta {
  display: flex;
  gap: 6px;
  font-size: .75rem;
  line-height: 1.4;
}
.cap-card__meta-label {
  color: var(--color-text-muted);
  font-weight: 600;
  min-width: 48px;
  flex-shrink: 0;
}
.cap-card__meta-val {
  color: var(--color-text-secondary);
  font-family: var(--font-mono, monospace);
  word-break: break-word;
}

/* ---------- Settings: Modules (templates/settings/modules.html) ---------
 *
 * Lifted from the inline <style> block in templates/settings/modules.html.
 * Used by the /settings/modules editable table (icon picker, section
 * dropdown, status badges) and the icon-picker <dialog> modal.
 */

/* ── Module table ─────────────────────────────────────────── */
.mod-tbl .mod-col-icon    { width: 120px; }
.mod-tbl .mod-col-module  { min-width: 160px; }
.mod-tbl .mod-col-section { width: 140px; }
.mod-tbl .mod-col-version { width: 80px; white-space: nowrap; }
.mod-tbl .mod-col-status  { width: 90px; }

.mod-icon-cell {
    display: flex;
    align-items: center;
    gap: 8px;
}
.mod-icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border: 1px solid var(--border-subtle);
    border-radius: 6px;
    background: var(--surface-1);
    cursor: pointer;
    color: var(--text-primary);
    transition: background 0.12s, border-color 0.12s;
}
.mod-icon-btn:hover {
    background: var(--surface-2);
    border-color: var(--border-default);
}
.mod-icon-svg {
    width: 18px;
    height: 18px;
    pointer-events: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    overflow: visible;
}
.mod-icon-name {
    font-size: 11px;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 64px;
}
.mod-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-primary);
}
.mod-id {
    font-size: 11px;
    color: var(--text-muted);
    font-family: var(--font-mono, monospace);
}
.mod-section-sel {
    font-size: 12px;
    padding: 4px 8px;
    min-width: 100px;
}
.mod-error {
    display: inline;
    margin-left: 4px;
    color: var(--color-danger, #ef4444);
    cursor: help;
    font-size: 12px;
}

/* ── Icon picker modal (uses .ds-modal shell) ─────────────── */
/* Widen the shared .ds-modal base (default ~480px) for the icon grid. */
.ds-modal--icon-picker {
    max-width: min(700px, 96vw);
    max-height: 82vh;
    display: flex;
    flex-direction: column;
}
.ds-modal--icon-picker[open] {
    display: flex;
}
.ds-modal--icon-picker .ip-header {
    gap: 12px;
    align-items: center;
    flex-shrink: 0;
}
.ip-title {
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    color: var(--text-primary);
    margin: 0;
}
.ip-search {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    padding: 5px 10px;
}
.ip-close-btn {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    font-size: inherit;
    line-height: 1;
}
.ip-close-glyph { width: 16px; height: 16px; }
.ip-count {
    font-size: 11px;
    color: var(--text-muted);
    padding: 4px 18px;
    flex-shrink: 0;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--surface-0, var(--surface-1));
    min-height: 24px;
}
.ip-grid {
    flex: 1;
    overflow-y: auto;
    padding: 12px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
    gap: 2px;
    align-content: start;
}
.ip-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 8px 4px 6px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: none;
    cursor: pointer;
    color: var(--text-primary);
    transition: background 0.1s, border-color 0.1s;
    min-width: 0;
}
.ip-item:hover {
    background: var(--surface-2);
    border-color: var(--border-subtle);
}
.ip-item.ip-selected {
    background: rgba(99,102,241,.1);
    border-color: rgba(99,102,241,.4);
}
.ip-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    color: var(--text-primary);
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.ip-label {
    font-size: 9px;
    color: var(--text-muted);
    text-align: center;
    line-height: 1.2;
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ip-hidden { display: none !important; }
.ip-loading {
    grid-column: 1 / -1;
    padding: 48px 0;
    text-align: center;
    color: var(--text-muted);
    font-size: 13px;
}

/* Settings: i18n editor (templates/settings/i18n.html) ---- */
/* ── Locale tabs ─────────────────────────────────────────────────────────── */
.i18n-locale-tabs {
    display: flex;
    gap: 4px;
    align-items: center;
}
.i18n-locale-tab {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 12px;
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
    border: 1px solid transparent;
    transition: background 0.15s, color 0.15s;
}
.i18n-locale-tab:hover {
    background: rgba(110,135,152,.08);
    color: var(--text-primary);
}
.i18n-locale-tab--active {
    background: rgba(110,135,152,.12);
    color: var(--text-primary);
    border-color: var(--border-subtle);
}
.i18n-locale-tab__code {
    font-size: 0.72rem;
    font-family: var(--font-mono, monospace);
    opacity: 0.6;
}

/* ── Filter bar ──────────────────────────────────────────────────────────── */
.i18n-filter-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 16px;
}
.i18n-missing-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.875rem;
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
}
.i18n-global-stats {
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin-left: auto;
}

/* ── Accordion ───────────────────────────────────────────────────────────── */
.i18n-accordion {
    margin-bottom: 8px;
    /* overflow:clip clips visually (border-radius) without creating a scroll
       container — sticky positioning inside the panel still works.
       overflow:hidden would break sticky by trapping it. */
    overflow: clip;
}
.i18n-section-form {
    display: contents;
}
.i18n-accordion__header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 16px 0 0;
    position: sticky;
    top: 0;
    z-index: 20;
    background: #fff;
    /* Hairline shadow to visually separate header from content below when stuck */
    box-shadow: 0 1px 0 var(--border-subtle, var(--border));
}
.i18n-accordion__trigger {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 16px;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    color: var(--text-primary);
    min-width: 0;
}
.i18n-accordion__trigger:hover {
    background: rgba(110,135,152,.04);
}
.i18n-accordion__icon {
    line-height: 1;
    width: 20px;
    height: 20px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
    flex-shrink: 0;
    transition: color 0.15s;
}
.i18n-accordion__icon-collapsed,
.i18n-accordion__icon-expanded {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
}
.i18n-accordion__icon-expanded { display: none; }
.i18n-accordion__trigger[aria-expanded="true"] .i18n-accordion__icon {
    color: var(--steel);
}
.i18n-accordion__trigger[aria-expanded="true"] .i18n-accordion__icon-collapsed { display: none; }
.i18n-accordion__trigger[aria-expanded="true"] .i18n-accordion__icon-expanded { display: inline-flex; }
.i18n-accordion__icon .lucide-svg { width: 16px; height: 16px; }
.i18n-ai-glyph { width: 14px; height: 14px; }
.i18n-translate-btn__text { margin-left: 4px; }
.i18n-accordion__label {
    font-size: 0.9rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 7px;
}
.i18n-group-badge {
    font-size: 0.67rem;
    font-weight: 500;
    background: rgba(110,135,152,.15);
    color: var(--text-secondary);
    padding: 2px 6px;
    border-radius: 3px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.i18n-accordion__stats {
    font-size: 0.8rem;
    color: var(--text-secondary);
    font-weight: 400;
    margin-left: 4px;
    white-space: nowrap;
}
.i18n-complete {
    color: var(--color-ok, #16a34a);
}
.i18n-missing-count {
    color: var(--color-warn, #d97706);
    margin-left: 6px;
}
.i18n-stats-filter {
    font-style: italic;
}
.i18n-translate-btn {
    font-size: 0.78rem;
    padding: 5px 10px;
    flex-shrink: 0;
    background: var(--ai-accent-soft);
    color: var(--ai-accent);
    border-color: var(--ai-accent-strong);
    transition: all 0.15s;
}
.i18n-translate-btn:hover:not(:disabled) {
    background: var(--ai-accent-strong);
}
.i18n-translate-btn:disabled {
    opacity: 0.55;
    cursor: default;
}
.i18n-save-btn {
    font-size: 0.8rem;
    padding: 5px 12px;
    flex-shrink: 0;
    opacity: 0.6;
    transition: opacity 0.15s;
}
.i18n-save-btn:hover {
    opacity: 1;
}
.i18n-accordion__body {
    border-top: 1px solid var(--border-subtle);
}

/* ── Table ───────────────────────────────────────────────────────────────── */
/* ds-tbl-wrap normally sets overflow:auto for horizontal scrolling, which
   creates a scroll container and breaks position:sticky on thead. Override it
   inside accordions so sticky works relative to the page scroll container. */
.i18n-accordion .ds-tbl-wrap {
    overflow: clip;
}
/* Fixed layout makes the table respect its container width: column widths
   declared on <th> are honored strictly, and long content wraps inside cells
   instead of forcing the table to grow past .ds-main. Without this, the
   KEY column's min-width + percent widths on other columns + 24px cell
   padding combine to overflow the card on standard viewports. */
.i18n-tbl {
    table-layout: fixed;
}
.i18n-tbl thead th {
    position: sticky;
    top: 49px; /* sits just below the stuck accordion header (~49px tall) */
    z-index: 10;
    background: var(--bg-subtle, #f9f8f6);
}
.i18n-tbl .i18n-key {
    font-size: 0.72rem;
    display: block;
    cursor: pointer;
    word-break: break-all;
    line-height: 1.4;
    min-width: 0; /* let flex/block context shrink but prevents collapse on cell */
}
.i18n-tbl .i18n-source {
    font-size: 0.85rem;
    color: var(--text-secondary);
    line-height: 1.4;
}
.i18n-row--hidden {
    display: none;
}
.i18n-no-matches {
    padding: 16px;
    color: var(--text-secondary);
    font-size: 0.875rem;
    font-style: italic;
}
/* ── Translation cell: preview (ellipsis) + editor (textarea) ────────────── */
.i18n-input-wrap {
    display: flex;
    align-items: center;
    gap: 4px;
}
.i18n-field {
    flex: 1;
    min-width: 0;
}
/* Preview div: looks like an input, clips long text with ellipsis */
.i18n-field__preview {
    display: block;
    width: 100%;
    box-sizing: border-box;
    font-size: 0.875rem;
    font-family: inherit;
    padding: 6px 10px;
    line-height: 1.45;
    color: var(--text-primary);
    background: var(--bg-input, #fff);
    border: 1px solid var(--border-input, var(--border));
    border-radius: 6px;
    min-height: 34px;
    cursor: text;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: border-color 0.12s;
}
.i18n-field__preview:hover,
.i18n-field__preview:focus-visible {
    outline: none;
    border-color: var(--steel, #4f7194);
}
.i18n-field__preview--empty {
    color: var(--text-secondary);
    font-style: italic;
}
.i18n-field__preview--missing {
    border-color: var(--color-warn, #d97706) !important;
}
/* Editor textarea: hidden until clicked, auto-grows to fit content */
.i18n-input {
    width: 100%;
    box-sizing: border-box;
    font-size: 0.875rem;
    display: block;
    resize: none;
    overflow: hidden;
    min-height: 34px;
    line-height: 1.45;
}
.i18n-input--missing {
    border-color: var(--color-warn, #d97706) !important;
}
.i18n-row--missing td:first-child {
    border-left: 3px solid var(--color-warn, #d97706);
}
/* ── Per-row AI button ───────────────────────────────────────────────────── */
/* Invisible until you hover the row; states (done/error/loading) keep it shown */
.i18n-row-ai-btn {
    flex-shrink: 0;
    padding: 0 5px;
    height: 28px;
    font-size: 0.75rem;
    background: none;
    border: none;
    border-radius: 4px;
    color: var(--text-secondary);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s, color 0.15s;
    line-height: 1;
}
.i18n-row:hover .i18n-row-ai-btn:not(.is-loading):not(.is-done):not(.is-error) {
    opacity: 0.35;
}
.i18n-row-ai-btn:hover:not(:disabled) {
    opacity: 1 !important;
    color: var(--ai-accent);
}
.i18n-row-ai-btn.is-loading {
    opacity: 1;
    color: var(--ai-accent);
    animation: i18n-spin 1s linear infinite;
}
.i18n-row-ai-btn.is-done {
    opacity: 0.7;
    color: var(--color-ok, #16a34a);
}
.i18n-row-ai-btn.is-error {
    opacity: 1;
    color: var(--color-err, #dc2626);
}
@keyframes i18n-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* Settings: Tools (templates/settings/tools.html) ---- */
/* ── Dependency rows ─────────────────────────────────────────────────────── */
.dep-row {
    padding: 16px 24px;
}
.dep-row--bordered {
    border-bottom: 1px solid var(--border-subtle);
}
.dep-row__hd {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 4px;
}
.dep-status {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    flex-shrink: 0;
}
.dep-status svg { width: 12px; height: 12px; stroke-width: 3; }
.dep-status--ok   { background: rgba(34,197,94,.12);  color: var(--success, #16a34a); }
.dep-status--miss { background: rgba(249,115,22,.12); color: var(--error, #c2410c); }
.dep-row__name {
    font-weight: 600;
    font-size: .9rem;
    color: var(--text-primary);
}
.dep-row__version {
    font-size: .72rem;
    color: var(--text-secondary);
    background: var(--bg-subtle, #f5f5f5);
    padding: 1px 5px;
    border-radius: 3px;
}
.dep-row__detail {
    font-size: .8rem;
    color: var(--text-secondary);
    margin-left: 2px;
}
.dep-row__detail--miss { color: #c2410c; }
.dep-row__desc {
    font-size: .8125rem;
    color: var(--text-secondary);
    margin: 0 0 10px;
    line-height: 1.5;
}
.dep-row__used {
    font-size: .75rem;
    color: var(--text-tertiary, var(--text-secondary));
    font-style: italic;
}
.dep-row__install {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 8px;
}
.dep-install-line {
    display: flex;
    align-items: center;
    gap: 10px;
}
.dep-install-line__label {
    font-size: .72rem;
    color: var(--text-secondary);
    min-width: 140px;
    flex-shrink: 0;
}
.dep-install-line__cmd {
    font-family: var(--font-mono, monospace);
    font-size: .78rem;
    color: var(--text-primary);
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 3px 10px;
    margin: 0;
    cursor: pointer;
    white-space: nowrap;
    overflow-x: auto;
    max-width: 480px;
}
.dep-install-line__cmd:hover { background: var(--bg-subtle, #f0f0f0); }

/* ── Admin script cards ──────────────────────────────────────────────────── */
.tools-card { padding: 20px 24px; }
.tools-card--bordered { border-bottom: 1px solid var(--border-subtle); }
.tools-card__hd {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 6px;
    flex-wrap: wrap;
}
.tools-card__title {
    font-weight: 600;
    font-size: .95rem;
    color: var(--text-primary);
}
.tools-card__script {
    font-size: .78rem;
    color: var(--text-secondary);
    background: var(--bg-subtle, #f5f5f5);
    padding: 2px 6px;
    border-radius: 4px;
    cursor: pointer;
}
.tools-card__desc {
    color: var(--text-secondary);
    font-size: .875rem;
    margin: 0 0 12px;
    line-height: 1.5;
}
.tools-card__cmd-wrap {
    background: var(--bg-code, #1a1a2e);
    border-radius: 6px;
    overflow: hidden;
}
.tools-card__cmd {
    font-family: var(--font-mono, monospace);
    font-size: .8rem;
    color: #c9d1d9;
    padding: 12px 16px;
    margin: 0;
    white-space: pre-wrap;
    word-break: break-all;
    cursor: pointer;
    line-height: 1.6;
}
.tools-card__cmd:hover { background: rgba(255,255,255,0.04); }

/* Memory: index (templates/memory/index.html) ---- */
.memory-search-hit {
    display: flex;
    align-items: baseline;
    gap: 8px;
    text-decoration: none;
    padding: 6px 0;
}
.memory-search-hit__kind { flex-shrink: 0; }
.memory-search-hit__title { font-weight: 600; color: var(--text-primary); }
.memory-search-hit__snippet { color: var(--text-secondary); font-size: 0.875rem; margin-left: auto; padding-left: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40%; }

/* Upload modal per-file row: filename truncates, badge anchors right with
   a max-width so error messages don't push the row out of the modal. */
.mu-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--border-subtle);
    min-width: 0;
}
.mu-row__name {
    min-width: 0;          /* allows ellipsis inside grid */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.875rem;
}
.mu-row__badge {
    flex-shrink: 0;
    max-width: 180px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: right;
}

/* Per-row upload progress — thin slot that spans both grid columns so
   the canonical ``.ds-progress`` primitive sits under the filename/badge
   pair.  The slot itself is layout-only (grid positioning + display
   gating); the visual modes (``is-percent`` / ``is-indeterminate``) are
   applied to the inner ``.ds-progress`` element by progress.js. See
   docs/PROGRESS.md + the style-guide ``#progress`` section. */
.mu-row__progress-slot {
    grid-column: 1 / -1;
    margin-top: 4px;
    display: none;
}
.mu-row.is-uploading .mu-row__progress-slot,
.mu-row.is-processing .mu-row__progress-slot {
    display: block;
}

/* Memory: document (templates/memory/document.html) ---- */
.ds-meta-field { display:flex; flex-direction:column; gap:4px; align-items:flex-start; }
.ds-meta-field__label { font-size:0.75rem; color:var(--text-secondary); line-height:1.4; }
.ds-meta-field__value { font-size:0.875rem; font-weight:500; line-height:1.4; }
/* ---------- Proposals: extraction overlay ---------- */
.extraction-overlay {
    position: fixed; inset: 0;
    background: rgba(22, 28, 33, 0.55);
    display: flex; align-items: center; justify-content: center;
    z-index: 9000;
}
.extraction-overlay__card {
    background: var(--bg-primary, #fff);
    color: var(--text-primary, #1a2128);
    border-radius: 12px;
    box-shadow: 0 24px 60px -12px rgba(0,0,0,0.25);
    width: min(420px, 90vw);
    padding: 28px 24px;
    text-align: center;
}
.extraction-overlay__title { font-size: 1.05rem; font-weight: 600; margin: 0 0 4px; }
.extraction-overlay__filename { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 18px; word-break: break-word; }
.extraction-overlay__loader { display: flex; justify-content: center; margin: 16px 0 20px; min-height: 140px; }

/* Sequential 4-step progress bar.
   Steps fire strictly in order: ocr -> classify -> annotation -> entity.
   Each step is a pill: muted "pending", indigo pulsing "extracting",
   green-with-tick "done", grey-strike "skipped", red "errored". A connector
   line between adjacent pills tints to match the LEFT pill's terminal state
   so the eye can scan completion left-to-right. */
.extraction-overlay__progress {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    align-items: start;
    gap: 0;
    margin: 8px 0 4px;
    position: relative;
}
.extraction-overlay__progress-step {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    position: relative;
    text-align: center;
    padding: 0 4px;
}
.extraction-overlay__progress-dot {
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--bg-subtle, #ece8df);
    color: var(--text-secondary);
    display: flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 600;
    transition: background-color 220ms ease, color 220ms ease, transform 220ms ease;
    z-index: 1;
}
.extraction-overlay__progress-step[data-state="extracting"] .extraction-overlay__progress-dot {
    background: var(--accent, #6366f1);
    color: #fff;
    animation: ovl-pulse 1.4s ease-in-out infinite;
    transform: scale(1.08);
}
.extraction-overlay__progress-step[data-state="extracted"] .extraction-overlay__progress-dot {
    background: #10b981; color: #fff;
}
.extraction-overlay__progress-step[data-state="errored"] .extraction-overlay__progress-dot {
    background: #ef4444; color: #fff;
}
.extraction-overlay__progress-step[data-state="skipped"] .extraction-overlay__progress-dot {
    background: var(--bg-subtle, #ece8df);
    color: var(--text-secondary);
    opacity: 0.55;
}
@keyframes ovl-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.45); }
    50%      { box-shadow: 0 0 0 6px rgba(99,102,241,0.0); }
}
.extraction-overlay__progress-label {
    font-size: 0.72rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 500;
}
.extraction-overlay__progress-step[data-state="extracting"] .extraction-overlay__progress-label,
.extraction-overlay__progress-step[data-state="extracted"]  .extraction-overlay__progress-label {
    color: var(--text-primary, #1a2128);
}
.extraction-overlay__progress-step[data-state="skipped"] .extraction-overlay__progress-label {
    text-decoration: line-through;
    opacity: 0.6;
}
/* Connector line between steps: drawn on the right side of each step except the last. */
.extraction-overlay__progress-step:not(:last-child)::after {
    content: "";
    position: absolute;
    top: 11px; /* dot center */
    left: calc(50% + 12px);
    right: calc(-50% + 12px);
    height: 2px;
    background: var(--bg-subtle, #ece8df);
    z-index: 0;
    transition: background-color 220ms ease;
}
.extraction-overlay__progress-step[data-state="extracted"]:not(:last-child)::after {
    background: #10b981;
}
.extraction-overlay__progress-step[data-state="errored"]:not(:last-child)::after {
    background: #ef4444;
}

/* Agents: detail (templates/agents/detail.html) ---- */
.agent-detail-grid{
    display:grid;
    grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    gap:24px;
    align-items:start;
}
@media (max-width: 1080px) {
    .agent-detail-grid{
        grid-template-columns:1fr;
    }
}

/* WhatsApp (templates/whatsapp.html) ---- */
.wa-status-dot { display:inline-block; width:10px; height:10px; border-radius:50%; }
.wa-status-dot--idle          { background:var(--text-secondary); }
.wa-status-dot--starting      { background:var(--warning); }
.wa-status-dot--waiting_for_qr{ background:var(--warning); animation: wa-pulse 1.5s ease-in-out infinite; }
.wa-status-dot--ready         { background:var(--success); }
.wa-status-dot--error         { background:var(--error); }
.wa-status-dot--disconnected  { background:var(--error); }
.wa-status-dot--reconnecting  { background:var(--warning); animation: wa-pulse 1.5s ease-in-out infinite; }
.wa-status-dot--logged_out    { background:var(--error); }
.wa-status-dot--stopped       { background:var(--text-secondary); }
@keyframes wa-pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }

/* Settings: Memory entities (templates/settings/memory/entities.html) ---- */
.ds-sticky-bar {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: .75rem;
    background: var(--surface-elevated, #fff);
    border: 1px solid var(--border, #e2e8f0);
    border-radius: 8px;
    padding: .625rem 1rem;
    box-shadow: 0 4px 16px rgba(0,0,0,.12);
    z-index: 200;
    white-space: nowrap;
}
.ds-sticky-bar__label {
    font-size: .875rem;
    color: var(--text-secondary);
    margin-right: .25rem;
}
.ds-sticky-bar[hidden] { display: none; }
.ent-row:has(.ent-cb:checked) { background: var(--surface-highlight, #f0f4ff); }

/* ---------- Memory: inline capture composer (templates/memory/index.html)
 *
 * Quiet by default: a single 1-row prompt ("Capture into Memory…").
 * Click → expands into the full composer (textarea + doctype select +
 * Browse + Capture). Pattern mirrors the .note-composer in PR #393 so
 * it reads as part of the same family.
 *
 * The expanded state is also entered automatically when files are
 * dropped onto the collapsed prompt or when the textarea is focused.
 */
.capture {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 0;
    margin: 0 0 16px;
    transition: background 180ms ease, border-color 180ms ease;
}

/* Collapsed state — single row, quiet */
.capture .capture__expanded { display: none; }
.capture.is-expanded .capture__collapsed { display: none; }
.capture.is-expanded .capture__expanded { display: block; }

.capture__collapsed {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    color: var(--text-muted);
    font-size: 13px;
    cursor: text;
    user-select: none;
}
.capture__collapsed svg {
    width: 16px;
    height: 16px;
    color: var(--text-secondary);
    flex-shrink: 0;
}
.capture__collapsed:hover { color: var(--text-secondary); }
.capture__collapsed:hover svg { color: var(--text-primary); }
.capture__prompt { flex: 1; }

/* Drop-target wash works in both collapsed and expanded states. */
.capture.is-drop-target {
    background: var(--accent-soft);
    border-color: rgba(185, 91, 55, 0.45);
}

/* Expanded state */
.capture__expanded { padding: 12px 14px 10px; }

.capture-zone__text {
    width: 100%;
    border: none;
    background: transparent;
    resize: vertical;
    font-size: 13px;
    line-height: 1.5;
    padding: 4px 6px;
    min-height: 64px;
    color: var(--text-primary);
    font-family: inherit;
}
.capture-zone__text:focus { outline: none; }
.capture-zone__text::placeholder { color: var(--text-muted); }

.capture-zone__bar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    padding: 0 4px;
    flex-wrap: wrap;
}

/* Doctype select sized to fit the longest option ("Auto-detect (recommended)")
 * with the chevron visible — fixes the regression where the chevron sat
 * underneath the option text. */
.capture-zone__doctype {
    font-size: 12px;
    font-weight: 500;
    line-height: 1.5;
    color: var(--text-secondary);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 4px 26px 4px 10px;
    /* Native chevron is unstyleable; ensure the text never overlaps it
     * by giving the select an explicit width that fits the default
     * "Auto-detect (recommended)" string + chevron room. */
    min-width: 240px;
    max-width: 260px;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2316202a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 12px 12px;
}
.capture-zone__doctype:focus { outline: 2px solid var(--steel); outline-offset: -1px; }

.capture-zone__hint {
    font-size: 11px;
    color: var(--text-muted);
}

/* AI routing — inline-extracted */
.ai-add-wrap { position: relative; }
.ai-add-menu {
    position: absolute; right: 0; top: calc(100% + 4px);
    background: #fff;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,.08);
    min-width: 160px;
    padding: 6px 4px;
    list-style: none;
    margin: 0;
    z-index: 100;
}
.ai-add-item {
    display: block; width: 100%;
    padding: 7px 12px;
    background: none; border: none;
    border-radius: 5px;
    font-size: .875rem; text-align: left;
    cursor: pointer; color: var(--text-primary);
}
.ai-add-item:hover { background: rgba(110,135,152,.08); }
.override-row td { vertical-align: middle; }

/* Platform admin — tenant access */
.access-tbl { table-layout: fixed; width: 100%; }
.access-tbl col.col-check    { width: 80px; }
.access-tbl th:first-child, .access-tbl td:first-child { text-align: center; }
.access-tbl col.col-maturity { width: 110px; }
.access-tbl col.col-date     { width: 110px; }
.access-tbl col.col-badge    { width: 150px; }

/* implemented by claude code, to be reviewed by claude design */
.ds-chip.ds-chip--steel { background: var(--steel); color: #fff; border-color: var(--steel); }

/* ── Cmd-K command palette ─────────────────────────────────────────────────
 * implemented by claude code, to be reviewed by claude design
 * ───────────────────────────────────────────────────────────────────────── */

/* Topline trigger button */
.app-cmdk {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 5px 10px;
    width: 260px;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 13px;
    font-family: var(--font-sans);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.app-cmdk:hover { border-color: var(--border-strong); box-shadow: var(--shadow-card); }
/* Suppress the browser default focus outline (blue ring) on the cmdk
 * trigger. After the dialog closes, focus returns to this button and
 * Chrome/Safari paint a stark blue focus ring on a tan-on-cream surface
 * — visually jarring and not part of the design language. Keyboard
 * users still get a subtle accent-coloured ring via :focus-visible. */
.app-cmdk:focus { outline: none; }
.app-cmdk:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-color: var(--accent);
}
.app-cmdk .ds-icon { width: 14px; height: 14px; flex-shrink: 0; }
.app-cmdk__label { flex: 1; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-cmdk kbd {
    margin-left: auto;
    font-family: var(--font-sans);
    font-size: 11px;
    background: var(--bg-page);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 6px;
    color: var(--text-secondary);
    flex-shrink: 0;
}
/* The cmdk trigger is desktop-only — phones don't have ⌘K, the bar would
 * eat most of the topline row, and tap-driven navigation makes it
 * redundant. Hide entirely below the mobile breakpoint. */
@media (max-width: 900px) {
    .app-cmdk { display: none; }
}

/* Palette dialog — anchored ~96px from top so it always clears the
 * topline header (matches the Linear/Notion/GitHub cmdk pattern).
 *
 * Browsers' UA stylesheet for `dialog:modal` sets
 *   position: fixed; inset: 0; margin: auto;
 * which centers the dialog. We need to override that to anchor at the
 * top instead. Using `inset` + `margin-inline: auto` is more robust than
 * fighting the UA `margin: auto` shorthand: explicit `top: 96px` and
 * `bottom: auto` win deterministically across Chrome/Safari/Firefox,
 * and it side-steps a Chrome quirk where flex children can stretch the
 * dialog to fill the auto-vertical-centered max-height instead of
 * shrinking to fit content. */
.ds-cmdk {
    position: fixed;
    top: 96px;
    bottom: auto;
    left: 0;
    right: 0;
    margin: 0 auto;
    border: none;
    border-radius: var(--radius-lg);
    padding: 0;
    box-shadow: 0 20px 60px rgba(22, 32, 42, 0.22), 0 4px 12px rgba(22, 32, 42, 0.10);
    background: var(--bg-elevated);
    width: 520px;
    max-width: calc(100vw - 32px);
    max-height: 60vh;
    overflow: hidden;
    flex-direction: column;
}
.ds-cmdk[open] {
    display: flex;
}
.ds-cmdk::backdrop {
    background: rgba(15, 20, 25, 0.30);
    backdrop-filter: blur(2px);
}

/* Header row: search icon + input + Esc hint */
.ds-cmdk__hd {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-subtle);
    flex-shrink: 0;
}
.ds-cmdk__hd .ds-icon { width: 16px; height: 16px; color: var(--text-secondary); flex-shrink: 0; }
.ds-cmdk__input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    font-family: var(--font-sans);
    font-size: 15px;
    font-weight: 500;
    color: var(--text-primary);
    line-height: 1.4;
    min-width: 0;
}
.ds-cmdk__input::placeholder { color: var(--text-muted); font-weight: 400; }
.ds-cmdk__esc {
    font-family: var(--font-sans);
    font-size: 11px;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 2px 7px;
    color: var(--text-secondary);
    flex-shrink: 0;
}

/* Scrollable results body */
.ds-cmdk__bd {
    overflow-y: auto;
    flex: 1;
    padding: 4px 0 8px;
}

/* Group heading */
.ds-cmdk__group {
    padding: 8px 16px 4px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--text-muted);
}

/* Result row */
.ds-cmdk__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 16px;
    cursor: pointer;
    border: none;
    background: transparent;
    width: 100%;
    text-align: left;
    color: var(--text-primary);
    font-family: var(--font-sans);
    font-size: .875rem;
    font-weight: 500;
    transition: background .1s ease;
    border-radius: 0;
    text-decoration: none;
}
.ds-cmdk__item:hover,
.ds-cmdk__item[aria-selected="true"] {
    background: var(--accent-soft);
    color: var(--accent);
    outline: none;
}
.ds-cmdk__item[aria-selected="true"] .ds-cmdk__item-section,
.ds-cmdk__item:hover .ds-cmdk__item-section {
    color: var(--accent);
    opacity: .7;
}
.ds-cmdk__item .ds-icon { width: 15px; height: 15px; color: var(--text-secondary); flex-shrink: 0; }
.ds-cmdk__item[aria-selected="true"] .ds-icon,
.ds-cmdk__item:hover .ds-icon { color: var(--accent); }
.ds-cmdk__item-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-cmdk__item-section {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-muted);
    flex-shrink: 0;
    transition: color .1s ease;
}

/* Empty state */
.ds-cmdk__empty {
    padding: 32px 16px;
    text-align: center;
    color: var(--text-muted);
    font-size: .875rem;
}

/* ── responsive degradation: tablet ≤768px / phone ≤480px ── */
/* These rules live at the END of the file so they override component styles
   without touching the existing layout code above. The mobile PWA lives at
   /m; these breakpoints are "don't overflow/scroll-break", not "polished
   mobile UX". The shell already switches to an overlay-drawer at ≤1280px
   (see the existing @media (max-width:1280px) block above). */

@media (max-width: 768px) {
  /* ── Page chrome ── */
  .app-page { padding: 16px 16px 32px; }
  .density-comfortable .app-page { padding: 20px 16px 48px; }

  /* ── Page header: let actions wrap below the title on narrow viewports ── */
  .app-page__header { flex-direction: column; align-items: stretch; }
  .app-page__actions { flex-wrap: wrap; gap: 8px; }
  /* Inputs inside action forms should shrink before forcing horizontal scroll */
  .app-page__actions form .ds-input { min-width: 120px; }
  .app-page__actions form select.ds-input { min-width: 100px; }

  /* ── Workflow step-picker: clamp width so it doesn't overflow ── */
  .ds-ctx.wf-step-picker { width: calc(100vw - 32px); max-width: 640px; }
  .ds-ctx.wf-step-picker:has(.wf-step-picker__col--wide) { width: calc(100vw - 32px); }

  /* ── Utility: opt-in column hiding for wide tables ──
     Add .ds-tbl--hide-md to a <table> to hide its 4th column at this width.
     Future agents can extend this pattern (nth-child 3, 5, …) as needed. */
  .ds-tbl--hide-md th:nth-child(4),
  .ds-tbl--hide-md td:nth-child(4) { display: none; }
}

@media (max-width: 480px) {
  /* ── Drawers: go full-width on phones so content is readable ── */
  .drawer,
  .drawer.drawer--sm,
  .drawer.drawer--lg,
  .drawer.drawer--wide { width: 100vw; max-width: 100vw; border-left: none; }

  /* ── Drawer definition lists: stack label above value ── */
  .drawer-dl { grid-template-columns: 1fr; gap: 2px 0; }
  .drawer-dl dt { padding-top: 8px; }
  .drawer-dl dt:first-child { padding-top: 0; }

  /* ── Kanban: columns take ~90% of viewport so they're still readable
     while the board stays horizontally scrollable ── */
  .kanban__col { flex: 0 0 90%; width: 90%; }

  /* ── Context-menu submenus: flip to right edge so they don't clip left ── */
  .ds-ctx__flyout {
    left: auto;
    right: 0;
    max-width: calc(100vw - 32px);
  }

  /* ── Modals: clamp to screen width with a small margin ── */
  .ds-modal { max-width: calc(100vw - 16px); }

  /* ── Account chip dropdown: pin to right edge ── */
  .app-account-chip__menu {
    right: 8px;
    min-width: min(220px, calc(100vw - 32px));
  }

  /* ── Notification panel: keep it inside the viewport ── */
  .ntf-panel { max-width: calc(100vw - 16px); right: 8px; }
}
/* ── end responsive degradation ── */

/* ====================================================================
   Progress primitive (.ds-progress)  —  see docs/PROGRESS.md
   --------------------------------------------------------------------
   Canonical visual for long-running operations. Three numeric modes
   driven by class:
     .is-step          — deterministic countable progress (k/N)
     .is-percent       — deterministic 0–100% fill
     .is-indeterminate — animated pulse for "no knowable total"
   Plus terminal:
     .is-done          — full bar, success color
     .is-error         — full bar, error color
   And a brief animation:
     .is-milestone-flash — 1.5 s overlay flash on phase change
   ==================================================================== */

.ds-progress {
    --ds-progress-track:      rgba(0,0,0,.08);
    --ds-progress-fill-color: var(--warning);
    --ds-progress-done-color: var(--success);
    --ds-progress-err-color:  var(--error);
    display: block;
    width: 100%;
    min-width: 0;
}
.ds-progress__label {
    font-size: .813rem;
    color: var(--text-secondary);
    line-height: 1.4;
    margin-bottom: 4px;
    min-height: 1.2em;          /* reserve vertical space so absence of label doesn't jump */
}
.ds-progress__bar {
    position: relative;
    height: 4px;
    background: var(--ds-progress-track);
    border-radius: 2px;
    overflow: hidden;
}
.ds-progress__fill {
    height: 100%;
    background: var(--ds-progress-fill-color);
    width: 0;
    transition: width .15s linear;
    border-radius: 2px;
}
.ds-progress.is-indeterminate .ds-progress__fill { display: none; }
.ds-progress.is-indeterminate .ds-progress__bar::after {
    content: "";
    position: absolute;
    inset: 0;
    width: 35%;
    background: linear-gradient(90deg,
        transparent 0%, var(--ds-progress-fill-color) 50%, transparent 100%);
    animation: ds-progress-pulse 1.4s linear infinite;
}
@keyframes ds-progress-pulse {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(285%); }
}
.ds-progress.is-done .ds-progress__fill {
    background: var(--ds-progress-done-color);
    width: 100%;
}
.ds-progress.is-error .ds-progress__fill {
    background: var(--ds-progress-err-color);
    width: 100%;
}
.ds-progress.is-error .ds-progress__label,
.ds-progress.is-done  .ds-progress__label {
    color: var(--text-primary);
}
/* Milestone flash — a subtle scale on the bar so the eye notices the
   phase change.  Doesn't affect fill / mode. */
.ds-progress.is-milestone-flash .ds-progress__bar {
    box-shadow: 0 0 0 2px var(--ds-progress-fill-color);
    transition: box-shadow .25s ease;
}
.ds-progress.is-milestone-flash .ds-progress__label {
    font-weight: 600;
    color: var(--text-primary);
}

/* Tight inline variant for use inside table rows / modal lists, where
   we want the bar to sit under name+badge without a separate label
   row.  Caller-controlled; the primitive itself is layout-neutral. */
.ds-progress--inline {
    margin-top: 4px;
}
.ds-progress--inline .ds-progress__label { display: none; }
.ds-progress--inline .ds-progress__bar { height: 2px; }
/* ── end progress primitive ── */

/* ====================================================================
   ChecklistPanel (CHCK-1)  —  see docs/PRIMITIVES.md § ChecklistPanel
   --------------------------------------------------------------------
   Record-agnostic FILL-IN surface. Pure composition over .rrcard +
   .ds-tbl + .ds-progress + chips/badges; the classes below are layout
   glue only (no new colours). The macros live in
   core/web/templates/_partials/checklist_panel.html.
   ==================================================================== */
.checklist-panel { display: flex; flex-direction: column; gap: 14px; }
.checklist-panel__empty { padding: 4px 2px; font-size: .813rem; }

/* group-card header: title (left) + meter/counts cluster (right) */
.checklist-group__head-right {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.checklist-group__gate { display: inline-flex; align-items: center; gap: 4px; }
.checklist-group__gate svg { width: 12px; height: 12px; }
.checklist-group__meter { display: inline-flex; align-items: center; gap: 8px; }
.checklist-group__meter .ds-progress { width: 84px; }
.checklist-group__meter-text {
    font-size: .75rem;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* check rows */
.checklist-row__note { margin-top: 2px; font-size: 11px; color: var(--text-muted); }
.checklist-tbl__col-actions { text-align: right; }
.checklist-row__actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    flex-wrap: wrap;          /* inline when there's room; wrap (never clip) when cramped */
}
.checklist-row__photo-form { display: inline-flex; }
.checklist-row__photo-label {
    margin: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.checklist-row__photo-input { display: none; }
.checklist-row__reopen-form { display: inline; }

/* right-rail glance card */
.checklist-rail__meter { margin-bottom: 8px; }
.checklist-rail__counts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* Tablet/phone: drop the low-priority columns so the label + action
   button are always reachable for the "tap → take photo → done" flow. */
@media (max-width: 768px) {
    .checklist-tbl th.checklist-tbl__col-kind,
    .checklist-tbl td.checklist-tbl__col-kind,
    .checklist-tbl th.checklist-tbl__col-due,
    .checklist-tbl td.checklist-tbl__col-due,
    .checklist-tbl th.checklist-tbl__col-resp,
    .checklist-tbl td.checklist-tbl__col-resp { display: none; }
}

/* archived (parked) checklists — collapsed disclosure at the panel foot */
.checklist-archived { margin-top: 4px; }
.checklist-archived__summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    font-size: .813rem;
    color: var(--text-secondary);
    padding: 4px 2px;
    user-select: none;
}
.checklist-archived__summary svg { width: 13px; height: 13px; }
.checklist-archived__body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 8px;
}
.checklist-archived__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    background: var(--bg-subtle);
}
.checklist-archived__meta { min-width: 0; }
.checklist-archived__title { font-size: .875rem; font-weight: 500; }
.checklist-archived__ctx { font-size: .75rem; }
.checklist-archived__reason { font-size: 11px; margin-top: 2px; }
.checklist-archived__form { flex-shrink: 0; }
/* ── end ChecklistPanel ── */

/* ── Memory Ask · Slice 4 — home Ask hero ──────────────────────────────
   The "so what" front door on /memory. The hero is the only loud element;
   the capture composer and the Browse grid below are the back room. */
.ask-hero { margin: 4px 0 28px; }
.ask-hero__form { display: flex; gap: 10px; align-items: stretch; }
.ask-hero__input { flex: 1 1 auto; font-size: 1.15rem; padding: 15px 18px; }
.ask-hero__submit { flex: 0 0 auto; }
.ask-hero__results { margin-top: 16px; }
.ask-hero__loader { display: flex; justify-content: center; padding: 20px 0; }
.ask-hero__chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.ask-hero__chips--suggest { opacity: .85; }
.ask-decline {
    background: var(--surface-2);
    border: 1px solid var(--border-subtle);
    border-radius: 10px;
    padding: 14px 18px;
}
.ask-decline__msg { margin: 0; color: var(--text-secondary); }
.ask-decline__label { margin: 12px 0 6px; font-size: .8rem; color: var(--text-muted); }
/* Browse = the back room, collapsed behind a disclosure (front door = ask). */
.ask-browse { margin-top: 26px; }
.ask-browse__summary {
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-secondary);
    padding: 6px 0;
}
.ask-browse__summary::-webkit-details-marker { display: none; }
.ask-browse__summary::after {
    content: "\203A"; /* › */
    font-size: 1.15rem;
    line-height: 1;
    transition: transform .15s ease;
}
.ask-browse[open] .ask-browse__summary::after { transform: rotate(90deg); }
.ask-browse > *:not(summary) { margin-top: 14px; }
.ask-browse__tools { display: flex; flex-wrap: wrap; gap: 6px; }
/* Capture = a quiet push affordance (dashed drop), not a card vs. the hero. */
.ask-capture .panel.capture {
    border: 1px dashed var(--border-subtle);
    background: transparent;
    box-shadow: none;
}
