  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  /* Desktop title-bar height — 0 by default, 32px when the page is
     loaded inside the Wails desktop shell (body.kc-desktop). 32px
     puts the bar's geometric center at y=16, close to macOS
     traffic-light center (~y=18 with TitleBarHidden), so the
     custom nav buttons sit visually aligned with the OS-drawn
     buttons. Earlier 38px left a perceptible offset. */
  :root { --kc-titlebar-h: 0px; }
  html.kc-desktop { --kc-titlebar-h: 32px; }

  /* Custom title bar (Discord pattern). Drag-region by default; any
     interactive child must opt out via -webkit-app-region: no-drag.
     On macOS the first 74px are empty so the OS-drawn traffic-light
     buttons sit on top of our --void background. */
  .kc-desktop-titlebar {
    display: none;
    position: fixed;
    top: 0; left: 0; right: 0;
    height: var(--kc-titlebar-h, 32px);
    background: var(--void);
    color: var(--ink-muted);
    font-size: var(--font-xs);
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    z-index: 10000;
    user-select: none;
    -webkit-user-select: none;
    /* Wails uses its own DOM-event dispatcher for window dragging
       (--wails-draggable), NOT Chromium's -webkit-app-region — the
       latter is silently ignored by WKWebView on macOS. Both
       properties are set so previewing the SPA in a regular
       browser still feels right; only --wails-draggable actually
       moves the window in the desktop shell. */
    --wails-draggable: drag;
    -webkit-app-region: drag;
    /* Thin separator line so the bar reads as its own surface above
       the rounded panel below — without it, titlebar and .left-shell
       share the same --void and visually merge into one continuous
       block. The line is just `--border`, the same hairline divider
       used everywhere else in the chrome. */
    border-bottom: 1px solid var(--border);
  }
  body.kc-desktop .kc-desktop-titlebar { display: flex; }
  /* macOS: first 74px reserved for OS traffic-light buttons. The
     drag-region still applies — clicking that area to drag the
     window is fine because the buttons themselves intercept their
     own clicks before our handler sees them. */
  body.kc-desktop-mac .kc-desktop-titlebar-traffic-spacer,
  html.kc-desktop-mac .kc-desktop-titlebar-traffic-spacer {
    display: block;
    width: 62px;
    flex-shrink: 0;
  }
  .kc-desktop-titlebar-traffic-spacer { display: none; }

  /* Back/forward buttons. no-drag so click reaches the handler;
     square 26×26 with rounded corners, fade on hover. */
  .kc-desktop-titlebar-nav {
    display: flex;
    gap: 4px;
    /* No vertical offset — TitleBarHidden (without inset) puts
       traffic lights at macOS' default ~12px-from-top, which centers
       cleanly in our 38px bar. Buttons are 26×26 flex-centered, both
       end up at the bar's geometric center. */
    /* Children that take clicks must opt out of the parent's drag
       region or the click is consumed by the move-window handler. */
    --wails-draggable: no-drag;
    -webkit-app-region: no-drag;
  }
  .kc-desktop-titlebar-btn {
    width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--ink-muted);
    cursor: pointer;
    transition: background var(--transition), color var(--transition), opacity var(--transition);
  }
  .kc-desktop-titlebar-btn:hover:not([disabled]) {
    background: var(--surface);
    color: var(--ink);
  }
  .kc-desktop-titlebar-btn:active:not([disabled]) {
    background: var(--raised);
  }
  /* Discord convention: button greyed when there's nowhere to go.
     Pointer-events:none so hover doesn't light up the background;
     opacity 0.35 dims both the icon and the visual button shape. */
  .kc-desktop-titlebar-btn[disabled] {
    opacity: 0.35;
    cursor: default;
    pointer-events: none;
  }

  .kc-desktop-titlebar-spacer { flex: 1; }

  /* Right-aligned actions cluster — hosts the auto-updater indicator
     and any future global affordances. Discord puts equivalent icons
     here (inbox, help, download arrow). draggable: no-drag so clicks
     reach the button, not the window-move handler. */
  .kc-desktop-titlebar-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    padding-right: 8px;
    --wails-draggable: no-drag;
    -webkit-app-region: no-drag;
  }

  /* Update-button accent — success-green (DS --live), Discord-style.
     No glyph dot — the colour itself carries the "update ready" signal. */
  .kc-desktop-titlebar-update {
    color: var(--success);
  }
  .kc-desktop-titlebar-update:hover {
    color: var(--ink);
    background: var(--success);
  }
  /* Section group (icon + title) — flex-row, icon on the left.
     Discord shows the server avatar next to the server name in the
     title bar; the same mini-pill pattern applies here. */
  .kc-desktop-titlebar-section {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
  }
  .kc-desktop-titlebar-icon {
    width: 18px; height: 18px;
    border-radius: 4px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-2xs);
    font-weight: 700;
    color: var(--ink-muted);
  }
  .kc-desktop-titlebar-icon:empty { display: none; }
  .kc-desktop-titlebar-title {
    color: var(--ink);
    font-size: var(--font-sm);
    font-weight: 600;
    letter-spacing: 0.01em;
    white-space: nowrap;
  }

  html {
    /* Lock the document element too — body is already overflow:hidden,
       but Element.scrollIntoView() (called by the pin-panel "go to
       source" action and a few other scroll-restore paths) walks up
       to the documentElement when body is unscrollable and silently
       scrolls *that* instead. In the desktop shell, the fixed-position
       .kc-desktop-titlebar then visually "uplifts" because the rest of
       the page slides up under it. Pinning html.scrollTop to 0 here
       costs nothing in browser (body still owns the layout) and stops
       the desktop regression at its source. */
    height: 100%;
    overflow: hidden;
  }
  body {
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
    /* Pure black so the rounded corners on .left-shell read against
       a darker outer canvas — without this, body shares --void with
       the panel itself and the radius is invisible. body is overflow:
       hidden, so the only places this colour shows are the curve
       cut-outs around .app's rounded children. */
    background: var(--color-black-absolute-0);
    color: var(--ink);
    height: 100vh;
    overflow: hidden;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  /* Every <button> is clickable — default to pointer unless explicitly
     disabled. Individual rules can still override (e.g. cursor: text on
     composer-style buttons) but this removes the default "I-beam" /
     arrow cursor that appears whenever a new button ships without a
     cursor: pointer declaration of its own. */
  button:not(:disabled) { cursor: pointer; }
  button:disabled { cursor: not-allowed; }

  /* Chrome surfaces — header bars, message author labels, date and
     thread dividers — should never participate in drag-select. Mirrors
     Discord / Slack: only the message body and form fields are
     selectable; everything else is UI scaffolding. Each rule lists
     its own selector group so removing a feature (e.g. retiring the
     thread side panel) cleanly drops its entry. */
  .chat-header,
  .thread-panel-header,
  .tfv-header,
  .mfv-header,
  .pin-panel-header,
  .date-divider,
  .dm-intro,
  .kc-update-banner,
  .msg-thread-preview {
    user-select: none;
    -webkit-user-select: none;
  }

  /* Внутри chat-header / msg-header — текстовые поля (имя собеседника,
     last-seen status, имя автора, время сообщения) хочется уметь
     выделять (copy-paste). UI-кнопки рядом остаются user-select:none
     по умолчанию через parent .chat-header. */
  .chat-header .chat-title,
  .chat-header .chat-title + span,
  .msg-header .author,
  .msg-header .timestamp {
    user-select: text;
    -webkit-user-select: text;
  }

  .i { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
  .i svg { display: block; }

  /* Phase 2 — verification banner. Top-of-window sticky bar; уменьшает
     высоту .app через --kc-verify-h, который JS выставляет/сбрасывает.
     Coral-soft фон, ink-secondary текст, accent-кнопка справа. */
  .kc-verify-banner {
    position: fixed;
    top: var(--kc-titlebar-h, 0px);
    left: 0; right: 0;
    z-index: 50;
    display: flex; align-items: center; justify-content: center;
    gap: 12px;
    padding: 10px 16px;
    /* Plain opaque deep — без полупрозрачности, чтобы header под
       баннером не просвечивал. */
    background: var(--deep);
    color: var(--ink);
    font-size: 13px;
    border-bottom: 1px solid var(--coral);
  }
  .kc-verify-banner-text { line-height: 1.3; }
  .kc-verify-banner-text > span { font-weight: 600; }
  .kc-verify-banner-btn {
    padding: 6px 12px;
    background: var(--accent); color: var(--on-accent);
    border: none; border-radius: 6px;
    font: inherit; font-weight: 600; font-size: 12px;
    cursor: pointer;
    transition: opacity var(--transition);
  }
  .kc-verify-banner-btn:hover { opacity: 0.9; }
  .kc-verify-banner-btn:disabled { opacity: 0.5; cursor: default; }
  /* Banner накладывается поверх верхней полосы интерфейса (по выбору
     UX), не толкает .app вниз. Slack/Discord-стиль floating-bar. */

  .app {
    display: grid;
    grid-template-columns: auto 1fr;
    /* Subtract the desktop title-bar height (0 on web, 30 in Wails)
       so the .app sits below the draggable bar without overflowing. */
    height: calc(100vh - var(--kc-titlebar-h, 0px));
    margin-top: var(--kc-titlebar-h, 0px);
    position: relative;
  }
  .app.thread-open .chat-area { padding-right: var(--thread-panel-width, 420px); }

  .app.members-open {
    grid-template-columns: auto 1fr 260px;
  }

  /* Left shell — wraps the server-rail + channel-sidebar row plus the
     unified bottom block (voice-bar + account-strip). The bottom block
     spans both columns (72 + sidebar width) so it visually echoes the
     chat-area's composer on the right. */
  .left-shell {
    display: flex;
    flex-direction: column;
    background: var(--void);
    min-width: 0;
    /* Pin to viewport height so the flex children (row + strip) split
       a fixed budget. Without this, the implicit grid auto-row can
       grow past 100vh when the channel list is intrinsically tall —
       pushing .account-strip below the viewport on first paint. The
       strip only "appeared" later when re-render shrunk content above.
       Subtracts the desktop title-bar height (0 on web, 30 in Wails). */
    height: calc(100vh - var(--kc-titlebar-h, 0px));
    position: relative;
  }
  /* Single continuous boundary line on the right edge of the whole
     left panel. Done as a pseudo-element on the outer shell so the
     line cannot break between the row, voice-bar and account-strip
     regardless of which children render at any given moment. */
  .left-shell::after {
    content: '';
    position: absolute;
    top: 0; bottom: 0; right: 0;
    width: 1px;
    background: var(--border);
    pointer-events: none;
  }
  .left-shell-row {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
  }

  .server-rail {
    background: var(--void);
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: var(--space-md) 0;
    gap: var(--space-sm);
    border-right: 1px solid var(--border);
    overflow-y: auto;
    scrollbar-width: none;
    width: 72px;
    flex-shrink: 0;
    /* Match the body's outer black canvas with a rounded top-left
       corner so the panel reads as a card sitting under the desktop
       title bar. Bottom-left stays square — the .account-strip below
       continues the same panel surface flush to the window edge. */
    border-top-left-radius: var(--radius-m);
  }
  .server-rail::-webkit-scrollbar { display: none; }

  .server-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-l);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: border-radius var(--transition), background var(--transition), transform var(--transition);
    background: var(--raised);
    color: var(--ink-secondary);
    font-size: 18px;
    font-weight: 600;
    position: relative;
    flex-shrink: 0;
    user-select: none;
    -webkit-user-select: none;
  }

  .server-icon:hover {
    border-radius: var(--radius-m);
    background: var(--accent);
    color: white;
    transform: translateY(-1px);
  }

  .server-icon.active {
    border-radius: var(--radius-m);
    background: var(--accent);
    color: white;
  }

  .server-icon .pill {
    position: absolute;
    left: -14px;
    width: 4px;
    height: 0;
    background: var(--ink);
    border-radius: 0 4px 4px 0;
    transition: height var(--transition);
  }

  .server-icon:hover .pill { height: 20px; }
  .server-icon.active .pill { height: 36px; }

  .server-icon.home-icon,
  .server-icon[data-server="calendar"] {
    background: var(--surface);
    color: var(--ink-secondary);
  }
  .server-icon.home-icon:hover,
  .server-icon.home-icon.active,
  .server-icon[data-server="calendar"]:hover,
  .server-icon[data-server="calendar"].active {
    background: var(--elevated);
    color: var(--ink);
  }

  /* Dynamic real-server icons rendered by server-rail.js: full-size
     coloured face that takes the .server-icon's background and shows
     the first letter (or a custom .server.icon image). */
  .server-icon-face {
    width: 100%; height: 100%;
    display: flex; align-items: center; justify-content: center;
    border-radius: inherit;
    color: #fff; font-weight: 700; font-size: 18px;
    overflow: hidden;
    /* WKWebView (Wails) doesn't always inherit user-select from
       the parent .server-icon — pin it here so a click-and-drag
       on the initial letter doesn't paint a text-selection box. */
    user-select: none;
    -webkit-user-select: none;
  }
  .server-owner-crown {
    position: absolute; top: -3px; right: -3px;
    width: 18px; height: 18px;
    border-radius: 50%; background: var(--surface);
    display: flex; align-items: center; justify-content: center;
    font-size: 10px;
    box-shadow: var(--shadow-xs);
    pointer-events: none;
  }

  .server-divider {
    width: 32px;
    height: 2px;
    background: var(--border-mid);
    border-radius: 1px;
    flex-shrink: 0;
  }

  .server-icon.add-server {
    background: var(--surface);
    color: var(--live);
  }
  .server-icon.add-server:hover {
    background: var(--live);
    color: white;
  }

  .server-icon .notification-badge {
    position: absolute;
    bottom: -3px;
    right: -3px;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    background: var(--coral);
    border-radius: var(--radius-pill);
    border: 3px solid var(--void);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-2xs);
    font-weight: 700;
    color: white;
    line-height: 1;
  }

  .channel-sidebar {
    /* Same --void as the server rail so the whole left column reads
       as one surface; the 1px border-right on .server-rail is the
       only thing separating the two zones now. */
    background: var(--void);
    display: flex;
    flex-direction: column;
    /* Right border drawn by .left-shell::after — single rule covers
       the full panel height (row + bottom block) without alignment
       issues between flex children. */
    overflow: hidden;
    position: relative;
    user-select: none;
    -webkit-user-select: none;
    width: var(--channel-sidebar-width, 248px);
    flex-shrink: 0;
  }
  .channel-sidebar-resize {
    position: absolute;
    top: 0; right: -3px; bottom: 0;
    width: 6px;
    cursor: ew-resize;
    z-index: 50;
    user-select: none;
  }
  .channel-sidebar-resize::after {
    content: '';
    position: absolute;
    top: 0; bottom: 0;
    left: 50%; width: 2px;
    background: transparent;
    transform: translateX(-50%);
    transition: background var(--transition);
  }
  .channel-sidebar-resize:hover::after,
  .channel-sidebar-resize.dragging::after { background: var(--accent); }
  body.resizing-sidebar { cursor: ew-resize !important; user-select: none; }
  body.resizing-sidebar * { cursor: ew-resize !important; }

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

  /* Discord-style title pill: clickable region is just the name + chevron,
     not the whole header. Invite button on the right is its own target. */
  .server-header-text {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    max-width: calc(100% - 40px);
    height: 32px;
    padding: 0 var(--space-sm);
    border: none;
    border-radius: var(--radius-s);
    background: transparent;
    color: inherit;
    cursor: pointer;
    transition: background var(--transition);
  }
  .server-header-text:hover { background: var(--surface); }

  .server-header h2 {
    font-size: var(--font-md);
    font-weight: 600;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .server-header .chevron {
    flex-shrink: 0;
    color: var(--ink-muted);
    transition: transform var(--transition);
  }

  .server-header-invite {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin-left: auto;
    border: none;
    border-radius: var(--radius-s);
    background: transparent;
    color: var(--ink-muted);
    cursor: pointer;
    transition: color var(--transition);
  }
  .server-header-invite:hover { color: var(--ink); }

  /* Discord-style admin dropdown opened from #serverHeader. Floats just
     below the header — sidebar is fixed-width, so absolute positioning
     against the sidebar edges is enough. */
  .server-menu {
    display: none;
    position: absolute;
    top: 56px;
    left: var(--space-sm);
    right: var(--space-sm);
    background: var(--elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-m);
    padding: 4px;
    z-index: 50;
    flex-direction: column;
  }
  .server-menu.visible { display: flex; }
  .server-menu-item {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 10px;
    background: transparent; border: none; cursor: pointer;
    border-radius: var(--radius-s);
    color: var(--ink);
    font-size: var(--font-md); text-align: left;
    transition: background var(--transition), color var(--transition);
  }
  .server-menu-item:hover { background: var(--surface); }
  .server-menu-icon {
    color: var(--ink-muted);
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px;
  }
  .server-menu-item:hover .server-menu-icon { color: var(--ink); }
  /* Right-aligned trailing icon — used for the "Скрывать замьюченные
     каналы" checkmark. Pushes itself to the far end of the row. */
  .server-menu-check {
    margin-left: auto;
    color: var(--accent);
    display: inline-flex; align-items: center; justify-content: center;
  }
  .server-menu-separator {
    height: 1px;
    margin: 4px 4px;
    background: var(--border);
  }
  /* Destructive item ("Покинуть сервер") — red text + red icon. */
  .server-menu-item--danger,
  .server-menu-item--danger .server-menu-icon {
    color: var(--coral);
  }
  .server-menu-item--danger:hover {
    background: rgba(245, 75, 72, 0.12);
  }

  .sidebar-nav {
    padding: var(--space-sm) var(--space-sm) var(--space-sm);
    display: flex; flex-direction: column; gap: 2px;
    flex-shrink: 0;
    border-bottom: 1px solid var(--border);
  }
  .sidebar-nav-item {
    display: flex; align-items: center; gap: var(--space-md);
    height: 34px;
    padding: 0 var(--space-sm);
    border-radius: var(--radius-m);
    color: var(--ink-secondary);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
    user-select: none;
  }
  .sidebar-nav-item:hover { background: var(--surface); color: var(--ink); }
  .sidebar-nav-item .sidebar-nav-icon {
    display: flex; align-items: center; justify-content: center;
    color: var(--ink-muted);
    flex-shrink: 0;
  }
  .sidebar-nav-item:hover .sidebar-nav-icon { color: var(--ink); }
  .sidebar-nav-item.active { background: var(--elevated); color: var(--ink); }
  .sidebar-nav-item.active:hover { background: var(--elevated); color: var(--ink); }
  .sidebar-nav-item.active .sidebar-nav-icon { color: var(--ink); }
  .sidebar-nav-label { font-size: var(--font-md); font-weight: 500; flex: 1; }
  .sidebar-nav-badge {
    background: var(--elevated); color: var(--ink);
    min-width: 18px; height: 18px;
    border-radius: 9px;
    padding: 0 7px;
    font-size: 10px; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    border: 1px solid var(--border-mid);
  }
  .sidebar-nav-mention {
    display: inline-flex; align-items: center;
    color: var(--mention-self-fg);
    padding: 0;
    margin-right: -8px;
    flex-shrink: 0;
  }
  .sidebar-nav-mention svg { color: var(--mention-self-fg); }

  /* ── Popup-open cursor scope ─────────────────────────────────────
     Когда любое контекст-меню/dropdown открыто, любой клик за пределами
     меню только закрывает его. Чтобы не вводить в заблуждение
     (рука над «кликабельными» каналами/сообщениями), переключаем
     cursor:default на весь интерфейс кроме popup'a. Toggle ставится
     через MutationObserver в ui-overlays.js. */
  body.kc-popup-open,
  body.kc-popup-open * { cursor: default !important; }

  body.kc-popup-open .context-menu,
  body.kc-popup-open .context-menu *,
  body.kc-popup-open [id$="Menu"].open,
  body.kc-popup-open [id$="Menu"].open *,
  body.kc-popup-open [id$="Menu"].visible,
  body.kc-popup-open [id$="Menu"].visible * {
    cursor: pointer !important;
    user-select: none;
    -webkit-user-select: none;
  }

  body.kc-popup-open .context-menu input,
  body.kc-popup-open .context-menu textarea,
  body.kc-popup-open [id$="Menu"].open input,
  body.kc-popup-open [id$="Menu"].visible input,
  body.kc-popup-open [id$="Menu"].open textarea,
  body.kc-popup-open [id$="Menu"].visible textarea {
    cursor: text !important;
    user-select: text;
    -webkit-user-select: text;
  }

  /* ── Slash-commands help overlay (Этап B / kcShowSlashHelp) ───── */
  .kc-slash-help-overlay {
    position: fixed; inset: 0; z-index: 200;
    background: var(--color-black-32);
    display: none;
    align-items: center; justify-content: center;
  }
  .kc-slash-help-overlay.visible { display: flex; }
  .kc-slash-help-modal {
    background: var(--raised);
    border: 1px solid var(--border-mid);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-l);
    min-width: 380px;
    max-width: 520px;
    padding: 0;
    animation: fadeIn 120ms;
  }
  .kc-slash-help-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-md) var(--space-lg);
    border-bottom: 1px solid var(--border);
    font-weight: 600; font-size: var(--font-md);
  }
  .kc-slash-help-close {
    background: transparent; border: none;
    color: var(--ink-muted); font-size: 20px; line-height: 1;
    cursor: pointer; padding: 0 4px;
  }
  .kc-slash-help-close:hover { color: var(--ink); }
  .kc-slash-help-list {
    list-style: none; margin: 0; padding: var(--space-sm) 0;
    max-height: 60vh; overflow-y: auto;
  }
  .kc-slash-help-list li {
    display: grid; grid-template-columns: 160px 1fr;
    gap: var(--space-md);
    padding: 8px var(--space-lg);
    align-items: baseline;
    font-size: var(--font-sm);
  }
  .kc-slash-help-list code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--ink);
    background: var(--glass);
    padding: 3px 8px;
    border-radius: var(--radius-s);
    font-size: var(--font-xs);
  }
  .kc-slash-help-list li span { color: var(--ink-secondary); }
