/* ==========================================================================
   Информирование жителей СВАО: единая система оформления.

   Один файл на все три страницы платформы:

     * панель управления      — ui/index.html;
     * рабочее место оператора — ui/operator.html (+ ui/operator.css,
       где лежит только то, чего нет больше нигде: раскладка разговора,
       таймер, плитки исходов);
     * публичная страница отказа — разметку печатает
       app/api/routers/optout.py, стили берёт отсюда же.

   Раньше страниц было три и систем оформления тоже три: свои токены, свои
   кнопки, свои карточки. Житель, оператор и руководитель округа видели три
   разных продукта, а правка цвета требовала трёх правок в трёх местах.
   Поэтому здесь ровно один набор токенов и один набор базовых компонентов
   (кнопка, карточка, таблица, поле, значок состояния, вкладки, модальное
   окно, пустое состояние), а различия страниц выражены модификаторами:
   `.btn--lg`, `.card--dense`, `.empty--lg`, а не второй вёрсткой.

   Стили вынесены в отдельный файл сознательно: перед API стоит nginx с
   заголовком `Content-Security-Policy: default-src 'self'`, при котором
   браузер не применяет ни <style> в разметке, ни атрибуты style=. Внешний
   файл с того же origin политике соответствует, поэтому всё оформление
   живёт здесь, а динамические размеры (ширина столбика, доля шкалы)
   задаются из JS через CSSOM — его политика содержимого не ограничивает.

   Порядок разделов: токены -> база -> иконки -> шапка -> раскладка ->
   компоненты (карточки, показатели, диаграммы, таблицы, формы) -> экраны ->
   состояния (загрузка, ошибка, пусто) -> модальное окно и вход ->
   публичная страница -> адаптив.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Токены темы
   Числа — главный элемент интерфейса, поэтому фон приглушён, а насыщенный
   цвет остаётся редким: им помечается только то, что требует решения.
   -------------------------------------------------------------------------- */
:root{
  color-scheme: light dark;
  --bg:#eef1f5; --panel:#ffffff; --panel-2:#f6f8fa; --panel-3:#eef2f6;
  --fg:#111725; --muted:#596373; --faint:#8791a1;
  --border:#dfe4ea; --border-strong:#c3ccd8;
  --accent:#1f6feb; --accent-soft:#e8f0ff; --accent-fg:#ffffff;
  --ok:#0d7a45; --ok-soft:#e1f4ea;
  --warn:#96560a; --warn-soft:#fdf0dc;
  --bad:#b3261e; --bad-soft:#fceceb;
  --info:#0b6b82; --info-soft:#e0f2f6;
  /* Ряды диаграмм. Различимы и при чёрно-белой печати справки для управы. */
  --c1:#1f6feb; --c2:#0d7a45; --c3:#6b46c1; --c4:#b8860b;
  --c5:#0b6b82; --c6:#b3261e; --c7:#8a6d3b; --c8:#a2adbb;
  --grid:#e6ebf1;
  --shadow:0 1px 2px rgba(16,24,40,.05), 0 10px 26px rgba(16,24,40,.07);
  --radius:14px; --radius-s:9px;
}
@media (prefers-color-scheme: dark){
  :root{
    --bg:#0c0f16; --panel:#141822; --panel-2:#1a1f2b; --panel-3:#212736;
    --fg:#e6eaf1; --muted:#98a2b3; --faint:#6d7889;
    --border:#242b39; --border-strong:#364051;
    --accent:#5b9bff; --accent-soft:#15243f; --accent-fg:#08111f;
    --ok:#45c58c; --ok-soft:#102a20;
    --warn:#e0a852; --warn-soft:#2c2314;
    --bad:#f0736b; --bad-soft:#2f1817;
    --info:#4dd0e1; --info-soft:#10272c;
    --c1:#5b9bff; --c2:#45c58c; --c3:#a78bfa; --c4:#e0a852;
    --c5:#4dd0e1; --c6:#f0736b; --c7:#c9a227; --c8:#6d7889;
    --grid:#222a38;
    --shadow:0 1px 2px rgba(0,0,0,.45), 0 10px 26px rgba(0,0,0,.35);
  }
}
:root[data-theme="light"]{
  color-scheme: light;
  --bg:#eef1f5; --panel:#ffffff; --panel-2:#f6f8fa; --panel-3:#eef2f6;
  --fg:#111725; --muted:#596373; --faint:#8791a1;
  --border:#dfe4ea; --border-strong:#c3ccd8;
  --accent:#1f6feb; --accent-soft:#e8f0ff; --accent-fg:#ffffff;
  --ok:#0d7a45; --ok-soft:#e1f4ea;
  --warn:#96560a; --warn-soft:#fdf0dc;
  --bad:#b3261e; --bad-soft:#fceceb;
  --info:#0b6b82; --info-soft:#e0f2f6;
  --c1:#1f6feb; --c2:#0d7a45; --c3:#6b46c1; --c4:#b8860b;
  --c5:#0b6b82; --c6:#b3261e; --c7:#8a6d3b; --c8:#a2adbb;
  --grid:#e6ebf1;
  --shadow:0 1px 2px rgba(16,24,40,.05), 0 10px 26px rgba(16,24,40,.07);
}
:root[data-theme="dark"]{
  color-scheme: dark;
  --bg:#0c0f16; --panel:#141822; --panel-2:#1a1f2b; --panel-3:#212736;
  --fg:#e6eaf1; --muted:#98a2b3; --faint:#6d7889;
  --border:#242b39; --border-strong:#364051;
  --accent:#5b9bff; --accent-soft:#15243f; --accent-fg:#08111f;
  --ok:#45c58c; --ok-soft:#102a20;
  --warn:#e0a852; --warn-soft:#2c2314;
  --bad:#f0736b; --bad-soft:#2f1817;
  --info:#4dd0e1; --info-soft:#10272c;
  --c1:#5b9bff; --c2:#45c58c; --c3:#a78bfa; --c4:#e0a852;
  --c5:#4dd0e1; --c6:#f0736b; --c7:#c9a227; --c8:#6d7889;
  --grid:#222a38;
  --shadow:0 1px 2px rgba(0,0,0,.45), 0 10px 26px rgba(0,0,0,.35);
}

/* --------------------------------------------------------------------------
   2. База
   -------------------------------------------------------------------------- */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
/* Атрибут hidden обязан скрывать. Правило браузера `[hidden]{display:none}`
   слабее любого авторского `display`, поэтому карточка входа с display:grid
   и плашки с display:flex оставались бы на экране навсегда. */
[hidden]{display:none !important}
body{
  background:var(--bg); color:var(--fg); min-height:100vh;
  font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
h1,h2,h3{margin:0;font-weight:640;letter-spacing:-.011em}
p{margin:0}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
button{font:inherit;cursor:pointer}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em}
/* Разряды в числах обязательны, и цифры обязаны стоять в колонку. */
.num,.val,td.num,th.num,.tabnum{font-variant-numeric:tabular-nums}
.skip{
  position:absolute;left:-9999px;top:0;padding:10px 16px;background:var(--panel);
  border:1px solid var(--border);border-radius:0 0 var(--radius-s) 0;z-index:100;
}
.skip:focus{left:0}
.grow{flex:1 1 auto;min-width:0}
.sp{flex:1 1 auto}
.mb{margin-bottom:16px}
.muted{color:var(--muted)}
.faint{color:var(--faint)}
.center{text-align:center}
/* Скрытие классом. Атрибут hidden удобнее, но скрипт рабочего места
   переключает именно класс, и правило обязано быть общим для всех страниц. */
.hidden{display:none !important}
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.row--center{justify-content:center}
.row--end{justify-content:flex-end}
.i{display:inline-flex;width:18px;height:18px;flex:none;align-items:center;justify-content:center}
.i svg{width:100%;height:100%;display:block;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
/* Сгенерированные иконки нарисованы заливкой, встроенные — обводкой.
   Правило CSS сильнее презентационного атрибута fill в самом файле,
   поэтому без этой строки подставленная иконка стала бы невидимой. */
.i svg.gen{fill:currentColor;stroke:none}
/* Значок переключателя темы: круг, у которого закрашена половина. Своё
   начертание, потому что значка темы в общем наборе нет, а рисуется он
   границей и заливкой — ни скрипта, ни лишнего символа в спрайте. Один
   класс на обе страницы: кнопка темы обязана выглядеть одинаково и в
   панели, и на рабочем месте оператора. */
.i--theme{
  width:17px;height:17px;border-radius:50%;border:1.7px solid currentColor;
  background:linear-gradient(90deg,transparent 50%,currentColor 50%);
}

/* --------------------------------------------------------------------------
   2а. Иконки из общего спрайта
   Разметка одинакова на всех трёх страницах:
     <svg class="icon" aria-hidden="true"><use href="/ui/assets/icons.svg#icon-call"></use></svg>
   Размер задан в em, а не в пикселях: иконка обязана расти вместе с текстом
   кнопки — на рабочем месте оператора шрифт крупнее, чем в панели, и
   пиксельный размер там смотрелся бы иголкой. Цвет наследуется от текста,
   поэтому одна и та же иконка годится и в тёмной теме, и на цветной плашке.
   Спрайт не загрузился — остаётся пустая коробка в одну строку: вёрстка
   не прыгает, подпись рядом с иконкой остаётся на месте.
   -------------------------------------------------------------------------- */
.icon{
  width:1em;height:1em;flex:none;display:inline-block;
  vertical-align:-.14em;fill:currentColor;color:inherit;pointer-events:none;
}
/* Сам спрайт, встроенный в начало страницы. Из потока он убран размером, а
   не `display:none`: скрытый так набор символов остаётся доступен ссылкам
   `<use>` во всех браузерах, и это самый проверенный способ его спрятать. */
.sprite-host{position:absolute;width:0;height:0;overflow:hidden}
.icon--lg{width:1.35em;height:1.35em;vertical-align:-.25em}
.icon--xl{width:2.4em;height:2.4em;vertical-align:-.5em}
.icon--ok{color:var(--ok)} .icon--warn{color:var(--warn)} .icon--bad{color:var(--bad)}
.icon--accent{color:var(--accent)} .icon--muted{color:var(--muted)}

/* Растровые иллюстрации нарисованы на светлой бумаге. На тёмной карточке
   они превращаются в светящийся прямоугольник, поэтому в тёмной теме не
   показываются вовсе — так же поступает и панель (см. artNode в app.js). */
.art-img{display:block;background:center/contain no-repeat;width:100%}
@media (prefers-color-scheme: dark){.art-img{display:none}}
:root[data-theme="light"] .art-img{display:block}
:root[data-theme="dark"] .art-img{display:none}
.art-img--hero{height:150px;background-image:url("/ui/assets/img/dashboard_hero.png");background-size:cover}
.art-img--empty{height:132px;background-image:url("/ui/assets/img/empty_no_data.png")}
.art-img--done{height:132px;background-image:url("/ui/assets/img/empty_all_done.png")}
.art-img--list{height:132px;background-image:url("/ui/assets/img/empty_no_campaigns.png")}

/* --------------------------------------------------------------------------
   3. Шапка
   -------------------------------------------------------------------------- */
header{
  position:sticky; top:0; z-index:30;
  background:color-mix(in srgb, var(--panel) 90%, transparent);
  backdrop-filter:saturate(1.5) blur(12px);
  border-bottom:1px solid var(--border);
}
.head-in{max-width:1560px;margin:0 auto;padding:11px 22px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:11px;min-width:0}
.logo{
  width:38px;height:38px;border-radius:11px;flex:none;
  background:linear-gradient(140deg,var(--accent),var(--c3));
  display:grid;place-items:center;color:#fff;font-weight:700;font-size:12.5px;letter-spacing:-.4px;
}
.brand-txt{min-width:0}
.brand-txt b{display:block;font-size:14.5px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.brand-txt span{display:block;font-size:12.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:46ch}
.head-field{min-width:190px}
.head-field .field-lbl{display:none}
/* Значок состояния. Один компонент на все три страницы: «данные живые» в
   шапке панели, «свободен / в разговоре / перерыв» у оператора, «отказ
   принят» на публичной странице. Цвет несёт смысл, поэтому рядом всегда
   стоит слово или иконка: по одному цвету состояние не читается. */
.pill,.state{
  display:inline-flex;align-items:center;gap:7px;padding:6px 11px;border-radius:999px;
  border:1px solid var(--border);background:var(--panel-2);font-size:12.5px;color:var(--muted);white-space:nowrap;
}
.state{font-weight:620}
.state--lg{padding:9px 16px;font-size:15px;gap:9px}
.pill.live,.state--ok{
  color:var(--ok);border-color:color-mix(in srgb,var(--ok) 38%,var(--border));background:var(--ok-soft);
}
.pill.live{background:var(--panel-2)}
.pill.stale,.pill.demo,.state--warn{
  color:var(--warn);border-color:color-mix(in srgb,var(--warn) 45%,var(--border));background:var(--warn-soft);
}
.state--bad{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,var(--border));background:var(--bad-soft)}
.state--accent{
  color:var(--accent);border-color:color-mix(in srgb,var(--accent) 40%,var(--border));background:var(--accent-soft);
}
.state--muted{color:var(--muted)}
.dot{width:8px;height:8px;border-radius:50%;background:currentColor;animation:pulse 2.6s ease-in-out infinite}
.state .dot{animation:none;width:10px;height:10px}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.25}}
.icon-btn{
  width:36px;height:36px;border-radius:10px;border:1px solid var(--border);
  background:var(--panel-2);color:var(--fg);display:grid;place-items:center;
}
.icon-btn:hover{border-color:var(--border-strong);background:var(--panel-3)}
.icon-btn.spin .i,.icon-btn.spin .icon{animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* Вкладки. Компонент общий: `.tabs`/`.tab` — сам переключатель, `nav` в
   шапке панели — он же, но растянутый по ширине содержимого страницы. */
.tabs,nav{display:flex;gap:2px;overflow-x:auto;scrollbar-width:none}
.tabs::-webkit-scrollbar,nav::-webkit-scrollbar{display:none}
nav{max-width:1560px;margin:0 auto;padding:0 22px}
.tab,nav button{
  display:inline-flex;align-items:center;gap:8px;
  border:0;background:none;color:var(--muted);padding:11px 13px;font-size:14px;font-weight:520;
  border-bottom:2px solid transparent;white-space:nowrap;
}
.tab:hover,nav button:hover{color:var(--fg)}
.tab[aria-selected="true"],nav button[aria-selected="true"]{
  color:var(--accent);border-bottom-color:var(--accent);font-weight:600;
}
.count,nav .count{
  padding:1px 7px;border-radius:999px;background:var(--panel-2);
  border:1px solid var(--border);font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums;
}
.count.warn,nav .count.warn{
  background:var(--bad-soft);border-color:color-mix(in srgb,var(--bad) 40%,var(--border));color:var(--bad);
}

/* --------------------------------------------------------------------------
   4. Раскладка
   -------------------------------------------------------------------------- */
main{max-width:1560px;margin:0 auto;padding:20px 22px 40px}
.screen{display:none}
.screen.on{display:block;animation:fade .2s ease}
@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.grid{display:grid;gap:16px}
/* Число колонок задано явно, а не auto-fit: показателей на экране «Обзор»
   ровно шесть, и при автоподборе последняя плитка регулярно оставалась одна
   в строке — ряд из пяти и сирота под ним. Шесть, три, два, один делят
   шестёрку нацело на любой ширине. */
.g-kpi{grid-template-columns:repeat(2,minmax(0,1fr))}
@media (min-width:700px){.g-kpi{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (min-width:1240px){.g-kpi{grid-template-columns:repeat(6,minmax(0,1fr))}}
/* На экране состояния показателей восемь: четвёрка делит их поровну. */
#sys-kpi{grid-template-columns:repeat(2,minmax(0,1fr))}
@media (min-width:1000px){#sys-kpi{grid-template-columns:repeat(4,minmax(0,1fr))}}
.g-2{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr)}
.g-half{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.card{
  background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
  padding:18px;box-shadow:var(--shadow);min-width:0;
}
/* Плотная карточка: заголовок и тело — отдельные полосы с собственными
   отступами и разделителем. Так собрано рабочее место оператора, где на
   карточку приходится не абзац текста, а список строк. */
.card--dense{padding:0;overflow:hidden}
.card--dense .card-head{
  padding:14px 18px;margin:0;border-bottom:1px solid var(--border);background:var(--panel-2);
}
.card--dense .card-body{padding:18px}
.card--flat{box-shadow:none}
.card-body{min-width:0}
.card-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:6px}
/* Заголовок раздела: иконка и текст. Флекс, а не строка с пробелом, —
   иначе иконка из спрайта прилипает к первой букве. */
.card-head h2,.card-head h3,.h-sub{
  font-size:12.5px;text-transform:uppercase;letter-spacing:.055em;color:var(--muted);font-weight:640;
  display:flex;align-items:center;gap:8px;
}
.card-head h2,.card-head h3{flex:1 1 auto}
.h-sub{margin:22px 0 12px}
.card .sub,.sub{font-size:12.5px;color:var(--faint);margin-bottom:14px;max-width:78ch}
.hint{font-size:12px;color:var(--faint);margin-top:10px;line-height:1.5}
.chip{
  display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;background:var(--panel-2);
  border:1px solid var(--border);font-size:12px;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums;
}
.chip.ok{background:var(--ok-soft);color:var(--ok);border-color:color-mix(in srgb,var(--ok) 35%,var(--border))}
.chip.bad{background:var(--bad-soft);color:var(--bad);border-color:color-mix(in srgb,var(--bad) 35%,var(--border))}
.chip.warn{background:var(--warn-soft);color:var(--warn);border-color:color-mix(in srgb,var(--warn) 35%,var(--border))}

footer{
  max-width:1560px;margin:0 auto;padding:0 22px 34px;color:var(--faint);font-size:12px;line-height:1.6;
}
.footer-muted{margin-top:4px}

/* --------------------------------------------------------------------------
   5. Показатели
   -------------------------------------------------------------------------- */
.kpi{display:flex;flex-direction:column;gap:2px}
.kpi .label{font-size:12.5px;color:var(--muted);display:flex;align-items:center;gap:7px}
.kpi .val{font-size:32px;font-weight:660;letter-spacing:-.025em;margin:8px 0 2px;line-height:1.05}
.kpi .val.sm{font-size:24px}
.kpi .note{font-size:12.5px;color:var(--faint);min-height:1.2em}
.kpi .val.ok{color:var(--ok)} .kpi .val.warn{color:var(--warn)} .kpi .val.bad{color:var(--bad)}
.kpi .val .unit{font-size:.55em;font-weight:520;color:var(--muted);margin-left:4px;letter-spacing:0}
.bar{height:6px;border-radius:99px;background:var(--panel-3);overflow:hidden;margin-top:12px}
.bar > i{display:block;height:100%;border-radius:99px;background:var(--accent);width:0;transition:width .55s cubic-bezier(.2,.8,.2,1)}
.bar > i.ok{background:var(--ok)} .bar > i.warn{background:var(--warn)} .bar > i.bad{background:var(--bad)}

/* Главный экран заказчика: одно число крупно и разбор рядом. */
.hero{display:grid;grid-template-columns:minmax(0,320px) minmax(0,1fr);gap:26px;align-items:center}
.hero-main .hero-label{font-size:13px;color:var(--muted)}
.hero-main .hero-val{font-size:62px;font-weight:680;letter-spacing:-.035em;line-height:1.02;margin:6px 0 4px}
.hero-main .hero-note{font-size:13px;color:var(--faint);max-width:34ch}
.hero-split{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px}
.hero-split .cellv{border-left:3px solid var(--border);padding:2px 0 2px 12px}
.hero-split .cellv b{display:block;font-size:23px;font-weight:640;letter-spacing:-.02em}
.hero-split .cellv span{font-size:12.5px;color:var(--muted)}
.hero-split .cellv.c1{border-left-color:var(--c1)}
.hero-split .cellv.c2{border-left-color:var(--c2)}
.hero-split .cellv.c3{border-left-color:var(--c3)}
.hero-split .cellv.c4{border-left-color:var(--c4)}
.hero-split .cellv.c5{border-left-color:var(--c5)}
.hero-split .cellv.c6{border-left-color:var(--c6)}

/* --------------------------------------------------------------------------
   6. Воронка
   -------------------------------------------------------------------------- */
.funnel-wide{width:100%;overflow:hidden}
.funnel-wide svg{width:100%;height:auto;display:block}
.funnel-rows{display:none;flex-direction:column;gap:9px}
.fstep{display:grid;grid-template-columns:1fr auto;gap:2px 12px;align-items:center}
.fstep .name{font-size:13px;color:var(--muted)}
.fstep .num{text-align:right;font-weight:620;font-size:14px}
.fstep .num small{display:block;font-weight:400;color:var(--faint);font-size:11px}
.fstep .track{grid-column:1 / -1;height:20px;border-radius:6px;background:var(--panel-3);overflow:hidden}
.fstep .fill{height:100%;border-radius:6px;width:0;transition:width .6s cubic-bezier(.2,.8,.2,1)}
.converge{
  margin-top:16px;padding:12px 14px;border-radius:var(--radius-s);background:var(--panel-2);
  border:1px solid var(--border);font-size:13px;line-height:1.6;
}
.converge.bad{background:var(--bad-soft);border-color:color-mix(in srgb,var(--bad) 35%,var(--border))}
.converge .eq{color:var(--muted);font-size:12.5px;margin-top:6px}
.converge b{font-variant-numeric:tabular-nums}

/* Заливки рядов диаграмм: цвет задаётся классом, а не атрибутом —
   инлайновые стили запрещены политикой содержимого. */
.f1{fill:var(--c1)} .f2{fill:var(--c2)} .f3{fill:var(--c3)} .f4{fill:var(--c4)}
.f5{fill:var(--c5)} .f6{fill:var(--c6)} .f7{fill:var(--c7)} .f8{fill:var(--c8)}
.f-ok{fill:var(--ok)} .f-warn{fill:var(--warn)} .f-bad{fill:var(--bad)}
.f-muted{fill:var(--faint)} .f-track{fill:var(--panel-3)} .f-fg{fill:var(--fg)}
.s1{stroke:var(--c1)} .s2{stroke:var(--c2)} .s3{stroke:var(--c3)} .s4{stroke:var(--c4)}
.s5{stroke:var(--c5)} .s6{stroke:var(--c6)} .s7{stroke:var(--c7)} .s8{stroke:var(--c8)}
.s-ok{stroke:var(--ok)} .s-bad{stroke:var(--bad)} .s-accent{stroke:var(--accent)}
.b1{background:var(--c1)} .b2{background:var(--c2)} .b3{background:var(--c3)} .b4{background:var(--c4)}
.b5{background:var(--c5)} .b6{background:var(--c6)} .b7{background:var(--c7)} .b8{background:var(--c8)}
.b-ok{background:var(--ok)} .b-warn{background:var(--warn)} .b-bad{background:var(--bad)}
.b-muted{background:var(--faint)}

/* --------------------------------------------------------------------------
   7. Диаграммы
   -------------------------------------------------------------------------- */
.chart{width:100%;overflow-x:auto}
.chart svg{display:block;width:100%;height:auto;min-width:0}
.chart svg.wide{min-width:520px}
/* Подписи диаграмм. Размер задаётся классом, а не атрибутом font-size:
   любое правило CSS перебивает презентационный атрибут SVG, и подпись,
   выставленная атрибутом, молча уехала бы к размеру класса. */
.ax{fill:var(--muted);font-size:11px}
.ax-strong{fill:var(--fg);font-size:11.5px;font-weight:600}
.ax-num{fill:var(--fg);font-size:11.5px;font-weight:600;font-variant-numeric:tabular-nums}
.ax-big{fill:var(--fg);font-size:15.5px;font-weight:660;font-variant-numeric:tabular-nums}
.dv{fill:var(--fg);font-size:21px;font-weight:700;font-variant-numeric:tabular-nums}
.dl{fill:var(--muted);font-size:11px}
.gl{stroke:var(--grid);stroke-width:1}
.gl-dash{stroke:var(--border-strong);stroke-width:1;stroke-dasharray:3 4}
.legend{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:14px}
.legend div{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--muted)}
.legend i{width:10px;height:10px;border-radius:3px;flex:none}
.legend b{color:var(--fg);font-variant-numeric:tabular-nums}
.legend-col{flex-direction:column;gap:8px;align-items:stretch}
.legend-col div{justify-content:flex-start}
.legend-col b{margin-left:auto}
.donut-wrap{display:flex;gap:20px;align-items:center;flex-wrap:wrap}
.donut-wrap svg{width:168px;height:168px;flex:none}

/* Столбцы по районам: подпись, полоса, число. */
.rowbars{display:flex;flex-direction:column;gap:7px}
.rowbar{display:grid;grid-template-columns:minmax(120px,168px) minmax(0,1fr) 92px;gap:12px;align-items:center;
  padding:2px 0;border-radius:6px}
.rowbar:hover{background:var(--panel-2)}
.rowbar.lag .rname{color:var(--bad);font-weight:600}
.rname{font-size:13px;color:var(--fg);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rtrack{height:18px;border-radius:5px;background:var(--panel-3);overflow:hidden;display:flex}
.rtrack i{display:block;height:100%;width:0;transition:width .5s cubic-bezier(.2,.8,.2,1)}
.rval{text-align:right;font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}
.rval small{display:block;font-weight:400;color:var(--faint);font-size:11px}

/* Полоса прогноза «сейчас -> срок». */
.timeline-note{display:flex;flex-wrap:wrap;gap:8px 20px;margin-top:12px;font-size:12.5px;color:var(--muted)}
.kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:14px}
.kv > div .k{font-size:12.5px;color:var(--muted)}
.kv > div .v{font-size:21px;font-weight:640;letter-spacing:-.02em;margin-top:3px;font-variant-numeric:tabular-nums}
.kv > div .v.sm{font-size:15px;font-weight:600}
.kv > div .v.ok{color:var(--ok)} .kv > div .v.bad{color:var(--bad)} .kv > div .v.warn{color:var(--warn)}

/* --------------------------------------------------------------------------
   8. Таблицы
   Свой скроллпорт: без него sticky-заголовок таблицы прилипает к окну и
   налезает на шапку страницы, которая тоже sticky.
   -------------------------------------------------------------------------- */
.tw{overflow:auto;max-height:72vh;border-radius:var(--radius-s);border:1px solid var(--border)}
table{width:100%;border-collapse:collapse;font-size:13.5px;min-width:680px}
thead th{
  position:sticky;top:0;z-index:1;background:var(--panel-2);text-align:left;padding:10px 12px;
  font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);
  border-bottom:1px solid var(--border);white-space:nowrap;cursor:pointer;user-select:none;
}
thead th.no-sort{cursor:default}
thead th[data-dir]{color:var(--accent)}
thead th[data-dir]::after{content:" ↓"}
thead th[data-dir="1"]::after{content:" ↑"}
tbody td{padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:middle}
tbody tr:last-child td{border-bottom:0}
tbody tr:hover{background:var(--panel-2)}
tbody tr.lag td{background:color-mix(in srgb,var(--bad-soft) 55%,transparent)}
tbody tr.lag:hover td{background:var(--bad-soft)}
tfoot td{padding:10px 12px;border-top:1px solid var(--border-strong);background:var(--panel-2);font-weight:640}
td.num,th.num{text-align:right;white-space:nowrap}
td.name b{font-weight:600}
.mini{display:inline-block;width:72px;height:7px;border-radius:99px;background:var(--panel-3);overflow:hidden;vertical-align:middle}
.mini > i{display:block;height:100%;background:var(--accent);width:0;border-radius:99px}
.tag{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border-radius:6px;font-size:11.5px;
  font-weight:600;white-space:nowrap;background:var(--panel-3);color:var(--muted)}
.tag--lg{padding:6px 13px;border-radius:999px;font-size:14px}
.tag.ok{background:var(--ok-soft);color:var(--ok)}
.tag.warn{background:var(--warn-soft);color:var(--warn)}
.tag.bad{background:var(--bad-soft);color:var(--bad)}
.tag.info{background:var(--info-soft);color:var(--info)}
.tag.accent{background:var(--accent-soft);color:var(--accent)}

/* --------------------------------------------------------------------------
   9. Формы и кнопки
   -------------------------------------------------------------------------- */
.filters{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;margin-bottom:14px}
.field{display:flex;flex-direction:column;gap:4px;min-width:0}
.field-lbl{
  display:flex;align-items:center;gap:6px;
  font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--faint);
}
select,textarea,input[type="text"],input[type="search"],input[type="password"],input[type="tel"]{
  font:inherit;font-size:13.5px;padding:8px 11px;border-radius:var(--radius-s);
  border:1px solid var(--border);background:var(--panel-2);color:var(--fg);min-width:0;width:100%;
}
select:hover,textarea:hover,input:hover{border-color:var(--border-strong)}
input::placeholder,textarea::placeholder{color:var(--faint)}
textarea{resize:vertical;min-height:64px;line-height:1.5}
/* Крупное поле. Шрифт не ниже 16 пикселей осознанно: Safari на телефоне
   приближает страницу при фокусе в поле с более мелким текстом, и
   публичная страница отказа начинала бы работу с рывка. */
.field--lg select,.field--lg textarea,.field--lg input,input.input--lg,textarea.input--lg{
  font-size:17px;padding:14px 16px;border-radius:12px;border-color:var(--border-strong);
}
.field--lg .field-lbl{font-size:12px}
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  padding:8px 14px;border-radius:var(--radius-s);border:1px solid var(--border);
  background:var(--panel-2);color:var(--fg);font-size:13.5px;font-weight:530;white-space:nowrap;
}
.btn:hover:not(:disabled){border-color:var(--border-strong);background:var(--panel-3)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.primary,.btn--primary{background:var(--accent);border-color:var(--accent);color:var(--accent-fg)}
.btn.primary:hover:not(:disabled),.btn--primary:hover:not(:disabled){filter:brightness(1.07)}
.btn--ok{background:var(--ok);border-color:var(--ok);color:#fff}
.btn--ok:hover:not(:disabled){filter:brightness(1.07);background:var(--ok);border-color:var(--ok)}
.btn.ghost,.btn--ghost{background:none}
.btn.danger,.btn--danger{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 35%,var(--border))}
.btn.danger:hover:not(:disabled),.btn--danger:hover:not(:disabled){
  background:var(--bad-soft);border-color:var(--bad);
}
.btn.toggle[aria-pressed="true"]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}
/* Размерные модификаторы. Рабочее место оператора собрано на них, а не на
   собственной вёрстке: смена идёт восемь часов, промах по кнопке стоит
   разговора, поэтому кнопка там крупная — но это та же кнопка. */
.btn--lg{padding:13px 20px;font-size:16px;border-radius:12px;font-weight:600}
.btn--xl{padding:19px 34px;font-size:19px;border-radius:16px;font-weight:700;min-width:270px}
.btn--block{display:flex;width:100%}
.seg{display:inline-flex;border:1px solid var(--border);border-radius:999px;background:var(--panel-2);padding:2px}
.seg button{
  border:0;background:none;color:var(--muted);font-size:12.5px;padding:5px 12px;border-radius:999px;white-space:nowrap;
}
.seg button.on{background:var(--panel);color:var(--fg);font-weight:600;box-shadow:0 1px 2px rgba(16,24,40,.08)}

/* Переключатель «черновик / в эфире»: состояние читается без подписи. */
.sw{display:inline-flex;align-items:center;gap:9px;cursor:pointer;user-select:none}
.sw input{position:absolute;opacity:0;width:0;height:0}
.sw .track{width:44px;height:25px;border-radius:999px;background:var(--panel-3);border:1px solid var(--border);
  position:relative;transition:background .18s ease,border-color .18s ease;flex:none}
.sw .track::after{
  content:"";position:absolute;top:2px;left:2px;width:19px;height:19px;border-radius:50%;
  background:var(--panel);box-shadow:0 1px 3px rgba(16,24,40,.3);transition:transform .18s ease;
}
.sw input:checked + .track{background:var(--ok);border-color:var(--ok)}
.sw input:checked + .track::after{transform:translateX(19px)}
.sw input:focus-visible + .track{outline:2px solid var(--accent);outline-offset:2px}
.sw input:disabled + .track{opacity:.5;cursor:not-allowed}
.sw .sw-lbl{font-size:13px;font-weight:600}

/* --------------------------------------------------------------------------
   10. Ролики
   -------------------------------------------------------------------------- */
.cov{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));gap:10px}
.cov .cell{
  border:1px solid var(--border);border-radius:var(--radius-s);padding:11px 12px;background:var(--panel-2);
  display:flex;flex-direction:column;gap:3px;
}
.cov .cell b{font-size:13.5px;font-weight:600}
.cov .cell span{font-size:11.5px;color:var(--muted)}
.cov .cell.ready{border-color:color-mix(in srgb,var(--ok) 40%,var(--border));background:var(--ok-soft)}
.cov .cell.ready span{color:var(--ok)}
.cov .cell.miss{border-color:color-mix(in srgb,var(--bad) 40%,var(--border));background:var(--bad-soft)}
.cov .cell.miss span{color:var(--bad)}

.creative{border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px;background:var(--panel-2)}
.creative.live{border-color:color-mix(in srgb,var(--ok) 45%,var(--border))}
.creative .top{display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
.creative .top .cname{font-size:15.5px;font-weight:640;margin-right:2px}
.creative .top .sp{flex:1 1 auto}
.creative .text{
  font-size:14.5px;line-height:1.65;background:var(--panel);border:1px solid var(--border);
  border-radius:var(--radius-s);padding:13px 15px;white-space:pre-wrap;
}
.creative .text .ph{background:var(--warn-soft);color:var(--warn);border-radius:5px;padding:0 5px;font-weight:600}
.creative .meta{display:flex;gap:8px 18px;flex-wrap:wrap;margin-top:10px;font-size:12px;color:var(--faint)}
.creative .acts{display:flex;gap:9px;flex-wrap:wrap;margin-top:12px;align-items:center}
.creative audio{width:100%;margin-top:11px;display:none}
.creative audio.on{display:block}
.creative .ph-note{margin-top:10px;font-size:12.5px;color:var(--warn);display:flex;gap:7px;align-items:flex-start}

/* --------------------------------------------------------------------------
   11. Состояние системы
   -------------------------------------------------------------------------- */
.nodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px}
.node{border:1px solid var(--border);border-radius:var(--radius-s);padding:11px 12px;background:var(--panel-2);
  display:flex;gap:10px;align-items:flex-start}
.node .led{width:9px;height:9px;border-radius:50%;flex:none;margin-top:5px;background:var(--faint)}
.node.ok .led{background:var(--ok)} .node.warn .led{background:var(--warn)} .node.down .led{background:var(--bad)}
.node.down{border-color:color-mix(in srgb,var(--bad) 40%,var(--border));background:var(--bad-soft)}
.node.warn{border-color:color-mix(in srgb,var(--warn) 40%,var(--border));background:var(--warn-soft)}
.node b{display:block;font-size:13px;font-weight:600}
.node span{display:block;font-size:11.5px;color:var(--muted);margin-top:2px;overflow-wrap:anywhere}
.alert{display:flex;gap:11px;padding:11px 0;border-bottom:1px solid var(--border);align-items:flex-start}
.alert:last-child{border-bottom:0}
.alert .sev{flex:none;margin-top:1px}
.alert .atx b{display:block;font-size:13.5px;font-weight:600}
.alert .atx span{font-size:12.5px;color:var(--muted)}

/* --------------------------------------------------------------------------
   12. Состояния: загрузка, ошибка, пусто
   -------------------------------------------------------------------------- */
.banner{
  display:flex;gap:11px;align-items:flex-start;padding:12px 14px;border-radius:var(--radius-s);
  border:1px solid color-mix(in srgb,var(--bad) 40%,var(--border));background:var(--bad-soft);
  color:var(--bad);font-size:13.5px;margin-bottom:16px;
}
.banner.warn{border-color:color-mix(in srgb,var(--warn) 45%,var(--border));background:var(--warn-soft);color:var(--warn)}
.banner .btx{flex:1 1 auto}
.banner .btx b{display:block}
.banner .btx span{display:block;color:var(--muted);margin-top:2px;font-size:12.5px}

.sk{background:linear-gradient(90deg,var(--panel-3) 25%,var(--panel-2) 37%,var(--panel-3) 63%);
  background-size:400% 100%;animation:sh 1.3s ease-in-out infinite;border-radius:7px}
@keyframes sh{0%{background-position:100% 0}100%{background-position:0 0}}
.sk-line{height:13px;margin:9px 0}
.sk-val{height:30px;width:60%;margin:10px 0 6px}
.sk-block{height:180px;margin-top:6px}
.sk-row{height:19px;margin:8px 0}
@media (prefers-reduced-motion: reduce){
  .sk{animation:none} .dot{animation:none} .screen.on{animation:none}
  .bar > i,.rtrack i,.fstep .fill{transition:none}
}

/* Пустое состояние. Иллюстрация, заголовок, объяснение и — если человеку
   есть что сделать — кнопка. Пустой экран без объяснения читается как
   поломка, поэтому текст здесь обязателен, а картинка помогает отличить
   «данных ещё нет» от «всё сделано». */
.empty{
  display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center;
  padding:34px 18px;color:var(--muted);font-size:13.5px;
}
.empty--sm{padding:22px 14px;gap:8px}
.empty--lg{padding:52px 24px;gap:16px}
/* Пустая таблица: ячейка обязана остаться ячейкой, иначе строка ломается. */
td.empty{display:table-cell;text-align:center;padding:24px 12px;color:var(--muted)}
.empty .art{width:150px;max-width:60%;opacity:.9}
.empty .art img{width:100%;height:auto;display:block;border-radius:var(--radius-s)}
.empty .art svg{width:100%;height:auto;display:block}
.empty .art-img{width:min(230px,64%);opacity:.92}
.empty b,.empty h2{color:var(--fg);font-size:15px;font-weight:600}
.empty--lg h2{font-size:24px;letter-spacing:-.02em}
.empty p{max-width:46ch;line-height:1.6;margin:0}
.err-box{
  display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;padding:28px 18px;
  color:var(--bad);font-size:13.5px;
}
.err-box b{font-size:15px}
.err-box span{color:var(--muted);max-width:52ch}

/* --------------------------------------------------------------------------
   13. Модальное окно и вход
   `.overlay` — затемнение поверх страницы, `.modal` — то же самое под общим
   именем: окно подтверждения и карточка входа устроены одинаково.
   -------------------------------------------------------------------------- */
.overlay,.modal{
  position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:20px;
  background:color-mix(in srgb,var(--bg) 78%,transparent);backdrop-filter:blur(6px);
}
.login-card,.modal__card{
  width:min(460px,100%);background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
  padding:26px;box-shadow:var(--shadow);max-height:calc(100vh - 40px);overflow:auto;
}
.modal__card h2{font-size:20px;margin-bottom:8px}
.modal__acts,.login-acts{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}
/* Заставка на входе: та же иллюстрация маршрутизации сообщения по районам,
   что и на обложке отчёта. Полоса во всю ширину карточки, поэтому
   отрицательные отступы гасят её собственный паддинг. */
.login-hero{margin:-26px -26px 18px;border-radius:var(--radius) var(--radius) 0 0}
.login-art{width:96px;margin:0 auto 14px}
/* Под заставкой замок становится значком, а не второй картинкой.
   В тёмной теме заставка не показывается, и замок возвращает свой размер. */
.login-hero + .login-art{width:60px}
@media (prefers-color-scheme: dark){.login-hero + .login-art{width:96px}}
:root[data-theme="light"] .login-hero + .login-art{width:60px}
:root[data-theme="dark"] .login-hero + .login-art{width:96px}
.login-art svg,.login-art img{width:100%;height:auto;display:block}
.login-card h2{font-size:20px;margin-bottom:8px;text-align:center}
.login-card .sub{font-size:13px;color:var(--muted);line-height:1.6;margin-bottom:18px}
.login-err{margin-top:12px;color:var(--bad);font-size:13px}

/* --------------------------------------------------------------------------
   13а. Публичная страница отказа
   Единственная страница платформы, которую видит житель, и заходят на неё
   почти всегда с телефона. Отсюда одна колонка, крупное поле и кнопка во
   всю ширину: попасть пальцем нужно с первого раза.
   -------------------------------------------------------------------------- */
.public-wrap{max-width:560px;margin:0 auto;padding:40px 18px 32px}
.public-card{padding:26px 24px}
.public-card h1{font-size:23px;line-height:1.3;margin:0 0 14px;display:flex;gap:10px;align-items:flex-start}
.public-card h1 .icon{margin-top:.14em}
.public-card p{color:var(--muted);margin:0 0 14px;font-size:15.5px;line-height:1.6}
.public-card p.ok{color:var(--ok);font-weight:640}
.public-card p.err{color:var(--bad);font-weight:640}
.public-card form{margin-top:18px}
.public-foot{max-width:560px;margin:22px auto 0;font-size:13px;color:var(--faint);line-height:1.6}
@media (max-width:520px){
  .public-wrap{padding:22px 14px 26px}
  .public-card{padding:20px 18px}
  .public-card h1{font-size:20.5px}
}

/* --------------------------------------------------------------------------
   14. Адаптив
   На узком экране воронка перестаёт быть силуэтом и становится строками:
   трапеции с подписями в 360 точках нечитаемы.
   -------------------------------------------------------------------------- */
@media (max-width:1180px){
  .g-2{grid-template-columns:1fr}
}
@media (max-width:900px){
  .hero{grid-template-columns:1fr;gap:18px}
  .hero-main .hero-val{font-size:48px}
  .funnel-wide{display:none}
  .funnel-rows{display:flex}
  .rowbar{grid-template-columns:minmax(96px,1fr) minmax(0,1.4fr) 78px;gap:9px}
}
@media (max-width:720px){
  main{padding:16px 14px 32px}
  .head-in{padding:10px 14px;gap:10px}
  nav{padding:0 14px}
  footer{padding:0 14px 28px}
  .card{padding:15px}
  .brand-txt span{max-width:22ch}
  .head-field{order:5;min-width:100%}
  .kpi .val{font-size:27px}
  .donut-wrap svg{width:140px;height:140px}
}
