/* Admin UI stylesheet (ADM-P0-08). Self-hosted (CSP style-src 'self'); no inline styles anywhere.
   Responsive: single-column under ~760px; every wide block sits in .table-scroll (no horizontal
   page scroll at 390x844). Theme-aware via prefers-color-scheme. Fixed-size controls (no layout
   shift on HTMX loading). */

:root {
  --bg: #f6f7f9;
  --panel: #ffffff;
  --ink: #1c2128;
  --muted: #5b6470;
  --line: #dfe3e8;
  --accent: #1f6feb;
  --accent-ink: #ffffff;
  --ok-bg: #e7f6ec; --ok-ink: #1a7f37;
  --info-bg: #e8f0fe; --info-ink: #1f4fb0;
  --warn-bg: #fdf3e0; --warn-ink: #9a6700;
  --error-bg: #fdecec; --error-ink: #b42318;
  --radius: 8px;
  --gap: 16px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d1117; --panel: #161b22; --ink: #e6edf3; --muted: #9198a1; --line: #2d333b;
    --accent: #4493f8; --accent-ink: #0d1117;
    --ok-bg: #12261a; --ok-ink: #57ab5a; --info-bg: #12233f; --info-ink: #6cb0ff;
    --warn-bg: #2b2417; --warn-ink: #d9a441; --error-bg: #2c1618; --error-ink: #f27c72;
  }
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 15px/1.5 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
a { color: var(--accent); }
h1 { font-size: 1.6rem; margin: 0 0 .4rem; letter-spacing: -.01em; }
h2 { font-size: 1.15rem; font-weight: 600; margin: 1.2rem 0 .5rem; }
/* `.muted` — ТОЛЬКО цвет. Раньше класс склеивал цвет и кегль, поэтому предупреждение
   «надісланий лист не відкликати» печаталось самым мелким текстом на всём экране. Мелкий
   кегль там, где он действительно уместен (метаданные строк, попытки доставки), задаёт `.small`. */
.muted { color: var(--muted); }
.small { font-size: .85rem; }
/* Мера строки: без ограничения абзац-подсказка растягивался на всю ширину .wrap (~1080px). */
.panel p, .tone { max-width: 76ch; }

.topbar {
  display: flex; flex-wrap: wrap; gap: var(--gap); align-items: center; justify-content: space-between;
  padding: 12px 20px; background: var(--panel); border-bottom: 1px solid var(--line);
}
.topbar .brand { font-weight: 600; color: var(--ink); text-decoration: none; }
.topbar nav { display: flex; gap: 4px; align-items: center; }
/* Текущий раздел подсвечен: три страницы контура выглядели одинаково, «где я» не читалось. */
.topbar nav [aria-current="page"] { color: var(--ink); background: var(--bg); }
.topbar .spacer { flex: 1; }
.wrap { max-width: 1120px; margin: 0 auto; padding: 20px; }
.panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; margin-bottom: var(--gap);
}
/* Панель внутри панели — секция, а не вторая карточка: белое на белом с почти невидимым
   контуром не передавало вложенность и съедало ~36px ширины на каждом уровне (на 390px
   три уровня отнимали 38% экрана). Третий уровень — просто блок без разделителя. */
.panel .panel {
  background: transparent; border: 0; border-radius: 0; padding: 0;
  margin: 14px 0 0; border-top: 1px solid var(--line); padding-top: 14px;
}
.panel .panel .panel { border-top: 0; padding-top: 0; margin-top: 12px; }
.panel > h1:first-child, .panel > h2:first-child { margin-top: 0; }
/* Передперегляд листа — единственный блок, показывающий ЧУЖОЙ контент (тема, вложения, текст
   письма). Сброс вложенных панелей лишил бы его любой границы, и письмо слилось бы с формой
   отправки, поэтому слоту возвращаем собственную рамку. */
#email-preview-slot > .panel {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px; margin-top: 12px;
}
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); }
@media (max-width: 760px) { .grid-2 { grid-template-columns: 1fr; } }

/* forms */
label { display: block; font-weight: 500; margin: .6rem 0 .2rem; }
input[type=text], input[type=search], input[type=password], input[type=date], input[type=email],
select, textarea {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--ink); font: inherit;
  /* На 1920px колонка формы ~540px, и поле под email во всю ширину читалось как «форма
     большая и сложная»; в поля вводят email и 10-значный код, 460px хватает с запасом. */
  max-width: 460px;
}
textarea { min-height: 72px; resize: vertical; }
input[type=checkbox], input[type=radio] { width: 18px; height: 18px; accent-color: var(--accent); }
/* `.row` — только для полей (фильтры): растяжка детей осмысленна для инпутов и калечит кнопки. */
.row { display: flex; flex-wrap: wrap; gap: var(--gap); }
.row > * { flex: 1 1 180px; }
/* `.actions` — ряд действий: ширина кнопки по её тексту, иначе размер (самый сильный сигнал
   приоритета) задавался случайно — одиночная второстепенная кнопка растягивалась на всю панель. */
.actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
fieldset { border: 1px solid var(--line); border-radius: var(--radius); margin: 0 0 var(--gap); padding: 12px 14px; }
legend { padding: 0 6px; color: var(--muted); font-size: .85rem; }
/* Поля получателя нужны только каналу email. Правило скрывает их, когда выбран «Лише
   згенерувати» — обратная логика выбрана намеренно: в браузере без `:has()` селектор не
   сработает и поля останутся видимыми (сегодняшнее поведение), а не исчезнут навсегда. */
fieldset:has(select[name="delivery_channel"] option[value="none"]:checked) .delivery-fields {
  display: none;
}

.btn {
  display: inline-flex; align-items: center; justify-content: center; min-height: 40px;
  padding: 0 16px; border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent); color: var(--accent-ink); font: inherit; font-weight: 600;
  cursor: pointer; text-decoration: none;
}
/* Три веса вместо двух: до этого «Вийти» выглядела как «Новий звіт», а необратимое
   надсилання листа — как «Завантажити HTML». `.btn` — одно главное действие экрана,
   `.secondary` — рядовое, `.ghost` — тихое (выход, пагинация), `.danger` — необратимое. */
.btn.secondary { background: transparent; color: var(--ink); border-color: var(--line); }
.btn.ghost {
  background: transparent; color: var(--muted); border-color: transparent;
  font-weight: 500; padding: 0 10px;
}
.btn.danger { background: var(--error-bg); color: var(--error-ink); border-color: var(--error-ink); }
.btn:hover { filter: brightness(1.08); }
.btn.secondary:hover, .btn.ghost:hover { background: var(--bg); color: var(--ink); filter: none; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; filter: none; }
/* Запрос в полёте отличается от «недоступно»: htmx-шим вешает aria-busy и disabled разом,
   поэтому без своего правила оба состояния выглядели одинаково бледной кнопкой. */
.btn[aria-busy="true"] { opacity: 1; cursor: progress; filter: none; }
.btn[aria-busy="true"]::after {
  content: ""; width: 14px; height: 14px; margin-left: 8px; flex: 0 0 auto;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
/* Клавиатурный фокус: браузерный дефолт на синей заливке .btn в тёмной теме почти не виден. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:focus-visible { outline-offset: 3px; }

/* radios (niche candidates) */
.candidate { display: flex; gap: 10px; align-items: flex-start; padding: 8px; border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 8px; }
.candidate input { margin-top: 4px; }
.candidate .code { font-weight: 600; }

/* badges + tone panels */
.badge {
  display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: .8rem; font-weight: 600;
}
.badge[data-tone=ok], .tone[data-tone=ok] { background: var(--ok-bg); color: var(--ok-ink); }
.badge[data-tone=info], .tone[data-tone=info] { background: var(--info-bg); color: var(--info-ink); }
.badge[data-tone=warn], .tone[data-tone=warn] { background: var(--warn-bg); color: var(--warn-ink); }
.badge[data-tone=error], .tone[data-tone=error] { background: var(--error-bg); color: var(--error-ink); }
.badge[data-tone=muted], .tone[data-tone=muted] { background: var(--bg); color: var(--muted); border: 1px solid var(--line); }
.tone { padding: 12px 14px; border-radius: var(--radius); }
/* Инлайновая тональная пометка внутри строки (ошибка попытки доставки): у обычного .tone
   вертикальный padding 12px на inline-элементе налезал на соседние строки списка. */
.tone.inline { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: .8rem; }
.form-error { color: var(--error-ink); background: var(--error-bg); padding: 10px 12px; border-radius: var(--radius); margin: 8px 0 0; }

/* Глобальная плашка ошибок мутаций (наполняется шимом). `[hidden]` уважаем явно: display у
   .app-error задан, а он перебил бы стандартный display:none у hidden. */
.app-error {
  display: block; margin: 0; padding: 12px 20px; font-weight: 500;
  color: var(--error-ink); background: var(--error-bg);
  border-bottom: 1px solid var(--line);
}
.app-error[hidden] { display: none; }

/* table */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* min-width под 4 колонки (было 7): на 390px таблица теперь помещается без бокового свайпа. */
table { width: 100%; border-collapse: collapse; min-width: 480px; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
/* Без uppercase: это было самое громкое типографическое решение в админке, и оно
   доставалось служебной строке заголовков таблицы, а не заголовкам секций. */
th { color: var(--muted); font-size: .8rem; font-weight: 600; }
tbody tr:hover { background: var(--bg); }

.pager { margin-top: 12px; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; word-break: break-word; }
.stack > * + * { margin-top: 12px; }

/* Раскрытия («Технічні деталі», «Історія», «Деталі покриття»): всё, что нужно при разборе
   инцидента, но не нужно каждый день, живёт здесь — узлы остаются в DOM, экран чистый. */
details > summary {
  cursor: pointer; color: var(--muted); font-weight: 500; padding: 4px 0;
  list-style-position: outside;
}
details > summary:hover { color: var(--ink); }
details[open] > summary { margin-bottom: 8px; }

/* Списки доставок, попыток и аудита: без этих правил браузер рисовал маркеры и
   наследовал padding-left 40px на каждом уровне вложенности. */
.deliveries, .attempts, .audit-timeline { list-style: none; margin: 0; padding: 0; }
.deliveries > li + li { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 12px; }
.delivery-item > p { margin: 0 0 6px; }
.attempts > li, .audit-event {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; font-size: .85rem;
}
.attempts > li + li, .audit-event + .audit-event { margin-top: 6px; }
.audit-actor { font-weight: 500; }
.jobbar { margin: 0; }

/* progress: индикатор генерации отчёта. Воркер рапортует стадии (uati_jobs/generate.py: 15/40/
   75/95 и 100 на финале), но `job_progress_bucket` округляет ЛЮБОЕ значение до 5, поэтому ниже
   лежит полная лестница шагом 5% — недостающее правило схлопнуло бы полосу в ноль. Пока стадия
   не отрапортована, прогресс НЕОПРЕДЕЛЁННЫЙ и полоса бежит. Ширина задаётся здесь, а не
   inline: CSP запрещает style=. */
.progress {
  height: 8px; background: var(--bg); border: 1px solid var(--line);
  border-radius: 999px; overflow: hidden;
}
.progress > span { display: block; height: 100%; border-radius: 999px; background: var(--accent); }

/* Неопределённый (нет data-pct): воркер ещё не рапортовал стадию — бежит подсветка. */
.progress:not([data-pct]) > span {
  width: 38%;
  background: linear-gradient(90deg, transparent, var(--accent) 35%, var(--accent) 65%, transparent);
  animation: progress-slide 1.5s ease-in-out infinite;
}

@keyframes progress-slide {
  from { transform: translateX(-100%); }
  to   { transform: translateX(263%); } /* 263% своей ширины = правый край дорожки */
}

/* Определённый: ширина приходит атрибутом (CSP запрещает inline style), шаг 5%.
   transition сглаживает скачок между поллингами раз в 2 с. */
.progress[data-pct] > span { transition: width .4s ease; }
.progress[data-pct="5"]   > span { width: 5%; }
.progress[data-pct="10"]  > span { width: 10%; }
.progress[data-pct="15"]  > span { width: 15%; }
.progress[data-pct="20"]  > span { width: 20%; }
.progress[data-pct="25"]  > span { width: 25%; }
.progress[data-pct="30"]  > span { width: 30%; }
.progress[data-pct="35"]  > span { width: 35%; }
.progress[data-pct="40"]  > span { width: 40%; }
.progress[data-pct="45"]  > span { width: 45%; }
.progress[data-pct="50"]  > span { width: 50%; }
.progress[data-pct="55"]  > span { width: 55%; }
.progress[data-pct="60"]  > span { width: 60%; }
.progress[data-pct="65"]  > span { width: 65%; }
.progress[data-pct="70"]  > span { width: 70%; }
.progress[data-pct="75"]  > span { width: 75%; }
.progress[data-pct="80"]  > span { width: 80%; }
.progress[data-pct="85"]  > span { width: 85%; }
.progress[data-pct="90"]  > span { width: 90%; }
.progress[data-pct="95"]  > span { width: 95%; }
.progress[data-pct="100"] > span { width: 100%; }

.jobbar__caption { margin: 0 0 10px; font-weight: 500; }
.jobbar__pct {
  margin-left: 8px; color: var(--muted); font-weight: 600;
  font-variant-numeric: tabular-nums; /* цифры не «прыгают» при смене процента */
}

.jobbar__steps {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  list-style: none; margin: 10px 0 0; padding: 0;
  font-size: .82rem; color: var(--muted);
}
.jobbar__steps li { display: flex; align-items: center; gap: 6px; }
.jobbar__steps li + li::before { content: "→"; color: var(--line); }
.jobbar__steps .dot {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--line);
}
.jobbar__steps li[data-status=done] { color: var(--ok-ink); }
.jobbar__steps li[data-status=done] .dot { background: var(--ok-ink); }
.jobbar__steps li[data-status=active] { color: var(--ink); font-weight: 600; }
.jobbar__steps li[data-status=active] .dot {
  background: var(--accent); animation: progress-pulse 1.5s ease-in-out infinite;
}

@keyframes progress-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .3; }
}

/* Никакого движения при prefers-reduced-motion: полоса просто заполнена приглушённым цветом. */
@media (prefers-reduced-motion: reduce) {
  .progress:not([data-pct]) > span {
    width: 100%; background: var(--accent); opacity: .35; animation: none;
  }
  .progress[data-pct] > span { transition: none; }
  .jobbar__steps li[data-status=active] .dot { animation: none; }
  .btn[aria-busy="true"]::after { animation: none; }
}

/* Узкий экран: базовые padding'и (20px у .wrap + 18px у .panel) на 390px съедали треть ширины.
   Широкий: поле ввода email во всю колонку в 540px читается как «форма большая и сложная». */
@media (max-width: 760px) {
  .wrap { padding: 12px; }
  .panel { padding: 14px; }
  .topbar { gap: 8px; padding: 10px 12px; }
}

.checksum { font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; font-size: .8rem; word-break: break-all; }

/* передперегляд листа: показуємо текст як є (переноси автора значущі), але не даємо
   довгому листу розтягнути картку — власна прокрутка замість горизонтального скролу
   сторінки. НЕ .checksum: там word-break: break-all, який рве слова посеред літер. */
.mailtext {
  font-family: inherit; font-size: .85rem; line-height: 1.5; margin: 0;
  white-space: pre-wrap; overflow-wrap: break-word;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px; max-height: 320px; overflow-y: auto;
}
