/* Spirit UI — design tokens, light/dark themes, mobile-first responsive layout. */

:root,
:root[data-theme="light"] {
  --bg: #f4f5fb;
  --bg-gradient: radial-gradient(1200px 600px at 80% -10%, #e6e4ff 0%, transparent 60%);
  --card-bg: #ffffff;
  --text: #1c1e2b;
  --muted: #6b7086;
  --border: #e3e5f0;
  --accent: #5b5bd6;
  --accent-hover: #4a4ac4;
  --accent-contrast: #ffffff;
  --accent-soft: #eeeefc;
  --warn-bg: #fff7e0;
  --warn-border: #f0d48a;
  --warn-text: #7a5b00;
  --code-bg: #f0f1f7;
  --shadow: 0 1px 2px rgba(20, 22, 40, 0.05), 0 8px 24px rgba(20, 22, 40, 0.06);
  --radius: 14px;
  --radius-sm: 9px;
  /* Section RF14 (specs/ui/settings-panel.md, design-settings extension):
     theme-independent (same in light/dark) -- user-tunable via the
     settings panel, client/js/designSettingsRegistry.js. */
  --font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-size-base: 15px;
  /* Section RF15 (specs/ui/settings-panel.md, width extension): also
     theme-independent -- user-tunable via the settings panel,
     client/js/designSettingsRegistry.js. */
  --content-max-width: 1400px;
  --sidebar-width: 300px;
}

:root[data-theme="dark"] {
  --bg: #14161f;
  --bg-gradient: radial-gradient(1200px 600px at 80% -10%, #23244a 0%, transparent 60%);
  --card-bg: #1d2029;
  --text: #e8eaf2;
  --muted: #9aa0b5;
  --border: #2c3040;
  --accent: #8b8bf0;
  --accent-hover: #a0a0f5;
  --accent-contrast: #14161f;
  --accent-soft: #262a44;
  --warn-bg: #33290f;
  --warn-border: #66531e;
  --warn-text: #ecd28a;
  --code-bg: #171923;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
}

* {
  box-sizing: border-box;
}

/* The hidden attribute must always win over any display rule below. */
[hidden] {
  display: none !important;
}

html {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

body {
  margin: 0;
  font-family: var(--font-family);
  font-size: var(--font-size-base);
  line-height: 1.5;
  color: var(--text);
  background: var(--bg-gradient), var(--bg);
  background-attachment: fixed;
  min-height: 100vh;
  /* Section footer-1: reserves space for the fixed .app-footer below so it
     never covers the last row of page content -- same pattern as
     body.conversation-toolbar-visible's margin-top for the toolbar. */
  padding-bottom: 32px;
}

/* ---------- Header ---------- */

/* Header/toolbar backgrounds stay full-bleed (edge to edge), but their
   CONTENT lines up with .app-body's actual content edges (which centers
   at max-width: 1400px on wide viewports, per Section RF7 bug report --
   the header/toolbar previously used a flat 16px padding regardless of
   viewport width, so on anything wider than 1400px their content sat
   noticeably inboard of/outboard of the sidebar and main content below).
   max() falls back to the plain 16px once the viewport is narrower than
   1400px (the calc() would go negative, which max() clamps away). */
.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: grid;
  /* First column matches #app-sidebar's own width (300px) exactly, so
     centering the brand within it centers the logo over the SIDEBAR
     specifically (per follow-up feedback) rather than across the whole
     header/page -- not the same thing once the header's content padding
     already lines up with the sidebar's left edge (see the padding rule
     below). */
  grid-template-columns: var(--sidebar-width) 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 10px max(16px, calc((100% - var(--content-max-width)) / 2 + 16px));
  background: color-mix(in srgb, var(--card-bg) 85%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

/* Logo flush against the far left edge (matching the sidebar's own left
   edge), per follow-up feedback -- not centered over the sidebar's width
   either, just anchored at its start. */
.app-header .brand {
  grid-column: 1;
  justify-self: start;
}

.app-header #guest-quick-actions {
  grid-column: 2;
  justify-self: start;
}

.app-header > .header-controls:not(#guest-quick-actions) {
  grid-column: 3;
  justify-self: end;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.brand .logo {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 15px;
}

.header-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* ---------- App body (SD1: persistent sidebar shell) ---------- */
/* specs/ui/persistent-sidebar.md -- wraps #app-sidebar (always in the DOM,
   outside router.js's [data-screen] mechanism entirely) and <main
   class="layout"> side by side. Below the mobile breakpoint they stack
   instead, toggled via the .main-active class (see app.js's sidebar
   back/forward wiring) rather than any router route. */

.app-body {
  display: flex;
  align-items: flex-start;
  max-width: var(--content-max-width);
  margin: 0 auto;
}

/* Reserves space for the fixed .conversation-toolbar (Section RF4) so it
   doesn't sit on top of the sidebar/main content underneath it -- toggled
   by app.js alongside the toolbar's own .hidden (see onScreenChange).
   --conversation-toolbar-height is set from JS (positionConversationToolbar)
   to the toolbar's REAL rendered height -- a hardcoded guess here left a
   visible gap/overlap once the toolbar's content (title + "Ви зараз" +
   status + invite icon) made it taller than the guess. */
body.conversation-toolbar-visible .app-body {
  margin-top: var(--conversation-toolbar-height, 40px);
}

#app-sidebar {
  width: var(--sidebar-width);
  flex: 0 0 var(--sidebar-width);
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  border-right: 1px solid var(--border);
  min-height: calc(100vh - 60px);
  box-sizing: border-box;
}

/* Section RF17 (specs/ui/design-edit-mode.md): sidebar/main-content swap --
   :root[data-sidebar-side] set by designSettingsRegistry.js's applyDesignSettings
   for the "sidebarSide" choice setting. Only `order` needs flipping (.app-body
   is a flex row, DOM order is left untouched) -- the border moves with it so
   it still reads as the sidebar's OUTER edge regardless of which side it's on. */
:root[data-sidebar-side="right"] #app-sidebar {
  order: 2;
  border-right: none;
  border-left: 1px solid var(--border);
}

:root[data-sidebar-side="right"] .app-body > .layout {
  order: 1;
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.sidebar-filters {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.sidebar-filters .chip {
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--code-bg);
  color: var(--muted);
  font-size: 12.5px;
  cursor: default;
}

.sidebar-filters .chip-active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent);
}

.app-body > .layout {
  flex: 1 1 auto;
  min-width: 0;
}

/* Desktop default: this is a mobile-only affordance -- hidden here, only
   ever revealed inside the mobile breakpoint below. */
#btn-sidebar-back {
  display: none;
}

@media (max-width: 768px) {
  .app-body {
    display: block;
  }

  #app-sidebar {
    width: 100%;
    flex: none;
    border-right: none;
    min-height: auto;
  }

  /* Default (no route "active" yet): show the sidebar full-width, hide the
     main content pane entirely. */
  .app-body > .layout {
    display: none;
  }

  /* Once a contact/route has been opened (body.main-active, set by a small
     JS toggle in app.js -- not a router route), flip: hide the sidebar,
     show main full-width with a "back" affordance at its top. */
  body.main-active #app-sidebar {
    display: none;
  }

  body.main-active .app-body > .layout {
    display: grid;
  }

  body.main-active #btn-sidebar-back {
    display: inline-flex;
  }
}

/* ---------- Layout ---------- */

.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  max-width: 1100px;
  margin: 0 auto;
  padding: 16px;
}

/* Section RF22 (specs/ui/design-edit-mode.md, Stage 2): scoped to the
   conversation screen specifically -- .card-wide is reused by
   manage/history too (client/index.html), which must keep filling the FULL
   .layout width regardless of this setting. --conversation-width defaults
   to 1100px, matching .layout's own hardcoded max-width above, so leaving
   this design setting unset changes nothing visually.

   Exec review finding 1 (specs/reviews/design-edit-mode-RF22-iter1.md):
   the FIRST version of this rule only widened .card-wide itself, but
   .layout's own max-width: 1100px (with 24px padding each side at
   desktop) still capped the actual available box at ~1052px -- every
   value above that in the registry's 600-1600 range was silently inert.
   .layout's own cap now grows to match whenever this setting exceeds the
   1100px default (max() never SHRINKS it below the original hardcode, so
   every OTHER screen sharing .layout is unaffected when this setting is
   at its default or below it). Still bounded by .app-body's own
   --content-max-width (1400px default) further out -- the same kind of
   nested-constraint interaction sidebarWidth/contentMaxWidth already have,
   not a new class of bug.

   Live-verification finding (2026-08-05, caught in browser, NOT by
   jsdom-based unit tests -- jsdom has no real CSS cascade engine, so a
   selector that structurally can't match anything still passes every
   getPropertyValue()-on-:root assertion): .layout is the PARENT of
   [data-screen="conversation"] in the real DOM (client/index.html --
   <main class="layout"> wraps ALL [data-screen] sections, including this
   one), never its descendant. The original
   `[data-screen="conversation"] .layout` selector therefore matched
   NOTHING in a real browser -- .layout's cap silently never grew,
   reproducing the exact bug the iter1 fix above believed it had already
   fixed. :has() selects the ANCESTOR based on a live (non-[hidden])
   descendant, which is exactly the relationship needed here. */
.layout:has(> [data-screen="conversation"]:not([hidden])) {
  max-width: max(1100px, var(--conversation-width, 1100px));
}
[data-screen="conversation"] .card-wide {
  max-width: var(--conversation-width, 1100px);
  margin-inline: auto;
}

@media (min-width: 768px) {
  .layout {
    grid-template-columns: 1fr 1fr;
    padding: 24px;
    gap: 20px;
  }

  .card-wide {
    grid-column: 1 / -1;
  }

  /* A screen with a single, narrow card (account, room) should sit
     centered on the page, not pinned to the left column of the 2-col
     grid. Explicitly excludes .card-wide (conversation/contacts/history,
     which already span the full grid via the rule above and must keep
     their full width -- exec review caught this rule initially clobbering
     them down to 520px) and multi-card screens (profile, server), which
     :only-of-type doesn't match anyway. */
  .screen > .card:only-of-type:not(.card-wide) {
    grid-column: 1 / -1;
    justify-self: center;
    width: 100%;
    max-width: 520px;
  }
}

/* ---------- Modals (Section H1) ---------- */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  padding: 16px;
}

.modal-overlay .modal-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
  max-width: 420px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.modal-overlay .modal-card h2 {
  margin: 0;
  font-size: 16px;
}

.modal-overlay .modal-card p {
  margin: 0;
  color: var(--muted);
  line-height: 1.5;
}

/* ---------- Cards ---------- */

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.card > h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.card h3 {
  margin: 4px 0 0;
  font-size: 14px;
  font-weight: 600;
}

.card p {
  margin: 0;
  color: var(--muted);
  font-size: 13.5px;
}

.subgroup {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}

/* ---------- Form controls ---------- */

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--muted);
}

.checkbox-field {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}

.checkbox-field input[type="checkbox"] {
  width: auto;
}

input[type="text"],
input[type="password"],
select {
  width: 100%;
  padding: 9px 12px;
  font: inherit;
  color: var(--text);
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: border-color 0.15s, box-shadow 0.15s;
}

input:focus-visible,
select:focus-visible,
button:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}

button {
  font: inherit;
  font-weight: 600;
  padding: 9px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
}

button:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

button:active {
  transform: translateY(1px);
}

button:disabled {
  opacity: 0.55;
  cursor: wait;
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-contrast);
}

.btn-icon {
  padding: 8px 10px;
  line-height: 1;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ---------- Status & identity ---------- */

.status {
  font-size: 13px;
  color: var(--muted);
  padding: 8px 12px;
  background: var(--code-bg);
  border-radius: var(--radius-sm);
  min-height: 1.5em;
  overflow-wrap: anywhere;
}

.status:empty {
  display: none;
}

.identity {
  font-size: 13px;
  color: var(--muted);
}

.identity code {
  display: block;
  margin-top: 2px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--text);
  background: var(--code-bg);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  overflow-wrap: anywhere;
}

.banner-warn {
  padding: 10px 12px;
  font-size: 13.5px;
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  color: var(--warn-text);
  border-radius: var(--radius-sm);
}

/* Section RF10: emoji rendering of the safety-number value -- a friendlier
   read-aloud/eyeball-compare surface than raw hex, shown alongside it
   (not instead of it). */
.safety-hint-emoji {
  font-size: 20px;
  letter-spacing: 4px;
  margin-top: 4px;
}

/* Section RF11: blinks 5 times the moment a NEW peer's safety code first
   appears -- both sides trigger this independently the moment their own
   identity-announce verifies (naturally close in time, since it's the
   same handshake), so a first-time user actually notices there's a code
   worth comparing instead of it just silently sitting in a banner. */
@keyframes safety-hint-attention {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50% { box-shadow: 0 0 0 3px var(--accent); }
}

.safety-hint-attention {
  animation: safety-hint-attention 0.5s ease-in-out 5;
}

/* Section RF12: transient "copied" tooltip positioned at whichever invite-
   copy button was actually clicked (#btn-copy-invite or the icon-only
   #btn-invite-from-chat in the fixed toolbar) -- fixed positioning + JS-set
   left/top (see showCopiedTooltip) since the two buttons live in
   completely different parts of the layout. */
.copied-tooltip {
  position: fixed;
  transform: translate(-50%, 0);
  background: var(--accent);
  color: var(--accent-contrast);
  padding: 4px 10px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  z-index: 200;
  transition: opacity 0.15s ease-in-out;
}

.copied-tooltip-visible {
  opacity: 1;
}

.secret-output {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  background: var(--code-bg);
  border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
}

.secret-output:not(:empty) {
  padding: 10px 12px;
}

.recovery-share-export-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.recovery-share-export-row:last-child {
  border-bottom: none;
}

.recovery-share-qr {
  display: flex;
  justify-content: center;
}

.recovery-share-qr svg {
  max-width: 160px;
  height: auto;
}

/* ---------- Chat ---------- */

.chat-log {
  min-height: 180px;
  max-height: 45vh;
  overflow-y: auto;
  overflow-wrap: anywhere;
  font-size: 14px;
  padding: 14px;
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Message bubbles -- UI redesign follow-up to SD1: appendChat/appendGroupChat
   build these elements instead of concatenating text, matching the agreed
   mockup's chat-bubble look. A trailing "\n" text node between rows keeps
   .textContent newline-delimited for existing per-line test assertions. */
.row-in,
.row-out {
  display: flex;
}

.row-out {
  justify-content: flex-end;
}

.bubble {
  max-width: 72%;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  border: 1px solid var(--border);
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.row-out .bubble {
  background: var(--accent-soft);
  border-color: var(--accent-soft);
}

.bubble-meta {
  display: block;
  margin-top: 3px;
  font-size: 10.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.bubble-sender {
  display: block;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 2px;
}

.imported-badge {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: 5px;
  margin-bottom: 3px;
  background: var(--warn-bg);
  color: var(--warn-text);
}

/* Section RF9: a queued-not-yet-sent outgoing message (no active
   connection when the user sent it) -- same badge shape as
   .imported-badge above, muted instead of warn-colored since this isn't
   an error, just "not delivered yet". */
.pending-badge {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--code-bg);
  color: var(--muted);
}

.chat-input-row {
  display: flex;
  gap: 8px;
}

.chat-input-row input {
  flex: 1;
}

@media (max-width: 480px) {
  .btn-row button {
    flex: 1;
  }

  .chat-input-row {
    flex-direction: row;
  }
}

/* ---------- Navigation: settings dropdown (Section H2) ---------- */

.settings-wrap {
  position: relative;
}

.settings-wrap .app-nav {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 101; /* above .modal-overlay's 100 -- a menu opened from within a
                   modal (e.g. future H4 forms) must still be reachable */
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 180px;
  padding: 6px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

.settings-wrap .nav-item {
  flex-direction: row;
  justify-content: flex-start;
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  text-align: left;
}

#guest-quick-actions {
  gap: 6px;
}

/* ---------- Navigation (bottom tab bar on mobile, top bar on desktop) ---------- */

.app-nav {
  position: sticky;
  top: 49px; /* right under the header */
  z-index: 9;
  display: flex;
  overflow-x: auto;
  gap: 2px;
  padding: 6px 10px;
  background: color-mix(in srgb, var(--card-bg) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 1 0 auto;
  min-width: 64px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}

.nav-item .nav-icon {
  font-size: 18px;
  line-height: 1;
}

.nav-item:hover {
  background: var(--accent-soft);
  color: var(--text);
}

.nav-item[aria-current="page"] {
  color: var(--accent);
  background: var(--accent-soft);
}

@media (min-width: 768px) {
  .app-nav {
    justify-content: center;
    gap: 6px;
  }

  .nav-item {
    flex: 0 1 auto;
    flex-direction: row;
    gap: 6px;
    padding: 8px 14px;
  }
}

/* ---------- Screens ---------- */

.screen {
  /* A screen is just a visibility boundary (toggled via the global [hidden]
     rule above, which wins via !important); its cards flow directly into
     .layout's grid as if the wrapper weren't there. */
  display: contents;
}

/* Section H4: the account screen (Створити/Увійти) renders as a modal over
   whatever's behind it (the chat) instead of a normal in-flow screen --
   [hidden]'s !important still wins over this when the router hides it. */
.modal-screen {
  display: flex;
  position: fixed;
  inset: 0;
  /* Below the H1 welcome modal (100): a fresh visitor with no identity yet
     lands on THIS screen by default (defaultRoute="account") while the
     welcome modal is also showing (exec review finding) -- the welcome
     modal must win so its confirm button stays reachable and its
     localStorage seen-flag can actually get set. Below the H2 settings-menu
     (101) too, so it stays reachable if opened from within this modal. */
  z-index: 99;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  padding: 16px;
}

/* User report (screenshot, 2026-07-31): suppresses the account modal's
   brief visual flash on a fresh auto-start visit (F4 invite-join, H5
   ephemeral quick-start) -- app.js toggles this class synchronously,
   well before the async work that actually navigates away resolves.
   display:none per user request (2026-07-31 follow-up) -- opacity alone
   left the element painted/hit-testable for a frame on some runs; this
   is a harder, unambiguous cut. Paired with `hidden` now being the
   DEFAULT on every [data-screen] section in index.html (closes the
   larger flash this was only a partial fix for -- see that markup
   change's own note) -- router.js unconditionally sets `.hidden` for
   every screen on its first render anyway, so neither default changes
   any JS-observable behavior, only paint timing before JS runs. */
body.account-modal-suppressed [data-screen="account"].modal-screen {
  display: none;
}

/* User follow-up (2026-07-31): fills the gap left by suppressing the
   account modal above -- a plain spinner + status text, toggled by
   app.js's own `.hidden` (not CSS-scoped to the body class, so its
   visibility is independently testable/inspectable, same convention as
   every other element in this codebase). */
.auto-start-loading {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--bg);
  color: var(--muted);
  font-size: 14px;
}

.auto-start-loading .spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: auto-start-spin 0.8s linear infinite;
}

@keyframes auto-start-spin {
  to {
    transform: rotate(360deg);
  }
}

.modal-screen > .card {
  position: relative;
  /* !important: the pre-existing `.screen > .card:only-of-type:not(.card-wide)`
     rule (max-width: 520px, higher specificity) otherwise wins on desktop
     viewports and widens this modal beyond its intended size (exec review
     finding) -- that rule targets the old display:contents grid-flow
     layout, not this fixed-overlay one, so overriding it here is correct. */
  max-width: 420px !important;
  width: 100%;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}

.modal-close {
  position: absolute;
  top: 8px;
  right: 8px;
  font-size: 20px;
  line-height: 1;
}

/* ---------- Video call (Section V, specs/ui/video-call.md;
   Section RF4: floating window + fixed toolbar redesign) ---------- */

/* Fixed row right under the global header (not inside the conversation
   card) -- title + "Ви зараз" on the left, connection status/error +
   invite icon on the right, shown only while route === "conversation"
   (app.js's onScreenChange toggles .hidden; `top` is set from JS to the
   header's real rendered height, see positionConversationToolbar). */
.conversation-toolbar {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px max(16px, calc((100% - var(--content-max-width)) / 2 + 16px));
  background: var(--card-bg);
  border-bottom: 1px solid var(--border);
}

.conversation-toolbar-left,
.conversation-toolbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

/* Section RF18 (specs/ui/design-edit-mode.md): swaps which side carries
   the title/nickname vs the status/invite -- same `order`-flip pattern
   as RF17's sidebar side, DOM order untouched. */
:root[data-toolbar-side="right"] .conversation-toolbar-left {
  order: 2;
}

:root[data-toolbar-side="right"] .conversation-toolbar-right {
  order: 1;
}

.conversation-toolbar-left h2 {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  white-space: nowrap;
}

.conversation-toolbar-right .status {
  margin: 0;
  padding: 4px 10px;
  min-height: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Call/camera/mic icons moved into the global header itself (Section
   RF6) -- a plain inline flex span among the header's other controls. */
.header-call-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.header-call-controls .btn-icon.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* The video call window floats over the ENTIRE page (not confined to the
   conversation card), draggable via .floating-video-handle and resizable
   via the native CSS `resize` handle -- both persisted to localStorage by
   app.js (spirit.floatingVideoRect). Position/size are set inline from JS;
   the values below are only the very first pre-JS paint's fallback. */
.floating-video {
  position: fixed;
  top: 96px;
  left: 16px;
  width: 320px;
  height: 240px;
  min-width: 160px;
  min-height: 120px;
  max-width: 90vw;
  max-height: 90vh;
  resize: both;
  overflow: hidden;
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  z-index: 95;
}

.floating-video-handle {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 22px;
  cursor: move;
  background: rgba(0, 0, 0, 0.35);
  z-index: 4;
}

/* Section RF21 (specs/ui/design-edit-mode.md, Stage 2): "docked" mode --
   app.js actually reparents #floating-video into the conversation card's
   content flow (it's not a descendant of the card otherwise, see app.js's
   own comment on this), so all this needs to do is undo the FLOATING
   presentation (fixed position, drag handle, native resize) and let it lay
   out like a normal block. app.js's applyVideoDockMode() clears the
   float-mode inline left/top/width/height while docked (exec review
   finding 1 -- an inline style otherwise wins over width:100%/aspect-ratio
   below, silently keeping whatever pixel size float mode last had) and
   restores them byte-for-byte on undock.

   position: relative (not static -- exec review finding 2): .video-tile-remote
   below is `position: absolute; bottom/right` -- its containing block is
   normally THIS element (.floating-video is position:fixed in float mode).
   `position: static` would make it establish NO containing block of its
   own, so the remote peer's thumbnail would resolve against the page's
   initial containing block instead and visibly escape the panel, floating
   over the rest of the page. `position: relative` keeps the exact same
   "this element clips/positions its children" contract as float mode,
   just without the fixed-to-viewport behavior. */
:root[data-video-mode="docked"] .floating-video {
  position: relative;
  top: auto;
  left: auto;
  width: 100%;
  height: auto;
  max-width: none;
  max-height: none;
  aspect-ratio: 4 / 3;
  resize: none;
  margin-bottom: 12px;
}
:root[data-video-mode="docked"] .floating-video-handle {
  display: none;
}

/* Self-view fills the ENTIRE floating window (per follow-up mockup
   feedback) -- there's usually no remote peer stream yet the moment the
   window appears, so a tiny self-view corner box was mostly wasted
   space. The remote peer's video becomes the small corner overlay
   instead, appearing once someone actually joins. */
.floating-video .video-tile-local {
  width: 100%;
  height: 100%;
  background: var(--code-bg);
  object-fit: cover;
}

.floating-video .video-tile-remote {
  position: absolute;
  bottom: 8px;
  right: 8px;
  width: 30%;
  max-width: 120px;
  min-width: 60px;
  aspect-ratio: 4 / 3;
  background: var(--code-bg);
  border: 2px solid var(--bg);
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35);
  object-fit: cover;
  z-index: 2;
}

.hint-text {
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
  font-style: italic;
}

/* Section B6 (specs/reviews/spirit-evaluation-triage.md): the device-linking
   SAS code both sides must visually compare -- large and monospaced so a
   digit transposition is easy to catch at a glance. */
.verification-code {
  font-family: monospace;
  font-size: 28px;
  font-weight: bold;
  letter-spacing: 4px;
  text-align: center;
  padding: 8px;
  margin: 8px 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
}

/* A button styled to read as an inline text link (account create/login
   toggle, de-emphasized quick-chat entry -- Section F2/F3, specs/ui/ephemeral-spirit-mode.md). */
.btn-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-style: italic;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
}

.btn-link:hover {
  color: var(--accent-hover);
}

/* ---------- Lists (contacts, history) ---------- */

.list-area {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Section RF13: one row per tunable setting -- label+input on top,
   description hint below, reset link at the end. */
.settings-row {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}

.settings-row:last-child {
  border-bottom: none;
}

.settings-row .hint-text {
  margin: 4px 0;
}

/* Section RF17: two-option toggle for "choice"-type design settings
   (e.g. sidebar side) -- same chip look as .sidebar-filters .chip
   (scoped there, not global, so restated here), but clickable. */
.choice-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.choice-toggle .chip {
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--code-bg);
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
}

.choice-toggle .chip-active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent);
}

/* Section RF19: reorderable list for "order"-type design settings (e.g.
   header controls order) -- one row per item, current position implicit
   in list order, ▲/▼ move buttons instead of drag-and-drop (see
   specs/ui/design-edit-mode.md for why). */
.order-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.order-list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--code-bg);
}

.order-list-item span {
  color: var(--text);
  font-size: 13px;
}

.settings-row input[type="color"] {
  width: 44px;
  height: 30px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
}

.list-row {
  padding: 10px 12px;
  font-size: 13.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
  cursor: pointer;
}

.list-row:hover {
  border-color: var(--accent);
}

/* #contacts-list rows specifically (UI redesign follow-up to SD1): a
   two-line Telegram-style row -- avatar left, name+trust-shield on top,
   proof badges + message button below -- instead of one flat monospace
   line. Scoped to #contacts-list so #groups-list/#import-pending-list
   (still plain rows) are unaffected. */
#contacts-list .list-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: inherit;
}

#contacts-list .c-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

#contacts-list .c-top {
  display: flex;
  align-items: center;
  gap: 6px;
}

#contacts-list .contact-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#contacts-list .c-sub {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

#contacts-list .c-sub .btn-icon {
  margin-left: auto;
  font-size: 12px;
  padding: 4px 10px;
}

/* ---------- Sidebar search + folder tree (UI redesign follow-up) ---------- */

.sidebar-search input {
  width: 100%;
  padding: 8px 12px;
  font: inherit;
  font-size: 13.5px;
  color: var(--text);
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: 999px;
}

.folder-tree {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-size: 13px;
}

.folder-tree-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 4px 4px 2px;
}

.folder-tree-label button {
  border: none;
  background: none;
  padding: 0 4px;
  font-size: 14px;
  line-height: 1;
  color: var(--muted);
}

.folder-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px;
  border-radius: var(--radius-sm);
  cursor: grab;
}

.folder-row:hover {
  background: var(--accent-soft);
}

.folder-row.drag-over {
  outline: 1.5px dashed var(--accent);
  background: var(--accent-soft);
}

.folder-row.selected {
  background: var(--accent-soft);
  font-weight: 600;
}

.folder-row .chev {
  width: 12px;
  font-size: 10px;
  color: var(--muted);
  flex: none;
  transition: transform 0.15s;
}

.folder-row.collapsed .chev {
  transform: rotate(-90deg);
}

.folder-row .folder-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.folder-row .folder-count {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.folder-row .folder-rename-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 1px 4px;
  background: var(--bg);
  color: inherit;
}

.folder-actions {
  display: flex;
  gap: 2px;
  flex: none;
  opacity: 0;
  transition: opacity 0.1s;
}

.folder-row:hover .folder-actions,
.folder-row:focus-within .folder-actions {
  opacity: 1;
}

.folder-action {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: var(--radius-sm);
}

.folder-action:hover {
  background: var(--accent-soft);
  color: var(--text);
}

.folder-action-delete.confirming {
  color: var(--danger, #d33);
  opacity: 1;
}

.folder-tree-label .folder-action.active {
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
}

.folder-children {
  margin-left: 16px;
  border-left: 1px dashed var(--border);
  padding-left: 2px;
}

.folder-children.collapsed {
  display: none;
}

/* ---------- Avatar shapes + trust shield (Section RF, specs/ui/redesign-foundation.md) ---------- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-right: 10px;
  color: var(--accent);
  background: var(--accent-soft);
  overflow: hidden;
  flex-shrink: 0;
  vertical-align: middle;
}

.avatar svg {
  width: 70%;
  height: 70%;
}

/* Звичайний постійний контакт (identity-announce verified) -- коло. */
.shape-user {
  border-radius: 50%;
}

/* Група (groups.js) -- заокруглений квадрат. */
.shape-group {
  border-radius: var(--radius-sm);
}

/* Ефемерна личность (сесія без vault key) -- хвилястий контур із легкою
   "дихаючою" анімацією; вимикається за prefers-reduced-motion. */
.shape-ghost {
  border-radius: 42% 58% 55% 45% / 45% 42% 58% 55%;
  animation: ghost-breathe 3.2s ease-in-out infinite;
}

@keyframes ghost-breathe {
  0%, 100% {
    border-radius: 42% 58% 55% 45% / 45% 42% 58% 55%;
  }
  50% {
    border-radius: 55% 45% 42% 58% / 58% 55% 45% 42%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shape-ghost {
    animation: none;
  }
}

.contact-name {
  vertical-align: middle;
}

.trust-shield {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin-left: 8px;
  vertical-align: middle;
  color: var(--muted);
}

.trust-shield-verified {
  color: var(--accent);
}

/* ---------- Linked-identity proof badges (Section E, specs/phase2c/identity-verification.md) ---------- */

.proof-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  font-size: 11.5px;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  background: var(--accent-soft);
  border-radius: 999px;
  color: var(--muted);
}

/* ---------- App footer (Section footer-1, 2026-07-31) ---------- */

.app-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 16px;
  padding: 6px max(16px, calc((100% - var(--content-max-width)) / 2 + 16px));
  background: var(--card-bg);
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--muted);
}

.footer-link {
  color: var(--muted);
  text-decoration: none;
}

.footer-link:hover,
.footer-link:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

.footer-version {
  color: var(--muted);
}

.footer-advanced-toggle {
  background: none;
  border: none;
  font: inherit;
  font-size: 12px;
  padding: 0;
  cursor: pointer;
}

/* Section FC3 (specs/ui/footer-customization.md): a user-authored custom
   HTML block rendered inline in the footer -- deliberately minimal, no
   assumed layout (the user's own markup decides what it looks like). */
.footer-custom-block {
  display: inline-flex;
  align-items: center;
}

/* The footer-settings panel's own textarea (Settings screen), distinct
   from the rendered .footer-custom-block above. */
#footer-settings-list textarea {
  width: 100%;
  min-height: 4em;
  font-family: monospace;
  font-size: 12px;
  resize: vertical;
}

/* Section SM3 (specs/ui/simplified-ephemeral-mode.md): a brief, dismissible
   notice when a restricted route redirects away -- same fixed-position
   fade-timeout pattern as .copied-tooltip, but a stable slot near the
   footer rather than anchored to a click target.

   Section RF23 (specs/ui/design-edit-mode.md, Stage 2): position is now
   controlled by :root[data-notice-position] (design setting). The base
   rule below keeps its ORIGINAL bottom-center left/bottom/transform
   values unconditionally, since that's the default position an unset
   setting must resolve to -- the 5 override rules below only apply for
   the other, non-default choices. */
.advanced-mode-notice {
  position: fixed;
  left: 50%;
  bottom: 40px;
  transform: translateX(-50%);
  z-index: 85;
  padding: 8px 16px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 13px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
:root[data-notice-position="top-left"] .advanced-mode-notice {
  left: 24px;
  top: 24px;
  bottom: auto;
  transform: none;
}
:root[data-notice-position="top-center"] .advanced-mode-notice {
  left: 50%;
  top: 24px;
  bottom: auto;
  transform: translateX(-50%);
}
:root[data-notice-position="top-right"] .advanced-mode-notice {
  left: auto;
  right: 24px;
  top: 24px;
  bottom: auto;
  transform: none;
}
:root[data-notice-position="bottom-left"] .advanced-mode-notice {
  left: 24px;
  bottom: 40px;
  transform: none;
}
:root[data-notice-position="bottom-right"] .advanced-mode-notice {
  left: auto;
  right: 24px;
  bottom: 40px;
  transform: none;
}
