:root {
  color-scheme: dark;
  --bg: #0d0e0d;
  --panel: #111312;
  --panel-soft: #141716;
  --text: #d8ddd7;
  --muted: #858d87;
  --dim: #555d58;
  --line: #232826;
  --line-soft: #1a1d1c;
  --danger: #c28f86;
  --focus: #b8c7b7;
  --column-side: 260px;
  --topbar-h: 38px;
  --font: "IBM Plex Mono", "SFMono-Regular", Menlo, Consolas, monospace;
}

* {
  box-sizing: border-box;
}

/* Global belt-and-braces enforcement of the HTML `hidden` attribute.
   Component CSS that sets a custom display (.pairing-card { display:
   flex }, .notifications__list { display: flex }, etc.) will silently
   override the user-agent `[hidden] { display: none }` rule on
   specificity grounds; toggling el.hidden then leaves the element
   visible. This rule wins everywhere so any element with `hidden`
   stops painting AND stops reserving space. Per-component overrides
   that previously fixed this individually (.pairing-card[hidden], etc.)
   stay in place as documentation of the prior gotcha. */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
  /* Phase 10.1: kill the mobile rubber-band + horizontal pan that
     made the whole page feel scrollable. overscroll-behavior stops
     iOS's bounce; the explicit overflow:hidden on both elements
     keeps any rogue child from creating page-level scroll. */
  overflow: hidden;
  overscroll-behavior: none;
}

html {
  /* Pin the viewport so iOS Safari can't pan the document under
     the URL bar. Combined with the body lock below this prevents
     "I can move the whole page side-to-side and up/down" without
     stopping intended internal pane scrolling. */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.55 var(--font);
  letter-spacing: 0;
  overflow: hidden;
  overscroll-behavior: none;
  /* Block any horizontal touch pan at the document level. Vertical
     intent passes through to intended scrolling panes (chat body,
     dialog body, feed list) which set touch-action: pan-y on
     themselves implicitly via overflow-y: auto. */
  touch-action: pan-y;
}

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

/* Phase 10.1: top-level offline banner. Sits above the shell on
   desktop and above the topbar on mobile. Calm copy, no emoji,
   subdued color so it doesn't look like an error. */
.offline-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: var(--warn-bg, #2a2a2a);
  color: var(--warn-fg, #f1f1f1);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  padding: 6px 12px;
  text-align: center;
}
.offline-banner[hidden] { display: none; }
body[data-offline="1"] .shell { padding-top: 30px; }

/* Auth-state visibility */
body:not([data-auth-state="signed-in"]) .topbar,
body:not([data-auth-state="signed-in"]) .shell {
  display: none;
}

body[data-auth-state="signed-in"] .landing,
body[data-auth-state="auth-signin"] .landing,
body[data-auth-state="auth-signup"] .landing,
body[data-auth-state="auth-restore"] .landing {
  opacity: 0;
  pointer-events: none;
}

/* ============================================================
 * Topbar
 * ============================================================ */
.topbar {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  background: #0a0b0a;
}

.brand {
  padding-inline: 18px;
  font-size: 15px;
  color: var(--text);
}

.topbar__account {
  display: flex;
  align-items: center;
  padding-right: 14px;
}

.account-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 3px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.account-button:hover,
.account-button:focus-visible,
.account-button[aria-expanded="true"] {
  border-color: var(--line);
  color: var(--text);
  outline: none;
}

.account-button__handle {
  font-size: 13px;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-button__caret {
  font-size: 10px;
  color: var(--muted);
}

/* Hide account button until we have a handle */
.account-button:not([data-handle]) {
  visibility: hidden;
}

/* ============================================================
 * Account dropdown menu
 * ============================================================ */
.account-menu {
  position: fixed;
  top: calc(var(--topbar-h) + 6px);
  right: 14px;
  z-index: 6;
  width: min(280px, calc(100vw - 28px));
  border: 1px solid var(--line);
  background: var(--panel);
  display: flex;
  flex-direction: column;
  padding: 8px 0;
  animation: account-menu-in 120ms ease-out;
}

.account-menu[hidden] {
  display: none;
}

.account-menu__head {
  padding: 4px 14px 10px;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 6px;
}

.account-menu__handle {
  color: var(--text);
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu__fingerprint {
  color: var(--muted);
  font-size: 11px;
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu__relay {
  color: var(--dim);
  font-size: 11px;
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu__item {
  display: block;
  width: 100%;
  padding: 8px 14px;
  border: 0;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  font-size: 13px;
}

.account-menu__item:hover,
.account-menu__item:focus-visible {
  background: var(--panel-soft);
  outline: none;
}

.account-menu__item--danger {
  color: var(--muted);
}

.account-menu__item--danger:hover,
.account-menu__item--danger:focus-visible {
  color: var(--danger);
}

@keyframes account-menu-in {
  from { opacity: 0; transform: translateY(-3px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ============================================================
 * Landing
 * ============================================================ */
.landing {
  position: fixed;
  inset: 0;
  z-index: 3;
  height: 100vh;
  display: grid;
  place-items: center;
  background: #000;
  color: var(--text);
  opacity: 1;
  overflow: hidden;
  transition: opacity 220ms ease;
}

/* Phase 14B polish: interactive constellation behind the landing
   card. Canvas is drawn by main.ts (initLandingConstellation). Pure
   visual layer; pointer-events forwarded to the card above. The
   canvas paints a static state when prefers-reduced-motion is set. */
.landing__constellation {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
  display: block;
  /* Stronger fade behind the card on mobile so dots never compete
     with text. Desktop keeps a gentler curve. */
  mask-image: radial-gradient(ellipse 60% 50% at 50% 50%, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.95) 65%);
  -webkit-mask-image: radial-gradient(ellipse 60% 50% at 50% 50%, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.95) 65%);
}
@media (max-width: 480px) {
  .landing__constellation {
    mask-image: radial-gradient(ellipse 70% 45% at 50% 50%, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.98) 60%);
    -webkit-mask-image: radial-gradient(ellipse 70% 45% at 50% 50%, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.98) 60%);
  }
}

.landing__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 18px;
  justify-items: center;
  /* safe-area on bottom for iOS home indicator; the constellation
     mask already keeps the card area readable. */
  padding: 28px 22px max(28px, env(safe-area-inset-bottom));
  width: 100%;
  max-width: min(86vw, 380px);
  box-sizing: border-box;
}
@media (max-width: 480px) {
  .landing__inner {
    max-width: min(94vw, 360px);
    gap: 14px;
    padding: 20px 18px max(20px, env(safe-area-inset-bottom));
  }
}

.landing__tagline {
  margin: -6px 0 2px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted, rgba(255, 255, 255, 0.55));
  text-align: center;
  animation: landing-actions-in 280ms ease-out 90ms both;
}

/* Phase 14B polish: about is a real button alongside sign in / sign
   up, just visually secondary (matches .auth-link base shape but
   wider and full-width, sits one row below). */
.landing__about-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 32px;
  margin: 6px 0 0;
  appearance: none;
  background: transparent;
  border: 1px solid var(--line, rgba(255, 255, 255, 0.18));
  border-radius: 3px;
  padding: 5px 12px;
  color: var(--muted, rgba(255, 255, 255, 0.65));
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
  transition: color 140ms ease, border-color 140ms ease, background 140ms ease;
}

.landing__about-link:hover,
.landing__about-link:focus-visible {
  color: var(--text);
  border-color: var(--text);
  background: rgba(255, 255, 255, 0.03);
  outline: none;
}

/* ============================================================
 * About overlay (Phase 14B mobile polish)
 * ============================================================ */
.about-overlay {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgba(0, 0, 0, 0.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--text);
  z-index: 100;
  overflow: hidden;
}
.about-overlay::backdrop { background: rgba(0, 0, 0, 0.6); }
.about-overlay[open] {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  animation: about-overlay-in 240ms ease-out;
}

.about-overlay__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(92vw, 600px);
  max-height: 92vh;
  padding: 0;
  background: transparent;
  animation: about-panel-in 280ms ease-out 40ms both;
}

.about-overlay__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 4px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  margin: 0 0 4px;
  flex-shrink: 0;
}

.about-overlay__title {
  margin: 0;
  font-size: 1.4rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.about-overlay__close {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  color: var(--muted, rgba(255, 255, 255, 0.65));
  font: inherit;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: color 140ms ease, border-color 140ms ease, background 140ms ease;
  /* Larger tap target than the visual circle suggests. */
  -webkit-tap-highlight-color: transparent;
}
.about-overlay__close:hover,
.about-overlay__close:focus-visible {
  color: var(--text);
  border-color: var(--text);
  background: rgba(255, 255, 255, 0.04);
  outline: none;
}

.about-overlay__scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  /* iOS momentum scrolling */
  -webkit-overflow-scrolling: touch;
  padding: 18px 4px 6px;
  /* Fade the bottom edge so the scroll cue is obvious without a
     visible scrollbar on macOS. */
  mask-image: linear-gradient(to bottom, black, black calc(100% - 24px), transparent);
  -webkit-mask-image: linear-gradient(to bottom, black, black calc(100% - 24px), transparent);
}

.about-overlay__section {
  margin: 0 0 20px;
  padding: 0;
}
.about-overlay__section:last-of-type { margin-bottom: 16px; }

.about-overlay__lede {
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(251, 146, 60, 0.85);
}

.about-overlay__section p {
  margin: 0 0 8px;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.88);
}
.about-overlay__section p:last-child { margin-bottom: 0; }

.about-overlay__docs {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  display: grid;
  gap: 8px;
}

.doc-card {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "title arrow" "desc arrow";
  gap: 2px 14px;
  padding: 14px 16px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  color: var(--text);
  text-decoration: none;
  transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.doc-card:hover,
.doc-card:focus-visible {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.22);
  outline: none;
}
.doc-card:focus-visible {
  box-shadow: 0 0 0 2px rgba(251, 146, 60, 0.3);
}
.doc-card__title {
  grid-area: title;
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--text);
}
.doc-card__desc {
  grid-area: desc;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.58);
}
.doc-card__arrow {
  grid-area: arrow;
  align-self: center;
  font-size: 18px;
  color: rgba(255, 255, 255, 0.45);
  transition: transform 140ms ease, color 140ms ease;
}
.doc-card:hover .doc-card__arrow,
.doc-card:focus-visible .doc-card__arrow {
  transform: translateX(2px);
  color: var(--text);
}

@media (max-width: 480px) {
  .about-overlay__panel {
    width: 100%;
    max-height: 100dvh;
    /* Fill the screen on mobile so the scrollable region uses the
       full viewport — avoids the sandwich-overflow issue where the
       panel itself is shorter than the device but inner content
       overflows. */
    height: 100dvh;
    padding: max(env(safe-area-inset-top), 16px) 18px max(env(safe-area-inset-bottom), 14px);
  }
  .about-overlay__header {
    padding-bottom: 12px;
  }
  .about-overlay__title { font-size: 1.25rem; }
  .about-overlay__section p { font-size: 0.9375rem; line-height: 1.55; }
  .about-overlay__lede { font-size: 11px; }
  .doc-card { padding: 12px 14px; border-radius: 8px; }
}

@keyframes about-overlay-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes about-panel-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.landing__brand {
  border: 0;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 18px;
  padding: 0;
  animation: landing-brand-in 220ms ease-out;
}

.landing__brand:focus-visible {
  outline: 1px solid var(--focus);
  outline-offset: 4px;
}

.landing__actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: min(86vw, 320px);
  animation: landing-actions-in 240ms ease-out 70ms both;
}

.landing__row {
  display: flex;
  gap: 10px;
  width: 100%;
}

.landing__row .auth-link {
  flex: 1 1 0;
  justify-content: center;
  text-align: center;
  /* Phase 14B mobile polish: minimum 44px tall on touch viewports
     (Apple HIG recommended tap target). Desktop keeps the 32px
     base from .auth-link. */
  min-height: 32px;
}

@media (max-width: 480px) {
  .landing__row .auth-link,
  .landing__about-link {
    min-height: 44px;
    font-size: 14px;
  }
}

.landing__stale {
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-muted, #888);
  text-align: center;
  padding: 8px 12px;
  border: 1px solid var(--line, #ddd);
  border-radius: 4px;
  background: var(--bg-soft, transparent);
  max-width: min(86vw, 320px);
}

/* Plain-language hint shown under the brand on the landing page.
   Replaces the prior "save your recovery information" / reset
   surface with a single line that sets expectations: keys live on
   this device, not on the server. */
.landing__hint {
  margin: 8px auto 14px;
  max-width: min(86vw, 360px);
  text-align: center;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

/* ----- Settings dialog ----- */
.settings-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line-soft, var(--line));
}

.settings-section:last-of-type {
  border-bottom: 0;
}

.settings-section__action {
  align-self: flex-start;
}

.settings-section__hint {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

.settings-section__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}

/* Phase 14B: bio editor inside the settings dialog. Textarea, counter,
   save/clear actions, and a calm aria-live state line for save/error. */
.settings-section--profile {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.settings-bio__input {
  width: 100%;
  margin-top: 2px;
  padding: 8px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 3px;
  font: inherit;
  font-size: 13px;
  line-height: 1.45;
  resize: vertical;
  min-height: 56px;
  max-height: 180px;
}
.settings-bio__input:focus {
  outline: none;
  border-color: var(--focus, var(--text));
}
.settings-bio__input::placeholder {
  color: var(--muted);
}
.settings-bio__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 4px;
}
.settings-bio__counter {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.settings-bio__counter[data-overlimit="true"] {
  color: var(--danger, #e87b5e);
}
.settings-bio__actions {
  display: flex;
  gap: 6px;
}
.settings-bio__state {
  font-size: 12px;
  color: var(--muted);
  min-height: 1.2em;
}
.settings-bio__state[data-state="error"] {
  color: var(--danger, #e87b5e);
}

/* Phase 11.6: privacy toggle row. */
.settings-toggle {
  display: flex;
  gap: 10px;
  align-items: center;
  cursor: pointer;
  font-size: 13px;
  color: var(--text);
}
.settings-toggle input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--focus);
}

/* Phase 11.6: inline unlock CTA inside a conversation when there
   are pending_decrypt rows. Calm, non-modal, never replaces the
   chat history. */
.chat-pending-decrypt-cta {
  display: flex;
  gap: 12px;
  align-items: center;
  margin: 12px 16px;
  padding: 10px 12px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.chat-pending-decrypt-cta__label {
  color: var(--text);
  font-size: 13px;
  flex: 1 1 auto;
}
.chat-pending-decrypt-cta__btn {
  flex-shrink: 0;
}

.settings-danger {
  margin-top: 6px;
  border: 1px solid var(--line);
  background: #1a1112;
}

.settings-danger--quiet {
  background: transparent;
  border-color: var(--line-soft, var(--line));
}

.settings-danger__summary {
  list-style: none;
  cursor: pointer;
  padding: 8px 12px;
  font-size: 13px;
  color: var(--muted);
}

.settings-danger__summary::-webkit-details-marker {
  display: none;
}

.settings-danger[open] .settings-danger__summary {
  border-bottom: 1px solid var(--line);
  color: var(--text);
}

.settings-danger__body {
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.settings-danger__warning {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
}

.settings-danger__label {
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.settings-danger__action {
  align-self: flex-start;
}

/* ----- Account dialog ----- */
.account-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  margin-bottom: 12px;
  border: 1px solid var(--line);
  background: var(--panel);
}

.account-card__header {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: space-between;
}

.account-card__handle {
  font-size: 18px;
  color: var(--text);
  min-width: 0;
  flex: 1 1 auto;
  /* Truncate long handles so they don't push the fingerprint icon
     off-screen or wrap into multiple lines. The full handle still
     lives in the element's title attribute (set in JS) and the
     aria-label so screen readers and tooltips see it. The cap is
     deliberately handle-shaped (16ch) — long enough for a normal
     "@somelonghandle" but short enough that the icon stays
     visible. */
  max-width: 16ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-card__fingerprint {
  flex: 0 0 auto;
}

.account-card__fingerprint-text {
  font-size: 12px;
  letter-spacing: 0.04em;
}

.account-card__bio {
  resize: vertical;
  min-height: 64px;
}

.account-card__status {
  margin-top: 6px;
  padding: 8px 10px;
  border-left: 2px solid var(--line);
  font-size: 12px;
  line-height: 1.5;
}

.account-card__status.is-ok {
  border-left-color: #2e7d32;
}

.account-card__status.is-warn {
  border-left-color: #b78605;
}

.account-card__status.is-danger {
  border-left-color: #b32424;
  background: #1a1112;
}

.account-card__canonical {
  word-break: break-all;
  font-size: 11px;
  letter-spacing: 0.02em;
}

/* Passive recovery indicator inside the account dropdown header.
   Subtle by design — it sits under the handle, never grabs focus,
   and just reflects current posture. The reminder banner above the
   feed is the active prompt; this is an at-a-glance check. */
.account-menu__recovery {
  margin-top: 4px;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.account-menu__recovery.is-ok {
  color: #6db872;
}

.account-menu__recovery.is-warn {
  color: #c9a23a;
}

.account-menu__recovery.is-danger {
  color: #c66766;
}

/* Phase 12.2 + 13.1: transport line in the account-menu dropdown.
   Always visible (honest about both onion + clearnet). Onion gets
   green, clearnet gets muted gray. */
.account-menu__transport {
  margin-top: 4px;
  font-size: 11px;
  letter-spacing: 0.02em;
}
.account-menu__transport--onion { color: #6db872; }
.account-menu__transport--clearnet { color: var(--muted); }
.account-menu__transport[hidden] { display: none; }

/* Inline passphrase prompt shown inside the linked-devices dialog
   when the user is signed in (identity restored) but the in-memory
   crypto account is locked — typically after a page reload. */
.device-passphrase-prompt {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  padding: 12px;
  border: 1px solid var(--line);
  background: #101211;
}

/* The HTML `hidden` attribute maps to `display: none` in the user-
   agent stylesheet, but `.device-passphrase-prompt { display: flex }`
   above wins on specificity grounds and keeps the panel visible
   (and reserving space) even when JS sets `el.hidden = true`. The
   same trap bit the pairing card below. Make `[hidden]` authoritative
   on both. */
.device-passphrase-prompt[hidden] {
  display: none;
}

.device-passphrase-prompt__hint {
  font-size: 13px;
  color: var(--text);
}

.device-passphrase-prompt__feedback {
  font-size: 12px;
}

.device-passphrase-prompt__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* Pairing card shown inside the linked-devices dialog after the
   user creates a pairing code. Holds the code, the URL the new
   device can navigate to, an expiry countdown, and a cancel button.
   Visual emphasis is on the code itself (large monospace) so the
   user can read it across the room. */
.pairing-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  padding: 12px;
  border: 1px solid var(--line);
  background: #101211;
}

.pairing-card[hidden],
.pairing-card__success[hidden] {
  display: none;
}

/* Camera QR scanner panel shown inside the collect-account dialog
   after the user clicks "scan QR". The video element is sized to
   the dialog width and the hint sits above it so the user knows
   the camera is live. Hidden by default; only the "scan QR" button
   reveals it, and any close path (cancel/success/dialog close)
   tears down the video tracks. */
.qr-scanner {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
  padding: 12px;
  border: 1px solid var(--line);
  background: #101211;
}

.qr-scanner[hidden] {
  display: none;
}

.qr-scanner__hint {
  font-size: 12px;
  color: var(--text);
}

.qr-scanner__video {
  width: 100%;
  max-height: 260px;
  background: #050605;
  border: 1px solid var(--line-soft, var(--line));
  border-radius: 3px;
  object-fit: cover;
}

.qr-scanner__feedback {
  font-size: 12px;
}

.qr-scanner__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

.pairing-card__title {
  font-size: 14px;
}

.pairing-card__hint {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
  line-height: 1.5;
}

.pairing-card__code {
  margin: 4px 0;
  padding: 8px 10px;
  border: 1px solid var(--line-soft, var(--line));
  background: #0a0c0b;
  font-size: 22px;
  letter-spacing: 0.12em;
  text-align: center;
  user-select: all;
}

.pairing-card__url {
  font-size: 11px;
  letter-spacing: 0.02em;
  word-break: break-all;
  user-select: all;
}

.pairing-card__expires {
  font-size: 11px;
}

/* QR slot above the human-typeable code. The encoder emits a self-
   contained <svg> with a white background and crisp module rendering;
   centering it lets it scale naturally inside the dialog without
   stretching. Empty container collapses cleanly when the encoder
   isn't usable. */
.pairing-card__qr {
  align-self: center;
  line-height: 0;
}

.pairing-card__qr svg {
  display: block;
  max-width: 220px;
  width: 100%;
  height: auto;
}

.pairing-card__success {
  font-size: 13px;
  color: #6db872;
}

/* Secondary actions row inside the sign-in dialog. Lives below the
   loading state and above the back/sign-in actions. Holds the
   "collect from another device" + "restore from backup" entry
   points so they aren't on landing. */
.signup-dialog__secondary {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--line-soft, var(--line));
}

.signup-dialog__secondary .signup-dialog__mode {
  align-self: flex-start;
  font-size: 12px;
}

.pairing-card__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* ============================================================
 * Three-column shell
 * ============================================================ */
.shell {
  /* Phase 10.1: dvh (dynamic viewport) on mobile so the shell tracks
     the URL-bar show/hide instead of jumping when iOS Safari resizes
     the viewport. Vh fallback for older browsers — the visible delta
     is small (the topbar height) so an old browser still renders a
     usable, if slightly clipped, shell. */
  height: calc(100vh - var(--topbar-h));
  height: calc(100dvh - var(--topbar-h));
  display: grid;
  grid-template-columns: var(--column-side) minmax(0, 1fr) var(--column-side);
}

.column {
  min-width: 0;
  background: var(--bg);
  border-right: 1px solid var(--line);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.column:last-child {
  border-right: 0;
}

.column__title {
  height: 34px;
  display: flex;
  align-items: center;
  padding-inline: 16px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 12px;
  text-transform: lowercase;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}

.column__title--row {
  justify-content: space-between;
  gap: 8px;
}

.notifications__clear-all {
  background: transparent;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  cursor: pointer;
  padding: 2px 4px;
}

.notifications__clear-all:hover,
.notifications__clear-all:focus-visible {
  color: var(--text);
  outline: none;
}

.column__search {
  padding: 10px 14px 8px;
  border-bottom: 1px solid var(--line-soft);
  flex-shrink: 0;
}

/* Left column */
.column--left {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
}

.column__top {
  min-height: 0;
  /* Without min-width: 0, this flex child's intrinsic content
     (a long lookup row) lets the column grow past the grid track
     width. Pinning min-width to 0 keeps the column bounded by its
     parent track, which lets per-element ellipsis truncation
     actually fire. */
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.column__top .search-results,
.column__top .lookup__result {
  flex: 1 1 auto;
  min-height: 0;
  /* Vertical scroll for long result lists; never horizontal. Without
     overflow-x: hidden, a row whose content (long handle, long bio)
     exceeds the column width would let the row expand AND scroll
     horizontally, defeating the per-element ellipsis truncation. */
  overflow-y: auto;
  overflow-x: hidden;
}

.column__bottom {
  border-top: 1px solid var(--line);
  background: var(--panel);
  flex-shrink: 0;
}

/* Center column */
.column--center {
  background: var(--bg);
  display: flex;
  flex-direction: column;
}

.feed-tabs {
  display: flex;
  justify-content: center;
  gap: 28px;
  height: 34px;
  border-bottom: 1px solid var(--line-soft);
  flex-shrink: 0;
}

.feed-tab {
  height: 100%;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  position: relative;
}

.feed-tab:hover,
.feed-tab:focus-visible {
  color: var(--text);
  outline: none;
}

.feed-tab.is-active {
  color: var(--text);
}

.feed-tab.is-active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--text);
}

/* Composer */
.composer {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 12px 18px 10px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg);
  flex-shrink: 0;
}

.composer__input {
  flex: 1 1 auto;
  min-height: 32px;
  max-height: 280px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
  color: var(--text);
  resize: none;
  outline: none;
  overflow: hidden;
  line-height: 1.55;
  transition: height 120ms ease, border-color 120ms ease;
}

.composer__input:focus {
  border-color: var(--focus);
}

.composer__input::placeholder {
  color: var(--dim);
}

.composer__input.is-overflowing {
  overflow-y: auto;
}

.composer__post {
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
}

.composer__post:hover,
.composer__post:focus-visible {
  border-color: var(--focus);
  outline: none;
}

.composer__post:disabled {
  color: var(--muted);
  cursor: default;
}

.composer__state {
  min-height: 16px;
  padding: 0 18px 6px;
  color: var(--muted);
  font-size: 12px;
  flex-shrink: 0;
}

.feed-pane {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}

.feed-pane[hidden] {
  display: none;
}

.stream-list {
  padding: 6px 0 40px;
}

.stream-post {
  padding: 12px 18px 14px;
  border-bottom: 1px solid var(--line-soft);
}

.stream-post__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 12px;
}

.stream-post__handle {
  color: var(--text);
}

.stream-post__time {
  color: var(--dim);
  flex-shrink: 0;
}

.stream-post__body {
  max-width: 78ch;
  color: var(--text);
}

.stream-post__actions {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 10px;
  color: var(--muted);
}

.stream-post__action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px;
  margin: 0;
  background: transparent;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  line-height: 1;
}

.stream-post__action:hover,
.stream-post__action:focus-visible {
  color: var(--text);
  outline: none;
}

.stream-post__action-glyph {
  font-size: 14px;
  line-height: 1;
}

/* Phase 13.1: delete-own-post action. Sits on the right with calm
   muted styling. The armed state (after first click) goes danger-
   colored so the user knows the second click commits. */
.stream-post__action--delete {
  margin-left: auto;
  font-size: 12px;
}
.stream-post__action--delete-armed {
  color: #d9534f !important;
  font-weight: 600;
}

.stream-post__action-count {
  font-variant-numeric: tabular-nums;
  color: var(--dim);
  min-width: 1ch;
}

.stream-post__empty {
  padding: 18px;
  color: var(--muted);
  font-size: 13px;
}

.stream-post__action--vote-liked {
  color: var(--text);
}

.stream-post__action--vote-disliked {
  color: var(--text);
}

.stream-post__ref {
  margin: 4px 0 6px;
  color: var(--dim);
  font-size: 12px;
}

.stream-post__embed {
  display: block;
  margin: 8px 0 0;
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  border-radius: 2px;
  background: var(--panel-soft);
  color: var(--muted);
  font-size: 13px;
  white-space: pre-wrap;
  cursor: pointer;
}

.stream-post__embed:hover,
.stream-post__embed:focus-visible {
  border-color: var(--muted);
  outline: none;
}

.stream-post__embed-meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  color: var(--muted);
  font-size: 12px;
  margin-bottom: 2px;
}

.stream-post__embed-handle {
  color: var(--text);
}

.stream-post__embed-time {
  color: var(--dim);
}

.stream-post__embed-body {
  white-space: pre-wrap;
  color: var(--text);
}

.stream-post__embed-more {
  margin-top: 6px;
  font-size: 12px;
  color: var(--dim);
  text-decoration: underline;
}

.stream-post__replies {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--line-soft);
}

.stream-post__reply-form {
  display: flex;
  gap: 6px;
  align-items: flex-start;
  flex-wrap: wrap;
  width: 100%;
  box-sizing: border-box;
}

.stream-post__reply-input {
  flex: 1 1 12rem;
  min-width: 0;
  background: transparent;
  border: 1px solid var(--line-soft);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  padding: 6px 8px;
  resize: vertical;
  box-sizing: border-box;
}

.stream-post__reply-submit {
  background: transparent;
  border: 1px solid var(--line-soft);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  cursor: pointer;
}

.stream-post__reply-submit:hover,
.stream-post__reply-submit:focus-visible {
  border-color: var(--text);
  outline: none;
}

.stream-post__reply-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
}

.stream-post__reply-focused {
  margin: 10px 0 4px;
  padding: 8px;
  border: 1px solid var(--focus, #6aa);
  border-radius: 4px;
  background: rgba(106, 170, 170, 0.08);
}

/* One-shot fade triggered the first render after a notification
 * click. Quick (~1s) so it draws the eye and gets out of the way;
 * the resting background on .stream-post__reply-focused remains so
 * the pinned reply is still visually identifiable post-fade. JS
 * timer length matches the keyframe duration. */
@keyframes sudo-reply-focus-flash {
  0%   { background: rgba(106, 170, 170, 0.40); }
  60%  { background: rgba(106, 170, 170, 0.20); }
  100% { background: rgba(106, 170, 170, 0.08); }
}

.stream-post__reply-focused.is-flash {
  animation: sudo-reply-focus-flash 1s ease-out 0s 1 normal forwards;
}

@media (prefers-reduced-motion: reduce) {
  .stream-post__reply-focused.is-flash { animation: none; }
}

.stream-post__reply-focused-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--focus, #6aa);
  margin-bottom: 6px;
}

.stream-post__reply-focused .stream-post__reply-item.is-focused {
  background: transparent;
}

.stream-post__reply-focused-missing {
  margin: 10px 0 4px;
  padding: 8px;
  font-style: italic;
  color: var(--muted, #888);
  border: 1px dashed var(--line, #333);
  border-radius: 4px;
}

.stream-post__reply-list--nested {
  /* Indent only — no border-left / connector line. The arrow on each
   * reply is the sole nesting cue. */
  margin: 6px 0 0 14px;
  padding-left: 12px;
}

.stream-post__reply-item {
  display: grid;
  grid-template-columns: 16px 1fr;
  grid-column-gap: 8px;
  padding: 8px 0;
  font-size: 13px;
  color: var(--text);
  background: transparent;
  border-bottom: 1px solid var(--line-soft);
}

.stream-post__reply-item:last-child {
  border-bottom: 0;
}

.stream-post__reply-content {
  /* Content column is a normal block container; meta, body, actions,
   * inline composer, and the child sublist stack vertically and push
   * each other down without overlap. */
  display: block;
  min-width: 0;
}

.stream-post__reply-arrow {
  color: var(--dim);
  font-size: 13px;
  line-height: 1.4;
}

.stream-post__reply-meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  color: var(--muted);
  font-size: 12px;
}

.stream-post__reply-handle {
  color: var(--text);
}

.stream-post__reply-time {
  color: var(--dim);
}

.stream-post__reply-ref {
  color: var(--dim);
  font-size: 11px;
  margin-bottom: 2px;
}

.stream-post__reply-body {
  color: var(--text);
  white-space: pre-wrap;
  margin-top: 2px;
}

.stream-post__reply-actions {
  /* Inline action row: ↩ reply lives next to the [-]/[+] collapse
   * toggle when there are descendants, separated by a small gap.
   * Aligned against the content column (not the arrow gutter). */
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 4px;
}

.stream-post__reply-action {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.stream-post__reply-action:hover,
.stream-post__reply-action:focus-visible {
  color: var(--text);
  outline: none;
}

.stream-post__reply-form--nested {
  /* Inline composer for replying to a specific reply. Lives in the
   * content column under the action row, in normal flow, so it
   * never overlaps the meta/body and pushes any child sublist
   * downward. Inherits flex-wrap from .stream-post__reply-form so
   * the submit stacks below the textarea on narrow widths. */
  margin-top: 6px;
  width: 100%;
}

.stream-post__reply-error {
  color: var(--dim);
  font-size: 12px;
  margin-top: 4px;
}

.stream-post__reply-empty {
  color: var(--dim);
  font-size: 12px;
  padding: 4px 0;
}

.stream-post__action--repost.is-already {
  color: var(--text);
  cursor: default;
  opacity: 0.7;
}

.feed-composer__error {
  color: var(--dim);
  font-size: 12px;
  margin-top: 4px;
}

.stream-post__main {
  cursor: pointer;
}

.stream-post__main:hover .stream-post__handle {
  text-decoration: underline;
}

.stream-post__reply-collapse {
  /* Sits inline with the ↩ reply action inside .stream-post__reply-actions. */
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  color: var(--dim);
  font: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.stream-post__reply-collapse:hover,
.stream-post__reply-collapse:focus-visible {
  color: var(--text);
  outline: none;
}

.thread-view {
  padding: 0 0 40px;
}

.thread-view__back {
  /* Match the .column__title row in the left column so the
     horizontal divider lines up across columns. Same height, same
     border-bottom, same horizontal rhythm; back arrow is vertically
     centered. */
  display: flex;
  align-items: center;
  width: 100%;
  height: 34px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line-soft);
  padding: 0 16px;
  margin: 0;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-align: left;
  cursor: pointer;
  flex-shrink: 0;
}

.thread-view__back:hover,
.thread-view__back:focus-visible {
  color: var(--text);
  outline: none;
}

.thread-view__parent {
  border-top: 1px solid var(--line-soft);
}

/* Inside thread view the parent post itself shouldn't show the
 * pointer cursor — there's nowhere to click it to. */
.thread-view__parent .stream-post__main {
  cursor: default;
}

.thread-view__parent .stream-post__main:hover .stream-post__handle {
  text-decoration: none;
}

/* Thread view replies panel is always open, so suppress the dashed
 * separator that the inline feed-list mode draws above it. */
.thread-view__parent .stream-post__replies {
  border-top: 0;
  padding-top: 0;
  margin-top: 14px;
}

.text-surface {
  white-space: pre;
}

.text-surface__line {
  display: block;
  min-height: 22px;
}

/* Right column */
.column--right {
  background: var(--panel);
}

.column--right .chat-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}

/* Search/lookup results */
.search-results {
  border-bottom: 1px solid var(--line-soft);
  overflow: auto;
  min-height: 0;
}

.search-results > .lookup__empty {
  padding: 9px 14px 10px;
}

.search-result {
  position: relative;
  padding: 9px 78px 10px 46px;
  border-top: 1px solid var(--line-soft);
}

.search-result:hover {
  background: var(--panel-soft);
}

.search-result__handle {
  color: var(--text);
  /* Truncate handles at the row's content width so long usernames
     don't push the +/follow button onto a new line. The element
     also carries the full handle for title/aria-label fallbacks
     handled by the renderer (search-result rows mirror the
     lookup-card pattern). */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-result > .is-muted {
  /* Clamp the bio line to 2 visual lines and ellipsis the rest.
     Without this, a long bio pushes the row tall and the action
     button drifts away from the handle. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  word-break: break-word;
}

.search-result__add {
  position: absolute;
  top: 12px;
  right: 14px;
  min-width: 54px;
  min-height: 26px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: #101211;
  color: var(--text);
  cursor: pointer;
}

.search-result__add:disabled {
  color: var(--muted);
  cursor: default;
}

.lookup__input {
  width: 100%;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 3px;
  /* Reserve right padding so the browser-native clear button on
     <input type="search"> (rendered by Safari/Chrome) doesn't sit
     on top of the user's text. 24px is wide enough for the glyph;
     keeps left padding at 10px. */
  padding: 0 24px 0 10px;
  background: var(--panel);
  color: var(--text);
  outline: none;
}

.lookup__input::placeholder {
  color: var(--dim);
}

.lookup__input:focus {
  border-color: var(--focus);
}

.lookup__result {
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-soft);
  overflow: auto;
}

.lookup__result:empty {
  display: none;
}

.lookup__empty {
  color: var(--muted);
  padding: 10px 14px;
  font-size: 12px;
}

/* Phase 13: chat-list empty state. Calm two-line placeholder shown
   when the signed-in user has no conversations yet. */
.chat-list__empty {
  padding: 16px 14px;
}
.chat-list__empty-title {
  color: var(--text);
  font-size: 13px;
  margin-bottom: 4px;
}
.chat-list__empty-hint {
  color: var(--muted);
  font-size: 12px;
}

.column__hint {
  padding: 8px 14px 10px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 12px;
}

.stream-list .lookup__empty {
  padding: 12px 18px;
}

.lookup-card {
  position: relative;
  padding-left: 32px;
  color: var(--text);
}

.lookup-card__handle {
  display: block;
  /* `white-space: nowrap` alone doesn't shrink-wrap the element;
     it grows to content width and overflows the parent. Capping
     `max-width: 100%` makes the element bound by the parent so
     `overflow: hidden + text-overflow: ellipsis` can actually fire
     when the handle exceeds the column. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Phase 14C: bio shown under the handle on the directory card. Bio
   stays in normal text color (not muted) so it reads as content
   rather than metadata. Wraps; ~3 lines worth of room before any
   ellipsis is needed. */
.lookup-card__bio {
  display: block;
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
  overflow-wrap: anywhere;
}

.lookup-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.lookup-card__button {
  min-height: 26px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
}

.lookup-card__button.is-active {
  border-color: var(--focus);
  color: var(--text);
}

.lookup-card--error {
  padding-left: 0;
  color: var(--danger);
}

.lookup-card__advanced {
  margin-top: 12px;
  font-size: 12px;
}

.lookup-card__advanced-summary {
  cursor: pointer;
  color: var(--muted, #888);
  user-select: none;
  list-style: none;
}

.lookup-card__advanced-summary::-webkit-details-marker {
  display: none;
}

.lookup-card__advanced-summary::before {
  content: "▸ ";
  display: inline-block;
  width: 12px;
}

.lookup-card__advanced[open] .lookup-card__advanced-summary::before {
  content: "▾ ";
}

.lookup-card__advanced-fields {
  margin-top: 8px;
  padding-left: 14px;
}

.lookup-card__advanced-note {
  margin-top: 8px;
  padding-left: 14px;
  font-style: italic;
}

.lookup-card__advanced-actions {
  margin-top: 6px;
  padding-left: 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Fingerprint grid. Default layout is inline (flex/grid sibling)
   so it sits where its parent places it. The lookup/search rows
   below position theirs absolutely against their relative parent
   (.lookup-card / .search-result both have position: relative)
   to keep the historical "icon hugs the left rail" look. Anywhere
   else (e.g. the account dialog) the grid renders inline. */
.identity-fingerprint-grid {
  width: 20px;
  height: 20px;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  border: 1px solid var(--line);
  background: #050605;
}

.lookup-card > .identity-fingerprint-grid,
.search-result > .identity-fingerprint-grid {
  position: absolute;
  top: 11px;
  left: 14px;
}

.identity-fingerprint-grid__cell {
  width: 100%;
  height: 100%;
  opacity: 0.32;
}

.identity-fingerprint-grid__cell.is-on {
  opacity: 1;
}

/* Profile card (bottom-left) */
.profile-card {
  padding: 12px 14px;
  position: relative;
}

.identity-card {
  max-width: 30ch;
  color: var(--text);
}

.identity-card__handle {
  color: var(--text);
  font-size: 13px;
}

/* Notifications panel — lower-left column. Sparse terminal style
   matching the rest of the column. Horizontal padding lines up with
   .column__title (16px) and the .lookup__input row above it; rows
   wrap their text and their action button strip independently so
   long handles never push action buttons off-screen. */
.notifications {
  display: flex;
  flex-direction: column;
  padding: 4px 0 12px;
  font-size: 12px;
  color: var(--muted);
  min-width: 0;
  overflow: hidden;
}

.notifications__empty {
  color: var(--muted);
  padding: 8px 16px;
}

.notifications__list {
  display: flex;
  flex-direction: column;
  /* Cap the list at roughly half the viewport so a long notifications
     backlog doesn't push the rest of the column off screen. The
     header (#notifications-title) and the "clear all" button live
     outside this scroll container so they stay pinned. Each row
     keeps its own border-top + padding so there's no external gap
     between rows. */
  max-height: 50vh;
  overflow-y: auto;
  /* min-height:0 is required for a flex child to actually shrink and
     activate overflow scrolling — without it the list would still
     grow unbounded inside a column flex container. */
  min-height: 0;
}

.notification-row {
  border-top: 1px solid var(--line-soft);
  padding: 8px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.notification-row__line {
  color: var(--text);
  font-size: 12px;
  /* Long handles + canonical_id fragments must wrap inside the
     panel rather than push the action row off-screen. */
  overflow-wrap: anywhere;
  word-break: break-word;
}

.notification-row__sub {
  color: var(--muted);
  overflow-wrap: anywhere;
  word-break: break-word;
}

.notification-row__actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 2px;
}

.notification-row__action {
  background: transparent;
  border: 1px solid var(--line-soft);
  color: var(--text);
  font: inherit;
  padding: 2px 8px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.notification-row__action:hover,
.notification-row__action:focus-visible {
  background: var(--panel-soft);
  outline: none;
}

/* Chat rows */
.chat-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 14px 10px;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
}

.chat-row:focus-visible,
.chat-row:hover,
.chat-row.is-selected {
  background: var(--panel-soft);
  outline: none;
}

.chat-row__handle {
  color: var(--text);
  font-size: 13px;
}

.chat-row__preview {
  color: var(--muted);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-row__time {
  color: var(--dim);
  font-size: 11px;
}

.chat-row__unread {
  position: absolute;
  top: 8px;
  right: 12px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent, #2563eb);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
}

.chat-row--unread .chat-row__handle {
  font-weight: 600;
}

/* Discovery */
.discovery-card {
  position: relative;
  padding: 9px 14px 10px 32px;
  border-top: 1px solid var(--line-soft);
  color: var(--text);
}

.discovery-card__handle,
.discovery-card__excerpt {
  overflow-wrap: anywhere;
}

.discovery-toolbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--line-soft);
}

.discovery-toolbar__label {
  color: var(--muted);
  font-size: 12px;
  padding-top: 3px;
}

.discovery-toolbar__modes {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Lines and rules */
.line {
  min-height: 22px;
  overflow-wrap: anywhere;
}

.rule {
  width: 16ch;
  height: 1px;
  margin: 8px 0 9px;
  background: var(--line);
}

.is-muted {
  color: var(--muted);
}

.is-danger {
  color: var(--danger);
}

/* Buttons (auth-link reused in landing + dialogs) */
.auth-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  border: 1px solid transparent;
  border-radius: 3px;
  padding: 5px 9px;
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  text-decoration: none;
}

.auth-link:hover,
.auth-link:focus-visible {
  color: var(--text);
  border-color: var(--line);
  outline: none;
}

.text-button {
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
  min-height: 32px;
  padding: 5px 9px;
}

.text-button:focus-visible {
  outline: 1px solid var(--focus);
  outline-offset: 2px;
}

.text-button--primary {
  color: var(--text);
  border-color: var(--focus);
}

.text-button--danger {
  color: var(--danger);
  border-color: var(--line);
}

.text-button--danger:hover,
.text-button--danger:focus-visible {
  border-color: var(--danger);
  outline: none;
}

.auth-recovery {
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel-soft);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.auth-recovery--waiting {
  border-color: var(--line-soft);
}

.auth-recovery__hint {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.55;
}

.auth-recovery__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.auth-recovery__advanced {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 12px;
}

.auth-recovery__advanced > summary {
  cursor: pointer;
  list-style: none;
  color: var(--muted);
}

.auth-recovery__advanced > summary::-webkit-details-marker {
  display: none;
}

.auth-recovery__advanced > summary::before {
  content: "▸ ";
  color: var(--dim);
}

.auth-recovery__advanced[open] > summary::before {
  content: "▾ ";
}

.auth-recovery__advanced .auth-recovery__hint {
  margin-top: 6px;
}

.auth-recovery__advanced .auth-recovery__actions {
  margin-top: 8px;
}

.auth-recovery__reset {
  /* The destructive action is intentionally muted in tone — never the
   * dominant button in the recovery panel. */
}

.signup-dialog input:focus-visible {
  outline: 1px solid var(--focus);
  outline-offset: 2px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
 * Floating chat popup
 * ============================================================ */
.chat-popup {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 5;
  width: min(360px, calc(100vw - 32px));
  max-width: 360px;
  height: min(480px, calc(100vh - var(--topbar-h) - 32px));
  border: 1px solid var(--line);
  background: var(--panel);
  display: grid;
  /* Floating-popup layout — single column, header / body / reply
     chip / form rows. The optional sidebar slot is collapsed
     (zero-width); .is-fullscreen on desktop re-expands it. */
  grid-template-columns: 0 minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "header header"
    "sidebar body"
    "sidebar reply"
    "sidebar form";
  overflow: hidden;
  animation: chat-popup-in 140ms ease-out;
}

.chat-popup__header { grid-area: header; }
.chat-popup__sidebar { grid-area: sidebar; }
.chat-popup__body { grid-area: body; }
.chat-popup__reply-context { grid-area: reply; }
.chat-popup__form { grid-area: form; }

.chat-popup[hidden] {
  display: none;
}

.chat-popup.is-minimized {
  height: auto;
  grid-template-rows: auto;
}

.chat-popup.is-minimized .chat-popup__body,
.chat-popup.is-minimized .chat-popup__form,
.chat-popup.is-minimized .chat-popup__reply-context {
  display: none;
}

/* Fullscreen chat. Covers the main app area below the topbar and
   above any safe-area inset. Behaves like a full-page route on
   mobile (where it's the default for any opened chat) and like a
   focused mode on desktop (one click of the fullscreen icon).
   Animation is suppressed in fullscreen so the switch feels
   instantaneous, not a slide-up popup. */
.chat-popup.is-fullscreen {
  position: fixed;
  top: var(--topbar-h);
  left: 0;
  right: 0;
  bottom: 0;
  width: 100vw;
  max-width: 100vw;
  height: calc(100vh - var(--topbar-h));
  border: 0;
  border-radius: 0;
  animation: none;
  /* Desktop fullscreen pulls the chat list into a left rail so the
     user can switch conversations without exiting. The 240px is
     wide enough for handles + the search-result truncation rule
     ellipsizes anything longer. */
  grid-template-columns: 240px minmax(0, 1fr);
}

.chat-popup__sidebar {
  display: none;
  flex-direction: column;
  border-right: 1px solid var(--line-soft);
  background: var(--panel-soft);
  min-width: 0;
  overflow: hidden;
}

.chat-popup.is-fullscreen .chat-popup__sidebar {
  display: flex;
}

.chat-popup__sidebar-search {
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-soft);
}

.chat-popup__sidebar-input {
  width: 100%;
  height: 26px;
  padding: 0 24px 0 8px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--bg);
  color: var(--text);
  font-size: 12px;
  outline: none;
}

.chat-popup__sidebar-input:focus {
  border-color: var(--focus);
}

.chat-popup__sidebar-input::placeholder {
  color: var(--dim);
}

.chat-popup__sidebar-list {
  flex: 1 1 auto;
  overflow-y: auto;
}

/* Sidebar row layout: handle on top, optional preview underneath,
   unread badge anchored top-right. The row is a regular flex
   column so the preview can flow to a second line; max-width
   ellipsis handles overflow on either line. */
.chat-popup__sidebar-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: 6px;
  row-gap: 0;
  width: 100%;
  background: transparent;
  border: 0;
  text-align: left;
  padding: 8px 12px;
  color: var(--text);
  cursor: pointer;
  font-size: 13px;
  border-bottom: 1px solid var(--line-soft);
}

.chat-popup__sidebar-row:hover,
.chat-popup__sidebar-row:focus-visible {
  background: rgba(255, 255, 255, 0.03);
  outline: none;
}

.chat-popup__sidebar-row.is-active {
  background: rgba(255, 255, 255, 0.10);
  border-left: 2px solid var(--focus);
  padding-left: 10px;
}

.chat-popup__sidebar-row__handle {
  grid-column: 1;
  grid-row: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-popup__sidebar-row__unread {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: var(--focus);
  color: var(--bg);
  border-radius: 9px;
}

.chat-popup__sidebar-row__preview {
  grid-column: 1;
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
}

.chat-popup__sidebar-row__preview-tick {
  flex-shrink: 0;
  letter-spacing: -0.5px;
}

.chat-popup__sidebar-row__preview-tick--sent { color: var(--dim); }
.chat-popup__sidebar-row__preview-tick--delivered { color: var(--muted); }
.chat-popup__sidebar-row__preview-tick--read { color: var(--focus); }

.chat-popup__sidebar-row__preview-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.chat-popup__sidebar-empty {
  padding: 12px;
  color: var(--muted);
  font-size: 12px;
}

.chat-popup.is-fullscreen .chat-popup__handle {
  font-size: 15px;
}

.chat-popup.is-fullscreen.is-minimized {
  /* Fullscreen never collapses; ignore the minimized class while
     fullscreen is active so the row-click toggle (which mostly
     exists for the floating popup) doesn't strand the user. */
  height: calc(100vh - var(--topbar-h));
  grid-template-rows: auto minmax(0, 1fr) auto auto;
}

.chat-popup.is-fullscreen.is-minimized .chat-popup__body,
.chat-popup.is-fullscreen.is-minimized .chat-popup__form {
  display: flex;
}

.chat-popup__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px 8px 12px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--panel-soft);
  cursor: pointer;
  user-select: none;
}

.chat-popup__header:focus-visible {
  outline: 1px solid var(--focus);
  outline-offset: -1px;
}

.chat-popup.is-minimized .chat-popup__header {
  border-bottom: 0;
}

.chat-popup__identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

/* Back-arrow is hidden on the floating popup and only revealed in
   fullscreen mode. It nests inside the header next to the handle so
   the layout doesn't shift when fullscreen toggles. */
.chat-popup__back {
  display: none;
  font-size: 18px;
  margin-right: 4px;
}

.chat-popup.is-fullscreen .chat-popup__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Compact TTL badge inline with the handle when disappearing
   messages are active on this conversation. Tiny and muted so it
   doesn't fight the handle for attention. */
.chat-popup__ttl-badge {
  display: inline-block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.chat-popup__ttl-badge[hidden] {
  display: none;
}

.chat-popup__handle {
  color: var(--text);
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-popup__fingerprint {
  color: var(--dim);
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-popup__controls {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

.chat-popup__icon {
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
}

.chat-popup__icon:hover,
.chat-popup__icon:focus-visible {
  color: var(--text);
  outline: none;
}

.chat-popup__body {
  padding: 10px 12px;
  overflow-x: hidden;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.chat-message {
  /* Block layout (not nested flex) clamps reliably on Safari/WebKit:
   * Safari was letting bubbles inside a column-flex chat-message exceed
   * the wrapper's max-width when the bubble's intrinsic content was long.
   * fit-content + max-width keeps the wrapper at content width, capped at
   * 75%, no matter what the bubble inside wants. */
  display: block;
  width: fit-content;
  max-width: 75%;
  min-width: 0;
}

.chat-message--received {
  align-self: flex-start;
}

.chat-message--sent {
  align-self: flex-end;
}

.chat-message__bubble {
  display: block;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  padding: 6px 10px;
  margin: 0 0 2px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--panel-soft);
  color: var(--text);
  font-size: 13px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  line-height: 1.5;
}

.chat-message--sent .chat-message__bubble {
  background: #1a1f1d;
  border-color: var(--line);
}

.chat-message--received .chat-message__bubble {
  background: var(--panel-soft);
  border-color: var(--line-soft);
}

.chat-message__meta {
  display: block;
  color: var(--dim);
  font-size: 11px;
  padding: 0 2px;
}

.chat-message--sent .chat-message__meta {
  text-align: right;
}

.chat-message--received .chat-message__meta {
  text-align: left;
}

.chat-message__actions {
  display: flex;
  gap: 6px;
  margin-top: 2px;
}

.chat-message--sent .chat-message__actions {
  justify-content: flex-end;
}

/* Three-dot kebab button — only visible on hover/focus of the
   message row on desktop. On touch devices long-press summons the
   menu via JS; the button stays in DOM (so smokes can find it) but
   the focus-only reveal keeps the resting chat tidy. */
.chat-message__menu-trigger {
  background: transparent;
  border: 0;
  color: var(--muted);
  font-size: 14px;
  padding: 0 4px;
  cursor: pointer;
  line-height: 1;
  opacity: 0;
  transition: opacity 100ms ease;
}

.chat-message:hover .chat-message__menu-trigger,
.chat-message:focus-within .chat-message__menu-trigger,
.chat-message__menu-trigger:focus-visible {
  opacity: 1;
}

/* On touch-primary devices (no hover) the menu trigger is always
   visible so the user has a discoverable affordance even without
   long-press. */
@media (hover: none) {
  .chat-message__menu-trigger {
    opacity: 0.65;
  }
}

.chat-message__menu-trigger:hover,
.chat-message__menu-trigger:focus-visible {
  color: var(--text);
  outline: none;
}

/* Inline reply-snippet shown above a reply's body. Compact preview
   of the message being replied to. The snippet text is clamped so
   the chat doesn't grow tall on a long quoted line. */
.chat-message__reply-snippet {
  display: block;
  margin: 0 0 4px;
  padding: 4px 8px;
  border-left: 2px solid var(--line);
  background: rgba(255, 255, 255, 0.02);
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  cursor: pointer;
}

.chat-message__reply-snippet:hover {
  color: var(--text);
}

/* Sent-message status indicators (ticks). One tick = locally
   stored / submitted to relay; the placeholder is rendered next to
   the timestamp inside .chat-message__meta. Delivered/read across
   different accounts is not modeled yet and the meta text is
   honest about that. */
.chat-message__tick {
  margin-left: 6px;
  letter-spacing: -0.5px;
}

.chat-message__tick--sent { color: var(--dim); }
.chat-message__tick--queued { color: var(--muted); }
.chat-message__tick--delivered { color: var(--dim); }
.chat-message__tick--read { color: var(--focus); }
.chat-message__tick--retrying { color: var(--muted); }
.chat-message__tick--failed { color: #d9534f; }

/* Failed-send recovery row. Rendered BELOW the bubble for sent
   messages whose pending_outbound row hit a non-recoverable error.
   The reason text is dim; retry is the primary action. */
.chat-message__recovery {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 12px;
  margin-top: 4px;
}
.chat-message__recovery-reason {
  color: #d9534f;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-message__recovery-btn {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 3px;
  font-size: 11px;
  padding: 2px 8px;
  cursor: pointer;
}
.chat-message__recovery-btn--retry { border-color: var(--focus); color: var(--focus); }
.chat-message__recovery-btn--retry:hover { background: var(--focus); color: var(--bg); }
.chat-message__recovery-btn--cancel:hover { border-color: #d9534f; color: #d9534f; }

.chat-message--deleted .chat-message__bubble {
  font-style: italic;
  color: var(--dim);
}

/* "↪ forwarded" label rendered above the body of any forwarded
   message bubble. Dim and small so the body still reads as the
   focus. */
.chat-message__forwarded-label {
  font-size: 10px;
  color: var(--muted);
  letter-spacing: 0.04em;
  margin: 0 0 2px;
}

/* Forward-picker dialog list. One row per existing chat. */
.forward-picker__preview {
  margin: 8px 0 12px;
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  background: rgba(255, 255, 255, 0.02);
  font-size: 12px;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 80px;
  overflow: hidden;
}

.forward-picker__list {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line-soft);
  max-height: 240px;
  overflow-y: auto;
  margin-bottom: 12px;
}

.forward-picker__row {
  background: transparent;
  border: 0;
  text-align: left;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
  border-bottom: 1px solid var(--line-soft);
}

.forward-picker__row:last-child {
  border-bottom: 0;
}

.forward-picker__row:hover,
.forward-picker__row:focus-visible {
  background: var(--panel-soft);
  outline: none;
}

.forward-picker__empty {
  padding: 12px;
  color: var(--muted);
  font-size: 12px;
}

/* System message rendered inline in the chat body for events like
   "@A changed message expiry to 24 hours". Centered, dim, no
   bubble. */
.chat-system {
  align-self: center;
  max-width: 80%;
  text-align: center;
  color: var(--muted);
  font-size: 11px;
  padding: 4px 8px;
  font-style: italic;
}

.chat-popup__empty {
  color: var(--muted);
  font-size: 12px;
  padding: 4px 0;
}

.chat-popup__relay {
  color: var(--dim);
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Reply context chip above the composer. Renders the snippet of
   the message being replied to plus a cancel button. Hidden by
   default; revealed when the user picks "reply" from a message's
   action menu. */
.chat-popup__reply-context {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-top: 1px solid var(--line-soft);
  background: var(--panel-soft);
  font-size: 12px;
  min-width: 0;
}

.chat-popup__reply-context[hidden] {
  display: none;
}

.chat-popup__reply-snippet {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-left: 2px solid var(--line);
  padding-left: 8px;
}

.chat-popup__reply-cancel {
  background: transparent;
  border: 0;
  color: var(--muted);
  font-size: 14px;
  width: 22px;
  height: 22px;
  cursor: pointer;
  line-height: 1;
  flex-shrink: 0;
}

.chat-popup__reply-cancel:hover,
.chat-popup__reply-cancel:focus-visible {
  color: var(--text);
  outline: none;
}

/* Floating action menu for individual messages. Positioned at the
   pointer/long-press point by JS. Compact, keyboard-navigable. */
.message-menu {
  position: fixed;
  z-index: 6;
  min-width: 140px;
  padding: 4px;
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
}

.message-menu[hidden] {
  display: none;
}

.message-menu__item {
  background: transparent;
  border: 0;
  color: var(--text);
  text-align: left;
  padding: 8px 10px;
  font-size: 13px;
  cursor: pointer;
  border-radius: 3px;
}

.message-menu__item:hover:not(:disabled),
.message-menu__item:focus-visible {
  background: var(--panel-soft);
  outline: none;
}

.message-menu__item:disabled {
  color: var(--dim);
  cursor: default;
}

.message-menu__item--danger {
  color: #c66766;
}

/* Mobile bottom-sheet mode for the message menu. Same trigger
   conditions as the reaction picker bottom-sheet — phone-sized
   viewport at long-press/kebab time. Items grow to ≥44px tall so
   thumb taps don't miss-fire onto the wrong one. */
.message-menu--bottom-sheet {
  left: 0 !important;
  right: 0 !important;
  top: auto !important;
  bottom: 0;
  width: 100%;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
}
.message-menu--bottom-sheet .message-menu__item {
  min-height: 44px;
  font-size: 15px;
  padding: 12px 16px;
}

/* Reaction picker — same visual family as message-menu but
   horizontal, hosting the fixed 5-emoji set. Two modes:
     - desktop (default): floating popover positioned near the
       triggering kebab.
     - mobile (--bottom-sheet modifier): full-width sheet anchored
       to the bottom of the viewport with safe-area padding,
       larger emoji buttons (≥44px hit area) for thumb input. */
.reaction-picker {
  position: fixed;
  z-index: 7;
  padding: 4px;
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: row;
  gap: 2px;
}
.reaction-picker[hidden] { display: none; }
.reaction-picker__item {
  background: transparent;
  border: 0;
  padding: 6px 8px;
  font-size: 18px;
  cursor: pointer;
  border-radius: 3px;
  line-height: 1;
  /* Minimum tap target: 44×44px is the iOS HIG floor. The padded
     emoji at the default size lands just under that on small
     phones, so set min-width/min-height to guarantee thumb fit on
     desktop too. */
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.reaction-picker__item:hover,
.reaction-picker__item:focus-visible {
  background: var(--panel-soft);
  outline: none;
}
.reaction-picker--bottom-sheet {
  /* Mobile mode: pin to the bottom of the viewport, full-width.
     env(safe-area-inset-bottom) accounts for the iOS home-bar so
     emoji don't sit under it on standalone PWA installs. */
  left: 0 !important;
  right: 0 !important;
  top: auto !important;
  bottom: 0;
  padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
  justify-content: space-around;
  gap: 8px;
}
.reaction-picker--bottom-sheet .reaction-picker__item {
  /* Phone-friendly emoji size; thumb-distance comfortable.
     Buttons grow uniformly so all five take the row evenly. */
  flex: 1;
  font-size: 28px;
  min-height: 56px;
}

/* Reaction pills appear directly below the message bubble. They
   render aggregate counts by emoji; the current user's reaction
   carries a distinguishing background. */
.chat-message__reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
  /* The row inherits the parent .chat-message's max-width: 75%
     ceiling. Reactions with a dozen+ aggregates wrap to the next
     line rather than push the bubble's container wider, which
     would jitter the message's right-edge as new reactions land. */
  max-width: 100%;
}
.chat-message__reaction-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel-soft);
  color: var(--text);
  font-size: 12px;
  line-height: 1.4;
  cursor: pointer;
  /* Prevent a runaway emoji count text from forcing a pill wider
     than its line — instead, the pill stays compact and the row
     wraps. */
  white-space: nowrap;
  max-width: 100%;
  /* On a touch device the pill is the toggle-off target for the
     user's own reaction; sit at minimum thumb size on the cross
     axis so the tap doesn't miss. Desktop still reads compact
     because the inner content drives width. */
  min-height: 28px;
}
.chat-message__reaction-pill.is-mine {
  border-color: var(--focus);
  background: var(--panel);
}
.chat-message__reaction-pill:hover:not(:disabled) {
  background: var(--panel);
}
.chat-message__reaction-pill:disabled {
  cursor: default;
  opacity: 0.7;
}

/* Phase 13.1: typing indicator now lives inline in the chat header
   next to the handle, plus a subtle "is-typing" state on sidebar
   rows. The old between-body-and-composer bar is kept hidden via
   display:none but retained as an aria-live region for future use
   (assistive tech doesn't care about visual position). */
.chat-popup__typing { display: none; }
.chat-popup__header-typing {
  margin-left: 8px;
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
}
.chat-popup__header-typing[hidden] { display: none; }
.chat-popup__sidebar-row.is-typing .chat-popup__sidebar-row__preview-text {
  color: var(--muted);
  font-style: italic;
}

/* Phase 10.2: onboarding carousel. Five steps; only one .is-active
   at a time. Dots track position. Skip lives next to next/back. */
.onboarding__panel {
  max-width: 480px;
}
.onboarding__dots {
  display: flex;
  gap: 6px;
  justify-content: center;
  margin: 8px 0 4px;
}
.onboarding__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--line);
  transition: background 0.18s ease;
}
.onboarding__dot.is-active { background: var(--focus); }
.onboarding__step {
  display: none;
  flex-direction: column;
  gap: 12px;
  padding: 8px 0;
}
.onboarding__step.is-active { display: flex; }
.onboarding__heading {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}
.onboarding__body {
  margin: 0;
  color: var(--text);
  line-height: 1.55;
  font-size: 14px;
}
.onboarding__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 12px;
}
.onboarding__skip {
  margin-right: auto;
  color: var(--muted);
}

/* Phase 10.3 reduced-motion: respect prefers-reduced-motion by
   skipping transitions/animations across the app. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Composer attachment plus button + upload-progress line. */
.chat-popup__attach {
  background: transparent;
  border: 0;
  color: var(--text);
  font-size: 22px;
  font-weight: 400;
  padding: 0 8px;
  cursor: pointer;
  line-height: 1;
}
.chat-popup__attach:hover { color: var(--focus); }
.chat-popup__attach:disabled { opacity: 0.5; cursor: not-allowed; }
.chat-popup__attachment-status {
  padding: 4px 12px;
  font-size: 12px;
  color: var(--muted);
  border-top: 1px solid var(--line-soft);
  display: flex;
  gap: 8px;
  align-items: center;
}
.chat-popup__attachment-status[hidden] { display: none; }
.chat-popup__attachment-status .chat-popup__attachment-cancel,
.chat-popup__attachment-status .chat-popup__attachment-retry {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 3px;
  font-size: 11px;
  padding: 2px 8px;
  cursor: pointer;
}

/* Attachment renders inside a .chat-message row, BELOW the
   .chat-message__bubble (which carries text body, if any). */
.chat-message__attachment {
  margin-top: 4px;
  max-width: 100%;
}
.chat-message__attachment-image {
  max-width: 100%;
  max-height: 320px;
  border-radius: 4px;
  cursor: zoom-in;
  display: block;
  /* Keep transparency under control while the blob decrypts and
     loads. Background uses panel-soft so a slow-decode looks
     deliberate, not blank. */
  background: var(--panel-soft);
  min-height: 80px;
}
.chat-message__attachment-image.is-loading {
  cursor: progress;
}
.chat-message__attachment-video {
  max-width: 100%;
  max-height: 320px;
  border-radius: 4px;
  display: block;
  background: var(--panel-soft);
}
.chat-message__attachment-file {
  display: flex;
  flex-direction: row;
  gap: 8px;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--panel-soft);
  font-size: 13px;
  min-width: 200px;
  max-width: 100%;
}
.chat-message__attachment-file__ext {
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 2px 6px;
  text-transform: uppercase;
  flex: 0 0 auto;
}
.chat-message__attachment-file__name {
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.chat-message__attachment-file__size {
  font-size: 11px;
  color: var(--muted);
  flex: 0 0 auto;
}
.chat-message__attachment-file__download {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 3px;
  padding: 4px 8px;
  font-size: 11px;
  cursor: pointer;
  flex: 0 0 auto;
  min-height: 32px;
}
.chat-message__attachment-error {
  font-size: 12px;
  color: var(--danger);
  padding: 4px 0;
}
.chat-message__attachment-placeholder {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  padding: 4px 0;
}

/* Full-screen media viewer. Dark overlay, image centered, Escape /
   close button dismiss. No pinch zoom in v1. */
.media-viewer {
  position: fixed;
  inset: 0;
  z-index: 9;
  background: rgba(0, 0, 0, 0.92);
  display: flex;
  flex-direction: column;
}
.media-viewer[hidden] { display: none; }
.media-viewer__stage {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 16px;
}
.media-viewer__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}
.media-viewer__close {
  position: absolute;
  top: calc(8px + env(safe-area-inset-top));
  right: 8px;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text);
  font-size: 16px;
  border-radius: 50%;
  width: 36px;
  height: 36px;
  cursor: pointer;
  z-index: 1;
}

.chat-popup__form {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 8px 10px;
  border-top: 1px solid var(--line-soft);
  background: var(--panel);
}

.chat-popup__input {
  flex: 1 1 auto;
  min-height: 28px;
  max-height: 120px;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--bg);
  color: var(--text);
  resize: none;
  outline: none;
  overflow: hidden;
  line-height: 1.5;
  transition: height 120ms ease, border-color 120ms ease;
}

.chat-popup__input:focus {
  border-color: var(--focus);
}

.chat-popup__send {
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
  font-size: 12px;
}

.chat-popup__send:hover,
.chat-popup__send:focus-visible {
  border-color: var(--focus);
  outline: none;
}

@keyframes chat-popup-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ============================================================
 * Dialogs (signup/signin/restore/devices)
 * ============================================================ */
.signup-dialog {
  width: min(calc(100vw - 32px), 520px);
  max-width: 520px;
  max-height: calc(100vh - 48px);
  margin: auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0;
  background: var(--panel);
  color: var(--text);
}

.signup-dialog--signup {
  width: min(calc(100vw - 32px), 760px);
  max-width: 760px;
}

.signup-dialog--restore {
  width: min(calc(100vw - 32px), 720px);
  max-width: 720px;
}

.signup-dialog--devices {
  width: min(calc(100vw - 32px), 640px);
  max-width: 640px;
}

.signup-dialog::backdrop {
  background: rgb(0 0 0 / 0.58);
}

.signup-dialog__panel {
  width: 100%;
  max-width: 100%;
  padding: 18px;
  max-height: calc(100vh - 48px);
  overflow: auto;
  animation: auth-card-in 220ms ease-out;
}

.signup-dialog__panel > * {
  max-width: 100%;
}

.signup-dialog input,
.signup-dialog select,
.signup-dialog textarea {
  width: 100%;
  max-width: 100%;
}

.signup-dialog__title {
  margin-bottom: 14px;
  color: var(--text);
}

.signup-dialog__warning,
.signup-dialog__hint,
.signup-dialog__state {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.55;
}

/* Phase 11.5: secondary copy variants. --lead sits ABOVE the main
   content, --secondary BELOW; --note is the inline private-window
   caveat; --strong upgrades the note when private mode is detected. */
.signup-dialog__hint--lead { margin-top: 4px; }
.signup-dialog__hint--secondary { margin-top: 12px; }
.signup-dialog__hint--note {
  padding: 8px 10px;
  background: var(--panel-soft);
  border-left: 2px solid var(--line);
  border-radius: 3px;
}
.signup-dialog__hint--strong {
  border-left-color: #d9a000;
  color: var(--text);
}

.pairing-card__hint--note {
  margin-top: 4px;
  padding: 6px 8px;
  background: var(--panel-soft);
  border-left: 2px solid var(--line);
  border-radius: 3px;
  font-size: 11px;
}

.signup-dialog__label {
  display: block;
  margin-top: 14px;
  margin-bottom: 8px;
  color: var(--muted);
  font-size: 12px;
}

.signup-dialog--signup .signup-dialog__label {
  margin-top: 18px;
  margin-bottom: 8px;
}

.signup-dialog__label:first-of-type {
  margin-top: 0;
}

.signup-dialog__select {
  width: 100%;
  height: 34px;
  margin-bottom: 14px;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 10px;
  background: var(--panel);
  color: var(--text);
}

.signup-dialog__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 16px;
  row-gap: 20px;
}

.signup-dialog--signup .signup-dialog__hint {
  margin-top: 12px;
  margin-bottom: 16px;
}

.signup-dialog--signup .lookup__input {
  margin-bottom: 14px;
}

.signup-dialog__mode {
  display: inline-block;
  margin-top: 12px;
  font-size: 12px;
}

.signup-dialog__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

.signup-dialog__actions .signup-dialog__mode {
  margin-right: auto;
}

.signup-dialog__actions > .text-button {
  flex: 0 1 auto;
}

.restore-tabs {
  display: flex;
  gap: 8px;
  margin: 10px 0 10px;
}

.restore-tabs .lookup-card__button.is-active {
  border-color: var(--focus);
  color: var(--text);
}

.restore-panel {
  display: grid;
  gap: 8px;
}

.signup-result__label {
  color: var(--text);
}

.signup-result__secret {
  margin: 4px 0 8px;
  padding: 8px;
  border: 1px solid var(--line);
  background: #101211;
  color: var(--text);
  overflow-wrap: normal;
  overflow-x: auto;
  white-space: nowrap;
}

.signup-dialog__panel .signup-dialog__warning:last-of-type {
  margin-bottom: 14px;
}

/* Stale-account banner CTA shown when the local account is no longer
   on the server. Restores from an encrypted backup file in one click,
   without sending the user through the signin dialog first. */
.landing__stale-message {
  margin-bottom: 8px;
}

.landing__stale-restore {
  margin-top: 4px;
}

.landing__stale-reset {
  background: transparent;
  border: 0;
  padding: 6px 0 0;
  margin: 0;
  color: var(--muted);
  font-size: 11px;
  text-decoration: underline;
  cursor: pointer;
  align-self: flex-start;
}

.landing__stale-reset:hover,
.landing__stale-reset:focus-visible {
  color: var(--text);
}

/* Devices dialog */
.devices-panel__list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}

.devices-panel__list:empty {
  display: none;
}

.devices-panel__actions {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 8px;
  align-items: center;
  margin-bottom: 12px;
}

.devices-panel__feedback {
  min-height: 18px;
  color: var(--muted);
  font-size: 12px;
}

.devices-panel__section {
  margin-bottom: 14px;
}

.devices-panel__section-title {
  font-size: 12px;
  margin: 0 0 6px;
  text-transform: lowercase;
  letter-spacing: 0.02em;
}

.devices-panel__revoked-details summary {
  cursor: pointer;
}

.device-row {
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  border-radius: 3px;
  margin-bottom: 6px;
}

.device-row:last-child {
  margin-bottom: 0;
}

.device-row__name {
  color: var(--text);
}

.device-row__status {
  margin-top: 2px;
}

.device-row__status--failed,
.device-row__status--retry_pending {
  color: var(--warn, #b45309);
}

.device-row__status--revoked {
  color: var(--dim);
}

.device-row__status--syncing {
  color: var(--text);
}

.device-row__retry {
  margin-top: 6px;
  margin-right: 6px;
}

.device-row__advanced {
  margin-top: 6px;
  font-size: 12px;
}

.device-row__advanced-summary {
  cursor: pointer;
}

.device-row__advanced-body {
  margin-top: 4px;
  padding-left: 8px;
  border-left: 1px solid var(--line-soft);
}

.device-row__history {
  margin: 2px 0 0;
  padding-left: 16px;
  font-size: 12px;
}

.device-row__history li {
  list-style: disc;
}

.device-row__actions {
  margin-top: 8px;
}

.device-row__help {
  margin-top: 4px;
  font-size: 12px;
}

.device-row__confirm {
  margin-top: 6px;
  padding: 8px;
  border: 1px solid var(--line-soft);
  border-radius: 3px;
  background: var(--bg-elev, var(--bg));
}

.device-row__confirm-title {
  font-weight: 600;
  margin-bottom: 2px;
}

.device-row__confirm-body {
  font-size: 12px;
  margin-bottom: 8px;
}

.device-row__confirm-actions {
  display: flex;
  gap: 8px;
}

.device-row__confirm-go {
  background: var(--danger, #b91c1c);
  color: #fff;
  border-color: transparent;
}

.device-row__link-again {
  margin-right: 8px;
}

/* Floating account-feedback toast (status messages) */
.account-feedback {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 4;
  min-width: 0;
  max-width: min(520px, calc(100vw - 48px));
  padding: 8px 14px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font-size: 12px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 220ms ease;
}

.account-feedback:not(:empty) {
  opacity: 1;
}

/* ============================================================
 * Animations
 * ============================================================ */
@keyframes app-enter {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

body[data-auth-state="signed-in"] .shell {
  animation: app-enter 160ms ease-out;
}

@keyframes landing-brand-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes landing-actions-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes auth-card-in {
  from { opacity: 0; transform: translateY(8px) scale(0.99); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .landing__brand,
  .landing__actions,
  .landing__tagline,
  .about-overlay,
  .about-overlay__panel,
  .signup-dialog__panel,
  .account-menu,
  .chat-popup,
  body[data-auth-state="signed-in"] .shell {
    animation: none;
  }

  .landing,
  .composer__input,
  .chat-popup__input {
    transition: none;
  }
}

/* ============================================================
 * Responsive
 * ============================================================ */

/* Bottom tab bar — hidden on desktop, shown on the mobile breakpoint
   below. The tabs map directly onto data-mobile-region attributes
   on each column section so the same layout primitives drive both
   desktop (three columns) and mobile (one pane at a time). */
.mobile-tabs {
  display: none;
}

@media (max-width: 1100px) {
  :root {
    --column-side: 220px;
  }

  /* Compact directory mode. At narrow desktop widths (where the
     side column shrinks but we're still in the 3-column layout)
     the directory row was wrapping awkwardly: the follow/remove
     button would slip onto a second line after a long handle. The
     fix is to swap the button's text label for a single-character
     glyph (+ / ×) while preserving the full label on `title` and
     `aria-label` so screen readers still announce the action. We
     also tighten the lookup-card layout so the icon, handle, and
     button stay on one row. */
  .column--left .lookup-card__actions {
    flex-wrap: nowrap;
  }
  .column--left .lookup-card__button {
    flex: 0 0 auto;
    min-width: 28px;
    padding: 2px 6px;
    text-align: center;
  }
  .column--left .lookup-card__button-text {
    display: none;
  }
  /* set-subscribe → "follow"  → "+" */
  /* set-unsubscribe → "unfollow" → "×" */
  /* set-block → "block" → "×" (also destructive) */
  /* set-unblock → "unblock" → "+" */
  .column--left .lookup-card__button[data-relationship-action="set-subscribe"]::before,
  .column--left .lookup-card__button[data-relationship-action="set-unblock"]::before {
    content: "+";
  }
  .column--left .lookup-card__button[data-relationship-action="set-unsubscribe"]::before,
  .column--left .lookup-card__button[data-relationship-action="set-block"]::before {
    content: "×";
  }
  /* search-result rows render their own follow button with full
     "follow"/"following" text; compact-mode swaps it via the same
     pseudo-element trick. */
  .column--left .search-result__add {
    font-size: 0;
    min-width: 28px;
    padding: 0;
  }
  .column--left .search-result__add::before {
    font-size: 14px;
    content: "+";
  }
  .column--left .search-result {
    /* Tighten the right padding so the +/× sits closer to the
       column edge; the smaller button reduces the reserved gutter
       that the original 78px right-pad was sized for. */
    padding-right: 42px;
  }
  .column--left .search-result__add[disabled]::before,
  .column--left .search-result__add[data-following="true"]::before {
    content: "✓";
  }
}

@media (max-width: 880px) {
  :root {
    --column-side: 200px;
  }
  .feed-tabs {
    gap: 18px;
  }
}

@media (max-width: 760px) {
  body {
    font-size: 15px;
    /* Reserve room at the bottom for the mobile tab bar so content
       doesn't sit under it. The bar is 52px tall plus safe-area. */
    padding-bottom: calc(52px + env(safe-area-inset-bottom, 0px));
    /* Phase 10.1 viewport containment: the user-visible symptom we
       want to kill is "I can drag the whole page side-to-side". A
       single child that is even 1px wider than the viewport (a long
       handle, a settings card, a media row) lets iOS Safari rubber-
       band the entire document horizontally. We belt-and-braces here
       by clamping the document's horizontal axis; intended internal
       overflow lives on specific scrolling panes (chat body, dialog
       body, feed list) — none of which need horizontal scroll. */
    overflow-x: hidden;
    max-width: 100vw;
  }
  html {
    overflow-x: hidden;
    max-width: 100vw;
  }

  .shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
  }

  /* Single-pane mode: each .column--* contains regions with
     data-mobile-region. We display only the region matching
     body[data-mobile-pane]. Defaults to "feed" if unset. */
  .column--left {
    display: none;
  }
  .column--right {
    display: none;
  }
  .column--center {
    display: none;
  }

  body[data-mobile-pane="feed"] .column--center,
  body:not([data-mobile-pane]) .column--center {
    display: flex;
  }
  body[data-mobile-pane="chats"] .column--right {
    display: flex;
  }
  body[data-mobile-pane="directory"] .column--left,
  body[data-mobile-pane="notifications"] .column--left {
    display: grid;
    grid-template-rows: minmax(0, 1fr);
  }
  body[data-mobile-pane="directory"] .column--left .column__bottom,
  body[data-mobile-pane="notifications"] .column--left .column__top {
    display: none;
  }
  body[data-mobile-pane="notifications"] .column--left .column__bottom {
    border-top: 0;
  }

  .mobile-tabs {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 52px;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--panel);
    border-top: 1px solid var(--line);
    z-index: 200;
  }

  .mobile-tab {
    background: transparent;
    border: 0;
    color: var(--muted);
    font: inherit;
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: lowercase;
    cursor: pointer;
  }

  .mobile-tab.is-active {
    color: var(--text);
    background: var(--panel-soft);
  }

  /* Auth screen owns the viewport; hide the mobile tab bar until
     the user is signed in. */
  body:not([data-auth-state="signed-in"]) .mobile-tabs {
    display: none;
  }

  .composer__input {
    min-height: 36px;
  }

  .chat-popup {
    right: 8px;
    bottom: calc(60px + env(safe-area-inset-bottom, 0px));
    width: calc(100% - 16px);
    height: min(60dvh, 420px);
  }

  /* On mobile widths, fullscreen mode reclaims the whole viewport
     under the topbar and covers the bottom tab bar so the chat
     reads as a full-page route. The back arrow in the header is
     the user's way out. */
  .chat-popup.is-fullscreen {
    right: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    max-width: 100%;
    height: calc(100vh - var(--topbar-h));
    height: calc(100dvh - var(--topbar-h));
    /* Mobile fullscreen collapses the sidebar — the chat takes the
       whole viewport. The back-arrow in the header is the route
       back to the chat list. */
    grid-template-columns: 0 minmax(0, 1fr);
  }

  .chat-popup.is-fullscreen .chat-popup__sidebar {
    display: none;
  }

  body[data-chat-fullscreen="1"] .mobile-tabs {
    display: none;
  }

  body[data-chat-fullscreen="1"] {
    /* Reclaim the mobile-tabs gutter when the chat takes over. */
    padding-bottom: 0;
  }

  /* Desktop reveals message kebab on hover; on mobile we keep the
     trigger always-visible for discoverability since long-press is
     a hidden gesture. */
  .chat-message__menu-trigger {
    opacity: 0.8;
  }

  .signup-dialog__grid {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .signup-dialog__actions {
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .signup-dialog__actions > .text-button {
    width: 100%;
  }

  .signup-dialog__actions .signup-dialog__mode {
    margin-right: 0;
  }

  .restore-tabs {
    flex-direction: column;
  }

  .restore-tabs .lookup-card__button {
    width: 100%;
  }
}
