/* ETEMA Dialogs: one stylesheet for every page. The Content Security Policy is
   default-src 'self', so nothing is styled inline: no style attributes, no style elements.

   UI stage 1: design tokens (colors, font stack, radii, shadows) are copied from the ETEMA team
   portal (etema-team-portal/app/static/style.css :root, lines 19-55) so Dialogs reads as the same
   product family. Every existing rule below keeps its own custom-property names (--bg, --card,
   --accent, --waiting, ...): only their values move to the portal palette, so the whole app
   re-themes from the :root block without renaming a single class JS or a test depends on. Status
   colors (waiting/in progress/resolved) and channel tints (TG/VK/Max) are new for Dialogs, mapped
   onto the portal's own tokens, not invented separately. */

@font-face {
  /* No local font file ships in this repository (no bundler, no CDN: a hard constraint of the
     CSP and the project's no-new-dependency rule, same choice the portal made). The variable
     Inter font is used only when it is already installed on the machine; otherwise the system
     fallback in --sans below applies. This rule makes no network request and never breaks
     offline use. */
  font-family: "Inter";
  src: local("Inter Variable"), local("Inter");
  font-weight: 100 900;
  font-display: swap;
}

:root {
  /* --- portal tokens, copied verbatim --- */
  --dark: #232524;
  --dark-2: #2c2e2d;
  --green: #4dc57c;
  --green-deep: #3eb56d;
  --green-ink: #1e7e4d;
  --green-wash: rgba(77, 197, 124, 0.2);
  --green-edge: #c3e7d1;
  --head: #152045;
  --head-2: #2a396c;
  --paper: #ffffff;
  --mist: #f8f9fc;
  --mist-2: #eef0f6;
  --line: #e8eaf0;
  --line-strong: #d5d9e4;
  --ink: #222222;
  --ink-2: #5c6062;
  --ink-3: #787c7e;
  --error: #b5301b;
  --error-wash: #fdf1ec;
  --amber-wash: #f1f3f9;
  --amber-line: #dfe3ee;

  --sans: Inter, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --shadow-sm: 0 1px 2px rgba(21, 32, 69, 0.05);
  --shadow-md: 0 6px 20px rgba(21, 32, 69, 0.07), 0 1px 3px rgba(21, 32, 69, 0.05);
  --shadow-lg: 0 20px 48px rgba(21, 32, 69, 0.12);
  --r: 18px;
  --r-sm: 10px;
  --pill: 50px;

  /* --- new for Dialogs: status and channel tints, derived from the tokens above, not invented --- */
  --st-wait: var(--error);
  --st-wait-bg: var(--error-wash);
  --st-work: var(--head-2);
  --st-work-bg: var(--amber-wash);
  --st-done: var(--green-ink);
  --st-done-bg: #e6f6ec;
  --ch-tg: #1d6fa5;
  --ch-tg-bg: #e7f2fa;
  --ch-vk: #2a5cad;
  --ch-vk-bg: #eaf0fb;
  --ch-max: #5b3fb0;
  --ch-max-bg: #f1ecfb;
  --st-degraded: #e9b44c; /* channel health "degraded" dot, product review round 2 */

  /* --- the app's own names: every rule in this file keeps using these, only the values changed --- */
  --bg: var(--mist);
  --card: var(--paper);
  --fill: var(--mist-2);
  --border: var(--line);
  --text: var(--ink);
  --muted: var(--ink-2);
  --accent: var(--green-ink);
  --accent-soft: var(--green-wash);
  /* --accent on --accent-soft alone is 4.33:1 (below the 4.5:1 AA text minimum, technical review
     round 2): this darker shade is for text that sits ON --accent-soft (the "Сотрудник" badge,
     .btn:hover), never for --accent's other uses (links, borders), which already pass on --card. */
  --accent-contrast: #1c7849;
  --waiting: var(--st-wait);
  --waiting-bg: var(--st-wait-bg);
  --progress: var(--st-work);
  --progress-bg: var(--st-work-bg);
  --done: var(--st-done);
  --done-bg: var(--st-done-bg);
  --danger: var(--error);
  --danger-bg: var(--error-wash);
  --in: var(--paper);
  --out: #eaf7ef;
  --radius: var(--r-sm);
  --topbar-h: 54px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.4;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

h1 {
  font-size: 20px;
  margin: 0 0 12px;
}

h1,
h2,
h3,
h4 {
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--head);
}

/* --green is only 2.19:1 on white and 2.08:1 on mist (technical review): below the 3:1 WCAG
   1.4.11 minimum for a non-text focus indicator. --green-ink (5.06:1 on white, 4.81:1 on mist)
   is the default everywhere; the dark .topbar overrides it back to --green below, which is
   7.05:1 there. */
:where(a, button, input, select, textarea, summary, .split):focus-visible {
  outline: 2px solid var(--green-ink);
  outline-offset: 2px;
}

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

.skip-link {
  position: absolute;
  left: 8px;
  top: -48px;
  z-index: 100;
  padding: 8px 12px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.skip-link:focus {
  top: 8px;
}

.muted {
  color: var(--muted);
  font-size: 13px;
}

[hidden] {
  display: none !important;
}

/* controls */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border: 1px solid var(--line-strong);
  border-radius: var(--pill);
  background: var(--paper);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.btn:hover {
  border-color: var(--green);
  color: var(--accent-contrast);
  background: var(--green-wash);
}

.btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.btn-primary {
  background: var(--green);
  border-color: var(--green-deep);
  color: var(--dark);
}

.btn-primary:hover {
  background: var(--green-deep);
  border-color: var(--green-deep);
  color: var(--dark);
}

.btn-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  font-weight: 600;
}

.btn-quiet:hover {
  background: var(--fill);
  border-color: transparent;
  color: var(--text);
}

.btn-small {
  padding: 5px 12px;
  font-size: 13px;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="date"],
select,
textarea {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--mist);
  color: var(--text);
  font: inherit;
}

/* the browser default placeholder colour measured 4.38:1 on --mist (technical review round 2):
   below the 4.5:1 AA text minimum. --muted is 6:1 there. */
input::placeholder,
textarea::placeholder {
  color: var(--muted);
  opacity: 1;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
input[type="date"]:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--green-ink);
  background: var(--paper);
  box-shadow: 0 0 0 3px var(--green-wash);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 14px;
  color: var(--muted);
}

/* UI stage 3 round 2 (product review): keeps a field's own label and its "?" on the same line
   although .field itself is a column flex container (each direct child its own row). */
.field-label {
  display: inline-flex;
  align-items: center;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.alert {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: var(--radius);
}

.alert-error {
  background: var(--danger-bg);
  color: var(--danger);
}

/* the frame of every signed-in page */

body.has-topbar {
  display: flex;
  flex-direction: column;
}

.topbar {
  display: flex;
  flex: none;
  align-items: center;
  gap: 16px;
  min-height: calc(var(--topbar-h) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) max(16px, env(safe-area-inset-right)) 0
    max(16px, env(safe-area-inset-left));
  background: var(--dark);
  border-bottom: 1px solid rgba(0, 0, 0, 0.25);
}

/* the header is dark: the shared focus ring is now --green-ink (technical review round 2), which
   is only 2.19:1 there, so .topbar keeps the brighter --green (7.05:1 on --dark) for every
   focusable element inside it. Every other dark-on-dark color below (nav, health badges, the
   logout button) needs its own override too. */
.topbar :focus-visible {
  outline-color: var(--green);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  color: #fff;
  text-decoration: none;
}

.brand:hover {
  text-decoration: none;
}

.brand-logo {
  height: 24px;
  width: auto;
  display: block;
  flex: none;
}

.brand-sub {
  color: rgba(255, 255, 255, 0.4);
  font: 500 9.5px/1.3 var(--mono);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  padding-left: 10px;
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}

/* the mobile hamburger menu (owner request, stage 1 round 2): .nav-panel wraps .topnav, .health
   and .who so all three can collapse together on a phone. On a wider screen it is a plain flex
   pass-through (flex:1, the same gap as .topbar) so the visible layout is unchanged: .health's
   own margin-left:auto still separates the nav links from the user name inside it. */
.nav-panel {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 16px;
  min-width: 0;
}

/* hidden on a wide screen; shown only inside the phone media query below. Progressive
   enhancement: without nav.js nothing ever adds .nav-collapsed to #nav-panel (see nav.js), so a
   visitor with JS off always sees the full nav, exactly as before this button existed. */
.nav-toggle {
  display: none;
  flex: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius);
  background: transparent;
  cursor: pointer;
}

.nav-toggle:hover {
  background: rgba(255, 255, 255, 0.12);
}

.nav-toggle-bar {
  display: block;
  width: 18px;
  height: 2px;
  margin: 3px 0;
  background: #fff;
  border-radius: 1px;
}

.topnav {
  display: flex;
  gap: 4px;
}

.topnav a {
  padding: 8px 10px;
  border-bottom: 2px solid transparent;
  color: rgba(255, 255, 255, 0.65);
  font-size: 13.5px;
  font-weight: 500;
  border-radius: 0;
}

.topnav a[aria-current="page"] {
  background: transparent;
  color: #fff;
  border-bottom-color: var(--green);
  font-weight: 600;
}

.topnav a:hover {
  color: #fff;
}

.who {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: rgba(255, 255, 255, 0.75);
}

.who form {
  margin: 0;
}

/* «Выйти» as a ghost pill (UI stage 2): .btn already gives the pill shape and padding, this only
   recolours it for the dark header. Replaces the .topbar .btn-quiet override this button used to
   be the only match for. */
.btn-ghost {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.16);
  color: #fff;
}

.btn-ghost:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.28);
  color: #fff;
}

/* owner-only links (UI stage 2): a thin divider after the links every signed-in person sees, and
   slightly dimmer text, so the owner console section reads as a distinct, secondary group. */
.topnav-sep {
  flex: none;
  width: 1px;
  height: 16px;
  align-self: center;
  margin: 0 2px;
  background: rgba(255, 255, 255, 0.16);
}

.topnav-own {
  color: rgba(255, 255, 255, 0.5);
}

.topnav-own:hover {
  color: #fff;
}

.topnav-own[aria-current="page"] {
  color: #fff;
}

.has-topbar .main {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

body:not(.has-topbar) .main {
  height: 100%;
}

.auth-mark {
  display: flex;
  justify-content: center;
  margin-bottom: 14px;
}

.auth-card {
  max-width: 380px;
  margin: 10vh auto 0;
  padding: 32px 28px 28px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow-md);
}

/* compact chips (product review round 2, refined rounds 3 and 4): a channel-kind mark (TG/TB/VK/
   Max, from views.channel_marks, the same short marks the queue's .chan avatars use) plus a
   coloured dot, never wrapping; a degraded or down channel also shows a short state word right in
   the mark (.health-mark, views.MARK_HEALTH_WORDS), so colour is never the only signal. Round 4:
   the owner's wider header (four more top-bar links than an operator's) still overflowed past
   1280px with five channels and two not ok, because a flex item's `min-width` defaults to `auto`,
   which floors it at its own content's width and defeats `flex-shrink` (.nav-panel above already
   has `min-width: 0` for the same reason); `.health` now has it too, plus `overflow-x: auto` so
   content that still does not fit scrolls inside .health's own box rather than bursting the page.
   The rest of the header (.topnav, .who, .brand) is left alone: it never shrinks below its own
   content, as before. The channel's real name and health in words always sit in `title` and a
   visually hidden span (views.channel_badges' full_label, the full word, never the short mark
   one), never a bare `aria-label`; the phone media query below reveals that full text inside the
   open mobile menu in place of the mark. */
.health {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  min-width: 0;
  overflow-x: auto;
}

.health-badge {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  font: 700 11px/1 var(--mono);
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.75);
  cursor: default;
}

.health-badge::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--green);
}

.health-degraded::before {
  background: var(--st-degraded);
}

.health-down {
  color: var(--danger);
  background: var(--danger-bg);
  border-color: var(--danger);
  font-weight: 600;
}

.health-down::before {
  background: var(--danger);
}

.conn {
  align-self: center;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--danger-bg);
  color: var(--danger);
  font-size: 12px;
}

/* two panes: the queue on the left, the conversation on the right */

.workspace {
  display: flex;
  flex: 1;
  gap: 12px;
  min-height: 0;
  padding: 12px;
}

.pane-queue,
.pane-thread {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.pane-queue {
  flex: none;
  width: var(--w-queue, 440px);
}

.pane-thread {
  flex: 1;
}

/* UI stage 2: the view bar above the workspace (density, text size, the side-panel toggle,
   "Сбросить вид"), and the drag handles between the panes. Desktop only: see the phone media
   query and the (min-width: 769px) block near .side below, which is where these preferences
   actually change anything. */
.viewbar {
  display: flex;
  flex: none;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--card);
  font-size: 12.5px;
  color: var(--muted);
}

.viewbar-label {
  font: 600 10px/1 var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted); /* product review LOW: --ink-3 measured 4.21:1, below AA; --muted is 6.35:1 */
}

.viewbar .seg {
  display: inline-flex;
  align-items: center; /* product review LOW: without it #fs-val sat high next to A-/A+ */
  gap: 2px;
  padding: 2px;
  border-radius: var(--pill);
  background: var(--fill);
}

.viewbar .seg button {
  padding: 4px 11px;
  border: 0;
  border-radius: var(--pill);
  background: none;
  color: var(--muted);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.viewbar .seg button[aria-pressed="true"] {
  background: var(--card);
  color: var(--head);
  box-shadow: var(--shadow-sm);
}

.tbtn {
  padding: 4px 11px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: var(--card);
  color: var(--muted);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.tbtn:hover {
  border-color: var(--green);
  color: var(--accent-contrast);
}

.tbtn[aria-pressed="true"] {
  border-color: var(--green-edge);
  background: var(--accent-soft);
  color: var(--accent-contrast);
}

.fs-val {
  display: inline-block;
  min-width: 20px;
  font: 600 11px/1 var(--mono);
  color: var(--head);
  text-align: center;
}

.viewbar-sp {
  flex: 1;
}

/* UI stage 3: the first-run tip strip, above the view bar (queue.html; shared with
   conversation.html). A few rotating tips cycled by app.js, dismissible for good; both the
   dismissal and the view bar's own "Подсказки" toggle below are read from localStorage and applied
   to <html> by prefs.js before the first paint (data-tip, data-hints), the same anti-flash
   convention UI stage 2 established for density and the side panel. */
.tipstrip {
  display: flex;
  flex: none;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--green-edge);
  background: var(--accent-soft);
  color: var(--head);
  font-size: 13px;
}

.tipstrip-next {
  margin-left: auto;
  border: 0;
  background: none;
  color: var(--accent-contrast);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.tipstrip-close {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

html[data-tip="off"] .tipstrip,
html[data-hints="off"] .tipstrip {
  display: none;
}

/* UI stage 3 round 2 (product review): each legend label gets its own "?" right after it,
   inline, rather than one unlabelled button per line (.rbar-help wraps a list of these). */
.help-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

/* UI stage 3: light "?" hints next to non-obvious things (channel health, request statuses, the
   "/" template shortcut, segment, "Наблюдение", ...). One accessible disclosure for all of them: a
   button with aria-expanded/aria-controls reveals a plain-text panel next to it (nav.js: click,
   Escape, click outside; never hover-only, so it works the same by touch). The button carries no
   text of its own: a CSS ::before glyph stands in for it and aria-label gives its accessible name,
   because several of these buttons sit right next to elements a plain-text HTML scan asserts the
   exact text of (the request row, the segment label) and must add nothing to it. */
.help {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
  margin-left: 6px;
}

.help-btn {
  width: 16px;
  height: 16px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: var(--card);
  color: var(--muted);
  font-size: 10px;
  line-height: 14px;
  cursor: pointer;
}

.help-btn::before {
  content: "?";
}

.help-btn:hover,
.help-btn:focus-visible,
.help-btn[aria-expanded="true"] {
  border-color: var(--green);
  color: var(--accent-contrast);
  background: var(--accent-soft);
}

/* Technical final check, HIGH: .help-panel is a descendant of .help-item (a status legend's own
   wrapper, see .help-item above) but set no white-space of its own, so it inherited that
   ancestor's nowrap although its own text is a multi-sentence explanation meant to wrap; it
   rendered as one long unbroken line spilling past its own 240px box (an explicit width, not sized
   to content, so the box itself never reported the overflow) rather than as readable text inside
   its own dark background. */
.help-panel {
  position: fixed;
  z-index: 20;
  width: 240px;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  background: var(--dark);
  color: #fff;
  font-size: 12.5px;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  box-shadow: var(--shadow-lg);
}

/* Technical final check, HIGH: every open panel is now position: fixed and placed by nav.js from
   its own button's rect (opening above the button when there is no room below, clamped to the
   viewport, re-placed on scroll or resize, closed if the button itself scrolls out of view), which
   escapes every ancestor's own overflow entirely, so it only ever needs to fit the viewport, never
   a clipping ancestor's own box; this replaces both the previous ancestor-walking clamp and the
   static .help-up class, which always opened a panel upward regardless of how much room there
   actually was (nav.js decides that dynamically now). */
html[data-hints="off"] .help {
  display: none;
}

/* a drag handle between two panes: a focusable role="separator" (pointer drag, arrow-key resize,
   double-click or Enter/Home resets; see app.js), hidden on a phone (the mobile media query
   below), where there is only ever one pane at a time. */
.split {
  flex: none;
  width: 8px;
  position: relative;
  background: transparent;
  cursor: col-resize;
  touch-action: none;
}

.split::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--border);
  transform: translateX(-50%);
}

.split:hover::before,
.split:focus-visible::before,
.split.dragging::before {
  width: 2px;
  background: var(--green);
}

/* product review, MEDIUM: the line alone was about 1.14:1 against its surroundings and gave no
   grip to hold on to; a visible bar fixes both. */
.split::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 34px;
  border-radius: var(--r-sm);
  background: var(--line-strong);
  transform: translate(-50%, -50%);
}

.split:hover::after,
.split:focus-visible::after,
.split.dragging::after {
  background: var(--green);
}

.placeholder,
.empty {
  margin: 0;
  padding: 24px 16px;
  color: var(--muted);
  text-align: center;
}

.tabs {
  display: flex;
  gap: 0;
  padding: 6px 8px 0;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}

.tab {
  padding: 8px 7px;
  font-size: 14px;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  white-space: nowrap;
}

.tab:hover {
  text-decoration: none;
  color: var(--text);
}

.tab[aria-current="page"] {
  border-bottom-color: var(--green);
  color: var(--head);
  font-weight: 600;
}

.count {
  display: inline-block;
  min-width: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--text);
  font: 700 11px/1.5 var(--mono);
  text-align: center;
}

.tab[aria-current="page"] .count {
  background: var(--green);
  color: var(--dark);
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px;
  border-bottom: 1px solid var(--border);
}

.filters select,
.filters input[type="search"] {
  flex: 1 1 100px;
  width: auto;
  min-width: 0;
  padding: 6px 8px;
  font-size: 13px;
}

.queue-scroll {
  flex: 1;
  overflow-y: auto;
}

.queue-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.queue-item {
  position: relative;
  display: flex;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  color: inherit;
  text-decoration: none;
}

.queue-item:hover {
  background: var(--fill);
  text-decoration: none;
}

.queue-item.active {
  background: var(--accent-soft);
}

.queue-item.active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--green);
}

.queue-item:focus-visible {
  outline-offset: -2px;
}

.chan {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  /* the default (kindless) avatar: white on --ink-3 measured 4.21:1 (technical review round 2,
     below the 4.5:1 AA text minimum); --ink-2 is 6.35:1 and is already the app's own muted-text
     token, so no new colour is introduced for this fallback case */
  background: var(--ink-2);
  color: #fff;
  font: 700 12px/1 var(--mono);
}

.chan-tg_bot {
  background: var(--ch-tg-bg);
  color: var(--ch-tg);
}

.chan-tg_business {
  background: var(--ch-tg);
  color: #fff;
}

.chan-vk {
  background: var(--ch-vk-bg);
  color: var(--ch-vk);
}

.chan-max {
  background: var(--ch-max-bg);
  color: var(--ch-max);
}

.qi-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.qi-head {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.qi-title {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.qi-time {
  flex: none;
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

.qi-time.overdue {
  color: var(--danger);
  font-weight: 600;
  font-family: var(--mono);
}

.qi-meta,
.qi-sub {
  font-size: 12px;
  color: var(--muted);
}

.qi-text {
  overflow: hidden;
  color: var(--muted);
  font-size: 0.87em; /* 13px of the default 15px base; scales with the view bar's text size */
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.chip::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.chip-waiting_us {
  background: var(--waiting-bg);
  color: var(--waiting);
}

.chip-in_progress {
  background: var(--progress-bg);
  color: var(--progress);
}

.chip-resolved {
  background: var(--done-bg);
  color: var(--done);
}

/* the conversation */

.pane-thread {
  flex-direction: row;
  position: relative; /* round 4: the containing block for the side panel's drawer mode below */
}

.thread {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.thread-back {
  display: none;
}

.thread-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}

.thread-title {
  margin: 0;
  overflow: hidden;
  font-size: 16px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.thread-sub {
  color: var(--muted);
  font-size: 13px;
}

.request-bar {
  display: flex;
  flex-direction: column;
}

.request-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--fill);
}

.rr-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* UI stage 3: explains the status chip and «Ответ не нужен» once for the request bar, not once
   per row (a plain-text scan of .request-row asserts its exact text in several tests, so nothing
   new can be added inside it; see .help below). */
.rbar-help {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 4px 14px 0;
  font-size: 12px;
  color: var(--muted);
}

.banner {
  margin: 0;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--progress-bg);
  color: var(--progress);
  font-size: 14px;
}

/* a banner that actually closes the composer («отправка невозможна», the closed Business window,
   ...) gets --error-wash; the down-channel warning, which never blocks, keeps the plain .banner
   colour above (product review round 2, views.send_state's per-banner `blocking` flag). */
.banner-blocking {
  background: var(--danger-bg);
  color: var(--danger);
}

.messages {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 12px 16px;
  overflow-y: auto;
}

.load-older {
  align-self: center;
  margin-bottom: 8px;
}

.day {
  align-self: center;
  margin: 10px 0 4px;
  padding: 2px 12px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--muted);
  font-size: 12px;
}

.req-sep {
  margin: 10px -16px 6px;
  padding: 6px 16px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  background: var(--fill);
  font-size: 13px;
}

.msg {
  align-self: flex-start;
  max-width: min(78%, 720px);
  margin: 3px 0;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 14px 14px 14px 4px;
  background: var(--in);
  overflow-wrap: anywhere;
}

.msg-out {
  align-self: flex-end;
  background: var(--out);
  border-color: var(--green-edge);
  border-radius: 14px 14px 4px 14px;
}

.author-auto {
  border-style: dashed;
}

.author-external_staff_unknown {
  border-color: var(--progress);
  background: var(--progress-bg);
}

.author-staff_shared {
  border-left: 3px solid var(--accent);
}

.msg.flash {
  outline: 3px solid var(--green);
  outline-offset: 2px;
}

.req-sep.flash {
  outline: 3px solid var(--green);
}

.msg-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 3px;
  font-size: 12px;
  color: var(--muted);
}

.badge {
  display: inline-block;
  padding: 0 7px;
  border-radius: 999px;
  background: var(--mist-2);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 600;
}

/* product review LOW: the stage-2 mockup's uppercase mono badge is for an author-type badge
   (buyer/organizer/staff, inside a message's own header) only; .out-head and .draft-head reuse
   the bare .badge class for a person's name and a card header, which must stay in normal case. */
.msg-head .badge {
  font: 700 11px/1.5 var(--mono);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.badge-buyer,
.badge-organizer,
.badge-organizer_staff {
  background: var(--done-bg);
  color: var(--done);
}

.badge-staff,
.badge-staff_shared {
  background: var(--accent-soft);
  color: var(--accent-contrast);
}

.badge-external_staff_unknown {
  background: var(--progress-bg);
  color: var(--progress);
}

.msg-who {
  font-weight: 600;
  color: var(--text);
}

.msg-flag {
  font-style: italic;
}

.msg-text {
  margin: 0;
  white-space: pre-wrap;
}

.msg-deleted .msg-text {
  text-decoration: line-through;
  color: var(--muted);
}

.quote-block {
  display: block;
  margin: 2px 0 4px;
  padding: 3px 8px;
  border-left: 3px solid var(--green);
  background: rgba(255, 255, 255, 0.6);
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
  white-space: pre-wrap;
}

.att-image img {
  display: block;
  max-width: 260px;
  max-height: 260px;
  margin: 2px 0 4px;
  border-radius: 10px;
}

.att-file {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 2px 0 4px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
  color: var(--text);
  font-size: 13px;
}

.att-missing {
  color: var(--muted);
}

.att-note {
  font-style: italic;
}

.att-page-text {
  margin: 2px 0 4px;
  white-space: pre-wrap;
}

/* the VK repost card: a plain link attachment (.att-file.att-link) is already the bordered,
   radius'd box .att-file gives every attachment; a repost also carries page_text (its quoted
   wall post), and the two together should read as one card, not two stacked boxes, so a link
   followed by page_text squares its own bottom and the page_text becomes a shaded continuation
   with the matching bottom radius. A plain link with no page_text is unaffected. */
.att-link:has(+ .att-page-text) {
  border-radius: 10px 10px 0 0;
  border-bottom: 0;
}

.att-link + .att-page-text {
  margin: -1px 0 4px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-top: 0;
  border-radius: 0 0 10px 10px;
  background: var(--fill);
}

.quote-btn {
  margin-top: 2px;
  color: var(--muted);
}

/* Technical review: a flex item of .thread (see .messages above) with no overflow rule of its own
   has a content-based automatic minimum height, so it never actually shrinks when the pane runs
   out of room; min-height: 0 plus overflow-y: auto (the same pair .messages already uses) lets it
   shrink and scroll instead of pushing the composer below .pane-thread's own bottom edge. */
.outgoing {
  padding: 0 16px;
  min-height: 0;
  overflow-y: auto;
}

.out-row {
  margin: 4px 0;
  padding: 6px 12px;
  border: 1px dashed var(--border);
  border-radius: 12px;
  background: #fff;
}

.out-uncertain,
.out-failed,
.out-blocked {
  border-color: var(--danger);
  background: var(--danger-bg);
}

.out-row.out-superseded {
  border-color: var(--border);
  background: var(--fill);
}

.out-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}

.out-state {
  font-weight: 600;
}

.out-text {
  margin: 4px 0 0;
  color: var(--muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Claude's drafts (spec 9.3): a card above the composer */

/* Technical review: the same min-height/overflow-y pair as .outgoing above, so a busy drafts area
   shrinks and scrolls under the same squeeze instead of pushing the composer out of the pane. */
.drafts {
  padding: 0 16px;
  min-height: 0;
  overflow-y: auto;
}

.draft-card {
  margin: 4px 0;
  padding: 8px 12px;
  border: 1px solid var(--green);
  border-radius: 12px;
  background: var(--accent-soft);
}

.draft-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}

.draft-text {
  margin: 6px 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.draft-quote {
  margin: 0 0 6px;
  font-size: 13px;
}

.draft-code {
  margin: 6px 0;
}

.draft-code strong {
  font-family: var(--mono);
  font-size: 1.3em;
  letter-spacing: 0.15em;
}

/* Product low, technical final check: sticky at the bottom of .drafts's own scroll box (the same
   box .outgoing/.drafts's own overflow-y: auto, above, can now shrink and scroll), so a busy card
   never hides «Отправить» behind a scroll of its own; the matching background keeps the card's own
   text from showing through underneath once it scrolls past. */
.draft-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  position: sticky;
  bottom: 0;
  background: var(--accent-soft);
}

/* the composer */

.composer {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 14px max(12px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
}

.quote-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-left: 3px solid var(--green);
  background: var(--accent-soft);
  font-size: 13px;
}

.quote-body {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.attachments li {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--fill);
  font-size: 13px;
}

.composer-field {
  position: relative;
}

textarea {
  resize: vertical;
  min-height: 56px;
}

textarea:disabled {
  background: var(--fill);
  cursor: not-allowed;
}

.picker {
  position: absolute;
  right: 0;
  bottom: 100%;
  left: 0;
  z-index: 5;
  max-height: 260px;
  margin: 0 0 6px;
  padding: 4px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--card);
  box-shadow: var(--shadow-lg);
  list-style: none;
}

.picker-item {
  display: flex;
  flex-direction: column;
  padding: 6px 10px;
  border-radius: 8px;
  cursor: pointer;
}

.picker-item.selected {
  background: var(--accent-soft);
}

.picker-empty {
  padding: 8px 10px;
  color: var(--muted);
}

.preview {
  padding: 6px 10px;
  border-radius: var(--radius);
  background: var(--done-bg);
  font-size: 13px;
  white-space: pre-wrap;
}

.composer-error {
  margin: 0;
  padding: 6px 10px;
  border-radius: var(--radius);
  background: var(--danger-bg);
  color: var(--danger);
  font-size: 13px;
}

.composer-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.composer-tools .btn-primary {
  margin-left: auto;
}

/* the side panel */

.side {
  flex: none;
  width: var(--w-side, 280px);
  padding: 10px 14px;
  border-left: 1px solid var(--border);
  overflow-y: auto;
}

/* UI stage 2: the view bar's density, text-size and side-panel preferences. Guarded to a screen
   wide enough for the three-pane layout, so a preference set on a wide window never reaches the
   phone's single-pane view, which stays exactly as it was (see the phone media query, which also
   hides .viewbar and .split outright). --app-fs, data-density and data-side are read from
   localStorage and applied to <html> by prefs.js before the first paint, and kept in sync by
   app.js after a view-bar click. */
@media (min-width: 769px) {
  .workspace {
    font-size: var(--app-fs, 15px);
  }

  html[data-density="compact"] .queue-item {
    padding-top: 5px;
    padding-bottom: 5px;
  }

  html[data-density="compact"] .qi-text {
    display: none;
  }

  html[data-density="comfortable"] .queue-item {
    padding-top: 14px;
    padding-bottom: 14px;
  }

  html[data-side="off"] .side,
  html[data-side="off"] .split-side {
    display: none;
  }

  /* product and technical review, HIGH: this is not a fallback the JS falls back on, it is what
     makes .pane-thread's own overflow (scrollWidth minus clientWidth) correctly report when there
     is not enough room: without a floor here, .thread would just flex-shrink to whatever is left,
     even to a sliver, and .pane-thread would never overflow at all, so there would be nothing for
     app.js's own real protection (applyPaneWidthClamped, reclampAll, both in app.js) to measure. A
     regression once measured .thread's OWN reported width here instead of .pane-thread's overflow:
     that width reads as exactly this 480px number always, CSS-forced, so the deficit always came
     out zero and .pane-thread's own overflow: hidden silently clipped the side panel rather than
     the queue or side pane giving way. A bare (unguarded) min-width would be wrong on a 320 to
     375px phone; see the phone media query, which hides .viewbar and .split outright and never
     reaches this block. */
  .thread {
    min-width: 480px;
  }

  /* round 4 (product and technical re-check, HIGH: a regression the round-3 fix itself
     introduced): an explicit "Панель «О разговоре»" open that still has no room to dock (see
     reclampAll in app.js) used to apply the panel's normal docked width regardless, leaving part
     of it hidden behind .pane-thread's own overflow: hidden (measured 146px hidden at 900px,
     right edge 1033 vs .pane-thread's own right edge 888) and to silently re-collapse it on the
     next resize even though the stored preference still said open. Shown as an overlay drawer
     instead: taken out of the flex row entirely (so it no longer competes with .thread for room,
     which is what actually frees enough space for .thread at these widths), floated over the
     conversation's own right edge, at its own normal width (.side's existing width: var(--w-side)
     rule, untouched here) rather than shrunk to fit. */
  html[data-side="drawer"] .side {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 5;
    background: var(--card);
    box-shadow: var(--shadow-lg);
  }

  html[data-side="drawer"] .split-side {
    display: none; /* meaningless while the panel is not part of the flex layout */
  }

  html[data-side="drawer"] .side-close {
    display: inline-block;
  }
}

.side-close {
  display: none; /* shown only in drawer mode, see the (min-width: 769px) block above */
  margin-bottom: 8px;
}

.side-details > summary {
  margin-bottom: 8px;
  font-weight: 600;
  color: var(--head);
  cursor: pointer;
}

.side-list {
  margin: 0 0 12px;
}

.side-term {
  margin-top: 8px;
  color: var(--muted);
  font-size: 12px;
}

.side-value {
  margin: 0;
}

/* the conflict dialog */

.modal {
  width: calc(100% - 32px);
  max-width: 560px;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--card);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}

.modal::backdrop {
  background: rgba(21, 32, 69, 0.45);
}

.modal h2 {
  margin: 0;
  font-size: 18px;
}

.conflict-messages {
  display: flex;
  flex-direction: column;
  max-height: 40vh;
  overflow-y: auto;
}

.conflict-messages .msg {
  max-width: 100%;
}

.conflict-messages .quote-btn {
  display: none;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* UI stage 3: the "?" shortcuts sheet, the same <dialog class="modal"> as the conflict dialog
   above. */
.keys-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.keys-table td {
  padding: 5px 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.keys-table td:last-child {
  padding-left: 12px;
  white-space: normal;
  color: var(--muted);
}

kbd {
  display: inline-block;
  min-width: 1.4em;
  padding: 2px 5px;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--card);
  color: var(--head);
  font: 600 11px/1 var(--mono);
  text-align: center;
}

/* search and the KB templates: a scrolling column of its own inside the frame */

.page {
  flex: 1;
  width: 100%;
  max-width: 960px;
  margin: 0 auto;
  padding: 16px;
  overflow-y: auto;
}

.search-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin-bottom: 12px;
}

.search-form .field {
  flex: 1 1 150px;
}

.search-form .field-wide {
  flex: 2 1 260px;
}

.hint {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--fill);
  color: var(--muted);
}

.hits,
.templates {
  margin: 0;
  padding: 0;
  list-style: none;
}

.hit {
  display: flex;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: inherit;
  text-decoration: none;
}

.hits li + li {
  margin-top: 8px;
}

.hit:hover {
  background: var(--accent-soft);
  text-decoration: none;
}

.hit-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.hit-head {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.hit-title {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hit-time,
.hit-meta {
  color: var(--muted);
  font-size: 12px;
}

.hit-snippet {
  overflow-wrap: anywhere;
}

mark {
  padding: 0 2px;
  border-radius: 3px;
  background: rgba(77, 197, 124, 0.35);
  color: inherit;
}

.kb-state {
  margin-bottom: 12px;
}

.kb-state p {
  margin: 0 0 4px;
}

.tpl {
  margin-bottom: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
}

.tpl-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.tpl-title {
  flex: 1;
  margin: 0;
  font-size: 16px;
}

.tpl-body {
  margin: 8px 0;
  color: var(--muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* round 5 (technical review), pre-existing from UI stage 1: the owner's header (.topnav's base
   three links plus five owner-only ones, .health's chips, .who's name and «Выйти») is a plain
   flex row at any width the phone block below does not reach, and measured 1036px wide at 900
   and 1024px, well short of the phone block's own 768px, which is tuned for the three-pane
   workspace's (min-width: 769px)/(max-width: 768px) pair above and must not move. A second,
   wider, header-only breakpoint reuses the same hamburger mechanism (nav.js, whose own init
   check now matches this 1099px) without touching the workspace, the view bar or the pane
   splitters, which stay governed by their own 769px breakpoint exactly as before; desktop widths
   of 1100px and up are unaffected, keeping the current always-expanded nav. */
@media (max-width: 1099px) {
  .topbar {
    flex-wrap: wrap;
    gap: 0 8px;
    min-height: 0;
    padding-bottom: 0;
  }

  .brand {
    padding: 12px 0;
  }

  .nav-toggle {
    display: inline-flex;
    width: 44px;
    height: 44px;
    margin-left: auto;
  }

  .nav-panel {
    flex: none;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 100%;
  }

  .nav-panel.nav-collapsed {
    display: none;
  }

  .topnav {
    width: 100%;
    flex-wrap: wrap;
  }

  .health {
    width: 100%;
    margin-left: 0;
    padding: 6px 0;
    overflow-x: auto;
  }

  .who {
    justify-content: center;
    padding: 10px 0;
    border-top: 1px solid rgba(255, 255, 255, 0.14);
  }
}

/* a phone (spec 6.1): one pane at a time, the queue or the conversation of the address, the
   composer at the bottom of the screen inside the safe areas, touch targets of 44 px, form
   controls at 16 px so that iOS does not zoom into them. This block stays last: it overrides
   the rules above at equal weight. */

@media (max-width: 768px) {
  body {
    height: 100vh; /* where dvh is unknown */
    height: 100dvh;
    -webkit-text-size-adjust: 100%;
  }

  /* UI stage 3 round 2 (product review): a real 44px touch target on a phone without enlarging
     the glyph itself, moved onto ::before (a small circle centred in a larger, invisible box)
     rather than growing .help-btn's own visible size, which the desktop rule above sets. */
  .help-btn {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
  }

  .help-btn::before {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 1px solid var(--line-strong);
    border-radius: 50%;
    background: var(--card);
    color: var(--muted);
    font-size: 10px;
    line-height: 14px;
  }

  /* the tip strip's rotating tips assume a keyboard (j/k, the drag handles, A-/A+), so it is
     simply hidden on a phone rather than shown one line at a time. */
  .tipstrip {
    display: none;
  }

  .topbar {
    flex-wrap: wrap;
    gap: 0 8px;
    min-height: 0;
    padding-bottom: 0;
  }

  .brand {
    order: 1;
    padding: 12px 0;
  }

  /* the hamburger button (owner request, stage 1 round 2): the nav links, health chips and the
     user name/«Выйти» collapse into #nav-panel behind it; see nav.js for the toggle and the
     collapsed-by-default init, and the .nav-panel[.nav-collapsed] rule below for the actual hide. */
  .nav-toggle {
    order: 2;
    display: inline-flex;
    width: 44px;
    height: 44px;
    margin-left: auto;
  }

  .conn {
    order: 3;
  }

  .nav-panel {
    order: 4;
    flex: none;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 100%;
  }

  .nav-panel.nav-collapsed {
    display: none;
  }

  .topnav {
    width: 100%;
  }

  .topnav a {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    min-height: 44px;
  }

  .health {
    width: 100%;
    margin-left: 0;
    padding: 6px 0;
    overflow-x: auto;
  }

  /* inside the open menu there is room to name the channel, not just its kind (product review
     round 3: two VK channels both read "VK" in the compact mark); this undoes .sr-only's own rule
     for a health badge's full label and hides the short mark it replaces, so the panel shows one
     text per channel, not both. */
  .nav-panel .health {
    flex-wrap: wrap;
  }

  .nav-panel .health-badge .sr-only {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    overflow: visible;
    clip: auto;
    white-space: normal;
  }

  .nav-panel .health-badge .health-mark {
    display: none;
  }

  .who {
    justify-content: center;
    padding: 10px 0;
    border-top: 1px solid rgba(255, 255, 255, 0.14);
  }

  /* product review round 3: a phone conversation used to hide the toggle along with the panel to
     reclaim height, leaving no way to reach «Меню» or «Выйти» from a conversation; the toggle now
     stays (it fits the existing header row, no extra height) and the panel still starts collapsed
     like on every other page (nav.js, .nav-panel.nav-collapsed above). */
  .composer-tools .muted {
    display: none;
  }

  /* UI stage 2: the view bar and the pane splitters are a desktop feature (see the
     (min-width: 769px) block near .side); on a phone there is only ever one pane, so neither
     ever shows, whatever a wider window's view preferences say. */
  .viewbar,
  .split {
    display: none;
  }

  .workspace {
    gap: 0;
    padding: 0;
  }

  .pane-queue,
  .pane-thread {
    flex: 1;
    width: auto;
    border-width: 1px 0 0;
    border-radius: 0;
  }

  body:not([data-chat-id]) .pane-thread {
    display: none;
  }

  body[data-chat-id] .pane-queue {
    display: none;
  }

  .pane-thread {
    flex-direction: column;
  }

  .thread-back {
    display: flex;
    flex: none;
    align-items: center;
    order: -2;
    min-height: 44px;
    padding: 0 max(14px, env(safe-area-inset-left));
    border-bottom: 1px solid var(--border);
    font-weight: 600;
  }

  .thread-back::before {
    content: "\2039";
    margin-right: 8px;
    font-size: 24px;
  }

  .side {
    flex: none;
    order: -1;
    width: auto;
    max-height: 45vh;
    padding: 0 14px;
    border-bottom: 1px solid var(--border);
    border-left: 0;
  }

  .side-details > summary {
    display: flex;
    align-items: center;
    min-height: 44px;
    margin-bottom: 0;
  }

  .messages {
    padding: 8px 10px;
  }

  .req-sep {
    margin-right: -10px;
    margin-left: -10px;
  }

  .msg {
    max-width: 92%;
  }

  .composer {
    padding-right: max(14px, env(safe-area-inset-right));
    padding-left: max(14px, env(safe-area-inset-left));
  }

  .outgoing,
  .drafts {
    padding: 0 10px;
  }

  .btn,
  .btn-small,
  .tab,
  .picker-item {
    min-height: 44px;
  }

  .tab {
    padding-top: 12px;
  }

  .quote-btn {
    margin-top: 0;
  }

  .filters select,
  .filters input[type="search"],
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="date"],
  select,
  textarea {
    min-height: 44px;
    font-size: 16px;
  }

  .auth-card {
    margin: 5vh 16px 0;
  }

  .page {
    padding: 12px max(12px, env(safe-area-inset-right)) 12px max(12px, env(safe-area-inset-left));
  }
}

.tpl-more {
  margin: 0 0 8px;
}

.tpl-more > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  color: var(--accent);
  cursor: pointer;
}

.tpl-full {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius);
  background: var(--fill);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The owner's pages: settings, users, audit (Sprint 12a). Placed after the phone block, so the
   phone rules at the end are the only ones that follow the base rules. */
.admin .card {
  margin: 0 0 16px;
  padding: 12px 16px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-sm);
}

/* UI stage 3 round 2 (product review): the heading and its "?" on the same line, although <h1> is
   block-level and would otherwise force the hint onto a line of its own; the hint stays a sibling
   of the <h1>, never nested inside it, so a test reading the heading's own text sees only it. */
.obs-title {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* UI stage 3: the observation page's KPI tiles, above its table (the same figures the paragraphs
   below already state in full; the tiles are a glance, not a replacement). */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px;
  margin: 0 0 16px;
  padding: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.kpi {
  padding: 12px 14px;
  background: var(--card);
}

.kpi-n {
  font-size: 22px;
  font-weight: 700;
  color: var(--head);
  line-height: 1.2;
}

.kpi-l {
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.35;
}

/* UI stage 3 round 2 (product review): "сейчас ждут нас" turns warn-coloured once above zero. */
.kpi-n-warn {
  color: var(--danger);
}

/* "получили первый ответ" gets a CSS-only share bar: the width is one of the classes below,
   computed in Python (observation_views.summary_view), never an inline style or Jinja math. */
.kpi-bar {
  margin-top: 6px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  overflow: hidden;
}

.kpi-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
}

.bar-0 { width: 0%; }
.bar-10 { width: 10%; }
.bar-20 { width: 20%; }
.bar-30 { width: 30%; }
.bar-40 { width: 40%; }
.bar-50 { width: 50%; }
.bar-60 { width: 60%; }
.bar-70 { width: 70%; }
.bar-80 { width: 80%; }
.bar-90 { width: 90%; }
.bar-100 { width: 100%; }

.admin h2,
.admin h3 {
  margin: 0 0 6px;
}

.anchors {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: 0 0 16px;
}

.alert-ok {
  background: var(--done-bg);
  color: var(--done);
}

.admin .field-wide textarea,
.admin .field-wide input[type="text"] {
  width: 100%;
}

.topics label {
  display: block;
  margin-top: 8px;
}

.table {
  width: 100%;
  border-collapse: collapse;
  overflow-wrap: anywhere;
}

.table th,
.table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

.table th {
  background: var(--fill);
  font: 600 11px/1.3 var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.channel-form,
.identity-form,
.admin-form {
  margin: 0 0 16px;
  padding: 12px 0 0;
  border-top: 1px solid var(--border);
}

.secret-once {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 2px solid var(--green);
  border-radius: var(--radius);
  background: var(--accent-soft);
}

.secret-once code {
  display: block;
  margin: 6px 0;
  font-size: 15px;
  overflow-wrap: anywhere;
  user-select: all;
}

.inline-form {
  display: inline;
}

/* The reports page (Sprint 12b): the weeks list, wide tables scrolling on their own on a phone. */
.weeks ul {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
}

.week-current {
  font-weight: 600;
}

.table-scroll {
  overflow-x: auto;
}

.reports .table {
  overflow-wrap: normal;
}

.reports .table th:first-child {
  min-width: 9em;
}

.reports caption {
  padding: 8px 0 4px;
  font-weight: 600;
  text-align: left;
}

.reports .figure {
  margin: 4px 0;
  font-size: 18px;
}

.target-line progress {
  width: 100%;
  max-width: 320px;
  vertical-align: middle;
}

@media (max-width: 768px) {
  .topnav {
    flex-wrap: wrap;
  }

  .topnav a {
    flex: 1 1 auto;
  }
}
