/* ================================================================ FONT ======
   Vazirmatn - a Persian/Arabic UI face with good Latin coverage too, which
   matters here because room names and messages are a mix of both.

   woff2 is the one browsers actually download; the woff and ttf entries are
   fallbacks for older engines. Only regular is bundled, so the weight is
   declared once and the browser synthesises bold/italic where it must - that
   is deliberate, since a separate 700 file was not supplied.
   ========================================================================== */
@font-face {
  font-family: "Vazirmatn";
  src:
    url("../fonts/Vazir-Regular.woff2") format("woff2"),
    url("../fonts/Vazir-Regular.woff") format("woff"),
    url("../fonts/Vazir-Regular.ttf") format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --font-ui: "Vazirmatn", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --tg-blue: #0088cc;
  --tg-bg: #80a67a;
  /* Classic muted Telegram wallpaper green */
  --sidebar-bg: #ffffff;
  --item-hover: #f4f4f5;
  --item-active: #eff7ff;
  --sent-bubble: #effdde;
  --recv-bubble: #ffffff;
  --text-main: #222222;
  --text-hint: #55697c;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  --border-color: #f2f2f2;
}

/* Text selection.
   `user-select` is NOT an inherited property, so it has to be declared on
   every element: a single `*` rule is the only way to get it everywhere. It
   stays, and the block below opts the readable surfaces back in - message
   bodies, room names and previews, member names, notes, code. Users can then
   highlight a single sentence to copy it, instead of hunting for the bubble's
   long-press "Copy text" menu. */
* {
    user-select: none;
}

/* Everything a reader might legitimately want to copy out of the app. */
.text,
.bubble,
.bubble pre,
.bubble code,
.bubble blockquote,
.bubble a,
.room-name-text,
.last-msg,
.last-msg-empty,
.msg-menu-head,
.room-menu-title,
.starred-row-text,
.set-note,
.set-k,
.welcome-card,
.empty-state,
.sidebar-empty,
.sidebar-no-results,
.pass-hint,
.import-hint,
.kbd-note,
.member-name,
.member-list,
#msg-input,
.unlock-input,
#setup-name-input,
#setup-room,
#import-data,
#reply-text,
#unlock-pass-display,
#storage-status,
#members-list,
#alert-msg,
.card p {
    user-select: text;
    -webkit-user-select: text;
}

/* Legacy blocks removed: duplicate .date-separator, unused .settings-bar and
   the global * { transition: background } (it slowed repaints and fought the
   component-level transitions below). */

body {
  margin: 0;
  font-family: var(--font-ui);
  background: #f0f2f5;
  color: var(--text-main);
  display: flex;
  /* --app-h is maintained by initKeyboardViewport() from
     window.visualViewport, so the app is exactly as tall as the area the
     browser is actually showing us. `100dvh` is only the fallback for the
     moment before the first measurement (and for very old engines). See
     body.kb-open below for what happens while the keyboard is up. */
  height: var(--app-h, 100dvh);
  overflow: hidden;
}

/* While the on-screen keyboard is up the visible strip is short, and the
   composer is the one thing that must never be pushed off it. initKeyboardViewport()
   also closes the emoji strip, which would otherwise eat a third of what
   little room is left; this just tightens the spacing around the input. */
body.kb-open .composer { padding-bottom: 2px; }

/* Custom Scrollbar */
::-webkit-scrollbar {
  width: 6px;
}

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

::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.1);
  border-radius: 10px;
}

::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, 0.2);
}

/* SIDEBAR
   Only the non-sizing properties live here. The width itself is owned by the
   "UI REFRESH" #sidebar rule further down this file - which used to hard-code
   336px and, being later in the sheet, silently beat the width declared here.
   The resizer writes --sidebar-w onto <html>; that rule is the single place
   the value is consumed. */
#sidebar {
  background: var(--sidebar-bg);
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  z-index: 10;
}

/* The drag handle. It is a real button in the tab order, not a bare div: the
   arrow keys are a genuine resize control, so it needs a role and a name. */
#sidebar-resizer {
  flex: 0 0 auto;
  width: 7px;
  margin-left: -3px;
  margin-right: -4px;
  z-index: 30;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: col-resize;
  touch-action: none;   /* let pointermove track instead of scrolling the page */
  position: relative;
}
/* The visible 1px rule lives on a ::before so the hit area can be wider than
   the line itself - 7px of target instead of 1px. */
#sidebar-resizer::before {
  content: "";
  position: absolute;
  inset: 0 3px;
  background: #dfe5ec;
  transition: background 0.15s var(--ease);
}
#sidebar-resizer:hover::before,
#sidebar-resizer:focus-visible::before,
#sidebar-resizer.is-dragging::before { background: var(--accent); }
[data-theme="dark"] #sidebar-resizer::before { background: #2b3340; }
#sidebar-resizer:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }

.sidebar-header {
  padding: 18px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: white;
}

#room-list {
  flex: 1;
  overflow-y: auto;
}

.room-item {
  padding: 12px 20px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 15px;
  border-bottom: 0.5px solid #f2f2f2;
}

.room-item:hover {
  background: var(--item-hover);
}

.room-item.active {
  background: var(--item-active);
}

.room-item b {
  font-size: 15px;
  color: #000;
}

[data-theme="dark"] .room-item b {
  color: #fefefe;
}

/* MAIN CHAT AREA */
#main-chat {
  flex: 1;
  display: flex;
  flex-direction: column;
  background-color: var(--tg-bg);
  position: relative;
  min-width: 0;
}

/* Doodle Wallpaper Pattern */
#main-chat::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(15, 23, 42, 0.055) 1px, transparent 1px);
  background-size: 22px 22px;
  pointer-events: none;
}
[data-theme="dark"] #main-chat::before {
  background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
}

header {
  background: white;
  color: #000;
  padding: 12px 25px;
  font-weight: 600;
  font-size: 16px;
  box-shadow: var(--shadow);
  z-index: 5;
  display: flex;
  flex-direction: column;
}
[data-theme="dark"] header {
  background: #161c26;
  color: #eef2f8;
  box-shadow: 0 1px 0 rgba(255,255,255,0.06);
}
[data-theme="dark"] footer {
  background: #161c26;
  border-top: 1px solid rgba(255,255,255,0.06);
}
[data-theme="dark"] #chat-window {
  background-color: transparent;
}
[data-theme="dark"] .welcome-card {
  background: #161c26;
  border-color: #262d3a;
}

#chat-window {
  flex: 1;
  overflow-y: auto;
  padding: 25px 50px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  z-index: 2;
}

/* AVATARS */
.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: white;
  flex-shrink: 0;
  font-size: 16px;
  box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
}

/* MESSAGES */
.msg {
  max-width: 85%;
  display: flex;
  gap: 12px;
  align-items: flex-end;
}

.msg.sent {
  align-self: flex-end;
  flex-direction: row-reverse;
  word-wrap: break-word;
}

.msg.received {
  align-self: flex-start;
  word-wrap: break-word;
}

.bubble {
  padding: 5px 12px;
  border-radius: 15px;
  font-size: 14.5px;
  line-height: 1.15;
  box-shadow: var(--shadow);
  position: relative;
  min-width: 40px;
}

/* Add this to your style.css */
.bubble .text {
  /* Existing styles for the text inside the bubble */

  /* NEW RULE: This tells the browser to respect newlines (\n) 
  and wrap long lines when necessary. */
  white-space: pre-wrap;
  max-width: 50dvw;
  user-select:text;
}

/* Bubble Tails */
/*.bubble::after {*/
/*    content: "";*/
/*    position: absolute;*/
/*    bottom: 0;*/
/*    width: 10px;*/
/*    height: 10px;*/
/*}*/
/* Legacy bubble-tail rules removed: the refresh styles own the bubble and
   the ::after tail base is commented out above. */

.u-name {
  font-size: 12px;
  color: var(--tg-blue);
  font-weight: bold;
  display: block;
  margin-bottom: 2px;
}


/* Legacy textarea#msg-input styling removed: the composer input is now
   styled by #msg-input + .composer-box (see "UI REFRESH 2026" below), so the
   textarea and its wrapper render as ONE box - no nested double border or
   duplicate focus ring. */

/* MODALS */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(4px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.card {
  background: white;
  padding: 30px;
  border-radius: 20px;
  width: 320px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
  text-align: center;
}

.card h3 {
  margin: 0 0 20px;
  font-size: 20px;
  color: var(--tg-blue);
}

.card input {
  width: 100%;
  padding: 12px;
  margin-bottom: 12px;
  box-sizing: border-box;
  border-radius: 10px;
  border: 1px solid #ddd;
  background: #f9f9f9;
}

.btn-primary {
  background: var(--tg-blue);
  color: white;
  border: none;
  padding: 12px;
  border-radius: 10px;
  cursor: pointer;
  font-weight: bold;
  width: 100%;
}

.btn-primary:hover {
  filter: brightness(1.1);
}

/* Sidebar Last Msg & Badge */
.room-meta {
  flex: 1;
  min-width: 0;
}

.last-msg {
  font-size: 12px;
  color: var(--text-hint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  width: 100%;
}

.badge {
  background: #31ad45;
  color: white;
  font-size: 10px;
  font-weight: bold;
  min-width: 10px;
  text-align: center;
  padding: 4px 6px;
  border-radius: 50%;
  margin: 0px 18px 0 0;
}

/* Header Avatar logic */
.header-info {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Improved Dark Mode variables and application */
[data-theme="dark"] {
  --tg-bg: #0e1621;
  /* Main Chat Wallpaper Background */
  --sidebar-bg: #17212b;
  /* Sidebar/Modal Card Background */
  --item-hover: #212c37;
  /* Item hover color (darker than background) */
  --item-active: #334455;
  /* Active room selection color */
  --sent-bubble: #2b5278;
  --recv-bubble: #182533;
  --text-main: #ffffff;
  --text-hint: #8fa0b3;
  --border-color: #313d4b;
  /* New: A standard dark border color */
}

/* Ensure Headers and Footers switch in dark mode */
[data-theme="dark"] header,
[data-theme="dark"] footer,
[data-theme="dark"] .sidebar-header,
[data-theme="dark"] .card {
  background-color: var(--sidebar-bg) !important;
  color: var(--text-main) !important;
}

/* Emoji Grid for the Modal */
.emoji-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-bottom: 20px;
}

.emoji-opt {
  font-size: 30px;
  cursor: pointer;
  padding: 5px;
  border-radius: 10px;
  transition: 0.2s;
  border: 2px solid transparent;
}

.emoji-opt.selected {
  border-color: var(--tg-blue);
  background: rgba(0, 136, 204, 0.1);
}

/* Circular, Classy Buttons */
.icon-btn {
  background: #f1f4f7;
  border: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 18px;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.icon-btn:hover {
  background: #e2e8f0;
  transform: scale(1.05);
}

[data-theme="dark"] .icon-btn {
  background: #242f3d;
}

[data-theme="dark"] .icon-btn:hover {
  background: #2c394a;
}

/* My Profile Avatar in Sidebar (The trigger) */
#my-avatar-trigger {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  background: var(--tg-blue);
  border: 2px solid transparent;
  transition: 0.2s;
}

#my-avatar-trigger:hover {
  border-color: var(--tg-blue);
  opacity: 0.8;
}

/* Skeleton Container */
.skeleton-wrapper {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}

/* The Shimmer Animation */
@keyframes shimmer {
  0% {
    background-position: -450px 0;
  }

  100% {
    background-position: 450px 0;
  }
}

.skeleton {
  background: var(--recv-bubble);
  background-image: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.05) 20%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0) 100%);
  background-repeat: no-repeat;
  background-size: 450px 100%;
  animation: shimmer 1.5s infinite linear;
  border-radius: 15px;
  height: 45px;
  position: relative;
}

/* Legacy .sk-recv/.sk-sent skeleton sizes removed (rows replaced them). */

/* Dark mode adjustment for skeleton */
[data-theme="dark"] .skeleton {
  background-image: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.05) 20%, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0) 100%);
}

/* Small bar inside the bubble to show it's a reply */
.reply-preview {
  background: rgba(0, 0, 0, 0.05);
  border-left: 3px solid var(--tg-blue);
  padding: 5px 8px;
  border-radius: 4px;
  margin-bottom: 5px;
  font-size: 12px;
  cursor: pointer;
}

[data-theme="dark"] .reply-preview {
  background: rgba(255, 255, 255, 0.1);
}

/* The bar above the footer when you are currently typing a reply */
#reply-bar {
  display: none;
  background: var(--sidebar-bg);
  padding: 8px 20px;
  border-top: 1px solid var(--border);
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
}

.room-item {
  position: relative;
}

/* Legacy .leave-btn / #scroll-badge copies removed - positioning now lives in
   the refresh rules below; @keyframes popIn stays for the badge animation. */
.bubble { touch-action: pan-y; /* Prevents vertical scroll interference */ }

/* Legacy @keyframes highlightMsg removed: the refresh .highlight-flash uses
   the flash animation instead. */

/* Legacy footer arrow-button rule removed (send button has its own class). */

/* Custom Alert Animation */
#alert-modal .card {
  transform: scale(0.8);
  opacity: 0;
  transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

#alert-modal.active {
  display: flex;
}

#alert-modal.active .card {
  transform: scale(1);
  opacity: 1;
}

/* Red style for errors */
#alert-modal.error h3 {
  color: #ff4d4d;
}

#alert-modal.error .btn-primary {
  background: #ff4d4d;
}



/* --- New Global Classes for Mobile Hiding/Showing --- */
.mobile-only {
  display: none !important;
}

/* 1. Mobile Layout Definition */
@media (max-width: 768px) {

  /* 1. Reset Body to prevent horizontal scrolling. No position:fixed - it
     stops iOS from scrolling a focused input above the on-screen keyboard. */
  body {
    width: 100vw;
    height: var(--app-h, 100dvh);
    overflow: hidden;
  }

  /* 2. Both panels become full-screen FIXED overlays */
  #sidebar,
  #main-chat {
    position: fixed;
    /* <--- Change 'absolute' to 'fixed' */
    top: 0;
    left: 0;
    width: 100vw;
    /* var(--app-h), not 100dvh: the panes are position:fixed, so they lay out
       against the LAYOUT viewport, which does not shrink when the on-screen
       keyboard opens. Measuring the visual viewport instead is what keeps the
       composer above the keyboard. */
    height: var(--app-h, 100dvh);
    transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
    /* Snappier animation */
  }

  /* The resizer is a desktop affordance: on a phone the sidebar is a
     full-screen overlay, so a handle between the two panes is meaningless and
     would only steal touch targets. */
  #sidebar-resizer { display: none; }

  /* 3. Initial State: Sidebar is front and center */
  #sidebar {
    transform: translateX(0);
    z-index: 10;
  }

  /* 4. Initial State: Chat is hidden off-screen to the RIGHT */
  #main-chat {
    transform: translateX(100%);
    z-index: 20;
  }

  /* 5. Active State: Chat slides IN, Sidebar slides OUT */
  body.chat-active #sidebar {
    transform: translateX(-30%);
    /* Moves sidebar left for parallax effect */
  }

  body.chat-active #main-chat {
    transform: translateX(0);
    /* Moves chat into view */
  }

  /* 6. Utilities */
  .mobile-only {
    display: flex !important;
  }

  /* Adjust padding for touch screens */
  #chat-window {
    padding: 15px;
  }

  .sidebar-header {
    padding: 15px;
  }
}



.emoji-option:hover {
  transform: scale(1.1);
}


/* --- NEW CSS FOR UNLOCK/DIAL PAD MODAL --- */

/* Logic to show only one view at a time */
.setup-view,
.unlock-view {
  display: none;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

#name-modal.setup-mode .setup-view {
  display: flex;
}

#name-modal.unlock-mode .unlock-view {
  display: flex;
}


/* Styling for the Unlock View elements */
#unlock-avatar {
  width: 70px;
  height: 70px;
  font-size: 40px;
  margin-bottom: 15px;
  background: var(--tg-blue);
}

#unlock-pass-display {
  height: 30px;
  margin-bottom: 20px;
  font-size: 30px;
  letter-spacing: 10px;
  color: var(--text-main);
}

/* Dial Pad Grid */
.dial-pad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 15px;
  width: 100%;
  margin-bottom: 25px;
}

.dial-btn {
  background: var(--item-hover);
  border: none;
  border-radius: 50%;
  /* Circular buttons */
  height: 65px;
  width: 65px;
  justify-self: center;
  /* Center button in grid cell */
  font-size: 24px;
  font-weight: 500;
  font-family: inherit;
  color: var(--text-main);
  cursor: pointer;
  transition: background-color 0.15s ease;
  /* Flex centring. The digit glyphs were centred by the line box, but the
     backspace key holds an inline <svg> that sat on the text baseline - so the
     icon drifted off-centre and read as smaller than the numbers beside it.
     Centring both kinds of content identically is what makes the key match. */
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}

/* The empty cell before "0" keeps the 3-column grid aligned. It is a span now,
   not an <button>, so it is not a focusable dead control. */
.dial-spacer {
  width: 65px;
  height: 65px;
  justify-self: center;
}

/* Backspace key: tinted as a destructive control, and dimmed when there is
   nothing left to delete, so its state is legible at a glance. */
.dial-btn-backspace { color: var(--text-hint); }
.dial-btn-backspace .ic { width: 24px; height: 24px; }
.dial-btn-backspace:disabled { opacity: 0.35; cursor: default; }
.dial-btn-backspace:disabled:active {
  background-color: var(--item-hover);
  color: var(--text-hint);
}

.dial-btn:hover {
  background-color: #e0e0e0;
}

[data-theme="dark"] .dial-btn:hover {
  background-color: #2c394a;
}

.dial-btn:active {
  background-color: var(--tg-blue) !important;
  color: white;
}

/* ADD THIS CSS to style.css */
.profile-actions {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--item-hover);
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.btn-secondary {
  background: var(--item-hover);
  color: var(--text-main);
  border: 1px solid var(--border-color);
  padding: 10px;
  border-radius: 8px;
  cursor: pointer;
  font-weight: bold;
  width: 100%;
}

.import-section textarea {
  width: 100%;
  min-height: 60px;
  padding: 8px;
  box-sizing: border-box;
  border-radius: 8px;
  border: 1px solid #ddd;
  background: #f9f9f9;
  margin-bottom: 10px;
  font-size: 12px;
}

/* Explains that importing replaces the profile on this device - the section
   has no heading of its own, so the warning lives with the field. */
.import-hint {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-hint);
  margin: 0 0 8px;
}

/* Add this CSS block to the end of your style.css */

/* Hide chat-specific UI elements by default */
#main-chat.no-room header,
#main-chat.no-room footer,
#main-chat.no-room #reply-bar,
#main-chat.no-room #scroll-badge {
  display: none !important;
}

/* Ensure the chat window content is centered/styled for the welcome message */
#main-chat.no-room #chat-window {
  padding: 0;
  justify-content: center;
  /* Center the welcome message vertically */
  align-items: center;
  /* Center the welcome message horizontally */
  background: transparent;
  /* Let the main-chat background show through */
}

/* Add this to style.css */

#export-btn {
  display: none;
}

/* Add this CSS to your style.css */

/* Green style for success */
#alert-modal.success h3 {
  color: #31ad45;
  /* A pleasant green color */
}

#alert-modal.success .btn-primary {
  background: #31ad45;
}

/* Styling for the new last message time */
.last-time {
  font-size: 12px;
  color: var(--text-hint);
  flex-shrink: 0;
  /* Prevents text from being compressed */
  margin-left: 10px;
  font-weight: normal !important;
}

/* Ensure room-meta handles the two lines of text better */
.room-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  /* Make room-meta a flex container */
  flex-direction: column;
  gap: 3px;
  /* Small gap between name/time and last message */
}

.room-meta>div {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* In style.css */

.room-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Reduced gap */
  margin-right: 15px;
  /* Add margin to prevent text touching the badge/time */
}

/* New row to align name and time */
.room-meta .name-time-row {
  display: flex;
  justify-content: space-between;
  /* Pushes name to left, time to right */
  width: 100%;
  align-items: center;
}

/* Ensure room name is not bold when not active */
.room-item b {
  font-size: 15px;
  color: var(--text-main);
}


/* B. Adjust the Badge Position */

/* Find the existing badge style and update its margin */
.badge {
  /* ... (existing styles) ... */
  margin: 0;
  /* Remove top margin */
  align-self: flex-start;
  /* Aligns badge to the top */
  margin-right: 20px;
  margin-top: 10px;
  margin-left: -30px;
}

/* Legacy leave-button / room-item alignment fixes removed (refresh rules own
   the positioning now). */

/* Right-hand end of the first row: the pin marker sits immediately left of the
   timestamp, so a pinned room reads "📌 12:04" instead of reserving a whole
   column at the edge of the list. */
.row-end {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
}
.name-time-row .last-time {
  /* The old 10px right margin existed to clear the hover-tool pill. With the
     pill gone there is nothing to clear, so the row can end flush. */
  margin-top: 0;
  margin-right: 0;
}




footer {
    width: 100%;
    box-sizing: border-box;
  padding: 10px 20px;
  background: white;
  display: flex;
  flex-direction: column; /* Stack picker on top of input */
  gap: 8px;
  align-items: center;
  z-index: 5;
}

#emoji-picker {
  display: none; /* Hidden by default, shown via JS when room is active */
  position: static; /* Remove absolute positioning */
  width: 100%;
  min-width: 0;
  max-width: 100%;
  overflow-x: auto; /* Horizontal scroll */
  white-space: nowrap; /* Keep emojis in one line */
  padding: 1px 0;
  background: transparent;
  box-shadow: none;
  border: none;
  border-bottom: 1px solid var(--item-hover);
  gap: 0px;
  scrollbar-width: none; /* Hide scrollbar Firefox */
  -ms-overflow-style: none; /* Hide scrollbar IE */
  -webkit-overflow-scrolling: touch; /* Smooth scroll on mobile */
}

/* Hide scrollbar Chrome/Safari (the emoji row still scrolls via wheel/touch) */
#emoji-picker::-webkit-scrollbar {
  width: 0 !important;
  height: 0 !important;
  display: none !important;
}
#emoji-picker {
    scrollbar-width: none !important;
    scrollbar-color: transparent transparent !important;
    -ms-overflow-style: none;
}

.emoji-option {
  display: inline-block;
  font-size: 18px;
  padding: 5px 10px;
  cursor: pointer;
  transition: transform 0.1s, background 0.15s;
}


/* Legacy rule removed: it no longer matches anything (#file-input is the last
   child of <footer>) and its `align-items: center` fought .composer's
   `align-items: flex-end` when it did match. */




/* Dark Mode Adjustments */
[data-theme="dark"] footer {
  background-color: var(--sidebar-bg);
}
[data-theme="dark"] #emoji-picker {
  border-bottom-color: var(--border-color);
}

#qrcode-container canvas, #qrcode-container img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0 auto;
}

/* --- QR Scanner Modal & Viewport --- */
.scanner-card {
    width: 360px;
    max-width: 92vw;
    padding: 24px;
    text-align: center;
}
.scanner-viewport {
    position: relative;
    width: 100%;
    height: 250px;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: #000;
    margin: 12px 0 16px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.scanner-viewport video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.scanner-target {
    position: absolute;
    width: 180px;
    height: 180px;
    border: 2px dashed rgba(255, 255, 255, 0.7);
    border-radius: 12px;
    pointer-events: none;
    box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.35);
}
.scanner-laser {
    position: absolute;
    left: 10%;
    right: 10%;
    height: 2px;
    background: #31ad45;
    box-shadow: 0 0 8px #31ad45;
    animation: scannerScan 2s infinite alternate ease-in-out;
}
@keyframes scannerScan {
    0% { top: 12%; }
    100% { top: 88%; }
}

/* --- Passphrase hints and the unlock keyboard input --- */
.pass-hint {
    font-size: 11px;
    line-height: 1.4;
    color: var(--text-hint);
    text-align: center;
    margin: 0 0 12px 0;
    max-width: 290px;
}

.unlock-input {
    /* The passphrase box is hidden on the unlock screen: the dial pad and the
       keyboard (captured in handleGlobalKeydown) write into it in the
       background while #unlock-pass-display shows the dots. */
    display: none;
    width: 100%;
    max-width: 260px;
    margin: 4px 0 12px 0;
    padding: 10px;
    border-radius: 8px;
    border: 1px solid var(--border-color);
    background: var(--sidebar-bg);
    color: var(--text-main);
    text-align: center;
    box-sizing: border-box;
}

[data-theme="dark"] .unlock-input {
    background: #232323;
    border-color: #3a3a3a;
    color: #f1f1f1;
}

/* Text selection is granted at the top of this file, in one block that lists
   every readable surface. This rule used to be a second, partial copy of that
   list; it is gone so the two can never drift apart. */

/* ===================================================================
   UI REFRESH 2026 - tokens, layout, sidebar
   =================================================================== */
:root {
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 24px;
  --ring: 0 0 0 2px rgba(0, 136, 204, 0.45);
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.07);
  --shadow-md: 0 6px 16px rgba(16, 24, 40, 0.10);
  --shadow-lg: 0 18px 40px rgba(16, 24, 40, 0.18);
  --accent: #0f8ad8;
  --accent-strong: #067cb8;
  --danger: #e5484d;
  --success: #2ea043;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --chat-surface: #eef1f5;
}
[data-theme="dark"] { --chat-surface: #10151d; }

/* The chat surface stays neutral until a room paints its own tint. */
#main-chat { background-color: var(--chat-surface); }

/* Empty pane: no-room always uses the theme surface (fixes: open-then-close
   a room left a white RHS in dark mode because leftover header/footer and
   #chat-window paint kept the light panels). */
#main-chat.no-room { background-color: var(--chat-surface) !important; }
#main-chat.no-room #chat-window,
#main-chat.no-room header,
#main-chat.no-room footer { background: transparent !important; }

/* Room search + in-chat search. */
.room-search { padding: 6px 14px 2px; }
.room-search input {
  width: 100%; box-sizing: border-box;
  padding: 9px 12px; border-radius: var(--radius-md);
  border: 1px solid var(--border-color); background: var(--item-hover);
  color: var(--text-main); font-size: 13.5px;
}
.room-search input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); background: var(--sidebar-bg); }
.room-item.is-filtered-out { display: none; }
.sidebar-no-results { padding: 18px 14px; text-align: center; color: var(--text-hint); font-size: 13px; display: none; }
.sidebar-no-results.is-visible { display: block; }

.inchat-search {
  display: none; align-items: center; gap: 6px;
  padding: 7px 16px; background: var(--sidebar-bg);
  border-bottom: 1px solid var(--border-color); font-size: 13px;
}
.inchat-search.is-open { display: flex; }
.inchat-search input {
  flex: 1; min-width: 0; padding: 7px 10px; border-radius: var(--radius-md);
  border: 1px solid var(--border-color); background: var(--item-hover); color: var(--text-main);
}
.inchat-search input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); background: var(--sidebar-bg); }
.inchat-search .inchat-count { color: var(--text-hint); font-size: 12px; white-space: nowrap; }
.msg mark.search-hit { background: rgba(255, 193, 7, 0.55); border-radius: 3px; padding: 0 1px; }
.msg.is-dimmed { opacity: 0.28; }
[data-theme="dark"] .msg mark.search-hit { background: rgba(255, 193, 7, 0.4); color: #fff; }

/* Keyboard cursor: which bubble ↑ / ↓ (and Ctrl+↑ / ↓) currently points at. */
.bubble.is-cursor {
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-md);
  transform: translateY(-1px);
}
.last-msg-empty { opacity: 0.65; font-style: normal; }

/* Single-emoji messages render big, like modern messengers. */
.bubble .text.is-big-emoji { font-size: 44px; line-height: 1.15; letter-spacing: 1px; }
body.compact .bubble .text.is-big-emoji { font-size: 40px; }

/* Richer chat header: presence dot + actions (leave moved here). */
.header-room { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; }
.header-actions-right { display: flex; align-items: center; gap: 2px; margin-left: auto; }
/* The retention chip in the header, sitting among the other icon buttons.
   Replaces the old green "presence" dot: this app is anonymous and the server
   exposes no presence data, so a coloured dot beside the room name could only
   ever be misread as "someone is online". */
/* Scoped to .header-actions-right so it beats the later, generic `.icon-btn`
   rule - the chip is a <button>, and without this it would inherit the round
   36px icon box and the width could not grow to fit the label. */
.header-actions-right .room-ttl {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 36px;
  width: auto;
  min-width: 0;
  padding: 0 9px;
  border-radius: 999px;
  color: var(--text-hint);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  max-width: 140px;
  overflow: hidden;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.header-actions-right .room-ttl:hover { background: var(--item-hover); color: var(--text-main); }
.header-actions-right .room-ttl .ic { flex: 0 0 auto; }
/* The full sentence is the tooltip; the visible label stays short so the chip
   never crowds the other header buttons. */
.header-actions-right .room-ttl .ttl-text { overflow: hidden; text-overflow: ellipsis; }
.header-leave-btn { color: var(--text-hint); }
.header-leave-btn:hover { background: rgba(229,72,77,0.12); color: var(--danger); }
.room-emoji-tag { font-size: 16px; line-height: 1; }
.inchat-search-btn.is-on { background: rgba(15,138,216,0.12); color: var(--accent-strong); }

/* kbd hints + shortcut help list. */
.kbd-hints { font-size: 11px; color: var(--text-hint); margin-top: 10px; }
kbd {
  font-family: inherit; font-size: 11px; background: var(--item-hover);
  border: 1px solid var(--border-color); border-bottom-width: 2px;
  border-radius: 5px; padding: 0 5px;
}
.shortcut-list { text-align: left; font-size: 13px; display: grid; gap: 7px; margin: 12px 0 4px; color: var(--text-main); }
.shortcut-list div { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
.shortcut-list span:last-child { color: var(--text-hint); }

/* `height: 100%` on body belonged with this `html, body` pair, but it also
   outranked the `body { height: var(--app-h) }` declared earlier in this file
   - which is the rule that keeps the composer above the on-screen keyboard.
   `html` keeps its height; the body is sized from the measured visual
   viewport instead. */
html { height: 100%; overscroll-behavior: none; }

body {
  /* Inherits --font-ui from the first body rule; repeated here only for the
     smoothing below, which must not re-declare a different family and undo
     the Vazirmatn default. */
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
}

button, select, input, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}
/* The composer draws one ring around the whole box - no second inner ring. */
#msg-input:focus-visible { box-shadow: none; }
/* The switch hides its checkbox: show the ring on the visible slider instead. */
.switch input:focus-visible + .slider { box-shadow: var(--ring); }

.welcome-card {
  max-width: 420px;
  text-align: center;
  padding: 32px 28px;
  color: var(--text-main);
}
.welcome-emoji { font-size: 44px; line-height: 1; }
.welcome-card h2 { margin: 12px 0 6px; font-size: 20px; font-weight: 650; }
.welcome-card p { margin: 0 0 18px; color: var(--text-hint); font-size: 14px; line-height: 1.5; }
.welcome-tips {
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
  display: grid;
  gap: 8px;
}
.welcome-tips li {
  font-size: 13px;
  color: var(--text-hint);
  background: rgba(15, 23, 42, 0.03);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 8px 11px;
}
.welcome-tips b { color: var(--text-main); font-weight: 600; }
[data-theme="dark"] .welcome-tips li { background: rgba(255, 255, 255, 0.03); }

/* ------------------------------------------------------------- sidebar
   THE single place #sidebar's width is defined. It used to hard-code 336px,
   which meant the resizer's --sidebar-w was written to <html> and then
   overridden here. The min/max are the resizer's own limits (SIDEBAR_MIN /
   SIDEBAR_MAX in tgchat.js), and the border is dropped because the resizer
   handle draws it - see #sidebar-resizer::before. */
#sidebar {
  width: var(--sidebar-w, 336px);
  min-width: 220px;
  max-width: 560px;
  border-right: 0;
  position: relative;
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border-color);
  background: var(--sidebar-bg);
  position: sticky;
  top: 0;
  z-index: 3;
}

.profile-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 0;
  padding: 4px 6px 4px 4px;
  border-radius: var(--radius-md);
  cursor: pointer;
  min-width: 0;
  text-align: left;
  transition: background 0.15s var(--ease);
}
.profile-trigger:hover { background: var(--item-hover); }
/* The original stylesheet forced this button to a 38px circle, which clipped
   the name next to the avatar. Restore the full profile row. */
#my-avatar-trigger {
  width: auto;
  height: auto;
  min-width: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  justify-content: flex-start;
  font-size: inherit;
  padding: 4px 6px 4px 4px;
}
#my-avatar-trigger:hover { background: var(--item-hover); }

.avatar-ring {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 20px;
  background: linear-gradient(140deg, var(--tg-blue), #6ad0f5);
  box-shadow: 0 0 0 2px var(--sidebar-bg), 0 0 0 4px rgba(0, 136, 204, 0.25);
}

.profile-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.profile-meta b { font-size: 14.5px; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.profile-meta small { font-size: 11px; color: var(--text-hint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.header-actions { display: flex; align-items: center; gap: 2px; }

.icon-btn {
  border: 0;
  background: transparent;
  font-size: 18px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: var(--text-hint);
  transition: background 0.15s var(--ease), transform 0.15s var(--ease), color 0.15s var(--ease);
}
.icon-btn:hover { background: var(--item-hover); color: var(--text-main); transform: translateY(-1px); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn.is-off { opacity: 0.45; }

.room-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 12px 14px 6px; }
.room-actions .btn-primary {
  padding: 10px 12px;
  font-size: 13.5px;
  font-weight: 600;
  border-radius: var(--radius-md);
  background: var(--item-hover);
  color: var(--accent-strong);
  border: 1px solid var(--border-color);
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.room-actions .btn-create {
  background: linear-gradient(135deg, var(--accent), #35b3ef);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 4px 12px rgba(15, 138, 216, 0.28);
}
.room-actions .btn-primary:hover { transform: translateY(-1px); }
.room-actions .btn-create:hover { box-shadow: 0 8px 18px rgba(15, 138, 216, 0.34); }

#room-list { overflow-y: auto; flex: 1; padding: 4px 8px 16px; }

.room-item {
  padding: 9px 10px;
  border-radius: var(--radius-md);
  gap: 11px;
  border-bottom: 0;
  border-left: 3px solid transparent;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
  cursor: pointer;
}
.room-item:hover { background: var(--item-hover); }
.room-item.active { background: var(--item-active); border-left-color: var(--accent); }
.room-item b { font-size: 14.5px; font-weight: 600; color: var(--text-main); }

.room-avatar {
  width: 42px;
  height: 42px;
  flex: 0 0 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 16px;
  color: #fff;
  box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.12);
}
/* The placeholder avatar (no room open) carries an icon instead of a letter,
   so it needs its own tinted background - `.room-avatar` paints white text. */
.room-avatar-blank {
  background: var(--item-hover);
  color: var(--accent-strong);
  box-shadow: none;
}

.room-meta { gap: 2px; }
.room-meta b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.last-time { font-size: 11px; color: var(--text-hint); flex: 0 0 auto; }
.last-msg {
  font-size: 13px;
  color: var(--text-hint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.badge {
  background: var(--accent);
  color: #fff;
  border-radius: 999px;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  display: grid;
  place-items: center;
  font-size: 11.5px;
  font-weight: 700;
  box-shadow: 0 2px 8px rgba(15, 138, 216, 0.35);
}

@keyframes spin { to { transform: rotate(360deg); } }

.pending-dot { font-size: 13px; opacity: 0.7; animation: spin 3s linear infinite; }

/* Leaving a room is a header action now (see renderChatHeader), so the
   sidebar rows have no per-row ✕ any more. */
.room-item { position: relative; }

.sidebar-empty,
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  color: var(--text-hint);
  padding: 34px 20px;
  font-size: 13.5px;
  line-height: 1.5;
}
.sidebar-empty b, .empty-state b { color: var(--text-main); font-size: 15px; }
.sidebar-empty .btn-primary { margin-top: 6px; padding: 10px 16px; border-radius: var(--radius-md); }
.empty-emoji { font-size: 34px; }

/* ------------------------------------------------------------- messages */
#chat-window {
  padding: 18px clamp(12px, 4vw, 48px) 24px;
  gap: 3px;
  /* NOT `smooth`. A smooth scroll behaviour turns EVERY programmatic
     `scrollTop = n` into an animation, and this pane re-pins itself on every
     poll, every "load older" and every image that finishes decoding. Each of
     those writes then restarted the previous animation instead of replacing
     it, which is what made the view drift off and then snap back. Anchoring
     below relies on the write taking effect immediately. The places that DO
     want an animation (jump to latest, jump to a message) ask for it
     explicitly with scrollTo({behavior:'smooth'}). */
  scroll-behavior: auto;
}

#chat-window::after { content: ""; display: block; height: 4px; }

.msg {
  max-width: min(78%, 620px);
  gap: 8px;
  align-items: flex-end;
  animation: msgIn 0.22s var(--ease);
}
.msg.grouped { margin-top: -1px; }
.msg.grouped .avatar-col { visibility: hidden; }
.msg + .msg { margin-top: 2px; }

@keyframes msgIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.avatar-col { flex: 0 0 36px; display: flex; justify-content: center; }
.avatar { width: 36px; height: 36px; font-size: 15px; }
.avatar-ghost { background: transparent !important; box-shadow: none; }

.bubble {
  position: relative;
  padding: 6px 11px 5px;
  border-radius: var(--radius-lg);
  font-size: 14.5px;
  line-height: 1.38;
  box-shadow: var(--shadow-sm);
  max-width: 100%;
  display: flow-root; /* keeps the floated timestamp inside the bubble */
  transition: box-shadow 0.2s var(--ease), transform 0.15s var(--ease);
}
.msg.sent .bubble { background: var(--sent-bubble); border-bottom-right-radius: 6px; }
.msg.received .bubble { background: var(--recv-bubble); border-bottom-left-radius: 6px; }
.msg.received:not(.grouped) .bubble { border-top-left-radius: 6px; }
.msg.sent:not(.grouped) .bubble { border-top-right-radius: 6px; }
.bubble:hover { box-shadow: var(--shadow-md); }
.bubble .text { white-space: pre-wrap; word-break: break-word; display: inline; line-height: 1.38; }

/* ------------------------------------------------- rich text in messages ---
 * Messages are rendered client-side from an escape-first allowlist renderer
 * (see renderRichText in tgchat.js), so the only elements that can ever appear
 * inside a bubble are the ones listed there. These rules style them; nothing
 * here widens what the renderer permits.
 *
 * `display: inline` above is deliberate for a plain message (it lets the time
 * sit on the same line as a short text). A formatted message introduces block
 * elements, which needs a block container to lay them out.
 */
.bubble .text.is-rich { display: block; }
.bubble .text.is-rich > :first-child { margin-top: 0; }
.bubble .text.is-rich > :last-child { margin-bottom: 0; }

.bubble .text.is-rich p { margin: 0 0 6px; }
.bubble .text.is-rich h1,
.bubble .text.is-rich h2,
.bubble .text.is-rich h3,
.bubble .text.is-rich h4,
.bubble .text.is-rich h5,
.bubble .text.is-rich h6 {
  margin: 8px 0 4px;
  line-height: 1.3;
  font-weight: 700;
}
.bubble .text.is-rich h1 { font-size: 1.28em; }
.bubble .text.is-rich h2 { font-size: 1.18em; }
.bubble .text.is-rich h3 { font-size: 1.1em; }
.bubble .text.is-rich h4, .bubble .text.is-rich h5, .bubble .text.is-rich h6 { font-size: 1em; }

.bubble .text.is-rich ul,
.bubble .text.is-rich ol { margin: 4px 0; padding-inline-start: 22px; }
.bubble .text.is-rich li { margin: 2px 0; }
.bubble .text.is-rich ul { list-style: disc; }
.bubble .text.is-rich ol { list-style: decimal; }

.bubble .text.is-rich blockquote {
  margin: 4px 0;
  padding: 2px 10px;
  border-inline-start: 3px solid var(--border-color);
  color: var(--text-hint);
}

.bubble .text.is-rich hr { border: none; border-top: 1px solid var(--border-color); margin: 8px 0; }

/* Inline code. Wraps (rather than scrolls) so a long token never widens the
 * bubble past the message max-width. */
.bubble .text.is-rich code.rich-code,
.bubble .text.is-rich kbd {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
  background: var(--item-hover);
  border: 1px solid var(--border-color);
  border-radius: 5px;
  padding: 1px 5px;
  word-break: break-all;
  unicode-bidi: plaintext;
}

/* Fenced / <pre> blocks. These keep their own line breaks instead of inheriting
 * the bubble's pre-wrap, and scroll horizontally when a line is longer than the
 * bubble - a code sample that wrapped mid-token is unreadable. */
.bubble .text.is-rich pre.rich-pre,
.bubble .text.is-rich pre {
  display: block;
  margin: 6px 0;
  padding: 9px 11px;
  background: var(--item-hover);
  border: 1px solid var(--border-color);
  border-radius: 8px;
  overflow-x: auto;
  max-width: 100%;
  white-space: pre;
  direction: ltr;
  text-align: left;
  unicode-bidi: plaintext;
}
.bubble .text.is-rich pre code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
  background: none;
  border: none;
  padding: 0;
  white-space: pre;
}
.bubble .text.is-rich pre[data-lang]::before {
  content: attr(data-lang);
  display: block;
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-hint);
  margin-bottom: 5px;
}

.bubble .text.is-rich a { color: var(--accent-strong); text-decoration: underline; }
.bubble .text.is-rich mark { background: rgba(255, 193, 7, 0.4); color: inherit; border-radius: 3px; }
.bubble .text.is-rich s, .bubble .text.is-rich del { opacity: 0.75; }
.bubble .text.is-rich sub, .bubble .text.is-rich sup { font-size: 0.75em; }

.u-name {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent-strong);
  margin-bottom: 2px;
}

.bubble-image img {
  max-width: 100%;
  max-height: 320px;
  border-radius: var(--radius-md);
  display: block;
  cursor: zoom-in;
  background: var(--item-hover);
  /* Reserve space before the bitmap arrives. Without a reserved box the image
     grows the bubble after it has been painted, which shifts everything below
     it and is what used to leave the chat scrolled to the wrong message. */
  aspect-ratio: 4 / 3;
  object-fit: cover;
  /* Once loaded the true shape takes over, so only pre-load images are cropped. */
  height: auto;
}
.bubble-image img[data-loaded="1"] { aspect-ratio: auto; }

/* The timestamp shares the last line of the message and drops onto its own
   line only when there is no room left - it can never cover the text. */
.bubble-body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: flex-end;
  column-gap: 8px;
}
.bubble-body > * { min-width: 0; }

.msg-meta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin: 0 0 0 auto;
  font-size: 10.5px;
  color: var(--text-hint);
  opacity: 0.8;
  line-height: 1;
  user-select: none;
  white-space: nowrap;
  flex: 0 0 auto;
}
.msg.sent .msg-meta { margin: 0 0 0 auto; }
.msg-time { font-variant-numeric: tabular-nums; }

.msg-status { font-size: 11px; line-height: 1; }
.msg-status.status-sending,
.msg-status.status-queued { animation: spin 1.4s linear infinite; display: inline-block; }
.msg-status.status-sent { color: var(--success); }
.msg-status.status-failed { color: var(--danger); font-weight: 700; animation: none; }

/* sending / failed states */
.msg.status-sending .bubble,
.msg.status-queued .bubble { opacity: 0.78; }
.msg.is-failed .bubble,
.msg.status-failed .bubble,
.bubble[data-failed="1"] {
  background: #fdecec;
  box-shadow: inset 0 0 0 1px rgba(229, 72, 77, 0.45);
}
[data-theme="dark"] .msg.is-failed .bubble,
[data-theme="dark"] .msg.status-failed .bubble,
[data-theme="dark"] .bubble[data-failed="1"] { background: #3b2024; }
.msg.is-failed .msg-status,
.msg.status-failed .msg-status { animation: none; }

/* hover actions */
.msg-actions {
  position: absolute;
  top: -14px;
  display: flex;
  gap: 2px;
  opacity: 0;
  pointer-events: none;
  /* The row sits ABOVE the bubble, so the pointer has to cross the bubble's top
     edge to reach it. The instant it left the bubble the row faded out, which
     made the buttons feel like they vanished before you could click. Two
     fixes: keep them visible while the pointer is anywhere over the message
     row (.msg:hover, not just .bubble:hover), and add an invisible bridge that
     covers the gap between the bubble and the row. The delayed transition
     means a cursor merely passing over a message does not flash the row. */
  transition: opacity 0.12s var(--ease) 0.18s;
  z-index: 6;
}
/* The bridge: an invisible strip hanging down from the action row into the
   bubble, so moving the cursor up from the text never leaves the target. */
.msg-actions::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 16px; /* covers the -14px offset, plus a little slack */
}
.msg.received .msg-actions { left: 40px; }
.msg.sent .msg-actions { right: 6px; }
/* the failed message's retry row sits fully below its bubble */
.msg.status-failed, .msg.is-failed { margin: 8px 0 26px !important; }
.msg.status-failed .msg-actions,
.msg.is-failed .msg-actions { top: auto; bottom: -22px; }
/* Hover the whole message row, not just the bubble. The action strip is
   positioned outside the bubble's box, so `:hover` on the bubble alone dropped
   the pointer out of the selector the instant it crossed the top edge. */
.msg:hover .msg-actions,
.msg:focus-within .msg-actions,
.msg.is-failed .msg-actions,
.msg.status-failed .msg-actions,
.msg-actions:hover,
.msg-actions:focus-within { opacity: 1; pointer-events: auto; }

.msg-action {
  border: 0;
  background: var(--sidebar-bg);
  color: var(--text-hint);
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: 12.5px;
  cursor: pointer;
  /* Flexbox, not grid. With `display: grid` the implicit row is sized to the
     16px icon and sits at the TOP of the 26px button, so `place-items: center`
     only centred the glyph inside that 16px cell - the reply / star / copy /
     heart icons all sat a few pixels high. Flex centres in the real box, and
     also lays the retry button's icon + label out on one line. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s var(--ease), color 0.15s var(--ease), transform 0.15s var(--ease);
}
.msg-action:hover { color: var(--accent-strong); transform: translateY(-1px); }
.msg-retry { width: auto; padding: 0 10px; gap: 4px; color: var(--danger); font-weight: 600; }
.msg-retry:hover { background: rgba(229, 72, 77, 0.12); }

.reply-preview {
  display: block;
  background: rgba(0, 0, 0, 0.045);
  border-left: 3px solid var(--accent);
  padding: 5px 8px;
  border-radius: 6px;
  margin-bottom: 4px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--text-hint);
  cursor: pointer;
  max-width: 100%;
  transition: background 0.15s var(--ease);
}
.reply-preview:hover { background: rgba(0, 0, 0, 0.08); }

.date-separator {
  position: sticky;
  top: 6px;
  z-index: 2;
  align-self: center;
  margin: 12px 0 8px;
  padding: 3px 11px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2px;
  background: rgba(16, 22, 30, 0.78);
  color: #fff;
  border-radius: 999px;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
  backdrop-filter: blur(8px);
  opacity: 0.95;
}

.unread-divider {
  align-self: stretch;
  text-align: center;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent-strong);
  background: rgba(15, 138, 216, 0.12);
  border-radius: 999px;
  padding: 3px 12px;
  margin: 8px 0;
}

.load-more {
  display: none;
  align-self: center;
  border: 1px solid var(--border-color);
  background: var(--sidebar-bg);
  color: var(--accent-strong);
  font-size: 12.5px;
  font-weight: 600;
  padding: 7px 16px;
  border-radius: 999px;
  cursor: pointer;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s var(--ease);
}
.load-more.is-visible { display: block; }
.load-more:hover { background: var(--item-hover); }
.load-more.is-loading { opacity: 0.6; pointer-events: none; }

.highlight-flash { animation: flash 1.6s ease; }
@keyframes flash {
  0%, 100% { transform: none; }
  25% { transform: scale(1.02); filter: brightness(1.04); }
}

.skeleton { height: 40px; border-radius: var(--radius-lg); }

/* Loading rows mimic avatar + bubble layout so the swap to real messages
   does not change the page shape. */
.skeleton-row { display: flex; gap: 10px; align-items: center; }
.skeleton-row.sk-right { flex-direction: row-reverse; }
.skeleton-row .skeleton { height: 34px; }
.skeleton-row .skeleton-avatar { width: 36px; height: 36px; flex: 0 0 36px; border-radius: 50%; }

/* -------------------------------------------------------- header + chat */
header {
  padding: 10px 16px;
  background: var(--sidebar-bg);
  border-bottom: 1px solid var(--border-color);
  box-shadow: none;
  z-index: 6;
}
.header-info { display: flex; align-items: center; gap: 10px; width: 100%; }
.room-title { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.room-name { font-weight: 600; font-size: 15px; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.room-sub { font-size: 11.5px; color: var(--text-hint); }
/* The retention chip no longer lives on a subline; these old subline rules are
   gone. The chip is styled as .room-ttl up in the header block. */
.room-title .ttl-text { font-size: inherit; }
header .room-avatar { width: 36px; height: 36px; flex: 0 0 36px; font-size: 15px; }
/* The action cluster already carries the auto margin (see .header-actions-right). */
#members-btn { margin-left: 0; }

/* ------------------------------------------------------------- composer */
footer { padding: 10px 16px 12px; gap: 8px; background: var(--sidebar-bg); position: relative; width: 100%; box-sizing: border-box; }
.composer {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  width: 100%;
  max-width: 100%;
  margin: 0;
}
.composer-tool { flex: 0 0 38px; width: 38px; height: 38px; }
.composer-box {
  flex: 1;
  position: relative;
  background: var(--item-hover);
  border: 1px solid transparent;
  border-radius: var(--radius-xl);
  padding: 9px 14px;
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease), background 0.15s var(--ease);
}
.composer-box:focus-within {
  background: var(--recv-bubble);
  border-color: rgba(15, 138, 216, 0.45);
  box-shadow: 0 0 0 3px rgba(15, 138, 216, 0.12);
}
#msg-input {
  width: 100%;
  border: 0;
  outline: 0;
  resize: none;
  background: transparent;
  color: var(--text-main);
  font-size: 15px;
  line-height: 1.4;
  max-height: 148px;
  min-height: 22px;
  padding: 0;
  display: block;
}
#msg-input::placeholder { color: var(--text-hint); }
#char-counter {
  position: absolute;
  right: 10px;
  bottom: 4px;
  font-size: 10.5px;
  color: var(--text-hint);
  background: var(--sidebar-bg);
  padding: 0 6px;
  border-radius: 999px;
  opacity: 0.85;
}
#char-counter:empty { display: none; }
#char-counter.is-warn { color: var(--danger); opacity: 1; font-weight: 600; }

.send-btn {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 50%;
  background: var(--border-color);
  color: var(--text-hint);
  font-size: 15px;
  cursor: default;
  display: grid;
  place-items: center;
  transition: transform 0.15s var(--ease), background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.send-btn.is-ready {
  cursor: pointer;
  color: #fff;
  background: linear-gradient(135deg, var(--accent), #35b3ef);
  box-shadow: 0 4px 12px rgba(15, 138, 216, 0.32);
}
.send-btn.is-ready:hover { transform: scale(1.06); }
.send-btn.is-ready:active { transform: scale(0.94); }

#emoji-picker { border-bottom: 0; padding: 4px 0 2px; }
#emoji-picker.is-open { display: block; }
.composer-tool.is-on { background: rgba(15, 138, 216, 0.12); color: var(--accent-strong); }
.emoji-option { font-size: 20px; border-radius: var(--radius-sm); padding: 4px 6px; }
.emoji-option:hover { background: var(--item-hover); }

#reply-bar {
  display: none;
  align-items: center;
  gap: 10px;
  padding: 8px 16px 0;
  background: var(--sidebar-bg);
  border-top: 0;
  z-index: 5;
  font-size: 12.5px;
  color: var(--text-hint);
}
#reply-bar.open { display: flex; }
#reply-text {
  flex: 1;
  border-left: 3px solid var(--accent);
  padding: 4px 10px;
  border-radius: 6px;
  background: var(--item-hover);
  min-width: 0;
  overflow: hidden;
}
#reply-text small { color: var(--accent-strong); font-weight: 600; }

/* Lives inside <footer> and anchors to its top edge, so it always clears the
   composer, emoji strip and reply bar regardless of their height. */
#scroll-badge {
  display: none;
  position: absolute;
  right: 22px;
  top: -48px;
  border: 0;
  background: var(--accent);
  color: #fff;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  z-index: 8;
}
#scroll-badge.is-visible { display: block; animation: popIn 0.28s var(--ease); }
#scroll-badge:hover { background: var(--accent-strong); }
#reply-bar.open ~ footer #scroll-badge { top: -98px; }

/* The old #upload-progress-bar / #upload-progress-fill pair was removed with the
   blocking upload alert: progress now lives in the per-file #upload-tray. */

/* --------------------------------------------------------- connection bar */
#net-status {
  position: relative;
  z-index: 20;
  display: none;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 500;
  background: rgba(229, 72, 77, 0.12);
  color: #b42318;
  border-bottom: 1px solid rgba(229, 72, 77, 0.25);
}
.net-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--danger);
  animation: pulse 1.4s ease-in-out infinite;
  flex: 0 0 8px;
}
.net-retry {
  margin-left: auto;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  border-radius: 999px;
  padding: 2px 10px;
  font-size: 11.5px;
  cursor: pointer;
}
.net-retry:hover { background: rgba(229, 72, 77, 0.12); }
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.8); }
}

/* -------------------------------------------------------- modals + settings */
.modal { display: none; background: rgba(15, 23, 42, 0.45); }
.modal.open { display: flex; }
.card {
  background: var(--recv-bubble);
  padding: 26px 24px;
  border-radius: var(--radius-xl);
  width: 340px;
  max-width: calc(100vw - 32px);
  max-height: calc(var(--app-h, 100vh) - 48px);
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
  text-align: center;
  animation: cardIn 0.22s var(--ease);
}
@keyframes cardIn {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
.card h3 { margin: 0 0 14px; font-size: 17px; color: var(--text-main); }
.card input[type="text"],
.card input[type="password"] {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 13px;
  margin-bottom: 9px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  background: var(--sidebar-bg);
  color: var(--text-main);
  font-size: 15px;
}
.card input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.btn-primary {
  width: 100%;
  padding: 12px 16px;
  border: 0;
  border-radius: var(--radius-md);
  background: linear-gradient(135deg, var(--accent), #35b3ef);
  color: #fff;
  font-weight: 600;
  font-size: 15px;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(15, 138, 216, 0.28);
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 20px rgba(15, 138, 216, 0.32); }
.btn-secondary {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--item-hover);
  color: var(--text-main);
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.btn-secondary:hover { background: var(--border-color); }

.settings-card { text-align: left; width: 420px; }
/* Column layout so the header can be pinned and only the settings list scrolls.
   The card used to be one scrolling block, which carried the only Back/Close
   buttons off the top whenever the list was longer than the viewport. */
.settings-card { display: flex; flex-direction: column; overflow: hidden; }
.settings-card > .modal-head {
  flex: 0 0 auto;
  background: var(--recv-bubble);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-color);
}
.settings-body {
  overflow-y: auto;
  min-height: 0;
  /* Stop iOS momentum from scrolling the page behind the sheet, and keep the
     rubber-band inside the list. */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.settings-back { margin-left: -8px; flex: 0 0 auto; }
/* The title now has a back button on one side and a close button on the other,
   so it is centred and allowed to shrink instead of being pinned left. */
.modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; gap: 8px; }
.modal-head h3 { margin: 0; flex: 1 1 auto; text-align: center; min-width: 0; }

.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border-color);
}
.setting-label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.setting-label b { font-size: 14px; font-weight: 600; color: var(--text-main); }
.setting-label small { font-size: 11.5px; color: var(--text-hint); line-height: 1.4; }

.setting-control {
  flex: 0 0 auto;
  min-width: 132px;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  background: var(--sidebar-bg);
  color: var(--text-main);
  font-size: 13.5px;
  cursor: pointer;
}

.switch { position: relative; flex: 0 0 auto; width: 46px; height: 26px; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch .slider {
  position: absolute;
  inset: 0;
  background: var(--border-color);
  border-radius: 999px;
  transition: background 0.2s var(--ease);
  pointer-events: none;
}
.switch .slider::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: transform 0.2s var(--ease);
}
.switch input:checked + .slider { background: var(--accent); }
.switch input:checked + .slider::after { transform: translateX(20px); }
.switch input:focus-visible + .slider { box-shadow: var(--ring); }

.setting-status {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin: 14px 0 4px;
  padding: 12px 14px;
  background: var(--item-hover);
  border-radius: var(--radius-md);
  font-size: 12.5px;
}
.setting-status > div { display: flex; justify-content: space-between; gap: 10px; }
.setting-status span { color: var(--text-hint); }
.setting-status b { color: var(--text-main); font-weight: 600; font-variant-numeric: tabular-nums; }
.setting-status b.ok { color: var(--success); }
.setting-status b.bad { color: var(--danger); }
.setting-status b.is-warn { color: #d97706; }

.setting-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; }

/* ------------------------------------------------------- message context menu */
/* Opened by right-click or long-press. Fixed-positioned and JS-clamped to the
   viewport, so it works identically on desktop and on a phone.

   z-index sits just below the alert dialog (10002) on purpose: the alert is
   raised by toasts and error paths that can fire while a menu is open, and it
   must be the one thing on top. */
.msg-menu {
  position: fixed;
  z-index: 9001;
  min-width: 168px;
  padding: 5px;
  border-radius: var(--radius-lg);
  background: var(--sidebar-bg);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-lg);
  display: none;
  /* The menu is a transient overlay: text inside must stay selectable so a
     mis-tap can still be copied, but the icons must not be. */
  user-select: none;
  -webkit-user-select: none;
  animation: popIn 0.14s var(--ease);
}
.msg-menu.is-open { display: block; }
.msg-menu[hidden] { display: none !important; }

.msg-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-main);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.msg-menu-item:hover,
.msg-menu-item:focus-visible {
  background: var(--item-hover);
  outline: none;
}
.msg-menu-item .ic { color: var(--text-hint); }
.msg-menu-item:hover .ic { color: var(--accent-strong); }

/* Touch targets need to be comfortably tappable. */
@media (max-width: 768px) {
  .msg-menu { min-width: 190px; }
  .msg-menu-item { padding: 12px 13px; font-size: 15px; }
}

/* ------------------------------------------------------------------ toasts */
#toast-container {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  z-index: 9000;
  pointer-events: none;
}
.toast {
  background: rgba(23, 28, 38, 0.94);
  color: #fff;
  padding: 10px 16px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease);
  max-width: min(92vw, 460px);
  text-align: center;
  /* icon + label now live side by side, so centre the pair as a row */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.toast > .ic { width: 16px; height: 16px; opacity: 0.95; }
.toast > span { min-width: 0; }
.toast.in { opacity: 1; transform: none; }
.toast-success { background: rgba(21, 128, 61, 0.95); }
.toast-error { background: rgba(185, 28, 28, 0.95); }

/* ------------------------------------------------------------ compact mode */
body.compact #chat-window { gap: 0; padding-top: 12px; }
body.compact .msg { max-width: min(86%, 720px); }
body.compact .bubble { padding: 4px 10px 3px; font-size: 14px; }
body.compact .msg + .msg { margin-top: 0; }
body.compact .avatar-col { flex-basis: 30px; }
body.compact .avatar { width: 30px; height: 30px; font-size: 13px; }
body.compact .date-separator { margin: 8px 0 5px; }

/* ------------------------------------------------------------- scrollbars */
#chat-window, #room-list, .card {
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 150, 0.35) transparent;
}
#chat-window::-webkit-scrollbar, #room-list::-webkit-scrollbar, .card::-webkit-scrollbar { width: 8px; height: 8px; }
#chat-window::-webkit-scrollbar-thumb, #room-list::-webkit-scrollbar-thumb, .card::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 150, 0.3);
  border-radius: 999px;
}
#chat-window::-webkit-scrollbar-track, #room-list::-webkit-scrollbar-track { background: transparent; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------- dark mode */
[data-theme="dark"] .msg.sent .bubble { background: #1f3b34; }
[data-theme="dark"] .msg.received .bubble { background: #1d222b; }
[data-theme="dark"] .msg-meta,
[data-theme="dark"] .last-time,
[data-theme="dark"] .room-sub { color: #8b97a8; }
[data-theme="dark"] .msg-action { background: #262d3a; }
[data-theme="dark"] .reply-preview { background: rgba(255, 255, 255, 0.05); }
[data-theme="dark"] .composer-box { background: #1c222c; }
[data-theme="dark"] .composer-box:focus-within { background: #1a2029; }
[data-theme="dark"] .settings-card,
[data-theme="dark"] .card { background: #1b2029; }
[data-theme="dark"] .setting-row { border-color: #262d3a; }
[data-theme="dark"] .setting-status,
[data-theme="dark"] .setting-control { background: #222836; }
[data-theme="dark"] .net-status { color: #ffb4ab; background: rgba(229, 72, 77, 0.18); }
[data-theme="dark"] .msg.sent .bubble { border-bottom-right-radius: 6px; }
[data-theme="dark"] .load-more { background: #1b2029; border-color: #2b3342; }
[data-theme="dark"] .date-separator {
  background: rgba(255, 255, 255, 0.12);
  color: #e8edf5;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
}

/* ------------------------------------------------------------------ mobile */
@media (max-width: 768px) {
  /* Full-bleed on a phone. max-width has to be reset as well as width: the
     resizer's 560px cap would otherwise shrink the overlay on a tablet. */
  #sidebar { width: 100%; min-width: 100%; max-width: none; }
  header { padding: 8px 10px; }
  footer { padding: 8px 10px calc(8px + env(safe-area-inset-bottom)); }
  #chat-window { padding: 12px 10px 16px; }
  .msg { max-width: 88%; }
  .bubble .text { max-width: 60dvw; }
  #scroll-badge { right: 12px; }
  .room-actions { padding: 10px 12px 4px; }
  .room-item { padding: 10px; }
  /* The header runs out of room on a phone, so the retention chip starts
     collapsed to just the clock. Pressing it expands it in place to reveal the
     expiry text - no modal, no second screen - and it collapses again on
     release. It is a real <button>, so `:active` is scoped to the chip itself
     (pressing the room name must not expand it) and keyboard focus works too. */
  .header-info .room-title { min-width: 0; }
  .header-actions-right .room-ttl {
    max-width: 36px;
    padding: 0;
    justify-content: center;
    overflow: hidden;
    /* max-width animates cleanly and keeps the collapsed pill exactly icon
       sized, so the widened label never shoves the other header buttons. */
    transition: max-width 0.22s var(--ease), padding 0.22s var(--ease),
                background 0.15s var(--ease), color 0.15s var(--ease);
  }
  .header-actions-right .room-ttl .ttl-text { display: none; }
  /* Pressed (touch) or keyboard-focused - both reveal the label. */
  .header-actions-right .room-ttl:active,
  .header-actions-right .room-ttl:focus-visible {
    max-width: 120px;
    padding: 0 9px;
    justify-content: flex-start;
    background: var(--item-hover);
    color: var(--text-main);
    z-index: 2;
  }
  .header-actions-right .room-ttl:active .ttl-text,
  .header-actions-right .room-ttl:focus-visible .ttl-text { display: inline; }
  /* Full-bleed bottom sheet: the id selector beats the generic .card rule
     further down this media block, which used to shrink it back. */
  #settings-modal .card { width: 100%; max-width: 100%; border-radius: var(--radius-xl) var(--radius-xl) 0 0; align-self: flex-end; max-height: calc(var(--app-h, 100dvh) - 24px); }
  /* The sheet is a full-bleed bottom sheet and its content is taller than a
     phone screen, so only .settings-body scrolls (see .settings-card above).
     The header stays pinned, keeping the Back / Close buttons reachable. */
  #settings-modal .modal-head { padding: 14px 24px 10px; margin-bottom: 8px; }
  .setting-row { padding: 12px 0; }
  .setting-label small { display: none; }
  /* Touch devices have no hover: long-press (contextmenu) reveals the actions. */
  .msg-actions { display: none; }
  /* …but a failed message must always offer its retry. */
  .msg.status-failed .msg-actions,
  .msg.is-failed .msg-actions {
    display: flex;
    opacity: 1;
    pointer-events: auto;
    position: static;
    margin-top: 6px;
  }
  /* A long-pressed bubble used to push the next message down: this rule made the
     action row `position: static`, so revealing it added real height to the
     bubble and shoved the whole conversation down under the reader's thumb. It
     now overlays instead.

     A FAILED message is excluded on purpose - its Retry button is the one
     control that must stay readable, and `.msg.status-failed` already reserves
     the extra height (see the margin rule above), so revealing it in flow is
     correct there. */
  .bubble.show-actions .msg-actions:not(.msg.status-failed .msg-actions) {
    position: absolute;
    margin-top: 0;
    top: -14px;
  }
  .card { width: calc(100vw - 24px); }
}
/* Long-press / right-click opens a bubble's actions. The display rule is
   repeated here because touch devices hide .msg-actions with display:none. */
.bubble.show-actions .msg-actions {
  display: flex;
  opacity: 1;
  pointer-events: auto;
}

/* Compact-mode density toggle eases the properties it changes. */
.msg, .avatar, .avatar-col, .date-separator {
  transition: margin 0.18s var(--ease), width 0.18s var(--ease),
    height 0.18s var(--ease), font-size 0.18s var(--ease),
    max-width 0.18s var(--ease), flex-basis 0.18s var(--ease);
}
.bubble {
  transition: box-shadow 0.2s var(--ease), transform 0.15s var(--ease),
    padding 0.18s var(--ease), font-size 0.18s var(--ease);
}

/* Touch devices: bigger targets (platform guidance is 44px). */
@media (pointer: coarse) {
  .icon-btn { width: 44px; height: 44px; }
  .emoji-option { padding: 8px 10px; font-size: 22px; }
  .msg-action { width: 40px; height: 40px; font-size: 15px; }
  .msg-retry { width: auto; padding: 0 12px; }
}

/* ===================================================================== ICONS ==
 * One rule set for every glyph in the app. Icons are stroke-drawn and inherit
 * `currentColor`, so they re-theme automatically and never clash with the
 * light/dark palettes. Sizes are set here rather than baked into the SVG.
 * =========================================================================== */
svg.ic {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  overflow: visible;
  /* Icons should never be selected by accident while copying message text. */
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

/* Size scale. `ic` alone = 20px (the default). */
.ic-xs { width: 13px; height: 13px; stroke-width: 2.4; }
.ic-sm { width: 16px; height: 16px; stroke-width: 2.2; }
.ic-md { width: 22px; height: 22px; }
.ic-lg { width: 28px; height: 28px; }
.ic-xl { width: 40px; height: 40px; stroke-width: 1.8; }
.ic-hero { width: 60px; height: 60px; stroke-width: 1.4; }

/* Slightly heavier strokes read better once the glyph is large. */
.ic-lg, .ic-xl, .ic-hero { stroke-linecap: round; stroke-linejoin: round; }

/* The send arrow reads better as a solid triangle than an outline. */
.ic-solid { fill: currentColor; stroke: none; }

/* Buttons that contain only an icon centre it on both axes and stop the
   browser from adding its own inline-text baseline gap. */
.icon-btn > svg.ic,
.composer-tool > svg.ic,
/* Flex centring above already handles this; `margin: auto` here fought it. */
.msg-action > svg.ic {
  margin: 0;
  display: block;
}

/* Accent-tinted slots so key actions pop consistently across the app. */
.tone-accent { color: var(--accent-strong); }
.tone-danger { color: var(--danger); }
.tone-success { color: var(--success); }
.tone-muted  { color: var(--text-hint); }

/* Empty / welcome states: a soft tinted disc behind the glyph looks far more
   considered than a bare floating emoji. */
.icon-disc {
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  margin: 0 auto 10px;
  border-radius: 50%;
  color: var(--accent-strong);
  background: rgba(15, 138, 216, 0.10);
}
.icon-disc .ic { width: 34px; height: 34px; stroke-width: 1.7; }
.icon-disc-sm { width: 56px; height: 56px; margin-bottom: 8px; }
.icon-disc-sm .ic { width: 26px; height: 26px; }
.icon-disc-danger { color: var(--danger); background: rgba(229, 72, 77, 0.10); }
.icon-disc-success { color: var(--success); background: rgba(38, 166, 91, 0.12); }
[data-theme="dark"] .icon-disc { background: rgba(15, 138, 216, 0.16); }
[data-theme="dark"] .icon-disc-danger { background: rgba(229, 72, 77, 0.18); }
[data-theme="dark"] .icon-disc-success { background: rgba(38, 166, 91, 0.18); }

/* Legacy emoji placeholders are now icons - keep the old class names working
   so the JS markup changes stay small and the spacing is unchanged. */
.welcome-emoji { font-size: 44px; line-height: 1; }
.empty-emoji { font-size: 34px; }
.room-avatar > svg.ic { width: 19px; height: 19px; opacity: 0.9; }

/* Buttons with an inline icon + label (Scan QR, Save QR, Upload…). */
.btn-icon-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}
.btn-icon-label .ic { width: 17px; height: 17px; }

/* Headings that carry an icon, e.g. the logout "Danger Zone" card. */
.card-title-with-icon {
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-title-with-icon .ic { width: 19px; height: 19px; color: var(--danger); }

/* The dial pad's backspace key is sized by .dial-btn-backspace in the dial-pad
   block; the old 21px override that shrank the icon is gone. */

/* Message status ticks sit inline with the timestamp, so they need to be
   aligned to the text baseline rather than centred in a block. */
.msg-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: -2px;
}
.msg-status .ic { width: 13px; height: 13px; stroke-width: 2.4; }
.msg-status.status-failed .ic { stroke-width: 2.6; }

/* "Load older messages" and the "N new" scroll badge. */
.load-more .ic,
#scroll-badge .ic {
  display: inline-block;
  vertical-align: -3px;
  margin-right: 4px;
  width: 15px;
  height: 15px;
  stroke-width: 2.4;
}
#scroll-badge .ic { margin-right: 3px; }

/* The in-chat search up/down arrows. */
.inchat-count + .icon-btn .ic,
#inchat-search .icon-btn .ic { width: 17px; height: 17px; }

/* Mobile back button in the chat header. */
.mobile-only .ic { width: 22px; height: 22px; }

/* Touch targets grow the glyph a little so it stays legible. */
@media (max-width: 768px) {
  .icon-btn > svg.ic, .composer-tool > svg.ic { width: 22px; height: 22px; }
  .msg-action > svg.ic { width: 17px; height: 17px; }
}



/* ============================================================================
 * APPEARANCE: accent colour, text scale, density
 * --accent / --accent-strong / --sent-bubble are written onto <html> by
 * applyAccent(), so they win over the :root defaults without !important.
 * --font-scale is written by applyDensity() and used as a multiplier.
 * ========================================================================== */
:root { --font-scale: 1; }

/* Text scale. Each reading surface is multiplied against its own size, so
   nothing else on the page shifts when the slider moves. */
body { font-size: calc(14px * var(--font-scale)); }
.bubble .text { font-size: calc(14.5px * var(--font-scale)); }
.bubble .u-name { font-size: calc(13px * var(--font-scale)); }
.room-item b { font-size: calc(14.5px * var(--font-scale)); }
.last-msg { font-size: calc(13px * var(--font-scale)); }
.last-time { font-size: calc(11px * var(--font-scale)); }
#msg-input { font-size: calc(14.5px * var(--font-scale)); }
.room-title .room-name { font-size: calc(15.5px * var(--font-scale)); }
.room-title .room-sub { font-size: calc(12.5px * var(--font-scale)); }
.welcome-card h2 { font-size: calc(20px * var(--font-scale)); }
.welcome-card p, .welcome-card li { font-size: calc(13.5px * var(--font-scale)); }
.msg-menu-item { font-size: calc(14px * var(--font-scale)); }

/* A third density step on top of the existing cozy/compact pair. */
body.density-ultra #chat-window { gap: 0; padding-top: 8px; }
body.density-ultra .bubble { padding: 3px 9px 2px; }
body.density-ultra .msg + .msg { margin-top: 0; }
body.density-ultra .avatar-col { flex-basis: 26px; }
body.density-ultra .avatar { width: 26px; height: 26px; font-size: 12px; }
body.density-ultra .date-separator { margin: 6px 0 4px; padding: 3px 12px; }
body.density-ultra .msg-time { font-size: 10.5px; }

/* ======================================================== COMPACT SETTINGS ==
 * The sheet is a set of short sections. Each row is one line: a fixed-width
 * label on the left, the control on the right. The old layout gave every
 * option a full-width paragraph, which made the modal taller than the screen
 * on a phone.
 * ========================================================================== */
.set-group { margin-bottom: 14px; }
.set-group-title {
  margin: 0 0 4px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-hint);
}
.set-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 34px;
  padding: 5px 0;
  border-bottom: 1px solid var(--border-color);
}
.set-group .set-row:last-child { border-bottom: 0; }
.set-k {
  font-size: 13.5px; color: var(--text-main);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.set-row .setting-control { flex: 0 0 auto; min-width: 128px; }
.set-row .switch { flex: 0 0 auto; }
/* A row that needs a second line (a long note, or the accent grid). */
.set-row-block { flex-wrap: wrap; align-items: center; }
.set-row-block .set-k { flex: 1 0 100%; margin-bottom: 4px; }
/* Same idea, but the label and control STAY on one line and only the
   explanatory note wraps underneath - used by Notifications. */
.set-row-note { flex-wrap: wrap; }
.set-row-note .set-note { flex: 1 0 100%; }
.set-note {
  display: block; margin-top: 3px;
  font-size: 11.5px; line-height: 1.4; color: var(--text-hint);
}
.set-control { display: flex; align-items: center; gap: 9px; }
.set-control-range input[type="range"] { width: 132px; accent-color: var(--accent); }
.set-control-range output {
  font-size: 12px; color: var(--text-hint); min-width: 40px; text-align: right;
  font-variant-numeric: tabular-nums;
}
.set-more { margin-bottom: 12px; }
.set-more > summary {
  cursor: pointer; list-style: none;
  font-size: 13px; font-weight: 600; color: var(--accent-strong);
  padding: 6px 0;
}
.set-more > summary::-webkit-details-marker { display: none; }
.set-more > summary::before { content: '▸ '; }
.set-more[open] > summary::before { content: '▾ '; }
.set-more .shortcut-list { margin-top: 4px; }

/* Cheat sheet for the Formatting switch in Settings. */
.rich-help {
  flex: 1 0 100%;
  font-size: 11.5px; line-height: 1.55; color: var(--text-hint);
  margin: 2px 0 8px;
}
.rich-help code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.94em;
  background: var(--item-hover);
  border: 1px solid var(--border-color);
  border-radius: 4px;
  padding: 0 4px;
  white-space: nowrap;
}

/* ======================================================= HEARTS (reaction) ==
 * One reaction: a heart, as a plain integer counter on the message row. It
 * arrives with the message, so the badge needs no extra request. Hearts are
 * anonymous: nothing here says who gave one.
 * ======================================================================== */
.msg-action.msg-heart.is-hearted { color: #e5484d; }
.msg-action.msg-heart.is-hearted .ic { fill: #e5484d; }
.msg-menu-item.is-hearted .ic { color: #e5484d; fill: #e5484d; }

/* The count sits under the bubble, aligned to the same side as the text. */
.heart-count {
  display: inline-flex; align-items: center; gap: 3px;
  margin-top: 3px; padding: 1px 7px;
  border-radius: 999px;
  background: var(--item-hover);
  color: var(--text-hint);
  font-size: 11px; font-weight: 600; line-height: 1.5;
  font-variant-numeric: tabular-nums;
  user-select: none; -webkit-user-select: none;
}
[data-theme="dark"] .heart-count { background: rgba(255, 255, 255, 0.09); }
/* A lone heart: glyph only, no "1 heart" text. The count is still in the
   tooltip and the aria-label. */
.heart-count.is-single { padding: 1px 6px; color: #e5484d; }
.heart-count.is-single .ic { stroke: currentColor; fill: currentColor; }
.msg.sent .heart-count { margin-left: auto; }

/* Header line of the long-press / right-click menu: the heart count. */
.msg-menu-head {
  padding: 7px 13px 6px;
  border-bottom: 1px solid var(--border-color);
  color: var(--text-hint);
  font-size: 12px; font-weight: 700;
  white-space: nowrap;
}
.msg-menu-item:first-of-type { margin-top: 4px; }

/* Accent swatch grid in Settings. */
.accent-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 8px; width: 100%;
}
.swatch {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 9px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: var(--item-hover); color: var(--text-main);
  cursor: pointer; font-size: 12.5px; font-weight: 600;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.swatch:hover { background: var(--sidebar-bg); }
.swatch.is-on { border-color: var(--sw); }
.swatch-dot {
  width: 17px; height: 17px; flex: 0 0 17px; border-radius: 50%;
  background: var(--sw); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
}
.swatch.is-on .swatch-dot { background: var(--sw-strong); }
.setting-row-block { flex-direction: column; align-items: stretch; gap: 10px; }
.setting-control-range { display: flex; align-items: center; gap: 10px; }
.setting-control-range input[type="range"] { flex: 1; min-width: 120px; accent-color: var(--accent); }
.setting-control-range output {
  font-size: 12.5px; color: var(--text-hint); min-width: 42px;
  text-align: right; font-variant-numeric: tabular-nums;
}

/* ============================================================================
 * STARRED MESSAGES
 * ========================================================================== */
.msg-action.is-starred { color: #f0a30a; }
.msg-action.is-starred .ic { fill: #f0a30a; }
.msg-menu-item.is-starred .ic { color: #f0a30a; fill: #f0a30a; }

/* Room context menu: reuses .msg-menu, so only the extras live here. */
.msg-menu-item.is-on { color: var(--text-main); }
.msg-menu-item.is-on .ic { color: var(--accent-strong); fill: var(--accent-strong); }
/* A disabled entry ("Mark as read" with nothing unread) must not look tappable. */
.msg-menu-item:disabled,
.msg-menu-item:disabled .ic { color: var(--text-hint); opacity: 0.45; cursor: default; }
.msg-menu-item:disabled:hover { background: transparent; }
/* The row whose menu is open stays highlighted. */
.room-item.show-room-menu { background: var(--item-active); }
/* Suppress the iOS callout so a long-press opens our menu, not the system one. */
@media (hover: none) {
  .room-item { -webkit-touch-callout: none; }
}

.starred-panel {
  position: fixed; inset: 0; z-index: 8000;
  display: none; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(10, 14, 20, 0.55);
}
.starred-panel.is-open { display: flex; }
.starred-card {
  width: min(560px, 100%); max-height: min(76vh, 640px);
  display: flex; flex-direction: column;
  background: var(--sidebar-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.starred-list { overflow-y: auto; padding: 6px 8px 14px; }
/* The card has no .card padding of its own, so the header needs its own inset. */
.starred-card .modal-head {
  padding: 16px 18px 12px;
  margin-bottom: 0;
  border-bottom: 1px solid var(--border-color);
  flex: 0 0 auto;
}
.starred-card .modal-head h3 { font-size: 16px; font-weight: 700; color: var(--text-main); }
.starred-empty {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 42px 22px; text-align: center; color: var(--text-hint);
}
.starred-empty .ic { color: #f0a30a; }
.starred-empty b { color: var(--text-main); font-size: 15px; }
.starred-empty span { font-size: 13px; line-height: 1.5; }
.starred-group { margin-top: 10px; }
.starred-group-head {
  display: flex; align-items: center; gap: 5px;
  padding: 6px 10px 5px; font-size: 12px; font-weight: 700;
  color: var(--text-hint); text-transform: uppercase; letter-spacing: 0.04em;
  position: sticky; top: 0; background: var(--sidebar-bg); z-index: 1;
}
.starred-group-head .ic { color: var(--accent); }
.starred-row {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 9px 11px; margin-bottom: 4px;
  border: 0; border-radius: var(--radius-md);
  background: var(--item-hover); color: var(--text-main);
  transition: background 0.15s var(--ease);
}
.starred-row:hover { background: var(--item-active); }
.starred-row-user {
  display: block; font-size: 12.5px; font-weight: 700;
  color: var(--accent-strong); margin-bottom: 2px;
}
.starred-row-text {
  display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
  font-size: 13.5px; line-height: 1.4;
}
.starred-row-text i { color: var(--text-hint); }
.icon-btn.is-on { color: var(--accent); background: var(--item-hover); }


/* ============================================================================
 * IMAGE LIGHTBOX
 * ========================================================================== */
.lightbox {
  position: fixed; inset: 0; z-index: 9500;
  display: none; align-items: center; justify-content: center;
  background: rgba(6, 9, 14, 0.94);
  padding: 64px 60px 74px;
}
.lightbox.is-open { display: flex; }
body.lightbox-open { overflow: hidden; }
.lightbox-img {
  max-width: 100%; max-height: 100%; object-fit: contain;
  border-radius: 6px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.lightbox-btn {
  position: absolute;
  display: grid; place-items: center;
  width: 42px; height: 42px; padding: 0;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.12); color: #fff; cursor: pointer;
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
}
.lightbox-btn:hover { background: rgba(255, 255, 255, 0.24); }
.lightbox-btn:active { transform: scale(0.94); }
.lightbox-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.lightbox-close { top: 16px; right: 16px; }
.lightbox-nav { top: 50%; transform: translateY(-50%); }
.lightbox-nav:active { transform: translateY(-50%) scale(0.94); }
.lightbox-prev { left: 14px; }
.lightbox-next { right: 14px; }
.lightbox-bar {
  position: absolute; left: 50%; bottom: 18px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px;
  padding: 6px 8px 6px 16px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff; font-size: 13px; font-variant-numeric: tabular-nums;
}
.lightbox-count { min-width: 18px; text-align: center; }
.lightbox-download { position: static; width: 34px; height: 34px; }


/* ============================================================================
 * SWIPE TO REPLY
 * The cue sits behind the bubble and fades in as the drag nears the trigger
 * distance, so the gesture is legible before the finger lifts.
 * ========================================================================== */
.bubble { position: relative; }

/* Long-press on a phone must open THIS app's menu, not the operating system's.
   `-webkit-touch-callout: none` removes the iOS text-selection callout (the
   magnifier plus the Copy/Paste bubble) that otherwise pops up over the message
   for a frame before the app's own menu replaces it.

   It deliberately does NOT touch `user-select`, which stays `text` on message
   bodies (see the selection block at the top of this file). A reader can still
   drag out a sentence and copy it; they just no longer get a second, competing
   menu the instant they press and hold. */
.bubble, .bubble * { -webkit-touch-callout: none; }

.bubble.is-swiping { transition: none; will-change: transform; }
.bubble:not(.is-swiping) { transition: transform 0.18s var(--ease); }
.bubble .swipe-cue {
  position: absolute; top: 50%; left: 8px;
  transform: translateY(-50%) scale(0.7);
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: #fff;
  opacity: 0; pointer-events: none;
  transition: opacity 0.14s var(--ease), transform 0.14s var(--ease);
}
.bubble .swipe-cue .ic { width: 16px; height: 16px; }
.bubble.is-swiping .swipe-cue { opacity: 0.75; transform: translateY(-50%) scale(1); }
.bubble.is-swipe-armed .swipe-cue { opacity: 1; transform: translateY(-50%) scale(1.12); }


/* ============================================================================
 * ROOM LIST: tabs, drag-to-reorder, per-room tools, state flags
 * ========================================================================== */
.room-tabs {
  display: flex; gap: 4px; padding: 4px 14px 8px;
  border-bottom: 1px solid var(--border-color);
}
.room-tab {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 8px; border: 0; border-radius: var(--radius-md);
  background: transparent; color: var(--text-hint);
  font-size: 13px; font-weight: 600; cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.room-tab:hover { background: var(--item-hover); color: var(--text-main); }
.room-tab.is-on { background: var(--item-active); color: var(--accent-strong); }
.room-tab-count {
  min-width: 19px; padding: 0 5px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.07); color: inherit;
  font-size: 11px; font-weight: 700; line-height: 17px; text-align: center;
}
.room-tab.is-on .room-tab-count { background: var(--accent); color: #fff; }
[data-theme="dark"] .room-tab-count { background: rgba(255, 255, 255, 0.12); }

/* The empty-state card is now driven by JS (it also covers the empty archive). */
.sidebar-empty { display: none; }
.sidebar-empty.is-visible { display: flex; }

/* The per-row pin / mute / archive buttons were removed from the row markup:
   they are reachable from the right-click (or long-press) room menu, which is
   the only place they live now. That also frees the strip of blank space the
   hover pill used to reserve on the right edge of every row. */
.room-tools,
.room-tool { display: none; }
/* Compact: the three controls are deliberately small and tightly packed so the
   pinned indicator and the room name keep their room. */
.room-tool {
  display: grid; place-items: center;
  width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--text-hint); cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.room-tool:hover { background: var(--item-hover); color: var(--accent-strong); }
.room-tool:active { transform: scale(0.9); }
.room-tool .ic { width: 14px; height: 14px; pointer-events: none; }

/* Persistent state flags, aligned with the FIRST line of the room name.
   The pin used to sit inside the <b> before the name, where `display: grid`
   made it block-level and pushed the name onto a second line. It now lives in
   its own flex column and is nudged down to the optical centre of the name
   line rather than being centred against the whole two-line row. */
.room-flags {
  display: flex; align-items: center; gap: 4px; flex: 0 0 auto;
  align-self: flex-start;
  /* The row's first text line starts ~4px below the content box top; this puts
     a 13px glyph on the same optical line as the 14.5px name. */
  margin-top: 3px;
  transition: opacity 0.15s var(--ease);
}
.room-flag { display: grid; place-items: center; color: var(--text-hint); line-height: 0; }
.room-item.is-muted .room-flag { color: #e0930c; }
.room-item.is-pinned .room-pin-flag { color: var(--accent); }
.room-item.is-archived { opacity: 0.72; }
.room-item.is-muted .room-avatar { filter: saturate(0.45); }
[data-theme="dark"] .room-item.is-archived { opacity: 0.6; }
.room-item:hover .badge { opacity: 0; }
.room-muted-tag { display: inline-flex; vertical-align: -1px; color: #e0930c; margin-left: 3px; }

/* Touch: the row tools are gone (the room context menu covers pin / mute /
   archive), so there is nothing extra to reveal and no reserved edge space. */
@media (hover: none) {
  .room-item { padding-right: 10px; }
}

/* ============================================================ IMAGE UPLOADS ==
   Drop zone, upload tray and the pre-send preview added on top of the old
   blocking "Image uploading..." alert. The tray sits above the composer so a
   queued picture is visible while the user keeps typing.
   ========================================================================== */

#upload-tray {
  display: flex;
  gap: 8px;
  padding: 8px 12px 0;
  overflow-x: auto;
  scrollbar-width: thin;
}
#upload-tray[hidden] { display: none; }

.upload-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  flex: 0 0 auto;
  max-width: 260px;
  padding: 6px 8px;
  border-radius: 12px;
  background: var(--sidebar-bg);
  box-shadow: var(--shadow);
  animation: cardIn 0.18s var(--ease);
}
.upload-item.is-done { border: 1px solid var(--success); }
.upload-item.is-failed { border: 1px solid var(--danger); }
.upload-item.is-failed .upload-bar i { background: var(--danger); }

.upload-thumb {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  border-radius: 9px;
  overflow: hidden;
  background: var(--item-hover);
  display: grid;
  place-items: center;
}
.upload-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.upload-thumb-fallback { color: var(--text-hint); line-height: 0; }

.upload-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}
.upload-name {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-main);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.upload-size { font-size: 11px; color: var(--text-hint); }

.upload-bar {
  height: 3px;
  border-radius: 3px;
  background: var(--item-hover);
  overflow: hidden;
}
.upload-bar i {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 3px;
  background: var(--accent, var(--tg-blue));
  transition: width 0.15s linear;
}

.upload-cancel {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--text-hint);
  cursor: pointer;
  padding: 3px;
  border-radius: 50%;
  line-height: 0;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.upload-cancel:hover { color: var(--danger); background: var(--item-hover); }

/* ====================================================== SHARED MODAL PIECES ==
   These used to be inline `style="..."` attributes scattered across
   partials/modals/*.php. Three of them were outright bugs: room.php's Cancel
   button declared `color:#666` and then `color: var(--text-main)` and then a
   third time `color: var(--item-hover)` in the same attribute, so the winning
   colour depended on declaration order rather than intent; and profile.php's
   Danger Zone copy carried literal `**markdown**` that rendered as asterisks,
   because that text is never run through the message renderer.
   Everything below is now one named class per intent, so a theme change or a
   dark-mode fix is a single edit and cannot half-apply. */
.btn-ghost {
  width: 100%;
  margin-top: 5px;
  padding: 9px 8px;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-hint);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.btn-ghost:hover { background: var(--item-hover); color: var(--text-main); }

/* The DESTRUCTIVE pair: a solid red fill for "this destroys data", and an
   outlined variant for the entry point in a screen that is not about danger. */
.btn-danger {
  width: 100%;
  padding: 11px 16px;
  margin-bottom: 10px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--danger, #e5484d);
  color: #fff;
  font-weight: 600;
  font-size: 14.5px;
  cursor: pointer;
  transition: filter 0.15s var(--ease);
}
.btn-danger:hover { filter: brightness(0.94); }
.btn-danger-outline {
  width: 100%;
  margin-top: 10px;
  padding: 9px 8px;
  background: none;
  border: 1px solid var(--danger, #e5484d);
  border-radius: var(--radius-sm);
  color: var(--danger, #e5484d);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.btn-danger-outline:hover { background: rgba(229, 72, 77, 0.12); }

/* Full-bleed primary used for "Go" / "Confirm" inside a narrow card. */
.btn-wide { width: 100%; margin-top: 10px; }

/* A note paragraph. `tone` is the only variable: hint (secondary), plain
   (default body colour) or danger. */
.modal-note { font-size: 14px; line-height: 1.45; margin-bottom: 20px; color: var(--text-hint); }
.modal-note-tight { font-size: 12.5px; line-height: 1.45; margin-bottom: 8px; }
.modal-note-sm { font-size: 12px; line-height: 1.45; margin-bottom: 15px; }
.modal-note-plain { color: var(--text-main); font-size: 15px; margin-bottom: 20px; }
/* The Danger Zone warning. The old copy wrapped the key phrase in `**...**`,
   which is markdown and rendered as literal asterisks outside the message
   renderer. It is now real emphasis. */
.modal-note-danger { color: var(--text-main); font-size: 15px; margin-bottom: 20px; }
.modal-note-danger strong { color: var(--danger, #e5484d); font-weight: 700; }

/* A full-width field inside a modal card. */
.field-block { width: 100%; }
.field-block-spaced { width: 100%; margin-bottom: 10px; }
.field-block-last { width: 100%; margin-bottom: 20px; }

/* The small local-storage meter in the profile sheet. */
.storage-meter {
  text-align: center;
  margin-bottom: 10px;
  background: var(--item-hover);
  font-size: 13px;
  padding: 10px;
  border-radius: var(--radius-sm);
}
.storage-meter.is-warn { color: #f5a623; }
.storage-meter.is-critical { color: var(--danger, #e5484d); }

/* A row of buttons. */
.btn-row { display: flex; gap: 10px; }
/* A vertical stack of buttons. */
.btn-stack { display: flex; flex-direction: column; gap: 8px; }
/* A block element hidden from sight but still announced by a screen reader. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Card widths. Was `style="width: 320px"` / `350px` / `460px` in the partials. */
.card-w-xs { width: 280px; }
.card-w-sm { width: 320px; }
.card-w-md { width: 350px; }
.card-w-lg { width: 460px; max-width: 94vw; }

/* Scrollable region inside a members card. The card holds the scroll area; the
   <ul> it contains only resets its own list styling. */
.member-list {
  max-height: 400px;
  overflow-y: auto;
  text-align: left;
  margin-bottom: 20px;
}
.member-list ul { list-style: none; padding: 0; margin: 0; }
.member-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--item-hover);
}
.member-row:last-child { border-bottom: 0; }
.member-name { color: var(--text-main); font-weight: 600; }
.member-joined { color: var(--text-hint); text-align: right; white-space: nowrap; }
.member-empty, .member-error { text-align: center; margin: 0; }

/* The white plate the QR canvas is drawn on. */
.qr-plate {
  background: #fff;
  padding: 10px;
  display: inline-block;
  margin-bottom: 15px;
  border-radius: var(--radius-md);
  max-width: 100%;
}

/* A label wrapping a hidden file input, styled as a button. */
.file-drop-label {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

/* The room form's join/create radios. They carry state for openRoomModal()
   and toggleTTL() and have no user-facing control of their own, so they are
   display:none rather than .visually-hidden: a visually hidden input still
   occupies a box, which made it the first focusable thing in the dialog and
   put keyboard focus on an invisible radio instead of the room-name field. */
.mode-radio { display: none; }

/* The room form's lifespan select is shown for "create" and hidden for
   "join". It used to be hidden with an inline `style="display:none"` that
   openRoomModal() and toggleTTL() then overwrote from JS, so the hidden state
   lived in a PHP string and the visible state lived in two JS call sites.
   Both are now this one class. */
#setup-ttl {
  display: none;
  width: 100%;
  padding: 10px;
  margin-top: 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  background: var(--sidebar-bg);
  color: var(--text-main);
}
#setup-ttl.is-shown { display: block; }
[data-theme="dark"] #setup-ttl { border-color: #2b3340; }

/* ======================================================= FOCUS MANAGEMENT ==
   The overlays are plain .modal divs and are opened by poking
   `style.display` from a dozen different call sites, so the focus rules live
   in one place in JS (focusTrap.js section) rather than in each open path.

   Two pieces are needed on the CSS side:
     1. `inert` on the page behind a dialog. That is an attribute, not a class,
        so the only styling is a guard: older engines without `inert` support
        still get the Tab trap from JS, they just do not get the click shield.
     2. A visible ring on a focused dialog, so a keyboard user can always see
        where they are inside a sheet that scrolled. */
.modal[aria-hidden="true"] { display: none !important; }
.modal:focus { outline: none; }
/* Only draw a ring around the dialog itself when it was focused as a
   container (no inner control took the focus). */
.modal:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

/* The subtitle on the unlock screen. Was an inline style, and it needs to be
   overridable per theme like everything else. */
.unlock-sub { font-size: 14px; color: var(--text-hint); margin: -15px 0 20px; }

/* The avatar grid is a radiogroup now (see profile.php): one tile is the
   roving tab stop, the rest are reached with the arrow keys. */
.emoji-grid [role="radio"] { cursor: pointer; border-radius: var(--radius-sm); }
.emoji-grid [role="radio"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The alert is the one dialog that must sit above everything, including the
   settings sheet, so it carries its own stacking level as a class rather than an
   inline z-index. */
.modal-alert { z-index: 10002; }

/* The `hidden` attribute is the single source of truth for "this control does
   not apply right now". A UA `display:none` alone loses to an inline
   `style.display` set from JS, which is how the Logout button used to end up
   visible in a state that was supposed to hide it. */
[hidden] { display: none !important; }

/* The resizer is a <button>, which inherits the global button reset; make sure
   it stays a 7px strip and never grows a UA border or focus ring offset. */
#sidebar-resizer { -webkit-appearance: none; appearance: none; }

/* Drop overlay: covers the chat pane, sits under the header and the composer. */
#drop-overlay {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: none;
  place-items: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.34);
  backdrop-filter: blur(2px);
  pointer-events: none;
}
#main-chat.is-dragging #drop-overlay { display: grid; }

.drop-card {
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 26px 34px;
  border-radius: 18px;
  border: 2px dashed rgba(255, 255, 255, 0.85);
  background: rgba(0, 0, 0, 0.28);
  color: #fff;
  text-align: center;
}
.drop-card b { font-size: 16px; }
.drop-card span { font-size: 12.5px; opacity: 0.85; }
