/* Work Radar CRM.
 *
 * One stylesheet, organised in layers: tokens, reset, layout, components,
 * views. No framework and no build step — a CRM that needs npm to change a
 * label is a CRM that does not get changed.
 *
 * Dark is the default because this is a tool someone has open all day; light is
 * a full theme rather than an afterthought, and both are one variable swap.
 *
 * Two rules run through the whole file. The price of a job is the most
 * important text on any screen and is styled like it — its own type scale, its
 * own colour, never a bare dash. And nothing ever blanks: a screen waiting for
 * data dims by a few percent and keeps its shape, because a layout that
 * collapses and reflows reads as slow however fast the server was.
 */

/* ── the face ───────────────────────────────────────────────────────────── */

/* JetBrains Mono, self-hosted with its full Cyrillic set. It is the voice of
   the machine half of this interface: every figure, label, status and age is
   set in it, while human text — postings, messages, drafts — stays in the
   system sans. The split is the design: the operator can tell at a glance
   whether they are reading the world or the instrument panel. */
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/JetBrainsMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/JetBrainsMono-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ── tokens ─────────────────────────────────────────────────────────────── */

/* Graphite with a breath of green in every neutral — the tint that makes a
   dark UI read as an instrument rather than a void — and one cyan accent for
   everything interactive. Cyan, not green: green belongs to money alone (the
   rule the operator screens live by), and an accent that shared its hue would
   cost that glance its meaning. No neon anywhere; the terminal feeling comes
   from type and structure, not glow. */
:root {
  --bg: #0b0e0c;
  --bg-raised: #101512;
  --bg-sunken: #070a08;
  --bg-hover: #161d19;
  --border: #1e2822;
  --border-strong: #2d3b33;

  --text: #e7eee9;
  --text-dim: #93a49a;
  --text-faint: #5d6f6499;

  --accent: #45b8c9;
  --accent-dim: #0e2f35;
  --accent-soft: #45b8c91a;
  --ok: #35c07a;
  --warn: #dca23f;
  --danger: #e0604f;
  --auto: #9d8cf0;
  --money: #3fd68f;

  --radius: 10px;
  --radius-sm: 6px;
  --radius-lg: 14px;
  --gap: 12px;
  --pad: 18px;

  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;

  --shadow: 0 8px 30px rgb(0 0 0 / 0.4);
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.3);
  --sidebar-w: 220px;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

:root[data-theme="light"] {
  --bg: #f5f8f6;
  --bg-raised: #ffffff;
  --bg-sunken: #eaefec;
  --bg-hover: #e6ede9;
  --border: #dde5e0;
  --border-strong: #c2cfc8;

  --text: #131a16;
  --text-dim: #58685f;
  --text-faint: #86968c;

  --accent: #0d7d8c;
  --accent-dim: #d9eef1;
  --accent-soft: #0d7d8c14;
  --ok: #17864f;
  --warn: #90630c;
  --danger: #c8352f;
  --auto: #6a52c9;
  --money: #0f7a4a;

  --shadow: 0 8px 26px rgb(21 42 32 / 0.10);
  --shadow-sm: 0 1px 2px rgb(21 42 32 / 0.06);
}

::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); }

/* ── reset ──────────────────────────────────────────────────────────────── */

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

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Focus that is actually visible. A keyboard-driven interface whose focus ring
   was removed for tidiness is a keyboard-driven interface nobody can use. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

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

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
::-webkit-scrollbar-track { background: transparent; }

/* ── layout ─────────────────────────────────────────────────────────────── */

#app { min-height: 100%; }

.boot { display: grid; place-items: center; height: 100vh; }
.boot-pulse {
  width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
}

.sidebar {
  background: var(--bg-sunken);
  border-right: 1px solid var(--border);
  padding: var(--pad) 10px;
  display: flex; flex-direction: column; gap: 2px;
  position: sticky; top: 0; height: 100vh;
}

.brand {
  display: flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-weight: 700;
  letter-spacing: 0.01em; font-size: 13px;
  padding: 4px 10px 18px;
}
.brand .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent);
  transition: background 200ms var(--ease);
  flex: none;
  position: relative;
}
.brand .dot[data-state="paused"] {
  background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent);
}
/* The radar sweep, on the radar's own dot. One expanding ring every few
   seconds — the product's name drawn in sixty bytes of CSS. It stops when the
   connection is paused, because a dead instrument must not look alive. */
@media (prefers-reduced-motion: no-preference) {
  .brand .dot::after {
    content: ""; position: absolute; inset: 0; border-radius: 50%;
    border: 1px solid var(--ok);
    animation: radar-ping 3.2s var(--ease) infinite;
  }
  .brand .dot[data-state="paused"]::after { animation: none; content: none; }
  @keyframes radar-ping {
    0% { transform: scale(1); opacity: 0.7; }
    60%, 100% { transform: scale(3.4); opacity: 0; }
  }
}

.nav-group { display: flex; flex-direction: column; gap: 2px; }

.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  color: var(--text-dim); background: none; border: 0;
  width: 100%; text-align: left;
  transition: background 120ms var(--ease), color 120ms var(--ease);
}
.nav-item:hover { background: var(--bg-hover); color: var(--text); }
.nav-item[aria-current="page"] {
  background: var(--accent-dim); color: var(--text); font-weight: 570;
  box-shadow: inset 2px 0 0 var(--accent);
}
.nav-glyph { width: 17px; text-align: center; opacity: 0.85; font-size: 13px; flex: none; }
.nav-item .badge { margin-left: auto; }

.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 2px; }

.main { min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; gap: var(--gap);
  padding: 15px var(--pad);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 5;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(10px);
}
/* The screen's name, set like a process title. Mono and uppercase makes it a
   fixed instrument label rather than a heading competing with the content. */
.topbar h1 {
  font-family: var(--mono); font-size: 12.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  margin: 0; color: var(--text-dim);
}
.topbar .spacer, .spacer { flex: 1; }

.content { padding: var(--pad); min-width: 0; flex: 1; }

/* A view arrives, it does not pop. Four pixels of rise and a fade, once, on
   the top-level container only — table repaints and live rows are nested a
   level deeper and never re-trigger it. */
@media (prefers-reduced-motion: no-preference) {
  .content > * { animation: view-in 220ms var(--ease); }
  @keyframes view-in {
    from { opacity: 0; transform: translateY(4px); }
  }
}

/* Waiting looks like this, and only this. The old router replaced the whole
   screen with the word "loading…", which collapsed the page to a small box and
   grew it back on every navigation — a flash that read as slowness even when
   the answer arrived in six milliseconds. Now the previous screen stays where
   it is and goes slightly quiet. */
.content[data-busy="true"] {
  opacity: 0.6;
  transition: opacity 140ms var(--ease) 80ms;
  pointer-events: none;
}

.skeleton { display: flex; flex-direction: column; gap: 10px; }
.skeleton-row {
  height: 58px; border-radius: var(--radius);
  background: linear-gradient(90deg, var(--bg-raised) 25%, var(--bg-hover) 50%, var(--bg-raised) 75%);
  background-size: 240% 100%;
  animation: shimmer 1.3s linear infinite;
}
@keyframes shimmer { from { background-position: 140% 0; } to { background-position: -40% 0; } }

/* ── components ─────────────────────────────────────────────────────────── */

.card {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--pad);
  box-shadow: var(--shadow-sm);
}

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px; border-radius: var(--radius-sm);
  background: var(--bg-raised); color: var(--text);
  border: 1px solid var(--border-strong);
  font-weight: 520;
  transition: background 120ms var(--ease), border-color 120ms var(--ease),
    filter 120ms var(--ease), transform 80ms var(--ease);
  white-space: nowrap;
}
.btn:hover { background: var(--bg-hover); }
.btn:active { transform: scale(0.97); }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }
/* Dark ink on the cyan, not white: the accent is mid-luminance and white text
   on it fails contrast in the dark theme. In light the accent deepens and the
   ink flips with it — both live in tokens so neither theme guesses. */
.btn-primary {
  background: var(--accent); border-color: var(--accent);
  color: var(--bg-sunken); font-weight: 640;
}
:root[data-theme="light"] .btn-primary { color: #fff; }
.btn-primary:hover { filter: brightness(1.1); }
.btn-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.btn-danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }
.btn-ghost { background: none; border-color: transparent; color: var(--text-dim); }
.btn-ghost:hover { color: var(--text); background: var(--bg-hover); }
.btn-sm { padding: 4px 10px; font-size: 12.5px; }

input[type="text"], input[type="password"], input[type="search"], textarea, select {
  width: 100%; padding: 8px 11px;
  background: var(--bg-sunken);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  transition: border-color 120ms var(--ease), box-shadow 120ms var(--ease);
}
input:focus, textarea:focus, select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
textarea { resize: vertical; min-height: 78px; font-family: inherit; line-height: 1.5; }

/* Filter pills. A toggle that shows its state without a label saying "on". */
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip {
  padding: 4px 11px; border-radius: 999px;
  font-family: var(--mono); font-size: 11.5px; font-weight: 400;
  background: var(--bg-raised); border: 1px solid var(--border);
  color: var(--text-dim);
  transition: all 120ms var(--ease);
}
.chip:hover { border-color: var(--border-strong); color: var(--text); }
.chip[data-active="true"] {
  background: var(--accent-dim); border-color: var(--accent);
  color: color-mix(in srgb, var(--accent) 75%, var(--text));
}

.toolbar { display: flex; align-items: center; gap: var(--gap); flex-wrap: wrap; margin-bottom: 10px; }

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 8px; border-radius: 999px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 400;
  white-space: nowrap;
  background: var(--bg-hover); color: var(--text-dim);
  border: 1px solid var(--border);
}
.badge-ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
.badge-warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.badge-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.badge-auto { color: var(--auto); border-color: color-mix(in srgb, var(--auto) 40%, transparent); }
.badge-accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
/* Пометка, которая не должна тянуть на себя взгляд: «система против», «цену не
   назвали». Она объясняет строку, а не зовёт на неё нажать. */
.badge-mute { color: var(--text-dim); border-color: var(--border); opacity: 0.85; }

.tag {
  display: inline-block; margin-left: 8px;
  font-size: 11px; color: var(--text-faint);
  padding: 0 6px; border-radius: 4px; background: var(--bg-hover);
}

.muted { color: var(--text-dim); }
.faint { color: var(--text-faint); }
.mono { font-family: var(--mono); font-size: 12.5px; }
.nowrap { white-space: nowrap; }
.right { margin-left: auto; }
.row { display: flex; align-items: center; gap: var(--gap); }
.col { display: flex; flex-direction: column; gap: var(--gap); }
.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.empty { padding: 44px 16px; text-align: center; color: var(--text-faint); }
.empty-good { color: var(--text-dim); }
.empty-glyph {
  font-size: 26px; color: var(--ok); margin-bottom: 8px;
}

.section-title {
  font-family: var(--mono);
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-faint); font-weight: 700; margin: 0 0 9px;
}
/* The comment marker. Every section reads like annotated source — the one
   typewriter flourish this design allows itself, and it doubles as a scanning
   anchor: the eye finds `//` faster than it finds a change of case. */
.section-title::before { content: "// "; opacity: 0.55; }
.section-head { display: flex; align-items: flex-end; gap: var(--gap); flex-wrap: wrap; margin-bottom: 10px; }
.section-head .section-title { margin: 0 auto 0 0; }

/* ── the price ──────────────────────────────────────────────────────────── */

/* The number the operator opened the page for. Tabular figures so a column of
   them lines up, the money colour so the eye finds it without reading, and a
   distinct treatment for each of the three ways a price can be absent —
   "not stated" is our parser failing, "negotiable" is the client's choice, and
   they are not the same problem. */
/* `align-items: flex-start` is load-bearing: in a column flexbox the default
   `stretch` made the little "yearly" pill span the whole column width, so it
   read as a progress bar under the price rather than as a qualifier on it. */
.price { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; line-height: 1.3; }
.price-main {
  font-family: var(--mono); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--money); white-space: nowrap; letter-spacing: -0.02em;
}
.price-usd {
  font-family: var(--mono); font-size: 11px;
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.price-missing { color: var(--danger); font-size: 12.5px; opacity: 0.75; }
.price-soft { color: var(--text-faint); font-size: 12.5px; font-style: italic; }

.stat-strip { display: flex; gap: 20px; }
.stat-value {
  font-family: var(--mono); font-size: 18px; font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: -0.03em;
}
.stat-label {
  font-family: var(--mono); font-size: 10px; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: 0.07em;
}

.toasts {
  position: fixed; bottom: 16px; right: 16px; z-index: 100;
  display: flex; flex-direction: column; gap: 8px; max-width: min(380px, 90vw);
}
.toast {
  background: var(--bg-raised); border: 1px solid var(--border-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius); padding: 10px 14px; box-shadow: var(--shadow);
}
.toast[data-kind="error"] { border-left-color: var(--danger); }
.toast[data-kind="ok"] { border-left-color: var(--ok); }

/* Tables scroll inside themselves. The page body never scrolls sideways. */
.table-scroll { overflow-x: auto; padding: 0; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--border); }
th {
  font-family: var(--mono);
  font-size: 10.5px; color: var(--text-faint); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em;
  position: sticky; top: 0; background: var(--bg-raised); z-index: 1;
}
tbody tr { transition: background 100ms var(--ease); }
tbody tr:hover { background: var(--bg-hover); }
tbody tr:last-child td { border-bottom: 0; }

.lead-table .lead-title { max-width: 460px; }
.lead-table td { vertical-align: top; }

/* Площадка перед названием. Тот же кегль — она читается вместе с заголовком,
   как одна фраза, — но приглушённая и моноширинная: машина называет источник,
   человек читает работу. */
.lead-source { color: var(--text-dim); font-family: var(--mono); font-size: 0.92em; }

/* ── login ──────────────────────────────────────────────────────────────── */

.login-wrap { display: grid; place-items: center; min-height: 100vh; padding: 16px; }
.login { width: min(380px, 100%); }
.login h1 { margin: 0 0 4px; font-size: 21px; letter-spacing: -0.02em; }
.login p { margin: 0 0 18px; color: var(--text-dim); }
.login label { display: block; margin-bottom: 12px; }
.login label span { display: block; margin-bottom: 5px; font-size: 12.5px; color: var(--text-dim); }
.login .error { color: var(--danger); margin: 10px 0 0; }

/* ── inbox ──────────────────────────────────────────────────────────────── */

.inbox-list { display: flex; flex-direction: column; gap: 8px; }

.inbox-item {
  display: flex; gap: var(--gap); align-items: flex-start;
  padding: 12px 15px; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--bg-raised);
  cursor: pointer; width: 100%; text-align: left;
  transition: background 110ms var(--ease), border-color 110ms var(--ease);
}
.inbox-item:hover { background: var(--bg-hover); border-color: var(--border-strong); }
.inbox-item[data-selected="true"] { border-color: var(--accent); }
.inbox-item .title { font-weight: 570; }
.inbox-item .preview { color: var(--text-dim); font-size: 13px; margin-top: 3px; }

/* Messages the machine is about to send on its own. The most consequential
   thing on the screen, so it is the only row with a coloured edge. */
.inbox-item-hot { box-shadow: inset 3px 0 0 var(--warn); }

/* The countdown is the operator's veto made visible. It must be impossible to
   miss, which is why it gets its own colour and its own weight. */
.countdown {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--warn); font-weight: 650; font-size: 15px;
}
.countdown[data-expired="true"] { color: var(--text-faint); }

/* ── pipeline ───────────────────────────────────────────────────────────── */

/* Строка над бортом. Кнопка стоит слева и одна: борт — это место, куда
   смотрят, а не панель управления, и второе действие рядом сделало бы первое
   менее заметным. */
.board-bar { display: flex; align-items: center; gap: 10px; margin-bottom: var(--gap); }
.board { display: flex; gap: var(--gap); overflow-x: auto; padding-bottom: 8px; align-items: flex-start; }
.column {
  min-width: 268px; flex: 0 0 268px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 11px;
  transition: border-color 120ms var(--ease), background 120ms var(--ease);
}
.column[data-dragover="true"] { border-color: var(--accent); background: var(--accent-soft); }

/* Этап списком — то, чем сделку двигают там, где перетаскивания нет. Набран
   мелко и тихо: это управление, а не заголовок карточки. */
.stage-pick {
  width: 100%; font-size: 12px; padding: 4px 6px;
  background: var(--bg-sunken); color: var(--text-dim);
}
.stage-pick:hover { color: var(--text); border-color: var(--accent); }
.column-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; }
.column-head h3 { margin: 0; font-size: 13px; text-transform: capitalize; font-weight: 620; }
.column-head .total {
  margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--money);
  font-variant-numeric: tabular-nums;
}

.deal-card {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 11px; margin-bottom: 8px;
  cursor: grab; transition: border-color 110ms var(--ease), transform 110ms var(--ease);
}
.deal-card:hover { border-color: var(--border-strong); }
.deal-card:active { cursor: grabbing; }
.deal-card[data-dragging="true"] { opacity: 0.4; transform: scale(0.98); }
.deal-card .name { font-weight: 560; margin-bottom: 5px; }
.deal-card .meta { display: flex; gap: 8px; align-items: center; font-size: 12px; color: var(--text-dim); }
.deal-card .meta > span:first-child { color: var(--money); font-weight: 600; font-variant-numeric: tabular-nums; }

/* ── the conversation ───────────────────────────────────────────────────── */

/* Three columns: the other threads, the one that is open, and who it is with.
   Switching client used to mean going back to the inbox and finding them
   again, which is a round trip through two screens for something done fifty
   times a day. */
.chat {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr) 292px;
  gap: var(--gap);
  align-items: stretch;
  height: calc(100vh - 108px);
}

.thread-list {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column; min-height: 0;
}
.thread-list-head {
  padding: 11px 14px; border-bottom: 1px solid var(--border);
  font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-faint); font-weight: 650;
}
.thread-list-body { overflow-y: auto; min-height: 0; }
.thread-item {
  display: block; width: 100%; text-align: left;
  padding: 10px 14px; background: none; border: 0;
  border-bottom: 1px solid var(--border);
  transition: background 110ms var(--ease);
}
.thread-item:hover { background: var(--bg-hover); }
.thread-item[data-current="true"] { background: var(--accent-dim); box-shadow: inset 2px 0 0 var(--accent); }
.thread-item-title { font-weight: 550; font-size: 13px; }
.thread-item .row { gap: 8px; }
.thread-item .faint { font-size: 11.5px; }

.chat-main { display: flex; flex-direction: column; min-height: 0; padding: 0; overflow: hidden; }

.chat-head {
  display: flex; align-items: center; gap: var(--gap);
  padding: 13px var(--pad); border-bottom: 1px solid var(--border);
  flex: none;
  /* The price block wraps under the title instead of over it. The mono face
     is wider than the sans it replaced, and «цена не согласована» over a long
     deal title was two lines of text sharing one line of space. */
  flex-wrap: wrap;
}
.chat-head .grow { min-width: 220px; }
.chat-price { text-align: right; margin-left: auto; }
.chat-price .price-main { font-size: 15px; }

/* The deal title doubles as the door to the lead card (the full ТЗ). Styled as
   the text it replaces, with only the underline offering itself on hover — a
   bright link in the header would compete with every number on the screen. */
.chat-title-link { color: inherit; text-decoration: none; min-width: 0; }
.chat-title-link:hover strong { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.chat-title-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

.thread-wrap { position: relative; flex: 1; min-height: 0; }
.thread {
  position: absolute; inset: 0;
  overflow-y: auto; padding: var(--pad);
  display: flex; flex-direction: column; gap: 4px;
  /* Deliberately NOT `scroll-behavior: smooth`. It made the jump-to-bottom on
     open an animation, which does not run in a backgrounded tab and left the
     thread showing its oldest message. The one place smooth scrolling is
     wanted — the "new messages ↓" button — asks for it per call. */
  overflow-anchor: none;
}

.bubble-row { display: flex; flex-direction: column; margin-bottom: 8px; }
.bubble-row[data-direction="out"] { align-items: flex-end; }

.bubble {
  max-width: 78%; padding: 10px 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--border); background: var(--bg-raised);
  white-space: pre-wrap; overflow-wrap: anywhere;
  border-bottom-left-radius: var(--radius-sm);
}
.bubble[data-direction="out"] {
  background: var(--accent-dim); border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-sm);
}
.bubble[data-status="draft"] { border-style: dashed; opacity: 0.9; }
.bubble[data-status="queued"] { box-shadow: 0 0 0 1px color-mix(in srgb, var(--warn) 45%, transparent); }
.bubble[data-status="failed"] { border-color: var(--danger); }
.bubble-meta {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  font-size: 11.5px; color: var(--text-faint); margin-top: 5px;
}
/* The status word is the machine talking about the message, so it speaks in
   the machine's face — and a draft's status is amber so an unsent message can
   never be mistaken for a sent one at a glance. */
.bubble-meta .status { text-transform: lowercase; font-family: var(--mono); font-size: 10.5px; }
.bubble[data-status="draft"] + .bubble-meta .status,
.bubble-row:has(.bubble[data-status="draft"]) .status { color: var(--warn); }

/* A date line, so a two-week negotiation does not read as one wall of text. */
.day-sep {
  display: flex; align-items: center; gap: 10px;
  margin: 14px 0 8px; color: var(--text-faint); font-size: 11.5px;
}
.day-sep::before, .day-sep::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.unread-sep { color: var(--accent); font-weight: 600; }
.unread-sep::before, .unread-sep::after { background: color-mix(in srgb, var(--accent) 40%, transparent); }

/* Shown when a message arrives while the operator is reading back through the
   history. Scrolling them to the bottom mid-sentence is the rudest thing a
   chat interface can do. */
.jump-new {
  position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%);
  padding: 6px 14px; border-radius: 999px;
  background: var(--accent); color: var(--bg-sunken); border: 0;
  box-shadow: var(--shadow); font-size: 12.5px; font-weight: 640;
  z-index: 2;
}
:root[data-theme="light"] .jump-new { color: #fff; }

.edit-wrap { width: 100%; max-width: 78%; margin-top: 6px; display: flex; flex-direction: column; gap: 6px; }
.edit-box { min-height: 90px; }

.composer { border-top: 1px solid var(--border); padding: 12px var(--pad); flex: none; }
.composer textarea { min-height: 62px; max-height: 260px; }
.composer-bar { display: flex; align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }

.context-panel {
  display: flex; flex-direction: column; gap: var(--gap);
  overflow-y: auto; min-height: 0;
}
.context-panel .card { padding: 14px; gap: 7px; }

.kv { display: flex; gap: 10px; align-items: baseline; font-size: 13px; }
.kv-key { color: var(--text-faint); width: 74px; flex: none; font-size: 12px; }
.kv-value { color: var(--text); }

/* ── analytics ──────────────────────────────────────────────────────────── */

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--gap); }
.stat {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 15px;
}
.stat .label {
  font-family: var(--mono); font-size: 10.5px; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: 0.07em;
}
.stat .value {
  font-family: var(--mono); font-size: 24px; font-weight: 700;
  font-variant-numeric: tabular-nums; margin-top: 4px; letter-spacing: -0.035em;
}

/* The one number the entire system exists to maximise. */
.stat.hero { grid-column: span 2; border-color: var(--accent); background: linear-gradient(160deg, var(--accent-soft), transparent 60%); }
.stat.hero .value { font-size: 42px; color: var(--accent); }

/* Герой и ровно четыре карточки рядом с ним.
 *
 * Пришло на смену `.stat-row` с `auto-fit`, куда клали героя шириной в две
 * колонки и четыре обычные карточки: при типичной ширине помещалось четыре
 * места, герой занимал два, и пятая карточка оставалась висеть одна во втором
 * ряду. Рваный ряд читается как поломка вёрстки, а не как расстановка
 * приоритетов. Здесь две колонки заданы явно, а четвёрка внутри своей — сетка
 * 2×2, которая не может осиротеть. */
.stat-lead { display: grid; grid-template-columns: minmax(240px, 1fr) minmax(0, 1.4fr); gap: var(--gap); }
.stat-lead .stat.hero { grid-column: auto; }
.stat-quad { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); }

/* Переключатель разделов: сегментная полоска, не кнопки вразброс. Активная
   вкладка держится на подчёркивании акцентом, а не на заливке — заливка
   соперничала бы с карточкой-героем прямо под ней. */
.tabs {
  display: flex; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
}
.tab {
  appearance: none; background: none; border: 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  padding: 8px 14px; cursor: pointer;
  font: inherit; font-size: 13.5px; color: var(--text-dim);
  border-radius: 6px 6px 0 0;
}
.tab:hover { color: var(--text); background: var(--bg-raised); }
.tab[data-active="true"] {
  color: var(--accent); border-bottom-color: var(--accent); font-weight: 600;
}
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Inside a section head the stats are compact rather than boxed. */
.section-head .stat { background: none; border: 0; padding: 0; }

.funnel { display: flex; flex-direction: column; gap: 6px; }
.funnel-row { display: grid; grid-template-columns: 90px 1fr 60px; align-items: center; gap: 10px; }
.funnel-bar { height: 22px; background: var(--accent); border-radius: 4px; min-width: 2px; }

/* ── responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  .chat { grid-template-columns: minmax(0, 1fr) 280px; }
  .thread-list { display: none; }
}

@media (max-width: 900px) {
  .chat { grid-template-columns: minmax(0, 1fr); height: auto; }
  .context-panel { overflow: visible; }
  .chat-main { height: 70vh; }
  .stat-strip { gap: 14px; }
}

@media (max-width: 720px) {
  /* "Did they reply?" gets asked from a phone. */
  .shell { grid-template-columns: 1fr; }
  .sidebar {
    position: sticky; top: 0; height: auto; z-index: 10;
    flex-direction: row; overflow-x: auto; padding: 8px;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  .brand { padding: 4px 8px; }
  .brand span:last-child { display: none; }
  .nav-group { flex-direction: row; }
  .sidebar-foot { margin-top: 0; flex-direction: row; }
  .nav-item { width: auto; white-space: nowrap; }
  .stat.hero { grid-column: span 1; }
  /* На телефоне герой встаёт над четвёркой, а не рядом с ней. */
  .stat-lead { grid-template-columns: 1fr; }
  .tab { padding: 8px 10px; font-size: 13px; }
  .bubble { max-width: 92%; }
  .lead-table .lead-title { max-width: 220px; }
}

@media (prefers-reduced-motion: no-preference) {
  .toast { animation: rise 160ms ease-out; }
  @keyframes rise { from { opacity: 0; transform: translateY(6px); } }
  .bubble-row { animation: settle 180ms var(--ease); }
  @keyframes settle { from { opacity: 0; transform: translateY(4px); } }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  .thread { scroll-behavior: auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE OPERATOR'S OWN SCREENS
   ═══════════════════════════════════════════════════════════════════════════

   Everything above this line is about leads. What follows is about the person
   working them, and it is styled to a different brief: the owner asked for an
   atmosphere that pushes them to do more, "но при этом чтобы было крайне
   комфортно работать" — motivating without becoming a slot machine.

   The line that draws is: the interface may celebrate a RESULT and must never
   manufacture urgency. Money earned gets the loudest treatment on the page. A
   streak gets warmth. Nothing counts down at you, nothing flashes red because
   you are behind, and there is no leaderboard — you are the only player, and a
   tool that nags is a tool that gets closed.

   Green is money and only money. Amber is a streak and only a streak. If those
   two colours appear anywhere else on a screen they stop meaning anything, and
   the glance that is supposed to answer "am I ahead?" needs a second look. */

:root {
  --gold: #f0b429;
  --gold-soft: #f0b4291f;
  --money-soft: #4ad1951a;
  --grid: #ffffff08;
}
:root[data-theme="light"] {
  --gold: #b57b00;
  --gold-soft: #f0b42922;
  --money-soft: #0f7a4a14;
  --grid: #0f172a0a;
}

/* ── today ──────────────────────────────────────────────────────────────── */

.today {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--gap);
  align-items: start;
}
.today-col { display: flex; flex-direction: column; gap: var(--gap); min-width: 0; }

/* The one card the day is read from. Bigger than everything else on purpose:
   if the operator looks at exactly one thing before starting work, this is the
   thing, and a card that competes with the panel beside it is a card that gets
   skimmed. */
.goal-card {
  display: flex; align-items: center; gap: 26px;
  padding: 24px var(--pad);
  background:
    radial-gradient(120% 140% at 0% 0%, var(--money-soft), transparent 62%),
    var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.goal-figures { min-width: 0; flex: 1; }
.goal-earned {
  font-family: var(--mono);
  font-size: 38px; font-weight: 700; letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums; color: var(--money);
  line-height: 1.05;
}
.goal-of { color: var(--text-dim); font-size: 14px; margin-top: 4px; }
.goal-meta { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 14px; }

.ring-wrap { position: relative; width: 168px; height: 168px; flex: none; }
.ring { width: 100%; height: 100%; display: block; }
.ring-track { stroke: var(--border); }
.ring-arc {
  stroke: var(--money);
  transition: stroke-dashoffset 700ms var(--ease);
}
/* Past the target the ring stops being green and starts being gold. Nothing
   else on the page uses gold, so the change is legible from across the room —
   which is the entire job of this element. */
.ring-arc[data-over="true"] { stroke: var(--gold); }
.ring-label {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; pointer-events: none;
}
.ring-value { font-family: var(--mono); font-size: 25px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.04em; }
.ring-sub { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }

.streak {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px; border-radius: 999px;
  background: var(--gold-soft); color: var(--gold);
  font-weight: 640; font-size: 13px;
  border: 1px solid color-mix(in srgb, var(--gold) 30%, transparent);
}
.streak[data-cold="true"] { background: none; color: var(--text-faint); border-color: var(--border); }

/* ── the timer ──────────────────────────────────────────────────────────── */

.pomodoro { display: flex; flex-direction: column; gap: 14px; align-items: center; padding: 22px; }
.pomo-clock {
  font-family: var(--mono);
  font-size: 52px; font-weight: 700; letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums; line-height: 1;
}
.pomo-clock[data-running="true"] { color: var(--accent); }
.pomo-clock[data-kind="break"] { color: var(--ok); }
.pomo-modes { display: flex; gap: 6px; }
.pomo-note { width: 100%; }
.pomo-done { display: flex; gap: 5px; flex-wrap: wrap; justify-content: center; min-height: 12px; }
/* One dot per finished session today. Deliberately not a number: four dots
   reads as "a decent morning" without the operator having to compare it to
   anything, and a number invites comparison with yesterday's number. */
.pomo-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent); opacity: 0.85;
}
.pomo-dot[data-kind="break"] { background: var(--border-strong); }

/* ── metrics ────────────────────────────────────────────────────────────── */

.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: var(--gap); }
.metric {
  padding: 14px 16px; border-radius: var(--radius);
  background: var(--bg-raised); border: 1px solid var(--border);
}
.metric-value {
  font-family: var(--mono);
  font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums;
  letter-spacing: -0.035em; line-height: 1.15;
  /* Не всякое «значение» — число: `duration()` возвращает «меньше минуты», и
     в моноширинном кегле 21px это ломалось на две строки, из-за чего соседние
     карточки в ряду разъезжались по высоте и ряд выглядел сломанным. Слова
     ужимаются, цифры остаются крупными. */
  overflow-wrap: anywhere;
  text-wrap: balance;
}
.metric-value:not([data-numeric="true"]) { font-size: clamp(15px, 2.2vw, 21px); }
.metric-value[data-tone="money"] { color: var(--money); }
.metric-value[data-tone="gold"] { color: var(--gold); }
.metric-label { font-size: 11.5px; color: var(--text-faint); margin-top: 3px; }
.metric-hint { font-size: 11.5px; color: var(--text-dim); margin-top: 5px; }

/* ── charts ─────────────────────────────────────────────────────────────── */

.bars {
  display: flex; align-items: flex-end; gap: 4px;
  height: 168px; padding-top: 8px;
}
.bar-slot { flex: 1; display: flex; flex-direction: column; height: 100%; min-width: 0; }
.bar-col { flex: 1; display: flex; align-items: flex-end; }
.bar-fill {
  width: 100%; border-radius: 4px 4px 2px 2px;
  background: linear-gradient(180deg, var(--money), color-mix(in srgb, var(--money) 45%, transparent));
  transition: height 500ms var(--ease);
  min-height: 2px;
}
.bar-slot[data-hot="true"] .bar-fill {
  background: linear-gradient(180deg, var(--gold), color-mix(in srgb, var(--gold) 45%, transparent));
}
.bar-slot:hover .bar-fill { filter: brightness(1.18); }
.bar-label {
  font-size: 10px; color: var(--text-faint); text-align: center;
  margin-top: 6px; overflow: hidden; white-space: nowrap;
}

/* A ranked list — by specialist, by source. A bar behind the row rather than
   beside it: the number is what matters and the bar is context, so the bar goes
   underneath where it cannot be mistaken for the subject. */
.rank-row {
  position: relative; display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: var(--radius-sm);
  overflow: hidden;
}
.rank-bar {
  position: absolute; inset: 0 auto 0 0;
  background: var(--money-soft); z-index: 0;
  transition: width 500ms var(--ease);
}
/* Содержимое — над полосой, а САМА полоса остаётся вынутой из потока.
   `.rank-row > *` без исключения перебивало `position: absolute` у `.rank-bar`
   (та же специфичность, правило ниже), полоса становилась обычным flex-
   элементом и съедала строку своей шириной: в верхней строке любого рейтинга
   она равна 100%, и текст рядом сжимался до нуля. Полоса — это фон, а не
   колонка; она никогда не должна занимать место. */
.rank-row > *:not(.rank-bar) { position: relative; z-index: 1; }
.rank-value { margin-left: auto; font-variant-numeric: tabular-nums; font-weight: 620; color: var(--money); }
/* Тот же ряд, но в списке-справочнике: восемь валют или семь дней недели —
   это перечень, а не находка, и обычная высота строки превращает его в
   полэкрана. */
.rank-row-sm { padding: 4px 8px; font-size: 12.5px; gap: 8px; }
.rank-row-sm .rank-value { font-weight: 560; color: var(--text-dim); }

/* ── поток заявок ────────────────────────────────────────────────────────── */

/* Таблица разбросов: четыре четверти и покрытие.
   Сетка, а не <table>, по одной причине — на телефоне колонки надо схлопывать,
   и у грида это одно правило, а у таблицы переопределение display у пяти
   элементов подряд. Числа моноширинные и выровнены по правому краю: четыре
   столбца чисел читаются сравнением, а сравнивать разной ширины цифры нельзя. */
.spread-table { display: flex; flex-direction: column; gap: 1px; }
.spread-row {
  display: grid;
  grid-template-columns: minmax(150px, 2.2fr) repeat(4, minmax(58px, 1fr)) minmax(64px, 0.9fr);
  align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: var(--radius-sm);
}
.spread-row > *:not(:first-child) {
  text-align: right; font-variant-numeric: tabular-nums; font-size: 13px;
}
.spread-row:nth-child(even) { background: var(--bg-raised); }
.spread-head {
  font-size: 11.5px; color: var(--text-faint); text-transform: lowercase;
  background: none !important; padding-bottom: 2px;
}
.spread-head > * { font-weight: 500; }
/* Строка, посчитанная по горстке заявок. Приглушена, а не спрятана: «известно
   у 0.4%» — это и есть находка, ради которой строка здесь стоит. */
.spread-row[data-thin="true"] > *:not(:first-child) { color: var(--text-faint); }

.mix-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--gap); }
.mix-col { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.mix-title {
  font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.02em;
  color: var(--text-dim); text-transform: uppercase;
}

/* ── leads ──────────────────────────────────────────────────────────────── */

/* The button the whole flow hangs off. The operator's decision is "yes, answer
   this one", and everything after it — picking the specialist, writing the
   message, attaching the portfolio — is the machine's job. So it is the widest,
   brightest thing in the row and it says what happens, not what it does. */
.btn-start {
  background: linear-gradient(180deg, var(--money), color-mix(in srgb, var(--money) 78%, black));
  border-color: color-mix(in srgb, var(--money) 60%, transparent);
  color: #04150d; font-weight: 680; letter-spacing: -0.01em;
  box-shadow: 0 1px 0 color-mix(in srgb, var(--money) 40%, transparent);
}
/* Dark ink works on the bright phosphor green of the dark theme and fails on
   the deep green the light theme uses — same class, opposite luminance. */
:root[data-theme="light"] .btn-start { color: #fff; }
.btn-start:hover { filter: brightness(1.09); background: var(--money); }
.btn-start[disabled] { filter: grayscale(0.6); }

.specialist-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 560;
  border: 1px solid var(--border); color: var(--text-dim); white-space: nowrap;
}
.specialist-chip[data-id="frontend_dev"] { color: #63b3ff; border-color: #63b3ff40; }
.specialist-chip[data-id="backend_dev"] { color: #7ee0a8; border-color: #7ee0a840; }
.specialist-chip[data-id="graphic_designer"] { color: #d9a0ff; border-color: #d9a0ff40; }
.specialist-chip[data-ambiguous="true"] { border-style: dashed; }
.specialist-chip[data-id="none"] { color: var(--text-faint); }

/* ── the DeepSeek mark ──────────────────────────────────────────────────── */

/* This column replaced «Где», which told the operator that a remote job was
   remote — true of nearly every row and therefore worth nothing. What sits
   there now is the only number that says whether the machine can do the work
   at all, and it is drawn as well as printed: a bar is read down a whole column
   in one movement of the eye, and a hundred rows never get read any other way.

   Three bands and no gradient. The question is "can we take this ourselves",
   and it has three answers. */
.score { display: flex; align-items: center; gap: 8px; min-width: 84px; }
.score-num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 12.5px;
  min-width: 22px; text-align: right; color: var(--text-dim);
}
.score-bar {
  position: relative; flex: 1; min-width: 36px; height: 5px;
  background: var(--bg-sunken); border-radius: 999px; overflow: hidden;
}
.score-fill {
  position: absolute; inset: 0 auto 0 0;
  border-radius: 999px; background: var(--text-faint);
  transform-origin: left;
}
/* The mark draws itself on arrival — a bar that grows reads as "measured",
   a bar that appears reads as "printed". */
@media (prefers-reduced-motion: no-preference) {
  .score-fill { animation: score-grow 420ms var(--ease); }
  @keyframes score-grow { from { transform: scaleX(0); } }
}
.score[data-tier="mid"] .score-num { color: var(--warn); }
.score[data-tier="mid"] .score-fill { background: var(--warn); }
.score[data-tier="high"] .score-num { color: var(--money); }
.score[data-tier="high"] .score-fill { background: var(--money); }

/* ── чей это вердикт ────────────────────────────────────────────────────── */

/* Строка, по которой система сказала «не стоит», — и до сих пор выглядела
   ровно как одобренная. В открытой пачке живого окна таких 234 из 260: девять
   прочитанных заданий из десяти впустую, при том что вердикт и его причина
   лежали в той же строке ответа сервера.

   Приглушение, а не скрытие: отказ рубрики остаётся в ленте сознательно —
   политика правится тем, что оператор читает, что она зря выбросила. */
.lead-table tr[data-verdict="reject"] { opacity: 0.62; }
.lead-table tr[data-verdict="reject"]:hover { opacity: 1; }
.lead-table tr[data-verdict="pass"] { box-shadow: inset 2px 0 0 color-mix(in srgb, var(--money) 55%, transparent); }
.verdict-why { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; font-size: 12px; }
/* Причина отказа не имеет права ломать таблицу.
   Строка приходит с сервера и бывает длинной — G15 перечислял все покрытые
   категории и разъезжался на весь экран, вытесняя заголовок заявки. Здесь
   ставится потолок: две строки, дальше многоточие, целиком — в подсказке. */
.verdict-why > span:last-child {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; max-width: 62ch;
}

/* ── the arrival ────────────────────────────────────────────────────────── */

/* A row that landed while the operator was on the screen. The bar down the left
   edge is the part that survives `prefers-reduced-motion`, because "this one is
   new" is information and not decoration — the fade is only how it stops being
   new without anybody pressing anything. */
.lead-table tr[data-fresh="true"] { box-shadow: inset 2px 0 0 var(--accent); }

@media (prefers-reduced-motion: no-preference) {
  .lead-table tr[data-fresh="true"] { animation: lead-arrive 5s var(--ease) forwards; }
  @keyframes lead-arrive {
    0%, 55% { background: var(--accent-soft); }
    100% { background: transparent; }
  }
}

.live-dot {
  display: inline-flex; align-items: center; gap: 6px; margin-left: 10px;
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--ok); font-weight: 400;
}
.live-dot::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok); flex: none;
}
/* The terminal caret after «живой эфир» — a cursor that blinks because the
   line really is still being written: rows append themselves under it once a
   second. The one blink in the whole interface, and it is earned. */
.live-dot::after { content: "▍"; margin-left: 1px; }
@media (prefers-reduced-motion: no-preference) {
  .live-dot::before { animation: live-pulse 2.4s ease-in-out infinite; }
  @keyframes live-pulse { 50% { opacity: 0.2; } }
  .live-dot::after { animation: caret-blink 1.1s steps(2, start) infinite; }
  @keyframes caret-blink { 50% { opacity: 0; } }
}
@media (prefers-reduced-motion: reduce) {
  .live-dot::after { content: none; }
}

/* ── the filter sliders ─────────────────────────────────────────────────── */

/* Budget and the DeepSeek mark are continuous questions and get continuous
   controls. Both are two native range inputs' worth of markup and no library:
   the whole front end is served as-is with no build step, and a slider is not
   worth breaking that for. */
.filter-row { display: flex; gap: var(--gap); flex-wrap: wrap; }

.slider {
  flex: 1 1 300px; min-width: 240px;
  padding: 9px 14px 10px;
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.slider-head { display: flex; align-items: baseline; gap: 10px; }
.slider-name {
  font-family: var(--mono);
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--text-faint); font-weight: 700;
}
/* The live figure under the thumb. It is money, so it is money-coloured and
   tabular — a value that changes width as you drag makes the whole row jitter. */
.slider-value {
  margin-left: auto; font-family: var(--mono); font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--money); white-space: nowrap;
}
.slider-hint { font-size: 11px; color: var(--text-faint); }

.range { position: relative; height: 26px; }
.range-track {
  position: absolute; left: 0; right: 0; top: 11px; height: 4px;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: 999px;
}
.range-fill {
  position: absolute; top: 11px; height: 4px;
  background: var(--accent); border-radius: 999px;
}
.range[data-tier="mid"] .range-fill { background: var(--warn); }
.range[data-tier="high"] .range-fill { background: var(--money); }

/* Both inputs cover the whole strip, so `pointer-events` is what decides who
   gets a press: none on the input, auto on the thumb. Without that the input
   drawn last swallows every press meant for the one underneath and the left
   handle can never be moved again. */
.range input[type="range"] {
  position: absolute; left: 0; top: 0; width: 100%; height: 26px;
  margin: 0; padding: 0; background: none; border: 0;
  -webkit-appearance: none; appearance: none;
  pointer-events: none;
}
.range input[type="range"]:focus { outline: none; }

/* One rule per engine, never a shared selector list: a browser that does not
   know one of these pseudo-elements discards the entire rule it appears in,
   which would take the other engine's thumb down with it. */
.range input[type="range"]::-webkit-slider-runnable-track { height: 26px; background: none; }
.range input[type="range"]::-moz-range-track { height: 26px; background: none; }

/* The thumb is the page's own surface with a ring in the fill's colour, rather
   than a solid dot: `var(--text)` filled it black in the light theme and white
   in the dark one, so the same control read as two different widgets depending
   on the time of day the operator had last pressed the theme button. */
.range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  pointer-events: auto; cursor: grab;
  width: 16px; height: 16px; margin-top: 5px;
  border-radius: 50%; border: 3px solid var(--accent);
  background: var(--bg-raised); box-shadow: var(--shadow-sm);
}
.range input[type="range"]::-moz-range-thumb {
  pointer-events: auto; cursor: grab;
  width: 16px; height: 16px;
  border-radius: 50%; border: 3px solid var(--accent);
  background: var(--bg-raised); box-shadow: var(--shadow-sm);
}
.range[data-tier="mid"] input[type="range"]::-webkit-slider-thumb { border-color: var(--warn); }
.range[data-tier="mid"] input[type="range"]::-moz-range-thumb { border-color: var(--warn); }
.range[data-tier="high"] input[type="range"]::-webkit-slider-thumb { border-color: var(--money); }
.range[data-tier="high"] input[type="range"]::-moz-range-thumb { border-color: var(--money); }
.range input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.range input[type="range"]:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 4px var(--accent-soft);
}

/* ── messengers ─────────────────────────────────────────────────────────── */

.messengers { display: flex; gap: 6px; flex-wrap: wrap; }
.msgr {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: var(--radius-sm); font-size: 12.5px; font-weight: 560;
  border: 1px solid var(--border-strong); color: var(--text);
  text-decoration: none;
}
.msgr:hover { text-decoration: none; background: var(--bg-hover); }
.msgr[data-kind="whatsapp"] { color: #3fc351; border-color: #3fc35140; }
.msgr[data-kind="telegram"] { color: #4aa8e8; border-color: #4aa8e840; }
.msgr[data-kind="viber"] { color: #9a7bd8; border-color: #9a7bd840; }

/* ── the closed marker ──────────────────────────────────────────────────── */

/* A deal that is finished should LOOK finished from the other side of the
   board. Anything less than a whole-card treatment and the operator keeps
   re-reading rows they have already dealt with. */
.deal-card[data-closed="won"] { border-color: color-mix(in srgb, var(--money) 45%, transparent); }
.deal-card[data-closed="won"] .name { text-decoration: none; }
.deal-card[data-closed="lost"] { opacity: 0.55; }
.closed-mark {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; font-weight: 640;
}
.closed-mark[data-outcome="won"] { color: var(--money); }
.closed-mark[data-outcome="lost"] { color: var(--text-faint); }
.closed-mark[data-outcome="open"] { color: var(--text-faint); font-weight: 500; }

/* ── the empty states ───────────────────────────────────────────────────── */

/* An empty screen is where a new operator decides whether the tool works. Every
   one of them says what to do next rather than that there is nothing. */
.guide {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 46px 22px; text-align: center; color: var(--text-dim);
}
.guide-title { font-size: 16px; font-weight: 620; color: var(--text); }
.guide-body { max-width: 460px; line-height: 1.6; }

/* Вывод, а не пустота.
 *
 * `.guide` — состояние «здесь ничего нет»: центр, 46 пикселей воздуха, тихий
 * цвет. Ровно эти свойства и делали найденный вывод («8 заявок ждут первого
 * сообщения») похожим на заглушку — центрированный текст в пустой карточке
 * читается как «показывать нечего», хотя это самое важное предложение на
 * экране. Находка выравнивается по левому краю, как всё, что читают, и
 * держится на акцентной полосе слева. */
.finding {
  display: flex; flex-direction: column; gap: 6px;
  padding: 14px 16px; border-radius: var(--radius);
  background: var(--bg-raised); border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
}
.finding[data-tone="warn"] { border-left-color: var(--warn); }
.finding[data-tone="ok"] { border-left-color: var(--money); }
.finding[data-tone="calm"] { border-left-color: var(--border); }
.finding-title { font-size: 15px; font-weight: 620; color: var(--text); text-wrap: balance; }
.finding-body { line-height: 1.6; color: var(--text-dim); max-width: 68ch; }
.guide code {
  font-family: var(--mono); font-size: 12.5px;
  background: var(--bg-sunken); padding: 1px 6px; border-radius: 4px;
}

@media (max-width: 1100px) {
  .today { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .goal-card { flex-direction: column; text-align: center; gap: 18px; }
  .goal-meta { justify-content: center; }
  .goal-earned { font-size: 33px; }
  .bars { height: 128px; }
}

/* ── deal history ───────────────────────────────────────────────────────────
   The record under the board. Collapsed by default: it is the reference, not
   the workspace, and a hundred finished rows above eight live ones is a board
   nobody scrolls back up from. */
.deal-history { margin: 18px 0 8px; border-top: 1px solid var(--border); padding-top: 12px; }
.deal-history > summary {
  cursor: pointer; user-select: none; font-weight: 500;
  color: var(--text-dim); padding: 4px 0;
}
.deal-history > summary:hover { color: var(--text); }
.deal-history[open] > summary { margin-bottom: 10px; }
.history-body { overflow-x: auto; }

/* ── the typewriter sweep ─────────────────────────────────────────────────
   The remaining machine-voice surfaces, gathered in one place. The rule the
   whole redesign follows: if the text was produced by the system — an age, a
   status, a stage name, a key, a count — it is set in the mono face; if a
   person wrote it, it stays in the sans. */

.age { font-family: var(--mono); font-size: 11.5px; }
/* Явный таймстемп перед возрастом: «12:07 · 3 мин» */
.age-abs { color: var(--text-dim); }
.age-abs:not(:empty)::after { content: ' · '; }
.tag { font-family: var(--mono); font-size: 10.5px; }
.kv-key { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
.day-sep { font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; }
.thread-list-head { font-family: var(--mono); }
.column-head h3 {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-dim);
}
.specialist-chip { font-family: var(--mono); font-size: 10.5px; }
.closed-mark { font-family: var(--mono); font-size: 11px; }
.streak { font-family: var(--mono); font-size: 12px; }
.metric-label { font-family: var(--mono); font-size: 10.5px; }
.bar-label { font-family: var(--mono); }
.goal-of { font-variant-numeric: tabular-nums; }
.ring-sub { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; }

/* Sidebar navigation: quiet sans for the words, and the whole rail sits on the
   sunken ground so the content plane reads as raised — two layers, no shadows. */
.nav-item { font-size: 13.5px; }

/* The window heading carries a mono count («Заявки · за сутки») — the number
   half is data, so the whole line joins the instrument panel. */
.section-head .section-title { font-size: 12px; }

/* Cards drop their drop-shadow. Hairlines carry the structure; the shadow was
   the one soft edge in an otherwise drawn interface. */
.card { box-shadow: none; }

/* Toasts arrive from the right edge like log lines being appended. */
@media (prefers-reduced-motion: no-preference) {
  .toast { animation: toast-in 200ms var(--ease); }
  @keyframes toast-in {
    from { opacity: 0; transform: translateX(14px); }
  }
}

/* A fresh lead slides into the table instead of teleporting. The green edge
   and slow highlight fade stay as they were — they are information; this is
   only the arrival. */
@media (prefers-reduced-motion: no-preference) {
  .lead-table tr[data-fresh="true"] { animation: lead-arrive 5s var(--ease) forwards, row-in 240ms var(--ease); }
  @keyframes row-in {
    from { opacity: 0; transform: translateY(-4px); }
  }
}

/* Kanban deal cards lift a hair under the cursor — they are draggable, and a
   thing that responds to approach reads as a thing that can be picked up. */
.deal-card:hover { transform: translateY(-1px); }

/* The pomodoro dots breathe in while the timer runs. */
@media (prefers-reduced-motion: no-preference) {
  .pomo-clock[data-running="true"] { animation: clock-soft 2.4s ease-in-out infinite; }
  @keyframes clock-soft { 50% { opacity: 0.82; } }
}

/* ── выпадающие мультиселекты ─────────────────────────────────────────────
   «Красивое окошко» тулбара: плашка-кнопка ровно по содержимому — не на весь
   экран — и панель с галочками под ней. Стиль сайта: моно-шрифт машины,
   графит, циановый акцент на выбранном. */

.dd { position: relative; display: inline-flex; }

.dd-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px; border-radius: 999px;
  font-family: var(--mono); font-size: 11.5px;
  background: var(--bg-raised); border: 1px solid var(--border);
  color: var(--text-dim); white-space: nowrap;
  transition: all 120ms var(--ease);
}
.dd-btn:hover { border-color: var(--border-strong); color: var(--text); }
.dd-btn[data-active="true"] {
  background: var(--accent-dim); border-color: var(--accent);
  color: color-mix(in srgb, var(--accent) 75%, var(--text));
}
.dd-caret { font-size: 9px; opacity: 0.6; transition: transform 140ms var(--ease); }
.dd[data-open="true"] .dd-caret { transform: rotate(180deg); }

.dd-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  min-width: 220px; max-height: 320px; overflow-y: auto;
  padding: 5px;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  display: none;
}
.dd[data-open="true"] .dd-panel { display: block; animation: dd-in 120ms var(--ease); }
@keyframes dd-in { from { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) {
  .dd[data-open="true"] .dd-panel { animation: none; }
}

.dd-opt {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 7px 9px; border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: 12px; text-align: left;
  color: var(--text-dim);
  /* Голая <button> приходит со светлой заливкой агента — в тёмной панели она
     выглядела как чужой светлый ряд. Фон здесь рисует только ховер. */
  background: none; border: 0; cursor: pointer;
  transition: background 100ms var(--ease), color 100ms var(--ease);
}
.dd-opt:hover { background: var(--bg-hover); color: var(--text); }
.dd-opt[data-on="true"] { color: color-mix(in srgb, var(--accent) 75%, var(--text)); }
.dd-opt[data-dim="true"] { opacity: 0.55; }
.dd-opt-all { border-bottom: 1px solid var(--border); border-radius: var(--radius-sm) var(--radius-sm) 0 0; margin-bottom: 4px; }
.dd-check {
  width: 14px; flex: none; text-align: center;
  color: var(--accent); font-weight: 700;
}
.dd-opt-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.dd-opt-hint { flex: none; font-size: 10.5px; color: var(--text-faint); }

/* ── помодоро-плашка в «Заявках» ─────────────────────────────────────────
   Таймер переехал из «Сегодня» и сжался до одной строки: точка-пульс, что
   идёт, часы и две кнопки. Не карточка на полэкрана — полоска над лентой. */

.pomo-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 12px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.pomo-strip[data-running="true"] {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 4%, var(--bg-raised));
}
.pomo-strip-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--accent);
  animation: pomo-pulse 2s ease-in-out infinite;
}
@keyframes pomo-pulse { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .pomo-strip-dot { animation: none; } }
.pomo-strip-label {
  font-size: 12.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%;
}
.pomo-strip-clock {
  font-family: var(--mono); font-weight: 700; font-variant-numeric: tabular-nums;
  font-size: 14px; letter-spacing: 0.02em;
}
.pomo-strip-clock[data-kind="break"] { color: var(--ok); }

/* ── путь к цели ─────────────────────────────────────────────────────────── */

/* Плашка над лентой заявок, на месте бывшего помодоро.
   Единственное место в интерфейсе, где деньги нарисованы крупно и празднично —
   и это осознанно: всё остальное здесь считает работу, а это считает смысл.
   Зелёный тут по правилу палитры (зелёный = только деньги), а золото
   появляется ровно один раз в жизни — когда цель взята. */
.mission {
  position: relative;
  display: flex; flex-direction: column; gap: 9px;
  padding: 14px 16px 15px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Свечение слева, откуда растёт полоса: карточка светится там, где уже
     есть деньги, и остаётся ровной там, где их ещё нет. */
  background:
    radial-gradient(120% 180% at 0% 100%, var(--money-soft), transparent 62%),
    var(--bg-raised);
  overflow: hidden;
}
.mission[data-state="moving"] {
  border-color: color-mix(in srgb, var(--money) 30%, var(--border));
}
.mission[data-state="done"] {
  border-color: color-mix(in srgb, var(--gold) 55%, var(--border));
  background:
    radial-gradient(120% 180% at 0% 100%, color-mix(in srgb, var(--gold) 12%, transparent), transparent 62%),
    var(--bg-raised);
}
.mission-skeleton { height: 92px; }

.mission-head { display: flex; align-items: baseline; gap: 10px; }
.mission-spacer { flex: 1; }
.mission-title {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-faint);
}
.mission[data-state="done"] .mission-title { color: var(--gold); }
/* Цель — кнопка, но выглядит как подпись: её нажимают раз в полгода, и
   настоящая кнопка рядом с главной цифрой тянула бы на себя взгляд. */
.mission-target {
  appearance: none; background: none; border: 0; padding: 0;
  font: inherit; font-family: var(--mono); font-size: 11.5px;
  color: var(--text-faint); cursor: pointer;
  border-bottom: 1px dashed transparent;
}
.mission-target:hover { color: var(--accent); border-bottom-color: currentColor; }

.mission-figure { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.mission-earned {
  font-family: var(--mono); font-weight: 700;
  font-size: 30px; line-height: 1.05; letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  color: var(--money);
}
.mission[data-state="empty"] .mission-earned { color: var(--text-dim); }
.mission[data-state="done"] .mission-earned { color: var(--gold); }
.mission-of { font-size: 13px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.mission-percent {
  margin-left: auto;
  font-family: var(--mono); font-size: 13px; font-weight: 620;
  font-variant-numeric: tabular-nums; color: var(--text-dim);
}

.mission-track {
  position: relative; height: 10px; border-radius: 999px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  overflow: hidden;
}
/* Что стоит в живых сделках, позади заработанного. Полосатая заливка и цвет
   акцента, а не денег: правило палитры — зелёный означает деньги, которые
   ПРИШЛИ, и покрасить обещание в тот же зелёный значит его этим цветом
   узаконить. */
.mission-flight {
  position: absolute; inset: 0 auto 0 0;
  width: var(--mission-flight, 0%);
  border-radius: 999px;
  background: repeating-linear-gradient(
    115deg,
    color-mix(in srgb, var(--accent) 26%, transparent) 0 5px,
    transparent 5px 10px);
  transition: width 900ms var(--ease, cubic-bezier(0.22, 1, 0.36, 1));
}
.mission-fill {
  position: absolute; inset: 0 auto 0 0;
  width: var(--mission-progress, 0%);
  min-width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--money) 55%, transparent), var(--money));
  transition: width 900ms var(--ease, cubic-bezier(0.22, 1, 0.36, 1));
}
.mission[data-state="done"] .mission-fill {
  background: linear-gradient(90deg, color-mix(in srgb, var(--gold) 60%, transparent), var(--gold));
}
/* Блеск идёт ТОЛЬКО по заполненной части: полоса «дышит» там, где есть
   деньги, и молчит на пустой дорожке — иначе движение обещает прогресс,
   которого нет. */
.mission-shine {
  position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 20%,
    color-mix(in srgb, white 34%, transparent) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: mission-shine 3.6s ease-in-out infinite;
}
@keyframes mission-shine { 55%, 100% { transform: translateX(100%); } }
.mission[data-state="empty"] .mission-shine { display: none; }

/* Засечки — круглые деньги на пути. Пройденная гаснет: она позади, и спорить
   с полосой ей незачем. */
.mission-mark {
  position: absolute; top: 50%; width: 2px; height: 10px;
  transform: translate(-50%, -50%);
  background: color-mix(in srgb, var(--text) 26%, transparent);
  border-radius: 1px;
}
.mission-mark[data-passed="true"] { background: color-mix(in srgb, white 45%, transparent); }

.mission-facts {
  display: flex; gap: 6px 14px; flex-wrap: wrap;
  font-size: 12px; color: var(--text-dim);
}
.mission-fact[data-tone="money"] { color: var(--money); }
.mission-fact[data-tone="flight"] { color: var(--accent); }
.mission-fact[data-tone="pace"] { color: var(--text-faint); }

@media (prefers-reduced-motion: reduce) {
  .mission-shine { animation: none; display: none; }
  .mission-fill, .mission-flight { transition: none; }
}
@media (max-width: 720px) {
  .mission { padding: 12px 13px 13px; }
  .mission-earned { font-size: 25px; }
  /* На 375 пикселях процент уезжал на вторую строку и оставлял её себе
     одному; в шапке он стоит рядом с целью и строки не занимает. */
  .mission-percent { margin-left: 0; }
}

/* ── балансы в настройках ────────────────────────────────────────────────── */

.balance-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.balance-row:last-of-type { border-bottom: 0; }
.balance-name { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 180px; }
.balance-title { font-weight: 620; }
.balance-note { font-size: 12px; color: var(--text-faint); }
.balance-amount {
  font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--money);
}
.balance-amount[data-low="true"] { color: var(--warn); }

/* Счёт конкретного аккаунта под своей площадкой. Отступ и полоска слева, а не
   вторая карточка: это тот же ряд, разложенный по владельцам денег, и рвать
   его рамкой значило бы читать площадку и её счета как разные вещи. */
.balance-sub {
  padding-left: 14px;
  border-left: 2px solid var(--border);
  margin-left: 2px;
}
.balance-sub .balance-title { font-weight: 520; font-size: 13.5px; color: var(--text-dim); }
.balance-sub .balance-amount { font-size: 14px; }

/* Строка настройки: слева вопрос, справа ответ и одна поясняющая фраза.
   Название набрано машинным моно, как всякий ярлык, а объяснение — обычным
   текстом: его читают, а не сканируют. */
.setting-row {
  display: grid;
  grid-template-columns: minmax(120px, 190px) minmax(0, 1fr);
  gap: 14px; align-items: start;
  padding: 11px 0; border-bottom: 1px solid var(--border);
}
.setting-row:last-child { border-bottom: 0; padding-bottom: 0; }
.setting-label {
  font-family: var(--mono); font-size: 11px; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: 0.07em; padding-top: 6px;
}
@media (max-width: 720px) {
  .setting-row { grid-template-columns: 1fr; gap: 6px; }
  .setting-label { padding-top: 0; }
}

/* Заголовок свёрнутого блока. Выглядит как ссылка, а не как заголовок раздела:
   то, что за ним, читают один раз и больше не открывают. */
.fold-summary {
  cursor: pointer; color: var(--text-dim); font-size: 13px;
  padding: 6px 0; user-select: none;
}
.fold-summary:hover { color: var(--accent); }
.balance-amount.faint { color: var(--text-faint); font-weight: 400; }

/* ── «Сегодня» внутри Статистики ─────────────────────────────────────────── */

.today-block-row { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.today-block .metric-grid { flex: 1; min-width: 260px; }
/* Карточки дня одной высоты: ряд, где одна выше другой из-за длинной подписи,
   читается как случайность вёрстки, а не как разная важность. */
.today-block .metric { display: flex; flex-direction: column; justify-content: center; }

/* ── the phone ────────────────────────────────────────────────────────────
   The CRM is reached from two pockets now. Everything here is subtraction:
   fewer columns, folded filters, no stray scrollbars — the phone screen is
   for deciding, and deciding is price + title + mark + «Взять». */

/* The card's fact strip wraps instead of clipping; six mono figures do not
   fit one phone row and have never needed to. */
.stat-strip { flex-wrap: wrap; row-gap: 10px; }

/* The filters fold. Desktop never sees the button and never folds. */
.filters-toggle { display: none; }

/* The in-row age lives only where the «Найдено» column does not. */
.age-inline { display: none; }

@media (max-width: 720px) {
  /* The horizontal nav scrolls; its scrollbar was a grey stump under the
     menu on every screen. The nav still scrolls — by finger, as it should. */
  .sidebar { scrollbar-width: none; }
  .sidebar::-webkit-scrollbar { display: none; }

  /* Search takes the whole row instead of hanging off a spacer's edge. */
  .toolbar input[type="search"] { max-width: none !important; flex: 1 1 100%; }

  /* Панель дропдауна не шире экрана, и никогда не уезжает за правый край. */
  .dd-panel { max-width: calc(100vw - 32px); }
  .dd:nth-last-child(-n+2) .dd-panel { left: auto; right: 0; }

  .filters-toggle { display: inline-flex; }
  .filters[data-open="false"] .filters-body { display: none; }

  /* Таблица разбросов складывается в две строки: название сверху, четыре
     четверти и покрытие — под ним. Шесть колонок на 375 пикселях дают по
     сорок точек на число, а числа тут и есть содержание. Шапка при этом
     убирается: подписи «четверть / обычно / три четверти» перестают стоять
     над своими столбцами, и остаться им значило бы врать о том, что где. */
  .spread-row {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    row-gap: 2px; padding: 8px 8px 10px;
  }
  .spread-row > *:first-child { grid-column: 1 / -1; text-align: left; }
  .spread-head { display: none; }
  /* Без шапки колонку надо подписать саму: `title` на телефоне не показать. */
  .spread-row > *:not(:first-child)::after {
    display: block; font-size: 9.5px; color: var(--text-faint);
    font-family: var(--mono); letter-spacing: 0;
  }
  .spread-row > *:nth-child(2)::after { content: "¼"; }
  .spread-row > *:nth-child(3)::after { content: "обычно"; }
  .spread-row > *:nth-child(4)::after { content: "¾"; }
  .spread-row > *:nth-child(5)::after { content: "топ 10%"; }
  .spread-row > *:nth-child(6)::after { content: "есть у"; }

  /* The decision columns and only those: price, title, mark, action.
     «Моё время», «Кто ответит» and «Найдено» live on the card, one tap away —
     while «Взять» behind a horizontal scroll was the main action hidden on
     the main screen. */
  /* Лента прячет «Моё время», «Кто ответит» и «Найдено» — они и так в
     карточке. Таблица скорости пользуется той же вёрсткой, но её колонки 4–6
     («Оценить», «Ответить», «Очередь») — это то, ради чего её открывают, и
     прятать их значит показать с телефона таблицу без ответа. */
  /* Колонка «Отклик» (2) добавлена 2026-08-20 — индексы сдвинулись на +1:
     прячутся она сама и «Моё время»/«Кто ответит»/«Опубликовано» (5–7);
     стоимость отклика на телефоне есть в карточке заявки. */
  .lead-table:not(.speed-table) th:nth-child(2),
  .lead-table:not(.speed-table) td:nth-child(2),
  .lead-table:not(.speed-table) th:nth-child(5),
  .lead-table:not(.speed-table) td:nth-child(5),
  .lead-table:not(.speed-table) th:nth-child(6),
  .lead-table:not(.speed-table) td:nth-child(6),
  .lead-table:not(.speed-table) th:nth-child(7),
  .lead-table:not(.speed-table) td:nth-child(7) { display: none; }
  /* Взамен — таблица скорости прокручивается вбок в своём контейнере, а не
     режется: `.table-scroll` вокруг неё уже стоит. */
  .speed-table { min-width: 520px; }
  .lead-table .lead-title { max-width: 46vw; }
  .age-inline { display: inline; margin-left: 8px; font-size: 11px; }

  /* One thumb-height for every press target in the row. */
  .btn-start { padding: 9px 14px; }

  /* «Взять» rides the right edge, whatever the horizontal scroll does. The
     row still slides under it; the decision button never leaves the thumb. */
  .lead-table td:last-child, .lead-table th:last-child {
    position: sticky; right: 0;
    background: var(--bg-raised);
    box-shadow: -10px 0 10px -8px rgb(0 0 0 / 0.55);
  }
  .lead-table tbody tr:hover td:last-child { background: var(--bg-hover); }

  /* Slimming the row to what the thumb decides by: the native currency and
     the mark's bar are desktop garnish — the rouble figure and the number
     carry the decision on a phone. */
  .price-usd { display: none; }
  .score-bar { display: none; }
  .score { min-width: 0; }
  th, td { padding: 9px 8px; }

  /* The chat fills what the browser chrome leaves, and the composer stays
     reachable above the keyboard. */
  .chat-main { height: calc(100dvh - 170px); }
}

/* ── the dialog list ──────────────────────────────────────────────────────
   The messenger face of the inbox: who, when, and the last line said. */
.dlg-row { align-items: stretch; }
.dlg-row .preview { margin-top: 4px; max-width: 100%; }
.dlg-prefix {
  font-family: var(--mono); font-size: 11px; color: var(--warn);
}
.dlg-row .dlg-prefix:first-child { color: var(--text-dim); }

/* ── the notch ────────────────────────────────────────────────────────────
   Installed on a home screen the page runs edge-to-edge, and the status bar
   sat ON the navigation — the menu was unusable on exactly the surface the
   PWA exists for. env() is zero in a plain browser tab, so nothing shifts
   anywhere else. */
.sidebar { padding-top: calc(var(--pad) + env(safe-area-inset-top)); }
.toasts { bottom: calc(16px + env(safe-area-inset-bottom)); }
@media (max-width: 720px) {
  .sidebar { padding-top: calc(8px + env(safe-area-inset-top)); }
  .composer { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
  .content { padding-bottom: calc(var(--pad) + env(safe-area-inset-bottom)); }

  /* Доска — в столбик.
   *
   * Десять колонок по 268 пикселей — это полотно шириной под три тысячи,
   * которое на телефоне листается вбок вслепую: соседнюю колонку не видно, а
   * перетаскивания, которым карточки двигают, на айфоне не существует вовсе.
   * Столбиком видно этап целиком, а двигают сделку списком на карточке. */
  .board { flex-direction: column; overflow-x: visible; }
  .column { min-width: 0; flex: 1 1 auto; width: 100%; }
  /* Пустые этапы на телефоне только листаются мимо: их шесть из десяти. */
  .column:not(:has(.deal-card)) { display: none; }
}

/* A dialog with an auto-send on the clock: the one row allowed to be loud. */
.dlg-row[data-hot="true"] { box-shadow: inset 3px 0 0 var(--warn); }
.dlg-row .badge .countdown { font-variant-numeric: tabular-nums; }

/* ── из чего сложилась оценка ────────────────────────────────────────────── */

/* Разбор числа на карточке заявки. Оценка складывается из пяти частей —
   выполнимость, деньги за минуту, заказчик, очередь, названа ли цена, — и
   оператор, который с числом не согласен, должен видеть слагаемые, а не
   спорить с чёрным ящиком. */
.score-parts { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.score-part-head {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 13px; margin-bottom: 3px;
}
.score-part .score-bar { display: block; width: 100%; }

/* Предупреждения: платный отклик, поправленное время, выдуманная цена. Их
   место — перед кнопкой «Взять», а не после разговора с клиентом. */
.warnings {
  margin-top: 10px; padding: 8px 10px; border-radius: 8px;
  background: color-mix(in srgb, var(--warn) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent);
}
.warning-line { font-size: 12.5px; color: var(--warn); }
.warning-line + .warning-line { margin-top: 4px; }

/* ── ждут вас ────────────────────────────────────────────────────────────── */

/* Строка рабочего списка: причина, дело, деньги, сколько ждёт. Кнопка, а не
   ссылка — она ведёт внутрь приложения, и ей нужен клавиатурный фокус. */
.waiting-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 9px; border-radius: 8px; text-align: left;
  background: var(--bg-hover); border: 1px solid var(--border);
  color: var(--text); font: inherit; cursor: pointer;
}
.waiting-row:hover { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.waiting-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
