:root{
    --sky:#00AFF0;
    --sky-soft:#9BE0F9;
    --sky-pale:#EAF7FD;
    --bg:#FFFFFF;
    --sidebar:#F5FAFC;
    --border:#E3EEF3;
    --text:#1B2733;
    --text-dim:#7C8A97;
    --bubble-me:#E5F7FD;
    --bubble-them:#C7EDFC;
    --danger:#D14747;
    --dnd:#E24343;
    --offline:#9AA7B0;
    --file-pdf:#F4473F;
    --file-text:#2D93F3;
    --file-audio:#FF8A1E;
    --file-sheet:#3FA548;
    --file-archive:#A3AFB8;
  }

  /* ---------- Dark Mode (Experimental / ongoing development - see Settings > Appearance) ----------
     The variables above cover most surfaces automatically since they already reference var(),
     but a lot of surfaces here (mainly the Settings modal) predate the variable system and use
     hardcoded hex colors directly - those need explicit overrides below or they just stay light.
     Still not a 100% audit (that's what the "Experimental" tag next to the toggle is for), but
     this covers everything actually reachable from the app chrome + Settings. The auth/login
     screens are intentionally left alone - those stay branded sky-blue regardless of theme. */
  body.dark-theme{
    --sky-pale:#2b343b;
    --bg:#1f1f1f;
    --sidebar:#2b2b2b;
    --border:#313131;
    --text:#EAF2F6;
    --text-dim:#8B98A3;
    --bubble-me:#173142;
    --bubble-them:#28292b;
    --offline:#6B7680;
  }
  /* app chrome */
  body.dark-theme .titlebar{background:var(--sidebar);}
  body.dark-theme .titlebar .win-btn{color:var(--text);}
  body.dark-theme .me-card{background:var(--sidebar);border-bottom-color:var(--border);}
  body.dark-theme .me-card .status-badge{background:var(--sidebar);}
  body.dark-theme .avatar{background:#2B3A44;}
  body.dark-theme .me-card .avatar{border-color:var(--sidebar);background:#2B3A44;}
  body.dark-theme .search-box{background:var(--sidebar);}
  body.dark-theme .search-box input{color:var(--text);}
  body.dark-theme .profile{background:var(--sidebar);border-left-color:var(--border);}
  body.dark-theme .pinned{background:var(--sidebar);}
  body.dark-theme .rail-btn.add{background:var(--sky-pale);border-color:var(--offline);}
  body.dark-theme #composer-input:focus{background:none;}
  body.dark-theme .rail{background:var(--border);}
  body.dark-theme .tab-indicator{opacity:0.2;}
  body.dark-theme .home-row:hover{background:var(--sky-pale);}
  body.dark-theme .home-row{background:var(--border);}
  body.dark-theme .dm-item.active{background:var(--sky-pale);}
  body.dark-theme .dm-item{background:var(--sidebar);}
  body.dark-theme .dm-item:hover{background:#293238;}
  body.dark-theme .filter-menu,
  body.dark-theme .context-menu,
  body.dark-theme .emoji-picker,
  body.dark-theme .call-card,
  body.dark-theme .modal-card,
  body.dark-theme .hdr-btn.ghost,
  body.dark-theme .home-add-friend-row input:focus{
    background:var(--sidebar);
  }
  body.dark-theme .filter-menu div,
  body.dark-theme .context-menu div{
    color:var(--text);
  }
  body.dark-theme .ep-category-label{background:var(--sidebar);}
  body.dark-theme .ep-search{background:var(--bg);}
  body.dark-theme .ep-sticker-item{background:var(--bg);}
  body.dark-theme .ep-gif-item{background:var(--bg);}
  body.dark-theme .track{background:#3A444D;}
  body.dark-theme .settings-nav-item.active,
  body.dark-theme .home-tab:not(.active),
  body.dark-theme .friend-row:hover{
    background:var(--sky-pale);
  }
  /* settings modal - the biggest offender, almost entirely hardcoded #000/#fff before this */
  body.dark-theme .settings-card{background:var(--bg);}
  body.dark-theme .settings-sidebar{background:var(--sidebar);}
  body.dark-theme .settings-sidebar h2,
  body.dark-theme .settings-nav-item,
  body.dark-theme .settings-nav-item.active,
  body.dark-theme .settings-panel h1,
  body.dark-theme .settings-help-version,
  body.dark-theme .settings-update-row,
  body.dark-theme .settings-link-row{
    color:var(--text);
  }
  body.dark-theme .settings-nav-item:hover{background:var(--border);}
  body.dark-theme .settings-link-row{border-bottom-color:var(--border);}
  body.dark-theme .settings-close-x svg path{stroke:var(--text);}
  body.dark-theme .patch-notes-card{background:var(--bg);}
  body.dark-theme .patch-note-version,
  body.dark-theme .patch-note-content h3,
  body.dark-theme .patch-note-content strong{
    color:var(--text);
  }
  body.dark-theme .patch-note-content,
  body.dark-theme .patch-note-date{
    color:var(--text-dim);
  }
  @font-face{font-family:"Segoe UI";src:url("assets/fonts/segoe-ui-light-latin.ttf") format("truetype");font-weight:300;font-style:normal;font-display:swap;}
  @font-face{font-family:"Segoe UI";src:url("assets/fonts/segoe-ui-regular-latin.ttf") format("truetype");font-weight:400;font-style:normal;font-display:swap;}
  @font-face{font-family:"Segoe UI";src:url("assets/fonts/segoe-ui-semibold-latin.ttf") format("truetype");font-weight:600;font-style:normal;font-display:swap;}
  @font-face{font-family:"Segoe UI";src:url("assets/fonts/segoe-ui-semibold-latin.ttf") format("truetype");font-weight:700;font-style:normal;font-display:swap;}
  @font-face{font-family:"Segoe UI";src:url("assets/fonts/segoe-ui-semibold-latin.ttf") format("truetype");font-weight:800;font-style:normal;font-display:swap;}
  *{box-sizing:border-box;}
  html,body{margin:0;height:100%;width:100%;background:var(--bg);font-family:"Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,Helvetica,Arial,sans-serif;overflow:hidden;}
  button{font-family:inherit;}
  .icn{display:inline-flex;align-items:center;justify-content:center;}
  .icn svg{display:block;}
  a{color:var(--sky);}

  /* ---------- custom scrollbar: thinner than default, square thumb, sky color ---------- */
  ::-webkit-scrollbar{width:8px;height:8px;}
  ::-webkit-scrollbar-track{background:transparent;}
  ::-webkit-scrollbar-thumb{background:var(--sky-pale);border-radius:0;}
  ::-webkit-scrollbar-thumb:hover{background:var(--sky-soft);}
  ::-webkit-scrollbar-corner{background:transparent;}

  .window{
    width:100%;height:100%;
    background:var(--bg);overflow:hidden;
    display:flex;flex-direction:column;color:var(--text);
    position:relative;
  }

  /* ---------- title bar ---------- */
  .titlebar{height:44px;flex-shrink:0;display:flex;align-items:center;padding:0 0 0 14px;border-bottom:1px solid var(--border);background:#FCFEFF;position:relative;z-index:150;-webkit-app-region:drag;}
  .titlebar .no-drag{-webkit-app-region:no-drag;}
  .nav-btn{width:26px;height:26px;border-radius:50%;background:var(--sky);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:1;}
  .nav-btn:disabled{opacity:.35;cursor:default;}
  .nav-btn + .nav-btn{margin-left:8px;}
  .titlebar .brand{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);height:22px;cursor:pointer;display:flex;align-items:center;gap:8px;}
  .titlebar .brand img{height:100%;display:block;}
  .titlebar .right{margin-left:auto;display:flex;align-items:stretch;gap:0;align-self:stretch;}
  .titlebar .win-btn{background:none;border:none;cursor:pointer;color:#1B2733;display:flex;align-items:center;justify-content:center;width:46px;border-radius:0;-webkit-app-region:no-drag;}
  .titlebar .win-btn:hover{background:rgba(0,0,0,.06);}
  .titlebar .win-btn.close:hover{background:#E81123;color:#fff;}
  .titlebar .win-btn.close:active{background:#F1707A;}
  .titlebar .win-btn.update-btn{width:26px;height:26px;border-radius:50%;background:var(--sky);color:#fff;align-self:center;justify-content:center;margin:0px 6px;display:flex;}
  /* The web version has no window of its own to minimize, maximize or close */
  .web-app .titlebar .win-btn{display:none;}
  /* Browsers only have their own notifications, and closing the tab is all the window does */
  .web-app .desktop-only{display:none !important;}
  .titlebar .win-btn.update-btn:hover{background:#0E9BD6;}
  .titlebar .win-btn.update-btn.hidden{display:none;}
  .emergency-notice-btn{width:20px;height:20px;border-radius:50%;background:#E81123;border:none;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;}
  .emergency-notice-btn:hover{background:#F1707A;}
  .emergency-notice-btn.hidden{display:none;}

  /* ---------- body layout ---------- */
  .body{flex:1;display:grid;grid-template-columns:78px 300px 1fr 300px;min-height:0;}
  /* While a voice call is expanded, the right sidebar hosts the real text chat
     instead of the profile/media panel (see #chat-main-col docking below) - a
     little wider than the normal 300px profile column since it's now doing the
     job of the whole chat, not just showing supplementary info. */
  .body.call-sidebar-active{grid-template-columns:78px 300px 1fr 340px;}
  /* Manual "hide the chat sidebar" toggle (call-ctrl-sidebar-toggle) - collapses the
     4th column instead of the call area giving up its own width, so the call grid
     doesn't have to be remeasured/relaid-out, just gets more room. */
  .body.call-sidebar-active.call-sidebar-hidden{grid-template-columns:78px 300px 1fr 0px;}
  .body.call-sidebar-active.call-sidebar-hidden .profile.call-chat-dock{border-left:none;}

  /* ---------- rail ---------- */
  .rail{background:var(--sky-pale);border-right:1px solid var(--border);display:flex;flex-direction:column;align-items:center;padding:16px 0;gap:12px;overflow-y:auto;}
  .rail-btn{width:44px;height:44px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:none;padding:0;position:relative;flex-shrink:0;color:#fff;font-weight:700;font-size:15px;overflow:hidden;}
  .rail-btn img{width:100%;height:100%;object-fit:cover;border-radius:inherit;}
  .rail-btn.square{border-radius:100%;margin-bottom:3px;}
  .rail-btn.square:hover, .rail-btn.square.active{border-radius:15px;}
  .rail-btn.active::after{content:"";position:absolute;left:-11px;top:50%;transform:translateY(-50%);width:4px;height:22px;border-radius:15px;background:#0E7FB5;}
  .rail-btn-wrap{position:relative;flex-shrink:0;margin-bottom:3px;}
  .rail-dm-unread{display:flex;flex-direction:column;align-items:center;gap:12px;}
  .rail-dm-unread:empty{display:none;}
  .rail-dm-unread .rail-btn-wrap{margin-bottom:0;}
  .rail-unread-dot{display:none;position:absolute;right:-2px;bottom:-2px;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:var(--sky);border:2px solid var(--sky-pale);pointer-events:none;}
  .rail-btn-wrap.has-unread .rail-unread-dot{display:block;}
  .rail-divider{width:28px;height:1px;background:var(--border);margin:2px 0;flex-shrink:0;}
  .rail .spacer{flex:1;}
  .rail-btn.add{background:#fff;border:2px dashed #B9D9E7;color:var(--sky);}
  .rail-btn.add:hover{background:var(--sky);color:#fff;border-style:solid;}
  .rail-tooltip{position:fixed;background:#1B2733;color:#fff;font-size:12px;padding:5px 9px;border-radius:6px;pointer-events:none;opacity:0;z-index:50;white-space:nowrap;}
  .rail-tooltip.show{opacity:1;}

  /* ---------- dm column ---------- */
  .dmcol{border-right:1px solid var(--border);background:var(--sidebar);display:flex;flex-direction:column;min-height:0;}
  .avatar{width:42px;height:42px;border-radius:50%;position:relative;flex-shrink:0;background:#CFE3EC;display:flex;align-items:center;justify-content:center;font-weight:700;color:#fff;font-size:15px;}
  .avatar img{width:100%;height:100%;object-fit:cover;display:block;border-radius:50%;}
  /* Custom channel icons (Comfort/Legacy Chats Mode) sit inside the round avatar
     slot but, unlike photo avatars, must respect a safe zone: only the inner 50%
     of the circle, not the full slot. */
  .avatar img.channel-icon-img{width:50%;height:50%;object-fit:contain;border-radius:0;}
  .status-badge{position:absolute;bottom:-1px;right:-1px;width:16px;height:16px;border-radius:50%;background:var(--sidebar);display:flex;align-items:center;justify-content:center;cursor:pointer;padding:2px;}
  .status-badge img{width:100%;height:100%;}

  .me-card{position:relative;background:#F0FAFC;border-bottom:1px solid #E7F1F3;padding:0 16px 12px;overflow:hidden;}
  .me-card-banner{height:44px;margin:0 -16px 0;overflow:hidden;position:relative;}
  .me-card-banner img{position:absolute;top:0;left:0;width:100%;height:auto;display:block;}
  .me-card-row{display:flex;align-items:center;gap:12px;padding-left:55px;min-height:64px;margin-top:-29px;cursor:pointer;}
  .me-card .avatar{position:absolute;left:12.5px;top:20px;width:51.5px;height:51.5px;font-size:20px;border:3px solid #F0FAFC;background:#CFE3EC;z-index:1;}
  .me-card .status-badge{background:#F0FAFC;}
  .me-info{padding-bottom:4px;min-width:0;}
  .me-info .name{font-weight:700;font-size:16px;line-height:1.25;color:var(--text);}
  .me-info .status{font-size:12.5px;color:var(--text-dim);}
  .balance{margin-left:auto;font-weight:600;font-size:13px;color:var(--sky);white-space:nowrap;}
  .me-card .round-btn.ghost{margin-left:auto;background:none;width:28px;height:28px;flex-shrink:0;}
  .me-card .round-btn.ghost:hover{background:var(--sky-pale);}

  .icon-row{display:flex;align-items:center;gap:8px;justify-content:flex-end;padding:1px 0 0;}
  /* Active-call indicator on the opposite side of the row from the mic/deafen/
     settings buttons - margin-right:auto claims the row's leftover space without
     needing to touch icon-row's own flex-end alignment for the buttons. */
  .me-card-call-info{margin-right:auto;display:flex;align-items:center;gap:3px;min-width:0;background:none;border:none;cursor:pointer;padding:0;font-size:11px;font-weight:700;color:var(--sky);}
  .me-card-call-info.hidden{display:none;}
  .me-card-call-info:hover{text-decoration:underline;}
  .me-card-call-info span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  #me-card-call-name{max-width:70px;}
  .me-card-call-sep{opacity:.6;flex-shrink:0;}
  /* .round-btn.X beats plain .round-btn's own background at equal specificity
     regardless of source order - the danger red kept losing to .round-btn's
     var(--sky) without this. Slightly smaller than the other icon-row buttons
     (28px, matching .me-card .round-btn.ghost's own size) since a 5th 32px button
     was crowding the row in the narrow 300px sidebar. */
  .round-btn.me-card-hangup-btn{width:28px;height:28px;background:var(--danger);}
  .round-btn.me-card-hangup-btn:hover{background:#B93838;}
  .me-card-hangup-btn.hidden{display:none;}
  .me-card-hangup-btn img{width:13px;height:13px;transform:rotate(135deg);}
  .round-btn{width:32px;height:32px;border-radius:50%;border:none;cursor:pointer;background:var(--sky);display:flex;align-items:center;justify-content:center;}
  .round-btn.toggled-off{background:var(--danger);}
  .round-btn.toggled-off:hover{background:var(#E81123)}
  .round-btn:hover{background:#0E9BD6;}

  /* Search/Home: flat nav rows (icon + label, no visible input-box chrome on Search)
     matching the reference sidebar - was a small bordered search pill at
     barely-bigger-than-tab-label size. First pass overcorrected (19px read as oversized
     next to the rest of the sidebar's type scale) - dialed back, and the divider the
     reference has between Search and Home (see .search-home-divider below) was missing
     outright, not just under-styled. */
  .search-box{margin:10px 12px 2px;display:flex;align-items:center;gap:10px;background:none;border:none;border-radius:10px;padding:7px 8px;}
  .search-box svg{flex-shrink:0;}
  .search-box input{border:none;outline:none;flex:1;font-size:15px;font-weight:500;color:var(--text);background:none;}
  .search-box input::placeholder{color:var(--text);}

  .search-home-divider{height:1px;background:var(--border);margin:4px 16px 6px;flex-shrink:0;}

  .home-row{display:flex;align-items:center;gap:10px;margin:0 12px 4px;padding:7px 8px;border-radius:10px;cursor:pointer;font-size:15px;font-weight:500;color:var(--text);}
  .home-row svg{flex-shrink:0;}
  .home-row:hover, .home-row.active{background:#E5F3FB;color:var(--sky);}

  .nav-tabs{display:flex;align-items:center;gap:24px;padding:2px 16px 10px;font-size:12px;font-weight:700;letter-spacing:.03em;color:var(--text-dim);position:relative;margin-left:8px;}
  .nav-tabs .tab{cursor:pointer;padding-bottom:2px;}
  .nav-tabs .tab.active{color:var(--sky);}
  .tab-indicator{width:100%;line-height:0;margin-bottom:4px;}
  .tab-indicator img{width:100%;height:auto;display:block;}
  .nav-tabs .filter{margin-left:auto;display:flex;align-items:center;gap:5px;font-weight:600;color:var(--text-dim);cursor:pointer;position:relative;}
  .filter-menu{position:absolute;top:24px;right:16px;background:#fff;border:2px solid var(--sky);border-radius:0px;box-shadow:3px 3px 5px rgba(15,50,75,.25);padding:3px;display:none;z-index:20;min-width:110px;}
  .filter-menu.show{display:block;}
  .filter-menu div{padding:7px 10px;font-size:13px;font-weight:500;color:var(--text);border-radius:0px;cursor:pointer;}
  .filter-menu div:hover{background:var(--sky-pale);}
  .filter-menu div.active{color:var(--sky);}

  .section-label{padding:10px 16px 4px;font-size:11px;font-weight:700;color:var(--text-dim);letter-spacing:.05em;display:flex;justify-content:space-between;align-items:center;}
  .section-label button{background:none;border:none;color:var(--sky);cursor:pointer;font-size:11px;font-weight:700;}
  .dm-scroll{flex:1;overflow-y:auto;padding-bottom:10px;}
  .dm-unread-strip{display:flex;gap:12px;padding:14px 16px 6px;overflow-x:auto;}
  .dm-unread-icon{cursor:pointer;flex-shrink:0;}
  .dm-unread-icon .avatar{width:48px;height:48px;font-size:16px;}
  .dm-unread-icon .ping{position:absolute;top:-2px;right:-2px;width:13px;height:13px;box-sizing:border-box;border-radius:50%;background:var(--sky);border:2px solid var(--sidebar);}
  .dm-item{display:flex;align-items:center;gap:10px;padding:9px 16px;cursor:pointer;border-left:3px solid transparent;}
  .dm-item:hover{background:#EEF6FA;}
  .dm-item.active{background:#E2F1FA;border-left-color:var(--sky);}
  .dm-item .avatar{width:38px;height:38px;font-size:13px;}
  .dm-item .status-badge{width:14px;height:14px;padding:1.5px;}
  .dm-text{min-width:0;}
  .dm-name{font-size:13.5px;font-weight:600;}
  .dm-preview{font-size:12px;color:var(--text-dim);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .empty-state{padding:40px 24px;text-align:center;color:var(--text-dim);font-size:13.5px;line-height:1.6;}
  .empty-state.small{padding:18px 16px;}

  /* ---------- chat pane ---------- */
  .chat{display:flex;flex-direction:column;min-width:0;min-height:0;position:relative;}
  .chat-header{display:flex;align-items:center;gap:12px;padding:14px 22px;border-bottom:1px solid var(--border);}
  /* Hidden while a call is expanded - the call gets its own dark header
     (.call-view-header below) instead of the normal white text-chat one, which
     looked mismatched sitting directly above the dark call area. */
  .chat-header.call-mode{display:none;}
  .chat-header .avatar{width:38px;height:38px;font-size:14px;flex-shrink:0;}
  .chat-header .header-info{flex:1;min-width:0;}
  .chat-header .dm-name{font-size:15.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .chat-header .dm-preview{font-size:12.5px;max-width:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .header-actions{flex-shrink:0;margin-left:auto;display:flex;gap:8px;position:relative;}
  .hdr-btn{width:34px;height:34px;border-radius:50%;background:var(--sky);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;}
  .hdr-btn:hover{background:#0E9BD6;}
  .hdr-btn.ghost{background:#fff;border:1px solid var(--border);}
  .hdr-btn.ghost svg path{fill:var(--sky);}

  .context-menu{position:absolute;top:40px;right:0;background:#fff;border:2px solid var(--sky);border-radius:0px;box-shadow:3px 3px 5px rgba(15,50,75,.25);padding:3px;display:none;z-index:30;min-width:190px;}
  .context-menu.show{display:block;}
  .context-menu div{padding:8px 12px;font-size:13.5px;font-weight:500;border-radius:0px;cursor:pointer;color:var(--text);}
  .context-menu div:hover{background:var(--sky-pale);}
  .context-menu div.danger{color:var(--danger);}
  .context-menu div.danger:hover{background:var(--danger);color:#FFF;}
  .context-menu hr{border:none;border-top:1px solid var(--border);margin:4px 2px;}

  .pinned{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 22px;font-size:13px;font-weight:600;color:var(--sky);border-bottom:1px solid var(--border);background:#FBFEFF;transition:opacity .2s, max-height .2s;overflow:hidden;}
  .pinned.hidden{max-height:0;padding-top:0;padding-bottom:0;opacity:0;border-bottom:none;}
  .pinned .pin-text{display:flex;align-items:center;gap:8px;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .pinned button{background:none;border:none;cursor:pointer;display:flex;flex-shrink:0;}

  /* overflow-x:hidden guards against an unwanted horizontal scrollbar - most likely
     to actually bite when this same markup (fixed-width attachment/media items sized
     for the normal wide chat column) gets docked into the much narrower call-sidebar
     chat (see .profile.call-chat-dock), but there's no good reason for a chat pane to
     ever need horizontal scroll regardless of context. */
  .messages-wrap{position:relative;flex:1;min-height:0;display:flex;flex-direction:column;}
  .messages{flex:1;overflow-y:auto;overflow-x:hidden;padding:20px 22px;display:flex;flex-direction:column;gap:10px;}
  /* pinned to .messages-wrap so it doesn't scroll with the list; toggled in app.js */
  .scroll-bottom-btn{
    position:absolute;right:20px;bottom:20px;width:34px;height:34px;border-radius:50%;
    background:var(--sky);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;
    box-shadow:0 2px 6px rgba(15,50,75,.25);opacity:0;pointer-events:none;
    transform:translateY(6px);transition:opacity .15s,transform .15s;
  }
  .scroll-bottom-btn.show{opacity:1;pointer-events:auto;transform:translateY(0);}
  .scroll-bottom-btn:hover{background:#0E9BD6;}
  .msg-row{display:flex;flex-direction:column;max-width:62%;}
  .msg-row.mine{align-self:flex-end;align-items:flex-end;}
  .msg-row.theirs{align-self:flex-start;align-items:flex-start;}

  /* ---------- system messages (join/leave/pin/name-change) ---------- */
  .msg-row.system-row{align-self:center;max-width:100%;width:100%;margin:2px 0;}
  .system-msg{display:flex;align-items:center;justify-content:center;gap:7px;padding:4px 10px;font-size:12px;color:var(--text-dim);text-align:center;flex-wrap:wrap;}
  .system-msg-icon{display:flex;flex-shrink:0;opacity:.85;}
  .system-msg-icon-img{width:13px;height:13px;display:block;}
  .system-msg-icon.join{color:#3FA548;}
  .system-msg-icon.leave{color:var(--text-dim);}
  .system-msg-time{color:var(--text-dim);opacity:.7;font-size:10.5px;}
  .msg-time{font-size:10.5px;color:var(--text-dim);margin-bottom:3px;}
  .msg-author{font-size:11.5px;color:var(--text-dim);font-weight:700;margin-bottom:2px;}
  /* indented 46px (avatar 38px + gap 8px) to align with the bubble, not the avatar */
  .msg-header{display:flex;align-items:baseline;gap:6px;margin:0 0 3px 46px;}
  .msg-header .msg-time,.msg-header .msg-author{margin-bottom:0;}
  .bubble{position:relative;padding:10px 14px;border-radius:5px;font-size:14px;line-height:1.4;word-break:break-word;width:fit-content;max-width:100%;}
  .edited-tag{font-size:10.5px;color:var(--text-dim);margin-left:6px;font-weight:500;}
  .msg-link{color:var(--sky);text-decoration:underline;word-break:break-all;}
  .msg-link:hover{text-decoration:none;}
  .msg-mention{color:var(--sky);background:rgba(30,167,225,.14);border-radius:3px;padding:0 3px;font-weight:600;cursor:default;}
  .msg-mention-channel{cursor:pointer;}
  .msg-mention-channel:hover{text-decoration:underline;}
  .content-emoji{width:20px;height:20px;vertical-align:-5px;object-fit:contain;}
  /* ---------- emoji-only messages: up to 3 emoji, no bubble, shown bigger (see other platforms) ---------- */
  .emoji-only-msg{display:flex;align-items:flex-end;gap:4px;padding:2px 0;line-height:1;}
  .content-emoji.big-emoji{width:40px;height:40px;vertical-align:middle;}
  .emoji-only-msg .edited-tag{margin-left:2px;}

  /* ---------- markdown (messages + bios, see renderMessageContent) ---------- */
  .bubble code,.bio code,.md-codeblock code{background:rgba(127,140,151,.18);border-radius:4px;padding:1px 5px;font-family:Consolas,"Courier New",monospace;font-size:.92em;}
  .md-codeblock{background:rgba(127,140,151,.12);border-radius:5px;padding:8px 10px;margin:4px 0;overflow-x:auto;}
  .md-codeblock code{background:none;padding:0;white-space:pre;}
  .md-quote{margin:4px 0;padding:2px 0 2px 10px;border-left:3px solid var(--sky-soft);color:var(--text-dim);}
  .md-list{margin:4px 0;padding-left:20px;}
  .md-list li{margin:2px 0;}
  .md-h{font-weight:800;line-height:1.3;margin:2px 0;}
  .md-h1{font-size:1.3em;}
  .md-h2{font-size:1.15em;}
  .md-h3{font-size:1.05em;}
  .spoiler{background:var(--text-dim);color:transparent;border-radius:3px;padding:0 2px;cursor:pointer;transition:background .1s;}
  .spoiler[data-revealed="true"]{background:var(--sky-pale);color:inherit;cursor:default;}

  /* ---------- reply quote (shown above a message's own content when it's a reply) ---------- */
  .reply-quote{display:flex;align-items:center;gap:5px;max-width:100%;padding:3px 10px;margin-bottom:2px;font-size:12px;color:var(--text-dim);cursor:pointer;border-radius:5px;}
  .reply-quote:hover{background:var(--sky-pale);color:var(--text);}
  .reply-quote-icon{display:flex;flex-shrink:0;opacity:.7;transform:scaleX(-1);}
  .reply-quote-author{font-weight:700;color:var(--sky);flex-shrink:0;}
  .reply-quote-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .reply-quote-missing{cursor:default;font-style:italic;}
  .reply-quote-missing:hover{background:none;color:var(--text-dim);}
  .msg-row.flash-highlight .msg-bubble-stack{animation:replyJumpFlash 1.2s ease;border-radius:5px;}
  @keyframes replyJumpFlash{
    0%{background:var(--sky-pale);}
    100%{background:transparent;}
  }

  /* ---------- composer reply banner ---------- */
  .reply-banner{display:flex;align-items:center;gap:8px;padding:7px 14px;margin:0 16px;background:var(--sky-pale);border-radius:5px 5px 0 0;font-size:12.5px;color:var(--text);}
  .reply-banner-icon{display:flex;flex-shrink:0;color:var(--sky);transform:scaleX(-1);}
  .reply-banner-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .reply-banner-text strong{color:var(--sky);}
  .reply-banner-cancel{border:none;background:none;color:var(--text-dim);cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:50%;padding:3px;flex-shrink:0;}
  .reply-banner-cancel:hover{background:#0000001a;color:var(--text);}
  body.dark-theme .reply-banner-cancel:hover{background:#ffffff1a;}

  /* ---------- composer nonce picker (see app.js) ---------- */
  .nonce-bar{position:relative;padding:2px 18px 0;}
  .nonce-btn{border:none;background:none;cursor:pointer;font-size:11.5px;color:var(--text-dim);padding:2px 0;display:flex;align-items:center;gap:3px;}
  .nonce-btn span#nonce-btn-label{color:var(--sky);font-weight:700;}
  .nonce-btn:hover{color:var(--text);}
  .nonce-btn-chevron{font-size:9px;position:relative;top:1px;}
  /* opens upward - composer sits near the bottom, downward would clip off-screen */
  .nonce-menu{top:auto;bottom:100%;left:18px;right:auto;margin-bottom:4px;}

  /* ---------- right-click "Sent via X" popup (see app.js) ---------- */
  .ctx-popup{position:fixed;background:#fff;border:2px solid var(--sky);box-shadow:3px 3px 5px rgba(15,50,75,.25);padding:7px 10px;font-size:12.5px;font-weight:600;color:var(--text);display:none;z-index:50;}
  .ctx-popup.show{display:block;}
  body.dark-theme .ctx-popup{background:var(--bg);}
  /* Per-message context menu - same visual language as .context-menu/.filter-menu
     (2px sky border, square corners, same shadow) but with tighter row padding
     ("less thick" buttons) and a real sharp/pointed tail (clip-path, not the
     rounded picker_tail.svg used for the emoji picker - deliberately different
     shape per request). Tail always sits top-left since the menu only ever opens
     below-and-right of its trigger (the "..." hover button or the RMB point). */
  .msg-context-menu{position:fixed;background:#fff;border:2px solid var(--sky);border-radius:0;box-shadow:3px 3px 5px rgba(15,50,75,.25);padding:3px;display:none;z-index:60;min-width:180px;}
  .msg-context-menu.show{display:block;}
  .msg-context-menu .mcm-info{padding:6px 10px 7px;font-size:11.5px;font-weight:600;color:var(--text-dim);}
  .msg-context-menu .mcm-item{display:flex;align-items:center;gap:8px;padding:5px 10px;font-size:12.5px;font-weight:500;border-radius:0;cursor:pointer;color:var(--text);}
  .msg-context-menu .mcm-item svg{flex-shrink:0;width:13px;height:13px;}
  .msg-context-menu .mcm-item:hover{background:var(--sky-pale);}
  .msg-context-menu .mcm-item.danger{color:var(--danger);}
  .msg-context-menu .mcm-item.danger:hover{background:var(--danger);color:#fff;}
  .msg-context-menu hr{border:none;border-top:1px solid var(--border);margin:4px 2px;}
  body.dark-theme .msg-context-menu{background:var(--sidebar);}
  .msg-context-menu::before,.msg-context-menu::after{content:"";position:absolute;left:14px;width:16px;height:9px;}
  .msg-context-menu::before{top:-9px;background:var(--sky);clip-path:polygon(0% 100%,50% 0%,100% 100%);}
  .msg-context-menu::after{top:-7px;background:#fff;clip-path:polygon(11% 100%,50% 22%,89% 100%);}
  body.dark-theme .msg-context-menu::after{background:var(--sidebar);}

  .user-volume-popup{width:180px;}
  .user-volume-popup-name{margin-bottom:6px;}
  .user-volume-popup-row{display:flex;align-items:center;gap:8px;}
  .user-volume-popup-row .settings-slider{flex:1;}
  #user-volume-popup-value{font-weight:400;min-width:34px;text-align:right;}
  .msg-row{position:relative;}
  .msg-actions{position:absolute;top:-14px;right:0;display:none;gap:4px;background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:3px;box-shadow:0 2px 8px rgba(15,50,75,0);}
  .msg-row:hover .msg-actions{display:flex;}
  .msg-action-btn{width:22px;height:22px;border:none;background:none;border-radius:3px;color:var(--text-dim);display:flex;align-items:center;justify-content:center;cursor:pointer;}
  .msg-action-btn:hover{background:var(--sky);color:var(--sky-pale);}
  .msg-action-btn[data-action="delete"]:hover{background:var(--danger);color:#FBE3E3;}
  .msg-action-btn.gif-fav-btn svg{width:14px;height:14px;}
  .msg-action-btn.gif-fav-btn.active{color:#FFC94A;}
  .msg-action-btn.gif-fav-btn.active:hover{background:none;color:#FFC94A;}
  .edit-box{width:100%;min-width:320px;max-width:480px;}
  .edit-textarea{width:100%;resize:none;border:1px solid var(--sky);border-radius:5px;padding:9px 12px;font-size:14px;font-family:inherit;line-height:1.4;outline:none;color:var(--text);background:var(--sidebar);min-height:38px;}
  .edit-hint{font-size:11px;color:var(--text-dim);margin-top:4px;}
  .edit-hint span{color:var(--sky);cursor:pointer;font-weight:600;}
  .msg-row.mine .bubble{background:var(--bubble-me);}
  .msg-row.theirs .bubble{background:var(--bubble-them);}

  /* ---------- speech-bubble tail (see reference_messagebubbles_rules.png) ---------- */
  /* Flat-color tail: used for plain text bubbles, file attachments, and any attachment
     bubble that doesn't have its own image to pull the tail from (multi-image grids, video).
     Same 10x14 nub silhouette (straight base, gently rounded tip) as the image-masked tail
     below, just filled with the bubble's solid color via clip-path instead of a border-triangle
     hack -- that's what let the two tail styles drift out of shape from each other. */
  .msg-row.theirs .bubble::before,
  .msg-row.theirs .attach-combo:not(.tail-image)::before{
    content:"";position:absolute;left:-8px;top:14px;width:10px;height:14px;
    background:var(--bubble-them);
    clip-path:path("M10,1 L10,13 L5.5,9.5 Q2.5,7 5.5,4.5 Z");
  }
  .msg-row.mine .bubble::after,
  .msg-row.mine .attach-combo:not(.tail-image)::after{
    content:"";position:absolute;right:-8px;top:14px;width:10px;height:14px;
    background:var(--bubble-me);
    clip-path:path("M0,1 L0,13 L4.5,9.5 Q7.5,7 4.5,4.5 Z");
  }

  /* overflowing caption bubble: needs ancestor-qualified selector to out-specificity the tail rule above */
  .msg-row.theirs .bubble.no-tail::before,
  .msg-row.mine .bubble.no-tail::after{content:none;}
  /* Bug: this used to be margin-top:-6px with squared corners, deliberately pulling this
     bubble flush against the attachment above it - but a caption long enough to overflow
     into its own bubble should visibly split from the image, not look glued to it. No
     override needed now: .msg-bubble-stack's own gap:6px and .bubble's own border-radius
     already give the right look once this bubble isn't artificially yanked upward. */

  /* ---------- image-masked tail (single image attachment) ----------
     Rather than sampling a small separate sliver of the photo (which never lines up with
     how the main image itself got cropped), the actual <img> is rendered 10px wider than
     its visible square and shifted so the extra strip sits on the tail side. A clip-path
     then carves the visible shape (rounded square + pointed nub) out of that single,
     continuous image element -- the nub is genuinely made of the same pixels, not a copy.
     See reference_messagebubbles_rules.png "Attachment Bubble". */
  .attach-combo.tail-image .media-row{overflow:visible;}
  .attach-combo.tail-image .media-row.grid-1 .tail-host{position:relative;width:230px;height:220px;flex:none;}
  .attach-combo.tail-image .tail-host img{width:100%;height:100%;object-fit:cover;display:block;cursor:zoom-in;}
  .msg-row.theirs .attach-combo.tail-image .tail-host{
    margin-left:-10px;
    clip-path:path("M15,0 L225,0 A5,5 0 0 1 230,5 L230,215 A5,5 0 0 1 225,220 L15,220 A5,5 0 0 1 10,215 L10,27 L5.5,23.5 Q2.5,21 5.5,18.5 L10,15 L10,5 A5,5 0 0 1 15,0 Z");
  }
  .msg-row.mine .attach-combo.tail-image .tail-host{
    clip-path:path("M5,0 L215,0 A5,5 0 0 1 220,5 L220,15 L224.5,18.5 Q227.5,21 224.5,23.5 L220,27 L220,215 A5,5 0 0 1 215,220 L5,220 A5,5 0 0 1 0,215 L0,5 A5,5 0 0 1 5,0 Z");
  }

  .theirs-wrap{display:flex;gap:8px;align-items:flex-start;}
  .theirs-wrap .avatar{width:38px;height:38px;font-size:14px;border-radius:50%;flex-shrink:0;overflow:hidden;}
  .theirs-wrap .avatar-spacer{width:38px;flex-shrink:0;}
  /* avatar aligns with flex-start (top of the stack), which is the reply-quote line
     for a reply - offset down to the real bubble/tail below it instead (30px = quote's
     own measured height + gap) */
  .theirs-wrap:has(.reply-quote:first-child) .avatar{margin-top:30px;}
  .msg-bubble-stack{display:flex;flex-direction:column;gap:6px;min-width:0;max-width:340px;}
  .msg-row.mine .msg-bubble-stack{align-items:flex-end;}
  .msg-row.theirs .msg-bubble-stack{align-items:flex-start;}

  /* ---------- message chunks: consecutive messages from the same person within a few
     minutes connect together (see continuesGroup in app.js) instead of repeating the
     avatar/name/timestamp and tail on every single message. ---------- */
  .msg-row.grouped-cont{margin-top:-6px;}
  .msg-row.grouped-notail .bubble::before,
  .msg-row.grouped-notail .bubble::after,
  .msg-row.grouped-notail .attach-combo:not(.tail-image)::before,
  .msg-row.grouped-notail .attach-combo:not(.tail-image)::after{content:none;}

  /* ---------- attachments: images / video, grouped, with optional caption ---------- */
  .attach-combo{position:relative;border-radius:5px;background:var(--bubble-them);display:flex;flex-direction:column;align-items:flex-start;width:fit-content;max-width:100%;}
  .msg-row.mine .attach-combo{background:var(--bubble-me);}
  /* No caption: nothing but the image itself, no colored pill behind it (matters for
     transparent PNGs, which would otherwise show the bubble color through them) - tail
     goes with it since there's no colored body left to anchor a colored tail stub to. */
  .attach-combo.no-caption{background:transparent;}
  .msg-row.mine .attach-combo.no-caption{background:transparent;}
  .msg-row.theirs .attach-combo.no-caption:not(.tail-image)::before,
  .msg-row.mine .attach-combo.no-caption:not(.tail-image)::after{content:none;}
  .attach-combo .media-row{display:grid;gap:2px;border-radius:5px 5px 0 0;overflow:hidden;}
  /* Full rounding only when this combo has no caption riding underneath it. Using an
     explicit class here (rather than :only-child) keeps this correct now that a tailed
     image's .tail-host can add a sibling wrapper next to .media-row. */
  .attach-combo.no-caption .media-row{border-radius:5px;}
  .attach-combo .media-row.grid-1{grid-template-columns:220px;}
  .attach-combo .media-row.grid-1 .media-item{width:220px;height:220px;}
  .attach-combo .media-row.grid-2{grid-template-columns:repeat(2,140px);}
  .attach-combo .media-row.grid-2 .media-item{width:140px;height:140px;aspect-ratio:1/1;}
  /* Exactly-3 attachments: single row of 3, same treatment as the 2-attachment row. */
  .attach-combo .media-row.grid-3row{grid-template-columns:repeat(3,108px);}
  .attach-combo .media-row.grid-3row .media-item{width:108px;height:108px;aspect-ratio:1/1;}
  .attach-combo .media-row.grid-3{grid-template-columns:repeat(3,90px);}
  .attach-combo .media-row.grid-3 .media-item{width:90px;height:90px;aspect-ratio:1/1;}
  .attach-combo .media-item{position:relative;min-width:0;background:#0000000d;display:flex;}
  /* Bug: the faint placeholder tint above is invisible once a normal opaque photo
     covers it - but for a no-caption combo (see above) it sits directly behind the
     actual <img>, so any transparent area of a transparent PNG showed this near-black
     tint through it instead of true transparency. Only ever mattered for this case. */
  .attach-combo.no-caption .media-item{background:transparent;}
  .attach-combo .media-row.grid-1 .media-item.kind-image{aspect-ratio:1/1;}
  .attach-combo .media-row.grid-1 .media-item.kind-video{aspect-ratio:16/9;height:auto;}
  .attach-combo .media-item img,.attach-combo .media-item video{display:block;width:100%;height:100%;object-fit:cover;cursor:pointer;}
  .attach-combo .media-caption{max-width:340px;padding:8px 12px;font-size:14px;line-height:1.4;word-break:break-word;}
  .video-play-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;}
  .video-play-overlay svg{width:44px;height:44px;filter:drop-shadow(0 1px 4px rgba(0,0,0,.5));}
  .media-item.playing .video-play-overlay{display:none;}

  /* ---------- stickers: big image, no bubble, no background (see reference_messagebubbles_rules.png) ---------- */
  .sticker-frame{width:152px;height:152px;display:flex;align-items:center;justify-content:center;box-sizing:border-box;}
  .sticker-frame img{width:140px;height:140px;object-fit:contain;display:block;}
  .sticker-fallback{display:none;flex-direction:column;align-items:center;gap:8px;color:#6FA9C2;text-align:center;padding:0 8px;}
  .sticker-fallback span{font-size:12px;font-weight:600;word-break:break-word;}
  .sticker-frame.broken img{display:none;}
  .sticker-frame.broken .sticker-fallback{display:flex;}

  /* ---------- rich message embeds (embedHtml in app.js - msg.embeds, Discord's
     standard server-generated embed object) - never rendered at all before. Left
     accent bar color is set per-embed via inline style (embed.color), var(--sky)
     here is just the CSS fallback for one with no color set. ---------- */
  .msg-embed{position:relative;max-width:340px;border-left:4px solid var(--sky);border-radius:5px;background:var(--sidebar);padding:10px 12px;margin-top:2px;}
  .embed-body{display:flex;gap:12px;align-items:flex-start;}
  .embed-main{flex:1;min-width:0;}
  .embed-author{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--text);margin-bottom:4px;}
  .embed-author-icon{width:18px;height:18px;border-radius:50%;object-fit:cover;flex-shrink:0;}
  .embed-author a{color:inherit;text-decoration:none;}
  .embed-author a:hover{text-decoration:underline;}
  .embed-title{font-size:14px;font-weight:700;color:var(--text);margin-bottom:4px;word-break:break-word;}
  .embed-title a{color:var(--sky);text-decoration:none;}
  .embed-title a:hover{text-decoration:underline;}
  .embed-desc{font-size:13px;line-height:1.4;color:var(--text);word-break:break-word;}
  .embed-fields{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;}
  .embed-field{flex:1 1 100%;min-width:0;}
  .embed-field.inline{flex:1 1 calc(33% - 8px);}
  .embed-field-name{font-size:12px;font-weight:700;color:var(--text);margin-bottom:2px;}
  .embed-field-value{font-size:12.5px;line-height:1.4;color:var(--text-dim);word-break:break-word;}
  .embed-image{display:block;max-width:100%;border-radius:4px;margin-top:8px;cursor:pointer;}
  .embed-thumb{width:70px;height:70px;object-fit:cover;border-radius:4px;flex-shrink:0;}
  .embed-footer{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--text-dim);margin-top:8px;}
  .embed-footer-icon{width:16px;height:16px;border-radius:50%;object-fit:cover;}

  /* File (non-media) attachment: no flat pill/tail like other bubbles -- the tail is
     baked into the colored frame's own SVG path instead (see Assets/file-attachment-frame.svg),
     same "pull the tail from the attachment itself" approach as .tail-image single images. */
  .attach-bubble{position:relative;display:flex;align-items:center;gap:12px;}
  .msg-row.mine .attach-bubble{flex-direction:row-reverse;}
  .file-frame{position:relative;width:78px;height:74px;flex-shrink:0;display:flex;align-items:center;justify-content:center;}
  .file-frame svg{position:absolute;inset:0;width:100%;height:100%;color:inherit;}
  /* frame's tail is baked in on its right edge -- mirror it for theirs (icon sits on the
     left, tail should point left toward the avatar) instead of drawing a second shape */
  .msg-row.theirs .file-frame svg{transform:scaleX(-1);}
  .file-frame .file-ext{position:relative;color:#fff;font-weight:600;font-size:12px;letter-spacing:1px;text-transform:uppercase;pointer-events:none;padding:0 6px;text-align:center;}
  .file-frame.kind-pdf{color:var(--file-pdf);}
  .file-frame.kind-text{color:var(--file-text);}
  .file-frame.kind-audio{color:var(--file-audio);}
  .file-frame.kind-sheet{color:var(--file-sheet);}
  .file-frame.kind-archive{color:var(--file-archive);}
  .attach-meta{display:flex;flex-direction:column;gap:2px;}
  .msg-row.mine .attach-meta{align-items:flex-end;}
  .msg-row.theirs .attach-meta{align-items:flex-start;}
  .attach-meta .fname{font-size:14px;font-weight:600;color:var(--text);}
  .attach-meta .fsize{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-dim);}
  .attach-meta .fsize .dl-link{color:var(--sky);text-decoration:none;font-weight:600;}
  .attach-meta .fsize .dl-link:hover{text-decoration:underline;}
  .attach-meta .fsize .dl-dot{color:var(--text-dim);}
  .system-note{align-self:center;font-size:12px;color:var(--text-dim);padding:6px 12px;background:var(--sidebar);border-radius:10px;}

  .typing-indicator{display:none;align-items:center;gap:8px;padding:0 22px 8px;color:var(--text-dim);font-size:12.5px;}
  .typing-indicator.show{display:flex;}
  .typing-indicator .icn{width:15px;height:15px;background:var(--sky);border-radius:4px;padding:3px;transition:transform .12s;}
  .typing-indicator .icn.tap{transform:scale(.8);}

  /* ---------- pending attachment tray (staged before send) ---------- */
  .attachment-tray{display:flex;gap:10px;flex-wrap:wrap;padding:0 18px;max-height:0;overflow:hidden;opacity:0;transition:max-height .15s ease,opacity .15s ease,padding .15s ease;border-top:1px solid transparent;}
  .attachment-tray.show{max-height:180px;opacity:1;padding:14px 18px 0;border-top:1px solid var(--border);}
  .attachment-chip{position:relative;width:96px;height:96px;border-radius:0;overflow:hidden;background:var(--sidebar);border:1px solid var(--border);flex-shrink:0;}
  .attachment-chip img{width:100%;height:100%;object-fit:cover;display:block;}
  .attachment-chip .file-chip{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:8px;text-align:center;}
  .attachment-chip .file-chip .file-icn{color:var(--sky);}
  .attachment-chip .file-chip .file-name{font-size:10.5px;font-weight:600;color:var(--text-dim);width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .attachment-chip-actions{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;justify-content:center;gap:6px;padding:6px;background:linear-gradient(180deg,rgba(10,20,30,.68),rgba(10,20,30,0));opacity:0;}
  .attachment-chip:hover .attachment-chip-actions{opacity:1;}
  .attachment-chip-actions button{width:22px;height:22px;border-radius:50%;border:none;background:rgba(255,255,255,.92);color:var(--text);display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;}
  .attachment-chip-actions button:hover{background:#fff;}
  .attachment-chip-actions button.danger{color:var(--danger);}
  .attachment-chip-actions button svg{width:12px;height:12px;}
  .attachment-chip-caption-badge{position:absolute;bottom:4px;left:4px;right:4px;background:rgba(10,20,30,.68);color:#fff;font-size:9.5px;font-weight:600;padding:2px 5px;border-radius:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .attachment-caption-editor{position:absolute;inset:0;background:rgba(10,20,30,.82);display:none;flex-direction:column;justify-content:flex-end;padding:6px;gap:4px;}
  .attachment-caption-editor.show{display:flex;}
  .attachment-caption-editor input{width:100%;border:none;border-radius:0;padding:5px 6px;font-size:10.5px;font-family:inherit;}
  .attachment-caption-editor button{align-self:flex-end;border:none;background:var(--sky);color:#fff;font-size:10px;font-weight:700;border-radius:999px;padding:3px 8px;cursor:pointer;}
  .composer-wrap-dragover{outline:2px dashed var(--sky);outline-offset:-6px;background:var(--sky-pale);}

  /* ---------- unread ping badge (distinct from bold-name affordance) ---------- */
  .dm-item{position:relative;}
  .unread-badge{display:none;color:#ff990b;font-size:15px;font-weight:800;flex-shrink:0;margin-left:auto;}
  .dm-item.has-unread .unread-badge{display:inline-block;}
  /* DesignRules.md: unread chats use #ff990b for the name as well as the count badge. */
  .dm-item.has-unread .dm-name{color:#ff990b;}

  /* ---------- guild channel categories ---------- */
  .channel-category{padding:12px 16px 4px;font-size:11px;font-weight:700;color:var(--text-dim);letter-spacing:.05em;text-transform:uppercase;display:flex;align-items:center;gap:6px;cursor:default;}
  .channel-category.collapsible{cursor:pointer;user-select:none;}
  .channel-category.collapsible:hover{color:var(--text);}
  .channel-category-chevron{display:inline-flex;flex-shrink:0;transition:transform .12s ease;font-size:9px;}
  .channel-category.collapsed .channel-category-chevron{transform:rotate(-90deg);}
  .channel-group-items.collapsed{display:none;}
  .channel-category-unread-dot{display:none;width:6px;height:6px;border-radius:50%;background:var(--sky);flex-shrink:0;margin-left:2px;}
  .channel-category.collapsed.has-unread .channel-category-unread-dot{display:block;}
  .dm-item.channel-item{padding:7px 16px 7px 22px;gap:8px;}
  .dm-item.channel-item .channel-hash{color:var(--text-dim);font-weight:700;font-size:14px;width:16px;text-align:center;flex-shrink:0;}
  .dm-item.channel-item .channel-hash img{width:16px;height:16px;border-radius:4px;object-fit:cover;display:block;}
  /* Bug: voice channels are just as clickable as text ones (clicking joins the
     call), but cursor:default + a flat opacity + a disabled hover made every one of
     them look permanently greyed-out/disabled, including the one you're actually
     connected to. Voice items now look and behave like any other channel row. */
  .dm-item.voice-item{flex-wrap:wrap;}
  .voice-item-participants{width:100%;display:flex;align-items:center;gap:6px;padding:2px 0 2px 24px;}
  .voice-item-participants .avatar{width:20px;height:20px;font-size:9px;margin-left:-8px;flex-shrink:0;}
  .voice-item-participants .avatar:first-child{margin-left:0;}
  .voice-item-hangup{width:24px;height:24px;border-radius:50%;border:none;cursor:pointer;background:var(--danger);display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:8px;}
  .voice-item-hangup:hover{background:#B93838;}
  .voice-item-hangup img{width:11px;height:11px;transform:rotate(135deg);}

  .composer{display:flex;align-items:center;gap:10px;padding:14px 18px;border-top:1px solid var(--border);position:relative;}
  .square-btn{width:34px;height:34px;border-radius:50%;background:var(--sky);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  .square-btn:hover{background:#0E9BD6;}
  /* contenteditable, not a <textarea> - lets emoji render as actual inline images while
     typing instead of raw "<:name:id>"/":shortcode:" text (see getComposerText() /
     tryAutoConvertTypedEmoji() in app.js). white-space:pre-wrap replaces the
     textarea's own built-in wrapping/newline behavior. */
  #composer-input{flex:1;min-width:0;border:0;border-radius:5px;padding:11px 16px;font-size:14px;line-height:1.4;outline:none;background:none;color:var(--text);font-family:inherit;overflow-y:auto;max-height:140px;white-space:pre-wrap;word-wrap:break-word;cursor:text;}
  #composer-input:focus{border-color:0;background:#fff;}
  #composer-input[contenteditable="false"]{opacity:.6;cursor:default;}
  #composer-input:empty::before{content:attr(data-placeholder);color:var(--text-dim);pointer-events:none;}
  .composer-inline-emoji{width:20px;height:20px;vertical-align:-5px;object-fit:contain;user-select:none;}

  /* ---------- composer markdown live preview (symbols stay visible, see composerHighlightHtml) ---------- */
  .cmd-bold{font-weight:700;}
  .cmd-italic{font-style:italic;}
  .cmd-underline{text-decoration:underline;}
  .cmd-strike{text-decoration:line-through;}
  .cmd-code{font-family:Consolas,"Courier New",monospace;background:rgba(127,140,151,.18);border-radius:3px;}
  .cmd-spoiler{background:var(--sky-pale);border-radius:3px;}
  .cmd-quote{color:var(--text-dim);border-left:2px solid var(--sky-soft);padding-left:6px;}
  .cmd-header{font-weight:700;}
  .cmd-header.cmd-h1{font-size:1.3em;}
  .cmd-header.cmd-h2{font-size:1.15em;}
  .cmd-header.cmd-h3{font-size:1.05em;}
  .cmd-codeblock{font-family:Consolas,"Courier New",monospace;background:rgba(127,140,151,.18);}
  .send-btn{width:32px;height:32px;border-radius:50%;background:none;;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  .send-btn:disabled{opacity:.4;cursor:default;}
  .send-btn svg path{fill:var(--sky);}
  .emoji-btn{background:none;border:none;cursor:pointer;color:var(--text-dim);display:flex;padding:4px;flex-shrink:0;}
  .emoji-btn svg path{fill:var(--sky);}
  #voice-msg-btn.recording{color:var(--danger);}
  #voice-msg-btn.recording svg path{fill:var(--danger);animation:voiceMsgPulse 1s ease-in-out infinite;}
  @keyframes voiceMsgPulse{
    0%,100%{opacity:1;}
    50%{opacity:.4;}
  }

  /* -- composer :/@/# suggestion dropdown (see initComposerAutocomplete in app.js) --
     position:fixed, anchored to the caret's own pixel position (measured via a hidden
     mirror element - textareas have no native API for this), same reasoning as the
     emoji picker's own position:fixed switch: escapes ancestor overflow/reparenting
     regardless of where the composer currently lives. */
  .composer-autocomplete{position:fixed;width:260px;max-width:80vw;max-height:220px;overflow-y:auto;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 8px rgba(15,40,60,.14);z-index:30;display:none;}
  .composer-autocomplete.show{display:block;}
  body.dark-theme .composer-autocomplete{background:var(--sidebar);}
  .ac-item{display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:pointer;}
  .ac-item:hover,.ac-item.selected{background:var(--sky-pale);}
  .ac-item-icon{width:20px;height:20px;flex-shrink:0;border-radius:50%;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--sidebar);}
  .ac-item-icon img{width:100%;height:100%;object-fit:cover;}
  .ac-item-icon.square{border-radius:3px;}
  .ac-item-name{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .ac-item-sub{font-size:11.5px;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-left:auto;padding-left:6px;}
  .ac-empty{padding:10px;font-size:12.5px;color:var(--text-dim);text-align:center;}

  /* position:fixed (not absolute) and anchored via JS (positionEmojiPicker() in app.js)
     from #emoji-btn's own bounding rect - it used to be position:absolute against
     .composer, which broke the moment a call is expanded: renderVoiceCallBanner()
     reparents #chat-main-col (the picker's ancestor) into #profile-panel, which sets
     overflow:hidden on itself for unrelated reasons (containing the docked call chat),
     silently clipping/misplacing the picker. Fixed positioning escapes that ancestor
     entirely regardless of where the composer currently lives. Width is set inline by
     the resize handle (see .ep-resize-handle) / restored from localStorage, not fixed
     here - only bounds are set as CSS. */
  .emoji-picker{position:fixed;width:336px;min-width:300px;max-width:640px;height:420px;max-height:70vh;background:#fff;border:1px solid #BFD2D9;border-radius:8px;box-shadow:0 4px 8px rgba(15,40,60,.14);display:none;flex-direction:column;overflow:visible;z-index:30;}
  .emoji-picker.show{display:flex;}
  /* dark-theme background already comes from the shared ".emoji-picker{background:var(--sidebar)}"
     grouped rule near the top of this file - just the border needs its own override here. */
  body.dark-theme .emoji-picker{border-color:var(--border);}
  /* picker_tail.svg pointing down at the emoji button, used as a mask (not a plain
     background image) twice - a slightly bigger copy in the picker's own border
     color sits behind the real fill, peeking out a couple px to read as an outline.
     Sits a fixed distance from the right edge, which never moves during a resize
     (only the left edge does - see .ep-resize-handle), so it stays lined up under
     the button regardless of width. Bug: the tail's point used to land at -15px/
     -13px below the picker box, well past positionEmojiPicker()'s real 8px gap to
     the button - looked disconnected/misaligned instead of touching it. The tip
     sits at each shape's own bottom edge (see picker_tail.svg's path - the notch
     between its two humps reaches the full 14px height), so `bottom` directly
     controls where the point lands: -8px puts the fill's tip exactly at the button. */
  .emoji-picker::before,.emoji-picker::after{
    content:"";position:absolute;
    -webkit-mask:url(assets/picker_tail.svg) no-repeat center/contain;
    mask:url(assets/picker_tail.svg) no-repeat center/contain;
  }
  .emoji-picker::before{bottom:-10px;right:18px;width:49px;height:18px;background:#BFD2D9;}
  .emoji-picker::after{bottom:-8px;right:20px;width:45px;height:14px;background:#fff;}
  body.dark-theme .emoji-picker::before{background:var(--border);}
  body.dark-theme .emoji-picker::after{background:var(--sidebar);}

  /* Drag-to-resize handle on the left edge, widening the picker leftward (its right
     edge/tail stay pinned to the emoji button - see positionEmojiPicker() in app.js). */
  .ep-resize-handle{position:absolute;left:-3px;top:10px;bottom:10px;width:6px;border-radius:3px;cursor:ew-resize;z-index:1;}
  .ep-resize-handle:hover,.ep-resize-handle.dragging{background:var(--sky-pale);}

  /* -- top-level Emoji / Stickers / GIFs tabs -- */
  .ep-tabs{display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid var(--border);flex-shrink:0;}
  .ep-tab{position:relative;width:30px;height:30px;border:none;border-radius:50%;background:none;color:var(--text-dim);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;}
  .ep-tab:hover{background:var(--sky-pale);}
  .ep-tab.active{background:var(--sky);color:#fff;}

  .ep-panel-emoji,.ep-panel-stickers,.ep-panel-gifs{display:flex;flex-direction:column;flex:1;min-height:0;}
  .ep-panel-emoji.hidden,.ep-panel-stickers.hidden,.ep-panel-gifs.hidden{display:none;}

  /* -- emoji tab -- */
  .ep-search{display:flex;align-items:center;gap:8px;margin:10px 12px 0;padding:7px 10px;background:var(--sidebar);border:1px solid var(--border);border-radius:0;color:var(--text-dim);flex-shrink:0;}
  .ep-search input{flex:1;border:none;background:none;outline:none;font-size:13px;font-family:inherit;color:var(--text);}
  .ep-preview{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);flex-shrink:0;min-height:20px;}
  .ep-preview-glyph{width:34px;height:34px;flex-shrink:0;}
  .ep-preview-glyph img{width:100%;height:100%;display:block;}
  .ep-preview-text{display:flex;align-items:baseline;gap:8px;flex:1;min-width:0;}
  .ep-preview-name{font-size:13.5px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .ep-preview-code{font-size:12px;color:var(--text-dim);white-space:nowrap;}
  .ep-scroll{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:0 10px;}
  .ep-category{margin-top:12px;}
  .ep-category-label{font-size:12px;font-weight:700;color:var(--text-dim);padding:0 4px 6px;position:sticky;top:0;background:#fff;}
  /* Fixed-size columns (not 1fr) so resizing the picker changes how many emoji fit
     per row, not how big each one is - auto-fill just adds/removes columns to match
     the available width instead of stretching existing ones. */
  .ep-emoji-grid{display:grid;grid-template-columns:repeat(auto-fill,43px);gap:2px;}
  .ep-emoji-btn{width:100%;aspect-ratio:1/1;border:none;background:none;border-radius:0;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:4px;box-sizing:border-box;outline:2px solid transparent;outline-offset:-2px;}
  .ep-emoji-btn:hover,.ep-emoji-btn.focused{outline-color:var(--sky);}
  .ep-emoji-btn img{width:100%;height:100%;object-fit:contain;pointer-events:none;}
  .ep-no-results{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-dim);font-size:13px;padding-bottom:20px;}
  .ep-no-results.hidden{display:none;}
  .ep-catnav{display:flex;align-items:center;gap:2px;padding:6px 8px;border-top:1px solid var(--border);flex-shrink:0;overflow-x:auto;}
  .ep-catnav-btn{width:26px;height:26px;flex-shrink:0;border:none;border-radius:50%;background:none;color:var(--text-dim);display:flex;align-items:center;justify-content:center;font-size:14px;cursor:pointer;}
  .ep-catnav-btn:hover{background:var(--sky-pale);}
  .ep-catnav-btn.active{background:var(--sky);color:#fff;}

  /* -- stickers tab: one labeled section per guild you're in (see refreshAllGuildStickers
     in app.js) instead of a single flat grid, same as the emoji tab's per-guild sections -
     stickers work identically in DMs now, no more guild-only gating. -- */
  .ep-sticker-empty{display:none;flex-direction:column;align-items:center;justify-content:center;gap:6px;height:100%;padding:30px 24px;text-align:center;color:var(--text-dim);}
  .ep-sticker-empty.show{display:flex;}
  .ep-sticker-empty p{margin:4px 0 0;font-size:13.5px;font-weight:700;color:var(--text);}
  .ep-sticker-empty span{font-size:12px;line-height:1.4;}
  .ep-sticker-sections .ep-category:first-child{margin-top:0;}
  /* Same fixed-column-size reasoning as .ep-emoji-grid above. */
  .ep-sticker-grid{display:grid;grid-template-columns:repeat(auto-fill,98px);gap:8px;padding:0 2px 12px;}
  .ep-sticker-item{border:none;background:var(--sidebar);border-radius:0;padding:8px;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:4px;}
  .ep-sticker-item:hover{background:var(--sky-pale);}
  .ep-sticker-item img{width:100%;aspect-ratio:1/1;object-fit:contain;pointer-events:none;}
  .ep-sticker-item span{font-size:10.5px;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}

  /* -- gifs tab -- */
  .ep-gif-subtabs{display:flex;gap:6px;padding:10px 12px 0;flex-shrink:0;}
  .ep-gif-subtab{border:none;background:none;border-radius:0;padding:5px 10px;font-size:12.5px;font-weight:700;color:var(--text-dim);cursor:pointer;}
  .ep-gif-subtab:hover{background:var(--sky-pale);}
  .ep-gif-subtab.active{background:var(--sky);color:#fff;}
  .ep-gif-providers{margin-left:auto;display:flex;align-items:center;gap:2px;}
  .ep-gif-provider{border:none;background:none;border-radius:0;padding:4px 6px;font-size:11px;font-weight:600;color:var(--text-dim);cursor:pointer;}
  .ep-gif-provider:hover{color:var(--sky);}
  .ep-gif-provider.active{color:var(--sky);text-decoration:underline;}
  .ep-search#ep-gif-search-row.hidden{display:none;}
  /* flex-wrap (not a fixed grid) since each tile keeps its own real aspect ratio
     (width set inline per-tile in app.js's gifTileHtml, from the GIF's actual
     width/height) instead of being cropped to a uniform square - a fixed column
     count can't wrap variable-width tiles the way flex-wrap does. Height is fixed
     (resizing the picker just fits more/fewer tiles per row, same as the emoji/
     sticker grids), and gifTileWidth() caps each tile's width well under even the
     narrowest allowed picker width so no single tile can force horizontal overflow. */
  .ep-gif-grid{display:flex;flex-wrap:wrap;gap:6px;padding:10px;}
  .ep-gif-item{position:relative;height:90px;flex-shrink:0;border-radius:0;overflow:hidden;}
  .ep-gif-send{position:absolute;inset:0;width:100%;height:100%;border:none;background:var(--sidebar);padding:0;cursor:pointer;}
  .ep-gif-item:hover .ep-gif-send{outline:2px solid var(--sky);outline-offset:-2px;}
  .ep-gif-send img{width:100%;height:100%;object-fit:cover;pointer-events:none;display:block;}
  .ep-gif-fav-btn{position:absolute;top:4px;right:4px;width:20px;height:20px;border:none;border-radius:50%;background:rgba(15,25,35,.55);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;}
  .ep-gif-fav-btn svg{width:12px;height:12px;}
  .ep-gif-fav-btn.active{color:#FFC94A;}
  .ep-gif-fav-btn.active svg path{fill:#FFC94A;}
  .ep-gif-empty{display:none;flex-direction:column;align-items:center;justify-content:center;gap:6px;height:100%;padding:30px 24px;text-align:center;color:var(--text-dim);}
  .ep-gif-empty.show{display:flex;}
  .ep-gif-empty p{margin:4px 0 0;font-size:13.5px;font-weight:700;color:var(--text);}
  .ep-gif-empty span{font-size:12px;line-height:1.4;}

  .chat-placeholder{flex:1;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:10px;color:var(--text-dim);}

  /* ---------- home / friends page ---------- */
  .home-panel{flex:1;display:flex;flex-direction:column;min-height:0;}
  .home-panel-header{padding:20px 22px 0;flex-shrink:0;}
  .home-panel-title{font-size:19px;font-weight:800;margin-bottom:14px;}
  .home-tabs{display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--border);padding-bottom:14px;}
  .home-tab{border:none;background:var(--sky-pale);color:var(--text-dim);font-size:12.5px;font-weight:700;padding:7px 14px;border-radius:16px;cursor:pointer;}
  .home-tab:hover{background:#DDF0FA;}
  .home-tab.active{background:var(--sky);color:#fff;}
  .home-tab.add-friend-tab{margin-left:auto;background:none;color:var(--sky);border:1px solid var(--sky);}
  .home-tab.add-friend-tab.active{background:var(--sky);color:#fff;}
  .home-add-friend{padding:18px 22px;border-bottom:1px solid var(--border);flex-shrink:0;}
  .home-add-friend-label{font-size:11px;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:.03em;margin-bottom:8px;}
  .home-add-friend-row{display:flex;gap:10px;}
  .home-add-friend-row input{flex:1;border:1px solid var(--border);border-radius:10px;padding:10px 14px;font-size:13.5px;outline:none;background:var(--sidebar);color:var(--text);}
  .home-add-friend-row input:focus{border-color:var(--sky);background:#fff;}
  .home-add-friend-row .primary-btn{width:auto;padding:0 18px;white-space:nowrap;}
  .home-add-friend-msg{font-size:12.5px;margin-top:10px;display:none;line-height:1.5;}
  .home-add-friend-msg.show{display:block;}
  .home-add-friend-msg.error{color:var(--danger);}
  .home-add-friend-msg.success{color:#2E9E5B;}
  .home-list{flex:1;min-height:0;overflow-y:auto;padding:6px 22px 22px;}
  .home-section-label{font-size:11px;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:.03em;margin:16px 0 6px;}
  .friend-row{display:flex;align-items:center;gap:12px;padding:9px 6px;border-bottom:1px solid var(--border);}
  .friend-row:hover{background:var(--sky-pale);border-radius:0px;}
  .friend-row .avatar{width:36px;height:36px;font-size:13px;}
  .friend-info{flex:1;min-width:0;}
  .friend-name{font-weight:600;font-size:13.5px;}
  .friend-sub{font-size:11.5px;color:var(--text-dim);}
  .friend-actions{display:flex;gap:6px;flex-shrink:0;}
  .friend-actions button{border:none;border-radius:50%;width:30px;height:30px;display:flex;align-items:center;justify-content:center;cursor:pointer;background:var(--bg);color:var(--sky);box-shadow:0 0 0 1px var(--border);}
  .friend-actions button:hover{background:var(--sky-pale);}
  .friend-actions button.danger{color:var(--danger);}
  .friend-actions button.danger:hover{background:#FBE3E3;}
  .chat-placeholder img{width:64px;opacity:.5;}

  /* ---------- profile panel ---------- */
  .profile{border-left:1px solid #E7F1F3;overflow-y:auto;background:#F0FAFC;}
  /* While a call is expanded, this panel hosts the real text chat (#chat-main-col,
     reparented in here by renderVoiceCallBanner()) instead of profile/media info -
     switch to a flex column so #chat-main-col's own flex:1/min-height:0 sizing
     actually works, and hide the normal profile content underneath it. */
  .profile.call-chat-dock{display:flex;flex-direction:column;overflow:hidden;background:var(--bg);}
  .profile.call-chat-dock .profile-body{display:none;}
  /* Bug: "body.dark-theme .profile{background:var(--sidebar)}" above (line 44) is
     MORE specific than ".profile.call-chat-dock" (an extra body+class vs. two plain
     classes), so it silently won in dark theme regardless of source order, leaving
     the docked call chat on the wrong (sidebar-grey) background instead of matching
     the real chat's. Matching that rule's specificity here is what actually beats it. */
  body.dark-theme .profile.call-chat-dock{background:var(--bg);}
  .sky-banner{width:100%;height:96px;position:relative;background:linear-gradient(180deg,#EAF7FD,#D9F0FB);}
  .sky-banner svg{position:absolute;bottom:0;left:0;width:100%;height:auto;}
  .profile-body{padding:26px 20px;position:relative;}
  /* Bug: ".profile .avatar" matched ANY .avatar under #profile-panel, including
     message-row avatars from the docked call chat (#chat-main-col lives as a sibling
     of .profile-body inside .profile, not inside it, but a plain descendant selector
     doesn't care) - blowing them up to the 88px profile-picture size. Scoped to
     .profile-body specifically, which only ever wraps the actual profile info. */
  .profile-body .avatar{width:88px;height:88px;margin:0 auto 14px;border:4px solid var(--sidebar);font-size:26px;}
  .profile .status-badge{width:22px;height:22px;bottom:6px;right:6px;padding:3px;}
  .profile h2{text-align:center;font-size:17px;margin:0 0 2px;font-weight:700;}
  .profile .sub{text-align:center;font-size:13px;color:var(--text-dim);margin-bottom:18px;}
  .profile-actions{display:flex;justify-content:center;gap:10px;margin-bottom:6px;}
  .profile h4{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin:18px 0 8px;font-weight:700;}
  .bio{font-size:13.5px;line-height:1.55;margin:0;white-space:pre-wrap;}
  .profile .number-row{display:flex;align-items:center;gap:9px;font-size:13.5px;color:var(--text-dim);cursor:pointer;}
  .profile .number-row:hover{color:var(--sky);}
  .media-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:2px;margin-top:6px;}
  .media-grid-item{position:relative;width:100%;aspect-ratio:1;overflow:hidden;cursor:pointer;}
  .media-grid-item img,.media-grid-item video{width:100%;height:100%;object-fit:cover;display:block;}
  .media-grid-item.kind-video .video-play-overlay svg{width:26px;height:26px;}
  .media-show-more-btn{grid-column:1/-1;margin-top:6px;padding:9px;border:none;border-radius:0;background:var(--sky-pale);color:var(--sky);font-weight:700;font-size:12.5px;cursor:pointer;font-family:inherit;}
  .media-show-more-btn:hover{background:#DDF0FA;}
  .media-show-more-btn:disabled{opacity:.6;cursor:default;}

  .toast{position:absolute;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);background:#1B2733;color:#fff;padding:10px 18px;border-radius:10px;font-size:13.5px;opacity:0;transition:opacity .2s, transform .2s;pointer-events:none;z-index:60;white-space:nowrap;max-width:80%;overflow:hidden;text-overflow:ellipsis;}
  .toast.show{opacity:1;transform:translateX(-50%) translateY(0);}

  .call-overlay{position:absolute;inset:0;background:rgba(20,40,60,.55);display:none;align-items:center;justify-content:center;z-index:70;}
  .call-overlay.show{display:flex;}
  .call-card{background:#fff;border-radius:18px;padding:30px 40px;text-align:center;min-width:260px;}
  .call-card .avatar{width:76px;height:76px;margin:0 auto 14px;font-size:24px;}
  .call-card h3{margin:0 0 4px;font-size:16px;}
  .call-card p{margin:0 0 18px;color:var(--text-dim);font-size:13px;}
  /* Voice channel preview (not joined yet) - reuses .call-view-inline/-header/-grid/
     -controls wholesale (see index.html) so it looks like the same call, just with an
     inert button row and a green Join in place of hangup. Only its own rules live
     here. See renderVoiceChannelPreview() in app.js. */
  .voice-preview-empty{margin:auto;color:rgba(255,255,255,.6);font-size:13.5px;}
  .call-ctrl-btn:disabled{opacity:.35;cursor:default;}
  .call-ctrl-btn.join{background:#4CAF7D;}
  .call-ctrl-btn.join:hover{background:#3D9268;}
  .call-card-actions{display:flex;gap:16px;justify-content:center;}
  .call-card-btn{width:48px;height:48px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;}
  .call-card-btn.decline{background:var(--danger);}
  .call-card-btn.decline:hover{background:#B93838;}
  .call-card-btn.accept{background:#4CAF7D;}
  .call-card-btn.accept:hover{background:#3D9268;}

  /* ---- collapsed voice call banner - the only thing shown until clicked ---- */
  .voice-call-banner{display:flex;align-items:center;gap:8px;width:100%;padding:9px 22px;border:none;border-bottom:1px solid var(--border);background:var(--sky-pale);color:var(--sky);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;text-align:left;}
  .voice-call-banner:hover{background:#DDF0FA;}
  .voice-call-banner.hidden{display:none;}
  .voice-call-banner-dot{width:8px;height:8px;border-radius:50%;background:#4CAF7D;flex-shrink:0;}
  .voice-call-banner-sep{opacity:.6;}

  /* ---- expanded call panel: fills the whole main chat column with the call
     background/tiles/controls, matching Assets/reference_voicecall_guild.png -
     the real text chat (#chat-main-col) is docked into the right sidebar instead
     of splitting this column (see .profile.call-chat-dock below and
     renderVoiceCallBanner()'s reparenting), so it stays flexible here and just
     takes whichever of the two is actually present. ---- */
  .chat-body-row{flex:1;min-height:0;display:flex;overflow:hidden;}
  .chat-body-row.hidden{display:none;}
  .chat-main-col{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0;}
  /* Call's own header, replacing the normal white text-chat header while expanded -
     dark, matching the call background instead of looking like a mismatched leftover.
     The minimize/collapse button lives in this header's own top-right corner. */
  .call-view-header{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 22px;flex-shrink:0;font-weight:700;font-size:15px;border-bottom:1px solid rgba(255,255,255,.12);}
  .call-view-header-left{display:flex;align-items:center;gap:10px;min-width:0;}
  .call-view-header-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .call-view-header-right{display:flex;align-items:center;gap:10px;flex-shrink:0;}
  .call-view-header-timer{font-size:13px;font-weight:600;color:rgba(255,255,255,.75);}
  /* Small utility buttons pinned to a corner (collapse in the header's own top-right,
     settings/sidebar-toggle bottom-right over the call area) rather than mixed into
     the main camera/mic/headphone/screenshare/hangup row. */
  .call-corner-btn{width:32px;height:32px;border-radius:50%;border:none;cursor:pointer;background:rgba(255,255,255,.08);display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  .call-corner-btn:hover{background:rgba(255,255,255,.16);}
  .call-view-corner-actions{position:absolute;bottom:16px;right:16px;display:flex;flex-direction:column;gap:10px;}
  .call-view-inline{position:relative;flex:1;min-width:0;display:flex;flex-direction:column;color:#fff;
    background-image:url(assets/call_background_resizeable.svg);background-size:cover;background-position:center;background-repeat:no-repeat;}
  .call-view-inline.hidden{display:none;}
  .call-view-grid{flex:1;min-height:0;display:flex;flex-wrap:wrap;align-content:center;justify-content:center;gap:20px;padding:24px;overflow-y:auto;}
  .call-tile-wrap{display:flex;flex-direction:column;align-items:center;width:160px;}
  .call-tile{position:relative;width:160px;aspect-ratio:1;overflow:hidden;background:#1B2E3A;display:flex;align-items:center;justify-content:center;}
  .call-tile img{width:100%;height:100%;object-fit:cover;display:block;}
  .call-tile-fallback{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:700;color:#fff;}
  /* Gradient fading to black behind the name, so it stays legible over any avatar */
  .call-tile-name{position:absolute;left:0;bottom:0;right:0;padding:20px 10px 7px;font-size:12.5px;font-weight:500;color:#fff;background:linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,.85) 100%);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .call-tile-mute-badge{position:absolute;top:6px;right:6px;width:22px;height:22px;border-radius:50%;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;}
  .call-tile-mute-badge svg{width:12px;height:12px;}
  /* Speaking indicator: a sky-colored bar under the whole tile (not a ring around it) */
  .call-tile-speak-bar{width:100%;height:4px;margin-top:5px;background:transparent;}
  .call-tile-wrap.speaking .call-tile-speak-bar{background:var(--sky);}
  /* Just the buttons floating over the call background near the bottom - no bar/pill
     container behind them - matches reference_voicecall_guild.png. */
  .call-view-controls{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:12px;margin:0 auto 20px;}
  .call-ctrl-btn{width:40px;height:40px;border-radius:50%;border:none;cursor:pointer;background:var(--sky);display:flex;align-items:center;justify-content:center;position:relative;flex-shrink:0;}
  .call-ctrl-btn img{display:block;}
  .call-ctrl-btn:hover{background:var(--file-text);}
  .call-ctrl-btn.hangup{background:var(--danger);}
  .call-ctrl-btn.hangup:hover{background:#B93838;}
  .call-ctrl-btn.active{background:var(--sky);}
  .call-ctrl-btn .call-ctrl-slash{display:none;}
  .call-ctrl-btn.active .call-ctrl-slash{display:block;position:absolute;width:2px;height:24px;background:#fff;left:50%;top:50%;transform:translate(-50%,-50%) rotate(45deg);border-radius:2px;}

  /* voice channel sidebar entries */
  .voice-item.voice-item-active .dm-name{color:var(--sky);font-weight:700;}

  /* ---- Audio & Video settings widgets ---- */
  .settings-select{width:100%;padding:9px 10px;border-radius:0;border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-family:inherit;}
  .av-meter{width:100%;height:6px;border-radius:0;background:var(--border);overflow:hidden;}
  .av-meter-fill{height:100%;width:0%;background:var(--sky);transition:width .08s linear;}
  .settings-slider{-webkit-appearance:none;width:100%;height:4px;border-radius:0;background:var(--border);outline:none;}
  .settings-slider::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--sky);cursor:pointer;}
  .settings-inline-btn.ghost{background:var(--sky-pale);color:var(--sky);}
  .settings-inline-btn.ghost:hover{background:#DDF0FA;}

  /* ---- screenshare source picker ---- */
  .screenshare-picker-card{width:560px;max-width:92vw;}
  .screenshare-source-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;max-height:340px;overflow-y:auto;margin:16px 0;}
  .screenshare-source-item{cursor:pointer;border:2px solid var(--border);border-radius:0;overflow:hidden;text-align:center;}
  .screenshare-source-item:hover{border-color:var(--sky);}
  .screenshare-source-item img{width:100%;height:100px;object-fit:cover;background:#DDE7EC;display:block;}
  .screenshare-source-name{font-size:12px;padding:6px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .modal-actions{display:flex;justify-content:flex-end;}

  body.dark-theme .voice-call-banner{background:#16323f;}
  body.dark-theme .voice-call-banner:hover{background:#1b3d4c;}
  body.dark-theme .settings-select{background:var(--sidebar);color:var(--text);border-color:var(--border);}


  /* ---------- overlays: connect / login ---------- */
  .overlay{position:absolute;inset:0;background:var(--sky);display:flex;align-items:center;justify-content:center;z-index:100;overflow:hidden;}
  .overlay.hidden{display:none;}

  .auth-overlay{
    align-items:stretch;justify-content:flex-start;
    background:
      radial-gradient(circle at 0vw -1.85vh, #55CAF5 0, #55CAF5 7.8vw, #8EDCF8 7.8vw, #8EDCF8 10.7vw, #B2E7FA 10.7vw, #B2E7FA 13.5vw, transparent 13.5vw),
      radial-gradient(circle at 192.7vw 50%, #FFFFFF 0, #FFFFFF 127.9vw, #B2E7FA 127.9vw, #B2E7FA 130.2vw, #8EDCF8 130.2vw, #8EDCF8 132.6vw, #55CAF5 132.6vw, #55CAF5 134.9vw, #00AFF0 134.9vw);
  }
  .auth-content{position:relative;z-index:1;display:flex;flex-direction:column;justify-content:flex-start;width:320px;max-width:80vw;margin:auto 0 auto 13%;padding:24px 0;}
  .auth-logo-row{margin-bottom:clamp(32px, 9vh, 100px);}
  .auth-logo-row img{height:102px;display:block;}
  .auth-status-line{display:flex;align-items:center;color:#fff;font-size:15px;font-weight:600;}

  .auth-tabs-plain{display:flex;gap:22px;margin-bottom:16px;}
  .auth-tabs-plain button{border:none;background:none;padding:0 0 8px;font-size:15px;font-weight:700;color:rgba(255,255,255,.65);cursor:pointer;border-bottom:2px solid transparent;}
  .auth-tabs-plain button.active{color:#fff;border-bottom-color:#fff;}
  .auth-tabs-plain #tab-discord{cursor:default;}

  .auth-field-group{margin-bottom:20px;}
  .auth-field-with-dropdown{display:flex;}
  .auth-input-dropdown{width:38px;flex-shrink:0;border:2px solid #ffffff;border-radius:0;background:#00aff0;display:flex;align-items:center;justify-content:center;cursor:pointer;}
  .auth-input-dropdown:hover{background:#0A6C99;}
  .auth-input{width:100%;border:none;border-radius:0;padding:9.5px 15px;font-size:14.5px;outline:none;background:#C7EDFC;color:#0B2733;}
  .auth-input::placeholder{color:#4E7A8C;}
  .auth-input:focus{background:#fff;}
  .auth-hint{font-size:11.5px;color:rgba(255,255,255,.85);margin:-4px 0 14px;line-height:1.5;}
  .auth-hint code{background:rgba(255,255,255,.2);border-radius:4px;padding:1px 5px;}

  .auth-submit-btn{width:100%;background:#0C7DAF;color:rgba(255,255,255,.5);border:none;border-radius:22px;padding:9.5px;font-size:15px;font-weight:500;cursor:pointer;}
  .auth-submit-btn.ready{color:#fff;}
  .auth-submit-btn:hover{background:#0A6C99;}
  .auth-submit-btn:disabled{cursor:default;}

  .auth-ghost-link{display:block;text-align:center;margin-top:clamp(20px, 4vh, 35px);font-size:13.5px;color:#fff;text-decoration:underline;cursor:pointer;background:none;border:none;width:100%;}

  .auth-social-row{display:flex;justify-content:center;gap:25px;margin-top:clamp(20px, 4vh, 35px);}
  .auth-social{width:31px;height:31px;border:none;cursor:pointer;padding:0;background-color:#ffffff;-webkit-mask-size:contain;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center;mask-size:contain;mask-repeat:no-repeat;mask-position:center;opacity:.92;}
  .auth-social:hover{opacity:1;}
  #auth-social-trello{-webkit-mask-image:url(assets/socials/trello_icon.svg);mask-image:url(assets/socials/trello_icon.svg);}
  #auth-social-discord{-webkit-mask-image:url(assets/socials/clyde_icon.svg);mask-image:url(assets/socials/clyde_icon.svg);}

  .auth-error{background:rgba(0, 0, 0, 0);color:#fff;border-radius:0px;padding:6px 12px;font-size:12.5px;margin-bottom:20px;line-height:1.5;display:none;}
  .auth-error.show{display:block;}

  /* Sits under the socials like reference-loginpage.png, starting 6.8vw from the left edge
     (the column itself starts at 13vw). The gap matches the reference at 1920px wide. */
  .auth-bottom-row{display:flex;align-items:center;gap:max(40px, calc(25.6vw - 210px));width:max-content;margin:clamp(28px, 6.5vh, 70px) 0 0 -6.2vw;}
  body.auth-mode #max-btn{display:none;}
  .auth-bottom-link{display:flex;align-items:center;gap:8px;border:none;background:none;color:#fff;font-size:13px;cursor:pointer;padding:0;}
  .auth-remember{display:flex;align-items:center;gap:8px;color:#fff;font-size:13px;cursor:pointer;}
  .auth-remember input{accent-color:#0C7DAF;width:14px;height:14px;cursor:pointer;}

  .spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;display:inline-block;animation:spin .7s linear infinite;vertical-align:-3px;margin-right:8px;}
  .primary-btn{width:100%;background:var(--sky);color:#fff;border:none;border-radius:12px;padding:12px;font-size:14.5px;font-weight:700;cursor:pointer;}
  .primary-btn:hover{background:#0E9BD6;}
  .primary-btn:disabled{opacity:.5;cursor:default;}
  @keyframes spin{to{transform:rotate(360deg);}}
  .back-link{font-size:12.5px;color:var(--text-dim);cursor:pointer;margin-bottom:16px;display:inline-flex;align-items:center;gap:5px;}

  /* ---------- settings modal ---------- */
  /* ---------- image lightbox: fullscreen viewer with zoom/pan, opened by clicking an attachment ---------- */
  .lightbox-overlay{position:fixed;inset:0;background:rgba(8,18,26,.9);z-index:999;display:none;align-items:center;justify-content:center;}
  .lightbox-overlay.show{display:flex;}
  .lightbox-stage{width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;}
  .lightbox-img{max-width:88vw;max-height:82vh;object-fit:contain;user-select:none;-webkit-user-drag:none;border-radius:4px;transform-origin:center center;}
  .lightbox-video{max-width:88vw;max-height:82vh;border-radius:4px;background:#000;}
  .lightbox-btn{width:36px;height:36px;border-radius:50%;border:none;background:rgba(255,255,255,.12);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;}
  .lightbox-btn:hover{background:rgba(255,255,255,.24);}
  .lightbox-close{position:absolute;top:22px;right:24px;}
  .lightbox-controls{position:absolute;bottom:26px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.08);padding:7px 14px;border-radius:24px;backdrop-filter:blur(8px);}
  .lightbox-zoom-level{font-size:12.5px;color:#fff;min-width:40px;text-align:center;font-weight:600;user-select:none;}

  .modal-backdrop{position:absolute;inset:0;background:rgba(20,40,60,.35);display:none;align-items:center;justify-content:center;z-index:80;}
  .modal-backdrop.show{display:flex;}
  /* Hover states snap like Skype did; buttons still press in a touch and dialogs fade and pop in */
  @media (prefers-reduced-motion: no-preference){
    .round-btn,.nav-btn,.settings-inline-btn,.primary-btn,.home-tab,.lightbox-btn{transition:transform .12s ease;}
    .round-btn:active,.nav-btn:active,.settings-inline-btn:active,.primary-btn:active,.home-tab:active,.lightbox-btn:active{transform:scale(.94);}
    .modal-backdrop.show{animation:modal-fade .16s ease-out;}
    .modal-backdrop.show > *{animation:modal-pop .2s cubic-bezier(.2,.9,.3,1.15);}
  }
  @keyframes modal-fade{from{opacity:0;}to{opacity:1;}}
  @keyframes modal-pop{from{opacity:0;transform:scale(.96) translateY(6px);}to{opacity:1;transform:none;}}
  .modal-card{background:#fff;border-radius:5px;width:380px;max-width:90%;padding:24px;}
  .modal-card h3{margin:0 0 18px;font-size:16px;}
  /* Above the login and boot screens (100), since a notice matters most when signing in is what broke */
  #notice-modal{z-index:120;}
  .notice-card{width:420px;}
  .notice-head{display:flex;align-items:center;gap:12px;margin-bottom:10px;}
  .notice-head img{width:32px;height:32px;flex-shrink:0;}
  .notice-head h3{margin:0;}
  .notice-text{margin:0 0 12px;font-size:13.5px;line-height:1.5;white-space:pre-wrap;color:var(--text);}
  .notice-link{display:block;margin-bottom:14px;font-size:13px;color:var(--sky);word-break:break-all;}
  .notice-link.hidden{display:none;}
  .patch-note-picture{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;margin:8px 0 4px;}
  .setting-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--border);}
  .setting-row:last-child{border-bottom:none;}
  .setting-row .label{font-size:13.5px;font-weight:600;}
  .setting-row .desc{font-size:12px;color:var(--text-dim);margin-top:2px;}
  .switch{position:relative;width:40px;height:22px;flex-shrink:0;}
  .switch input{opacity:0;width:0;height:0;}
  .switch .track{position:absolute;inset:0;background:#C7D8E0;border-radius:20px;cursor:pointer;transition:background .15s;}
  .switch input:checked + .track{background:var(--sky);}
  .switch .thumb{position:absolute;top:2px;left:2px;width:18px;height:18px;background:#fff;border-radius:50%;transition:transform .15s;}
  .switch input:checked + .track .thumb{transform:translateX(18px);}
  .modal-close{width:100%;margin-top:18px;background:var(--sidebar);border:none;border-radius:10px;padding:10px;font-weight:600;cursor:pointer;color:var(--text);}

  /* ---------- settings menu ---------- */
  .settings-card{position:relative;background:#fff;width:960px;max-width:92vw;height:700px;max-height:88vh;border-radius:5px;display:flex;overflow:hidden;box-shadow:3px 3px 5px rgba(15,50,75,.25);}
  .settings-close-x{position:absolute;top:26px;left:26px;width:26px;height:26px;border:none;background:none;border-radius:3px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:2;}
  .settings-close-x:hover{background:#0000001a;}

  .settings-sidebar{width:320px;flex-shrink:0;background:#F0F4F8;display:flex;flex-direction:column;padding:64px 0 16px;}
  .settings-sidebar h2{margin:0 24px 14px;font-size:26px;font-weight:800;color:#000;}
  .settings-nav{display:flex;flex-direction:column;flex:1;overflow-y:auto;}
  .settings-nav-item{text-align:left;border:none;background:none;padding:13px 24px;font-size:16px;font-weight:700;color:#000;cursor:pointer;}
  .settings-nav-item:hover{background:#E4ECF1;}
  .settings-nav-item.active{background:#C7EDFC;color:#000;}
  .settings-sidebar-footer{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:16px 24px;border-top:1px solid var(--border);flex-shrink:0;}
  .settings-social-row{display:flex;gap:18px;}
  .settings-social{width:20px;height:20px;border:none;cursor:pointer;padding:0;background-color:#818181;-webkit-mask-size:contain;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center;mask-size:contain;mask-repeat:no-repeat;mask-position:center;}
  .settings-social:hover{background-color:var(--sky);}
  #settings-social-trello{-webkit-mask-image:url(assets/socials/trello_icon.svg);mask-image:url(assets/socials/trello_icon.svg);}
  #settings-social-discord{-webkit-mask-image:url(assets/socials/clyde_icon.svg);mask-image:url(assets/socials/clyde_icon.svg);}
  .settings-patch-notes-link{border:none;background:none;padding:0;font-size:12.5px;font-weight:700;color:#818181;cursor:pointer;}
  .settings-patch-notes-link:hover{color:var(--sky);}

  /* ---------- patch notes modal ---------- */
  .patch-notes-card{width:560px;max-width:92vw;height:640px;max-height:88vh;border-radius:5px;background:#fff;box-shadow:3px 3px 5px rgba(15,50,75,.25);position:relative;overflow:hidden;display:flex;flex-direction:column;}
  .patch-notes-card .settings-close-x{left:auto;right:22px;top:22px;}

  #invite-create-modal .modal-card,
  #invite-join-modal .modal-card{position:relative;}
  #invite-create-modal .settings-close-x,
  #invite-join-modal .settings-close-x{left:auto;right:18px;top:18px;}
  #invite-create-modal h3, #invite-join-modal h3{color:var(--text);padding-right:22px;}
  .invite-modal-hint{font-size:12px;color:var(--text-dim);margin:-8px 0 14px;line-height:1.5;}
  .invite-modal-error{color:var(--danger);font-size:12.5px;margin-top:8px;line-height:1.5;display:none;}
  .invite-modal-error.show{display:block;}
  .invite-modal-input{width:100%;box-sizing:border-box;border:1px solid var(--border);border-radius:5px;padding:9.5px 12px;font-size:14px;outline:none;background:var(--bg);color:var(--text);}
  .invite-modal-input:focus{border-color:var(--sky);}
  #invite-create-link{font-family:inherit;}
  #invite-create-copy{display:flex;align-items:center;justify-content:center;color:var(--sky);border:1px solid var(--border);border-radius:5px;background:none;cursor:pointer;}
  #invite-create-copy:hover{background:var(--sky-pale);}
  #invite-create-copy.copied{color:#2CA85B;}
  #invite-join-submit{margin-top:14px;width:100%;background:var(--sky);color:#fff;border:none;border-radius:22px;padding:9.5px;font-size:15px;font-weight:500;cursor:pointer;}
  #invite-join-submit:hover{background:#0A6C99;}

  .home-row-invite-btn{border:none;background:none;color:var(--sky);cursor:pointer;padding:4px;margin-left:auto;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  .home-row-invite-btn:hover{background:#0000000f;}
  body.dark-theme .home-row-invite-btn:hover{background:#ffffff1a;}
  .patch-notes-body{flex:1;display:flex;flex-direction:column;min-height:0;padding:28px 30px 24px;}
  .patch-notes-body h2{margin:0 0 16px;font-size:22px;}
  .patch-notes-list{flex:1;overflow-y:auto;padding-right:4px;}
  .patch-note-entry{padding-bottom:20px;margin-bottom:20px;border-bottom:1px solid #EAF1F4;}
  .patch-note-entry:last-child{border-bottom:none;margin-bottom:0;}
  .patch-note-version{font-size:15px;font-weight:800;color:#1B2733;}
  .patch-note-date{font-size:12px;color:#95A2AC;margin:2px 0 10px;}
  .patch-note-content{font-size:13.5px;line-height:1.65;color:#3A444D;}
  .patch-note-content h1,.patch-note-content h2,.patch-note-content h3{font-size:13.5px;font-weight:800;color:#1B2733;margin:12px 0 6px;}
  .patch-note-content ul{margin:6px 0;padding-left:20px;}
  .patch-note-content li{margin:3px 0;}
  .patch-note-content p{margin:6px 0;}
  .patch-note-content code{background:#F2F6F8;border-radius:4px;padding:1px 5px;font-size:12.5px;}
  .patch-note-content strong{color:#1B2733;}

  .settings-content{flex:1;overflow-y:auto;padding:70px 56px 40px;}
  .settings-panel{display:none;}
  .settings-panel.active{display:block;}
  .settings-panel h1{margin:0 0 22px;font-size:24px;font-weight:800;color:#000;}
  .settings-empty{color:var(--text-dim);font-size:14px;padding:20px 0;}
  .settings-inline-btn{border:none;border-radius:999px;padding:9px 16px;font-weight:700;font-size:13px;cursor:pointer;background:var(--sky);color:#fff;}
  .desc kbd{background:var(--sky-pale);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:1px 5px;font-family:inherit;font-size:11px;font-weight:700;}
  .label-with-tag{display:flex;align-items:center;gap:8px;}
  .label-with-tag img{height:15px;width:auto;display:block;}
  .settings-inline-btn.danger{background:#FBE3E3;color:var(--danger);}
  .settings-inline-btn.danger:hover{background:#F6CFCF;}
  .account-field{padding:6px 0 14px;border-bottom:1px solid #DDECF0;}
  .account-field-label{display:block;font-size:13.5px;font-weight:600;margin-bottom:6px;}
  .account-field-row{display:flex;align-items:center;gap:14px;margin-top:6px;}
  .account-field-hint{font-size:12px;color:var(--text-dim);margin-top:6px;}
  .account-value{flex:1;font-size:14px;color:var(--text);}
  .account-input{flex:1;max-width:320px;border:1px solid #BFE3F3;border-radius:0;padding:7px 10px;font:inherit;font-size:14px;color:var(--text);background:#fff;outline:none;transition:border-color .15s;}
  .account-input:focus{border-color:var(--sky);}
  body.dark-theme .account-input{background:var(--sky-pale);border-color:var(--border);}
  body.dark-theme .account-input:focus{border-color:var(--sky);}
  .account-link-btn{border:none;background:none;padding:4px 0;font:inherit;font-size:13.5px;font-weight:700;color:var(--sky);cursor:pointer;}
  .account-link-btn:hover{text-decoration:underline;}
  .account-link-btn:disabled{opacity:.45;cursor:default;text-decoration:none;}
  .account-link-btn.muted{color:var(--text-dim);}
  .settings-username-form.hidden{display:none;}
  .settings-name-error{color:var(--danger);font-size:12.5px;margin-top:8px;}
  .settings-name-error:empty{display:none;}
  /* Choices sit in a dropdown to the right of the option, like Skype's Options window */
  .setting-dropdown{width:auto;min-width:180px;flex-shrink:0;margin-left:16px;cursor:pointer;}
  .setting-split{display:flex;align-items:center;justify-content:space-between;}


  .settings-help-top{display:flex;flex-direction:column;align-items:center;text-align:center;margin-bottom:34px;}
  .settings-help-logo{width:64px;height:64px;margin-bottom:14px;}
  .settings-help-version{font-weight:700;font-size:15px;color:#000;}
  .settings-help-copyright{font-size:12.5px;color:var(--text-dim);margin-top:2px;}
  .app-info-trigger{cursor:pointer;}
  .app-info-trigger:hover{color:var(--sky);text-decoration:underline;}
  .settings-update-row{display:flex;align-items:center;gap:8px;margin-top:14px;font-size:13.5px;color:#000;}
  .settings-update-row.hidden{display:none;}
  .settings-update-check{width:16px;height:16px;border-radius:50%;background:#7DBC00;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  .settings-update-btn{width:26px;height:26px;border-radius:50%;border:none;background:var(--sky);display:flex;align-items:center;justify-content:center;cursor:pointer;}
  .settings-update-btn:hover{background:#0E9BD6;}

  .settings-section-label{font-size:11px;font-weight:700;color:#818181;text-transform:uppercase;letter-spacing:.04em;margin:22px 0 4px;}
  .settings-link-row{display:block;width:100%;text-align:left;border:none;background:none;border-bottom:1px solid #DDECF0;padding:14px 2px;font-size:16px;font-weight:700;color:#303030;cursor:pointer;}
  .settings-link-row:hover{color:var(--sky);}
