/* Components — popover/kebab + msg-action + icon sprite */

/* ----- Hover-revealed actions on assistant bubbles ------------------- */
.chat-msg-actions {
    display: flex;
    gap: 2px;
    margin-top: 4px;
    opacity: 0;
    transition: opacity 0.15s;
}

/* Continue button — appears in the composer after a turn paused at the cap. */
.composer-continue-btn {
    display: inline-flex; align-items: center; gap: 6px;
    margin-right: auto; padding: 5px 12px;
    border: 1px solid var(--primary); border-radius: 999px;
    background: var(--primary-light); color: var(--primary);
    font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
}
.composer-continue-btn:hover { background: var(--primary); color: #fff; }
.composer-continue-btn .icon { width: 14px; height: 14px; }
.chat-msg-assistant:hover .chat-msg-actions,
.chat-msg-actions:focus-within {
    opacity: 1;
}
/* Only the turn-final assistant message carries copy/regenerate. An assistant
   message immediately followed by a tool group or another assistant message is
   mid-turn (e.g. the "Let me look…" preamble before tool calls), so drop its
   actions — one set per turn, on the final answer, and it reclaims the vertical
   space the buttons would otherwise reserve under every bubble. */
.chat-msg-assistant:has(+ .tool-group) .chat-msg-actions,
.chat-msg-assistant:has(+ .chat-msg-assistant) .chat-msg-actions {
    display: none;
}
/* …EXCEPT when that tool group is the LAST thing in the thread — the turn was
   interrupted/incomplete (e.g. a tool denied by the restart sweep) and never
   produced a final assistant answer. Without this, a broken turn has no retry
   affordance at all. Re-show the preceding assistant's actions AND keep them
   visible (not hover-only) so Regenerate is always reachable on a broken turn. */
.chat-msg-assistant:has(+ .tool-group:last-child) .chat-msg-actions {
    display: flex;
    opacity: 1;
}
.msg-action {
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--text-dim);
    cursor: pointer;
    font-family: inherit;
    transition: background 0.12s, color 0.12s;
}
.msg-action:hover {
    background: var(--primary-light);
    color: var(--primary);
}
.msg-action[data-copied] {
    color: var(--success);
    background: var(--success-bg);
}
.msg-action[data-copied]::after {
    content: '✓';
    font-size: 11px;
    margin-left: 2px;
}
/* Touch devices: actions always visible (no hover) */
@media (hover: none) {
    .chat-msg-actions { opacity: 1; transform: none; }
}

/* =====================================================================
   Reusable popover menu primitives.
   Pair: .popover-wrap > .kebab-btn + .popover-menu > .popover-item[+]
   Open/close is controlled by the client-only signal $_menu.open.
   ===================================================================== */

.popover-wrap {
    position: relative;
    flex-shrink: 0;
}

.kebab-btn {
    width: 28px;
    height: 28px;
    margin-right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    background: transparent;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    opacity: 0;                 /* hidden until parent .conversation-item hovers */
    transition: opacity 0.15s, background 0.15s, color 0.15s;
}
.conversation-item:hover .kebab-btn,
.kebab-btn.kebab-active,
.kebab-btn:focus-visible {
    opacity: 1;
}
.kebab-btn:hover {
    background: rgba(0,0,0,0.06);
    color: var(--text);
}

.popover-menu {
    position: absolute;
    top: calc(100% + 4px);
    min-width: 160px;
    /* Cap so the menu never exceeds the viewport — on iPad / mobile
       a left-anchored header popover would otherwise extend past
       the right edge and get cut off. Vertical cap + scroll handles
       tall content (e.g. lots of bindings or skills). */
    max-width: calc(100vw - 24px);
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.10), 0 2px 6px rgba(0, 0, 0, 0.06);
    padding: 4px;
    z-index: 50;
    /* simple entrance */
    animation: popover-in 0.10s ease-out;
}
.popover-menu-right { right: 0; }
.popover-menu-left  { left: 0; }

/* On narrow viewports (iPad portrait, phone), header-anchored
   popovers should align to the RIGHT edge of their trigger so they
   open toward the center rather than spilling off the right of the
   screen. The chat header is right-loaded (Persona, Skills, Memory,
   Channels all near the right edge). */
@media (max-width: 900px) {
    .chat-header .popover-menu-left {
        left: auto;
        right: 0;
    }
}
@keyframes popover-in {
    from { opacity: 0; transform: translateY(-3px); }
    to   { opacity: 1; transform: translateY(0); }
}

.popover-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    background: transparent;
    border: none;
    text-align: left;
    padding: 7px 10px;
    border-radius: 6px;
    color: var(--text);
    cursor: pointer;
    font-size: 13.5px;
    transition: background 0.12s, color 0.12s;
}
.popover-item:hover { background: var(--bg-surface); }
.popover-item svg { color: var(--text-dim); flex-shrink: 0; }
.popover-item:hover svg { color: var(--text); }
.popover-item-danger { color: var(--error); }
.popover-item-danger svg { color: var(--error); }
.popover-item-danger:hover { background: var(--error-bg); }
.popover-item-danger:hover svg { color: var(--error); }

.popover-divider {
    height: 1px;
    background: var(--border);
    margin: 4px 2px;
}

/* Outside-click backdrop: covers the viewport beneath the menu so clicking
   anywhere else clears $_menu.open. Invisible (transparent), no pointer
   blocking when not shown. */
.menu-backdrop {
    position: fixed;
    inset: 0;
    background: transparent;
    z-index: 40;        /* below popover-menu (z 50), above page content */
}

/* ----- Icon sprite usage --------------------------------------------- */
/* Default size = 1em. Inline-SVG with <use href="/static/icons.svg#…"/>.
   Inherits color via currentColor so any button/badge wrapping an icon
   can paint it just by setting `color`. */
.icon {
    width: 1em;
    height: 1em;
    vertical-align: -0.15em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    /* fallback for symbols that use fill instead of stroke; their own
       fill="currentColor" overrides this when inherited. */
}
/* Composer-area icons feel right slightly larger than their em-size. */
.composer-icon-btn .icon { width: 1.1em; height: 1.1em; }
.header-icon-btn .icon   { width: 1em;   height: 1em; }
.btn-newchat .icon       { width: 14px;  height: 14px; }
.chat-model-badge .icon  { width: 14px;  height: 14px; color: var(--primary-muted); }
.kebab-btn .icon         { width: 14px;  height: 14px; }
.popover-item .icon      { width: 14px;  height: 14px; }
.msg-action .icon        { width: 14px;  height: 14px; }
.chat-empty-icon .icon   { width: 44px;  height: 44px; }


/* ----- Tool call cards ----------------------------------------------- */
/* Same centered max-width column as messages — placed via chat-thread
   so it inherits the rule .chat-thread-messages > * { ... }.            */
.tool-call {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 12px 14px 14px;
    font-size: 13px;
    color: var(--text-secondary);
}

/* Tool-call group: one collapsible <details> box wrapping a run of tool
   calls. Server-rendered (no JS), closed by default on full render. The
   summary is the always-visible row ("⚙ N tool calls"); clicking it
   reveals the individual tool cards in .tool-group-body. */
.tool-group {
    max-width: var(--chat-col);
    margin: 0 auto 14px;
    padding: 0 24px;
    width: 100%;
}
/* When the disclosure is nested INSIDE an answer bubble, the bubble already
   provides the centered lane + 24px gutter — so drop the tool-group's own lane
   geometry (else it double-indents) and just add breathing room above it. */
.chat-msg-answer .tool-group {
    max-width: none;
    margin: 16px 0 2px;
    padding: 0;
    width: 100%;
}
.tool-group-summary {
    cursor: pointer;
    list-style: none;
    user-select: none;
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 8px 14px;
    color: var(--text-secondary);
    font-size: 12.5px;
}
.tool-group-summary::-webkit-details-marker { display: none; }
.tool-group-summary:hover { border-color: var(--primary-muted); }
.tool-group-summary::after {
    content: "▸";
    margin-left: auto;
    color: var(--text-dim);
    transition: transform 0.15s;
}
.tool-group[open] > .tool-group-summary::after { transform: rotate(90deg); }
.tool-group-summary .icon { width: 14px; height: 14px; color: var(--primary-muted); }
.tool-group-count {
    background: var(--primary-muted);
    color: var(--bg);
    font-weight: 700;
    font-size: 11px;
    padding: 1px 7px;
    border-radius: 8px;
    min-width: 16px;
    text-align: center;
}
.tool-group-label { font-weight: 600; color: var(--text); }

/* Sources disclosure — the pages an answer grounded in, collapsed like the
   Worked steps. Lives inside the answer bubble, below the Worked block. */
.sources-list { margin: 8px 0 2px; }
.sources-summary {
    cursor: pointer; list-style: none; user-select: none;
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-surface); border: 1px solid var(--border);
    border-radius: 12px; padding: 8px 14px;
    color: var(--text-secondary); font-size: 12.5px;
}
.sources-summary::-webkit-details-marker { display: none; }
.sources-summary:hover { border-color: var(--primary-muted); }
.sources-summary::after { content: "▸"; margin-left: auto; color: var(--text-dim); transition: transform 0.15s; }
.sources-list[open] > .sources-summary::after { transform: rotate(90deg); }
.sources-summary .icon { width: 14px; height: 14px; color: var(--primary-muted); }
.sources-label { font-weight: 600; color: var(--text); }
.sources-count {
    background: var(--primary-muted); color: var(--bg);
    font-weight: 700; font-size: 11px; padding: 1px 7px; border-radius: 8px;
    min-width: 16px; text-align: center;
}
.sources-body { margin: 8px 0 0; padding: 0 0 0 23px; list-style: none; display: flex; flex-direction: column; gap: 4px; }
.sources-body li { font-size: 13px; line-height: 1.4; }
.sources-body a {
    color: var(--primary); text-decoration: none;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    display: inline-block; max-width: 100%; vertical-align: bottom;
}
.sources-body a:hover { text-decoration: underline; }

/* Cards inside the group don't get the centered-lane treatment (the
   group wrapper already provides it) — strip per-card lane geometry and
   stack them tightly. */
.tool-group-body { margin-top: 8px; }
.tool-group-body > .chat-msg-tool {
    max-width: none;
    margin: 0 0 8px;
    padding: 0;
    width: 100%;
}
.tool-group-body > .chat-msg-tool:last-child { margin-bottom: 0; }
.tool-call-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: nowrap;
    min-width: 0;
}
.tool-call-icon { color: var(--primary-muted); display: inline-flex; flex-shrink: 0; }
/* Status glyph in front of a finished command: ✓ clean exit, ! failure. */
.tool-status { font-weight: 800; font-size: 15px; line-height: 1; display: inline-flex; width: 14px; justify-content: center; }
.tool-status-ok { color: hsl(142, 55%, 40%); }
.tool-status-err { color: var(--error); }
.tool-call-name {
    font-weight: 700;
    color: var(--text);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    flex-shrink: 0;
}
.tool-call-cmd {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Blank: no box/background — just the command text inline. */
    background: none;
    border: none;
    padding: 0;
    border-radius: 0;
    color: var(--text);
    font-size: 12.5px;
}

/* Steps folded inside a "Worked · N steps" disclosure are not standalone
   responses: hide copy/refresh, strip the card chrome, and tighten the gap
   between a narration line and its command. */
.tool-group-body .chat-msg-actions { display: none; }
.tool-group-body > .chat-msg {
    max-width: none;
    margin: 0 0 4px;
    padding: 0;
    width: 100%;
}
.tool-group-body > .chat-msg:last-child { margin-bottom: 0; }
.tool-group-body .chat-msg-content { font-size: 13px; margin: 0; }
.tool-group-body .tool-call {
    background: none;
    border: none;
    padding: 2px 0;
}

/* Small dimmed line directly under the command — exit code, duration,
   "awaiting approval", etc. Far less prominent than the command itself. */
.tool-call-meta {
    margin-top: 6px;
    font-size: 11.5px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    /* indent to roughly align with where the command starts */
    padding-left: 30px;
}

/* per-status accents */
.tool-call-pending { border-color: hsl(40, 85%, 70%); background: rgba(255, 193, 7, 0.06); }
.tool-call-pending .tool-call-meta { color: hsl(38, 80%, 35%); font-weight: 600; }
.tool-call-running { border-color: var(--primary-muted); }
.tool-call-running .tool-call-meta { color: var(--primary); }
.tool-call-denied  { border-color: var(--text-dim); opacity: 0.85; }
.tool-call-blocked,
.tool-call-timeout { border-color: var(--error); background: var(--error-bg); }
.tool-call-blocked .tool-call-meta,
.tool-call-timeout .tool-call-meta { color: var(--error); font-weight: 600; }

.tool-call-actions {
    margin-top: 14px;
    display: flex;
    gap: 8px;
    padding-left: 30px;
}
/* Sizing comes from .btn-sm; this only sets the success color. */
.tool-approve {
    background: var(--success);
    box-shadow: none;
}
.tool-approve:hover { filter: brightness(1.05); }

.tool-call-output {
    margin-top: 14px;
    padding-left: 30px;
}
.tool-call-output summary {
    cursor: pointer;
    color: var(--text-dim);
    font-size: 12px;
    user-select: none;
    padding: 2px 0;
}
.tool-call-output summary:hover { color: var(--text); }
.tool-call-output pre {
    margin-top: 6px;
    background: #0f172a;
    color: #e2e8f0;
    border-radius: 8px;
    padding: 10px 12px;
    font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    font-size: 12px;
    line-height: 1.5;
    max-height: 320px;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-all;
}

/* ----- Header skill picker button (sibling of .chat-model-badge) -----
   Same pill geometry as the model badge, with hover + open states since
   this one is clickable. */
.header-skill-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 10px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 12px;
    font-family: inherit;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.header-skill-btn:hover {
    color: var(--primary);
    border-color: var(--primary-muted);
}
.header-skill-btn.kebab-active {
    color: var(--primary);
    border-color: var(--primary);
    background: var(--primary-light);
}
.header-skill-label { font-weight: 600; }
.header-skill-value {
    font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    font-size: 11px;
    color: var(--primary);
    background: transparent;
    padding: 0;
    border-radius: 0;
    max-width: 18ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.header-skill-value.dim { color: var(--text-dim); }
.header-skill-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    background: var(--primary);
    color: #fff;
    border-radius: 8px;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
}

/* Popover menu: wider variant for skill picker (which can have long titles) */
.popover-menu-wide { min-width: 240px; max-width: 320px; }
.popover-empty {
    padding: 10px 12px;
    color: var(--text-dim);
    font-size: 12.5px;
}
.popover-empty a { color: var(--primary); font-weight: 600; text-decoration: none; }
.popover-empty a:hover { text-decoration: underline; }

.popover-item-checkable { gap: 8px; }
.popover-check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 1.5px solid var(--border);
    border-radius: 4px;
    color: transparent;
    font-size: 11px;
    flex-shrink: 0;
    background: var(--bg);
}
.popover-check-on {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
    font-weight: 700;
}
.popover-item-label { flex: 1; }
.popover-item-link {
    text-decoration: none;
    color: var(--text);
    font-size: 13.5px;
}
.popover-item-link:hover { background: var(--bg-surface); color: var(--text); }

/* ----- Model picker: badge looks identical to the static one,
   but the wrapping button is clickable + has hover/open states. ------- */
.model-picker-btn {
    cursor: pointer;
    font-family: inherit;
    /* same shape as .chat-model-badge inherited from the class */
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.model-picker-btn:hover {
    color: var(--primary);
    border-color: var(--primary-muted);
}
.model-picker-btn.kebab-active {
    color: var(--primary);
    border-color: var(--primary);
    background: var(--primary-light);
}

/* Popover rows that need TWO lines (label + metadata sub-row). */
.popover-item-with-meta { align-items: flex-start; padding-top: 8px; padding-bottom: 8px; }
.popover-item-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}
.popover-item-sub {
    font-size: 11px;
    color: var(--text-dim);
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Persona-linked skill rows are visually present but not toggleable. */
.popover-item-locked {
    cursor: default;
    opacity: 0.75;
}
.popover-item-locked:hover { background: transparent; }
.popover-item-locked .popover-check {
    background: var(--primary-light);
    border-color: var(--primary-muted);
    color: var(--primary);
}

/* ----- Modal (memory inspector + future dialogs) --------------------- */
.modal-backdrop {
    display: none; /* shown via .modal-open (data-class) — no FOUC, no inline-style race */
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 100;
    animation: backdrop-in 0.12s ease-out;
}
.modal-backdrop.modal-open { display: block; }
@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }

.modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(900px, calc(100vw - 48px));
    height: min(640px, calc(100vh - 48px));
    background: var(--bg);
    border-radius: 14px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.22), 0 4px 12px rgba(0, 0, 0, 0.08);
    z-index: 110;
    display: none; /* hidden by default (CSS) → no flash on load; shown via .modal-open */
    flex-direction: column;
    overflow: hidden;
    animation: modal-in 0.14s ease-out;
}
.modal.modal-open { display: flex; }
@keyframes modal-in {
    from { opacity: 0; transform: translate(-50%, -48%) scale(0.985); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.modal-head {
    flex-shrink: 0;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}
.modal-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--text);
}
.modal-sub {
    font-size: 12px;
    color: var(--text-dim);
    margin-top: 2px;
}
.modal-close {
    background: transparent;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    font-size: 24px;
    line-height: 1;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: inherit;
    transition: background 0.15s, color 0.15s;
}
.modal-close:hover { background: var(--bg-surface); color: var(--text); }

/* Modal-head hamburger (reveals the memory summary-list drawer on mobile) and
   its backdrop — desktop-hidden; activated in the mobile @media below. */
.modal-head-nav {
    display: none;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-right: 6px;
    flex-shrink: 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    cursor: pointer;
}
.modal-head-nav:hover { background: var(--bg-surface); }
.memory-list-backdrop { display: none; }

.modal-body {
    flex: 1;
    min-height: 0;
    overflow: hidden;
}
.modal-body-split {
    display: grid;
    grid-template-columns: 240px 1fr;
}
.modal-empty {
    padding: 40px;
    text-align: center;
    color: var(--text);
}
.modal-empty .dim { color: var(--text-dim); font-size: 13px; margin-top: 8px; }

/* Memory list (left) */
.memory-list {
    border-right: 1px solid var(--border);
    overflow-y: auto;
    padding: 8px;
    background: var(--bg-surface);
}
.memory-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 9px 10px;
    margin-bottom: 4px;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.12s, border-color 0.12s;
}
.memory-item:hover { background: var(--bg); }
.memory-item-active {
    background: var(--bg) !important;
    border-color: var(--primary-muted);
}
.memory-item-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
}
.memory-item-range {
    font-weight: 700;
    font-size: 12.5px;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.memory-item-date {
    font-size: 11px;
    color: var(--text-dim);
}
.memory-item-meta {
    font-size: 11px;
    color: var(--text-dim);
    margin-top: 4px;
}
.memory-item-model { color: var(--primary-muted); }

/* Detail pane (right) */
.memory-detail {
    overflow-y: auto;
    padding: 20px 24px;
}
.memory-detail-pane {
    display: flex;
    flex-direction: column;
    gap: 20px;
}
.memory-detail-section h3 {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    font-weight: 700;
    color: var(--text-dim);
    margin-bottom: 8px;
}
.memory-detail-count { color: var(--text-dim); font-weight: 400; text-transform: none; letter-spacing: 0; }
.memory-narrative {
    font-size: 14px;
    line-height: 1.6;
    color: var(--text);
}

.memory-facts {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.memory-fact {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 12px;
}
.memory-fact-text {
    font-size: 13.5px;
    color: var(--text);
    line-height: 1.5;
}
.memory-fact-meta {
    margin-top: 6px;
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
}
.fact-confidence {
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 2px 7px;
    border-radius: 10px;
}
.fact-confidence-high   { background: var(--success-bg); color: var(--success); }
.fact-confidence-medium { background: var(--warning-bg); color: hsl(38, 80%, 35%); }
.fact-confidence-low    { background: var(--bg-surface); color: var(--text-dim); }
.fact-ttl {
    font-size: 11px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.fact-ttl-permanent { color: var(--primary); font-weight: 600; }

.memory-detail-meta {
    font-size: 11px;
    color: var(--text-dim);
    padding-top: 12px;
    border-top: 1px solid var(--border);
    font-variant-numeric: tabular-nums;
}

/* Mobile: stack the split */
@media (max-width: 700px) {
    /* ALL modal variants go full-screen on mobile — consistently. The
       compound selectors (.modal.modal-files = specificity 0,2,0) outrank the
       later .modal-files / .modal-narrow size rules (0,1,0), so this wins
       regardless of source order — the bug was Files/Bind keeping their
       desktop size because they're defined after the old @media rule. */
    .modal,
    .modal.modal-files,
    .modal.modal-narrow {
        top: 0;
        left: 0;
        transform: none;
        width: 100vw;
        height: 100vh;
        max-height: 100vh;
        border-radius: 0;
    }
    /* Memory: the summary list becomes an off-canvas drawer (toggled by the
       modal-head hamburger) so the narrative detail gets the full screen
       instead of being crammed under a list. */
    .modal-body-split {
        grid-template-columns: 1fr;
        grid-template-rows: minmax(0, 1fr);
        position: relative;
    }
    .memory-list {
        position: absolute;
        top: 0;
        left: 0;
        bottom: 0;
        width: 80%;
        max-width: 300px;
        z-index: 5;
        border-right: 1px solid var(--border);
        border-bottom: none;
        overflow-y: auto;
        transform: translateX(-100%);
        transition: transform 0.2s ease;
    }
    .memory-list.mem-nav-open { transform: translateX(0); }
    .memory-list-backdrop {
        display: block;
        position: absolute;
        inset: 0;
        z-index: 4;
        background: rgba(0, 0, 0, 0.4);
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s ease;
    }
    .memory-list-backdrop.mem-nav-open { opacity: 1; pointer-events: auto; }
    .modal-head-nav { display: inline-flex; }

    /* Files modal: the workspace tree becomes an off-canvas drawer (toggled by
       the modal-head hamburger) so the table fills the width but the tree is
       still reachable — ready for dir-switching when subdirs land. Drop the
       secondary columns so the filename gets the room and can wrap. */
    .files-body { position: relative; }
    .files-tree {
        position: absolute;
        top: 0;
        left: 0;
        bottom: 0;
        width: 220px;
        max-width: 80%;
        z-index: 5;
        transform: translateX(-100%);
        transition: transform 0.2s ease;
    }
    .files-tree.files-nav-open { transform: translateX(0); }
    .files-tree-backdrop {
        display: block;
        position: absolute;
        inset: 0;
        z-index: 4;
        background: rgba(0, 0, 0, 0.4);
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s ease;
    }
    .files-tree-backdrop.files-nav-open { opacity: 1; pointer-events: auto; }
    .files-col-size,
    .files-col-time,
    .files-col-creator { display: none; }
    .files-name-link { overflow-wrap: anywhere; }
    .files-table td,
    .files-table thead th { padding: 10px; }
}
.memory-tools { list-style: none; padding: 0; margin: 0; display: flex; gap: 8px; flex-wrap: wrap; }
.memory-tool { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 14px; padding: 4px 10px; font-size: 12px; }
.memory-tool code { background: transparent; color: var(--primary); padding: 0; }
.memory-tool-count { color: var(--text-dim); margin-left: 4px; font-variant-numeric: tabular-nums; }
/* ----- Modal tabs ---------------------------------------------------- */
.modal-tabs {
    flex-shrink: 0;
    display: flex;
    gap: 4px;
    padding: 0 16px;
    border-bottom: 1px solid var(--border);
    background: var(--bg);
}
.modal-tab {
    background: transparent;
    border: none;
    padding: 10px 14px;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    color: var(--text-dim);
    cursor: pointer;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    transition: color 0.15s, border-color 0.15s;
}
.modal-tab:hover { color: var(--text); }
.modal-tab-active {
    color: var(--primary);
    border-bottom-color: var(--primary);
}
.modal-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    margin-left: 4px;
    background: var(--bg-surface);
    color: var(--text-dim);
    border-radius: 9px;
    font-size: 11px;
    font-weight: 700;
}
.modal-tab-active .modal-tab-count {
    background: var(--primary);
    color: #fff;
}

.modal-pane {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.modal-body-single { overflow-y: auto; padding: 20px 24px; }

/* ----- Rules pane ---------------------------------------------------- */
.rules-intro {
    color: var(--text-dim);
    font-size: 13px;
    margin-bottom: 16px;
    line-height: 1.5;
    max-width: 60ch;
}
.rules-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.rule-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--primary);
    border-radius: 8px;
    padding: 12px 14px;
}
.rule-body { flex: 1; min-width: 0; }
.rule-text { font-size: 14px; color: var(--text); line-height: 1.5; }
.rule-meta { margin-top: 4px; font-size: 11.5px; color: var(--text-dim); }
.rule-delete { width: 28px; height: 28px; font-size: 18px; flex-shrink: 0; }

/* Popover row variant for list-with-remove: body label/sub on the left,
   small × action button on the right. Used by the Channels popover.
   Visual rhythm matches popover-item but the whole row is not a single
   button (the × is a nested action). */
.popover-item-row {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: default;
}
.popover-item-row:hover { background: transparent; }
.popover-row-action {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--text-dim);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    flex-shrink: 0;
    transition: background 0.12s, color 0.12s;
}
.popover-row-action:hover { background: var(--error-bg); color: var(--error); }

/* Hidden file input (a11y-focusable but visually 0×0). Paired with a
   <label for="…"> as the click target. Used by the composer paperclip
   and the files modal's drop-zone. */
.file-upload-input {
    position: absolute;
    width: 1px; height: 1px;
    opacity: 0;
    pointer-events: none;
}

/* Composer paperclip — small icon button at the start of the textarea
   row. Wrapped with the textarea in .chat-composer-row so they share
   one horizontal line (the composer itself is flex-direction:column).
   The <input type=file> it targets lives in a sibling <form
   class="composer-upload"> so the upload posts to the files endpoint,
   not /send. */
.composer-upload { display: contents; }
.chat-composer-row {
    display: flex;
    align-items: flex-start;  /* paperclip pins to top as textarea grows */
    gap: 8px;
}
.composer-attach {
    cursor: pointer;
    color: var(--text-dim);
    flex-shrink: 0;
    /* Match the textarea's first-line geometry: font-size 15px × line-
       height 1.5 = 22.5px tall. inline-flex + align-items:center puts
       the 18px paperclip vertically centered inside that single-line
       box, so it lines up with the placeholder / first row of text. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 24px;
    height: 22.5px;
    padding: 0;
    margin: 0;
}
.composer-attach:hover { color: var(--primary); }

/* Files modal — two-pane file manager.
   Wider + taller than the bind modal: the right pane is a sortable
   table, the left pane a folder tree (just root for now). */
.modal-files {
    width: min(900px, calc(100vw - 32px));
    height: min(640px, calc(100vh - 48px));
    max-height: calc(100vh - 48px);
    padding: 0;
    /* display:flex + flex-direction come from .modal / .modal.modal-open — do
       NOT set display here or it overrides the default-hidden .modal rule and
       the modal renders open on load. */
}
.modal-files .modal-head {
    padding: 18px 22px 14px;
    border-bottom: 1px solid var(--border);
}

.files-body {
    flex: 1;
    min-height: 0;
    display: flex;
}

/* Left tree pane */
.files-tree {
    width: 180px;
    flex-shrink: 0;
    border-right: 1px solid var(--border);
    padding: 12px 10px;
    background: var(--bg-surface);
    overflow-y: auto;
}
.files-tree-backdrop { display: none; } /* mobile-only; activated in @media */

/* Tree folders (native details/summary — expand/collapse, zero JS) */
.files-tree-folder > summary { list-style: none; cursor: pointer; }
.files-tree-folder > summary::-webkit-details-marker { display: none; }
.files-tree-children { padding-left: 13px; }

/* Breadcrumb above the listing */
.files-breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1px;
    padding: 10px 14px 2px;
}
.files-crumb {
    background: none;
    border: none;
    padding: 2px 6px;
    border-radius: 6px;
    color: var(--primary);
    cursor: pointer;
    font: inherit;
    font-size: 13px;
}
.files-crumb:hover { background: var(--bg-surface); }
.files-crumb-active { color: var(--text); font-weight: 600; cursor: default; }
.files-crumb-active:hover { background: none; }
.files-crumb-sep { color: var(--text-dim); font-size: 12px; }

/* Folder row in the listing (a button styled like the file name link) */
.files-name-dir { background: none; border: none; cursor: pointer; font: inherit; text-align: left; }
.files-name-dir span { font-weight: 600; }

/* Create-folder mini-form inside the New menu */
.files-newfolder {
    display: flex;
    gap: 4px;
    padding: 6px 4px 2px;
    border-top: 1px solid var(--border);
    margin-top: 4px;
}
.files-newfolder-input {
    flex: 1;
    min-width: 0;
    padding: 5px 8px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font: inherit;
    font-size: 13px;
    background: var(--bg);
    color: var(--text);
}
.files-newfolder-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    flex-shrink: 0;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg);
    color: var(--text);
    cursor: pointer;
}
.files-newfolder-btn:hover { background: var(--bg-surface); }

/* Per-row folder delete (folders aren't multi-selectable) */
.files-row-del {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: none;
    background: none;
    border-radius: 6px;
    color: var(--text-dim);
    cursor: pointer;
}
.files-row-del:hover { background: var(--bg-surface); color: var(--danger, #d14343); }
.files-row-del .icon { width: 14px; height: 14px; }
.files-tree-node {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 8px;
    color: var(--text);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}
.files-tree-node:hover { background: var(--bg); border-color: var(--border); }
.files-tree-node-active {
    background: var(--primary-light);
    border-color: var(--primary-muted);
    color: var(--primary);
    font-weight: 600;
}
.files-tree-node .icon { width: 14px; height: 14px; color: var(--primary-muted); }
.files-tree-count {
    margin-left: auto;
    background: var(--bg);
    color: var(--text-dim);
    font-size: 11px;
    font-weight: 700;
    padding: 1px 7px;
    border-radius: 8px;
    min-width: 18px;
    text-align: center;
}
.files-tree-node-active .files-tree-count {
    background: var(--primary-muted);
    color: var(--bg);
}

/* Right pane: toolbar + table, with drag-and-drop overlay */
.files-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}
.files-main.files-drag-over::before {
    content: "Drop to upload";
    position: absolute;
    inset: 0;
    background: rgba(var(--primary-rgb, 14 165 233) / 0.08);
    border: 2px dashed var(--primary);
    border-radius: 8px;
    margin: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    font-weight: 600;
    color: var(--primary);
    pointer-events: none;
    z-index: 5;
}
.files-upload-form { display: contents; }

/* Inline file viewer (markdown preview) — replaces the listing in #files-main */
.files-viewer { overflow: hidden; }
.files-viewer-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    flex: none;
}
.files-viewer-head .icon { width: 18px; height: 18px; color: var(--text-muted); flex: none; }
.files-viewer-name {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.files-viewer-action,
.files-viewer-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    flex: none;
}
.files-viewer-action:hover,
.files-viewer-close:hover { background: var(--bg-surface); color: var(--text); }
.files-viewer-action .icon,
.files-viewer-close .icon { width: 18px; height: 18px; }

.files-viewer-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 18px 22px 28px;
}
.files-viewer-frontmatter {
    margin: 0 0 18px;
    padding: 12px 14px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 13px;
}
.files-viewer-fm-row {
    display: flex;
    gap: 10px;
    padding: 3px 0;
}
.files-viewer-fm-row dt {
    flex: none;
    min-width: 110px;
    font-weight: 600;
    color: var(--text-muted);
}
.files-viewer-fm-row dd {
    margin: 0;
    min-width: 0;
    word-break: break-word;
    white-space: pre-wrap;
}
.files-viewer-body { max-width: 80ch; }

/* Toolbar */
.files-toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
}
.files-toolbar-spacer { flex: 1; }
.files-toolbar-group { position: relative; }
.files-toolbar-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    font-family: inherit;
    font-size: 13px;
    cursor: pointer;
    text-decoration: none;
}
.files-toolbar-btn:hover { border-color: var(--primary-muted); color: var(--primary); }
.files-toolbar-btn .icon { width: 14px; height: 14px; }
.files-toolbar-btn-icon { padding: 6px 8px; }
.files-toolbar-btn-disabled,
.files-toolbar-btn:disabled,
.files-toolbar-btn[aria-disabled="true"] {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: none;
}
.files-toolbar-btn-danger:hover:not(:disabled):not([aria-disabled="true"]) {
    border-color: var(--error);
    color: var(--error);
}
.files-toolbar-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 10;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.10);
    padding: 4px;
    min-width: 200px;
    animation: popover-in 0.10s ease-out;
}
.files-toolbar-menu-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--text);
    font-family: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    width: 100%;
}
.files-toolbar-menu-item:hover:not(:disabled) { background: var(--bg-surface); }
.files-toolbar-menu-item .icon { width: 14px; height: 14px; color: var(--text-dim); }
.files-toolbar-menu-item-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.files-toolbar-menu-hint {
    margin-left: auto;
    font-size: 10.5px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* In-progress uploads list. Empty in the server-rendered template;
   JS appends a row per picked file before the @post fires. Pinned to
   the BOTTOM of the modal so adding/removing rows doesn't shift the
   file table position. flex-shrink:0 keeps each in-flight row
   visible even when the table is full; max-height + overflow stops
   the list from squashing the table off-screen on large batches. */
.files-inflight-list {
    list-style: none;
    margin: 0;
    padding: 0;
    flex-shrink: 0;
    max-height: 35%;
    overflow-y: auto;
}
.files-inflight-list:empty { display: none; }
.files-inflight-list:not(:empty) { border-top: 1px solid var(--border); }
.files-inflight-item {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-rows: auto auto;
    align-items: center;
    column-gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--primary-light);
    color: var(--primary);
    font-size: 13px;
}
.files-inflight-item:last-child { border-bottom: none; }
.files-inflight-icon { grid-row: 1 / 3; display: inline-flex; }
.files-inflight-icon .icon { width: 16px; height: 16px; }
.files-inflight-name {
    grid-column: 2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    font-weight: 500;
}
.files-inflight-status {
    grid-column: 3;
    grid-row: 1;
    font-size: 11.5px;
    color: var(--primary);
}
.files-inflight-bar {
    grid-column: 2 / 4;
    grid-row: 2;
    height: 3px;
    background: var(--primary-muted);
    border-radius: 2px;
    overflow: hidden;
    margin-top: 4px;
    position: relative;
}
/* Indeterminate animated fill — slides back and forth while the
   multipart POST is in flight. (Real per-file % requires switching
   to XHR + manual SSE parsing; deferred until needed.) */
.files-inflight-fill {
    position: absolute;
    height: 100%;
    width: 30%;
    background: var(--primary);
    border-radius: 2px;
    animation: files-inflight-slide 1.1s ease-in-out infinite;
}
@keyframes files-inflight-slide {
    0%   { left: -30%; }
    50%  { left: 50%; }
    100% { left: 100%; }
}

/* Chat-thread upload notice — server-appended after each successful
   file write. Subtle centered pill so it doesn't compete with
   real messages but still confirms the upload visibly. */
.chat-msg-notice { padding: 4px 24px; }
.chat-msg-notice-body {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-secondary);
    font-size: 12px;
}
.chat-msg-notice-body .icon { width: 12px; height: 12px; color: var(--primary-muted); }
.chat-msg-notice-body strong { color: var(--text); font-weight: 600; }
.chat-msg-notice-size {
    color: var(--text-dim);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 11px;
}
/* Grouped (multi-file) upload notice: one box listing every uploaded path. */
.chat-msg-notice-body:has(.chat-msg-notice-multi) {
    align-items: flex-start;
    border-radius: 10px;
}
.chat-msg-notice-body .icon { flex: none; }
.chat-msg-notice-multi { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.chat-msg-notice-files {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.chat-msg-notice-files li code {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 11px;
    color: var(--text);
    word-break: break-all;
}

/* File table */
.files-table-wrap {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}
.files-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.files-table thead th {
    position: sticky;
    top: 0;
    background: var(--bg);
    z-index: 1;
    text-align: left;
    padding: 10px 12px;
    color: var(--text-dim);
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    border-bottom: 1px solid var(--border);
}
.files-table tbody tr {
    border-bottom: 1px solid var(--border);
    transition: background 0.08s;
}
.files-table tbody tr:hover { background: var(--bg-surface); }
.files-row-selected { background: var(--primary-light) !important; }
.files-table td { padding: 10px 12px; vertical-align: middle; }
.files-col-check { width: 32px; padding: 10px 0 10px 14px !important; }
/* Secondary columns share one consistent style: same font as the name, dim,
   slightly smaller — no monospace, no badges (kept the table readable, not a
   mix of fonts/weights). */
.files-col-size,
.files-col-time,
.files-col-creator { color: var(--text-dim); font-size: 12.5px; white-space: nowrap; }
.files-col-size,
.files-col-time { width: 110px; font-variant-numeric: tabular-nums; }
.files-col-creator { width: 100px; }
.files-name-link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--text);
    text-decoration: none;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Reset native <button> chrome — markdown rows render as buttons. */
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.files-name-link:hover { color: var(--primary); text-decoration: underline; }
.files-name-link .icon { width: 16px; height: 16px; color: var(--text-dim); flex-shrink: 0; }

/* Creator: plain dim text (no badge), consistent with the other secondary
   columns. Uploads get a subtle primary tint to distinguish from agent files. */
.files-creator { font-size: 12.5px; color: var(--text-dim); }
.files-creator-user { color: var(--primary); }
.files-creator-folder { color: var(--text-dim); }

/* Empty state when no files */
.files-empty-state {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 24px;
    text-align: center;
}
.files-empty-icon { color: var(--text-dim); }
.files-empty-title { font-size: 15px; font-weight: 600; color: var(--text); margin: 0; }
.files-empty-hint { margin: 0; font-size: 12.5px; }
.files-empty-hint strong { color: var(--primary); }

/* Narrow modal — used by the Bind-channel form. Same chrome as
   .modal but content-sized: the base .modal forces a 640px height
   that swims around a short form. Override to width-cap + auto
   height. Also drops the modal width below the 900px base. */
.modal-narrow {
    width: min(520px, calc(100vw - 32px));
    height: auto;
    max-height: calc(100vh - 48px);
}

.bind-form {
    padding: 20px 22px 18px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}
.bind-field { display: flex; flex-direction: column; gap: 6px; }
.bind-label {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--text-dim);
}
.bind-input {
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    font-size: 14px;
    font-family: inherit;
}
.bind-input:focus {
    outline: none;
    border-color: var(--primary-muted);
    box-shadow: 0 0 0 3px var(--primary-light);
}

/* =====================================================================
   Custom dropdown component — replacement for browser-native <select>
   in modals so the rendering is consistent across iOS/macOS/Linux and
   matches the rest of the styling.

   Markup:
     <div class="dropdown" data-on:click="evt.stopPropagation()">
       <button class="dropdown-trigger" ...>
         <span class="dropdown-value" data-text="...">label</span>
         <svg class="dropdown-chevron">...</svg>
       </button>
       <div class="dropdown-menu" data-show="...">
         <button class="dropdown-item" ...>option</button>
       </div>
       <input type="hidden" name="..." data-bind="..."/>
     </div>

   The hidden input mirrors the bound signal, so the standard form
   POST picks the value up via FormData encoding — no special
   handling required on the server side.
   ===================================================================== */
.dropdown {
    position: relative;
    width: 100%;
}
.dropdown-trigger {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    font-size: 14px;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.dropdown-trigger:hover {
    border-color: var(--primary-muted);
}
.dropdown-trigger:focus-visible {
    outline: none;
    border-color: var(--primary-muted);
    box-shadow: 0 0 0 3px var(--primary-light);
}
.dropdown-trigger-open {
    border-color: var(--primary-muted);
    box-shadow: 0 0 0 3px var(--primary-light);
}
.dropdown-value {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dropdown-chevron {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    color: var(--text-dim);
    transition: transform 0.15s;
}
.dropdown-trigger-open .dropdown-chevron {
    transform: rotate(180deg);
    color: var(--primary);
}
.dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.10), 0 2px 6px rgba(0, 0, 0, 0.06);
    padding: 4px;
    z-index: 200;          /* above modal (z 110) */
    max-height: 240px;
    overflow-y: auto;
    animation: popover-in 0.10s ease-out;
}
.dropdown-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    padding: 7px 10px;
    border-radius: 6px;
    color: var(--text);
    cursor: pointer;
    font-size: 13.5px;
    font-family: inherit;
    transition: background 0.12s;
}
.dropdown-item:hover {
    background: var(--bg-surface);
}
.dropdown-item-active {
    background: var(--primary-light);
    color: var(--primary);
    font-weight: 600;
}
.dropdown-empty {
    padding: 10px 12px;
    color: var(--text-dim);
    font-size: 12.5px;
}
.dropdown-empty code {
    background: var(--bg-surface);
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 11.5px;
}
.bind-hint {
    margin: 0;
    font-size: 12px;
    color: var(--text-dim);
}
.bind-hint code {
    background: var(--bg-surface);
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 11.5px;
}
.bind-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    margin-top: 4px;
}
.bind-note {
    margin: 0;
    font-size: 11.5px;
    color: var(--text-dim);
    font-style: italic;
    text-align: center;
}

/* Combobox — a text input fused with a chevron-toggled picker menu.
   The input is the canonical value (and the filter query for the
   picker). Used in the bind-channel modal for room selection.   */
.combobox {
    position: relative;
    display: flex;
    align-items: stretch;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    transition: border-color 0.12s, box-shadow 0.12s;
}
.combobox:focus-within {
    border-color: var(--primary-muted);
    box-shadow: 0 0 0 3px var(--primary-light);
}
.combobox-input {
    flex: 1;
    min-width: 0;
    border: none;
    background: transparent;
    padding: 8px 10px;
    font-size: 14px;
    font-family: inherit;
    color: var(--text);
    outline: none;
    border-radius: 8px 0 0 8px;
}
.combobox-input::placeholder { color: var(--text-dim); }
.combobox-chevron {
    flex-shrink: 0;
    width: 32px;
    border: none;
    border-left: 1px solid var(--border);
    background: var(--bg-surface);
    color: var(--text-dim);
    cursor: pointer;
    border-radius: 0 8px 8px 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color 0.12s, background 0.12s;
}
.combobox-chevron:hover { color: var(--primary); }
.combobox-chevron:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
.combobox-chevron svg {
    width: 12px;
    height: 12px;
    transition: transform 0.15s;
}
.combobox-chevron-open svg {
    transform: rotate(180deg);
    color: var(--primary);
}
.combobox-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 200;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.10), 0 2px 6px rgba(0, 0, 0, 0.06);
    padding: 4px;
    max-height: 280px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    animation: popover-in 0.10s ease-out;
}
.dropdown-rooms-list {
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}
.dropdown-loading {
    padding: 12px;
    color: var(--text-dim);
    font-size: 12.5px;
    text-align: center;
}
.dropdown-item-room {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
}
.dropdown-item-room .dropdown-item-label {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dropdown-item-room .dropdown-item-sub {
    font-size: 11.5px;
    color: var(--text-dim);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
