/* Dark mission control theme */

:root {
    --bg-primary: #0f172a;
    --bg-secondary: #1a202c;
    --border: #334155;
    --text-primary: #e2e8f0;
    --text-secondary: #94a3b8;
    --status-idle: #6b7280;
    --status-thinking: #3b82f6;
    --status-tooluse: #f59e0b;
    --status-active: #22c55e;
    --status-background: #a855f7;
    --accent: #8b5cf6;
    /* #302: peer-to-peer (agent-to-agent) chat accent — indigo, distinct from
       status-idle/thinking/tooluse/active/background above, which mean something else. */
    --peer-accent: #6366f1;
    --peer-bg: rgba(99, 102, 241, 0.16);
    --peer-border: rgba(99, 102, 241, 0.45);
    /* Raised neutral surface, one step up from --bg-secondary and short of --border.
       For any element that must read as lifted off its base panel — hover, but also
       non-hover raised states (active panels, selected rows). Deliberately NOT named
       --bg-hover: the same step is wanted for states that have nothing to do with hover.
       Approved by Nova 2026-08-15. */
    --bg-elevated: #232b3a;
    /* Brightest text rung, above --text-primary. For emphasised/raised states where
       --text-primary would read as slightly recessed against an elevated surface. */
    --text-emphasis: #f8fafc;
}

* {
    box-sizing: border-box;
}

body {
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-size: 14px;
    line-height: 1.5;
    margin: 0;
    padding: 0;
}

/* v0.36.0 ISSUE 1 -- THE APP SHELL MUST NOT BE THE SCROLLER.

   Symptom (Stace's screenshot of /channels): the OVERSIGHT title bar was scrolled off the top of
   the window and the channel sidebar's first row was sliced mid-row, with the filter bar flush
   against the very top of the viewport.

   MEASURED root cause -- and it was NOT the header. `.oversight` is `height:100vh` with NO
   overflow declaration, and `html`/`body` are `height:100%` with the default `overflow:visible`.
   So the instant ANY descendant of `.oversight` reported a content height greater than 100vh, the
   overflow escaped to the document and the BODY became the scroller. The header was pinned
   perfectly correctly inside a box that was itself sliding upward -- which is why every previous
   attempt to fix this by styling the header did nothing (see the v0.32.0 note on
   `.channels-page__main`, the same class of mistake one level down).

   The specific overflowing descendant on /channels was `.channel-list` inside
   `.channels-page__sidebar`: the sidebar is a flex column with `min-height:0`, but `min-height:0`
   on a flex CONTAINER only governs how that container behaves as a CHILD -- it does nothing to cap
   its own children. `.channel-list` carried `min-height:0` but no `flex` declaration, so it took the
   default `flex: 0 1 auto` with a content-derived `flex-basis`, grew to the full height of the
   channel list, and pushed the grid row past the viewport.

   `.channel-list { flex: 1 1 0 }` (below, at the channel-list rule) fixes that specific offender.
   THIS rule is the structural guarantee that no FUTURE offender can ever reproduce the symptom:
   with `overflow: hidden` here, a descendant that mis-sizes itself gets CLIPPED instead of being
   allowed to push the app chrome off-screen. A clipped panel is a visible, local, obvious bug; a
   scrolled-away header is a confusing global one that took three releases to diagnose.

   `overflow: hidden` on html/body is deliberately ALSO set: `.oversight` is not the only child of
   #app in every state (MainLayout renders bare auth/denied shells too), and belt-and-braces here
   costs nothing because no route in this app is designed to document-scroll. Every scrollable
   surface owns its own scrollport: `.timeline-view` (agent + channel feeds), `.channel-list__items`,
   and the settings panels.

   REJECTED: `position: fixed` on `.oversight__header`. It would have hidden the symptom while
   leaving the document overflowing, so the sidebar and feed would still have been mis-measured,
   and it would have required a matching top-padding hack on `.oversight__body` that every future
   header height change would silently break.

   100dvh with a 100vh fallback: on mobile Safari/Chrome, 100vh resolves against the LARGEST
   viewport (browser chrome retracted), so a 100vh shell is taller than the visible area whenever
   the URL bar is showing -- which reintroduces exactly this overflow on a phone. `dvh` tracks the
   dynamic viewport. Declaration order is load-bearing: 100vh first so browsers without dvh support
   keep the old (working-on-desktop) behaviour, dvh second so supporting browsers override it. */
html {
    height: 100%;
    /* No route document-scrolls; every scrollable surface owns its own scrollport. */
    overflow: hidden;
}

body {
    height: 100%;
    overflow: hidden;
}

#app {
    height: 100%;
}

/* Main layout */

.oversight {
    display: flex;
    flex-direction: column;
    /* Load-bearing pair -- keep BOTH, in this order. See the html/body block above. */
    height: 100vh;
    height: 100dvh;
    background: var(--bg-primary);
    /* Load-bearing: without this a mis-sized descendant scrolls the DOCUMENT and takes the
       app header off-screen with it. This is the containment boundary for the whole shell. */
    overflow: hidden;
}

.oversight__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 20px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-secondary);
    /* v0.36.0: never grow, never shrink, always content height. NB this rule is SUPERSEDED by the
       second .oversight__header definition further down this file (same specificity, later wins) --
       it is set in both so the declaration survives whichever one a future edit deletes. */
    flex: 0 0 auto;
}

.oversight__title {
    margin: 0;
    font-size: 16px;
    font-weight: 700;
    letter-spacing: 2px;
}

.oversight__brand {
    font-size: 12px;
    font-weight: 600;
    color: var(--accent);
}

/* v0.49.3: the wordmark is now a link to home. It must not look like a link at rest -- inherit
   colour and drop the underline so the header is visually unchanged -- but it still needs a real
   hover/focus affordance so it is discoverable as clickable. inline-flex keeps OVERSIGHT and the
   WermWerx span on one baseline; the negative margin + matching padding give it a comfortable hit
   area without shifting the header layout. */
.oversight__title-link {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    color: inherit;
    text-decoration: none;
    padding: 4px 6px;
    margin: -4px -6px;
    border-radius: 4px;
    transition: background-color 120ms ease, opacity 120ms ease;
}

.oversight__title-link:hover {
    background: rgba(255, 255, 255, 0.06);
}

/* Keyboard users get the same discoverability as mouse users. :focus-visible only, so a mouse
   click does not leave a lingering ring. */
.oversight__title-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.oversight__title-link:hover .oversight__brand {
    opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
    .oversight__title-link { transition: none; }
}

.oversight__header-right {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 12px;
    position: relative;
}

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

/* DUPLICATE DEFINITION -- THIS IS THE ONE THAT WINS.
   `.oversight__header` is declared twice in this file with equal specificity (the first is ~40
   lines above, with 12px 20px padding); the later rule takes precedence, so the app header is
   actually 8px 16px. Left as-is rather than merged: the two were verified to differ ONLY in
   padding, and merging them is a cosmetic refactor that would change the shipped header height as
   a side effect -- out of scope for a layout bug fix. If you consolidate them later, keep the 8px
   16px padding and keep `flex: 0 0 auto`.

   v0.36.0: `flex: 0 0 auto` is load-bearing. As a flex child of `.oversight` this header had the
   default `flex-shrink: 1`, so a greedy sibling (`.oversight__body`, which is `flex: 1`) could
   compress it. Shrinking was not the reported bug -- the document-level overflow fixed above was --
   but an uncapped header is a second, independent way to produce a clipped title bar, and the two
   would have been indistinguishable in a screenshot. */
.oversight__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 16px;
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border);
    flex: 0 0 auto;
}

.oversight__header-right {
    display: flex;
    align-items: center;
    gap: 16px;
}

.oversight__user {
    color: #94a3b8;
    font-size: 0.85rem;
}

.oversight__signout {
    color: #3b82f6;
    text-decoration: none;
}

.oversight__settings-link {
    text-decoration: none;
    font-size: 1.4rem;
    opacity: 0.7;
    transition: opacity 0.2s ease;
}

.oversight__settings-link:hover {
    opacity: 1;
}

.oversight__statusbar {
    padding: 4px 12px;
    font-size: 11px;
    border-bottom: 1px solid var(--border);
}

.oversight__connection {
    font-size: 11px;
    color: var(--text-secondary);
}

.oversight__connection--connected {
    color: var(--status-active);
}

.oversight__connection--reconnecting {
    color: var(--status-tooluse);
    animation: pulse 1s ease-in-out infinite;
}

.oversight__connection--disconnected {
    color: #ef4444;
}

.oversight__map {
    flex: 1;
    padding: 16px;
    border-bottom: 1px solid var(--border);
    overflow: auto;
}

.room-map {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    padding: 16px;
    align-items: flex-start;
}

.room {
    background: var(--bg-secondary);
    border: 2px solid var(--border);
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    transition: border-color 0.2s ease;
    flex-shrink: 0;
    min-width: 300px;
    min-height: 300px;
    position: relative;
}

.room__resize-handle {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 24px;
    height: 24px;
    cursor: nwse-resize;
    background: linear-gradient(135deg, transparent 50%, var(--border) 50%);
    border-radius: 0 0 10px 0;
    opacity: 0.3;
    transition: opacity 0.2s;
    z-index: 10;
}

.room__resize-handle:hover {
    opacity: 1;
}

.room__name {
    flex: 1;
    text-align: center;
    cursor: default;
}

.room__name--renameable {
    cursor: text;
}

/* Lock icon — floats on the top-right corner of the room border */

.room__lock {
    position: absolute;
    top: -14px;
    right: -14px;
    background: none;
    border: none;
    padding: 0;
    font-size: 16px;
    line-height: 1;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s ease;
    z-index: 5;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.5));
}

.room:hover .room__lock { opacity: 0.55; }
.room:hover .room__lock--locked { opacity: 0.9; }
.room__lock:hover { opacity: 1 !important; }

.room--locked {
    border-color: rgba(139, 92, 246, 0.4);
}

.room--locked:hover {
    border-color: rgba(139, 92, 246, 0.65);
}

.room__name-input {
    flex: 1;
    text-align: center;
    background: var(--bg-primary, #1a1a2e);
    color: var(--text-primary);
    border: 1px solid var(--accent);
    border-radius: 4px;
    font-weight: 700;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    font-size: inherit;
    padding: 2px 8px;
    outline: none;
}

.room__delete {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    color: #ef4444;
    font-size: 1rem;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s;
    padding: 2px 6px;
}

.room:hover .room__delete { opacity: 0.6; }
.room__delete:hover { opacity: 1 !important; }

.room-map__add {
    align-self: flex-start;
    padding: 8px 18px;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.2s, opacity 0.2s;
    opacity: 0;
}

.room-map:hover .room-map__add { opacity: 1; }
.room-map__add:hover { background: #2563eb; }

.room:hover {
    border-color: var(--accent);
}

.room--drag-over {
    border-color: #3b82f6 !important;
    background: rgba(59, 130, 246, 0.08) !important;
    box-shadow: inset 0 0 20px rgba(59, 130, 246, 0.15);
}

.agent-node[draggable="true"] {
    cursor: grab;
}

.agent-node[draggable="true"]:active {
    cursor: grabbing;
    opacity: 0.6;
}

.room__header {
    text-align: center;
    font-size: 18px;
    font-weight: 700;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--text-primary);
    padding: 12px 0 8px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.room__header[draggable="true"] {
    cursor: grab;
}

.room__header[draggable="true"]:active {
    cursor: grabbing;
}

.room__header--dragging {
    opacity: 0.5;
}

.room__header--drop-target {
    outline: 2px dashed var(--accent, #8b5cf6);
    outline-offset: -2px;
    background: rgba(139, 92, 246, 0.08);
}

.room__content {
    flex: 1;
    overflow: auto;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    justify-content: center;
    gap: 16px;
    padding: 16px;
}

.agent-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-shrink: 0;
}

.agent-node__svg {
    display: block;
    /* Bug fix (v0.25.1): consistency with .agent-avatar-badge__svg -- SVG root
       elements clip to their own box by default; explicit overflow:visible
       ensures the status ring is never cropped even if a future viewBox pad
       gets tightened. */
    overflow: visible;
}

.agent-node__label {
    text-align: center;
    font-weight: 600;
    color: var(--text-primary);
    margin-top: 4px;
    /* #93: truncate long agent names by default */
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Nameplate — dark translucent band overlapping avatar bottom (#67) */

.agent-node__nameplate {
    position: relative;
    z-index: 2;
    background: rgba(10, 16, 30, 0.78);
    border-radius: 4px;
    padding: 2px 10px;
    text-align: center;
    font-weight: 600;
    color: #f1f5f9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    pointer-events: none;
    backdrop-filter: blur(2px);
}

.agent-node__tool-label {
    position: relative;
    z-index: 2;
    background: rgba(245, 158, 11, 0.15);
    border: 1px solid rgba(245, 158, 11, 0.4);
    border-radius: 3px;
    padding: 1px 6px;
    font-size: 10px;
    font-weight: 500;
    color: #fbbf24;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    pointer-events: none;
    margin-top: 2px;
    animation: tool-label-pulse 2s ease-in-out infinite;
}

@keyframes tool-label-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.6; }
}

.agent-node__label--truncate {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.agent-node__label--wrap {
    word-wrap: break-word;
    overflow-wrap: break-word;
}

.agent-node__label--none {
    white-space: nowrap;
}

.agent-node__connector {
    background: #4b5563;
    height: 24px;
    flex-shrink: 0;
    border-radius: 1px;
}

.agent-node__subs {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    justify-content: center;
    align-items: flex-start;
    position: relative;
    /* #93: cap height so many concurrent subs don’t overflow the room */
    max-height: 240px;
    overflow-y: auto;
    padding-bottom: 4px;
}

.sub-node {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.sub-node__line {
    width: 24px;
    flex-shrink: 0;
    border-radius: 1px;
    margin-bottom: 2px;
    opacity: 0.5;
}

.sub-node__label {
    text-align: center;
    color: var(--text-secondary);
    margin-top: 2px;
    max-width: 80px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.agent-dot__circle {
    transition: cx 0.6s ease-in-out, cy 0.6s ease-in-out, fill 0.3s ease;
    filter: drop-shadow(0 1px 3px rgba(0,0,0,0.4));
}

.agent-dot__pulse {
    fill: none;
    stroke-width: 2;
    animation: pulse-ring 2s ease-out infinite;
}

.agent-dot__label {
    fill: var(--text-primary);
    font-size: 32px;
    font-weight: 600;
    pointer-events: none;
    user-select: none;
}

/* Satellite nodes (subagents) connected to parent agents */

.satellite-link {
    stroke-width: 2;
    opacity: 0.4;
    transition: opacity 0.3s ease, stroke-width 0.3s ease;
}

.satellite-dot {
    opacity: 0;
    animation: satellite-spawn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.satellite-dot__circle {
    transition: fill 0.3s ease;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
}

.satellite-dot__label {
    fill: var(--text-primary);
    font-size: 12px;
    font-weight: 600;
    pointer-events: none;
    user-select: none;
}

@keyframes satellite-spawn {
    from {
        opacity: 0;
        r: 0;
    }
    to {
        opacity: 1;
        r: 5;
    }
}

@keyframes satellite-exit {
    to {
        opacity: 0;
        r: 0;
    }
}

/* Agent panel stats bar (moved below satellite styles) */

/* Message preview inside expanded panel (#73) */

.agent-panel__messages {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 8px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(51,65,85,0.4);
}

.agent-panel__message {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: 11px;
    font-family: 'Monaco', monospace;
    line-height: 1.4;
}

.agent-panel__message-icon {
    flex-shrink: 0;
    font-size: 12px;
}

.agent-panel__message-text {
    color: var(--text-secondary);
    word-break: break-word;
}

.agent-panel__message--in  .agent-panel__message-text { color: var(--text-primary); }
.agent-panel__message--out .agent-panel__message-text { color: var(--text-secondary); }

.agent-panel__stats {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    padding: 8px 0;
    margin-bottom: 8px;
    border-bottom: 1px solid rgba(51,65,85,0.4);
    font-size: 11px;
    font-family: 'Monaco', monospace;
    color: var(--text-secondary);
}

.agent-panel__stat {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}

.agent-panel__stat:first-child {
    color: #fbbf24;
    font-weight: 600;
}

/* Agent panels */

.oversight__panels {
    flex: 0 1 40%;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    background: var(--bg-secondary);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.oversight__empty {
    padding: 20px;
    text-align: center;
    color: var(--text-secondary);
    font-style: italic;
}

.agent-panel {
    margin: 0;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg-primary);
    overflow: hidden;
}

.agent-panel--expanded {
    box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

.agent-panel__header {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 12px;
    background: transparent;
    border: none;
    color: var(--text-primary);
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    text-align: left;
    transition: all 0.2s ease;
}

.agent-panel:hover .agent-panel__header {
    background: rgba(139,92,246,0.1);
}

.agent-panel--expanded .agent-panel__header {
    border-bottom: 1px solid var(--border);
}

.agent-panel__chevron {
    display: inline-block;
    font-size: 9px;
    color: var(--text-secondary);
}

.agent-panel__name {
    flex: 1;
    font-weight: 600;
}

.agent-panel__status {
    font-size: 11px;
    padding: 2px 6px;
    border-radius: 2px;
    font-weight: 500;
}

.agent-panel__status--idle {
    color: var(--status-idle);
    background: rgba(107,114,128,0.2);
}

.agent-panel__status--thinking {
    color: var(--status-thinking);
    background: rgba(59,130,246,0.2);
}

.agent-panel__status--tooluse {
    color: var(--status-tooluse);
    background: rgba(245,158,11,0.2);
}

.agent-panel__status--active {
    color: var(--status-active);
    background: rgba(34,197,94,0.2);
}

.agent-panel__status--background {
    color: var(--status-background);
    background: rgba(168,85,247,0.2);
}

.agent-panel__model {
    font-size: 10px;
    color: var(--text-secondary);
    background: rgba(139,92,246,0.15);
    padding: 2px 4px;
    border-radius: 2px;
}

.agent-panel__cost {
    font-size: 11px;
    color: #fbbf24;
    font-family: 'Monaco', monospace;
}

/* #108: session cost badge — slightly dimmer than request cost */
.agent-panel__cost--session {
    color: #d97706;
    opacity: 0.85;
}

/* Fullscreen mode styles */
.agent-panel--fullscreen {
    position: fixed !important;
    top: 0; left: 0;
    width: 100vw; height: 100vh;
    z-index: 9999;
    border-radius: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    margin: 0;
}

/* v0.29.3: fullscreen keeps the same single-scroller contract — the body is a bounded,
   non-scrolling flex column and .timeline-view remains the only scroller. Previously this
   re-introduced overflow-y:auto here, which would have recreated the nested-scroller bug
   in fullscreen even after the collapsed-panel fix. */
.agent-panel--fullscreen .agent-panel__body {
    flex: 1;
    overflow: hidden;
    max-height: none;
    min-height: 0;
}

.agent-panel__header-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 4px 8px 0;
}

.agent-panel__maximize {
    background: none; border: none;
    color: var(--text-secondary, #999);
    cursor: pointer; font-size: 1rem;
    padding: 0 6px;
    transition: color 0.15s;
    margin-right: 2px;
}

.agent-panel__maximize:hover { 
    color: var(--accent, #8b5cf6); 
}

/* #307: close-from-within-the-chat — header ✕ button, same visual family as
   .agent-panel__maximize but reserved a distinct color-on-hover (red) so it reads
   as destructive/closing rather than a view toggle. */
.agent-panel__close {
    background: none; border: none;
    color: var(--text-secondary, #999);
    cursor: pointer; font-size: 0.95rem;
    padding: 0 6px;
    transition: color 0.15s;
    margin-right: 2px;
}

.agent-panel__close:hover {
    color: #f87171;
}

/* #306: fullscreen participant strip — horizontal row of every conversation
   participant's avatar (with the shared status-ring treatment) shown only while
   this panel is fullscreen, so the user can see who else is thinking/using
   tools/active without exiting fullscreen. Sits above the maximize/close/title
   row as its own line in the header-wrapper flex column. */
.agent-panel__participant-strip {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px 4px;
    flex-wrap: wrap;
    background: rgba(10, 16, 30, 0.5);
    border-bottom: 1px solid var(--border);
}

.agent-panel__fullscreen-badge {
    font-size: 0.65rem;
    background: var(--accent, #8b5cf6);
    color: white;
    padding: 2px 6px;
    border-radius: 999px;
    margin-left: 8px;
    letter-spacing: 0.05em;
    font-weight: 600;
    white-space: nowrap;
}

.agent-panel__header-wrapper {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.agent-panel__filter-toolbar {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: 0.75rem 0.75rem;
    background: rgba(26, 32, 44, 0.8);
    border-top: 1px solid var(--border);
    flex-wrap: wrap;
    flex-shrink: 0;
    font-size: 0.8rem;
}

.agent-panel__filter-section {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.agent-panel__filter-section--end {
    margin-left: auto;
}

.agent-panel__filter-label {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    white-space: nowrap;
}

.agent-panel__filter-checkboxes {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    align-items: center;
}

.agent-panel__filter-checkbox {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    user-select: none;
    font-size: 0.8rem;
    color: var(--text-secondary);
    transition: color 0.2s ease;
}

.agent-panel__filter-checkbox:hover {
    color: var(--text-primary);
}

.agent-panel__filter-checkbox input[type="checkbox"] {
    width: 14px;
    height: 14px;
    cursor: pointer;
    accent-color: var(--accent);
}

.agent-panel__filter-select {
    padding: 0.4rem 0.6rem;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    font-size: 0.8rem;
    cursor: pointer;
    transition: border-color 0.2s ease;
}

.agent-panel__filter-select:hover,
.agent-panel__filter-select:focus {
    border-color: var(--accent);
    outline: none;
}

.agent-panel__filter-btn {
    padding: 0.5rem 1rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    cursor: pointer;
    font-size: 0.8rem;
    font-weight: 600;
    transition: all 0.2s ease;
    white-space: nowrap;
}

.agent-panel__filter-btn:hover {
    border-color: #ef4444;
    color: #ef4444;
    background: rgba(239, 68, 68, 0.08);
}

.agent-panel__filter-btn--clear {
    color: #ef4444;
}

/* Modal styles for agent panel */

.agent-panel__modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    z-index: 299;
}

.agent-panel__modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 300;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 8px;
    width: 400px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}

.agent-panel__modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem;
    border-bottom: 1px solid var(--border);
}

.agent-panel__modal-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.5px;
}

.agent-panel__modal-close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 1.25rem;
    padding: 0.25rem 0.5rem;
    border-radius: 3px;
    transition: color 0.2s ease, background 0.2s ease;
    line-height: 1;
}

.agent-panel__modal-close:hover {
    color: var(--text-primary);
    background: rgba(139, 92, 246, 0.1);
}

.agent-panel__modal-body {
    padding: 1rem;
    color: var(--text-primary);
    font-size: 0.9rem;
    line-height: 1.5;
}

.agent-panel__modal-body p {
    margin: 0;
}

.agent-panel__modal-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 1rem;
    padding: 1rem;
    border-top: 1px solid var(--border);
}

.agent-panel__modal-btn {
    padding: 0.5rem 1rem;
    border-radius: 4px;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
    border: none;
}

.agent-panel__modal-btn--cancel {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-primary);
}

.agent-panel__modal-btn--cancel:hover:not(:disabled) {
    background: rgba(139, 92, 246, 0.1);
    border-color: var(--accent);
}

.agent-panel__modal-btn--confirm {
    background: #ef4444;
    color: #fff;
}

.agent-panel__modal-btn--confirm:hover:not(:disabled) {
    background: #dc2626;
}

.agent-panel__modal-btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* v0.29.3 — TWO NESTED SCROLLERS WAS THE REAL BUG.
   Measured in the live DOM: the ONLY genuinely scrolling element on the page was
   .agent-panel__body (clientHeight 689 / scrollHeight 12307). .timeline-view existed but
   was NOT scrolling, so the scroll listener registered by registerScrollWatcher on
   .timeline-view never fired, OnScrollStateChanged was never invoked, and BOTH auto-follow
   and the jump-to-latest pill were dead. The latch C# was correct the whole time — it was
   simply bound to an element that never scrolled, because this ancestor absorbed it all.

   Fix: __body keeps the SAME 400px visual bound, but becomes a bounded flex COLUMN that
   does not scroll itself (overflow: hidden). The timeline child flexes to fill the leftover
   space and owns the single scroll; stats/messages stay pinned as non-shrinking siblings.
   One scroller, and it is the one the watcher already targets. */
.agent-panel__body {
    padding: 8px 12px;
    background: var(--bg-secondary);
    border-top: 1px solid var(--border);
    max-height: 400px;
    /* was overflow-y:auto — that made THIS the scroller and starved .timeline-view */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    min-height: 0;
    animation: slideDown 0.2s ease;
}

/* Non-scrolling siblings inside the flex body: never shrink, never absorb the scroll. */
.agent-panel__body > .agent-panel__stats,
.agent-panel__body > .agent-panel__messages {
    flex: 0 0 auto;
}

/* Activity feed */

.activity-feed {
    max-height: 150px;
    overflow-y: auto;
    font-size: 11px;
    font-family: 'Monaco', monospace;
}

.activity-feed__empty {
    padding: 8px;
    color: var(--text-secondary);
    text-align: center;
    font-size: 12px;
}

.activity-feed__entry {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 4px 0;
    border-bottom: 1px solid rgba(51,65,85,0.4);
    line-height: 1.3;
}

.activity-feed__entry:last-child {
    border-bottom: none;
}

.activity-feed__entry--error {
    color: #ef4444;
}

.activity-feed__time {
    flex: 0 0 auto;
    color: var(--text-secondary);
    min-width: 55px;
    font-size: 10px;
}

.activity-feed__icon {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--text-secondary);
    min-width: 14px;
    text-align: center;
}

.activity-feed__source {
    flex: 0 0 auto;
    color: var(--accent);
    font-weight: 600;
    min-width: 60px;
    max-width: 60px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 10px;
}

.activity-feed__detail {
    flex: 1;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* #106: Rich tool activity cards */

.activity-feed__tool-card {
    padding: 6px 8px;
    margin: 2px 0;
    border-radius: 4px;
    background: rgba(59,130,246,0.07);
    border-left: 2px solid rgba(59,130,246,0.4);
    font-size: 11px;
    font-family: 'Monaco', monospace;
}

.activity-feed__tool-card--result {
    background: rgba(34,197,94,0.07);
    border-left-color: rgba(34,197,94,0.4);
}

.activity-feed__tool-card--error {
    background: rgba(239,68,68,0.07);
    border-left-color: rgba(239,68,68,0.4);
}

.activity-feed__tool-header {
    display: flex;
    align-items: center;
    gap: 6px;
    line-height: 1.4;
}

.activity-feed__tool-icon { flex-shrink: 0; font-size: 12px; }

.activity-feed__tool-name {
    font-weight: 700;
    color: var(--text-primary);
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.activity-feed__tool-duration {
    flex-shrink: 0;
    color: var(--text-secondary);
    font-size: 10px;
    opacity: 0.8;
}

.activity-feed__time--right {
    flex-shrink: 0;
    color: var(--text-secondary);
    font-size: 10px;
}

.activity-feed__tool-snippet {
    margin-top: 3px;
    color: var(--text-secondary);
    font-size: 10px;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 60px;
    overflow: hidden;
    line-height: 1.4;
}

.activity-feed__tool-exit {
    margin-top: 2px;
    color: #ef4444;
    font-size: 10px;
    font-weight: 600;
}

.activity-feed__cost {
    flex: 0 0 auto;
    color: #fbbf24;
    font-weight: 600;
    font-size: 10px;
}

/* Message entries in activity feed */

.message-entry {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px 0;
    margin: 2px 0;
    border-bottom: 1px solid rgba(51,65,85,0.4);
}

.message-entry:last-child {
    border-bottom: none;
}

.message-entry__header {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 10px;
}

.message-entry__direction {
    flex: 0 0 auto;
    font-weight: 600;
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.message-entry--outbound .message-entry__direction {
    color: #22c55e;
}

.message-entry--inbound .message-entry__direction {
    color: #3b82f6;
}

.message-entry__time {
    flex: 1;
    text-align: right;
    color: var(--text-secondary);
    font-size: 10px;
}

.message-entry__bubble {
    margin-left: 12px;
    padding: 6px 8px;
    background: rgba(59, 130, 246, 0.1);
    border-left: 2px solid rgba(59, 130, 246, 0.4);
    border-radius: 3px;
}

.message-entry--outbound .message-entry__bubble {
    background: rgba(34, 197, 94, 0.1);
    border-left-color: rgba(34, 197, 94, 0.4);
}

.message-entry__bubble--empty {
    background: rgba(107, 114, 128, 0.08);
    border-left-color: rgba(107, 114, 128, 0.2);
}

.message-entry__text {
    margin: 0;
    padding: 0;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 11px;
    color: var(--text-primary);
    line-height: 1.4;
    word-wrap: break-word;
    white-space: pre-wrap;
}

.message-entry__text--placeholder {
    font-style: italic;
    color: var(--text-secondary);
    font-size: 10px;
}

/* Footer */

.oversight__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 16px;
    border-top: 1px solid var(--border);
    background: var(--bg-secondary);
    font-size: 11px;
    color: var(--text-secondary);
    gap: 16px;
    flex-wrap: wrap;
}

.oversight__versions {
    font-family: 'Monaco', 'Courier New', monospace;
    font-size: 10px;
    color: var(--text-secondary);
    opacity: 0.7;
    transition: opacity 0.2s ease;
}

.oversight__footer:hover .oversight__versions {
    opacity: 1;
}

/* Version info styles (end) */

/* Animations */

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

/* v0.99.0: "responder is typing…" placeholder — three bouncing dots in an agent bubble, shown the
   instant the user sends and replaced by the real reply. Mirrors a chat typing indicator. */
.channel-row__typing {
    display: inline-flex;
    align-items: center;
    min-height: 1.2em;
}
.channel-row__typing-dots {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.channel-row__typing-dots > span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.35;
    animation: channel-typing-bounce 1.2s infinite ease-in-out;
}
.channel-row__typing-dots > span:nth-child(2) { animation-delay: 0.18s; }
.channel-row__typing-dots > span:nth-child(3) { animation-delay: 0.36s; }

@keyframes channel-typing-bounce {
    0%, 80%, 100% { transform: translateY(0);    opacity: 0.35; }
    40%           { transform: translateY(-3px); opacity: 0.9;  }
}

/* Respect reduced-motion: fall back to a gentle opacity pulse, no vertical bounce. */
@media (prefers-reduced-motion: reduce) {
    .channel-row__typing-dots > span {
        animation: channel-typing-fade 1.4s infinite ease-in-out;
    }
    @keyframes channel-typing-fade {
        0%, 100% { opacity: 0.3; }
        50%      { opacity: 0.8; }
    }
}

@keyframes pulse-ring {
    0% {
        r: 9px;
        opacity: 1;
    }
    100% {
        r: 22px;
        opacity: 0;
    }
}

@keyframes slideDown {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ============================================================================
   USER AVATAR (Task #1)
   ============================================================================ */

.user-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    cursor: pointer;
    overflow: hidden;
    border: 2px solid var(--border);
    transition: border-color 0.2s ease, transform 0.15s ease;
    flex-shrink: 0;
}

.user-avatar:hover {
    border-color: var(--accent);
    transform: scale(1.05);
}

.user-avatar__img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.user-avatar__initials {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--accent), #6366f1);
    color: #ffffff;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.5px;
    user-select: none;
}

/* Account panel overlay */

.account-panel__overlay {
    position: fixed;
    inset: 0;
    z-index: 99;
}

.account-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 100;
    min-width: 240px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    animation: slideDown 0.15s ease;
    overflow: hidden;
}

.account-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
}

.account-panel__title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.account-panel__close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 14px;
    padding: 2px 4px;
    border-radius: 3px;
    transition: color 0.2s ease, background 0.2s ease;
    line-height: 1;
}

.account-panel__close:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.05);
}

.account-panel__body {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px;
}

.account-panel__avatar-large {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    overflow: hidden;
    flex-shrink: 0;
    border: 2px solid var(--border);
}

.account-panel__avatar-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.account-panel__avatar-initials {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--accent), #6366f1);
    color: #ffffff;
    font-size: 16px;
    font-weight: 700;
    user-select: none;
}

.account-panel__info {
    min-width: 0;
    flex: 1;
}

.account-panel__name {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.account-panel__email {
    font-size: 11px;
    color: var(--text-secondary);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.account-panel__footer {
    padding: 12px 16px;
    border-top: 1px solid var(--border);
}

.account-panel__signout {
    font-size: 12px;
    color: #ef4444;
    text-decoration: none;
    font-weight: 500;
    transition: color 0.2s ease;
}


.account-panel__photo-section {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 16px 12px;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}

.account-panel__photo-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px 10px;
    border: 1px solid var(--border);
    border-radius: 4px;
    transition: color 0.2s ease, border-color 0.2s ease;
    user-select: none;
}

.account-panel__photo-label:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}

.account-panel__photo-input {
    display: none;
}

.account-panel__photo-clear {
    background: none;
    border: 1px solid #ef4444;
    color: #ef4444;
    font-size: 11px;
    padding: 3px 8px;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}

.account-panel__photo-clear:hover {
    background: rgba(239, 68, 68, 0.1);
}

.account-panel__photo-error {
    font-size: 11px;
    color: #ef4444;
    width: 100%;
}

/* ─── Display Name ─────────────────────────────────────────────────── */
.account-panel__display-name-section {
    padding: 10px 16px 12px;
    border-bottom: 1px solid var(--border);
}

.account-panel__display-name-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 6px;
    display: block;
}

.account-panel__display-name-row {
    display: flex;
    gap: 6px;
    align-items: center;
}

.account-panel__display-name-input {
    flex: 1;
    padding: 5px 8px;
    font-size: 12px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg-secondary);
    color: var(--text-primary);
    outline: none;
}

.account-panel__display-name-input:focus {
    border-color: var(--accent);
}

.account-panel__display-name-save {
    padding: 5px 10px;
    font-size: 11px;
    font-weight: 600;
    border: none;
    border-radius: 4px;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
}

.account-panel__display-name-save:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.account-panel__display-name-status {
    display: block;
    font-size: 11px;
    color: var(--text-secondary);
    margin-top: 4px;
}

/* ─── Linked Identities ────────────────────────────────────────────── */
.account-panel__identities-section {
    padding: 10px 16px 12px;
    border-bottom: 1px solid var(--border);
}

.account-panel__identities-header {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

.account-panel__identities-list {
    list-style: none;
    margin: 0 0 8px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.account-panel__identity-item {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    padding: 4px 6px;
    border-radius: 4px;
    background: var(--surface-alt, rgba(255,255,255,0.03));
}

.account-panel__identity-platform {
    font-weight: 600;
    text-transform: capitalize;
    min-width: 60px;
    color: var(--accent);
}

.account-panel__identity-id {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-primary);
}

.account-panel__identity-remove {
    background: none;
    border: none;
    color: #ef4444;
    cursor: pointer;
    font-size: 12px;
    padding: 2px 4px;
    border-radius: 3px;
    opacity: 0.6;
    transition: opacity 0.2s;
}

.account-panel__identity-remove:hover {
    opacity: 1;
    background: rgba(239, 68, 68, 0.1);
}

/* v0.57.0 — admin deep-link in the account popover.
   Self-service (photo / display name / own identities) stays IN the popover; managing OTHER
   users lives on /users. This button is rendered only when the SERVER resolved the caller as
   admin, so it never appears for members who would just hit an access-denied page. Styled as a
   quiet full-width row rather than a primary button: it is navigation, not the panel's main
   action (which is editing your own profile). */
.account-panel__admin-link-section {
    padding: 10px 16px 12px;
    border-bottom: 1px solid var(--border);
}

.account-panel__admin-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    gap: 8px;
    padding: 8px 10px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary);
    background: var(--surface-alt, rgba(255,255,255,0.03));
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    text-align: left;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.account-panel__admin-link:hover {
    background: rgba(139, 92, 246, 0.12);
    border-color: var(--accent);
}

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

/* Narrow variant for the reassignment confirm — a short question should not inherit the
   400px form width used by the create/edit modals. */
.modal--narrow {
    width: 360px;
}

/* The shared <IdentityLinker> renders inside BOTH the 400px admin modal and the much narrower
   account popover. Without this the users-identity-* rows (built for the wide modal) overflow
   the popover horizontally. Constrain the long external-id text instead of letting it push. */
.account-panel .identity-linker .users-identity-list__ext {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 150px;
}

.account-panel .identity-linker .users-identity-form {
    padding: 8px;
}

.account-panel__identity-add {
    display: flex;
    gap: 4px;
    align-items: center;
}

.account-panel__identity-select {
    font-size: 11px;
    padding: 3px 6px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text-primary);
}

.account-panel__identity-input {
    flex: 1;
    font-size: 11px;
    padding: 3px 6px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text-primary);
    min-width: 0;
}

.account-panel__identity-link-btn {
    font-size: 11px;
    padding: 3px 8px;
    border: 1px solid var(--accent);
    border-radius: 4px;
    background: none;
    color: var(--accent);
    cursor: pointer;
    transition: background 0.2s, color 0.2s;
}

.account-panel__identity-link-btn:hover:not(:disabled) {
    background: var(--accent);
    color: #fff;
}

.account-panel__identity-link-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.account-panel__signout:hover {
    color: #fca5a5;
}

/* ============================================================================
   ROOM MAP — Lock, Delete, Rename (Tasks #4 & #5)
   ============================================================================ */

/* Locked room gets a different border color */

.room-map__room--locked .room-map__rect {
    stroke: rgba(139, 92, 246, 0.4);
}

.room-map__room--locked:hover .room-map__rect {
    stroke: rgba(139, 92, 246, 0.6);
}

/* Lock icon */

.room-map__lock {
    cursor: pointer;
    opacity: 0.5;
    transition: opacity 0.2s ease;
}

.room-map__lock:hover {
    opacity: 1;
}

.room-map__lock--locked {
    opacity: 0.85;
}

.room-map__lock-body {
    fill: #334155;
    stroke: #94a3b8;
    stroke-width: 1;
}

.room-map__lock--locked .room-map__lock-body {
    fill: rgba(139, 92, 246, 0.3);
    stroke: #8b5cf6;
}

.room-map__lock-shackle {
    fill: none;
    stroke: #94a3b8;
    stroke-width: 1.5;
    stroke-linecap: round;
}

.room-map__lock--locked .room-map__lock-shackle {
    stroke: #8b5cf6;
}

.room-map__lock-shackle--open {
    stroke: #94a3b8;
}

.room-map__lock-keyhole {
    fill: #94a3b8;
}

.room-map__lock--locked .room-map__lock-keyhole {
    fill: #8b5cf6;
}

/* Delete button */

.room-map__delete {
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s ease;
}

.room-map__room:hover .room-map__delete {
    opacity: 1;
}

.room-map__delete-bg {
    fill: rgba(239, 68, 68, 0.15);
    stroke: rgba(239, 68, 68, 0.4);
    stroke-width: 1;
    transition: fill 0.15s ease;
}

.room-map__delete:hover .room-map__delete-bg {
    fill: rgba(239, 68, 68, 0.4);
    stroke: #ef4444;
}

.room-map__delete-x {
    fill: #ef4444;
    font-size: 9px;
    pointer-events: none;
    user-select: none;
}

/* Room rename input (foreignObject) */

.room-map__rename-input {
    width: 100%;
    height: 22px;
    padding: 2px 6px;
    background: var(--bg-primary);
    border: 1px solid var(--accent);
    border-radius: 3px;
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 700;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    outline: none;
}

.room-map__rename-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.25);
}

/* Agent dragging states (Task #5) */

.agent-dot--draggable {
    cursor: grab;
}

.agent-dot--dragging {
    cursor: grabbing;
    filter: drop-shadow(0 4px 12px rgba(139, 92, 246, 0.5));
    z-index: 10;
}

/* ============================================================================
   DRAG DROP ZONES (#80 trash, #82 onboard)
   ============================================================================ */

.drag-zone {
    position: fixed;
    bottom: 32px;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 2px dashed;
    cursor: pointer;
    transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
    z-index: 200;
    animation: drag-zone-pop 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.drag-zone--trash  { right: 40px; background: rgba(239,68,68,0.1);  border-color: rgba(239,68,68,0.4); }
.drag-zone--onboard { left: 40px;  background: rgba(34,197,94,0.1); border-color: rgba(34,197,94,0.4); }

.drag-zone--active.drag-zone--trash  { background: rgba(239,68,68,0.3);  border-color: #ef4444; border-style: solid; transform: scale(1.1); }
.drag-zone--active.drag-zone--onboard { background: rgba(34,197,94,0.3); border-color: #22c55e; border-style: solid; transform: scale(1.1); }

.drag-zone__icon  { font-size: 28px; line-height: 1; pointer-events: none; }
.drag-zone__label { font-size: 11px; font-weight: 600; color: var(--text-secondary); pointer-events: none; text-align: center; }
.drag-zone--active .drag-zone__label { color: var(--text-primary); }

.drag-zone__error {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(239,68,68,0.9);
    color: #fff;
    padding: 10px 20px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 500;
    z-index: 300;
    cursor: pointer;
    max-width: 400px;
    text-align: center;
}

@keyframes drag-zone-pop {
    from { opacity: 0; transform: scale(0.6); }
    to   { opacity: 1; transform: scale(1); }
}

/* ============================================================================
   ONBOARD MODAL (#82)
   ============================================================================ */

.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.6);
    z-index: 300;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: fadeIn 0.15s ease;
}

.modal {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 12px;
    width: 400px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 16px 48px rgba(0,0,0,0.5);
    overflow: hidden;
    animation: slideDown 0.2s ease;
    /* Bug #336: tall CREATE-mode content (Users admin) pushed .modal__footer below the
       viewport with no way to scroll to it. Cap the modal height and make it a flex
       column so header/footer stay pinned and only the body scrolls. */
    max-height: 90vh;
    display: flex;
    flex-direction: column;
}

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

.modal__title { margin: 0; font-size: 15px; font-weight: 700; letter-spacing: 0.5px; }

.modal__close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 16px;
    padding: 2px 6px;
    border-radius: 4px;
    transition: color 0.2s, background 0.2s;
    line-height: 1;
}
.modal__close:hover { color: var(--text-primary); background: rgba(255,255,255,0.06); }

.modal__body { padding: 20px; display: flex; flex-direction: column; gap: 14px; overflow-y: auto; flex: 1 1 auto; }

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

.modal__label {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.modal__optional { font-weight: 400; text-transform: none; opacity: 0.7; }

.modal__input,
.modal__select {
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-primary);
    font-size: 13px;
    padding: 8px 10px;
    outline: none;
    transition: border-color 0.2s;
    font-family: inherit;
}
.modal__input:focus, .modal__select:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(139,92,246,0.2); }
/* v0.49.5 (#340): a readonly field still holds REAL data and must read as data.

   `opacity: 0.5` over the dark input background dimmed the text to roughly the same value as the
   placeholder colour, so a populated User ID ("adam") was indistinguishable from an empty field.
   The value was always rendered -- value="@_editing.Id" -- and repeatedly got reported as the id
   "not showing up", because visually it simply did not.

   Fixed by dropping opacity (which fades CONTENT) and signalling non-editability through the
   CHROME instead: flatter background, dashed border, default cursor. The text itself now renders
   at full primary colour. Keep it that way -- do not reintroduce an opacity rule here. */
.modal__input--readonly {
    cursor: default;
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.03);
    border-style: dashed;
}

/* The value is the point of the field, so let it be selected/copied even though it cannot be
   edited -- operators copy ids for attribution lookups. */
.modal__input--readonly:focus {
    outline: none;
    border-color: var(--accent);
}

.modal__footer { display: flex; justify-content: flex-end; gap: 10px; padding: 14px 20px; border-top: 1px solid var(--border); flex: 0 0 auto; }

.modal__btn { padding: 8px 20px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: none; transition: background 0.15s, opacity 0.15s; }
.modal__btn--cancel { background: rgba(255,255,255,0.06); color: var(--text-secondary); }
.modal__btn--cancel:hover { background: rgba(255,255,255,0.1); color: var(--text-primary); }
.modal__btn--confirm { background: var(--accent); color: #fff; }
.modal__btn--confirm:hover:not(:disabled) { background: #7c3aed; }
.modal__btn--confirm:disabled { opacity: 0.4; cursor: not-allowed; }

/* Avatar file picker (#89) */

.modal__avatar-picker {
    display: flex;
    align-items: center;
    gap: 12px;
}

.modal__avatar-preview {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid var(--border);
    flex-shrink: 0;
}

.modal__file-input {
    flex: 1;
    font-size: 12px;
    color: var(--text-secondary);
    cursor: pointer;
}

.modal__clear-avatar {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 14px;
    padding: 2px 4px;
    border-radius: 3px;
    line-height: 1;
    flex-shrink: 0;
    transition: color 0.15s;
}
.modal__clear-avatar:hover { color: #ef4444; }

.modal__field-error {
    font-size: 11px;
    color: #ef4444;
    margin-top: 2px;
}

/* ============================================================================
   CONVERSATION VIEW (#118)
   ============================================================================ */

.conv-view {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-height: 360px;
    overflow-y: auto;
    padding: 12px 8px;
    scroll-behavior: smooth;
    /* Anchor for the absolutely-positioned "new messages" pill. */
    position: relative;
}

.conv-view__empty {
    text-align: center;
    color: var(--text-secondary);
    font-style: italic;
    font-size: 12px;
    padding: 24px 0;
}

/* Row layout */

.conv-view__row {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    max-width: 85%;
}

.conv-view__row--in  { align-self: flex-start; flex-direction: row; }
.conv-view__row--out { align-self: flex-end;   flex-direction: row-reverse; }

/* Avatars */

.conv-view__avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex-shrink: 0;
    object-fit: cover;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    user-select: none;
}

.conv-view__avatar--generic {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    color: var(--text-secondary);
}

.conv-view__avatar--agent {
    background: linear-gradient(135deg, var(--accent), #6366f1);
    color: #fff;
}

/* Bubble wrapper */

.conv-view__bubble-wrap {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.conv-view__row--out .conv-view__bubble-wrap { align-items: flex-end; }
.conv-view__row--in  .conv-view__bubble-wrap { align-items: flex-start; }

.conv-view__sender {
    font-size: 10px;
    font-weight: 600;
    color: var(--text-secondary);
    padding: 0 4px;
}

.conv-view__bubble {
    padding: 8px 12px;
    border-radius: 12px;
    font-size: 13px;
    line-height: 1.5;
    word-break: break-word;
    white-space: pre-wrap;
    max-width: 100%;
}

.conv-view__bubble--in {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    color: var(--text-primary);
    border-bottom-left-radius: 4px;
}

.conv-view__bubble--out {
    background: rgba(139, 92, 246, 0.2);
    border: 1px solid rgba(139, 92, 246, 0.35);
    color: var(--text-primary);
    border-bottom-right-radius: 4px;
}

/* v0.58.0: #302's peer-only bubble/sender/avatar modifiers (distinct indigo tint/border)
   were removed here — peer (agent-to-agent) rows now reuse .conv-view__bubble--in /
   .conv-view__sender / .conv-view__avatar--generic, the same classes as a normal inbound
   message, so they read as "just another chat bubble" rather than a special element.
   Direction is now conveyed by the header text ("Carl → Adam") instead of color.
   The --peer-* custom properties (:root, near top of file) are NOT removed — they're still
   used elsewhere (e.g. .users-badge--agent) for the unrelated "agent type" accent meaning. */

.conv-view__time {
    font-size: 10px;
    color: var(--text-secondary);
    opacity: 0.6;
    padding: 0 4px;
    font-family: 'Monaco', monospace;
}

.conv-view__expand {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
    padding-top: 4px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    font-size: 11px;
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
    transition: color 0.15s;
}
.conv-view__expand:hover {
    color: var(--text-primary);
}
.conv-view__expand-arrow {
    font-size: 9px;
}

/* ============================================================================
   ORBITAL SUBAGENT POSITIONING (#121 legacy — kept for reference)
   FLOATING OVERLAY approach implemented in #123
   ============================================================================ */

.agent-node__orbital {
    position: relative;
    flex-shrink: 0;
}

.agent-node__orbital-lines {
    /* SVG overlay for connection lines */
}

.agent-node__orbital-label {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: center;
    font-weight: 600;
    color: var(--text-primary);
    pointer-events: none;
    max-width: 120px;
}

.agent-node__orbital-sub {
    position: absolute;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    animation: satellite-spawn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.agent-node__orbital-sub-label {
    text-align: center;
    color: var(--text-secondary);
    font-weight: 600;
    max-width: 64px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    user-select: none;
}

/* ============================================================================
   FLOATING ORBITAL OVERLAY (#123)
   Absolutely-positioned overlay covering each room; subagent nodes float
   freely in polar orbit around their parent agent tile.
   ============================================================================ */

/* Full-room transparent overlay; sits above tiles but below room chrome */
.room-orbital-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 8;
    overflow: visible;
}

/* Individual subagent node on the overlay */
.room-orbital-sub {
    position: absolute;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    /* left/top set inline from polar-coordinate math */
    pointer-events: auto;
    cursor: default;
    animation: satellite-spawn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.room-orbital-sub__label {
    text-align: center;
    background: rgba(10, 16, 30, 0.78);
    color: #f1f5f9;
    font-weight: 600;
    border-radius: 4px;
    padding: 2px 6px;
    max-width: 64px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    user-select: none;
    backdrop-filter: blur(2px);
}

/* Scrollbar */

::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--text-secondary);
}

/* ── Agent Avatar Ring System ─────────────────────────────────────────── */

/* v0.25.3: Status ring/glow display RESTORED (ADO #304) — the hide added in
   v0.25.2 pending product review was conflated with the separate #304
   context-usage gauge; the two are independent instruments and this one was
   never meant to stay hidden. Reverting the one-line display:none. */

/* v0.51.0 (#342): portrait backdrop for rig-player avatars.

   Every other agent's avatar PNG ships with this blue disc baked into the artwork. The .rigpkg
   frames are fully transparent by design (the character has to composite over whatever is behind
   it), so Adam rendered directly onto the page background and read as "missing his background"
   next to the rest of the dev team.

   #4783BF is sampled from the shipped avatar artwork, not invented -- it is ART, not a theme
   token, which is why it deliberately has no CSS custom property. If Nova ever tokenises the
   avatar palette this should follow that token instead.

   Painted as an SVG <circle> beneath the <foreignObject> so the rig keeps its transparency and
   composites on top. It intentionally reuses the same cx/cy/r as the clip-path circle so the
   backdrop can never drift out of alignment with the avatar's clipped edge or the status ring. */
.agent-avatar__backdrop {
    fill: #4783BF;
    transition: fill 0.4s ease;
}

/* v0.51.1: idle treatment for rig avatars lands on the DISC, not the character.

   For every other agent the avatar is a flat <image>, so .agent-avatar__img--idle dims "the avatar"
   and correctly reads as inactive. The rig is a live character on a transparent canvas -- running the
   same filter over the element desaturates ADAM HIMSELF, which reads as "the animation is broken"
   rather than "this agent is idle".

   #35617F is #4783BF put through the same saturate(0.5) brightness(0.8) the flat avatars get, so the
   two idle treatments match visually. Precomputed rather than applying a CSS filter because a filter
   on the <circle> would also catch the rig compositing above it in the same SVG stacking context. */
.agent-avatar__backdrop--idle {
    fill: #35617F;
}

.agent-avatar__img {
    transition: filter 0.4s ease;
}

.agent-avatar__img--idle {
    filter: saturate(0.5) brightness(0.8);
}

.agent-avatar__ring {
    fill: none;
    stroke-width: 4;
    transition: stroke 0.3s ease, stroke-width 0.3s ease, opacity 0.3s ease;
}

.agent-avatar__ring--idle {
    stroke-width: 2;
    opacity: 0.4;
}

.agent-avatar__ring--thinking {
    stroke-width: 8;
    animation: avatar-breathe 2s ease-in-out infinite;
}

.agent-avatar__ring--tooluse {
    stroke-width: 8;
    stroke-dasharray: 24 14;
    animation: avatar-spin 1.5s linear infinite;
    transform-box: fill-box;
    transform-origin: center;
}

.agent-avatar__ring--active {
    stroke-width: 8;
    filter: drop-shadow(0 0 20px currentColor);
}

/* v0.25.5: "Completed" used to reuse --active (green, glowing) which reads as
   "still working" per product feedback. This renders a fully blank ring --
   same visual as a plain avatar with no status decoration at all, not even
   the faint idle ring -- once a task genuinely finishes. Does not touch
   --idle, --thinking, --tooluse, --active, or --background. */
.agent-avatar__ring--none {
    stroke-width: 0;
    opacity: 0;
}

.agent-avatar__ring--background {
    stroke-width: 6;
    stroke-dasharray: 16 12;
    animation: avatar-breathe 3s ease-in-out infinite;
}

.agent-avatar__ring--conversing {
    stroke-width: 8;
    animation: avatar-breathe 1.5s ease-in-out infinite;
    filter: drop-shadow(0 0 24px currentColor);
}

/* Outer glow ring — only visible when active */
.agent-avatar__glow {
    fill: none;
    stroke-width: 2;
    opacity: 0.3;
    animation: avatar-glow-pulse 2s ease-in-out infinite;
}

@keyframes avatar-breathe {
    0%, 100% { stroke-width: 6; opacity: 0.8; }
    50% { stroke-width: 10; opacity: 1; }
}

@keyframes avatar-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

@keyframes avatar-glow-pulse {
    0%, 100% { opacity: 0.15; }
    50% { opacity: 0.4; }
}

/* ── #306: AgentAvatarBadge — compact avatar-with-status-ring reused in the
   chat/agent panel header + fullscreen participant strip (Stace's request to
   "render like it does above [RoomMap]" without leaving fullscreen). Shares
   the same .agent-avatar__ring / .agent-avatar__img classes as RoomMap so the
   ring animations/colors are pixel-for-pixel identical; these rules only cover
   badge-specific layout (sizing wrapper, fallback initials, click affordance). */

.agent-avatar-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 50%;
    position: relative;
}

.agent-avatar-badge__svg {
    display: block;
    /* Bug fix (v0.25.1): SVG root elements clip to their own box by default
       (implicit overflow:hidden UA behavior) even when width/height/viewBox
       have padding for the ring. Explicit overflow:visible stops the
       spinning/breathing status ring from being cropped at the SVG edge. */
    overflow: visible;
}

.agent-avatar-badge--clickable {
    cursor: pointer;
}

.agent-avatar-badge--clickable:hover {
    filter: brightness(1.25);
}

.agent-avatar-badge--highlighted {
    filter: drop-shadow(0 0 6px rgba(255,255,255,0.5));
}

.agent-avatar-badge__fallback-circle {
    fill: rgba(148, 163, 184, 0.25);
}

.agent-avatar-badge__initials {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    color: var(--text-primary, #e2e8f0);
    font-size: 10px;
    font-weight: 700;
    pointer-events: none;
    user-select: none;
}

/* ── #304: Context Usage Gauge ────────────────────────────────────────── */
/* Thin outer arc ring around the avatar/dot, independent of the status ring.
   Track is a faint full circle (HUD baseline); the arc is drawn on top using
   stroke-dasharray/dashoffset as a percent-fill indicator, rotated -90deg in
   markup so 0% starts at 12 o'clock and fills clockwise like a loading ring. */

/* ADO #304: Context gauge HIDDEN pending a tooltip-based visualization
   (context % is now surfaced via the host tooltip instead — see
   BuildHostTooltip in RoomMap.razor). Underlying markup/helpers left fully
   intact; this is a one-line revert (delete this block) once a visual
   redesign of the ring itself ships. */
.agent-context-gauge__track,
.agent-context-gauge__arc {
    display: none !important;
}

.agent-context-gauge__track {
    fill: none;
    stroke: rgba(148, 163, 184, 0.18);
    stroke-width: 3;
}

.agent-context-gauge__arc {
    fill: none;
    stroke-width: 3;
    stroke-linecap: round;
    transition: stroke-dashoffset 0.5s ease, stroke 0.5s ease;
    filter: drop-shadow(0 0 3px currentColor);
}

.agent-context-gauge__arc--critical {
    animation: context-gauge-warn 1.2s ease-in-out infinite;
}

@keyframes context-gauge-warn {
    0%, 100% { opacity: 1; stroke-width: 3; }
    50% { opacity: 0.6; stroke-width: 4; }
}

/* ── Per-session context % hover popover (oversight-api v1.50.0) ───────────
   Lazy-fetched on hover over an agent tile (RoomMap.razor). Deliberately simple
   for v1: a small floating card, plain bar gauges, no heavy visual design.
   Positioned above the tile via absolute + bottom, same overlay pattern as
   .card-clipboard elsewhere on this map. */
/* #ctx-hover-clip: the agent context popover.

   This used to be position:absolute inside the agent tile, i.e. inside
   `.room__content { overflow: auto }`. A scroll container is ALSO a clipping container, and
   clipping is applied before stacking is resolved, so the popover was sliced off at the room
   border and z-index could not save it. It is now rendered in a position:fixed layer at the
   .room-map root (see RenderContextPopoverLayer) and anchored to the tile's measured viewport
   rect, which puts it above rooms, panels and neighbouring tiles. */
.agent-context-popover {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    min-width: 200px;
    max-width: 280px;
    background: var(--bg-secondary, #1a1d24);
    border: 1px solid var(--border, #333);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    padding: 8px 10px;
    z-index: 40;
    pointer-events: auto;
    font-size: 0.75rem;
    text-align: left;
}

/* Floating variant: coordinates come from the anchor rect measured in JS, so all of the
   tile-relative positioning above is cancelled. `left`/`top`/`bottom` are set inline.
   The high z-index is what wins against room chrome now that clipping is no longer in play. */
.agent-context-popover--floating {
    position: fixed;
    /* Inline styles supply left + (top | bottom); neutralise the absolute-mode rules. */
    right: auto;
    transform: none;
    /* Above rooms, resize handles, locks and the add-room button. */
    z-index: 1200;
    /* Never let a long session list run off the bottom of the screen. */
    max-height: calc(100vh - 24px);
    overflow-y: auto;
}

/* Above-tile placement (default) pins by `bottom`, so clear the inherited `top`. */
.agent-context-popover--floating:not(.agent-context-popover--below) {
    top: auto;
}

/* Flipped below the tile: pinned by `top` instead. */
.agent-context-popover--below {
    bottom: auto;
}

.agent-context-popover__title {
    font-weight: 600;
    font-size: 0.7rem;
    color: var(--text-secondary, #aaa);
    margin-bottom: 6px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.agent-context-popover__state {
    color: var(--text-tertiary, #888);
    font-size: 0.72rem;
    padding: 2px 0;
}

.agent-context-popover__state--error {
    color: #f87171;
}

.agent-context-popover__row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 0;
}

.agent-context-popover__row--subagent {
    margin-left: 10px;
    opacity: 0.92;
}

.agent-context-popover__row--inactive {
    opacity: 0.55;
}

.agent-context-popover__tag {
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary, #888);
    border: 1px solid var(--border, #333);
    border-radius: 3px;
    padding: 0 4px;
    white-space: nowrap;
}

.agent-context-popover__model {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--text-primary, #eee);
}

.agent-context-popover__bar-track {
    width: 48px;
    height: 6px;
    border-radius: 3px;
    background: rgba(148, 163, 184, 0.18);
    overflow: hidden;
    flex-shrink: 0;
}

.agent-context-popover__bar-fill {
    height: 100%;
    border-radius: 3px;
    transition: width 0.3s ease;
}

.agent-context-popover__pct {
    font-variant-numeric: tabular-nums;
    color: var(--text-primary, #eee);
    width: 34px;
    text-align: right;
    flex-shrink: 0;
}

.agent-context-popover__pct--unknown {
    color: var(--text-tertiary, #888);
    font-size: 0.68rem;
}

.agent-context-popover__inactive-badge {
    font-size: 0.6rem;
    color: var(--text-tertiary, #888);
    white-space: nowrap;
}

/* ── Settings Page ─────────────────────────────────────────────────────── */

.settings-page {
    /* v0.41.2 -- .oversight__body (wraps @Body for every page) is a capped flex column
       (flex:1; min-height:0) with NO overflow, and deliberately so: the channels page owns
       its own scrollport (.timeline-view) and a scroller on __body would break it. Settings
       has no such inner scroller, so its content (taller than the viewport once Chat Defaults
       is added) was clipped with no way to scroll. Make THIS page its own scrollport instead
       of touching __body: flex:1 to fill the capped column, min-height:0 so it may shrink below
       content size, overflow-y:auto to scroll the overflow. margin:auto still centres the 700px
       column horizontally; block margin becomes padding so the top gap survives inside a scroller. */
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    width: 100%;
    max-width: 700px;
    margin: 0 auto;
    padding: 2rem 1.5rem;
    color: var(--text-primary);
}

.settings-page__header h1 {
    font-size: 1.6rem;
    margin: 0 0 0.3rem;
}

.settings-page__subtitle {
    color: var(--text-secondary);
    font-size: 0.85rem;
    margin: 0 0 1.5rem;
}

.settings-section {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 1rem 1.2rem;
    margin-bottom: 1rem;
}

.settings-section h2 {
    font-size: 1rem;
    margin: 0 0 0.8rem;
}

.settings-row {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    margin-bottom: 0.6rem;
}

.settings-row label {
    flex: 0 0 200px;
    font-size: 0.85rem;
    color: var(--text-secondary);
}

.settings-row input[type="range"] {
    flex: 1;
    accent-color: var(--accent);
}

.settings-value {
    flex: 0 0 50px;
    text-align: right;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--accent);
}

.settings-page__actions {
    display: flex;
    gap: 1rem;
    margin-top: 1.5rem;
}

.settings-select {
    flex: 1;
    padding: 4px 8px;
    background: var(--bg-primary, #1a1a2e);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: 0.85rem;
}

.settings-btn {
    padding: 0.6rem 1.4rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg-secondary);
    color: var(--text-primary);
    font-size: 0.9rem;
    cursor: pointer;
    transition: background 0.2s ease;
}

.settings-btn:hover {
    background: var(--bg-tertiary, #333);
}

.settings-btn--save {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

/* ─────────────────────────────────────────────────────────────────────────────
   #129: TimelineView — Messages + Tool Events in Chronological Order
   ───────────────────────────────────────────────────────────────────────────── */

/* v0.29.3: this is the link between .agent-panel__timeline and the real scroller
   (.timeline-view). It must (a) participate as a flex CHILD so it inherits the parent's
   bound, and (b) carry min-height:0 for the same reason .timeline-view does — a flex item
   defaults to min-height:auto and will refuse to shrink below content height, which would
   break the bound right here and re-inflate the feed. `height:100%` alone is not enough:
   it resolves against a parent that is itself flex-sized. */
.timeline-view-wrapper {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    gap: 0;
}

.timeline-filter-bar {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: 0.75rem 0.75rem;
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
    flex-shrink: 0;
}

.timeline-filter-bar__section {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.timeline-filter-bar__section--end {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

/* v0.74.0: the Channels toolbar reuses AgentPanel's .agent-panel__filter-btn--clear for its
   "Clear DB" button. This only trims the button's vertical padding so it sits flush next to the
   shown-count status text inside the timeline filter bar's end section — all colours/hover/danger
   styling still come from the shared .agent-panel__filter-btn rules. */
.timeline-filter-bar__section--end .agent-panel__filter-btn--clear {
    padding: 0.3rem 0.75rem;
    font-size: 0.75rem;
}

.timeline-filter-bar__label {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    white-space: nowrap;
}

.timeline-filter-bar__checkboxes {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    align-items: center;
}

.timeline-filter-bar__checkbox {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    user-select: none;
    font-size: 0.8rem;
    color: var(--text-secondary);
    transition: color 0.2s ease;
}

.timeline-filter-bar__checkbox:hover {
    color: var(--text-primary);
}

.timeline-filter-bar__checkbox input[type="checkbox"] {
    width: 14px;
    height: 14px;
    cursor: pointer;
    accent-color: var(--accent);
}

.timeline-filter-bar__select {
    padding: 0.4rem 0.6rem;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    font-size: 0.8rem;
    cursor: pointer;
    transition: border-color 0.2s ease;
}

.timeline-filter-bar__select:hover,
.timeline-filter-bar__select:focus {
    border-color: var(--accent);
    outline: none;
}

.timeline-filter-bar__btn {
    padding: 0.5rem 1rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    cursor: pointer;
    font-size: 0.8rem;
    font-weight: 600;
    transition: all 0.2s ease;
    white-space: nowrap;
}

.timeline-filter-bar__btn:hover {
    border-color: #ef4444;
    color: #ef4444;
    background: rgba(239, 68, 68, 0.08);
}

.timeline-filter-bar__btn--clear {
    color: #ef4444;
}

/* Modal overlay and styles */

.timeline-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    z-index: 299;
}

.timeline-modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 300;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 8px;
    width: 400px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}

.timeline-modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem;
    border-bottom: 1px solid var(--border);
}

.timeline-modal__title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.5px;
}

.timeline-modal__close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 1.25rem;
    padding: 0.25rem 0.5rem;
    border-radius: 3px;
    transition: color 0.2s ease, background 0.2s ease;
    line-height: 1;
}

.timeline-modal__close:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.06);
}

.timeline-modal__body {
    padding: 1.25rem;
    font-size: 0.9rem;
    color: var(--text-primary);
    line-height: 1.5;
}

.timeline-modal__body p {
    margin: 0;
}

.timeline-modal__footer {
    display: flex;
    justify-content: flex-end;
    gap: 0.75rem;
    padding: 1rem;
    border-top: 1px solid var(--border);
}

.timeline-modal__btn {
    padding: 0.6rem 1.2rem;
    border: none;
    border-radius: 4px;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
}

.timeline-modal__btn--cancel {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-secondary);
    border: 1px solid var(--border);
}

.timeline-modal__btn--cancel:hover {
    background: rgba(255, 255, 255, 0.1);
    color: var(--text-primary);
}

.timeline-modal__btn--confirm {
    background: #ef4444;
    color: #fff;
}

.timeline-modal__btn--confirm:hover {
    background: #dc2626;
}

/* TimelineView itself */

.timeline-view {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 0.75rem;
    font-size: 0.875rem;
    flex: 1;
    /* v0.29.2 — THE ACTUAL SCROLL BUG.
       Measured in the live DOM: scrollHeight === clientHeight === 12731px, i.e. this element
       grew to fit ALL its content and had ZERO scrollable overhang, so `overflow-y: auto` had
       nothing to clip, no 'scroll' event ever fired, registerScrollWatcher's listener never
       ran, OnScrollStateChanged was never called — killing BOTH auto-follow and the
       jump-to-latest pill. The latch C# was correct all along; it sat on a container that
       could not scroll.
       Cause: a flex item defaults to `min-height: auto`, which refuses to shrink below content
       size, so `flex: 1` can never clamp it to the parent's height. (.timeline-view-wrapper
       already had min-height:0; this inner element was missed.)
       `min-height: 0` lets flex actually resolve the height -> overflow clips -> scrollHeight
       exceeds clientHeight -> scroll events fire -> the latch works. */
    min-height: 0;
    overflow-y: auto;
    /* Anchor for the absolutely-positioned "new messages" pill. */
    position: relative;
}

/* v0.29.2: the timeline's host inside AgentPanel is a flex item in the same chain and was
   computing h=12731px / overflow:visible, letting the feed punch straight through
   .agent-panel__body's max-height:400px so the PANEL scrolled instead of the feed.
   min-height:0 lets the bound apply; the feed itself owns the scrolling. */
/* v0.29.3: flex CHILD of .agent-panel__body (which is now a bounded, non-scrolling flex
   column). `flex: 1 1 auto` makes it absorb the space left over by the pinned stats/messages
   siblings; min-height:0 lets that bound actually apply instead of inflating to content. */
.agent-panel__timeline {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.timeline-view__empty {
    color: var(--text-secondary, #999);
    text-align: center;
    padding: 1rem;
    font-style: italic;
}

/* Message entries — iPhone-style bubbles: agent LEFT, user RIGHT */
.timeline-view__message {
    display: flex;
    gap: 0.5rem;
    align-items: flex-end;
}

/* Agent (outbound) stays left — no override needed */
/* User/inbound messages pushed to the right */
.timeline-view__message--in {
    flex-direction: row-reverse;
}

/* v0.36.0 ISSUE 3 LAYER 2 -- INTRINSIC SIZE, SO A LATE-DECODING PORTRAIT CANNOT MOVE THE FEED.

   This box was already 56x56 with `flex-shrink: 0`, which is correct for the AVATAR ELEMENT -- but
   when the element IS an `<img loading="lazy">` with no `width`/`height` ATTRIBUTES, the browser has
   no intrinsic ratio to reserve before the bytes arrive, and some engines lay the row out against
   the image's pre-decode size first. Each row that settles taller than it was measured adds height
   BELOW the viewport after `scrollToBottomInstant` already ran, which is precisely how auto-scroll
   ended up stranded mid-conversation.

   `aspect-ratio` + `min-height` + `object-fit` make the reserved box authoritative regardless of the
   decoded bitmap's real dimensions, so the row height is final at first layout. The matching
   `width`/`height` ATTRIBUTES are also set in the markup (ChannelChat.razor / TimelineView) -- the
   attributes are what the browser uses before CSS-relevant layout for the reservation, the CSS is
   what keeps it correct if a portrait is not square. Both halves are needed; neither alone is
   sufficient. `min-height` rather than only `height` because these rows can be flex children and
   `height` alone is overridable by `align-items: stretch` contexts. */
.timeline-view__avatar {
    width: 56px;
    height: 56px;
    /* Load-bearing (v0.36.0): guarantees a stable reserved box for lazy portraits. */
    min-width: 56px;
    min-height: 56px;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 50%;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    font-weight: 600;
    background: var(--bg-secondary, #222);
    color: var(--text-primary, #fff);
    border: 1px solid var(--border-color, #444);
}

.timeline-view__avatar--generic {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
}

.timeline-view__avatar--subagent {
    background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
    border: none;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.timeline-view__delivery-label {
    display: block;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted, #8b949e);
    margin-bottom: 0.25rem;
}

.timeline-view__avatar--agent {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
    border: none;
}

.timeline-view__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
}

.timeline-view__bubble-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    max-width: 60%;
}

.timeline-view__message--in .timeline-view__bubble-wrap {
    align-items: flex-end;
}

.timeline-view__sender {
    font-size: 0.65rem;
    font-weight: 600;
    color: #60a5fa;
    padding: 0 0.5rem;
}

.timeline-view__bubble {
    padding: 0.5rem 0.75rem;
    border-radius: 8px;
    word-wrap: break-word;
    line-height: 1.4;
    background: var(--bg-secondary, #222);
    color: var(--text-primary, #fff);
    border-left: 2px solid var(--accent, #667eea);
}

/* Agent bubble (left side) — msg-viz style */
.timeline-view__bubble--out {
    background: linear-gradient(135deg, #059669, #047857);
    border-radius: 16px;
    border-bottom-left-radius: 4px;
    border-left: none;
    color: #fff;
    padding: 0.5rem 0.75rem;
    font-size: 0.85rem;
    line-height: 1.5;
}

/* User bubble (right side) — msg-viz style */
.timeline-view__bubble--in {
    background: linear-gradient(135deg, #1e40af, #1d4ed8);
    border-radius: 16px;
    border-bottom-right-radius: 4px;
    border-left: none;
    border-right: none;
    color: #f0f9ff;
    padding: 0.5rem 0.75rem;
    font-size: 0.85rem;
    line-height: 1.5;
}

.timeline-view__bubble--empty {
    color: var(--text-secondary, #666);
    font-style: italic;
    opacity: 0.5;
}

.timeline-view__time {
    font-size: 0.7rem;
    color: var(--text-secondary, #999);
    padding: 0 0.5rem;
}

.timeline-view__expand {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
    padding-top: 4px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    font-size: 11px;
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
    transition: color 0.15s;
}
.timeline-view__expand:hover {
    color: var(--text-primary);
}
.timeline-view__expand-arrow {
    font-size: 9px;
}

/* Copy-to-clipboard button — hidden until bubble hover */
.timeline-view__bubble {
    position: relative;
}
.timeline-view__copy {
    position: absolute;
    top: 4px;
    right: 4px;
    opacity: 0;
    font-size: 12px;
    cursor: pointer;
    user-select: none;
    transition: opacity 0.15s;
    z-index: 2;
    line-height: 1;
}
.timeline-view__bubble:hover .timeline-view__copy {
    opacity: 0.7;
}
.timeline-view__bubble:hover .timeline-view__copy:hover {
    opacity: 1;
}
.timeline-view__copy--done {
    opacity: 1 !important;
    color: #4caf50;
}

/* v0.61.0: Subagent transcript replay — collapsible affordance nested under a SpawnViz pair.
   Indented to line up under the spawn/reply bubbles above it (56px avatar + 0.5rem gap match
   .timeline-view__message's own layout), with a left accent border echoing .tool-expando's
   "spawn" category color so a replayed subagent visually reads as an extension of the spawn
   bubbles rather than a new unrelated block. */
.timeline-view__subtranscript {
    margin-left: 64px;
    margin-top: 2px;
    margin-bottom: 4px;
    padding-left: 12px;
    border-left: 2px solid rgba(168, 85, 247, 0.3);
}
.timeline-view__subtranscript-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 0.5rem;
    font-size: 0.72rem;
    color: var(--text-secondary, #999);
    cursor: pointer;
    user-select: none;
    transition: color 0.15s;
}
.timeline-view__subtranscript-toggle:hover {
    color: var(--text-primary, #fff);
}
.timeline-view__subtranscript-dots {
    display: inline-block;
    animation: memory-dot-pulse 1.4s ease-in-out infinite;
}
.timeline-view__subtranscript-body {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 0.4rem 0.5rem 0.6rem;
    max-height: 420px;
    overflow-y: auto;
}
.timeline-view__subtranscript-empty {
    padding: 0.3rem 0.5rem 0.6rem;
    font-size: 0.72rem;
    font-style: italic;
    color: var(--text-secondary, #777);
    opacity: 0.7;
}
.timeline-view__subtranscript-error {
    padding: 0.3rem 0.5rem 0.6rem;
    font-size: 0.72rem;
    color: #ef4444;
}

/* Individual replayed rows — deliberately smaller/quieter than the parent's own bubbles so the
   nesting reads as "inside" rather than competing at the same visual weight. */
.timeline-view__subrow {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 0.3rem 0.5rem;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.03);
    font-size: 0.78rem;
    max-width: 90%;
}
.timeline-view__subrow--out {
    align-self: flex-start;
    border-left: 2px solid rgba(5, 150, 105, 0.5);
}
.timeline-view__subrow--in {
    align-self: flex-end;
    border-right: 2px solid rgba(29, 78, 216, 0.5);
}
.timeline-view__subrow-meta {
    font-size: 0.62rem;
    font-weight: 600;
    color: var(--text-secondary, #888);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* Tool event entries wrapper — expandable */
.timeline-view__tool-event-wrapper {
    display: flex;
    flex-direction: column;
    gap: 0;
    border-radius: 4px;
    transition: all 0.2s ease;
    cursor: pointer;
    margin-left: 64px;
    padding-left: 12px;
    border-left: 2px solid rgba(251, 191, 36, 0.3);
}

.timeline-view__tool-event-wrapper:hover .timeline-view__tool-event {
    background: rgba(251, 191, 36, 0.05);
}

.timeline-view__tool-event-wrapper--expanded {
    background: var(--bg-secondary, #222);
    border-left: 2px solid #fbbf24;
}

.timeline-view__tool-event-wrapper--expanded .timeline-view__tool-event--error {
    border-left-color: #ef4444;
}

/* Tool event entries — inline compact rows */
.timeline-view__tool-event {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg-secondary, #222);
    border-left: 2px solid #fbbf24;
    border-radius: 4px;
    font-size: 0.8rem;
    transition: background 0.2s ease;
}

.timeline-view__tool-event--collapsed {
    white-space: nowrap;
    overflow: hidden;
}

.timeline-view__tool-event--expanded {
    border-radius: 4px 4px 0 0;
    border-left: 2px solid #fbbf24;
    padding: 0.5rem 0.75rem;
}

.timeline-view__tool-event--error {
    border-left-color: #ef4444;
    background: rgba(239, 68, 68, 0.08);
}

.timeline-view__tool-event--error.timeline-view__tool-event--expanded {
    border-left-color: #ef4444;
}

.timeline-view__tool-expando-icon {
    margin-left: auto;
    flex-shrink: 0;
    color: var(--text-secondary);
    font-size: 0.7rem;
    transition: color 0.2s ease;
}

.timeline-view__tool-event:hover .timeline-view__tool-expando-icon {
    color: var(--text-primary);
}

/* Expanded detail panel */
.timeline-view__tool-detail-panel {
    background: var(--bg-secondary, #222);
    border-left: 2px solid #fbbf24;
    border-radius: 0 0 4px 4px;
    padding: 0.5rem 0.75rem;
    max-height: 300px;
    border-top: 1px solid var(--border);
}

.timeline-view__tool-event-wrapper--expanded .timeline-view__tool-detail-panel {
    display: flex;
    flex-direction: column;
}

.timeline-view__tool-detail-content {
    overflow-y: auto;
    font-family: 'Monaco', monospace;
    font-size: 0.75rem;
    color: var(--text-secondary);
    line-height: 1.4;
    word-break: break-all;
    white-space: pre-wrap;
    padding: 0.5rem 0;
}

.timeline-view__tool-icon {
    font-size: 1rem;
    flex-shrink: 0;
}

.timeline-view__tool-result--error {
    color: #ef4444;
}

.timeline-view__tool-result--success {
    color: #10b981;
}

.timeline-view__tool-name {
    font-weight: 600;
    color: var(--text-primary, #fff);
    min-width: max-content;
}

.timeline-view__tool-detail {
    color: var(--text-secondary, #aaa);
    margin-left: 0.25rem;
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.timeline-view__tool-duration {
    color: var(--text-tertiary, #888);
    font-size: 0.75rem;
    white-space: nowrap;
}

.timeline-view__tool-exitcode {
    color: #ef4444;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}

.timeline-view__tool-time {
    font-size: 0.7rem;
    color: var(--text-secondary, #999);
    margin-left: auto;
    white-space: nowrap;
}


/* ============================================================================
   CARD CLIPBOARD — realistic clipboard on agent tiles
   ============================================================================ */

.agent-node {
    position: relative;
}

.card-clipboard {
    position: absolute;
    top: -20px;
    left: 50%;
    min-width: 80px;
    max-width: 120px;
    z-index: 20;
    pointer-events: auto;
    filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5));
    cursor: grab;
    touch-action: none;
    user-select: none;
    transition: filter 0.15s ease;
}

.card-clipboard:active {
    cursor: grabbing;
    filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.6));
}

/* Wooden board backing */
.card-clipboard__board {
    background: linear-gradient(170deg, #a0713c 0%, #8b5e34 20%, #7a4f2b 50%, #6d4425 80%, #5c3a20 100%);
    border-radius: 6px;
    padding: 4px 4px 6px;
    position: relative;
}

/* Wood grain texture overlay */
.card-clipboard__board::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 6px;
    background: repeating-linear-gradient(
        175deg,
        transparent,
        transparent 3px,
        rgba(0,0,0,0.04) 3px,
        rgba(0,0,0,0.04) 4px
    );
    pointer-events: none;
}

/* Metal clip at top center */
.card-clipboard__clip {
    width: 24px;
    height: 14px;
    margin: 0 auto -4px;
    position: relative;
    z-index: 25;
}

.card-clipboard__clip-body {
    width: 22px;
    height: 12px;
    background: linear-gradient(180deg, #d1d5db 0%, #9ca3af 40%, #b0b5bc 60%, #9ca3af 100%);
    border-radius: 4px 4px 2px 2px;
    border: 1px solid #6b7280;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.4);
    display: flex;
    align-items: center;
    justify-content: center;
}

.card-clipboard__clip-initial {
    font-size: 6px;
    font-weight: 800;
    font-family: 'Monaco', 'Menlo', monospace;
    color: #4b5563;
    text-shadow: 0 0.5px 0 rgba(255,255,255,0.5);
    letter-spacing: 0.5px;
    line-height: 1;
}

/* Paper sheet */
.card-clipboard__paper {
    background: linear-gradient(180deg, #fdf8e8 0%, #f5edd0 100%);
    border-radius: 3px;
    padding: 6px 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 0;
    position: relative;
    min-height: 30px;
}

/* Subtle paper texture */
.card-clipboard__paper::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 3px;
    background: url("data:image/svg+xml,%3Csvg width='4' height='4' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='4' height='4' fill='%23f0e8cf' opacity='0.3'/%3E%3Crect x='1' y='1' width='1' height='1' fill='%23e8dfc0' opacity='0.2'/%3E%3C/svg%3E");
    pointer-events: none;
}

.card-clipboard__entry {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 3px;
    font-size: 10px;
    font-weight: 600;
    color: #4a3520;
    cursor: default;
    white-space: nowrap;
    transition: background 0.15s ease, opacity 0.5s ease;
    position: relative;
}

.card-clipboard__entry:hover {
    background: rgba(160, 113, 60, 0.1);
    border-radius: 2px;
}

.card-clipboard__entry + .card-clipboard__entry {
    border-top: 1px dotted rgba(160, 130, 80, 0.35);
}

.card-clipboard__heading {
    font-size: 8px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #7a5230;
    padding: 4px 3px 2px;
    border-bottom: 1px solid rgba(160, 130, 80, 0.4);
    margin-bottom: 1px;
    pointer-events: none;
    user-select: none;
}

.card-clipboard__icon {
    font-size: 11px;
    flex-shrink: 0;
}

.card-clipboard__text {
    font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
    font-size: 9px;
    font-weight: 700;
    color: #3d2b14;
    overflow: hidden;
    text-overflow: ellipsis;
}

.card-clipboard__text--link {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    border-bottom: 1px dotted rgba(61, 43, 20, 0.4);
    transition: border-color 0.15s ease, opacity 0.15s ease;
}

.card-clipboard__text--link:hover {
    border-bottom-color: rgba(61, 43, 20, 0.9);
    opacity: 0.8;
}



/* ============================================================================
   GATEWAY CONNECTION INDICATOR (UC #194)
   ============================================================================ */

.gateway-indicator {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: 4px;
    background: rgba(139, 92, 246, 0.1);
    border: 1px solid rgba(139, 92, 246, 0.2);
    font-size: 12px;
    font-weight: 500;
    transition: all 0.3s ease;
}

.gateway-indicator__dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
    transition: all 0.3s ease;
    box-shadow: 0 0 8px currentColor;
}

.gateway-indicator__label {
    white-space: nowrap;
    letter-spacing: 0.3px;
}

/* Connected state — healthy, green pulse */
.gateway-indicator--connected {
    color: #22c55e;
    background: rgba(34, 197, 94, 0.1);
    border-color: rgba(34, 197, 94, 0.2);
}

.gateway-indicator--connected .gateway-indicator__dot {
    background: #22c55e;
    animation: pulse-fade 2s ease-in-out infinite;
}

/* Reconnecting state — amber, faster pulse (grace period) */
.gateway-indicator--reconnecting {
    color: #f59e0b;
    background: rgba(245, 158, 11, 0.1);
    border-color: rgba(245, 158, 11, 0.2);
}

.gateway-indicator--reconnecting .gateway-indicator__dot {
    background: #f59e0b;
    animation: pulse-fade 0.8s ease-in-out infinite;
}

/* Disconnected state — red, persistent */
.gateway-indicator--disconnected {
    color: #ef4444;
    background: rgba(239, 68, 68, 0.1);
    border-color: rgba(239, 68, 68, 0.3);
}

.gateway-indicator--disconnected .gateway-indicator__dot {
    background: #ef4444;
    animation: none;
    box-shadow: 0 0 12px rgba(239, 68, 68, 0.4);
}

/* Reconnected state — green with brief success flash */
.gateway-indicator--reconnected {
    color: #22c55e;
    background: rgba(34, 197, 94, 0.2);
    border-color: rgba(34, 197, 94, 0.4);
}

.gateway-indicator--reconnected .gateway-indicator__dot {
    background: #22c55e;
    animation: pulse-success-flash 0.4s ease-out;
}

@keyframes pulse-success-flash {
    0% {
        box-shadow: 0 0 8px #22c55e, 0 0 16px rgba(34, 197, 94, 0.5);
    }
    100% {
        box-shadow: 0 0 8px currentColor;
    }
}

/* ============================================================================
   TOAST NOTIFICATIONS (Bottom-right corner)
   ============================================================================ */

.toast-container {
    position: fixed;
    bottom: 24px;
    right: 24px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    z-index: 9999;
    pointer-events: none;
    max-width: 420px;
}

.toast {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    border-radius: 6px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
    animation: toast-slide-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
    pointer-events: auto;
    max-width: 100%;
    word-wrap: break-word;
}

.toast__content {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
}

.toast__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 14px;
    font-weight: 600;
    width: 20px;
    height: 20px;
}

.toast__message {
    font-size: 13px;
    line-height: 1.4;
    color: var(--text-primary);
    flex: 1;
}

.toast__close {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    border-radius: 3px;
    transition: all 0.2s ease;
    font-size: 16px;
    padding: 0;
}

.toast__close:hover {
    background: rgba(139, 92, 246, 0.1);
    color: var(--text-primary);
}

/* Info toast — neutral */
.toast--info {
    border-color: rgba(59, 130, 246, 0.3);
    background: rgba(59, 130, 246, 0.08);
}

.toast--info .toast__icon {
    color: #3b82f6;
}

/* Success toast — green */
.toast--success {
    border-color: rgba(34, 197, 94, 0.3);
    background: rgba(34, 197, 94, 0.08);
}

.toast--success .toast__icon {
    color: #22c55e;
}

/* Warning toast — amber */
.toast--warning {
    border-color: rgba(245, 158, 11, 0.3);
    background: rgba(245, 158, 11, 0.08);
}

.toast--warning .toast__icon {
    color: #f59e0b;
}

/* Error toast — red */
.toast--error {
    border-color: rgba(239, 68, 68, 0.3);
    background: rgba(239, 68, 68, 0.08);
}

.toast--error .toast__icon {
    color: #ef4444;
}

@keyframes toast-slide-in {
    from {
        opacity: 0;
        transform: translateX(420px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

@keyframes toast-slide-out {
    from {
        opacity: 1;
        transform: translateX(0);
    }
    to {
        opacity: 0;
        transform: translateX(420px);
    }
}

/* ============================================================================
   GATEWAY SIMULATION TEST (QA automation mode)
   ============================================================================ */

.gateway-simulation-test {
    position: fixed;
    bottom: 100px;
    left: 24px;
    z-index: 9998;
    font-size: 12px;
}

.gateway-simulation-test__summary {
    cursor: pointer;
    padding: 8px 12px;
    background: rgba(139, 92, 246, 0.15);
    border: 1px solid rgba(139, 92, 246, 0.3);
    border-radius: 4px;
    color: var(--accent);
    font-weight: 600;
    user-select: none;
    transition: all 0.2s ease;
}

.gateway-simulation-test__summary:hover {
    background: rgba(139, 92, 246, 0.25);
    border-color: rgba(139, 92, 246, 0.5);
}

.gateway-simulation-test__content {
    margin-top: 12px;
    padding: 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.gateway-simulation-test__label {
    margin: 0;
    color: var(--text-secondary);
    font-weight: 500;
}

.gateway-simulation-test__input {
    padding: 6px 8px;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    font-size: 12px;
    font-family: monospace;
}

.gateway-simulation-test__buttons {
    display: flex;
    gap: 8px;
}

.gateway-simulation-test__btn {
    flex: 1;
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    font-size: 11px;
    font-weight: 600;
    transition: all 0.2s ease;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.gateway-simulation-test__btn--simulate {
    border-color: rgba(245, 158, 11, 0.3);
    color: #f59e0b;
}

.gateway-simulation-test__btn--simulate:hover {
    background: rgba(245, 158, 11, 0.15);
    border-color: rgba(245, 158, 11, 0.5);
}

.gateway-simulation-test__btn--stop {
    border-color: rgba(34, 197, 94, 0.3);
    color: #22c55e;
}

.gateway-simulation-test__btn--stop:hover {
    background: rgba(34, 197, 94, 0.15);
    border-color: rgba(34, 197, 94, 0.5);
}

.gateway-simulation-test__info {
    margin: 0;
    padding: 8px;
    background: rgba(59, 130, 246, 0.08);
    border: 1px solid rgba(59, 130, 246, 0.2);
    border-radius: 3px;
    color: var(--text-secondary);
    font-family: monospace;
    font-size: 11px;
}

.gateway-simulation-test__note {
    margin: 0;
    color: var(--text-secondary);
    font-size: 11px;
    font-style: italic;
}

/* Tool badge styling */

.tool-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}

.tool-badge--shell {
    background: rgba(59, 130, 246, 0.15);
    color: #3b82f6;
    border: 1px solid rgba(59, 130, 246, 0.3);
}

.tool-badge--spawn {
    background: rgba(168, 85, 247, 0.15);
    color: #a855f7;
    border: 1px solid rgba(168, 85, 247, 0.3);
}

.tool-badge--message {
    background: rgba(34, 197, 94, 0.15);
    color: #22c55e;
    border: 1px solid rgba(34, 197, 94, 0.3);
}

.tool-badge--cron {
    background: rgba(245, 158, 11, 0.15);
    color: #f59e0b;
    border: 1px solid rgba(245, 158, 11, 0.3);
}

.tool-badge--web {
    background: rgba(139, 92, 246, 0.15);
    color: #8b5cf6;
    border: 1px solid rgba(139, 92, 246, 0.3);
}

.tool-badge--memory {
    background: rgba(236, 72, 153, 0.15);
    color: #ec4899;
    border: 1px solid rgba(236, 72, 153, 0.3);
}

.tool-badge--file {
    background: rgba(14, 165, 233, 0.15);
    color: #0ea5e9;
    border: 1px solid rgba(14, 165, 233, 0.3);
}

.tool-badge--media {
    background: rgba(249, 115, 22, 0.15);
    color: #f97316;
    border: 1px solid rgba(249, 115, 22, 0.3);
}

.tool-badge--default {
    background: rgba(148, 163, 184, 0.15);
    color: #94a3b8;
    border: 1px solid rgba(148, 163, 184, 0.3);
}

/* Markdown content styling */

.markdown-content {
    display: flex;
    flex-direction: column;
    gap: 8px;
    word-wrap: break-word;
    overflow-wrap: break-word;
}

.markdown-content p {
    margin: 0;
    line-height: 1.5;
}

.markdown-content code {
    background: rgba(15, 23, 42, 0.8);
    color: #22c55e;
    padding: 2px 6px;
    border-radius: 3px;
    font-family: 'Monaco', 'Menlo', monospace;
    font-size: 12px;
}

.markdown-content pre {
    background: rgba(15, 23, 42, 0.95);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 12px;
    overflow-x: auto;
    font-family: 'Monaco', 'Menlo', monospace;
    font-size: 11px;
    line-height: 1.4;
    margin: 0;
}

.markdown-content pre code {
    background: none;
    color: #22c55e;
    padding: 0;
    border-radius: 0;
}

.markdown-content blockquote {
    margin: 0;
    padding-left: 12px;
    border-left: 3px solid var(--accent);
    color: var(--text-secondary);
    font-style: italic;
}

.markdown-content ul,
.markdown-content ol {
    margin: 0;
    padding-left: 24px;
}

.markdown-content li {
    margin: 4px 0;
}

.markdown-content strong {
    color: #fff;
    font-weight: 600;
}

.markdown-content em {
    font-style: italic;
    color: var(--text-secondary);
}

.markdown-content h1,
.markdown-content h2,
.markdown-content h3,
.markdown-content h4,
.markdown-content h5,
.markdown-content h6 {
    margin: 8px 0 4px 0;
    color: #fff;
    font-weight: 600;
}

.markdown-content h1 { font-size: 18px; }
.markdown-content h2 { font-size: 16px; }
.markdown-content h3 { font-size: 14px; }
.markdown-content h4,
.markdown-content h5,
.markdown-content h6 { font-size: 13px; }

.markdown-content table {
    border-collapse: collapse;
    width: 100%;
    margin: 8px 0;
    font-size: 12px;
}

.markdown-content table th,
.markdown-content table td {
    border: 1px solid var(--border);
    padding: 8px;
    text-align: left;
}

.markdown-content table th {
    background: rgba(139, 92, 246, 0.1);
    color: #8b5cf6;
    font-weight: 600;
}

.markdown-content a {
    color: #3b82f6;
    text-decoration: none;
}

.markdown-content a:hover {
    text-decoration: underline;
}

/* Update timeline-view tool styling to use badges */

.timeline-view__tool-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}

.activity-feed__tool-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}

/* ============================================================
   ToolExecutionCard — Mission Control HUD Component
   ============================================================ */

/* Keyframe animations */
@keyframes hud-pulse {
    0% {
        box-shadow: 0 0 8px rgba(59, 130, 246, 0.6), inset 0 0 8px rgba(59, 130, 246, 0.3);
    }
    50% {
        box-shadow: 0 0 20px rgba(59, 130, 246, 0.9), inset 0 0 12px rgba(59, 130, 246, 0.5);
    }
    100% {
        box-shadow: 0 0 8px rgba(59, 130, 246, 0.6), inset 0 0 8px rgba(59, 130, 246, 0.3);
    }
}

@keyframes hud-success-glow {
    0% {
        box-shadow: 0 0 8px rgba(34, 197, 94, 0.6), inset 0 0 8px rgba(34, 197, 94, 0.3);
    }
    100% {
        box-shadow: 0 0 4px rgba(34, 197, 94, 0.4), inset 0 0 4px rgba(34, 197, 94, 0.2);
    }
}

@keyframes checkmark-scale {
    0% {
        transform: scale(0);
        opacity: 0;
    }
    50% {
        transform: scale(1.2);
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}

@keyframes error-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-4px); }
    75% { transform: translateX(4px); }
}

@keyframes pulse-dot {
    0%, 100% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.3);
        opacity: 0.6;
    }
}

/* Main card container */
.tool-execution-card {
    background: linear-gradient(135deg, rgba(15, 23, 42, 0.95), rgba(26, 32, 44, 0.95));
    border: 2px solid rgba(59, 130, 246, 0.3);
    border-radius: 8px;
    padding: 16px;
    margin: 12px 0;
    font-family: 'Monaco', 'Menlo', monospace;
    transition: all 0.3s ease;
    position: relative;
    overflow: hidden;
}

/* Executing state — pulsing blue */
.tool-execution-card.executing {
    border-color: rgba(59, 130, 246, 0.8);
    background: linear-gradient(135deg, rgba(30, 58, 138, 0.15), rgba(15, 23, 42, 0.95));
    animation: hud-pulse 2s ease-in-out infinite;
}

/* Success state — green checkmark */
.tool-execution-card.success {
    border-color: rgba(34, 197, 94, 0.6);
    background: linear-gradient(135deg, rgba(16, 87, 38, 0.1), rgba(15, 23, 42, 0.95));
    animation: hud-success-glow 1s ease-out;
}

/* Error state — red with shake */
.tool-execution-card.error {
    border-color: rgba(239, 68, 68, 0.7);
    background: linear-gradient(135deg, rgba(127, 29, 29, 0.15), rgba(15, 23, 42, 0.95));
    animation: error-shake 0.5s ease;
}

/* Idle state */
.tool-execution-card.idle {
    border-color: rgba(51, 65, 85, 0.6);
}

/* Header section */
.tool-execution-card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid rgba(59, 130, 246, 0.2);
}

.tool-execution-card__title {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #3b82f6;
}

/* Indicator section with state icon and status text */
.tool-execution-card__indicator {
    display: flex;
    align-items: center;
    gap: 8px;
    position: relative;
}

/* Pulsing indicator for executing state */
.tool-execution-card__pulse {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #3b82f6;
    animation: pulse-dot 1.5s ease-in-out infinite;
}

/* Checkmark for success state */
.tool-execution-card__checkmark {
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #22c55e;
    font-weight: 900;
    font-size: 16px;
    animation: checkmark-scale 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Error indicator */
.tool-execution-card__error-indicator {
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ef4444;
    font-weight: 900;
    font-size: 16px;
    animation: error-shake 0.5s ease;
}

.tool-execution-card__status-text {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.tool-execution-card.executing .tool-execution-card__status-text {
    color: #3b82f6;
}

.tool-execution-card.success .tool-execution-card__status-text {
    color: #22c55e;
}

.tool-execution-card.error .tool-execution-card__status-text {
    color: #ef4444;
}

/* Section titles */
.tool-execution-card__section-title {
    margin: 0 0 8px 0;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #8b5cf6;
    opacity: 0.9;
}

/* Input parameters section — dims during execution */
.tool-execution-card__inputs {
    margin-bottom: 12px;
    transition: opacity 0.3s ease, color 0.3s ease;
}

.tool-execution-card__inputs.dimmed {
    opacity: 0.5;
    color: var(--text-secondary);
}

.tool-execution-card__param-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.tool-execution-card__param {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 8px;
    background: rgba(139, 92, 246, 0.08);
    border-left: 2px solid rgba(139, 92, 246, 0.4);
    border-radius: 4px;
    font-size: 12px;
}

.tool-execution-card__param-key {
    color: #a78bfa;
    font-weight: 600;
    min-width: 80px;
}

.tool-execution-card__param-value {
    color: var(--text-secondary);
    font-family: 'Courier New', monospace;
    word-break: break-all;
}

.tool-execution-card__empty {
    color: var(--text-secondary);
    font-size: 12px;
    margin: 0;
    font-style: italic;
}

/* Result section — inline display after success */
.tool-execution-card__result {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 8px;
    background: rgba(34, 197, 94, 0.08);
    border-left: 2px solid rgba(34, 197, 94, 0.5);
    border-radius: 4px;
    animation: slideIn 0.4s ease;
}

@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateX(-12px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

.tool-execution-card__result-label {
    color: #22c55e;
    font-weight: 700;
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    min-width: 50px;
}

.tool-execution-card__result-value {
    color: #86efac;
    font-family: 'Courier New', monospace;
    font-size: 12px;
    flex: 1;
    word-break: break-all;
}

.tool-execution-card__duration {
    color: var(--text-secondary);
    font-size: 11px;
    margin-left: auto;
    white-space: nowrap;
}

/* Error section */
.tool-execution-card__error {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 8px 8px;
    background: rgba(239, 68, 68, 0.08);
    border-left: 2px solid rgba(239, 68, 68, 0.5);
    border-radius: 4px;
    animation: slideIn 0.4s ease;
}

.tool-execution-card__error-label {
    color: #ef4444;
    font-weight: 700;
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    min-width: 50px;
    flex-shrink: 0;
}

.tool-execution-card__error-message {
    color: #fca5a5;
    font-family: 'Courier New', monospace;
    font-size: 12px;
    word-break: break-word;
}

.tool-execution-card {
    margin-bottom: 8px;
    border: 1px solid var(--color-border);
    border-radius: 4px;
    background: var(--color-bg-primary);
    overflow: hidden;
    cursor: pointer;
    transition: all 0.2s ease;
}

.tool-execution-card:hover {
    background: rgba(139, 92, 246, 0.05);
}

.tool-execution-card--expanded {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
    border-color: var(--color-accent);
}

.tool-execution-card__header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background: transparent;
    border: none;
    font-size: 14px;
    font-weight: 500;
    text-align: left;
    user-select: none;
}

/* Icon with animation based on state */
.tool-execution-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    font-size: 16px;
    border-radius: 4px;
    flex-shrink: 0;
}

/* Tool call executing: Orange rotating dotted border */
.tool-execution-card__icon--executing {
    border: 2px dashed var(--color-status-tooluse);
    animation: rotate-border 2s linear infinite;
}

/* Thinking executing: Blue pulsing */
.tool-execution-card__icon--thinking {
    border: 2px solid var(--color-status-thinking);
    animation: pulse-thinking 1.5s ease-in-out infinite;
}

/* Success state: Green checkmark */
.tool-execution-card__icon--success {
    background: rgba(16, 185, 129, 0.15);
    color: var(--color-status-active);
}

/* Error state: Red with shake */
.tool-execution-card__icon--error {
    background: rgba(239, 68, 68, 0.15);
    color: #ef4444;
    animation: shake 0.5s ease-in-out;
}

.tool-execution-card__name {
    flex: 1;
    color: var(--color-text-primary);
    font-weight: 600;
}

.tool-execution-card__status {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--color-text-secondary);
    font-size: 12px;
    font-weight: 400;
}

.tool-execution-card__duration {
    font-family: 'Monaco', 'Courier New', monospace;
    font-size: 12px;
}

.tool-execution-card__status-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    font-weight: 600;
    font-size: 12px;
}

.tool-execution-card__status-badge--success {
    background: rgba(16, 185, 129, 0.2);
    color: var(--color-status-active);
}

.tool-execution-card__status-badge--error {
    background: rgba(239, 68, 68, 0.2);
    color: #ef4444;
}

.tool-execution-card__expand-icon {
    display: inline-block;
    font-size: 10px;
    color: var(--color-text-secondary);
    transition: transform 0.2s ease;
    flex-shrink: 0;
}

.tool-execution-card--expanded .tool-execution-card__expand-icon {
    transform: rotate(0deg);
}

/* Expanded details section */
.tool-execution-card__details {
    padding: 12px 16px;
    background: var(--color-bg-secondary);
    border-top: 1px solid var(--color-border);
    animation: slideDown 0.2s ease;
}

.tool-execution-card__detail-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 8px 0;
    font-size: 13px;
    line-height: 1.4;
}

.tool-execution-card__detail-row--error {
    color: #ef4444;
}

.tool-execution-card__detail-label {
    flex: 0 0 auto;
    color: var(--color-text-secondary);
    font-weight: 600;
    min-width: 70px;
}

.tool-execution-card__detail-value {
    flex: 1;
    color: var(--color-text-primary);
    word-break: break-word;
}

.tool-execution-card__detail-code {
    flex: 1;
    display: block;
    padding: 6px 10px;
    background: rgba(0, 0, 0, 0.2);
    border-left: 2px solid var(--color-accent);
    border-radius: 3px;
    font-family: 'Monaco', 'Courier New', monospace;
    font-size: 12px;
    color: var(--color-text-primary);
    overflow-x: auto;
    word-break: break-all;
}

.tool-execution-card__type-badge {
    display: inline-block;
    padding: 3px 8px;
    border-radius: 3px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
}

.tool-execution-card__type-badge--tool-use {
    background: rgba(59, 130, 246, 0.2);
    color: var(--color-status-tooluse);
}

.tool-execution-card__type-badge--thinking {
    background: rgba(245, 158, 11, 0.2);
    color: var(--color-status-thinking);
}

.tool-execution-card__type-badge--message {
    background: rgba(16, 185, 129, 0.2);
    color: var(--color-status-active);
}

.tool-execution-card__type-badge--error {
    background: rgba(239, 68, 68, 0.2);
    color: #ef4444;
}

.tool-execution-card__time-running {
    animation: pulse-fade 1s ease-in-out infinite;
}

/* ============================================================================
   TOOL CARD — INLINE EXECUTION FEEDBACK
   ============================================================================ */

.tool-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    margin: 6px 0;
    border: 2px dashed transparent;
    border-radius: 6px;
    background: linear-gradient(135deg, rgba(15, 23, 42, 0.8), rgba(26, 32, 44, 0.8));
    font-family: 'Monaco', 'Menlo', monospace;
    font-size: 13px;
    transition: all 0.3s ease;
    overflow: hidden;
}

/* Executing state: Orange dashed border with 360° rotation animation */
.tool-card--executing {
    border: 2px dashed #ff9800;
    background: linear-gradient(135deg, rgba(255, 152, 0, 0.08), rgba(26, 32, 44, 0.9));
}

/* Complete state: Green tint with checkmark */
.tool-card--complete {
    border: 2px solid rgba(34, 197, 94, 0.5);
    background: linear-gradient(135deg, rgba(34, 197, 94, 0.08), rgba(26, 32, 44, 0.9));
}

/* Error state: Red tint with shake on first render */
.tool-card--error {
    border: 2px solid rgba(239, 68, 68, 0.6);
    background: linear-gradient(135deg, rgba(239, 68, 68, 0.08), rgba(26, 32, 44, 0.9));
    animation: tool-card-error-shake 0.5s ease;
}

/* Icon (left): 24px, consistent sizing */
.tool-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    font-size: 16px;
    flex-shrink: 0;
}

/* Content (middle): Single-line, truncate with ellipsis */
.tool-card__content {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tool-card__message {
    color: var(--text-primary);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tool-card__result-summary {
    color: #86efac;
    font-weight: 400;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tool-card__error-message {
    color: #fca5a5;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Status Icon (right): Spinner, checkmark, or error X */
.tool-card__status-icon {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    white-space: nowrap;
}

.tool-card__spinner {
    display: inline-block;
    width: 12px;
    height: 12px;
    border: 2px solid rgba(255, 152, 0, 0.3);
    border-top: 2px solid #ff9800;
    border-radius: 50%;
    animation: tool-card-spin 1s linear infinite;
}

.tool-card__checkmark {
    color: #22c55e;
    font-weight: 700;
    font-size: 14px;
    animation: tool-card-checkmark-scale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.tool-card__error-icon {
    color: #ef4444;
    font-weight: 700;
    font-size: 14px;
}

.tool-card__duration {
    color: var(--text-secondary);
    font-size: 11px;
    font-weight: 400;
    font-family: 'Courier New', monospace;
}

/* ============================================================================
   TOOL CARD — MAIN ROW WRAPPER
   Used for all cards; allows column layout when expanded content is present.
   ============================================================================ */

.tool-card__main-row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
}

/* ============================================================================
   TOOL CARD — MEMORY TOOL EXPANDED CONTENT
   Shown below the main row for memory_search / memory_get results.
   ============================================================================ */

/* Column layout when expanded content is present */
.tool-card--with-content {
    flex-direction: column;
    align-items: stretch;
}

.tool-card__result-content {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid rgba(34, 197, 94, 0.2);
    animation: tool-card-content-fade 0.4s ease;
}

.tool-card__result-text {
    margin: 0;
    font-family: 'Monaco', 'Menlo', monospace;
    font-size: 11px;
    color: #86efac;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 200px;
    overflow-y: auto;
    line-height: 1.5;
}

@keyframes tool-card-content-fade {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================================
   TOOL CARD ANIMATIONS
   ============================================================================ */

/* Border rotate: 360° smooth continuous rotation for executing state */
/* tool-card-border-rotate removed — static dashed border only */

/* Spin: Loading spinner for executing state */
@keyframes tool-card-spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

/* Checkmark scale: Subtle pop animation on completion */
@keyframes tool-card-checkmark-scale {
    0% {
        transform: scale(0);
        opacity: 0;
    }
    50% {
        transform: scale(1.2);
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}

/* Error shake: Subtle shake on first render of error state */
@keyframes tool-card-error-shake {
    0%, 100% {
        transform: translateX(0);
    }
    25% {
        transform: translateX(-4px);
    }
    75% {
        transform: translateX(4px);
    }
}

/* ============================================================================
   DEMO TOOL EXECUTION
   ============================================================================ */

.demo-tool-execution {
    padding: 24px;
    background: var(--color-bg-primary);
}

.demo-tool-execution__title {
    margin: 0 0 24px 0;
    font-size: 24px;
    font-weight: 700;
    color: var(--color-text-primary);
}

.demo-tool-execution__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
    gap: 24px;
    margin-bottom: 32px;
}

.demo-tool-execution__section {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.demo-tool-execution__subtitle {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--color-text-secondary);
}

.demo-tool-execution__card {
    background: var(--color-bg-secondary);
    padding: 8px;
    border-radius: 4px;
    border: 1px solid var(--color-border);
}

.demo-tool-execution__legend {
    padding: 16px 20px;
    background: var(--color-bg-secondary);
    border: 1px solid var(--color-border);
    border-radius: 4px;
    color: var(--color-text-primary);
}

.demo-tool-execution__legend h3 {
    margin: 0 0 12px 0;
    font-size: 14px;
    font-weight: 600;
}

.demo-tool-execution__legend ul {
    margin: 0;
    padding-left: 20px;
    list-style: disc;
}

.demo-tool-execution__legend li {
    margin-bottom: 8px;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.demo-tool-execution__legend li strong {
    color: var(--color-text-primary);
    font-weight: 600;
}

/* ============================================================================
   ANIMATIONS
   ============================================================================ */

@keyframes rotate-border {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

@keyframes pulse-thinking {
    0%, 100% {
        opacity: 1;
        box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.3);
    }
    50% {
        opacity: 0.8;
        box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
    }
}

@keyframes shake {
    0%, 100% {
        transform: translateX(0);
    }
    25% {
        transform: translateX(-4px);
    }
    75% {
        transform: translateX(4px);
    }
}

.demo-tool-execution__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
    gap: 24px;
    margin-bottom: 32px;
}

.demo-tool-execution__section {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.demo-tool-execution__subtitle {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--color-text-secondary);
}

.demo-tool-execution__card {
    background: var(--color-bg-secondary);
    padding: 8px;
    border-radius: 4px;
    border: 1px solid var(--color-border);
}

.demo-tool-execution__legend {
    padding: 16px 20px;
    background: var(--color-bg-secondary);
    border: 1px solid var(--color-border);
    border-radius: 4px;
    color: var(--color-text-primary);
}

.demo-tool-execution__legend h3 {
    margin: 0 0 12px 0;
    font-size: 14px;
    font-weight: 600;
}

.demo-tool-execution__legend ul {
    margin: 0;
    padding-left: 20px;
    list-style: disc;
}

.demo-tool-execution__legend li {
    margin-bottom: 8px;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.demo-tool-execution__legend li strong {
    color: var(--color-text-primary);
    font-weight: 600;
}

/* ============================================================================
   ANIMATIONS (Dedup)
   ============================================================================ */

@keyframes rotate-border {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

@keyframes pulse-thinking {
    0%, 100% {
        opacity: 1;
        box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.3);
    }
    50% {
        opacity: 0.8;
        box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
    }
}

@keyframes shake {
    0%, 100% {
        transform: translateX(0);
    }
    25% {
        transform: translateX(-4px);
    }
    75% {
        transform: translateX(4px);
    }
}

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

@keyframes pulse-ring {
    0% {
        r: 10px;
        opacity: 1;
    }
    100% {
        r: 22px;
        opacity: 0;
    }
}

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

/* ============================================================================
   SCROLLBAR (WebKit)
   ============================================================================ */

::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--color-border);
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--color-text-secondary);
}

/* ============================================================================
   TOOL EXPANDO
   ============================================================================ */

/* Tool Expando */
.tool-expando {
    display: flex;
    flex-direction: column;
    gap: 0;
    border-radius: 4px;
    cursor: pointer;
    margin-left: 64px;
    padding-left: 12px;
    border-left: 2px solid rgba(148, 163, 184, 0.3);
    transition: all 0.2s ease;
}
.tool-expando:hover .tool-expando__header { background: rgba(255,255,255,0.04); }
.tool-expando--open { background: var(--bg-secondary, #222); }

/* Per-category left border accents */
.tool-expando--shell    { border-left-color: rgba(59, 130, 246, 0.4); }
.tool-expando--spawn    { border-left-color: rgba(168, 85, 247, 0.4); }
.tool-expando--message  { border-left-color: rgba(34, 197, 94, 0.4); }
.tool-expando--cron     { border-left-color: rgba(245, 158, 11, 0.4); }
.tool-expando--web      { border-left-color: rgba(139, 92, 246, 0.4); }
.tool-expando--memory   { border-left-color: rgba(236, 72, 153, 0.4); }
.tool-expando--file     { border-left-color: rgba(14, 165, 233, 0.4); }
.tool-expando--media    { border-left-color: rgba(249, 115, 22, 0.4); }

/* Error modifier — overrides category left border with red */
.tool-expando--error { border-left-color: #ef4444 !important; }

/* Result modifier — subagent tool_result rows (dashed border distinguishes them from the
   preceding tool_call row without overriding the category color) */
.tool-expando--result { border-left-style: dashed; }

/* Stronger accent when open */
.tool-expando--open.tool-expando--shell    { border-left-color: #3b82f6; }
.tool-expando--open.tool-expando--spawn    { border-left-color: #a855f7; }
.tool-expando--open.tool-expando--message  { border-left-color: #22c55e; }
.tool-expando--open.tool-expando--cron     { border-left-color: #f59e0b; }
.tool-expando--open.tool-expando--web      { border-left-color: #8b5cf6; }
.tool-expando--open.tool-expando--memory   { border-left-color: #ec4899; }
.tool-expando--open.tool-expando--file     { border-left-color: #0ea5e9; }
.tool-expando--open.tool-expando--media    { border-left-color: #f97316; }

.tool-expando__header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg-secondary, #222);
    border-radius: 4px;
    font-size: 0.8rem;
    transition: background 0.2s ease;
}
.tool-expando--open .tool-expando__header { border-radius: 4px 4px 0 0; }

/* ── Agent-side (right) tool_call mirror (v0.73.5) ─────────────────────────────────────────
   Tool calls are ALWAYS agent-originated — humans never fire tools — so EVERY channel-chat
   tool_call row (m.IsToolCall) flips to the agent (right) side unconditionally, matching how
   agent message bubbles already sit on the right (blue side). No per-row attribution check.

   The feed (.timeline-view.channel-chat__feed) is `display:flex; flex-direction:column`, so a
   .channel-row is a flex child and the codebase's right/agent convention (align-self:flex-end)
   applies directly — same mechanism as .conv-view__row--out and .timeline-view__message--in.

   Only the CHROME mirrors (icon / badge / accent bar / right-cluster reverse and the card hugs
   the right edge). The COMMAND/PAYLOAD TEXT stays LTR + left-aligned so it never becomes hard
   to read. */

/* a. Right-anchor the row and the card. .channel-row--tool is display:block (not the flex row
      itself) so we anchor at BOTH levels: align-self on the flex-child row, and swap the inner
      card's auto margins so it hugs the right instead of sitting 64px from the left. */
.channel-row--tool-agent {
    align-self: flex-end;
}
.channel-row--tool-agent.channel-row--tool {
    /* mirror the left gutter to the right so the card clears the (right-drawn) avatar zone */
    margin-left: 0;
    margin-right: 52px;
}
.channel-row--tool-agent .tool-expando {
    /* card was pushed 64px from the LEFT; push it from the RIGHT and let it shrink-to-fit so it
       genuinely hugs the right edge rather than stretching the full column width. */
    margin-left: auto;
    margin-right: 0;
    padding-left: 0;
    padding-right: 12px;
    width: fit-content;
    max-width: 100%;
}

/* b. Reverse the header chrome: [right-cluster] ... [content] ... [badge][icon]. */
.channel-row--tool-agent .tool-expando__header {
    flex-direction: row-reverse;
}

/* c. Move the per-category accent bar from the LEFT edge to the RIGHT edge. Same colors,
      only the side changes. Base bar first, then every category, then the brighter --open
      variants, then --error / --result. */
.channel-row--tool-agent .tool-expando {
    border-left: none;
    border-right: 2px solid rgba(148, 163, 184, 0.3);
}
.channel-row--tool-agent .tool-expando--shell    { border-right-color: rgba(59, 130, 246, 0.4); }
.channel-row--tool-agent .tool-expando--spawn    { border-right-color: rgba(168, 85, 247, 0.4); }
.channel-row--tool-agent .tool-expando--message  { border-right-color: rgba(34, 197, 94, 0.4); }
.channel-row--tool-agent .tool-expando--cron     { border-right-color: rgba(245, 158, 11, 0.4); }
.channel-row--tool-agent .tool-expando--web      { border-right-color: rgba(139, 92, 246, 0.4); }
.channel-row--tool-agent .tool-expando--memory   { border-right-color: rgba(236, 72, 153, 0.4); }
.channel-row--tool-agent .tool-expando--file     { border-right-color: rgba(14, 165, 233, 0.4); }
.channel-row--tool-agent .tool-expando--media    { border-right-color: rgba(249, 115, 22, 0.4); }
.channel-row--tool-agent .tool-expando--error    { border-right-color: #ef4444 !important; }
.channel-row--tool-agent .tool-expando--result   { border-right-style: dashed; }
.channel-row--tool-agent .tool-expando--open.tool-expando--shell    { border-right-color: #3b82f6; }
.channel-row--tool-agent .tool-expando--open.tool-expando--spawn    { border-right-color: #a855f7; }
.channel-row--tool-agent .tool-expando--open.tool-expando--message  { border-right-color: #22c55e; }
.channel-row--tool-agent .tool-expando--open.tool-expando--cron     { border-right-color: #f59e0b; }
.channel-row--tool-agent .tool-expando--open.tool-expando--web      { border-right-color: #8b5cf6; }
.channel-row--tool-agent .tool-expando--open.tool-expando--memory   { border-right-color: #ec4899; }
.channel-row--tool-agent .tool-expando--open.tool-expando--file     { border-right-color: #0ea5e9; }
.channel-row--tool-agent .tool-expando--open.tool-expando--media    { border-right-color: #f97316; }

/* d. Keep command/payload text LTR + left-aligned. row-reverse only reorders the flex CHILDREN;
      it must not turn the query text or the detail-panel <pre> right-to-left. */
.channel-row--tool-agent .tool-expando__content {
    direction: ltr;
    text-align: left;
}
.channel-row--tool-agent .tool-expando__query-text,
.channel-row--tool-agent .tool-expando__result-summary {
    direction: ltr;
    text-align: left;
}
.channel-row--tool-agent .tool-expando__detail-panel,
.channel-row--tool-agent .tool-expando__section,
.channel-row--tool-agent .tool-expando__section-content {
    direction: ltr;
    text-align: left;
}
/* arrow margin was left-side (row-reverse would leave the gap on the wrong side) */
.channel-row--tool-agent .tool-expando__arrow {
    margin-left: 0;
    margin-right: 0.25rem;
}

/* e. Replay-transcript control under spawn rows: right-align it to match the right-anchored
      card. Reordering only its own justification; the button stays fully functional. */
.channel-row--tool-agent .timeline-view__subtranscript {
    display: flex;
    justify-content: flex-end;
}

.tool-expando__icon { font-size: 1rem; flex-shrink: 0; }

.tool-expando__content {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    overflow: hidden;
}
.tool-expando__separator { color: var(--text-secondary); flex-shrink: 0; }
.tool-expando__query-text {
    color: var(--text-secondary, #aaa);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tool-expando__result-summary {
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tool-expando__right {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
}
.tool-expando__waiting-dots { display: inline-flex; gap: 2px; }
.tool-expando__waiting-dots span {
    display: inline-block;
    color: #94a3b8;
    animation: memory-dot-pulse 1.4s ease-in-out infinite;
    font-size: 1rem;
}
.tool-expando__waiting-dots span:nth-child(2) { animation-delay: 0.16s; }
.tool-expando__waiting-dots span:nth-child(3) { animation-delay: 0.32s; }

.tool-expando__checkmark { color: #10b981; font-weight: 600; }
.tool-success-icon { color: #22c55e; font-weight: 600; }
.tool-error-icon { color: #ef4444; font-weight: 600; }

/* #364 three-state tool outcome — the UNKNOWN state.
   A tool row whose outcome was never recorded (in flight, hub-sourced, or written before the
   api v1.93.1 deploy) must read as NEUTRAL, not as success. This is the MAJORITY of historical
   rows — measured 145 of 181 tool rows on prod 2026-09-07 — so rendering it green would have
   overstated successes by 145 and made the whole backlog look healthy.
   Deliberately muted/desaturated: it carries no claim either way. */
.tool-unknown-icon {
    color: var(--text-tertiary, #8b949e);
    font-weight: 600;
    opacity: 0.85;
}
.tool-expando--unknown { border-left-color: var(--text-tertiary, #8b949e) !important; }

/* Secondary note inside the expanded panel: truncation marker, and the "tool was rejected
   before it ran, so no arguments exist" case, which is distinct from a normal failure. */
.tool-expando__truncated-note {
    display: block;
    margin-top: 0.35rem;
    color: var(--text-tertiary, #8b949e);
    font-size: 0.72rem;
    font-style: italic;
}
.tool-expando__duration { color: var(--text-tertiary, #888); font-size: 0.75rem; }
.tool-expando__arrow { color: var(--text-secondary); font-size: 0.7rem; margin-left: 0.25rem; }

.tool-expando__detail-panel {
    background: var(--bg-secondary, #222);
    border-radius: 0 0 4px 4px;
    padding: 0.5rem 0.75rem;
    border-top: 1px solid var(--border, #333);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.tool-expando__section { display: flex; flex-direction: column; gap: 0.25rem; }
.tool-expando__section-label {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-tertiary, #888);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.tool-expando__section-content {
    font-family: 'Monaco', monospace;
    font-size: 0.75rem;
    color: var(--text-secondary, #aaa);
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-all;
    overflow-y: auto;
    max-height: 200px;
    margin: 0;
}

/* ============================================================================
   MEMORY SEARCH EXPANDO
   ============================================================================ */

.memory-expando {
    display: flex;
    flex-direction: column;
    cursor: pointer;
    margin-left: 64px;
    padding-left: 12px;
    border-left: 2px solid rgba(139, 92, 246, 0.3);
    border-radius: 4px;
    transition: border-color 0.2s ease, background 0.2s ease;
}

.memory-expando:hover {
    background: rgba(139, 92, 246, 0.05);
}

.memory-expando--open {
    border-left: 2px solid #8b5cf6;
    background: var(--bg-secondary);
}

.memory-expando__header {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.8rem;
    background: var(--bg-secondary);
    border-radius: 4px;
}

.memory-expando--open .memory-expando__header {
    border-radius: 4px 4px 0 0;
}

/* ── Sprite Sheet Avatar Animation ──────────────────────────────────────── */

.sprite-avatar {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    overflow: hidden;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: center;
}

.memory-expando__icon {
    font-size: 1rem;
    flex-shrink: 0;
}

.memory-expando__content {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    overflow: hidden;
}

.memory-expando__tool-name {
    font-weight: 600;
    color: var(--text-primary);
}

.memory-expando__separator {
    color: var(--text-secondary);
}

.memory-expando__query-text {
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.memory-expando__result-summary {
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.memory-expando__right {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
}

.memory-expando__waiting-dots {
    display: inline-flex;
    gap: 2px;
}

.memory-expando__waiting-dots span {
    display: inline-block;
    color: #8b5cf6;
    animation: memory-dot-pulse 1.4s ease-in-out infinite;
    font-size: 1rem;
}

.memory-expando__waiting-dots span:nth-child(2) {
    animation-delay: 0.16s;
}

.memory-expando__waiting-dots span:nth-child(3) {
    animation-delay: 0.32s;
}

@keyframes memory-dot-pulse {
    0%, 80%, 100% {
        opacity: 0.3;
        transform: scale(0.8);
    }
    40% {
        opacity: 1;
        transform: scale(1);
    }
}

.memory-expando__checkmark {
    color: #10b981;
    font-weight: 600;
}

.memory-expando__duration {
    color: var(--text-tertiary);
    font-size: 0.75rem;
}

.memory-expando__arrow {
    color: var(--text-secondary);
    font-size: 0.7rem;
    margin-left: 0.25rem;
}

.memory-expando__detail-panel {
    background: var(--bg-secondary);
    border-left: 2px solid #8b5cf6;
    border-radius: 0 0 4px 4px;
    padding: 0.5rem 0.75rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.memory-expando__section {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.memory-expando__section-label {
    font-size: 0.7rem;
    font-weight: 600;
    color: #8b5cf6;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.memory-expando__section-content {
    font-family: Monaco, monospace;
    font-size: 0.75rem;
    color: var(--text-secondary);
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-all;
    overflow-y: auto;
    max-height: 200px;
    margin: 0;
}

/* ── MessageToolViz ────────────────────────────────────────────── */

.msg-viz {
    margin: 0.25rem 0;
    border-radius: 12px;
    background: rgba(15, 15, 20, 0.5);
    cursor: pointer;
    transition: background 0.15s;
}

.msg-viz:hover {
    background: rgba(20, 20, 30, 0.7);
}

.msg-viz:not(.msg-viz--open):hover .msg-viz__header-arrow {
    color: rgba(255, 255, 255, 0.7);
    transform: scale(1.2);
}

.msg-viz:not(.msg-viz--open):hover .msg-viz__header-route {
    text-decoration: underline;
    text-decoration-color: rgba(74, 222, 128, 0.4);
    text-underline-offset: 2px;
}

.msg-viz--open {
    cursor: default;
}

/* --- Collapsed header row --- */
.msg-viz__header {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.75rem;
    min-height: 2rem;
    font-size: 0.82rem;
    line-height: 1.3;
}

.msg-viz__header-icon {
    flex-shrink: 0;
    font-size: 0.9rem;
}

.msg-viz__header-route {
    flex-shrink: 0;
    font-weight: 600;
    color: #4ade80;
    white-space: nowrap;
}

.msg-viz__header-separator {
    color: rgba(255, 255, 255, 0.3);
    flex-shrink: 0;
}

.msg-viz__header-preview {
    color: rgba(255, 255, 255, 0.6);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1;
}

.msg-viz__header-right {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    margin-left: auto;
}

.msg-viz__header-dots {
    display: inline-flex;
    gap: 0.15rem;
    color: rgba(255, 255, 255, 0.4);
    font-size: 1.1rem;
    line-height: 1;
}

.msg-viz__header-dots span {
    animation: msg-dot-bounce 1.4s ease-in-out infinite;
}

.msg-viz__header-dots span:nth-child(2) { animation-delay: 0.2s; }
.msg-viz__header-dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes msg-dot-bounce {
    0%, 80%, 100% { opacity: 0.3; }
    40% { opacity: 1; }
}

.msg-viz__header-check {
    color: #4ade80;
    font-size: 0.85rem;
}

.msg-viz__header-duration {
    color: rgba(255, 255, 255, 0.45);
    font-size: 0.75rem;
}

.msg-viz__header-arrow {
    color: rgba(255, 255, 255, 0.4);
    font-size: 0.7rem;
    transition: transform 0.15s;
}

/* --- Expanded conversation panel --- */
.msg-viz__conversation {
    padding: 0.75rem;
    padding-top: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    cursor: default;
}

/* Row layout — sender left, receiver right */
.msg-viz__row {
    display: flex;
    gap: 0.5rem;
    align-items: flex-end;
}

.msg-viz__row--out {
    justify-content: flex-start;
}

.msg-viz__row--in {
    justify-content: flex-end;
}

.msg-viz__avatar-col {
    flex-shrink: 0;
}

.msg-viz__avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
}

.msg-viz__avatar--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, #34d399, #059669);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
}

.msg-viz__avatar--fallback-in {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, #60a5fa, #3b82f6);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
}

.msg-viz__content-col {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    max-width: 70%;
}

.msg-viz__content-col--in {
    align-items: flex-end;
}

.msg-viz__sender-name {
    font-size: 0.65rem;
    font-weight: 600;
    color: #34d399;
    padding: 0 0.5rem;
}

.msg-viz__sender-name--in {
    color: #60a5fa;
}

/* Bubbles */
.msg-viz__bubble {
    position: relative;
    padding: 0.5rem 0.75rem;
    border-radius: 16px;
    line-height: 1.5;
    font-size: 0.85rem;
    word-wrap: break-word;
    overflow-wrap: break-word;
}

.msg-viz__copy {
    position: absolute;
    top: 4px;
    right: 6px;
    cursor: pointer;
    font-size: 0.7rem;
    opacity: 0;
    transition: opacity 0.15s;
    z-index: 1;
}

.msg-viz__bubble:hover .msg-viz__copy {
    opacity: 0.7;
}

.msg-viz__copy:hover {
    opacity: 1 !important;
}

.msg-viz__bubble--out {
    background: linear-gradient(135deg, #059669, #047857);
    color: #fff;
    border-bottom-left-radius: 4px;
}

.msg-viz__bubble--in {
    background: linear-gradient(135deg, #1e40af, #1d4ed8);
    color: #f0f9ff;
    border-bottom-right-radius: 4px;
}

.msg-viz__bubble--out .markdown-content,
.msg-viz__bubble--in .markdown-content {
    color: inherit;
}

.msg-viz__bubble--out .markdown-content p,
.msg-viz__bubble--in .markdown-content p {
    margin: 0;
}

.msg-viz__empty {
    color: rgba(255, 255, 255, 0.5);
    font-style: italic;
    font-size: 0.8rem;
}

.msg-viz__meta {
    font-size: 0.6rem;
    color: rgba(255, 255, 255, 0.4);
    padding: 0 0.5rem;
}

.msg-viz__meta--in {
    text-align: right;
}

.msg-viz__duration {
    color: rgba(255, 255, 255, 0.35);
}

.msg-viz__status--error {
    font-size: 0.65rem;
    color: #f87171;
    padding: 0 0.5rem;
}

/* Typing indicator */
.msg-viz__typing {
    display: inline-flex;
    gap: 3px;
    padding: 0.25rem 0;
}

.msg-viz__typing span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.6);
    animation: msg-viz-dot-bounce 1.4s infinite ease-in-out both;
}

.msg-viz__typing span:nth-child(1) { animation-delay: -0.32s; }
.msg-viz__typing span:nth-child(2) { animation-delay: -0.16s; }
.msg-viz__typing span:nth-child(3) { animation-delay: 0s; }

.msg-viz__raw-toggle {
    background: none;
    border: none;
    color: rgba(255, 255, 255, 0.35);
    font-size: 0.65rem;
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    margin-top: 0.25rem;
    opacity: 0.7;
    transition: opacity 0.15s;
}

.msg-viz__raw-toggle:hover {
    opacity: 1;
    color: rgba(255, 255, 255, 0.7);
}

.msg-viz__expand {
    display: inline;
    background: none;
    border: none;
    color: var(--accent, #8b5cf6);
    font-size: 0.7rem;
    cursor: pointer;
    padding: 0.1rem 0.3rem;
    opacity: 0.8;
    transition: opacity 0.15s, color 0.15s;
    user-select: none;
    white-space: nowrap;
}

.msg-viz__expand--hidden {
    display: none;
}

.msg-viz__expand:hover {
    opacity: 1;
    color: #a78bfa;
}

.msg-viz__raw {
    margin-top: 0.25rem;
    border-top: 1px dashed rgba(255, 255, 255, 0.1);
    padding-top: 0.25rem;
}

@keyframes msg-viz-dot-bounce {
    0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
    40% { opacity: 1; transform: scale(1.2); }
}

/* Typing indicator — TimelineView "recipient responding…" pending bubble */
.timeline-view__typing {
    display: inline-flex;
    gap: 3px;
    padding: 0.25rem 0;
}

.timeline-view__typing span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.6);
    animation: timeline-view-typing-bounce 1.4s infinite ease-in-out both;
}

.timeline-view__typing span:nth-child(1) { animation-delay: -0.32s; }
.timeline-view__typing span:nth-child(2) { animation-delay: -0.16s; }
.timeline-view__typing span:nth-child(3) { animation-delay: 0s; }

@keyframes timeline-view-typing-bounce {
    0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
    40% { opacity: 1; transform: scale(1.2); }
}

/* ═══════════════════════════════════════════════════════════════
   SpawnToolViz — iPhone-style spawn/subagent conversation
   Purple tones to distinguish from green/blue message viz
   ═══════════════════════════════════════════════════════════════ */

.spawn-viz {
    margin: 0.25rem 0;
    border-radius: 12px;
    background: rgba(88, 28, 135, 0.15);
    border: 1px solid rgba(168, 85, 247, 0.2);
    cursor: pointer;
    transition: background 0.15s;
}

.spawn-viz:hover {
    background: rgba(88, 28, 135, 0.25);
}

.spawn-viz:not(.spawn-viz--open):hover .spawn-viz__header-arrow {
    color: rgba(255, 255, 255, 0.7);
    transform: scale(1.2);
}

.spawn-viz:not(.spawn-viz--open):hover .spawn-viz__header-route {
    text-decoration: underline;
    text-decoration-color: rgba(168, 85, 247, 0.5);
    text-underline-offset: 2px;
}

.spawn-viz--open {
    cursor: default;
}

/* --- Collapsed header row --- */
.spawn-viz__header {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.75rem;
    min-height: 2rem;
    font-size: 0.82rem;
    line-height: 1.3;
}

.spawn-viz__header-icon {
    flex-shrink: 0;
    font-size: 0.9rem;
}

.spawn-viz__header-route {
    flex-shrink: 0;
    font-weight: 600;
    color: #a855f7;
    white-space: nowrap;
}

.spawn-viz__header-separator {
    color: rgba(255, 255, 255, 0.3);
    flex-shrink: 0;
}

.spawn-viz__header-preview {
    color: rgba(255, 255, 255, 0.6);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1;
}

.spawn-viz__header-right {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    margin-left: auto;
}

.spawn-viz__header-dots {
    display: inline-flex;
    gap: 0.15rem;
    color: rgba(255, 255, 255, 0.4);
    font-size: 1.1rem;
    line-height: 1;
}

.spawn-viz__header-dots span {
    animation: spawn-dot-bounce 1.4s ease-in-out infinite;
}

.spawn-viz__header-dots span:nth-child(2) { animation-delay: 0.2s; }
.spawn-viz__header-dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes spawn-dot-bounce {
    0%, 80%, 100% { opacity: 0.3; }
    40% { opacity: 1; }
}

.spawn-viz__header-check {
    color: #a855f7;
    font-size: 0.85rem;
}

.spawn-viz__header-duration {
    color: rgba(255, 255, 255, 0.45);
    font-size: 0.75rem;
}

.spawn-viz__header-arrow {
    color: rgba(255, 255, 255, 0.4);
    font-size: 0.7rem;
    transition: transform 0.15s;
}

/* --- Expanded conversation panel --- */
.spawn-viz__conversation {
    padding: 0.75rem;
    padding-top: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    cursor: default;
}

.spawn-viz__row {
    display: flex;
    gap: 0.5rem;
    align-items: flex-end;
}

.spawn-viz__row--out {
    justify-content: flex-start;
}

.spawn-viz__row--in {
    justify-content: flex-end;
}

.spawn-viz__avatar-col {
    flex-shrink: 0;
}

.spawn-viz__avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
}

.spawn-viz__avatar--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, #a855f7, #7c3aed);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
}

.spawn-viz__avatar--fallback-in {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, #c084fc, #a855f7);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
}

.spawn-viz__content-col {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    max-width: 70%;
}

.spawn-viz__content-col--in {
    align-items: flex-end;
}

.spawn-viz__sender-name {
    font-size: 0.65rem;
    font-weight: 600;
    color: #a855f7;
    padding: 0 0.5rem;
}

.spawn-viz__sender-name--in {
    color: #c084fc;
}

.spawn-viz__bubble {
    position: relative;
    padding: 0.5rem 0.75rem;
    border-radius: 16px;
    line-height: 1.5;
    font-size: 0.85rem;
    word-wrap: break-word;
    overflow-wrap: break-word;
}

.spawn-viz__copy {
    position: absolute;
    top: 4px;
    right: 6px;
    cursor: pointer;
    font-size: 0.7rem;
    opacity: 0;
    transition: opacity 0.15s;
    z-index: 1;
}

.spawn-viz__bubble:hover .spawn-viz__copy {
    opacity: 0.7;
}

.spawn-viz__copy:hover {
    opacity: 1 !important;
}

.spawn-viz__bubble--out {
    background: linear-gradient(135deg, #7c3aed, #6d28d9);
    color: #fff;
    border-bottom-left-radius: 4px;
}

.spawn-viz__bubble--in {
    background: linear-gradient(135deg, #4c1d95, #581c87);
    color: #f5f3ff;
    border-bottom-right-radius: 4px;
}

.spawn-viz__bubble--out .markdown-content,
.spawn-viz__bubble--in .markdown-content {
    color: inherit;
}

.spawn-viz__bubble--out .markdown-content p,
.spawn-viz__bubble--in .markdown-content p {
    margin: 0;
}

.spawn-viz__task-label {
    font-size: 0.7rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.7);
    margin-bottom: 0.25rem;
    padding-bottom: 0.25rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}

.spawn-viz__empty {
    color: rgba(255, 255, 255, 0.5);
    font-style: italic;
    font-size: 0.8rem;
}

.spawn-viz__meta {
    font-size: 0.6rem;
    color: rgba(255, 255, 255, 0.4);
    padding: 0 0.5rem;
}

.spawn-viz__meta--in {
    text-align: right;
}

.spawn-viz__duration {
    color: rgba(255, 255, 255, 0.35);
}

.spawn-viz__status--error {
    font-size: 0.65rem;
    color: #f87171;
    padding: 0 0.5rem;
}

.spawn-viz__typing {
    display: inline-flex;
    gap: 3px;
    padding: 0.25rem 0;
}

.spawn-viz__typing span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.6);
    animation: spawn-viz-dot-bounce 1.4s infinite ease-in-out both;
}

.spawn-viz__typing span:nth-child(1) { animation-delay: -0.32s; }
.spawn-viz__typing span:nth-child(2) { animation-delay: -0.16s; }
.spawn-viz__typing span:nth-child(3) { animation-delay: 0s; }

.spawn-viz__expand {
    display: inline;
    background: none;
    border: none;
    color: #c084fc;
    font-size: 0.7rem;
    cursor: pointer;
    padding: 0.1rem 0.3rem;
    opacity: 0.8;
    transition: opacity 0.15s, color 0.15s;
    user-select: none;
    white-space: nowrap;
}

.spawn-viz__expand--hidden {
    display: none;
}

.spawn-viz__expand:hover {
    opacity: 1;
    color: #e9d5ff;
}

@keyframes spawn-viz-dot-bounce {
    0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
    40% { opacity: 1; transform: scale(1.2); }
}

/* ── Multi-host: offline agent visual treatment ──────────────────────────── */
.agent-node--offline {
    filter: grayscale(60%);
    transition: opacity 0.3s ease;
}

.agent-node__offline-badge {
    display: block;
    font-size: 0.6rem;
    color: #9ca3af;
    text-align: center;
    letter-spacing: 0.04em;
    margin-top: 2px;
    pointer-events: none;
    user-select: none;
}

/* ────────────────────────────────────────────────────────────────────────────
   #316 CHANNEL CHAT — merged per-channel conversation view
   Dark mission-control aesthetic, reusing the existing :root tokens.
   Kind accents:  human = --status-thinking (blue)
                  agent = --peer-accent    (indigo, same token as peer chat)
                  unknown = --status-tooluse (amber = "needs attention")
   ──────────────────────────────────────────────────────────────────────────── */

/* Accessible label that is visually hidden but read by screen readers. */
.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;
}

.channels-page {
    display: grid;
    grid-template-columns: minmax(240px, 320px) 1fr;
    gap: 16px;
    height: 100%;
    min-height: 0;
    padding: 16px;
    box-sizing: border-box;
}

.channels-page__sidebar {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-height: 0;
}

.channels-page__back {
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 0.85rem;
    padding: 4px 2px;
}
.channels-page__back:hover,
.channels-page__back:focus-visible { color: var(--text-primary); }

/* v0.32.0 -- THE MISSING LINK IN THE HEIGHT CHAIN.

   `.oversight__body` (MainLayout's <main>, the element that wraps @Body for EVERY page) had
   NO CSS RULE WHATSOEVER. Its parent `.oversight` is a flex column with height:100vh, so as
   an un-styled flex child this <main> defaulted to flex-grow:0 / min-height:auto and simply
   grew to fit its content instead of being capped at the remaining viewport height.

   That is why height:100% on .channels-page resolved against an ancestor with no definite
   height and did nothing. Percentage heights need a resolvable basis; without one the whole
   chain below (.channels-page -> .channels-page__main -> .channel-chat -> .timeline-view)
   stayed content-sized, so the document scrolled and .timeline-view never did.

   flex:1 gives it the leftover column space, and min-height:0 is required because a flex
   item's default min-height:auto refuses to shrink below content size - the classic reason
   "overflow:auto on the child" appears to be ignored. Both are load-bearing. */
.oversight__body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* v0.32.0 -- THE HEIGHT CHAIN, and the single root cause behind BOTH the scrolling
   title bar AND the missing jump-to-latest pill.

   This rule previously read `min-height: 0; display: flex;` with NO height. As a CSS
   grid item in `.channels-page` (grid-template-columns, height:100%), a bare grid cell
   sizes to its CONTENT along the block axis, so this box grew as tall as the whole
   transcript. Everything below inherited that unbounded height: `.channel-chat` is
   flex:1/min-height:0/overflow:hidden, but flex:1 can only shrink relative to a parent
   that HAS a definite height. With an unbounded parent there was nothing to shrink
   against, so `.timeline-view` never became scrollable and the PAGE scrolled instead.

   Two user-visible symptoms, one cause:
     1. The title bar scrolled away, because the outer page was the scroller and the
        header was just content inside it. Making the header flex:0 0 auto (v0.31.0)
        could not possibly help - the header was pinned correctly inside a box that was
        itself moving.
     2. The jump-to-latest pill never appeared. The JS scroll watcher is registered
        against _viewRef (.timeline-view). That element never scrolled, so the watcher
        never fired, _followLatest never flipped to false, and the pill stayed hidden.
        The watermark logic was correct all along - it was simply never told.

   min-height:0 alone was not enough: it permits shrinking but does not establish the
   definite height that flex:1 resolves against. `height: 100%` against the grid row is
   what actually bounds the box. min-height:0 is retained because a flex/grid item's
   default min-height:auto would otherwise refuse to shrink below content size and
   silently reintroduce the same overflow. Both declarations are load-bearing.

   DO NOT add overflow to this rule. The single scrollport must remain .timeline-view
   (_viewRef) - every scroll measurement (read watermark, unread count, auto-follow,
   suppressScrollForRestore) targets it. A second scroller here would break all of it. */
.channels-page__main {
    min-height: 0;
    height: 100%;
    display: flex;
    /* Children (.channel-chat) are flex:1 and must stack vertically to fill the cell. */
    flex-direction: column;
    /* Belt-and-braces: if a future child forgets min-height:0, clip rather than let the
       page grow a second scrollbar and resurrect the bug above. */
    overflow: hidden;
}

.channels-page__empty {
    margin: auto;
    color: var(--text-secondary);
}

/* ── Channel list ─────────────────────────────────────────────────────────── */

/* v0.36.0 ISSUE 1 -- THE ACTUAL OVERFLOWING ELEMENT.

   This rule is where the /channels "header scrolled off the top" bug was BORN. `.channel-list` had
   `min-height: 0` but NO `flex` declaration, so it inherited the default `flex: 0 1 auto` -- and
   with `flex-basis: auto` its base size is its CONTENT height. Inside `.channels-page__sidebar`
   (a flex column in a grid cell), that meant the sidebar sized to the full, unbounded height of
   every channel in the list. That overflowed the grid row, overflowed `.oversight`, and -- because
   `.oversight` had no `overflow` at the time -- escaped to the document, making BODY the scroller
   and dragging the app header out of view.

   `min-height: 0` was already here and looked like it should have prevented this. It could not:
   `min-height` on a flex CONTAINER constrains it as a CHILD of its own parent, and with
   `flex-basis: auto` the used base size is still content-derived, so there was nothing telling this
   box to be smaller than its contents.

   `flex: 1 1 0` is the fix: `flex-basis: 0` discards the content-derived base size entirely, so the
   box is sized by the space the sidebar actually has, and `.channel-list__items` (already
   `overflow-y: auto; min-height: 0`) becomes the real scrollport for a long channel list -- which
   is what it was always written to be. Both the grow factor and the ZERO basis are load-bearing;
   `flex: 1` alone expands to `flex: 1 1 0%` and works, but is written out in full here so the basis
   is not mistaken for `auto` by a later reader. */
.channel-list {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 0;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}

.channel-list__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
}

.channel-list__title {
    margin: 0;
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* v0.31.0: .channel-chat__refresh dropped from these shared rules — ChannelChat's "Reload now"
   button is gone. .channel-list__refresh (the SIDEBAR's refresh, a different component and still
   very much in use) and both __retry buttons are untouched. */
.channel-list__refresh,
.channel-list__retry,
.channel-chat__retry {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    border-radius: 6px;
    padding: 3px 10px;
    font-size: 0.78rem;
    cursor: pointer;
}
.channel-list__refresh:hover:not(:disabled),
.channel-list__retry:hover,
.channel-chat__retry:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}
.channel-list__refresh:disabled { opacity: 0.5; cursor: default; }

/* v0.31.0: the .channel-chat__refresh--recovery block was removed with the "Reload now" button.
   Rationale lives in ChannelChat.razor: the feed has two independent delivery paths, so a manual
   reload was never the user's escape hatch — the Retry buttons on the actual failure states are,
   and those keep the shared .channel-chat__retry styling above. */

.channel-list__items {
    list-style: none;
    margin: 0;
    padding: 4px;
    overflow-y: auto;
    min-height: 0;
}

.channel-list__item {
    padding: 8px 10px;
    border-radius: 6px;
    cursor: pointer;
    border-left: 3px solid transparent;
    /* GPU-friendly: only color-ish properties transition, no layout thrash. */
    transition: background-color 120ms ease, border-color 120ms ease;
}
.channel-list__item:hover { background: rgba(148, 163, 184, 0.08); }

.channel-list__item--selected {
    background: rgba(139, 92, 246, 0.14);
    border-left-color: var(--accent);
}

/* Visible keyboard focus — required for WCAG 2.4.7. */
.channel-list__item:focus-visible,
.channel-chat__feed:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.channel-list__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.channel-list__row--meta { margin-top: 4px; }

.channel-list__label {
    color: var(--text-primary);
    font-size: 0.9rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* RAW ID STYLE — labelResolved:false. Muted + monospace + smaller so an unresolved
   identifier can never be misread as a real, human channel name. */
.channel-list__label--rawid,
.channel-chat__title--rawid {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.78rem;
    font-weight: 400;
    /* #94a3b8 on #1a202c ≈ 7.2:1 — comfortably above the 4.5:1 AA floor. */
    color: var(--text-secondary);
    opacity: 0.92;
}

.channel-chat__rawid-tag {
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-secondary);
    border: 1px dashed var(--border);
    border-radius: 4px;
    padding: 1px 5px;
}

.channel-list__count {
    font-size: 0.7rem;
    color: var(--text-secondary);
    background: rgba(148, 163, 184, 0.12);
    border-radius: 10px;
    padding: 1px 7px;
    flex: none;
}

/* Phase 1 (#374): overlapping roster stack. Pips carry real portraits now; the negative gap
   overlaps them like a Slack/Discord member stack, and a ring in the row background colour keeps
   each face separated. Hover fans them apart slightly for legibility. */
.channel-list__participants { display: flex; align-items: center; padding-left: 3px; }

.channel-list__pip {
    width: 20px; height: 20px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.6rem;
    font-weight: 700;
    color: var(--bg-primary);
    background: var(--status-idle);
    overflow: hidden;
    margin-left: -6px;
    box-shadow: 0 0 0 2px var(--bg-secondary);
    transition: margin-left 120ms ease;
}
.channel-list__pip:first-child { margin-left: 0; }
.channel-list__item:hover .channel-list__pip { margin-left: -3px; }
.channel-list__item:hover .channel-list__pip:first-child { margin-left: 0; }
.channel-list__pip img { width: 100%; height: 100%; object-fit: cover; }
.channel-list__pip--human   { background: var(--status-thinking); }
.channel-list__pip--agent   { background: var(--peer-accent); }
.channel-list__pip--unknown { background: var(--status-tooluse); }
.channel-list__pip--more    { background: rgba(148, 163, 184, 0.18); color: var(--text-secondary); box-shadow: 0 0 0 2px var(--bg-secondary); }

/* Phase 1 (#374): sidebar channel search */
.channel-list__search {
    position: relative;
    margin: 8px 12px 0;
}
.channel-list__search-icon {
    position: absolute;
    left: 8px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.72rem;
    opacity: 0.55;
    pointer-events: none;
}
.channel-list__search-input {
    width: 100%;
    box-sizing: border-box;
    font: inherit;
    font-size: 0.8rem;
    color: var(--text-primary);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 5px 8px 5px 26px;
}
.channel-list__search-input::placeholder { color: var(--text-secondary); opacity: 0.7; }
.channel-list__search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── #354 origin / kind descriptors ──────────────────────────────────────────
   Origin answers WHERE (colour + glyph, left-anchored, reads first); kind answers
   WHAT SHAPE (monochrome qualifier). Two visually distinct treatments so they never
   read as one tag: origin CARRIES colour, kind stays secondary/monochrome.

   Additive only — these are NEW classes; nothing existing is restyled. Against the
   current API (no origin/kind) OriginClass falls to --unknown (neutral, no colour)
   and the kind chip is omitted entirely, so the row renders exactly as before. */
.channel-list__origin {
    flex: none;
    width: 20px; height: 20px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.72rem;
    line-height: 1;
    /* Colourless base: origin colour is applied per-modifier below. The neutral/unknown
       state deliberately keeps THIS treatment (no accent) so "?" reads as "no origin". */
    background: rgba(148, 163, 184, 0.12);
    color: var(--text-secondary);
    border: 1px solid transparent;
}
/* Discord blurple has no brand token — external brand colour, so a literal is correct here. */
.channel-list__origin--discord   { background: rgba(88, 101, 242, 0.18);  border-color: rgba(88, 101, 242, 0.55);  color: #c7ccf8; }
/* OpenClaw = brand slate → the existing slate token family. */
.channel-list__origin--openclaw  { background: rgba(148, 163, 184, 0.16); border-color: var(--border);             color: var(--text-primary); }
/* Oversight = the app accent (violet). */
.channel-list__origin--oversight { background: rgba(139, 92, 246, 0.18);   border-color: rgba(139, 92, 246, 0.55);   color: var(--accent); }
/* Hermes = accent family too (§5), nudged via the peer accent so it is distinguishable. */
.channel-list__origin--hermes    { background: rgba(99, 102, 241, 0.18);   border-color: var(--peer-border);         color: var(--peer-accent); }
/* Unknown/absent origin: NEUTRAL, no colour — inherits the colourless base above. */
.channel-list__origin--unknown   { /* base treatment only */ }

.channel-list__kind {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 0.68rem;
    color: var(--text-secondary);   /* monochrome, secondary emphasis */
}
.channel-list__kind-text { text-transform: lowercase; }

.channel-list__time { font-size: 0.7rem; color: var(--text-secondary); }

.channel-list__state,
.channel-chat__state {
    padding: 16px 12px;
    color: var(--text-secondary);
    font-size: 0.85rem;
    text-align: center;
}
.channel-list__state--error,
.channel-chat__state--error { color: #fca5a5; }

/* ── Channel chat ─────────────────────────────────────────────────────────── */

.channel-chat {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}

/* v0.36.0 ISSUE 2 -- CHANNEL CHAT FULLSCREEN.

   Modelled DELIBERATELY on `.agent-panel--fullscreen` (~line 736) rather than inventing a second
   fullscreen concept: same fixed/inset-0 approach, same z-index band, same border-radius reset, and
   the same single-scroller reasoning documented at `.agent-panel--fullscreen .agent-panel__body`.
   Two different fullscreen implementations in one app is how they drift apart.

   z-index 10000 sits ONE above `.agent-panel--fullscreen`'s 9999 and above the app header, which is
   in the normal flow (no z-index). The two can never be on screen simultaneously -- they live on
   different routes -- so the ordering between them is arbitrary; what matters is that both clear the
   header and neither is under `.toast-container` (also 9999, and `pointer-events: none`, so toasts
   remain visible over fullscreen without stealing clicks -- checked, this is desirable).

   THE SINGLE-SCROLLER CONTRACT IS THE WHOLE RISK HERE.
   `.channel-chat` is already `display:flex; flex-direction:column; min-height:0; overflow:hidden`,
   and every chrome row inside it is `flex: 0 0 auto` (v0.31.0) while `.timeline-view-wrapper` is
   `flex: 1 1 auto; min-height: 0`. Going fullscreen must not disturb ANY of that, because
   `.timeline-view` (_viewRef) is the one element the read watermark, unread count, auto-follow and
   `suppressScrollForRestore` all measure. So this rule changes only POSITION and SIZE and
   re-asserts the flex column + `min-height: 0` + `overflow: hidden` explicitly.

   `min-height: 0` is restated even though the base rule already has it: `position: fixed` re-parents
   this box to the initial containing block, so it is no longer a flex ITEM of
   `.channels-page__main` and its `flex: 1` becomes inert. `height: 100dvh` (with the 100vh fallback,
   same ordering rationale as `.oversight`) is what bounds it now, and `min-height: 0` is what lets
   the wrapper inside it shrink below content size so `.timeline-view` actually scrolls. Both are
   load-bearing.

   REJECTED: the native Fullscreen API (`requestFullscreen`). It needs a user-gesture-bound JS call,
   it cannot be exited by our own Escape handler (the browser owns Escape in that mode, so the
   component's `_fullscreen` flag would desync from reality on browser-initiated exit), and it would
   have needed a `fullscreenchange` listener to resync -- more moving parts, and no styling benefit
   over fixed positioning. AgentPanel made the same call. */
.channel-chat--fullscreen {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    z-index: 10000;
    border: 0;
    border-radius: 0;
    margin: 0;
    /* Re-assert the contract: fixed positioning removed this box from its old flex context. */
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

/* Focus ring for the fullscreen host. The <section> carries tabindex="0" so it can receive the
   Escape keydown, which makes it focusable -- and a focusable element with NO visible focus
   indicator is a WCAG 2.4.7 failure. `:focus-visible` only, so a mouse click on the panel does not
   paint a ring, but keyboard focus always does. */
.channel-chat:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* Fullscreen toggle button -- visual clone of `.agent-panel__maximize` so the same affordance looks
   the same in both places. Not a shared class: AgentPanel's button sits in its own
   `__header-actions` bar with different padding/margins, and aliasing the two selectors would mean a
   future tweak to one silently moves the other. */
.channel-chat__maximize {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: var(--text-secondary, #999);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    padding: 4px 6px;
    border-radius: 4px;
    transition: color 0.15s;
}

.channel-chat__maximize:hover {
    color: var(--accent, #8b5cf6);
}

/* Keyboard focus must be visible on the button itself (WCAG 2.4.7), not just on the panel. */
.channel-chat__maximize:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    color: var(--text-emphasis);
}

/* v0.82.0 channel info toggle (ℹ). Shares the maximize button's visual language so the header
   action cluster reads as one control set; only difference is the aria-expanded pressed tint. */
.channel-chat__header-btn {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: var(--text-secondary, #999);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    padding: 4px 6px;
    border-radius: 4px;
    transition: color 0.15s, background 0.15s;
}
.channel-chat__header-btn:hover { color: var(--accent, #8b5cf6); }
.channel-chat__header-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    color: var(--text-emphasis);
}
/* Pressed state is not colour-only: the tinted background + the panel appearing below both signal
   "open", so a user who can't perceive the accent hue still gets the state (WCAG 1.4.1). */
.channel-chat__info-toggle[aria-expanded="true"] {
    color: var(--accent);
    background: var(--surface-2, rgba(139, 92, 246, 0.12));
}

/* ── Channel attributes / details panel (v0.82.0) ───────────────────────────────────────
   Frozen chrome like the header: never grows/shrinks, sits above the scrolling feed. */
.channel-chat__info {
    flex: 0 0 auto;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.08));
    background: var(--surface-1, rgba(255, 255, 255, 0.02));
    font-size: 0.85rem;
}
.channel-chat__info-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 4px 24px;
    margin: 0 0 10px;
}
.channel-chat__info-row {
    display: flex;
    gap: 8px;
    align-items: baseline;
    min-width: 0;
}
.channel-chat__info-row dt {
    flex: 0 0 auto;
    color: var(--text-secondary, #999);
    font-weight: 600;
    min-width: 92px;
}
.channel-chat__info-row dd {
    margin: 0;
    color: var(--text-emphasis, #eee);
    min-width: 0;
    overflow-wrap: anywhere;
}
.channel-chat__info-code {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 0.78rem;
    color: var(--text-secondary, #999);
}
.channel-chat__info-roster { margin-top: 4px; }
.channel-chat__info-subhead {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 0 6px;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--text-emphasis, #eee);
}
.channel-chat__info-caption {
    font-weight: 400;
    font-size: 0.72rem;
    color: var(--text-secondary, #999);
}
.channel-chat__info-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
}
.channel-chat__info-agent {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.channel-chat__info-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
}
.channel-chat__info-avatar--generic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-2, rgba(255, 255, 255, 0.1));
    color: var(--text-secondary, #bbb);
    font-size: 0.62rem;
    font-weight: 700;
}
.channel-chat__info-agent-name { color: var(--text-emphasis, #eee); }
.channel-chat__info-empty,
.channel-chat__info-humans {
    margin: 6px 0 0;
    color: var(--text-secondary, #999);
    font-size: 0.78rem;
}

/* ─── #354 §5b assign/unassign editor (v0.83.0) ────────────────────────────────
   The CONFIGURED responder roster, editable inline. One row per bound agent with a Set-default /
   Unassign action; a picker below adds agents. Vertical list (not the wrapping chip flow of the
   activity roster) because each row now carries actions and needs a stable hit target. */
.channel-chat__assign { margin-top: 14px; }
.channel-chat__assign-list {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 4px;
    margin-top: 6px;
}
.channel-chat__assign-agent {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 3px 4px;
    border-radius: 6px;
}
.channel-chat__assign-agent:hover { background: var(--surface-2, rgba(255, 255, 255, 0.05)); }
.channel-chat__assign-name {
    color: var(--text-emphasis, #eee);
    font-size: 0.82rem;
    margin-right: auto;   /* push actions to the right edge */
}
.channel-chat__assign-badge {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--accent, #8b5cf6);
    border: 1px solid var(--accent, #8b5cf6);
    border-radius: 4px;
    padding: 1px 6px;
    white-space: nowrap;
}
.channel-chat__assign-btn {
    flex: 0 0 auto;
    font-size: 0.72rem;
    font-weight: 600;
    padding: 3px 9px;
    border-radius: 5px;
    border: 1px solid var(--border, rgba(255, 255, 255, 0.18));
    background: transparent;
    color: var(--text-secondary, #bbb);
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.channel-chat__assign-btn:hover:not(:disabled) {
    background: var(--surface-2, rgba(255, 255, 255, 0.08));
    color: var(--text-emphasis, #eee);
}
.channel-chat__assign-btn:disabled { opacity: 0.5; cursor: default; }
.channel-chat__assign-btn:focus-visible {
    outline: 2px solid var(--accent, #8b5cf6);
    outline-offset: 2px;
}
.channel-chat__assign-btn--remove:hover:not(:disabled) {
    border-color: var(--danger, #ef4444);
    color: var(--danger, #ef4444);
}
.channel-chat__assign-btn--add,
.channel-chat__assign-btn--default:hover:not(:disabled) {
    border-color: var(--accent, #8b5cf6);
    color: var(--accent, #8b5cf6);
}
.channel-chat__assign-btn--open-picker {
    margin-top: 8px;
    width: 100%;
    border-style: dashed;
}
.channel-chat__assign-picker {
    margin-top: 8px;
    padding: 8px;
    border: 1px dashed var(--border, rgba(255, 255, 255, 0.18));
    border-radius: 6px;
}
.channel-chat__assign-pick-list {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 4px;
    /* Scrollable: the roster can be dozens of agents; cap the height and let it scroll rather than
       pushing the Done button off-screen (Problem 2 — could not scroll the assign list). */
    max-height: 260px;
    overflow-y: auto;
    margin-top: 6px;
    padding-right: 2px;   /* room for the scrollbar so rows aren't clipped */
}
.channel-chat__assign-search {
    width: 100%;
    box-sizing: border-box;
    padding: 5px 9px;
    font-size: 0.8rem;
    color: var(--text-emphasis, #eee);
    background: var(--surface-1, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--border, rgba(255, 255, 255, 0.18));
    border-radius: 5px;
}
.channel-chat__assign-search:focus-visible {
    outline: 2px solid var(--accent, #8b5cf6);
    outline-offset: 1px;
}
.channel-chat__assign-btn--cancel { margin-top: 8px; }

/* Mirrors `.agent-panel__fullscreen-badge`: a non-colour-dependent cue that the view is in a modal
   state, so the only way out is not purely "notice the icon changed" (WCAG 1.4.1). */
.channel-chat__fullscreen-badge {
    flex: 0 0 auto;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
    white-space: nowrap;
}

/* Right-aligned cluster in the chat header. `margin-left: auto` rather than reordering the flex
   children, so the heading keeps its natural `min-width: 0` ellipsis behaviour on a long title. */
.channel-chat__header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    flex: 0 0 auto;
}

/* v0.31.0 FROZEN CHROME.
   wermfood: "the top bar of a channel and its options" must stay put while only the messages
   scroll. .channel-chat is already a bounded flex column (min-height:0 + overflow:hidden above),
   so the whole job is deciding which children may absorb leftover space and which may not.

   `flex: 0 0 auto` on every chrome row = never grow, never shrink, always content height. Without
   the `0` shrink factor these rows are shrinkable by default, so a long transcript would squeeze
   the header instead of scrolling the feed. The complementary half is .timeline-view-wrapper's
   `flex: 1 1 auto; min-height: 0` (already present, see v0.29.3) — the wrapper takes all remaining
   height and .timeline-view inside it does the actual scrolling.

   Deliberately NOT position:sticky: the real scrollport is .timeline-view (_viewRef), which is a
   DESCENDANT of the feed frame, not an ancestor of this header. Sticky positions against the
   nearest scrolling ancestor, so it would either do nothing here or require making the section
   itself scroll — and that would move the scrollport off _viewRef, breaking the read watermark,
   the jump-to-latest pill and auto-follow, all of which measure _viewRef. Flex sizing keeps the
   single-scrollport invariant intact. */
.channel-chat__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    flex: 0 0 auto;
}

/* Phase 1 (#374): heading is now a vertical stack — title row on top, room-banner subline
   (origin/kind + live roster) beneath. The title itself keeps its inline chrome (raw-id tag). */
.channel-chat__heading { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
.channel-chat__heading > .channel-chat__title,
.channel-chat__heading > .channel-chat__title-input { width: 100%; }

/* ── Phase 1 (#374): room-banner subline ─────────────────────────────────────── */
.channel-chat__banner {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.channel-chat__banner-kind {
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: lowercase;
    letter-spacing: 0.02em;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-radius: 999px;
    padding: 1px 8px;
    white-space: nowrap;
}
/* Banner roster avatars/rings REMOVED (Stace, 2026-08-30): duplicated the feed's per-message
   avatars + live status. Only .channel-chat__banner-kind (the origin/kind chip) survives. */

.channel-chat__title {
    margin: 0;
    font-size: 1rem;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.channel-chat__meta { display: flex; align-items: center; gap: 10px; }

/* v0.31.0: .channel-chat__stale / .channel-chat__stale-dot and the channel-pulse keyframes were
   removed with the "Live updates disconnected" banner (see ChannelChat.razor). The pulse animation
   had no other consumer — .channel-chat__partial-dot and .channel-list__item are styled
   independently — so it went with them rather than lingering as an unreferenced keyframe. */

/* ── Data-consistency states ──────────────────────────────────────────────────
   The channel summary advertises messageCount, but /conversation is authoritative.
   When they disagree the server has a split channel key space; these two states make
   that visible instead of letting it render as an ordinary empty feed. */

/* Hard mismatch: summary says N messages, server returned zero. Amber = needs attention. */
.channel-chat__mismatch {
    margin: 8px 2px;
    padding: 12px 14px;
    border: 1px solid var(--status-tooluse);
    border-left-width: 4px;
    border-radius: 6px;
    background: rgba(245, 158, 11, 0.08);
    text-align: left;
}

.channel-chat__mismatch-title {
    margin: 0 0 6px;
    font-size: 0.86rem;
    font-weight: 700;
    /* #fbbf24 on the dark shell clears WCAG AA for this size; the raw --status-tooluse
       amber is reserved for the border/dot so text stays the higher-contrast tint. */
    color: #fbbf24;
}

.channel-chat__mismatch-body {
    margin: 0 0 8px;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--text-primary);
}
.channel-chat__mismatch-body strong { color: #fde68a; }

.channel-chat__mismatch-id {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin: 0 0 10px;
    font-size: 0.72rem;
}
.channel-chat__mismatch-id-label { color: var(--text-secondary); }

/* Selectable so a human can copy the exact id straight into a curl. */
.channel-chat__mismatch-code {
    font-family: 'Monaco', 'Menlo', monospace;
    font-size: 0.72rem;
    color: #fde68a;
    background: rgba(15, 23, 42, 0.7);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 2px 6px;
    user-select: all;
    overflow-wrap: anywhere;
}

/* Soft mismatch: feed rendered, but fewer messages than advertised. Informational only. */
.channel-chat__partial {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 5px 14px;
    border-bottom: 1px solid var(--border);
    background: rgba(245, 158, 11, 0.06);
    font-size: 0.72rem;
    color: #fde68a;
    /* Pinned chrome, not part of the scrolling feed — see .channel-chat__header (v0.31.0). */
    flex: 0 0 auto;
}
.channel-chat__partial-dot {
    flex: none;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--status-tooluse);
}

/* Load older history (v0.39.0).

   Deliberately NOT styled like .channel-chat__partial above. That block is amber warning chrome,
   and this replaced it for the ordinary case of a channel larger than one page — paging working
   correctly must not wear warning colours. Neutral secondary text on the standard surface. */
.channel-chat__older {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 6px 14px;
    border-bottom: 1px solid var(--border);
    font-size: 0.72rem;
    /* Same reason as .channel-chat__header / __partial: chrome ABOVE the scrollport, never squashed. */
    flex: 0 0 auto;
}
.channel-chat__older-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-2, rgba(255, 255, 255, 0.04));
    color: var(--text-secondary);
    font-size: 0.72rem;
    font-family: inherit;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.channel-chat__older-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text-primary);
    border-color: var(--text-secondary);
}
.channel-chat__older-btn:disabled {
    opacity: 0.6;
    cursor: default;
}
.channel-chat__older-count {
    color: var(--text-secondary);
    opacity: 0.8;
}
.channel-chat__older-spinner {
    flex: none;
    width: 9px; height: 9px;
    border: 1.5px solid var(--text-secondary);
    border-top-color: transparent;
    border-radius: 50%;
    animation: channel-chat-older-spin 700ms linear infinite;
}
@keyframes channel-chat-older-spin {
    to { transform: rotate(360deg); }
}
/* Respect reduced-motion: the spinner is decorative, the text already says "Loading". */
@media (prefers-reduced-motion: reduce) {
    .channel-chat__older-spinner { animation: none; }
}

/* flex:0 0 auto for the same reason as the header — these are chrome ABOVE the scrollport, and a
   wrapped multi-line roster must push the feed down rather than being squashed by it. */
.channel-chat__members {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    border-bottom: 1px solid var(--border);
    font-size: 0.72rem;
    flex: 0 0 auto;
}
.channel-chat__members-label { color: var(--text-secondary); }

/* v0.33.0 ISSUE 4: the roster is a strip of FACES, not labelled chips.
   The chip is now just a ring around a portrait — no padding, no pill border, no background,
   because at 34px the face itself is the affordance and any chrome around it only competes.
   The old pill styling (padding/border-radius/border) is deliberately gone rather than
   overridden, so nothing draws a stray outline around each head. */
.channel-chat__member {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: none;
    border-radius: 50%;
    color: var(--text-secondary);
    line-height: 0; /* kill inline-descender gap so the strip's height == the avatar's */
    cursor: pointer; /* #374: faces are click-to-filter toggles now */
    transition: transform 100ms ease, box-shadow 120ms ease;
}
.channel-chat__member:hover { transform: translateY(-1px); }
.channel-chat__member:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 50%;
}
/* #374: pinned speaker — accent ring + others dim so the pin reads at a glance. */
.channel-chat__member--pinned {
    box-shadow: 0 0 0 2px var(--bg-secondary), 0 0 0 4px var(--accent);
    border-radius: 50%;
}
.channel-chat__members:has(.channel-chat__member--pinned) .channel-chat__member:not(.channel-chat__member--pinned) {
    opacity: 0.45;
}

/* #374 active speaker-filter chip in the filter bar */
.channel-chat__speaker-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font: inherit;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    border-radius: 999px;
    padding: 2px 8px;
    cursor: pointer;
    max-width: 180px;
}
.channel-chat__speaker-chip:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.channel-chat__speaker-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.channel-chat__speaker-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.channel-chat__speaker-chip-x { font-size: 0.66rem; opacity: 0.8; }
/* v0.97.0 multi-select: the "Clear all" chip is muted so it reads as a secondary action next to the
   per-speaker chips, not another speaker. */
.channel-chat__speaker-chip--clear-all {
    color: var(--text-secondary);
    background: color-mix(in srgb, var(--text-secondary) 10%, transparent);
    border-color: color-mix(in srgb, var(--text-secondary) 35%, transparent);
}
.channel-chat__speaker-chip--clear-all:hover { background: color-mix(in srgb, var(--text-secondary) 18%, transparent); }

/* #322: wrapper for the NO-LIVE-STATUS branch (offline agents and all humans).
   AgentAvatarBadge brings its own box when a status exists; this span is the ringless
   equivalent, kept the same shape so the strip does not jump between the two branches. */
.channel-chat__member-inner {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
}

/* v0.33.0 ISSUE 4: participant faces, sized to be RECOGNISABLE.
   34px matches the floorplan participant strip — the reference the user pointed at. The old
   16px was legible as a colour blob but not as a person, which was the complaint.
   Fixed box + flex-shrink:0 so a slow-loading portrait cannot reflow the strip as it arrives
   (images are loading=lazy) — the space is reserved before the bytes land.
   The 2px accent ring replaces the removed chip border as the per-participant colour cue. */
.channel-chat__member-avatar {
    width: 34px;
    height: 34px;
    /* v0.36.0 ISSUE 3 LAYER 2: the v0.33.0 comment above already intended to reserve this space
       before the bytes land, but `width`/`height` in CSS alone does not stop a lazy `<img>` with no
       intrinsic ratio from being laid out at its pre-decode size first in some engines. min-* plus
       aspect-ratio makes the reservation unconditional, and the markup now carries matching
       width/height ATTRIBUTES too. The roster sits ABOVE the scrollport, so any reflow here shifts
       the feed's whole offset -- one of the two layout-shift sources behind the mid-conversation
       auto-scroll landing. Load-bearing. */
    min-width: 34px;
    min-height: 34px;
    aspect-ratio: 1 / 1;
    flex: 0 0 auto;
    border-radius: 50%;
    object-fit: cover;
    /* Ring carries the participant accent so roster and feed agree on colour. */
    border: 2px solid var(--participant-accent, var(--border));
    background: var(--bg-elevated);
    display: block;
}

/* Initials fallback when neither the server nor the identity resolver has a face.
   Centres a 1-2 char string in the SAME 34px box so both branches are pixel-identical and the
   strip never jumps as faces resolve in. */
.channel-chat__member-avatar--generic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1;
    color: var(--text-secondary);
    text-transform: uppercase;
}

/* Kind tint now rides on the RING, not a chip border — the chip border no longer exists.
   Specificity note: these must beat .channel-chat__member-avatar's own border-color, so they
   target the avatar inside the kind modifier rather than the chip itself. */
.channel-chat__member--human   .channel-chat__member-avatar { border-color: var(--status-thinking); }
.channel-chat__member--agent   .channel-chat__member-avatar { border-color: var(--peer-border); }
.channel-chat__member--unknown .channel-chat__member-avatar { border-color: var(--status-tooluse); }

/* v0.73.3: RECENCY. Stale participants (kind-visible but not seen within RailRecencyWindow and
   with no live status ring) are dimmed, not deleted — they only appear when the "+N earlier"
   overflow pill is expanded, and read as clearly de-emphasised versus the active row above. */
.channel-chat__member--stale { opacity: 0.4; }

/* v0.73.3: the "+N earlier" overflow pill. Reuses the roster's own colour vocabulary
   (--text-secondary / --border / --bg-elevated) so it reads as part of the strip rather than a
   foreign control, and sits at the roster's 34px height so expanding it does not reflow the row. */
.channel-chat__member-overflow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 34px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 17px;
    background: var(--bg-elevated);
    color: var(--text-secondary);
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    flex: 0 0 auto;
}
.channel-chat__member-overflow:hover { color: var(--text-primary); border-color: var(--peer-border); }

/* v0.28.0: the feed IS a .timeline-view now (same scroll container, same row gap, same
   padding, same position:relative pill anchor). This class only carries the two things
   TimelineView's own host does not: paint isolation for 1000-message feeds, and the
   focus ring for the tabbable role="log" region. Do NOT re-add overflow/padding here —
   .timeline-view already owns them and duplicating them caused a double scrollbar. */
/* v0.31.0: pin the filter toolbar too, SCOPED to .channel-chat.
   The shared .timeline-filter-bar already sets flex-shrink:0, but it is a shared class also used
   by AgentPanel's toolbar in a different flex chain, so the belt-and-braces `flex: 0 0 auto` is
   applied here rather than edited into the shared rule — changing shared geometry to fix one view
   is how the other view breaks. Result: header, roster, partial note and this bar are all fixed,
   and .timeline-view-wrapper (flex:1 1 auto, min-height:0) absorbs everything left over, so
   .timeline-view remains the ONE element that scrolls.
   The pill is unaffected: it is position:sticky/bottom INSIDE .timeline-view, so it anchors to the
   scrollport below this bar and the two cannot overlap. */
.channel-chat > .timeline-filter-bar {
    flex: 0 0 auto;
}

.channel-chat__feed {
    /* v0.29.1: `contain: content` was REMOVED here.
       It implies `contain: layout paint style` on the very element that must scroll
       (.timeline-view owns overflow-y:auto). Containment on a flex-sized scroll container
       can collapse its resolved height, so scrollHeight never exceeds clientHeight, the
       container never actually scrolls, and the scroll listener registered by
       oversightInterop.registerScrollWatcher never fires. That silently kills BOTH halves
       of the latch: no auto-follow on new messages AND no jump-to-latest pill, because both
       are driven by OnScrollStateChanged.
       If paint isolation is wanted again, apply it to a NON-scrolling ancestor/descendant,
       never to the scroll container itself. */
}

/* ── Group-chat message rows (v0.28.0) ───────────────────────────────────────
   ChannelChat now renders the SAME rows as TimelineView (.timeline-view__message /
   __avatar / __bubble-wrap / __sender / __bubble / __time / __expand / __copy) and reuses
   the shared .newmsg-pill. Everything below is ONLY the delta a group chat needs that a
   2-party timeline does not:

     1. per-participant accent colour  (N speakers must be told apart at a glance)
     2. consecutive-run indentation    (iOS-style avatar-once-per-run)
     3. keyboard-operable expand/copy  (TimelineView uses <span>; here they are <button>,
                                        so the button UA styling has to be neutralised)

   The accent arrives as an inline --participant-accent custom property set on the row, so
   there are no hardcoded per-colour classes and the palette can grow without new CSS. */

.channel-row {
    /* Fallback keeps the row sane if the inline property is ever missing. */
    --participant-accent: var(--accent, #8b5cf6);
}

/* Accent ring on the avatar — the at-a-glance "who" signal. */
.channel-row__avatar {
    border: 2px solid var(--participant-accent);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
}

/* v0.35.0: EVERY row now carries a face (Stace: "it needs to be for every chat"), so the
   empty-gutter spacer that continuation rows used is gone — the rule was deleted rather than
   left orphaned, because dead CSS is how selectors get resurrected by accident later.

   Continuation avatars are drawn slightly recessed so a run still READS as one run. Grouping
   information is preserved visually without withholding identity: the face is always present
   and always identifiable, just de-emphasised on repeat. Opacity only — no size change — so
   the gutter width stays constant and bubbles remain aligned down the column. */
.channel-row__avatar--continued {
    opacity: 0.55;
    /* Accent ring is the loudest part of the chip; soften it on repeats so a long run does not
       turn into a stack of hard rings competing with the text. */
    border-color: color-mix(in srgb, var(--participant-accent) 55%, transparent);
}

/* Sender name takes the participant accent. Colour is never the ONLY differentiator — the
   name text itself is always rendered (WCAG 1.4.1). The palette hues are the ToolExpando
   category accents; 600 weight (inherited from .timeline-view__sender) + 0.72rem keeps the
   amber/orange end of the palette legible on the dark feed background. */
.channel-row__sender {
    color: var(--participant-accent);
    font-size: 0.72rem;
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
}

/* 'unknown' kind cue only — humans/agents are self-evident from the name. */
.channel-row__kind {
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0 5px;
    border-radius: 4px;
    background: rgba(245, 158, 11, 0.2);
    color: #fde68a;
}

/* Subtle participant accent on the bubble edge. Deliberately a low-alpha inset outline
   rather than a background change, so the --in/--out side gradients stay the primary
   side cue and bubble text contrast is untouched. */
.channel-row__bubble {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--participant-accent) 45%, transparent);
}

/* Live-merged row: ordering is an estimate (no server ingestSeq on the SignalR path), so it
   is marked with a dashed edge rather than shown as authoritative. */
.channel-row__bubble--live {
    outline: 1px dashed color-mix(in srgb, var(--participant-accent) 60%, transparent);
    outline-offset: -1px;
}

/* Tighter vertical rhythm inside a run, so a burst reads as one utterance block.
   Cancels part of .timeline-view's 0.75rem row gap for continuation rows only. */
.channel-row--continued {
    margin-top: -0.5rem;
}

/* The expand and copy controls are real <button>s here (keyboard operability + aria-expanded).
   Strip the UA button chrome so they render identically to TimelineView's span versions. */
.channel-row__expand,
.channel-row__copy {
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.channel-row__expand {
    /* .timeline-view__expand is display:flex; keep the button from shrink-wrapping oddly. */
    width: 100%;
    text-align: left;
    padding: 4px 0 0;
}

/* Copy button is hover-revealed by .timeline-view__copy; keyboard users need it on focus too. */
.channel-row__copy:focus-visible {
    opacity: 1;
    outline: 2px solid var(--accent, #8b5cf6);
    outline-offset: 2px;
    border-radius: 3px;
}

.channel-row__expand:focus-visible {
    outline: 2px solid var(--accent, #8b5cf6);
    outline-offset: 2px;
    border-radius: 3px;
    color: var(--text-primary);
}

/* v0.33.0: the 3px accent bar that used to sit on the LEFT EDGE of each pill chip is gone.
   Chips are bare circular portraits now, so a left border would render as a stray arc clipped
   by border-radius:50%. The accent moved onto the avatar's ring (see .channel-chat__member-avatar). */

/* Respect reduced-motion: kill the pulse and all transitions in this view.
   (.channel-chat__stale-dot dropped from this list in v0.31.0 — the element no longer exists.) */
@media (prefers-reduced-motion: reduce) {
    .channel-chat__partial-dot { animation: none; }
    .channel-chat__mismatch    { animation: none; }
    .channel-list__item        { transition: none; }
    /* v0.28.0 group-chat rows: no transitions on the accent/focus affordances either. */
    .channel-row__expand,
    .channel-row__copy         { transition: none; }
}

@media (max-width: 900px) {
    .channels-page { grid-template-columns: 1fr; }
}

/* ============================================================================
   JUMP-TO-LATEST PILL — shared by TimelineView + ConversationView

   v0.29.7: DUAL STATE. Shown whenever the user has scrolled up off the bottom,
   NOT only when new entries arrived (that older rule left a quiet channel with no
   affordance back down at all). The feed never auto-scrolls while unlatched; this
   pill is the way back down, and unread escalates it rather than gating it:

     .newmsg-pill--quiet  unread == 0  passive "Jump to latest". Low emphasis: it
                                      must not compete with live text for attention.
     .newmsg-pill--alert  unread  > 0  "N new messages". The loud accent treatment
                                      that shipped in v0.29.5 is preserved verbatim.

   Both states keep the >=44px touch target from the v0.29.5 a11y fix and keep the
   arrow glyph as the glanceable "go down" cue. Geometry is IDENTICAL across states, so
   switching state can never change the hit box or move the target under the cursor.

   SHARED-CLASS CONTRACT — IMPORTANT:
   TimelineView and ConversationView still render a BARE `.newmsg-pill` with no state
   modifier (their pills remain unread-gated; only ChannelChat got the dual-state
   treatment). So the BASE class must keep the full loud v0.29.5 appearance as its
   default, and `--alert` is a no-op alias of that default. If you strip the base
   styling into `--alert` only, those two components render an unstyled transparent
   pill. Do not "clean that up" without also updating both consumers.
   ============================================================================ */

/* Sticky host: participates in the flex column as a zero-height row and pins itself
   to the bottom of the scrollport, so the pill floats over the feed without ever
   consuming layout space or covering the last message's text.
   The parent scroll container (.timeline-view / .conv-view) sets position: relative. */
.newmsg-pill-host {
    position: sticky;
    /* v0.29.5: lifted off the baseline so the larger pill clears the newest bubble. */
    bottom: 1rem;
    align-self: center;
    height: 0;
    /* Lift the pill above the feed content without adding scrollable height. */
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    pointer-events: none;   /* empty host must never eat clicks on messages below */
    z-index: 5;
}

.newmsg-pill {
    /* Sit just above the sticky baseline so the newest message stays readable. */
    position: relative;
    transform: translateY(-100%);
    pointer-events: auto;

    display: inline-flex;
    align-items: center;
    /* v0.29.5 — MUCH LARGER per Stace. This is a live-feed call to action that has to be
       spotted instantly while text is streaming past, so it is sized as a primary button
       rather than a subtle badge. Height lands ~44px, which also clears the WCAG 2.1 AA
       target-size guidance (44x44) that the old 0.35rem/0.75rem pill did not.
       GEOMETRY IS STATE-INDEPENDENT ON PURPOSE — see the block comment above. */
    gap: 0.6rem;

    padding: 0.75rem 1.5rem;
    min-height: 44px;
    border: 2px solid var(--accent, #8b5cf6);
    border-radius: 999px;

    /* DEFAULT = LOUD. Opaque elevated surface — the pill overlaps live text, so it must not
       be translucent. --text-emphasis on #2a1f4a clears WCAG AA comfortably. Kept on the BASE
       class so TimelineView/ConversationView (bare .newmsg-pill) are visually unchanged.
       #2a1f4a stays literal: it is a violet-tinted accent surface, NOT the neutral
       --bg-elevated step, so tokenising it here would be wrong. */
    background: #2a1f4a;
    color: var(--text-emphasis, #f8fafc);
    /* Stronger elevation so it reads as floating above the feed at the new size. */
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55);

    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;

    transition: background-color 160ms ease, border-color 160ms ease,
                color 160ms ease, box-shadow 160ms ease, transform 120ms ease;

    /* ENTRANCE ONLY. The animation lives on the base class, which mounts exactly once per
       scroll-up (the element is created when ShowNewMessagePill flips true). Because the
       quiet→alert transition is a CLASS SWAP on the SAME element rather than a remount,
       this keyframe does NOT re-fire on every count increment — that "pop on every new
       message" was the jarring behaviour we were asked to avoid. Count changes are carried
       by the `transition` above plus the one-shot emphasis pulse on --alert below. */
    animation: newmsg-pill-in 140ms ease-out;
}

.newmsg-pill:active {
    transform: translateY(-100%) scale(0.97);
}

/* Hover for the loud treatment (base default AND --alert, which is the same surface). */
.newmsg-pill:hover {
    background: #3b2a63;
    border-color: #a78bfa;
}

/* ── ALERT state (unread > 0) ───────────────────────────────────────────────────
   Intentionally inherits the loud surface from the base class rather than restating it —
   the only thing this modifier adds is the one-shot escalation pulse. */
.newmsg-pill--alert {
    /* One-shot emphasis on quiet→alert. Runs once (no `infinite`) and only scales/glows —
       it never moves the element, so the click target stays put mid-animation. Disabled
       under prefers-reduced-motion below. */
    animation: newmsg-pill-in 140ms ease-out, newmsg-pill-escalate 420ms ease-out;
}

/* ── QUIET state (unread == 0) — passive way back down ────────────────────────────────── */
.newmsg-pill--quiet {
    /* Uses existing theme tokens rather than new brand colours (no dedicated "muted
       surface" token exists in :root — noted for Nova).
       CONTRAST: --bg-secondary #1a202c over the feed's --bg-primary #0f172a, with
       --text-primary #e2e8f0 label text = ~13.5:1, far above the 4.5:1 AA floor. We
       deliberately did NOT use --text-secondary (#94a3b8 on #1a202c is ~6.2:1, still AA,
       but the label is the whole affordance so it gets the primary text token).
       De-emphasis is carried by the SURFACE and BORDER, not by fading the text — opacity
       on the whole pill is what usually pushes these controls under AA. */
    background: var(--bg-secondary, #1a202c);
    color: var(--text-primary, #e2e8f0);
    border-color: var(--border, #334155);
    /* Flatter than the loud state: present, but not shouting. */
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
    font-weight: 600;   /* one notch down from the loud state's 700 */
}

/* Subtle hover LIFT so the quiet state still reads as clickable. Combined with the base
   translateY(-100%) that positions the pill, so it must repeat that translate. */
.newmsg-pill--quiet:hover {
    background: var(--bg-elevated, #232b3a);
    border-color: var(--accent, #8b5cf6);
    color: var(--text-emphasis, #f8fafc);
    transform: translateY(calc(-100% - 2px));
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}

/* Keep the pressed state consistent with the base (hover lift must not fight :active). */
.newmsg-pill--quiet:active {
    transform: translateY(-100%) scale(0.97);
}

/* The arrow is the one cue that stays fully opaque in both states — it is the glanceable
   "go down" affordance and must not be de-emphasised with the surface. */
.newmsg-pill--quiet .newmsg-pill__arrow {
    color: var(--accent, #8b5cf6);
}

/* Visually-hidden polite live region (v0.29.7). The pill's visible label is aria-hidden;
   THIS is the only element that announces, and the Razor populates it only when unread > 0,
   so ordinary scroll-up does not nag screen-reader users. Standard sr-only clip pattern —
   NOT display:none, which would stop live-region announcements entirely. */
.newmsg-pill__live {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Focus ring matches the existing convention (.channel-list__item / .channel-chat__feed). */
.newmsg-pill:focus-visible {
    outline: 2px solid var(--accent, #8b5cf6);
    outline-offset: 2px;
}

.newmsg-pill__arrow {
    /* v0.29.5: scaled with the pill — the arrow is the glanceable "go down" cue. */
    font-size: 1.15rem;
    line-height: 1;
}

@keyframes newmsg-pill-in {
    from { opacity: 0; transform: translateY(calc(-100% + 4px)); }
    to   { opacity: 1; transform: translateY(-100%); }
}

/* Quiet→alert emphasis. Scale + accent glow only, no translation, so the 44px target never
   moves while it plays. One shot, 420ms, then it settles into the static alert treatment. */
@keyframes newmsg-pill-escalate {
    0%   { transform: translateY(-100%) scale(1);    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55); }
    45%  { transform: translateY(-100%) scale(1.05); box-shadow: 0 4px 22px rgba(139, 92, 246, 0.55); }
    100% { transform: translateY(-100%) scale(1);    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55); }
}

/* Reduced motion: no entrance animation, no smooth scrolling on the feeds.
   (The JS side also falls back to an instant jump when this preference is set.) */
@media (prefers-reduced-motion: reduce) {
    .newmsg-pill,
    /* --alert re-declares `animation` (entrance + escalate pulse), so it needs its own
       reset here: a bare `.newmsg-pill { animation: none }` loses the specificity race. */
    .newmsg-pill--alert {
        animation: none;
        transition: none;
    }
    .newmsg-pill:active { transform: translateY(-100%); }
    /* No hover lift either — motion-sensitive users still get the colour change. */
    .newmsg-pill--quiet:hover { transform: translateY(-100%); }
    .conv-view      { scroll-behavior: auto; }
    .timeline-view  { scroll-behavior: auto; }
}

/* ── v0.37.0 — channel rename + hide ─────────────────────────────────────────────────────────
   Rename controls are visual clones of `.channel-chat__maximize` rather than aliases of it, for
   the same reason that button is not an alias of AgentPanel's: sharing a selector means a future
   tweak to one affordance silently moves the others. Duplication here is deliberate and cheap. */
.channel-chat__rename,
.channel-chat__rename-reset {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: var(--text-secondary, #999);
    cursor: pointer;
    line-height: 1;
    padding: 4px 6px;
    border-radius: 4px;
    transition: color 0.15s;
}

.channel-chat__rename { font-size: 1rem; }

/* Reset is a word, not a glyph, so it needs to read as secondary chrome next to the icon buttons. */
.channel-chat__rename-reset {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.channel-chat__rename:hover,
.channel-chat__rename-reset:hover { color: var(--accent, #8b5cf6); }

/* WCAG 2.4.7 — focus must be visible on the control itself, matching the maximize button. */
.channel-chat__rename:focus-visible,
.channel-chat__rename-reset:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    color: var(--text-emphasis);
}

/* Disabled = a write is in flight. Pointer-events stay ON so the cursor communicates "busy"
   rather than the button silently becoming inert under the mouse. */
.channel-chat__rename-reset:disabled { opacity: 0.5; cursor: wait; }

/* The inline editor REPLACES the <h2>, so it inherits the title's metrics to avoid a layout jump
   between display and edit mode — a header that changes height on click reads as a glitch. */
.channel-chat__title-input {
    margin: 0;
    font-size: 1rem;
    font-family: inherit;
    color: var(--text-primary);
    background: var(--bg-input, rgba(148, 163, 184, 0.12));
    border: 1px solid var(--accent, #8b5cf6);
    border-radius: 4px;
    padding: 2px 6px;
    min-width: 0;
    flex: 1 1 auto;
}

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

.channel-chat__title-input:disabled { opacity: 0.6; cursor: wait; }

/* ── Sidebar: auto-hide control + hidden section ──────────────────────────────────────────── */
.channel-list__prefs {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
}

/* Pairs with .timeline-filter-bar__select (already styled): this only adds the sidebar-specific
   sizing, so the shared control keeps its existing look in both toolbars. */
.channel-list__select {
    flex: 1 1 auto;
    min-width: 0;
}

/* Section wrapper for revealed rows. The heading sits above a dimmed group, so it needs to read as
   a divider rather than as another channel row. */
.channel-list__hidden-section {
    border-top: 1px solid var(--border-subtle, rgba(148, 163, 184, 0.25));
    margin-top: 4px;
}

.channel-list__items--hidden { opacity: 0.9; }

/* "stale" vs "hidden" is the one distinction a dimmed row cannot make on its own, and the reason
   a row is suppressed changes what the user should do about it (wait vs unhide). Text, not colour
   alone (WCAG 1.4.1). */
.channel-list__stale-tag {
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary, #999);
    border: 1px solid var(--border-subtle, rgba(148, 163, 184, 0.25));
    border-radius: 3px;
    padding: 0 4px;
    flex: 0 0 auto;
}

.channel-list__show-hidden {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    color: var(--text-secondary, #999);
    cursor: pointer;
    font-size: 0.75rem;
    padding: 6px 10px;
}

.channel-list__show-hidden:hover { color: var(--accent, #8b5cf6); }

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

/* Dimmed, not hidden: these rows are revealed on purpose, so they must stay legible while reading
   as clearly de-emphasized relative to the live list above them. */
.channel-list__item--dimmed { opacity: 0.55; }

/* Per-row hide/unhide. Hover-reveal is a PROGRESSIVE ENHANCEMENT layered on top of an always-
   focusable button: opacity (not display:none) keeps it in the tab order and in the a11y tree, so
   keyboard and screen-reader users can reach it even though it is invisible until hover. Using
   display/visibility here would make the control keyboard-unreachable — the exact trap this
   pattern usually falls into. */
.channel-list__hide,
.channel-list__unhide {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: var(--text-secondary, #999);
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 2px 4px;
    border-radius: 4px;
    transition: opacity 0.15s, color 0.15s;
}

/* Only the HIDE button is hover-revealed. Unhide stays permanently visible: it lives in the
   revealed "hidden" section, which the user opened specifically to act on those rows, so making
   its only action hover-dependent would defeat the point of the section. */
.channel-list__hide { opacity: 0; }

.channel-list__item:hover .channel-list__hide,
.channel-list__hide:focus-visible { opacity: 1; }

.channel-list__hide:hover,
.channel-list__unhide:hover { color: var(--accent, #8b5cf6); }

.channel-list__hide:focus-visible,
.channel-list__unhide:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.channel-list__hide:disabled,
.channel-list__unhide:disabled { opacity: 0.4; cursor: wait; }

/* Touch devices get no hover event at all, so a hover-only control would be permanently invisible
   there rather than merely subtle. */
@media (hover: none) {
    .channel-list__hide { opacity: 1; }
}

/* ────────────────────────────────────────────────────────────────────────────
   #354 Phase D — per-row ⋯ menu (Rename + Delete) and inline delete confirm.
   Reuses the existing hide-button idiom (always in DOM/tab order, hover/focus reveal)
   and the app's surface/border tokens — no new design language. Scoped to
   .channel-list__menu* / confirm as required.
   ──────────────────────────────────────────────────────────────────────────── */
.channel-list__menu {
    position: relative;
    flex: 0 0 auto;
}

/* Same reveal contract as the hide button: opacity (not display) so the trigger stays keyboard-
   and AT-reachable even while visually subtle. */
.channel-list__menu-trigger {
    background: none;
    border: none;
    color: var(--text-secondary, #999);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    padding: 2px 6px;
    border-radius: 4px;
    opacity: 0;
    transition: opacity 0.15s, color 0.15s;
}
.channel-list__item:hover .channel-list__menu-trigger,
.channel-list__menu-trigger:focus-visible,
.channel-list__menu-trigger[aria-expanded="true"] { opacity: 1; }

.channel-list__menu-trigger:hover { color: var(--accent, #8b5cf6); }
.channel-list__menu-trigger:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.channel-list__menu-trigger:disabled { opacity: 0.4; cursor: wait; }

/* Popover panel — floats above the row, right-aligned to the ⋯ trigger. */
.channel-list__menu-panel {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 20;
    min-width: 160px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px;
    background: var(--surface-2, #1f2733);
    border: 1px solid var(--border, #334155);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.channel-list__menu-item {
    text-align: left;
    background: none;
    border: none;
    color: var(--text-primary);
    cursor: pointer;
    font-size: 0.85rem;
    padding: 6px 10px;
    border-radius: 5px;
    transition: background-color 0.12s;
}
.channel-list__menu-item:hover { background: rgba(148, 163, 184, 0.12); }
.channel-list__menu-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.channel-list__menu-item--danger { color: var(--danger, #f87171); }
.channel-list__menu-item--danger:hover { background: rgba(248, 113, 113, 0.14); }

/* Destructive confirm step — wider, spells out the consequence, names the channel. */
.channel-list__menu-panel--confirm { min-width: 220px; padding: 10px; gap: 8px; }

.channel-list__confirm-text {
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.35;
    color: var(--text-secondary);
}
.channel-list__confirm-text strong { color: var(--text-primary); }

.channel-list__confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

.channel-list__confirm-cancel,
.channel-list__confirm-delete {
    cursor: pointer;
    font-size: 0.8rem;
    padding: 5px 10px;
    border-radius: 5px;
    border: 1px solid var(--border, #334155);
    transition: background-color 0.12s, border-color 0.12s;
}
.channel-list__confirm-cancel {
    background: none;
    color: var(--text-primary);
}
.channel-list__confirm-cancel:hover { background: rgba(148, 163, 184, 0.12); }

.channel-list__confirm-delete {
    background: var(--danger, #f87171);
    border-color: var(--danger, #f87171);
    color: #1a202c;
    font-weight: 600;
}
.channel-list__confirm-delete:hover { filter: brightness(1.08); }

.channel-list__confirm-cancel:focus-visible,
.channel-list__confirm-delete:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.channel-list__confirm-cancel:disabled,
.channel-list__confirm-delete:disabled { opacity: 0.5; cursor: wait; }

/* The ⋯ trigger must stay reachable on touch, same as the hide button. */
@media (hover: none) {
    .channel-list__menu-trigger { opacity: 1; }
}

/* ────────────────────────────────────────────────────────────────────────────
   v0.40.0 — Users & Roles console (/users)

   Reuses the existing :root tokens only; no new colour values are introduced.
   Role accents deliberately reuse the established meanings rather than inventing
   a parallel palette:
     admin      = --accent          (violet: elevated capability)
     member     = --status-idle     (grey: ordinary, not a warning)
     agent type = --peer-accent     (indigo: same token as agent-to-agent chat)
     human type = --status-thinking (blue)
     inactive   = dimmed, NOT red — a deactivated user is a normal state, not an error.
     destructive= --status-tooluse / danger red only on hard-delete, which is the one
                  genuinely irreversible action on this page.
   ──────────────────────────────────────────────────────────────────────────── */

/* v0.48.2 -- /users was clipped at the viewport with no way to scroll: 44 users render a table
   far taller than the fold, but nothing in the chain owns a scrollport. `.oversight__body` (wraps
   @Body for every page) is a capped flex column (flex:1; min-height:0) with NO overflow, and that
   is deliberate -- the channels page owns its own scrollport (.timeline-view) and putting a
   scroller on __body would break it. So make THIS page its own scrollport, exactly as
   `.settings-page` does (see the note at that rule): flex:1 to fill the capped column, min-height:0
   so it may shrink below content size, overflow-y:auto to scroll the overhang.

   min-height:0 is load-bearing, not decoration: a flex item's default min-height is `auto`, which
   floors it at content height, so the box never becomes shorter than the table and `overflow-y`
   has zero overhang to scroll. Dropping it silently restores the clipped bug.

   The block margin becomes padding: `margin: 0 auto` still centres the 1100px column horizontally,
   but vertical margin on a scroll container's own box does not scroll with the content, so the
   bottom gap is kept as padding (already present in the shorthand below). */
.users-page {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    width: 100%;
    padding: 20px 24px 40px;
    max-width: 1100px;
    margin: 0 auto;
    color: var(--text-primary);
}

.users-page__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 4px;
}

.users-page__subtitle {
    color: var(--text-secondary);
    font-size: 13px;
    margin: 0 0 18px;
    line-height: 1.5;
}

.users-page__hint {
    color: var(--text-secondary);
    font-size: 12.5px;
    margin: 8px 0;
}

/* Non-admin / unresolved-identity state. Informational, not an error: the common
   cause is the cross-origin principal gap, which is not the user's fault. */
.users-page__denied {
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-left: 3px solid var(--status-tooluse);
    border-radius: 6px;
    padding: 18px 20px;
    margin-top: 12px;
}

.users-page__denied-title {
    color: var(--text-emphasis);
    font-size: 15px;
    font-weight: 600;
    margin: 0 0 8px;
}

.users-page__denied-detail {
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1.6;
    margin: 0;
}

.users-page__error {
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(239, 68, 68, 0.4);
    border-radius: 6px;
    padding: 10px 14px;
    color: #fca5a5;
    font-size: 13px;
    margin: 12px 0;
}

/* aria-live target: must stay in the DOM so screen readers announce changes. */
.users-page__status {
    min-height: 20px;
    margin: 10px 0;
}

.users-page__status-text {
    color: var(--text-secondary);
    font-size: 12.5px;
}

.users-page__danger-text {
    color: #fca5a5;
    font-size: 12.5px;
    line-height: 1.6;
}

/* ── filter bar ─────────────────────────────────────────────────────────── */

.users-filter-bar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    flex-wrap: wrap;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 12px 14px;
    margin-bottom: 16px;
}

.users-filter-bar label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
}

/* v0.49.0 -- the search box gets more room than a select: it holds free text (ids like
   `distracteds-mac-mini.local:adam` are long), whereas the selects hold short fixed words.
   flex-grow lets it absorb spare width in the filter bar instead of leaving a dead gap. */
.users-filter-bar__search {
    min-width: 240px;
    flex: 1 1 240px;
    max-width: 380px;
}

.users-filter-bar select,
.users-filter-bar input {
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    padding: 6px 8px;
    font-size: 13px;
    min-width: 130px;
}

.users-filter-bar select:focus-visible,
.users-filter-bar input:focus-visible {
    outline: 2px solid var(--status-thinking);
    outline-offset: 1px;
}

/* ── table ──────────────────────────────────────────────────────────────── */

.users-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
    font-size: 13px;
}

.users-table__caption {
    caption-side: top;
    text-align: left;
    color: var(--text-secondary);
    font-size: 12px;
    padding: 0 0 8px;
}

.users-table th {
    background: var(--bg-elevated);
    color: var(--text-secondary);
    text-align: left;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
    padding: 9px 12px;
    border-bottom: 1px solid var(--border);
}

.users-table td {
    padding: 9px 12px;
    border-bottom: 1px solid rgba(51, 65, 85, 0.5);
    vertical-align: middle;
}

.users-table tr:last-child td { border-bottom: none; }

.users-table tbody tr:hover { background: var(--bg-elevated); }

/* Deactivated users stay legible — dimmed, not struck out or reddened. They are
   retained deliberately so historical messages remain attributable. */
.users-table__row--inactive td {
    opacity: 0.55;
}

/* v0.49.1 -- the Name cell stacks display name over email. Both children are inline <span>s, so
   before this rule they rendered on ONE line with no separator and read as a single mangled token
   ("Coopcooper@scolesfamily.com"). Inline elements collapse the whitespace between them to nothing
   here because Razor emits them adjacent, so the fix is a layout on the CELL, not a margin hack or
   a literal &nbsp;. This was a regression from splitting the old combined User column in v0.49.0:
   in the previous single-column layout the same spans wrapped naturally on their own lines.

   TRADE-OFF, deliberate: `display:flex` on a <td> replaces its table-cell box, so the shared
   `.users-table td { vertical-align: middle }` no longer applies to THIS cell -- it is re-created
   here with `justify-content:center` on the column axis. The alternative (leaving the td alone and
   making the two spans `display:block`) keeps the table box intact, but then the em-dash/name and
   email each become full-width blocks and `align-items:flex-start` is unavailable, so a short name
   gets an oversized hover/selection target. Flex is the smaller compromise; just don't assume the
   sibling `vertical-align` rule is doing anything for this cell. */
.users-table__name-cell {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 2px;
    /* Long emails must wrap inside the cell rather than widening the column and squeezing the
       User ID column, which is nowrap and cannot give any width back. */
    min-width: 0;
    overflow-wrap: anywhere;
}

/* v0.69.0 (#362): avatar + name laid out as a row inside the (column) name cell. The wrap owns the
   horizontal pairing; the text keeps its own vertical name/email stack in .users-table__name-text. */
.users-table__avatar-wrap {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    min-width: 0;
    width: 100%;
}

.users-table__name-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 2px;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Fixed-size circular avatar so rows stay aligned whether a face resolves or the letter fallback
   shows. flex-shrink:0 keeps a long email from crushing the avatar to an ellipse. */
.users-table__avatar-img,
.users-table__avatar-initials {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex-shrink: 0;
    object-fit: cover;
}

.users-table__avatar-initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--accent), #6366f1);
    color: #ffffff;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.5px;
    user-select: none;
}

.users-table__name {
    color: var(--text-emphasis);
    font-weight: 600;
}

/* v0.49.0 -- user ids are opaque machine tokens (`distracteds-mac-mini.local:adam`), not prose.
   Monospace makes them scannable and makes near-identical ids visually distinguishable, which is
   the whole point of splitting this out of the name column. Kept nowrap so an id never silently
   wraps into something that looks like two separate values. */
.users-table__userid {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
    font-size: 12px;
    color: var(--text-secondary);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 2px 6px;
    white-space: nowrap;
}

/* A user with no display name shows an em dash, not a silently-duplicated id. Dimmed so it reads
   as "absent" rather than as a literal value someone typed. */
.users-table__name--unset {
    color: var(--text-secondary);
    opacity: 0.7;
}

.users-table__id,
.users-table__time {
    color: var(--text-secondary);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11.5px;
}

.users-table__actions {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* ── badges ─────────────────────────────────────────────────────────────── */

.users-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    border: 1px solid var(--border);
    background: var(--bg-primary);
    color: var(--text-secondary);
    white-space: nowrap;
}

.users-badge--admin {
    border-color: rgba(139, 92, 246, 0.5);
    background: rgba(139, 92, 246, 0.16);
    color: #c4b5fd;
}

.users-badge--member {
    border-color: var(--border);
    background: var(--bg-primary);
    color: var(--text-secondary);
}

.users-badge--agent {
    border-color: var(--peer-border);
    background: var(--peer-bg);
    color: #a5b4fc;
}

.users-badge--human {
    border-color: rgba(59, 130, 246, 0.45);
    background: rgba(59, 130, 246, 0.14);
    color: #93c5fd;
}

.users-badge--inactive {
    border-color: rgba(107, 114, 128, 0.5);
    background: rgba(107, 114, 128, 0.14);
    color: #cbd5e1;
}

/* ── buttons ────────────────────────────────────────────────────────────── */

.users-page__button {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    padding: 5px 11px;
    font-size: 12.5px;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.users-page__button:hover:not(:disabled) {
    background: var(--border);
    color: var(--text-emphasis);
}

.users-page__button:focus-visible {
    outline: 2px solid var(--status-thinking);
    outline-offset: 1px;
}

.users-page__button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.users-page__button--primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 600;
}

.users-page__button--primary:hover:not(:disabled) {
    background: #7c4ddb;
    border-color: #7c4ddb;
    color: #fff;
}

/* Hard delete only. Irreversible and destroys history attribution, so it is the
   one control on this page allowed to shout. */
.users-page__button--danger {
    background: rgba(239, 68, 68, 0.14);
    border-color: rgba(239, 68, 68, 0.5);
    color: #fca5a5;
}

.users-page__button--danger:hover:not(:disabled) {
    background: rgba(239, 68, 68, 0.26);
    color: #fecaca;
}

/* ── linked identities ──────────────────────────────────────────────────── */

.users-identity-list {
    list-style: none;
    margin: 10px 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.users-identity-list__item {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 7px 10px;
}

.users-identity-list__ext {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    color: var(--text-primary);
}

.users-identity-list__label {
    color: var(--text-secondary);
    font-size: 12px;
}

.users-identity-list__item .users-page__button {
    margin-left: auto;
}

.users-identity-form {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: flex-end;
    border-top: 1px solid var(--border);
    padding-top: 12px;
    margin-top: 12px;
}

.users-identity-form label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
}

.users-identity-form input {
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
    padding: 6px 8px;
    font-size: 13px;
    min-width: 140px;
}

.users-identity-form input:focus-visible {
    outline: 2px solid var(--status-thinking);
    outline-offset: 1px;
}

@media (max-width: 720px) {
    .users-page { padding: 16px 14px 32px; }
    .users-table__id,
    .users-table__time { display: none; }
}

/* v0.40.0: active-user badge. Green = the ordinary, healthy state, matching
   --status-active elsewhere in the app. Pairs with .users-badge--inactive. */
.users-badge--active {
    border-color: rgba(34, 197, 94, 0.45);
    background: rgba(34, 197, 94, 0.14);
    color: #86efac;
}

/* v0.40.0: explanatory hint inside a modal field group. The shared .modal__*
   family had no hint rung — every existing dialog used only label + input.
   --warn variant is for the hard-delete confirmation, where the consequence
   (history attribution is destroyed) must be legible before the user commits. */
/* Agent login row: local part + @ + domain dropdown on one line. The local input flexes so a
   long name never squeezes the domain select below a readable width; the select is sized to its
   content so both domains stay fully legible. */
.modal__email-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.modal__email-local {
    flex: 1 1 auto;
    min-width: 0;   /* allow shrink inside flex instead of overflowing the modal */
}

/* Platform select sits LEFT of the id input (reads "discord <id>"), so unlike the domain select
   it must not shrink -- the platform name is the label for what the number means. */
.modal__platform-select {
    flex: 0 0 auto;
    width: auto;
}

.modal__email-at {
    color: var(--text-secondary);
    font-size: 13px;
    flex: 0 0 auto;
}

.modal__email-domain {
    flex: 0 1 auto;
    width: auto;
}

.modal__hint {
    display: block;
    color: var(--text-secondary);
    font-size: 11.5px;
    line-height: 1.5;
    margin-top: 4px;
}

.modal__hint--warn {
    color: #fca5a5;
}

/* ── #328 channel tool rows (API v1.44.0) ─────────────────────────────────────
   A tool_call row is machine activity, not speech. It deliberately does NOT use the
   bubble/avatar/side treatment: no portrait, no sender name, no left/right assignment, so it
   reads as an event in the transcript rather than as an agent saying something.

   Distinct from TimelineView's .tool-expando, which represents a tool_use/tool_result PAIR and
   shows pending/complete/duration state. The channel wire has no result half, so there is no
   pending state to animate and no duration to report -- showing one would be fiction. */
.channel-row--tool {
    display: block;
    margin: 2px 0 2px 52px;   /* aligns with bubble text, past the avatar gutter */
}

.channel-tool {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 4px 10px;
    border-left: 2px solid #4a5568;
    background: rgba(255, 255, 255, 0.02);
    border-radius: 0 4px 4px 0;
    font-size: 12px;
    color: #a0aec0;
}

.channel-tool__icon { opacity: 0.7; }

.channel-tool__name {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: #cbd5e0;
}

.channel-tool__badge {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 1px 6px;
    border-radius: 3px;
    /* Text label carries the meaning, colour only reinforces it (WCAG 1.4.1). */
}

.channel-tool__badge--redacted  { background: rgba(237, 137, 54, 0.18); color: #ed8936; }
.channel-tool__badge--truncated { background: rgba(160, 174, 192, 0.15); color: #a0aec0; }

/* Peer attribution (2026-09-06): work performed by one agent inside ANOTHER agent's channel.
   Reuses the existing --peer-* tokens already used for agent-to-agent traffic (channel pips,
   peer chat) so delegated work reads as peer traffic rather than a new visual language — no
   new colour introduced. Not uppercased like the status badges above: this carries AGENT NAMES,
   and uppercasing proper nouns hurts readability. Per the parent rule, the "Performer → Owner"
   text carries the meaning and colour only reinforces it (WCAG 1.4.1). */
.channel-tool__badge--delegated {
    background: var(--peer-bg);
    color: var(--peer-accent);
    border: 1px solid var(--peer-border);
    text-transform: none;
    letter-spacing: 0;
    font-weight: 600;
    white-space: nowrap;
}

/* The shared separator is dimmed inside the badge so the two agent names stay dominant. */
.channel-tool__badge--delegated .tool-expando__separator {
    opacity: 0.7;
    margin: 0 2px;
}

/* ── Inter-session relay rendering (v0.58.0) ──────────────────────────────────────────────
   Stace 2026-08-23: a relayed message must read like a normal reply, person-to-person, with
   the routing guts collapsed. These styles deliberately REUSE the existing sender/tool-row
   vocabulary rather than inventing a new visual language, because the whole point is that a
   relayed message looks CONSISTENT with every other message kind in the feed. */

/* Screen-reader-only. Did not exist in this sheet before; the relay sender line needs it so the
   "→" glyph (decorative, aria-hidden) still reads as a real relationship to assistive tech. */
.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;
}

/* "Carl → Adam". Inherits .timeline-view__sender sizing/weight; the ORIGINATOR keeps the
   full-strength sender colour and the recipient is de-emphasised, so the eye lands on who
   spoke. Name text always present -- colour is never the only differentiator (WCAG 1.4.1). */
.channel-row__relay-from {
    font-weight: 600;
}

.channel-row__relay-arrow {
    margin: 0 0.25rem;
    opacity: 0.65;
}

.channel-row__relay-to {
    font-weight: 500;
    opacity: 0.75;
}

/* Collapsed routing detail. Sits INSIDE the bubble, below the prose, styled as a quiet
   affordance rather than a competing element -- it is debugging data, not content. */
.channel-relay {
    margin-top: 0.4rem;
    border-top: 1px solid var(--border);
    padding-top: 0.3rem;
}

.channel-relay__summary {
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    list-style: none;
    font-size: 10px;
    opacity: 0.7;
    transition: opacity 0.15s ease;
}

.channel-relay__summary::-webkit-details-marker { display: none; }

.channel-relay__summary:hover { opacity: 1; }

.channel-relay__summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 3px;
}

.channel-relay__summary::before {
    content: "▸";
    display: inline-block;
    transition: transform 0.15s ease;
}

.channel-relay[open] .channel-relay__summary::before {
    transform: rotate(90deg);
}

/* Same badge treatment as tool rows, so "relayed" reads as the same CLASS of metadata. */
.channel-relay__badge {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 1px 6px;
    border-radius: 3px;
    background: rgba(139, 92, 246, 0.18);
    color: #a78bfa;
}

.channel-relay__tool {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 10px;
    opacity: 0.8;
}

/* Key/value grid. min-content on the key column keeps long session keys from being squeezed
   into a one-character-per-line ribbon in a narrow bubble. */
.channel-relay__detail {
    display: grid;
    grid-template-columns: min-content 1fr;
    gap: 2px 10px;
    margin: 0.35rem 0 0;
    font-size: 10px;
    line-height: 1.45;
}

.channel-relay__detail dt {
    font-weight: 600;
    opacity: 0.6;
    white-space: nowrap;
}

.channel-relay__detail dd {
    margin: 0;
    font-family: var(--font-mono, ui-monospace, monospace);
    /* Session keys are long, colon-heavy and unbreakable -- force them to wrap instead of
       widening the bubble or overflowing it. */
    overflow-wrap: anywhere;
    opacity: 0.85;
}

.channel-tool__toggle {
    margin-left: auto;
    background: none;
    border: none;
    color: #63b3ed;
    font-size: 11px;
    cursor: pointer;
    padding: 2px 4px;
    border-radius: 3px;
}

.channel-tool__toggle:hover { text-decoration: underline; }
.channel-tool__toggle:focus-visible { outline: 2px solid #63b3ed; outline-offset: 1px; }

.channel-tool__payload {
    margin: 4px 0 0 12px;
    padding: 8px 10px;
    background: #12161f;
    border: 1px solid #2d3748;
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    line-height: 1.5;
    color: #cbd5e0;
    white-space: pre-wrap;   /* payload may be one long JSON line */
    word-break: break-word;
    max-height: 320px;
    overflow: auto;
}

/* ─── #348: correlated cross-agent exchange threading (channel feed) ──────────────────────────
   Groups consecutive channel rows that share a non-null correlationId into ONE threaded frame,
   reusing the SAME visual language as the subagent-transcript grouping (a quiet left rail +
   indentation, purple accent, lighter weight so the thread reads as "an inner exchange" rather
   than competing with top-level chat). Only rows with a real correlationId get .channel-corr;
   every ordinary row (the common null case) is untouched by all of this. Sides/attribution are
   unchanged — this is purely a wrapping frame. */

/* Thread head: a single quiet marker above the first row of a correlated run. */
.channel-corr__head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 8px 0 0 64px;      /* align the rail with the bubble gutter, mirroring subtranscript */
    padding-left: 12px;
    border-left: 2px solid rgba(168, 85, 247, 0.45);
}
.channel-corr__badge {
    display: inline-flex;
    align-items: center;
    padding: 1px 8px;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: rgba(216, 180, 254, 0.95);
    background: rgba(168, 85, 247, 0.12);
    border: 1px solid rgba(168, 85, 247, 0.35);
    border-radius: 999px;
}
.channel-corr__closed-tag {
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--text-secondary, #8a8a99);
    opacity: 0.85;
}
.channel-corr__head--closed .channel-corr__badge {
    color: var(--text-secondary, #9a9aa8);
    background: rgba(168, 85, 247, 0.06);
    border-color: rgba(168, 85, 247, 0.2);
}

/* The threaded rows themselves: a continuous purple rail down the left of the whole run, with the
   run indented so it visibly nests under its head. --start/--end round the rail so a run reads as a
   single bracketed exchange rather than N unrelated stripes. */
.channel-row.channel-corr {
    margin-left: 12px;
    padding-left: 12px;
    border-left: 2px solid rgba(168, 85, 247, 0.3);
    position: relative;
}
.channel-row.channel-corr--start {
    border-top-left-radius: 8px;
    margin-top: 2px;
}
.channel-row.channel-corr--end {
    border-bottom-left-radius: 8px;
    margin-bottom: 8px;
    padding-bottom: 4px;
}
/* A finalized exchange: dim the rail so a closed thread recedes without disappearing (it stays
   auditable). Deliberately does not hide anything — close is a de-emphasis, not a delete. */
.channel-row.channel-corr--closed {
    border-left-color: rgba(168, 85, 247, 0.16);
    opacity: 0.82;
}

/* ═══ #354 Channel composer (Phase B) ═════════════════════════════════════════════════════
   Scoped to .channel-composer* only (design §8). No new design language: reuses the app's
   existing surface/border/text tokens so the composer reads as native to ChannelChat. Sits as
   the last child of the .channel-chat <section>; the feed above scrolls, this stays put. */
.channel-composer {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 12px;
    border-top: 1px solid var(--color-border, var(--border));
    background: var(--color-bg-secondary, var(--bg-secondary, var(--surface)));
}
.channel-composer--disabled {
    opacity: 0.85;
}
.channel-composer__disabled-note {
    margin: 0;
    padding: 8px 10px;
    font-size: 0.85rem;
    color: var(--color-text-secondary, var(--text-secondary));
    background: var(--color-bg-primary, var(--bg-primary));
    border: 1px dashed var(--color-border, var(--border));
    border-radius: 8px;
}
.channel-composer__input-row {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}
.channel-composer__textarea {
    flex: 1 1 auto;
    min-height: 38px;
    max-height: 148px;              /* ~6 lines then scroll (matches JS autoGrow cap) */
    resize: none;
    overflow-y: hidden;            /* JS/field-sizing flips to auto past the cap */
    field-sizing: content;         /* progressive enhancement; JS autoGrow is the fallback */
    padding: 8px 10px;
    font: inherit;
    line-height: 1.4;
    color: var(--color-text-primary, var(--text-primary));
    background: var(--color-bg-primary, var(--bg-primary));
    border: 1px solid var(--color-border, var(--border));
    border-radius: 8px;
}
.channel-composer__textarea:focus {
    outline: none;
    border-color: var(--color-accent, var(--accent));
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent, var(--accent)) 30%, transparent);
}
.channel-composer__textarea:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}
.channel-composer__send {
    flex: 0 0 auto;
    padding: 8px 16px;
    font: inherit;
    font-weight: 600;
    color: #fff;
    background: var(--color-accent, var(--accent));
    border: 1px solid var(--color-accent, var(--accent));
    border-radius: 8px;
    cursor: pointer;
}
.channel-composer__send:hover:not(:disabled) {
    filter: brightness(1.08);
}
.channel-composer__send:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.channel-composer__status {
    min-height: 1em;
    font-size: 0.8rem;
    color: var(--color-text-secondary, var(--text-secondary));
}
.channel-composer__status-text--error {
    color: var(--color-status-tooluse, #e0554e);
}

/* ─── #369 composer attachments (Discord parity) ──────────────────────────────── */
.channel-composer__file-input {
    /* Hidden native input; driven by the paperclip button + paste/drop shims. */
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0);
    white-space: nowrap; border: 0;
}
.channel-composer__attach {
    flex: 0 0 auto;
    padding: 8px 10px;
    font-size: 1.05rem;
    line-height: 1;
    color: var(--color-text-secondary, var(--text-secondary));
    background: transparent;
    border: 1px solid var(--color-border, var(--border));
    border-radius: 8px;
    cursor: pointer;
}
.channel-composer__attach:hover:not(:disabled) {
    color: var(--color-text-primary, var(--text-primary));
    border-color: var(--color-accent, var(--accent));
}
.channel-composer__attach:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.channel-composer__input-row--dragover {
    outline: 2px dashed var(--color-accent, var(--accent));
    outline-offset: 3px;
    border-radius: 10px;
    background: color-mix(in srgb, var(--color-accent, var(--accent)) 8%, transparent);
}
.channel-composer__attachment {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
    padding: 6px 8px;
    background: var(--color-bg-secondary, var(--bg-secondary));
    border: 1px solid var(--color-border, var(--border));
    border-radius: 8px;
    max-width: 100%;
}
.channel-composer__attachment--error {
    border-color: var(--color-status-tooluse, #e0554e);
}
.channel-composer__attachment-thumb {
    width: 40px; height: 40px;
    object-fit: cover;
    border-radius: 6px;
    flex: 0 0 auto;
}
.channel-composer__attachment-icon {
    font-size: 1.2rem;
    flex: 0 0 auto;
}
.channel-composer__attachment-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.85rem;
    color: var(--color-text-primary, var(--text-primary));
}
.channel-composer__attachment-status {
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--color-text-secondary, var(--text-secondary));
}
.channel-composer__attachment-status--error {
    color: var(--color-status-tooluse, #e0554e);
}
.channel-composer__attachment-remove {
    flex: 0 0 auto;
    width: 22px; height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    line-height: 1;
    color: var(--color-text-secondary, var(--text-secondary));
    background: transparent;
    border: none;
    border-radius: 50%;
    cursor: pointer;
}
.channel-composer__attachment-remove:hover {
    color: #fff;
    background: var(--color-status-tooluse, #e0554e);
}

/* ─── #369 rendered attachments in the feed ───────────────────────────────────── */
.channel-attachment {
    display: inline-flex;
    margin-top: 6px;
    text-decoration: none;
    max-width: 100%;
}
.channel-attachment--image {
    border-radius: 8px;
    overflow: hidden;
}
.channel-attachment__img {
    display: block;
    max-width: min(380px, 100%);
    max-height: 320px;
    object-fit: contain;
    border-radius: 8px;
    border: 1px solid var(--color-border, var(--border));
    background: var(--color-bg-secondary, var(--bg-secondary));
}
.channel-attachment--file {
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: var(--color-bg-secondary, var(--bg-secondary));
    border: 1px solid var(--color-border, var(--border));
    border-radius: 8px;
    color: var(--color-text-primary, var(--text-primary));
}
.channel-attachment--file:hover {
    border-color: var(--color-accent, var(--accent));
}
.channel-attachment__icon { font-size: 1.1rem; flex: 0 0 auto; }
.channel-attachment__name {
    font-size: 0.85rem;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.channel-attachment__hint {
    flex: 0 0 auto;
    color: var(--color-text-secondary, var(--text-secondary));
    font-size: 0.9rem;
}

/* Per-bubble optimistic affordance (rendered inside a message row in ChannelChat). */
.channel-composer__pending {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
    font-size: 0.75rem;
    color: var(--color-text-secondary, var(--text-secondary));
}
.channel-composer__pending--failed {
    color: var(--color-status-tooluse, #e0554e);
}
.channel-composer__pending-spinner {
    width: 10px;
    height: 10px;
    border: 2px solid color-mix(in srgb, var(--color-text-secondary, var(--text-secondary)) 40%, transparent);
    border-top-color: var(--color-accent, var(--accent));
    border-radius: 50%;
    animation: channel-composer-spin 0.7s linear infinite;
}
@keyframes channel-composer-spin { to { transform: rotate(360deg); } }
.channel-composer__pending-tick { color: var(--color-status-active, var(--status-active, #3fb950)); }
.channel-composer__pending-retry {
    padding: 1px 8px;
    font: inherit;
    font-size: 0.72rem;
    color: var(--color-accent, var(--accent));
    background: transparent;
    border: 1px solid var(--color-accent, var(--accent));
    border-radius: 6px;
    cursor: pointer;
}
.channel-composer__pending-retry:hover { filter: brightness(1.1); }







/* ─── #354 CREATE-CHANNEL DIALOG (Pixel) ──────────────────────────────────────────────────────
   Plain scrim + card, brand tokens only (no MudBlazor). Mirrors the sidebar's design language:
   --bg-secondary card on a dimmed --bg-primary scrim, --accent for the primary action. */
.channel-list__header-actions {
    display: flex;
    align-items: center;
    gap: 0.375rem;
}
.channel-list__new {
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-emphasis);
    background: var(--accent);
    border: none;
    border-radius: 6px;
    padding: 0.25rem 0.5rem;
    cursor: pointer;
    line-height: 1.2;
    white-space: nowrap;
}
.channel-list__new:hover:not(:disabled) { filter: brightness(1.1); }
.channel-list__new:disabled { opacity: 0.5; cursor: default; }
.channel-list__new:focus-visible { outline: 2px solid var(--text-emphasis); outline-offset: 2px; }

.channel-create__scrim {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.66);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 1rem;
}
.channel-create__card {
    width: 100%;
    max-width: 26rem;
    max-height: 90vh;
    overflow-y: auto;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.25rem;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
}
.channel-create__title {
    margin: 0;
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--text-emphasis);
}
.channel-create__field {
    display: flex;
    flex-direction: column;
    gap: 0.3125rem;
    border: none;
    margin: 0;
    padding: 0;
    min-width: 0;
}
.channel-create__label {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}
.channel-create__input {
    font: inherit;
    color: var(--text-primary);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.4375rem 0.5rem;
    width: 100%;
    box-sizing: border-box;
}
.channel-create__input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.channel-create__input:disabled { opacity: 0.6; }
.channel-create__hint {
    font-size: 0.6875rem;
    color: var(--text-secondary);
}
.channel-create__hint code {
    color: var(--accent);
    background: var(--bg-primary);
    padding: 0.05rem 0.25rem;
    border-radius: 4px;
    font-size: 0.6875rem;
    word-break: break-all;
}
.channel-create__responders {
    gap: 0.375rem;
    max-height: 11rem;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.5rem;
    background: var(--bg-primary);
}
.channel-create__responders legend {
    padding: 0 0.25rem;
}
.channel-create__agent {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    color: var(--text-primary);
    cursor: pointer;
    padding: 0.1875rem 0;
}
.channel-create__agent input { accent-color: var(--accent); }

/* ── #373: DB-backed responder picker (avatar card grid) ─────────────────────── */
.channel-create__pickerfs {
    border: none;
    padding: 0;
    margin: 0;
    min-width: 0;
}
.channel-create__pickerlegend {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    width: 100%;
    padding: 0;
}
.channel-create__count {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-radius: 999px;
    padding: 0.0625rem 0.5rem;
}
.channel-create__search {
    position: relative;
    margin-bottom: 0.5rem;
}
.channel-create__search-icon {
    position: absolute;
    left: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.75rem;
    opacity: 0.6;
    pointer-events: none;
}
.channel-create__search-input {
    width: 100%;
    font: inherit;
    font-size: 0.8125rem;
    color: var(--text-primary);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.375rem 0.5rem 0.375rem 1.75rem;
    box-sizing: border-box;
}
.channel-create__search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.channel-create__agentgrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
    gap: 0.5rem;
    max-height: 15rem;
    overflow-y: auto;
    padding: 0.125rem;
}
.channel-create__agentcard {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.375rem;
    padding: 0.625rem 0.5rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-primary);
    cursor: pointer;
    text-align: center;
    transition: border-color 0.12s ease, background 0.12s ease, transform 0.08s ease;
}
.channel-create__agentcard:hover {
    border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
    background: color-mix(in srgb, var(--accent) 6%, var(--bg-primary));
}
.channel-create__agentcard:active { transform: scale(0.98); }
.channel-create__agentcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.channel-create__agentcard--selected {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, var(--bg-primary));
}
.channel-create__avatar {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--accent) 20%, var(--bg-secondary));
    flex: 0 0 auto;
}
.channel-create__avatar img { width: 100%; height: 100%; object-fit: cover; }
.channel-create__avatar-initials {
    font-size: 0.9375rem;
    font-weight: 700;
    color: var(--text-emphasis);
    letter-spacing: 0.02em;
}
.channel-create__agentname {
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-primary);
    line-height: 1.15;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.channel-create__check {
    position: absolute;
    top: 0.375rem;
    left: 0.375rem;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: 50%;
    background: var(--accent);
    color: var(--text-emphasis);
    font-size: 0.7rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.channel-create__starbtn {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    border: none;
    background: transparent;
    color: var(--text-secondary);
    font-size: 0.95rem;
    line-height: 1;
    padding: 0.125rem;
    cursor: pointer;
    border-radius: 4px;
    opacity: 0.75;
    transition: color 0.12s ease, opacity 0.12s ease, transform 0.08s ease;
}
.channel-create__starbtn:hover { color: #fbbf24; opacity: 1; transform: scale(1.15); }
.channel-create__starbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
.channel-create__starbtn--on { color: #fbbf24; opacity: 1; }
.channel-create__defaulthint {
    margin: 0.5rem 0 0;
    font-size: 0.75rem;
    color: var(--text-secondary);
}
.channel-create__star-inline, .channel-create__defaulthint .channel-create__star-inline { color: #fbbf24; }

.channel-create__empty {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--text-secondary);
}
.channel-create__error {
    margin: 0;
    font-size: 0.8125rem;
    color: #f87171;
    background: rgba(248, 113, 113, 0.1);
    border: 1px solid rgba(248, 113, 113, 0.3);
    border-radius: 6px;
    padding: 0.4375rem 0.5rem;
}
.channel-create__actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.25rem;
}
.channel-create__cancel,
.channel-create__submit {
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    border-radius: 6px;
    padding: 0.4375rem 0.875rem;
    cursor: pointer;
    border: 1px solid var(--border);
}
.channel-create__cancel {
    background: transparent;
    color: var(--text-secondary);
}
.channel-create__cancel:hover:not(:disabled) { color: var(--text-primary); border-color: var(--text-secondary); }
.channel-create__submit {
    background: var(--accent);
    color: var(--text-emphasis);
    border-color: var(--accent);
}
.channel-create__submit:hover:not(:disabled) { filter: brightness(1.1); }
.channel-create__cancel:disabled,
.channel-create__submit:disabled { opacity: 0.5; cursor: default; }

<!-- deploy: v0.84.0 create-channel UI, fire SWA auto-trigger (manual queue stalled) 2026-08-30 -->

<!-- deploy: v0.85.0 Discord-parity composer (Alt+Enter newline), fire SWA auto-trigger 2026-08-30 -->

<!-- deploy2: v0.85.0 fire SWA (retry auto-trigger; manual queue stalled) 2026-08-30 -->
