/* ==========================================================================
   cocono-chat — structural stylesheet.
   Never hard-codes colors: everything visual comes from the theme tokens
   below (defaults = Dark). A theme file (themes/<id>/theme.css) overrides
   these custom properties; see docs/THEMES.md.
   ========================================================================== */

:root {
  /* default tokens (Dark) — themes override these */
  --bg: #0b141a;
  --panel: #111b21;
  --panel-raised: #202c33;
  --border: #222d34;
  --divider: #1c262d;
  --text: #e9edef;
  --muted: #8696a0;
  --accent: #7d6ce0;           /* light purple — PRIMARY (buttons, links, highlights) */
  --accent-strong: #4b3fa8;    /* dark purple — SECONDARY (hovers, avatar discs) */
  --on-accent: #fff;
  --bubble-in: #2b2545;   /* deep purple (receiving) */
  --bubble-out: #7d6ce0;   /* light purple (sending) */
  --bubble-text: #e9edef;
  --danger: #ef6975;
  --success: #00a884;
  --warning: #ffa355;
  --info: #58a6ff;          /* the "trusted" (third stage) blue */
  --dot-off: #556068;
  --dot-on: #00a884;
  --dot-busy: #ffa355;
  --overlay: rgba(0, 0, 0, 0.55);   /* drawer scrim */
  --shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
  --radius-bubble: 8px;

  --sidebar-w: 380px;
  --head-h: 54px;
  /* bottom safe-area padding: only installed (standalone) apps sit under a
     home indicator — in a browser the toolbar already provides the space */
  --sa-b: 0px;
  font-size: 16px;
}
@media (display-mode: standalone) {
  :root { --sa-b: env(safe-area-inset-bottom); }
}

* { box-sizing: border-box; margin: 0; }

html, body { height: 100%; overflow: hidden; }
/* Lock the DOCUMENT: with plain height:100% the page itself is scrollable,
   and iOS pans the whole document when a near-edge input takes focus — the
   header slides out of frame and the old scrollTo(0,0) correction then
   yanked it back (visible "jump down, animate up" keyboard jank). The app
   owns all scrolling (app-shell clips; lists scroll internally), so the
   body is fixed and the document can NEVER be panned. */
body { position: fixed; inset: 0; overflow: hidden; height: var(--app-h, 100dvh); }
/* STANDALONE (installed PWA) unlocks the body: iOS resizes the webview
   natively there (no pan to ride), and a position:fixed layer is exactly
   what the classic standalone keyboard bug displaces INVISIBLY (native
   content offsets that scrollY/offsetTop never report — traces pristine,
   header still jumping). In normal flow the JS-fitted document is exactly
   viewport-sized at rest (nothing to scroll), and any native offset during
   the resize animation surfaces as a REAL document scroll keyboard.js can
   see and squash per frame. --kb-room (2px, managed by keyboard.js while
   the keyboard machinery is active) guarantees a minimal scroll RANGE:
   without it the exactly-fitted document is unscrollable, scrollTo(0,0)
   is a no-op, and WKWebView's displacement (including the negative bounce
   that leaves a band at the top) is neither observable nor resettable.
   NOTE: 100dvh does NOT track the standalone
   keyboard resize (iOS freezes viewport units there — field-proven: the
   composer ended up behind the keys), so the height always comes from the
   --app-h px mirror. */
@media (display-mode: standalone) {
  body { position: static; height: calc(var(--app-h, 100dvh) + var(--kb-room, 0px)); }
}
/* --vv-top ride — TAB-MODE iOS ONLY (keyboard.js adds .kb-fit there; the
   standalone media rule above keeps the body flow-positioned and
   transform-free, so its fixed overlays stay viewport-anchored): when
   tab-mode Safari pans the visual viewport, the body — and with it the
   WHOLE app incl. drawers/modals/toast — shifts down by exactly the pan.
   Net effect on screen: nothing moves, the header stays glued to the top
   edge. Android/desktop never set --vv-top → no-op. */
body.kb-fit { transform: translateY(var(--vv-top, 0px)); }
/* Mobile: no double-tap zoom anywhere (tap targets are app chrome, not a
   document). Panning stays alive — .drawer-tabs swipe and the message list
   scroll depend on it; pinch is off via viewport meta + gesturestart guard. */
body { touch-action: manipulation; }

body {
  font-family: "Segoe UI", system-ui, -apple-system, Roboto, Ubuntu, sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

button { font: inherit; color: inherit; cursor: pointer; }
input, select { font: inherit; }
ul { list-style: none; padding: 0; }
[hidden] { display: none !important; }

.muted { color: var(--muted); }
.small { font-size: 0.85rem; }
.error, .status.error { color: var(--danger); }
.status { min-height: 1.2rem; color: var(--muted); font-size: 0.85rem; margin-top: .5rem; }

/* ---------- generic controls ---------- */

.btn {
  border: 0; border-radius: 999px; padding: .6rem 1.2rem;
  background: var(--panel-raised); color: var(--text);
}
.btn:disabled { opacity: .5; cursor: default; }
.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover:not(:disabled) { background: var(--accent-strong); }
.btn-accent { background: var(--accent); color: var(--on-accent); }
.btn-ghost { background: transparent; color: var(--muted); }
.btn-block { display: block; width: 100%; margin-top: .6rem; }
.btn.round { border-radius: 50%; width: 44px; height: 44px; padding: 0; flex: none; }

.icon-btn {
  background: transparent; border: 0; color: var(--muted);
  font-size: 1.15rem; padding: .3rem .5rem; border-radius: 8px;
}
.icon-btn:hover { color: var(--text); background: var(--panel-raised); }
/* .dot / .dot-on / .dot-busy removed with the sidebar connection dot (
   the only online indicator we had; the offline banner covers outages). */

.linkish { background: none; border: 0; color: var(--accent); padding: .2rem; }
.linkish:hover { text-decoration: underline; }

.field input, .new-chat input, .approve-code input, .select {
  background: var(--panel-raised); border: 1px solid var(--border);
  color: var(--text); border-radius: 999px; padding: .6rem 1rem; width: 100%;
}
.field input:focus, .new-chat input:focus, .approve-code input:focus, .select:focus {
  outline: none; border-color: var(--accent);
}

.avatar {
  width: 38px; height: 38px; border-radius: 50%; flex: none;
  background: var(--accent-strong); color: var(--on-accent);
  display: grid; place-items: center; font-weight: 600; text-transform: uppercase;
}


/* ---------- auth ---------- */
/* The document is locked (body fixed) — the auth view is its OWN scroller
   and pads above the keyboard (--kb-h from keyboard.js) so focused inputs
   are never stranded under the keys. */
#view-auth { overflow-y: auto; padding-bottom: calc(1rem + var(--kb-h, 0px)); }

.view { height: 100%; display: grid; place-items: center; padding: 1rem; }

.auth-card {
  width: min(420px, 100%); background: var(--panel); border-radius: 16px;
  padding: 2rem; box-shadow: var(--shadow); display: grid; gap: 1rem;
}
.auth-brand { text-align: center; display: grid; gap: .3rem; }
.logo {
  width: 56px; height: 56px; margin: 0 auto; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  display: grid; place-items: center; font-size: 1.8rem;
}
/* Icon variant (login + empty chat): the app icon PNG instead of the glyph.
   Background off so nothing bleeds around non-square/transparent art. */
img.logo { object-fit: cover; background: none; }
.logo.big { width: 84px; height: 84px; font-size: 2.6rem; }
.auth-links { display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; }

.code-display {
  font-size: 2.4rem; letter-spacing: .35em; text-align: center;
  font-variant-numeric: tabular-nums; color: var(--accent); padding: .5rem 0;
}

/* ---------- app shell: two panes ---------- */

/* --app-h tracks the ACTUALLY visible viewport (keyboard excluded) — set by
   js/keyboard.js; 100dvh is the boot/no-VisualViewport fallback. Result:
   opening the keyboard shrinks the conversation between the header and the
   keys instead of pushing the header off-screen. */
.app-shell { display: flex; height: var(--app-h, 100dvh); overflow: hidden; }

.sidebar {
  width: 100%; min-width: 0; display: flex; flex-direction: column;
  background: var(--panel); border-right: 1px solid var(--border);
}
.chat-pane { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--bg); }

.side-head {
  /* min-height (not fixed): the head may carry the double-line component
     WITH its Verified sub — a fixed one-line height clipped it */
  min-height: var(--head-h); flex: none; display: flex; align-items: center;
  justify-content: space-between; padding: .35rem 1rem; gap: .6rem;
  /* Non-scrollable chrome must not be a scroll candidate: iOS otherwise
     lets a drag here pull the layout viewport (and everything anchored
     to it) around — research: ios-pwa-keyboard-fix mistake #3. */
  touch-action: none;
}
.me { display: flex; align-items: center; gap: .5rem; min-width: 0; cursor: pointer; border-radius: 10px; padding: .15rem .3rem; }
.me:hover, .me:focus-visible { background: var(--panel-raised); outline: none; }
/* .own-avatar deliberately has NO size overrides: every double-line
   component avatar renders at the base .avatar size (38px) */
.own-avatar { object-fit: cover; }
img.own-avatar { border-radius: 50%; }
#tabpanel-profile h3, .profile-h3 { margin: 1.1rem 0 .5rem; }
#tabpanel-profile .profile-id.own-profile { margin: .6rem 0 1rem; }
.profile-actions { margin: .2rem 0 .4rem; gap: .8rem; align-items: center; }
.bio-meta { margin: .15rem 0 0; }
.save-chip {
  margin-left: auto; font-size: .72rem; font-weight: 700; color: var(--success);
  background: rgba(125, 108, 224, 0.14); border-radius: 999px; padding: .18rem .6rem;
  transition: opacity .3s ease;
}
.save-chip.bad { color: var(--danger); background: rgba(239, 105, 117, 0.12); }
.bio {
  display: block; box-sizing: border-box; width: 100%; min-height: 5rem;
  font-family: inherit; font-size: .9rem; line-height: 1.4;
  margin: .1rem 0 .15rem; resize: vertical;
  border-radius: 10px; padding: .55rem .8rem;
  background: var(--panel-raised); border: 1px solid var(--border); color: var(--text);
}
.bio:focus { outline: none; border-color: var(--accent); }
.btn i { margin-right: .45em; }
.btn:not(:has(i)) { }
.you-chip { color: var(--success); font-weight: 700; }

/* two-row identity block: name (+red badge) with "Get verified" beneath */
.me-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.me-line { display: flex; align-items: center; gap: .35rem; min-width: 0; }
.me-line .me-name { min-width: 0; }
.me-name {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* the ellipsis clip box must INCLUDE descenders: with the inherited tight
     line-height, the bottom of g/j/p/y was shaved off by overflow:hidden */
  line-height: 1.45;
}
.side-actions { display: flex; align-items: center; gap: .3rem; }

.side-status { padding: 0 1rem; flex: none; }

.new-chat { display: flex; gap: .5rem; padding: .6rem 1rem; flex: none; position: relative; }
.new-chat .btn { flex: none; }

.device-list li {
  display: flex; justify-content: space-between; align-items: baseline;
  flex-wrap: wrap; gap: .5rem;
  padding: .35rem 0; border-bottom: 1px dashed var(--divider); font-size: .9rem;
}
.device-list .dim { color: var(--muted); }
.device-list .device-name { font-weight: 600; }
.device-list .device-rename-input {
  font: inherit; font-weight: 600; width: 100%;
  background: var(--panel-raised); color: var(--text);
  border: 1px solid var(--accent); border-radius: 6px; padding: .15rem .4rem;
}
.approve-code { display: flex; gap: .5rem; }
.approve-code .btn { flex: none; }

/* Settings > Limits: the vouching-budget table */
.usage-table { width: 100%; border-collapse: collapse; font-size: .88rem; margin: .35rem 0 .5rem; }
.usage-table th, .usage-table td {
  text-align: left; padding: .35rem .5rem; border-bottom: 1px dashed var(--divider);
}
.usage-table thead th {
  color: var(--muted); font-weight: 500; font-size: .74rem;
  text-transform: uppercase; letter-spacing: .05em;
}
.usage-table tbody th { font-weight: 600; }
.usage-table td { font-variant-numeric: tabular-nums; }
.usage-spent { color: var(--danger); font-weight: 600; }

/* Pairing review modal: the 'who is knocking' block */
.pair-who {
  display: flex; align-items: center; gap: .7rem;
  background: var(--panel-raised); border-radius: 10px; padding: .6rem .8rem;
  margin-bottom: .8rem;
}
.pair-ic { font-size: 1.5rem; color: var(--accent); }
.pair-body { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.pair-platform { font-weight: 700; font-size: 1.02rem; }
.pair-name-field { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .6rem; }
.row-actions { display: flex; gap: .5rem; align-items: center; }
.row-actions .select { width: auto; }

.convo-wrap { flex: 1; overflow-y: auto; }

/* Sidebar tag FILTER bar (foot): a row of quiet icons. Active = brand
   purple (var(--accent)); inactive = muted grey. 'All' is just another
   slot that happens to be the default — it lights purple whenever no
   real tag is chosen. Never a badge/count here: this is an index control. */
.convo-filter {
  display: flex; justify-content: space-around; align-items: center;
  gap: .3rem; padding: .5rem .6rem; flex: none;
  border-top: 1px solid var(--border); background: var(--panel);
}
.filter-btn {
  display: grid; place-items: center; width: 2.4rem; height: 2.4rem;
  border: 0; border-radius: 10px; background: none; cursor: pointer;
  color: var(--muted); font-size: 1.05rem; transition: color .12s, background .12s;
}
.filter-btn:hover { background: var(--panel-raised); }
.filter-btn.filter-on { color: var(--accent); }
.filter-btn.filter-on i { transform: none; } /* colour, not size — stays quiet */

/* Chat TAG row (under the head): same purple/grey language as the sidebar
   filter so the two read as one system; the mute button rides the strip's
   RIGHT edge (margin-left:auto absorbs every px of leftover space). */
.chat-tagbar {
  display: flex; gap: .2rem; align-items: center; padding: .2rem .8rem .5rem;
  border-bottom: 1px solid var(--border); flex: none;
}
.tagbar-icons { display: inline-flex; gap: .2rem; align-items: center; }
.chat-tagbar .tagbar-mute { margin-left: auto; }
/* the row announces itself — quiet 'Tag:' lead-in; its right margin joins
   the gap rhythm so the first icon never sits glued to the colon */
.chat-tagbar .tagbar-label {
  font-size: .78rem; color: var(--muted); white-space: nowrap; margin-right: .15rem;
}
.tag-btn {
  display: grid; place-items: center; width: 2.1rem; height: 2.1rem;
  border: 0; border-radius: 9px; background: none; cursor: pointer;
  color: var(--muted); font-size: .98rem; transition: color .12s, background .12s;
}
.tag-btn:hover { background: var(--panel-raised); }
.tag-btn.tag-on { color: var(--accent); }
.tag-btn:disabled { opacity: .5; cursor: default; }
.convo-list li > button {
  width: 100%; display: flex; gap: .75rem; align-items: center; text-align: left;
  padding: .7rem 1rem; background: none; border: 0;
}
.convo-list li > button:hover { background: var(--panel-raised); }
.convo-list .convo-meta { flex: 1; min-width: 0; display: grid; gap: .1rem; }
.convo-list .convo-name { font-weight: 600; }
.convo-list .convo-name.gone { font-style: italic; }
.convo-list .convo-name i { font-size: .72rem; margin-right: .5em; vertical-align: .05em; }
/* the chat-head sub-line ("N devices", flags) uses the exact message-preview
   typography + overflow behaviour of the sidebar rows */
.sub-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.convo-list .convo-last, #chat-sub {
  color: var(--muted); font-size: .85rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.convo-list .convo-last.empty { font-style: italic; }
.convo-list .convo-side { display: grid; justify-items: end; gap: .3rem; }
.convo-list .convo-time { color: var(--muted); font-size: .75rem; }
.unread { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }

/* ---------- chat pane ---------- */

.chat-empty { flex: 1; display: grid; place-items: center; text-align: center; padding: 2rem; }
.empty-inner { display: grid; gap: .8rem; justify-items: center; }

/* ---------- settings drawer (top sheet + scrim) ---------- */

.overlay {
  position: fixed; inset: 0; z-index: 40;
  background: var(--overlay);
  animation: overlay-in .18s ease-out;
}
@keyframes overlay-in { from { opacity: 0; } to { opacity: 1; } }

/* Settings content blocks (chrome now uses the shared .side-menu panel). */
.drawer-head {
  position: sticky; top: 0; display: flex; align-items: center; justify-content: space-between;
  background: var(--panel); padding: .5rem 0; z-index: 1;
}
.drawer-head h2 { font-size: 1.05rem; }
.drawer-body { display: grid; gap: .6rem; }
.drawer-body h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: .6rem 0 .55rem; }

.chat-view { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* the slide transforms 18% to the right — clip laterally so it can never
   provoke a horizontal scrollbar or page wobble mid-animation */
.app-shell > main { overflow-x: clip; }
/* Opening a chat slides the pane in from the right — SNAPPY: short distance,
   fast 160ms, decisive ease-out so it decelerates into place. The animation
   restarts naturally because [hidden] (display:none) -> shown re-triggers it.
   Reduced-motion users get it instantly, no slide. */
@keyframes chat-slide-in {
  from { transform: translateX(-18%); opacity: .55; }
  to   { transform: none; opacity: 1; }
}
/* class-driven (JS adds .entering) so SWITCHING chats — where the pane is
   never display-toggled and a selector-based animation could not restart —
   animates the new conversation in too */
@media (prefers-reduced-motion: no-preference) {
  .chat-view.entering { animation: chat-slide-in .185s cubic-bezier(.22, .9, .3, 1) both; }
}
/* swipe/back close: the reverse hop — accelerated ease-IN so it leaves
   quickly and never feels like it's being dragged away */
@keyframes chat-slide-out {
  from { transform: none; opacity: 1; }
  to   { transform: translateX(-18%); opacity: .55; }
}
@media (prefers-reduced-motion: no-preference) {
  .chat-view.closing { animation: chat-slide-out .16s cubic-bezier(.4, 0, .85, .35) both; }
}

/* Mobile: 18% travel on a narrow pane covers few physical pixels, so the
   identical duration reads as abrupt versus desktop. Phones get a slightly
   slower, slightly longer glide — still quick, just legible. */
@media (prefers-reduced-motion: no-preference) and (max-width: 720px) {
  @keyframes chat-slide-in {
    from { transform: translateX(-26%); opacity: .45; }
    to   { transform: none; opacity: 1; }
  }
  @keyframes chat-slide-out {
    from { transform: none; opacity: 1; }
    to   { transform: translateX(-26%); opacity: .45; }
  }
  .chat-view.entering { animation-duration: .33s; }
  .chat-view.closing { animation-duration: .285s; }
}

.chat-head {
  height: var(--head-h); flex: none; display: flex; align-items: center; gap: .6rem;
  padding: 0 1rem; background: var(--panel); border-bottom: 1px solid var(--border);
  touch-action: none; /* see .side-head — chrome is not a scroll candidate */
}
.chat-peer { display: grid; }
/* THE name surface is the uname component (userline.js) — inline flow with
   the shared icon/chip margins. No flex gap here: it stacked ON TOP of the
   component's own .5em/.3em rhythm and made the chat head spacing differ
   from the conversation list it is supposed to match. */
.chat-peer-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.messages {
  flex: 1; overflow-y: auto; padding: 1rem clamp(.6rem, 6vw, 4rem);
  display: flex; flex-direction: column; gap: .35rem;
}
.msg {
  max-width: min(68%, 560px); width: fit-content; padding: .45rem .7rem;
  border-radius: var(--radius-bubble); background: var(--bubble-in); color: var(--bubble-text);
  position: relative; word-wrap: break-word; overflow-wrap: anywhere; white-space: pre-wrap;
}
.msg.out { align-self: flex-end; background: var(--bubble-out); }
.msg.in { align-self: flex-start; }
.msg .meta {
  display: inline-flex; gap: .35rem; align-items: baseline; float: right;
  margin: .35rem 0 0 .5rem; font-size: .7rem; color: var(--muted);
}
.msg.out .meta { color: #e3dcff; }

/* Security/system timeline notices: centred, amber, non-interactive */
.msg-sys {
  align-self: center; max-width: 88%;
  text-align: center; font-size: .75rem; line-height: 1.4;
  color: var(--warning); background: rgba(255, 163, 85, 0.10);
  border: 1px solid rgba(255, 163, 85, 0.25);
  border-radius: 12px; padding: .4rem .8rem; margin: .3rem 0;
}
.msg { cursor: pointer; } /* tap -> message modal (copy / forward / delete) */

/* Chat header options button (⋮ -> modal, marked right of the peer name). */
.chat-menu-btn { margin-left: auto; }

/* Message modal: the text scrolls, the action row stays fixed underneath. */
.msg-modal-text {
  margin: .4rem 0 0; padding: .6rem .8rem; min-height: 3rem;
  max-height: 45vh; overflow-y: auto;
  background: rgba(127, 127, 127, .08); border-radius: 10px;
  white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
  font-size: .95rem; line-height: 1.45;
}
/* Message modal action row: link-style options (not pill buttons). */
.msg-modal-actions {
  margin-top: .5rem; flex-wrap: nowrap; gap: .35rem; padding: 0 .1rem .1rem;
}
/* keep Copy | Forward | Delete on one line at every panel width */
.msg-modal-actions .linkish { white-space: nowrap; padding-inline: .3rem; font-size: .85rem; }
/* As a side menu the text area fills the panel and the action row stays
   fixed at the bottom (scoped: the forward dialog keeps its 45vh preview). */
#msg-modal.side-menu { display: flex; flex-direction: column; }
#msg-modal .msg-modal-text { flex: 1 1 auto; max-height: none; }
.linkish.danger { color: var(--danger); }

/* ---- BLOCK confirm: required reason choice-list (three lines, never a
   dropdown — the options must be readable at a glance on a phone) ---- */
.block-reasons { display: flex; flex-direction: column; gap: .55rem; margin-top: .4rem; }
.block-reasons label {
  display: flex; align-items: flex-start; gap: .55rem;
  padding: .55rem .7rem; border: 1px solid var(--border); border-radius: .6rem;
  cursor: pointer; font-size: .9rem;
}
.block-reasons label:hover { background: rgba(255, 255, 255, 0.05); }
.block-reasons input { margin-top: .15rem; accent-color: var(--danger); }
.block-reasons label:has(input:checked) { border-color: var(--danger); }
.block-reasons .reason-icon { margin-top: .1rem; opacity: .85; }
.rel-pill .reason-icon { margin-right: .35em; }

/* ---- REPORT modal: shares the block-reason choice-list styling above;
   the description + optional-block rows continue the same rhythm ---- */
.report-desc {
  display: block; box-sizing: border-box; width: 100%; resize: vertical;
  min-height: 4.2rem; margin-top: .55rem; padding: .55rem .85rem;
  background: var(--panel-raised); border: 1px solid var(--border);
  color: var(--text); border-radius: .6rem; font: inherit; font-size: .9rem;
}
.report-desc:focus { outline: none; border-color: var(--danger); }
.report-block {
  display: flex; align-items: flex-start; gap: .55rem; margin-top: .55rem;
  padding: .55rem .7rem; border: 1px solid var(--border); border-radius: .6rem;
  cursor: pointer; font-size: .9rem;
}
.report-block:hover { background: rgba(255, 255, 255, 0.05); }
.report-block input { margin-top: .15rem; accent-color: var(--danger); }
.report-block .reason-icon { margin-top: .1rem; opacity: .85; }

/* ---- Settings > Relationships ---- */
.rel-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: .6rem; }
.rel-toolbar .input { flex: 1 1 10rem; }
.rel-toggles { display: inline-flex; gap: .4rem; }
.toggle-chip {
  padding: 4px 12px; border-radius: 999px; border: 1px solid var(--border);
  background: transparent; color: inherit; font: inherit; font-size: .82rem; cursor: pointer;
}
.toggle-chip[aria-pressed="true"] { border-color: currentColor; font-weight: 600; }
.toggle-chip.off { opacity: .45; }
/* Settings > Relationships TABLE: fixed four columns, actions aligned —
   nothing shifts as mutes/blocks change */
.rel-list { margin: 0; padding: 0; }
.rel-table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.rel-table thead th {
  text-align: left; font-size: .7rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
  padding: .35rem .4rem; border-bottom: 1px solid var(--border);
}
.rel-table td { padding: .5rem .4rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
.rel-table td:first-child { min-width: 9.5rem; }
.rel-row-blocked td { background: rgba(239, 105, 117, 0.06); }
.rel-reason {
  display: flex; align-items: center; gap: .35em;
  font-size: .75rem; color: var(--muted); margin-top: .15rem;
}
.rel-table .uname { font-weight: 600; }
.rel-action { white-space: nowrap; }
.rel-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  padding: .55rem .2rem; border-bottom: 1px solid var(--border);
}
.rel-name { font-weight: 600; min-width: 7.5rem; }
.rel-info { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.rel-pill {
  padding: 2px 9px; border-radius: 999px; font-size: .75rem;
  border: 1px solid var(--border); color: inherit;
}
.rel-pill.ok { color: var(--success, #4caf7d); border-color: currentColor; }
.rel-pill.warn { color: var(--warning); border-color: currentColor; }
.rel-pill.bad { color: var(--danger); border-color: currentColor; }
.rel-pill.blocked { color: var(--danger); border-color: currentColor; font-weight: 700; }
.rel-pill.reason { opacity: .8; }
.rel-row-blocked { background: rgba(239, 105, 117, 0.06); }
.rel-action { margin-left: .2rem; }

/* sidebar: muted-conversation marker before the preview text */
.convo-muted { font-size: .68rem; margin-right: .45em; vertical-align: .05em; color: var(--muted); }

/* trust-warning actions: a NEW centered line of pills under the bar text
   (the bar itself is text-align:center — inline tails would crowd the last
   word). One rem rhythm: .6rem above, .15rem below the symmetric .4rem bar
   padding. */
.chat-warn .warn-actions {
  display: flex;
  justify-content: center;
  gap: .6rem;
  margin: .6rem auto .15rem;
}
.chat-warn .warn-action {
  padding: 4px 14px;
  border-radius: 999px;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.chat-warn .warn-action:hover { background: rgba(255, 255, 255, 0.12); }
.chat-warn .warn-action:disabled { opacity: .55; cursor: default; }

/* drawer head: logout + close sit together as a pair */
.drawer-actions { display: inline-flex; align-items: center; gap: 6px; }
.icon-btn.danger { color: var(--danger); }
.icon-btn.danger:hover { background: rgba(239, 105, 117, 0.14); }

/* Off-screen selection helper for the clipboard fallback (never visible). */
.copy-helper { position: fixed; top: -100vw; left: -100vw; opacity: 0; }

/* forward flow: a target is selected, Send is armed */
.btn.ready { box-shadow: 0 0 0 2px var(--accent), 0 0 14px rgba(125, 108, 224, 0.45); }

/* Safety number panel: smaller text; the WHOLE box is the copy target with
   a tag bottom-right; .copied highlights until the next tap re-copies. */
.identity-number {
  position: relative; margin: .6rem 0 .3rem; padding: .7rem .7rem 1.5rem;
  min-height: 3.6rem;
  font-family: ui-monospace, monospace; font-size: .85rem; letter-spacing: .05em;
  line-height: 1.7; text-align: center; word-break: break-all;
  background: rgba(127, 127, 127, .08); border: 1px solid transparent;
  border-radius: 10px; user-select: none; cursor: pointer;
}
.identity-number:hover { border-color: var(--border); }
.identity-number.copied {
  border-color: var(--accent);
  background: rgba(125, 108, 224, 0.10);
}
.identity-copy-tag {
  position: absolute; right: .55rem; bottom: .35rem;
  display: inline-flex; align-items: center; gap: .3em;
  font-size: .68rem; color: var(--muted);
}
.identity-number.copied .identity-copy-tag { color: var(--accent); }

/* Grey certificate: admin-verified real person (independent of the peer
   trust ladder) — shown after names wherever they appear. */
.verify-badge { color: var(--danger) !important; font-size: .72em; margin-left: .4em; }
.badge-slot { display: inline; }
.badge-slot .verify-badge { margin-left: .35em; }
.verify-link {
  font-size: .68rem; margin-left: 0; opacity: .9; color: var(--warning) !important;
  padding: 0; line-height: 1.15; border: 0; background: none; align-self: flex-start;
}
/* chat-head sub-line flags (Unverified user / Account deleted) */
#chat-sub .sub-flag { color: var(--danger); }

/* Trust warning strip (unverified chat): stays under the header, above the
   transcript. Amber = caution, not danger — encryption still holds. */
.chat-warn {
  flex: none; padding: .4rem .9rem; font-size: .82rem; text-align: center;
  background: rgba(255, 163, 85, 0.12); color: var(--warning);
  border-bottom: 1px solid var(--border);
}
/* trust ladder tiers for the strip: base = amber (warn), .danger = red */
.chat-warn.danger { background: rgba(239, 105, 117, 0.12); color: var(--danger); }

/* Font Awesome icons: every glyph comes from js/icons.js (vendored FA Free,
   /vendor/fontawesome). Sizing + tint helpers only — no per-view magic. */
.icon-btn i { font-size: 1.05rem; }
#btn-send i { font-size: 1rem; }
.linkish i { margin-right: .45em; }
.icon-danger { color: var(--danger) !important; }
.icon-warn { color: var(--warning) !important; }
/* safety-number panel: the "they must add you back" gate, same amber tier */
.identity-mutual-note { color: var(--warning); font-size: .85rem; margin: .35rem 0 0; }
.identity-mutual-note i { margin-right: .35em; }
.icon-friend { color: var(--success) !important; }
.icon-trust { color: var(--info) !important; }

/* Shared username tag (components/peername.js): icon + name, one layout. */
.peer-tag { display: inline-flex; align-items: center; gap: .45em; min-width: 0; }
.peer-tag i { flex: none; }
.peer-tag.gone > span:last-child { font-style: italic; }
.msg .meta i { font-size: .68rem; margin-left: .3em; color: var(--muted); }
.msg.out .meta i { color: #e3dcff; }
.msg.out .meta i.icon-danger, .msg .meta i.icon-danger { color: var(--danger); }
.convo-last i { font-size: .72rem; margin: 0 .15em; color: var(--muted); }
.chat-warn i { margin-right: .45em; }

/* Settings drawer tabs (Devices / Verify / General / Diagnostics).
   Overflow affordance lives on the WRAPPER (outside the scroller): edge
   gradients that fade the cut-off tabs into the panel colour — clearly
   visible on dark themes where shadows disappear. .fade-left/.fade-right
   are toggled by JS and only appear while content is actually clipped
   that way. */
.drawer-tabs-wrap { position: relative; margin: .2rem 0 .6rem; min-width: 0; }
.drawer-tabs { min-width: 0; max-width: 100%; }
.drawer-tabs-wrap::before, .drawer-tabs-wrap::after {
  content: ''; position: absolute; top: 0; bottom: 1px; width: 34px;
  pointer-events: none; z-index: 1; opacity: 0; transition: opacity .15s ease;
}
.drawer-tabs-wrap::before { left: 0; background: linear-gradient(to right, var(--panel) 20%, rgb(from var(--panel) r g b / 0)); }
.drawer-tabs-wrap::after  { right: 0; background: linear-gradient(to left,  var(--panel) 20%, rgb(from var(--panel) r g b / 0)); }
.drawer-tabs-wrap.fade-left::before { opacity: 1; }
.drawer-tabs-wrap.fade-right::after { opacity: 1; }
@supports not (color: rgb(from red r g b)) {
  .drawer-tabs-wrap::before { background: linear-gradient(to right, var(--panel) 40%, transparent); }
  .drawer-tabs-wrap::after  { background: linear-gradient(to left,  var(--panel) 40%, transparent); }
}
.drawer-tabs {
  display: flex; gap: .2rem; border-bottom: 1px solid var(--border);
  overflow-x: auto; scrollbar-width: none;
  background: var(--panel);
}
.drawer-tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--muted); padding: .45rem .65rem; white-space: nowrap;
  border-radius: 8px 8px 0 0; font-size: .9rem;
}
.drawer-tab i { margin-right: .4em; }
.drawer-tab:hover { color: var(--text); background: var(--panel-raised); }
.drawer-tab.active { color: var(--text); border-bottom-color: var(--accent); }
/* Desktop: the tab bar is drag-scrollable (home.js pointer handlers); a
   grab cursor sells it. Touch keeps native swipe and gets no cursor. */
@media (pointer: fine) {
  .drawer-tabs { cursor: grab; user-select: none; }
  .drawer-tabs.dragging { cursor: grabbing; }
}

/* Devices tab: the account's device budget belongs to the section, not to
   each row — it sits right-aligned in the 'Devices' header. */
.devices-head { display: flex; justify-content: space-between; align-items: baseline; gap: .6rem; }
.devices-head #device-max { font-weight: 400; }

/* Verify tab: status chip beside the heading + roomy upload action */
.idverify-head { display: flex; align-items: baseline; gap: .7rem; }
.idverify-state { font-size: .72rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.idverify-state.ok { color: var(--success); }
.idverify-state.bad { color: var(--danger); }
.idverify-actions { margin: 1rem 0 1.1rem; }
.idverify-actions .btn { padding-inline: 1.3rem; }

/* Peer profile: TOP sheet (drawer-style) over the translucent scrim */
.side-sheet {
  position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 50;
  z-index: 50; width: min(560px, 100%);
  max-height: min(86dvh, 640px); overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border); border-top: 0;
  border-radius: 0 0 16px 16px; box-shadow: var(--shadow);
  padding: .4rem 1.2rem calc(1rem + var(--sa-b));
  padding-top: calc(.4rem + env(safe-area-inset-top));
  animation: sheet-down .22s cubic-bezier(.2, .8, .3, 1);
}
/* EXIT mirrors of the sheet/scrim entries — applied via .closing while the
   element stays visible; JS flips `hidden` only when the exit lands (same
   defer-the-paint pattern as the chat pane) */
@keyframes overlay-out { from { opacity: 1; } to { opacity: 0; } }
.overlay.closing { animation: overlay-out .18s ease-in both; }
@keyframes sheet-up {
  from { transform: translate(-50%, 0); opacity: 1; }
  to   { transform: translate(-50%, -100%); opacity: .85; }
}
.side-sheet.closing { animation: sheet-up .2s cubic-bezier(.4, 0, .85, .35) both; }
@keyframes side-menu-out { from { transform: none; } to { transform: translateX(100%); } }
.side-menu.closing { animation: side-menu-out .2s cubic-bezier(.4, 0, .85, .35) both; }

@keyframes sheet-down {
  from { transform: translate(-50%, -100%); }
  to   { transform: translate(-50%, 0); }
}
.sheet-body { padding-top: .4rem; }
.profile-id { display: flex; align-items: center; gap: .9rem; margin: .4rem 0 1rem; }
/* sheet header: photo beside name+joined+description, whole block above Safety */
.profile-hero { align-items: center; text-align: left; gap: 1rem; margin: .5rem 0 1rem; }
.profile-hero .profile-nameline { font-size: 1.05rem; }
/* profile surfaces (sheet, preview, settings hero) render the avatar a few
   px larger — the hero has the vertical room (name line + sub line) and the
   zoom magnifier corner wants breathing space. Sidebar/chat/rows stay at
   the base .avatar size. */
.profile-id .avatar-stack .avatar,
.avatar-stack.avatar-hero .avatar { width: 44px; height: 44px; font-size: 1.15rem; }
.profile-id .avatar-stack img.avatar,
.avatar-stack.avatar-hero img.avatar { border-radius: 50%; object-fit: cover; }
.profile-preview-row { margin-top: 1.1rem; }
.profile-preview-row .btn { margin-left: auto; margin-right: auto; }
.profile-idtext { display: flex; flex-direction: column; min-width: 0; }
.profile-nameline { display: flex; align-items: baseline; gap: .45rem; min-width: 0; }
#profile-name { font-weight: 700; font-size: 1.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.profile-section { border-top: 1px solid var(--divider); padding-top: .7rem; margin-top: .4rem; }
.profile-section h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0 0 .5rem; }
.profile-id-state { margin: .1rem 0 0; font-weight: 700; font-size: .9rem; }
.profile-id-state.ok { color: var(--success); }
.profile-id-state.bad { color: var(--danger); }
.profile-id-state.warn { color: var(--warning); }
#profile-id-note { margin: 0 0 .7rem; line-height: 1.45; }
.profile-id-state.trust { margin-top: .2rem; }
#profile-trust-note { margin: 0; line-height: 1.45; }
.profile-id-state.trust { margin-top: .55rem; }
#profile-app-state { margin-top: .3rem; }
#profile-social-state, #profile-you-state { margin-top: .7rem; }
#profile-coco.coco { color: var(--accent); font-weight: 700; }
.coco-hero { font-size: .8rem; }
#profile-account-state { margin-top: .1rem; }
#profile-joined { margin: 0 0 .3rem; }
#profile-app-state { margin-top: .7rem; }
#profile-reputation.reputation { letter-spacing: .02em; margin: 0 0 .2rem; }

/* Chat-head peer button: opens the profile view */
.peer-btn {
  display: flex; align-items: center; gap: .75rem;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  border-radius: 8px; min-width: 0;
}
.chat-peer-text { display: grid; gap: .05rem; min-width: 0; }
.peer-btn:hover, .peer-btn:focus-visible { background: var(--panel-raised); outline: none; }
.profile-hero .avatar-stack img.avatar { border-radius: 50%; object-fit: cover; }
/* cached avatars rendered as circles anywhere an initial would sit */
.avatar-img { object-fit: cover; }
.chat-avatar { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; flex: none; }
span.avatar.chat-avatar { display: inline-grid; place-items: center; font-size: 1rem;
  background: var(--accent-strong); color: var(--on-accent); text-transform: uppercase; }
.bio-line { margin-top: .15rem; white-space: pre-wrap; line-height: 1.4; }

/* Profile view status lines */
.profile-id-state { margin: .1rem 0 0; font-weight: 700; font-size: .9rem; }
.profile-id-state.ok { color: var(--success); }
.profile-id-state.bad { color: var(--danger); }
#profile-id-note { margin: 0 0 .4rem; line-height: 1.45; }
#profile-trust { margin: 0 0 .2rem; }

/* Chat options: right-hand side menu over the translucent scrim. */
/* height follows --app-h: when the keyboard shrinks the visible viewport,
   the panel shrinks WITH it (the stationary message area compresses via its
   flex:1) instead of the keys covering the recipient list. */
.side-menu {
  position: fixed; top: 0; right: 0; height: var(--app-h, 100dvh); z-index: 50;
  width: min(320px, 84vw);
  background: var(--panel); border-left: 1px solid var(--border);
  box-shadow: var(--shadow); overflow-y: auto; overflow-x: hidden;
  padding: calc(1.1rem + env(safe-area-inset-top)) 1rem 1.2rem 1rem;
  animation: side-menu-in .22s cubic-bezier(.2, .8, .3, 1);
}
/* wide: settings — denser content needs more room */
.side-menu.wide { width: min(440px, 94vw); padding-inline: 1.2rem; }
@keyframes side-menu-in { from { transform: translateX(100%); } to { transform: none; } }
.side-menu-title {
  display: flex; align-items: center; gap: .45rem;
  font-size: .8rem; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .07em; margin: 0 0 .6rem .55rem;
}
.peer-status i { font-size: .95rem; margin-right: .3em; }
.peer-status:empty { display: none; }

/* Menu rows: one icon per row (the action), full-width tap targets. */
.menu-list { display: grid; gap: .1rem; margin: 0; }
.menu-row { display: flex; align-items: center; }
/* a budget note lives UNDER its button: wrap the row so the full-width
   action keeps line one and the quiet gauge line gets line two */
.menu-row:has(.budget-note) { flex-wrap: wrap; }
.budget-note {
  flex: 1 1 100%; margin: .15rem 0 .35rem; font-size: .78rem;
  color: var(--muted); text-align: center;
}
.menu-action {
  flex: 1; text-align: left; background: none; border: 0; border-radius: 10px;
  color: var(--text); padding: .6rem .55rem;
}
.menu-action:hover { background: var(--panel-raised); }
.menu-action i { margin-right: .65em; width: 1.15em; text-align: center; }
.menu-action.danger, .menu-action.danger i { color: var(--danger); }

/* Header ⋮/✕ toggle: swaps glyph and spins it in; when open the button
   lifts ABOVE the scrim so a tap on it closes the menu again. */
.chat-menu-btn.menu-open { position: relative; z-index: 55; color: var(--text); }
.chat-menu-btn i { display: inline-block; }
.chat-menu-btn.menu-open i { animation: icon-morph .25s ease; }
.chat-menu-btn:not(.menu-open) i { animation: icon-morph .2s ease; }
@keyframes icon-morph {
  from { transform: rotate(-150deg) scale(.3); opacity: .15; }
  to { transform: none; opacity: 1; }
}

/* Local-user suggestion lists (new chat + forward dialog): tap to pick,
   filtered while typing. Hidden entirely when there is nothing to suggest. */
.peer-list {
  display: grid; gap: .15rem; max-height: 200px; overflow-y: auto;
  margin: .4rem .6rem 0; border: 1px solid var(--border); border-radius: 10px;
  background: var(--panel); padding: .25rem 0;
}
.peer-list button {
  display: flex; gap: .6rem; align-items: center; width: 100%; text-align: left;
  background: none; border: 0; color: var(--text); padding: .35rem .7rem;
}
.peer-list button:hover { background: var(--panel-raised); }
.peer-list .avatar { width: 28px; height: 28px; font-size: .8rem; }

/* Floating variant: dropdown OVER the content (anchor: .new-chat), fully
   opaque + shadowed so nothing shows through the gaps between rows. */
.peer-list.floating {
  position: absolute; left: 1rem; right: 1rem; top: calc(100% - .15rem);
  z-index: 25; margin: 0;
  background: var(--panel-raised); box-shadow: var(--shadow);
}
.peer-list.floating button:hover { background: var(--panel); }

.composer {
  flex: none; display: flex; gap: .6rem; align-items: center;
  padding: .6rem 1rem calc(.6rem + var(--sa-b));
  background: var(--panel);
}
.composer input { flex: 1; }

/* ---------- mobile: one pane at a time ---------- */

@media (max-width: 899px) {
  .chat-pane { display: none; }
  body.chat-open .sidebar { display: none; }
  body.chat-open .chat-pane { display: flex; }
  .msg { max-width: 82%; }
}

@media (min-width: 900px) {
  .sidebar { width: var(--sidebar-w); flex: none; }
}

@media (min-width: 1400px) {
  :root { --sidebar-w: 430px; }
  .messages { padding-inline: 8vw; }
}

/* Diagnostics: page footer (login screen) + preview modal */
.page-footer {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
  display: none;
  padding: .5rem 1rem calc(.5rem + var(--sa-b));
  background: var(--bg); border-top: 1px solid var(--border);
  font-size: .8rem; opacity: .9;
}
body[data-view="auth"] .page-footer,
body:not([data-view]) .page-footer,
#view-auth:not([hidden]) ~ main .page-footer,
#view-auth:not([hidden]) ~ .page-footer { display: block; }
body[data-view="app"] .page-footer,
body[data-view="blocked"] .page-footer { display: none; }
.diag-row { display: flex; gap: 0.6rem; align-items: center; justify-content: center; }

.diag-modal {
  position: fixed; top: 50%; left: 50%; z-index: 50;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100% - 2rem));
  max-height: min(80dvh, 640px);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: var(--shadow);
  padding: .4rem 1.2rem 1rem;
  animation: overlay-in .18s ease-out;
}
.diag-modal .drawer-head { margin-bottom: .4rem; }
.diag-modal .row-actions { display: flex; gap: .6rem; align-items: center; margin-top: .6rem; }
.diag-report {
  margin: 0; padding: .6rem;
  font-family: ui-monospace, monospace; font-size: .8rem;
  background: rgba(127,127,127,0.08); border-radius: .4rem;
  white-space: pre-wrap; word-break: break-all;
  max-height: 45vh; overflow-y: auto;
}
.btn-small { padding: 0.35rem 0.8rem; font-size: 0.85rem; }

/* Saved-accounts manager (auth view) */
.account-list { list-style: none; margin: .3rem 0 .8rem; padding: 0; display: grid; gap: .4rem; }
.account-list li {
  display: flex; gap: .6rem; align-items: center; justify-content: space-between;
  border: 1px solid var(--border); border-radius: 10px; padding: .45rem .7rem;
}
.account-list .row-actions { display: flex; gap: .4rem; }

/* Danger action button + confirm dialog body */
.btn-danger { background: var(--danger); border-color: var(--danger); color: #14161a; }
.btn-danger:hover:not(:disabled) { filter: brightness(1.1); }
#confirm-body { margin: .4rem 0 .8rem; line-height: 1.45; }
.confirm-subline {
  margin: .3rem 0 .1rem; font-size: .8rem; font-weight: 600; text-align: center;
  color: var(--muted); background: rgba(127, 127, 127, 0.08);
  border-radius: 8px; padding: .35rem .6rem;
}
.confirm-warning {
  margin: -0.2rem 0 .8rem; line-height: 1.4; font-weight: 700;
  color: var(--danger);
}

/* Install nudge: auth card + sidebar foot. */
.install-hint {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; justify-content: space-between;
  margin: .55rem .75rem; padding: .55rem .7rem; border-radius: .6rem;
  background: var(--panel-raised); border: 1px solid var(--accent);
  font-size: .82rem; line-height: 1.35;
}
.auth-view .install-hint { margin: 0 0 1rem; }
.sidebar .install-hint { margin: .5rem .75rem; flex: none; }
.install-hint .ih-text { flex: 1 1 10rem; min-width: 0; }
.install-hint em { font-style: normal; font-weight: 600; }
.install-hint em i { margin-right: .3em; }
/* Install rows (Settings drawer) */
.switch-row { display: flex; gap: .6rem; align-items: center; cursor: pointer; }

/* Offline banner (app view) */
.offline-banner {
  position: fixed; left: 50%; bottom: calc(.75rem + var(--sa-b));
  transform: translateX(-50%);
  z-index: 35; background: var(--panel); color: var(--text);
  border: 1px solid var(--accent); border-radius: 999px;
  padding: .45rem 1rem; font-size: .85rem; box-shadow: var(--shadow);
  max-width: calc(100vw - 2rem); text-align: center;
}

/* Sidebar footer: client build */
.side-foot {
  margin-top: auto; padding: .5rem .9rem calc(.5rem + var(--sa-b));
  border-top: 1px solid var(--border); opacity: .75; user-select: none;
}

/* trust-modal evidence table + consequence box */
.trust-facts {
  /* thin grey rules cap the evidence table top & bottom so it reads as a
     distinct block between the body copy and the CoCo note */
  width: 100%; border-collapse: collapse; margin: .5rem 0 .5rem;
  border-top: 1px solid var(--divider); border-bottom: 1px solid var(--divider);
}
.coco-note { margin-top: 0; text-align: center; }
.trust-facts th {
  text-align: left; font-weight: 600; color: var(--muted);
  font-size: .82rem; padding: .28rem .4rem .28rem 0;
}
.trust-facts td { text-align: right; padding: .28rem 0; }
/* plain verdicts: no pills, no borders — just the tick, the words, colour */
.tf-val { font-size: .88rem; font-weight: 700; }
.tf-val.ok  { color: var(--success); }
.tf-val.bad { color: var(--danger); }
.tf-val.dim { color: var(--muted); font-weight: 600; }
.tf-val.num { color: var(--accent, #7d6ce0); }
/* the confirm dialog has a single action (cancel is the header ✕) — it
   belongs centered, not shy in the corner */
#confirm-modal .row-actions { justify-content: center; }

.tf-consequence {
  margin-top: .8rem; padding: .6rem .8rem;
  border: 1px solid var(--danger); border-radius: 10px;
  background: rgba(239, 105, 117, 0.10); color: var(--danger);
  font-size: .85rem; font-weight: 600; text-align: center;
}

/* Transient toast pill. FIXED (not absolute inside .chat-view): it must be
   visible while the settings drawer / profile sheets / modals (z 50) and the
   lightbox (z 70) are on screen — the toast is the status channel, so it
   floats above all of them (75). */
/* ---- message panel: fixed-height carousel, two stacked slides ----
   The slides share ONE box (absolute, inset 0) whose height the panel
   dictates; only the transforms animate. Forward enters from the right and
   pushes the message slide LEFT; cancel reverses exactly (forward exits
   right). No track-width gymnastics — a real carousel. */
#msg-modal { display: flex; flex-direction: column; overflow: hidden;
  padding-bottom: calc(.7rem + env(safe-area-inset-bottom, 0px)); }
/* the message text is the STATIONARY star: it fills the panel above the
   bottom carousel and never slides or resizes. */
#msg-modal .msg-modal-text { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* Bottom carousel: the FORWARD slide lays out in flow and thus FIXES the
   box height (search + 3-row floor + action bar); the action slide is an
   absolute overlay. Height never changes when swapping, so the message
   above it does not move either. */
.msg-panes { position: relative; flex: none; overflow: hidden; }
/* overflow MUST live on the carousel box itself: the modal clips at its
   PADDING box, so a translated slide peeked through the panel's right
   padding (the 1rem strip of search field visible beside Copy/Forward). */
.msg-pane { display: flex; flex-direction: column; gap: .35rem;
  transition: transform .24s cubic-bezier(.2, .8, .3, 1); }
.msg-pane-fwd { transform: translateX(100%); }
.msg-pane-view { position: absolute; inset: 0; padding-right: 16px; }
/* the picked recipient row (locked selection with an ✕ to release it) */
.fwd-picked { display: flex; align-items: center; gap: .6rem; padding: .3rem .1rem; }
.fwd-picked .uname { font-size: 1rem; }
.fwd-picked button { flex: none; }
.fwd-nomatches { font-style: italic; color: var(--muted); padding: .5rem .7rem; }
.msg-panes.showing-fwd .msg-pane-view { transform: translateX(-100%); }
.msg-panes.showing-fwd .msg-pane-fwd { transform: translateX(0); }
/* (message-text sizing lives with the carousel block above) */
/* fixed 3-row floor, expands with the page; buttons below never shift */
.fwd-results { flex: 1 1 auto; min-height: calc(3 * 44px); overflow-y: auto; }
.fwd-actions {
  flex: none; display: flex; justify-content: space-between; align-items: center;
  gap: .5rem; padding-top: .3rem;
  border-top: 1px solid var(--border); margin-top: .4rem;
}

.chat-view { position: relative; }
.toast {
  position: fixed; left: 50%; bottom: calc(4.4rem + var(--sa-b));
  transform: translateX(-50%); z-index: 75;
  max-width: min(92%, 520px);
  background: var(--panel-raised); color: var(--text);
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 10px; padding: .55rem .9rem;
  font-size: .85rem; line-height: 1.35; box-shadow: var(--shadow);
  animation: toast-in .18s ease-out; text-align: center;
}
.toast.error { border-left-color: var(--danger); }
.toast.ok { border-left-color: var(--success); }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 6px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* In-app incoming-message banner (open app, other conversation) */
.notif-banner {
  position: fixed; top: calc(.6rem + env(safe-area-inset-top)); left: 50%;
  transform: translateX(-50%); z-index: 45; cursor: pointer;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--accent); border-radius: 999px;
  padding: .45rem 1.1rem; font-size: .9rem; box-shadow: var(--shadow);
  max-width: min(92vw, 480px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* photo lightbox */
.lightbox-overlay { z-index: 70; cursor: zoom-out; }
.lightbox {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 71; max-width: min(86vw, 520px); max-height: 80vh;
  border-radius: 50%; border: 2px solid var(--panel-raised);
  box-shadow: var(--shadow); image-rendering: auto; cursor: zoom-out;
}
/* zoom affordance = .avatar-stack.zoomable (cursor) + .avatar-zoom-hint
   (badge); profile + preview opt in, chat top and sidebar head do not */

.bio-text { margin: 0; line-height: 1.45; white-space: pre-wrap; word-break: break-word; }

/* Premium gold certificate — layers on top of any trust-state mark */
.premium-badge { color: #f0c04a; font-size: .82em; margin-left: .28em; vertical-align: -0.05em; }
.profile-premium-badge { font-size: 1rem; }

/* Premium modal + clickable certificate on profile sheets */
/* position:relative + z-index lifts the hero ABOVE the modal's sticky
   header (which paints its opaque background at z-index 1 — that band, not
   overflow, was shearing off the top of the glow) and gives vertical room. */
.premium-hero { text-align: center; margin: 1.1rem 0 .9rem; position: relative; z-index: 2; }
/* the modal content is tiny — let the gold glow spill instead of clipping */
/* Dedicated layer ABOVE every sheet (profile is .side-sheet z-50 and was
   winning the tie on DOM order, letting its scrim close the sheet under an
   open premium modal). Also keep lightbox (>70) above this one. */
#premium-overlay { z-index: 60; }
/* ---- the badge modal's shine choreography -------------------------------
   ONE custom property (--badge-glow, set per badge by showBadgeModal)
   dresses the whole panel: the tinted wash behind the hero, the glowing
   border, the halo ring spinning behind the artwork, the gradient title,
   the points pill. The artwork's own gloss band and twinkles are animated
   from here too (see .badge-glint / .badge-spark below). */
#premium-modal {
  --badge-glow: var(--accent);
  text-align: left; overflow: visible; z-index: 61;
  /* an award CARD, not a diagnostics sheet: narrow, centred, jewellery-first */
  width: min(400px, calc(100% - 2rem));
  padding-bottom: 1.1rem;
}
/* the sticky opaque header band was shearing the top wash off the card —
   this modal's content is short, it neither sticks nor paints over */
#premium-modal .drawer-head { position: static; background: transparent; }
#premium-modal-text, #premium-modal-desc { text-align: center; }
#premium-modal .row-actions { justify-content: center; }
  background:
    radial-gradient(120% 85% at 50% -12%, color-mix(in srgb, var(--badge-glow) 26%, transparent), transparent 62%),
    var(--panel);
  border-color: color-mix(in srgb, var(--badge-glow) 42%, var(--border));
  box-shadow:
    0 18px 60px rgb(0 0 0 / .55),
    0 0 46px color-mix(in srgb, var(--badge-glow) 26%, transparent),
    inset 0 1px 0 rgb(255 255 255 / .09);
  animation: badge-modal-in .45s cubic-bezier(.16, 1.3, .3, 1);
}
@keyframes badge-modal-in {
  0% { transform: translate(-50%, -50%) scale(.86); opacity: 0; }
  60% { transform: translate(-50%, -50%) scale(1.03); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}
/* the title wears the badge colour with a slow shine sweep across it */
#premium-title {
  background: linear-gradient(100deg,
    var(--text) 18%, color-mix(in srgb, var(--badge-glow) 80%, #fff) 38%, #fff 50%,
    color-mix(in srgb, var(--badge-glow) 80%, #fff) 62%, var(--text) 82%);
  background-size: 260% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  animation: badge-title-shine 4.5s linear infinite;
}
@keyframes badge-title-shine { 0% { background-position: -150% 0; } 100% { background-position: 150% 0; } }
#premium-modal .muted { line-height: 1.45; }
.badge-modal-foot {
  display: flex; justify-content: space-between; align-items: center;
  gap: .6rem; margin-top: .8rem; min-height: 1.2rem;
}
/* the CoCo points as a lit pill in the badge's own colour */
#premium-modal .premium-points {
  margin: 0; padding: .18rem .7rem; border-radius: 999px;
  color: color-mix(in srgb, var(--badge-glow) 62%, #fff);
  font-weight: 800; font-size: .95rem;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--badge-glow) 34%, transparent),
    color-mix(in srgb, var(--badge-glow) 10%, transparent));
  border: 1px solid color-mix(in srgb, var(--badge-glow) 55%, transparent);
  box-shadow:
    0 0 16px color-mix(in srgb, var(--badge-glow) 30%, transparent),
    inset 0 1px 0 rgb(255 255 255 / .28);
  text-shadow: 0 0 10px color-mix(in srgb, var(--badge-glow) 60%, transparent);
}
#profile-premium .premium-badge, #profile-premium i { cursor: pointer; }
#profile-premium i { font-size: 1.05rem; }

/* ---------- Badges ---------- */
.badge-row { display: flex; flex-wrap: wrap; gap: .45rem; align-items: stretch; }
.badge-chip {
  display: inline-flex; align-items: center; gap: .35rem;
  background: var(--panel-raised); border: 1px solid var(--border);
  border-radius: 999px; padding: .3rem .65rem .3rem .4rem;
  font: inherit; font-size: .8rem; color: var(--text); cursor: default;
}
.badge-chip { cursor: pointer; }
.badge-chip-premium { border-color: rgba(240, 192, 74, .55); }
/* the certificate is GOLD everywhere: chips, name badges, row icons, modal.
   No shadow at the small sizes — the animated drop shadow belongs to the
   modal hero only (.premium-hero i), where it is meant to breathe. */
.badge-ic-premium i { color: #f0c04a; }
.badge-chip-og { border-color: rgba(141, 127, 240, .6); }
.badge-chip-earlybird { border-color: rgba(127, 212, 255, .5); }
.badge-selected { outline: 2px solid var(--accent); outline-offset: 1px; }
/* ---- THE username component (js/components/userline.js) ----
   Spacing truth copied from the conversation list (the reference): trust
   icon .72rem / .5em right / .05em baseline nudge; badges and chips follow
   the name with .3em; gone = italic name only. Every surface that names a
   user gets these via .uname — per-context font-size stays with the host. */
.uname { font-weight: 600; min-width: 0; display: inline-flex; align-items: center; }
.uname-text { min-width: 0; }
.uname > i { font-size: .72rem; margin-right: .5em; vertical-align: .05em; }
.uname .premium-badge, .uname .name-chip-inline, .uname .verify-badge { margin-left: .3em; }
/* all glyph markers in a name line share the reference size + baseline
   (.72rem / .05em). .premium-badge's default .82em scale grows with the
   host font (the chat head!), so inside .uname it is pinned to match the
   trust icon and the red unverified mark — one rhythm, aligned baselines. */
.uname .premium-badge, .uname .verify-badge { font-size: .72rem; vertical-align: .05em; }
.uname.gone .uname-text { font-style: italic; }

.udouble { display: inline-flex; align-items: center; gap: .6rem; min-width: 0; }
.udouble-meta { display: grid; gap: .1rem; min-width: 0; }

.avatar-stack { display: inline-flex; flex: none; }
/* chat-head mute speaker: muted reads as struck-through quiet */
#btn-chat-mute.muted-on { color: var(--danger); }
#btn-chat-mute.muted-on:hover { background: rgba(239, 105, 117, 0.12); }
.avatar-stack { position: relative; }
.avatar-stack.zoomable .avatar-img { cursor: zoom-in; }
/* magnifier corner: the zoom affordance, shown only when a real photo can
   be enlarged (initials never advertise zoom) */
.avatar-zoom-hint {
  position: absolute; right: -4px; bottom: -4px;
  font-size: .62rem; line-height: 1; padding: 3px;
  background: var(--panel-raised); color: var(--text);
  border: 1px solid var(--border); border-radius: 50%;
  box-shadow: var(--shadow); pointer-events: none;
}

/* identity sub line under a name: green shield-person + "Verified" */
.user-verified-sub {
  display: inline-flex; align-items: center; gap: .4em;
  font-size: .78rem; font-weight: 600; color: var(--success, #4caf7d);
}
.user-verified-sub > i { font-size: .72rem; }

.badge-name-chip, .name-chip-inline { display: inline-flex; vertical-align: -2px; margin-left: .3em; cursor: pointer; }
#profile-badges .badge-chip { cursor: pointer; }
/* chips lift a touch on hover so they read as the buttons they are */
.badge-chip, .badge-name-chip, .name-chip-inline { transition: transform .12s ease, box-shadow .12s ease; }
.badge-chip:hover, .badge-name-chip:hover, .name-chip-inline:hover {
  transform: translateY(-1px);
  box-shadow: 0 3px 12px rgb(0 0 0 / .35);
}
.badge-hero { display: inline-block; line-height: 0; position: relative; z-index: 1; }
/* ---- hero stage: breathing glow + a halo ring spinning behind the art --- */
.premium-hero::before, .premium-hero::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  translate: -50% -50%; border-radius: 50%; pointer-events: none;
}
.premium-hero::after {
  width: 216px; height: 216px;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--badge-glow, var(--accent)) 55%, transparent) 0%,
    color-mix(in srgb, var(--badge-glow, var(--accent)) 18%, transparent) 42%,
    transparent 68%);
  filter: blur(10px);
  animation: badge-breathe 3.2s ease-in-out infinite;
}
@keyframes badge-breathe { 0%, 100% { opacity: .55; scale: .94; } 50% { opacity: .95; scale: 1.06; } }
.premium-hero::before {
  width: 178px; height: 178px;
  /* two opposed comets chasing each other round the badge */
  background: conic-gradient(from 0deg,
    transparent 0 18%,
    color-mix(in srgb, var(--badge-glow, var(--accent)) 85%, #fff) 30%,
    transparent 44% 68%,
    color-mix(in srgb, var(--badge-glow, var(--accent)) 85%, #fff) 80%,
    transparent 94% 100%);
  -webkit-mask: radial-gradient(closest-side, transparent 56%, #000 66%, #000 78%, transparent 86%);
  mask: radial-gradient(closest-side, transparent 56%, #000 66%, #000 78%, transparent 86%);
  filter: blur(7px);
  opacity: .6;
  animation: badge-halo 5.5s linear infinite;
}
@keyframes badge-halo { to { rotate: 360deg; } }
/* the hero itself floats gently, lit in the badge's colour */
.premium-hero .badge-hero { animation: badge-float 3.8s ease-in-out infinite; }
@keyframes badge-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -5px; } }
/* the artwork's gloss band sweeps and its twinkles pop — HERE only; chips,
   name chips and table rows keep the calm static sheen */
.premium-hero .badge-glint {
  transform-box: fill-box; transform-origin: center;
  animation: badge-glint 3.6s ease-in-out .5s infinite;
}
@keyframes badge-glint {
  0% { transform: rotate(24deg) translateX(-80px); opacity: 0; }
  10% { opacity: .9; }
  42%, 100% { transform: rotate(24deg) translateX(150px); opacity: 0; }
}
.premium-hero .badge-spark {
  transform-box: fill-box; transform-origin: center;
  animation: badge-spark 3.6s ease-in-out infinite;
}
.premium-hero .badge-spark-2 { animation-delay: 1.8s; }
@keyframes badge-spark {
  0%, 52%, 100% { opacity: 0; transform: scale(.15) rotate(0deg); }
  64% { opacity: 1; transform: scale(1) rotate(90deg); }
  78% { opacity: 0; transform: scale(.35) rotate(150deg); }
}
@media (prefers-reduced-motion: reduce) {
  #premium-modal, #premium-title,
  .premium-hero::before, .premium-hero::after,
  .premium-hero .badge-hero, .premium-hero .badge-glint, .premium-hero .badge-spark {
    animation: none;
  }
  .premium-hero::before { opacity: .35; }
  .premium-hero .badge-glint { opacity: 0; }
  .premium-hero .badge-spark { opacity: .8; transform: none; }
}

/* Badge picker table + profile sheet dates */
.badge-table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.badge-table th { text-align: left; color: var(--muted); font-weight: 500; font-size: .74rem; text-transform: uppercase; letter-spacing: .05em; padding: .2rem .4rem; }
.badge-table td { padding: .35rem .4rem; border-top: 1px solid var(--divider); vertical-align: middle; }
.badge-table td:first-child { display: flex; align-items: center; gap: .35rem; font-weight: 600; }

/* Badge modal hero: guaranteed square, image-first (all badge types),
   lit from the badge's own glow colour */
.premium-hero svg {
  width: 96px; height: 96px; display: inline-block; border-radius: 22px;
  filter:
    drop-shadow(0 8px 22px color-mix(in srgb, var(--badge-glow, var(--accent)) 55%, transparent))
    drop-shadow(0 2px 6px rgb(0 0 0 / .5));
}

/* STAFF MODERATION notice at the TOP of the peer profile sheet (chat.js):
   a red alarm strip — the platform's own verdict about this account. Same
   red family as .chat-warn.danger; the icon leads, text follows (the .45em
   icon margin is the app-wide pattern). */
.moderation-banner {
  display: flex; align-items: flex-start; gap: .5rem;
  margin: 0 0 .8rem; padding: .6rem .75rem;
  background: rgba(239, 105, 117, 0.12); color: var(--danger);
  border: 1px solid currentColor; border-radius: .6rem;
  font-weight: 700; font-size: .85rem; line-height: 1.45;
}
.moderation-banner i { margin-right: .45em; }
