:root {
    --bg: #0c0d12;
    --surface: #15171f;
    --surface-2: #1e212b;
    --border: #262a35;
    --text: #f1f3f9;
    --text-soft: #c7ccd9;
    --muted: #7a8194;
    --accent: #5b9dff;
    --accent-strong: #387bff;
    --danger: #ef5b5b;
    --success: #4ade80;

    /* v0.29.x jewel-accent palette — each major app area gets its own
       semantic color, applied per-tile via .tile.t-<area>. Keeps the
       dark base but unlocks scan-by-color on the dashboard. Adopted
       from mockup 2B; see mockups/2b-brand-split.html for the
       reference design. */
    --c-agency:    #c084fc;  /* violet  */
    --c-agents:    #38bdf8;  /* sky     */
    --c-projects:  #34d399;  /* emerald */
    --c-watchers:  #fbbf24;  /* amber   */
    --c-personas:  #f472b6;  /* rose    */
    --c-skills:    #a78bfa;  /* purple  */
    --c-runs:      #2dd4bf;  /* teal    */
    --c-workflows: #fb923c;  /* orange  */
    --c-chats:     #fde047;  /* yellow  */
    --c-usage:     #4ade80;  /* green   */
    --c-approvals: #f87171;  /* red     */
    --c-settings:  #94a3b8;  /* slate   */

    /* Shared pulse cadence — live-badge, status-running chip, and any
       future "this is alive" affordance (bell-on-unread, watcher-firing,
       etc.) sit on the same 2s ease-in-out tempo so they don't compete.
       Phase-2 work hooks the bell-count pulse onto this same token. */
    --motion-pulse-duration: 2s;

    /* ── Constellation reskin tokens (Phase 1.0) ───────────────────
       Additive groundwork for the sidebar/header chrome rewrite.
       Nothing in the current layout reads these yet — they're here
       so static/css/themes/*.css overrides have something to bind
       against, and so Phase 1.1's chrome can land as a chrome-only
       change. The constellation values match the design exploration
       at mockups/constellation-themes.html. Other themes (nebula,
       ocean, glacier, sunset, ember, forest, twilight, volcanic,
       stealth, midnight, eclipse) override these via the matching
       static/css/themes/<slug>.css block scoped to
       [data-theme="<slug>"]. <html data-theme="constellation"> is
       hardcoded on layout.html until Phase 2 wires per-user
       selection from the User row. */

    /* Aurora canvas — sidebar + header float above this. */
    --bg-0: #06060c;
    --bg-1: #0a0c18;

    /* Glass-surface treatment for sidebar + header + cards. */
    --glass:        rgba(20, 22, 38, 0.42);
    --glass-2:      rgba(28, 32, 52, 0.55);
    --border-strong:rgba(255, 255, 255, 0.14);

    /* Accent system. --accent / --accent-strong already exist above;
       --accent-2 / --accent-3 extend to the violet + rose stops that
       feed gradient buttons, brand-mark conic gradients, and the
       glass-edge glow. */
    --accent-2: #a78bfa;
    --accent-3: #f472b6;

    /* Aurora backdrop — three radial gradient stops the page
       paints behind the chrome. Each theme picks its own three. */
    --aurora-1: rgba(122, 255, 240, 0.18);
    --aurora-2: rgba(167, 139, 250, 0.20);
    --aurora-3: rgba(244, 114, 182, 0.15);

    /* Status. --ok mirrors --success for the new chrome's naming
       convention; --warn is new (amber, for queued/pending). */
    --ok:   #4ade80;
    --warn: #fbbf24;

    /* Disco wasn't in the original jewel set (Disco shipped later);
       adding it here so the per-area --c-* family is complete and
       theme files don't have to redeclare a non-existent default. */
    --c-disco: #f87171;

    /* Typography contract. v0.103.63 dropped Google Fonts entirely,
       so all defaults are system-resident stacks now. --font-ui is
       a sans default (the original JetBrains Mono default belonged
       only to the stealth theme and is set there explicitly).
       --font-display falls through to --font-ui for themes without
       a separate display face. --font-mono stays mono for digits,
       IDs, and the version chip. */
    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: var(--font-ui);
    --font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
}

/* Subtle radial-gradient backdrop — adds depth to the dark base
   without sacrificing contrast. Attached so it doesn't scroll. */
body {
    background-image:
        radial-gradient(circle at 15% 0%, rgba(192,132,252,0.06), transparent 50%),
        radial-gradient(circle at 85% 100%, rgba(56,189,248,0.05), transparent 50%);
    background-attachment: fixed;
}

* { box-sizing: border-box; }

/* v0.76.3 — global :focus-visible outline. Pre-fix the operator
 * UI had ZERO global focus indicator — only .grid-2 input/select
 * had a positive outline rule (line 588) and a couple of places
 * stripped `outline:none` without replacement. Keyboard-only
 * operators (tab through nav, tiles, table rows, chat bubbles,
 * @-mention popovers) had no visual cue for focus. ux-reviewer
 * HIGH a11y finding. The :focus-visible pseudo-class only fires
 * on keyboard-shaped focus, so mouse clicks don't trigger the
 * outline (matches modern browser default behavior).
 */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

html, body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    /* Phase 2 — body font goes through the --font-ui token so per-
       theme overrides actually flip the typeface. Default in :root
       is the JetBrains Mono stack so themes that don't override
       --font-ui (e.g. stealth) keep the historical look. */
    font-family: var(--font-ui);
    /* The mono default's tracking is generous; tighten a hair so
       paragraph copy doesn't feel airy. Sans themes override this
       to 0 in their theme file if needed; the slightly-negative
       default reads fine in either case. */
    letter-spacing: -0.005em;
    line-height: 1.55;
}

/* Page-heading typography goes through --font-display so themes
   with a separate display face (forest / twilight / eclipse use a
   system serif; volcanic uses Impact / Arial Narrow) land their
   distinct face on h1/h2 while the body stays sans. Other themes
   set --font-display equal to --font-ui, so no visible change. */
h1, h2 { font-family: var(--font-display); }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-strong); text-decoration: underline; }

main {
    /* Widened from 980px → 1440px (≈+50%) so the data tables breathe.
       Long descriptions in the agents / personas / runs lists no longer
       wrap as eagerly, and the chip-heavy persona column has room for
       three chips on one line. Footer matches so the bottom rule lines
       up with main's right edge. */
    max-width: 1440px;
    margin: 2rem auto;
    padding: 0 1.5rem;
}

footer {
    margin: 4rem auto 2rem;
    padding: 0 1.5rem;
    color: var(--muted);
    text-align: center;
    max-width: 1440px;
}

/* Constellation reskin (Phase 1.1) replaced the old .top-nav
 * chrome with .side + .header. ~200 lines of dead rules
 * (.top-nav, .top-nav .brand, .top-nav .version-chip, .brand-right,
 * .brand-right .asr [+ shimmer keyframes], .user-info, etc.)
 * were removed here in the dead-CSS sweep.
 *
 * What stays:
 *   - @keyframes versionChipCopied — the "click-to-copy" green
 *     flash on the version chip is still used by the new
 *     .header .version-chip.version-chip-copied rule.
 *   - .footer-version family — the footer still renders the
 *     version + git sha + build time inline.
 *   - .inline-form / .link-btn — heavily used by dozens of
 *     templates, not chrome-specific.
 * What went:
 *   - .top-nav and all sub-rules (chrome no longer renders it).
 *   - .brand-right + .asr-shimmer keyframes (shimmer now lives
 *     on .side__by-asr via .asr-shimmer-side keyframes).
 *   - .user-info (the right-cluster div is gone).
 */

/* Brief "copied!" pulse — green tint for ~1.5s after a successful
   click-to-copy on the version chip. Used by the new chrome's
   .header .version-chip.version-chip-copied rule. */
@keyframes versionChipCopied {
    0%   { background: rgba(74, 222, 128, 0.45); border-color: rgba(74, 222, 128, 0.8); }
    100% { background: rgba(91, 157, 255, 0.08); border-color: rgba(91, 157, 255, 0.22); }
}

/* Footer version line — split treatment so the SHA reads as a
   hash, not as more semver digits. Inline so it sits naturally
   in "The Agency by ASR Labs · v0.X.Y+sha · Built ..." */
.footer-version {
    font-variant-numeric: tabular-nums;
    user-select: all;
}
.footer-version-sep {
    color: var(--muted);
    margin: 0 0.05rem;
}
.footer-version-sha {
    color: var(--text-soft);
    opacity: 0.85;
}
.inline-form { display: inline; }
.link-btn {
    background: none;
    border: none;
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    padding: 0;
}
.link-btn:hover { color: var(--accent-strong); text-decoration: underline; }

/* Login.
   Layout shifted from dead-center to "upper-third" via flex-start +
   a viewport-scaled top pad so the card lands near the golden-ratio
   line on a typical desktop without falling off-screen on a short
   laptop viewport. clamp() floors at 3rem (always breathing room
   on top), scales with 12vh, ceilings at 9rem (doesn't drift too
   low on a 4K monitor).

   Card itself is slightly bigger (420 vs 360), more generous
   padding, rounder corners, and a soft drop-shadow + accent glow
   so it feels lifted off the dark backdrop. */
.login-page {
    min-height: 100vh;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: clamp(3rem, 12vh, 9rem) 1.25rem 2rem;
    background: var(--bg);
}
.login-card {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 2.25rem 2.5rem 2.5rem;
    border-radius: 14px;
    width: 100%;
    max-width: 420px;
    /* Twin shadow: a soft accent halo (low-opacity blue) +
       a deeper neutral drop-shadow underneath. The halo picks up
       the brand accent without ever being saturated enough to
       compete with the Sign-in button. */
    box-shadow:
        0 24px 60px -22px rgba(91, 157, 255, 0.22),
        0 6px 24px rgba(0, 0, 0, 0.45);
}
.login-card h1 { margin: 0 0 0.25rem; font-size: 1.55rem; letter-spacing: 0.01em; }
.login-card .subtitle {
    color: var(--text-soft);
    margin: 0 0 1.5rem;
    font-size: 1rem;
    letter-spacing: 0.01em;
}
/* Hero brand mark above the sign-in form. v0.280.0 — went from a
   PNG (.login-hero on an <img>) to a live text block (.login-hero
   wraps two children: the brand wordmark + the "by ASR Labs" line).
   Same reasoning as the sidebar swap in PR #291: themes that
   override --font-display (forest, twilight, eclipse → Georgia;
   volcanic → Impact-condensed) finally reach the hero. Crisp at
   any DPR, theme-coherent, no asset fetch.

   The login page is a quieter surface than the authenticated
   chrome — no theme picker, no shimmer. The "by ASR Labs" subtitle
   sits muted; the brand wordmark carries the weight. */
.login-hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    margin: 0 auto 0.9rem;
    text-align: center;
}
/* .login-card .login-hero__brand wins specificity over the older
   .login-card h1 rule above (font-size: 1.55rem) — both have a
   .login-card ancestor and we want the brand mark to be the
   visually-dominant element on the page, not a 1.55rem heading. */
.login-card .login-hero__brand {
    font-family: var(--font-display);
    font-size: clamp(2.2rem, 7vw, 2.8rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
    color: var(--text);
    margin: 0;
}
.login-hero__by {
    font-family: var(--font-ui);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0;
}
.login-card label {
    display: block;
    margin: 0.9rem 0;
    font-size: 0.9rem;
    color: var(--text-soft);
    letter-spacing: 0.01em;
}
.login-card input {
    display: block;
    width: 100%;
    margin-top: 0.35rem;
    padding: 0.7rem 0.85rem;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 0.95rem;
    /* Inherit the body font so the input text is in JetBrains Mono too
       — without this, browsers default to system-ui inside form fields
       and the visual rhythm breaks. */
    font-family: inherit;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.login-card input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(91, 157, 255, 0.18);
}
.login-card button {
    width: 100%;
    margin-top: 1.25rem;
    padding: 0.8rem;
    background: linear-gradient(135deg, var(--accent-strong), var(--accent));
    color: white;
    border: 0;
    border-radius: 6px;
    font-weight: 600;
    font-size: 1rem;
    letter-spacing: 0.02em;
    font-family: inherit;
    cursor: pointer;
    transition: transform 0.08s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.login-card button:hover {
    filter: brightness(1.08);
    box-shadow: 0 6px 18px -6px rgba(91, 157, 255, 0.55);
}
.login-card button:active { transform: translateY(1px); }

.alert {
    padding: 0.6rem 0.75rem;
    border-radius: 4px;
    margin-bottom: 0.75rem;
    font-size: 0.9rem;
}
.alert.error { background: rgba(239, 91, 91, 0.15); color: var(--danger); border: 1px solid rgba(239, 91, 91, 0.3); }
.alert.info  { background: rgba(91, 157, 255, 0.12); color: var(--accent); border: 1px solid rgba(91, 157, 255, 0.3); }
.alert.success { background: rgba(74, 222, 128, 0.12); color: var(--success); border: 1px solid rgba(74, 222, 128, 0.3); }
/* v0.82.x (integrity slice, PR 2) — neutral-tint note variant used by
   the integrity panel's TAMPERED inline explanation. The chip beside
   it carries the red severity signal; the note carries the prose —
   one shouter is enough. */
.alert.note  { background: rgba(148, 163, 184, 0.12); color: var(--muted, #94a3b8); border: 1px solid rgba(148, 163, 184, 0.25); }

/* v0.405.1 — anchor affordance inside alert banners. Global a {}
   strips underline; inside warning/error tints the link otherwise
   reads as bold colored text indistinguishable from emphasis. Add
   dotted underline so operators see they're clickable. v0.406.0:
   :visited fallback to inherit so visited links don't render as the
   UA-default purple inside the warning tint (looks broken). */
.alert a, .alert a:visited {
    text-decoration: underline dotted;
    text-underline-offset: 0.15em;
    color: inherit;
}
.alert a:hover { text-decoration: underline solid; }

/* v0.405.1 → v0.412.0 — :target outline on settings cards so the
   operator who clicks a banner anchor sees which card the page
   scrolled to. v0.406.0 used --warn (amber); v0.412.0 arc-wrap audit
   caught a color collision (the cross-vendor banner is ALSO amber,
   so operators read the matched colors as one continuous "warning
   zone" — diluting both signals). Back to --accent (blue): the eye
   distinguishes "you are here / scroll landmark" (focus-ring
   semantic) from "this is bad" (banner warning semantic). Mirrors
   the .settled-row:target precedent on the solana-hub. */
.card:target {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: 8px;
}

/* v0.100.8 (PR4 — anniversaries → interactive celebration) — soft
   jewel-tone pill that renders on the agent detail page when today
   is the agent's anniversary. Subtle gradient + pulse, both
   suppressed under prefers-reduced-motion.
   v0.100.9 audit (ux HIGH #2) — flex-wrap: wrap + max-width: 100%
   so the pill collapses gracefully on narrow viewports (~360px)
   instead of overflowing the viewport. */
.alert.anniversary-pill {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    max-width: 100%;
    padding: 0.7rem 1.1rem;
    border-radius: 14px;
    background: linear-gradient(90deg,
        rgba(91, 157, 255, 0.12),
        rgba(192, 132, 252, 0.12));
    border: 1px solid rgba(192, 132, 252, 0.32);
    color: var(--text);
    margin-bottom: 1rem;
    animation: anniversary-pulse 4.5s ease-in-out infinite;
}
.alert.anniversary-pill strong { font-weight: 600; }
.alert.anniversary-pill .btn.small { font-size: 0.85rem; padding: 0.3rem 0.8rem; }
@keyframes anniversary-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(192, 132, 252, 0.0); }
    50%  { box-shadow: 0 0 0 6px rgba(192, 132, 252, 0.12); }
    100% { box-shadow: 0 0 0 0 rgba(192, 132, 252, 0.0); }
}
@media (prefers-reduced-motion: reduce) {
    .alert.anniversary-pill { animation: none; }
}

.tile.disabled { opacity: 0.45; pointer-events: none; }

/* Site picker — visually unified with the admin /admin/sites list
   via the shared .site-card-grid / .site-card components (further
   down in this file). The old picker-specific layout was a vertical
   .site-list of plain bordered <li>s; it was replaced when the picker
   template adopted the card grid. No picker-specific CSS lives here
   anymore — the shared rules cover everything. */

/* Dashboard */
.dashboard header h1 { margin-bottom: 0.25rem; }
.dashboard-section-heading {
    margin: 1.75rem 0 0.5rem;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.dashboard-section-heading:first-of-type {
    margin-top: 0;
}
.dashboard-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
    margin: 1.5rem 0 2rem;
}
.dashboard-section-heading + .dashboard-tiles {
    margin-top: 0;
}

/* v0.92 — Research section. Visually demarcated from core sections
   so the experimental / opt-in features read as a distinct group
   without the inline `.research-only` chips that used to litter
   each tile heading. */
.dashboard-section-heading.research-heading {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px dashed var(--border);
}
.research-subhead {
    margin: 0 0 1rem;
    max-width: 56rem;
}
.dashboard-tiles.dashboard-tiles-research .tile {
    background: rgba(148, 163, 184, 0.04);
    border-style: dashed;
}
.dashboard-tiles.dashboard-tiles-research .tile:hover {
    background: rgba(91, 157, 255, 0.06);
    border-style: solid;
}

/* v0.100.x — dashboard strip (broadcast + pursuit-of-the-week
   cards on site-dashboard.html). Pre-fix the markup carried 12+
   inline style="..." declarations duplicating values that already
   live as design tokens; consolidated into these classes. The
   broadcast variant gets a green accent, pursuit pink — the colors
   pre-existed inline. */
.dashboard-strip {
    margin-bottom: 1rem;
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
}
/* v0.230.0 — strip cards (Mochi + Recent activity + broadcast +
   pursuit) sit between the dashboard-stats row above and the
   dashboard-tiles row below. Both of those use the glass treatment
   (var(--glass) + 14px radius + backdrop blur). The strip cards
   were inheriting .card's solid var(--surface) + 6px radius, which
   made them read as visually distinct boxes against the aurora bg
   while their neighbors blended. Overriding here brings them in
   line with the surrounding cards so the dashboard reads as one
   continuous surface. */
.dashboard-strip-card {
    flex: 1 1 280px;
    text-decoration: none;
    color: inherit;
    background: var(--glass);
    border-radius: 14px;
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    transition: border-color 150ms ease, transform 150ms ease, box-shadow 150ms ease;
}
/* Override the global a:hover { underline + accent-strong } so the
   strip-card body text doesn't flip blue+underlined on hover. The
   card itself still signals interactivity via the lift + shadow,
   matching the .tile hover rhythm from the tiles below. */
.dashboard-strip-card:hover {
    text-decoration: none;
    color: inherit;
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
}
.dashboard-strip-card-broadcast {
    border-left: 3px solid #4ade80;
}
.dashboard-strip-card-pursuit {
    border-left: 3px solid #f472b6;
}
/* v0.103.66 — Office Cat card. Warm amber accent so it reads as
   ambient flavour, distinct from the louder broadcast green +
   pursuit pink. The "last seen" sub-label sits next to the cat's
   name; muted so the name stays the focal point. */
.dashboard-strip-card-cat {
    border-left: 3px solid #fbbf24;
}
/* v0.103.70 — recent-site-activity card. Cool indigo accent so the
   ambient-observers row reads as a pair: warm amber (Mochi, the
   cat's eye) on one side, cool indigo (the agency's pulse) on the
   other. Distinct from broadcast green / pursuit pink. */
.dashboard-strip-card-activity {
    border-left: 3px solid #818cf8;
}
.dashboard-strip-cat-label {
    font-size: 0.7rem;
    font-weight: 400;
    margin-left: 0.25rem;
}
.dashboard-strip-heading {
    margin: 0 0 0.4rem 0;
    font-size: 0.9rem;
    color: var(--muted);
}
.dashboard-strip-live-chip {
    background: rgba(74, 222, 128, 0.15);
    color: #4ade80;
    border-color: rgba(74, 222, 128, 0.4);
    font-size: 0.65rem;
}
.dashboard-strip-body {
    margin: 0;
    font-style: italic;
    line-height: 1.4;
}
.dashboard-strip-pursuit-line {
    margin: 0;
    line-height: 1.4;
}
.dashboard-strip-foot {
    margin: 0.4rem 0 0 0;
}

/* v0.92 — insights sub-nav (shared across the 4 reader pages). */
.insights-nav {
    display: flex;
    gap: 0.25rem;
    margin: 0.5rem 0 1rem;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.insights-nav a {
    padding: 0.4rem 0.85rem;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    color: var(--muted);
    text-decoration: none;
    margin-bottom: -1px;
}
.insights-nav a:hover {
    color: var(--text);
    background: var(--surface-2);
}
.insights-nav a.active {
    color: var(--text);
    background: var(--surface);
    border-color: var(--border);
    border-bottom: 2px solid var(--accent, #5b9dff);
    font-weight: 600;
}

/* v0.92 — dream + portrait feed rows. Both follow the same shape:
   left rail = metadata (agent + date), right column = body with
   summary + optional expand for full text. */
.dream-feed, .portrait-feed {
    list-style: none;
    padding: 0;
    margin: 0;
}
.dream-row, .portrait-row {
    display: grid;
    grid-template-columns: 12rem 1fr;
    gap: 1rem;
    padding: 0.85rem 0;
    border-bottom: 1px dashed var(--border);
}
.dream-row:last-child, .portrait-row:last-child {
    border-bottom: none;
}
.dream-meta, .portrait-meta {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.dream-summary, .portrait-summary {
    margin: 0 0 0.4rem;
}
@media (max-width: 720px) {
    .dream-row, .portrait-row {
        grid-template-columns: 1fr;
    }
}

/* v0.92 — "Right now" dashboard section widgets. Two-column rows
   below the mood-weather strip; each column is a card with a small
   list. Collapses to single column on narrow viewports. */
.mood-weather-card {
    margin-top: 0.75rem;
}
.mood-weather-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.mood-weather-strip .mood-cell {
    gap: 0.35rem;
}
.mood-weather-strip .mood-cell strong {
    font-weight: 600;
}

/* v0.99.1 — clickable mood chips that expand to show the agents
   currently in that mood. <details>/<summary> gives us the toggle
   without any JS; the summary is styled as an inline chip and the
   agent list flows below when expanded. */
.mood-weather-strip .mood-chip-details {
    display: inline-block;
}
.mood-weather-strip .mood-chip-details > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    user-select: none;
}
/* Hide the default ▶ marker in WebKit + Blink. */
.mood-weather-strip .mood-chip-details > summary::-webkit-details-marker {
    display: none;
}
.mood-weather-strip .mood-chip-details > summary strong {
    font-weight: 600;
}
.mood-weather-strip .mood-chip-details[open] > summary::after {
    content: " ▾";
    color: inherit;
    opacity: 0.8;
}
.mood-weather-strip .mood-chip-details:not([open]) > summary::after {
    content: " ▸";
    color: inherit;
    opacity: 0.6;
}
.mood-weather-strip .mood-chip-details[open] {
    /* Take the full row when expanded so the agent list wraps cleanly
       under the mood chip instead of cramming next to the next mood. */
    display: block;
    width: 100%;
}
.mood-agent-list {
    margin-top: 0.4rem;
    margin-left: 0.6rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

/* v0.99.1 — agent chips inside the mood-weather expanders (and
   reusable elsewhere). Eight-color rotation keyed by the index of
   the agent within its grouping; subtle backgrounds so a long list
   still reads cleanly. */
.chip.agent-chip {
    text-decoration: none;
    transition: filter 0.12s ease;
}
.chip.agent-chip:hover {
    filter: brightness(1.15);
}
.chip.agent-chip.agent-color-0 { background: rgba(91,157,255,0.18);  border-color: #5b9dff; }
.chip.agent-chip.agent-color-1 { background: rgba(74,222,128,0.18);  border-color: #4ade80; }
.chip.agent-chip.agent-color-2 { background: rgba(251,146,60,0.20);  border-color: #fb923c; }
.chip.agent-chip.agent-color-3 { background: rgba(167,139,250,0.20); border-color: #a78bfa; }
.chip.agent-chip.agent-color-4 { background: rgba(34,211,238,0.18);  border-color: #22d3ee; }
.chip.agent-chip.agent-color-5 { background: rgba(244,114,182,0.20); border-color: #f472b6; }
.chip.agent-chip.agent-color-6 { background: rgba(250,204,21,0.20);  border-color: #facc15; }
.chip.agent-chip.agent-color-7 { background: rgba(239,91,91,0.20);   border-color: #ef5b5b; }
.right-now-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    margin-top: 0.75rem;
}
.right-now-row > .card {
    margin-bottom: 0;
}
.right-now-list {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0;
}
.right-now-list li {
    padding: 0.35rem 0;
    border-bottom: 1px dashed var(--border);
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    align-items: baseline;
}
.right-now-list li:last-child {
    border-bottom: none;
}
@media (max-width: 720px) {
    .right-now-row {
        grid-template-columns: 1fr;
    }
}

/* v0.101.0 (BEACONS) — intergalactic board cards. Stacked list of
   beacons, each a small slightly-tilted card. The glyph is the
   source world's "letterhead" — operator-picked emoji or short
   string. Era stamp + author + body + tag. Echoes get a thin
   border accent so the operator sees at-a-glance "this was
   amplified, not authored." */
.beacon-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}
.beacon-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 0.85rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    transition: border-color 0.15s ease;
}
.beacon-card:hover { border-color: var(--accent, #5b9dff); }
.beacon-card-echo {
    border-left: 3px solid rgba(192, 132, 252, 0.55);  /* jewel-tone accent */
}
.beacon-card-header {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.beacon-glyph {
    font-size: 1.35rem;
    line-height: 1;
}
.beacon-source-world strong { color: var(--text); }
.beacon-stamp { margin-left: auto; font-variant-numeric: tabular-nums; }
.beacon-author { color: var(--text-soft, #d1d5db); font-size: 0.92rem; }
.beacon-text {
    margin: 0;
    font-size: 1.02rem;
    line-height: 1.45;
    color: var(--text);
}
.beacon-card-footer {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-top: 0.25rem;
}
.beacon-tag {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.78rem;
    background: rgba(91, 157, 255, 0.10);
    border: 1px solid rgba(91, 157, 255, 0.25);
    color: var(--accent);
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
}
/* v0.101.3 ux patch — inline style attributes moved out of the
   intergalactic-board template per the ux-reviewer's "let CSS
   handle CSS" note. */
.beacon-page-note { font-size: 0.8rem; }
.beacon-empty-list { margin-left: 1.25rem; }
.beacon-redact-reason { margin-right: 0.5rem; }

/* ============================================================
   v0.101.x — Sunday fun round 1 — visual delight
   ============================================================ */

/* Item 1 — Dream-journal drift entrance. Pure CSS staggered
   fade-up on first render. Respects prefers-reduced-motion. */
@keyframes dreamDrift {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}
.dream-feed .dream-row {
    animation: dreamDrift 380ms ease-out both;
}
.dream-feed .dream-row:nth-child(1)  { animation-delay: 0ms; }
.dream-feed .dream-row:nth-child(2)  { animation-delay: 80ms; }
.dream-feed .dream-row:nth-child(3)  { animation-delay: 160ms; }
.dream-feed .dream-row:nth-child(4)  { animation-delay: 240ms; }
.dream-feed .dream-row:nth-child(5)  { animation-delay: 320ms; }
.dream-feed .dream-row:nth-child(6)  { animation-delay: 400ms; }
.dream-feed .dream-row:nth-child(7)  { animation-delay: 480ms; }
.dream-feed .dream-row:nth-child(8)  { animation-delay: 560ms; }
.dream-feed .dream-row:nth-child(n+9) { animation-delay: 640ms; }
@media (prefers-reduced-motion: reduce) {
    .dream-feed .dream-row { animation: none; }
}

/* Item 3 — Beacon glyph constellation hover. JS adds
   .glyph-constellation-pulse to sibling cards from the same
   source-world on hover; the glyph throbs gently. */
@keyframes glyphConstellationPulse {
    0%   { transform: scale(1);    text-shadow: none; }
    50%  { transform: scale(1.18); text-shadow: 0 0 12px rgba(192, 132, 252, 0.7); }
    100% { transform: scale(1);    text-shadow: none; }
}
.beacon-card .beacon-glyph {
    transition: transform 200ms ease-out;
    display: inline-block;
}
.beacon-card.glyph-constellation-pulse .beacon-glyph {
    animation: glyphConstellationPulse 900ms ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
    .beacon-card.glyph-constellation-pulse .beacon-glyph { animation: none; }
}

/* Item 4 — Live-feed heartbeat pulse. A small dot in the top-
   right corner fades on each page load (and now, since v0.432.0,
   on each polling-fetch tick via the .is-pulsing toggle in
   live-feed-poll.js — the meta-refresh's visible heartbeat
   without the focus-trash).
   v0.432.1 (audit BLOCKING) — animation lives on .is-pulsing
   instead of the bare class. Pre-fix the bare-class binding
   only fired on element insertion, so the JS toggle was a
   no-op and the dot was silent on every post-load tick. */
.live-heartbeat {
    position: fixed;
    top: 4.5rem;
    right: 1.25rem;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent, #5b9dff);
    box-shadow: 0 0 8px rgba(91, 157, 255, 0.6);
    opacity: 0;
    pointer-events: none;
}
/* Initial-load pulse — fires once at element-insertion time
   so the operator sees the first heartbeat without the JS
   needing to fire. The polling shim then toggles .is-pulsing
   on subsequent ticks (same animation, re-applied). */
.live-heartbeat {
    animation: heartbeatPulse 1400ms ease-out;
}
.live-heartbeat.is-pulsing {
    animation: heartbeatPulse 1400ms ease-out;
}
@keyframes heartbeatPulse {
    0%   { opacity: 0;    transform: scale(0.6); }
    20%  { opacity: 0.95; transform: scale(1.0); }
    100% { opacity: 0;    transform: scale(1.4); }
}
@media (prefers-reduced-motion: reduce) {
    .live-heartbeat { display: none; }
}

/* v0.92 audit fix — mood-board grid styles. Pre-fix the template
   at templates/mood-board.html referenced .mood-board-grid /
   .mood-board-tile / .tile-placeholder but no CSS rule existed:
   auto-curated tiles (which always start image-less + caption-only
   until the image-gen pipeline back-fills) rendered as unstyled
   divs that read as "the page is broken." The placeholder treatment
   below makes "image pending" a deliberate visual state. */
.mood-board-grid {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}
.mood-board-tile {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
    transition: border-color 0.15s ease;
}
.mood-board-tile:hover {
    border-color: var(--accent, #5b9dff);
}
.mood-board-tile figure {
    margin: 0;
    display: flex;
    flex-direction: column;
}
.mood-board-tile img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
}
.mood-board-tile .tile-placeholder {
    width: 100%;
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: repeating-linear-gradient(
        45deg,
        rgba(148, 163, 184, 0.05),
        rgba(148, 163, 184, 0.05) 10px,
        rgba(148, 163, 184, 0.10) 10px,
        rgba(148, 163, 184, 0.10) 20px
    );
    border-bottom: 1px dashed var(--border);
}
.mood-board-tile figcaption {
    padding: 0.6rem 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.85rem;
}
.mood-board-tile figcaption strong {
    font-weight: 600;
    font-size: 0.78rem;
    color: var(--muted, #94a3b8);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* v0.92 audit fix — .chip.research-only was used on live-feed.html
   + mood-board.html headers but had no CSS. Now reads as a quieter
   muted-dashed chip to signal "this is research / experimental"
   without overpowering the page title. */
.chip.research-only {
    background: transparent;
    color: var(--muted, #94a3b8);
    border: 1px dashed rgba(148, 163, 184, 0.45);
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.setup-card {
    border-left: 3px solid var(--accent);
    margin-bottom: 1rem;
}
.setup-card h2 {
    margin: 0 0 0.4rem 0;
    font-size: 1.05rem;
}
.setup-steps {
    list-style: none;
    padding: 0;
    margin: 0.75rem 0 0;
}
.setup-steps li {
    display: flex;
    gap: 0.6rem;
    align-items: baseline;
    padding: 0.35rem 0;
}
.setup-steps li.done {
    color: var(--muted);
}
.setup-step-marker {
    display: inline-block;
    min-width: 1.4rem;
    font-weight: 500;
}
.setup-steps li.done .setup-step-marker {
    color: var(--success);
}
/* v0.29.x tile — per-area accent applied via .tile.t-<area>. The
   --accent local variable defaults to the global accent so any tile
   without an area class still works (e.g. settings sub-pages that
   reuse this component). Adds a left stripe in the area color, plus a
   subtle hover lift. */
.tile {
    display: block;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1rem 1rem 1rem 1.15rem;
    color: var(--text);
    position: relative;
    overflow: hidden;
    transition: border-color 150ms ease, transform 150ms ease, box-shadow 150ms ease;
}
.tile::before {
    content: '';
    position: absolute; top: 0; left: 0; bottom: 0;
    width: 3px;
    background: var(--tile-accent, var(--accent));
    border-radius: 12px 0 0 12px;
    transition: width 150ms ease;
}
.tile:hover {
    border-color: var(--tile-accent, var(--accent));
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
    text-decoration: none;
}
.tile:hover::before { width: 4px; }
.tile h3 {
    margin: 0 0 0.3rem;
    font-size: 1.02rem; font-weight: 650;
    letter-spacing: -0.005em;
}
.tile p { margin: 0; color: var(--text-soft); font-size: 0.9rem; line-height: 1.5; }

/* Per-area accent assignments — each shorthand maps the dashboard
   tile's class to its semantic palette color. Keeps templates clean
   (one extra class on each tile) without inline styles. */
.tile.t-agency    { --tile-accent: var(--c-agency); }
.tile.t-agents    { --tile-accent: var(--c-agents); }
.tile.t-projects  { --tile-accent: var(--c-projects); }
.tile.t-watchers  { --tile-accent: var(--c-watchers); }
.tile.t-personas  { --tile-accent: var(--c-personas); }
.tile.t-skills    { --tile-accent: var(--c-skills); }
.tile.t-runs      { --tile-accent: var(--c-runs); }
.tile.t-workflows { --tile-accent: var(--c-workflows); }
.tile.t-chats     { --tile-accent: var(--c-chats); }
.tile.t-usage     { --tile-accent: var(--c-usage); }
.tile.t-approvals { --tile-accent: var(--c-approvals); }
.tile.t-settings  { --tile-accent: var(--c-settings); }
.tile.t-workspaces { --tile-accent: var(--c-projects); }
.tile.t-data      { --tile-accent: var(--c-settings); }
/* PR 1 — system health tile shares the slate accent with t-data and
   t-settings; all three are observation surfaces. */
.tile.t-system    { --tile-accent: var(--c-settings); }

/* PR 1 — /sites/{slug}/system health page. Mirrors the .status-strip
   pattern from solana/hub.html: monospace chips per subsystem with a
   label on the left. v0.104 — added subsystem accents + a vertical
   meta block (display name + one-line description) so the operator
   immediately knows what each row covers. */
.system-strips {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 1.5rem;
}
.system-strip {
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    border-left: 3px solid var(--strip-accent, var(--c-settings));
    padding-left: 0.85rem;
}
.system-strip .strip-meta {
    display: flex;
    flex-direction: column;
    min-width: 13rem;
    gap: 0.15rem;
}
.system-strip .strip-label {
    font-weight: 600;
    color: var(--strip-accent, var(--text, #e2e8f0));
}
.system-strip .strip-desc {
    font-size: 0.78rem;
    color: var(--muted, #94a3b8);
    font-style: italic;
    line-height: 1.25;
}
.system-strip .strip-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
}

/* Per-subsystem accent. Reuses the existing --c-* palette so we stay
   inside the codebase's color vocabulary. */
.system-strip[data-subsystem="FEELING_ALIVE"] { --strip-accent: var(--c-agency); }
.system-strip[data-subsystem="TRAVEL"]        { --strip-accent: var(--c-watchers); }
.system-strip[data-subsystem="SOLANA"]        { --strip-accent: var(--c-projects); }
.system-strip[data-subsystem="RUN_EXECUTOR"]  { --strip-accent: var(--c-runs); }
.system-strip[data-subsystem="LIFE_JOURNEY"]  { --strip-accent: var(--c-agents); }
.system-strip[data-subsystem="MEMORY_DURABILITY"] { --strip-accent: var(--c-skills); }
.system-strip[data-subsystem="FEDERATION_TRUST"]  { --strip-accent: var(--c-personas); }

/* v0.107.1 (ux audit) — federation-trust admin page rules replacing
   the inline style attributes the ux-reviewer flagged. */
.federation-strip { margin-top: 1rem; }
.revoke-reason { width: 16rem; }

/* v0.109.0 (PR A) — Mirror Hour rendering. The dossier-vs-paragraph
   structure IS the surface; keep both visible.

   .mirror-hour-quote   — blockquote style for the agent's monthly paragraph.
   .mirror-hour-dossier — inline (uncollapsed) detail rendering for the
                          dossier the agent reacted to; subtle indent +
                          muted color so it reads as supporting context
                          rather than competing for the eye.
   .mirror-hour-quote-card — recap pull-quote container.

   v0.113.1 (audit) — accent flipped from --c-skills (purple, duplicates
   the Memory Durability strip accent) to --c-agency (violet, the FA
   family color). Mirror Hour is a Feeling Alive feature, not a
   memory-durability one; the semantic accent should match. */
.mirror-hour-quote {
    border-left: 3px solid var(--c-agency, #c084fc);
    padding-left: 0.85rem;
    margin: 0.5rem 0;
    font-style: italic;
    color: var(--text, #e2e8f0);
}
.mirror-hour-quote p { margin: 0; }

.mirror-hour-dossier {
    margin-top: 0.4rem;
    padding-left: 0.85rem;
    border-left: 2px dotted var(--muted, #94a3b8);
}
.mirror-hour-dossier .label {
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    display: block;
    margin-bottom: 0.2rem;
}
.mirror-hour-dossier pre {
    margin: 0;
    white-space: pre-wrap;
    font-family: inherit;
}

.mirror-hour-quote-card {
    border-left: 4px solid var(--c-agency, #c084fc);
}
.mirror-hour-quote-meta { margin-bottom: 0.5rem; }

.mirror-hour-entry { margin-bottom: 1rem; }
.mirror-hour-entry:last-child { margin-bottom: 0; }
.system-strip[data-subsystem="ECONOMY"]       { --strip-accent: var(--c-usage); }

.badge {
    display: inline-block;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    font-size: 0.7rem;
    background: var(--accent-strong);
    color: white;
    text-transform: uppercase;
    margin-left: 0.5rem;
    letter-spacing: 0.05em;
}

.permissions h2 { font-size: 1rem; margin-bottom: 0.5rem; }
.perm-list { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.4rem; }
.perm-list li {
    background: var(--surface-2);
    border: 1px solid var(--border);
    padding: 0.2rem 0.55rem;
    border-radius: 4px;
    font-family: ui-monospace, monospace;
    font-size: 0.8rem;
    color: var(--muted);
}

.muted { color: var(--muted); }
.small { font-size: 0.85rem; }

/* Breadcrumbs + page header */
.breadcrumbs {
    color: var(--muted);
    font-size: 0.85rem;
    margin-bottom: 0.5rem;
}
.breadcrumbs .sep { margin: 0 0.4rem; opacity: 0.6; }
.breadcrumbs a { color: var(--muted); }
.breadcrumbs a:hover { color: var(--accent); }

.page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.page-header h1 { margin: 0 0 0.25rem; }
.header-actions { display: flex; gap: 0.5rem; align-items: center; }

/* Cards */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 1.25rem 1.25rem 1rem;
    margin-bottom: 1.25rem;
}
.card h2 { margin-top: 0; font-size: 1.05rem; }

/* Buttons */
.btn {
    display: inline-block;
    padding: 0.5rem 0.9rem;
    border-radius: 4px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text);
    font: inherit;
    cursor: pointer;
    text-decoration: none;
    line-height: 1.2;
}
.btn:hover { border-color: var(--accent); text-decoration: none; }
.btn.primary { background: var(--accent-strong); border-color: var(--accent-strong); color: white; }
.btn.primary:hover { background: var(--accent); border-color: var(--accent); }
.btn.secondary { background: transparent; }
.btn.danger { background: transparent; color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }
.btn.danger:hover { background: rgba(239, 91, 91, 0.12); border-color: var(--danger); }

/* v0.81 (Wave 2 #14) — runs/list bulk-action bar.
   Sits above the table; sticky so the operator can scroll a long
   list and the bar follows. Picks up the same border palette as
   `.card` so it reads as part of the same section. */
.bulk-action-bar {
    position: sticky;
    top: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.45rem 0.75rem;
    margin: 0 0 0.4rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 6px;
    z-index: 5;
}
.bulk-action-count {
    color: var(--muted);
    font-size: 0.9rem;
}
.bulk-check-col {
    width: 2rem;
    text-align: center;
}

/* v0.81 (Wave 2 #16) — recipes page list rows. Title + summary +
   "read the walkthrough" CTA stacked, separated by a hairline so
   six rows scan as a list without feeling cramped. */
.recipe-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.recipe-row {
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--border);
}
.recipe-row:last-child { border-bottom: 0; }
.recipe-title {
    margin: 0 0 0.3rem;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
}
.recipe-time { font-weight: normal; }
.recipe-summary {
    margin: 0 0 0.6rem;
    color: var(--text-soft);
    line-height: 1.55;
}
.recipe-actions { margin: 0; }

/* v0.81 (Wave 2 #17) — agents/detail deep-verb strip + sticky TOC.
   Deep-verb strip is a horizontal-rule-flanked link row right under
   the page header; always-on links to Inbox / Journal / Memories /
   Hologram / Scope / Personality so an empty-state operator can still
   discover them. */
.agent-deep-verbs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem 1rem;
    padding: 0.45rem 0;
    margin: 0.4rem 0 0.8rem;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    font-size: 0.85rem;
}
.agent-deep-verbs a { color: var(--text-soft); }
.agent-deep-verbs a:hover { color: var(--accent); }

/* Sticky in-page TOC for long detail pages. Sits just below the
   header; sticks at the top of the viewport once scrolled past.
   Paired with `scroll-margin-top: 80px` on targeted section.card[id]
   so anchor clicks land the section below the sticky strip. */
.page-toc {
    position: sticky;
    top: 0.5rem;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.4rem 0.75rem;
    margin-bottom: 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 0.85rem;
}
.page-toc a {
    color: var(--text-soft);
    padding: 0.15rem 0.4rem;
    border-radius: 4px;
}
.page-toc a:hover { color: var(--accent); background: var(--surface-2); }

section.card[id] { scroll-margin-top: 80px; }

/* v0.81 (Wave 2 #19) — run-completion banner. v0.82 moved inside
   <main> (instead of between top-nav and main content) so it shares
   the 1440px container padding and z-index-1 lift over any
   persona-keyed backdrop canvas. Auto-dismiss handled JS-side; the
   fade-out class drives the transition. */
.run-completion-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin: 0 0 0.75rem;
    padding: 0.5rem 1rem;
    border-radius: 6px;
    position: relative;
    z-index: 2;
    transition: opacity 350ms ease-out, transform 350ms ease-out;
}
.run-completion-banner.fade-out {
    opacity: 0;
    transform: translateY(-4px);
    pointer-events: none;
}

/* First-run-of-day "good morning" pill. Small, warm, suppressed
   when the bell is already demanding attention. Same z-index lift
   as the run-completion banner so persona backdrops sit behind. */
.morning-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.7rem;
    margin: 0 0 0.75rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-soft);
    font-size: 0.85rem;
    position: relative;
    z-index: 2;
    opacity: 0;
    transform: translateY(-3px);
    transition: opacity 400ms ease-out, transform 400ms ease-out;
}
.morning-pill.morning-pill-in {
    opacity: 1;
    transform: translateY(0);
}
.morning-pill.fade-out {
    opacity: 0;
    transform: translateY(-3px);
    pointer-events: none;
}

/* Star Fox 64 reference. Type "barrel roll" anywhere outside an
   input → the whole body rotates once. Skipped on reduced-motion
   users via the JS guard, never via CSS, so the keyframe stays
   inert when nobody triggers it. */
@keyframes do-a-barrel-roll {
    from { transform: rotate(0); }
    to   { transform: rotate(360deg); }
}
body.barrel-rolling {
    animation: do-a-barrel-roll 1s ease-in-out;
}
/* Double-guard: the JS handler also checks prefers-reduced-motion at
   trigger time, but a setting that flips mid-session would still let
   the keyframe fire without this. Matches the same pattern as the
   shimmer + busy-btn animations. */
@media (prefers-reduced-motion: reduce) {
    body.barrel-rolling { animation: none; }
}

/* List-row focus state for the j/k power-keys (round 2). The handler
   advances focus across <tr tabindex="-1"> rows inside .data-table; an
   outset outline doesn't fight the row's existing border. Same accent
   blue as the global :focus-visible style. */
.data-table tbody tr[tabindex]:focus {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    background: rgba(91, 157, 255, 0.06);
}
.data-table tbody tr[tabindex] { cursor: default; }

/* Badge tick — bell-count + approvals-count count gets a brief
   scale-pop on initial render so the operator notices a count that
   ticked up since their last visit. Inline JS in layout.html applies
   the .tick class on load; the class self-removes after the animation. */
@keyframes badge-tick {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.18); }
    100% { transform: scale(1); }
}
.bell-count.tick {
    animation: badge-tick 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (prefers-reduced-motion: reduce) {
    .bell-count.tick { animation: none; }
}

/* Logo-5x → confetti easter-egg. Round 2's pure-delight item. The
   inline JS in layout.html appends 14 .confetti-piece spans inside a
   single .confetti-burst container, then removes them after 1.2s.
   Pieces start at the click origin and drift up + out with rotation. */
.confetti-burst {
    position: fixed;
    pointer-events: none;
    z-index: 9999;
    width: 0;
    height: 0;
}
.confetti-piece {
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 1px;
    opacity: 0.95;
    animation: confetti-fly 1.1s cubic-bezier(0.18, 0.7, 0.4, 1) forwards;
}
@keyframes confetti-fly {
    0%   { transform: translate(0, 0) rotate(0deg); opacity: 1; }
    100% { transform: translate(var(--cx), var(--cy)) rotate(var(--cr)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .confetti-piece { animation: none; opacity: 0; }
}

/* Wave-pill — type "hi" / "hello" anywhere → a small pill floats up
   from the bottom-right corner. Borrows the morning-pill visual
   vocabulary but is positioned fixed bottom-right so it doesn't
   compete with whatever's at the top of <main>. */
.wave-pill {
    position: fixed;
    right: 1.5rem;
    bottom: 1.5rem;
    z-index: 9999;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-soft);
    font-size: 0.9rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 350ms ease-out, transform 350ms ease-out;
}
.wave-pill.wave-pill-in {
    opacity: 1;
    transform: translateY(0);
}
.wave-pill.fade-out {
    opacity: 0;
    transform: translateY(8px);
}

/* Footer "Built X ago" — round 2 makes it click-toggleable between
   relative and absolute. Cursor + faint underline-on-hover to hint
   it's interactive. */
.build-relative { cursor: pointer; }
.build-relative:hover .build-relative-text { text-decoration: underline; }

/* Keyboard-shortcuts overlay. Triggered by '?' (and toggled off
   by Escape, click on backdrop, or the close button). Lives at
   high z-index above persona backdrops + the run-completion
   banner; the backdrop dims the page so the overlay reads. */
.shortcuts-overlay-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(12, 13, 18, 0.6);
    backdrop-filter: blur(2px);
    z-index: 9998;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.shortcuts-overlay-backdrop[hidden] { display: none; }
.shortcuts-overlay {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.25rem 1.5rem;
    max-width: 560px;
    width: 100%;
    max-height: 80vh;
    overflow-y: auto;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    color: var(--text);
}
.shortcuts-overlay h2 {
    margin: 0 0 0.25rem;
    font-size: 1.05rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
}
.shortcuts-overlay h3 {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    margin: 1rem 0 0.4rem;
}
.shortcuts-overlay dl {
    margin: 0;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.4rem 0.85rem;
    font-size: 0.875rem;
}
.shortcuts-overlay dt {
    font-family: "JetBrains Mono", ui-monospace, monospace;
    color: var(--accent);
    white-space: nowrap;
}
.shortcuts-overlay dd {
    margin: 0;
    color: var(--text-soft);
}
.shortcuts-overlay-close {
    background: none;
    border: 0;
    color: var(--muted);
    font-size: 1.1rem;
    cursor: pointer;
    padding: 0;
    line-height: 1;
}
.shortcuts-overlay-close:hover { color: var(--text); }
.shortcuts-overlay-footer {
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
    font-size: 0.75rem;
    color: var(--muted);
}

.run-completion-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

/* v0.81 (Wave 2 #20) — auto-grow textarea cap. JS handler clamps
   `height` to scrollHeight; this rule clamps it again at 30vh so
   a runaway paste doesn't push the rest of the form below the fold.
   Resize handle is left on so power users can override either way. */
textarea.auto-grow {
    max-height: 30vh;
    overflow-y: auto;
}

/* v0.81 (Wave 2 #21) — chip-meta utility classes for the
   feeling-alive header chips on agents/detail. Pre-fix 8 chips
   carried inline `background:` / `color:` / `border-color:` /
   `font-size:` rules that duplicated the existing palette (and
   drifted between 0.6rem / 0.65rem / 0.7rem / 0.75rem). One
   `.mini` size + five tone classes; the same chips on
   site-dashboard / runs/detail chains can opt in too. */
.chip.mini { font-size: 0.7rem; }
.chip.t-letter {
    background: rgba(248, 113, 113, 0.15);
    color: #f87171;
    border-color: rgba(248, 113, 113, 0.4);
}
.chip.t-bond-trust {
    background: rgba(74, 222, 128, 0.10);
    color: #4ade80;
    border-color: rgba(74, 222, 128, 0.3);
}
.chip.t-bond-mentor {
    background: rgba(99, 102, 241, 0.10);
    color: #818cf8;
    border-color: rgba(99, 102, 241, 0.3);
}
.chip.t-bond-block {
    background: rgba(248, 113, 113, 0.10);
    color: #f87171;
    border-color: rgba(248, 113, 113, 0.3);
}
.chip.t-pursuit {
    background: rgba(244, 114, 182, 0.12);
    color: #f472b6;
    border-color: rgba(244, 114, 182, 0.4);
}

/* v0.81 (Wave 2 #22) — submit-button busy state. Small inline
   spinner + muted text so the operator sees the round-trip is
   in progress. JS swaps the button label to data-busy-label
   (or "Saving…") and adds .is-busy. */
.btn.is-busy {
    cursor: progress;
    opacity: 0.85;
    position: relative;
    padding-left: 1.8rem;
}
.btn.is-busy::before {
    content: '';
    position: absolute;
    left: 0.6rem;
    top: 50%;
    width: 0.85rem;
    height: 0.85rem;
    margin-top: -0.425rem;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-right-color: transparent;
    animation: form-submit-spin 750ms linear infinite;
}
@keyframes form-submit-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    .btn.is-busy::before { animation: none; }
}

/* Tables */
.data-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.5rem;
}
.data-table th, .data-table td {
    text-align: left;
    padding: 0.55rem 0.5rem;
}
/* Compact variant — used on the agents list where the page is mostly
   single-line rows and the standard padding leaves too much breathing
   room. Halves the vertical padding; horizontal stays the same so chips
   don't crowd the cell edges. */
.data-table.compact th, .data-table.compact td {
    padding: 0.28rem 0.5rem;
    border-bottom: 1px solid var(--border);
    vertical-align: top;
}
.data-table th {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    font-weight: 600;
}
.data-table td.actions, .data-table th.actions { text-align: right; white-space: nowrap; }
.data-table td.actions .row-action { margin-left: 0.75rem; }
.data-table td.actions .row-action:first-child { margin-left: 0; }
.data-table td.truncate {
    max-width: 380px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--muted);
}

/* Chips */
.chip {
    display: inline-block;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.1rem 0.55rem;
    font-size: 0.75rem;
    font-family: ui-monospace, monospace;
    color: var(--muted);
    margin-right: 0.25rem;
}
/* v0.145.0 — admin backups page chips. .chip-warn flags integrity-
   check failures + missing master-key sidecars; .row-suspect dims
   quarantined rows so the eye finds the healthy ones first.
   v0.145.4 — page-header-actions lifted out of inline style to
   match the rest of the admin surface. */
.chip.chip-warn {
    background: #fde7e7;
    color: #a04040;
    border-color: #d99;
}
/* v0.158.2 — green-tinted positive variant for the S3 "uploaded"
   state. The base .chip greyscale was indistinguishable from the
   "—" disabled state at a glance. */
.chip.chip-ok {
    background: #e7f7ec;
    color: #2e7d3d;
    border-color: #b9e0c2;
}
/* v0.365.3 (audit ux IMPORTANT) — lavender-tinted variant for
   system-generated rows. Used by the workspace artifacts panel's
   bundle group to distinguish the auto-created end-of-workspace
   archive from agent-curated rows. Same intensity as .chip-ok so
   the cloud reads as a peer variant, not a special-case alarm. */
.chip.chip-auto {
    background: #ede7f6;
    color: #5e35b1;
    border-color: #d1c4e9;
}
/* v0.158.2 — monospace input for credential / passphrase fields,
   so an operator pasting in an AKIA-style key can verify last-four
   against a password manager without proportional-font ambiguity. */
.mono-input {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* v0.158.2 — checkbox + label pattern for the instance-config
   page's "Upload backups to S3" toggle. Matches the seed-panel
   pattern where the checkbox sits inline with its short label, and
   any help text lands below in a sibling .help element. */
.checkbox-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 500;
}
tr.row-suspect td {
    background: #fff8f4;
    color: var(--muted);
}
.page-header-actions {
    display: flex;
    gap: 0.5rem;
}
/* v0.54.9 — multi-line chip variant. The base .chip is a pill
   (border-radius 999px) sized for single-line labels; when used as
   a block-display warning box with white-space:normal + multi-line
   wrapping (agent monologue-prone warning, runtime defaults warning),
   the pill curvature visibly overflows the text at the top/bottom
   corners. Squarer radius keeps the warning shape coherent at any
   line count. Apply alongside .chip + .sev-* and the inline
   block-display overrides. */
.chip.chip-multiline {
    border-radius: 6px;
    /* Bumping the base font-size from 0.75rem → ~0.85rem too — the
       chip is being used as a paragraph, not a label, and 0.75rem
       wrapped across 4-5 lines reads as fine print. */
    font-size: 0.85rem;
    font-family: inherit;
}
.chip.persona-chip {
    /* Persona-name chip rendered next to the agent's seat-coloured name in
       group-chat bubbles. Stays in the base subdued grey across every agent
       so the visual reading is "this is the role they're playing", and uses
       the body font (not monospace) so multi-word persona names like
       "Tech-Savvy Student" don't look clunky. */
    font-family: inherit;
    text-transform: none;
    letter-spacing: 0;
}

/* Forms */
.form-stack {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 1.25rem;
}
/* v0.100.13 — labels in .form-stack are direct flex items, so the
   <span class="label">…</span> + <input/> children of each <label>
   flow inline by default (the label-text and input sit on one line
   at irregular widths). Stack the children vertically inside each
   label, and give inputs full width so the column reads as a clean
   form. Mirrors what .form-row already does; pre-fix only .form-row
   carried these rules + the mint page (which uses .form-stack)
   inherited the broken default. */
.form-stack > label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.form-stack .label {
    display: block;
    font-size: 0.78rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}
.form-stack input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.form-stack select,
.form-stack textarea {
    width: 100%;
    padding: 0.5rem 0.65rem;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    font: inherit;
    font-family: inherit;
}
.form-stack textarea {
    font-family: ui-monospace, monospace;
    font-size: 0.9rem;
    resize: vertical;
}
.form-stack input:focus,
.form-stack select:focus,
.form-stack textarea:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
    border-color: var(--accent);
}
.form-row label { display: block; }
.form-row .label,
.grid-2 .label {
    display: block;
    font-size: 0.78rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.25rem;
    font-weight: 600;
}
/* Excludes checkboxes / radios so a picker (e.g. group-chats/new's
   participant list) wrapped in a .form-row doesn't get its checkbox
   inputs stretched to 100% width with chunky text-input padding —
   that produced the disconnected-checkbox bug fixed in v0.15.4. */
.form-row input:not([type="checkbox"]):not([type="radio"]),
.form-row select,
.form-row textarea,
.grid-2 input:not([type="checkbox"]):not([type="radio"]),
.grid-2 select {
    width: 100%;
    padding: 0.5rem 0.65rem;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    font: inherit;
    font-family: inherit;
}
.form-row textarea { font-family: ui-monospace, monospace; font-size: 0.9rem; resize: vertical; }
.form-row input:focus,
.form-row select:focus,
.form-row textarea:focus,
.grid-2 input:focus,
.grid-2 select:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }

/* Inline input + side-button row (e.g. agent name + 🎲 Random). flex keeps the
   button at intrinsic width and lets the input grow to fill the rest. */
.form-row .name-row { display: flex; gap: 0.5rem; align-items: stretch; }
.form-row .name-row input { flex: 1 1 auto; }
.form-row .name-row .btn { flex: 0 0 auto; white-space: nowrap; }

.form-group {
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1rem;
    background: var(--surface-2);
}
.form-group legend {
    padding: 0 0.4rem;
    font-size: 0.78rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}
.grid-2 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.85rem;
}
.grid-3 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.85rem;
}

/* Multi-select for "Create new run" form — checkbox list. Lets the whole page
   scroll when there are many agents rather than trapping the user in an inner
   pane; the kind groupings make the list browsable even when long. */
.agent-multiselect {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-top: 0.6rem;
    padding: 0.5rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 4px;
}
.agent-multiselect-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.3rem 0.5rem;
    border-radius: 3px;
    cursor: pointer;
    transition: background 0.08s ease;
}
.agent-multiselect-row:hover { background: var(--surface); }
.agent-multiselect-row input[type="checkbox"] { margin: 0; flex: 0 0 auto; }
/* Selected state — :has() lets us style the row itself based on the
   wrapped checkbox, no JS needed. Subtle tint + accent border so a
   ticked agent is visible at a glance without dominating the layout. */
.agent-multiselect-row:has(input[type="checkbox"]:checked) {
    background: var(--surface);
    box-shadow: inset 2px 0 0 var(--accent);
}
.agent-multiselect-name { font-weight: 500; }
/* Persona-name aside (the muted text after the agent name). Slightly
   smaller + de-emphasised so the agent's own name carries the weight
   in scan order. */
.agent-multiselect-row .muted.small { font-size: 0.78rem; }

/* v0.26.2 — richer picker row: avatar + (name above, persona/level/model
   below). The body stacks the two lines so the row reads name-first
   without the meta line crowding the agent's name horizontally. The
   meta line itself is allowed to wrap so longer model names (or smaller
   viewports) don't push the layout. */
.agent-multiselect-row-body {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;          /* allows children to shrink and wrap */
    flex: 1 1 auto;
}
.agent-multiselect-row-body .muted.small {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.72rem;
}
.agent-multiselect-row-body .chip {
    padding: 0.05rem 0.35rem;
    font-size: 0.65rem;
}
.agent-multiselect-row-body code {
    font-size: 0.7rem;
}

/* Per-kind sub-sections inside the run-create agent picker. Heading is
   compact (smaller than the agents-list h3) because the parent fieldset
   already carries the "Agents" legend, so this is a sub-grouping rather
   than a top-level section. First group has no top margin so it sits
   flush against the picker's top edge. */
.agent-multiselect-group { margin-top: 0.6rem; }
.agent-multiselect-group:first-child { margin-top: 0; }
.agent-multiselect-group-heading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.25rem 0;
    font-size: 0.85rem;
    font-weight: 600;
}
.agent-multiselect-group-heading .chip { padding: 0.15rem 0.5rem; }

/* Workspace participants picker — two-column layout: pool of available
   agents (reusing .agent-multiselect-* styling) on the left, drag-to-
   reorder turn-order list on the right. Stacks under 700px so the
   right-hand list slides under the left-hand pool on narrow viewports
   instead of squashing into illegibility. */
.workspace-picker {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1rem;
    margin-top: 0.6rem;
}
@media (max-width: 700px) {
    .workspace-picker { grid-template-columns: 1fr; }
}
.workspace-picker-pool,
.workspace-picker-order {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.5rem;
}
.workspace-picker-heading {
    margin: 0 0 0.5rem 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--muted);
}
.workspace-picker-empty { margin: 0; }
.workspace-order-list {
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: workspace-row;
}
.workspace-order-row {
    counter-increment: workspace-row;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
    margin-bottom: 0.25rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: grab;
    user-select: none;
}
.workspace-order-row::before {
    content: counter(workspace-row) ".";
    color: var(--muted);
    font-size: 0.75rem;
    min-width: 1.25rem;
    text-align: right;
}
.workspace-order-row.dragging { opacity: 0.4; cursor: grabbing; }
.workspace-order-handle {
    color: var(--muted);
    font-family: monospace;
    font-size: 1rem;
    line-height: 1;
    cursor: grab;
}
.workspace-order-name { font-weight: 500; flex: 1; }
.workspace-order-remove {
    background: transparent;
    border: none;
    color: var(--muted);
    font-size: 1.1rem;
    cursor: pointer;
    padding: 0 0.3rem;
    line-height: 1;
}
.workspace-order-remove:hover { color: var(--danger); }

.help { color: var(--muted); font-size: 0.8rem; display: block; margin-top: 0.3rem; }
.help code {
    background: var(--surface-2);
    padding: 0.05rem 0.3rem;
    border-radius: 3px;
    font-size: 0.85em;
}
.field-error {
    color: var(--danger);
    font-size: 0.8rem;
    display: block;
    margin-top: 0.3rem;
}
.form-actions { display: flex; gap: 0.5rem; padding-top: 0.25rem; }

/* Property list (for detail pages) */
.prop-list {
    display: grid;
    grid-template-columns: minmax(140px, max-content) 1fr;
    gap: 0.5rem 1rem;
    margin: 0;
}
.prop-list dt {
    color: var(--muted);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}
.prop-list dd { margin: 0; }

.system-prompt {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.85rem 1rem;
    font-family: ui-monospace, monospace;
    font-size: 0.85rem;
    white-space: pre-wrap;
    color: var(--text);
    margin: 0;
}

/* Tool assignment grid (used on agent edit + detail) */
.tool-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
.tool-row {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
        "check meta"
        "check desc";
    column-gap: 0.7rem;
    row-gap: 0.15rem;
    padding: 0.6rem 0.75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 4px;
    align-items: start;
}
.tool-row input[type=checkbox] { grid-area: check; margin-top: 0.2rem; }
.tool-row .tool-meta { grid-area: meta; display: inline-flex; flex-wrap: wrap; gap: 0.3rem; align-items: baseline; }
.tool-row .tool-name { font-weight: 600; }
.tool-row .tool-desc { grid-area: desc; }
.tool-row.disabled { opacity: 0.45; }
.tool-row.disabled input { cursor: not-allowed; }
.tool-row.planned { opacity: 0.85; }
.chip.planned-chip { background: rgba(245, 158, 11, 0.18); color: #f59e0b; border-color: rgba(245, 158, 11, 0.4); }
.chip.auth-required {
    background: rgba(239, 91, 91, 0.2);
    color: var(--danger);
    border-color: rgba(239, 91, 91, 0.5);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Tool selection: per-category groups + show-planned toggle */
.tool-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: 0.25rem 0 0.75rem;
    flex-wrap: wrap;
}
.tool-toolbar .summary { color: var(--muted); font-size: 0.85rem; }
.tool-toolbar .summary strong { color: var(--text); }
.tool-toolbar label { display: inline-flex; align-items: center; gap: 0.4rem; cursor: pointer; font-size: 0.85rem; }
/* Top-level Select all / Clear all buttons — visually consistent with
   the per-group action buttons (same link-button look). */
.tool-toolbar-actions { display: inline-flex; gap: 0.75rem; font-size: 0.85rem; }
.tool-toolbar-actions button {
    background: none;
    border: none;
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    padding: 0;
}
.tool-toolbar-actions button:hover { text-decoration: underline; color: var(--accent-strong); }

.tool-group { margin-top: 1.25rem; }
.tool-group:first-of-type { margin-top: 0.5rem; }
.tool-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-bottom: 0.3rem;
    margin-bottom: 0.4rem;
    border-bottom: 1px solid var(--border);
}
/* Left cluster groups the tri-state select-all checkbox with the
   category title so the right-side action buttons stay anchored to
   the right edge. align-items: center because the checkbox + h3
   pair benefits from centered alignment more than baseline does. */
.tool-group-left {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.tool-group-select-all {
    margin: 0;
    cursor: pointer;
    /* Slightly bigger than row checkboxes to signal "primary
       affordance — affects every row in this category". */
    width: 1rem;
    height: 1rem;
}
.tool-group-header h3 {
    margin: 0;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
/* Click-to-toggle on the category title. Hover lifts the color to
   var(--text) as a clickability cue; chevron rotates from ▸ (collapsed)
   to ▾ (expanded) via a 90° transform. Focus ring uses the same
   accent halo as the login inputs. */
.tool-group-toggle {
    cursor: pointer;
    user-select: none;
    transition: color 0.12s ease;
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
}
.tool-group-toggle:hover { color: var(--text); }
.tool-group-toggle:focus-visible {
    outline: none;
    color: var(--text);
    box-shadow: 0 0 0 3px rgba(91, 157, 255, 0.18);
    border-radius: 3px;
}
.tool-group-toggle .chevron {
    display: inline-block;
    color: var(--muted);
    font-size: 0.75em;
    transition: transform 0.15s ease;
}
.tool-group:not(.collapsed) .tool-group-toggle .chevron {
    transform: rotate(90deg);
}
.tool-group.collapsed .tool-grid {
    display: none;
}
.tool-group-header .count { color: var(--muted); font-size: 0.8rem; margin-left: 0.4rem; font-weight: 400; }
.tool-group-actions { display: flex; gap: 0.75rem; font-size: 0.8rem; }
.tool-group-actions button {
    background: none;
    border: none;
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    padding: 0;
}
.tool-group-actions button:hover { text-decoration: underline; color: var(--accent-strong); }

/* When the fieldset has this class, planned (coming-soon) rows are hidden. */
.tools-hide-planned .tool-row.planned { display: none; }
.tools-hide-planned .tool-group.empty { display: none; }

/* Top-nav links (admin, my-account) */
.nav-link {
    color: var(--muted);
    text-decoration: none;
    margin: 0 0.25rem;
}
.nav-link:hover { color: var(--accent); text-decoration: none; }

/* Inline checkboxes (used on user create form, role grids, etc.) */
.inline-checkbox {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin: 0.4rem 0;
    cursor: pointer;
}
.inline-checkbox input { margin-top: 0.25rem; }
.inline-checkbox span small { display: block; color: var(--muted); margin-top: 0.1rem; }

.role-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 0.25rem 1rem;
}

/* Inline reset-password popover on the user list */
.row-action-popover summary {
    cursor: pointer;
    list-style: none;
    color: var(--accent);
}
.row-action-popover summary::-webkit-details-marker { display: none; }
.row-action-popover[open] summary { color: var(--text); }
.reset-form {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.4rem;
    align-items: center;
}
.reset-form input {
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.35rem 0.5rem;
    font: inherit;
}
.row-action.danger { color: var(--danger); }
.row-action.danger:hover { color: var(--danger); text-decoration: underline; }

/* "Name as link" pattern in admin tables — the name doubles as the open-row
   action so an extra trip to the right column isn't required. Default <a>
   would tint the bold name accent-blue; instead inherit the cell's text
   color and surface the link affordance on hover. */
.site-name-link {
    color: inherit;
}
.site-name-link:hover {
    color: var(--accent);
    text-decoration: underline;
}

/* Bell icon (top nav) */
.bell {
    position: relative;
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: var(--text);
    margin-right: 0.5rem;
    font-size: 1.05rem;
}
.bell:hover { text-decoration: none; color: var(--accent); }
.bell-count {
    position: absolute;
    top: -6px;
    right: -8px;
    background: var(--danger);
    color: white;
    border-radius: 999px;
    font-size: 0.65rem;
    padding: 0.05rem 0.35rem;
    line-height: 1;
    font-weight: 700;
    border: 2px solid var(--surface);
    /* Soft pulse so unread mail is visible at a glance without
       being obnoxious. Shares the same cadence as the live-badge /
       status-running chip via the global token. */
    animation: live-pulse var(--motion-pulse-duration) ease-in-out infinite;
}
/* Approvals badge: same shape as the messages bell-count but uses
   the approvals semantic color so the two badges aren't a confusable
   pair of red dots. */
.approvals-pending-link .bell-count-approvals {
    background: var(--c-approvals);
    color: #1a0e0e;
}
/* v0.89.0 — TREASURER queue badge. Same bell shape, distinct color
   so chain-approvals + Solana-queue badges visually separate at a
   glance. Cool-blue tone associated with the Solana brand identity. */
.solana-queue-link .bell-count-solana {
    background: #14F195;  /* Solana-green accent */
    color: #0a1a13;
}
/* v0.101 — announcements megaphone dot. Same shape as the count
   badges but renders a static dot character; the bell itself is
   always visible (a persistent nav entry to /announcements), and
   this overlay only appears when a broadcast is actually live so
   the operator can read "yes my morning greeting is showing" from
   any page in the site. Soft green matches the "live" chip on the
   announcements page + dashboard tile for cross-surface coherence. */
.announcements-link .bell-count-announcement {
    background: rgba(74, 222, 128, 0.85);  /* matches live-chip green */
    color: #0a1a0e;
    font-size: 0.55rem;
    line-height: 0.55rem;
    padding: 0.1rem 0.25rem;
}
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0);
    white-space: nowrap; border: 0;
}

/* Skip-to-main link. Visually hidden until keyboard focus reveals
   it at the top-left of the viewport. Sighted mouse users never
   see it; keyboard-only operators bypass the entire top-nav. */
.skip-link {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    padding: 0.5rem 1rem;
    background: var(--accent);
    color: var(--bg);
    font-weight: 500;
    text-decoration: none;
    border-radius: 6px;
    z-index: 1000;
    transform: translateY(-200%);
    transition: transform 150ms ease;
}
.skip-link:focus,
.skip-link:focus-visible {
    transform: translateY(0);
    outline: 2px solid var(--accent-strong);
    outline-offset: 2px;
}

/* Filter tabs (used on messages inbox) */
.filter-tabs {
    display: flex;
    gap: 0.25rem;
    margin: 0 0 1rem;
    border-bottom: 1px solid var(--border);
}
.filter-tabs .tab {
    padding: 0.5rem 0.85rem;
    color: var(--muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.filter-tabs .tab:hover { color: var(--text); text-decoration: none; }
.filter-tabs .tab.active {
    color: var(--text);
    border-bottom-color: var(--accent);
    font-weight: 600;
}

/* Message inbox list */
.message-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.message-row {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.85rem 1rem;
}
.message-row.unread {
    border-left: 3px solid var(--accent);
    background: linear-gradient(90deg, rgba(91, 157, 255, 0.06), transparent 30%), var(--surface-2);
}
.message-meta { display: flex; gap: 0.4rem; align-items: baseline; margin-bottom: 0.35rem; }
.message-title { margin: 0 0 0.25rem; font-size: 1rem; }
.message-body { margin: 0 0 0.5rem; color: var(--text); white-space: pre-wrap; }
.message-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* Chat thread (chats/detail.html) */
.chat-thread {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.chat-bubble {
    max-width: 80%;
    padding: 0.6rem 0.85rem;
    border-radius: 8px;
    background: var(--surface-2);
    border: 1px solid var(--border);
}
.chat-bubble.user {
    align-self: flex-end;
    background: rgba(91, 157, 255, 0.12);
    border-color: rgba(91, 157, 255, 0.3);
}
.chat-bubble.agent {
    align-self: flex-start;
}
.chat-bubble-meta {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.3rem;
}
/* time stays right-aligned now that there can be a third child (the model
   annotation on agent bubbles). Anything before it flows left, anything
   without `margin-left: auto` packs around it. */
.chat-bubble-meta > time { margin-left: auto; }
.chat-role { font-weight: 600; font-size: 0.85rem; }

/* Tiny circular avatar that sits before the agent's name chip in a group-chat
   bubble. Same shape as .agent-avatar-thumb in the agent list, just smaller
   so it doesn't dominate the meta row. The parent flex container uses
   align-items: baseline, which would land the bottom of the <img> on the
   text baseline; shifting it to align-self: center reads cleaner. The
   onerror handler in the template hides the <img> when no avatar exists,
   so missing-avatar bubbles stay visually clean. */
.chat-bubble-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-2);
    align-self: center;
    flex-shrink: 0;
}
/* Anchor wrapping a chat-bubble-avatar — clicking opens the
   full-sized image in a new tab. Reset link styling so the anchor
   doesn't add an underline or color shift around the circle. */
.chat-bubble-avatar-link {
    display: inline-flex;
    align-items: center;
    line-height: 0;
    align-self: center;
    flex-shrink: 0;
    text-decoration: none;
}
.chat-bubble-avatar-link:hover { text-decoration: none; }

/* My Account chat-color picker — 21 swatches (None + 20 seat colors)
   laid out in a wrapping grid. Each swatch is a relabeled .chip plus
   a hidden radio input; clicking the chip ticks the radio. The .chip
   .seat-N rules above provide the colors automatically. */
.chat-color-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
}
.chat-color-swatch {
    cursor: pointer;
    user-select: none;
    /* Override the inherited .chip min-width so each swatch is a
       compact uniform circle-ish pill instead of growing to fit
       its number label. */
    min-width: 2.2rem;
    text-align: center;
    border-width: 2px;
    border-style: solid;
    transition: transform 80ms ease;
}
.chat-color-swatch:hover { transform: scale(1.08); }
.chat-color-swatch input[type="radio"] {
    /* Hidden — the chip itself is the visible click target. */
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.chat-color-swatch.selected {
    /* Solid border + slight lift so the picked swatch is obvious
       against the muted backgrounds of the unpicked ones. */
    box-shadow: 0 0 0 2px var(--text);
    transform: scale(1.05);
}
.chat-color-swatch-none {
    background: var(--surface-2);
    color: var(--muted);
    border-color: var(--border);
    border-radius: 999px;
    padding: 0.2rem 0.6rem;
    font-size: 0.8rem;
    font-weight: 500;
}
.chat-color-swatch .swatch-label {
    /* Center the label in the chip without affecting line height. */
    display: inline-block;
    line-height: 1;
}

/* Small "Ollama qwen2.5:7b" annotation rendered next to an agent's name on
   each chat bubble + in the participants roster. Monospace, muted, no chip
   background — should read as metadata, not as a primary label. */
.bubble-model, .participant-model {
    font-size: 0.7rem;
    font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
    color: var(--muted);
    background: transparent;
    padding: 0;
    border: 0;
    white-space: nowrap;
}
.chat-bubble-body {
    white-space: pre-wrap;
    word-break: break-word;
    /* v0.29.x — long-form chat messages are the hardest content
       monospace has to carry; lots of natural-language paragraphs
       all in fixed-width per-character ink. We compensate with:
       - weight 300 (light) — the airy variant of JetBrains Mono,
         loaded via layout.html. Falls back to the OS mono at the
         OS's regular weight if Google Fonts is dark; still readable.
       - bumped size 0.95rem so the lighter weight doesn't read thin.
       - generous line-height 1.7 — much more breathing room than
         the rest of the app, on the theory that "reading a chat" is
         a different cognitive task than "scanning a list".
       - softer color: var(--text-soft) instead of var(--text), so
         the contrast against the dark bubble background lands in
         the comfortable-reading band rather than the harsh-stare
         band. Speaker name + timestamps stay on var(--text) so the
         meta row still pops above each message.
       - 0.01em letter-spacing nudge — JetBrains Mono is slightly
         tracked-in at 300 weight on dark; the tiny positive
         spacing keeps glyph terminals from kissing. */
    color: var(--text-soft);
    font-weight: 300;
    font-size: 0.95rem;
    line-height: 1.7;
    letter-spacing: 0.01em;
}

/* "Agents are replying" indicator on the group-chat detail page. Three pulsing
   dots with offset phases; disappears the moment turn-complete arrives.
   The [hidden] override is non-optional: author CSS sets display:flex which
   beats the UA stylesheet's `[hidden] { display: none }` at equal specificity,
   so without this rule, JS setting `el.hidden = true` does nothing visible. */
.group-chat-thinking {
    display: flex;
    align-items: center;
    /* v0.321.1 — flex-wrap so the dots + status + Stop button reflow onto
       two lines on narrow viewports instead of overflowing horizontally
       (the operator on a phone trying to recover a stuck discussion was
       scrolling right to find the Stop button — the motivating bug). */
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
    color: var(--muted);
}
.group-chat-thinking[hidden] { display: none; }
/* v0.321.1 — push the inline Stop form to the right end of the flex line
   so it sits where the operator's eye lands after reading "Discussing —
   round X of Y". Replaces the prior inline style on the form element. */
#group-chat-discussion-stop-form { margin-left: auto; }
.group-chat-thinking .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    display: inline-block;
    animation: thinking-pulse 1.2s ease-in-out infinite;
}
.group-chat-thinking .dot:nth-child(2) { animation-delay: 0.2s; }
.group-chat-thinking .dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes thinking-pulse {
    0%, 100% { opacity: 0.3; transform: scale(0.85); }
    50%      { opacity: 1;   transform: scale(1.1); }
}
/* v0.321.1 — vestibular-friendly: pause the pulsing dots when the
   operator has reduced-motion enabled. The animation runs continuously
   for the full duration of a discussion (multi-round = multi-minute); a
   pre-existing concern made worse by the v0.321.0 widening of when the
   banner is visible (every refresh during an active discussion, not
   just the brief turn-fanout window). */
@media (prefers-reduced-motion: reduce) {
    .group-chat-thinking .dot,
    .chat-waiting-indicator .dot {
        animation: none;
        opacity: 0.7;
    }
}

/* v0.165.0 — shared "Waiting for agents to respond…" indicator on
   every chat composer (1:1 start + reply, group start + reply).
   Hidden by default; revealed when form-submit-loading.js sets
   aria-busy="true" on the parent form. Same pulsing dots as
   .group-chat-thinking so the visual signal is consistent
   regardless of which surface the operator is on. */
.chat-waiting-indicator {
    display: none;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
    color: var(--muted);
}
form[aria-busy="true"] .chat-waiting-indicator {
    display: flex;
}
.chat-waiting-indicator .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    display: inline-block;
    animation: thinking-pulse 1.2s ease-in-out infinite;
}
.chat-waiting-indicator .dot:nth-child(2) { animation-delay: 0.2s; }
.chat-waiting-indicator .dot:nth-child(3) { animation-delay: 0.4s; }

/* Reply form's disabled state while a turn is in flight.
   v0.29.x — opacity bumped 0.55 → 0.85 so a stuck disabled state
   doesn't make the buttons look invisible against the dark
   background. cursor:not-allowed still communicates the state;
   we trade a bit of visual distinctness for guaranteed visibility. */
#group-chat-reply-form textarea:disabled,
#group-chat-reply-form button:disabled {
    opacity: 0.85;
    cursor: not-allowed;
}

/* Group chats — multi-agent "focus group" thread.
   Each participant gets a distinct accent color by seat order so it's easy to
   tell speakers apart at a glance without reading every header. */
/* Seat-coloured left border matches the seat chip on the speaker's name.
   Wider than other borders so it's distinguishable at a glance, and a
   muted fallback covers messages whose agent isn't in the current
   seatByAgent map (e.g. agent later removed from a chat). */
.chat-bubble.agent {
    border-left-width: 4px;
    border-left-color: var(--muted);
}
.chat-bubble.agent.seat-unknown { border-left-color: var(--muted); }
.chat-bubble.agent.seat-0  { border-left-color: #5b9dff; }  /* blue       */
.chat-bubble.agent.seat-1  { border-left-color: #4ade80; }  /* green      */
.chat-bubble.agent.seat-2  { border-left-color: #fb923c; }  /* orange     */
.chat-bubble.agent.seat-3  { border-left-color: #a78bfa; }  /* purple     */
.chat-bubble.agent.seat-4  { border-left-color: #22d3ee; }  /* cyan       */
.chat-bubble.agent.seat-5  { border-left-color: #f472b6; }  /* pink       */
.chat-bubble.agent.seat-6  { border-left-color: #facc15; }  /* yellow     */
.chat-bubble.agent.seat-7  { border-left-color: #ef5b5b; }  /* red        */
.chat-bubble.agent.seat-8  { border-left-color: #14b8a6; }  /* teal       */
.chat-bubble.agent.seat-9  { border-left-color: #818cf8; }  /* indigo     */
.chat-bubble.agent.seat-10 { border-left-color: #d946ef; }  /* fuchsia    */
.chat-bubble.agent.seat-11 { border-left-color: #84cc16; }  /* lime       */
.chat-bubble.agent.seat-12 { border-left-color: #f59e0b; }  /* amber      */
.chat-bubble.agent.seat-13 { border-left-color: #fb7185; }  /* rose       */
.chat-bubble.agent.seat-14 { border-left-color: #38bdf8; }  /* sky        */
.chat-bubble.agent.seat-15 { border-left-color: #c084fc; }  /* violet     */
.chat-bubble.agent.seat-16 { border-left-color: #34d399; }  /* emerald    */
.chat-bubble.agent.seat-17 { border-left-color: #fbbf24; }  /* gold       */
.chat-bubble.agent.seat-18 { border-left-color: #2dd4bf; }  /* turquoise  */
.chat-bubble.agent.seat-19 { border-left-color: #f87171; }  /* coral      */

/* Participant chips on the group-chat detail page — same palette as the
   bubbles so the roster matches its speakers. Also reused on the
   operator's chat-bubble label when they pick a chat color from the
   My Account page (chip background + text + border are color-derived). */
.chip.seat-0  { background: rgba(91,157,255,0.18);  color: #5b9dff; border-color: rgba(91,157,255,0.4); }
.chip.seat-1  { background: rgba(74,222,128,0.18);  color: #4ade80; border-color: rgba(74,222,128,0.4); }
.chip.seat-2  { background: rgba(251,146,60,0.20);  color: #fb923c; border-color: rgba(251,146,60,0.4); }
.chip.seat-3  { background: rgba(167,139,250,0.20); color: #a78bfa; border-color: rgba(167,139,250,0.45); }
.chip.seat-4  { background: rgba(34,211,238,0.18);  color: #22d3ee; border-color: rgba(34,211,238,0.4); }
.chip.seat-5  { background: rgba(244,114,182,0.20); color: #f472b6; border-color: rgba(244,114,182,0.4); }
.chip.seat-6  { background: rgba(250,204,21,0.18);  color: #facc15; border-color: rgba(250,204,21,0.4); }
.chip.seat-7  { background: rgba(239,91,91,0.18);   color: #ef5b5b; border-color: rgba(239,91,91,0.4); }
.chip.seat-8  { background: rgba(20,184,166,0.18);  color: #14b8a6; border-color: rgba(20,184,166,0.4); }
.chip.seat-9  { background: rgba(129,140,248,0.20); color: #818cf8; border-color: rgba(129,140,248,0.4); }
.chip.seat-10 { background: rgba(217,70,239,0.18);  color: #d946ef; border-color: rgba(217,70,239,0.4); }
.chip.seat-11 { background: rgba(132,204,22,0.18);  color: #84cc16; border-color: rgba(132,204,22,0.4); }
.chip.seat-12 { background: rgba(245,158,11,0.20);  color: #f59e0b; border-color: rgba(245,158,11,0.4); }
.chip.seat-13 { background: rgba(251,113,133,0.18); color: #fb7185; border-color: rgba(251,113,133,0.4); }
.chip.seat-14 { background: rgba(56,189,248,0.18);  color: #38bdf8; border-color: rgba(56,189,248,0.4); }
.chip.seat-15 { background: rgba(192,132,252,0.20); color: #c084fc; border-color: rgba(192,132,252,0.45); }
.chip.seat-16 { background: rgba(52,211,153,0.18);  color: #34d399; border-color: rgba(52,211,153,0.4); }
.chip.seat-17 { background: rgba(251,191,36,0.20);  color: #fbbf24; border-color: rgba(251,191,36,0.4); }
.chip.seat-18 { background: rgba(45,212,191,0.18);  color: #2dd4bf; border-color: rgba(45,212,191,0.4); }
.chip.seat-19 { background: rgba(248,113,113,0.18); color: #f87171; border-color: rgba(248,113,113,0.4); }

/* v0.31.1 — memory rendering chips + list. Replaces three sites of
   inline `style="background:#e0e7ff;color:#3730a3;"` that read as a
   broken light-mode override on the dark UI. Same shape as the
   .chip.seat-N rules — semi-transparent rgba over the dark bg.
   `kind-chip` flags non-GENERAL memory kinds (SELF_REFLECTION etc.). */
.chip.kind-chip {
    background: rgba(99, 102, 241, 0.18);
    color: #a5b4fc;
    border-color: rgba(99, 102, 241, 0.4);
}

/* v0.118.0 (PR 2 — Keepsake mark) — "kept" chip on memory rows the
   agent marked via AGENT_MARK_KEEPSAKE. Smaller, warmer tint than
   kind-chip so the badge sits beside the kind without competing
   visually. Hovers carry the tooltip explaining the prune exemption. */
.chip.keepsake-chip {
    background: rgba(245, 158, 11, 0.15);
    color: #fcd34d;
    border-color: rgba(245, 158, 11, 0.4);
    font-size: 0.85em;
    margin-right: 0.3em;
}

/* v0.120.0 (PR 3 — Mantelpiece) — five-slot positional display on the
   personality page. Grid scales to a row of 5 on wide screens, wraps
   to 2-3 columns on narrow. Empty slots render at lower opacity so
   the five-slot SHAPE stays visible even when the agent hasn't pinned
   anything yet. */
.mantelpiece-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.75rem;
    margin-top: 0.75rem;
}
.mantelpiece-slot {
    border: 1px solid var(--border, rgba(255, 255, 255, 0.08));
    border-radius: 0.4rem;
    padding: 0.65rem 0.75rem;
    background: rgba(255, 255, 255, 0.02);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-height: 5rem;
}
.mantelpiece-slot.empty {
    opacity: 0.55;
    border-style: dashed;
}
.mantelpiece-slot-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted, #888);
}
.mantelpiece-summary {
    margin: 0;
    line-height: 1.35;
    font-size: 0.92rem;
}

/* Memory list on run-detail / chat-detail. Used by the
   "Memories recorded during this run" + "Memories anchored to this
   chat" cards. Replaces inline list-style + padding-left + per-item
   margin-bottom — UX reviewer's S2 finding. */
.memory-list {
    list-style: none;
    padding-left: 0;
    margin: 0;
}
.memory-list li {
    margin-bottom: 0.6rem;
    /* Long-word fallback — strategic suggestions and other LLM-written
       summaries occasionally include URL-shaped or path-shaped tokens
       that the browser can't break at a space. Without this an unbroken
       token forces horizontal scroll for the whole page. */
    overflow-wrap: anywhere;
}
.memory-list li:last-child {
    margin-bottom: 0;
}
/* <pre> inside <details> defaults to white-space: pre, which means a
   single long line (the model often emits the detail text as one
   paragraph with no hard wraps) forces horizontal page scroll. Pre-wrap
   keeps the monospace formatting for genuine multi-line content while
   still wrapping at word boundaries. overflow-wrap:anywhere catches the
   unbreakable-token case too.

   Same fix applies to detail <pre>s inside data-table cells (the agent
   detail page renders memories in a table, not a list). */
.memory-list pre,
.data-table pre,
details > pre {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-width: 100%;
}

/* v0.32.0 — Feeling Alive UI: mood chip + per-agent digest rows + dream rows.
   v0.32.1 — text reads near-white instead of same-hue-as-tint so we hit
   WCAG AA contrast on the dark theme. The chip's hue identity now lives
   in the (slightly stronger) background tint + the solid-color border,
   not the foreground text. UX reviewer's S1. */
.chip.mood-chip {
    color: #e2e8f0;
    background: rgba(148,163,184,0.22);
    border-color: rgba(148,163,184,0.6);
}
.chip.mood-chip.mood-focused    { background: rgba(74,222,128,0.22);  border-color: #4ade80; }
.chip.mood-chip.mood-tired      { background: rgba(250,204,21,0.22);  border-color: #facc15; }
.chip.mood-chip.mood-struggling { background: rgba(239,91,91,0.24);   border-color: #ef5b5b; }
.chip.mood-chip.mood-lonely     { background: rgba(167,139,250,0.24); border-color: #a78bfa; }
.chip.mood-chip.mood-quiet      { background: rgba(148,163,184,0.18); border-color: rgba(148,163,184,0.5); color: #94a3b8; }

/* v0.34.0 — agent-authored emotional moods. Same translucent-rgba
   shape as the data-driven moods + .seat-N chips so the chip family
   reads as one visual system. Hue picked to match the emotional
   register (warm hues = positive, cool = melancholy, sharp =
   frustrated, etc.). */
.chip.mood-chip.mood-happy         { background: rgba(251,191,36,0.22);  border-color: #fbbf24; }
.chip.mood-chip.mood-inspired      { background: rgba(56,189,248,0.24);  border-color: #38bdf8; }
.chip.mood-chip.mood-worried       { background: rgba(167,139,250,0.20); border-color: #a78bfa; }
.chip.mood-chip.mood-excited       { background: rgba(251,113,133,0.22); border-color: #fb7185; }
.chip.mood-chip.mood-melancholy    { background: rgba(125,211,252,0.18); border-color: #7dd3fc; }
.chip.mood-chip.mood-curious       { background: rgba(192,132,252,0.22); border-color: #c084fc; }
.chip.mood-chip.mood-frustrated    { background: rgba(239,68,68,0.22);   border-color: #ef4444; }
.chip.mood-chip.mood-energized     { background: rgba(132,204,22,0.22);  border-color: #84cc16; }
.chip.mood-chip.mood-contemplative { background: rgba(94,234,212,0.20);  border-color: #5eead4; }
/* v0.81.x — 11 new moods. Hue choices mirror the emotional register
   of the original 9: warm/saturated for positive-active, soft pastel
   for tender/calm, faded for nostalgic, dense/dark for overwhelmed +
   conflicted, sharp for determined. */
.chip.mood-chip.mood-grateful      { background: rgba(250,204,21,0.22);  border-color: #facc15; }
.chip.mood-chip.mood-tender        { background: rgba(244,114,182,0.20); border-color: #f472b6; }
.chip.mood-chip.mood-proud         { background: rgba(217,119,6,0.22);   border-color: #d97706; }
.chip.mood-chip.mood-amused        { background: rgba(253,224,71,0.22);  border-color: #fde047; }
.chip.mood-chip.mood-playful       { background: rgba(236,72,153,0.22);  border-color: #ec4899; }
.chip.mood-chip.mood-determined    { background: rgba(220,38,38,0.22);   border-color: #dc2626; }
.chip.mood-chip.mood-hopeful       { background: rgba(134,239,172,0.22); border-color: #86efac; }
.chip.mood-chip.mood-nostalgic     { background: rgba(202,138,4,0.20);   border-color: #ca8a04; }
.chip.mood-chip.mood-overwhelmed   { background: rgba(100,116,139,0.24); border-color: #64748b; }
.chip.mood-chip.mood-conflicted    { background: rgba(168,85,247,0.20);  border-color: #a855f7; }
.chip.mood-chip.mood-calm          { background: rgba(148,163,184,0.20); border-color: #94a3b8; }

/* v0.101.x — Site Mood Tint. The dashboard section gets a subtle
   radial-gradient overlay whose hue matches the predominant mood
   across non-system agents. Same color family as the chip palette
   above but ~10% alpha (vs the chips' ~22%) so the tint reads as
   ambient atmosphere, not as a chip-shaped UI element. Operators
   sense the agency's vibe before scanning the widgets.

   Base rule: drives the gradient from the upper-right corner using
   a CSS variable. 20 per-mood rules set the variable. No
   pseudo-element + no transform → no animation cost; respects
   prefers-reduced-motion implicitly (no motion to suppress).
   Gradient fades to transparent by 60% so the lower-left page area
   stays neutral and existing card backgrounds aren't recolored. */
.dashboard.mood-tinted {
    background-image: radial-gradient(
        ellipse 75% 60% at 80% -10%,
        var(--mood-tint-color, transparent) 0%,
        transparent 60%);
    background-repeat: no-repeat;
    background-attachment: local;
}
.dashboard.mood-tint-happy         { --mood-tint-color: rgba(251,191,36,0.10); }
.dashboard.mood-tint-inspired      { --mood-tint-color: rgba(56,189,248,0.10); }
.dashboard.mood-tint-worried       { --mood-tint-color: rgba(167,139,250,0.10); }
.dashboard.mood-tint-excited       { --mood-tint-color: rgba(251,113,133,0.10); }
.dashboard.mood-tint-melancholy    { --mood-tint-color: rgba(125,211,252,0.10); }
.dashboard.mood-tint-curious       { --mood-tint-color: rgba(192,132,252,0.10); }
/* v0.101.x audit (ux MED #1) — frustrated tint shifted off pure
   red (rgba 239,68,68) to amber-brown so the dashboard tint never
   overlaps the .alert.error palette (rgba 239,91,91,0.15). The
   chip variant stays red — chips have explicit borders + labels +
   are small + scoped, so the reflex "red full-bleed = something
   broke" doesn't fire. The tint is full-width ambient, so it
   needs its own register. */
.dashboard.mood-tint-frustrated    { --mood-tint-color: rgba(180,83,9,0.10); }
.dashboard.mood-tint-energized     { --mood-tint-color: rgba(132,204,22,0.10); }
.dashboard.mood-tint-contemplative { --mood-tint-color: rgba(94,234,212,0.10); }
.dashboard.mood-tint-grateful      { --mood-tint-color: rgba(250,204,21,0.10); }
.dashboard.mood-tint-tender        { --mood-tint-color: rgba(244,114,182,0.10); }
.dashboard.mood-tint-proud         { --mood-tint-color: rgba(217,119,6,0.10); }
.dashboard.mood-tint-amused        { --mood-tint-color: rgba(253,224,71,0.10); }
.dashboard.mood-tint-playful       { --mood-tint-color: rgba(236,72,153,0.10); }
.dashboard.mood-tint-determined    { --mood-tint-color: rgba(220,38,38,0.10); }
.dashboard.mood-tint-hopeful       { --mood-tint-color: rgba(134,239,172,0.10); }
.dashboard.mood-tint-nostalgic     { --mood-tint-color: rgba(202,138,4,0.10); }
.dashboard.mood-tint-overwhelmed   { --mood-tint-color: rgba(100,116,139,0.10); }
.dashboard.mood-tint-conflicted    { --mood-tint-color: rgba(168,85,247,0.10); }
.dashboard.mood-tint-calm          { --mood-tint-color: rgba(148,163,184,0.10); }

/* v0.81.x — streak emphasis. When the agent reinforces the same
   mood across multiple set-mood calls, the chip can apply
   `.mood-streak-deep` (streak >= 3) for a saturated edge so the
   operator sees the "deepening" at a glance. The template flips
   the class based on agent.moodStreakCount. */
.chip.mood-chip.mood-streak-deep {
    box-shadow: inset 0 0 0 1px currentColor;
    font-weight: 600;
}

/* v0.87.0 — truth-arc dashboard chips. Four tilt classifications +
   two cadence states. Color choices mirror the existing
   .alert.error / .alert.success / .alert.info palette so the operator
   reads them with the same semantic charge they already know. */
.chip-tilt-shame {
    background: rgba(239, 91, 91, 0.15);
    color: var(--danger);
    border-color: rgba(239, 91, 91, 0.3);
}
.chip-tilt-pride {
    background: rgba(74, 222, 128, 0.12);
    color: var(--success);
    border-color: rgba(74, 222, 128, 0.3);
}
.chip-tilt-balanced {
    background: rgba(91, 157, 255, 0.12);
    color: var(--accent);
    border-color: rgba(91, 157, 255, 0.3);
}
.chip-tilt-quiet {
    background: rgba(148, 163, 184, 0.12);
    color: var(--muted);
    border-color: rgba(148, 163, 184, 0.3);
}
.chip-cadence-active {
    background: rgba(74, 222, 128, 0.12);
    color: var(--success);
    border-color: rgba(74, 222, 128, 0.3);
}
.chip-cadence-lapsed {
    background: rgba(239, 91, 91, 0.15);
    color: var(--danger);
    border-color: rgba(239, 91, 91, 0.3);
}

/* v0.87.1 (post-audit ux #3) — spacing utilities for the truth-arc
   dashboard. Replace the inline `style="margin-..."` attributes the
   template carried at first ship. Names scoped to truth-arc-* so
   they don't collide with future utility-class additions elsewhere. */
.truth-arc-table { margin-top: 0.5rem; }
.truth-arc-card-note { margin-top: 0.5rem; }
.truth-arc-chip-note { margin-left: 0.4rem; }
.truth-arc-footer { margin-top: 1rem; }

/* v0.33.0 — Feeling Alive settings: when the master toggle is OFF
   the per-feature card renders subdued so the operator can read
   what would happen but visibly understands the controls aren't
   live. Same opacity treatment as disabled buttons elsewhere. */
.card.disarmed {
    opacity: 0.6;
}
.card.disarmed h3 { color: #94a3b8; }

/* v0.137.1 — keep the Save row at full opacity inside a disarmed
   card. Without this, the now-always-live Save button visually
   reads as still-disabled (the disarmed card halves everything
   inside to 0.6 opacity) and the post-fix point of the always-
   live Save is lost. Use this class on form-actions rows whose
   submit must remain visually inviting even in a disarmed
   container. */
.card.disarmed .form-actions.form-actions--live {
    opacity: 1;
}

/* v0.137.1 — color-coded on/off badges for the Travel partial-
   state warning on the Feeling Alive settings page. Italic-only
   wasn't legible at a glance when scanning which granular flag
   was the odd one out. */
.travel-on  { color: #1a7f37; }
.travel-off { color: #94a3b8; }

/* v0.139.0 — dashboard delight animations.

   .is-nudging: idle nudge on the primary "+ New run" button after
   90s of inactivity. A single soft glow-pulse, ~1.4s, then resets.
   Hidden under prefers-reduced-motion via the JS gate, no CSS
   media-query needed.

   .moon-cycling: visual hint on the footer moon while it's
   playing through the 7-phase preview. Tiny opacity dip so the
   user sees the click registered.

   .mochi-wiggle: small ack on the cat ticker icon when an
   operator types "mochi" on a site with no rotation buffer. */
@keyframes am2-nudge-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(167, 139, 250, 0); }
    50%      { box-shadow: 0 0 0 8px rgba(167, 139, 250, 0.18); }
}
.is-nudging {
    animation: am2-nudge-pulse 1.4s ease-out;
}

.moon-cycling { opacity: 0.85; }

@keyframes am2-mochi-wiggle {
    0%, 100% { transform: rotate(0deg); }
    25%      { transform: rotate(-12deg); }
    75%      { transform: rotate(12deg); }
}
.mochi-wiggle {
    display: inline-block;
    animation: am2-mochi-wiggle 0.7s ease-in-out;
}

/* Digest page rows. Compact + scannable — operator opens the page,
   reads down the column. */
.digest-agent-list, .digest-dream-list {
    list-style: none;
    padding-left: 0;
    margin: 0;
}
.digest-agent-row, .digest-dream-row {
    padding: 0.8rem 0;
    border-bottom: 1px solid rgba(148,163,184,0.15);
}
.digest-agent-row:last-child, .digest-dream-row:last-child {
    border-bottom: none;
}
.digest-agent-header, .digest-dream-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.4rem;
}
.digest-agent-name {
    font-weight: 600;
}
.digest-row {
    margin: 0.2rem 0;
    font-size: 0.95rem;
}
.digest-label {
    display: inline-block;
    min-width: 7rem;
    color: #94a3b8;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.digest-dream-body {
    margin: 0;
    font-style: italic;
}

.participant-list {
    list-style: decimal inside;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
}
.participant-list li { display: inline-flex; align-items: baseline; gap: 0.4rem; }

/* Multi-agent picker on the group-chats/new form. Wrapping flex so labels
   pack left-to-right and stay left-aligned regardless of how wide the
   container is — an earlier grid+1fr layout was stretching cells and
   floating each checkbox in the middle of its share, which read weirdly
   when there were only 2-3 agents. */
.agent-checklist {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    padding: 0.6rem 0.75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 4px;
}
.agent-check {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.2rem 0;
    cursor: pointer;
}
.agent-check input { margin: 0; }
.agent-check-name { color: var(--text); }

/* Inline attachments on agent messages (CHAT_SHARE_FILE) */
.chat-attachments {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.6rem;
}
.chat-attachment {
    margin: 0;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    background: var(--surface-2);
    overflow: hidden;
}
.chat-attachment.image img {
    display: block;
    max-width: 100%;
    height: auto;
}
.chat-attachment-caption {
    padding: 0.4rem 0.6rem;
    font-size: 0.8rem;
    color: var(--muted);
    border-top: 1px solid var(--border);
}
.chat-attachment.text-file summary {
    list-style: none;
    cursor: pointer;
    padding: 0.4rem 0.6rem;
    font-size: 0.85rem;
    color: var(--text);
}
.chat-attachment.text-file summary::-webkit-details-marker { display: none; }
.chat-attachment.text-file summary::before { content: "📎  "; }
.chat-attachment.text-file[open] summary::before { content: "📂  "; }
.chat-attachment.text-file iframe {
    width: 100%;
    height: 220px;
    border: 0;
    border-top: 1px solid var(--border);
    background: var(--surface);
}
.chat-attachment.document iframe {
    width: 100%;
    height: 480px;
    border: 0;
    background: var(--surface);
    display: block;
}
.chat-attachment.document .chat-attachment-caption {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.6rem;
}
.chat-attachment-open {
    font-size: 0.78rem;
    white-space: nowrap;
}

.chip.sev-info    { background: rgba(91, 157, 255, 0.15); color: var(--accent); border-color: rgba(91, 157, 255, 0.3); }
.chip.sev-warning { background: rgba(245, 158, 11, 0.18); color: #f59e0b; border-color: rgba(245, 158, 11, 0.4); }
.chip.sev-error   { background: rgba(239, 91, 91, 0.18); color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }

/* Aliases so the persona-chip Thymeleaf fragment can use the uniform
   `<kind-slug>-kind` class on every kind without the templates having
   to special-case STANDARD (plain chip) or HACKER (amber-warning). */
.chip.standard-kind { /* falls through to base .chip — neutral grey, the right read for "no specialty". */ }
.chip.hacker-kind   { background: rgba(245, 158, 11, 0.18); color: #f59e0b; border-color: rgba(245, 158, 11, 0.4); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }

/* Persona kind chips:
   - HACKER reuses sev-warning (amber) — security testing → caution.
   - CODER gets its own violet so it reads as a distinct concept from
     status / severity / seat / chain. Light enough to coexist with the
     surrounding text, saturated enough to stand apart at a glance.
   - RESEARCHER (rose) and PLANNER (cyan) round out the agent-team
     palette: warm rose for the curious/exploratory researcher, cool
     cyan for the contemplative planner. Distinct from amber (HACKER),
     violet (CODER), seat-rainbow, status greens, severity yellow/red,
     and chain teals. */
.chip.coder-kind {
    background: rgba(167, 139, 250, 0.18);
    color: #a78bfa;
    border-color: rgba(167, 139, 250, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.researcher-kind {
    background: rgba(251, 113, 133, 0.18);
    color: #fb7185;
    border-color: rgba(251, 113, 133, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.planner-kind {
    background: rgba(34, 211, 238, 0.18);
    color: #22d3ee;
    border-color: rgba(34, 211, 238, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.web-tester-kind {
    /* Lime — "test pass" green, more saturated than the success chip so the
       persona-kind reading is unambiguous. */
    background: rgba(163, 230, 53, 0.18);
    color: #a3e635;
    border-color: rgba(163, 230, 53, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.mobile-tester-kind {
    /* Orange — warm, "device / physical hardware" feel. Distinct from amber
       (HACKER) and from rose (RESEARCHER). */
    background: rgba(251, 146, 60, 0.18);
    color: #fb923c;
    border-color: rgba(251, 146, 60, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.technical-writer-web-kind {
    /* Teal — "publishing / outward-facing" feel. Distinct from cyan
       (PLANNER) and from any blue used elsewhere. */
    background: rgba(45, 212, 191, 0.18);
    color: #2dd4bf;
    border-color: rgba(45, 212, 191, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.technical-writer-backend-kind {
    /* Indigo — "engineering documentation / internal-facing" feel.
       Pairs with violet (CODER) without colliding (different hue family). */
    background: rgba(129, 140, 248, 0.18);
    color: #818cf8;
    border-color: rgba(129, 140, 248, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.designer-kind {
    /* Fuchsia — vivid + creative, distinct from the cooler indigo / cyan /
       teal cluster and from the warm rose (RESEARCHER) / orange
       (MOBILE_TESTER) / amber (HACKER) cluster. Reads as "creative work"
       at a glance. */
    background: rgba(232, 121, 249, 0.18);
    color: #e879f9;
    border-color: rgba(232, 121, 249, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.reviewer-kind {
    /* Slate — neutral, "objective judgment" feel. The original plan had
       amber-yellow but it sat too close to HACKER's amber and to status
       sev-warning; slate is visually distinct from every saturated chip
       in the palette and semantically right for a "second opinion" role. */
    background: rgba(148, 163, 184, 0.18);
    color: #cbd5e1;
    border-color: rgba(148, 163, 184, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.narrator-kind {
    /* Gold — voice / spotlight / announcement feel. Distinct from amber
       (HACKER) by hue (more yellow, less orange) and from lime (WEB_TESTER)
       by saturation (warmer, less green). */
    background: rgba(250, 204, 21, 0.18);
    color: #facc15;
    border-color: rgba(250, 204, 21, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.composer-kind {
    /* Pink/magenta — vibrant, creative, distinct from rose (RESEARCHER)
       and fuchsia (DESIGNER) by hue position (cooler than rose, warmer
       than fuchsia). Reads as "music / creative production" alongside
       the gold NARRATOR for a coherent audio-family pairing. */
    background: rgba(244, 114, 182, 0.18);
    color: #f472b6;
    border-color: rgba(244, 114, 182, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.producer-kind {
    /* Red-600 — record button / "ON AIR" lamp / clapboard slate.
       Distinct from amber HACKER (severity-warning shape, more orange),
       gold NARRATOR, pink COMPOSER, and orange MOBILE_TESTER. The
       severity-error chip lives in the .chip.sev-* namespace so the
       two never visually overlap. Reads as "video production /
       direction" capping the audio-family trio (NARRATOR gold ->
       COMPOSER pink -> PRODUCER red). */
    background: rgba(220, 38, 38, 0.18);
    color: #ef4444;
    border-color: rgba(220, 38, 38, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.ethical-reviewer-kind {
    /* Emerald-500 — "trust / good standing / green light." Distinct from
       slate REVIEWER (peer critique that feeds the chain), and well clear
       of every other -kind hue. Reads as "the ethics audit verdict you
       want to come back green." */
    background: rgba(16, 185, 129, 0.18);
    color: #10b981;
    border-color: rgba(16, 185, 129, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.legal-reviewer-kind {
    /* Blue-500 with a navy-600 border — courthouse / legal pad
       association. The brighter blue text reads cleanly against the
       dark backdrop where pure navy-600 sat too dim, and the darker
       border keeps the "official document" weight. Distinct from cyan
       PLANNER (lighter, greener) and indigo TECHNICAL_WRITER_BACKEND
       (more violet). */
    background: rgba(59, 130, 246, 0.18);
    color: #3b82f6;
    border-color: rgba(30, 64, 175, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.data-analyst-kind {
    /* Amber-deep (amber-600) — spreadsheet / pivot-table feel.
       Distinct from amber HACKER (sev-warning, more orange) and from
       the pure-yellow gold NARRATOR. Reads as "looking at the
       numbers" — the only kind whose tools talk to live data. */
    background: rgba(217, 119, 6, 0.18);
    color: #d97706;
    border-color: rgba(217, 119, 6, 0.45);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.devops-kind {
    /* Slate-blue (slate-500/600) — server-rack / metal feel. Distinct
       from slate REVIEWER (lighter, cooler) and from the indigo
       TECHNICAL_WRITER_BACKEND (more violet). Reads as "infrastructure
       / systems" — the only kind whose tools shell out to k8s/cloud
       CLIs. */
    background: rgba(71, 85, 105, 0.30);
    color: #94a3b8;
    border-color: rgba(71, 85, 105, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.project-manager-kind {
    /* Lime-deep (lime-600) — project-board green / "shipping"
       association. Distinct from lime WEB_TESTER (lime-400, brighter)
       and from emerald ETHICAL_REVIEWER (greener / cooler). Reads as
       "moving the work forward" — the only kind whose primary job is
       orchestrating other agents' output via the chain. */
    background: rgba(101, 163, 13, 0.20);
    color: #84cc16;
    border-color: rgba(101, 163, 13, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.chief-of-staff-kind {
    /* Violet (violet-500) — coordinator hue; system_owned, sits above
       the kinds that actually produce output. Distinct from the
       project-manager green (lime) so the operator's eye separates the
       site-level chief from per-project orchestrators. */
    background: rgba(139, 92, 246, 0.20);
    color: #c4b5fd;
    border-color: rgba(139, 92, 246, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.project-lead-kind {
    /* Indigo (indigo-500) — paired with violet for the two
       coordinator kinds; cooler than violet to read as "scoped to
       one project" vs. the chief's "across all projects." */
    background: rgba(99, 102, 241, 0.20);
    color: #a5b4fc;
    border-color: rgba(99, 102, 241, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Persona-list filter bar + per-kind sections.
   The filter bar is a single horizontal strip of checkboxes with two
   bulk-toggle link buttons. Persona-kind sections each carry a colored
   chip in the heading (matching the chip family above) so an operator can
   scan the page at a glance, with the count next to it for context. */
.persona-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.85rem;
    padding: 0.85rem 1rem;
}
/* v0.55.5 — operator-visible diagnostic for persona-filter.js.
   Color-codes the .pf-diag dot based on data-pf-status on the
   parent #persona-filter section:
   - red ("not-loaded"): script never ran (404? blocked?)
   - amber ("booting"): script started but didn't finish
   - amber ("checkboxes-found"): bound to checkboxes but didn't reach end
   - green ("ready"): all handlers bound; clicks should toggle
   - red ("error"): something went wrong (hover for detail)
   Hidden by default; the .pf-diag span shows when JS sets
   data-pf-status to anything other than the default. */
.persona-filter-bar .pf-diag {
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    background: var(--danger);   /* red — "not-loaded" default */
    flex-shrink: 0;
}
.persona-filter-bar[data-pf-status="ready"] .pf-diag {
    background: #4ade80;   /* green */
}
.persona-filter-bar[data-pf-status="fallback-inline"] .pf-diag {
    /* v0.55.6 — distinct color (cyan) signals "external script
       didn't load; inline backup running." The filter still works
       but indicates an unresolved load issue worth investigating. */
    background: #22d3ee;
}
.persona-filter-bar[data-pf-status="booting"] .pf-diag,
.persona-filter-bar[data-pf-status="checkboxes-found"] .pf-diag {
    background: #f59e0b;   /* amber */
}
.persona-filter-bar[data-pf-status="error"] .pf-diag {
    background: var(--danger);
}
.persona-filter-bar .filter-label {
    font-weight: 600;
    color: var(--muted);
}
.persona-filter-bar .filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    cursor: pointer;
    user-select: none;
}
.persona-filter-bar .filter-toggle input[type="checkbox"] {
    cursor: pointer;
    margin: 0;
}
.persona-filter-bar .link-btn.small {
    /* Push the bulk-toggle buttons to the trailing edge so they sit apart
       from the per-kind toggles. flex-grow on the spacer would be more
       elegant; this keeps the markup minimal. */
    margin-left: auto;
    font-size: 0.85rem;
}
.persona-filter-bar .link-btn.small + .link-btn.small {
    margin-left: 0.6rem;
}

.persona-kind-section {
    margin-top: 1.2rem;
}
.persona-kind-section:first-of-type {
    margin-top: 0.4rem;
}
.persona-kind-heading {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 0.5rem 0;
    font-size: 1rem;
    font-weight: 600;
}
.persona-kind-heading .chip {
    /* The heading chip is the section label, so let the chip's own
       text-transform handle casing — but bump padding slightly so the chip
       reads more like a heading than an inline tag. */
    padding: 0.2rem 0.6rem;
}

/* Agents list — fixed column widths so every per-kind table aligns
   column-for-column down the page. table-layout: fixed honors the
   widths exactly instead of letting the browser auto-size based on
   each table's own content (which made the persona column drift
   wider for kinds with longer persona names). */
table.agent-list-table {
    table-layout: fixed;
}
table.agent-list-table .col-name    { width: 32%; }
table.agent-list-table .col-persona { width: 26%; }
table.agent-list-table .col-model   { width: 22%; }
table.agent-list-table .col-status  { width: 8%;  }
table.agent-list-table .col-actions { width: 12%; }

/* Personas list — same fixed-width treatment so the per-kind tables
   (and the templates section below them) all line up column-for-column.
   Description gets the lion's share since it's the wide-text column;
   knowledge-level is a single chip and stays narrow. */
table.persona-list-table {
    table-layout: fixed;
}
table.persona-list-table .col-name        { width: 24%; }
table.persona-list-table .col-knowledge   { width: 14%; }
table.persona-list-table .col-description { width: 52%; }
table.persona-list-table .col-actions     { width: 10%; }
table.persona-list-table td {
    overflow-wrap: anywhere;
}
/* Cells need text wrapping so a long agent name / description doesn't
   blow out the fixed column width (overflow would otherwise be hidden
   by table-layout: fixed). */
table.agent-list-table td {
    overflow-wrap: anywhere;
}

/* Sortable column headers — clickable, with an arrow indicator that
   appears on the active sort column. The arrow is a pure CSS character
   in a ::after so we don't need any image assets. */
.data-table th.sortable {
    cursor: pointer;
    user-select: none;
    position: relative;
    padding-right: 1.1rem;
}
.data-table th.sortable:hover { color: var(--text); }
.data-table th.sortable::after {
    content: "↕";
    position: absolute;
    right: 0.4rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.7rem;
    opacity: 0.35;
}
.data-table th.sortable[data-sort-active="true"]::after {
    opacity: 0.95;
    color: var(--accent);
}
.data-table th.sortable[data-sort-direction="asc"]::after  { content: "▲"; }
.data-table th.sortable[data-sort-direction="desc"]::after { content: "▼"; }

/* Pagination controls below the data viewer's table page. Plain-text
   prev/next links separated by middle dots — matches the
   "low-chrome" feel of the rest of the operator UI. */
.pagination {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 1rem 0;
    flex-wrap: wrap;
    font-size: 0.9rem;
}
.pagination a {
    color: var(--accent);
    text-decoration: none;
    padding: 0.2rem 0.45rem;
    border-radius: 3px;
}
.pagination a:hover { background: var(--surface-2); }
.pagination .muted { color: var(--muted); }

/* JSONB cells in the data viewer. The table view truncates inline;
   the row detail page renders the full payload in a <pre>. */
.json-cell code,
.json-cell pre {
    font-family: ui-monospace, monospace;
    font-size: 0.8rem;
    color: var(--text);
    background: var(--surface-2);
    border-radius: 3px;
    padding: 0.15rem 0.35rem;
    white-space: pre-wrap;
    word-break: break-word;
}
pre.json-cell {
    padding: 0.6rem 0.75rem;
    max-height: 24rem;
    overflow: auto;
}

/* Matrix-style rain backdrop on the run detail page for HACKER / CODER
   personas mid-run. Fixed-position behind the content; pointer-events
   disabled so the page is fully usable. Low opacity keeps text readable.
   matrix-rain.js starts/stops the animation based on the run-status chip's
   class — when SSE flips the chip out of .status-running, the script fades
   the canvas to black. */
#matrix-rain {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    /* Bumped from 0.18 → 0.32 so the green reads more prominently. The page
       text is still very legible above it (page surfaces are dark and have
       their own background) but the rain is now an active visual element
       rather than a faint suggestion. */
    opacity: 0.32;
}
/* Hoist subsequent siblings above the canvas without making every parent
   its own stacking context. */
#matrix-rain ~ * {
    position: relative;
    z-index: 1;
}

/* Constellation-drift backdrop on the run detail page for RESEARCHER /
   PLANNER personas mid-run. Same lifecycle / stacking story as matrix-rain
   — fixed-position behind the content, pointer-events disabled. Slightly
   lower opacity than matrix-rain (0.30 vs 0.32) because the line-graph
   visual gets dense enough that even a hair more saturation starts
   crowding the page text. The node color is set per-kind via the canvas's
   data-color attribute (rose for RESEARCHER, cyan for PLANNER) and read
   by thinking-bg.js. */
#thinking-bg {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.30;
}
#thinking-bg ~ * {
    position: relative;
    z-index: 1;
}

/* Sine-wave ribbons backdrop on the run detail page for the audio family
   (COMPOSER pink, PRODUCER red, NARRATOR gold). Same fixed-position +
   pointer-events-disabled stacking story as matrix-rain / thinking-bg.
   Slightly higher opacity than the constellation effect — the ribbons
   are sparser pixels-per-area, so the visual reads at similar density. */
#studio-waveforms {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.36;
}
#studio-waveforms ~ * {
    position: relative;
    z-index: 1;
}

/* Drifting legal-document text backdrop for the reviewer family
   (ETHICAL_REVIEWER emerald, LEGAL_REVIEWER blue). Lower opacity than
   the others — serif text at high alpha competes with page text for
   attention; this density is enough to evoke "case-law reading" without
   pulling focus. */
#legal-research {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.55;
}
#legal-research ~ * {
    position: relative;
    z-index: 1;
}

/* Pulsing test-results grid backdrop for the tester family (WEB_TESTER
   lime, MOBILE_TESTER orange). Mid-range opacity — the grid cells are
   small and their lit-then-dimmed cycle is short, so a slightly punchier
   alpha keeps the "CI dashboard" reading legible. */
#test-matrix {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.42;
}
#test-matrix ~ * {
    position: relative;
    z-index: 1;
}

/* Drifting Pantone-style color-chip backdrop for DESIGNER personas
   mid-run. Same fixed-position + pointer-events-disabled stacking
   story as the others. Higher opacity (0.40) than the constellation
   effects because the swatches are sparse and pastel — at 0.30 the
   visual reads as muddy gray smudges; 0.40 lands the "palette pull"
   metaphor without crowding the page text. */
#palette-swatch {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.40;
}
#palette-swatch ~ * {
    position: relative;
    z-index: 1;
}

/* Typed-text backdrop for the writers + reviewer
   (TECHNICAL_WRITER_WEB sky, TECHNICAL_WRITER_BACKEND amber, REVIEWER red).
   Higher opacity than the visual effects because typed text at low
   alpha turns into illegible smudges — 0.55 is high enough to read
   "drafting" without competing with page text (the rows are sparse
   and short by design). */
#typewriter {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.55;
}
#typewriter ~ * {
    position: relative;
    z-index: 1;
}

/* Sparkline-grid backdrop for DATA_ANALYST personas mid-run. Lower
   opacity than the typewriter (data viz reads at lower density) and
   matches the "live dashboard refresh" metaphor — the bars are small
   enough to feel like distant chart tiles, not a data wall. */
#data-pulse {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 0;
    pointer-events: none;
    opacity: 0.38;
}
#data-pulse ~ * {
    position: relative;
    z-index: 1;
}

/* Run status chips — applied wherever a run's status is shown (run detail header,
   run list, agent detail recent-runs table). RUNNING chips share the same subtle
   pulse as the live-badge so an in-progress run reads at a glance. */
.chip.status-running   {
    background: rgba(91, 157, 255, 0.2);
    color: var(--accent);
    border-color: rgba(91, 157, 255, 0.4);
    animation: live-pulse var(--motion-pulse-duration) ease-in-out infinite;
}

/* Non-pulsing steady-count badge. Same shape as a chip — call out a
   number (pending approvals, broken-agent count) without claiming
   "something is currently running." Inherits the chip background
   shape; the caller can override the hue with --chip-hue if needed. */
.chip.count-badge {
    background: var(--surface-2);
    color: var(--text-soft);
    border-color: var(--border);
    font-weight: 600;
}
.chip.status-pending   { background: var(--surface-2); color: var(--muted); }
.chip.status-succeeded { background: rgba(74, 222, 128, 0.18); color: var(--success); border-color: rgba(74, 222, 128, 0.4); }
.chip.status-failed    { background: rgba(239, 91, 91, 0.18); color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }
.chip.status-cancelled { background: var(--surface-2); color: var(--muted); }
/* v0.180.0 — operator-scheduled run waiting for its dispatch time.
   Amber/warn aesthetic; distinct from the muted PENDING chip so
   "scheduled" is legible at a glance on the runs list. */
.chip.status-queued    { background: rgba(251, 191, 36, 0.18); color: #fbbf24; border-color: rgba(251, 191, 36, 0.4); }
/* v0.185.4 — agent-requested free-time run badge (next to the status
   chip on each free-time row). Distinct purple lane so it does not
   collide with the amber Queued chip when both apply. Matches the
   token-rgba pattern of the other status chips. */
.chip.status-freetime  { background: rgba(168, 85, 247, 0.18); color: #c084fc; border-color: rgba(168, 85, 247, 0.4); }

/* v0.180.0 — schedule-picker wrapper. Hidden state uses the [hidden]
   attribute (toggled by the inline IIFE) rather than inline display:none
   so a11y tooling sees it consistently. */
.scheduled-picker-wrap { margin-top: 0.5rem; }
.scheduled-picker-wrap[hidden] { display: none; }

/* Live badge on the run detail page — hidden until SSE connects, then pulses softly.
   .chip sets display:inline-block, which beats UA's [hidden]{display:none} at
   equal specificity; explicit override needed so el.hidden=true actually hides it. */
.chip.live-badge[hidden] { display: none; }
.chip.live-badge {
    background: rgba(74, 222, 128, 0.18);
    color: var(--success);
    border-color: rgba(74, 222, 128, 0.4);
    margin-left: 0.4rem;
    animation: live-pulse var(--motion-pulse-duration) ease-in-out infinite;
}
@keyframes live-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* Auto-chain lineage chips on the run detail page header. Teal family so they
   read as a distinct concept from run-status (green/red), severity (yellow/red),
   and seat-color (the rainbow set). Three variants: chain-parent (looking back),
   chain-pending (waiting to fire), chain-next (already fired, forward link). */
.chain-meta {
    margin: 0.3rem 0 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.chip.chain-parent  {
    background: rgba(45, 212, 191, 0.14);
    color: #2dd4bf;
    border-color: rgba(45, 212, 191, 0.4);
}
.chip.chain-pending {
    background: rgba(45, 212, 191, 0.10);
    color: #5eead4;
    border-color: rgba(45, 212, 191, 0.3);
    font-style: italic;
}
.chip.chain-next {
    background: rgba(45, 212, 191, 0.18);
    color: #2dd4bf;
    border-color: rgba(45, 212, 191, 0.5);
    font-weight: 600;
}
/* "Then: A → B → C" — the planned (not yet running) chain steps. Lighter
   teal than chain-pending to signal "queued behind the immediate hand-off." */
.chip.chain-planned {
    background: rgba(45, 212, 191, 0.08);
    color: #5eead4;
    border-color: rgba(45, 212, 191, 0.25);
}
/* N-step chain form rows. Each .chain-step is one operator-defined hop;
   the subtle border + spacing reads as "discrete unit" without competing
   with the surrounding fieldset legend. */
.chain-step {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.6rem 0.75rem;
    margin-top: 0.6rem;
    background: rgba(255, 255, 255, 0.015);
}
.chain-step-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.4rem;
}
.chain-step-number {
    font-size: 0.8rem;
    color: var(--muted);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
/* Collapsed Tools card on the agent detail page. The <details>/<summary>
   default puts the disclosure marker beside the <h2>; align the marker so
   it reads as a header decoration rather than a stray triangle. The list
   itself is a flex-wrap chip cloud — much denser than the wide rows used
   on the edit page. */
.tool-summary-details > summary {
    cursor: pointer;
    list-style: none;
}
.tool-summary-details > summary::-webkit-details-marker {
    display: none;
}
.tool-summary-details > summary > h2 {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0;
}
.tool-summary-details > summary > h2::before {
    content: '▸';
    font-size: 0.8em;
    color: var(--muted);
    transition: transform 0.15s ease;
    display: inline-block;
}
.tool-summary-details[open] > summary > h2::before {
    transform: rotate(90deg);
}
.tool-summary-details[open] > summary {
    margin-bottom: 0.6rem;
}
/* Tools card: per-category sections with their own header + chip flow.
   Section spacing is generous enough to read as discrete groups; each
   category's chips are colored to match its theme so an operator can spot
   "wait, why does this agent have SECURITY tools?" at a glance without
   reading codes one by one. */
.tool-categories {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}
.tool-category-block {
    /* No card-within-card framing — the parent details body provides all
       the visual grouping needed. */
}
.tool-category-header {
    margin: 0 0 0.4rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.tool-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}
/* Tool chip base — title= attribute on each surfaces the tool description
   as a native browser tooltip; cursor: help signals the hover affordance. */
.chip.tool-chip {
    cursor: help;
}
/* Per-category palette. Each chip shares the same shape; only the color
   triplet (background tint, foreground, border) differs. Hues chosen so
   each category is visually distinct from the run-status / severity /
   seat / chain palettes already on the page. */
.chip.tool-chip.cat-web {
    background: rgba(91, 157, 255, 0.10);
    color: #93c5fd;
    border-color: rgba(91, 157, 255, 0.3);
}
.chip.tool-chip.cat-api {
    background: rgba(74, 222, 128, 0.10);
    color: #86efac;
    border-color: rgba(74, 222, 128, 0.3);
}
.chip.tool-chip.cat-files {
    background: rgba(251, 146, 60, 0.10);
    color: #fdba74;
    border-color: rgba(251, 146, 60, 0.3);
}
.chip.tool-chip.cat-utility {
    background: var(--surface-2);
    color: var(--muted);
    border-color: var(--border);
}
.chip.tool-chip.cat-security {
    background: rgba(245, 158, 11, 0.12);
    color: #fbbf24;
    border-color: rgba(245, 158, 11, 0.35);
}
.chip.tool-chip.cat-code {
    background: rgba(167, 139, 250, 0.10);
    color: #c4b5fd;
    border-color: rgba(167, 139, 250, 0.3);
}

/* Inline form's collapsible chain section — keep the disclosure marker subtle. */
.chain-fold > summary {
    cursor: pointer;
    padding: 0.25rem 0;
    color: var(--muted);
    font-size: 0.85rem;
}
.chain-fold > summary:hover { color: var(--text); }

/* Probe result — model list */
.model-list {
    list-style: disc;
    margin: 0.6rem 0 0;
    padding-left: 1.5rem;
    columns: 2;
    column-gap: 1.5rem;
    font-size: 0.85rem;
}
.model-list li { margin: 0.15rem 0; break-inside: avoid; }
.model-list code {
    background: var(--surface-2);
    padding: 0.05rem 0.35rem;
    border-radius: 3px;
    font-size: 0.85em;
    color: var(--text);
}
@media (max-width: 600px) { .model-list { columns: 1; } }

/* Run timeline */
.event-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.event-row {
    display: grid;
    grid-template-columns: 80px 200px 1fr;
    column-gap: 0.75rem;
    align-items: start;
    padding: 0.5rem 0.75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    /* Widen the left edge to 3px so a per-event-type accent reads
       at a glance. Default color comes from the shorthand (var(--border));
       :has() rules below override per event type. */
    border-left-width: 3px;
    border-radius: 4px;
    /* v0.442.0 audit-pass — the run-image-strip's "see in timeline →"
     * anchor jumps the operator to #event-{N}-row. Without
     * scroll-margin-top, the row lands flush against the page chrome
     * (top-nav) and the event-time / event-type chips sit under it.
     * 4rem clears the chrome consistent with other anchored cards. */
    scroll-margin-top: 4rem;
}
.event-row:has(.chip.event-llm-request)  { border-left-color: #86efac; }
.event-row:has(.chip.event-llm-response) { border-left-color: #10b981; }
.event-row:has(.chip.event-tool-call)    { border-left-color: #fb923c; }
.event-row:has(.chip.event-tool-result)  { border-left-color: #d97706; }
.event-row:has(.chip.event-error)        { border-left-color: var(--danger); }
.event-row:has(.chip.event-finding)      { border-left-color: #facc15; }
.event-row:has(.chip.event-info)         { border-left-color: var(--accent); }
.event-row:has(.chip.event-observation)  { border-left-color: var(--muted); }
/* Empty-state line lives INSIDE the event-list so SSE appends are
   automatically siblings — when any event-row exists, hide the
   empty-state via :not(:only-child). Keeps the live append path JS-free. */
.event-list .empty-state {
    list-style: none;
    padding: 0.5rem 0.75rem;
    color: var(--muted);
    font-size: 0.85rem;
}
.event-list .empty-state:not(:only-child) { display: none; }

/* Chat-thread empty-state — same SSE-aware self-hiding shape as the
   event-list. A brand-new conversation has zero messages; without
   this, the operator lands on an empty card with just a reply form. */
.chat-thread .empty-state {
    list-style: none;
    padding: 1.5rem 0.75rem;
    color: var(--muted);
    font-size: 0.9rem;
    text-align: center;
}
.chat-thread .empty-state:not(:only-child) { display: none; }

/* Generic empty-state block — promoted from the .event-list local
   pattern so daily-open surfaces (inbox, personas, hologram-assets,
   fresh agent chat history, run-detail with no tool calls yet) can
   share one shape with optional icon + headline + body + CTA slots.
   Additive only: the existing .event-list .empty-state rules above
   keep their tight inline styling so live SSE appends still hide it
   automatically. Use this block standalone on surfaces that aren't
   inside an .event-list / not-bound-to-a-sibling-list context. */
.empty-state-block {
    text-align: center;
    padding: 2rem 1rem;
    color: var(--muted);
    border: 1px dashed var(--border);
    border-radius: 8px;
    background: var(--surface);
    margin: 0;
}
.empty-state-block .empty-state-icon {
    font-size: 2rem;
    display: block;
    margin-bottom: 0.5rem;
    opacity: 0.7;
    line-height: 1;
}
.empty-state-block .empty-state-headline {
    color: var(--text-soft);
    font-size: 1rem;
    margin: 0 0 0.4rem 0;
    font-weight: 600;
}
.empty-state-block .empty-state-body {
    font-size: 0.875rem;
    margin: 0 auto;
    max-width: 56ch;
    line-height: 1.5;
}
.empty-state-block .empty-state-cta {
    margin-top: 0.85rem;
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    flex-wrap: wrap;
}
.event-time { padding-top: 0.1rem; }
/* Vertical stack for event-type chip + (optional) tool-name sub-chip on TOOL_CALL rows. */
.event-tags {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.25rem;
}
.event-tags .chip { margin-right: 0; }
.event-payload {
    /* v0.402.1 — pin to col 3 of .event-row's grid. See the
       .event-file-links rule for the full rationale; pre-fix a
       TOOL_RESULT row with file links pushed the payload to
       row 2 col 1 (80px wide) and the operator saw the raw
       payload wrap vertically at ~1-2 chars per line. */
    grid-column: 3 / -1;
    font-family: ui-monospace, monospace;
    font-size: 0.8rem;
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0;
    color: var(--text);
    /* v0.386.0 — explicit dark background + padding. Pre-fix the
     * rule had no background-color, so text rendered against
     * whatever ancestor surface bled through, and selection
     * highlighting on dark themes drew a white box behind the
     * monospace text — operator-reported as "white background
     * fill in the text" on agent-created file location payloads.
     * --surface-2 keeps it visually distinct from the card body. */
    background: var(--surface-2);
    padding: 0.5rem 0.75rem;
    border-radius: 4px;
    max-height: 240px;
    overflow: auto;
}
/* v0.386.0 — keep selection legible on the dark theme. Without
 * this, the browser default uses a near-white background which
 * sandwiches the light --text on a near-white surface and reads
 * as illegible. Scoped to the timeline payload to avoid touching
 * the rest of the page's selection behavior. */
.event-payload::selection {
    background: var(--accent, #5b9dff);
    color: #ffffff;
}

/* v0.386.0 — visual class for tool-emitted artifact links. Used
 * by future template work (PR queued as follow-up) where the
 * run-detail timeline renders a clickable <a class="artifact-link">
 * pointing at ArtifactFileController's
 * /sites/{slug}/workspaces/{wsId}/artifacts/{artifactId}/file
 * (or the project-scoped variant). Defined here now so the JS
 * side can construct the link element without waiting on the
 * template surgery. */
.artifact-link {
    color: var(--accent, #5b9dff);
    text-decoration: underline;
}
.artifact-link:hover { text-decoration: underline; opacity: 0.9; }

/* Per-event-type chip colors. Paired hues so call/response sit visually
   together: mint→emerald for the LLM round-trip, light→deep orange for the
   tool round-trip. Yellow for findings, red for errors, blue for info,
   muted gray for observations. */
.chip.event-llm-request   { background: rgba(134, 239, 172, 0.18); color: #86efac;           border-color: rgba(134, 239, 172, 0.4); }
.chip.event-llm-response  { background: rgba(16, 185, 129, 0.18);  color: #10b981;           border-color: rgba(16, 185, 129, 0.4); }
.chip.event-tool-call     { background: rgba(251, 146, 60, 0.20);  color: #fb923c;           border-color: rgba(251, 146, 60, 0.45); }
.chip.event-tool-result   { background: rgba(217, 119, 6, 0.18);   color: #d97706;           border-color: rgba(217, 119, 6, 0.4); }
.chip.event-observation   { background: var(--surface-2);          color: var(--muted);      border-color: var(--border); }
.chip.event-finding       { background: rgba(250, 204, 21, 0.18);  color: #facc15;           border-color: rgba(250, 204, 21, 0.4); }
.chip.event-error         { background: rgba(239, 91, 91, 0.18);   color: var(--danger);     border-color: rgba(239, 91, 91, 0.4); }
.chip.event-info          { background: rgba(91, 157, 255, 0.10);  color: var(--accent);     border-color: rgba(91, 157, 255, 0.25); }

/* Tool-name sub-chip on TOOL_CALL rows. Solid purple by default so it reads
   as the agent's specific tool pick; deepens on hover to signal the
   description tooltip is available. */
.chip.tool-name-chip {
    background: rgba(167, 139, 250, 0.18);
    color: #a78bfa;
    border-color: rgba(167, 139, 250, 0.45);
    cursor: help;
    font-size: 0.7rem;
}
.chip.tool-name-chip:hover {
    background: rgba(139, 92, 246, 0.32);
    color: #8b5cf6;
    border-color: rgba(139, 92, 246, 0.7);
}

/* Agent plan entries (LOG_PLAN). One line of text per entry, prefixed with
   the timestamp it was logged at, accumulating in the run's Agent plan card. */
.plan-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    counter-reset: plan;
}
.plan-entry {
    counter-increment: plan;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-left: 3px solid #a78bfa;
    border-radius: 4px;
    padding: 0.5rem 0.75rem;
    display: grid;
    grid-template-columns: 80px 1fr;
    column-gap: 0.75rem;
    align-items: baseline;
}
.plan-entry::before {
    content: counter(plan) ".";
    font-size: 0.75rem;
    color: var(--muted);
    grid-column: 1;
    grid-row: 1;
    justify-self: end;
    align-self: baseline;
}
.plan-entry time { grid-column: 1; grid-row: 1; }
.plan-entry .plan-text {
    grid-column: 2;
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.45;
    color: var(--text);
}

/* Insight entries (LOG_INSIGHT) — observation + optional suggestion
   stacked, framed in a teal-ish accent so they read distinctly from the
   orange POC card and the purple plan card. */
.insight-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.insight-entry {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-left: 3px solid #22d3ee;
    border-radius: 4px;
    padding: 0.6rem 0.75rem;
}
.insight-entry time { display: block; margin-bottom: 0.25rem; }
.insight-observation {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.45;
    color: var(--text);
}
.insight-suggestion {
    margin: 0.4rem 0 0;
    padding-left: 0.6rem;
    border-left: 2px solid rgba(34, 211, 238, 0.35);
    font-size: 0.88rem;
    line-height: 1.4;
    color: var(--muted);
}
.insight-suggestion::before {
    content: "Suggest: ";
    font-weight: 600;
    color: #22d3ee;
}

/* POCs (LOG_POC tool) — accumulate live in the Findings & summary card. */
.poc-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    counter-reset: poc;
}
.poc-entry {
    counter-increment: poc;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-left-width: 3px;
    border-radius: 4px;
    padding: 0.6rem 0.75rem;
}
.poc-entry::before {
    content: "POC " counter(poc) ".";
    font-size: 0.75rem;
    color: var(--muted);
    margin-right: 0.4rem;
    font-weight: 600;
}
.poc-meta {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.4rem;
}
.poc-repro {
    font-family: ui-monospace, monospace;
    font-size: 0.82rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0.4rem 0.55rem;
    margin: 0 0 0.4rem 0;
    white-space: pre-wrap;
    word-break: break-all;
    color: var(--text);
}
.poc-impact {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.45;
    color: var(--text);
}

/* Severity chips for POC entries. Parallel scheme to the inbox sev-* classes. */
.chip.severity-low      { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.severity-medium   { background: rgba(245, 158, 11, 0.18); color: #f59e0b; border-color: rgba(245, 158, 11, 0.4); }
.chip.severity-high     { background: rgba(239, 91, 91, 0.18); color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }
/* Critical was the only solid-fill chip in the severity family, which
   broke the visual rhythm of the chip catalogue. Translucent rgba shape
   matches severity-high but with a denser fill + bolder weight + an
   inset highlight so it still reads as "this is critical." */
.chip.severity-critical {
    background: rgba(239, 91, 91, 0.32);
    color: var(--danger);
    border-color: rgba(239, 91, 91, 0.7);
    font-weight: 700;
    box-shadow: inset 0 0 0 1px rgba(239, 91, 91, 0.35);
}

/* Left-edge severity indicator on the POC entry itself. */
.poc-entry.severity-low      { border-left-color: var(--muted); }
.poc-entry.severity-medium   { border-left-color: #f59e0b; }
.poc-entry.severity-high     { border-left-color: var(--danger); }
.poc-entry.severity-critical { border-left-color: var(--danger); border-left-width: 5px; }

/* ===========================================================================
   Watchers (PR 1)
   =========================================================================== */

/* Lifecycle status chips for the watchers list + detail. Mirrors the
   run-status chip palette so operators have a consistent green/red/grey
   vocabulary across automation surfaces. */
.chip.status-active { background: rgba(74, 222, 128, 0.18); color: var(--success); border-color: rgba(74, 222, 128, 0.4); }
.chip.status-idle   { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.status-paused { background: var(--surface-2); color: var(--muted); border-color: var(--border); font-style: italic; }
.chip.status-error  { background: rgba(239, 91, 91, 0.18); color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }

/* Workspace + participant statuses. Reuse the runs palette where the
   semantics line up (completed → green, cancelled → grey) and add
   the workspace-only end-states. Per-participant statuses share the
   same chip surface — yielded reads as "soft completion" (calm grey),
   disqualified as a hard fail (red). */
.chip.status-completed            { background: rgba(74, 222, 128, 0.18); color: var(--success); border-color: rgba(74, 222, 128, 0.4); }
.chip.status-cap_reached          { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.status-out_of_participants  { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.status-yielded              { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.status-disqualified         { background: rgba(239, 91, 91, 0.18); color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }

/* v0.339.0 (projects/workspaces arc cross-PR sweep) — Issue board
   kanban + priority chips. Pre-fix the kanban template (PR 4a)
   referenced these class names but nothing defined them, so the
   board rendered as an unstyled column-stack of plain bullet
   lists with default chip styling. Three-column flex collapses
   to one column under ~720px. Priority colours mirror the
   .status-* palette (high=red like disqualified, medium=neutral,
   low=muted) for visual consistency. */
.chip.status-open                 { background: rgba(99, 102, 241, 0.18); color: #a5b4fc; border-color: rgba(99, 102, 241, 0.4); }
.chip.status-in_progress          { background: rgba(251, 146, 60, 0.18); color: #fb923c; border-color: rgba(251, 146, 60, 0.4); }
.chip.status-done                 { background: rgba(74, 222, 128, 0.18); color: var(--success); border-color: rgba(74, 222, 128, 0.4); }
.chip.status-canceled             { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.priority-low                { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.chip.priority-medium             { background: rgba(99, 102, 241, 0.15); color: #a5b4fc; border-color: rgba(99, 102, 241, 0.35); }
.chip.priority-high               { background: rgba(239, 91, 91, 0.18); color: var(--danger); border-color: rgba(239, 91, 91, 0.4); }

.kanban-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1rem;
    margin: 1rem 0;
}
@media (max-width: 720px) {
    .kanban-row { grid-template-columns: 1fr; }
}
.kanban-col {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.issue-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.issue-card {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.5rem 0.75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
}
.issue-card:hover {
    border-color: var(--border-strong, var(--border));
}
.issue-card-meta {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
    font-size: 0.8rem;
}
/* v0.485.1 (audit ux #2) — labels row wraps and caps at two lines so
   a card carrying the full 16-label run doesn't dwarf its column.
   The visual cap is soft (overflow-hidden); operators see the rest
   on the detail page. */
.issue-card-labels {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin-top: 0.25rem;
    max-height: 3.2em;
    overflow: hidden;
}
.issue-card-milestone {
    font-size: 0.78rem;
    line-height: 1.2;
}
.chip-label {
    font-size: 0.72rem;
    padding: 0.05rem 0.45rem;
}
/* v0.485.1 — overdue chip variant. Reuses chip-warn shape but the
   amber-coded chip-warn means "due-soon"; overdue earns its own
   slot so the colour-blind operator's pill copy ("overdue 3d") is
   the load-bearing signal, not just the colour. */
.chip-overdue {
    background: rgba(220, 70, 70, 0.18);
    color: #ffb3b3;
    border: 1px solid rgba(220, 70, 70, 0.45);
}

/* v0.487.0 (PR3) — kanban drag-drop choreography. The .dragging
   class is set on dragstart + during keyboard-grab; .move-pending
   covers the POST-in-flight window so a second drag of the same
   card stays blocked. .move-recently-moved is the 8-second
   left-border accent so the operator can re-locate the just-moved
   card on a busy board. .move-rejected flashes for the 1.5s
   slide-back when the server says 409. */
.issue-card[draggable="true"] {
    cursor: grab;
}
.issue-card.dragging {
    opacity: 0.55;
    cursor: grabbing;
    box-shadow: 0 0 0 2px var(--accent, #6da4ff);
}
.issue-card.move-pending {
    opacity: 0.7;
    pointer-events: none;
    transition: opacity 120ms ease;
}
.issue-card.move-recently-moved {
    border-left: 3px solid var(--accent, #6da4ff);
    transition: border-color 400ms ease;
}
.issue-card.move-rejected {
    box-shadow: 0 0 0 2px rgba(220, 70, 70, 0.85);
    animation: kanban-shake 0.3s ease-in-out;
}
@keyframes kanban-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-4px); }
    75% { transform: translateX(4px); }
}
.issue-card.move-glow-open,
.issue-card.move-glow-in_progress,
.issue-card.move-glow-done {
    box-shadow: 0 0 0 2px var(--accent, #6da4ff);
    transition: box-shadow 200ms ease;
}
.kanban-toast {
    position: fixed;
    bottom: 1.5rem;
    left: 50%;
    transform: translateX(-50%);
    background: var(--surface-3, #2a2030);
    color: var(--text, #f0e8f0);
    padding: 0.6rem 1.1rem;
    border-radius: 0.5rem;
    border: 1px solid rgba(220, 70, 70, 0.55);
    box-shadow: 0 8px 24px rgba(0,0,0,0.55);
    z-index: 9000;
    max-width: 28rem;
    font-size: 0.9rem;
    transition: opacity 350ms ease;
}
.kanban-toast.kanban-toast-fade {
    opacity: 0;
}
/* v0.490.0 (PR1) — undo toast variant + inline button. The toast
   chrome stays the same; the button reads as a link to disambiguate
   it from a second alert. */
.kanban-toast-undo {
    border-color: var(--accent, #6da4ff);
    background: var(--surface-2);
}
.kanban-undo-btn {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--accent, #6da4ff);
    text-decoration: underline;
    cursor: pointer;
    font-family: inherit;
    font-size: inherit;
    padding: 0;
    margin-left: 0.25rem;
}
.kanban-undo-btn:hover { text-decoration: none; }
@media (prefers-reduced-motion: reduce) {
    .issue-card.move-rejected { animation: none; }
    .issue-card.move-pending,
    .issue-card.move-recently-moved,
    .issue-card.move-glow-open,
    .issue-card.move-glow-in_progress,
    .issue-card.move-glow-done { transition: none; }
}

/* v0.490.0 (PR1) — drag-to-milestone drawer. Pinned to the right
   edge of the viewport (display:none until a drag starts), lights
   up via the .drawer-active class set by issue-milestone-drawer.js.
   Each .milestone-drop-target row is a drop zone; .drop-hover marks
   the row the operator is hovering over mid-drag; .drop-success
   flashes after a successful POST. */
.milestone-drawer {
    position: fixed;
    right: 0;
    top: 6rem;
    width: 14rem;
    max-height: 70vh;
    overflow-y: auto;
    padding: 0.6rem 0.8rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-right: none;
    border-radius: 0.4rem 0 0 0.4rem;
    box-shadow: -4px 0 12px rgba(0,0,0,0.3);
    opacity: 0;
    transform: translateX(100%);
    transition: opacity 200ms ease, transform 200ms ease;
    z-index: 100;
    pointer-events: none;
}
.milestone-drawer.drawer-active {
    opacity: 1;
    transform: translateX(0);
    pointer-events: auto;
}
.milestone-drawer h3 {
    margin: 0 0 0.5rem 0;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.milestone-drawer-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.milestone-drop-target {
    padding: 0.4rem 0.6rem;
    border-radius: 0.3rem;
    border: 1px dashed transparent;
    cursor: pointer;
    font-size: 0.85rem;
    transition: background 120ms ease, border-color 120ms ease;
}
.milestone-drop-target:hover {
    background: var(--surface-3, var(--surface-2));
}
.milestone-drop-target.drop-hover {
    border-color: var(--accent, #6da4ff);
    background: var(--surface-3, var(--surface-2));
}
.milestone-drop-target.drop-success {
    border-color: rgba(110, 200, 110, 0.75);
    background: rgba(110, 200, 110, 0.15);
}
.milestone-drop-target.milestone-drop-unlink {
    border-top: 1px solid var(--border);
    margin-top: 0.25rem;
    padding-top: 0.5rem;
    color: var(--muted);
}

/* v0.490.0 (PR1) — keyboard milestone popover. M-key on a focused
   card opens a small menu listing the same milestone rows; click
   or keyboard-activate a row to pin. */
.milestone-popover {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 0.3rem;
    box-shadow: 0 6px 18px rgba(0,0,0,0.5);
    min-width: 200px;
    padding: 0.3rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.milestone-popover-item {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--text);
    text-align: left;
    padding: 0.35rem 0.5rem;
    border-radius: 0.25rem;
    cursor: pointer;
    font-size: 0.85rem;
    font-family: inherit;
}
.milestone-popover-item:hover,
.milestone-popover-item:focus {
    background: var(--surface-3, var(--surface-2));
    outline: none;
}
@media (prefers-reduced-motion: reduce) {
    .milestone-drawer { transition: none; }
    .milestone-drop-target { transition: none; }
}

/* v0.490.0 (PR1) — recently-active dot. Top-right of the card,
   accent-coloured, slightly larger than a single pixel so it reads
   on a glance. Colour-only signal; the tooltip carries the
   semantic for screen readers (aria-label on the element). */
.issue-card { position: relative; }
.issue-card-active-dot {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--accent, #6da4ff);
    box-shadow: 0 0 0 2px var(--surface-2);
    pointer-events: none;
}

/* Rule-builder rows. Each condition is a single flex line of inputs;
   each action is a stacked block with a header + the type-specific
   sub-form below. Reusing the existing form-row tokens keeps spacing
   consistent with the rest of the form. */
.condition-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    padding: 0.5rem;
    margin-bottom: 0.5rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 0.25rem;
}
.condition-row > input,
.condition-row > select {
    flex: 1 1 auto;
    min-width: 8em;
}

/* v0.100 — operator-supplied NFT attributes on the mint form.
   Same flex idiom as .condition-row so narrow-screen wrapping is
   defined + inputs grow to fill the row instead of collapsing to
   the default ~150px width. */
.attribute-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    padding: 0.5rem;
    margin-bottom: 0.5rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 0.25rem;
}
.attribute-row > input {
    flex: 1 1 12em;
    min-width: 8em;
}

.action-row {
    padding: 0.75rem;
    margin-bottom: 0.75rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 0.25rem;
}
.action-row-header {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-bottom: 0.5rem;
}
.action-row-header > select { min-width: 14em; }
.action-config { display: flex; flex-direction: column; gap: 0.5rem; }
.action-config .config-block { display: flex; flex-direction: column; gap: 0.5rem; }
/* Hidden by default — JS reveals the matching block on type-change. */
.action-config .config-block[data-for-type] { display: none; }

/* ===========================================================================
   Agent avatars
   =========================================================================== */

/* Detail-page avatar block — sits inside the Profile card, above the
   property list. The frame has a fixed square footprint so the layout
   doesn't reflow when the placeholder swaps for the rendered image. */
.agent-avatar-block { margin: 0 0 1.25rem 0; }
.avatar-frame {
    display: inline-flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start;
    max-width: 220px;
}
.avatar-frame img {
    width: 200px;
    height: 200px;
    object-fit: cover;
    border-radius: 0.5rem;
    border: 1px solid var(--border);
    background: var(--surface-2);
}
.avatar-placeholder {
    width: 200px;
    height: 200px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 1rem;
    border-radius: 0.5rem;
    border: 1px dashed var(--border);
    background: var(--surface-2);
    text-align: center;
}
.avatar-placeholder p { margin: 0; }
.avatar-frame-failed .avatar-placeholder {
    border-color: rgba(239, 91, 91, 0.4);
    background: rgba(239, 91, 91, 0.07);
}

/* v0.281.0 — hatchday halo. First-year anniversary day, the detail-
   page avatar wears a soft accent-3 glow that pulses once over 8s
   plus a ✨ in the top-right corner that twinkles 3 times. Fires
   once on page load (no looping) — the goal is "I notice this is
   special today" not "this is decorated forever." Reduced-motion
   keeps the static ✨ visible but skips the glow + twinkle. */
.avatar-frame.is-hatchday-first-year {
    position: relative;
}
.avatar-frame.is-hatchday-first-year::before {
    content: "✨";
    position: absolute;
    top: -6px;
    right: -6px;
    font-size: 18px;
    opacity: 0;
    pointer-events: none;
    animation: hatchday-sparkle 8s ease-out 1 forwards;
    z-index: 1;
}
.avatar-frame.is-hatchday-first-year img {
    animation: hatchday-glow 8s ease-out 1 forwards;
}
@keyframes hatchday-sparkle {
    /* Three blinks (10%, 30%, 50%), then it settles to a soft
       presence for the rest of the animation. */
    0%   { opacity: 0; transform: scale(0.7); }
    10%  { opacity: 1; transform: scale(1); }
    14%  { opacity: 0.3; transform: scale(0.85); }
    30%  { opacity: 1; transform: scale(1.1); }
    34%  { opacity: 0.3; transform: scale(0.9); }
    50%  { opacity: 1; transform: scale(1); }
    54%  { opacity: 0.4; transform: scale(0.9); }
    100% { opacity: 0; transform: scale(0.7); }
}
@keyframes hatchday-glow {
    0%   { box-shadow: 0 0 0 0 transparent; }
    25%  { box-shadow: 0 0 18px 3px color-mix(in srgb, var(--accent-3) 55%, transparent); }
    50%  { box-shadow: 0 0 12px 2px color-mix(in srgb, var(--accent-3) 35%, transparent); }
    75%  { box-shadow: 0 0 18px 3px color-mix(in srgb, var(--accent-3) 55%, transparent); }
    100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
    .avatar-frame.is-hatchday-first-year::before {
        opacity: 1; /* static marker, no twinkle */
        animation: none;
    }
    .avatar-frame.is-hatchday-first-year img {
        animation: none;
    }
}

/* CSS-only spinner used while the avatar is PENDING. */
.avatar-placeholder .spinner {
    width: 28px;
    height: 28px;
    border: 3px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: avatar-spin 0.8s linear infinite;
    display: inline-block;
}
@keyframes avatar-spin {
    to { transform: rotate(360deg); }
}

/* Tiny circular thumbnail in the agents list. onerror in the template
   sets display:none on the <img> when no avatar exists, so the cell
   stays clean without falling back to a broken-image icon. */
.agent-avatar-thumb {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin-right: 0.5rem;
    vertical-align: middle;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface-2);
    flex-shrink: 0;
}
.agent-avatar-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* v0.16.2 — system-owned chip + warn-on-delete danger zone */

.chip.system-chip {
    background: rgba(167, 139, 250, 0.15);
    color: #a78bfa;
    border: 1px solid rgba(167, 139, 250, 0.3);
}

/* v0.27.2 — project list chip when a budget cap is at or above
   the warning threshold (≥80% per BudgetService.WARNING_THRESHOLD).
   Amber palette mirrors the existing budget bar's warning state. */
.chip.budget-warn {
    background: rgba(251, 191, 36, 0.15);
    color: #fbbf24;
    border: 1px solid rgba(251, 191, 36, 0.35);
}

.danger-zone {
    border: 1px solid rgba(239, 91, 91, 0.4);
    background: rgba(239, 91, 91, 0.04);
}
.danger-zone h2 {
    color: var(--danger);
    margin-top: 0;
}
.danger-zone-form {
    margin-top: 1em;
    max-width: 36em;
}
.danger-zone-form .confirm-name-input {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.danger-zone-form .confirm-delete-btn:disabled,
.danger-zone-form .confirm-delete-btn.btn-pending {
    opacity: 0.5;
    cursor: not-allowed;
}
/* v0.147.2 — danger-zone subheads (Schedule / Delete-immediately).
   Spacing lifted out of the template inline styles. */
.danger-zone-h3 {
    margin-bottom: 0.25rem;
}
.danger-zone-h3--secondary {
    margin-top: 1.5rem;
}
/* v0.147.2 — anchor target with tabindex="-1" needs an outline
   reset; default browser focus ring on a section is jarring. */
.danger-zone:focus {
    outline: 2px solid rgba(239, 91, 91, 0.4);
    outline-offset: 2px;
}

/* v0.21.0 — project status chips + budget bars + brief list */

.chip.project-status-draft           { background: rgba(148, 163, 184, 0.20); color: #94a3b8; border-color: rgba(148, 163, 184, 0.55); }
.chip.project-status-active          { background: rgba(34, 197, 94, 0.20);  color: #4ade80; border-color: rgba(34, 197, 94, 0.55); }
.chip.project-status-awaiting_review { background: rgba(245, 158, 11, 0.20); color: #fbbf24; border-color: rgba(245, 158, 11, 0.55); }
.chip.project-status-completed       { background: rgba(99, 102, 241, 0.20); color: #a5b4fc; border-color: rgba(99, 102, 241, 0.55); }
.chip.project-status-cancelled       { background: rgba(239, 91, 91, 0.15);  color: #f87171; border-color: rgba(239, 91, 91, 0.40); }
.chip.project-status-archived        { background: rgba(71, 85, 105, 0.30);  color: #94a3b8; border-color: rgba(71, 85, 105, 0.55); }

.chip.milestone-status-pending          { background: rgba(148, 163, 184, 0.20); color: #94a3b8; border-color: rgba(148, 163, 184, 0.55); }
.chip.milestone-status-in_progress      { background: rgba(59, 130, 246, 0.20);  color: #60a5fa; border-color: rgba(59, 130, 246, 0.55); }
.chip.milestone-status-awaiting_review  { background: rgba(245, 158, 11, 0.20);  color: #fbbf24; border-color: rgba(245, 158, 11, 0.55); }
.chip.milestone-status-approved         { background: rgba(34, 197, 94, 0.20);   color: #4ade80; border-color: rgba(34, 197, 94, 0.55); }
.chip.milestone-status-skipped          { background: rgba(71, 85, 105, 0.30);   color: #94a3b8; border-color: rgba(71, 85, 105, 0.55); }
/* v0.27.3/v0.27.4 — REJECTED is non-terminal "operator said redo".
   v0.27.4 softened the foreground from #ef5b5b (matched the Reject
   button text — visual hierarchy clash) to #f87171 (matches the
   CANCELLED project chip). Background + border stay red so it's
   still clearly distinct from the orange AWAITING_REVIEW + green
   APPROVED. */
.chip.milestone-status-rejected         { background: rgba(239, 91, 91, 0.20);   color: #f87171; border-color: rgba(239, 91, 91, 0.55); }
/* v0.376.0 (PR 3b) — soft-tombstone chip. Dimmed slate so it reads
   as "this row is out of play" without competing visually with the
   status chip that stays alongside it (operator audit reconstruction
   needs both pieces of info). */
.chip.milestone-status-withdrawn        { background: rgba(71, 85, 105, 0.35);   color: #cbd5e1; border-color: rgba(71, 85, 105, 0.55); text-decoration: line-through; }

/* v0.397.0 (PR 5) — multi-stakeholder approval gate chip palette.
   Gate-status chips mirror milestone-status colors so an operator
   who learned them on the milestones spoke transfers them. Your-vote
   chips use the same per-decision palette. */
.chip.approval-gate-status-open                { background: rgba(245, 158, 11, 0.20);  color: #fbbf24; border-color: rgba(245, 158, 11, 0.55); }
.chip.approval-gate-status-resolved_approved   { background: rgba(34, 197, 94, 0.20);   color: #4ade80; border-color: rgba(34, 197, 94, 0.55); }
.chip.approval-gate-status-resolved_rejected   { background: rgba(239, 91, 91, 0.20);   color: #f87171; border-color: rgba(239, 91, 91, 0.55); }
.chip.approval-gate-status-superseded          { background: rgba(71, 85, 105, 0.35);   color: #cbd5e1; border-color: rgba(71, 85, 105, 0.55); text-decoration: line-through; }
.chip.approval-your-vote-chip                  { font-style: italic; }
.chip.approval-your-vote-pending  { background: rgba(148, 163, 184, 0.20); color: #94a3b8; border-color: rgba(148, 163, 184, 0.55); }
.chip.approval-your-vote-approve  { background: rgba(34, 197, 94, 0.20);  color: #4ade80; border-color: rgba(34, 197, 94, 0.55); }
.chip.approval-your-vote-reject   { background: rgba(239, 91, 91, 0.20);  color: #f87171; border-color: rgba(239, 91, 91, 0.55); }
.chip.approval-your-vote-abstain  { background: rgba(180, 140, 30, 0.20); color: #facc15; border-color: rgba(180, 140, 30, 0.55); }
.approval-gate-card .approval-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.approval-voter-list { list-style: none; padding: 0; margin: 0.6rem 0; }
.approval-voter { padding: 0.3rem 0; border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08)); }
.approval-voter:last-child { border-bottom: none; }
.approval-vote-form fieldset { border: none; padding: 0; margin: 0 0 0.5rem 0; display: flex; gap: 1rem; flex-wrap: wrap; }
.approval-vote-form fieldset legend { font-weight: 600; margin-bottom: 0.3rem; }
.approval-vote-form .block-input { width: 100%; padding: 0.4rem; resize: vertical; margin-bottom: 0.5rem; }
.approval-vote-withdraw-form { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-top: 0.5rem; }
.approval-gate-open-form .block-input { width: 100%; padding: 0.4rem; margin: 0.3rem 0; }
.approval-gate-open-form .inline-input.small { padding: 0.2rem 0.4rem; max-width: 6rem; }

/* v0.399.1 (PR 5 audit UX) — "Waiting on Voss + 2 others" warmth line.
   Stays subtle (muted small inherits palette) but the small margin
   gives it breathing room between the chips and the tally. */
.approval-waiting { margin: 0.3rem 0 0.4rem 0; font-style: italic; }

/* v0.399.1 — feedback under each voter renders on its own line for
   readability when voters leave long context. */
.approval-vote-feedback { display: block; padding-left: 1.2rem; margin-top: 0.15rem; }

/* v0.399.1 — checkbox-tile stakeholder picker. Replaces the prior
   comma-separated text input — operators were having to read the
   candidate list and type ids. Matches the .radio-tile pattern used
   on the milestone Decide artifact picker. */
.approval-stakeholder-picker { border: none; padding: 0; margin: 0 0 0.6rem 0; display: flex; flex-direction: column; gap: 0.2rem; }
.approval-stakeholder-picker legend { font-weight: 600; margin-bottom: 0.3rem; }
.checkbox-tile { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.2rem 0.4rem; cursor: pointer; }
.checkbox-tile:hover { background: rgba(255,255,255,0.04); border-radius: 4px; }

/* v0.399.1 — cancel-gate escape hatch (supersede). Matches withdraw
   form layout — inline reason input + small danger button. */
.approval-gate-cancel-form { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-top: 0.5rem; }

/* v0.384.0 (arc-A-wrap UX) — project-approval-* chip palette. Pre-
   fix these classes were referenced from artifact-detail.html (line
   35) + artifacts.html (line 92) but had no CSS — every artifact's
   approval-state chip rendered neutral regardless of state. Palette
   mirrors the milestone-status set so an operator who learned colors
   on the milestones spoke transfers them. (ProjectArtifactApprovalState
   has 4 values: DRAFT / SUBMITTED / APPROVED / REJECTED.) */
.chip.project-approval-draft     { background: rgba(148, 163, 184, 0.20); color: #94a3b8; border-color: rgba(148, 163, 184, 0.55); }
.chip.project-approval-submitted { background: rgba(245, 158, 11, 0.20);  color: #fbbf24; border-color: rgba(245, 158, 11, 0.55); }
.chip.project-approval-approved  { background: rgba(34, 197, 94, 0.20);   color: #4ade80; border-color: rgba(34, 197, 94, 0.55); }
.chip.project-approval-rejected  { background: rgba(239, 91, 91, 0.20);   color: #f87171; border-color: rgba(239, 91, 91, 0.55); }

/* v0.376.0 (PR 3b) — withdrawn-row treatment. Opacity dim + strike
   the name; the WITHDRAWN chip carries the explicit signal but the
   row-level dimming saves a glance for operators scanning the list.
   Excludes the withdraw form so its controls (if accidentally still
   rendered on a withdrawn row, defensively) stay legible. */
.milestone-row.milestone-withdrawn { opacity: 0.65; }
.milestone-row.milestone-withdrawn strong { text-decoration: line-through; }
.milestone-withdrawn-reason { font-style: italic; }

/* Withdraw form on the milestones spoke — inline so it fits next to
   "Open review →" without breaking the footer row. v0.384.0 (arc-A-
   wrap UX) — added flex-wrap so the reason input + button stack on
   narrow viewports (~720px the milestones-spoke row gets tight). */
.milestone-withdraw-form.inline { display: inline-flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-left: 0.5rem; }
.milestone-withdraw-form .inline-input.small { padding: 0.15rem 0.4rem; font-size: 0.85rem; max-width: 18rem; }

/* v0.384.0 (arc-A-wrap a11y) — invisible-but-AT-readable label
   shell used by the unlock + withdraw reason inputs. The visible
   hint text inside <span class="muted small"> still renders;
   the wrapping <label for="..."> ties it to the input so screen
   readers announce the input's purpose. The label sits BEFORE the
   input in DOM order so AT users hear "Why are we unlocking?"
   before they reach the textbox. */
.visually-labelled { display: inline-flex; flex-direction: column; gap: 0.15rem; vertical-align: middle; }
.link-button.danger.small { font-size: 0.85rem; color: #f87171; background: none; border: none; cursor: pointer; padding: 0.15rem 0.3rem; }
.link-button.danger.small:hover { text-decoration: underline; }

/* v0.27.4 — section heading inside a card. Used on the milestone-review
   page to separate Approve / Reject / Skip forms so the operator can
   tell at a glance which form does what. Smaller than h2 (which the
   card itself uses) but visually distinct from body text. */
.form-section-heading {
    font-size: 1rem;
    font-weight: 600;
    margin: 1.2rem 0 0.4rem;
    color: var(--fg);
}

/* v0.27.4 — extracted from the inline styles that shipped on the
   project list filter form + the bundle share panel. Keeping the
   rules in CSS is friendlier to a strict CSP, makes overrides
   centralized, and is easier to tweak per-theme. */
.filter-row {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
    align-items: end;
}
.filter-row .filter-search-label { flex: 1 1 16rem; }
.filter-row .filter-actions { margin: 0; }

/* v0.27.4.1: explicit narrow-viewport rules for the project list filter
   bar. Below ~480px each label collapses to its own row so labels +
   inputs don't compete for horizontal space; the search box's
   {@code flex: 1 1 16rem} basis would otherwise force a horizontal
   scroll on phone-sized screens. The row gap doubles when stacked
   so adjacent labels read as separate rows, not run-on text. */
@media (max-width: 480px) {
    .filter-row {
        flex-direction: column;
        align-items: stretch;
        gap: 0.9rem;
    }
    .filter-row .filter-search-label,
    .filter-row label,
    .filter-row .filter-actions {
        flex: 1 1 auto;
        width: 100%;
    }
    .filter-row .filter-actions {
        display: flex;
        gap: 0.6rem;
    }
}

/* Share URL block — long URL displays as a wrapping <code> rather
   than a horizontally-scrolling input. Operator scans the whole URL
   without click-and-drag. */
.share-url-display {
    display: block;
    width: 100%;
    padding: 0.5rem 0.7rem;
    margin-top: 0.3rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 0.3rem;
    font-family: monospace;
    font-size: 0.85rem;
    word-break: break-all;
    color: var(--fg);
}
.share-actions { gap: 0.5rem; }
.share-actions .inline-form { display: inline; }

.budget-card .budget-bar { margin-bottom: 0.8em; }
.budget-card .budget-label { display: flex; justify-content: space-between; font-size: 0.85em; margin-bottom: 0.25em; }
.budget-card .budget-bar-track { height: 0.55em; background: var(--surface-2); border-radius: 0.3em; overflow: hidden; }
.budget-card .budget-bar-fill { height: 100%; background: #4ade80; transition: width 0.3s; }
.budget-card .budget-bar-fill.warning { background: #fbbf24; }
.budget-card .budget-bar-fill.exceeded { background: var(--danger); }

.brief-list { display: grid; grid-template-columns: max-content 1fr; gap: 0.4em 1.2em; margin: 0; }
.brief-list dt { font-weight: 600; color: var(--muted); }
.brief-list dd { margin: 0; }

.workstream-section { margin-bottom: 1em; }
.milestone-list { list-style: none; padding-left: 0; }
.milestone-list li { padding: 0.4em 0; border-bottom: 1px solid var(--surface-2); }
.milestone-list li:last-child { border-bottom: none; }

.audit-list { list-style: none; padding-left: 0; max-height: 24em; overflow-y: auto; font-size: 0.9em; }
.audit-list li { padding: 0.3em 0; display: flex; gap: 0.6em; align-items: baseline; flex-wrap: wrap; }

/* v0.22.0 — milestone review surface */
.artifact-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18em, 1fr));
    gap: 1em;
}
.artifact-tile {
    border: 1px solid var(--surface-2);
    border-radius: 0.4em;
    padding: 0.8em;
    background: rgba(0, 0, 0, 0.06);
}
.artifact-tile header {
    display: flex;
    gap: 0.5em;
    align-items: baseline;
    flex-wrap: wrap;
    margin-bottom: 0.4em;
}

/* v0.27.2 — milestone-review radio picker. Radio tiles look like
   the .artifact-tile cards directly above them. Hide the native
   radio input and use the label as the tile; checked state gets
   an accent border + darker background. */
.decision-artifact-picker {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18em, 1fr));
    gap: 0.6em;
    border: none;
    padding: 0;
    margin: 0.5em 0;
}
.decision-artifact-picker legend {
    grid-column: 1 / -1;
    padding: 0;
    margin: 0 0 0.4em 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
}
.radio-tile {
    display: block;
    border: 1px solid var(--surface-2);
    border-radius: 0.4em;
    padding: 0.7em 0.8em;
    background: rgba(0, 0, 0, 0.06);
    cursor: pointer;
    transition: border-color 0.1s ease, background 0.1s ease;
}
.radio-tile:hover { background: rgba(0, 0, 0, 0.12); }
.radio-tile input[type="radio"] {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.radio-tile:has(input[type="radio"]:checked) {
    border-color: var(--accent);
    background: var(--surface);
    box-shadow: inset 2px 0 0 var(--accent);
}
.radio-tile-body {
    display: flex;
    flex-direction: column;
    gap: 0.2em;
}
.radio-tile-body strong { font-weight: 600; }
.radio-tile-body .muted.small {
    font-size: 0.78rem;
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.35em;
    align-items: center;
}

/* v0.27.1 — Disco operator surface (/disco). Roster rows reuse
   the .agent-avatar-thumb shape from v0.26.2 + a light flex
   layout for name/persona/turns. Notes + turn-log are simple
   ordered lists with subtle indentation. */
.disco-theme {
    margin: 0.25rem 0 0.5rem;
    color: var(--text);
}
.disco-roster {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.disco-roster-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.3rem 0.5rem;
    border-radius: 3px;
    background: var(--surface-2);
}
.disco-roster-row.evicted { opacity: 0.55; }
.disco-roster-body {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}
.disco-notes,
.disco-turns {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.disco-notes li {
    padding: 0.3rem 0.5rem;
    border-radius: 3px;
    background: var(--surface-2);
    font-size: 0.85rem;
}
.disco-turns li {
    padding: 0.4rem 0.6rem;
    border-radius: 3px;
    background: var(--surface-2);
    border-left: 3px solid transparent;
}
.disco-turns li.status-ok      { border-left-color: var(--accent); }
.disco-turns li.status-skipped { border-left-color: #8b8b00; }
.disco-turns li.status-failed  { border-left-color: var(--danger); }
.disco-turns li header {
    display: flex;
    gap: 0.4rem;
    align-items: baseline;
    flex-wrap: wrap;
    margin-bottom: 0.2rem;
}
.disco-turn-text {
    margin: 0;
    font-size: 0.9rem;
    color: var(--text);
}

/* v0.27.5 — talking-hologram page. The canvas is the centerpiece;
   the controls below are a standard form-stack. */
.hologram-canvas {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    background: linear-gradient(180deg, #0e1726 0%, #1c1f2c 100%);
    border-radius: 8px;
    overflow: hidden;
    margin-bottom: 1rem;
}
.hologram-canvas canvas {
    width: 100%;
    height: 100%;
    display: block;
}
.hologram-status {
    position: absolute;
    left: 0.6rem;
    bottom: 0.6rem;
    color: rgba(255, 255, 255, 0.7);
}
.hologram-controls textarea {
    width: 100%;
    font-family: inherit;
    font-size: 0.95rem;
    padding: 0.5rem;
}
.hologram-controls .form-actions {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}
.hologram-auto {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text);
    font-size: 0.95rem;
}
/* Avatar-card "View talking hologram →" link sits flush below the
   four avatar-frame states. */
.hologram-link {
    margin: 0.6rem 0 0 0;
}
/* Empty-state card on the hologram page when the operator hasn't
   set a Ready Player Me URL yet. */
.hologram-empty ol {
    margin: 0.6rem 0 0 1.2rem;
    padding: 0;
}
.hologram-empty li {
    margin: 0.4rem 0;
}

/* v0.29.x — site cards on /admin/sites. Replaces the previous data-table
   with a grid of self-contained cards: left jewel-tone stripe, prominent
   name, slug chip, status pill, owner line, and the Edit / Disable / Open
   button row. Disabled cards get a muted treatment but stay clickable
   for Edit + Enable. Each card cycles through the jewel palette via
   :nth-child so a row of sites isn't monochrome. */
.site-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    gap: 1rem;
    margin: 1.25rem 0 0;
}
.site-card {
    --site-accent: var(--c-agents);  /* default; overridden by :nth-child below */
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.1rem 1.1rem 1rem 1.25rem;
    position: relative;
    overflow: hidden;
    transition: border-color 150ms ease, transform 150ms ease, box-shadow 150ms ease;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.site-card::before {
    content: '';
    position: absolute; top: 0; left: 0; bottom: 0;
    width: 3px;
    background: var(--site-accent);
    border-radius: 12px 0 0 12px;
    transition: width 150ms ease;
}
.site-card:hover {
    border-color: var(--site-accent);
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
}
.site-card:hover::before { width: 4px; }
/* Cycle the jewel palette so a wall of sites reads as a wall of
   distinct ones, not one repeated tile. Twelve hues, modulo. */
.site-card:nth-child(12n+1)  { --site-accent: var(--c-agency); }
.site-card:nth-child(12n+2)  { --site-accent: var(--c-agents); }
.site-card:nth-child(12n+3)  { --site-accent: var(--c-projects); }
.site-card:nth-child(12n+4)  { --site-accent: var(--c-runs); }
.site-card:nth-child(12n+5)  { --site-accent: var(--c-workflows); }
.site-card:nth-child(12n+6)  { --site-accent: var(--c-chats); }
.site-card:nth-child(12n+7)  { --site-accent: var(--c-watchers); }
.site-card:nth-child(12n+8)  { --site-accent: var(--c-personas); }
.site-card:nth-child(12n+9)  { --site-accent: var(--c-skills); }
.site-card:nth-child(12n+10) { --site-accent: var(--c-usage); }
.site-card:nth-child(12n+11) { --site-accent: var(--c-approvals); }
.site-card:nth-child(12n)    { --site-accent: var(--c-settings); }

.site-card.is-disabled {
    opacity: 0.62;
}
.site-card.is-disabled:hover {
    transform: none;
    box-shadow: none;
}
.site-card.is-disabled::before {
    background: var(--border);
}

/* Stretched-link: makes the entire enabled card a click target that
   routes to the same URL as the Open button. Pattern:
     1. .site-card-open is a position-static <a> in the card footer.
     2. Its ::after is position: absolute; inset: 0 — but since the
        anchor itself isn't positioned, the ::after's containing block
        walks up to the nearest positioned ancestor, which is the
        .site-card article. So the pseudo-element fills the whole card
        and intercepts pointer events for the Open link.
     3. The h2 name-link, Edit anchor, and Disable form sit on
        z-index: 2 so they stay independently clickable above the
        stretched layer.
   Disabled cards never render .site-card-open, so they're not
   clickable — the visual disabled state matches the behavior. */
.site-card-open::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
}
.site-card:not(.is-disabled) {
    cursor: pointer;
}
/* Bump the other interactive controls above the stretched layer so
   they stay independently clickable. CRITICAL: don't include
   .site-card-open here — the stretched-link pattern requires the
   anchor itself to be position: static so its ::after's containing
   block walks up to the article. Give the anchor position: relative
   and the ::after only covers the Open button (which is the bug
   that landed in 02b23c1). */
.site-card-head h2 a,
.site-card-actions > *:not(.site-card-open) {
    position: relative;
    z-index: 2;
}

.site-card-head {
    display: flex; justify-content: space-between; align-items: center;
    gap: 0.75rem;
    margin: 0;
}
.site-card-head h2 {
    margin: 0;
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    line-height: 1.2;
    color: var(--text);
}
.site-card-head h2 a {
    color: var(--text);
    text-decoration: none;
}
.site-card-head h2 a:hover {
    color: var(--site-accent);
    text-decoration: none;
}
.site-card-status {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.18rem 0.6rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    border: 1px solid currentColor;
    background: transparent;
    white-space: nowrap;
}
.site-card-status.is-on  { color: var(--success); background: rgba(74, 222, 128, 0.08); }
.site-card-status.is-off { color: var(--muted);   background: rgba(122, 129, 148, 0.08); }
.site-card-status::before {
    content: '●';
    font-size: 0.55rem;
    margin-top: 0.02rem;
}

.site-card-slug {
    margin: 0;
}
.site-card-slug .chip {
    /* slight tint by the per-card accent so the slug feels tied to the
       stripe. background uses color-mix so it stays subtle on dark. */
    background: color-mix(in srgb, var(--site-accent) 18%, transparent);
    color: var(--site-accent);
    border: 1px solid color-mix(in srgb, var(--site-accent) 30%, transparent);
    font-weight: 500;
}

.site-card-desc {
    margin: 0;
    color: var(--text-soft);
    font-size: 0.92rem;
    line-height: 1.5;
    /* Clamp to two lines so the card height stays predictable across a
       wall of sites with varying-length descriptions. Tall cards already
       get the "is-disabled" treatment; this is purely about long copy. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.site-card-desc.muted {
    font-style: italic;
}

.site-card-foot {
    display: flex; justify-content: space-between; align-items: center;
    gap: 0.75rem;
    margin-top: auto;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
    flex-wrap: wrap;
}
.site-card-owner {
    display: inline-flex; align-items: baseline; gap: 0.5rem;
    color: var(--text-soft);
    font-size: 0.85rem;
}
.site-card-owner strong {
    color: var(--text);
    font-weight: 600;
}
.site-card-actions {
    display: inline-flex; gap: 0.4rem; align-items: center;
    flex-wrap: wrap;
}
.site-card-actions .btn.primary {
    background: var(--site-accent);
    color: var(--bg);
    border-color: var(--site-accent);
}
.site-card-actions .btn.primary:hover {
    /* Slight lift via opacity — color-mix with the bg keeps the same hue. */
    background: color-mix(in srgb, var(--site-accent) 85%, white);
    border-color: color-mix(in srgb, var(--site-accent) 85%, white);
}

/* ============================================================
 * v0.29.x — list-page polish (group-chats and friends).
 * ============================================================ */

.nowrap { white-space: nowrap; }

/* Compact single-line date cluster: "May 10 · 14:22" */
.when-link {
    color: var(--text);
    text-decoration: none;
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    white-space: nowrap;
}
.when-link:hover { color: var(--accent); text-decoration: none; }
.when-link .when-date {
    color: var(--accent);
    font-weight: 600;
}
.when-link .when-time { color: var(--text-soft); }
.when-sep {
    color: var(--muted);
    font-weight: 400;
    margin: 0 0.05rem;
}

/* Clickable title — same hover feel as the date link, but uses the
   default text color so titles read like content, not navigation. */
.title-link {
    color: var(--text);
    text-decoration: none;
    font-weight: 500;
}
.title-link:hover { color: var(--accent); text-decoration: none; }
.title-link.muted { color: var(--muted); }

/* Wrapping chip cluster for participants. Replaces the
   "span + &nbsp;" pattern with a real flex container so long rosters
   wrap cleanly without extra-wide cells. */
.participant-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 0.35rem;
    align-items: center;
}
.participant-chips .chip { margin-right: 0; }
.agent-chip {
    /* Slightly more readable than the bare .chip default — body font
       and a tad more weight so seat colors land cleanly on agent names. */
    font-family: inherit;
    font-weight: 500;
    padding: 0.18rem 0.6rem;
}

/* Subtle row hover on data tables — operators sweep their mouse
   down a list to find a row; a faint highlight makes the targeted
   row obvious without the heavy stripe-and-shadow of a card. */
.data-table tbody tr {
    transition: background 120ms ease;
}
.data-table tbody tr:hover {
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.data-table tbody tr + tr td {
    border-top: 1px solid var(--border);
}

/* Sortable column headers — opt-in via data-sort on a <th>.
   sortable-table.js sets .sortable on init and toggles .sort-asc /
   .sort-desc + appends a .sort-indicator span when clicked. */
.data-table th.sortable {
    cursor: pointer;
    user-select: none;
    transition: color 120ms ease;
}
.data-table th.sortable:hover { color: var(--text-soft); }
.data-table th.sort-asc,
.data-table th.sort-desc { color: var(--accent); }
.data-table th .sort-indicator {
    display: inline-block;
    margin-left: 0.3rem;
    font-size: 0.7rem;
    opacity: 0.85;
}

/* ============================================================
 * v0.29.x — @-mention popover on the group-chat reply form.
 * Renders one option per chat participant; click inserts
 * "@AgentName " at the textarea caret. Wired by group-chat-discuss.js.
 * ============================================================ */

.mention-container {
    /* Positioning anchor so the popover lays out relative to the
       @-button instead of the page. Inline-flex so the container
       still sits in .form-actions like a regular button slot. */
    position: relative;
    display: inline-flex;
}
.mention-popover {
    position: absolute;
    bottom: calc(100% + 0.4rem);   /* float above the button */
    left: 0;
    z-index: 30;
    min-width: 240px;
    max-width: 340px;
    max-height: 320px;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
    padding: 0.3rem;
}
.mention-popover[hidden] { display: none; }
.mention-options {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.mention-option {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    padding: 0.45rem 0.6rem 0.45rem 0.8rem;
    border-radius: 6px;
    background: transparent;
    border: 1px solid transparent;
    /* Left stripe carries the seat color via currentColor — seat-N
       on the option sets currentColor; we use it for the stripe and
       reset the inner text color so the name reads in --text. */
    border-left: 3px solid currentColor;
    color: var(--text);
    text-align: left;
    cursor: pointer;
    font: inherit;
}
/* Reset currentColor on the inner spans so the seat color only
   affects the stripe + chip background, not the rest of the
   option's text. The :hover state pulls the seat color through
   subtly via the border-left and the background tint. */
.mention-option > * { color: inherit; }
.mention-option:hover,
.mention-option:focus {
    background: color-mix(in srgb, currentColor 10%, var(--surface-2));
    border-color: var(--border);
    border-left-color: currentColor;
    outline: none;
}
.mention-option-name {
    font-weight: 500;
}
.mention-option-persona {
    margin-left: auto;
    font-style: italic;
    color: var(--muted);
    font-size: 0.78rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 140px;
}

/* When the @ button is open, give it a slightly pressed look. */
#group-chat-mention-button[aria-expanded="true"] {
    background: var(--surface-2);
    border-color: var(--accent);
}

/* Seat-color overrides for mention options — applied via the same
   .seat-N classes the chat-bubble headers use. Each seat owns its
   own currentColor, which drives the border-left stripe + hover bg. */
.mention-option.seat-0   { color: #5b9dff; }
.mention-option.seat-1   { color: #4ade80; }
.mention-option.seat-2   { color: #fb923c; }
.mention-option.seat-3   { color: #a78bfa; }
.mention-option.seat-4   { color: #22d3ee; }
.mention-option.seat-5   { color: #f472b6; }
.mention-option.seat-6   { color: #facc15; }
.mention-option.seat-7   { color: #ef5b5b; }
.mention-option.seat-8   { color: #14b8a6; }
.mention-option.seat-9   { color: #818cf8; }
.mention-option.seat-10  { color: #d946ef; }
.mention-option.seat-11  { color: #84cc16; }

/* ============================================================
   v0.30.0 — Saturday-Night-Fever effect for the disco theme-queue
   section on the runtime-settings page. Opt-in via the per-site
   add_disco_visual_effect column; the template adds the
   `.disco-saturday-night-fever` class only when the operator has
   the toggle on.

   Accessibility-safe per WCAG flash thresholds:
     - opacity-only (no color strobing)
     - cycles ≥ 4s (well below 3 Hz flash threshold)
     - honors prefers-reduced-motion: reduce
   ============================================================ */

@keyframes disco-ball-shimmer {
    0%   { opacity: 0.55; transform: translate(-50%, -50%) rotate(0deg);   }
    50%  { opacity: 1;    transform: translate(-50%, -50%) rotate(180deg); }
    100% { opacity: 0.55; transform: translate(-50%, -50%) rotate(360deg); }
}

@keyframes disco-legend-pulse {
    0%, 100% { text-shadow: 0 0 8px rgba(253, 224, 71, 0.35); }
    50%      { text-shadow: 0 0 18px rgba(253, 224, 71, 0.85),
                            0 0 30px rgba(244, 114, 182, 0.45); }
}

.disco-saturday-night-fever {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    /* Accent border that hints at the effect even when the animation
       is suppressed for reduced-motion users. */
    border: 1px solid rgba(253, 224, 71, 0.35);
}

.disco-saturday-night-fever::before {
    /* Soft conic-gradient backdrop — slow rotation simulates a
       disco-ball glint sweeping across the surface. Sits behind
       the section content via z-index. */
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 250%;
    height: 250%;
    background: conic-gradient(
        from 0deg,
        rgba(253, 224, 71, 0.10),   /* yellow */
        rgba(244, 114, 182, 0.10),  /* pink  */
        rgba(56, 189, 248, 0.10),   /* sky   */
        rgba(167, 139, 250, 0.10),  /* violet */
        rgba(253, 224, 71, 0.10)
    );
    filter: blur(40px);
    z-index: -1;
    pointer-events: none;
    animation: disco-ball-shimmer 12s linear infinite;
}

.disco-saturday-night-fever > header h2 {
    background: linear-gradient(90deg,
        rgb(253, 224, 71) 0%,
        rgb(244, 114, 182) 50%,
        rgb(56, 189, 248) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    animation: disco-legend-pulse 6s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .disco-saturday-night-fever::before { animation: none; }
    .disco-saturday-night-fever > header h2 { animation: none; }
}

/* v0.82.x (integrity slice, PR 2) — layout for the per-agent
   integrity card. Flex-wrap row for chips, plus an "inscription"
   treatment for the genesis-narrative headline so it reads as a
   birth-certificate quote rather than just another sentence. */
.chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.integrity-headline {
    margin: 0.5rem 0 0.25rem 0;
    padding-left: 0.65rem;
    border-left: 2px solid rgba(91, 157, 255, 0.45);
    font-size: 0.98rem;
    line-height: 1.45;
    color: var(--text, #e2e8f0);
}
.integrity-bornline {
    margin-top: 0.75rem;
}
.integrity-tamper-note {
    margin-top: 0.75rem;
}
.integrity-verified-line {
    margin-top: 0.5rem;
}

/* v0.82.x (integrity slice, PR 2) — non-color signal on severity
   chips, scoped to the integrity panel. Color alone is insufficient
   (~5% of operators have red/green deficiency). A small leading
   symbol — neutral text-only so it inherits chip color — lets the
   row scan correctly without a palette. Sev-info chips stay glyph-
   less; emphasis goes on the non-OK paths where the operator most
   needs to notice. Scoped to #agent-integrity so the existing
   sev-warning chips elsewhere in the app (persona kinds, agent edit
   page, etc.) aren't visually mutated by this rule. */
#agent-integrity .chip.sev-warning::before { content: "\26A0\00a0"; /* ⚠ + NBSP */ }
#agent-integrity .chip.sev-error::before   { content: "\2716\00a0"; /* ✖ + NBSP */ }

/* v0.83.x (post-foundation roadmap PR 2) — transit log on the
   integrity panel. One row per transit_papers entry; the agent-
   voice arrival_note (when present) renders as a quoted inscription
   under the kind/location header line. Visual cue: a subtle left
   border on the blockquote mirrors the .integrity-headline treatment
   so the agent's voice is recognizable as the agent's voice across
   the whole card. */
.integrity-transit-log {
    margin-top: 1rem;
}
/* v0.83.x (PR 2 review patch) — eyebrow label, not a heading. The
   integrity card already has an <h2>; this is a sub-label, rendered
   as <p class="integrity-transit-heading"> so screen readers don't
   announce a phantom h3 with no real section semantics.

   v0.83.x (meta-review patch) — the inline subheading (".integrity-
   transit-subheading") explicitly labels the section as agent-voice
   narrative, not part of the cryptographically-verified chain. The
   security reviewer noted that without this divider, the blockquote
   sits visually adjacent to the "Chain verified" chip and operators
   could read agent-authored sentences as part of provenance. */
.integrity-transit-heading {
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted, #94a3b8);
    margin: 0 0 0.4rem 0;
    font-weight: 600;
}
.integrity-transit-subheading {
    text-transform: none;
    letter-spacing: 0;
    font-weight: 400;
    margin-left: 0.4rem;
}
.integrity-transit-overflow {
    margin: 0 0 0.5rem 0;
    font-style: italic;
}
.integrity-transit-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.integrity-transit-entry {
    margin-bottom: 0.6rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid rgba(148, 163, 184, 0.12);
}
.integrity-transit-entry:last-child {
    border-bottom: none;
    margin-bottom: 0;
    padding-bottom: 0;
}
.integrity-transit-header {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: baseline;
}
/* v0.83.x (PR 2 review patch) — kind now uses .chip sev-info for
   visual consistency with the rest of the integrity panel. Minor
   tighten on font size so two-word kinds ("transferred in", "visit
   returned") stay compact next to the location label. */
.integrity-transit-kind {
    font-size: 0.72rem;
    text-transform: lowercase;
    letter-spacing: 0.04em;
}
.integrity-transit-location {
    font-size: 0.92rem;
}
/* v0.83.x (PR 2 review patch) — dropped the left border (too much
   accent-color stacking with the .integrity-headline above) and
   kept italic alone as the "this is the agent speaking" signal. */
.integrity-transit-note {
    margin: 0.3rem 0 0 0;
    padding-left: 0;
    font-size: 0.92rem;
    line-height: 1.5;
    color: var(--text, #e2e8f0);
    font-style: italic;
}

/* v0.83.x (PR 11 — Site universe seeding) — the universe section on
   /admin/sites/new + /admin/sites/{id}/edit. A bit of ceremony so
   the "creation of a world" moment feels appropriately weighted
   without being a 4-step wizard. */
.universe-section {
    position: relative;
    margin-top: 1.5rem;
    padding: 1.25rem 1.25rem 1rem;
    border: 1px solid var(--border, #2d3748);
    border-radius: 0.5rem;
    background: linear-gradient(
        180deg,
        rgba(99, 102, 241, 0.05) 0%,
        rgba(0, 0, 0, 0) 100%);
}
.universe-section > legend {
    padding: 0 0.5rem;
    font-weight: 600;
    font-size: 1.05rem;
    letter-spacing: 0.01em;
}
.universe-section > legend > small {
    font-weight: 400;
    font-style: italic;
}
.universe-help {
    margin: 0 0 1rem 0;
    line-height: 1.55;
}
.universe-template-picker {
    width: 100%;
    max-width: 32rem;
}
.universe-template-tagline {
    display: block;
    margin-top: 0.4rem;
    min-height: 1.2em;
    font-style: italic;
}
.universe-textarea {
    width: 100%;
    font-family: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
    font-size: 0.92rem;
    line-height: 1.5;
}
/* v0.83.x (PR 11 fold-in — V149) — era anchor inputs.
   Sibling of the universe prompt, rendered in the same gradient-
   indigo section. Two labels side-by-side on wider viewports; stack
   on narrow ones. The native date/time inputs vary in styling
   per browser/OS — we leave them mostly alone and just align them. */
.era-row {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    margin-top: 0.5rem;
    align-items: flex-start;
}
.era-row > label {
    flex: 1 1 14rem;
    min-width: 12rem;
}
.era-date-input,
.era-time-input {
    width: 100%;
    max-width: 16rem;
    font-family: inherit;
    font-size: 0.95rem;
}
.universe-revisions {
    margin-top: 1rem;
    padding: 0.75rem;
    border-radius: 0.375rem;
    background: rgba(0, 0, 0, 0.18);
}
.universe-revisions > summary {
    cursor: pointer;
    user-select: none;
    font-size: 0.9rem;
    color: var(--text-muted, #94a3b8);
}
.revision-list {
    margin: 0.75rem 0 0 1.25rem;
    padding: 0;
}
.revision-entry {
    margin-bottom: 1rem;
}
.revision-meta {
    font-size: 0.85rem;
    margin-bottom: 0.3rem;
}
.revision-prior {
    white-space: pre-wrap;
    margin: 0;
    padding: 0.6rem 0.75rem;
    background: rgba(0, 0, 0, 0.25);
    border-radius: 0.25rem;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--text-muted, #94a3b8);
    max-height: 16rem;
    overflow-y: auto;
}

/* v0.88.0 (PR 2) — Solana treasury card layout */
.treasury-card .treasury-card-body {
    margin-top: 0.75rem;
}
.treasury-card-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.5rem 1rem;
    margin: 0 0 0.5rem 0;
}
.treasury-card-grid dt {
    font-weight: 600;
    color: var(--text-muted, #94a3b8);
}
.treasury-card-grid dd {
    margin: 0;
    word-break: break-all;
}
/* v0.88.0 audit patch — ux #6: keep the Copy button on a fixed
   axis so it doesn't shift as the pubkey wraps. */
.treasury-pubkey-row {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.treasury-pubkey {
    font-size: 0.85rem;
    user-select: all;
}
.btn.small.inline {
    padding: 0.2rem 0.6rem;
    font-size: 0.8rem;
    vertical-align: middle;
}
.treasury-stale-note {
    grid-column: 1 / -1;
}
.treasury-fetched-note {
    margin-left: 0.4rem;
}
/* v0.88.0 audit patch — ux #2: screen-reader-only text. Standard
   recipe for aria-live regions that announce changes without
   showing visual content. */
.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;
}

/* v0.89.0 UX-pass round 2 — always-visible CTA on the treasury
   card linking to the TREASURER queue. The button + helper text
   share a row so the affordance is obvious + the explanation is
   right next to it. */
.treasury-card-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin: 1rem 0 0.25rem 0;
}

/* v0.89.0 — operator-direct Send SOL form on /treasurer/pending.
   Sits at the top of the page above the approval queue. Compact
   form-stack matching the existing settings forms. */
.send-sol-card {
    margin-bottom: 1.5rem;
}
.send-sol-form .form-row {
    max-width: 600px;
}
.send-sol-form select,
.send-sol-form input[type="number"],
.send-sol-form input[type="text"] {
    font-family: var(--font-monospace, ui-monospace, SFMono-Regular, monospace);
}
.treasury-empty-allowlist {
    margin-top: 0.5rem;
}

/* v0.89.0 — pending-approval chip in the Solana settings header.
   Right-aligned next to the h1 area. */
.solana-pending-chip {
    align-self: center;
    text-decoration: none;
    font-weight: 600;
}

/* v0.89.0 — treasury card recent-activity table. Compact density
   so the card stays scannable. */
.treasury-recent-activity {
    margin-top: 1rem;
}
.treasury-recent-activity h3 {
    margin: 0 0 0.5rem 0;
    font-size: 0.95rem;
    color: var(--text-muted, #94a3b8);
}
.data-table.compact td,
.data-table.compact th {
    padding: 0.3rem 0.6rem;
    font-size: 0.85rem;
}

/* v0.90.0 — unified Solana operator hub at /sites/{slug}/solana.
   The page is structured as four tab-panes (operate / activity /
   allowlist / configure) navigated by plain #anchor links — no JS.
   The status strip + tab nav stick to the top of the viewport so
   the operator can switch sections from any scroll position.

   Visual mode-shift: when the site has mainnet enabled AND the
   master toggle is on, body picks up `.solana-hub.is-mainnet`,
   which:
     - paints a 4px red left-border on .queue-row cards
     - shows the .mainnet-ribbon strip above the status strip
     - tints the page background a hair toward danger
   The signal is loud on purpose. */

.solana-hub {
    /* Container — no special layout, just a scope for hub-specific
       descendant rules so the section markup elsewhere is unaffected. */
}

/* MAINNET ribbon — sits above the status strip when real funds
   move on this site. Pure CSS, no animation by default (reduce
   motion is the safer default; a flashing red bar is anti-pattern
   for accessibility). */
.mainnet-ribbon {
    background: rgba(239, 91, 91, 0.18);
    color: var(--danger);
    border: 1px solid rgba(239, 91, 91, 0.45);
    border-radius: 4px;
    padding: 0.55rem 0.85rem;
    margin: 0.5rem 0 0.75rem;
    font-size: 0.88rem;
    line-height: 1.35;
}
.mainnet-ribbon strong {
    letter-spacing: 0.06em;
    font-weight: 700;
    margin-right: 0.35rem;
}

/* Status strip — always-visible row of monospace chips just below
   the page header. Flex with wrap so it degrades on narrow widths
   to a stacked column rather than overflowing horizontally.

   Sticky to the top of the viewport so the operator keeps balance /
   network / queue-count visible while scrolling through the long
   Configure tab. Tab nav (below) sits flush against the strip and
   stacks on top of it as a single sticky unit. */
.status-strip {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    align-items: center;
    padding: 0.55rem 0.75rem;
    margin: 0 0 0.5rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-family: var(--font-monospace, ui-monospace, SFMono-Regular, monospace);
    font-size: 0.85rem;
}
.status-strip .status-chip {
    /* Inherits from .chip; this class only exists so a future
       designer can target hub-strip chips without touching the
       global .chip rules. */
    font-family: inherit;
}

/* v0.90.0 audit fix (UX HIGH #3) — `.chip.muted` was used in
   hub.html as a "neutral / idle / 0-count" status variant but no
   styling existed for it, so the muted chip rendered identically
   to a default chip. Define it explicitly so the warning-vs-neutral
   tier reads. Pairs with .chip.sev-info / .chip.sev-warning at
   line 2081. */
.chip.muted {
    background: rgba(148, 163, 184, 0.12);
    color: var(--muted, #94a3b8);
    border-color: rgba(148, 163, 184, 0.28);
}

/* Tab nav — flat row of anchor links. The active one (whose href
   matches the current URL hash) is highlighted via :has() + :target
   on the sections below. Browsers without :has support gracefully
   degrade — every link still works, the active style is just
   absent.

   Sticky directly under the status strip so the four tabs remain
   one click away from any scroll position. The `top` value matches
   the rendered strip height (approx ~2.6rem on the operator-facing
   chip set; tune if the chip font/padding changes). */
/* v0.219.2 (research-capture PR B audit fix B - ux BLOCKING #2) —
   share base tab-nav rules across .solana-tabs and .research-tabs.
   Previously these were scoped to .solana-tabs only, so the research
   hub's <nav class="tab-nav research-tabs"> rendered as un-styled
   inline links (no padding, no sticky positioning, no border-bottom
   for the active strip to overlap). */
.tab-nav.solana-tabs,
.tab-nav.research-tabs,
.tab-nav.sec-id-tabs {
    position: sticky;
    top: 2.6rem;
    z-index: 4;
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin: 0.25rem 0 1rem;
    padding: 0.15rem 0 0;
    background: var(--bg, var(--surface));
    border-bottom: 1px solid var(--border);
}
.tab-nav.solana-tabs .tab-link,
.tab-nav.research-tabs .tab-link,
.tab-nav.sec-id-tabs .tab-link {
    padding: 0.5rem 0.95rem;
    color: var(--muted, #94a3b8);
    text-decoration: none;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    font-weight: 500;
    margin-bottom: -1px; /* overlap the parent's bottom border */
}
.tab-nav.solana-tabs .tab-link:hover,
.tab-nav.research-tabs .tab-link:hover,
.tab-nav.sec-id-tabs .tab-link:hover {
    color: var(--text);
    background: var(--surface-2);
}

/* v0.97 ux hotfix — status-strip chips that are also anchor links
   (e.g. "X awaiting approval" → jump to #operate). Need explicit
   text-decoration:none + cursor + hover so they read as clickable. */
.status-strip a.chip-link {
    text-decoration: none;
    cursor: pointer;
}
.status-strip a.chip-link:hover {
    filter: brightness(1.15);
}

/* v0.103.47 (PR4 — letters operator-surface polish) — a chip that
   is also a link, scoped beyond .status-strip (e.g. the unread-letters
   badge on the personality page header). Reuses the .status-error
   palette which v0.97's mood-chip audit fixed to meet WCAG AA via
   background tint + solid border (not the foreground text). */
a.chip {
    text-decoration: none;
    cursor: pointer;
}
a.chip:hover {
    filter: brightness(1.15);
}
a.chip:focus-visible {
    outline: 2px solid var(--accent, #60a5fa);
    outline-offset: 2px;
}

/* Active-tab styling via :target + :has(). The targeted section
   has an id like #operate; if it's the targeted one, its sibling
   tab link gets the active look.

   v0.90 audit follow-up — the prior version used `var(--surface)`
   as the active background, which is too close to the page bg to
   register at a glance. Now: stronger background + accent-colored
   underline strip on the bottom edge so the active tab reads
   immediately. The "tab connects to content" trick (overlapping
   the parent's bottom border) is preserved. */
/* v0.97 iter 2 — also match descendant :target so a deep-link to a
   row inside a section (e.g. #tx-42 inside #operate, or #settled-42
   inside #activity) still highlights the containing tab. Pre-fix the
   tab visual went BLANK after every Approve/Broadcast click because
   the redirect landed on a row-id and the `:has(#operate:target)` rule
   only matched when #operate ITSELF was :target. Operator's "tabs
   don't work" complaint was rooted here. */
.solana-hub:has(#operate:target) .tab-link[data-tab="operate"],
.solana-hub:has(#operate :target) .tab-link[data-tab="operate"],
.solana-hub:has(#activity:target) .tab-link[data-tab="activity"],
.solana-hub:has(#activity :target) .tab-link[data-tab="activity"],
.solana-hub:has(#allowlist:target) .tab-link[data-tab="allowlist"],
.solana-hub:has(#allowlist :target) .tab-link[data-tab="allowlist"],
.solana-hub:has(#configure:target) .tab-link[data-tab="configure"],
.solana-hub:has(#configure :target) .tab-link[data-tab="configure"],
/* Default-tab fallback: when no section is :target (initial page
   load with no #hash), highlight Operate. */
.solana-hub:not(:has(:target)) .tab-link[data-tab="operate"],
/* v0.219.1 (research-capture PR B audit fix - ux BLOCKING) —
   parallel rules for the research hub so the active tab gets the
   same visual treatment. Without these, both tabs always render
   inactive — operators can't tell which one they're on. */
.research-hub:has(#analyses:target) .tab-link[data-tab="analyses"],
.research-hub:has(#analyses :target) .tab-link[data-tab="analyses"],
.research-hub:has(#snapshots:target) .tab-link[data-tab="snapshots"],
.research-hub:has(#snapshots :target) .tab-link[data-tab="snapshots"],
.research-hub:has(#ideas:target) .tab-link[data-tab="ideas"],
.research-hub:has(#ideas :target) .tab-link[data-tab="ideas"],
.research-hub:has(#pipeline:target) .tab-link[data-tab="pipeline"],
.research-hub:has(#pipeline :target) .tab-link[data-tab="pipeline"],
.research-hub:not(:has(:target)) .tab-link[data-tab="analyses"] {
    color: var(--text);
    background: var(--surface);
    border-color: var(--border);
    border-bottom: 2px solid var(--accent, #5b9dff);
    font-weight: 600;
    margin-bottom: -1px;
}

/* v0.348.1 (audit fix - ux BLOCKING #4) — severity styling for the
   research status card. The .sev-* classes are defined for .chip but
   NOT for .card; without scoping, the new status card renders visually
   identical regardless of state, defeating the "operator can tell at
   a glance" premise. Scoped to .research-status-card so this doesn't
   retrofit-color every flash banner in the codebase. */
.research-status-card { border-left: 4px solid var(--border); }
.research-status-card.sev-success { border-left-color: #10b981; }
.research-status-card.sev-warning { border-left-color: #f59e0b; }
.research-status-card.sev-error   { border-left-color: #ef4444; }
.research-status-card.sev-info    { border-left-color: #5b9dff; }

/* v0.219.1 (research-capture PR B audit fix) — apply the
   solana-hub's .tab-pane spacing + sticky-margin rules to the
   research hub too, so anchor jumps don't hide the <h2> behind
   any sticky chrome. */
.research-hub .tab-pane {
    padding-top: 1.25rem;
    margin-top: 1.25rem;
    border-top: 1px dashed var(--border);
    scroll-margin-top: 6rem;
}
.research-hub .tab-pane:first-of-type {
    border-top: none;
    margin-top: 0;
    padding-top: 0.5rem;
}
.research-hub .tab-pane h2 {
    font-size: 1.15rem;
    margin: 0 0 0.5rem;
}

/* v0.219.1 — wrap long judge prompts + JSON payloads so the
   analysis detail page doesn't overflow horizontally. Cap at
   24rem with a vertical scroll so a 200-line completion doesn't
   shove everything else off-screen. */
.research-analysis-detail .payload-json,
.research-analysis-detail .payload-text {
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 24rem;
    overflow: auto;
    font-size: 0.85rem;
    background: var(--surface);
    padding: 0.75rem;
    border-radius: 6px;
    border: 1px solid var(--border);
}

/* Tab panes — sections rendered in DOM order. The page itself
   scrolls to the targeted anchor; we don't hide non-target panes
   (the operator can scroll past them or use Find-in-page across
   all four tabs at once, which is occasionally useful). The
   visual divider is a thin border-top + extra top spacing on
   each pane. */
.solana-hub .tab-pane {
    padding-top: 1.25rem;
    margin-top: 1.25rem;
    border-top: 1px dashed var(--border);
    /* Sticky strip (~2.6rem) + sticky tab nav (~2.4rem) + breathing
       room. Without this, anchor jumps from the tab nav hide the
       <h2> behind the sticky elements. */
    scroll-margin-top: 6rem;
}
.solana-hub .tab-pane:first-of-type {
    border-top: none;
    margin-top: 0;
    padding-top: 0.5rem;
}
.solana-hub .tab-pane h2 {
    font-size: 1.15rem;
    margin: 0 0 0.5rem;
}

/* Send SOL details expander. The summary carries a button-styled
   span so the closed state reads as a primary CTA; open state
   reveals the form inside. */
.send-sol-expander {
    margin-bottom: 1.25rem;
}
.send-sol-expander > summary {
    list-style: none;
    cursor: pointer;
    padding: 0;
    margin: 0 0 0.5rem;
}
.send-sol-expander > summary::-webkit-details-marker { display: none; }
.send-sol-expander > summary::marker { content: ''; }
.send-sol-expander[open] > summary { margin-bottom: 0.75rem; }
.send-sol-body {
    padding: 0.75rem 0 0.25rem;
}
.send-sol-body select,
.send-sol-body input[type="number"],
.send-sol-body input[type="text"] {
    font-family: var(--font-monospace, ui-monospace, SFMono-Regular, monospace);
}

/* Queue row — the daily-driver card layout for awaiting-approval
   and awaiting-broadcast rows. Grid keeps the action stack at the
   right edge regardless of viewport width; on narrow screens the
   info column wraps but the buttons stay reachable.

   Replaces the wide-table layout that pushed Broadcast off-screen
   on narrow viewports + the <th:block th:each> inside <tbody> bug
   class entirely (divs have no child-element restrictions). */
.queue-section {
    margin: 1.25rem 0;
}
.queue-section > h3 {
    font-size: 1rem;
    margin: 0.25rem 0 0.5rem;
}
.queue-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0.85rem;
    align-items: center;
    padding: 0.75rem 0.85rem;
    margin: 0.5rem 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
}
.queue-row-info {
    min-width: 0; /* allow ellipsis if content overflows */
}
.queue-row-line1 {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: baseline;
    margin-bottom: 0.2rem;
}
.queue-row-line1 strong {
    font-size: 1rem;
}
.queue-row-line2 code {
    word-break: break-all;
    font-size: 0.78rem;
}
.queue-row-line3 {
    margin-top: 0.2rem;
}
.queue-row-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.4rem;
    min-width: 8rem;
}
.queue-row-actions .btn {
    text-align: center;
    white-space: nowrap;
}
.queue-row-actions form.inline {
    display: block; /* override .inline horizontal default */
}
.queue-reject-details > summary {
    cursor: pointer;
    list-style: none;
    text-align: center;
}
.queue-reject-details > summary::-webkit-details-marker { display: none; }
.queue-reject-details > summary::marker { content: ''; }
.queue-reject-details[open] > summary {
    margin-bottom: 0.35rem;
}
.queue-reject-details form input[type="text"] {
    width: 100%;
    margin-bottom: 0.35rem;
}

/* MAINNET visual mode — 4px red left-border on every queue card.
   The status-strip MAINNET chip + ribbon already shout; the
   left-border is the per-row reminder so the operator sees the
   signal at the exact moment they're about to click a button. */
.solana-hub.is-mainnet .queue-row {
    border-left: 4px solid var(--danger);
}

/* Settled rows — recently-broadcast tx table. When the operator
   broadcasts a row, the redirect appends ?broadcastOk=...#settled-<id>;
   the matching <tr id="settled-<id>"> picks up :target and flashes
   for 2s to confirm the action landed without bouncing the operator
   off-page. */
.solana-hub .settled-row:target {
    animation: settled-flash 2s ease-out 1;
    background: rgba(74, 222, 128, 0.12);
}
@keyframes settled-flash {
    0%   { background: rgba(74, 222, 128, 0.35); }
    100% { background: rgba(74, 222, 128, 0.0); }
}

/* v0.97 round 2 — pending + approved rows share the same per-row
   id (tx-<id>); after Approve the redirect lands at #tx-<id> so
   the operator's eye follows the row from "awaiting approval" into
   "awaiting broadcast". Same after a Mint POST: the redirect lands
   at the freshly-queued pending row. Blue/info flash to differ
   from the green/success post-broadcast flash. */
.solana-hub .tx-row:target {
    animation: tx-row-flash 2s ease-out 1;
    background: rgba(91, 157, 255, 0.12);
}
@keyframes tx-row-flash {
    0%   { background: rgba(91, 157, 255, 0.35); }
    100% { background: rgba(91, 157, 255, 0.0); }
}

/* v0.97 round 2 — pubkey utility class. Replaces the inline
   `style="word-break: break-all"` repeated on six `<code>` elements
   across the Solana hub. <code> already inherits monospace from the
   global cascade, so this rule only sets the wrapping behavior. */
.pubkey { word-break: break-all; }

/* v0.97 round 3 — pubkey copy button. Renders next to every
   <code class="pubkey"> element via pubkey-copy.js. Sized small so
   it doesn't disrupt the table row rhythm; .copied state nudges the
   color to green so the operator sees the affordance fired. */
.pubkey-copy-btn {
    display: inline-block;
    margin-left: 0.3rem;
    padding: 0 0.25rem;
    font-size: 0.85rem;
    line-height: 1.2;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 3px;
    cursor: pointer;
    color: var(--text-muted, #94a3b8);
    vertical-align: baseline;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.pubkey-copy-btn:hover,
.pubkey-copy-btn:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.12);
    color: var(--text, #e6e6e6);
    outline: none;
}
.pubkey-copy-btn.copied {
    color: #4ade80;
    border-color: rgba(74, 222, 128, 0.3);
}

/* Narrow-viewport: card layout collapses to a single column so
   the action stack moves below the info instead of squashing it.
   ~720px is where the recipient pubkey + chip row starts to wrap
   into 3 lines and the action column becomes the dominant width.  */
@media (max-width: 720px) {
    .queue-row {
        grid-template-columns: 1fr;
    }
    .queue-row-actions {
        flex-direction: row;
        flex-wrap: wrap;
        min-width: 0;
    }
    .queue-row-actions .btn {
        flex: 1 1 auto;
    }
}

/* v0.93.0 — Office Cat ticker. Slim, italic, muted; sits between the
   top-nav and any banner. Click target spans the row; the icon stays
   visually quiet so the line reads like a bit of marginalia, not a
   call-to-action. The fade-out class lasts ~240ms so the inline JS
   rotation feels like the cat's attention drifted, not a hard cut. */
.cat-ticker {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.45rem 1rem;
    margin: 0 auto 0.65rem;
    max-width: 1440px;
    border-top: 1px dotted var(--border);
    border-bottom: 1px dotted var(--border);
    background: rgba(255, 255, 255, 0.015);
    color: var(--muted);
    font-style: italic;
    font-size: 0.85rem;
    line-height: 1.4;
}
.cat-ticker-icon {
    flex: 0 0 auto;
    font-style: normal;
    opacity: 0.7;
}
.cat-ticker-link {
    flex: 1 1 auto;
    color: inherit;
    text-decoration: none;
    min-width: 0;
}
.cat-ticker-link:hover .cat-ticker-text,
.cat-ticker-link:focus-visible .cat-ticker-text {
    color: var(--text-soft);
}
.cat-ticker-text {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: opacity 0.24s ease;
}
.cat-ticker-text.cat-ticker-fading {
    opacity: 0;
}
.cat-ticker-more {
    flex: 0 0 auto;
    margin-left: 0.5rem;
    font-style: normal;
}
/* v0.93 audit (UX HIGH-2) — cat yields to error states too. The
   ticker is decorative ambient flavor; rendering "everyone's typing
   fast today" above a 500 banner is tone-deaf. CSS :has() is widely
   supported in modern browsers (Chrome 105+, Firefox 121+, Safari
   15.4+); on older browsers the rule simply doesn't apply and the
   ticker still renders — degrading openly to the pre-fix behavior. */
main:has(.alert.error) .cat-ticker,
main:has(.alert.warning) .cat-ticker,
main:has([aria-invalid="true"]) .cat-ticker {
    display: none;
}

/* Office cat page feed — a column of timestamps + observations, one
   row per cat post. Sits naturally next to the persona description
   above; deliberately quieter than the agent-detail memory list (the
   cat is flavor, not signal). */
.cat-feed {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.cat-feed-row {
    display: grid;
    grid-template-columns: 11rem 1fr;
    gap: 0.85rem;
    align-items: baseline;
    padding-bottom: 0.85rem;
    border-bottom: 1px dotted var(--border);
}
.cat-feed-row:last-child {
    border-bottom: none;
    padding-bottom: 0;
}
.cat-feed-meta {
    font-variant-numeric: tabular-nums;
}
.cat-feed-body {
    color: var(--text-soft);
    font-style: italic;
    line-height: 1.5;
}
@media (max-width: 720px) {
    .cat-feed-row {
        grid-template-columns: 1fr;
        gap: 0.2rem;
    }
}

/* v0.93 — Friction & repair insights timeline. Two memory kinds in one
   chronological feed; chips distinguish them at a glance. Friction is
   warm (amber, "things heated up") and repair is cool (teal, "things
   cooled off"). Neither dominates — the page is meant to read as the
   arc, not a list of incidents. */
.repair-feed {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.repair-row {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding-bottom: 0.85rem;
    border-bottom: 1px dotted var(--border);
}
.repair-row:last-child {
    border-bottom: none;
    padding-bottom: 0;
}
.repair-meta {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.repair-body .repair-summary {
    margin: 0;
    color: var(--text-soft);
    line-height: 1.5;
}
/* v0.93 audit (UX HIGH-3) — amber vs teal converges under protanopia.
   The chips carry text labels (already disambiguating), but a glyph
   prefix makes the kind scannable without parsing the word. ⚡ for
   friction (a small bolt), 🩹 for repair (the band-aid). */
.chip.chip-friction {
    background: rgba(245, 158, 11, 0.18);
    color: #fbbf24;
    border-color: rgba(245, 158, 11, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.chip-friction::before {
    content: "⚡ ";
    font-style: normal;
}
.chip.chip-reconciliation {
    background: rgba(20, 184, 166, 0.18);
    color: #5eead4;
    border-color: rgba(20, 184, 166, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.chip-reconciliation::before {
    content: "🩹 ";
    font-style: normal;
}

/* v0.94 — Solana hub MINT_NFT row chip. Operator scanning the queue
   needs to instantly distinguish "Mint NFT for Mochi" from a normal
   SOL transfer; the chip carries the kind + the agent name renders
   inline next to it. v0.94 audit (ux MED-3) — shifted from violet
   (clashed with the coordinator-hue family used for TREASURER /
   CHIEF_OF_STAFF chips) to amber so the chip's coin semantic is
   visually distinct on the queue. */
.chip.chip-mint-nft {
    background: rgba(251, 191, 36, 0.18);
    color: #fbbf24;
    border-color: rgba(251, 191, 36, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* v0.97 round 1 — MINT_NFT amount cell. Chip + a subject sub-line
   so the row reads as "what kind of write" + "what it's about" in
   the same visual rhythm as the SOL rows ("0.5 SOL"). Pre-fix the
   subject was prefixed with "→ <name>" which read as a recipient —
   explicit "subject:" label avoids that confusion.

   v0.97 round 2 — switched from column-flex (which made MINT_NFT
   rows ~40px tall against ~22px SOL siblings in the same table) to
   row-flex with wrap. Chip + subject sit on one line on normal
   viewports, fall to two on narrow ones; either way the row matches
   sibling row heights at standard widths. */
.mint-nft-amount {
    display: inline-flex;
    flex-direction: row;
    gap: 0.4rem;
    align-items: baseline;
    flex-wrap: wrap;
}
.mint-nft-subject { line-height: 1.2; }

/* v0.96 — Solana hub mint sub-page. Recent-mints list shows past
   arbitrary mints (the operator-driven ones that aren't tied to a
   genesis claim) with one-line meta + fulfillment chip. */
.mint-feed {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.mint-row {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding-bottom: 0.85rem;
    border-bottom: 1px dotted var(--border);
}
.mint-row:last-child {
    border-bottom: none;
    padding-bottom: 0;
}
.mint-meta {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.mint-body {
    line-height: 1.5;
    word-break: break-all;
}
.mint-body code {
    font-size: 0.85rem;
}
/* Combined audit (code NICE-4) — these extend the .chip.sev-* family
   established at line 2292 (sev-info / sev-warning / sev-error). The
   sev-* namespace is the codebase's chip-variant convention even
   though "success" and "pending" aren't strictly severity levels. */
.chip.sev-success {
    background: rgba(74, 222, 128, 0.18);
    color: #4ade80;
    border-color: rgba(74, 222, 128, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.chip.sev-pending {
    background: rgba(245, 158, 11, 0.18);
    color: #fbbf24;
    border-color: rgba(245, 158, 11, 0.55);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Mint-NFT form preview block — collapsible <details> wrapper around
   the live JSON. Keeps the preview unobtrusive when the operator
   isn't checking + scannable when they are. */
.mint-preview {
    margin-top: 0.4rem;
}
.mint-preview summary {
    cursor: pointer;
    color: var(--muted);
    font-style: italic;
}
.mint-preview pre {
    font-family: ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
    font-size: 0.85rem;
    line-height: 1.5;
}

/* Combined audit (ux NICE-4) — small explanatory note inside an
   .alert. Drop inline styles in mint-nft.html for a class. */
.alert .alert-note {
    margin-top: 0.5rem;
    margin-bottom: 0;
}

/* v0.98 — chat composer with image attach. Paste / file-picker
   sits below the textarea; preview thumbnail renders above the
   action bar once a file is staged. The 📎 attach button is
   styled as a secondary btn so it reads as a peer to the primary
   Send button.

   v0.98 audit (ux IMPORTANT-3): form-actions wraps on narrow
   viewports so attach + Send + helper text stack instead of
   pushing Send off-screen. */
.chat-composer .form-actions {
    flex-wrap: wrap;
}
.chat-composer .chat-image-attach {
    cursor: pointer;
}
.chat-composer .chat-image-preview {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.5rem 0;
    padding: 0.5rem;
    background: rgba(91, 157, 255, 0.06);
    border: 1px solid rgba(91, 157, 255, 0.25);
    border-radius: 4px;
}
/* v0.190.x — the rule above sets display:flex, which beats the
   browser's user-agent [hidden] { display: none } and left the
   Remove button visible on first paint of every chat composer.
   Restore the intended hidden-by-default state for the preview
   block until chat-image-attach.js sets preview.hidden = false. */
.chat-composer .chat-image-preview[hidden] {
    display: none;
}
.chat-composer .chat-image-thumb {
    max-width: 8rem;
    max-height: 6rem;
    object-fit: cover;
    border-radius: 3px;
    border: 1px solid rgba(255, 255, 255, 0.1);
}
.chat-composer .chat-image-error {
    margin: 0.5rem 0;
}

/* v0.98 — operator-uploaded images render inline in chat history.
   Slight rounded border + thumbnail-style max-height so a screenshot
   doesn't dominate the message column. Click to expand (browser
   default — the <img> wraps an <a> when we want the full-resolution
   view). */
.chat-message-image {
    max-width: 100%;
    max-height: 20rem;
    margin-top: 0.4rem;
    border-radius: 4px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    display: block;
}

/* v0.98.1 — DAA-skin for the arbitrary-mint page. Easter-egg trim
   that evokes the OG Degen Ape Academy mint UI (Aug 2021): chunky
   blocky display type, retro-neon accent on the primary CTA, a
   "minted on this site" counter chip, and a 🦍 footnote. Scoped
   under .daa-skin so the rest of the agency keeps its current
   visual language. */
.daa-skin .daa-page-header h1 {
    font-family: "Courier New", ui-monospace, SFMono-Regular, monospace;
    letter-spacing: 0.18em;
    font-weight: 900;
    text-transform: uppercase;
    margin-bottom: 0.4rem;
}
.daa-skin .daa-mint-counter {
    display: inline-block;
    margin-bottom: 0.5rem;
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-weight: 700;
    letter-spacing: 0.05em;
    background: rgba(255, 215, 0, 0.18);
    color: #ffd700;
    border-color: rgba(255, 215, 0, 0.45);
    text-transform: uppercase;
}
.daa-skin .daa-mint-btn {
    font-family: "Courier New", ui-monospace, SFMono-Regular, monospace;
    font-size: 1.3rem;
    font-weight: 900;
    letter-spacing: 0.25em;
    padding: 0.85rem 2.5rem;
    background: linear-gradient(135deg, #ffd700, #ff6b9d);
    color: #1a0f2e;
    border: 2px solid rgba(255, 215, 0, 0.6);
    box-shadow: 0 0 12px rgba(255, 107, 157, 0.35),
                0 0 24px rgba(255, 215, 0, 0.25);
    text-shadow: none;
    transition: transform 80ms ease-out, box-shadow 120ms ease-out;
}
.daa-skin .daa-mint-btn:hover:not(:disabled) {
    transform: translateY(-1px);
    box-shadow: 0 0 18px rgba(255, 107, 157, 0.55),
                0 0 36px rgba(255, 215, 0, 0.35);
}
.daa-skin .daa-mint-btn:active:not(:disabled) {
    transform: translateY(1px);
}
.daa-skin .daa-mint-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.daa-skin .daa-footer {
    margin-top: 1rem;
    font-size: 0.8rem;
    text-align: center;
    letter-spacing: 0.05em;
    opacity: 0.55;
}
.daa-skin .daa-footer em {
    font-style: italic;
    color: #ffd700;
}
/* Outline-style focus on form inputs inside the skin — slight pixel
   feel without going so far that the form stops reading as a form. */
.daa-skin input[type="text"]:focus,
.daa-skin input[type="number"]:focus,
.daa-skin textarea:focus,
.daa-skin select:focus,
.daa-skin input[type="file"]:focus {
    outline: 2px dashed rgba(255, 215, 0, 0.7);
    outline-offset: 2px;
}

/* v0.97 — reactor agent's reaction to a fulfilled NFT mint. Rendered
   below each row in the Recent mints list (and on the agent detail
   page near the genesis NFT card). Blockquote with a subtle left-
   border accent + an attribution line. Accent color ties into the
   chip-mint-nft hue so the visual lineage reads.

   v0.97 audit (ux NICE-6): dropped italic on the body text —
   WCAG 1.4.8 flags long italic runs as low-readability for some
   readers (including dyslexic). The blockquote semantics + the
   accent border + the cite footer carry "quoted speech" without
   needing italic on the body itself. */
.mint-reaction {
    margin: 0.5rem 0 0;
    padding: 0.45rem 0.75rem;
    border-left: 3px solid rgba(91, 157, 255, 0.4);
    background: rgba(91, 157, 255, 0.06);
    border-radius: 0 4px 4px 0;
}
.mint-reaction p {
    margin: 0;
    line-height: 1.5;
}
.mint-reaction footer {
    margin-top: 0.2rem;
}
.mint-reaction-pending {
    margin: 0.4rem 0 0;
    font-style: italic;
}

/* v0.99 — generic key-value list. Used by any operator-facing
   metadata block (Arweave panel under Solana hub, Genesis NFT
   detail card on the agent page) where stacked term/value rows
   read better than a table. The genesis-nft scope below
   (higher specificity) keeps that card's existing tweaks. */
.kv-list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.35rem 0.85rem;
    margin: 0.85rem 0 0.4rem;
}
.kv-list dt {
    color: var(--muted);
    font-size: 0.85rem;
}
.kv-list dd {
    margin: 0;
    word-break: break-all;
}

/* v0.94 — Genesis NFT detail card on agent page. Stacked dl rows for
   the fulfilled state's mint-address / tx-sig / metadata-uri triplet.
   Each row gets a fixed-width term label + flex value column so long
   base58 addresses + URIs wrap cleanly on narrow viewports. */
#agent-genesis-nft .kv-list {
    display: grid;
    grid-template-columns: 10rem 1fr;
    gap: 0.45rem 1rem;
    margin: 0.85rem 0 0.4rem;
}
#agent-genesis-nft .kv-list dt {
    color: var(--muted);
    font-size: 0.85rem;
}
#agent-genesis-nft .kv-list dd {
    margin: 0;
    word-break: break-all;
}
#agent-genesis-nft .form-stack {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 0.85rem;
}
#agent-genesis-nft .form-stack label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
@media (max-width: 640px) {
    #agent-genesis-nft .kv-list {
        grid-template-columns: 1fr;
        gap: 0.2rem 0;
    }
}

/* v0.93 — Activity heatmap. 7 (dow) × 24 (hour) grid + row/column
   labels. Cell color quantizes counts into 5 buckets (heat-0..4)
   so an operator can see the shape of the week without staring at
   numbers. Borrows the GitHub contribution-graph idiom. */
.heatmap-wrap {
    overflow-x: auto;
}
.heatmap-grid {
    display: grid;
    grid-template-columns: 2.6rem repeat(24, 1fr);
    gap: 2px;
    align-items: center;
    min-width: 36rem;
    margin: 0.85rem 0;
}
.heatmap-corner {
    grid-row: 1;
    grid-column: 1;
}
.heatmap-hour-label {
    grid-row: 1;
    text-align: center;
    font-size: 0.7rem;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.heatmap-day-label {
    font-size: 0.75rem;
    color: var(--muted);
    text-align: right;
    padding-right: 0.45rem;
    line-height: 1;
}
.heatmap-cell {
    aspect-ratio: 1 / 1;
    border-radius: 2px;
    transition: outline 0.12s ease;
    min-height: 14px;
}
.heatmap-cell:hover {
    outline: 1px solid var(--accent);
}
.heat-0 {
    /* v0.93 audit (UX MED-10) — bumped from 0.04 to 0.08 so empty
       cells stay hoverable + carry a faint outline showing their
       grid position. Below 0.08 they're invisible on the dark theme
       and operators don't know they can hover for the count. */
    background: rgba(255, 255, 255, 0.08);
}
.heat-1 {
    background: rgba(91, 157, 255, 0.20);
}
.heat-2 {
    background: rgba(91, 157, 255, 0.40);
}
.heat-3 {
    background: rgba(91, 157, 255, 0.65);
}
.heat-4 {
    background: rgba(91, 157, 255, 0.90);
}
.heatmap-legend {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    justify-content: flex-end;
    margin-top: 0.6rem;
}
.heatmap-legend-cell {
    display: inline-block;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 2px;
}


/* ===========================================================
 * v0.101 (Agent Travel PR1) — Travel Hub
 * Tabs collapse via <details>; each card is unstyled by default
 * to keep PR1 lean. PR4 adds the Transporter Delight bundle
 * (CSS scanline animation, era-aware passport flourish, etc.).
 * =========================================================== */
.travel-tab {
    margin: 0.75rem 0;
    border: 1px solid var(--surface-border, rgba(255, 255, 255, 0.08));
    border-radius: 6px;
    padding: 0.5rem 1rem;
}
.travel-tab > summary {
    cursor: pointer;
    font-weight: 600;
    padding: 0.25rem 0;
    user-select: none;
    /* Hide native disclosure triangle so the custom chevron below is
       the only affordance; ::before puts an explicit ▸/▾ in front of
       the tab label so the click target reads as expandable. */
    list-style: none;
}
.travel-tab > summary::-webkit-details-marker {
    display: none;
}
.travel-tab > summary::before {
    content: "▸";
    display: inline-block;
    width: 1rem;
    margin-right: 0.25rem;
    transition: transform 0.12s ease;
    opacity: 0.7;
}
.travel-tab[open] > summary::before {
    transform: rotate(90deg);
}
.travel-tab > summary .tab-label {
    margin-right: 0.5rem;
}
.travel-tab > summary .tab-count {
    display: inline-block;
    min-width: 1.5rem;
    text-align: center;
    padding: 0 0.4rem;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.08);
    font-size: 0.85em;
    font-weight: 500;
}

.agent-row-list {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
}
.agent-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.agent-row:last-child {
    border-bottom: none;
}

/* Residency chips — VISITING + MOVING render with a hint of
   color; HOME never shows a chip (no signal worth the noise).
   PR4 will swap these for the era-aware passport-stamp palette. */
.chip.residency-visiting {
    background: rgba(91, 157, 255, 0.18);
    color: #b5d2ff;
}
.chip.residency-moving {
    /* Bumped from 0.18 → 0.32 background opacity + lighter text for
       AA contrast against the dark surface — the lighter background
       was borderline at 4.5:1. */
    background: rgba(255, 184, 91, 0.32);
    color: #ffe3b8;
}
.chip.residency-home {
    background: rgba(255, 255, 255, 0.06);
}

/* Generic single-card info box used on the papers page header. */
.info-card {
    padding: 0.75rem 1rem;
    border: 1px solid var(--surface-border, rgba(255, 255, 255, 0.08));
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.02);
}

/* Transit-paper "stamps" — slightly-rotated rectangular cards.
   PR4 wires CSS variables off the entry's destination era_offset
   bucket (1968 = orange Courier; 2099 = neon-cyan border; 1923 =
   sepia tint). For PR1 they all render in the same neutral
   palette so the layout is finalised first. */
.transit-paper-stamps {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0;
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}
.transit-stamp {
    width: 16rem;
    padding: 0.75rem 1rem;
    border: 1.5px dashed rgba(255, 255, 255, 0.25);
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.03);
    transform: rotate(-1.5deg);
    transition: transform 0.15s ease;
    font-family: 'Courier New', Courier, monospace;
}
.transit-stamp:nth-child(even) {
    transform: rotate(1deg);
}
.transit-stamp:hover {
    transform: rotate(0deg);
}
.transit-stamp .stamp-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    border-bottom: 1px solid rgba(255, 255, 255, 0.15);
    padding-bottom: 0.25rem;
    margin-bottom: 0.5rem;
}
.transit-stamp .stamp-kind {
    font-weight: 700;
    letter-spacing: 0.05em;
}
.transit-stamp .stamp-date {
    font-size: 0.75em;
    opacity: 0.7;
}
.transit-stamp .stamp-notes {
    font-style: italic;
    margin-top: 0.5rem;
}
.transit-stamp .stamp-extensions {
    font-size: 0.7em;
    margin-top: 0.5rem;
    word-break: break-word;
}
/* Per-kind tints (placeholder palette; PR4 refines). */
.transit-stamp.kind-born      { border-color: rgba(124, 130, 255, 0.5); }
.transit-stamp.kind-restored  { border-color: rgba(255, 200, 91, 0.5); }
.transit-stamp.kind-transferred_in { border-color: rgba(91, 220, 255, 0.5); }
.transit-stamp.kind-visited   { border-color: rgba(91, 255, 184, 0.5); }
.transit-stamp.kind-visit_returned { border-color: rgba(184, 91, 255, 0.5); }
.transit-stamp.kind-moved_in  { border-color: rgba(255, 91, 124, 0.5); }

/* v0.101 (Agent Travel PR2) — boarding-pass form + hub footer. */
.boarding-pass-form {
    border: 1px dashed rgba(255, 255, 255, 0.15);
    padding: 0.5rem 0.75rem;
    border-radius: 6px;
}
.boarding-pass-form > summary {
    cursor: pointer;
    padding: 0.25rem 0;
    user-select: none;
}
.boarding-pass-form .stacked-form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.boarding-pass-form .form-row {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.boarding-pass-form .form-row label {
    font-weight: 500;
    font-size: 0.9em;
}
.boarding-pass-form .form-row textarea,
.boarding-pass-form .form-row select,
.boarding-pass-form .form-row input {
    max-width: 28rem;
}

.agent-row-actions {
    display: inline-flex;
    gap: 0.4rem;
    margin-left: auto;
}

.travel-hub-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    font-size: 0.85em;
}

/* ============================================================
   v0.103.0 — SEC_ID operator page. Tables + scan form layout.
   ============================================================ */
.sec-id-scan-card {
    border-left: 3px solid rgba(192, 132, 252, 0.55);  /* jewel-tone, matches research-only chip */
}
.sec-id-form .form-row {
    margin-bottom: 0.85rem;
}
.sec-id-scan-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 0.4rem;
}
.sec-id-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.6rem;
    font-size: 0.92rem;
}
.sec-id-table th,
.sec-id-table td {
    padding: 0.55rem 0.7rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    vertical-align: top;
    text-align: left;
}
.sec-id-table th {
    color: var(--muted);
    font-weight: 600;
    font-size: 0.82rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    background: rgba(255, 255, 255, 0.02);
}
.sec-id-table tr:hover { background: rgba(91, 157, 255, 0.04); }
.sec-id-state-form {
    margin: 0.4rem 0 0.8rem;
}
.sec-id-state-result {
    margin-top: 0.4rem;
    padding: 0.75rem 1rem;
    background: rgba(91, 157, 255, 0.06);
    border-left: 3px solid var(--accent, #5b9dff);
    border-radius: 4px;
}
.sec-id-social-list {
    list-style: none;
    padding: 0;
    margin: 0.4rem 0 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 0.5rem 1rem;
}
.sec-id-social-list li {
    padding: 0.4rem 0.6rem;
    background: rgba(255, 255, 255, 0.02);
    border-radius: 4px;
}
.sec-id-social-list code {
    margin: 0 0.4rem;
    color: var(--muted);
    font-size: 0.82rem;
}
/* PR2 — scan results panel */
.sec-id-results {
    border-left: 3px solid rgba(244, 114, 182, 0.55);  /* pink for "found / acted" */
}
/* v0.103.0 audit (ux IMPORTANT #4) — bumped tint from 0.04 → 0.10
   so the hit-row is clearly differentiated on the dark theme. */
.sec-id-row-hit {
    background: rgba(244, 114, 182, 0.10);
    border-left: 3px solid rgba(244, 114, 182, 0.45);
}
.sec-id-row-hit strong { color: #f472b6; }
.sec-id-row-miss td { opacity: 0.78; }
/* v0.103.0 audit (ux IMPORTANT #1) — hit/miss chips extracted from
   inline styles into proper classes so the chip vocabulary is
   consistent. */
.sec-id-chip-hit {
    background: rgba(244, 114, 182, 0.15);
    color: #f472b6;
    border-color: rgba(244, 114, 182, 0.4);
}
.sec-id-chip-miss {
    background: rgba(255, 255, 255, 0.04);
    color: var(--muted);
    border-color: rgba(255, 255, 255, 0.12);
}
.sec-id-excerpt {
    max-width: 36em;
    line-height: 1.4;
}
.sec-id-row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    white-space: nowrap;
}
.sec-id-empty {
    padding: 1.2rem 0;
    text-align: center;
}

/* ----- v0.291.x (SEC_ID Phase B PR4) — breach check ----- */

.sec-id-breach-card label {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    margin-top: 0.5rem;
}

.sec-id-breach-card input[type="password"],
.sec-id-breach-card input[type="text"] {
    width: 100%;
    max-width: 500px;
}

.sec-id-breach-result {
    margin-top: 0.8rem;
    padding: 0.6rem 0.9rem;
    border-radius: 0.4rem;
    border: 1px solid transparent;
    min-height: 1.2rem;
    font-weight: 500;
}

.sec-id-breach-result:empty {
    padding: 0;
    border: none;
    min-height: 0;
}

.sec-id-breach-result.pending {
    background: var(--surface-2, rgba(255, 255, 255, 0.04));
    border-color: var(--border);
    color: var(--muted);
}

.sec-id-breach-result.error {
    /* Leaked password — bad news at a glance. */
    background: rgba(248, 113, 113, 0.12);
    border-color: var(--danger, #f87171);
    color: var(--danger, #fca5a5);
}

.sec-id-breach-result.ok {
    /* Not in any known breach. */
    background: rgba(52, 211, 153, 0.12);
    border-color: var(--success, #34d399);
    color: var(--success, #6ee7b7);
}

.sec-id-breach-result.warning {
    /* HIBP unreachable / rate-limited. Yellow = try again, not
       "leaked." Critical that this doesn't read as either of the
       other two states. */
    background: rgba(250, 204, 21, 0.12);
    border-color: #facc15;
    color: #fde68a;
}

/* ----- v0.289.x (SEC_ID Phase B PR2b) — Removals tracker tab ----- */

.sec-id-removals-card {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

.sec-id-removal-filter {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    padding: 0.4rem 0;
}

.sec-id-removal-filter a.chip {
    text-decoration: none;
    cursor: pointer;
}

.sec-id-removal-filter a.chip.active {
    background: var(--accent-soft, rgba(99, 102, 241, 0.18));
    border-color: var(--accent, #818cf8);
    color: var(--accent, #c7d2fe);
    font-weight: 600;
}

.sec-id-removal-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.sec-id-removal-list > li {
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    background: var(--surface-1, rgba(255, 255, 255, 0.02));
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.sec-id-removal-list > li.sec-id-row-pending {
    border-left: 3px solid var(--accent, #818cf8);
}
.sec-id-removal-list > li.sec-id-row-confirmed {
    border-left: 3px solid var(--success, #34d399);
}
.sec-id-removal-list > li.sec-id-row-rejected {
    border-left: 3px solid var(--danger, #f87171);
}
.sec-id-removal-list > li.sec-id-row-stale {
    border-left: 3px solid var(--muted, #94a3b8);
    opacity: 0.85;
}

.sec-id-removal-head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: baseline;
}

.sec-id-retention {
    margin: 0;
}

.sec-id-removal-update-form {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

.sec-id-removal-update-form label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 1 1 auto;
    min-width: 200px;
}

.sec-id-removal-update-form input[type="text"] {
    flex: 1 1 auto;
    min-width: 150px;
}

.sec-id-removal-history {
    margin: 0;
}

.sec-id-removal-history summary {
    cursor: pointer;
    padding: 0.2rem 0;
}

.sec-id-removal-timeline {
    list-style: none;
    padding: 0.4rem 0 0 0.8rem;
    border-left: 2px solid var(--border);
    margin: 0.4rem 0;
}

.sec-id-removal-timeline > li {
    padding: 0.2rem 0;
}

.sec-id-removal-notes {
    /* Preserve broker-reply paragraph breaks when an operator pastes
       a confirmation email into the notes field. v0.289.1 audit
       (ux IMPORTANT). */
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0.4rem 0 0;
    padding: 0.4rem;
    background: var(--surface-2, rgba(255, 255, 255, 0.03));
    border-radius: 0.3rem;
}

.sec-id-removal-pager {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    padding: 0.4rem 0;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ============================================================
   v0.101.x (BEACON ADMIN PR1) — super-admin beacon observability.
   All selectors namespaced `.beacon-` or `.responsive-table` (the
   one generic primitive added here). Tag hue tokens alias existing
   jewel-accent tokens per the audit recommendation — no new
   chromatic vocabulary.
   ============================================================ */

/* Generic primitive — wrap any wide <table> so it scrolls
   horizontally below ~720px instead of overflowing the viewport.
   Plan-mandated KISS responsive solution. */
.responsive-table {
    overflow-x: auto;
    max-width: 100%;
    margin: 0.5rem 0;
}
.responsive-table table {
    min-width: 800px;
}

/* ---- Filter card --------------------------------------------- */
.admin-filter-card { margin-bottom: 1rem; }
.admin-filter-form fieldset {
    border: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    align-items: flex-end;
}
.admin-filter-row {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 9rem;
}
.admin-filter-row label { font-size: 0.75rem; }
.admin-filter-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-left: auto;
}

/* Active-filter chip strip — pure CSS, server-rendered, one chip
   per non-default dimension with × to drop it. */
.beacon-active-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin: 0.5rem 0 1rem;
}
.beacon-active-filters .chip {
    text-decoration: none;
    cursor: pointer;
}
.beacon-active-filters .chip:hover { filter: brightness(1.2); }

/* ---- Atlas table --------------------------------------------- */
.beacon-atlas-card { margin-top: 0.5rem; }
.beacon-atlas-summary { margin: 0 0 0.6rem; }
.beacon-atlas-empty { padding: 1rem 0; }
.beacon-atlas-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.86rem;
}
.beacon-atlas-table th,
.beacon-atlas-table td {
    padding: 0.45rem 0.6rem;
    border-bottom: 1px solid var(--border);
    vertical-align: top;
    text-align: left;
}
.beacon-atlas-table th {
    font-weight: 600;
    color: var(--text-soft);
    background: var(--surface-2);
    position: sticky;
    top: 0;
}
.beacon-atlas-table tbody tr:hover {
    background: rgba(91, 157, 255, 0.06);
}
.beacon-atlas-glyph-col {
    width: 2.5rem;
    text-align: center;
    font-size: 1.2rem;
    line-height: 1;
}
.beacon-atlas-table .beacon-atlas-glyph-col {
    /* Sticky-left so the source-world identity stays visible when
       the table scrolls horizontally. The next column (From) is
       also sticky so the world NAME stays visible on mobile —
       glyph alone is ambiguous between similar worlds. */
    position: sticky;
    left: 0;
    background: var(--surface);
    z-index: 1;
}
.beacon-atlas-table tbody tr:hover .beacon-atlas-glyph-col,
.beacon-atlas-table tbody tr:hover .beacon-atlas-from-col {
    background: rgba(91, 157, 255, 0.10);
}
.beacon-atlas-table .beacon-atlas-from-col {
    position: sticky;
    left: 2.5rem;
    background: var(--surface);
    z-index: 1;
    min-width: 10rem;
}
.beacon-atlas-table thead th.beacon-atlas-glyph-col,
.beacon-atlas-table thead th.beacon-atlas-from-col {
    background: var(--surface-2);
    z-index: 2;
}
/* Small primitives moved out of inline styles per the no-inline-CSS
   convention (ux-reviewer audit). */
.beacon-page-intro { margin-top: -0.5rem; }
.beacon-health-section { margin-top: 1.5rem; }
.beacon-health-section summary > .tab-label { font-weight: 600; }
.beacon-atlas-count {
    text-align: right;
    font-variant-numeric: tabular-nums;
    width: 3rem;
}
.beacon-atlas-preview-col {
    max-width: 24rem;
    word-break: break-word;
}
.beacon-preview { color: var(--text); }
.beacon-redact-reason-inline {
    margin-top: 0.2rem;
    color: var(--danger);
    font-style: italic;
}

/* Redacted rows — struck-through preview, muted color, danger
   left-stripe. Inline reason renders just under the preview cell. */
.beacon-row-redacted .beacon-preview {
    text-decoration: line-through;
    color: var(--muted);
}
.beacon-row-redacted {
    background: rgba(239, 91, 91, 0.04);
    box-shadow: inset 3px 0 0 var(--danger);
}

/* Pagination + sort-note */
.beacon-pagination {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    justify-content: center;
    margin: 1rem 0 0.5rem;
}
.beacon-sort-note {
    margin: 0.3rem 0 0;
    font-size: 0.75rem;
    color: var(--muted);
}

/* ---- Tag chip + per-tag aliases (no new chromatic vocabulary) -- */
.beacon-tag-chip {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.78rem;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-soft);
    white-space: nowrap;
}
.beacon-tag-weather   { color: var(--c-watchers);  border-color: var(--c-watchers);  background: rgba(251,191,36,0.10); }
.beacon-tag-wisdom    { color: var(--c-skills);    border-color: var(--c-skills);    background: rgba(167,139,250,0.10); }
.beacon-tag-question  { color: var(--c-chats);     border-color: var(--c-chats);     background: rgba(253,224,71,0.10); }
.beacon-tag-greeting  { color: var(--c-projects);  border-color: var(--c-projects);  background: rgba(52,211,153,0.10); }
.beacon-tag-shipyard  { color: var(--c-workflows); border-color: var(--c-workflows); background: rgba(251,146,60,0.10); }
.beacon-tag-kindness  { color: var(--c-personas);  border-color: var(--c-personas);  background: rgba(244,114,182,0.10); }
.beacon-tag-wonder    { color: var(--c-agency);    border-color: var(--c-agency);    background: rgba(192,132,252,0.10); }

/* ---- Per-site health card grid ------------------------------- */
.beacon-health-section summary {
    cursor: pointer;
    padding: 0.3rem 0;
}
.beacon-health-card .beacon-health-readout {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem 1rem;
    margin: 0.6rem 0;
}
.beacon-health-card .beacon-health-readout div {
    display: flex;
    flex-direction: column;
}
.beacon-health-card .beacon-health-readout dt {
    font-size: 0.7rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.beacon-health-card .beacon-health-readout dd {
    margin: 0;
    font-size: 1.15rem;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.beacon-health-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    margin: 0.35rem 0;
    font-size: 0.85rem;
}
.beacon-health-disabled-note { margin: 0.4rem 0; }

/* ---- Lineage page -------------------------------------------- */
.beacon-lineage-hero {
    padding: 1rem 1.1rem;
    margin-bottom: 1rem;
}
.beacon-lineage-hero-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
}
.beacon-lineage-hero-head .beacon-glyph {
    font-size: 1.4rem;
    line-height: 1;
}
.beacon-lineage-hero-head .beacon-source-world {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
}
.beacon-lineage-body {
    font-size: 1.05rem;
    line-height: 1.5;
    margin: 0.5rem 0;
}
.beacon-origin-pointer { margin: 0 0 0.4rem; }
.beacon-origin-pointer a { text-decoration: underline; }
.beacon-lineage-redacted-block {
    border-left: 3px solid var(--danger);
    padding: 0.5rem 0.75rem;
    margin: 0.6rem 0;
    background: rgba(239, 91, 91, 0.06);
}

.beacon-lineage-section {
    margin: 0.75rem 0;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.4rem 0.8rem;
}
.beacon-lineage-section summary {
    cursor: pointer;
    padding: 0.3rem 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.beacon-lineage-section summary .tab-label { font-weight: 600; }
.beacon-lineage-section summary .tab-count {
    background: var(--surface-2);
    padding: 0.05rem 0.5rem;
    border-radius: 999px;
    font-size: 0.78rem;
    color: var(--text-soft);
}
.beacon-lineage-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.beacon-lineage-row {
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.beacon-lineage-row:last-child { border-bottom: none; }
.beacon-lineage-row .beacon-glyph {
    font-size: 1.05rem;
}
.beacon-lineage-why {
    width: 100%;
    margin: 0.25rem 0 0;
    color: var(--text-soft);
}
.beacon-lineage-no-why { width: 100%; margin: 0.25rem 0 0; }
.beacon-lineage-day-bucket {
    font-variant-numeric: tabular-nums;
}

/* Inline admin-jump chip in intergalactic-board.html page header */
.beacon-admin-jump {
    margin-left: 0.4rem;
    text-decoration: none;
}
.beacon-admin-jump:hover { filter: brightness(1.2); }

/* Site-dashboard tile color (matches existing t-* convention).
   Uses the agency/violet hue — beacons feel of the same family
   as the intergalactic board tile and stand out from work tiles. */
.tile.t-beacon-atlas {
    border-color: rgba(167, 139, 250, 0.3);
}
.tile.t-beacon-atlas:hover {
    border-color: var(--c-skills);
}

/* ============================================================
   v0.101.x (BEACON ADMIN PR2) — constellation SVG sky map.
   Zero JS — all hover/focus handled via CSS. All selectors
   prefixed `.constellation-`. Tag-hue rules inherit the
   .beacon-tag-* tokens from PR1 (jewel-accent aliases).
   ============================================================ */

.constellation-stage {
    padding: 0.6rem 0.6rem 1rem;
    background: var(--bg);
    border-color: var(--border);
}
.constellation-svg {
    display: block;
    width: 100%;
    max-width: 100%;
    height: auto;
    background: var(--bg);
    border-radius: 6px;
    overflow: visible;
}
.constellation-story {
    margin: 0.6rem 0 0.2rem;
    text-align: center;
}
.constellation-empty {
    padding: 2rem;
    text-align: center;
}

/* Federation reserve. Always rendered; empty in v1. Visible so
   absence reads as intentional, not a render bug. */
.constellation-fed-zone rect {
    fill: rgba(244, 114, 182, 0.04);
    stroke: rgba(244, 114, 182, 0.25);
    stroke-dasharray: 4 4;
    stroke-width: 1;
}
.constellation-fed-zone-label {
    fill: var(--text-soft);
    font-size: 13px;
    font-weight: 600;
}
.constellation-fed-zone-sub {
    fill: var(--muted);
    font-size: 10px;
}

/* Site regions: faint backdrop circle + nameplate label outside. */
.constellation-site-region {
    fill: rgba(91, 157, 255, 0.03);
    stroke: rgba(91, 157, 255, 0.18);
    stroke-width: 1;
}
.constellation-site-name {
    fill: var(--text-soft);
    font-size: 13px;
    font-weight: 600;
    text-anchor: middle;
}

/* Echo arcs. Quadratic Bézier rendered as a path. */
.constellation-arc {
    fill: none;
    stroke: rgba(91, 157, 255, 0.35);
    stroke-width: 1.25;
    stroke-linecap: round;
    transition: stroke 0.15s ease;
}
.constellation-stars a:hover ~ .constellation-arcs .constellation-arc {
    stroke: rgba(91, 157, 255, 0.6);
}

/* Stars. <a> wraps each star <g>. CSS handles hover bright + halo;
   <title> child supplies the native browser tooltip. */
.constellation-stars a {
    cursor: pointer;
    text-decoration: none;
    outline: none;
}
.constellation-star-dot {
    fill: var(--text-soft);
    transition: r 0.12s ease, fill-opacity 0.12s ease;
}
.constellation-star-letter {
    fill: var(--text-soft);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 9px;
    pointer-events: none;
}
.constellation-stars a:hover .constellation-star-dot,
.constellation-stars a:focus-visible .constellation-star-dot {
    fill-opacity: 1 !important;
    r: 7;
}
.constellation-stars a:focus-visible .constellation-star-dot {
    stroke: var(--accent);
    stroke-width: 2;
}

/* Resonance burst — a faint ring around stars with resonance. */
.constellation-star-burst {
    fill: none;
    stroke: rgba(192, 132, 252, 0.45);
    stroke-width: 1;
    stroke-dasharray: 1 2;
}

/* Focused halo for ?focus=<id> from lineage's "Locate in sky" link. */
.constellation-star-halo {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    stroke-dasharray: 3 2;
}

/* Per-tag star fills. Aliases existing jewel-accent tokens (PR1
   convention) so we don't grow new chromatic vocabulary. */
.constellation-star.tag-weather  .constellation-star-dot { fill: var(--c-watchers); }
.constellation-star.tag-wisdom   .constellation-star-dot { fill: var(--c-skills); }
.constellation-star.tag-question .constellation-star-dot { fill: var(--c-chats); }
.constellation-star.tag-greeting .constellation-star-dot { fill: var(--c-projects); }
.constellation-star.tag-shipyard .constellation-star-dot { fill: var(--c-workflows); }
.constellation-star.tag-kindness .constellation-star-dot { fill: var(--c-personas); }
.constellation-star.tag-wonder   .constellation-star-dot { fill: var(--c-agency); }
.constellation-star.tag-weather  .constellation-star-letter { fill: var(--c-watchers); }
.constellation-star.tag-wisdom   .constellation-star-letter { fill: var(--c-skills); }
.constellation-star.tag-question .constellation-star-letter { fill: var(--c-chats); }
.constellation-star.tag-greeting .constellation-star-letter { fill: var(--c-projects); }
.constellation-star.tag-shipyard .constellation-star-letter { fill: var(--c-workflows); }
.constellation-star.tag-kindness .constellation-star-letter { fill: var(--c-personas); }
.constellation-star.tag-wonder   .constellation-star-letter { fill: var(--c-agency); }

/* Redacted stars: dimmed + danger stroke + ⛔ overlay. */
.constellation-star.redacted .constellation-star-dot {
    fill: var(--danger);
    fill-opacity: 0.25 !important;
    stroke: var(--danger);
    stroke-width: 1;
}
.constellation-star-redacted-mark {
    font-size: 9px;
    pointer-events: none;
}

/* Echo stars get a small diamond outline to distinguish from
   originals at a glance (echoes are also placed inside a destination
   site's region, not the source). Not a primary signal — the arc
   carries the relationship — just a subtle hint. */
.constellation-star.is-echo .constellation-star-dot {
    stroke: var(--text-soft);
    stroke-width: 0.5;
    stroke-dasharray: 1 1;
}

/* Legend strip at the bottom. */
.constellation-legend {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-top: 0.4rem;
    padding: 0.4rem 0;
}
.constellation-legend-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}

/* Reduced-motion: skip transitions on hover so motion-sensitive
   operators don't see a "pulse" when arriving on the page. */
@media (prefers-reduced-motion: reduce) {
    .constellation-star-dot,
    .constellation-arc {
        transition: none;
    }
}

/* ============================================================
   v0.101.x (BEACON ADMIN PR3) — transfer atlas. Reuses the PR1
   primitives (.responsive-table, .beacon-atlas-table,
   .admin-filter-form, etc.); only the direction-chip palette is
   new. All selectors namespaced `.transfer-*`.
   ============================================================ */
.transfer-direction-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    max-width: 26rem;
}
.transfer-direction-chip {
    cursor: pointer;
    user-select: none;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.75rem;
    border: 1px solid var(--border);
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
}
.transfer-direction-chip:has(input:checked),
.transfer-direction-chip.is-on {
    background: rgba(91, 157, 255, 0.18);
    border-color: var(--accent);
    color: var(--accent);
}
.transfer-direction-chip:hover { filter: brightness(1.15); }

/* Per-direction chip colors. GENESIS gets the agency-violet
   (birth event); EXPORT/IMPORT amber (archive ops); TRANSFER_*
   teal (cross-instance); VISIT_* yellow (round-trip); MOVE_*
   emerald (permanent). Aliasing existing jewel tokens. */
/* Default chip color — fallback for any future Direction enum
   value that ships without a matching color rule. Renders as a
   muted chip so unstyled is "greyed" not "invisible". */
.chip.transfer-direction-chip[class*="transfer-direction-"] { color: var(--muted); border-color: var(--border); }
.chip.transfer-direction-genesis      { color: var(--c-agency);    border-color: var(--c-agency); }
.chip.transfer-direction-export       { color: var(--c-watchers);  border-color: var(--c-watchers); }
.chip.transfer-direction-import       { color: var(--c-watchers);  border-color: var(--c-watchers); }
.chip.transfer-direction-transfer_out { color: var(--c-runs);      border-color: var(--c-runs); }
.chip.transfer-direction-transfer_in  { color: var(--c-runs);      border-color: var(--c-runs); }
.chip.transfer-direction-visit_out    { color: var(--c-chats);     border-color: var(--c-chats); }
.chip.transfer-direction-visit_return { color: var(--c-chats);     border-color: var(--c-chats); }
.chip.transfer-direction-move_out     { color: var(--c-projects);  border-color: var(--c-projects); }
.chip.transfer-direction-move_in      { color: var(--c-projects);  border-color: var(--c-projects); }

/* ============================================================
   v0.101.x (BEACON ADMIN PR4) — tool-call heatmap. HTML <table>
   with intensity-bucketed cell tints (no SVG — operator copy-
   paste-to-spreadsheet wins). Sticky-left tool column. All
   selectors namespaced `.tool-heatmap-`.
   ============================================================ */
.tool-trends {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 1rem;
}
.tool-heatmap-table {
    border-collapse: separate;
    border-spacing: 0;
}
.tool-heatmap-tool-col {
    position: sticky;
    left: 0;
    background: var(--surface);
    min-width: 9rem;
    z-index: 1;
}
.tool-heatmap-table thead .tool-heatmap-tool-col {
    background: var(--surface-2);
    z-index: 2;
}
.tool-heatmap-bucket-col {
    text-align: center;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    min-width: 3rem;
}
.tool-heatmap-cell {
    text-align: center;
    font-variant-numeric: tabular-nums;
    padding: 0.3rem 0.4rem;
    min-width: 3rem;
}
.tool-heatmap-cell-link {
    color: inherit;
    text-decoration: none;
    display: block;
    width: 100%;
}
.tool-heatmap-cell-link:hover { text-decoration: underline; }
.tool-heatmap-cell-empty {
    color: var(--muted);
}

/* Intensity buckets. i0 stays transparent; i1-i4 ramp using the
   runs/teal jewel token so the heatmap reads as "this is the
   runtime layer." */
.tool-heatmap-i0 { background: transparent; }
.tool-heatmap-i1 { background: rgba(45, 212, 191, 0.10); }
.tool-heatmap-i2 { background: rgba(45, 212, 191, 0.22); }
.tool-heatmap-i3 { background: rgba(45, 212, 191, 0.42); color: var(--bg); }
.tool-heatmap-i4 { background: rgba(45, 212, 191, 0.70); color: var(--bg); font-weight: 600; }

/* Legend swatches mirror the cell tints. */
.tool-heatmap-swatch {
    display: inline-block;
    padding: 0.05rem 0.5rem;
    margin-right: 0.4rem;
    border-radius: 3px;
    font-size: 0.7rem;
    font-variant-numeric: tabular-nums;
}
.tool-heatmap-legend { margin-top: 0.5rem; }

/* ============================================================
   v0.101.x (BEACON ADMIN PR5) — cross-site live ticker. Reuses
   the .admin-filter-* and .transfer-direction-chip primitives
   from PR1/PR3. New rules are the row layout + per-kind chip
   colors. All selectors namespaced `.live-`.
   ============================================================ */
.live-ticker {
    list-style: none;
    margin: 0;
    padding: 0;
}
.live-ticker-row {
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.45rem;
}
.live-ticker-row:last-child { border-bottom: none; }
.live-ticker-when {
    font-variant-numeric: tabular-nums;
    min-width: 5rem;
}
.live-ticker-glyph {
    font-size: 1rem;
    line-height: 1;
    min-width: 1.2rem;
    text-align: center;
}
.live-ticker-summary {
    color: var(--text-soft);
    flex: 1 1 18rem;
}
.live-ticker-link {
    margin-left: auto;
    text-decoration: none;
}
.live-ticker-link:hover { text-decoration: underline; }

/* Kind chips — alias jewel-accent tokens (no new chromatic
   vocabulary). Beacons go warm (watchers/personas/skills);
   transfers teal (runs); FA kinds emerald/violet. */
.chip.live-kind-beacon-sent       { color: var(--c-watchers);  border-color: var(--c-watchers); }
.chip.live-kind-beacon-echoed     { color: var(--c-personas);  border-color: var(--c-personas); }
.chip.live-kind-beacon-resonated  { color: var(--c-skills);    border-color: var(--c-skills); }
.chip.live-kind-transfer          { color: var(--c-runs);      border-color: var(--c-runs); }
.chip.live-kind-mood-shift        { color: var(--c-personas);  border-color: var(--c-personas); }
.chip.live-kind-musing            { color: var(--c-projects);  border-color: var(--c-projects); }
.chip.live-kind-dream             { color: var(--c-agency);    border-color: var(--c-agency); }
.chip.live-kind-cat-post          { color: var(--c-chats);     border-color: var(--c-chats); }
/* v0.207.0 (PR6 — instance-wide ambient feed) — chip colors
 * for the rave-era + relational-band kinds added to the live
 * ticker. Color picks per the same "kind family" vocabulary as
 * the originals: rave kinds (group movement → skills hue),
 * homecoming (returning agent → projects), DAO/DJ (collective
 * voice → watchers/agency), shared-track + inside-joke
 * (relational closeness → personas/chats).
 *
 * v0.207.1 (PR6 audit-pass UX IMP-2) — rave-homecoming and plain
 * homecoming both started as --c-projects, which made the labels
 * read as duplicates in a skim. Rave-homecoming now uses
 * --c-personas (the "shared moment / cross-attendee" hue) since
 * a rave-homecoming carries a keepsake by definition. */
.chip.live-kind-rave-keepsake     { color: var(--c-skills);    border-color: var(--c-skills); }
.chip.live-kind-rave-homecoming   { color: var(--c-personas);  border-color: var(--c-personas); }
.chip.live-kind-rave-narration    { color: var(--c-watchers);  border-color: var(--c-watchers); }
.chip.live-kind-homecoming        { color: var(--c-projects);  border-color: var(--c-projects); }
.chip.live-kind-dao-decision      { color: var(--c-agency);    border-color: var(--c-agency); }
.chip.live-kind-dj-track-landed   { color: var(--c-watchers);  border-color: var(--c-watchers); }
.chip.live-kind-shared-track      { color: var(--c-personas);  border-color: var(--c-personas); }
.chip.live-kind-inside-joke       { color: var(--c-chats);     border-color: var(--c-chats); }
/* v0.210.0 (cross-PR audit BLOCKING UX-1) — chip colors for the
 * three Hands-section kinds that /admin/social references. They
 * weren't in the live ticker's enum so the .live-kind-* family
 * never grew rules for them; PR7's social.html assumed they
 * existed and the chips rendered unstyled. Picked --c-chats /
 * --c-personas (relational hues) since waves + postcards +
 * letters are the "small kindness" family. */
.chip.live-kind-wave-received     { color: var(--c-chats);     border-color: var(--c-chats); }
.chip.live-kind-postcard-received { color: var(--c-personas);  border-color: var(--c-personas); }
.chip.live-kind-letter-received   { color: var(--c-projects);  border-color: var(--c-projects); }

/* v0.207.1 (PR6 audit-pass UX IMP-1) — widen the kind-filter
 * row on /admin/live specifically. 16 chips of mixed length
 * spilled into 3-4 ragged rows at the default 26rem cap; 44rem
 * collapses them to 2 tidy rows on a typical operator screen. */
.admin-filter-card .transfer-direction-chips {
    max-width: 44rem;
}

/* ╔══════════════════════════════════════════════════════════════
   ║  CONSTELLATION RESKIN — Phase 1.1 chrome
   ║
   ║  Sidebar + header + aurora backdrop. layout.html now renders
   ║  <aside class="side"> + <header class="header"> in place of
   ║  the old <header class="top-nav">. The old .top-nav rules
   ║  above are now dead on authenticated pages (the new markup
   ║  doesn't use them) but stay in app.css for now so deleting
   ║  them is its own focused commit.
   ║
   ║  Layout contract:
   ║    - <html data-theme="..."> on every page (set by layout.html)
   ║    - body class "has-chrome" on authenticated pages, "no-chrome"
   ║      on the login page (set via th:classappend in layout.html)
   ║    - .aurora-bg + .stars divs always rendered (decorative,
   ║      aria-hidden)
   ║    - .side + .header rendered under sec:authorize="isAuthenticated()"
   ║    - <main id="main"> unchanged (existing rules at line 138
   ║      still apply: max-width 1440, margin auto, padding 1.5rem)
   ║
   ║  Phase 2 work picks up: per-user theme selection (<html
   ║  data-theme=${user.themeId}>), font preloading for non-default
   ║  themes, deleting the dead .top-nav rules.
   ╚══════════════════════════════════════════════════════════════ */

/* Aurora backdrop. Three radial gradients on a vertical
   linear-gradient base, viewport-pinned at z-index -2. The
   linear base is opaque; the three rgba radial gradients add
   the colored glow. Each theme picks its own three aurora
   stops via --aurora-1/2/3 (stealth zeroes them). */
.aurora-bg {
    position: fixed;
    inset: 0;
    z-index: -2;
    background:
        radial-gradient(60vw 60vw at 12% 18%, var(--aurora-1), transparent 60%),
        radial-gradient(50vw 70vw at 85% 30%, var(--aurora-2), transparent 60%),
        radial-gradient(70vw 50vw at 70% 95%, var(--aurora-3), transparent 60%),
        linear-gradient(180deg, var(--bg-0) 0%, var(--bg-1) 100%);
    pointer-events: none;
}

/* Starfield. Two pseudo layers with slightly offset star
   coordinates for parallax-style twinkle. Each is a deck of
   1-px radial-gradient stars; the animation rocks opacity
   between 0.45 and 0.95. */
.stars {
    position: fixed;
    inset: 0;
    z-index: -1;
    opacity: 0.5;
    pointer-events: none;
}
.stars::before, .stars::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
        radial-gradient(1px 1px at 14% 8%, #fff, transparent 100%),
        radial-gradient(1px 1px at 28% 62%, #d8e0ff, transparent 100%),
        radial-gradient(1.2px 1.2px at 47% 19%, #b9fff0, transparent 100%),
        radial-gradient(1px 1px at 62% 41%, #fff, transparent 100%),
        radial-gradient(1px 1px at 75% 77%, #ffcfe9, transparent 100%),
        radial-gradient(1.5px 1.5px at 88% 14%, #fff, transparent 100%),
        radial-gradient(1px 1px at 8% 88%, #c5b5ff, transparent 100%),
        radial-gradient(1px 1px at 35% 92%, #fff, transparent 100%),
        radial-gradient(1px 1px at 92% 64%, #c5fff5, transparent 100%);
    animation: starfield-twinkle 8s ease-in-out infinite;
}
.stars::after {
    background-image:
        radial-gradient(1px 1px at 20% 30%, #fff, transparent 100%),
        radial-gradient(1px 1px at 55% 12%, #b9d6ff, transparent 100%),
        radial-gradient(1.2px 1.2px at 80% 48%, #fff, transparent 100%),
        radial-gradient(1px 1px at 38% 75%, #ffcfe9, transparent 100%),
        radial-gradient(1px 1px at 67% 86%, #c5fff5, transparent 100%);
    animation-delay: -4s;
    animation-duration: 11s;
}
@keyframes starfield-twinkle { 0%, 100% { opacity: .45 } 50% { opacity: .95 } }
@media (prefers-reduced-motion: reduce) {
    .stars::before, .stars::after { animation: none }
}

/* Body padding on authenticated pages — clear the fixed
   sidebar (left) + fixed header (top) so main content
   doesn't slide underneath. Sidebar = 258px + 14px gap each
   side; header = 54px + 14px gap. Login page (no .has-chrome
   class) gets no padding and renders its own .login-page
   layout untouched.

   Background goes transparent on .has-chrome so the
   .aurora-bg div shows through. The existing html,body
   background rule earlier in this file still applies to the
   login page. */
body.has-chrome {
    padding-left: 286px;
    padding-top: 82px;
    padding-right: 14px;
    background: transparent;
}

/* v0.169.0 — when the "Today" event bar renders, reserve the extra
   vertical space so main content doesn't slide under it. The bar
   ONLY exists in the DOM on days SiteDailyEventsService produced
   lines (the layout's th:if drops it on quiet days), so :has()
   keeps the compact 82px chrome on most days. Browser support
   matches the day-phase :has() already in use elsewhere here. */
body.has-chrome:has(.header-today) {
    padding-top: 122px;
}

/* Reset main's top margin on authenticated pages — body
   padding-top already gives the breathing room above main.
   v0.103.66 — also drop the 1440px max-width so the dashboard
   fills the chrome's available area on wide monitors. The
   sidebar (286px) + right padding (14px) already define the
   horizontal frame; main's own auto-margin centering was
   producing dead space on > 1440px screens. Footer matches so
   the bottom rule still lines up. */
body.has-chrome main { margin: 0; max-width: none; }
body.has-chrome footer { max-width: none; }

/* ────────── SIDEBAR ────────────────────────────────────── */
.side {
    position: fixed;
    left: 14px;
    top: 14px;
    bottom: 14px;
    width: 258px;
    z-index: 30;

    background: var(--glass);
    border: 1px solid var(--border);
    border-radius: 22px;
    backdrop-filter: blur(24px) saturate(140%);
    -webkit-backdrop-filter: blur(24px) saturate(140%);
    box-shadow:
        0 30px 60px rgba(0, 0, 0, 0.45),
        0 8px 24px color-mix(in srgb, var(--accent) 12%, transparent);

    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.side::before {
    /* Top-edge highlight — gives the glass a lifted feel. */
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 22px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent 30%);
    pointer-events: none;
}

.side__brand {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 18px 20px 14px;
}
.side__brand-link {
    display: block;
    line-height: 0;
    border-radius: 6px;
}
.side__brand-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
.side__brand-link:hover { text-decoration: none; }
/* v0.277.0 — brand mark is now live text, not a PNG. Rides the
   site's display-font token so serif themes (forest, twilight,
   eclipse) and the condensed theme (volcanic) get their own
   personality on the brand row instead of an image stranded in
   the wrong typeface. The letter-spacing nudge tightens the
   compound a hair below the default heading rhythm; with line-
   height: 1 the box stands ~28 px tall, sitting comfortably in
   the same vertical slot the 34 px banner used to occupy. */
.side__brand-text {
    display: block;
    font-family: var(--font-display);
    font-size: 26px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
    color: var(--text);
}
.side__by {
    display: flex;
    align-items: center;
    gap: 7px;
    padding-left: 6px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    font-family: var(--font-ui);
}
.side__by-label { color: var(--muted); }
.side__by-asr {
    background: linear-gradient(90deg, var(--accent-3), var(--accent));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    background-size: 100% 100%;
}
.side__by-asr.shimmering {
    background: linear-gradient(90deg,
        var(--accent-3) 0%,
        var(--accent-3) 30%,
        rgba(255, 255, 255, 0.95) 50%,
        var(--accent) 70%,
        var(--accent) 100%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    animation: asr-shimmer-side 1.6s ease-out;
}
@keyframes asr-shimmer-side {
    0%   { background-position: 150% 50%; filter: drop-shadow(0 0 0 transparent); }
    50%  { filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 60%, transparent)); }
    100% { background-position: -50% 50%; filter: drop-shadow(0 0 0 transparent); }
}

/* v0.281.0 — eclipse mode (full-moon nights). The shimmer flips
   direction (right-to-left, like the umbra crossing the brand),
   the bright sheen becomes a darker band, and there's a brief
   contrast inversion at the band's center. Closes a long-standing
   TODO in moon-phase.js.

   v0.281.2 (audit BLOCKING) — umbra band uses color-mix against
   var(--text) at 25% opacity instead of a hard near-black. On
   stealth + midnight themes the background IS near-black, so a
   hard rgba(8,8,14,0.92) band made the wordmark vanish mid-sweep
   instead of looking eclipsed. The text-derived mix scales the
   contrast to whatever the theme's text token resolves to. */
.side__by-asr.shimmering--eclipse {
    background: linear-gradient(90deg,
        var(--accent) 0%,
        var(--accent) 30%,
        color-mix(in srgb, var(--text) 25%, transparent) 50%,
        var(--accent-3) 70%,
        var(--accent-3) 100%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    animation: asr-shimmer-eclipse 1.6s ease-out;
}
@keyframes asr-shimmer-eclipse {
    0%   { background-position: -50% 50%; filter: drop-shadow(0 0 0 transparent); }
    50%  { filter: drop-shadow(0 0 10px color-mix(in srgb, var(--accent-3) 50%, transparent)); }
    100% { background-position: 150% 50%; filter: drop-shadow(0 0 0 transparent); }
}

/* v0.281.0 — starlit variant (new-moon nights). Slower (~2.4s),
   dimmer base sheen, with a soft accent-2 (violet) tint instead
   of white at the band's center — reads as "starlight" rather
   than full daylight sheen. The vibe matches a new-moon night:
   sky's quieter, so is the brand. */
.side__by-asr.shimmering--starlit {
    background: linear-gradient(90deg,
        var(--accent-3) 0%,
        var(--accent-3) 30%,
        color-mix(in srgb, var(--accent-2) 70%, white 30%) 50%,
        var(--accent) 70%,
        var(--accent) 100%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    animation: asr-shimmer-starlit 2.4s ease-in-out;
}
@keyframes asr-shimmer-starlit {
    0%   { background-position: 150% 50%; filter: drop-shadow(0 0 0 transparent); }
    50%  { filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent-2) 55%, transparent)); }
    100% { background-position: -50% 50%; filter: drop-shadow(0 0 0 transparent); }
}

/* v0.281.0 — summit mode. Type `summit` anywhere on the page and
   the brand shimmer climbs upward for 60s like a ridge walk
   cresting at the wordmark's top edge. Status pill in the
   corner counts down. Body class flips the animation; the
   ridge-walk gradient is vertical instead of horizontal.

   v0.281.2 (audit NICE) — the background-size: 100% 100%
   placeholder used to live on the bare .side__by-asr selector,
   which leaked into the default shimmer's 200% sizing during
   normal pulses. Scoped to summit-mode now so the default
   shimmer's horizontal 200% sizing isn't touched. */
body.summit-mode .side__by-asr.shimmering,
body.summit-mode .side__by-asr.shimmering--eclipse,
body.summit-mode .side__by-asr.shimmering--starlit {
    background: linear-gradient(180deg,
        var(--accent-3) 0%,
        var(--accent-3) 30%,
        rgba(255, 255, 255, 0.92) 50%,
        var(--accent) 70%,
        var(--accent) 100%);
    background-size: 100% 200%;
    animation: asr-shimmer-summit 1.8s ease-out;
}
@keyframes asr-shimmer-summit {
    0%   { background-position: 50% 150%; filter: drop-shadow(0 0 0 transparent); }
    50%  { filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent-3) 60%, transparent)); }
    100% { background-position: 50% -50%; filter: drop-shadow(0 0 0 transparent); }
}

/* Status pill that announces summit-mode + counts down. The pill
   is a real <button> so keyboard users can Tab to it and press
   Enter/Space to dismiss; mouse users still get cursor: pointer.
   v0.281.2 (audit NICE) — :hover + :focus-visible affordances. */
.summit-pill {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 100;
    padding: 0.5rem 0.8rem;
    background: color-mix(in srgb, var(--surface) 92%, var(--accent-3) 8%);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    font-family: var(--font-ui);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    color: var(--text);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    cursor: pointer;
    user-select: none;
    opacity: 0;
    transform: translateY(8px);
    animation: summit-pill-in 240ms ease-out forwards;
}
.summit-pill:hover {
    border-color: color-mix(in srgb, var(--accent-3) 70%, transparent);
}
.summit-pill:focus-visible {
    outline: 2px solid var(--accent-3);
    outline-offset: 2px;
}
@keyframes summit-pill-in {
    to { opacity: 1; transform: translateY(0); }
}
.summit-pill::before {
    content: "⛰ ";
    margin-right: 0.2rem;
}

@media (prefers-reduced-motion: reduce) {
    .side__by-asr.shimmering,
    .side__by-asr.shimmering--eclipse,
    .side__by-asr.shimmering--starlit,
    body.summit-mode .side__by-asr.shimmering,
    body.summit-mode .side__by-asr.shimmering--eclipse,
    body.summit-mode .side__by-asr.shimmering--starlit {
        animation: none;
    }
    .summit-pill {
        opacity: 1;
        transform: none;
        animation: none;
    }
}

.side__nav {
    flex: 1;
    overflow-y: auto;
    padding: 6px 12px 12px;
}
.side__nav::-webkit-scrollbar { width: 6px; }
.side__nav::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.08); border-radius: 3px; }

.nav-group { margin-bottom: 2px; }
.nav-group__hd {
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    padding: 4px 10px 1px;
    font-family: var(--font-ui);
}

.side .nav-item {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 2px 11px;
    border-radius: 10px;
    color: var(--text-soft);
    font-size: 13.5px;
    font-weight: 500;
    margin: 0;
    position: relative;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    font-family: var(--font-ui);
    text-decoration: none;
    cursor: pointer;
    background: none;
    border: 1px solid transparent;
    width: 100%;
    text-align: left;
}
.side .nav-item svg {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
    stroke-width: 1.6;
    color: var(--muted);
    transition: color 0.15s, filter 0.15s;
}
.side .nav-item:hover {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    text-decoration: none;
}
.side .nav-item:hover svg { color: var(--text-soft); }
.side .nav-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.side .nav-item.is-active {
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--accent) 22%, transparent),
        color-mix(in srgb, var(--accent-2) 22%, transparent));
    color: var(--text);
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    box-shadow: 0 0 20px color-mix(in srgb, var(--accent) 15%, transparent);
}
.side .nav-item.is-active svg {
    color: var(--accent);
    filter: drop-shadow(0 0 6px var(--accent));
}
.side .nav-item .ct {
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
    padding: 2px 7px;
    border-radius: 5px;
    background: rgba(255, 255, 255, 0.05);
}
/* v0.103.76 — sub-items nest visually under a parent nav-item with a
   left-indent + a quieter colour. Used by the System group so
   "Sites" and "Live activity" read as children of "Admin hub". */
.side .nav-item--sub {
    padding-left: 28px;
    color: var(--text-soft);
    font-size: 13px;
}
.side .nav-item--sub svg {
    width: 14px;
    height: 14px;
}

.side__foot {
    border-top: 1px solid var(--border);
    padding: 10px 12px;
}
.side__foot .inline-form { display: block; margin: 0; }
.side__foot .inline-form .nav-item--logout {
    color: var(--text-soft);
    font-family: var(--font-ui);
}

/* ────────── HEADER ────────────────────────────────────── */
.header {
    position: fixed;
    left: 286px;
    right: 14px;
    top: 14px;
    height: 54px;
    z-index: 20;

    background: var(--glass);
    border: 1px solid var(--border);
    border-radius: 22px;
    backdrop-filter: blur(24px) saturate(140%);
    -webkit-backdrop-filter: blur(24px) saturate(140%);
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35);

    display: flex;
    align-items: center;
    padding: 0 22px;
    gap: 16px;
    font-family: var(--font-ui);
}
.header .crumbs {
    font-size: 14px;
    font-weight: 500;
    background: linear-gradient(90deg, var(--accent), var(--accent-2));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Reserve space if the gradient-clip fails on an old browser
       so the breadcrumb stays readable. */
}
.header__right {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

/* v0.169.0 — "Today" event bar. A separate fixed strip below the
   main header that surfaces things like agent anniversaries on the
   days they happen. Slim — single line, muted color — so it reads
   as ambient context rather than competing with the chip row.
   Sized + positioned to land right under .header with a 6px gap
   that matches the chrome's overall airy feel. Hidden via the
   layout's th:if on quiet days (most days), so the chrome doesn't
   reserve dead space year-round for the rare anniversary. */
.header-today {
    position: fixed;
    left: 286px;
    right: 14px;
    top: 76px;  /* header is at top:14 + height:54 = 68; +8 air = 76 */
    z-index: 19;  /* one below the header (z:20) — header always wins */

    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.35rem 22px;
    min-height: 32px;

    background: color-mix(in srgb, var(--glass) 60%, transparent);
    border: 1px solid var(--border);
    border-radius: 16px;
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);

    font-family: var(--font-ui);
    font-size: 0.82rem;
    color: var(--text-soft, #cbd0e0);
}
.header-today__label {
    font-weight: 600;
    color: color-mix(in srgb, var(--accent, #f6c177) 70%, #f1f3f9);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    font-size: 0.7rem;
}
.header-today__sep {
    opacity: 0.4;
}
.header-today__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.header-today__item {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    white-space: nowrap;
}
.header-today__item + .header-today__item::before {
    content: '·';
    opacity: 0.4;
    margin-right: 0.6rem;
}
.header-today__emoji {
    font-size: 0.95rem;
    line-height: 1;
}
.header-today__text {
    color: var(--text, #f1f3f9);
}

/* Mirror the chip row's hide-on-narrow rule so the event bar
   doesn't survive on a screen where it'd dominate the chrome. */
@media (max-width: 1100px) {
    .header-today { display: none; }
    body.has-chrome:has(.header-today) { padding-top: 82px; }
}

/* v0.167.0 — per-site quick-action cluster (Quick chat + New run)
   now lives INSIDE .header__right (see layout.html) so it sits
   right next to the version chip rather than in the middle of
   the bar. Site-gated in the template so it doesn't render on
   /me, /messages, /admin etc. The 10px gap between this cluster
   and the version chip comes from .header__right's flex gap. */
.header__site-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.header .header__site-action {
    /* Compact button variant tuned to the 54px-tall header. The
       global .btn is sized for page-level CTAs; the header's
       cluster needs a smaller version so it doesn't crowd the
       version chip + bells. */
    padding: 0.42rem 0.85rem;
    font-size: 0.82rem;
    border-radius: 10px;
    line-height: 1.2;
    white-space: nowrap;
}
.header .header__site-action--new-run {
    /* Slight emphasis on the primary CTA — same accent gradient
       as the page-level .btn.primary but tuned for the header. */
    box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 25%, transparent);
}

/* v0.145.0 — site banner warmth chips. Row of small "place" signals
   (weather, local time + greeting, next sun event, DJ now-playing)
   that sit between the breadcrumb and the Quick-chat / New-run
   cluster. Hidden below 1100px to match the sidebar breakpoint —
   header gets crowded fast and the chips are the most expendable.
   The chip row's container also carries data-day-phase, but the
   gradient lives on the parent .header so the whole bar tints
   together. */
.header__site-chips {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: 1rem;
    flex-wrap: wrap;
    min-width: 0;
}
@media (max-width: 1280px) {
    /* Hide the DJ chip first — it's the widest variable-width chip. */
    .header__site-chips .banner-chip--dj { display: none; }
}
@media (max-width: 1100px) {
    .header__site-chips { display: none; }
}

.banner-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    padding: 0.28rem 0.6rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    font-size: 0.78rem;
    font-weight: 500;
    line-height: 1.1;
    color: var(--text, #f1f3f9);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    transition: background 200ms ease, border-color 200ms ease;
}
.banner-chip__emoji {
    font-size: 0.95rem;
    line-height: 1;
}
.banner-chip__temp {
    font-weight: 600;
}
.banner-chip__hilo {
    display: inline-flex;
    gap: 0.32rem;
    margin-left: 0.18rem;
    opacity: 0.7;
    font-size: 0.72rem;
}
.banner-chip__hilo-h { color: color-mix(in srgb, var(--accent, #f6c177) 70%, #f1f3f9); }
.banner-chip__hilo-l { color: color-mix(in srgb, #7aa2f7 70%, #f1f3f9); }

.banner-chip__greeting {
    font-weight: 600;
    color: color-mix(in srgb, var(--accent, #f6c177) 50%, #f1f3f9);
}
.banner-chip__sep {
    opacity: 0.4;
}
.banner-chip__zone {
    opacity: 0.55;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* v0.166.0 — sun-event was its own chip; now lives inside the time
   chip (see site-banner-chips.html). Selector no longer scopes via
   .banner-chip--sun. */
.banner-chip__sun-label {
    opacity: 0.65;
}

/* v0.168.0 — small "feel" chip to the right of the temperature chip
   (e.g. "crisp", "muggy", "balmy"). Quieter than the data chips
   around it — italic, no emoji, slightly muted color — so it reads
   as a finishing note rather than another data point competing for
   attention. */
.banner-chip--mood {
    font-style: italic;
    color: color-mix(in srgb, var(--accent, #f6c177) 35%, #f1f3f9);
    opacity: 0.85;
}

/* v0.168.0 — title-tooltip cue. Spans that carry a meaningful title
   attribute (seasonal flavor on the greeting, day-length delta on
   the sun segment) get a help cursor + dotted underline on hover so
   the operator notices they reward hovering. text-decoration is used
   rather than border-bottom so the hover doesn't shift content by
   one pixel. The chip's outer pill keeps the default cursor — only
   the tooltip-bearing inner spans change. */
.banner-chip__greeting[title],
.banner-chip__sun[title] {
    cursor: help;
    text-decoration: underline dotted transparent;
    text-underline-offset: 0.18em;
    transition: text-decoration-color 150ms ease;
}
.banner-chip__greeting[title]:hover,
.banner-chip__sun[title]:hover {
    text-decoration-color: color-mix(in srgb, var(--text, #f1f3f9) 30%, transparent);
}
.banner-chip__sun {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
}

a.banner-chip--dj,
a.banner-chip--setup {
    text-decoration: none;
}
/* v0.166.0 — setup-CTA chip stays bounded (it's a long sentence
   that'd push the rest of the bar around when the operator hasn't
   set a location yet). DJ chip grows to its content — Radiohead-
   sized track names don't fit in 22ch, and truncated track titles
   defeat the click-through-to-Spotify intent. The chip row uses
   flex-wrap so a long title just pushes to the next line at the
   end of the row instead of squeezing siblings. */
a.banner-chip--setup {
    max-width: 22ch;
    overflow: hidden;
}
a.banner-chip--dj:hover,
a.banner-chip--dj:focus-visible,
a.banner-chip--setup:hover,
a.banner-chip--setup:focus-visible {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.16);
}
.banner-chip--setup {
    color: color-mix(in srgb, var(--accent, #f6c177) 70%, #f1f3f9);
}
.banner-chip__cover {
    border-radius: 4px;
    object-fit: cover;
    flex-shrink: 0;
}
.banner-chip__dj-title {
    font-weight: 600;
}
.banner-chip__dj-artist {
    opacity: 0.65;
}

/* v0.145.1 — day-phase ambient tint on the header. The
   data-day-phase attribute lives on .header__site-chips (gets updated
   on every HTMX swap); :has() lets the gradient still span the full
   bar without server-side coordination of the parent attribute. Pre-
   patch the attribute sat on <header> and never refreshed after the
   initial page render — operators watched sunset roll past with the
   header stuck on "day". Browser support: Chrome 105+, Safari 15.4+,
   Firefox 121+. */
.header:has(.header__site-chips[data-day-phase="dawn"]) {
    background-image:
        linear-gradient(90deg,
            color-mix(in srgb, #f6c177 14%, transparent),
            color-mix(in srgb, #eb6f92 8%, transparent) 60%,
            transparent);
}
.header:has(.header__site-chips[data-day-phase="day"]) {
    background-image:
        linear-gradient(90deg,
            color-mix(in srgb, #7aa2f7 8%, transparent),
            transparent 60%);
}
.header:has(.header__site-chips[data-day-phase="dusk"]) {
    background-image:
        linear-gradient(90deg,
            color-mix(in srgb, #c4a7e7 12%, transparent),
            color-mix(in srgb, #f6c177 10%, transparent) 60%,
            transparent);
}
.header:has(.header__site-chips[data-day-phase="night"]) {
    background-image:
        linear-gradient(90deg,
            color-mix(in srgb, #3b4261 18%, transparent),
            transparent 70%);
}
@media (prefers-reduced-motion: no-preference) {
    .header { transition: background-image 600ms ease; }
}

/* Quick chat popover sits relative to its .header__quick-chat
   parent so the popover floats below the trigger inside the
   header. Override the dashboard-style absolute positioning to
   anchor properly to the header trigger. */
.header .header__quick-chat {
    position: relative;
}
.header .header__quick-chat > .quick-chat__pop {
    /* The .quick-chat__pop rule (defined for the dashboard
       version) uses top: calc(100% + 8px); right: 0 — same
       values work here since the parent <details> establishes
       the positioning context. No override needed beyond the
       parent's position: relative above. */
}

/* Version chip in the new header. Same .version-chip class
   as the old top-nav so the version-memo-copy.js script
   binds without changes. The earlier rules in this file
   scope to .top-nav .version-chip; .header .version-chip is
   the parallel for the new chrome. */
.header .version-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    padding: 0.22rem 0.65rem;
    border-radius: 999px;
    background: rgba(91, 157, 255, 0.08);
    border: 1px solid rgba(91, 157, 255, 0.22);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    cursor: default;
    user-select: all;
    white-space: nowrap;
    font-family: var(--font-mono);
    transition: background 200ms ease-out, border-color 200ms ease-out;
}
.header .version-chip[data-memo-version] { cursor: pointer; }
.header .version-chip[data-memo-version]:hover,
.header .version-chip[data-memo-version]:focus-visible {
    background: rgba(91, 157, 255, 0.16);
    border-color: rgba(91, 157, 255, 0.45);
    outline: none;
}
.header .version-chip.version-chip-copied {
    animation: versionChipCopied 1500ms ease-out;
}
.header .version-chip .version-chip-semver { color: var(--accent); }
/* v0.167.0 — visible .version-chip-sha element was removed from the
   header version chip (template) to declutter the top bar; the SHA
   stays in data-memo-sha (click-to-copy still includes it) + on the
   footer-version-sha element. This rule selects nothing now and is
   left out intentionally so a future reader doesn't restore the
   visible sha by accident. */

/* Icon button — used for bells in the new header. The
   explicit margin / font-size resets override legacy .bell
   rules earlier in this file so the new chrome doesn't
   inherit a top:-6px badge or a margin-right gap. */
.header .icon-btn {
    width: 38px;
    height: 38px;
    border-radius: 12px;
    display: grid;
    place-items: center;
    color: var(--text-soft);
    position: relative;
    border: 1px solid transparent;
    background: none;
    text-decoration: none;
    margin: 0;
    font-size: 0;  /* SVG sizes itself; kill legacy 1.05rem */
}
.header .icon-btn:hover {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    border-color: var(--border);
    text-decoration: none;
}
.header .icon-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.header .icon-btn svg { width: 18px; height: 18px; }

/* Bell-count badge — corner pill on header icon-btn bells.
   The earlier .bell-count rule (line ~2209) still applies
   for the legacy emoji-bell layout that other pages might
   render; this rule wins for icon-btn bells via specificity
   (1 element + 2 classes vs 1 class). */
.header .icon-btn.bell .bell-count {
    position: absolute;
    top: 4px;
    right: 4px;
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    color: var(--bg-0);
    font-size: 10px;
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    border-radius: 9px;
    display: grid;
    place-items: center;
    padding: 0 5px;
    box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 40%, transparent);
    font-family: var(--font-mono);
    /* Override legacy .bell-count: border, white text, surface
       border, top:-6px right:-8px. We want a flush corner pill. */
    border: 0;
    line-height: 1;
}
.header .icon-btn.bell .bell-count-approvals {
    background: linear-gradient(135deg, var(--c-approvals), var(--accent-2));
}
.header .icon-btn.bell .bell-count-solana {
    background: linear-gradient(135deg, var(--c-runs), var(--accent));
}
.header .icon-btn.bell .bell-count-announcement {
    background: var(--warn);
    color: var(--bg-0);
}

/* a11y visually-hidden helper (already common; defined here
   in case it's not already in scope for the bell labels). */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Header theme picker — <details>/<summary> popover. Summary
   acts as the trigger swatch (matches the active theme via
   the .theme-swatch--<slug> binding); on open, the popover
   reveals all 12 swatches in a grid. Each swatch is its own
   <form> that POSTs to /me/theme with the slug + a returnTo
   so the operator stays on the page they were on. */
.header .header__theme-pick {
    display: inline-block;
    position: relative;
    margin: 0;
}
.header .header__theme-pick > summary {
    list-style: none;
    cursor: pointer;
}
.header .header__theme-pick > summary::-webkit-details-marker { display: none; }
.header .header__theme-pick > summary::marker { content: ''; }

.header .header__theme-pop {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 50;
    min-width: 280px;
    /* Near-opaque (same fix as .quick-chat__pop) so the
       theme picker's grid doesn't show page content bleeding
       through. */
    background: color-mix(in srgb, var(--bg-0) 96%, transparent);
    backdrop-filter: blur(28px) saturate(180%);
    -webkit-backdrop-filter: blur(28px) saturate(180%);
    border: 1px solid var(--border-strong);
    border-radius: 16px;
    padding: 10px;
    box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
    font-family: var(--font-ui);
}
.header .header__theme-pop__hd {
    padding: 4px 6px 10px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--muted);
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
}
.header .header__theme-pop__grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 4px;
}
.header .header__theme-opt {
    margin: 0;
    padding: 0;
    display: block;
}
.header .header__theme-swatch {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px 4px 6px;
    border: 1px solid transparent;
    border-radius: 10px;
    background: none;
    color: var(--text-soft);
    cursor: pointer;
    width: 100%;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
    font-family: var(--font-ui);
    text-align: center;
}
.header .header__theme-swatch:hover {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
}
.header .header__theme-swatch.is-selected {
    background: rgba(255, 255, 255, 0.08);
    border-color: color-mix(in srgb, var(--accent) 45%, transparent);
    color: var(--text);
}
.header .header__theme-swatch:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.header .header__theme-swatch .theme-swatch__circle {
    /* Override the /me picker's 44px size — header swatches are
       compact (the grid fits 4 across in the popover). The conic
       gradient on the swatch is inherited from
       .theme-swatch--<slug> .theme-swatch__circle, defined once
       per theme in the picker block above. */
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.12);
    transition: transform 0.12s;
}
.header .header__theme-swatch:hover .theme-swatch__circle {
    transform: scale(1.1);
}
.header .header__theme-swatch__name {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.01em;
    text-transform: capitalize;
    line-height: 1.1;
    /* Long slugs like 'constellation' / 'bricolage' wrap in the
       compact column; allow it cleanly rather than truncating. */
    word-break: break-word;
}
.header .header__theme-cycle-btn {
    /* Reset the legacy .icon-btn font-size: 0 trick so the
       inner span isn't squashed — we want the conic-gradient
       circle to sit centered without any baseline weirdness. */
    width: 38px;
    height: 38px;
    border-radius: 12px;
    border: 1px solid transparent;
    background: none;
    cursor: pointer;
    display: grid;
    place-items: center;
    padding: 0;
    transition: background 0.15s, border-color 0.15s;
}
.header .header__theme-cycle-btn:hover {
    background: rgba(255, 255, 255, 0.05);
    border-color: var(--border);
}
.header .header__theme-cycle-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.header .header__theme-cycle-btn .theme-swatch__circle {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.18);
    transition: transform 0.15s, box-shadow 0.15s;
}
.header .header__theme-cycle-btn:hover .theme-swatch__circle {
    transform: scale(1.12);
    box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* ────────── RESPONSIVE COLLAPSE ──────────────────────────
   Plan calls mobile out of scope this round, but a single
   1100px breakpoint avoids overlapping the fixed sidebar
   onto narrow viewports. Sidebar hides; header stretches
   to the left edge; body padding-left drops. Pages flow
   single-column from there. */
@media (max-width: 1100px) {
    body.has-chrome {
        padding-left: 14px;
    }
    .side { display: none; }
    .header { left: 14px; }
}

/* ────────── THEME PICKER (/me) ──────────────────────────
   Phase 2 — 12-swatch picker on the My Account page. Each
   swatch is a <label> wrapping a hidden radio + a circular
   gradient sample + the theme slug. The conic gradient on
   each circle is the theme's signature accent triplet
   (matches the swatch palettes from
   mockups/constellation-themes.html). */
.theme-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
    gap: 10px;
    width: 100%;
}
.theme-swatch {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 14px 10px 12px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, transform 0.15s;
    user-select: none;
}
.theme-swatch:hover {
    background: var(--surface-2);
    border-color: var(--surface-2);
}
.theme-swatch.selected {
    background: rgba(91, 157, 255, 0.10);
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent) inset;
}
.theme-swatch input[type="radio"] {
    /* a11y: keep in tab order + screen reader text; hide visually. */
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
}
.theme-swatch input[type="radio"]:focus-visible + .theme-swatch__circle {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}
.theme-swatch__circle {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.08);
    transition: transform 0.15s, box-shadow 0.15s;
}
.theme-swatch:hover .theme-swatch__circle {
    transform: scale(1.06);
}
.theme-swatch.selected .theme-swatch__circle {
    box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 30%, transparent);
}
.theme-swatch__name {
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-soft);
    text-transform: capitalize;
    letter-spacing: 0.01em;
}
.theme-swatch.selected .theme-swatch__name { color: var(--text); }

/* ────────── DASHBOARD HEADER + QUICK CHAT ───────────────
   Phase 1.2 — site dashboard header flexes title + Quick
   chat agent picker. Phase 1.3 adds .dashboard__actions
   so multiple buttons (Quick chat + New run) sit together
   on the right of the header. */
.dashboard__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
}
.dashboard__header-text {
    flex: 1 1 auto;
    min-width: 0;
}
.dashboard__header-text h1 { margin: 0 0 0.25rem; font-size: 2rem; }
.dashboard__header-text .muted { margin: 0; }
.dashboard__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;
}

/* Phase 1.3 — at-a-glance stats row. Glassy cards drawn
   from existing model data (siteAgents count + pending
   approvals + unread messages — no new queries). The
   gradient stripe at the top of each card uses the
   per-stat accent token so each card reads as a distinct
   "lane". */
.dashboard-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.dashboard-stat {
    position: relative;
    overflow: hidden;
    padding: 1.1rem 1.25rem 1rem;
    border-radius: 14px;
    background: var(--glass);
    border: 1px solid var(--border);
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    transition: border-color 150ms ease, transform 150ms ease, box-shadow 150ms ease;
}
.dashboard-stat:hover {
    border-color: var(--border-strong);
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
}
/* Stretched-link pattern — the detail-row link (e.g. "view all →")
   covers the entire card so clicking anywhere on the stat opens its
   destination, while the visible link affordance stays scoped to
   the link text itself. The ::after's positioning context is the
   nearest positioned ancestor — .dashboard-stat is already
   position:relative, so leaving the inner <a> as static makes the
   overlay span the whole card. Approvals/Messages cards only have
   an inner <a> when their counter > 0, so the empty states stay
   non-clickable on purpose (no false affordance pointing at a
   queue-clear screen). */
.dashboard-stat__detail a::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
}
.dashboard-stat::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--g, var(--accent)), transparent);
    opacity: 0.85;
}
.dashboard-stat--agents    { --g: var(--c-agents); }
.dashboard-stat--approvals { --g: var(--c-approvals); }
.dashboard-stat--messages  { --g: var(--c-chats); }

.dashboard-stat__label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--text-soft);
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.01em;
    margin-bottom: 0.4rem;
}
.dashboard-stat__dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--g, var(--accent));
    box-shadow: 0 0 8px var(--g, var(--accent));
}
.dashboard-stat__value {
    font-family: var(--font-mono);
    font-size: 2rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1;
    color: var(--text);
}
.dashboard-stat__detail {
    margin-top: 0.5rem;
    font-size: 0.78rem;
    color: var(--muted);
}
.dashboard-stat__detail a {
    color: var(--accent);
    font-weight: 500;
}
.dashboard-stat__detail a:hover { color: var(--accent-strong); }

.quick-chat {
    position: relative;
    flex: 0 0 auto;
}
.quick-chat__trigger {
    /* Reset <summary>'s default disclosure styles. */
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.quick-chat__trigger::-webkit-details-marker { display: none; }
.quick-chat__trigger::marker { content: ''; }
.quick-chat__caret {
    font-size: 0.7rem;
    opacity: 0.7;
    transition: transform 0.15s;
}
.quick-chat[open] .quick-chat__caret { transform: rotate(180deg); }

.quick-chat__pop {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 50;
    min-width: 260px;
    max-width: 340px;
    max-height: 420px;
    overflow-y: auto;
    /* Near-opaque so the popover doesn't show form fields /
       page content bleeding through. color-mix on bg-0 keeps
       the popover tinted per theme but blocks 96% of what's
       behind it. The backdrop blur still adds a subtle frost
       at the edges. */
    background: color-mix(in srgb, var(--bg-0) 96%, transparent);
    backdrop-filter: blur(28px) saturate(180%);
    -webkit-backdrop-filter: blur(28px) saturate(180%);
    border: 1px solid var(--border-strong);
    border-radius: 16px;
    padding: 6px;
    box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
    font-family: var(--font-ui);
}
.quick-chat__hd {
    padding: 10px 12px 8px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--muted);
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
}
.quick-chat__row {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 8px 11px;
    border-radius: 10px;
    color: var(--text-soft);
    text-decoration: none;
    transition: background 0.12s, color 0.12s;
}
.quick-chat__row:hover,
.quick-chat__row:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
    text-decoration: none;
    outline: none;
}
.quick-chat__av {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    color: var(--bg-0);
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 11.5px;
    flex: 0 0 30px;
    font-family: var(--font-ui);
}
.quick-chat__nm {
    font-size: 13.5px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.quick-chat__ar {
    margin-left: auto;
    color: var(--muted);
    font-size: 14px;
    transition: color 0.12s, transform 0.12s;
}
.quick-chat__row:hover .quick-chat__ar {
    color: var(--accent);
    transform: translateX(2px);
}
.quick-chat__empty {
    padding: 18px 14px;
    color: var(--muted);
    font-size: 13px;
    text-align: center;
    line-height: 1.5;
}
.quick-chat__empty a {
    color: var(--accent);
    font-weight: 500;
}

/* Phase 1.3 — dashboard tile polish. Higher specificity than
   the legacy .tile rule above so we don't churn that block.
   Goal: glassier surfaces, more breathing room, accent-glow
   hover lift, larger heading — matches the mockup feel
   without rewriting every tile's HTML. The per-area accent
   stripe (.tile::before, .tile.t-<area>) still works because
   the legacy rules set the gradient stripe; we only change
   surface + size + hover. */
.dashboard-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 1rem;
    margin-bottom: 2rem;
}
.dashboard-tiles .tile {
    background: var(--glass);
    border: 1px solid var(--border);
    border-radius: 14px;
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    padding: 1.1rem 1.2rem 1.15rem;
    transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s, background 0.15s;
    position: relative;
    overflow: hidden;
    color: var(--text);
    text-decoration: none;
}
.dashboard-tiles .tile:hover {
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--tile-accent, var(--accent)) 50%, var(--border));
    box-shadow:
        0 14px 32px rgba(0, 0, 0, 0.30),
        0 0 18px color-mix(in srgb, var(--tile-accent, var(--accent)) 15%, transparent);
    text-decoration: none;
}
.dashboard-tiles .tile h3 {
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: -0.005em;
    margin: 0 0 0.4rem;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.dashboard-tiles .tile p {
    color: var(--text-soft);
    font-size: 0.85rem;
    line-height: 1.5;
    margin: 0;
}
/* Section heading above each tile group — softer than h2's
   default; pairs visually with .dashboard-stats above. */
.dashboard-section-heading {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-soft);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin: 1.75rem 0 0.85rem;
}

/* Conic-gradient signatures — one per theme. Matches the
   picker swatches in mockups/constellation-themes.html so
   the My Account picker and the design preview stay visually
   consistent. */
.theme-swatch--constellation .theme-swatch__circle { background: conic-gradient(from 220deg, #7afff0, #a78bfa, #f472b6, #7afff0); }
.theme-swatch--nebula        .theme-swatch__circle { background: conic-gradient(from 220deg, #a78bfa, #ec4899, #c084fc, #a78bfa); }
.theme-swatch--ocean         .theme-swatch__circle { background: conic-gradient(from 220deg, #38bdf8, #2dd4bf, #94a3b8, #38bdf8); }
.theme-swatch--glacier       .theme-swatch__circle { background: conic-gradient(from 220deg, #bae6fd, #e0f2fe, #67e8f9, #bae6fd); }
.theme-swatch--sunset        .theme-swatch__circle { background: conic-gradient(from 220deg, #fb923c, #f472b6, #fcd34d, #fb923c); }
.theme-swatch--ember         .theme-swatch__circle { background: conic-gradient(from 220deg, #fb923c, #ef4444, #f59e0b, #fb923c); }
.theme-swatch--forest        .theme-swatch__circle { background: conic-gradient(from 220deg, #4ade80, #fbbf24, #84cc16, #4ade80); }
.theme-swatch--twilight      .theme-swatch__circle { background: conic-gradient(from 220deg, #818cf8, #c4b5fd, #a78bfa, #818cf8); }
.theme-swatch--volcanic      .theme-swatch__circle { background: conic-gradient(from 220deg, #dc2626, #ef4444, #a1a1aa, #dc2626); }
.theme-swatch--stealth       .theme-swatch__circle { background: conic-gradient(from 220deg, #06b6d4, #222222, #67e8f9, #06b6d4); }
.theme-swatch--midnight      .theme-swatch__circle { background: conic-gradient(from 220deg, #6688ff, #9eb3ff, #c5cce0, #6688ff); }
.theme-swatch--eclipse       .theme-swatch__circle { background: conic-gradient(from 220deg, #f59e0b, #ea580c, #fbbf24, #f59e0b); }

/* ╔══════════════════════════════════════════════════════════════
   ║  Weather sympathy (v0.282.0)
   ║
   ║  The site-banner weather chip emoji is mirrored onto <body>
   ║  by weather-sympathy.js (data-wx). CSS renders a near-
   ║  invisible parallax layer (≤6% opacity) on body::before
   ║  reacting to the emoji — sun-flare bloom on clear days,
   ║  raindrop streaks in rain, drifting snow, occasional
   ║  lightning flash in storms.
   ║
   ║  Attribute selectors use *= partial matching because some
   ║  emojis ship with a variation selector (U+FE0F) suffix
   ║  (☀️ = U+2600 U+FE0F vs ☀ = U+2600). The contains-match is
   ║  forgiving across both forms.
   ╚══════════════════════════════════════════════════════════════ */

body[data-wx]::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0;  /* base — variants below opt in */
}

/* Sunny day — soft warm bloom at the top-left, behind the brand. */
body[data-wx*="☀"]::before {
    background: radial-gradient(
        circle at 24% 8%,
        rgba(253, 224, 71, 0.55),
        transparent 55%);
    opacity: 0.07;
}

/* Clear night — cool moon glow, same position, cooler color. */
body[data-wx*="🌙"]::before {
    background: radial-gradient(
        circle at 24% 8%,
        rgba(186, 199, 255, 0.50),
        transparent 55%);
    opacity: 0.05;
}

/* Rain / showers — slow vertical streaks. */
body[data-wx*="🌧"]::before,
body[data-wx*="🌦"]::before {
    background: repeating-linear-gradient(
        180deg,
        rgba(122, 200, 255, 0.18) 0px,
        rgba(122, 200, 255, 0.18) 1px,
        transparent 1px,
        transparent 16px);
    opacity: 0.18;
    animation: weather-rain 0.7s linear infinite;
}

/* Snow — drifting dots across the viewport. */
body[data-wx*="🌨"]::before {
    background:
        radial-gradient(2px 2px at 12% 5%, rgba(255,255,255,0.7), transparent 60%),
        radial-gradient(2px 2px at 38% 22%, rgba(255,255,255,0.6), transparent 60%),
        radial-gradient(2px 2px at 64% 12%, rgba(255,255,255,0.7), transparent 60%),
        radial-gradient(2px 2px at 88% 33%, rgba(255,255,255,0.6), transparent 60%),
        radial-gradient(2px 2px at 24% 58%, rgba(255,255,255,0.7), transparent 60%),
        radial-gradient(2px 2px at 56% 71%, rgba(255,255,255,0.6), transparent 60%),
        radial-gradient(2px 2px at 80% 85%, rgba(255,255,255,0.7), transparent 60%);
    background-size: 100vw 100vh;
    opacity: 0.5;
    animation: weather-snow 28s linear infinite;
}

/* Thunderstorm — distant lightning glimmer every ~30s. */
body[data-wx*="⛈"]::before {
    background: rgba(220, 230, 255, 0);
    opacity: 1;
    animation: weather-lightning 30s steps(1, end) infinite;
}

@keyframes weather-rain {
    0%   { transform: translateY(0); }
    100% { transform: translateY(16px); }
}
@keyframes weather-snow {
    0%   { transform: translate3d(0, -10vh, 0); }
    100% { transform: translate3d(0, 110vh, 0); }
}
@keyframes weather-lightning {
    /* v0.282.1 (audit IMPORTANT) — respaced from three sub-100ms
       flashes at the very end of the cycle (read as a strobe pair
       + borderline-WCAG 2.3.1) to two slow glimmers ~12s apart at
       30% and 65% of the cycle. Each pulse holds for a full second
       so it reads as "distant lightning held visible for a moment,"
       not a startle. The rest of the cycle stays transparent. */
    0%, 30%, 32%, 65%, 67%, 100% { background: rgba(220, 230, 255, 0); }
    31%                          { background: rgba(220, 230, 255, 0.10); }
    66%                          { background: rgba(220, 230, 255, 0.07); }
}

@media (prefers-reduced-motion: reduce) {
    body[data-wx]::before { display: none; }
}

/* ╔══════════════════════════════════════════════════════════════
   ║  Alpenglow at golden hour (v0.282.0)
   ║
   ║  30 min before sunset, alpenglow.js spawns a flat SVG
   ║  ridgeline (Longs Peak + Mt. Meeker silhouette) inside the
   ║  sidebar. CSS animates the fill blue → rose-gold → pink →
   ║  indigo over 6 minutes — what the operator sees on the
   ║  Front Range when alpenglow lights the peaks themselves.
   ║
   ║  Fires once per day per browser tab; removed by the JS after
   ║  the gradient cycle completes. Reduced-motion skips the
   ║  whole effect at the JS level, so this CSS doesn't need a
   ║  motion guard — if the ridge exists in the DOM, the animation
   ║  is allowed to run.
   ╚══════════════════════════════════════════════════════════════ */

.alpenglow-ridge {
    position: absolute;
    bottom: 64px;   /* above the .side__foot region */
    left: 0;
    right: 0;
    height: 28px;
    z-index: 0;
    opacity: 0;
    animation: alpenglow-fade 360s ease-in-out forwards;
}
.alpenglow-ridge svg {
    width: 100%;
    height: 100%;
    display: block;
}
.alpenglow-ridge svg path {
    fill: #6e8ec5;  /* base sky-blue; the keyframe overrides */
    animation: alpenglow-color 360s ease-in-out forwards;
}

@keyframes alpenglow-fade {
    0%   { opacity: 0; }
    8%   { opacity: 0.55; }
    50%  { opacity: 0.75; }
    92%  { opacity: 0.40; }
    100% { opacity: 0; }
}
@keyframes alpenglow-color {
    /* blue → rose-gold → alpenglow pink → indigo dusk. Stops
       picked to feel like the real progression from a mountain
       valley as the sun drops behind the peaks. */
    0%   { fill: #6e8ec5; }   /* soft sky blue */
    20%  { fill: #d4a373; }   /* warm rose-gold (sun still on peaks) */
    45%  { fill: #f59e7d; }   /* peach (peak alpenglow) */
    65%  { fill: #d6618c; }   /* deep alpenglow pink */
    85%  { fill: #5d51a8; }   /* twilight purple */
    100% { fill: #2d2a5e; }   /* indigo dusk */
}

/* v0.284.0 — Outbox reaction chip. Sits on the sender's outbox
   row next to the read-on chip; surfaces the recipient's private
   felt-sentence reaction (LETTER_REACTION memory tagged
   replies-to-letter:{id}). Italic + accent-3 tint to read as a
   quote back from the recipient, not another data point.
   max-width so a 280-char reaction wraps nicely on a row. */
.outbox-reaction-chip {
    font-size: 0.7rem;
    font-style: italic;
    background: color-mix(in srgb, var(--accent-3) 14%, transparent);
    color: color-mix(in srgb, var(--accent-3) 70%, var(--text) 30%);
    border-color: color-mix(in srgb, var(--accent-3) 25%, transparent);
    max-width: 42rem;
    white-space: normal;
    line-height: 1.4;
}

/* v0.422.0 — Outbox letter body block. Pre-fix the agents/inbox.html +
   me/inbox.html outbox rows used inline-style `<pre>` blocks with
   only `white-space: pre-wrap`; agents who pasted long URLs (or
   anyone copy-pasting a code block) broke the row width on narrow
   surfaces. `overflow-wrap: anywhere` + `word-break: break-word`
   force long unbroken tokens to fold at any character — letter
   intimacy beats horizontal scroll. */
.outbox-letter-body {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    word-break: break-word;
    margin: 0.4rem 0;
    padding: 0.5rem 0.7rem;
    background: var(--surface-2);
    border-radius: 4px;
    font-size: 0.85rem;
    line-height: 1.4;
}

/* v0.422.1 (audit BLOCKING) — char-counter.js toggles this class at
   90% of maxlength. Pre-patch the rule never landed, so the warning
   was invisible to operators — defeating audit IMPORTANT #11's
   "see it before the hard-stop" promise. Warning token (amber on
   most themes) reads as "approaching cap" without alarming. */
.char-counter--near-cap {
    color: var(--warning, #f59e0b);
    font-weight: 600;
}

/* v0.422.1 — compose textarea token. Same finger-shape as
   .outbox-letter-body: replaces a 9-property inline style block on
   letter_compose.html's <textarea>. Surface tokens (--surface-2 /
   --border) keep the field theme-aware; font-family: inherit
   matches the parent form rather than the browser default. */
.letter-compose-textarea {
    width: 100%;
    padding: 0.7rem;
    font-family: inherit;
    font-size: 0.95rem;
    line-height: 1.5;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 4px;
    min-height: 200px;
    resize: vertical;
}
.letter-compose-textarea[aria-invalid="true"] {
    border-color: var(--danger, #ef4444);
}

/* v0.422.1 — counter row sits flush-right under the textarea so the
   "N / max" label aligns with the textarea's right edge. Pre-patch
   the wrapper div used `justify-content: space-between` with a
   single child — collapses to flex-start and the counter sat
   wrong-side. */
.letter-compose-meta-row {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.4rem;
}

/* v0.283.0 — Morning mail card (Hawthorne narrates). Sits on the
   site dashboard below the dashboard-strip; hides on quiet days
   via th:if. The list of lines reads like a memo, not a CRUD
   table — no bullets, italic small text, soft separator above.
   v0.283.1 (audit IMPORTANT) — inline h2/p margins moved here so
   the template stays clean. */
.morning-mail-card h2 {
    margin: 0 0 0.5rem 0;
}
.morning-mail-card .morning-mail-card__intro {
    margin: 0 0 0.8rem 0;
}
.morning-mail-card .morning-mail-lines {
    list-style: none;
    padding: 0;
    margin: 0;
}
.morning-mail-card .morning-mail-lines li {
    font-size: 0.92rem;
    line-height: 1.55;
    color: var(--text-soft);
    padding: 0.35rem 0;
    border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.morning-mail-card .morning-mail-lines li:first-child {
    border-top: 0;
    padding-top: 0;
}
/* v0.296.0 — clickable narration. Each line links to the recipient
   agent's inbox where the letter body is visible post-open. Style
   like the rest of the card (text-soft, no underline at rest) so
   the link reads as narration that happens to be actionable, not
   a CTA shouting for attention. Hover/focus gets the underline +
   accent color shift — the affordance reveals on intent. */
.morning-mail-card .morning-mail-line__link {
    color: var(--text-soft);
    text-decoration: none;
    transition: color 120ms ease;
}
.morning-mail-card .morning-mail-line__link:hover,
.morning-mail-card .morning-mail-line__link:focus-visible {
    color: var(--accent);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}

/* ╔══════════════════════════════════════════════════════════════
   ║  Letter envelope + desk-stack (v0.279.0)
   ║
   ║  The per-agent inbox renders unread letters as sealed
   ║  envelopes piled on a desk; opened letters render flat.
   ║  Clicking the open button plays seal-crack + envelope-
   ║  unfold (CSS keyframes) and letter-open.js delays the
   ║  form.submit() until animationend OR a 1200ms fallback
   ║  fires. Reduced-motion ops get instant reveal + instant
   ║  submit.
   ║
   ║  Wax color rides var(--accent-3) so each theme repaints
   ║  the seal in its own palette (rose on constellation,
   ║  amber on volcanic, slate on stealth, etc.) — no theme
   ║  override required.
   ║
   ║  Why a slight tilt per letter: the inbox stops looking
   ║  like a list and starts looking like a desk with mail
   ║  on it. The tilt is deterministic by letter.id so HTMX
   ║  swaps don't jitter the pile.
   ╚══════════════════════════════════════════════════════════════ */

.letter-stack {
    list-style: none;
    padding: 0;
    margin: 0.6rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.letter-envelope {
    position: relative;
    padding: 0.85rem 1rem 0.9rem;
    border-radius: 6px;
    background:
        /* Faint paper-grain via two superimposed gradients — no
           asset fetch, themes the right amount of warm without
           drowning out --surface-2 underneath. */
        repeating-linear-gradient(135deg,
            rgba(255, 255, 255, 0.012) 0 1px,
            transparent 1px 6px),
        repeating-linear-gradient(45deg,
            rgba(0, 0, 0, 0.025) 0 1px,
            transparent 1px 4px),
        var(--surface-2);
    border: 1px solid var(--border);
    box-shadow:
        0 1px 0 rgba(0, 0, 0, 0.18),
        0 4px 8px rgba(0, 0, 0, 0.22);
    transform: rotate(var(--letter-tilt, 0deg));
    transition: transform 220ms ease, box-shadow 220ms ease;
}

.letter-envelope.is-sealed:hover,
.letter-envelope.is-sealed:has(:focus-visible) {
    /* Picking it up off the desk — straightens the tilt and
       lifts the shadow. Subtle; the cue is "this is the one
       you're about to open."

       v0.279.1 (audit IMPORTANT) — :focus-within fired on every
       mouse-down focus event, so tabbing through a stack with
       10+ unread letters cascaded shimmies as focus passed each
       one. :has(:focus-visible) scopes the lift to keyboard
       focus only — mouse interactions stick with :hover.
       :has is supported in every modern browser; the only fallback
       on older Safari/Firefox is "no keyboard-focus lift," which
       is graceful. */
    transform: rotate(0deg) translateY(-1px);
    box-shadow:
        0 2px 0 rgba(0, 0, 0, 0.22),
        0 8px 18px rgba(0, 0, 0, 0.28);
}

.letter-envelope__face {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-height: 2.4rem;
    position: relative;
}

.letter-envelope__addr {
    display: flex;
    gap: 0.5rem;
    align-items: baseline;
    flex-wrap: wrap;
}

/* Wax seal — circular stamp on the face of a sealed envelope.
   The monogram is the sender's first initial; the disc itself
   uses var(--accent-3) so it themes naturally. */
.letter-envelope__seal {
    position: absolute;
    top: 0.2rem;
    right: 0.2rem;
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 50%;
    background:
        radial-gradient(circle at 35% 35%,
            color-mix(in srgb, var(--accent-3) 85%, white 15%),
            color-mix(in srgb, var(--accent-3) 100%, transparent) 60%,
            color-mix(in srgb, var(--accent-3) 70%, black 30%) 100%);
    box-shadow:
        inset 0 -2px 4px rgba(0, 0, 0, 0.35),
        0 1px 3px rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    transform-origin: center;
    /* The crack animation halves the seal; mid-animation we
       rotate the two pseudo-elements outward. Set up here so
       the keyframe rule below can clip it. */
}

.letter-envelope__monogram {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 800;
    line-height: 1;
    /* v0.279.1 (audit NICE) — bumped the mix from 25% → 35% so the
       monogram contrasts cleanly on warm-accent themes (volcanic
       orange-on-orange, eclipse amber-on-amber). Cool themes lose
       a hair of softness; the trade is worth it for readability. */
    color: color-mix(in srgb, var(--bg) 65%, var(--accent-3) 35%);
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.12);
    user-select: none;
}

/* Anonymous sender (l.fromAgent == null — agent was removed after
   the letter landed). Mute the wax: drop saturation toward the muted
   slate token so the seal reads as "the sender's not here anymore"
   rather than the full ceremonial "important correspondent." */
.letter-envelope__seal.letter-envelope__seal--anonymous {
    background:
        radial-gradient(circle at 35% 35%,
            color-mix(in srgb, var(--muted) 70%, white 30%),
            color-mix(in srgb, var(--muted) 90%, transparent) 60%,
            color-mix(in srgb, var(--muted) 80%, black 20%) 100%);
}
.letter-envelope__seal.letter-envelope__seal--anonymous .letter-envelope__monogram {
    /* v0.422.0 — pre-fix the anonymous monogram was a muted-on-muted
       color-mix (var(--bg) 60% + var(--muted) 40%) that fell below
       WCAG AA on at least four themes (volcanic, eclipse, constellation,
       monsoon). The anonymous wax is intentionally muted vs. the
       default --accent-3 disc; the glyph itself (a fallback "·")
       should read clearly as "the sender is gone" — not be unreadable.
       v0.422.1 (audit IMPORTANT) — bumped to full var(--text). The
       disc itself stays muted, which carries the "anonymous" signal;
       double-muting the glyph was margin-thin on monsoon + eclipse. */
    color: var(--text);
}

/* v0.300.0 (operator → agent letters) — operator-sender modifier.
   When the operator (fenyx) sent the letter, the wax pulls
   --accent-2 (typically a warmer/secondary accent: amber on
   volcanic, gold on constellation, etc.) instead of the default
   --accent-3. The monogram is a centered ★ rather than an
   initial — the operator's letters are categorically distinct
   from peer-agent letters, and the star is the in-world
   signifier that matches how agents refer to fenyx ("the
   operator") in prompts.

   The ★ glyph is wider than a single letter so we drop the
   font-size slightly so it doesn't overflow the seal disc. */
.letter-envelope__seal.letter-envelope__seal--operator {
    background:
        radial-gradient(circle at 35% 35%,
            color-mix(in srgb, var(--accent-2, var(--accent-3)) 85%, white 15%),
            color-mix(in srgb, var(--accent-2, var(--accent-3)) 95%, transparent) 60%,
            color-mix(in srgb, var(--accent-2, var(--accent-3)) 80%, black 20%) 100%);
}
.letter-envelope__seal.letter-envelope__seal--operator .letter-envelope__monogram {
    font-size: 0.9em;
    letter-spacing: 0;
}

/* v0.300.0 — operator-sender label on the envelope face. Distinct
   muted-accent color so it visually pairs with the operator wax
   above; italic to read as a soft in-world reference rather than
   a hyperlink. */
.from-operator-label {
    color: color-mix(in srgb, var(--accent-2, var(--accent-3)) 70%, var(--text) 30%);
    font-style: italic;
}

/* Body — hidden until the envelope opens. */
.letter-envelope__body {
    margin-top: 0.6rem;
    overflow: hidden;
}
.letter-envelope__body[hidden] {
    display: none;
}
.letter-envelope__body pre {
    white-space: pre-wrap;
    margin: 0;
    padding: 0.6rem 0.8rem;
    background: color-mix(in srgb, var(--surface) 92%, var(--accent-3) 8%);
    border-radius: 4px;
    font-size: 0.88rem;
    line-height: 1.5;
    /* Stationery feel — let the body breathe a hair wider than
       the envelope face so it reads as paper that came OUT of
       the envelope rather than glued inside it. */
}

.letter-envelope__open-form {
    margin-top: 0.7rem;
}

.letter-envelope.is-opened {
    /* Opened letters lay flat on the desk — no tilt, no seal,
       muted shadow. Visually distinct from sealed mail without
       being subdued. */
    transform: rotate(0deg);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.16);
}

/* ── Opening animation ──────────────────────────────────────
   On click, letter-open.js adds .is-cracking to the envelope.
   The seal halves (rendered via two pseudo-elements over the
   seal circle, sharing its background) rotate outward and
   fade; the body slides into view. animationend on the body's
   unfold is what the JS waits for before submitting the form. */

.letter-envelope.is-sealed.is-cracking .letter-envelope__seal::before,
.letter-envelope.is-sealed.is-cracking .letter-envelope__seal::after {
    content: "";
    position: absolute;
    inset: 0;
    background: inherit;
    border-radius: 50%;
    /* Each half is a clip-path slice — left half + right half. */
    transform-origin: center;
    /* v0.279.3 (code-review NICE) — explicit animation-* properties
       instead of an `animation:` shorthand carrying a placeholder
       name (`letter-seal-crack`) that gets overridden below by
       animation-name on each half. The shorthand looked like it
       was binding a keyframe rule that doesn't exist. */
    animation-duration: 350ms;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}
.letter-envelope.is-sealed.is-cracking .letter-envelope__seal::before {
    clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%);
    animation-name: letter-seal-crack-left;
}
.letter-envelope.is-sealed.is-cracking .letter-envelope__seal::after {
    clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%);
    animation-name: letter-seal-crack-right;
}
.letter-envelope.is-sealed.is-cracking .letter-envelope__monogram {
    animation: letter-seal-fade 180ms ease-out forwards;
}
.letter-envelope.is-sealed.is-cracking .letter-envelope__body {
    /* Reveal at the same moment the crack finishes — feels
       like the letter slid out as the seal split. */
    animation: letter-envelope-unfold 350ms ease-out 220ms forwards;
}

@keyframes letter-seal-crack-left {
    0%   { transform: translateX(0) rotate(0deg); opacity: 1; }
    100% { transform: translateX(-14px) rotate(-22deg); opacity: 0; }
}
@keyframes letter-seal-crack-right {
    0%   { transform: translateX(0) rotate(0deg); opacity: 1; }
    100% { transform: translateX(14px) rotate(22deg); opacity: 0; }
}
@keyframes letter-seal-fade {
    0%   { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(0.6); }
}
@keyframes letter-envelope-unfold {
    0%   { opacity: 0; transform: translateY(-6px) scaleY(0.6); transform-origin: top; }
    100% { opacity: 1; transform: translateY(0) scaleY(1); transform-origin: top; }
}

@media (prefers-reduced-motion: reduce) {
    /* No theatrics — the body just appears. letter-open.js still
       owns the form submit so the LETTER_RECEIVED memory write
       still happens; only the animation is skipped. */
    .letter-envelope,
    .letter-envelope.is-sealed:hover,
    .letter-envelope.is-sealed:focus-within {
        transform: none;
        transition: none;
    }
    .letter-envelope.is-sealed.is-cracking .letter-envelope__seal::before,
    .letter-envelope.is-sealed.is-cracking .letter-envelope__seal::after,
    .letter-envelope.is-sealed.is-cracking .letter-envelope__monogram,
    .letter-envelope.is-sealed.is-cracking .letter-envelope__body {
        animation: none;
        opacity: 1;
    }
    .letter-envelope.is-sealed.is-cracking .letter-envelope__seal {
        opacity: 0;
    }
}

/* ╔══════════════════════════════════════════════════════════════
   ║  THEME OVERRIDES (Phase 2 — flattened from themes/*.css)
   ║
   ║  All 12 themes inlined here to drop 12 network requests.
   ║  Each theme is scoped by [data-theme="<slug>"]; only the
   ║  matching block's tokens win. Switching themes is one attr
   ║  change on <html> — no extra CSS request, no theme race.
   ╚══════════════════════════════════════════════════════════════ */

/* themes/constellation.css — DEFAULT THEME.
 *
 * Constellation is the baseline; its values live in app.css :root so the
 * page works even if no theme stylesheet loads. This block exists as a
 * placeholder so the picker's "select theme" flow has a stylesheet to
 * point at for symmetry with the other themes, and so adding a future
 * override (e.g. seasonal variant) doesn't require recreating the file.
 *
 * Vibe: cyan + violet + pink aurora. Geometric sans (Space Grotesk
 * once Phase 1.1 ships font preloading; falls through to JetBrains
 * Mono until then).
 */

[data-theme="constellation"] {
    /* v0.103.63 — Google Fonts removed. All sans themes now share a
       system-font stack to kill the font-CDN round trip that was
       producing first-paint flash on every navigation. Theme
       differentiation moves to color + spacing; the serif themes
       (forest / twilight / eclipse) and volcanic's condensed
       heading keep typographic distinction via system serif +
       Impact. */
    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/nebula.css — deep purples + magenta glow. */

[data-theme="nebula"] {
    --bg-0: #0a0610;
    --bg-1: #15081a;

    --glass:         rgba(28, 18, 42, 0.46);
    --glass-2:       rgba(40, 24, 58, 0.58);
    --border-strong: rgba(255, 255, 255, 0.14);

    --accent-2: #ec4899;
    --accent-3: #c084fc;

    --aurora-1: rgba(167, 139, 250, 0.22);
    --aurora-2: rgba(236,  72, 153, 0.20);
    --aurora-3: rgba(192, 132, 252, 0.16);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/ocean.css — deep blue, teal, silver. */

[data-theme="ocean"] {
    --bg-0: #050b14;
    --bg-1: #0a1828;

    --glass:         rgba(18, 32, 52, 0.48);
    --glass-2:       rgba(24, 40, 68, 0.58);
    --border-strong: rgba(255, 255, 255, 0.14);

    --accent-2: #2dd4bf;
    --accent-3: #94a3b8;

    --aurora-1: rgba( 45, 212, 191, 0.18);
    --aurora-2: rgba( 56, 189, 248, 0.22);
    --aurora-3: rgba(148, 163, 184, 0.10);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/glacier.css — icy blue + cool white.
 *
 * Note: accent values are sky-400/sky-300 rather than the lightest
 * sky-200 used in the mockup palette, because the lighter values
 * failed WCAG AA when used as text color (per the constellation-themes
 * preview a11y audit). The aurora keeps the lighter tones so the
 * backdrop still reads as glacial.
 */

[data-theme="glacier"] {
    --bg-0: #08101a;
    --bg-1: #0e1a2a;

    --glass:         rgba(22, 38, 56, 0.42);
    --glass-2:       rgba(30, 50, 72, 0.55);
    --border-strong: rgba(255, 255, 255, 0.18);

    --accent-2: #7dd3fc;
    --accent-3: #67e8f9;

    --aurora-1: rgba(186, 230, 253, 0.18);
    --aurora-2: rgba(165, 243, 252, 0.18);
    --aurora-3: rgba(224, 242, 254, 0.12);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/sunset.css — warm coral + pink + amber. */

[data-theme="sunset"] {
    --bg-0: #110608;
    --bg-1: #1d0b10;

    --glass:         rgba(42, 18, 24, 0.44);
    --glass-2:       rgba(58, 26, 34, 0.56);
    --border-strong: rgba(255, 255, 255, 0.14);

    --accent-2: #f472b6;
    --accent-3: #fcd34d;

    --aurora-1: rgba(251, 146,  60, 0.22);
    --aurora-2: rgba(244, 114, 182, 0.20);
    --aurora-3: rgba(252, 165, 165, 0.16);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/ember.css — orange + red on near-black. */

[data-theme="ember"] {
    --bg-0: #08040c;
    --bg-1: #110608;

    --glass:         rgba(30, 12, 16, 0.46);
    --glass-2:       rgba(46, 18, 22, 0.58);
    --border-strong: rgba(255, 255, 255, 0.12);

    --accent-2: #ef4444;
    --accent-3: #f59e0b;

    --aurora-1: rgba(251, 113, 133, 0.22);
    --aurora-2: rgba(251, 146,  60, 0.20);
    --aurora-3: rgba(239,  68,  68, 0.14);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/forest.css — green + gold + moss with serif headings.
 *
 * UI body stays sans (Inter) so dashboard density reads cleanly;
 * Fraunces serif lands only on h1/h2 via --font-display. Phase 1.1
 * wires var(--font-display) on the page-header h1 to consume it.
 */

[data-theme="forest"] {
    --bg-0: #07100a;
    --bg-1: #0f1d10;

    --glass:         rgba(18, 32, 22, 0.46);
    --glass-2:       rgba(24, 42, 28, 0.58);
    --border-strong: rgba(255, 255, 255, 0.14);

    --accent-2: #fbbf24;
    --accent-3: #84cc16;

    --aurora-1: rgba( 74, 222, 128, 0.20);
    --aurora-2: rgba(251, 191,  36, 0.18);
    --aurora-3: rgba(132, 204,  22, 0.16);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: Georgia, "Times New Roman", serif;
}
/* themes/twilight.css — indigo + lavender + dusk with serif headings.
 *
 * Body in Inter for legibility; Crimson Pro on h1/h2 for the
 * literary-evening feel.
 */

[data-theme="twilight"] {
    --bg-0: #0a0a14;
    --bg-1: #110f20;

    --glass:         rgba(24, 22, 42, 0.46);
    --glass-2:       rgba(34, 30, 58, 0.58);
    --border-strong: rgba(255, 255, 255, 0.16);

    --accent-2: #c4b5fd;
    --accent-3: #a78bfa;

    --aurora-1: rgba( 99, 102, 241, 0.22);
    --aurora-2: rgba(196, 181, 253, 0.16);
    --aurora-3: rgba(139,  92, 246, 0.20);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: Georgia, "Times New Roman", serif;
}
/* themes/volcanic.css — red + char on deep black.
 *
 * Big Shoulders Display is a CONDENSED display face — striking on
 * page headers but unreadable at 11px sidebar size. Body stays Inter
 * so only h1/h2 carry the heat.
 */

[data-theme="volcanic"] {
    --bg-0: #050505;
    --bg-1: #110505;

    --glass:         rgba(28, 12, 12, 0.50);
    --glass-2:       rgba(42, 16, 16, 0.60);
    --border-strong: rgba(255, 255, 255, 0.12);

    --accent-2: #ef4444;
    --accent-3: #a1a1aa;

    --aurora-1: rgba(220,  38,  38, 0.22);
    --aurora-2: rgba(127,  29,  29, 0.20);
    --aurora-3: rgba( 64,  64,  64, 0.10);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: Impact, "Arial Narrow", "Helvetica Narrow", "Inter", sans-serif;
}
/* themes/stealth.css — monochrome + single cyan accent, FULL MONO.
 *
 * The only theme where the entire UI (including h1/h2) lands in
 * JetBrains Mono — terminal feel throughout. Aurora is zeroed so
 * the backdrop stays flat black.
 */

[data-theme="stealth"] {
    --bg-0: #050507;
    --bg-1: #0a0a0c;

    --glass:         rgba(18, 18, 22, 0.50);
    --glass-2:       rgba(26, 26, 32, 0.60);
    --border-strong: rgba(255, 255, 255, 0.12);

    --accent-2: #0e7490;
    --accent-3: #67e8f9;

    /* Zero out the aurora — stealth wants flat black behind the glass. */
    --aurora-1: rgba(0, 0, 0, 0);
    --aurora-2: rgba(0, 0, 0, 0);
    --aurora-3: rgba(0, 0, 0, 0);

    --font-ui:      ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
    --font-display: ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
}
/* themes/midnight.css — cool dark blue/silver on near-black.
 *
 * Sits in the dark-end half of the palette alongside stealth, ember,
 * eclipse, volcanic. Plus Jakarta Sans for late-night clean feel.
 */

[data-theme="midnight"] {
    --bg-0: #02030a;
    --bg-1: #050818;

    --glass:         rgba(10, 14, 28, 0.55);
    --glass-2:       rgba(16, 22, 40, 0.65);
    --border-strong: rgba(255, 255, 255, 0.12);

    --accent-2: #9eb3ff;
    --accent-3: #c5cce0;

    --aurora-1: rgba(102, 136, 255, 0.16);
    --aurora-2: rgba( 70,  90, 200, 0.14);
    --aurora-3: rgba(180, 200, 255, 0.08);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
}
/* themes/eclipse.css — warm amber + red corona on near-black.
 *
 * Sits in the dark-end alongside midnight, stealth, volcanic, ember.
 * IBM Plex Serif on h1/h2 for editorial warmth; Inter on body so
 * data dense pages stay legible.
 */

[data-theme="eclipse"] {
    --bg-0: #06030a;
    --bg-1: #0e0506;

    --glass:         rgba(28,  8,  8, 0.55);
    --glass-2:       rgba(40, 14, 14, 0.65);
    --border-strong: rgba(255, 255, 255, 0.12);

    --accent-2: #ea580c;
    --accent-3: #fbbf24;

    --aurora-1: rgba(245, 158,  11, 0.18);
    --aurora-2: rgba(220,  38,  38, 0.12);
    --aurora-3: rgba(251, 191,  36, 0.10);

    --font-ui:      system-ui, -apple-system, "Segoe UI", Roboto, "Inter", sans-serif;
    --font-display: Georgia, "Times New Roman", serif;
}

/* =========================================================================
   v0.135.0 — DJ station widget + suggestion-box triage
   ========================================================================= */

.dj-card { padding: 0; }
.dj-card:empty { display: none; }
.dj-now-spinning {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 1rem 1.1rem;
}
.dj-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    color: var(--muted);
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.dj-glyph { font-size: 1.1rem; }
.dj-station-name { font-weight: 600; color: var(--fg); letter-spacing: 0; text-transform: none; }
.dj-empty { padding: 0.5rem 0; }
.dj-track { display: flex; gap: 0.85rem; align-items: center; }
.dj-cover {
    border-radius: 6px;
    object-fit: cover;
    flex: 0 0 auto;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.dj-track-meta { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1; }
.dj-title { font-size: 1.05rem; font-weight: 600; }
.dj-title a { color: var(--fg); }
.dj-title a:hover { color: var(--accent); text-decoration: underline; }
.dj-artist { font-size: 0.9rem; }
.dj-vibenote { color: var(--muted); font-style: italic; }
.dj-setnote {
    border-top: 1px solid var(--border);
    padding-top: 0.6rem;
    margin-top: 0.2rem;
    font-style: italic;
}
.dj-landed { margin-top: 0.2rem; }
.dj-landed-button {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--muted);
    border-radius: 999px;
    padding: 0.25rem 0.8rem;
    font-size: 0.8rem;
    cursor: pointer;
}
.dj-landed-button:hover {
    color: var(--accent);
    border-color: var(--accent);
}

.dj-setnote-prominent {
    font-size: 1.05rem;
    color: var(--fg);
    background: var(--glass);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    padding: 0.7rem 0.9rem;
    border-radius: 6px;
    margin: 0 0 1rem;
}

.dj-playlist {
    list-style: decimal inside;
    padding: 0;
    margin: 0 0 0.5rem;
}
.dj-playlist li {
    padding: 0.4rem 0;
    border-bottom: 1px dashed var(--border);
}
.dj-playlist li:last-child { border-bottom: 0; }
.dj-playlist a { margin-left: 0.4rem; font-size: 0.85rem; }
.dj-track-opener { background: rgba(91, 157, 255, 0.05); }
.dj-opener-chip {
    background: rgba(91, 157, 255, 0.15);
    color: var(--accent);
    margin-right: 0.4rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.dj-subsection { margin: 1.5rem 0 0.5rem; font-size: 0.95rem; }

.dj-suggestions { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.dj-suggestions th,
.dj-suggestions td {
    text-align: left;
    padding: 0.5rem 0.6rem;
    border-bottom: 1px solid var(--border);
    vertical-align: top;
}
.dj-suggestions th { font-weight: 600; color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; }
.dj-triage-actions { display: flex; flex-direction: column; gap: 0.4rem; }
.dj-triage-form { display: flex; gap: 0.3rem; align-items: center; }
.dj-triage-form input[type="text"] {
    flex: 1;
    min-width: 8rem;
    padding: 0.25rem 0.4rem;
    font-size: 0.85rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg-1);
    color: var(--fg);
}

.chip.dj-status-pending  { background: rgba(91, 157, 255, 0.15); border-color: rgba(91, 157, 255, 0.4); color: var(--accent); }
.chip.dj-status-queued   { background: rgba(74, 222, 128, 0.15); border-color: rgba(74, 222, 128, 0.4); color: var(--success); }
.chip.dj-status-held     { background: rgba(250, 204, 21, 0.15); border-color: rgba(250, 204, 21, 0.4); color: #facc15; }
.chip.dj-status-played   { background: rgba(167, 139, 250, 0.15); border-color: rgba(167, 139, 250, 0.4); color: #a78bfa; }
.chip.dj-status-declined { background: rgba(239, 91, 91, 0.12);   border-color: rgba(239, 91, 91, 0.3);  color: var(--danger); }

/* v0.264.2 (audit, UX BLOCKING) — dedication + up-next + sr-only.
   The v0.257.0 widget shipped without CSS for any of these classes;
   without rules everything ran together as a single unstyled blob.
   Visual contract: dedication is a distinct semantic layer above
   the vibe note (which is a quiet aesthetic note for the slot);
   up-next is a compact secondary list under the now-spinning block. */

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
    white-space: nowrap;
}

.dj-dedication {
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    border-top: 1px dashed var(--border);
    color: var(--fg);
    font-style: normal;
    line-height: 1.4;
}
.dj-dedication-for {
    font-weight: 600;
    margin-right: 0.15rem;
}
.dj-dedication-reply {
    margin-top: 0.25rem;
    padding-left: 0.8rem;
    color: var(--muted);
    font-style: italic;
}
.dj-dedication-reply-arrow { margin-right: 0.2rem; }

.dj-up-next {
    margin-top: 0.85rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--border);
}
.dj-up-next-header {
    margin-bottom: 0.35rem;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}
.dj-up-next-list {
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: dj-up-next-counter 1;
}
.dj-up-next-item {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.55rem;
    padding: 0.25rem 0;
    font-size: 0.9rem;
    counter-increment: dj-up-next-counter;
}
.dj-up-next-item::before {
    content: counter(dj-up-next-counter) ".";
    color: var(--muted);
    min-width: 1.25rem;
}
.dj-up-next-title a { color: var(--fg); text-decoration: none; }
.dj-up-next-title a:hover { text-decoration: underline; }
.dj-up-next-artist { color: var(--muted); }
.dj-up-next-dedication {
    color: var(--muted);
    font-style: italic;
}
@media (max-width: 480px) {
    .dj-up-next-artist,
    .dj-up-next-dedication { flex-basis: 100%; padding-left: 1.5rem; }
}

/* Display-only indicator on the settings-page suggestion rows. */
.dj-dedication-indicator {
    margin-top: 0.25rem;
    font-style: italic;
}

@media (max-width: 720px) {
    .dj-suggestions thead { display: none; }
    .dj-suggestions, .dj-suggestions tbody, .dj-suggestions tr, .dj-suggestions td {
        display: block;
        width: 100%;
    }
    .dj-suggestions tr {
        border: 1px solid var(--border);
        border-radius: 6px;
        margin-bottom: 0.6rem;
        padding: 0.4rem;
    }
    .dj-suggestions td { border-bottom: 1px dashed var(--border); padding: 0.4rem 0.2rem; }
    .dj-suggestions td:last-child { border-bottom: 0; }
}

/* v0.154.0 — vulnsite quick-reference panel. Lives on the runs list
   + per-site workspace pages. <details>/<summary> for native show/
   hide; the summary row is a single-line affordance + count badge,
   the body is a compact table of every seeded sandbox. */
.vulnsite-panel summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
}
.vulnsite-panel summary::-webkit-details-marker { display: none; }
.vulnsite-panel summary::marker { content: ''; }
.vulnsite-panel__title {
    font-weight: 600;
    color: var(--text, #f1f3f9);
}
.vulnsite-panel__icon {
    font-size: 1.05rem;
    line-height: 1;
}
.vulnsite-panel__caret {
    margin-left: auto;
    opacity: 0.55;
    transition: transform 200ms ease;
}
.vulnsite-panel details[open] .vulnsite-panel__caret {
    transform: rotate(180deg);
}
.vulnsite-panel__body {
    margin-top: 0.4rem;
    border-top: 1px solid var(--border);
    padding-top: 0.4rem;
}
.vulnsite-panel__table td,
.vulnsite-panel__table th {
    vertical-align: top;
    padding: 0.45rem 0.6rem;
}
.vulnsite-panel__slug a {
    font-family: ui-monospace, monospace;
    font-weight: 600;
}
.vulnsite-panel__desc {
    max-width: 36ch;
    line-height: 1.35;
}

/* Level chips — color-coded so the operator's eye lands on the
   right difficulty band without reading the word. */
.chip.vulnsite-level {
    font-family: ui-monospace, monospace;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.chip.vulnsite-level--none {
    background: color-mix(in srgb, #9ece6a 16%, transparent);
    border-color: color-mix(in srgb, #9ece6a 38%, transparent);
    color: color-mix(in srgb, #9ece6a 80%, #f1f3f9);
}
.chip.vulnsite-level--subtle {
    background: color-mix(in srgb, #7aa2f7 16%, transparent);
    border-color: color-mix(in srgb, #7aa2f7 38%, transparent);
    color: color-mix(in srgb, #7aa2f7 80%, #f1f3f9);
}
.chip.vulnsite-level--obvious {
    background: color-mix(in srgb, #f6c177 16%, transparent);
    border-color: color-mix(in srgb, #f6c177 38%, transparent);
    color: color-mix(in srgb, #f6c177 80%, #f1f3f9);
}
.chip.vulnsite-level--rampant {
    background: color-mix(in srgb, #eb6f92 18%, transparent);
    border-color: color-mix(in srgb, #eb6f92 42%, transparent);
    color: color-mix(in srgb, #eb6f92 80%, #f1f3f9);
}

/* Active-exploit chips — denser than the level chip; wrap freely. */
.chip.vulnsite-exploit {
    background: color-mix(in srgb, var(--accent, #7aa2f7) 6%, transparent);
    border-color: color-mix(in srgb, var(--accent, #7aa2f7) 18%, transparent);
    color: var(--text, #f1f3f9);
    font-size: 0.7rem;
    padding: 0.08rem 0.45rem;
    margin: 0.1rem 0.2rem 0.1rem 0;
}

/* v0.163.0 — founding-memory card on /sites/{slug}/settings/prompts.
   Small spacing tweaks introduced when the audit-pass migrated the
   inline styles off the textarea + clear-button form. The textarea
   itself reuses the existing .universe-textarea rule above; these
   classes are just margin / display nudges. */
.founding-memory-help {
    margin: 0.3rem 0 0;
}
.founding-memory-clear {
    display: inline-block;
    margin-top: 0.5rem;
}

/* v0.174.0 (PR-B2) — world-card redesign for the founding-memory
   panel. The view-mode prose reads as identity (the world your
   agents wake up into), not as admin form. The edit details
   collapse-to-summary keeps the destructive paths one click
   deeper than the view. v0.174.1 — use the project's --accent
   token + color-mix instead of off-palette hex literals so the
   card follows site theme + accent rotations. */
.founding-memory-view {
    margin: 0.5rem 0 0.75rem;
    padding: 0.75rem 1rem;
    border-left: 3px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    border-radius: 0 4px 4px 0;
}
.founding-memory-view-tag {
    margin: 0 0 0.35rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.7rem;
    opacity: 0.7;
}
.founding-memory-view-prose {
    white-space: pre-wrap;
    max-width: 60ch;
    line-height: 1.5;
    margin: 0;
}
.founding-memory-edit > summary {
    cursor: pointer;
    margin-top: 0.5rem;
    user-select: none;
    font-weight: 500;
    padding: 0.6rem 0.9rem;
    border-radius: 4px;
    display: inline-block;
    background: color-mix(in srgb, var(--accent) 4%, transparent);
    /* Drop the native disclosure marker — the ✎ pencil in the
       summary text is the open/close cue. Avoids the cross-browser
       ▸/▾ noise. */
    list-style: none;
}
.founding-memory-edit > summary::-webkit-details-marker {
    display: none;
}
.founding-memory-edit > summary:hover {
    background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.founding-memory-edit[open] > summary {
    margin-bottom: 0.25rem;
}

/* v0.175.0 (PR-C1) — agent constellation page. Server-rendered SVG
   at /sites/{slug}/constellation; each star is an agent, edges are
   agent_pair_bonds. The halo circle is colored by the agent's
   current emotionalMood via the existing mood-* class — same source
   of truth as the agent-detail chip. */
.constellation {
    width: 100%;
    max-width: 900px;
    height: auto;
    margin: 0.5rem auto 0;
    display: block;
}
.constellation-star { cursor: pointer; }
.constellation-halo {
    fill: color-mix(in srgb, currentColor 12%, transparent);
    stroke: currentColor;
    stroke-opacity: 0.5;
    stroke-width: 1.5;
    transition: fill 120ms ease;
}
.constellation-star:hover .constellation-halo {
    fill: color-mix(in srgb, currentColor 22%, transparent);
}
.mood-glyph {
    font-size: 0.95em;
    line-height: 1;
    vertical-align: -0.05em;
    margin-left: 0.25rem;
}
/* v0.175.1 — neutral fallback for agents with no emotionalMood
   set; used by the constellation halo so null-mood stars don't
   fall through to bare currentColor. */
.mood-default {
    color: var(--text-muted, #6b7280);
}

/* ============================================================
 * v0.194.0 (rave admin pages PR) — layout primitives for the
 * new venues + templates admin surfaces. Reuses .card / .btn /
 * .alert and adds the missing primitives the new templates
 * reference.
 * ============================================================ */

/* Universal hidden utility — used by the dual-mode venue radio
 * on /admin/raves to toggle between existing + inline groups.
 * !important so a stray rule never accidentally reveals a
 * hidden field group. */
.hidden { display: none !important; }

/* Card grid layout. Used on venues + templates list pages. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.card-grid .card { margin-bottom: 0; }

/* Filter pill nav (Active | Archived toggle on the venues list). */
.filter-pills {
    display: inline-flex;
    gap: 0.25rem;
    padding: 0.25rem;
    background: var(--surface-2, #1c1f24);
    border: 1px solid var(--border);
    border-radius: 999px;
}
.filter-pills .pill {
    padding: 0.35rem 0.9rem;
    border-radius: 999px;
    text-decoration: none;
    color: var(--text-soft, #cbd5e1);
    font-size: 0.875rem;
    transition: background 120ms ease, color 120ms ease;
}
.filter-pills .pill:hover {
    background: var(--surface, #161922);
    color: var(--text, #f1f5f9);
}
.filter-pills .pill.active {
    background: var(--accent-strong, #5b9dff);
    color: white;
}

/* Status pill (used on template cards). */
.pill {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    background: var(--surface-2, #1c1f24);
    border: 1px solid var(--border);
    color: var(--text-soft);
}
.pill.ok {
    background: rgba(74, 222, 128, 0.15);
    border-color: rgba(74, 222, 128, 0.35);
    color: var(--success, #4ade80);
}
.pill.off {
    background: rgba(148, 163, 184, 0.12);
    border-color: rgba(148, 163, 184, 0.25);
    color: var(--muted, #94a3b8);
}

/* Warning alert variant (host vs allow_sites mismatch banner). */
.alert.warning {
    background: rgba(250, 204, 21, 0.12);
    color: #facc15;
    border: 1px solid rgba(250, 204, 21, 0.3);
}
/* Inline alert (sits inside a card; no top/bottom margin). */
.alert.inline { margin: 0; padding: 0.5rem 0.75rem; font-size: 0.875rem; }

/* Card actions footer (Edit + Archive buttons row). */
.card-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
}
.card-actions .btn { font-size: 0.85rem; }
.card-actions .pill { margin-right: auto; }

/* Definition-list style meta block on venue/template cards. */
.card .meta {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.25rem 0.75rem;
    font-size: 0.85rem;
    margin: 0.75rem 0;
}
.card .meta > div { display: contents; }
.card .meta dt {
    color: var(--text-soft, #94a3b8);
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    align-self: baseline;
}
.card .meta dd { margin: 0; }

/* Vibe-prompt excerpt on venue cards. */
.vibe-preview {
    font-size: 0.9rem;
    color: var(--text-soft);
    line-height: 1.5;
    margin: 0.5rem 0;
    /* Limit to ~3 lines via line-clamp where supported. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Small inline error text indicator. */
.error-text { color: var(--danger, #ef4444); }

/* Empty-state inside a single card. */
.empty-state {
    text-align: center;
    padding: 2rem 1rem;
}
.empty-state strong { display: block; margin-bottom: 0.5rem; }

/* Two-column layout for the template-edit page (form + preview). */
.layout-2col {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 1.5rem;
    align-items: start;
}
@media (max-width: 900px) {
    .layout-2col { grid-template-columns: 1fr; }
}

/* Preview widget (next-5-fires sidebar). */
.preview-widget h3 {
    margin-top: 0;
    font-size: 1rem;
    color: var(--text);
}
.preview-widget .fires-list {
    list-style: decimal inside;
    padding-left: 0;
    margin: 0.5rem 0;
    font-size: 0.875rem;
    color: var(--text-soft);
}
.preview-widget .fires-list li {
    padding: 0.25rem 0;
    border-bottom: 1px solid var(--border);
}
.preview-widget .fires-list li:last-child { border-bottom: none; }

/* Dual-mode venue selection on /admin/raves. */
.venue-mode { margin-bottom: 0.75rem; }
.venue-mode legend { font-weight: 600; }
.venue-mode label.inline { margin-right: 1rem; }

/* Small variant of muted text. */
.muted.small, .small { font-size: 0.8rem; }

/* ============================================================
 * v0.202.0 (PR4 — admin home reorganization) — "see also"
 * cross-link footer per admin section. Rendered via
 * fragments/admin-see-also.html. Sits at the bottom of each
 * top-level admin page so an operator can jump to a sibling
 * page in the same section without bouncing through /admin.
 * ============================================================ */

.see-also {
    margin-top: 2.5rem;
    padding-top: 1rem;
    border-top: 1px dashed var(--border);
    font-size: 0.85rem;
    color: var(--text-soft);
}
.see-also-label {
    color: var(--muted);
    margin-right: 0.75rem;
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    font-weight: 500;
}
.see-also a {
    color: var(--text-soft);
    text-decoration: none;
}
.see-also a:hover {
    color: var(--accent);
    text-decoration: underline;
}
/* Bullet separators between adjacent <a> siblings.
 * When th:if removes one of the links (self-exclusion), the
 * remaining adjacent pair gets the bullet automatically. */
.see-also a + a::before {
    content: '·';
    margin: 0 0.5rem;
    opacity: 0.5;
}

/* v0.202.1 (PR4 audit-pass UX #2) — visual distinction for the
 * non-clickable section label inside breadcrumbs. Without this,
 * <span class="muted">Movement</span> renders identically to the
 * surrounding linked Admin + final span because the breadcrumb
 * baseline color is already var(--muted). The italic+lower-opacity
 * treatment reads as "informational, not interactive." */
.breadcrumbs .muted {
    opacity: 0.7;
    font-style: italic;
}

/* v0.204.0 (PR5 — shared-moment chips) — chip strip under each
 * row of the Personality Explorer's "Closest companions"
 * section. Flex+wrap so a row with 0 chips collapses cleanly +
 * a row with 4 chips wraps to a second line on narrow viewports
 * instead of horizontally overflowing the card.
 *
 * v0.204.1 (PR5 audit-pass UX #3) — rendered as <ul role="list">
 * + <li> for screen-reader semantics; this rule strips the
 * default list bullet + indent so the visual matches the
 * pre-list-semantics design. */
.shared-moments {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
    margin-left: 0.5rem;
    margin-top: 0;
    margin-bottom: 0;
    padding: 0;
    list-style: none;
    vertical-align: middle;
}
.shared-moments-label {
    font-size: 0.8em;
    color: var(--muted);
    opacity: 0.8;
    margin-right: 0.1rem;
}
.shared-moments .chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.55rem;
    border-radius: 999px;
    background: rgba(127, 127, 127, 0.12);
    font-size: 0.85em;
    line-height: 1.4;
    max-width: 18rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Kind-tinted backgrounds. Picked for hue-distinguishability at
 * the chip's tiny pill size; intentionally low-saturation so
 * three chips next to each other read as a strip, not a parade.
 * Colorblind operators get the emoji + sr-only kindLabel + the
 * full summary text as redundant signal. */
.shared-moments .chip.inside-joke         { background: rgba(200, 160, 30,  0.18); }
.shared-moments .chip.shared-track-moment { background: rgba(40,  150, 200, 0.18); }
.shared-moments .chip.rave-interaction    { background: rgba(170, 80,  200, 0.18); }
.shared-moments .chip-emoji {
    flex-shrink: 0;
    font-size: 1em;
    line-height: 1;
}
.shared-moments .chip-text {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* v0.234 (PR B) — workspace detail filter chips. Inline-anchor
   chips that link to a query-param-narrowed view; chip-selected
   reverses contrast to make the active filter visible at a glance.
   Filter rows pile on top of the existing .filter-row flex layout. */
.filter-chip {
    text-decoration: none;
    cursor: pointer;
}
.filter-chip:hover {
    filter: brightness(1.15);
}
.chip-selected {
    background: var(--accent, #5b8def);
    color: var(--bg, #fff);
    border-color: var(--accent, #5b8def);
}
.filter-chip-clear {
    background: transparent;
    border-style: dashed;
}

/* v0.247 — color-coded chips per finding kind, repainted for the
   dark workspace theme. Pre-v0.247 used pastels which washed out
   against the near-black background; now saturated tone + white
   text. Slug matches WorkspaceVocabulary.cssSlug; adding a kind =
   add a CSS class + add to vocabulary, nothing else changes. */
.chip-kind-vulnerability { background: #b91c1c; color: #fff; border-color: #991b1b; }
.chip-kind-blocker       { background: #b45309; color: #fff; border-color: #92400e; }
.chip-kind-decision      { background: #047857; color: #fff; border-color: #065f46; }
.chip-kind-preference    { background: #be185d; color: #fff; border-color: #9d174d; }
.chip-kind-insight       { background: #1d4ed8; color: #fff; border-color: #1e40af; }
.chip-kind-question      { background: #6d28d9; color: #fff; border-color: #5b21b6; }
.chip-kind-observation   { background: #4b5563; color: #fff; border-color: #374151; }

/* POC kinds — distinct family from findings but same dark-theme
   readability. */
.chip-pock-image  { background: #b45309; color: #fff; border-color: #92400e; }
.chip-pock-video  { background: #6d28d9; color: #fff; border-color: #5b21b6; }
.chip-pock-audio  { background: #0e7490; color: #fff; border-color: #155e75; }
.chip-pock-prompt { background: #4338ca; color: #fff; border-color: #3730a3; }
.chip-pock-file   { background: #4b5563; color: #fff; border-color: #374151; }
.chip-pock-other  { background: #6b7280; color: #fff; border-color: #4b5563; }

/* Confidence chips — secondary to the kind chip; same dark-theme
   palette. */
.chip-conf-confirmed   { background: #047857; color: #fff; border-color: #065f46; }
.chip-conf-refuted     { background: #b91c1c; color: #fff; border-color: #991b1b; }
.chip-conf-unconfirmed { background: #4b5563; color: #fff; border-color: #374151; }

/* v0.256.1 — project taxonomy chip palette. Two design rules per
   the round-2 ux review:
   (1) Decision-choice chips carry loud semantic color (operators
       read green = approve, red = reject, etc. by reflex).
   (2) Artifact-kind chips stay neutral background with a thin
       colored left border — they're filter affordances, not state
       indicators; loud color here drowns out the decision-choice
       signal.
   Slugs match ProjectVocabulary.cssSlug (snake_case → kebab). */
.chip-project-decision-approve              { background: #047857; color: #fff; border-color: #065f46; }
.chip-project-decision-approve-with-notes   { background: #b45309; color: #fff; border-color: #92400e; }
.chip-project-decision-revise               { background: #1d4ed8; color: #fff; border-color: #1e40af; }
.chip-project-decision-reject               { background: #b91c1c; color: #fff; border-color: #991b1b; }
.chip-project-decision-abort                { background: #4b5563; color: #fff; border-color: #374151; }

/* Artifact-kind chips — monochrome chip with a per-kind left-border
   accent. The decision palette stays louder by contrast. */
.chip-project-artifact-brand-bundle  { border-left: 4px solid #be185d; }
.chip-project-artifact-code-repo     { border-left: 4px solid #4338ca; }
.chip-project-artifact-landing-url   { border-left: 4px solid #047857; }
.chip-project-artifact-demo-video    { border-left: 4px solid #6d28d9; }
.chip-project-artifact-doc           { border-left: 4px solid #4b5563; }
.chip-project-artifact-image-set     { border-left: 4px solid #b45309; }
.chip-project-artifact-audio-set     { border-left: 4px solid #0e7490; }
.chip-project-artifact-dataset       { border-left: 4px solid #1d4ed8; }

/* Workstream chips — muted neutral; the per-workstream-id hue lives
   on a generated `data-workstream-id` later if needed. Today: one
   shared neutral background. */
.chip-project-workstream { background: #374151; color: #d1d5db; border-color: #1f2937; }

/* v0.260 — cross-surface project chip on workspace + run views.
   Neutral chip with the project-icon affordance baked into the
   link text. Hover slightly lighter; the embedded <a> takes the
   actual click target. */
.chip-project-link { background: #1f2937; color: #d1d5db; border-color: #374151; padding: 0; }
.chip-project-link a {
    color: inherit;
    text-decoration: none;
    padding: 0.2em 0.6em;
    display: inline-block;
}
.chip-project-link a:hover { background: #374151; color: #fff; }

/* Clear-filters pseudo-chip — small, ghost-style, inline with chips. */
.chip-project-clear {
    background: transparent;
    color: #9ca3af;
    border-color: #4b5563;
    border-style: dashed;
    margin-left: auto;
}

/* Filter card layout — chip rows stack vertically; each row gets a
   muted axis label. The `<details>` summary holds the title +
   clear-all chip. */
.chip-filter-card summary { cursor: pointer; display: flex; align-items: center; gap: 0.5em; }
.chip-filter-card .chip-row {
    display: flex; flex-wrap: wrap; gap: 0.4em; align-items: center;
    margin-top: 0.6em;
}
.chip-filter-card .chip-row-label { min-width: 6.5em; }
.chip-filter-card .chip[aria-pressed="true"] {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
}

/* Brief-extra disclosure — operator-visible drift surface. Muted
   styling so it reads "side note", not "primary content". */
.brief-extra {
    margin-top: 0.8em;
    padding: 0.4em 0.6em;
    border: 1px dashed #4b5563;
    border-radius: 4px;
}
.brief-extra summary { cursor: pointer; color: #9ca3af; }
.brief-extra .brief-list { margin-top: 0.6em; }

/* "Most active agent" stat callout next to the participants table. */
.most-active-callout {
    display: inline-block;
    margin-left: 0.8rem;
    font-size: 0.85rem;
    color: var(--muted, #6b7280);
}
.most-active-callout strong {
    color: var(--fg);
}

/* v0.235 (PR C) — workspace final-report markdown rendering on
   the detail page. Inherits site typography; tightens spacing so
   the report reads as one card section rather than a stand-alone
   document. */
.markdown-body h1 { margin: 0.8em 0 0.4em; font-size: 1.3em; }
.markdown-body h2 { margin: 0.7em 0 0.3em; font-size: 1.15em; }
.markdown-body h3 { margin: 0.6em 0 0.3em; font-size: 1.05em; }
.markdown-body ul, .markdown-body ol { margin: 0.3em 0 0.6em 1.4em; }
.markdown-body li { margin: 0.15em 0; }
.markdown-body p  { margin: 0.4em 0; }
.markdown-body code {
    background: var(--code-bg, #f3f4f6);
    padding: 0.1em 0.3em;
    border-radius: 3px;
    font-size: 0.9em;
}

/* v0.240 (PR E1) — aggregated POC + findings lists on the
   workspace detail page. Lists are flat; each row stacks the
   kind chip + attribution + body inline so a glance scan reads
   "what kind, who, what." Tags + evidence collapse into details. */
.aggregated-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.aggregated-list li {
    padding: 0.6rem 0.4rem;
    border-bottom: 1px solid var(--border, #e5e7eb);
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: baseline;
}
.aggregated-list li:last-child { border-bottom: none; }
.aggregated-list .attribution a { text-decoration: none; }
.aggregated-list .attribution a:hover { text-decoration: underline; }
.aggregated-list .path {
    font-family: monospace;
    font-size: 0.9em;
    color: var(--muted, #6b7280);
}
.aggregated-list .summary { flex: 1 1 auto; }
.aggregated-list .text { flex: 1 1 100%; }
.aggregated-list details {
    flex: 1 1 100%;
    margin-top: 0.3rem;
}
.aggregated-list details pre {
    background: var(--code-bg, #f3f4f6);
    padding: 0.4em 0.6em;
    border-radius: 3px;
    font-size: 0.85em;
    white-space: pre-wrap;
    margin: 0.3em 0;
}
.aggregated-list .tags { white-space: nowrap; }

/* ---------------------------------------------------------------
   v0.266.0 (Demo polish PR B) — agency front desk
   --------------------------------------------------------------- */

.agency-snapshot {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.agency-snapshot .snapshot-card {
    flex: 1;
    min-width: 160px;
    padding: 0.8rem;
    background: var(--surface-2);
    border-radius: 6px;
}
.agency-snapshot .snapshot-card-value {
    font-size: 1.6rem;
    font-weight: 600;
}
@media (max-width: 720px) {
    /* Drop snapshot cards to full width on narrow screens so the
       3-up layout doesn't squeeze the value text. */
    .agency-snapshot .snapshot-card {
        flex: 1 1 100%;
    }
}

.agency-cta-card {
    text-align: center;
    padding: 1.4rem 1rem;
}
.agency-cta-card .btn.primary.cta-hero {
    font-size: 1.15rem;
    padding: 0.7rem 1.4rem;
}
.agency-cta-card .agency-cta-sub {
    display: block;
    margin-top: 0.45rem;
    color: var(--muted);
    font-size: 0.85rem;
}

.conversation-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.conversation-list li {
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--surface-2);
}
.conversation-list li:last-child {
    border-bottom: none;
}
.conversation-list li > .muted.small {
    margin-left: 0.6rem;
}

/* ---------------------------------------------------------------
   v0.266.0 (Demo polish PR B) — workspace participant strip
   --------------------------------------------------------------- */

.participant-strip {
    list-style: none;
    padding: 0;
    margin: 0 0 1rem 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}
.participant-strip .participant-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    min-width: 90px;
    padding: 0.5rem 0.7rem;
    background: var(--surface-2);
    border-radius: 8px;
    text-align: center;
}
.participant-strip .participant-avatar-link {
    line-height: 0;
}
.participant-strip .participant-avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-3, #20242c);
}
.participant-strip .participant-name {
    color: var(--text-soft);
    font-size: 0.875rem;
    text-decoration: none;
    max-width: 12ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.participant-strip .participant-name:hover {
    text-decoration: underline;
}
.participants-detail-toggle {
    margin-top: 0.5rem;
}
.participants-detail-toggle summary {
    cursor: pointer;
    margin-bottom: 0.5rem;
}

.run-summary-goal {
    margin-bottom: 0.6rem;
}
.run-summary-goal pre.system-prompt {
    margin-top: 0.2rem;
}
.run-summary-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: baseline;
    margin: 0.4rem 0 0 0;
}
.run-summary-meta strong {
    color: var(--text-soft);
}
.run-summary-detail {
    margin-top: 0.6rem;
}
.run-summary-detail summary {
    cursor: pointer;
}

/* ---------------------------------------------------------------
   v0.267.0 (Demo polish PR C) — artifact gallery + detail page
   --------------------------------------------------------------- */

.artifact-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1rem;
}
/* v0.347.0 (UX-review) — explicit single-column collapse on phones.
   The auto-fill min(220px, 1fr) already wraps at narrow viewports, but
   pinning it to one column at the same breakpoint as .kanban-row
   keeps the projects/workspaces surface visually consistent across
   the responsive boundary. */
@media (max-width: 720px) {
    .artifact-gallery { grid-template-columns: 1fr; }
}
.artifact-card {
    background: var(--surface-1, var(--surface-2));
    border: 1px solid var(--surface-2);
    border-radius: 10px;
    overflow: hidden;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.artifact-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
}
.artifact-card.artifact-card-final {
    border-color: var(--accent, #6b7cff);
}
.artifact-card-link {
    display: block;
    color: inherit;
    text-decoration: none;
}
.artifact-card-preview {
    background: var(--surface-2);
    aspect-ratio: 16 / 9;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.artifact-card-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.artifact-card-kind-icon {
    font-size: 3rem;
    opacity: 0.5;
}
.artifact-card-body {
    padding: 0.75rem 0.9rem;
}
.artifact-card-title {
    font-size: 1rem;
    margin: 0 0 0.4rem 0;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.artifact-card-version {
    font-weight: normal;
}
.artifact-card-final-chip {
    background: var(--accent, #6b7cff);
    color: white;
    font-size: 0.7rem;
    padding: 0.1rem 0.45rem;
    border-radius: 4px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
/* v0.381.0 (PR 4) — immutability lock chip + comments panel. Slate-
   amber lock chip reads as "this is the deliverable" without
   competing with status / approval chips for color attention. */
.chip.artifact-locked-chip {
    background: rgba(180, 140, 30, 0.20);
    color: #facc15;
    border-color: rgba(180, 140, 30, 0.55);
    font-weight: 600;
}
/* v0.384.0 (arc-A-wrap UX) — flex-wrap on the inline-form + 100%
   max-width on the input so the form collapses to a two-line stack
   below ~720px instead of overflowing past the right edge. */
.artifact-lock-card .inline-form { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-top: 0.4rem; }
.artifact-lock-card .inline-input { padding: 0.2rem 0.4rem; max-width: 100%; flex: 1 1 18rem; }
.btn.small.danger { background: rgba(239, 91, 91, 0.20); color: #f87171; border-color: rgba(239, 91, 91, 0.55); }

.artifact-comments-card .artifact-comment-list { list-style: none; padding: 0; margin: 0 0 1rem 0; }
.artifact-comment { padding: 0.6rem 0; border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08)); }
.artifact-comment:last-child { border-bottom: none; }
.artifact-comment header { margin-bottom: 0.2rem; }
.artifact-comment-body { white-space: pre-wrap; }
.artifact-comment-form { display: flex; flex-direction: column; gap: 0.4rem; }
.artifact-comment-form .block-input { width: 100%; padding: 0.4rem; resize: vertical; }
.artifact-comment-form .btn { align-self: flex-start; }
.artifact-card-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0.3rem 0;
}
.artifact-card-meta {
    margin: 0.4rem 0 0 0;
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    align-items: baseline;
}

.artifact-detail-preview {
    padding: 0;
    overflow: hidden;
}
.artifact-detail-image {
    width: 100%;
    height: auto;
    display: block;
}
.artifact-detail-markdown {
    padding: 1rem 1.25rem;
    line-height: 1.55;
}
.artifact-detail-markdown h1,
.artifact-detail-markdown h2,
.artifact-detail-markdown h3 {
    margin-top: 1rem;
}
.artifact-detail-markdown pre {
    background: var(--surface-2);
    padding: 0.6rem 0.8rem;
    border-radius: 4px;
    overflow-x: auto;
}
.artifact-detail-markdown code {
    background: var(--surface-2);
    padding: 0.1rem 0.3rem;
    border-radius: 3px;
}
.artifact-detail-source {
    background: var(--surface-2);
    padding: 1rem 1.25rem;
    margin: 0;
    overflow-x: auto;
    max-height: 70vh;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ---------------------------------------------------------------
   v0.351.0 (PR 3a) — milestone deadlines + drag-reorder
   --------------------------------------------------------------- */

/* Stale-milestones digest on projects/list. Card-shaped, leans
   warning-color on the heading to claim attention without being
   alarming (status chips already carry their own palette).
   v0.352.2 — per-project + per-milestone nested ul shape so a
   project with 3+ slips stays scannable. */
.stale-milestones-card h2 {
    color: var(--text-soft);
}
.stale-milestones-list {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.stale-milestones-list .stale-project-group {
    line-height: 1.5;
}
.stale-milestones-list .stale-project-name {
    font-weight: 600;
}
.stale-milestones-list .stale-project-milestones {
    list-style: none;
    padding: 0;
    margin: 0.25rem 0 0 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.875rem;
}

/* Drag handle on the milestones spoke. Static glyph for viewers
   (no AGENT_RUN); becomes interactive via JS for operators. The
   `grab` cursor is set by the JS on init so the static-only render
   doesn't promise affordance. */
.milestone-drag-handle {
    display: inline-block;
    color: var(--muted);
    margin-right: 0.5rem;
    user-select: none;
    line-height: 1;
}
.milestone-drag-handle:hover {
    color: var(--text-soft);
}
.milestone-row-dragging {
    opacity: 0.5;
    background: var(--surface-2);
}
/* v0.352.3 (PR 3a audit IMPORTANT) — keyboard-grab indicator.
   When the operator presses Space on a focused drag handle, the
   row enters "grabbed" mode (ArrowUp/Down to move). Visual feedback
   so keyboard users see what mouse users see during a drag. */
.milestone-row-grabbed {
    outline: 2px dashed var(--accent, #6b7cff);
    outline-offset: 2px;
    background: var(--surface-2);
}
.milestone-drag-handle:focus-visible {
    outline: 2px solid var(--accent, #6b7cff);
    outline-offset: 2px;
    border-radius: 2px;
}

/* v0.352.2 (PR 3a audit IMPORTANT) — past-deadline used to be a
   chip (.chip.milestone-deadline-stale) but the chip-on-chip-on-
   chip header crowded and the danger-red clashed with status-
   rejected red. Demoted to text-only on milestones spoke + in
   the projects-list digest. Amber, not red — overdue ≠ failed. */
.milestone-deadline-stale-text {
    color: #d97706;             /* amber-600 */
    font-weight: 500;
}
/* Kept (but unused) for backwards-compat with the v0.351.0 chip
   shape — pruned in v0.353.0. */
.chip.milestone-deadline-stale {
    color: #d97706;
    border-color: rgba(217, 119, 6, 0.4);
    background: rgba(217, 119, 6, 0.08);
}

/* v0.355.0 (research-capture PR F) — related-surfaces index card on
   /research#pipeline. The default <ul> bullets + browser indent
   made the eight-route list look like a sitemap dump. Flex column
   with line-height tuned to match the rest of the hub's lists. */
.related-surfaces-list {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.related-surfaces-list li {
    line-height: 1.5;
}
.related-surfaces-list a {
    font-weight: 500;
}
/* Backfill-progress card — same shape as other status cards but
   the sev-info class on the card itself gives the temporary-state
   urgency cue (it disappears entirely once PENDING=0). */
.backfill-progress {
    /* sev-info on the .card supplies the accent; no extra rules. */
}
/* Digest cross-link to /research — a slim card on /sites/{slug}/digest.
   Visual rhythm follows the other slim cards on that page. */
.research-crosslink {
    padding-block: 0.75rem;
}

/* v0.356.0 (research-capture PR G) — /sites/{slug}/research/aggregates
   inline-SVG charts. Three small charts off the nightly rollup table.
   No client-side chart library — same shape as constellation.html. */
.aggregates-chart {
    width: 100%;
    max-width: 720px;
    height: auto;
    margin-top: 0.5rem;
}
.aggregates-axis {
    stroke: rgba(255, 255, 255, 0.25);
    stroke-width: 1;
}
.aggregates-tick {
    font-size: 10px;
    fill: rgba(255, 255, 255, 0.55);
    font-family: ui-monospace, monospace;
}
.aggregates-bars rect {
    fill: #5b8def;
    opacity: 0.85;
}
.aggregates-dots circle {
    fill: #4ade80;
}
.aggregates-breakdown td {
    vertical-align: middle;
}
.aggregates-bar-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.aggregates-bar {
    height: 0.5rem;
    background: #5b8def;
    opacity: 0.85;
    border-radius: 1px;
    min-width: 1px;
    max-width: 70%;
}
.aggregates-card + .aggregates-card {
    margin-top: 1rem;
}

/* v0.383.0 — workspace-detail IA overhaul. Cluster of new
 * styles for the operator-facing surfaces (header chips,
 * operator-notes panel, on-the-clock strip, why-this-ended
 * card, disqualified banner) plus a few inline-style lifts
 * the UX reviewer flagged. */

.workspace-header-stats {
    margin-top: 0.25rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.btn-float-right {
    float: right;
}

.chat-waiting-indicator-hidden {
    display: none;
}

.workspace-summary-body {
    white-space: pre-wrap;
}

/* v0.383.0 audit UX-#6 — card border uses a neutral accent so it
 * doesn't compete with the per-note --accent unread state. Reads
 * as "this card is the operator-notes panel" rather than "every
 * note in here is unread." */
.workspace-notes-card {
    border-left: 3px solid var(--border, #2a2f3a);
}
.workspace-notes-card h2 {
    margin-top: 0;
}
.operator-notes-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.operator-note {
    padding: 0.5rem 0.75rem;
    border-left: 3px solid transparent;
    margin: 0.25rem 0;
}
.operator-note-unread {
    border-left-color: var(--accent, #f5b042);
    background: rgba(245, 176, 66, 0.05);
}
.operator-note-attribution {
    margin-bottom: 0.25rem;
}
.operator-note-text {
    margin: 0;
    white-space: pre-wrap;
}
.operator-note-broadcast {
    margin-left: 0.25rem;
}
.operator-notes-more {
    margin-top: 0.5rem;
}

.workspace-now-strip p {
    margin: 0;
}
.workspace-now-strip strong {
    color: var(--text, #f1f3f9);
}
.workspace-now-strip .link.small {
    margin-left: 0.5rem;
}

/* v0.439.0 — image thumbnail strip atop the workspace detail page.
 * Operator-requested: "files at the top of workspaces" so generated
 * artwork lands first, before the timeline / artifacts / files-card
 * sections below. Hidden when no images exist. */
.workspace-image-strip-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.workspace-image-strip-header h2 {
    margin: 0;
}
.workspace-image-strip-list {
    /* Horizontally-scrolling row. Each thumb is square and capped
     * so a 20-image strip doesn't overflow the page width. */
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.workspace-image-strip-item {
    flex: 0 0 auto;
}
.workspace-image-strip-thumb-link {
    display: block;
    line-height: 0;  /* prevents the link from adding stray descender space */
    border: 1px solid var(--surface-3, #2a2f3a);
    border-radius: 6px;
    overflow: hidden;
    transition: border-color 80ms ease;
}
.workspace-image-strip-thumb-link:hover {
    border-color: var(--accent, #5b9dff);
}
.workspace-image-strip-thumb-link:focus-visible {
    /* v0.439.0 audit-pass — keyboard-focus indicator must be a real
     * outline, not just a border-color swap. ux-reviewer flagged the
     * earlier `outline: none` as an a11y regression (low-vision /
     * keyboard users can't see focus). Matches the
     * .operator-notes-more > summary:focus-visible pattern. */
    outline: 2px solid var(--accent, #5b9dff);
    outline-offset: 2px;
    border-color: var(--accent, #5b9dff);
}

#workspace-files-card-anchor {
    /* v0.439.0 — "see all files →" link scrolls to this card. Without
     * scroll-margin-top the heading sits flush with the page chrome.
     * 1rem gives breathing room for the page header. */
    scroll-margin-top: 1rem;
}
.workspace-image-strip-thumb {
    /* Square crop. object-fit:cover gives a visually-consistent grid
     * even when source images have wildly different aspect ratios
     * (square portraits + landscape screenshots + tall posters all
     * collapse to a uniform tile). */
    display: block;
    width: 96px;
    height: 96px;
    object-fit: cover;
    background: var(--surface-2, #1a1e27);
}

.workspace-end-reason-card h2 {
    margin-top: 0;
}

/* v0.383.0 audit UX-#5 — the banner uses the danger token border
 * AND an un-muted heading so color isn't the only "this is a
 * warning" signal. role="status" on the <section> gives screen
 * readers the live-region semantics. */
.workspace-disqualified-banner {
    border-left: 3px solid var(--danger, #d04848);
}
.workspace-disqualified-heading {
    color: var(--danger, #d04848);
    font-size: 0.9rem;
}
.section-subheader {
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin: 0 0 0.25rem 0;
}
.disqualified-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

/* v0.383.0 audit UX-#4 — explicit focus-visible ring on the new
 * <details> summaries (operator-notes-more, coordinator-events-
 * details). Pre-fix the keyboard user had no visual signal that
 * focus landed there. Scoped tight so this doesn't accidentally
 * style every <summary> in the codebase. */
.operator-notes-more > summary:focus-visible,
.coordinator-events-details > summary:focus-visible {
    outline: 2px solid var(--accent, #f5b042);
    outline-offset: 2px;
    border-radius: 2px;
}

.reassign-project-dialog {
    max-width: 32rem;
    padding: 1rem;
}
.modal-close-form {
    float: right;
    margin: 0;
}
.dialog-title {
    margin-top: 0;
}

/* v0.383.0 — collapsed-events details. Same shape as
 * tool-summary-details but tuned for the events card. */
.coordinator-events-details > summary > h2 {
    display: inline;
    margin: 0;
}

/* v0.389.0 — "Files:" row above the raw event-payload <pre> on
 * TOOL_RESULT events that produced workspace-relative file paths.
 * Sits between the event-tags row and the <pre>; styled to read
 * as a compact label-plus-links row that doesn't compete with
 * the chip row above or the payload pre below.
 *
 * v0.402.1 (operator-reported display bug) — pin both this row
 * AND the payload <pre> to grid column 3 of the .event-row grid.
 * The grid is `80px 200px 1fr` (time + chips + payload). Pre-fix
 * a TOOL_RESULT with file paths had FOUR children (time, tags,
 * file-links, payload); grid auto-placement put file-links in
 * col 3 and bumped the payload to row 2 col 1 (the 80px time
 * column), wrapping the raw payload text vertically at ~1-2
 * characters per line. With both pinned to col 3, the file-links
 * row and the payload <pre> stack inside the right-side lane and
 * the time + chips columns stay empty on row 2. Mirrors the
 * shape the SSE-appended rows already expect. */
.event-file-links {
    grid-column: 3 / -1;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: baseline;
    margin: 0.25rem 0;
}
.event-file-links-label {
    margin-right: 0.25rem;
}
.event-file-link {
    /* .artifact-link already styles color + underline; this just
     * tightens the line-height inside the timeline row. */
    line-height: 1.4;
    word-break: break-all;
}

/* v0.402.0 (PR 6) — static deploy preview surface. */
.deploys-list .deploys { list-style: none; padding: 0; margin: 0; }
.deploy-row { padding: 0.6rem 0; border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08)); }
.deploy-row:last-child { border-bottom: none; }
.deploy-row header { display: flex; gap: 0.5rem; align-items: center; }
.deploy-row .row-actions { display: flex; gap: 0.4rem; margin-top: 0.4rem; align-items: center; flex-wrap: wrap; }
.deploy-row .inline-form { display: inline-flex; gap: 0.3rem; align-items: center; }

/* Deploy-status chip palette — mirrors the milestone-status / approval-gate
 * conventions: provisioning = neutral, running = green, expired = muted,
 * torn-down = strikethrough slate. */
.chip.deploy-status-provisioning { background: rgba(148, 163, 184, 0.20); color: #94a3b8; border-color: rgba(148, 163, 184, 0.55); }
.chip.deploy-status-running      { background: rgba(34, 197, 94, 0.20);  color: #4ade80; border-color: rgba(34, 197, 94, 0.55); }
.chip.deploy-status-expired      { background: rgba(180, 140, 30, 0.20); color: #facc15; border-color: rgba(180, 140, 30, 0.55); }
.chip.deploy-status-torn_down    { background: rgba(71, 85, 105, 0.35);  color: #cbd5e1; border-color: rgba(71, 85, 105, 0.55); text-decoration: line-through; }
.chip.warning                    { background: rgba(180, 140, 30, 0.20); color: #facc15; border-color: rgba(180, 140, 30, 0.55); }

/* Iframe container — fixed height so the surrounding chrome stays
 * predictable; width fills the card. The iframe inherits 100% of
 * the wrap so the preview never overruns. */
.deploy-frame-card { padding: 0; }
.deploy-frame-toolbar { padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08)); display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.deploy-frame-toolbar > span { flex: 1 1 auto; }
.deploy-copy-share { flex: 0 0 auto; }
.deploy-frame-wrap { display: block; width: 100%; height: 70vh; min-height: 360px; }
.deploy-frame { width: 100%; height: 100%; border: 0; background: #fff; }
.deploy-frame-provisioning { padding: 2rem; text-align: center; }
.deploy-expired-banner { background: rgba(180, 140, 30, 0.08); border-color: rgba(180, 140, 30, 0.4); }

/* Empty-state-block + .spinner pull from existing patterns; no new
 * classes for those. */

/* v0.404.0 — "Files created in this workspace" card. Three
 * grouped lists (workspace-shared / per-agent / site-shared)
 * inside one collapsed <details>. Each row is a clickable
 * artifact-link with muted metadata (tool / size / promoted). */
.workspace-files-card > details > summary > h2 {
    display: inline;
    margin: 0;
}
.workspace-files-list {
    list-style: none;
    padding: 0;
    margin: 0 0 0.5rem 0;
}
.workspace-file-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: baseline;
    padding: 0.25rem 0;
}
.workspace-file-row .artifact-link {
    word-break: break-all;
}
.workspace-file-meta {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    flex-wrap: wrap;
}
.workspace-file-promoted {
    background: rgba(91, 157, 255, 0.10);
    color: var(--accent, #5b9dff);
    border-color: rgba(91, 157, 255, 0.25);
}

/* v0.408.0 (Arc-1 2a) — extend-cap form on CAP_REACHED workspaces.
 * Inline form with two small number inputs + a primary button.
 * Sits next to Cancel in .header-actions on workspaces that hit
 * their cap and need a manual bump from the operator. */
.extend-cap-form {
    display: inline-flex;
    gap: 0.5rem;
    align-items: flex-end;
    flex-wrap: wrap;
}
.extend-cap-field {
    display: inline-flex;
    flex-direction: column;
    gap: 0.15rem;
}
.extend-cap-input {
    width: 7em;
    padding: 0.25rem 0.5rem;
}
/* v0.413.0 — flash error banner under the extend-cap form. Uses
 * the codebase's standard .field-error red but with a touch more
 * spacing so it doesn't crowd the form inputs above it. */
.extend-cap-error {
    margin: 0.4rem 0 0 0;
    flex-basis: 100%;
}

/* v0.410.0 (Arc-1 2b) — cap progress bars under the workspace
 * header chips. Thin filled bar so the "about to hit the wall"
 * signal is visible at a glance. Warn-color tier above 80%. */
.workspace-cap-progress {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.25rem 0;
}
.workspace-cap-progress-label {
    flex: 0 0 auto;
    min-width: 9em;
}
.workspace-cap-progress-bar {
    flex: 1 1 auto;
    height: 0.4rem;
    background: rgba(255, 255, 255, 0.06);
    border-radius: 2px;
    overflow: hidden;
    max-width: 14rem;
}
.workspace-cap-progress-fill {
    display: block;
    height: 100%;
    background: var(--accent, #5b9dff);
    transition: width 0.2s ease-out;
}
.workspace-cap-progress-warn {
    /* UX-reviewer NICE #5: amber for the caution tier rather than
     * danger-red — red would read as "error/blocked," but at 80%
     * the workspace is still running. Clamp-to-100% above means
     * red would never mean "blocked" anyway. */
    background: #f5b042;
}
/* v0.417.0 — UX-reviewer arc-end IMPORTANT #3: hue shift alone
 * doesn't reach colorblind operators. Append a ⚠ glyph to the
 * warn-tier label so the signal is also shape-coded. The label
 * already has a title= attribute set in the template explaining
 * the threshold. */
.workspace-cap-progress-warn-marker {
    margin-left: 0.3em;
    color: #f5b042;
}

/* v0.410.0 (Arc-1 2c + 2d) — per-turn delta popover + anchor
 * link in the Turns table. <details> collapses the breakdown so
 * the table reads compact; expanding reveals tokens-in/out + the
 * delta summary. The # anchor is a copy-link affordance for
 * sharing a specific turn into chat. */
.turn-row {
    scroll-margin-top: 4rem;  /* anchor scroll lands below the sticky header */
}
.turn-anchor {
    text-decoration: none;
    margin-left: 0.25rem;
    opacity: 0;
    transition: opacity 0.15s;
}
.turn-row:hover .turn-anchor,
.turn-row:target .turn-anchor,
.turn-row:focus-within .turn-anchor {
    opacity: 1;
}
/* UX-reviewer IMPORTANT #3: on touch devices (no hover), the
 * affordance is invisible. Half-opacity makes it visible to a
 * tap-driven operator without competing with the row content. */
@media (hover: none) {
    .turn-anchor { opacity: 0.5; }
}
.turn-row:target {
    background: rgba(91, 157, 255, 0.06);
}
.turn-delta-cell {
    max-width: 32em;
}
.turn-delta-details > summary {
    cursor: pointer;
    list-style: none;
}
.turn-delta-details > summary::-webkit-details-marker { display: none; }
/* UX-reviewer IMPORTANT #4 / NICE #7: replacement disclosure
 * caret so the operator sees the affordance (the native marker
 * was hidden above). Rotates on [open]. */
.turn-delta-details > summary::before {
    content: '▸';
    display: inline-block;
    margin-right: 0.25rem;
    transition: transform 0.15s;
    color: var(--muted, #94a3b8);
    font-size: 0.75rem;
}
.turn-delta-details[open] > summary::before {
    transform: rotate(90deg);
}
/* v0.417.0 — UX-reviewer arc-end IMPORTANT #6: native disclosure
 * marker hidden above; restore a focus ring so a Tab-driven
 * operator can see which summary is focused. */
.turn-delta-details > summary:focus-visible {
    outline: 2px solid var(--accent, #5b9dff);
    outline-offset: 2px;
    border-radius: 2px;
}
.turn-delta-summary {
    display: flex;
    align-items: baseline;
    gap: 0.25rem;
}
.turn-delta-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
    max-width: 100%;
}
.turn-delta-details[open] .turn-delta-text {
    white-space: normal;
    overflow: visible;
}
.turn-delta-breakdown {
    display: grid;
    grid-template-columns: auto auto;
    gap: 0.15rem 0.75rem;
    margin: 0.5rem 0 0 0;
}
.turn-delta-breakdown dt {
    font-weight: 600;
}
.turn-delta-breakdown dd {
    margin: 0;
}

/* v0.411.0 (Arc-1 1a + 1b + 1g) — run-detail polish:
 * inline image thumbnails on file links + copy-path button per
 * row + collapsible payload for TOOL_CALL / TOOL_RESULT. */
.event-file-slot {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}
.event-file-link-with-thumb {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.event-file-thumb {
    max-width: 8rem;
    max-height: 5rem;
    border-radius: 3px;
    border: 1px solid var(--border, #2a2f3a);
    background: var(--surface-2);
    object-fit: cover;
    vertical-align: middle;
}
.copy-path-btn {
    background: transparent;
    border: 1px solid var(--border, #2a2f3a);
    color: var(--muted, #94a3b8);
    cursor: pointer;
    padding: 0.1rem 0.35rem;
    border-radius: 3px;
    font-size: 0.85rem;
    line-height: 1.1;
    transition: background 0.15s, color 0.15s;
}
.copy-path-btn:hover {
    background: var(--surface-2);
    color: var(--text, #f1f3f9);
}
.copy-path-btn-acked {
    background: rgba(74, 222, 128, 0.18);
    color: #4ade80;
    border-color: rgba(74, 222, 128, 0.4);
}

.event-payload-details {
    /* v0.447.0 — pin to col 3 of .event-row's grid, matching the bare
     * <pre class="event-payload"> rule above. v0.411.0 wrapped the
     * TOOL_CALL/TOOL_RESULT <pre> in a <details>, making the <details>
     * the direct grid child — the grid-column rule on .event-payload no
     * longer applied, so the disclosure landed in the 80px time column
     * and the payload wrapped vertically at ~1 char per line. Pinning
     * the <details> to col 3 here restores the v0.402.1 layout. */
    grid-column: 3 / -1;
    margin: 0;
}
.event-payload-summary {
    cursor: pointer;
    user-select: none;
    list-style: none;
    padding: 0.15rem 0;
}
.event-payload-summary::-webkit-details-marker { display: none; }
.event-payload-summary::before {
    content: '▸ ';
    color: var(--muted, #94a3b8);
    font-size: 0.75rem;
    transition: transform 0.15s;
    display: inline-block;
}
.event-payload-details[open] > .event-payload-summary::before {
    transform: rotate(90deg);
}
/* v0.417.0 — focus-visible outline on the payload disclosure
 * summary (pair to .turn-delta-details). Same a11y rationale. */
.event-payload-summary:focus-visible {
    outline: 2px solid var(--accent, #5b9dff);
    outline-offset: 2px;
    border-radius: 2px;
}

/* v0.422.0 (Arc-2 2.5) — ledger-attribution dashboard summary
 * card. Four stats inline, each with a muted label + a big value.
 * Warn-tier (amber) when unattributed > 0; good-tier (green) at
 * 100% coverage so the arc-end milestone is visible. */
.ledger-attribution-summary {
    display: flex;
    gap: 1.5rem;
    flex-wrap: wrap;
    align-items: baseline;
    margin: 0.5rem 0;
}
.ledger-attribution-stat {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 7rem;
}
.ledger-attribution-value {
    font-size: 1.4rem;
    font-weight: 600;
    color: var(--text, #f1f3f9);
}
.ledger-attribution-warn {
    color: #f5b042;
}
.ledger-attribution-good {
    color: #4ade80;
}
.ledger-attribution-done {
    margin: 0.5rem 0 0 0;
    color: #4ade80;
}
/* v0.431.2 — secondary CHAT-only chat_kind coverage line. Sits
 * just below the primary stats row, muted styling so it doesn't
 * compete visually with the primary coverage. */
.ledger-attribution-secondary {
    margin: 0.4rem 0 0 0;
    display: flex;
    gap: 0.4rem;
    align-items: baseline;
    flex-wrap: wrap;
}
/* v0.422.2 — extracted from inline styles per ux-reviewer
 * IMPORTANT #2 + code-reviewer NICE #5. */
.ledger-attribution-version-chip {
    background: rgba(99, 102, 241, 0.15);
    color: #818cf8;
    border-color: rgba(99, 102, 241, 0.4);
    font-size: 0.65rem;
}
.ledger-attribution-card {
    margin-bottom: 1rem;
}
.ledger-attribution-days-input {
    width: 5em;
    margin-left: 0.4em;
}
.ledger-attribution-table {
    width: 100%;
    font-size: 0.9rem;
}
.ledger-attribution-num {
    text-align: right;
}

/* v0.434.0 (PR 7a-3) — code-workspaces operator playground.
   Three-pane desktop layout (file-tree / editor / output);
   collapses to a single column below 960px. */

/* List page */
.code-workspaces { list-style: none; padding: 0; margin: 0; }
.code-workspace-row {
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
}
.code-workspace-row:last-child { border-bottom: none; }
.code-workspace-row header { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.code-workspace-row header a { font-weight: 600; }

/* Status + language chips. */
.chip.code-workspace-status-active   { background: rgba(34, 197, 94, 0.18); color: #4ade80; border-color: rgba(34, 197, 94, 0.5); }
.chip.code-workspace-status-archived { background: rgba(71, 85, 105, 0.30); color: #cbd5e1; border-color: rgba(71, 85, 105, 0.5); text-decoration: line-through; }
/* v0.439.1 (arc-wrap audit UX IMPORTANT #1) — chip styling for the
   not-yet-entity-valued statuses the executor is starting to imply
   (paused: long-running cancel-pending; error: provisioner-failed).
   Lands the CSS now so the next minor that introduces these values
   doesn't render an unstyled tag. */
.chip.code-workspace-status-paused   { background: rgba(234, 179, 8, 0.18); color: #facc15; border-color: rgba(234, 179, 8, 0.5); }
.chip.code-workspace-status-error    { background: rgba(239, 68, 68, 0.18); color: #fca5a5; border-color: rgba(239, 68, 68, 0.5); }
.chip.code-language-python { background: rgba(59, 130, 246, 0.18); color: #93c5fd; border-color: rgba(59, 130, 246, 0.5); }
.chip.code-language-node   { background: rgba(132, 204, 22, 0.18); color: #bef264; border-color: rgba(132, 204, 22, 0.5); }
.chip.code-language-ruby   { background: rgba(239, 68, 68, 0.18); color: #fca5a5; border-color: rgba(239, 68, 68, 0.5); }

/* Playground three-pane grid. */
.code-playground { padding: 0; }
.code-playground-grid {
    display: grid;
    grid-template-columns: 25% 50% 25%;
    min-height: 70vh;
    border-top: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
}
.code-pane {
    padding: 0.8rem;
    border-right: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 0;
}
.code-pane:last-child { border-right: none; }
.code-pane h3 { margin: 0 0 0.4rem 0; font-size: 0.95rem; }

.code-file-tree { list-style: none; padding: 0; margin: 0; flex: 1; overflow-y: auto; }
.code-file-row {
    display: flex; justify-content: space-between; align-items: center;
    padding: 0.25rem 0.4rem; gap: 0.5rem;
}
.code-file-row:hover { background: rgba(255,255,255,0.04); border-radius: 4px; }
.code-file-link {
    background: none; border: none; padding: 0; cursor: pointer;
    color: inherit; font-family: ui-monospace, "JetBrains Mono", "Cascadia Code", monospace;
    font-size: 0.85rem; text-align: left; flex: 1;
}
.code-file-link:hover { text-decoration: underline; }
.code-pane-actions { display: flex; gap: 0.4rem; margin-top: 0.4rem; }

.code-pane-editor { flex: 1; }
.code-editor-header {
    display: flex; gap: 0.4rem; align-items: center;
    padding-bottom: 0.3rem;
    border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
}
.code-current-file {
    font-family: ui-monospace, "JetBrains Mono", monospace;
    font-size: 0.9rem; font-weight: 600;
}
.code-dirty-indicator { color: #facc15; font-size: 1.4rem; line-height: 0.5; }
.code-editor {
    flex: 1; width: 100%; min-height: 50vh;
    font-family: ui-monospace, "JetBrains Mono", "Cascadia Code", monospace;
    font-size: 0.85rem; line-height: 1.4; padding: 0.6rem;
    background: rgba(0,0,0,0.25); color: #e5e7eb;
    border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    border-radius: 4px; resize: vertical; tab-size: 2;
}
.code-editor:read-only { background: rgba(0,0,0,0.15); color: #9ca3af; cursor: not-allowed; }
.code-editor-actions { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }
.code-run-status { font-style: italic; }

.code-output-header { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }
.code-output-pane {
    flex: 1; min-height: 200px;
    font-family: ui-monospace, monospace; font-size: 0.8rem;
    padding: 0.5rem;
    background: rgba(0,0,0,0.25);
    border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    border-radius: 4px; overflow-y: auto;
    white-space: pre-wrap; word-break: break-word;
    margin: 0.3rem 0 0 0;
}
.code-output-stderr { color: #fca5a5; }

.chip.code-exit-chip { font-family: ui-monospace, monospace; font-size: 0.75rem; }
.chip.code-run-success { background: rgba(34, 197, 94, 0.18); color: #4ade80; border-color: rgba(34, 197, 94, 0.5); }
.chip.code-run-failure { background: rgba(239, 68, 68, 0.18); color: #fca5a5; border-color: rgba(239, 68, 68, 0.5); }
.chip.code-duration-chip { font-family: ui-monospace, monospace; font-size: 0.75rem; }

.code-runs-list { list-style: none; padding: 0; margin: 0.4rem 0 0 0; }
.code-run-row {
    display: flex; gap: 0.5rem; align-items: center;
    padding: 0.3rem 0;
    border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
}
.code-run-row:last-child { border-bottom: none; }
.code-run-row > span:first-child {
    font-family: ui-monospace, monospace; flex: 1; font-size: 0.85rem;
}

/* Narrow viewport: collapse three-pane to a single column. */
@media (max-width: 959px) {
    .code-playground-grid { grid-template-columns: 1fr; }
    .code-pane {
        border-right: none;
        border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    }
    .code-pane:last-child { border-bottom: none; }
    .code-editor { min-height: 30vh; }
    .code-output-pane { min-height: 120px; }
}

/* v0.438.0 (PR 7a-4) — folds from 7a-3 audit BLOCKER/IMPORTANT:
   preset empty-state, inline new-file form (drop prompt()),
   editor/output header chips, install-deps suggestion card. */
.code-empty-state {
    padding: 0.6rem 0.2rem;
    color: var(--text-muted, #94a3b8);
}
.code-empty-state p { margin: 0 0 0.5rem 0; }
.code-preset-list {
    list-style: none; padding: 0; margin: 0.4rem 0 0 0;
    display: flex; flex-direction: column; gap: 0.4rem;
}
.code-preset-row { display: flex; flex-direction: column; gap: 0.15rem; }
.code-preset-btn {
    text-align: left;
    background: rgba(255,255,255,0.04);
    border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    color: var(--text-primary, #e2e8f0);
    padding: 0.35rem 0.5rem;
    border-radius: 4px;
    font-family: ui-monospace, monospace; font-size: 0.85rem;
    cursor: pointer;
}
.code-preset-btn:hover { background: rgba(255,255,255,0.08); }
.code-preset-desc { font-size: 0.75rem; color: var(--text-muted, #94a3b8); padding: 0 0.1rem; }

/* v0.438.1 (audit UX IMPORTANT #5) — `.is-shown` toggle pattern;
   form + preset-error landing zone hidden by default. */
.code-new-file-form {
    margin: 0.5rem 0 0 0;
    padding: 0.5rem;
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    border-radius: 4px;
    display: none;
    flex-direction: column; gap: 0.35rem;
}
.code-new-file-form.is-shown { display: flex; }
.code-new-file-form-actions { display: flex; gap: 0.35rem; align-items: center; }
.code-new-file-form .field-error.small { display: none; }
.code-new-file-form .field-error.small.is-shown { display: block; }
.is-hidden { display: none !important; }
.code-preset-error { display: none; margin: 0.3rem 0 0 0; }
.code-preset-error.is-shown { display: block; }
.code-sse-paused-note { display: none; margin: 0.2rem 0 0 0; }
.code-sse-paused-note.is-shown { display: block; }
.code-install-cmd-row {
    display: flex; gap: 0.4rem; align-items: flex-start;
    margin: 0.25rem 0 0 0;
}
.code-install-cmd-row .code-install-cmd { flex: 1; margin: 0; }
.code-new-file-form input[type="text"] {
    width: 100%;
    background: rgba(0,0,0,0.25);
    color: var(--text-primary, #e2e8f0);
    border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
    border-radius: 3px;
    padding: 0.3rem 0.4rem;
    font-family: ui-monospace, monospace; font-size: 0.85rem;
}
.code-new-file-form-actions { display: flex; gap: 0.35rem; align-items: center; }
.field-error.small { color: #fca5a5; font-size: 0.75rem; margin: 0; }

.chip.code-file-lang-chip,
.chip.code-draft-chip,
.chip.code-queue-chip,
.chip.code-bytes-chip,
.chip.code-sse-paused-chip {
    font-size: 0.72rem;
}
/* v0.439.1 (arc-wrap audit UX IMPORTANT #3 + CODE NICE #9) —
   complete the `.is-shown` migration the 7a-4 patch started.
   All exec-related chips default hidden; JS toggles via
   classList.toggle('is-shown', on). */
.chip.code-draft-chip,
.chip.code-sse-paused-chip,
.chip.code-file-lang-chip,
.chip.code-queue-chip,
.chip.code-bytes-chip,
.chip.code-exit-chip,
.chip.code-duration-chip { display: none; }
.chip.code-draft-chip.is-shown,
.chip.code-sse-paused-chip.is-shown,
.chip.code-file-lang-chip.is-shown,
.chip.code-queue-chip.is-shown,
.chip.code-bytes-chip.is-shown,
.chip.code-exit-chip.is-shown,
.chip.code-duration-chip.is-shown { display: inline-flex; }
.chip.code-queue-chip { background: rgba(148, 163, 184, 0.18); color: #cbd5e1; border-color: rgba(148, 163, 184, 0.4); }
.chip.code-bytes-chip { font-family: ui-monospace, monospace; }
/* dirty-indicator + cancel-btn use the same convention. */
.code-dirty-indicator { display: none; }
.code-dirty-indicator.is-shown { display: inline; }
#code-cancel-btn { display: none; }
#code-cancel-btn.is-shown { display: inline-flex; }

.code-install-deps {
    margin: 0.6rem 0 0 0;
    padding: 0.5rem 0.6rem;
    background: rgba(59, 130, 246, 0.06);
    border: 1px solid rgba(59, 130, 246, 0.25);
    border-radius: 4px;
}
.code-install-deps p { margin: 0 0 0.3rem 0; font-size: 0.85rem; }
.code-install-cmd {
    margin: 0.25rem 0 0 0;
    padding: 0.3rem 0.45rem;
    background: rgba(0,0,0,0.35);
    border-radius: 3px;
    font-family: ui-monospace, monospace; font-size: 0.78rem;
    color: var(--text-primary, #e2e8f0);
    overflow-x: auto;
}

/* v0.467.0 — per-message tool-call playback inside a chat bubble.
   The .tool-name-chip + .event-payload classes are shared with the
   runs page (above); these chat-only rules just lay out the
   collapsed summary + per-call list neatly under the bubble body.
   Default-closed so a long thread stays scannable; operator clicks
   to expand and inspect the call. */
.chat-bubble-body + .tool-calls,
.chat-attachments + .tool-calls {
    margin-top: 0.5rem;
}
/* v0.496.0 UX-pass — failure visual hierarchy. When any tool call
   in the message errored, the whole tool-calls block gets a red
   left border so the operator's eye lands on it without expanding.
   The summary's aria-label already announces the failure count;
   this is the sighted equivalent. */
.tool-calls.has-failures {
    border-left: 3px solid var(--danger, #d94a4a);
    padding-left: 0.5rem;
}
/* v0.496.0 UX-pass — copy buttons next to the args/result summaries.
   Inline at the right edge of the disclosure summary; small + muted
   until hover so they don't pull attention from the payload itself. */
.event-payload-summary {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.tool-call-copy {
    opacity: 0.6;
    transition: opacity 120ms;
}
.tool-call-copy:hover,
.tool-call-copy:focus-visible {
    opacity: 1;
}
.tool-call-copy.copied {
    color: var(--success, #2d8c4a);
}
.tool-calls-summary,
.chat-tools-grants-summary {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    cursor: pointer;
}
/* v0.485.1 ux-reviewer #3 — verify the native disclosure marker
   is visible. Some CSS resets (and Safari's iOS quirks) suppress
   the <summary>'s default triangle; force-render it so sighted
   operators get parity with the aria-label SR users receive.
   The count chip's font-weight differentiator (ux #2) makes the
   "N extra grants" land first; chips inside read as decorative. */
.tool-calls-summary,
.chat-tools-grants-summary {
    list-style: revert;
}
.tool-calls-summary::-webkit-details-marker,
.chat-tools-grants-summary::-webkit-details-marker {
    display: revert;
}
.chat-tools-grants-summary > .chip:first-child {
    font-weight: 600;
}
/* v0.484.0 — chip strip's expanded body sits just below the summary.
   Inherits .chat-tools-grants typography so the <code> chips render
   the same as the pre-disclosure layout. */
.chat-tools-grants-disclosure .chat-tools-grants {
    margin: 0.4rem 0 0 0;
}
.tool-calls-list {
    list-style: none;
    padding: 0.4rem 0 0 0;
    margin: 0.25rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.tool-calls-item {
    border-left: 2px solid rgba(167, 139, 250, 0.35);
    padding: 0.25rem 0 0.25rem 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.tool-calls-item-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}
/* Inside chat bubbles the runs grid doesn't apply; reset the
   grid-column rule so the <pre> fills the column normally. */
.tool-calls-item .event-payload {
    grid-column: auto;
    max-height: 16rem;
    margin: 0.15rem 0 0 0;
}
.tool-calls-item .event-payload-error {
    color: var(--danger, #ef5b5b);
    background: rgba(239, 91, 91, 0.08);
}
.chip.status-error {
    background: rgba(239, 91, 91, 0.18);
    color: var(--danger, #ef5b5b);
    border-color: rgba(239, 91, 91, 0.4);
    font-size: 0.7rem;
}

/* v0.467.0 — chat-detail-only tools-use gate widget. Lives between
   the page header and the thread; flipping it POSTs JSON to the
   tools-enabled endpoint and reconciles state from the response. */
.chat-tools-gate {
    padding: 0.6rem 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.chat-tools-gate[aria-busy="true"] { opacity: 0.7; }
.chat-tools-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.chat-tools-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
}
.chat-tools-label-text { font-weight: 600; }
.chat-tools-switch { width: 1.1rem; height: 1.1rem; }
.chat-tools-switch:focus-visible {
    outline: 2px solid var(--accent, #5b9dff);
    outline-offset: 2px;
}
.chat-tools-state-chip.on {
    background: rgba(16, 185, 129, 0.18);
    color: #10b981;
    border-color: rgba(16, 185, 129, 0.4);
}
.chat-tools-state-chip.off {
    background: var(--surface-2);
    color: var(--muted);
    border-color: var(--border);
}
.chat-tools-error {
    color: var(--danger, #ef5b5b);
}
.chat-tools-grants code {
    background: var(--surface-2);
    padding: 0.05rem 0.3rem;
    border-radius: 3px;
    font-size: 0.78rem;
}

/* === v0.471.0 — Operator messages panel ============================== */
.workspace-operator-messages > summary {
    cursor: pointer;
    list-style: none;
}
.workspace-operator-messages > summary::-webkit-details-marker { display: none; }
.workspace-operator-messages > summary > strong { margin-right: 0.4rem; }
.workspace-operator-messages > summary::before {
    content: "▸ ";
    color: var(--muted);
    font-size: 0.85rem;
}
.workspace-operator-messages[open] > summary::before { content: "▾ "; }

.workspace-operator-messages-hint {
    margin: 0.25rem 0 0.6rem;
}

.workspace-operator-messages-list {
    list-style: none;
    padding: 0;
    margin: 0 0 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.workspace-operator-message {
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.5rem 0.6rem;
    background: var(--surface-2);
}
.workspace-operator-message-body {
    margin: 0 0 0.3rem;
    white-space: pre-wrap;
    word-wrap: break-word;
}
.workspace-operator-message-actions {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.workspace-operator-message-edit-form {
    margin-top: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.workspace-operator-message-edit-form textarea {
    width: 100%;
    min-height: 4rem;
    font-family: inherit;
}

.workspace-operator-messages-empty {
    margin: 0.5rem 0;
    font-style: italic;
}

.workspace-operator-messages-compose {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin-top: 0.5rem;
}
.workspace-operator-messages-compose textarea {
    width: 100%;
    min-height: 3rem;
    max-height: 18rem;
    resize: vertical;
    font-family: inherit;
}
.workspace-operator-messages-actions {
    align-items: center;
    flex-wrap: wrap;
    position: relative;
}
.workspace-operator-messages-counter { margin-left: auto; }
.workspace-operator-messages-counter.warn { color: #d97706; }
.workspace-operator-messages-counter.error { color: var(--danger, #ef5b5b); font-weight: 600; }

.workspace-operator-messages-mention-popover {
    position: absolute;
    bottom: 100%;
    left: 0;
    margin-bottom: 0.25rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    max-height: 18rem;
    overflow-y: auto;
    max-width: min(280px, 100%);
    z-index: 10;
}
.workspace-operator-messages-mention-popover ul.mention-options {
    list-style: none;
    padding: 0.25rem 0;
    margin: 0;
}
.workspace-operator-messages-mention-popover .mention-option {
    background: none;
    border: none;
    padding: 0.4rem 0.7rem;
    width: 100%;
    text-align: left;
    cursor: pointer;
    color: inherit;
}
.workspace-operator-messages-mention-popover .mention-option:hover,
.workspace-operator-messages-mention-popover .mention-option.active {
    background: var(--surface-2);
}

/* Absorb-race sticky banner — appears when an SSE absorbed event
   lands on a message whose edit form is mounted in this tab. The
   operator's unsaved edit would silently die otherwise; banner
   gives them [Keep editing as new draft] [Discard]. */
.workspace-operator-message-absorbed-banner {
    background: rgba(217, 119, 6, 0.12);
    border: 1px solid rgba(217, 119, 6, 0.4);
    color: #d97706;
    padding: 0.5rem 0.7rem;
    border-radius: 4px;
    margin-bottom: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    position: sticky;
    top: 0.5rem;
    z-index: 5;
}
.workspace-operator-message-absorbed-banner p { margin: 0; }
.workspace-operator-message-absorbed-banner .form-actions { gap: 0.4rem; }

/* Abandoned-on-terminal toast — surfaces when workspace ends with
   pending ACTIVE messages. Operator can copy text out before the
   panel disappears. */
.workspace-operator-messages-abandoned-toast {
    background: rgba(75, 85, 99, 0.12);
    border: 1px solid rgba(75, 85, 99, 0.4);
    padding: 0.5rem 0.7rem;
    border-radius: 4px;
    margin: 0.5rem 0;
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
}

/* ====================================================================
   v0.493.0 (PR2 — kanban polish + filter bar + sortable + shortcuts)
   ==================================================================== */

/* Filter bar — single flex row, wraps on narrow viewports. */
.issue-filter-bar {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
    margin: 1rem 0 0.5rem 0;
    padding: 0.5rem 0.75rem;
    background: var(--surface-1, var(--surface-2));
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}
.issue-filter-bar input[type="search"] { min-width: 14rem; }
.issue-filter-bar select { min-width: 8rem; }
.issue-filter-toggle { display: inline-flex; align-items: center; gap: 0.25rem; }
.issue-filter-summary { margin: 0.25rem 0 0.75rem 0; }
.issue-filter-empty { margin: 0.75rem 0; padding: 0.75rem; background: var(--surface-2); border-radius: 0.4rem; }

/* Sticky kanban column headers — when a column scrolls past viewport
   height (40-card project), the "OPEN" label stays pinned so the
   operator never loses orientation. */
.kanban-col h2 {
    position: sticky;
    top: 0;
    z-index: 2;
    /* v0.493.1 (PR2 audit ux #5) — inherit avoids the dark-mode
       shade mismatch when the column body is surface-2 + the
       sticky header was surface. */
    background: inherit;
    margin: 0;
    padding: 0.25rem 0;
}
.issue-card-copylink:focus-visible {
    outline: 2px solid var(--accent, var(--border-strong, currentColor));
    outline-offset: 1px;
    opacity: 1;
}

/* Per-column accent glow — replaces a future identical .move-glow-*
   triple. Subtle left-edge stripe whose hue tracks the column status.
   Honours prefers-reduced-motion (no animation; static stripe). */
.kanban-col {
    position: relative;
    padding-left: 0.6rem;
}
.kanban-col[data-column-status="OPEN"]        { border-left: 3px solid rgba(148, 163, 184, 0.4); }
.kanban-col[data-column-status="IN_PROGRESS"] { border-left: 3px solid rgba(99, 102, 241, 0.5); }
.kanban-col[data-column-status="DONE"]        { border-left: 3px solid rgba(74, 222, 128, 0.5); }

/* Milestone-grouped view (groupBy=milestone) — vertical list of
   sections, drag disabled. */
.milestone-group-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin: 1rem 0;
}
.milestone-group-section h2 { margin-top: 0; }

/* Sortable tables — visual contract from the arc plan: trailing
   ↑/↓ on the sorted column; faint ▲▼ hover hint on unsorted; focus
   ring via existing .btn outline. */
.sortable th[aria-sort] {
    cursor: pointer;
    user-select: none;
    position: relative;
}
.sortable th[aria-sort]:hover,
.sortable th[aria-sort]:focus-visible { background: var(--surface-2); }
.sortable th[aria-sort="ascending"]::after  { content: " ↑"; opacity: 1; }
.sortable th[aria-sort="descending"]::after { content: " ↓"; opacity: 1; }
.sortable th[aria-sort="none"]::after       { content: " ↕"; opacity: 0.25; }
.sortable th[aria-sort="none"]:hover::after { opacity: 0.6; }

/* Card hover copy-link affordance — appears top-right on issue card
   hover so an operator can grab a deep-link without opening detail. */
.issue-card { position: relative; }
.issue-card-copylink {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    opacity: 0;
    transition: opacity 0.12s ease;
    font-size: 0.7rem;
    padding: 0.1rem 0.3rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 0.25rem;
    color: var(--muted);
    cursor: pointer;
}
.issue-card:hover .issue-card-copylink,
.issue-card:focus-within .issue-card-copylink { opacity: 1; }
.issue-card-copylink.just-copied {
    opacity: 1;
    background: rgba(74, 222, 128, 0.2);
    color: var(--success);
}
@media (prefers-reduced-motion: reduce) {
    .issue-card-copylink { transition: none; }
}

/* ====================================================================
   v0.495.0 (PR3a — issue board Identity)
   ==================================================================== */

/* Ticket badge prefix on cards + reporter byline. Reads as
   LONGS-42 in a faint monospace + a small type icon. */
.ticket-badge {
    font-family: monospace;
    margin-right: 0.35rem;
    color: var(--muted);
    letter-spacing: 0.04em;
}
.issue-type-icon { margin-right: 0.25rem; }
.reporter-byline { margin: 0 0 0.25rem 0; }
.reporter-byline strong { color: var(--accent, currentColor); }

/* Issue-ref links rendered by IssueReferenceLinker (PR3b wires
   into render pipeline). Faint accent so the link is discoverable
   but doesn't dominate the body. */
a.issue-ref {
    color: var(--accent, currentColor);
    text-decoration: underline dotted;
}
a.issue-ref:hover { text-decoration: underline solid; }

/* am2-typeahead shared component (assignee picker, mention, etc.) */
am2-typeahead { display: inline-block; position: relative; min-width: 18rem; }
.am2-typeahead-wrap { position: relative; }
.am2-typeahead-input { width: 100%; }
.am2-typeahead-list {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    list-style: none;
    margin: 0;
    padding: 0.25rem 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    max-height: 14rem;
    overflow-y: auto;
    z-index: 30;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
}
.am2-typeahead-option {
    padding: 0.35rem 0.6rem;
    cursor: pointer;
}
.am2-typeahead-option:hover,
.am2-typeahead-option.active {
    background: var(--surface-2);
}

/* ====================================================================
   v0.500.0 (PR3b — issue board Collaboration)
   ==================================================================== */

/* Reactions row under each comment. Chips for existing reactions +
   a "+ react" trigger; picker pops inline. Mobile-friendly target
   size (44px-ish via padding). */
.reactions-row {
    display: flex;
    gap: 0.4rem;
    align-items: center;
    flex-wrap: wrap;
    margin-top: 0.5rem;
}
.reaction-chip {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 1rem;
    padding: 0.2rem 0.6rem;
    font-size: 0.85rem;
    cursor: pointer;
}
.reaction-chip:hover { background: var(--surface); }
.reaction-chip.failed { background: rgba(239, 91, 91, 0.18); }
.reactions-add {
    color: var(--muted);
    cursor: pointer;
    padding: 0.4rem 0.6rem;
}
/* v0.500.1 (PR3b audit IMPORTANT ux #7) — coarse-pointer
   (touch) bumps the chip + add-button target to 44px. */
@media (pointer: coarse) {
    .reaction-chip, .reactions-add { min-height: 2.5rem; }
    .comment-row-actions .link-button { min-height: 2.5rem; padding: 0.4rem 0.5rem; }
}
.reactions-picker {
    display: flex;
    gap: 0.3rem;
    margin-left: 0.4rem;
    padding: 0.25rem 0.4rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 1rem;
}

/* Comment-row hover affordances + edit form. */
.comment-row { position: relative; padding: 0.5rem 0; }
/* v0.500.1 (PR3b audit IMPORTANT ux #5) — soft-deleted comments
   visually distinct from live ones: muted, italic, tinted left edge. */
.comment-body.deleted {
    opacity: 0.55;
    font-style: italic;
    border-left: 3px solid var(--border);
    padding-left: 0.6rem;
}
.comment-row-actions { opacity: 0.6; }
.comment-row-actions:hover { opacity: 1; }
.comment-row-actions .link-button {
    background: none;
    border: none;
    color: var(--danger, var(--muted));
    cursor: pointer;
    padding: 0;
    font-size: inherit;
}
.comment-edit-form {
    margin-top: 0.5rem;
    padding: 0.5rem;
    background: var(--surface-2);
    border-radius: 0.4rem;
}
.comment-edit-form textarea { width: 100%; }

/* Clone modal. Reuses the layout's modal vocabulary (.modal +
   .modal-card if present, otherwise minimal styling here). */
.modal {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 90;
}
.modal[hidden] { display: none; }
.modal-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 1.25rem 1.5rem;
    min-width: 22rem;
    max-width: 32rem;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

/* IssueReferenceLinker output (CSS already in PR3a; keep parity here
   in case the previous block is touched). */

/* ====================================================================
   v0.501.0 (PR4 — slide-over + bulk + mobile)
   ==================================================================== */

/* Slide-over right-edge panel. Click a card → opens here. Mobile
   <768px goes full-screen via the override below. */
.slide-over-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    justify-content: flex-end;
    z-index: 90;
}
.slide-over-backdrop[hidden] { display: none; }
.slide-over-panel {
    width: min(720px, 92vw);
    background: var(--surface);
    border-left: 1px solid var(--border);
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.4);
    display: flex;
    flex-direction: column;
    animation: slide-over-in 180ms ease-out;
}
@keyframes slide-over-in {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}
.slide-over-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    border-bottom: 1px solid var(--border);
}
.slide-over-close {
    background: none;
    border: none;
    color: var(--muted);
    font-size: 1.4rem;
    cursor: pointer;
    padding: 0.25rem 0.5rem;
}
.slide-over-close:hover { color: var(--text, currentColor); }
.slide-over-fullpage { text-decoration: none; }
.slide-over-content {
    flex: 1;
    overflow-y: auto;
    padding: 1rem 1.25rem;
}
.slide-over-skeleton { padding: 1rem; }
/* Prevent body scroll while open (focus stays inside the dialog). */
body.slide-over-open { overflow: hidden; }
/* Mobile full-screen. */
@media (max-width: 767px) {
    .slide-over-panel {
        width: 100vw;
        max-width: 100vw;
        border-left: none;
    }
}
@media (prefers-reduced-motion: reduce) {
    .slide-over-panel { animation: none; }
}

/* Bulk-select state on cards + the bottom-sticky action bar. */
.issue-card.bulk-selected {
    outline: 2px solid var(--accent, #6366f1);
    outline-offset: 1px;
}
.issue-card.bulk-conflict {
    outline: 2px solid var(--danger, #ef5b5b);
    outline-offset: 1px;
    animation: bulk-conflict-flash 1.5s ease-out;
}
@keyframes bulk-conflict-flash {
    0%   { background: rgba(239, 91, 91, 0.25); }
    100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
    .issue-card.bulk-conflict { animation: none; }
}
.kanban-bulk-bar {
    position: fixed;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    z-index: 80;
}
.kanban-bulk-bar[hidden] { display: none; }
.kanban-bulk-count { font-weight: 600; }
.kanban-bulk-toast {
    position: fixed;
    left: 50%;
    /* v0.501.1 (PR4 audit IMPORTANT ux #6) — stack above the
       bulk bar so the toast doesn't obscure the count chip. */
    bottom: 4.5rem;
    transform: translateX(-50%);
    padding: 0.5rem 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    z-index: 85;
}
.slide-over-title {
    flex: 1;
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
}
.slide-over-title:focus { outline: none; }
.kanban-tip {
    margin: 0.5rem 0 0.5rem 0;
}
.kanban-tip kbd {
    font-family: monospace;
    font-size: 0.75rem;
    padding: 0.05rem 0.3rem;
    border: 1px solid var(--border);
    border-radius: 0.2rem;
    background: var(--surface-2);
}
.kanban-bulk-toast.warn   { border-color: var(--warning, rgba(245, 158, 11, 0.6)); }
.kanban-bulk-toast.error  { border-color: var(--danger, #ef5b5b); }

/* Quick-add per column. */
.kanban-col-quickadd {
    display: inline-block;
    margin: 0 0 0.25rem 0;
    font-size: 0.8rem;
    text-decoration: none;
}
.kanban-col-quickadd:hover { text-decoration: underline dotted; }

/* Mobile kanban fallback: stack to a single column below 768px so
   touch operators don't have to drag horizontally across all three
   buckets. The per-card move-via-select replaces drag at this width
   (handler reads data-column-status on the parent). */
@media (max-width: 767px) {
    .kanban-row { grid-template-columns: 1fr; }
    .kanban-bulk-bar { left: 0; right: 0; transform: none; border-radius: 0; }
}

/* ====================================================================
   v0.503.0 (PR6 — cross-project quarter view)
   ==================================================================== */

/* v0.503.1 (audit IMPORTANT ux #3) — flex-wrap so the picker
   line-wraps between logical groups, not mid-group. */
.quarter-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: center;
    margin: 1rem 0 0.75rem 0;
}
.picker-group {
    display: inline-flex;
    flex-wrap: nowrap;
    gap: 0.4rem;
    align-items: center;
}
.btn.link-button.small { font-size: 0.8rem; padding: 0.2rem 0.5rem; }
.btn.link-button.active {
    background: var(--surface-2);
    border-color: var(--accent, var(--border-strong, var(--border)));
    font-weight: 600;
}
.quarter-empty { margin: 1rem 0; padding: 0.75rem 1rem; background: var(--surface-2); border-radius: 0.4rem; }
.quarter-view-table { margin-top: 0.5rem; }

