/* ── Threads feed / Members feed / Drafts-Scheduled — dim active
     channel/dm row while a feed page is open. Same trick для всех
     полноэкранных views: канал остаётся в DOM (sidebar клик-таргет),
     но визуально подсвечен только nav-item того feed'а. */
  .app.threads-feed-open .channel-item.active,
  .app.threads-feed-open .thread-item.active,
  .app.threads-feed-open .dm-conv.active,
  .app.members-feed-open .channel-item.active,
  .app.members-feed-open .thread-item.active,
  .app.members-feed-open .dm-conv.active,
  .app.drafts-scheduled-open .channel-item.active,
  .app.drafts-scheduled-open .thread-item.active,
  .app.drafts-scheduled-open .dm-conv.active,
  .app.drafts-scheduled-open .dm-item.active {
    background: transparent;
    color: var(--ink-muted);
  }
  .app.threads-feed-open .channel-item.active .channel-name,
  .app.threads-feed-open .channel-item.active .channel-icon,
  .app.members-feed-open .channel-item.active .channel-name,
  .app.members-feed-open .channel-item.active .channel-icon,
  .app.drafts-scheduled-open .channel-item.active .channel-name,
  .app.drafts-scheduled-open .channel-item.active .channel-icon { color: var(--ink-muted); }
  .app.threads-feed-open .channel-item.active:not(:hover) .channel-icon,
  .app.members-feed-open .channel-item.active:not(:hover) .channel-icon,
  .app.drafts-scheduled-open .channel-item.active:not(:hover) .channel-icon { opacity: 0.6; }
  .app.threads-feed-open .channel-item.active .channel-pin-mark,
  .app.members-feed-open .channel-item.active .channel-pin-mark,
  .app.drafts-scheduled-open .channel-item.active .channel-pin-mark { color: var(--ink-ghost); }
  .app.threads-feed-open .channel-item.active:hover,
  .app.members-feed-open .channel-item.active:hover,
  .app.drafts-scheduled-open .channel-item.active:hover {
    background: var(--surface);
    color: var(--ink-secondary);
  }
  .app.threads-feed-open .channel-item.active:hover .channel-name,
  .app.threads-feed-open .channel-item.active:hover .channel-icon,
  .app.members-feed-open .channel-item.active:hover .channel-name,
  .app.members-feed-open .channel-item.active:hover .channel-icon,
  .app.drafts-scheduled-open .channel-item.active:hover .channel-name,
  .app.drafts-scheduled-open .channel-item.active:hover .channel-icon { color: var(--ink-secondary); }

  .threads-feed-view {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 5;
    display: flex; flex-direction: column;
    background: var(--deep);
    transition: right var(--transition);
  }
  .app.threads-feed-open .thread-panel { display: none !important; }
  .app.threads-feed-open.thread-open .chat-area { padding-right: 0; }

  /* ── Members feed view ── */
  .members-feed-view {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 5;
    display: flex; flex-direction: column;
    background: var(--deep);
    transition: right var(--transition);
  }
  .app.thread-open .members-feed-view { right: var(--thread-panel-width, 420px); }
  .mfv-header {
    height: 60px; flex-shrink: 0;
    display: flex; align-items: center;
    padding: 0 var(--space-xl);
    border-bottom: 1px solid var(--border);
    gap: var(--space-md);
  }
  .mfv-title { font-size: 18px; font-weight: 600; color: var(--ink); flex: 1; }
  .mfv-invite-btn {
    background: var(--accent); color: white;
    border: none; font: inherit; font-size: var(--font-sm); font-weight: 500;
    padding: 8px 16px; border-radius: var(--radius-m);
    cursor: pointer;
    transition: background var(--transition);
  }
  .mfv-invite-btn:hover { background: var(--accent-hover); }
  .mfv-table-wrap {
    flex: 1; min-height: 0; overflow: auto;
    padding: 0 var(--space-xl);
  }
  .mfv-table {
    width: 100%; border-collapse: collapse;
    color: var(--ink);
  }
  .mfv-table thead th {
    position: sticky; top: 0;
    background: var(--deep);
    text-align: left;
    font-size: var(--font-xs); font-weight: 500; color: var(--ink-muted);
    padding: 14px var(--space-md);
    border-bottom: 1px solid var(--border);
  }
  .mfv-table tbody td {
    padding: 12px var(--space-md);
    font-size: var(--font-md);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
  }
  .mfv-table tbody tr:hover td { background: var(--surface); }
  .mfv-name-cell { display: flex; align-items: center; gap: 10px; }
  .mfv-avatar {
    width: var(--avatar-sm); height: var(--avatar-sm); border-radius: var(--radius-full);
    color: white; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--font-2xs); font-weight: 700;
  }
  /* `.mfv-role-tag` was a competing role-badge style — text-only,
     no background. Members feed now uses .kc-pill.kc-pill--role-*
     from components.css (UX PR 5.2). */
  /* Name column stacks display name + @handle vertically. The standalone
     «Никнейм» column was removed — the handle now lives under the name
     in the same cell. */
  .mfv-name-stack { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
  .mfv-name-stack .mfv-name {
    color: var(--ink); font-size: var(--font-md); line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .mfv-name-stack .mfv-handle {
    color: var(--ink-muted); font-size: var(--font-xs); line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .mfv-email { color: var(--ink-secondary); font-size: var(--font-sm); }
  /* Roles render as plain text in the same secondary tone — pills
     were too loud for a list with hundreds of rows of identical
     "АДМИНИСТРАТОР" labels. */
  .mfv-role { color: var(--ink-secondary); font-size: var(--font-sm); }
  .mfv-menu-btn {
    background: transparent; border: none; cursor: pointer;
    color: var(--ink-muted); font-size: 18px; line-height: 1;
    padding: 4px 8px; border-radius: var(--radius-s);
    opacity: 0; transition: opacity var(--transition), background var(--transition);
  }
  .mfv-table tbody tr:hover .mfv-menu-btn { opacity: 1; }
  .mfv-menu-btn:hover { background: var(--elevated); color: var(--ink); }
  .kc-member-menu { z-index: 1000; min-width: 220px; }
  .kc-member-menu .ctx-item.danger { color: var(--coral); }
  .tfv-header {
    height: 52px; flex-shrink: 0;
    display: flex; align-items: center;
    padding: 0 var(--space-lg);
    border-bottom: 1px solid var(--border);
  }
  .tfv-title { font-size: 18px; font-weight: 600; color: var(--ink); }
  .tfv-toolbar {
    flex-shrink: 0;
    display: flex; align-items: center; gap: var(--space-lg);
    padding: var(--space-md) var(--space-xl);
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
  }
  .tfv-switch {
    display: inline-flex; align-items: center; gap: var(--space-sm);
    cursor: pointer; user-select: none;
    color: var(--ink-secondary); font-size: var(--font-sm);
  }
  .tfv-switch input { display: none; }
  .tfv-switch-track {
    width: 32px; height: 18px; border-radius: 9px;
    background: var(--float); position: relative;
    transition: background var(--transition);
  }
  .tfv-switch-knob {
    position: absolute; top: 2px; left: 2px;
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--ink-secondary);
    transition: transform var(--transition), background var(--transition);
  }
  .tfv-switch input:checked + .tfv-switch-track { background: var(--accent); }
  .tfv-switch input:checked + .tfv-switch-track .tfv-switch-knob { transform: translateX(14px); background: white; }
  .tfv-filter {
    background: transparent; border: none; cursor: pointer;
    color: var(--ink-secondary); font: inherit; font-size: var(--font-sm);
    display: inline-flex; align-items: center; gap: 4px;
    padding: 4px 6px; border-radius: var(--radius-s);
    transition: background var(--transition), color var(--transition);
  }
  .tfv-filter:hover { background: var(--surface); color: var(--ink); }
  .tfv-filter--active {
    color: var(--accent);
    background: var(--accent-soft);
  }
  .tfv-filter--active:hover { background: var(--accent-soft); color: var(--accent); }

  /* Shared dropdown for sort + channel/author/participant pickers.
     Position is set in JS (anchored to the trigger button). */
  .tfv-filter-menu {
    position: fixed;
    z-index: 1000;
    min-width: 220px;
    max-width: 320px;
    max-height: 60vh;
    overflow-y: auto;
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-l);
    padding: 4px;
    display: flex; flex-direction: column;
  }
  .tfv-filter-menu[hidden] { display: none; }
  .tfv-menu-item {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    background: transparent; border: none;
    color: var(--ink); font: inherit; font-size: var(--font-sm);
    text-align: left;
    border-radius: var(--radius-s);
    cursor: pointer;
    transition: background var(--transition);
  }
  .tfv-menu-item:hover { background: var(--surface); }
  .tfv-menu-item--active { background: var(--surface); }
  .tfv-menu-item--active .tfv-menu-label { color: var(--accent); font-weight: 500; }
  .tfv-menu-item--disabled {
    color: var(--ink-ghost); cursor: not-allowed; opacity: 0.6;
  }
  .tfv-menu-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tfv-menu-check { color: var(--accent); font-size: var(--font-xs); }
  .tfv-menu-av {
    width: var(--avatar-2xs); height: var(--avatar-2xs);
    border-radius: var(--radius-full);
    color: white; font-size: 9px; font-weight: 600;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
  }
  .tfv-scroll {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: var(--space-lg) var(--space-xl);
    display: flex; flex-direction: column; gap: var(--space-lg);
  }
  .tfv-channel-group { display: flex; flex-direction: column; gap: var(--space-sm); }
  .tfv-channel-head {
    display: flex; align-items: center; gap: 6px;
    color: var(--ink-secondary); font-size: var(--font-sm); font-weight: 600;
    cursor: pointer;
    transition: color var(--transition);
  }
  .tfv-channel-head:hover { color: var(--ink); }
  .tfv-thread-card {
    background: var(--surface);
    border-radius: var(--radius-m);
    padding: var(--space-md) var(--space-lg);
    display: flex; flex-direction: column; gap: var(--space-sm);
  }
  .tfv-thread-block { display: flex; flex-direction: column; gap: var(--space-sm); }
  .tfv-card-head { display: flex; flex-direction: column; gap: 2px; }
  .tfv-card-channel {
    display: inline-flex; align-items: center; gap: 6px;
    color: var(--ink); font-size: var(--font-md); font-weight: 600;
    cursor: pointer; align-self: flex-start;
    border-radius: var(--radius-s);
    transition: color var(--transition);
  }
  .tfv-card-channel:hover { color: var(--accent); }
  .tfv-card-participants {
    color: var(--ink-muted); font-size: var(--font-xs);
  }
  .tfv-thread-meta {
    display: flex; align-items: center; gap: 8px;
    font-size: var(--font-xs); color: var(--ink-muted);
  }
  .tfv-thread-meta .tfv-thread-name {
    color: var(--ink); font-weight: 600; font-size: var(--font-sm);
    cursor: pointer;
  }
  .tfv-thread-meta .tfv-thread-name:hover { color: var(--accent); }
  .tfv-reply-row {
    padding: 8px var(--space-md) 10px;
    border-top: 1px solid var(--border);
  }
  .tfv-composer-inner {
    display: flex; align-items: center; gap: var(--space-sm);
    background: var(--glass); border-radius: var(--radius-l);
    padding: 6px var(--space-md) 6px var(--space-md);
    border: 1px solid var(--border);
    transition: border-color var(--transition);
    backdrop-filter: blur(var(--blur));
  }
  .tfv-composer-inner:focus-within { border-color: var(--border-accent); }
  .tfv-reply-input {
    flex: 1; background: transparent; border: none;
    color: var(--ink); font-size: var(--font-md); font-family: inherit;
    line-height: 1.5; resize: none; outline: none;
    min-height: 24px; max-height: 200px; padding: 6px 0; align-self: center;
    overflow-y: auto; word-break: break-word; white-space: pre-wrap;
  }
  .tfv-reply-input::placeholder { color: var(--ink-ghost); }
  .tfv-reply-input:empty:before {
    content: attr(data-placeholder); color: var(--ink-ghost); pointer-events: none;
  }
  .tfv-reply-send {
    flex-shrink: 0; border: none;
    border-radius: var(--radius-m);
    width: 32px; height: 32px; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    /* Mirror the main composer's "send-disabled" treatment: idle pill
       is grey on muted ink, only flips to the accent fill when there's
       something to send (.active toggled by JS on input). */
    background: var(--float); color: var(--ink-muted);
    transition: color var(--transition), background var(--transition);
  }
  .tfv-reply-send.active { background: var(--accent); color: #fff; }
  .tfv-reply-send.active:hover { background: var(--accent-hover); }
  .tfv-reply-send:disabled { cursor: default; }
  .tfv-comment-stub {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: 10px var(--space-md);
    background: var(--float);
    border-radius: var(--radius-m);
    color: var(--ink-muted); font-size: var(--font-sm);
    cursor: pointer;
  }
  .tfv-comment-stub:hover { background: var(--elevated); color: var(--ink-secondary); }
  .tfv-msg {
    position: relative;
    display: flex; gap: var(--space-lg); align-items: flex-start;
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-s);
    transition: background var(--transition);
  }
  .tfv-msg:hover { background: var(--deep); }
  .tfv-msg-avatar {
    width: var(--avatar-lg); height: var(--avatar-lg); border-radius: var(--radius-full);
    color: white; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--font-md); font-weight: 700;
  }
  .tfv-msg-body { flex: 1; min-width: 0; }
  .tfv-msg-meta { display: flex; gap: var(--space-sm); align-items: baseline; margin-bottom: 2px; }
  .tfv-msg-meta strong { color: var(--ink); font-weight: 600; font-size: var(--font-md); }
  .tfv-msg-meta span { color: var(--ink-ghost); font-size: var(--font-2xs); }
  .tfv-msg-text { color: var(--ink); font-size: var(--font-md); line-height: 1.45; word-wrap: break-word; }
  .tfv-msg.compact { padding-top: var(--space-xs); padding-bottom: var(--space-xs); }
  .tfv-msg:not(.compact):has(+ .tfv-msg.compact) { padding-bottom: var(--space-xs); }
  .tfv-msg.compact .tfv-msg-avatar { visibility: hidden; height: 0; margin: 0; }
  .tfv-msg.compact .msg-hover-time {
    position: absolute;
    left: var(--space-sm);
    width: 40px;
    text-align: center;
    font-size: 10px;
    color: var(--ink-ghost);
    line-height: 22px;
    font-variant-numeric: tabular-nums;
    opacity: 0;
    transition: opacity var(--transition);
    cursor: default;
  }
  .tfv-msg.compact:hover .msg-hover-time { opacity: 1; }
  .tfv-msg .message-actions { top: -14px; right: var(--space-sm); }
  .tfv-msg:hover .message-actions { display: flex; }
  .tfv-show-more {
    color: var(--accent); font-size: var(--font-sm); cursor: pointer;
    padding: 4px 0;
    align-self: flex-start;
    user-select: none;
  }
  .tfv-show-more:hover { text-decoration: underline; }
  .tfv-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 60px var(--space-lg);
    color: var(--ink-muted); text-align: center;
    gap: 8px;
  }

  .channel-list-wrapper {
    flex: 1; min-height: 0; position: relative;
    display: flex; flex-direction: column;
  }
  .channel-list {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-sm) var(--space-sm) var(--space-md);
    scrollbar-width: thin;
    scrollbar-color: var(--float) transparent;
    min-height: 0;
  }
  .unread-jump {
    position: absolute; left: var(--space-md); right: var(--space-md);
    background: var(--accent-soft);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-pill);
    padding: 8px 14px;
    font-size: var(--font-sm); font-weight: 600;
    color: var(--accent);
    cursor: pointer; user-select: none;
    display: none; align-items: center; gap: 8px;
    z-index: 5; backdrop-filter: blur(var(--blur));
    box-shadow: var(--shadow-s);
    transition: opacity var(--transition);
  }
  .unread-jump.visible { display: flex; }
  .unread-jump.top    { top: 8px; }
  .unread-jump.bottom { bottom: 8px; }
  .unread-jump:hover { background: var(--accent); color: white; border-color: var(--accent); }

  .channel-category { margin-bottom: var(--space-lg); }

  .category-header {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: 0 var(--space-sm);
    margin-bottom: var(--space-xs);
    cursor: pointer;
    user-select: none;
  }

  .category-header .arrow {
    color: var(--ink-muted);
    transition: transform var(--transition);
    width: 12px;
    display: flex;
    align-items: center;
  }

  .category-header.collapsed .arrow { transform: rotate(-90deg); }

  .category-header span {
    font-size: var(--font-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-muted);
    transition: color var(--transition);
  }

  .category-header:hover span { color: var(--ink-secondary); }

  .category-channels {
    overflow: hidden;
    transition: max-height var(--transition-slow);
  }

  .channel-item {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 7px var(--space-sm);
    border-radius: var(--radius-s);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
    color: var(--ink-muted);
    margin-bottom: 1px;
    position: relative;
  }
  /* Block text-selection on the row's children, NOT the row itself —
     setting user-select:none on a draggable element makes Chromium
     abort the drag right after dragstart (no dragover, dragend with
     dropEffect:'none'). */
  .channel-item > * {
    user-select: none;
    -webkit-user-select: none;
  }

  .channel-item:hover {
    background: var(--surface);
    color: var(--ink-secondary);
  }

  /* Drag & drop */
  .ch-drag-handle {
    position: absolute; left: -10px; top: 50%; transform: translateY(-50%);
    opacity: 0; width: 14px; height: 14px;
    display: flex; align-items: center; justify-content: center;
    color: var(--ink-ghost); cursor: grab;
    transition: opacity var(--transition);
  }
  .ch-drag-handle:active { cursor: grabbing; }
  /* Chromium on macOS aborts a drag if the source element has
     user-select:none and no explicit -webkit-user-drag — symptom is
     dragstart firing then dragend immediately with dropEffect:'none'
     and no dragover events. Forcing element-level user-drag tells the
     browser to treat the row as a real drag source. */
  .channel-item[draggable="true"] {
    -webkit-user-drag: element;
  }
  .channel-item.ch-dragging { opacity: 0.4; }
  .channel-item.ch-drop-before::before,
  .channel-item.ch-drop-after::after {
    content: ''; position: absolute; left: 0; right: 0; height: 2px;
    background: var(--accent); border-radius: 0; pointer-events: none;
  }
  .channel-item.ch-drop-before::before { top: -1px; }
  .channel-item.ch-drop-after::after { bottom: -1px; }

  /* Any empty .category-channels needs to be a real drop target while a
     channel is in flight — otherwise an empty (real or virtual) category
     has zero height and the user can't drop into it. The :empty selector
     gives every empty container a dashed slot during drag, the
     uncat-empty rule below extends margins for the virtual zone, and
     cat-drop-target tints whichever slot the cursor is over. */
  body.kc-dragging-channel .category-channels:empty {
    min-height: 36px;
    margin: 0 var(--space-sm) 6px;
    border: 1px dashed var(--border-mid);
    border-radius: var(--radius-s);
  }
  /* Only paint the dashed slot when the category is empty — otherwise
     the per-channel ch-drop-before/after indicator is already in play
     and a wrapping border around populated rows looks like noise. */
  body.kc-dragging-channel .category-channels:empty.cat-drop-target {
    border: 1px dashed var(--accent);
    background: var(--accent-soft);
    border-radius: var(--radius-s);
  }
  body.kc-dragging-channel .category-channels-uncat-empty {
    min-height: 36px;
    margin: 0 var(--space-sm) 6px;
    border: 1px dashed var(--border-mid);
    border-radius: var(--radius-s);
  }
  body.kc-dragging-channel .category-channels-uncat-empty.cat-drop-target {
    border-color: var(--accent);
    background: var(--accent-soft);
  }

  /* Category-header drag visuals (mirrors ch-* styles). category-header is
     `position: relative` so the ::before / ::after lines anchor correctly. */
  .category-header { position: relative; }
  .category-header.cat-dragging { opacity: 0.4; pointer-events: none; }
  .category-header.cat-drop-before::before,
  .category-header.cat-drop-after::after {
    content: ''; position: absolute; left: 0; right: 0; height: 2px;
    background: var(--accent); border-radius: 0; pointer-events: none;
  }
  .category-header.cat-drop-before::before { top: -1px; }
  .category-header.cat-drop-after::after { bottom: -1px; }

  /* Discord-style: while a category is in flight, dim the channel rows so
     category boundaries pop. Headers themselves stay full-opacity (the
     dragged one already gets cat-dragging). */
  body.kc-dragging-category .channel-item { opacity: 0.4; }
  /* Dim the icon + name only — opacity on the row would inherit down
     into .channel-mention-mark and dim the @ too, which we want to
     stay bright as a "you were addressed personally" signal. */
  .channel-item.muted .channel-icon,
  .channel-item.muted .channel-name { opacity: 0.45; }
  .channel-item.muted:hover .channel-icon,
  .channel-item.muted:hover .channel-name,
  .channel-item.muted.active .channel-icon,
  .channel-item.muted.active .channel-name { opacity: 1; }

  .channel-item.active {
    background: var(--elevated);
    color: var(--ink);
  }

  .channel-item .channel-icon {
    width: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    opacity: 0.6;
  }
  .channel-item.active .channel-icon,
  .channel-item:hover .channel-icon { opacity: 1; }

  .channel-item .channel-name {
    font-size: var(--font-md);
    font-weight: 500;
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Unread channel: white, bold name — the primary "there's something
     new here" cue (Slack/Discord pattern), so the row reads as unread at
     a glance instead of relying on the small badge/dot alone. The JS
     guarantees `.unread` never co-occurs with `.muted`. */
  .channel-item.unread .channel-name {
    color: var(--ink);
    font-weight: 600;
  }
  .channel-item.unread .channel-icon { opacity: 1; }

  .channel-item .unread-dot {
    width: 8px; height: 8px; border-radius: var(--radius-full);
    background: var(--ink); flex-shrink: 0;
  }
  .channel-item .muted-unread-dot {
    width: 8px; height: 8px; border-radius: var(--radius-full);
    background: var(--ink-muted); flex-shrink: 0;
  }

  /* Voice-channel presence (Speak Этап 3): avatars of who's in the call,
     indented under the channel name. */
  .channel-voice-members {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 1px 8px 5px 22px;
  }
  /* Discord-style row: avatar + name, one participant per line. */
  .cvm-row {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: 3px 6px;
    border-radius: var(--radius-s);
    cursor: pointer;
  }
  .cvm-row:hover { background: var(--glass-hover); }
  .cvm-name {
    font-size: var(--font-xs); color: var(--ink-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .cvm-avatar {
    width: 20px;
    height: 20px;
    font-size: var(--font-2xs);
    transition: box-shadow var(--transition);
  }
  /* Speaking ring (Этап 4) — Discord-style green halo on the talker.
     Pulses gently while talking (Этап 4.6, D7). */
  @keyframes kcSpeakPulse {
    0%   { box-shadow: 0 0 0 2px var(--live); }
    50%  { box-shadow: 0 0 0 3px var(--live); }
    100% { box-shadow: 0 0 0 2px var(--live); }
  }
  .cvm-avatar.speaking {
    box-shadow: 0 0 0 2px var(--live);
    animation: kcSpeakPulse 1.2s ease-in-out infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .cvm-avatar.speaking { animation: none; }
  }

  /* Active voice channel — the one I'm connected to lights up green. */
  .channel-item.voice-connected .channel-name,
  .channel-item.voice-connected .channel-icon { color: var(--live); }

  .channel-pin-mark {
    margin-left: auto; display: flex; align-items: center;
    color: var(--ink-ghost); flex-shrink: 0;
  }
  .ch-private-row {
    display: flex; align-items: center; gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    background: var(--deep); border-radius: var(--radius-m);
    cursor: pointer; user-select: none;
  }
  .ch-private-row:hover { background: var(--surface); }
  .channel-item:hover .channel-pin-mark { color: var(--ink-secondary); }
  .channel-item.active .channel-pin-mark { color: var(--ink); }
  /* Telegram-style draft mark: red inline text, no background. Sits
     between the channel name and any unread/mention badges. The
     channel-name's flex:1 already pushes everything to the right. */
  .channel-draft-mark {
    color: var(--coral);
    font-size: var(--font-2xs);
    font-weight: 600;
    flex-shrink: 0;
    text-transform: lowercase;
    margin-left: var(--space-xs);
  }

  .channel-badge {
    background: var(--elevated);
    color: var(--ink);
    font-size: 10px;
    font-weight: 700;
    padding: 1px 7px;
    border-radius: 9px;
    flex-shrink: 0;
    border: 1px solid var(--border-mid);
  }

  /* Discord-style hover actions on a channel row — only rendered for
     users with manage rights; CSS hides them until hover. Stays
     `inline-flex` at all times so the row reserves layout space for
     the buttons even when invisible — otherwise hover toggles between
     `display: none` and `inline-flex` and the channel name jitters as
     it reflows. `visibility` swap keeps the row width stable. */
  .channel-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: var(--space-xs);
    flex-shrink: 0;
    visibility: hidden;
  }
  .channel-item:hover .channel-actions { visibility: visible; }
  .channel-action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
    color: var(--ink-muted);
    cursor: pointer;
    border-radius: 4px;
    background: transparent;
    border: none;
  }
  .channel-action-btn:hover { color: var(--ink); }
  .channel-action-btn svg { display: block; }
  .channel-mention-mark {
    display: inline-flex; align-items: center;
    color: var(--mention-self-fg);
    margin-right: -4px;
    flex-shrink: 0;
  }
  .channel-mention-mark svg { color: var(--mention-self-fg); }

  .voice-users {
    padding-left: 40px;
    margin-bottom: var(--space-sm);
  }

  .voice-user {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 4px var(--space-sm);
    border-radius: var(--radius-s);
    font-size: var(--font-sm);
    color: var(--ink-secondary);
    cursor: default;
  }

  .voice-user .vu-avatar {
    width: var(--avatar-2xs);
    height: var(--avatar-2xs);
    border-radius: var(--radius-full);
    background: var(--elevated);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 600;
    flex-shrink: 0;
  }

  .voice-user .vu-status {
    margin-left: auto;
    display: flex;
    gap: 2px;
    color: var(--ink-ghost);
  }

  /* Discord-style account strip — sits at the bottom of .left-shell,
     spans both server-rail (72) and channel-sidebar (var width). The
     inner container mirrors .composer-inner on the right side: same
     glass background, same border, same radius. Outer .account-strip
     is just the padding wrapper, like .composer. */
  .account-strip {
    padding: 0 var(--space-md) var(--space-lg);
    flex-shrink: 0;
    /* The strip is the bottom of the unified left column — same
       --void surface as the server rail and channel sidebar above.
       The pill itself sits as an overlay on top via
       .account-strip-inner. */
    background: var(--void);
    position: relative;       /* anchor for #selfPopover */
  }
  .account-strip-inner {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    background: var(--glass);
    border: 1px solid var(--border);
    border-radius: var(--radius-l);
    padding: 4px;
    transition: border-color var(--transition);
    backdrop-filter: blur(var(--blur));
  }
  .account-strip-inner:hover { border-color: var(--border-mid); }

  .account-strip .as-grab {
    flex: 1; min-width: 0;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 4px 6px;
    border-radius: var(--radius-m);
    cursor: pointer;
    transition: background var(--transition);
  }
  .account-strip .as-grab:hover { background: var(--surface); }

  .account-strip .as-ctrl {
    width: 32px; height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: none;
    color: var(--ink-muted);
    border-radius: var(--radius-m);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
    flex-shrink: 0;
  }
  .account-strip .as-ctrl:hover { background: var(--surface); color: var(--ink); }

  /* Voice status + quick mic/deafen toggles in the account strip (Этап 4.7).
     Selectors carry `.account-strip` so they out-specify the base .as-ctrl. */
  .account-strip .as-voice-ctrl { display: none; }
  .account-strip.in-voice .as-voice-ctrl { display: inline-flex; }
  .account-strip .as-voice-ctrl.is-off { color: var(--coral); }
  .account-strip .as-voice-ctrl svg { width: 16px; height: 16px; }

  /* Self popover — Discord-style side panel. Anchored at the
     bottom-left of the sidebar, grows upward to fit content; tall
     enough to feel like a dedicated profile card, not a tooltip. */
  .self-popover {
    /* Fixed instead of absolute so .channel-sidebar's overflow:hidden
       doesn't clip the popover. Anchored to the viewport's bottom-left,
       just above the user-panel (height 56px + 4px gap). */
    position: fixed;
    bottom: 64px;
    left: 8px;
    width: 340px;
    max-height: calc(100vh - 80px);
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-l);
    z-index: 200;
    overflow: hidden;
    animation: fadeIn var(--transition) ease;
    display: flex; flex-direction: column;
  }
  .sp-banner {
    height: 90px;
    background: linear-gradient(135deg, #1f1535 0%, #2a1a3a 100%);
    flex-shrink: 0;
  }
  .sp-header {
    position: relative;
    padding: 0 20px 12px;
    margin-top: -36px;        /* avatar overlaps banner */
  }
  .sp-avatar {
    width: 80px; height: 80px;
    border-radius: var(--radius-full);
    position: relative;
    color: white;
    font-size: var(--font-2xl);
    font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    border: 6px solid var(--raised);
    box-sizing: border-box;
    margin-bottom: var(--space-sm);
  }
  .sp-avatar .status-dot {
    position: absolute;
    bottom: 2px; right: 2px;
    width: 15px; height: 15px;
    border-radius: 50%;
    background: var(--live);
    border: 4px solid var(--raised);
    box-sizing: content-box;
    z-index: 2;
  }
  /* Speech-bubble custom status — sits to the right of the avatar
     overlapping the banner→header transition (grey area). Empty state
     shows a + icon and "Добавить статус" placeholder. */
  /* Wrap absorbs the absolute positioning so the clear-button sits
     beside the bubble without breaking flex / overflow inside. */
  .sp-status-bubble-wrap {
    position: absolute;
    left: 110px;
    top: 46px;
    max-width: calc(100% - 130px);
    display: inline-flex;
    align-items: center;
    gap: 4px;
  }
  .sp-status-bubble {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    padding: 6px 12px;
    background: var(--surface);
    border: none;
    border-radius: 16px;
    color: var(--ink);
    font-family: inherit; font-size: var(--font-sm);
    cursor: pointer;
    transition: background var(--transition);
  }
  .sp-status-bubble-clear {
    flex-shrink: 0;
    width: 22px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; padding: 0;
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--ink-muted);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
  }
  .sp-status-bubble-clear:hover { background: var(--elevated); color: var(--ink); }
  .sp-status-bubble:hover { background: var(--elevated); }
  .sp-status-bubble.sp-status-bubble-empty { color: var(--ink-muted); }
  .sp-status-bubble-plus {
    width: 18px; height: 18px;
    border-radius: 50%;
    background: var(--accent);
    color: white;
    padding: 2px;
    flex-shrink: 0;
    box-sizing: border-box;
  }
  .sp-status-bubble:not(.sp-status-bubble-empty) .sp-status-bubble-plus { display: none; }
  .sp-status-bubble-text {
    flex: 1; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  .sp-identity { min-width: 0; }
  .sp-name {
    font-size: var(--font-xl); font-weight: 700; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    line-height: 1.2;
  }
  .sp-handle {
    margin-top: 2px;
    font-size: var(--font-md); color: var(--ink-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  .sp-group {
    margin: 0 12px 12px;
    padding: 4px;
    background: var(--surface);
    border-radius: var(--radius-m);
  }
  .sp-group:first-of-type { margin-top: 4px; }
  .sp-divider {
    height: 1px;
    margin: 4px 4px;
    background: var(--border);
  }
  .sp-item {
    width: 100%;
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px;
    background: transparent;
    border: none;
    border-radius: var(--radius-s);
    color: var(--ink-secondary);
    font-family: inherit; font-size: var(--font-md);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
    text-align: left;
  }
  .sp-item:hover { background: var(--glass-hover); color: var(--ink); }
  .sp-item-icon { flex-shrink: 0; color: var(--ink-muted); }
  .sp-item:hover .sp-item-icon { color: var(--ink); }
  .sp-item-label { flex: 1; }
  .sp-presence-dot {
    flex-shrink: 0;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--status-online);
    box-shadow: 0 0 0 2px var(--surface);
  }
  .sp-presence-dot[data-status="idle"]      { background: var(--status-idle); }
  .sp-presence-dot[data-status="dnd"]       { background: var(--status-dnd); }
  .sp-presence-dot[data-status="invisible"] { background: var(--status-invisible); }
  .sp-chevron { flex-shrink: 0; color: var(--ink-muted); }
  .sp-item:hover .sp-chevron { color: var(--ink); }

  /* Same colour mapping for the bottom-strip dot + popover header dot. */
  .user-avatar .status-dot[data-status="idle"]      { background: var(--status-idle); }
  .user-avatar .status-dot[data-status="dnd"]       { background: var(--status-dnd); }
  .user-avatar .status-dot[data-status="invisible"] { background: var(--status-invisible); }
  .sp-avatar  .status-dot[data-status="idle"]       { background: var(--status-idle); }
  .sp-avatar  .status-dot[data-status="dnd"]        { background: var(--status-dnd); }
  .sp-avatar  .status-dot[data-status="invisible"]  { background: var(--status-invisible); }

  /* Presence submenu — flyout to the right of the popover, like
     Discord's status picker. JS sets left/top on open. */
  .sp-presence-menu {
    position: fixed;
    width: 340px;
    padding: 6px;
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-l);
    z-index: 201;
    animation: fadeIn var(--transition) ease;
  }
  .sp-presence-option {
    width: 100%;
    display: flex; align-items: flex-start; gap: 12px;
    padding: 10px 12px;
    background: transparent;
    border: none;
    border-radius: var(--radius-s);
    color: var(--ink-secondary);
    font-family: inherit; font-size: var(--font-md);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
    text-align: left;
  }
  .sp-presence-option:hover { background: var(--glass-hover); color: var(--ink); }
  .sp-presence-option .sp-presence-dot { margin-top: 6px; }
  .sp-presence-option + .sp-presence-option {
    border-top: 1px solid var(--border);
  }
  .sp-presence-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
  .sp-presence-hint { font-size: var(--font-xs); color: var(--ink-muted); }

  .user-avatar {
    width: var(--avatar-md);
    height: var(--avatar-md);
    border-radius: var(--radius-full);
    background: linear-gradient(135deg, var(--accent), #8b7afc);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-md);
    font-weight: 700;
    flex-shrink: 0;
    position: relative;
  }

  .user-avatar .status-dot {
    position: absolute;
    bottom: -1px;
    right: -1px;
    width: 12px;
    height: 12px;
    border-radius: var(--radius-full);
    background: var(--live);
    border: 3px solid var(--void);
    z-index: 2;
  }
  /* `.kc-avatar` (added by renderSelf) sets overflow:hidden which would
     clip the status-dot. Restore visible overflow in the bottom user
     panel; .kc-avatar-img already has its own border-radius so the
     image stays circular. */
  .account-strip .user-avatar.kc-avatar,
  .sp-avatar.kc-avatar { overflow: visible; }

  .user-info { flex: 1; min-width: 0; }

  .user-info .name {
    font-size: var(--font-sm);
    font-weight: 600;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .user-info .tag {
    font-size: var(--font-xs);
    color: var(--ink-muted);
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
  }
  /* Voice status (Этап 4.7) — green "В голосовом · канал", replaces the
     presence tag while connected. */
  .user-info .as-voice-tag {
    display: none;
    font-size: var(--font-xs); font-weight: 600; color: var(--live);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .account-strip.in-voice .user-info .tag { display: none; }
  .account-strip.in-voice .user-info .as-voice-tag { display: block; }
  .user-info .tag .tag-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    color: var(--ink-secondary);
    font-weight: 500;
  }
  .user-info .tag .tag-clear {
    flex-shrink: 0;
    width: 14px; height: 14px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; padding: 0; margin: 0;
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--ink-muted);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
  }
  .user-info .tag .tag-clear:hover {
    background: var(--surface);
    color: var(--ink);
  }

  .chat-area {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    min-width: 0;
    overflow: hidden;
    position: relative;
  }
  .chat-body {
    position: relative; /* offset parent for the voice-room overlay (Этап 4.5) */
    flex: 1;
    display: flex;
    flex-direction: row;
    min-height: 0;
    overflow: hidden;
  }
  .chat-messages-wrap {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
  }
  .kc-scroll-bottom-btn {
    position: absolute; right: 24px; bottom: 100%; margin-bottom: 14px;
    width: 38px; height: 38px; border-radius: 50%;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--ink-secondary);
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    opacity: 0; pointer-events: none;
    transform: translateY(8px);
    transition: opacity var(--transition), transform var(--transition), background var(--transition), color var(--transition);
    z-index: 20;
    box-shadow: var(--shadow-s);
  }
  .kc-scroll-bottom-btn.visible { opacity: 1; pointer-events: auto; transform: none; }
  .kc-scroll-bottom-btn:hover { background: var(--elevated); color: var(--ink); }
  .kc-scroll-bottom-btn .kc-scroll-badge {
    position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px;
    padding: 0 5px; border-radius: 9px; background: var(--coral);
    color: white; font-size: var(--font-2xs); font-weight: 600;
    display: none; align-items: center; justify-content: center;
  }
  .kc-scroll-bottom-btn.has-unread .kc-scroll-badge { display: flex; }

  .chat-header {
    height: 52px;
    display: flex;
    align-items: center;
    padding: 0 var(--space-lg);
    border-bottom: 1px solid var(--border);
    gap: var(--space-md);
    flex-shrink: 0;
  }

  .chat-header .hash {
    color: var(--ink-muted);
    display: flex;
    align-items: center;
  }

  .chat-header .chat-title {
    font-size: var(--font-md);
    font-weight: 600;
    color: var(--ink);
  }

  .chat-header .topic-divider {
    width: 1px;
    height: 24px;
    background: var(--border-mid);
  }

  .chat-header .topic {
    font-size: var(--font-sm);
    color: var(--ink-muted);
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .chat-header-actions {
    display: flex;
    gap: var(--space-xs);
    flex-shrink: 0;
    align-items: center;
    /* When the channel has no topic the .topic span isn't rendered and
       can't push the actions over with its flex:1, so anchor them to
       the right ourselves. */
    margin-left: auto;
  }

  .header-btn {
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--glass);
    border: none;
    color: var(--ink-secondary);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
  }
  .header-btn:hover {
    background: var(--glass-hover);
    color: var(--ink);
  }
  .header-btn.active { color: var(--ink); background: var(--glass-active); }

  /* «Участники» button widens to fit a member-count badge to the
     right of its icon (Slack pattern). We push the inner svg slightly
     left and let `.kc-member-count` sit beside it. */
  #toggleMembers:has(.kc-member-count) {
    width: auto;
    padding: 0 10px 0 8px;
    gap: 6px;
  }
  .kc-member-count {
    font-size: var(--font-xs);
    font-weight: 600;
    color: var(--ink-secondary);
    line-height: 1;
    user-select: none;
  }
  #toggleMembers:hover .kc-member-count { color: var(--ink); }
  #toggleMembers.active .kc-member-count { color: var(--ink); }

  .search-box {
    width: 220px;
    height: 28px;
    background: var(--glass);
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    padding: 0 26px 0 28px;
    color: var(--ink);
    font-size: var(--font-sm);
    font-family: inherit;
    outline: none;
    transition: width var(--transition-slow), border-color var(--transition), background var(--transition);
    cursor: pointer;
  }
  .search-box:focus { width: 280px; cursor: text; border-color: var(--border-mid); background: var(--raised); }
  .search-box:disabled { opacity: 0.5; cursor: not-allowed; }
  .search-box::placeholder { color: var(--ink-muted); }

  .messages-scroller {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: 0;
    scrollbar-width: thin;
    scrollbar-color: var(--float) transparent;
    /* Take full control of scroll compensation. Browser scroll anchoring fights
       our _kcPinAnchor / kcVideoMetaLoaded / link-preview compensate logic
       because both adjust scrollTop on above-viewport height changes. */
    overflow-anchor: none;
  }

  .message-group {
    display: flex;
    gap: var(--space-lg);
    padding: var(--space-sm) var(--space-sm);
    border-radius: var(--radius-s);
    transition: background var(--transition);
    position: relative;
  }
  .message-group:hover { background: var(--deep); }
  /* Right-click context menu locks the row it's anchored to. Same
     fill as :hover (--deep) so the row reads as "still hovered"
     while the menu is open — no extra "selected" colour to learn,
     just hover that doesn't disappear when the cursor moves to the
     menu. Mirrored on .tfv-msg for Threads-feed cards. */
  .message-group.context-active,
  .tfv-msg.context-active {
    background: var(--deep);
  }
  .message-group.msg-highlight {
    background: var(--accent-soft) !important;
    animation: msgPulse 2.2s ease-out;
  }
  @keyframes msgPulse {
    0%, 30% { background: var(--accent-soft); }
    100% { background: transparent; }
  }

  /* Compact follow-up: same author consecutive — hide avatar/header */
  .message-group.compact {
    padding-top: var(--space-xs);
    padding-bottom: var(--space-xs);
    margin-top: 0;
  }
  /* Tighten the bottom padding of a non-compact message when followed by a compact one,
     so the gap between the first and the second message in a group matches the gap
     between subsequent compact messages. */
  .message-group:not(.compact):has(+ .message-group.compact) {
    padding-bottom: var(--space-xs);
  }
  .message-group.compact .msg-avatar { visibility: hidden; height: 0; margin: 0; }
  .message-group.compact .msg-header { display: none; }
  .message-group.compact .msg-hover-time {
    position: absolute;
    left: 0;
    width: 56px;
    text-align: center;
    font-size: 10px;
    color: var(--ink-ghost);
    line-height: 22px;
    font-variant-numeric: tabular-nums;
    opacity: 0;
    transition: opacity var(--transition);
    cursor: default;
  }
  .message-group.compact:hover .msg-hover-time { opacity: 1; }

  .message-group .msg-avatar {
    width: var(--avatar-lg);
    height: var(--avatar-lg);
    border-radius: var(--radius-full);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--font-md);
    color: white;
  }

  .message-group .msg-content { flex: 1; min-width: 0; }

  .msg-header {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    margin-bottom: 2px;
  }

  .msg-header .author {
    font-size: var(--font-md);
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
  }

  .msg-header .timestamp {
    font-size: var(--font-2xs);
    color: var(--ink-ghost);
    font-weight: 400;
  }

  /* S1.6 — inline status-emoji chip next to message authors. Rich
     hover popover (#kcStatusTooltipHost) carries emoji+text+expiry. */
  .kc-status-chip {
    display: inline-block;
    font-size: 14px;
    line-height: 1;
    vertical-align: -1px;
    cursor: default;
    flex-shrink: 0;
  }
  /* In .msg-header (which is flex with gap: var(--space-sm)), pull
     the chip closer to the author name — Slack-density. Other
     surfaces (DM list, member list) handle their own spacing. */
  .msg-header .kc-status-chip {
    margin-left: calc(var(--space-sm) * -1 + 4px);
  }

  /* S2 chat-visibility — shared rich tooltip mounted at document.body.
     Two lines: emoji+text crisp on top, expiry phrasing ghosted below.
     Positioned manually by user-status.js; no transition on transform
     because the position is recomputed per-show. */
  .kc-status-tooltip {
    position: fixed;
    z-index: 9999;
    display: none;
    max-width: 280px;
    padding: 8px 12px;
    border-radius: var(--radius-m);
    background: var(--void);
    color: var(--ink);
    box-shadow: var(--shadow-m);
    font-size: 13px;
    line-height: 1.3;
    pointer-events: none;
  }

  .kc-status-tooltip.visible {
    display: block;
  }

  .kc-status-tooltip-head {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    font-weight: 500;
  }

  .kc-status-tooltip-emoji {
    font-size: 16px;
    line-height: 1;
  }

  .kc-status-tooltip-text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 240px;
  }

  .kc-status-tooltip-expiry {
    margin-top: 2px;
    font-size: 12px;
    color: var(--ink-ghost);
  }

  /* .role-badge removed — was unused; use .kc-pill.kc-pill--role-* instead */

  .msg-text {
    font-size: var(--font-md);
    line-height: 1.45;
    color: var(--ink);
    word-wrap: break-word;
  }

  .msg-text code {
    background: var(--deep);
    padding: 2px 6px;
    border-radius: 4px;
    font-size: var(--font-sm);
    font-family: 'SF Mono', 'Fira Code', monospace;
    color: var(--accent);
  }

  .msg-text .mention {
    background: var(--mention-bg);
    color: var(--mention-fg);
    padding: 1px 6px;
    border-radius: 4px;
    cursor: pointer;
    font-weight: 600;
  }
  .msg-text .mention:hover { background: var(--mention-bg-hover); color: var(--mention-fg-hover); }
  .msg-text .mention.mention-me {
    background: var(--mention-self-bg);
    color: var(--mention-self-fg);
  }
  .msg-text .mention.mention-me:hover {
    background: var(--mention-self-bg-hover);
    color: var(--mention-self-fg-hover);
  }

  /* Mention autocomplete picker */
  /* Floating-picker над композером (mention / slash). Общая база —
     раньше .mention-picker и .slash-picker дублировали этот блок
     1-в-1, расходясь только размерами. Конкретный размер задаётся
     модификатором ниже. */
  .kc-float-picker {
    position: fixed;
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-l);
    padding: 6px;
    /* Slack-стиль: единый scrollable список без секционных заголовков.
       Max-height ~ 50vh чтобы не вылетать за экран, оставляя место
       композеру снизу. */
    overflow-y: auto;
    overscroll-behavior: contain;
    z-index: 300;
    display: none;
    backdrop-filter: blur(var(--blur));
    animation: fadeIn 80ms;
  }
  .mention-picker {
    min-width: 336px;
    max-width: 432px;
    max-height: min(360px, 50vh);
  }
  .mention-picker-title {
    padding: 6px 10px 4px;
    font-size: var(--font-2xs); text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--ink-muted); font-weight: 700;
  }
  .mention-item {
    display: flex; align-items: center; gap: 10px;
    padding: 6px 10px;
    border-radius: var(--radius-s);
    cursor: pointer;
    color: var(--ink-secondary);
  }
  .mention-item.selected { background: var(--accent); color: white; }
  .mention-item.selected .mention-sub { color: rgba(255,255,255,0.75); }
  .mention-item.selected .mention-handle { color: rgba(255,255,255,0.85); }
  .mention-avatar {
    width: var(--avatar-sm); height: var(--avatar-sm); border-radius: var(--radius-full);
    display: flex; align-items: center; justify-content: center;
    font-size: var(--font-2xs); font-weight: 700; color: white; flex-shrink: 0;
  }
  .mention-avatar-ctx {
    background: transparent;
    color: var(--accent);
  }
  .mention-item.selected .mention-avatar-ctx { color: white; }
  /* TWO-row layout for real users (Telegram-style):
       avatar | (name / @handle stacked) | role badge right
     Context items (@all/@here/@channel) keep the legacy single-row
     layout — see .mention-item:not(.mention-item-2row) below. */
  .mention-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; overflow: hidden; }
  .mention-name { font-size: var(--font-md); font-weight: 500; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .mention-handle { font-size: var(--font-xs); line-height: 1.2; color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .mention-sub { font-size: var(--font-xs); color: var(--ink-muted); white-space: nowrap; flex-shrink: 0; margin-left: auto; }
  /* Selected (highlighted accent bg) keeps both lines legible. */
  .mention-item.selected .mention-handle { color: rgba(255,255,255,0.85); }
  /* Context single-row tweaks: name and sub flow horizontally with sub
     right-anchored, same as before two-row layout was added. */
  .mention-item:not(.mention-item-2row) .mention-name { flex: 1; }

  /* ── Slash-command discovery picker ─────────────────────────────
     База в .kc-float-picker; здесь только размеры. */
  .slash-picker {
    min-width: 320px;
    max-width: 420px;
    max-height: min(320px, 50vh);
  }
  .slash-item {
    padding: 7px 10px;
    border-radius: var(--radius-s);
    cursor: pointer;
    color: var(--ink-secondary);
  }
  .slash-item.selected { background: var(--accent); }
  .slash-item-usage code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--font-sm);
    color: var(--ink);
    background: transparent;
    padding: 0;
  }
  .slash-item.selected .slash-item-usage code { color: white; }
  .slash-item-desc {
    font-size: var(--font-xs);
    color: var(--ink-muted);
    margin-top: 2px;
  }
  .slash-item.selected .slash-item-desc { color: var(--color-white-64); }

  .msg-reactions {
    display: flex;
    gap: var(--space-xs);
    margin-top: var(--space-sm);
    flex-wrap: wrap;
    user-select: none;
    -webkit-user-select: none;
  }

  /* Telegram/Discord-style reaction chip: solid filled background,
     no border, small radius (not a pill). Generous padding around
     the emoji glyph so it doesn't touch the chip edges — emoji
     fonts render larger than their declared font-size and a tight
     padding made the chip look like the emoji *was* the chip.
     - Idle: dark surface tint that reads as "tap to add yours".
     - .reacted: accent fill so the user's own reactions stand out
       without an extra outline. */
  .reaction {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-s);
    /* `--raised` sits one step above the chat surface so the chip
       reads as a tappable bubble instead of bleeding into the
       message background. */
    background: var(--raised);
    border: 0;
    cursor: pointer;
    font-size: var(--font-lg);
    line-height: 1;
    color: var(--ink);
    user-select: none;
    -webkit-user-select: none;
    transition: background var(--transition), color var(--transition);
  }
  .reaction:hover {
    background: var(--elevated);
  }
  /* Telegram pops "your" reaction with a saturated accent fill —
     --accent-soft (12% alpha) is too washed out against the dark
     surface to read as "this is mine". Use a much higher-alpha
     accent so the bubble reads at a glance. */
  .reaction.reacted {
    background: color-mix(in srgb, var(--accent) 65%, transparent);
  }
  .reaction.reacted:hover {
    background: color-mix(in srgb, var(--accent) 78%, transparent);
  }

  /* Emoji rendered slightly smaller than the chip's line-box so the
     glyph doesn't dominate the bubble. The chip itself stays 16px /
     line-height: 1 so the .reacted vs idle bubbles keep identical
     dimensions regardless of the inner glyph size. */
  .reaction .r-emoji {
    font-size: var(--font-md);
    line-height: 1;
  }
  /* P1.8 PR 3 — custom-emoji reactions use an <img> instead of a
     unicode span. Sized to match the unicode glyph height so the
     chip dimensions don't jump between reaction types. */
  .reaction .r-emoji-img {
    width: 16px;
    height: 16px;
    display: block;
    object-fit: contain;
  }
  .reaction .r-count {
    font-size: var(--font-sm);
    font-weight: 600;
    color: var(--ink-secondary);
  }
  /* The strong fill needs white text for contrast on the .reacted
     pill — accent-on-accent is unreadable. */
  .reaction.reacted .r-count {
    color: var(--color-white-100);
  }

  .message-actions {
    position: absolute;
    top: -14px;
    right: 8px;
    display: none;
    gap: 2px;
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-s);
    padding: 2px;
    box-shadow: var(--shadow-s);
    z-index: 10;
  }
  .message-group:hover .message-actions { display: flex; }
  /* когда в сообщении есть код — поднимаем actions выше чтобы не перекрывать хедер блока */
  .message-group:hover:has(.kc-code-block) .message-actions { top: -30px; }

  .msg-action-btn {
    width: 30px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    background: transparent;
    border: none;
    color: var(--ink-muted);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
  }
  .msg-action-btn:hover {
    background: var(--elevated);
    color: var(--ink);
  }

  .date-divider {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-lg) 0 var(--space-sm);
    /* user-select: none lives in the global chrome rule in layout.css */
  }

  .date-divider::before, .date-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border);
  }

  .date-divider span {
    font-size: var(--font-2xs);
    font-weight: 600;
    color: var(--ink-muted);
    white-space: nowrap;
  }

  /* Sticky date-pill — Slack-стиль floating chip в top-center
     scroll-контейнера. Position: sticky внутри scroller'а; top: 8px
     означает что pill всегда в 8px от верхней границы видимого
     viewport'а scroller'а. align-self:center центрирует по горизонтали
     внутри flex-column scroller'а.

     Контент label'а обновляет kcInitDatePill (priv/static/js/date-pill.js)
     при каждом scroll-событии — берёт текст первого «активного» divider'а.

     visible-класс делает pill видимым — пустой scroller (нет divider'ов)
     или ещё не загружено сообщение → pill скрыт. */
  .kc-date-pill {
    position: sticky;
    /* Negative top тянет pill вверх — компенсирует scroller padding-top
       (var(--space-lg)), pill реально прижимается к border-bottom
       header'а. */
    top: -8px;
    z-index: 5;
    align-self: center;
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    margin-bottom: -28px;
    border-radius: 999px;
    background: var(--raised);
    color: var(--ink-secondary);
    font-size: var(--font-2xs);
    font-weight: 600;
    box-shadow: var(--shadow-s);
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--transition);
    user-select: none;
    -webkit-user-select: none;
  }
  .kc-date-pill.visible { opacity: 1; }
  /* Phase 3 — clickable variant (только для channel/DM scroller'а,
     threads pill остаётся read-only). Pointer-events:auto переопределяет
     base-rule выше. */
  .kc-date-pill--clickable {
    pointer-events: auto;
    cursor: pointer;
  }
  .kc-date-pill--clickable:hover {
    /* var(--surface) совпадает с deep background — pill пропадал.
       --float — более контрастный raised-tone, гарантированно отличим. */
    background: var(--float);
    color: var(--ink);
  }

  /* Jump-to-date highlight — короткая вспышка вокруг target divider'а
     после jump-prыжка. */
  @keyframes kc-jump-flash {
    0%   { background: var(--accent-soft); }
    100% { background: transparent; }
  }
  .date-divider.kc-jump-highlight {
    animation: kc-jump-flash 1.8s ease-out;
  }

  .typing-indicator {
    height: 24px;
    padding: 8px var(--space-lg) 0;
    font-size: var(--font-xs);
    color: var(--ink-muted);
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    flex-shrink: 0;
  }

  .typing-dots {
    display: flex;
    gap: 3px;
    align-items: center;
  }

  .typing-dots span {
    width: 4px;
    height: 4px;
    border-radius: var(--radius-full);
    background: var(--ink-muted);
    animation: typing 1.4s infinite;
  }
  .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
  .typing-dots span:nth-child(3) { animation-delay: 0.4s; }

  @keyframes typing {
    0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
    30% { opacity: 1; transform: translateY(-3px); }
  }

  .composer {
    padding: 0 var(--space-lg) var(--space-lg);
    flex-shrink: 0;
    position: relative;
  }

  .composer-inner {
    display: flex;
    flex-direction: column;
    background: var(--glass);
    border-radius: var(--radius-l);
    border: 1px solid var(--border);
    transition: border-color var(--transition);
    backdrop-filter: blur(var(--blur));
  }
  .composer-inner:focus-within {
    border-color: var(--border-accent);
    /* Inner shadow вместо outer glow — Slack-style. Тоньше, не
       размывает chat-area вокруг. */
    box-shadow: inset 0 0 0 1px var(--accent-soft);
  }
  .composer-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 6px var(--space-md) 6px var(--space-md);
  }

  /* Slack-style двух-рядный composer: текст-area сверху, action-bar
     внизу. Text-row дышит вертикально под autogrow, action-row фикс
     высоты. */
  .composer-text-row {
    display: flex;
    /* center + фиксированная min-height стабилизируют высоту: между
       empty placeholder и с одной строкой текста composer-input не
       прыгает (раньше выглядело будто весь композер «вырастает» при
       первом нажатии). */
    align-items: center;
    padding: 4px var(--space-md) 0 var(--space-md);
    min-height: 44px;
  }
  .composer-action-row {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 0 var(--space-sm) 6px var(--space-sm);
  }
  .composer-action-divider {
    display: inline-block;
    width: 1px;
    height: 20px;
    background: var(--border);
    margin: 0 6px;
    flex-shrink: 0;
  }
  .composer-action-spacer { flex: 1; }

  /* Disabled-плейсхолдеры для будущих фич (/ slash, потом 📹/🎙) —
     выглядят visible, но не реагируют на hover/click. */
  .composer-btn--disabled {
    opacity: 0.4;
    cursor: default;
    pointer-events: none;
  }

  /* Aa-toggle для format-row — active state когда toolbar развёрнут. */
  .composer-inner.format-shown #aaToggleBtn {
    background: var(--glass-hover);
    color: var(--ink);
  }
  .aa-label {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: -0.3px;
    line-height: 1;
    /* `A` крупнее `a` — типографически имитируем формат-индикатор. */
    font-family: ui-sans-serif, -apple-system, system-ui, sans-serif;
  }

  /* Collapsible format toolbar поверх text area. Скрыт по умолчанию;
     class .format-shown на .composer-inner разворачивает. */
  .composer-format-row {
    display: none;
    align-items: center;
    gap: 2px;
    padding: 6px var(--space-sm) 2px var(--space-sm);
    border-bottom: 1px solid var(--border);
  }
  .composer-inner.format-shown .composer-format-row { display: flex; flex-wrap: wrap; }

  .cfr-btn {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-s);
    background: transparent;
    border: none;
    color: var(--ink-muted);
    cursor: pointer;
    flex-shrink: 0;
    transition: color var(--transition), background var(--transition);
  }
  .cfr-btn:hover { color: var(--ink); background: var(--glass-hover); }
  /* Active state — тот же стиль что и у floating .format-toolbar
     button.active (тёмная заливка + полный white-ink). */
  .cfr-btn.active { color: var(--ink); background: var(--deep); }
  .cfr-sep {
    display: inline-block;
    width: 1px;
    height: 18px;
    background: var(--border);
    margin: 0 4px;
  }

  /* «+»-dropdown — стилистически близок к send-options-menu.
     Выровнен над «+» кнопкой и сдвинут ещё на 20px левее (юзер
     попросил 2026-05-30) — теперь dropdown слегка выходит за левый
     край composer-inner, что центрирует его относительно иконки и
     визуально читается как «выход вверх-влево». */
  .composer-plus-menu {
    position: absolute;
    bottom: 56px;
    left: calc(var(--space-sm) - 20px);
    z-index: 50;
    min-width: 220px;
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-m);
    padding: 6px;
    display: none;
    animation: fadeIn 100ms;
  }
  .composer-plus-menu.visible { display: block; }
  .cpm-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: none;
    background: transparent;
    border-radius: var(--radius-s);
    color: var(--ink-secondary);
    font: inherit;
    font-size: var(--font-sm);
    cursor: pointer;
    text-align: left;
  }
  .cpm-item:hover { background: var(--glass-hover); color: var(--ink); }
  .cpm-item .cpm-icon {
    width: 22px;
    color: var(--ink-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .cpm-item--disabled { opacity: 0.5; cursor: default; pointer-events: none; }
  .cpm-soon {
    margin-left: 6px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--ink-ghost);
    background: var(--glass);
    border-radius: 4px;
    padding: 2px 5px;
  }
  .cpm-divider { height: 1px; background: var(--border); margin: 4px 6px; }

  .composer-btn {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Slack-стиль: квадратные кнопки с лёгким закруглением, не pill.
       Hover/active отрисовываются той же формой. */
    border-radius: var(--radius-s);
    background: transparent;
    border: none;
    color: var(--ink-muted);
    cursor: pointer;
    flex-shrink: 0;
    transition: color var(--transition), background var(--transition);
  }
  .composer-btn:hover {
    color: var(--ink-secondary);
    background: var(--glass-hover);
  }

  /* Discord-style "GIF" badge button. Sized to sit comfortably
     inside the 36x36 .composer-btn circle with breathing room — the
     previous 26×18 / 1.5px-border / 10px-font filled the circle
     edge-to-edge and looked cramped. */
  .composer-btn .gif-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 14px;
    padding: 0 3px;
    border: 1.2px solid currentColor;
    border-radius: 3px;
    font-size: 9px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0.2px;
    pointer-events: none;
  }

  .composer-input {
    flex: 1;
    background: transparent;
    border: none;
    color: var(--ink);
    font-size: var(--font-md);
    font-family: inherit;
    /* 1.4 keeps the contenteditable caret close to the actual text
       height. 1.5 made the blinking caret feel "tall" inside an
       empty composer — visually it sat ~6 px above the line. */
    line-height: 1.4;
    resize: none;
    outline: none;
    /* min-height = одна line-height (font-md ≈ 14×1.4 ≈ 20px) +
       небольшой запас. Это гарантирует что empty placeholder и
       первая строка текста занимают одинаковую вертикаль —
       композер не «прыгает» при первом символе. */
    min-height: 22px;
    max-height: 200px;
    padding: 0;
    overflow-y: auto;
    word-break: break-word;
    white-space: pre-wrap;
  }
  /* Custom emoji inside the composer share the message-bubble size by
     default — pull them down a touch so the tightened line-height
     above doesn't clip them at the top. */
  .composer-input .kc-custom-emoji {
    width: 20px;
    height: 20px;
    margin: -2px 1px 0;
  }
  /* WebKit (Wails desktop, Safari) renders the contenteditable caret
     visibly taller than Chromium for the same line-height. Tighten
     just for desktop so the caret matches what Chrome shows in the
     browser — desktop body is the only place we add `html.kc-desktop`. */
  html.kc-desktop .composer-input { line-height: 1.2; }
  .composer-input::placeholder { color: var(--ink-ghost); }
  /* contenteditable placeholder — shown only when the div has no text. */
  .composer-input:empty:before {
    content: attr(data-placeholder);
    color: var(--ink-ghost);
    pointer-events: none;
  }
  /* Inline formatting rendered inside the editor — same visual weight as
     rendered messages. */
  .composer-input strong, .composer-input b { font-weight: 700; }
  .composer-input em, .composer-input i { font-style: italic; color: var(--ink-secondary); }
  .composer-input u { text-decoration: underline; text-underline-offset: 2px; }
  .composer-input s, .composer-input strike, .composer-input del { text-decoration: line-through; color: var(--ink-muted); }
  .composer-input a { color: var(--accent); text-decoration: none; }
  .composer-input code {
    font-family: 'SF Mono', 'Fira Code', monospace; font-size: var(--font-sm);
    background: var(--surface); padding: 1px 6px; border-radius: 4px;
  }
  .composer-input pre {
    background: var(--deep); padding: 8px 10px; border-radius: 6px;
    font-family: 'SF Mono', 'Fira Code', monospace; font-size: var(--font-sm);
    margin: 4px 0; white-space: pre-wrap;
  }
  /* Внутри <pre> code-тэг не должен получать inline-code padding/
     background — иначе первая строка визуально сдвинута и подсвечена
     иначе, чем последующие. */
  .composer-input pre code {
    background: none;
    padding: 0;
    border-radius: 0;
    font-family: inherit;
    font-size: inherit;
  }
  .composer-input ul, .composer-input ol { margin: 4px 0; padding-left: 22px; }
  .composer-input blockquote {
    border-left: 3px solid var(--border-mid); padding-left: 10px;
    color: var(--ink-muted); margin: 4px 0;
  }

  /* Slack-стиль split send button:
     * Body (send) — зелёный (commit-action)
     * Divider 1px — между body и chevron
     * Chevron — открывает schedule menu
     * Disabled — outline-only, нейтральный (без заливки), показывает
       форму но не призывает к действию пока композер пустой. */
  .composer-send-group {
    display: flex;
    align-items: stretch;
    flex-shrink: 0;
    background: var(--accent);
    /* Border 1px transparent всегда — disabled state меняет ТОЛЬКО
       цвет, не добавляет border-size. Без этого active/disabled
       отличались на 2px по высоте, и action-row дёргался при первом
       символе. */
    border: 1px solid transparent;
    border-radius: var(--radius-s);
    overflow: hidden;
    transition: background var(--transition), border-color var(--transition);
  }
  .composer-send-group:hover { background: var(--accent-hover); }

  .composer-send-group .composer-send,
  .composer-send-group .composer-send-chevron {
    background: transparent; border: none; color: white;
    cursor: pointer; padding: 0;
    display: flex; align-items: center; justify-content: center;
  }
  .composer-send-group .composer-send {
    width: 36px; height: 36px;
  }
  .composer-send-group .composer-send-chevron {
    width: 22px; height: 36px;
    border-left: 1px solid var(--color-white-24);
  }
  .composer-send-group .composer-send-chevron:hover,
  .composer-send-group .composer-send:hover {
    background: var(--color-white-16);
  }

  /* Disabled (композер пустой) — outline-only без заливки. Border
     уже 1px transparent в базовом правиле — здесь меняем только цвет,
     чтобы высота group не отличалась от active state. */
  .composer-send-group.send-disabled {
    background: transparent;
    border-color: var(--border);
  }
  .composer-send-group.send-disabled:hover { background: transparent; }
  .composer-send-group.send-disabled .composer-send,
  .composer-send-group.send-disabled .composer-send-chevron {
    color: var(--ink-muted);
  }
  .composer-send-group.send-disabled .composer-send:hover,
  .composer-send-group.send-disabled .composer-send-chevron:hover {
    background: var(--glass-hover);
    color: var(--ink-secondary);
  }
  .composer-send-group.send-disabled .composer-send {
    pointer-events: none;
  }
  .composer-send-group.send-disabled .composer-send-chevron {
    border-left-color: var(--border);
  }

  /* Slow-mode countdown — replaces the send icon with MM:SS, hides
     the chevron, and dims the pill so it reads as "not ready yet". */
  .composer-send-group.kc-slowmode {
    background: var(--float);
    cursor: default;
  }
  .composer-send-group.kc-slowmode:hover { background: var(--float); }
  .composer-send-group.kc-slowmode .composer-send {
    width: auto;
    min-width: 44px;
    padding: 0 10px;
    color: var(--ink);
    pointer-events: auto;
  }
  /* Even when the composer is empty (.send-disabled), keep the
     countdown at full contrast so it's visible without typing. */
  .composer-send-group.kc-slowmode.send-disabled { background: var(--float); }
  .composer-send-group.kc-slowmode.send-disabled .composer-send {
    color: var(--ink);
    pointer-events: auto;
  }
  .composer-send-group.kc-slowmode .composer-send:hover { background: transparent; }
  .composer-send-group.kc-slowmode .composer-send-chevron { display: none; }
  .composer-send-cooldown {
    font-variant-numeric: tabular-nums;
    font-size: var(--font-sm);
    font-weight: 600;
    letter-spacing: 0.3px;
  }

  /* Persistent slow-mode hint above the composer (Discord-style) —
     absolutely positioned in the bottom-right of .composer, just
     above the composer-inner. */
  .slowmode-info {
    position: absolute;
    right: var(--space-lg);
    bottom: 100%;
    width: fit-content;
    padding: 0 4px 4px;
    color: var(--ink-muted);
    font-size: var(--font-xs);
    cursor: default;
    z-index: 4;
  }
  .slowmode-info > svg {
    vertical-align: -2px;
    margin-right: 4px;
    opacity: 0.8;
  }

  /* Transient tooltip shown when the user attempts to send during a
     cooldown — anchored above the slowmode-info badge. */
  .slowmode-tooltip {
    position: absolute;
    left: calc(var(--space-lg) + 48px);
    bottom: calc(100% + 8px);
    max-width: 280px;
    padding: 8px 12px;
    background: var(--deep);
    color: var(--ink);
    border: 1px solid var(--border);
    border-radius: var(--radius-m);
    font-size: var(--font-sm);
    line-height: 1.4;
    box-shadow: var(--shadow-m);
    opacity: 0;
    transform: translateY(4px);
    transition: opacity var(--transition), transform var(--transition);
    pointer-events: none;
    z-index: 5;
  }
  .composer { position: relative; }
  .slowmode-tooltip.visible { opacity: 1; transform: translateY(0); }

  /* ── Compact mode (Appearance → density: compact) ─────────────────
     `body.kc-compact` flips message rows to a tighter cadence —
     same content, fewer pixels of vertical air. Goal: see ~40%
     more messages on the same screen without hurting readability.
     Avatars shrink slightly, hover-actions still work the same.
  */
  body.kc-compact .message-group {
    padding-top: var(--space-xs);
    padding-bottom: var(--space-xs);
    gap: var(--space-sm);
  }
  body.kc-compact .message-group.compact {
    padding-top: 1px;
    padding-bottom: 1px;
  }
  body.kc-compact .msg-text {
    line-height: 1.35;
  }
  body.kc-compact .msg-reactions {
    margin-top: var(--space-xs);
  }
  body.kc-compact .msg-thread-preview {
    margin-top: var(--space-xs);
    padding: var(--space-xs) var(--space-sm);
  }
  body.kc-compact .msg-avatar {
    width: var(--avatar-md);
    height: var(--avatar-md);
  }
  body.kc-compact .msg-header {
    margin-bottom: 1px;
  }

  /* ── Font-size scale (Appearance → font-size: small/default/large)
     Targets the user-facing text surfaces — `.msg-text`, channel
     names, DM previews, message header. The full project still uses
     hardcoded px in many places; expanding the scale to every
     selector is a separate refactor. For now the top 6 surfaces
     adapt to var(--font-...) which itself multiplies by
     --kc-font-scale. */
  .msg-text { font-size: var(--font-md); }
  .msg-header strong { font-size: var(--font-md); }
  .channel-name { font-size: var(--font-sm); }
  .dm-name span { font-size: var(--font-sm); }
  .dm-preview { font-size: var(--font-xs); }
  .msg-hover-time { font-size: var(--font-2xs); }

/* ── Channel invite picker (Pachca-style multi-select) ──
   Opened from the hover «Пригласить в канал» icon in the channel list,
   wired in channel-invite.js. The modal shell is plain `.modal-overlay`
   so it inherits the global Esc/Enter/backdrop keyboard contract;
   everything below styles the inner rows + footer. */
.kc-invite-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  background: var(--deep);
}

.kc-invite-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-s);
  cursor: pointer;
  user-select: none;
  transition: background var(--transition);
}
.kc-invite-row:hover { background: var(--surface); }

.kc-invite-row .kc-invite-check {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--ink-ghost);
  border-radius: 4px;
  background: transparent;
  position: relative;
  transition: background var(--transition), border-color var(--transition);
}
.kc-invite-row.checked .kc-invite-check {
  background: var(--accent);
  border-color: var(--accent);
}
.kc-invite-row.checked .kc-invite-check::after {
  content: '';
  position: absolute;
  left: 5px;
  top: 1px;
  width: 5px;
  height: 10px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.kc-invite-row .kc-invite-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kc-invite-row .kc-invite-name {
  font-size: var(--font-md);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kc-invite-row .kc-invite-sub {
  font-size: var(--font-xs);
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Existing channel members are now clickable too — unchecking sends
   them to the "to remove" bucket on submit. The row stays full-opacity
   so the user can read the names; the tag on the right (".kc-invite-tag"
   for "уже в канале" / "убрать") signals intent. Owner is the only
   exception — see `.kc-invite-row--owner`. */
.kc-invite-row.existing { cursor: pointer; }

.kc-invite-row--owner {
  cursor: default;
  opacity: 0.7;
}
.kc-invite-row--owner:hover { background: transparent; }

.kc-invite-row .kc-invite-tag {
  flex: 0 0 auto;
  font-size: var(--font-2xs);
  color: var(--ink-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 2px 8px;
  border-radius: 999px;
  text-transform: lowercase;
  letter-spacing: 0.02em;
}
.kc-invite-row .kc-invite-tag--remove {
  color: var(--danger);
  border-color: var(--danger);
  background: transparent;
}

.kc-invite-empty {
  padding: 24px 12px;
  text-align: center;
  font-size: var(--font-sm);
  color: var(--ink-muted);
}
