/* ---- themes ----------------------------------------------------------------
   Every colour is light-dark(LIGHT, DARK); the chosen appearance sets
   `color-scheme` on <html> (light / dark / "light dark" = follow the OS), so a
   single set of tokens drives both modes. Themes are selected with a
   data-theme attribute on <html>. The app sets it to "classic" by default (see
   DEFAULT_THEME in ui.js); the bare :root values below double as the Lit Room
   theme and the no-JS fallback. Structural tokens (radius, fonts, shadow, pills,
   status colours) live here once; theme blocks only override what differs. Order
   matters: themes come after this block so they win on equal specificity. */
:root,
[data-theme="litroom"] {
  --radius: 18px;
  --font-display: ui-rounded, "SF Pro Rounded", "Avenir Next", -apple-system, system-ui, sans-serif;
  /* depth + status colours are theme-agnostic */
  --shadow: light-dark(rgba(20, 30, 80, 0.10), rgba(0, 0, 0, 0.55));
  --danger: light-dark(#c2410c, #f0936b);
  --warn: light-dark(#b4530a, #e29457);
  --pill-bg: light-dark(#23252e, #eef0f6);
  --pill-ink: light-dark(#ffffff, #1c2333);
  /* selected-chat row background; defaults to the hover grey, themes may override */
  --row-active: var(--bubble-them);

  /* Lit Room — warm dusk, honey glow */
  --bg: light-dark(#efe3d6, #1b1418);
  --panel: light-dark(#fff8f0, #241a1e);
  --sidebar: light-dark(#fbf1e6, #1f161a);
  --ink: light-dark(#2a211b, #f4e9dc);
  --muted: light-dark(#736657, #b09a89);
  --border: light-dark(#ece2d4, rgba(255, 255, 255, 0.10));
  --accent: light-dark(#f0a93c, #ffb23e);
  --accent-ink: light-dark(#3a2412, #2a1808);
  --link: light-dark(#956925, #ffb23e);
  --bubble-me: light-dark(#ffb23e, #ffb23e);
  --bubble-me-ink: light-dark(#3a2412, #2a1808);
  --bubble-them: light-dark(#f1ece4, #2f2329);
}

/* Classic — the original lchat blue (now with a dark mode) */
[data-theme="classic"] {
  --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --bg: light-dark(#f6f7fb, #15171c);
  --panel: light-dark(#ffffff, #1d2026);
  --sidebar: light-dark(#f8f9fc, #1a1d22);
  --ink: light-dark(#1c2333, #e7e9f0);
  --muted: light-dark(#5a6170, #9aa1b3);
  --border: light-dark(#e7e9f0, rgba(255, 255, 255, 0.10));
  --accent: light-dark(#5666f0, #7d8bff);
  --accent-ink: light-dark(#ffffff, #0a0e2a);
  --link: light-dark(#5666f0, #7d8bff);
  --bubble-me: light-dark(#5666f0, #5666f0);
  --bubble-me-ink: light-dark(#ffffff, #ffffff);
  --bubble-them: light-dark(#eef0f6, #2a2e38);
  --row-active: light-dark(#d9dfec, #2a2e38);
}

/* Lagoon — cool, fresh; a coral accent that pops */
[data-theme="lagoon"] {
  --bg: light-dark(#d7ecea, #06201f);
  --panel: light-dark(#f3fbfa, #0c2a29);
  --sidebar: light-dark(#e7f6f4, #082523);
  --ink: light-dark(#123a3a, #d8efec);
  --muted: light-dark(#4c7271, #6fa6a3);
  --border: light-dark(#d2e8e6, rgba(255, 255, 255, 0.10));
  --accent: light-dark(#b85840, #ff8a6a);
  --accent-ink: light-dark(#ffffff, #2a0f08);
  --link: light-dark(#b2553e, #ff8a6a);
  --bubble-me: light-dark(#0e827e, #16b3ad);
  --bubble-me-ink: light-dark(#ffffff, #042220);
  --bubble-them: light-dark(#ffffff, #123634);
}

/* Sunny — bright-side daytime */
[data-theme="sunny"] {
  --bg: light-dark(#ffeecb, #1c1710);
  --panel: light-dark(#fffaf0, #241d12);
  --sidebar: light-dark(#fff4df, #1f190f);
  --ink: light-dark(#3a2f1c, #f6ecd6);
  --muted: light-dark(#796d57, #b6a079);
  --border: light-dark(#f0e6d0, rgba(255, 255, 255, 0.10));
  --accent: light-dark(#ffc62e, #ffc62e);
  --accent-ink: light-dark(#3a2f1c, #2a2106);
  --link: light-dark(#8a6b19, #ffc62e);
  --bubble-me: light-dark(#2c7ab0, #3aa0e8);
  --bubble-me-ink: light-dark(#ffffff, #04243a);
  --bubble-them: light-dark(#ffffff, #2e2616);
}

/* Linen — quiet, grown-up; warmth through restraint */
[data-theme="linen"] {
  --font-display: "Avenir Next", "Helvetica Neue", -apple-system, system-ui, sans-serif;
  --bg: light-dark(#e7e1d6, #181714);
  --panel: light-dark(#f5f1ea, #211f1b);
  --sidebar: light-dark(#ece6dc, #1c1b17);
  --ink: light-dark(#33302a, #ece7dd);
  --muted: light-dark(#69655c, #9c958a);
  --border: light-dark(#e2dccf, rgba(255, 255, 255, 0.10));
  --accent: light-dark(#647686, #93a8ba);
  --accent-ink: light-dark(#ffffff, #0c1217);
  --link: light-dark(#5c6e7c, #93a8ba);
  --bubble-me: light-dark(#647686, #7d94a8);
  --bubble-me-ink: light-dark(#ffffff, #0c1217);
  --bubble-them: light-dark(#fbf9f4, #2a2823);
}

/* Plum — soft grape; playful but not childish, light or deep-aubergine dark */
[data-theme="plum"] {
  --bg: light-dark(#ece2f0, #1a1320);
  --panel: light-dark(#fdf8fd, #221829);
  --sidebar: light-dark(#f4ecf6, #1e1525);
  --ink: light-dark(#2f2436, #f0e6f4);
  --muted: light-dark(#6f6578, #ab9bb5);
  --border: light-dark(#e7dced, rgba(255, 255, 255, 0.10));
  --accent: light-dark(#9456c9, #b57ae8);
  --accent-ink: light-dark(#ffffff, #20102e);
  --link: light-dark(#9456c9, #b57ae8);
  --bubble-me: light-dark(#9456c9, #b57ae8);
  --bubble-me-ink: light-dark(#ffffff, #20102e);
  --bubble-them: light-dark(#f1e8f5, #2e2238);
}

/* Default appearance: follow the OS. The app overrides this on <html> per the
   saved preference (light / dark / system). */
html { color-scheme: light dark; }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}
/* The app shell must fill the viewport so the flex height chain (.tt → .thread-view
   → .feed flex:1) resolves and the composer pins to the bottom. Without an explicit
   height here, .tt's height:100% resolves against an auto-height parent, the layout
   collapses to content height, and the composer floats with blank space below it. */
#app { height: 100%; }

/* Full-screen loading cover (see index.html) — matches the pre-painted theme bg,
   fades out once the app's first real paint is ready. */
.app-loading {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: var(--bg);
  transition: opacity 0.25s ease;
}
.app-loading.hidden { opacity: 0; pointer-events: none; }
.spinner {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--ink) 15%, transparent);
  border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; opacity: 0.5; }
}

button {
  font: inherit;
  cursor: pointer;
  border: none;
  border-radius: 999px;
  padding: 10px 18px;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  white-space: nowrap;
  line-height: 1.2;
}
button.secondary {
  background: var(--bubble-them);
  color: var(--ink);
}
button:disabled { opacity: 0.5; cursor: default; }

input, textarea {
  font: inherit;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  background: var(--panel);
  color: var(--ink);
  width: 100%;
  caret-color: var(--accent); /* the typing cursor picks up the accent (à la iMessage) */
}
input:focus, textarea:focus { outline: 2px solid var(--accent); border-color: transparent; }
/* the message box doesn't get the bright focus ring (chat-app convention) — the
   coloured caret is the cue. */
.composer textarea:focus { outline: none; border-color: var(--border); }

/* ---- centered card layout (join / fallback) ---- */
.center {
  min-height: 100%;
  display: grid;
  place-items: center;
  padding: 24px;
}
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 28px;
  width: 100%;
  max-width: 420px;
  box-shadow: 0 12px 40px var(--shadow);
}
.card h1 { margin: 0 0 6px; font-size: 24px; font-family: var(--font-display); }
.card p { margin: 0 0 18px; color: var(--muted); }

/* Transient join states ("Setting up…", "Signing you in…") — a spinner + message
   centered in the card, instead of a small line stuck top-left. */
.card.status {
  min-height: 180px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.card.status .status-msg { margin: 0; font-size: 16px; color: var(--ink); }
.status-spinner {
  width: 30px; height: 30px; border-radius: 50%;
  border: 3px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite; margin-bottom: 14px; /* @keyframes spin defined above */
}
.card .row { display: flex; gap: 10px; margin-top: 14px; }



/* kebab settings menu */
.icon-btn {
  background: transparent;
  color: var(--ink);
  font-size: 22px;
  line-height: 1;
  padding: 4px 10px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.icon-btn svg { display: block; }
.icon-btn:hover { background: var(--bubble-them); }
.menu-wrap { position: relative; flex: none; }
.menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 12px 40px var(--shadow);
  min-width: 230px;
  padding: 6px;
  z-index: 20;
}
.menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  color: var(--ink);
  border-radius: 10px;
  padding: 10px 12px;
  font-weight: 500;
  white-space: normal;
}
.menu-item:hover { background: var(--bubble-them); }
.menu-item.danger { color: var(--danger); }
.menu-item.disabled,
.menu-item.disabled:hover { color: var(--muted); cursor: default; background: transparent; }

#feed {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden; /* clip messages mid swipe-to-reply; no horizontal scrollbar */
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* push a few messages to the bottom (near the composer); scrolls normally when full */
#feed::before {
  content: "";
  margin-top: auto;
}
.msg {
  display: flex;
  flex-direction: column;
  align-self: flex-start; /* shrink to the bubble width (me overrides to flex-end) */
  max-width: 78%;
  margin-top: 10px;
  position: relative; /* anchors hover affordances + the swipe-to-reply icon */
  touch-action: pan-y; /* vertical scroll stays native; we handle horizontal swipes */
}
/* continuation of a same-sender, same-minute run — tighter gap (see refreshGrouping) */
.msg.cont { margin-top: 2px; }
/* swipe-to-reply (touch): reply arrow that fades in just left of the bubble */
.swipe-reply-icon {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translate(-130%, -50%);
  color: var(--link);
  opacity: 0;
  pointer-events: none;
  display: inline-flex;
}
.swipe-reply-icon svg { width: 20px; height: 20px; }
.msg .name { font-size: 12px; color: var(--muted); margin: 0 6px 3px; }
.msg .bubble {
  padding: 9px 13px;
  border-radius: var(--radius);
  background: var(--bubble-them);
  line-height: 1.35;
  white-space: pre-wrap;
  /* break long unbroken strings (e.g. a giant URL) instead of overflowing */
  overflow-wrap: anywhere;
  min-width: 0;
}
.msg .bubble code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  padding: 1px 5px;
  border-radius: 5px;
}
.msg.me .bubble code { background: color-mix(in srgb, var(--bubble-me-ink) 22%, transparent); }
.msg.me { align-self: flex-end; align-items: flex-end; }
.msg.me .bubble { background: var(--bubble-me); color: var(--bubble-me-ink); border-bottom-right-radius: 6px; }
.msg.them .bubble { border-bottom-left-radius: 6px; }
.msg .bubble a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* centered time separator that opens a burst (replaces per-message timestamps) */
.time-sep {
  align-self: center;
  margin: 16px 0 6px;
  font-size: 11px;
  color: var(--muted);
  text-align: center;
}
.time-sep strong { color: var(--ink); font-weight: 600; }

/* persistent "Edited" label under an edited bubble */
.edited-tag { font-size: 11px; color: var(--muted); margin: 1px 6px 0; }

/* Hover affordances live in a full-height strip beside the bubble: the hover zone
   spans the whole message height, so you can move straight from anywhere in the
   bubble to an icon (no need to find the vertical centre), and the action icons +
   the exact time sit in a row so they never overlap. Revealed on hover (desktop). */
.msg-actions {
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 2px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s;
}
.msg.them .msg-actions { left: 100%; padding-left: 4px; } /* just right of the bubble */
.msg.me .msg-actions { right: 100%; padding-right: 4px; flex-direction: row-reverse; } /* just left */
.msg-actions button {
  background: transparent;
  color: var(--muted);
  padding: 4px 6px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  line-height: 1;
}
.msg-actions button:hover { background: var(--bubble-them); color: var(--accent); }
.msg-actions button svg { width: 18px; height: 18px; }
.msg-actions .msg-time {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  margin: 0 4px;
}
@media (hover: hover) {
  .msg:hover .msg-actions { opacity: 1; pointer-events: auto; }
}

/* message content (text bubble + any attachments) */
.content {
  position: relative; /* anchors the absolute hover reply icon */
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-start;
  min-width: 0;
}
.msg.me .content { align-items: flex-end; }

.msg-image {
  max-width: min(260px, 100%);
  max-height: 320px;
  border-radius: var(--radius);
  display: block;
  background: var(--bubble-them);
  cursor: zoom-in;
}

/* full-screen image viewer */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 8px;
  touch-action: none; /* we own the drag: flick-to-dismiss, no browser pan/zoom */
  user-select: none;
  -webkit-user-drag: none;
  will-change: transform;
}

/* open animation: the backdrop fades, the image rises into place */
.lightbox { animation: fade-in 0.18s ease; }
.lightbox img { animation: lightbox-img-in 0.26s cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes fade-in { from { opacity: 0; } }
@keyframes lightbox-img-in {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
}

/* a newly-sent/arrived message eases up into the feed (initial history doesn't) */
.msg.appear { animation: msg-appear 0.22s cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes msg-appear {
  from { opacity: 0; transform: translateY(10px); }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox,
  .lightbox img,
  .msg.appear { animation: none; }
}

/* drag-and-drop overlay */
.drop-overlay {
  position: absolute;
  inset: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 3px dashed var(--accent);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 18px;
  color: var(--link);
  pointer-events: none;
}
.file-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--bubble-them);
  color: var(--ink);
  padding: 10px 14px;
  border-radius: var(--radius);
  text-decoration: none;
  font-size: 14px;
  max-width: 260px;
  word-break: break-word;
}
.msg.me .file-chip { background: var(--bubble-me); color: var(--bubble-me-ink); }
.file-chip .file-icon { font-size: 22px; line-height: 1; flex: none; }
.file-chip .file-meta { display: flex; flex-direction: column; min-width: 0; }
.file-chip .file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-chip .file-sub { font-size: 12px; opacity: 0.7; }

/* link (OpenGraph) preview card */
.link-preview {
  display: flex;
  flex-direction: column;
  max-width: 280px;
  background: var(--bubble-them);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
}
.lp-image { width: 100%; max-height: 150px; object-fit: cover; display: block; }
.lp-body { padding: 8px 11px; }
.lp-title { font-weight: 600; font-size: 14px; line-height: 1.3; }
.lp-desc {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lp-host { font-size: 11px; color: var(--muted); margin-top: 5px; }

/* composer attachment preview */
.composer-preview { padding: 10px 16px 0; display: flex; flex-wrap: wrap; gap: 8px; }
.composer-preview.hidden { display: none; }
.preview-spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite; flex: none;
}
.preview-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--bubble-them);
  border-radius: 12px;
  padding: 6px 34px 6px 8px;
  max-width: 100%;
}
.preview-chip img { width: 40px; height: 40px; object-fit: cover; border-radius: 8px; }
.preview-file { font-size: 24px; }
.preview-name {
  font-size: 13px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
}
.preview-remove {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  background: var(--ink);
  color: var(--panel);
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.preview-remove svg { width: 13px; height: 13px; }

/* Text selection works everywhere now: desktop reacts with a stationary hold
   (drag-select cancels it) and touch reacts with a double-tap, so long-press is
   free for native selection/copy on mobile. */
.reactions { display: flex; flex-wrap: wrap; gap: 4px; margin: 3px 2px 0; }
.msg.me .reactions { justify-content: flex-end; }
.reactions:empty { display: none; }
.reaction-chip {
  background: var(--bubble-them);
  color: var(--ink);
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 2px 9px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.35;
}
.reaction-chip.mine { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, var(--panel)); }
.emoji-picker {
  position: fixed;
  display: flex;
  gap: 2px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 6px;
  box-shadow: 0 12px 40px var(--shadow);
  z-index: 60;
}
.emoji-opt {
  background: transparent;
  font-size: 22px;
  padding: 4px 6px;
  border-radius: 50%;
  line-height: 1;
}
.emoji-opt:hover { background: var(--bubble-them); }
/* the "Reply" action in the message menu, set off from the emoji row */
.picker-action {
  background: transparent;
  color: var(--link);
  padding: 4px 8px;
  margin-left: 2px;
  border-radius: 50%;
  border-left: 1px solid var(--border);
  display: inline-flex;
  align-items: center;
  line-height: 1;
}
.picker-action svg { width: 22px; height: 22px; }
.picker-action:hover { background: var(--bubble-them); }

/* mobile long-press message menu (WhatsApp-style): a dim backdrop, the emoji
   reactions floating above the message, and a labelled action menu below it
   (see openMessageSheet in chat.js). */
.msg-sheet {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(0, 0, 0, 0.38);
  animation: fade-in 0.15s ease;
}
/* the lifted copy of the pressed message, above the dim (see openMessageSheet) */
.msg-sheet-clone {
  position: fixed;
  margin: 0 !important;
  pointer-events: none;
}
.msg-sheet-reactions,
.msg-sheet-menu {
  position: absolute;
  background: var(--panel);
  border: 1px solid var(--border);
  box-shadow: 0 12px 40px var(--shadow);
}
.msg-sheet-reactions {
  display: flex;
  gap: 2px;
  padding: 5px 7px;
  border-radius: 999px;
}
.sheet-emoji {
  background: transparent;
  font-size: 26px;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 50%;
}
.sheet-emoji:active { background: var(--bubble-them); }
.msg-sheet-menu {
  min-width: 200px;
  border-radius: 14px;
  overflow: hidden;
}
.msg-sheet-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  width: 100%;
  padding: 13px 16px;
  background: transparent;
  font-size: 16px;
  text-align: left;
  color: var(--ink);
}
.msg-sheet-item:active { background: var(--bubble-them); }
.msg-sheet-item.danger { color: var(--danger); }
.msg-sheet-icon { display: inline-flex; color: var(--muted); flex: none; }
.msg-sheet-item.danger .msg-sheet-icon { color: var(--danger); }
.msg-sheet-icon svg { width: 20px; height: 20px; }

/* a reply: a tappable quote of the parent sits above the bubble (à la Messages),
   and the parent gets a "N Replies" tag below it. */
.reply-quote {
  align-self: stretch;
  text-align: left;
  max-width: 100%;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 6px 11px;
  margin-bottom: 1px;
  font-weight: 400;
  white-space: normal;
}
.msg.me .reply-quote { text-align: right; }
.reply-quote-name { font-size: 11px; color: var(--muted); margin-bottom: 1px; }
.reply-quote-text {
  font-size: 13px;
  color: var(--muted);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msg.me .reply-quote-text { margin-left: auto; }
/* "N Replies" tag under a message that's been replied to (jumps to the first reply) */
.reply-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--link);
  margin: 3px 6px 0;
  cursor: pointer;
}
.reply-count[hidden] { display: none; }
/* flash a message when you jump to it from a quote */
.msg.flash { animation: flash-msg 1.2s ease; border-radius: var(--radius); }
@keyframes flash-msg {
  0%, 100% { background: transparent; }
  25% { background: color-mix(in srgb, var(--accent) 16%, transparent); }
}

/* composer "Replying to …" / "Editing message" banners, above the composer
   (and any attachment preview). They share the .reply-bar inner markup. */
.composer-reply,
.composer-edit {
  padding: 8px 14px;
  border-top: 1px solid var(--border);
  background: var(--panel);
}
.composer-reply.hidden,
.composer-edit.hidden { display: none; }
.composer-edit { display: flex; align-items: center; justify-content: space-between; color: var(--muted); font-size: 14px; }
.banner-x { background: transparent; color: var(--muted); font-size: 20px; line-height: 1; padding: 0 6px; }
.reply-bar { display: flex; align-items: center; gap: 10px; }
.reply-bar-icon { flex: none; color: var(--link); display: inline-flex; }
.reply-bar-icon svg { width: 18px; height: 18px; }
.reply-bar-body {
  flex: 1;
  min-width: 0;
  border-left: 2px solid var(--accent);
  padding-left: 10px;
}
.reply-bar-name { font-size: 12px; font-weight: 600; color: var(--ink); }
.reply-bar-text {
  font-size: 13px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reply-bar-cancel {
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.reply-bar-cancel svg { width: 15px; height: 15px; }
.reply-bar-cancel:hover { background: var(--bubble-them); }

/* Subtle "… is typing" line, just above the composer. Transient presence only. */
.typing {
  padding: 2px 16px 6px;
  font-size: 12px;
  font-style: italic;
  color: var(--muted);
  background: var(--panel);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.typing.hidden { display: none; }

/* composer autocomplete (:emoji / @mention) — sits above the composer, in flow */
.typeahead {
  margin: 0 8px 6px;
  max-height: 216px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: var(--shadow);
}
.typeahead.hidden { display: none; }
.ta-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: transparent;
  color: var(--ink);
  border: none;
  padding: 8px 12px;
  font-size: 15px;
}
.ta-item.active { background: var(--bubble-them); }
.ta-item .avatar { width: 28px; height: 28px; font-size: 13px; flex: none; }
.ta-emoji { font-size: 20px; width: 24px; text-align: center; flex: none; }
.ta-code { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ta-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* an @mention inside a message — just bold; it inherits the bubble ink, so it
   stays legible in both the grey "them" and accent "me" bubbles. */
.mention { font-weight: 600; }
.mention-link { cursor: pointer; }
.mention-link:hover { text-decoration: underline; text-underline-offset: 2px; }
/* the little menu a tapped mention opens (anchored + fixed, like .emoji-picker) */
.mention-menu {
  position: fixed;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 12px 40px var(--shadow);
  padding: 6px;
  z-index: 60;
}
.mention-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  color: var(--ink);
  border-radius: 8px;
  padding: 9px 14px;
  font-weight: 500;
  white-space: nowrap;
}
.mention-menu-item:hover { background: var(--bubble-them); }

.composer {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  /* Desktop (fine pointer): the input hugs the bottom — env() is 0, so no fixed
     bottom padding. Touch devices get breathing room + the safe-area inset below
     (see the pointer:coarse rule), which desktop never picks up. */
  padding: 12px 16px;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  border-top: 1px solid var(--border);
  background: var(--panel);
}
/* Touch: 12px of breathing room ABOVE the Home indicator / swipe bar (the inset),
   so the input never sits flush against the bottom edge or a browser toolbar.
   Scoped to coarse pointers so desktop stays at the bare inset (≈0). */
@media (pointer: coarse) {
  .composer { padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
  /* While typing, the keyboard covers the home-indicator inset, so drop it entirely
     (the composer sits directly on the keyboard) — .editing is toggled on focus. */
  .composer.editing { padding-bottom: 0; }
  /* Long-press opens the message menu, so suppress the native selection/callout that
     would otherwise fight it (Copy lives in the menu instead). */
  .feed .bubble { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  /* A phone is held further from the eye than a laptop screen sits — bump the
     core reading + typing text up a notch so messages are comfortable. Desktop
     (fine pointer) keeps its sizes. Composer stays ≥16px so iOS doesn't
     auto-zoom the page when the field is focused. */
  .msg .bubble { font-size: 17px; }
  .composer textarea { font-size: 17px; }
  .thread-row-name { font-size: 17px; }
  .thread-row-preview { font-size: 14px; }
  .reply-quote-text { font-size: 15px; }
}
.composer-field {
  position: relative;
  flex: 1;
  display: flex;
}
.composer textarea {
  flex: 1;
  resize: none;
  max-height: 140px;
  /* 8+8 padding + 22 line + 2 border = 40px at rest. Symmetric so the text is
     vertically centred in the box; right padding leaves room for the send button. */
  padding: 8px 46px 8px 14px;
  line-height: 22px;
  min-height: 40px;
  border-radius: 20px;
  font-family: inherit;
  overflow: hidden;
}
/* circular up-arrow send button, tucked into the bottom-right of the field */
.send-btn {
  position: absolute;
  right: 4px;
  bottom: 3px;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--accent-ink);
}
.send-btn[hidden] { display: none; }
.send-btn:disabled { opacity: 0.45; cursor: default; }
/* Enlarge the send button's tap target — mostly to the right, into the composer's
   padding — so a near-miss on touch triggers Send instead of falling through to the
   field/feed and dismissing the keyboard. */
.send-btn::after {
  content: "";
  position: absolute;
  top: -10px;
  right: -20px;
  bottom: -10px;
  left: -6px;
}
/* left-side attach (＋) button */
.attach-btn {
  flex: none;
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bubble-them);
  color: var(--ink);
  font-size: 24px;
  line-height: 1;
}


/* invite QR (admin) — full-screen opaque takeover so nothing else is on screen */
.qr-screen {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--bg);
  overflow: auto;
}
.qr-modal {
  margin: 8vh auto;
  max-width: 360px;
  text-align: center;
  border: none;
  box-shadow: none;
}
.qr-modal h2 { margin: 0 0 6px; font-size: 20px; }
.qr-wrap {
  display: flex;
  justify-content: center;
  margin: 18px 0;
}
.qr-wrap canvas {
  max-width: 100%;
  height: auto;
  image-rendering: pixelated; /* keep modules crisp when CSS scales the canvas down */
  border-radius: 8px;
}
.qr-modal .row { justify-content: center; }

/* input + button on one row (e.g. Create invite) */
.inline-form {
  display: flex;
  gap: 10px;
  align-items: stretch;
}
.inline-form input {
  flex: 1;
  min-width: 0;
}

.banner {
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  padding: 6px;
}
.banner.warn { color: var(--warn); }


/* ---- admin ---- */
.admin { max-width: 760px; margin: 0 auto; padding: 24px; }
.admin h1 { margin: 0 0 4px; }
.admin .sub { color: var(--muted); margin: 0 0 24px; }
.admin section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 18px;
  margin-bottom: 18px;
}
.admin section h2 { margin: 0 0 12px; font-size: 16px; }
.invite {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.invite:first-of-type { border-top: none; }
.invite .meta { flex: 1; min-width: 0; }
.invite .meta .label { font-weight: 600; }
.invite .meta .status { font-size: 12px; color: var(--muted); }
.invite .device-note { flex: none; max-width: 220px; font-size: 12px; color: var(--muted); text-align: right; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.key-box {
  background: var(--bubble-them);
  border-radius: 12px;
  padding: 12px;
  word-break: break-all;
  margin: 10px 0;
}
.hidden { display: none !important; }
.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  background: var(--pill-bg); color: var(--pill-ink); padding: 10px 16px; border-radius: 999px;
  font-size: 14px; opacity: 0; transition: opacity .2s; pointer-events: none;
  z-index: 100; /* above full-screen overlays (qr-screen) so it's never hidden */
}
.toast.show { opacity: 1; }


/* full-screen onboarding cards */
.card.onboard { text-align: center; }
.card.onboard .row { justify-content: center; }
.card.onboard p { margin-bottom: 14px; }
.onboard-icon { font-size: 46px; line-height: 1; margin-bottom: 10px; }
.steps {
  text-align: left;
  color: var(--muted);
  line-height: 1.6;
  padding-left: 20px;
  margin: 4px 0 18px;
}
.steps li { margin-bottom: 6px; }
.steps strong { color: var(--ink); }

/* admin: a labelled checkbox row (e.g. "let members invite people") */
.toggle-row { display: flex; align-items: center; gap: 10px; cursor: pointer; font-weight: 600; }
.toggle-row input { width: 18px; height: 18px; }

/* pending DM: waiting for an invited person to join (web/static/chat.js). While
   waiting there's no one to talk to, so the feed + composer are hidden and a single
   centered card fills the pane. It clears once the invitee joins and chats. */
.thread-view.waiting .feed,
.thread-view.waiting .composer,
.thread-view.waiting .composer-reply,
.thread-view.waiting .composer-edit,
.thread-view.waiting .composer-preview,
.thread-view.waiting .typing { display: none !important; }
.pending-banner {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  overflow-y: auto;
}
.pending-card {
  width: 100%;
  max-width: 480px;
  text-align: center;
  padding: 22px 20px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--bubble-them);
}
.pending-card p { margin: 0 0 14px; font-size: 15px; color: var(--muted); }
.pending-card .key-box { margin: 0 0 14px; text-align: left; }
.pending-card .row { justify-content: center; gap: 10px; flex-wrap: wrap; }
.linkish.danger { color: var(--danger); }

/* ---- multi-thread layout (docs/threads-and-dms.md) ---- */
.tt { display: flex; height: 100%; width: 100%; background: var(--panel); }
/* floating connection-status pill (only shown during a sustained outage) */
.conn-status {
  position: fixed;
  top: calc(10px + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 90;
  background: var(--pill-bg);
  color: var(--pill-ink);
  font-size: 13px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 999px;
  box-shadow: 0 6px 20px var(--shadow);
}
.conn-status.hidden { display: none; }
.sidebar { width: 320px; flex: none; display: flex; flex-direction: column; background: var(--sidebar); border-right: 1px solid var(--border); min-height: 0; }
/* Both headers share a height so the thread pane's header lines up with the
   sidebar's. Only the thread header carries a divider — the sidebar's "Chats"
   header flows straight into the chat list (with the search field below it). */
.side-head, .thread-head { box-sizing: border-box; min-height: 62px; padding: 8px 16px; }
.thread-head { border-bottom: 1px solid var(--border); }
.side-head { display: flex; align-items: center; gap: 8px; }
.side-head .title { flex: 1; min-width: 0; font-family: var(--font-display); font-weight: 700; font-size: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.head-actions { display: flex; align-items: center; gap: 2px; flex: none; }
.avatar {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 34px; height: 34px; border-radius: 50%; color: #fff; font-weight: 700; font-size: 15px; line-height: 1;
}
.avatar-btn { background: transparent; padding: 0; border-radius: 50%; flex: none; }
.avatar-lg { width: 64px; height: 64px; font-size: 28px; }
.avatar.zoomable { cursor: pointer; } /* header avatar with a real photo: tap to zoom */
.field-label { display: block; font-size: 13px; color: var(--muted); margin: 2px 0 4px; }
/* Filter the chat list. Sits between the header and the list; hidden when there
   are no chats (renderThreadList toggles .hidden). */
.chat-search { flex: none; padding: 4px 12px 6px; }
.chat-search input {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 14px;
  padding: 8px 12px; border: 1px solid transparent; border-radius: 12px;
  background: var(--bubble-them); color: var(--ink); -webkit-appearance: none; appearance: none;
}
.chat-search input::placeholder { color: var(--muted); }
/* Keep the app's standard focus ring (global input:focus); just lift the fill. */
.chat-search input:focus { background: var(--panel); }
.thread-list { flex: 1; overflow-y: auto; min-height: 0; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.thread-empty-note { color: var(--muted); padding: 16px; font-size: 14px; }
.thread-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: transparent; color: var(--ink); border-radius: 14px; padding: 9px 14px;
}
.thread-row:hover { background: var(--bubble-them); }
.thread-row.active { background: var(--row-active); }
/* Bigger avatar in the chat list (à la WhatsApp/iMessage); scoped to the list so
   header/profile/reaction avatars keep their sizes. */
.thread-row .avatar { width: 52px; height: 52px; font-size: 21px; }
.thread-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.thread-row-name { font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Up to two lines of the latest message, then ellipsize (the spacious look).
   white-space:normal undoes the nowrap inherited from the global `button` rule
   (.thread-row is a <button>) — without it the line-clamp can't wrap and the
   preview gets hard-clipped on a single line. overflow-wrap lets long unbroken
   tokens (e.g. a pasted URL) wrap too. */
.thread-row-preview {
  font-size: 13px; font-weight: 400; color: var(--muted); line-height: 1.3;
  white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Time sits top-right, aligned with the name, even when the preview wraps to two lines. */
.thread-row-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; flex: none; align-self: flex-start; padding-top: 3px; }
.thread-row-time { font-size: 11px; font-weight: 400; color: var(--muted); white-space: nowrap; }
.unread-dot { width: 8px; height: 8px; border-radius: 50%; background: transparent; flex: none; }
.unread-dot.on { background: var(--accent); }
/* Mobile list treatment (à la Messages/WhatsApp): rows run edge-to-edge, the active
   state is a full-width fill (no rounded bubble), and a hairline divider separates
   rows starting just after the avatar (not under it). */
@media (pointer: coarse) {
  .thread-list { padding: 0; gap: 0; }
  .thread-row { border-radius: 0; padding: 11px 16px; position: relative; }
  .thread-row::after {
    content: "";
    position: absolute;
    left: calc(16px + 52px + 12px); /* row padding-left + avatar width + gap */
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--border);
  }
  /* No divider under the last row, nor under the active row (its fill owns the edge). */
  .thread-row:last-child::after,
  .thread-row.active::after { display: none; }
}
.thread-pane { flex: 1; display: flex; min-height: 0; min-width: 0; position: relative; }
.empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--muted); gap: 6px; text-align: center; padding: 24px; }
.empty-art { font-size: 42px; }
/* min-width:0 lets these panes shrink below their content so a long unbroken
   URL wraps inside a bubble instead of widening the whole column. */
.thread-view { flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0; width: 100%; position: relative; }
.thread-head { display: flex; align-items: center; gap: 6px; }
.thread-head-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.thread-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread-sub { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.back-btn { display: none; font-size: 24px; padding: 0 8px; }
.feed { flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0; min-width: 0; padding: 16px; display: flex; flex-direction: column; gap: 4px; }
.feed > .msg { max-width: 78%; }
.feed::before { content: ""; margin-top: auto; }

/* modal sheet (new chat / add device) — centered overlay */
.overlay {
  position: fixed; inset: 0; z-index: 60; background: rgba(20, 30, 80, 0.45);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.sheet { max-width: 460px; width: 100%; max-height: 82vh; overflow-y: auto; }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.sheet-head h2 { margin: 0; }
.linkish { background: transparent; color: var(--link); padding: 6px 8px; font-weight: 600; }
.sheet-input { margin-bottom: 10px; }
/* full-bleed list inside the sheet: rows span the card edges (which pads 28px) */
.pick-list { margin: 4px -28px 0; max-height: 56vh; overflow-y: auto; }
.list-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: transparent; color: var(--ink); border: none; border-bottom: 1px solid var(--border);
  padding: 14px 28px; border-radius: 0; font-weight: 500; font-size: 16px; cursor: pointer;
}
.list-row:hover:not(.static) { background: var(--bubble-them); }
.list-row.static { cursor: default; }
.list-row input { width: auto; }
/* member row: tappable identity on the left, a Remove action on the right */
.member-main {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 12px;
  background: transparent; color: inherit; border: none; text-align: left; font: inherit; padding: 0; cursor: pointer;
}
.member-main span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-remove { flex: none; background: transparent; color: var(--danger); font-weight: 600; font-size: 14px; padding: 4px 8px; }
.sheet-foot { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }

/* theme picker (kebab → Theme) */
.appearance-seg { display: flex; gap: 6px; background: var(--bubble-them); border-radius: 12px; padding: 4px; }
.appearance-seg button { flex: 1; background: transparent; color: var(--muted); border-radius: 9px; padding: 9px; font-weight: 600; }
.appearance-seg button[aria-pressed="true"] { background: var(--panel); color: var(--ink); box-shadow: 0 1px 4px var(--shadow); }
.theme-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.theme-swatch {
  display: flex; align-items: center; gap: 11px; text-align: left;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--border); border-radius: 14px; padding: 12px;
  font-weight: 600; font-family: var(--font-display);
}
.theme-swatch:hover { background: var(--bubble-them); }
.theme-swatch.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.theme-swatch .disc { width: 26px; height: 26px; border-radius: 50%; flex: none; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12); }

/* mobile: show one pane at a time, toggled by .show-thread */
@media (max-width: 720px) {
  .sidebar { width: 100%; border-right: none; }
  .thread-pane { display: none; }
  .tt.show-thread .sidebar { display: none; }
  .tt.show-thread .thread-pane { display: flex; }
  .back-btn { display: inline-flex; }
}
