:root {
  --green: #00a884;
  --green-dark: #008069;
  --green-light: #e7f8f3;
  --accent-gradient: linear-gradient(135deg, #00a884 0%, #00897b 100%);
  --bg: #eef1f4;
  --panel-bg: #ffffff;
  --bubble-mine: #d9fdd3;
  --bubble-theirs: #ffffff;
  --text: #111b21;
  --muted: #667781;
  --muted-light: #8b98a3;
  --border: #e9edef;
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --shadow-sm: 0 1px 2px rgba(16, 24, 32, 0.06);
  --shadow-md: 0 4px 16px rgba(16, 24, 32, 0.08);
  --shadow-lg: 0 16px 48px rgba(16, 24, 32, 0.16);
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  /* System-native font stack -- deliberately no web-font fetch (consistent
     with this project's zero-network-dependency build philosophy; it also
     means the UI looks crisp even when the server is reached over a LAN
     with no internet route out, which is this app's primary use case). */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

button, input { font-family: inherit; }

/* Slim, unobtrusive scrollbars throughout */
* { scrollbar-width: thin; scrollbar-color: #c7cdd3 transparent; }
*::-webkit-scrollbar { width: 7px; height: 7px; }
*::-webkit-scrollbar-thumb { background: #c7cdd3; border-radius: 10px; }
*::-webkit-scrollbar-thumb:hover { background: #aab2ba; }
*::-webkit-scrollbar-track { background: transparent; }

.view { display: none; height: 100%; }
.view.active { display: block; }

/* ---------- Auth ---------- */
#view-login.active, #view-register.active {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  padding: 16px;
  background:
    radial-gradient(circle at 15% 15%, rgba(0, 168, 132, 0.10), transparent 45%),
    radial-gradient(circle at 85% 85%, rgba(0, 137, 123, 0.10), transparent 45%),
    var(--bg);
}
.auth-card {
  background: var(--panel-bg);
  border-radius: 18px;
  padding: 36px 32px;
  max-width: 380px;
  width: 100%;
  box-shadow: var(--shadow-lg);
  border: 1px solid rgba(0, 0, 0, 0.03);
  animation: rise 0.35s var(--ease);
}
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
.auth-logo {
  width: 52px; height: 52px;
  border-radius: 16px;
  background: var(--accent-gradient);
  display: flex; align-items: center; justify-content: center;
  font-size: 24px;
  margin-bottom: 16px;
  box-shadow: 0 6px 16px rgba(0, 168, 132, 0.28);
}
.auth-card h1 { margin: 0 0 6px; color: var(--text); font-size: 23px; font-weight: 700; letter-spacing: -0.01em; }
.tagline { color: var(--muted); font-size: 13.5px; margin-bottom: 22px; line-height: 1.5; }
.auth-card form { display: flex; flex-direction: column; gap: 11px; }
.auth-card input {
  padding: 12px 14px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  font-size: 15px;
  background: #fafbfc;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.auth-card input:hover { border-color: #d6dbdf; }
.auth-card input:focus {
  outline: none;
  border-color: var(--green);
  background: #ffffff;
  box-shadow: 0 0 0 3px var(--green-light);
}
.auth-card button {
  padding: 12px;
  border: none;
  border-radius: 10px;
  background: var(--accent-gradient);
  color: white;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 4px;
  box-shadow: 0 4px 12px rgba(0, 168, 132, 0.25);
  transition: transform 0.12s var(--ease), box-shadow 0.12s var(--ease), filter 0.12s var(--ease);
}
.auth-card button:hover { filter: brightness(1.04); box-shadow: 0 6px 16px rgba(0, 168, 132, 0.32); }
.auth-card button:active { transform: translateY(1px) scale(0.99); }
.error { color: #d32f2f; font-size: 13px; min-height: 16px; margin: 2px 0 0; }
.switch { font-size: 13.5px; margin-top: 18px; text-align: center; color: var(--muted); }
.switch a { color: var(--green-dark); font-weight: 600; text-decoration: none; }
.switch a:hover { text-decoration: underline; }

/* ---------- App shell ---------- */
.app-shell.active {
  display: flex;
  height: 100vh;
}
.sidebar {
  width: 340px;
  flex-shrink: 0;
  background: var(--panel-bg);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
}
.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.sidebar-identity { display: flex; align-items: center; gap: 11px; min-width: 0; }
.sidebar-identity-text { min-width: 0; }
.my-display-name {
  font-weight: 600;
  font-size: 14.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 180px;
}
.avatar.me { box-shadow: 0 2px 6px rgba(0, 168, 132, 0.3); }
.sidebar-actions { display: flex; gap: 2px; }
.sidebar-actions button {
  background: none;
  border: none;
  font-size: 17px;
  cursor: pointer;
  width: 34px; height: 34px;
  border-radius: 50%;
  color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.sidebar-actions button:hover { background: var(--bg); color: var(--text); }
.status-line {
  font-size: 12px;
  color: var(--muted);
  margin-top: 1px;
}
.contact-list { list-style: none; margin: 0; padding: 4px 0; overflow-y: auto; flex: 1; }
.contact {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  cursor: pointer;
  border-left: 3px solid transparent;
  transition: background 0.12s var(--ease);
}
.contact:hover { background: #f5f7f8; }
.contact.active { background: var(--green-light); border-left-color: var(--green); }
/* Group rows reuse the same .contact layout -- distinguished only by a
   square-ish (less-rounded) avatar and a member-count line instead of
   online/offline, so they read as a different "kind" of row at a
   glance without a visually separate list/section. */
.avatar.group-avatar { border-radius: 30%; background: linear-gradient(135deg, #6b7fd7 0%, #5a67c9 100%); }
.contact-status.group-meta { color: var(--muted-light); }
.avatar {
  width: 42px; height: 42px;
  border-radius: 50%;
  background: var(--accent-gradient);
  background-size: cover; background-position: center; /* when a photo sets background-image via JS (see app.js's renderAvatarInto) */
  color: white;
  display: flex; align-items: center; justify-content: center;
  font-weight: 600;
  font-size: 14.5px;
  flex-shrink: 0;
  letter-spacing: 0.02em;
}
.contact-info { flex: 1; min-width: 0; }
/* Clips a long display name/username (and, for a group row, a long
   group name) with an ellipsis instead of overflowing this row --
   same pattern as .my-display-name/#chat-peer-name elsewhere in this
   file, and .contact-status right below. Was previously the one
   "name" surface in this file WITHOUT this protection: nothing
   server-side bounded display_name/group name length either (see
   server/src/routes.rs's MAX_DISPLAY_NAME_LEN/MAX_GROUP_NAME_LEN doc
   comment), so an unusually long, unbroken one would genuinely
   overflow this card's row and run past the sidebar's edge. Both
   halves of that are now fixed: the server caps the length, and this
   rule clips defensively too, exactly the same belt-and-suspenders
   relationship MAX_MESSAGE_LENGTH has with .bubble's min-width: 0 fix
   below. .contact-info's own min-width: 0 (right above) is required
   for this to actually take effect, for the same flex-item reason
   explained on .bubble. */
.contact-name {
  font-weight: 600;
  font-size: 14.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.contact-status { font-size: 12.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.presence-dot { width: 9px; height: 9px; border-radius: 50%; background: #ccc; box-shadow: 0 0 0 2px #fff; flex-shrink: 0; }
.presence-dot.online { background: var(--green); }
/* New-message notification on a contact/group row -- see app.js's
   receiveMessage/receiveGroupMessage (sets state.unreadPeers/
   unreadGroups) and renderContactList (renders this). A plain filled
   dot, not a count: the server's ephemeral-mailbox model means the
   client only ever knows THAT a thread has an unread message, never
   durably how many. */
.unread-badge { width: 11px; height: 11px; border-radius: 50%; background: var(--green); box-shadow: 0 0 0 2px #fff; flex-shrink: 0; }
.contact.has-unread .contact-name { font-weight: 700; }
.contact.has-unread .contact-status { color: var(--text); }

.chat-area { flex: 1; display: flex; flex-direction: column; background: #e9e3d9; position: relative; }
.chat-area::before {
  /* Subtle woven texture in place of WhatsApp's wallpaper image, without
     any external asset dependency -- keeps the whole app offline-buildable. */
  content: "";
  position: absolute; inset: 0;
  background-image:
    radial-gradient(rgba(0,0,0,0.025) 1px, transparent 1px);
  background-size: 18px 18px;
  pointer-events: none;
}
.chat-empty { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--muted-light); font-size: 14.5px; }
.chat-active { flex: 1; display: flex; flex-direction: column; position: relative; z-index: 1; }
.chat-header {
  padding: 10px 18px;
  background: var(--panel-bg);
  border-bottom: 1px solid var(--border);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 12px;
  box-shadow: var(--shadow-sm);
  z-index: 2;
}
#chat-back { cursor: pointer; font-size: 18px; color: var(--muted); display: none; }
.chat-header-avatar { width: 34px; height: 34px; font-size: 13px; }
#chat-peer-name {
  font-size: 15px;
  flex: 1; /* pushes #group-info-btn to the header's far edge */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#group-info-btn {
  background: none;
  border: none;
  font-size: 19px;
  cursor: pointer;
  width: 32px; height: 32px;
  border-radius: 50%;
  color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
#group-info-btn:hover { background: var(--bg); color: var(--text); }

.message-list { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 18px 20px; display: flex; flex-direction: column; gap: 7px; position: relative; z-index: 1; }
.bubble {
  /* min-width: 0 overrides the flex item's AUTOMATIC minimum size
     (CSS Flexbox spec section 7.1.4): without it, a flex item's
     automatic min-width defaults to roughly its content's min-content
     size, which for a long message containing one unbroken run of
     characters (a long URL, a wall of repeated characters, etc. --
     exactly what a "longest text ever" style message tends to have)
     can be far wider than 65%. That automatic minimum WINS over
     max-width in the spec's clamping order whenever it's larger than
     max-width, so max-width alone silently stopped applying and the
     bubble (and the page under it, since .message-list's own
     overflow-x was unset/visible) grew wide enough to force horizontal
     scrolling of the whole chat body. word-break on .bubble-text was
     never the missing piece -- it only gets a chance to run once the
     bubble's own width is actually capped, which min-width: 0 restores.
     Confirmed with a 2000-character unbroken test string: document
     scrollWidth went from 14211px (broken) to the viewport's own width
     after this fix. */
  min-width: 0;
  max-width: 65%;
  padding: 8px 11px;
  border-radius: var(--radius-md);
  font-size: 14.5px;
  box-shadow: var(--shadow-sm);
  position: relative;
  line-height: 1.4;
  animation: pop-in 0.16s var(--ease);
}
@keyframes pop-in {
  from { opacity: 0; transform: translateY(4px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.bubble.mine { align-self: flex-end; background: var(--bubble-mine); border-bottom-right-radius: 4px; }
.bubble.theirs { align-self: flex-start; background: var(--bubble-theirs); border-bottom-left-radius: 4px; }
.bubble-text { white-space: pre-wrap; word-break: break-word; }
.bubble-meta { font-size: 11px; color: var(--muted); text-align: right; margin-top: 3px; display: flex; align-items: center; justify-content: flex-end; gap: 3px; }
/* Message status dash: one consistent mark, colored by state rather
   than swapped for a different symbol/tick-count per state. */
.tick {
  display: inline-block;
  width: 12px;
  height: 2px;
  border-radius: 2px;
  margin-left: 1px;
}
.tick-sent { background: var(--muted-light); }
/* Delivered uses amber, not the app's teal-green, because teal and the
   read-blue below are too close in hue to tell apart at this size (the
   user flagged exactly this) -- amber/blue is one of the most reliably
   distinguishable hue pairs, including for the common red-green forms
   of color blindness. */
.tick-delivered { background: #f0a020; }
.tick-read { background: #2196f3; }

.message-form { display: flex; gap: 6px; padding: 10px 14px; background: var(--panel-bg); align-items: center; border-top: 1px solid var(--border); position: relative; z-index: 1; }
/* Holds #message-input and the absolutely-positioned #recording-
   indicator overlay (see the long comment in index.html on
   #message-form for why the indicator overlays rather than replaces
   this slot as a normal flex sibling -- in short: so toggling it never
   reflows #mic-btn). This wrapper is the thing that actually has
   flex:1 now; the input and the indicator just fill it. */
.message-input-wrap {
  flex: 1;
  min-width: 0;
  position: relative;
}
.message-form input[type="text"] {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 16px;
  border-radius: var(--radius-lg);
  border: 1.5px solid var(--border);
  font-size: 15px;
  background: #f5f6f7;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.message-form input[type="text"]:focus {
  outline: none;
  border-color: var(--green);
  background: #fff;
}
#send-btn {
  flex-shrink: 0;
  width: 42px; height: 42px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: var(--accent-gradient);
  color: white;
  cursor: pointer;
  font-size: 16px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 3px 10px rgba(0, 168, 132, 0.3);
  transition: transform 0.12s var(--ease), filter 0.12s var(--ease);
}
#send-btn:hover { filter: brightness(1.05); }
#send-btn:active { transform: scale(0.94); }
#attach-btn, #emoji-btn {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 19px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
#attach-btn:hover, #emoji-btn:hover { background: var(--bg); color: var(--text); }
#attach-btn[disabled] { opacity: 0.5; cursor: default; }

#mic-btn {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 19px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  /* Long-press is a touch gesture here, not a text-selection/scroll
     one -- without this, mobile browsers try to select text or
     scroll the page on press-and-hold instead of starting a
     recording. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
#mic-btn:hover { background: var(--bg); color: var(--text); }
#mic-btn.recording {
  background: #fde8e8;
  color: #d32f2f;
}

/* ---------- Voice recording indicator ---------- */
/* Absolutely positioned to exactly cover #message-input-wrap (its
   nearest `position: relative` ancestor), rather than being a normal
   flex sibling of #message-input -- so showing/hiding it NEVER
   changes any other element's box or position in the row. That
   matters specifically for #mic-btn: the browser releases pointer
   capture and synthesizes a pointerleave/pointercancel on an element
   the instant its layout box moves, even by a few pixels, which this
   app's own pointerleave handler reads as "finger lifted, stop and
   send" -- so #mic-btn's position must stay pixel-identical for the
   whole press-hold-release gesture (see the longer comment on
   #message-form in index.html). Default display:none here is a
   fallback; the inline style="display:none" in index.html is what
   actually governs it before JS runs, and app.js's
   showRecordingUI() toggles it to "flex" during a recording. */
.recording-indicator {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1; /* explicit, not left to auto-stacking: must paint (and
                 hit-test) above the sibling #message-input underneath
                 it, which stays in the DOM with visibility:hidden
                 rather than display:none (see index.html's comment on
                 #message-form for why) -- without this, the invisible
                 input could still end up the topmost hit-test target
                 at this spot depending on paint-order tie-breaking. */
  align-items: center;
  gap: 10px;
  background: var(--panel-bg);
}
.recording-dot {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: #e53935;
  flex-shrink: 0;
  animation: recording-pulse 1.1s ease-in-out infinite;
}
@keyframes recording-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.4; transform: scale(0.8); }
}
#recording-timer { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
.recording-hint { color: var(--muted); font-size: 13px; }
#cancel-recording-btn {
  margin-left: auto;
  border: 1.5px solid var(--border);
  background: #fff;
  color: var(--muted);
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.12s var(--ease), color 0.12s var(--ease);
}

/* Floats just above #message-input, right-aligned -- positioned
   relative to .message-input-wrap (position:relative, see above),
   same overlay technique as #recording-indicator so it never affects
   the input's own layout/width. Hidden until MESSAGE_COUNTER_SHOW_AT
   (app.js) is reached. */
.message-char-counter {
  position: absolute;
  bottom: 100%;
  right: 4px;
  margin-bottom: 4px;
  font-size: 11.5px;
  color: var(--muted);
  background: var(--panel-bg);
  padding: 1px 7px;
  border-radius: 999px;
  box-shadow: var(--shadow-sm);
  pointer-events: none;
}
.message-char-counter.at-limit { color: #d32f2f; font-weight: 600; }
#cancel-recording-btn:hover { background: #fde8e8; color: #d32f2f; border-color: #f3b8b8; }

.file-progress {
  padding: 7px 18px;
  font-size: 12.5px;
  color: var(--green-dark);
  background: var(--green-light);
  border-top: 1px solid var(--border);
  position: relative;
  z-index: 1;
}

/* ---------- Emoji picker ---------- */
.emoji-picker {
  position: absolute;
  bottom: 68px;
  left: 14px;
  width: 300px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--panel-bg);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
  padding: 12px;
  z-index: 10;
  animation: pop-in 0.14s var(--ease);
}
.emoji-group-label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 10px 2px 6px;
}
.emoji-group-label:first-child { margin-top: 0; }
.emoji-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.emoji-btn-item {
  border: none;
  background: transparent;
  font-size: 20px;
  line-height: 1;
  padding: 6px 0;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.1s var(--ease), transform 0.1s var(--ease);
}
.emoji-btn-item:hover { background: var(--bg); transform: scale(1.15); }

/* ---------- Message reactions ---------- */
/* Small circular trigger at the start of .bubble-meta's flex row
   (margin-right: auto pushes it left while time+tick stay right-
   aligned, same row, no extra wrapper needed). Always visible rather
   than a hover/long-press-only affordance -- deliberately, both for
   discoverability and because a click/tap-only trigger is what a
   Playwright test can reliably drive (this project's established
   testing convention -- see tests/voice_recording_test.mjs/
   local_history_test.mjs -- favors UI mechanics that don't depend on
   a gesture's exact timing). */
.bubble-react-btn {
  margin-right: auto;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 999px;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.12s var(--ease), background 0.12s var(--ease);
}
.bubble-react-btn:hover { opacity: 1; background: rgba(0, 0, 0, 0.06); }

/* Row of reaction pills, shown under the bubble's own content and
   above .bubble-meta -- only rendered at all when the message has at
   least one reaction (see renderReactionsRow in app.js, which returns
   null otherwise so no empty/zero-height row is ever inserted). */
.bubble-reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}
.reaction-pill {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: 1.5px solid transparent;
  background: rgba(0, 0, 0, 0.055);
  color: var(--text);
  font-size: 12.5px;
  line-height: 1;
  padding: 3px 7px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.12s var(--ease), border-color 0.12s var(--ease);
}
.reaction-pill:hover { background: rgba(0, 0, 0, 0.09); }
/* Highlights a pill whose emoji is the CURRENT user's own reaction
   (clicking it again removes it -- see applyReactionLocally/
   toggleReaction in app.js), same green identity color used for
   #profile-my-id/.bubble-sender-name elsewhere in this file. */
.reaction-pill.mine-reaction {
  border-color: var(--green);
  background: var(--green-light);
  color: var(--green-dark);
  font-weight: 600;
}
.reaction-pill-count { font-variant-numeric: tabular-nums; }

/* Floating picker opened by .bubble-react-btn. Positioned with
   JS-computed `left`/`top` (fixed, relative to the VIEWPORT, not the
   scrollable #message-list it's triggered from -- see
   openReactionPicker in app.js) and appended to <body> rather than
   nested inside a bubble, specifically so it is never clipped by
   #message-list's own `overflow-y: auto`/`overflow-x: hidden` the way
   an absolutely-positioned child of a bubble near the top/bottom edge
   of the scroll area would be. */
.reaction-picker {
  position: fixed;
  background: var(--panel-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
  padding: 8px;
  z-index: 20;
  animation: pop-in 0.14s var(--ease);
}
.reaction-picker-quick {
  display: flex;
  align-items: center;
  gap: 2px;
}
.reaction-picker-quick button {
  border: none;
  background: transparent;
  font-size: 22px;
  line-height: 1;
  padding: 5px 6px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.1s var(--ease), transform 0.1s var(--ease);
}
.reaction-picker-quick button:hover { background: var(--bg); transform: scale(1.2); }
/* Selected = this is the viewer's own current reaction on this
   message -- same visual language as .reaction-pill.mine-reaction. */
.reaction-picker-quick button.selected { background: var(--green-light); }
.reaction-picker-more-btn {
  font-size: 15px !important;
  color: var(--muted);
}

/* ---------- File bubbles ---------- */
.bubble-file {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 190px;
}
.bubble-file-icon {
  width: 38px; height: 38px;
  border-radius: 10px;
  background: var(--accent-gradient);
  color: white;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px;
}
.bubble-file-info { min-width: 0; flex: 1; }
.bubble-file-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 220px;
}
.bubble-file-size { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.bubble-file-download {
  flex-shrink: 0;
  text-decoration: none;
  font-size: 15px;
  color: white;
  background: var(--accent-gradient);
  width: 30px; height: 30px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.12s var(--ease);
}
.bubble-file-download:hover { transform: scale(1.08); }
.bubble-file-error { color: #d32f2f; font-size: 13px; padding: 2px 0; }

/* ---------- Voice note bubbles ---------- */
.bubble-voice { display: flex; align-items: center; gap: 10px; min-width: 220px; }
.bubble-voice-icon {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--accent-gradient);
  color: white;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px;
}
.bubble-voice-player { height: 34px; max-width: 230px; flex: 1; }
/* Mobile: the native <audio> control can overflow a narrow bubble at
   default width -- clamp it so it never forces the bubble (and the
   page) to scroll horizontally. */
@media (max-width: 720px) {
  .bubble-voice-player { max-width: 180px; }
}

/* ---------- Profile panel ---------- */
.panel {
  position: fixed; inset: 0;
  background: rgba(17, 27, 33, 0.45);
  backdrop-filter: blur(2px);
  display: none;
  align-items: center; justify-content: center;
  z-index: 50;
}
.panel.open { display: flex; }
.panel-content {
  background: var(--panel-bg);
  border-radius: 18px;
  padding: 24px;
  width: 340px;
  box-shadow: var(--shadow-lg);
  animation: rise 0.2s var(--ease);
}
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.panel-header h2 { font-size: 17px; margin: 0; }
.panel-header button {
  background: var(--bg); border: none; font-size: 14px; cursor: pointer;
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted);
  transition: background 0.15s var(--ease);
}
.panel-header button:hover { background: #e4e7ea; }
#profile-form { display: flex; flex-direction: column; gap: 9px; }
#profile-form label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: -4px; }
#profile-form input {
  padding: 10px 12px;
  border-radius: 9px;
  border: 1.5px solid var(--border);
  font-size: 14.5px;
  background: #fafbfc;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
#profile-form input:focus { outline: none; border-color: var(--green); background: #fff; box-shadow: 0 0 0 3px var(--green-light); }
#profile-form button {
  margin-top: 10px;
  padding: 11px;
  border: none;
  border-radius: 9px;
  background: var(--accent-gradient);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 168, 132, 0.25);
  transition: filter 0.12s var(--ease);
}
#profile-form button:hover { filter: brightness(1.05); }

/* ---------- Group-creation panel ---------- */
/* Shares #profile-form's visual language (same label/input/button
   rules) rather than a generic class, to keep this a small additive
   change instead of refactoring the existing profile-panel markup. */
#group-form { display: flex; flex-direction: column; gap: 9px; }
#group-form label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: -4px; }
#group-form input {
  padding: 10px 12px;
  border-radius: 9px;
  border: 1.5px solid var(--border);
  font-size: 14.5px;
  background: #fafbfc;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
#group-form input:focus { outline: none; border-color: var(--green); background: #fff; box-shadow: 0 0 0 3px var(--green-light); }
#group-form button[type="submit"] {
  margin-top: 10px;
  padding: 11px;
  border: none;
  border-radius: 9px;
  background: var(--accent-gradient);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 168, 132, 0.25);
  transition: filter 0.12s var(--ease);
}
#group-form button[type="submit"]:hover { filter: brightness(1.05); }
.group-member-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 220px;
  overflow-y: auto;
  border: 1.5px solid var(--border);
  border-radius: 9px;
}
.group-member-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.group-member-list li:last-child { border-bottom: none; }
.group-member-list input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--green); }
.group-member-list .empty-hint { color: var(--muted-light); font-size: 13px; padding: 12px; }

/* ---------- Group info panel (read-only member details) ---------- */
/* Reuses .group-member-list's list/border chrome; these rules add the
   avatar + name/handle/status layout, since this list shows full
   member identity rather than an add-member checkbox -- see
   index.html's #group-info-panel and app.js's renderGroupInfoPanel. */
.group-info-member-list li { gap: 12px; }
.group-info-member-list .avatar { width: 36px; height: 36px; font-size: 12.5px; }
.group-info-member-row-text { flex: 1; min-width: 0; }
.group-info-member-row-text .member-name { font-weight: 600; font-size: 14px; }
.group-info-member-row-text .member-name .contact-handle { font-weight: 400; }
.group-info-member-row-text .member-status { font-size: 12.5px; color: var(--muted); }
.group-info-member-row-text .creator-tag {
  font-size: 11px;
  font-weight: 600;
  color: var(--green-dark);
  background: var(--green-light);
  border-radius: 5px;
  padding: 1px 6px;
  margin-left: 6px;
}

/* ---------- Add-contact panel ---------- */
/* Same visual language as #profile-form/#group-form, duplicated per
   those panels' own precedent rather than refactored into a shared
   class (see #group-form's comment above). */
#add-contact-form { display: flex; flex-direction: column; gap: 9px; }
#add-contact-form label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: -4px; }
#add-contact-form input {
  padding: 10px 12px;
  border-radius: 9px;
  border: 1.5px solid var(--border);
  font-size: 14.5px;
  background: #fafbfc;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
#add-contact-form input:focus { outline: none; border-color: var(--green); background: #fff; box-shadow: 0 0 0 3px var(--green-light); }
#add-contact-form button[type="submit"] {
  margin-top: 10px;
  padding: 11px;
  border: none;
  border-radius: 9px;
  background: var(--accent-gradient);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 168, 132, 0.25);
  transition: filter 0.12s var(--ease);
}
#add-contact-form button[type="submit"]:hover { filter: brightness(1.05); }
.panel-content .hint { font-size: 12.5px; color: var(--muted); margin: 0 0 14px; line-height: 1.4; }

/* "Your ID" row at the top of the profile panel -- a read-only
   @handle plus a copy-to-clipboard button, so a user can hand their
   own unique ID to someone else to be added as a contact. */
.profile-id-row { margin-bottom: 16px; }
.profile-id-row label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; display: block; margin-bottom: 4px; }
.profile-id-display {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px;
  border-radius: 9px;
  border: 1.5px solid var(--border);
  background: #fafbfc;
}
#profile-my-id { font-weight: 700; font-size: 14.5px; color: var(--green-dark); flex: 1; min-width: 0; overflow-wrap: anywhere; }
#profile-copy-id-btn {
  background: var(--bg); border: none; font-size: 13px; cursor: pointer;
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s var(--ease);
}
#profile-copy-id-btn:hover { background: #e4e7ea; }
.profile-id-row .hint { margin: 8px 0 0; }

/* @handle shown next to a contact's display name in the sidebar list,
   so every entry is identifiable by its unique ID, not just a name
   someone could change. */
.contact-handle { font-weight: 400; color: var(--muted); font-size: 12.5px; margin-left: 4px; }
.contact-list .empty-hint { color: var(--muted-light); font-size: 13px; padding: 16px 14px; line-height: 1.4; }

/* ---------- Profile picture (profile panel) ---------- */
.profile-avatar-row { display: flex; align-items: center; gap: 14px; margin-bottom: 10px; }
.profile-avatar-preview.avatar {
  width: 64px; height: 64px;
  font-size: 20px;
}
.profile-avatar-actions { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.profile-avatar-upload-btn {
  display: inline-block;
  padding: 7px 14px;
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.profile-avatar-upload-btn:hover { background: #e4e7ea; }
#profile-remove-avatar-btn {
  background: none; border: none;
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
}
#profile-remove-avatar-btn:hover { color: #d32f2f; }

/* ---------- Local history retention (profile panel) ---------- */
.history-retention-row { margin-bottom: 16px; }
.history-retention-row label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; display: block; margin-bottom: 4px; }
.history-retention-controls { display: flex; align-items: center; gap: 8px; }
.history-retention-controls input[type="number"] {
  width: 70px;
  padding: 8px 10px;
  border-radius: 9px;
  border: 1.5px solid var(--border);
  font-size: 14.5px;
  background: #fafbfc;
}
.history-retention-controls input[type="number"]:focus { outline: none; border-color: var(--green); background: #fff; box-shadow: 0 0 0 3px var(--green-light); }
.history-retention-controls span { font-size: 13.5px; color: var(--muted); }
.history-retention-controls button {
  padding: 8px 14px;
  border: none;
  border-radius: 9px;
  background: var(--bg);
  color: var(--text);
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.history-retention-controls button:hover { background: #e4e7ea; }
.history-retention-row .hint { margin: 8px 0 0; }

/* Sender-name label on a group "theirs" bubble -- 1:1 chat never needs
   this since the peer is already implied by the whole conversation. */
/* Clips a long sender display name instead of overflowing the bubble
   -- same gap, same fix, and same server-side backstop
   (MAX_DISPLAY_NAME_LEN) as .contact-name above; relies on .bubble's
   own min-width: 0 (this is a plain block child of .bubble, not a
   flex item itself, so no min-width: 0 of its own is needed here). */
.bubble-sender-name {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--green-dark);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Mobile ---------- */
@media (max-width: 720px) {
  .app-shell.active { flex-direction: column; }
  .sidebar { width: 100%; height: 100%; border-right: none; border-bottom: 1px solid var(--border); }
  .chat-area { position: fixed; inset: 0; display: none; }
  body.chat-open .sidebar { display: none; }
  body.chat-open .chat-area { display: flex; }
  #chat-back { display: inline-block; margin-right: 2px; }
  .bubble { max-width: 85%; }
  .emoji-picker { width: calc(100vw - 28px); left: 14px; }
  .message-form { padding: 8px 10px; gap: 4px; }
}
