/* ============================================================================
   admin.css — дизайн-система админки Кадрума (Jinja2 + HTMX, без сборки).

   Оглавление
    1. Токены (:root); тёмные значения — admin-dark.css
    2. Reset / база / типографика
    3. Лейаут: .container, .topbar, .theme-switch, #progress, .page-head
    4. Карточки: .card, .stat-card, .chip, .attention
    5. Кнопки: .btn*
    6. Поля форм: .input/.select/.textarea, .field, .char-count, .grid-*, .toggle, .checkbox
    7. Бейджи: .badge*, статусы
    8. Таблицы: .table-wrap, .table, строки, .thumb-strip, .row-actions
    9. Фильтры, .list-meta, пейджер .pager (partials/pager.html), .bulk-bar
   10. Форма: .form-layout, .form-aside, .section, .save-bar
   11. Медиа: .slot*, .slot-grid, .preview-list, sortable, .app-card
   12. Комбобокс
   13. Рубрики, типы персон
   14. Тосты, спиннер, HTMX-индикаторы, empty-state
   15. Логин, страница ошибки
   16. Утилиты
   17. Экран /prompts: колонки, ячейки, фильтры, bulk-bar
   18. Форма промта: табы, тумблеры, слоты, превью «как в приложении»
   19. Экран /videos
   20. Форма видео: комбобокс, генерация, медиа, превью
   21. Адаптив: 21.1 планшет (≤1023), 21.2 телефон (≤767) и нижний таббар, 21.3 шторка .sheet,
       21.4 касания и iOS, 21.5 списки /prompts и /videos: шторка фильтров, чипы сортировки, карточки,
       bulk-bar, пагинация, 21.6 формы: сетки, табы 3×2, слоты, медиа видео, комбобокс, панель сохранения,
       шторка превью «Как в приложении», 21.7 дашборд и справочники: карточки статистики, строки и инлайн-форма
       рубрик, сетка типов персон
   22. Экран /tasks: тон статуса задания, панель (поиск, период, фильтры), сводка, чипы фильтров, строки, состояния,
       время страницы в пейджере; до 1023px — липкая панель, шторка периода, строки → карточки (вся карточка — ссылка)
   23. Карточка задания /tasks/{id}: липкая шапка (соседи, копирование id), вердикт, кадры, медиа, пользователь, запрос,
       провайдер, воркер, цепочка, все поля; от 1024px две колонки (медиа слева), до 767px — компактная шапка
   24. Поиск /tasks/find: строка поиска, карточки совпадений на выбор, форматы; «Спросить провайдера» в карточке задания:
       кнопка, ответ по каждому коду (тон, состояние, ошибка, факты, ссылки на результат); касание и телефон

   :hover — только внутри @media (hover: hover): на iOS наведение «залипает» после тапа.
   ========================================================================== */

/* 1. Токены ---------------------------------------------------------------- */
/* Onest — шрифт дизайна приложения (SIL OFL 1.1, Google Fonts); вариативный, один файл на подмножество. */
@font-face { font-family: "Onest"; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url("fonts/onest-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
@font-face { font-family: "Onest"; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url("fonts/onest-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; }
@font-face { font-family: "Onest"; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url("fonts/onest-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Onest"; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url("fonts/onest-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
:root {
  color-scheme: light; /* нативные скроллбары/селекты; тёмная — в admin-dark.css */
  --font: "Onest", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --fs-s: 12px;
  --fs: 13px;
  --fs-l: 15px;
  --header-h: 52px;
  /* Безопасные зоны iPhone (вырез, home-индикатор; нужен viewport-fit=cover). На десктопе env() = 0.
     --top-h — шапка вместе с зоной статус-бара: всё липкое под шапкой ставим на top: var(--top-h). */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --top-h: calc(var(--header-h) + var(--safe-t));
  --gutter: 24px; /* боковой отступ .topbar и .container */
  /* Нижний таббар телефона (раздел 21): высота, отступ от низа над home-индикатором;
     --dock — сколько он занимает снизу (0, где таббара нет). */
  --tabbar-h: 60px;
  --tabbar-b: max(12px, calc(var(--safe-b) - 8px));
  --dock: 0px;
  /* Скругления дизайна (8/10/…/16), уменьшенные под плотные контролы админки. */
  --radius-s: 8px;
  --radius: 10px;
  --radius-l: 16px;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --shadow-1: 0 1px 2px rgba(28, 21, 53, .05), 0 1px 3px rgba(28, 21, 53, .07);
  --shadow-2: 0 8px 24px rgba(28, 21, 53, .12), 0 1px 3px rgba(28, 21, 53, .08);
  --shadow-up: 0 -4px 16px rgba(28, 21, 53, .08);

  /* Палитра дизайна приложения (a31.js: LIGHT): фон #FBFAFF, текст #1C1535, акцент #D068CC.
     Нейтрали — «чернила» rgba(28,21,53,α) дизайна поверх фона, сведённые к непрозрачным цветам
     (липкие шапки, выпадашки и тосты не должны просвечивать). */
  --bg: #fbfaff;
  --surface: #ffffff;
  --surface-2: #f3f1f8;
  --surface-3: #e9e7ef;
  --ref-bg: #9a97a3; /* фон референса (PNG с альфой): средне-серый — видны и белые, и тёмные детали */
  --border: #e7e5ed;
  --border-strong: #d5d2dd;
  --text: #1c1535;
  --text-muted: #726e82;
  --text-faint: #898596;
  --on-primary: #ffffff;
  --primary: #d068cc;
  --primary-hover: #c257be;
  --primary-text: #a6429f; /* акцент для текста и ссылок: #D068CC на светлом фоне даёт контраст ~3:1 */
  --primary-soft: rgba(208, 104, 204, .14);
  --danger: #b82727;
  --danger-hover: #9e1f1f;
  --danger-soft: rgba(180, 28, 28, .08);
  --success: #217e46;
  --success-soft: rgba(22, 163, 74, .12);
  --warning: #8b561a;
  --warning-soft: rgba(161, 102, 9, .10);
  --info: #3563c9;
  --info-soft: rgba(53, 99, 201, .10);
  --hl: #fff1c2;
  --focus-ring: 0 0 0 3px rgba(208, 104, 204, .30);

  /* Статусы промтов: 1 тест, 2 опубликован, 3 отключен, 4 генерация, 5 «хуета», 6 резерв, 7 архив */
  --st-p1: #b45309;
  --st-p2: #15803d;
  --st-p3: #6b7280;
  --st-p4: #1d4ed8;
  --st-p5: #b91c1c;
  --st-p6: #6d28d9;
  --st-p7: #475569;
  /* Статусы видео: 1 тест, 2 опубликован, 3 временно отключен, 4 «хуета» */
  --st-v1: #b45309;
  --st-v2: #15803d;
  --st-v3: #6b7280;
  --st-v4: #b91c1c;
  --st-special: #7c3aed;
  --st-ultra: #a16207;
  --st-dry: #0f766e;
  --st-muted: #6b7684;
  /* Статусы заданий (/tasks): 1–2 в работе, 3 готово, 4 ошибка, 5 ошибка создания, зависло (py 1/2 с признаком) */
  --st-t-run: #3563c9;
  --st-t-ok: #15803d;
  --st-t-fail: #b91c1c;
  --st-t-cfail: #9a3412;
  --st-t-stuck: #b45309;
}

/* 2. Reset / база ---------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; }
body {
  margin: 0;
  font: 400 var(--fs)/1.45 var(--font);
  color: var(--text);
  background: var(--bg);
  min-width: 1024px;
}
h1, h2, h3, h4 { margin: 0 0 var(--sp-3); font-weight: 700; line-height: 1.25; letter-spacing: -.01em; }
h1 { font-size: 20px; letter-spacing: -.015em; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
p { margin: 0 0 var(--sp-3); }
a { color: var(--primary-text); text-decoration: none; }
@media (hover: hover) { a:hover { text-decoration: underline; } }
img, svg, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
code, .mono { font-family: var(--mono); font-size: 0.92em; }
small { font-size: var(--fs-s); }
ul, ol { margin: 0; padding: 0; }
table { border-collapse: collapse; }
:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.icon { width: 16px; height: 16px; flex: none; vertical-align: -3px; display: inline-block; }
.icon-spin { transform-origin: 8px 8px; animation: spin .8s linear infinite; }
.inline-form { display: inline; margin: 0; }

/* 3. Лейаут ---------------------------------------------------------------- */
.container { max-width: 1600px; margin: 0 auto; padding: 16px max(var(--gutter), var(--safe-r)) 32px max(var(--gutter), var(--safe-l)); }
/* Снизу место под плавающую панель массовых действий (.bulk-bar, fixed) — только пока она видна (выбраны строки
   в промтах или видео); в остальное время отступ как у заданий (владелец 27.09.2026). */
.container:has(.bulk-bar.is-visible) { padding-bottom: 96px; }

.topbar {
  position: sticky; top: 0; z-index: 50;
  height: var(--top-h); /* сверху — зона статус-бара (safe-area), содержимое — ниже */
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--safe-t) max(var(--gutter), var(--safe-r)) 0 max(var(--gutter), var(--safe-l));
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  -webkit-backdrop-filter: saturate(1.4) blur(12px); backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--border);
}
.topbar-brand { display: flex; align-items: center; gap: var(--sp-2); font-weight: 600; color: var(--text); white-space: nowrap; }
@media (hover: hover) { .topbar-brand:hover { text-decoration: none; } }
.topbar-brand img { flex: none; }
.topbar-nav { display: flex; gap: var(--sp-1); margin-left: var(--sp-3); }
.topbar-nav a { padding: 6px 10px; border-radius: var(--radius); color: var(--text-muted); font-weight: 500; }
@media (hover: hover) { .topbar-nav a:hover { color: var(--text); background: var(--surface-2); text-decoration: none; } }
.topbar-nav a.active { color: var(--primary-text); background: var(--primary-soft); font-weight: 600; }
.topbar-user { margin-left: auto; display: flex; align-items: center; gap: var(--sp-3); white-space: nowrap; }
.topbar-user-name { font-weight: 500; }
.topbar-user-roles { font-size: var(--fs-s); }

/* Переключатель темы: светлая / как в системе / тёмная (partials/theme_switch.html) */
.theme-switch { display: inline-flex; gap: 2px; padding: 2px; border-radius: var(--radius); background: var(--surface); border: 1px solid var(--border); }
.theme-switch-btn {
  display: inline-grid; place-items: center; width: 28px; height: 24px; padding: 0;
  border: 0; border-radius: var(--radius-s); background: transparent; color: var(--text-muted);
}
@media (hover: hover) { .theme-switch-btn:hover { color: var(--text); } }
.theme-switch-btn[aria-pressed="true"] { background: var(--primary-soft); color: var(--primary-text); }
.auth-theme { position: fixed; top: calc(var(--sp-4) + var(--safe-t)); right: calc(var(--sp-4) + var(--safe-r)); z-index: 10; }

#progress { position: fixed; top: var(--top-h); left: 0; right: 0; height: 2px; z-index: 60; pointer-events: none; }
#progress::before {
  content: ""; display: block; height: 100%; width: 0; background: var(--primary);
  opacity: 0; transition: width .3s, opacity .4s;
}
#progress.is-active::before { opacity: 1; animation: progress 1.6s ease-out forwards; }
@keyframes progress { 0% { width: 0; } 60% { width: 70%; } 100% { width: 90%; } }

.page-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-4); flex-wrap: wrap; }
.page-title { margin: 0; display: flex; align-items: center; gap: var(--sp-2); }
.page-actions { margin-left: auto; display: flex; gap: var(--sp-2); }

/* 4. Карточки -------------------------------------------------------------- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l); padding: var(--sp-4); }
.card-title { font-size: 14px; margin-bottom: var(--sp-3); }
.stats-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
.stat-card-label { font-size: var(--fs-s); text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); font-weight: 600; }
.stat-card-label a { color: inherit; }
.stat-card-value { font-size: 32px; font-weight: 700; line-height: 1.2; margin: var(--sp-1) 0 var(--sp-3); }
.stat-card-chips { display: flex; flex-wrap: wrap; gap: 6px; }

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px; font-size: var(--fs-s); line-height: 1.3;
  background: var(--surface-2); color: var(--text-muted); border: 1px solid transparent; white-space: nowrap;
}
.chip b { font-weight: 600; }
@media (hover: hover) { a.chip:hover { text-decoration: none; border-color: var(--border-strong); } }
.chip[data-status] { color: var(--st); background: color-mix(in srgb, var(--st) 13%, var(--surface)); }

.attention-list { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); gap: var(--sp-2); }
.attention-list a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: 8px 12px; border-radius: var(--radius); background: var(--warning-soft); color: var(--text);
}
@media (hover: hover) { .attention-list a:hover { text-decoration: none; filter: brightness(.97); } }
.attention-count { flex: none; color: var(--warning); font-weight: 700; } /* длинная подпись переносится, число не сжимается */

/* 5. Кнопки ---------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 34px; padding: 0 14px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
  font-weight: 600; font-size: var(--fs); line-height: 1; white-space: nowrap; cursor: pointer;
  transition: background .12s, border-color .12s, opacity .12s;
  user-select: none;
}
@media (hover: hover) { .btn:hover { text-decoration: none; background: var(--surface-2); } }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
@media (hover: hover) { .btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); } }
.btn-secondary { background: var(--surface); }
.btn-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); }
@media (hover: hover) { .btn-danger:hover { background: var(--danger-soft); } }
.btn-ghost { border-color: transparent; background: transparent; color: var(--text-muted); }
@media (hover: hover) { .btn-ghost:hover { color: var(--text); background: var(--surface-2); } }
.btn-sm { height: 28px; padding: 0 10px; font-size: var(--fs-s); }
.btn-icon { width: 30px; height: 30px; padding: 0; border-color: transparent; background: transparent; color: var(--text-muted); border-radius: var(--radius); }
@media (hover: hover) {
  .btn-icon:hover { background: var(--surface-3); color: var(--text); }
  .btn-icon.btn-danger:hover { color: var(--danger); background: var(--danger-soft); }
}
.btn-block { width: 100%; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; pointer-events: none; }
.btn.htmx-request { opacity: .65; pointer-events: none; }
.btn .spinner { display: none; }
.btn.htmx-request .spinner { display: inline-block; }

/* 6. Поля форм ------------------------------------------------------------- */
.input, .select, .textarea {
  display: block; width: 100%; height: 34px; padding: 0 10px;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  transition: border-color .12s, box-shadow .12s;
}
.textarea { height: auto; min-height: 68px; padding: 7px 10px; resize: vertical; line-height: 1.45; }
.textarea[data-autogrow] { overflow-y: hidden; resize: none; }
.input:focus, .select:focus, .textarea:focus { border-color: var(--primary); box-shadow: var(--focus-ring); outline: none; }
.input:disabled, .select:disabled, .textarea:disabled { background: var(--surface-2); color: var(--text-muted); cursor: not-allowed; }
.input::placeholder, .textarea::placeholder { color: var(--text-faint); }
.select {
  appearance: none; padding-right: 28px; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 15px) 50%, calc(100% - 10px) 50%; /* по центру при любой высоте (касание — 44px) */
  background-size: 5px 5px; background-repeat: no-repeat;
}
.select-sm, .input-sm { height: 28px; font-size: var(--fs-s); padding: 0 8px; }
.select-sm { padding-right: 24px; background-position: calc(100% - 13px) 50%, calc(100% - 8px) 50%; }
.input-auto { width: auto; }

.field { display: block; margin-bottom: var(--sp-4); }
.field-label, .field > label:not(.toggle) { display: block; font-weight: 500; margin-bottom: 5px; }
.field-label .req, .field-label .muted { font-weight: 400; }
.field-hint { margin-top: 4px; font-size: var(--fs-s); color: var(--text-muted); }
.field-err { margin-top: 4px; font-size: var(--fs-s); color: var(--danger); }
.field--error .input, .field--error .select, .field--error .textarea { border-color: var(--danger); }
.field--error .input:focus, .field--error .textarea:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 25%, transparent); }
.field-row { display: flex; align-items: center; gap: var(--sp-2); }
.field-row .input, .field-row .select { width: auto; flex: 1; }
.form-error { padding: 8px 12px; border-radius: var(--radius); background: var(--danger-soft); color: var(--danger); font-size: var(--fs); }

.char-count { float: right; font-size: var(--fs-s); color: var(--text-faint); font-variant-numeric: tabular-nums; margin-top: -2px; }
.char-count.is-warn { color: var(--warning); }
.char-count.is-over { color: var(--danger); font-weight: 600; }

.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-4); }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--sp-4); }

/* Тумблер: <label class="toggle"><input type="checkbox"><span class="toggle-track"></span><span>Текст</span></label> */
.toggle { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; vertical-align: middle; }
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle-track {
  position: relative; flex: none; width: 32px; height: 18px; border-radius: 999px;
  background: var(--border-strong); transition: background .15s;
}
.toggle-track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .3); transition: transform .15s;
}
.toggle input:checked + .toggle-track { background: var(--primary); }
.toggle input:checked + .toggle-track::after { transform: translateX(14px); }
.toggle input:focus-visible + .toggle-track { box-shadow: var(--focus-ring); }
.toggle input:disabled + .toggle-track { opacity: .45; cursor: not-allowed; }
.toggle input:disabled ~ * { cursor: not-allowed; }
.toggle.htmx-request { opacity: .6; pointer-events: none; }
.toggle-label { font-size: var(--fs-s); color: var(--text-muted); }
.toggle-group { display: flex; flex-direction: column; gap: 6px; }

.checkbox { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.checkbox input { width: 15px; height: 15px; margin: 0; accent-color: var(--primary); cursor: pointer; }

/* 7. Бейджи ---------------------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 7px; border-radius: var(--radius-s); font-size: 11px; font-weight: 600; line-height: 1.35;
  white-space: nowrap; vertical-align: middle;
  color: var(--st, var(--text-muted)); background: color-mix(in srgb, var(--st, var(--text-muted)) 13%, var(--surface));
}
.badge--instance { text-transform: uppercase; letter-spacing: .06em; --st: var(--text-muted); }
.badge--p1, [data-status="p1"] { --st: var(--st-p1); }
.badge--p2, [data-status="p2"] { --st: var(--st-p2); }
.badge--p3, [data-status="p3"] { --st: var(--st-p3); }
.badge--p4, [data-status="p4"] { --st: var(--st-p4); }
.badge--p5, [data-status="p5"] { --st: var(--st-p5); }
.badge--p6, [data-status="p6"] { --st: var(--st-p6); }
.badge--p7, [data-status="p7"] { --st: var(--st-p7); }
.badge--v1, [data-status="v1"] { --st: var(--st-v1); }
.badge--v2, [data-status="v2"] { --st: var(--st-v2); }
.badge--v3, [data-status="v3"] { --st: var(--st-v3); }
.badge--v4, [data-status="v4"] { --st: var(--st-v4); }
.badge--special { --st: var(--st-special); }
.badge--ultra { --st: var(--st-ultra); }
.badge--dry { --st: var(--st-dry); }
.badge--muted { --st: var(--st-muted); }
.badge--danger { --st: var(--danger); }
.badge--warning { --st: var(--warning); }
.badge--success { --st: var(--success); }
.badge--info { --st: var(--info); }

/* 8. Таблицы --------------------------------------------------------------- */
/* Липкий thead липнет под шапкой сайта (top: var(--top-h)), пока .table-wrap не является scroll-контейнером.
   Ниже 1240px включаем горизонтальную прокрутку: .table-wrap становится scroll-контейнером, и sticky считается уже
   от его верха — с top: var(--top-h) шапка уезжала бы на высоту топбара вниз и накрывала первые строки
   (так и было в карточке задания, владелец 29.09.2026). Поэтому внутри прокручиваемой обёртки top: 0 —
   шапка просто остаётся на месте. */
.table-wrap { overflow: visible; }
@media (max-width: 1240px) { .table-wrap { overflow-x: auto; } .table-wrap .table th { top: 0; } }
.table { width: 100%; table-layout: fixed; min-width: 1180px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l); border-collapse: separate; border-spacing: 0; }
.table th, .table td { padding: 8px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--border); }
.table th {
  position: sticky; top: var(--top-h); z-index: 5;
  background: var(--surface-2); font-weight: 600; font-size: var(--fs-s); color: var(--text-muted);
  white-space: nowrap; vertical-align: middle;
}
.table thead th:first-child { border-top-left-radius: var(--radius-l); }
.table thead th:last-child { border-top-right-radius: var(--radius-l); }
/* Шапка прилипла (admin.js «Липкая шапка таблицы» ставит .is-stuck): без скруглений — иначе в углах видны строки,
   уезжающие под неё (и полоса статуса строки заданий). Без тени — теней в админке нет, от строк её отделяет граница. */
.table.is-stuck thead th { border-top-left-radius: 0; border-top-right-radius: 0; }
.table tbody tr:last-child td { border-bottom: 0; }
/* Последняя строка: фон ячеек при наведении и полоса статуса слева не выходят за скруглённые нижние углы таблицы. */
@media (min-width: 1024px) {
  .table tbody tr:last-child td:first-child { border-bottom-left-radius: calc(var(--radius-l) - 1px); }
  .table tbody tr:last-child td:last-child { border-bottom-right-radius: calc(var(--radius-l) - 1px); }
}
@media (hover: hover) { .table tbody tr:hover td { background: color-mix(in srgb, var(--surface-2) 55%, var(--surface)); } }
.table th a.sort { color: inherit; display: inline-flex; align-items: center; gap: 3px; }
@media (hover: hover) { .table th a.sort:hover { color: var(--text); text-decoration: none; } }
.table th a.sort::after { content: "↕"; opacity: .35; font-size: 10px; }
.table th a.sort.asc::after { content: "↑"; opacity: 1; color: var(--primary-text); }
.table th a.sort.desc::after { content: "↓"; opacity: 1; color: var(--primary-text); }
.table th.col-check, .table td.col-check { width: 32px; padding-right: 0; }
.table td.cell-id { white-space: nowrap; }
.table td.cell-id .muted { display: block; font-size: var(--fs-s); }
.table td.cell-status { width: 190px; }
.table td.cell-status .select { margin-bottom: 6px; }
.table td.cell-status[data-status] .select { border-left: 3px solid var(--st); }
.table td.cell-name b { font-weight: 600; }
.table .clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.table .name-en { font-style: italic; color: var(--text-muted); }
.table tr.row--muted td { opacity: .55; }
.table tr.row-hl > td { animation: row-hl 3s ease-out forwards; }
.table tr.row-saved > td { animation: row-saved 1s ease-out; }
@keyframes row-hl { 0%, 40% { background: var(--hl); } 100% { background: transparent; } }
@keyframes row-saved { 0% { background: var(--success-soft); } 100% { background: transparent; } }

.dots { display: inline-flex; gap: 3px; }
.dots i { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong); }
.dots i.on { background: var(--success); }

.thumb-strip { display: grid; grid-template-columns: repeat(3, 44px); gap: 3px; } /* Ж М Пара / 🐶 🐱 реф */
.thumb-slot { width: 44px; height: 51px; border-radius: 3px; overflow: hidden; background: var(--surface-2); position: relative; }
.thumb-slot img { width: 100%; height: 100%; object-fit: cover; }
.thumb-slot--empty { border: 1px dashed var(--border-strong); background: transparent; display: flex; align-items: center; justify-content: center; font-size: 9px; color: var(--text-faint); text-align: center; line-height: 1.1; padding: 2px; }
.thumb-slot--ref { border-color: var(--primary); }
.thumb { border-radius: var(--radius-s); overflow: hidden; background: var(--surface-2); }
.thumb img { object-fit: cover; width: 100%; height: 100%; }
.thumb-poster { width: 72px; height: 84px; }

.row-actions { display: flex; gap: 2px; }

/* 9. Фильтры, пагинация, bulk-bar ------------------------------------------ */
.filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.filters .input, .filters .select { width: auto; }
.filters .select { min-width: 140px; max-width: 220px; }
.filters-search { width: 260px !important; }
.filters-sep { width: 1px; height: 24px; background: var(--border); margin: 0 var(--sp-1); }
.filters-reset { color: var(--primary-text); }
.chip-toggle { display: inline-flex; align-items: center; gap: 5px; padding: 0 10px; height: 28px; border-radius: 999px; border: 1px solid var(--border-strong); background: var(--surface); font-size: var(--fs-s); cursor: pointer; user-select: none; white-space: nowrap; }
.chip-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
@media (hover: hover) { .chip-toggle:hover { background: var(--surface-2); } }
.chip-toggle:has(input:checked) { background: var(--primary-soft); border-color: var(--primary); color: var(--primary-text); }
.chip-toggle:has(input:focus-visible) { box-shadow: var(--focus-ring); }

.list-meta { display: flex; align-items: center; gap: var(--sp-3); color: var(--text-muted); font-size: var(--fs-s); margin: var(--sp-2) 0; }
/* Пейджер (partials/pager.html). Сверху — .list-meta «Страница N из M · строки», справа (.pager-end) сортировка и
   маленькие ‹ ›; снизу — nav.pager: « ‹ номера › » и «На странице» справа. Телефон — 21.5. */
.pager-end { margin-left: auto; display: inline-flex; align-items: center; gap: var(--sp-3); }
.pager-mini { display: inline-flex; gap: var(--sp-1); }
.pager-mini .pg-btn { min-width: 26px; height: 26px; padding: 0 6px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--border-strong); border-radius: var(--radius-s); background: var(--surface); color: var(--text); font-size: var(--fs-l); line-height: 1; }
.pager-mini .is-disabled { opacity: .4; pointer-events: none; }
@media (hover: hover) { .pager-mini a:hover { background: var(--surface-2); text-decoration: none; } }
.pager { display: flex; align-items: center; gap: var(--sp-1); margin-top: var(--sp-3); flex-wrap: wrap; }
.pager a, .pager span { min-width: 30px; height: 30px; padding: 0 8px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius); color: var(--text-muted); }
@media (hover: hover) { .pager a:hover { background: var(--surface-2); text-decoration: none; color: var(--text); } }
.pager .is-current { background: var(--primary); color: var(--on-primary); font-weight: 600; }
.pager .is-disabled { opacity: .4; pointer-events: none; }
.pager .per-page { margin-left: auto; display: flex; align-items: center; gap: var(--sp-2); color: var(--text-muted); font-size: var(--fs-s); }
.pager .per-page .select { width: auto; }
.pager .pg-btn { font-size: var(--fs-l); }
.pager .pg-pad { visibility: hidden; } /* пара к « без »: на телефоне номер посередине */
/* «На странице» в шторке фильтров — поле формы #filters на любом экране, видно только на телефоне (21.5). */
.per-page-select { display: none; }
/* Пейджер прокручивает список к началу (show:#table:top) — под липкую шапку, а не за неё. */
#table { scroll-margin-top: calc(var(--top-h) + 12px); }

.bulk-bar {
  position: fixed; left: 50%; bottom: 20px; z-index: 40;
  transform: translate(-50%, 140%); transition: transform .2s ease;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 10px 14px; border-radius: var(--radius-l);
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-2);
}
.bulk-bar.is-visible { transform: translate(-50%, 0); }
.bulk-bar .bulk-count { font-weight: 600; margin-right: var(--sp-2); white-space: nowrap; }
.bulk-bar .select { width: auto; }
.bulk-bar .filters-sep { margin: 0 var(--sp-2); }

/* 10. Форма ---------------------------------------------------------------- */
.form-layout { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--sp-5); align-items: start; }
@media (max-width: 1400px) { .form-layout { grid-template-columns: minmax(0, 1fr); } .form-aside { position: static; } }
.form-aside { position: sticky; top: calc(var(--top-h) + 16px); display: flex; flex-direction: column; gap: var(--sp-4); }

fieldset.section { border: 1px solid var(--border); border-radius: var(--radius-l); background: var(--surface); padding: var(--sp-3) var(--sp-4) var(--sp-2); margin: 0 0 var(--sp-4); min-width: 0; }
fieldset.section > legend { padding: 0 6px; font-weight: 600; font-size: var(--fs-l); }
fieldset.section:disabled { opacity: .6; }
.section-hint { color: var(--text-muted); font-size: var(--fs-s); margin: -4px 0 var(--sp-3); }

.save-bar {
  position: sticky; bottom: 0; z-index: 30;
  display: flex; align-items: center; gap: var(--sp-2);
  margin: var(--sp-4) -4px 0; padding: 10px 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l);
  box-shadow: var(--shadow-up);
}
.save-bar .save-status, .save-bar .dirty-note { font-size: var(--fs-s); color: var(--text-muted); margin-left: var(--sp-2); display: none; align-items: center; gap: 6px; }
.save-bar.htmx-request .save-status { display: inline-flex; }
.save-bar.htmx-request .dirty-note { display: none; }
.save-bar.is-dirty .dirty-note { display: inline-flex; color: var(--warning); }
.save-bar .save-right { margin-left: auto; display: flex; gap: var(--sp-2); }

#model-hint { font-size: var(--fs-s); color: var(--text-muted); margin-top: 4px; }
#model-hint.is-over { color: var(--danger); font-weight: 600; }

/* 11. Медиа ---------------------------------------------------------------- */
.slot {
  position: relative; width: 100%; aspect-ratio: 6 / 7; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--border); overflow: hidden;
  transition: border-color .12s, box-shadow .12s;
}
.slot img, .slot video { width: 100%; height: 100%; object-fit: cover; }
.slot-open { display: block; width: 100%; height: 100%; cursor: zoom-in; } /* загруженное фото — крупный просмотр */
.slot-empty {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 2px dashed var(--border-strong); border-radius: inherit; color: var(--text-faint); font-size: var(--fs-s); text-align: center; padding: 8px; cursor: pointer;
}
.slot-empty .icon { width: 22px; height: 22px; opacity: .6; }
.slot--over { border-color: var(--primary); box-shadow: var(--focus-ring); }
.slot--over .slot-empty { border-color: var(--primary); color: var(--primary-text); }
.slot--error { border-color: var(--danger); }
.slot--filled .slot-empty { display: none; }
.slot input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.slot--filled input[type="file"] { pointer-events: none; }
.slot-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: rgba(0, 0, 0, .15); display: none; }
.slot.htmx-request .slot-progress, .slot:has(.htmx-request) .slot-progress { display: block; }
.slot-progress i { display: block; height: 100%; width: 0; background: var(--primary); transition: width .15s; }
.slot-actions {
  position: absolute; top: 6px; right: 6px; left: 6px; display: flex; justify-content: flex-end; flex-wrap: wrap;
  gap: 4px; opacity: 0; transition: opacity .12s;
}
.slot-actions .btn { height: 26px; padding: 0 8px; font-size: var(--fs-s); background: rgba(255, 255, 255, .92); color: #1c1535; border-color: transparent; box-shadow: var(--shadow-1); }
@media (hover: hover) { .slot-actions .btn:hover { background: #fff; } }
.slot-actions .btn-icon { width: 26px; padding: 0; }
.slot:focus-within .slot-actions { opacity: 1; }
@media (hover: hover) { .slot:hover .slot-actions { opacity: 1; } }
.slot-label {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 8px 6px; font-size: var(--fs-s); color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
  background: linear-gradient(to top, rgba(0, 0, 0, .55), transparent); pointer-events: none;
}
.slot:not(.slot--filled) .slot-label { color: var(--text-muted); text-shadow: none; background: none; text-align: center; }
.slot-err { font-size: var(--fs-s); color: var(--danger); margin-top: 4px; }
.slot-wrap { display: flex; flex-direction: column; gap: 4px; }
.slot-wrap .slot-caption { font-size: var(--fs-s); color: var(--text-muted); text-align: center; }
.slot-grid { display: grid; grid-template-columns: repeat(4, 150px); gap: var(--sp-3); }
.slot-grid .slot { width: 150px; }
.slot--video { aspect-ratio: 9 / 16; }
.slot--square { aspect-ratio: 1; }

.preview-list { list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.preview-list li { width: 120px; position: relative; border-radius: var(--radius); overflow: hidden; background: var(--surface-2); border: 1px solid var(--border); }
.preview-list video { width: 100%; aspect-ratio: 9 / 16; object-fit: cover; background: #000; }
.preview-list .drag { position: absolute; top: 4px; left: 4px; }
.preview-actions { display: flex; justify-content: space-between; align-items: center; padding: 4px; gap: 4px; background: var(--surface); }
.preview-actions .btn { height: 24px; padding: 0 6px; font-size: 11px; }
.preview-index { position: absolute; top: 4px; right: 6px; font-size: 11px; color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .7); }
.drag { cursor: grab; color: var(--text-faint); display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: var(--radius-s); flex: none; touch-action: none; }
@media (hover: hover) { .drag:hover { color: var(--text); background: var(--surface-3); } }
.drag:active { cursor: grabbing; }
.sortable-ghost { opacity: .35; }
.sortable-chosen { box-shadow: var(--shadow-2); }
.sortable-drag { opacity: .9; }

.app-card {
  position: relative; width: 300px; height: 350px; border-radius: 14px; overflow: hidden;
  background: var(--surface-3); box-shadow: var(--shadow-1); flex: none;
}
.app-card-blur, .app-card-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.app-card-blur { filter: blur(6px); transform: scale(1.06); }
.app-card::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 55%; background: linear-gradient(to top, rgba(0, 0, 0, .78), rgba(0, 0, 0, 0)); pointer-events: none; }
.app-card-title { position: absolute; left: 14px; right: 14px; bottom: 14px; z-index: 2; color: #fff; font-size: 18px; font-weight: 700; line-height: 1.2; text-shadow: 0 1px 3px rgba(0, 0, 0, .5); }
.app-card-badges { position: absolute; top: 10px; left: 10px; z-index: 2; display: flex; gap: 4px; }
.app-card-badges .badge { background: rgba(255, 255, 255, .92); }
.app-card-badges .badge--ultra { color: #fff; background: linear-gradient(135deg, #f59e0b, #d946ef); }
.app-card-badges .badge--new { color: #fff; background: var(--primary); }
.app-card-toolbar { display: flex; gap: var(--sp-1); margin-top: var(--sp-2); flex-wrap: wrap; }
.app-card-toolbar .chip-toggle { height: 24px; }

/* 12. Комбобокс ------------------------------------------------------------ */
.combobox { position: relative; }
.combobox .input { padding-right: 30px; }
.combobox-clear { position: absolute; right: 4px; top: 4px; width: 26px; height: 26px; border: 0; background: transparent; color: var(--text-faint); border-radius: var(--radius-s); display: none; align-items: center; justify-content: center; }
.combobox.has-value .combobox-clear { display: inline-flex; }
@media (hover: hover) { .combobox-clear:hover { color: var(--text); background: var(--surface-3); } }
.combobox-list {
  position: absolute; z-index: 45; left: 0; right: 0; top: calc(100% + 4px);
  display: none; max-height: 320px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-2);
}
.combobox.is-open .combobox-list:not(:empty) { display: block; }
.combobox-option { display: flex; align-items: center; gap: 8px; padding: 6px 10px; cursor: pointer; }
.combobox-option.is-active { background: var(--primary-soft); }
@media (hover: hover) { .combobox-option:hover { background: var(--primary-soft); } }
.combobox-option img { width: 24px; height: 28px; object-fit: cover; border-radius: 3px; flex: none; background: var(--surface-2); }
.combobox-option .opt-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combobox-option .opt-meta { font-size: var(--fs-s); color: var(--text-muted); white-space: nowrap; }
.combobox-empty { padding: 8px 10px; color: var(--text-muted); font-size: var(--fs-s); }
.combobox-link { margin-left: 6px; font-size: var(--fs-s); }

/* 13. Рубрики, типы персон ------------------------------------------------- */
.rubric-list { list-style: none; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l); }
.rubric-list li.rubric { display: flex; align-items: center; gap: var(--sp-3); padding: 8px 12px; border-bottom: 1px solid var(--border); }
.rubric-list li.rubric:last-child { border-bottom: 0; }
.rubric-list li.rubric.row-saved { animation: row-saved 1s ease-out; }
.rubric--off { opacity: .55; }
.rubric--off .rubric-name { text-decoration: line-through; text-decoration-color: var(--text-faint); }
.rubric-name { flex: 1; min-width: 0; font-weight: 500; }
.rubric-name small { display: block; color: var(--text-muted); font-weight: 400; font-style: italic; }
.rubric-count { font-family: var(--mono); font-size: var(--fs-s); color: var(--text-muted); white-space: nowrap; min-width: 80px; text-align: right; }
.rubric-count a { color: inherit; }
.rubric-ratio { font-size: var(--fs-s); color: var(--text-muted); min-width: 40px; }
.rubric-edit { display: grid; grid-template-columns: 1fr 1fr 110px 150px auto auto; gap: var(--sp-2); align-items: center; flex: 1; }
.rubric-edit .input, .rubric-edit .select { height: 30px; }

/* Экран /rubrics (pages/rubrics/*): шапка колонок, ширины ячеек, инлайн-форма. Ширины ячеек общие для .rubric-head и li.rubric. */
.page-count { font-size: 15px; font-weight: 400; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.page-hint { margin: 0; font-size: var(--fs-s); color: var(--text-muted); }
.rubric-head {
  position: sticky; top: var(--top-h); z-index: 5;
  display: flex; align-items: center; gap: var(--sp-3); padding: 6px 12px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-l) var(--radius-l) 0 0;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); white-space: nowrap;
}
.rubric-head + .rubric-list { border-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; }
.rubric-head .rubric-name, .rubric-head .rubric-ratio, .rubric-head .rubric-counts { font: inherit; color: inherit; }
.rubric-head-drag { width: 22px; flex: none; }
.rubric-special { width: 112px; flex: none; }
.rubric-ratio { width: 48px; flex: none; font-variant-numeric: tabular-nums; }
.rubric-counts { width: 96px; flex: none; display: inline-flex; justify-content: flex-end; gap: 4px; font-family: var(--mono); font-size: var(--fs-s); white-space: nowrap; }
.rubric-counts .rubric-count { min-width: 0; }
.rubric-counts .rubric-count:first-child { color: var(--text); font-weight: 600; }

.rubric-video { width: 72px; flex: none; }

.rubric-list .toggle { width: 32px; flex: none; }
.rubric-toggle { width: 32px; flex: none; text-align: center; }
.rubric-actions { width: 62px; flex: none; display: flex; gap: 2px; }
.rubric-list .btn:disabled { pointer-events: auto; } /* иначе title «Нет прав» не всплывает */
.rubric-list li.rubric--off { opacity: 1; } /* гасим только данные: тумблер и кнопки остаются контрастными */
.rubric--off .rubric-name, .rubric--off .rubric-special, .rubric--off .rubric-ratio, .rubric--off .rubric-counts, .rubric--off .rubric-video { opacity: .55; }
.rubric-list .rubric-empty { display: block; }
.rubric-list .rubric-empty .empty-state { border: 0; background: transparent; }
.rubric-list li.rubric ~ li.rubric-empty { display: none; }
/* инлайн-форма (_item_edit.html) */
.rubric-list li.rubric--edit { background: color-mix(in srgb, var(--primary-soft) 55%, var(--surface)); align-items: flex-start; }
.rubric-list li.rubric--edit .drag { margin-top: 21px; }
form.rubric-edit { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) 96px 150px auto auto; gap: var(--sp-2) var(--sp-3); align-items: start; margin: 0; }
.rubric-field { display: block; margin: 0; min-width: 0; }
.rubric-field-label { display: block; font-size: 11px; color: var(--text-muted); margin-bottom: 3px; white-space: nowrap; }
.rubric-field-label .req { color: var(--danger); }
.rubric-field .field-err { margin-top: 3px; }
.rubric-edit .rubric-field-toggle { height: 30px; margin-top: 18px; width: auto; }
.rubric-edit .rubric-edit-actions { display: flex; gap: var(--sp-1); margin-top: 18px; }
.rubric-edit .rubric-edit-actions .btn { height: 30px; }

.person-grid { display: grid; grid-template-columns: repeat(auto-fill, 220px); gap: var(--sp-4); }
.person-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l); overflow: hidden; }
.person-card .person-photo { position: relative; width: 220px; height: 220px; background: var(--surface-2); }
.person-card .person-photo img { width: 100%; height: 100%; object-fit: cover; }
.person-card .person-body { padding: var(--sp-3); }
.person-card .person-name { font-weight: 600; }
.person-card .person-many { color: var(--text-muted); }
.person-card .person-id { font-size: var(--fs-s); color: var(--text-faint); font-family: var(--mono); }
.person-card .person-actions { display: flex; gap: var(--sp-1); margin-top: var(--sp-2); }
.person-card.row-saved { animation: row-saved 1s ease-out; }

/* 14. Тосты, спиннер, HTMX ------------------------------------------------- */
.toasts { position: fixed; top: calc(var(--top-h) + 12px); right: 16px; z-index: 1000; display: flex; flex-direction: column; gap: 8px; width: 360px; max-width: calc(100vw - 32px); pointer-events: none; }
.toast {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 12px; border-radius: var(--radius-l); background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-left: 3px solid var(--info); box-shadow: var(--shadow-2);
  font-size: var(--fs); line-height: 1.4; pointer-events: auto; word-break: break-word;
  animation: toast-in .2s ease-out;
}
.toast[data-kind="success"] { border-left-color: var(--success); }
.toast[data-kind="error"] { border-left-color: var(--danger); }
.toast[data-kind="info"] { border-left-color: var(--info); }
.toast.is-hiding { animation: toast-out .25s ease-in forwards; }
.toast-text { flex: 1; }
.toast-close { border: 0; background: transparent; color: var(--text-faint); padding: 0 2px; font-size: 16px; line-height: 1; margin: -2px -4px 0 0; }
@media (hover: hover) { .toast-close:hover { color: var(--text); } }
@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateX(20px); } }

.spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; vertical-align: -2px; opacity: .8; }
@keyframes spin { to { transform: rotate(360deg); } }

.htmx-indicator { display: none; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline-block; }
#table { position: relative; transition: opacity .15s; }
#table.htmx-request { opacity: .55; pointer-events: none; }
.is-busy { opacity: .6; pointer-events: none; }

.empty-state { padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--text-muted); background: var(--surface); border: 1px dashed var(--border-strong); border-radius: var(--radius-l); }

/* 15. Логин, ошибки -------------------------------------------------------- */
.auth-body { min-width: 0; }
.auth-main { min-height: 100vh; min-height: 100dvh; display: grid; place-items: center; padding: var(--sp-5); }
.login-card { width: 360px; max-width: 100%; padding: 28px; box-shadow: var(--shadow-1); }
.login-brand { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-5); }
.login-brand img { flex: none; }
.login-title { margin: 0; font-size: 17px; }
.stack { display: flex; flex-direction: column; gap: var(--sp-3); }
.stack .field { margin-bottom: 0; }

.error-page { max-width: 560px; margin: 80px auto; text-align: center; }
.error-code { font-size: 72px; font-weight: 800; line-height: 1; color: var(--text-faint); letter-spacing: -.02em; margin-bottom: var(--sp-3); }
.error-title { font-size: 22px; }
.error-message { color: var(--text-muted); }
.error-perm { color: var(--text-muted); }
.error-actions { margin-top: var(--sp-5); }
.error-request-id { font-size: var(--fs-s); margin-top: var(--sp-5); }

/* 16. Утилиты -------------------------------------------------------------- */
.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }
.danger { color: var(--danger); }
.mono { font-family: var(--mono); }
.nowrap { white-space: nowrap; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.small { font-size: var(--fs-s); }
.flex { display: flex; align-items: center; gap: var(--sp-2); }
.flex-wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.ml-auto { margin-left: auto; }
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 17. Экран /prompts (pages/prompts/*) ------------------------------------- */
/* Ширины колонок задаёт thead (table-layout: fixed); «Название» — остаток. */
.table--prompts .col-id { width: 84px; }
.table--prompts .col-photos { width: 210px; }
.table--prompts .col-status { width: 190px; }
.table--prompts .col-prompts { width: 132px; }
.table--prompts .col-counter { width: 88px; }
.table--prompts .col-actions { width: 154px; }
.table--prompts th .th-sub { display: block; font-weight: 500; text-transform: none; letter-spacing: 0; margin-top: 1px; }
.table--prompts th .th-sub a.sort { color: var(--text-faint); }
.table--prompts td.cell-id .mono { font-weight: 600; color: var(--primary-text); }
.table--prompts td.cell-name { min-width: 0; }
.table--prompts .name-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
.table--prompts .name-line a { color: var(--primary-text); }
.table--prompts .name-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 6px; margin: 2px 0 4px; font-size: var(--fs-s); }
.table--prompts .rubric-link { color: var(--text-muted); }


.table--prompts .prompt-text { color: var(--text-muted); font-size: var(--fs-s); line-height: 1.35; }
.table--prompts td.cell-status .select { margin-bottom: 8px; }
.cell-toggles { display: flex; flex-wrap: wrap; gap: 6px 10px; }
.toggle--cell { gap: 5px; }
.toggle--cell .toggle-track { width: 28px; height: 16px; }
.toggle--cell .toggle-track::after { width: 12px; height: 12px; }
.toggle--cell input:checked + .toggle-track::after { transform: translateX(12px); }
.toggle--cell .toggle-label { font-size: 11px; }
.toggle--cell:has(input:checked) .toggle-label { color: var(--text); font-weight: 500; }
/* Подписи тумб: заполненный слот — подпись в углу; референс — 8-й слот с рамкой. */
.thumb-slot .thumb-cap { position: absolute; left: 0; bottom: 0; padding: 1px 4px; font-size: 9px; line-height: 1.3; font-style: normal; font-weight: 600; color: #fff; background: rgba(0, 0, 0, .55); border-top-right-radius: 3px; pointer-events: none; }
.thumb-slot--ref { box-shadow: inset 0 0 0 2px var(--primary); background: var(--ref-bg); } /* белый PNG с альфой виден */
.thumb-slot--ref img { opacity: .95; }
/* Заполненность промтов по типу персоны: точка + короткая подпись, 3 в ряд. */
.pdots { display: grid; grid-template-columns: repeat(3, auto); justify-content: start; gap: 3px 9px; font-size: 11px; color: var(--text-faint); }
.pdot { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; line-height: 1.3; }
.pdot i { width: 7px; height: 7px; border-radius: 50%; background: var(--border-strong); flex: none; }
.pdot.is-on { color: var(--text-muted); }
.pdot.is-on i { background: var(--success); }
.table--prompts td.cell-counter { font-variant-numeric: tabular-nums; }
.table--prompts td.cell-counter b { display: block; font-weight: 600; }
.table--prompts td.cell-counter .muted { display: block; font-size: var(--fs-s); }
.table--prompts .row-actions { flex-wrap: wrap; }
.table--prompts tr.row-empty td { padding: var(--sp-3); }
@media (hover: hover) { .table--prompts tr.row-empty:hover td { background: transparent; } }
.table--prompts tr.row-empty .empty-state { border: 0; }
/* Фильтры: чипы флагов группой, «Сбросить» справа. */
.filters--prompts .filters-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }

.pager .pg-gap { min-width: 20px; color: var(--text-faint); }
/* Bulk-bar: зависимые от действия блоки value (admin.js переключает hidden). */
.bulk-bar [data-bulk-value][hidden] { display: none; }
.bulk-bar .select-sm { height: 30px; }
.bulk-bar .btn-sm { height: 30px; }

/* 18. Форма промта (pages/prompts/form.html, macros/forms.html, macros/media.html) ------------------------------ */
/* Aside рядом с формой уже от 1100px: минимум формы = сетка слотов 4×150 + отступы (~670px) + aside 300 + gap. Перекрывает правило
   «<1400px aside под форму» из раздела 10 — на 1280 оглавление и карточка иначе уезжали вниз. */
@media (min-width: 1100px) { .form-layout { grid-template-columns: minmax(0, 1fr) 300px; } .form-aside { position: sticky; } }
.form-main { min-width: 0; }
.page-head--form .page-title { flex-wrap: wrap; font-size: 20px; }
.page-head--form .page-title-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60vw; }
.page-head--form .badge { font-size: 11px; }
fieldset.section { scroll-margin-top: calc(var(--top-h) + 12px); }
fieldset.section > legend .icon { vertical-align: -3px; }
.section-hint .icon { width: 13px; height: 13px; vertical-align: -2px; color: var(--text-muted); }
.card--tight { padding: var(--sp-3); }
/* Секция в aside (карточка «Как в приложении»): заголовок на рамке, как у секций формы. */
.form-aside fieldset.section { margin: 0; padding: var(--sp-2) var(--sp-3) var(--sp-3); }
.form-aside .card-title { margin-bottom: var(--sp-2); }

/* Подпись поля в строку: слева текст, справа инструменты (метка, копирование) и счётчик из admin.js. */
.field-label--row { display: flex; align-items: center; gap: 6px; }
.field-label--row .field-label-text { flex: 1; min-width: 0; }
.field-label--row .char-count { float: none; margin: 0; }
.field-label .req { color: var(--danger); }
/* Табы ([data-tabs]): сегменты как у переключателя темы; точка — поле заполнено; подпись поля в панели дублирует вкладку. */
.tabs-list { display: flex; flex-wrap: wrap; gap: 2px; width: fit-content; max-width: 100%; padding: 3px; margin-bottom: var(--sp-3); border-radius: 12px; background: var(--surface); border: 1px solid var(--border); }
.tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 12px; border: 0; border-radius: 9px; background: transparent; color: var(--text-muted); font-weight: 500; white-space: nowrap; }
@media (hover: hover) { .tab:hover { color: var(--text); } }
.tab[aria-selected="true"] { background: var(--primary-soft); color: var(--primary-text); font-weight: 600; }
.tab.has-error { color: var(--danger); }
.tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--border-strong); flex: none; }
.tab-dot.is-on { background: var(--success); }
.tab-panel .field-label { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); }

/* Тумблеры формы: обычный размер текста, подсказка под названием. */
.toggle-group--form { gap: 12px; }
.toggle--field { align-items: flex-start; }
.toggle--field .toggle-track { margin-top: 2px; }
.toggle--field .toggle-text { display: flex; flex-direction: column; gap: 1px; }
.toggle--field .toggle-label { font-size: var(--fs); color: var(--text); font-weight: 500; }
.toggle--field .toggle-hint { font-size: var(--fs-s); color: var(--text-muted); }

/* Оживление: текстовые поля слева, видео-слот 9:16 справа. */
.animate-layout { display: flex; gap: var(--sp-5); align-items: flex-start; }
.slot-grid--single { grid-template-columns: 150px; flex: none; }
.slot-grid--photos { margin-bottom: var(--sp-3); }
@media (max-width: 720px) { .slot-grid { grid-template-columns: repeat(auto-fill, 150px); } }

/* Слот: подпись-мета, ошибка поверх, референс с альфой (contain на «шахматке»), видео на чёрном. */
.slot-file:focus-visible ~ .slot-actions, .slot:has(.slot-file:focus-visible) { box-shadow: var(--focus-ring); }
.slot-meta { position: absolute; right: 8px; bottom: 6px; font-size: 10px; color: #fff; opacity: .85; text-shadow: 0 1px 2px rgba(0, 0, 0, .6); pointer-events: none; font-variant-numeric: tabular-nums; }
.slot:not(.slot--filled) .slot-meta { color: var(--text-faint); text-shadow: none; }
.slot .slot-err { position: absolute; left: 6px; right: 6px; bottom: 30px; z-index: 2; margin: 0; padding: 4px 7px; border-radius: var(--radius-s); background: color-mix(in srgb, var(--danger) 90%, #000); color: #fff; font-size: 11px; line-height: 1.3; }
.slot--error .slot-label { color: var(--danger); }
.slot--reference img { object-fit: contain; background: conic-gradient(var(--surface-3) 25%, var(--surface) 0 50%, var(--surface-3) 0 75%, var(--surface) 0) 0 0 / 16px 16px; }
.slot--video video { background: #000; }
.slot.htmx-request, .slot:has(.htmx-request) { opacity: .85; }
.slot--filled .slot-actions .btn { color: #1c2430; }

/* Карточка «как в приложении» + переключатели языка и типа персоны. */
.preview-card { display: flex; flex-direction: column; gap: var(--sp-2); }
.preview-card.htmx-request .app-card { opacity: .6; transition: opacity .15s; }
.preview-toolbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.seg { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); flex: none; }
.seg-btn { height: 22px; padding: 0 9px; border: 0; border-radius: var(--radius-s); background: transparent; color: var(--text-muted); font-size: var(--fs-s); font-weight: 600; cursor: pointer; }
@media (hover: hover) { .seg-btn:hover { background: var(--surface-2); color: var(--text); } }
.seg-btn.is-active { background: var(--primary-soft); color: var(--primary-text); }
.preview-variants { display: flex; gap: 4px; flex-wrap: wrap; }
.preview-variants .chip-toggle { height: 24px; padding: 0 8px; }
.chip-toggle.is-active { background: var(--primary-soft); border-color: var(--primary); color: var(--primary-text); }
.chip-toggle.is-off { opacity: .45; }
.chip-toggle.is-off.is-active { opacity: .8; }
.preview-variants .chip-toggle:disabled { cursor: not-allowed; }
@media (hover: hover) { .preview-variants .chip-toggle:disabled:hover { background: var(--surface); } }
.app-card--empty { background: var(--surface-3); }
.app-card-placeholder { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; color: var(--text-faint); font-size: var(--fs-s); pointer-events: none; }
.app-card-placeholder .icon { width: 28px; height: 28px; opacity: .5; }
.app-card--empty::after { opacity: .5; }
.app-card-sub { display: block; font-size: 11px; font-weight: 500; opacity: .85; margin-bottom: 3px; text-transform: uppercase; letter-spacing: .04em; }
.form-aside .app-card { width: 100%; max-width: 300px; height: auto; aspect-ratio: 6 / 7; }

/* Оглавление: счётчик ошибок, активный раздел (admin.js initTocSpy). */

/* Факты о промте в aside. */
.facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: var(--fs-s); margin: 0; }
.facts dt { color: var(--text-muted); }
.facts dd { margin: 0; }

/* Save-bar: подсказка хоткея справа, иконка у «несохранённых». */
.save-bar kbd { font-family: var(--mono); font-size: 11px; padding: 1px 5px; border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 4px; background: var(--surface-2); }
.save-bar .dirty-note .icon { width: 14px; height: 14px; }

/* 19. Экран /videos (pages/videos/*) --------------------------------------- */
/* Наследует .table--prompts (ID/название/статус/действия); свои ширины: ☐32 · ID 84 · Постер 100 · Название — остаток · Модель 150 · Статус 190 · Счётчик 84 · Действия 80. */
.table--videos .col-poster { width: 100px; }
.table--videos .col-model { width: 150px; }
.table--videos .col-counter { width: 84px; }
.table--videos .col-actions { width: 80px; }
.thumb-poster { display: block; }
.thumb-poster img { display: block; }
.thumb-poster--empty { display: flex; align-items: center; justify-content: center; border: 1px dashed var(--border-strong); background: transparent; font-size: 10px; color: var(--text-faint); text-align: center; line-height: 1.15; padding: 4px; }
.poster-meta { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; font-size: var(--fs-s); line-height: 1.2; white-space: nowrap; }
/* Индикатор 360p: точка серая/зелёная + подпись. */
.mark360 { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; font-weight: 600; color: var(--text-faint); }
.mark360::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--border-strong); }
.mark360.is-on { color: var(--success); }
.mark360.is-on::before { background: var(--success); }
.table--videos .session-link { color: var(--text-muted); }

.table--videos .name-sub .session-link::before { content: "·"; color: var(--text-faint); margin-right: 6px; }
/* Модель — полностью, в несколько строк; код без пробелов (нет подписи в справочнике) тоже переносится. */
.table--videos td.cell-model .model-name { display: block; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
.table--videos .model-meta { display: flex; flex-wrap: wrap; gap: 2px 8px; font-size: var(--fs-s); margin-top: 2px; font-variant-numeric: tabular-nums; }
.filters--videos .filters-search { width: 240px !important; }

/* 20. Форма видео (pages/videos/{form,_form_body,_previews,_gallery_job}.html, partials/combobox_results.html) ---------- */
/* Комбобокс фотосессии: поле + ссылка «открыть промт ↗» в строку; опции — кнопки на всю ширину. */
.combobox-row { display: flex; align-items: center; gap: var(--sp-2); }
.combobox-row .combobox { flex: 1; min-width: 0; }
.combobox-row .combobox-link { margin-left: 0; }
.combobox-option { width: 100%; border: 0; background: transparent; color: var(--text); font: inherit; text-align: left; }
.combobox-option .name-en { font-style: italic; color: var(--text-muted); }
.combobox-option .badge { flex: none; }
.combobox-thumb-empty { width: 24px; height: 28px; border-radius: 3px; border: 1px dashed var(--border-strong); flex: none; }
/* Генерация: модель шире, три числовых поля рядом. */
.gen-layout { display: grid; grid-template-columns: minmax(0, 2fr) repeat(3, minmax(0, 1fr)); gap: 0 var(--sp-4); }
@media (max-width: 800px) { .gen-layout { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Медиа: строка 150px-колонок «постер · 360p · референс», ниже превью на всю ширину. */
.media-layout { display: flex; flex-direction: column; gap: var(--sp-5); margin-bottom: var(--sp-3); }
.media-row { display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-5); align-items: flex-start; }
.media-layout .slot-wrap { width: 150px; }
.media-layout .slot-caption { text-align: left; }
.media-title { font-weight: 600; margin-bottom: var(--sp-2); }
.media-locked { display: flex; align-items: center; gap: 8px; padding: var(--sp-4); border: 1px dashed var(--border-strong); border-radius: var(--radius-l); color: var(--text-faint); font-size: var(--fs-s); }
.media-locked .icon { width: 20px; height: 20px; opacity: .6; flex: none; }
/* Превью: шапка с кнопкой-лейблом загрузки, список 150px, действия в две строки. */
.previews-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.previews-head .media-title { margin: 0; }
.previews-upload { cursor: pointer; margin-left: auto; }
.previews-upload:has(input:focus-visible) { box-shadow: var(--focus-ring); }
.previews.htmx-request { opacity: .6; pointer-events: none; }
.previews.htmx-request .previews-upload .spinner { display: inline-block; }
.previews > .slot-err { margin: 0 0 var(--sp-2); }
.previews .empty-state { padding: var(--sp-4); }
.preview-list li { width: 150px; }
.preview-list .drag { background: rgba(0, 0, 0, .45); color: #fff; z-index: 2; }
@media (hover: hover) { .preview-list .drag:hover { background: rgba(0, 0, 0, .65); color: #fff; } }
.preview-actions { flex-wrap: wrap; }
.preview-actions .btn:first-child { flex: 1 1 100%; }
.preview-actions .btn-icon { width: 24px; height: 24px; margin-left: auto; }
/* 360p: бокс 9:16 шириной слота (плеер или заглушка) и кнопка под ним. */
.gallery-job { display: flex; flex-direction: column; gap: var(--sp-2); width: 150px; }
.gallery-box { position: relative; width: 150px; aspect-ratio: 9 / 16; border-radius: var(--radius); overflow: hidden; background: #000; border: 1px solid var(--border); }
.gallery-box video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-box:not(.gallery-box--empty) .slot-label { top: 0; bottom: auto; padding: 6px 8px 14px; background: linear-gradient(to bottom, rgba(0, 0, 0, .55), transparent); }
.gallery-box--empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 8px 8px 28px;
  background: var(--surface-2); border: 2px dashed var(--border-strong); color: var(--text-faint); font-size: var(--fs-s); text-align: center;
}
.gallery-box--empty .icon { width: 22px; height: 22px; opacity: .6; }
.gallery-box--empty .slot-label { color: var(--text-muted); text-shadow: none; background: none; text-align: center; }
.gallery-box--error { border-color: var(--danger); }
.gallery-box--error .slot-err { margin: 0; }
.success-text { color: var(--success); }

/* 21. Адаптив -------------------------------------------------------------- */
/* Брейкпоинты: от 1024px — десктоп, не меняется; ≤1023.98 — планшет; ≤767.98 — телефон. Раскладка зависит
   от ширины, размеры контролов — от (pointer: coarse), всё, что держится на наведении, — от (hover: none).
   Старые пороги 720/800/1100/1240/1400 не трогаем. Раздел в конце файла: при равной специфичности он выигрывает. */

/* 21.1 Планшет: шапка без подписей, разделы — лента с горизонтальной прокруткой. */
@media (max-width: 1023.98px) {
  body { min-width: 0; }
  /* Подпись бренда — как .sr-only: у ссылки-логотипа остаётся доступное имя. */
  .topbar-brand span { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
  .topbar-user-name, .topbar-user-roles { display: none; }
  .topbar-nav { flex: 1; min-width: 0; margin-left: 0; overflow-x: auto; scrollbar-width: none; }
  .topbar-nav::-webkit-scrollbar { display: none; }
  .topbar-nav a { flex: none; }
}

/* 21.2 Телефон: строки с логотипом нет (владелец 27.09.2026: «просто занимает место») — разделы и пользователь в
   нижнем таббаре. От шапки остаётся только зона статус-бара (safe-area; в Safari это 0, на экране «Домой» — под
   вырезом), --top-h = она же: липкие панели встают к самому верху. Узкие поля, текст крупнее. */
@media (max-width: 767.98px) {
  :root { --header-h: 0px; --gutter: 12px; --fs: 14px; --dock: calc(var(--tabbar-h) + var(--tabbar-b)); }
  .topbar > * { display: none; }
  .topbar { border-bottom: 0; }
  .container { padding-bottom: calc(var(--dock) + 16px); }
  .page-actions { margin-left: 0; width: 100%; }
  .page-actions > .btn { flex: 1; }
  .page-head .page-hint { order: 3; flex-basis: 100%; }
  .toasts { left: max(12px, var(--safe-l)); right: max(12px, var(--safe-r)); width: auto; max-width: none; }
  .error-page { margin: 32px auto; }
}

/* Нижний таббар (layouts/base.html): вкладки разделов с NavItem.tab и «Ещё» — шторка #more-sheet с остальными
   разделами, пользователем, темой и выходом. Формы — экраны деталей: таббара нет (назад — «← К списку»,
   внизу — панель сохранения); кроме «Тексты» → «Промо и советы» (форма промо — часть страницы раздела со списками,
   .texts-lists, 28). Выбраны строки — место таббара занимает .bulk-bar, таббар уезжает вниз. */
.tabbar { display: none; }
@media (max-width: 767.98px) {
  .tabbar {
    position: fixed; z-index: 55; left: max(12px, var(--safe-l)); right: max(12px, var(--safe-r)); bottom: var(--tabbar-b);
    height: var(--tabbar-h); display: flex; gap: 2px; padding: 6px;
    border: 1px solid var(--border); border-radius: 22px; box-shadow: var(--shadow-2);
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
    transition: transform .2s, visibility .2s;
  }
  .tabbar-item {
    flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    padding: 0 2px; border: 0; border-radius: 16px; background: none; color: var(--text-muted);
    font: 500 11px/1.2 var(--font); text-decoration: none;
  }
  .tabbar-item > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tabbar-item .icon { width: 22px; height: 22px; }
  .tabbar-item:is([aria-current], .is-active) { background: var(--primary-soft); color: var(--primary-text); font-weight: 600; }
  body:has(.bulk-bar.is-visible) .tabbar { transform: translateY(calc(100% + 40px)); visibility: hidden; }
  body:has(form[data-dirty-guard]):not(:has(.texts-lists)) { --dock: 0px; }
  body:has(form[data-dirty-guard]):not(:has(.texts-lists)) .tabbar { display: none; }
}

/* 21.3 Шторка .sheet — нижняя панель на Popover API (iOS 17+), без JS: открывает <button popovertarget="id">,
   закрывают тап по фону, Esc, кнопка с popovertargetaction="hide" и свайп вниз (admin.js «Шторки»). Части: .sheet-handle — полоска сверху,
   .sheet-head — заголовок .sheet-title и «×», .sheet-foot — липкий низ, .sheet-group > .sheet-row — строки, как
   в настройках iOS. display задаём только открытой шторке — закрытую прячет браузер. color обязателен: браузер
   даёт popover цвет CanvasText. .sheet--inline — шторка только до 1023px: на десктопе содержимое стоит в разметке
   как обычно (display: contents), кнопка .sheet-open и служебные части шторки скрыты. */
.sheet {
  position: fixed; inset: auto 0 0; margin: 0 auto; width: 100%; max-width: 560px;
  max-height: calc(100dvh - var(--top-h) - 8px); overflow-y: auto; overscroll-behavior: contain;
  padding: 8px max(16px, var(--safe-r)) calc(16px + var(--safe-b)) max(16px, var(--safe-l));
  border: 0; border-radius: 20px 20px 0 0; background: var(--surface); color: var(--text); box-shadow: var(--shadow-up);
  /* Выезжает снизу и уезжает обратно: display/overlay держат закрывающуюся шторку на экране до конца анимации.
     Первая transition — для браузеров без allow-discrete (там вторая строка отбрасывается целиком). */
  /* Раскладка — в базе, а не в :popover-open: при закрытии display держится до конца анимации, и без неё
     содержимое уезжающей шторки на эти .25 с выстраивалось бы в ряд. */
  flex-direction: column; gap: 12px;
  transform: translateY(100%);
  transition: transform .25s cubic-bezier(.32, .72, 0, 1);
  transition: transform .25s cubic-bezier(.32, .72, 0, 1), display .25s allow-discrete, overlay .25s allow-discrete;
}
.sheet:popover-open { display: flex; transform: none; }
@starting-style { .sheet:popover-open { transform: translateY(100%); } }
.sheet::backdrop { background: rgb(0 0 0 / .4); }
/* Полоска-«ручка»: за неё и заголовок шторку стягивают вниз (admin.js «Шторки»), браузер при этом не прокручивает. */
.sheet-handle { flex: none; width: 36px; height: 4px; margin: 0 auto; border-radius: 2px; background: var(--border-strong); }
.sheet :is(.sheet-handle, .sheet-head) { touch-action: none; }
.sheet-head { display: flex; align-items: center; gap: var(--sp-2); min-height: 40px; }
.sheet-title { flex: 1; min-width: 0; margin: 0; font-size: 17px; }
.sheet-foot { position: sticky; bottom: 0; z-index: 1; }
.sheet-group { display: flex; flex-direction: column; border-radius: var(--radius-l); background: var(--surface-2); overflow: hidden; }
.sheet-group > * + * { border-top: 1px solid var(--border-strong); }
.sheet-row {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%; min-height: 52px; padding: 8px 16px;
  border: 0; background: none; color: var(--text); font: 500 15px/1.3 var(--font); text-align: left; text-decoration: none;
}
.sheet-row .icon { width: 20px; height: 20px; color: var(--text-muted); }
a.sheet-row::after { content: ""; flex: none; width: 8px; height: 8px; margin: 0 4px 0 auto; border: solid var(--text-faint); border-width: 1.6px 1.6px 0 0; transform: rotate(45deg); }
a.sheet-row[aria-current], a.sheet-row[aria-current] .icon { color: var(--primary-text); }
:is(a, button).sheet-row:active { background: var(--surface-3); }
@media (hover: hover) { :is(a, button).sheet-row:hover { background: var(--surface-3); text-decoration: none; } }
.sheet-row--danger { color: var(--danger); }
.sheet-row .theme-switch { margin-left: auto; }
.sheet-user-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.sheet-user-text small { font-weight: 400; font-size: var(--fs-s); }
@media (max-width: 1023.98px) {
  /* Под открытой шторкой страница не прокручивается. */
  html:has(.sheet:popover-open) { overflow: hidden; }
  /* …и не принимает касаний — ни пока шторка открыта, ни 400 мс после закрытия (admin.js «Поиск»): тап по затемнению
     только закрывает шторку, а не фокусирует поиск под пальцем (клавиатура на iOS) и не открывает ссылку. */
  :is(html:has(.sheet:popover-open), html.is-sheet-closing) body { pointer-events: none; }
  .sheet { pointer-events: auto; }
}
@media (min-width: 1024px) {
  .sheet--inline[popover] { display: contents; color: inherit; }
  .sheet--inline > :is(.sheet-handle, .sheet-head, .sheet-foot), .sheet-open { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sheet, .tabbar { transition: none; }
}

/* 21.4 Касания и iOS. :hover-правила выше обёрнуты в @media (hover: hover) — на касании они «залипают» после тапа. */
/* Подсказки по способу ввода: .hint-dnd — для мыши («перетащите»), .hint-tap — для касания («выбрать»). */
.hint-tap { display: none; }
@media (hover: none) {
  /* Что было видно только при наведении — видно всегда. */
  .row-actions, .rubric-actions, .slot-actions { opacity: 1; }
  /* Подсказки горячих клавиш (⌘S, «/», стрелки) и перетаскивания без клавиатуры и мыши бесполезны. */
  .save-bar .save-right { display: none; }
  main:has(#filters) .page-head .page-hint { display: none; }
  .hint-dnd { display: none; }
  .hint-tap { display: inline; }
}
@media (pointer: coarse) {
  /* 16px в полях: при меньшем шрифте iOS увеличивает страницу при фокусе. */
  .input, .select, .textarea, .input-sm, .select-sm { font-size: 16px; }
  /* Высоты под палец: поля и кнопки 44px, малые 40px, кнопки-иконки 40×40. */
  .input, .select, .btn { height: 44px; }
  .input-sm, .select-sm, .btn-sm { height: 40px; }
  .btn-icon { width: 40px; height: 40px; }
  /* Там, где высоту задаёт более специфичное правило. */
  .bulk-bar .select-sm, .bulk-bar .btn-sm { height: 40px; }
  .rubric-edit .input, .rubric-edit .select, .rubric-edit .rubric-field-toggle, .rubric-edit .rubric-edit-actions .btn { height: 44px; }
  .rubric-actions { width: 82px; } /* две кнопки по 40px и зазор 2px; ширина общая с колонкой .rubric-head */
  .rubric-head-drag { width: 40px; }
  /* Кнопки поверх слота и превью видео. */
  .slot-actions .btn { height: 34px; }
  .slot-actions .btn-icon { width: 34px; }
  .preview-actions .btn { height: 34px; }
  .preview-actions .btn-icon { width: 34px; height: 34px; }
  /* Чекбоксы, чипы-флаги, вкладки. */
  .checkbox input, .col-check input, [data-check-all] { width: 22px; height: 22px; }
  .chip-toggle, .preview-variants .chip-toggle, .app-card-toolbar .chip-toggle { height: 36px; font-size: 14px; }
  .tab { height: 36px; }
  /* Тумблер в ячейке списка — пилюля с рамкой, дорожка обычного размера. */
  .toggle--cell { min-height: 36px; padding: 0 10px; gap: 8px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); }
  .toggle--cell .toggle-track { width: 32px; height: 18px; }
  .toggle--cell .toggle-track::after { width: 14px; height: 14px; }
  .toggle--cell input:checked + .toggle-track::after { transform: translateX(14px); }
  .toggle--cell .toggle-label { font-size: 13px; }
  /* Ручка перетаскивания: крупная зона, без меню долгого нажатия и выделения текста. */
  .drag { width: 40px; height: 40px; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
  /* Мелкие кнопки. */
  .theme-switch-btn { width: 40px; height: 36px; }
  .topbar-nav a { display: inline-flex; align-items: center; min-height: 40px; }
  .pager a, .pager span { min-width: 40px; height: 40px; }
  .pager-mini .pg-btn { min-width: 36px; height: 36px; }
  .toast-close { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; margin: -6px -8px -6px 0; }
  .seg-btn { height: 32px; }
  /* Комбобокс: крестик 36×36 по центру поля, текст под него не заходит. */
  .combobox .input { padding-right: 44px; }
  .combobox-clear { top: 50%; translate: 0 -50%; width: 36px; height: 36px; }
  /* Autogrow не выше половины экрана: иначе поле вместе с клавиатурой не помещается. */
  .textarea[data-autogrow] { max-height: 50dvh; overflow-y: auto; }
}

/* 21.5 Списки /prompts и /videos (_filters, _table, _row, _bulk_bar, partials/pager.html). До 1023px: в строке
   фильтров — поиск и кнопка .filters-open, остальное — в шторке #filters-more (внутри той же формы #filters, так что
   hx-get и hx-push-url работают как раньше); thead — ряд чипов сортировки и «Все»; строки — карточки на CSS-сетке
   (шаблоны строк прежние, hx-target="closest tr" попадает в карточку). До 767px: .bulk-bar — на месте таббара,
   в пейджере вместо номеров « ‹ 5 / 20 › », «На странице» — в шторке фильтров. .table--videos несёт и .table--prompts:
   карточка промта — :not(.table--videos). */
.check-all-text, .pager .pg-status { display: none; }
/* Список без номеров страниц (Pager.kind none) — «5» между ‹ › и на десктопе. */
.pager .pg-status.is-always { display: inline-flex; color: var(--text); font-variant-numeric: tabular-nums; }
@keyframes card-hl { 0%, 40% { box-shadow: inset 0 0 0 100vmax var(--hl); } to { box-shadow: inset 0 0 0 100vmax transparent; } }
@keyframes card-saved { from { box-shadow: inset 0 0 0 100vmax var(--success-soft); } to { box-shadow: inset 0 0 0 100vmax transparent; } }
@media (max-width: 1023.98px) {
  /* Фильтры: поиск на всю строку и кнопка шторки; точка на ней — выбран хотя бы один фильтр (option с непустым
     value, кроме «На странице», или чип; select:has() здесь нельзя — :has() не вкладывается в :has(), правило
     отбросилось бы целиком). */
  .filters { flex-wrap: nowrap; }
  .filters .filters-search { flex: 1; min-width: 0; width: auto !important; }
  .filters .filters-open { flex: none; position: relative; width: 34px; height: 34px; border-color: var(--border-strong); background: var(--surface); color: var(--text); }
  .filters-open .icon { width: 18px; height: 18px; }
  #filters:has(.filters-more :is(select:not([name=per_page]) > option:checked:not([value=""]), input:checked)) .filters-open::after {
    content: ""; position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; border-radius: 50%;
    background: var(--primary); box-shadow: 0 0 0 2px var(--surface);
  }
  /* Шторка: заголовок и «Сбросить» в одной строке, ниже поля и чипы в одну колонку
     (при 16px «Любой тип персоны» в полколонки не помещается), внизу «Показать». */
  .filters-more { grid-template-columns: minmax(0, 1fr) auto; align-items: center; } /* сетка и на время закрытия */
  .filters-more:popover-open { display: grid; }
  .filters-more > * { grid-column: 1 / -1; }
  .filters-more > .sheet-head { grid-column: 1; grid-row: 2; }
  .filters-more > .filters-reset { grid-column: 2; grid-row: 2; justify-self: end; display: inline-flex; align-items: center; min-height: 40px; margin: 0; }
  .filters-more .select { width: 100%; min-width: 0; max-width: none; }
  .filters-more .filters-sep { display: none; }
  .filters-more .filters-chips { gap: 8px; }

  /* thead — ряд чипов: сортировки (активная — с акцентом и стрелкой) и «Все» (label.check-all). Перерисовывается вместе с #table. */
  .table thead { display: block; }
  .table thead tr { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
  .table thead th, .table th .th-sub { display: contents; }
  .table thead th:not(:has(a.sort, [data-check-all])) { display: none; }
  .table thead :is(a.sort, .check-all), .table--prompts th .th-sub a.sort {
    display: inline-flex; align-items: center; gap: 6px; height: 36px; padding: 0 12px;
    border: 1px solid var(--border-strong); border-radius: 999px; background: var(--surface);
    color: var(--text-muted); font-size: var(--fs); font-weight: 500; cursor: pointer;
  }
  .table thead a.sort:is(.asc, .desc), .table thead .check-all:has(input:checked) { background: var(--primary-soft); border-color: var(--primary); color: var(--primary-text); }
  .table thead .check-all-text { display: inline; }
  /* Активная сортировка видна по чипу — подпись «сортировка: …» не нужна. */
  .list-meta { flex-wrap: wrap; }
  .list-meta .ml-auto { display: none; }

  /* Строки → карточки: на телефоне одна колонка, на планшете две (соседние карточки одной высоты, содержимое сверху). */
  .table-wrap { overflow: visible; }
  .table { display: block; min-width: 0; border: 0; background: none; }
  .table tbody { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: 10px; }
  .table tbody tr { display: grid; gap: 8px 10px; align-items: start; align-content: start; padding: 12px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l); }
  .table tbody td { display: block; width: auto; padding: 0; border: 0; background: none; }
  /* Ячейка статуса раскладывается: select и тумблеры — отдельные строки карточки (--st от data-status наследуется). */
  .table td.cell-status { display: contents; }
  .table td.cell-status > .select { grid-area: status; margin: 0; }
  .table .cell-toggles { grid-area: toggles; }
  .table td.col-check { grid-area: check; justify-self: end; }
  .table td.cell-id { grid-area: id; }
  .table td.cell-name { grid-area: name; }
  .table td.cell-counter { grid-area: counter; align-self: center; white-space: nowrap; }
  .table td.cell-actions { grid-area: actions; justify-self: end; align-self: center; }
  .table td.cell-photos { grid-area: photos; }
  .table td.cell-prompts { grid-area: prompts; align-self: center; }
  .table td.cell-poster { grid-area: poster; }
  .table td.cell-model { grid-area: model; }
  .table td.cell-id .muted { display: inline; margin-left: 6px; }
  .table tbody tr:has(input[name=ids]:checked) { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
  .table tr.row--muted td { opacity: 1; }
  .table tr.row--muted { opacity: .6; }
  .table tr.row-empty { display: block; grid-column: 1 / -1; padding: 0; border: 0; background: none; }
  .table--prompts tr.row-empty td { padding: 0; }
  .table--prompts tr.row-empty .empty-state { border: 1px dashed var(--border-strong); }
  /* Подсветка ?hl= и «сохранено» — внутренней тенью по карточке: row-hl на td заканчивается прозрачным фоном
     (forwards) и стёр бы фон карточки. */
  .table tr:is(.row-hl, .row-saved) > td { animation: none; }
  .table tr.row-hl { animation: card-hl 3s ease-out; }
  .table tr.row-saved { animation: card-saved 1s ease-out; }

  /* Карточка промта: #id и дата · счётчики · ☐ / название / фото / статус / тумблеры / промты · действия. */
  .table--prompts:not(.table--videos) tbody tr {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "id counter check" "name name name" "photos photos photos" "status status status" "toggles toggles toggles" "prompts actions actions";
  }
  .table--prompts:not(.table--videos) td:is(.cell-id, .col-check) { align-self: center; }
  /* Счётчики в строку, с видимыми подписями (title на касании не показывается). */
  .table--prompts td.cell-counter :is(b, .muted) { display: inline; }
  .table--prompts td.cell-counter .muted { margin-left: 8px; }
  .table--prompts td.cell-counter :is(b, .muted)::before { font-weight: 400; color: var(--text-muted); }
  .table--prompts:not(.table--videos) td.cell-counter b::before { content: "ген. "; }
  .table--prompts:not(.table--videos) td.cell-counter .muted::before { content: "поп. "; }
  /* Фото — все слоты в одну строку, по ширине карточки. */
  .table .thumb-strip { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); grid-template-columns: none; max-width: 420px; }
  .table .thumb-slot { width: auto; height: auto; aspect-ratio: 44 / 51; }

  /* Карточка видео: постер слева на три строки (#id · ☐ / название / модель), ниже статус, тумблеры, счётчики · действия.
     Лишнюю высоту постера забирает строка модели — #id и название прижаты кверху. */
  .table--videos tbody tr {
    grid-template-columns: 72px minmax(0, 1fr) auto;
    grid-template-rows: auto auto 1fr;
    grid-template-areas: "poster id check" "poster name name" "poster model model" "status status status" "toggles toggles toggles" "counter counter actions";
  }
  .table--videos td.cell-counter b::before { content: "генераций "; }
  .table--videos td.cell-counter .muted::before { content: "изм. "; }

  /* Панель массовых действий на планшете помещается как есть — только над home-индикатором. */
  .bulk-bar { bottom: calc(20px + var(--safe-b)); }
}
@media (max-width: 1023.98px) and (hover: hover) {
  .table tbody tr:hover td { background: none; }
}
@media (max-width: 1023.98px) and (pointer: coarse) {
  .filters .filters-open { width: 44px; height: 44px; }
}
@media (max-width: 767.98px) {
  /* Панель массовых действий встаёт на место таббара (он уезжает вниз): «Выбрано N · Снять» / действие и значение / «Применить». */
  .bulk-bar {
    left: max(8px, var(--safe-l)); right: max(8px, var(--safe-r)); bottom: var(--tabbar-b);
    transform: translateY(calc(100% + 40px));
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px;
    grid-template-areas: "count clear" "action value" "apply apply";
  }
  .bulk-bar.is-visible { transform: none; }
  .bulk-bar .bulk-count { grid-area: count; align-self: center; margin: 0; }
  .bulk-bar [data-bulk-clear] { grid-area: clear; justify-self: end; }
  .bulk-bar [name=action] { grid-area: action; }
  .bulk-bar [data-bulk-value] { grid-area: value; }
  .bulk-bar [type=submit] { grid-area: apply; }
  /* Действие без значения («Поднять», «В архив») — select на всю ширину. */
  .bulk-bar:not(:has([data-bulk-value]:not([hidden]))) [name=action] { grid-column: 1 / -1; }
  .bulk-bar .select { width: 100%; }
  .bulk-bar .filters-sep { display: none; }
  body:has(.bulk-bar.is-visible) .container { padding-bottom: calc(180px + var(--safe-b)); }
  /* Пейджер: « ‹ 5 / 20 › » во всю ширину, номер посередине, кнопки 48px; «На странице» — последним полем шторки
     фильтров (одна страница — внизу ничего). */
  .pager .pg-num, .pager .per-page { display: none; }
  .pager.is-single { display: none; }
  .pager { gap: var(--sp-2); }
  .pager .pg-btn { min-width: 48px; height: 48px; font-size: 20px; }
  .pager .pg-status { display: inline-flex; flex: 1; min-width: 0; padding: 0 8px; color: var(--text); font-size: var(--fs-l); font-variant-numeric: tabular-nums; }
  .filters-more .per-page-select { display: block; }
}

/* 21.6 Формы промта и видео (macros/forms.html, pages/{prompts,videos}/form.html и _form_body.html, partials/upload_slot.html).
   До 1023px: «Как в приложении» и факты промта — шторка #preview-sheet (.sheet--inline; открывает кнопка-глаз .sheet-open
   в панели сохранения), панель сохранения переносится и стоит над home-индикатором. До 767px: поля в одну колонку, табы 3×2,
   слоты по два в ряд, панель сохранения во всю ширину экрана без «Отмены» (её дублирует «← К списку» в шапке).
   Aside формы видео (факты) остаётся под формой. */
@media (max-width: 1023.98px) {
  .save-bar { flex-wrap: wrap; padding-bottom: calc(10px + var(--safe-b)); }
  .save-bar > .sheet-open { flex: none; width: 34px; height: 34px; border-color: var(--border-strong); background: var(--surface); color: var(--text); }
  .save-bar > .sheet-open .icon { width: 18px; height: 18px; }
  /* Aside, у которого всё в шторке, места под формой не занимает (открытая шторка — в top layer, display: contents ей не мешает). */
  .form-aside:has(> .sheet--inline) { display: contents; }
  /* В шторке секция без рамки (заголовок — у шторки), переключатели и карточка — колонкой 300px по центру. */
  .preview-sheet > fieldset.section { margin: 0; padding: 0; border: 0; background: none; }
  .preview-sheet > fieldset.section > legend { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
  .preview-sheet .preview-card { width: 100%; max-width: 300px; margin: 0 auto; }
}
@media (max-width: 1023.98px) and (pointer: coarse) {
  .save-bar > .sheet-open { width: 44px; height: 44px; }
}
@media (max-width: 767.98px) {
  /* Шапка формы: «← К списку» первой строкой, как «назад» в iOS, ниже заголовок с бейджами. */
  .page-head--form { flex-direction: column; align-items: flex-start; }
  .page-head--form .page-actions { order: -1; width: auto; }
  /* Поля в одну колонку, секции плотнее. */
  .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); }
  fieldset.section { padding: 10px 12px 4px; }
  /* Табы промтов по типу персоны — сетка 3×2: все вкладки, точки «заполнено» и ошибки видны сразу. */
  .tabs-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); width: 100%; }
  .tab { justify-content: center; height: 36px; padding: 0 6px; }
  /* Слоты фото — по два в ряд на ширину секции («Заменить» и «×» помещаются); оживление — поля над видео-слотом. */
  .slot-grid:not(.slot-grid--single) { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }
  .slot-grid:not(.slot-grid--single) .slot { width: 100%; }
  .animate-layout { flex-direction: column; align-items: stretch; }
  .animate-layout .slot-grid--single { grid-template-columns: 150px; align-self: flex-start; }
  /* Форма видео: постер · 360p, ниже референс — по два в ряд, превью тоже; в «Генерации» модель на всю ширину, три числа в ряд. */
  .media-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .media-layout .slot-wrap, .gallery-job { width: auto; }
  .gallery-box { width: 100%; }
  .preview-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .preview-list li { width: auto; }
  .gen-layout { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--sp-2); }
  .gen-layout > :first-child { grid-column: 1 / -1; }
  /* Комбобокс: ссылка «открыть промт ↗» под полем, список не выше 40% экрана (над клавиатурой),
     опция в две строки — картинка и название / рубрика и статус. */
  .combobox-row { flex-wrap: wrap; }
  .combobox-row .combobox { flex-basis: 100%; }
  .combobox-list { max-height: min(320px, 40dvh); }
  .combobox-option { flex-wrap: wrap; min-height: 44px; }
  .combobox-option .opt-name { flex-basis: calc(100% - 32px); white-space: normal; }
  .combobox-option .opt-meta { margin-left: 32px; }
  /* Панель сохранения — во всю ширину экрана, снизу над home-индикатором; статус — отдельной строкой над кнопками. */
  .save-bar {
    margin: 16px calc(-1 * max(var(--gutter), var(--safe-r))) 0 calc(-1 * max(var(--gutter), var(--safe-l)));
    padding: 8px max(12px, var(--safe-r)) calc(8px + var(--safe-b)) max(12px, var(--safe-l));
    border-width: 1px 0 0; border-radius: 16px 16px 0 0;
  }
  .save-bar > .btn:not(.sheet-open) { flex: 1 1 auto; padding: 0 10px; }
  .save-bar > a.btn-ghost, .save-bar .save-right { display: none; }
  .save-bar .save-status, .save-bar .dirty-note { order: -1; flex-basis: 100%; margin-left: 0; }
  /* Форма промта кончается панелью сохранения (aside — в шторке): внизу страницы она прилегает к краю экрана. */
  .container:has(.preview-sheet) { padding-bottom: 0; }
}
@media (pointer: coarse) {
  /* Поле в фокусе — панель сохранения возвращается в поток, в конец формы: липкая висела бы над клавиатурой и закрывала поле. */
  form[data-dirty-guard]:has(:is(.input, .textarea):focus) .save-bar { position: static; }
  /* Поле комбобокса прокручивается к верху экрана (admin.js при фокусе), чтобы список не ушёл под клавиатуру. */
  .combobox .input { scroll-margin-top: calc(var(--top-h) + 8px); }
}

/* 21.7 Дашборд (pages/dashboard.html) и справочники (pages/rubrics/*, pages/person_types/*). До 1023px: инлайн-форма
   рубрики в две строки (поля / тумблер и кнопки). До 767px: карточки статистики — одной колонкой, компактные (подпись
   и число в строку, чипы ниже); строка рубрики — карточка из двух рядов (название, тумблер, кнопки / спецрубрика,
   формат, счётчики, видео), ручка — полоса слева во всю высоту строки; шапка колонок скрыта; инлайн-форма — в две
   колонки; типы персон — по два в ряд, карточка в режиме редактирования — на всю ширину, фото слева от полей. */
@media (max-width: 1023.98px) {
  /* Фиксированные колонки формата и спецрубрики оставляем, тумблер и кнопки — второй строкой. */
  form.rubric-edit { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) 96px 150px; }
  .rubric-edit .rubric-field-toggle { grid-column: 1 / 3; justify-self: start; margin-top: 0; }
  .rubric-edit .rubric-edit-actions { grid-column: 3 / -1; justify-content: flex-end; margin-top: 0; }
}
@media (max-width: 767.98px) {
  /* Дашборд: подпись слева, число справа, чипы статусов — строкой ниже. */
  .stats-row { grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
  .stat-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; column-gap: var(--sp-3); padding: 12px 16px; }
  .stat-card-value { grid-area: 1 / 2; margin: 0; font-size: 26px; font-variant-numeric: tabular-nums; }
  .stat-card-chips { grid-column: 1 / -1; margin-top: var(--sp-2); }

  /* Рубрики: без шапки колонок (подписи ячеек понятны по содержимому) — у списка снова верхняя рамка и скругление. */
  .rubric-head { display: none; }
  .rubric-head + .rubric-list { border-top: 1px solid var(--border); border-radius: var(--radius-l); }
  /* Строка в два ряда: перенос делает ::after на всю ширину (order 4). row-gap маленький — пустой ряд ::after его удваивает. */
  .rubric-list li.rubric:not(.rubric--edit) { position: relative; flex-wrap: wrap; gap: 3px 8px; padding: 10px 8px 10px 12px; }
  .rubric-list li.rubric:not(.rubric--edit)::after { content: ""; order: 4; flex-basis: 100%; }
  .rubric-list li.rubric:not(.rubric--edit):has(> .drag) { padding-left: 48px; }
  .rubric-list li.rubric:not(.rubric--edit) > .drag { position: absolute; top: 0; bottom: 0; left: 2px; width: 44px; height: auto; }
  .rubric-list li.rubric > .rubric-name { order: 1; flex: 1 1 0; }
  .rubric-list li.rubric > .toggle { order: 2; }
  .rubric-list li.rubric > .rubric-actions { order: 3; width: auto; }
  .rubric-list li.rubric > :is(.rubric-special, .rubric-ratio, .rubric-counts, .rubric-video) { order: 5; width: auto; justify-content: flex-start; }
  .rubric-list li.rubric > :is(.rubric-special, .rubric-video):empty { display: none; }
  /* Инлайн-форма: названия, тумблер и кнопки — на всю ширину, формат и спецрубрика — рядом; ручки нет (форма не тащится). */
  .rubric-list li.rubric--edit { padding: 12px; }
  .rubric-list li.rubric--edit > .drag { display: none; }
  form.rubric-edit { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  form.rubric-edit > .rubric-field:nth-of-type(-n+2), .rubric-edit .rubric-field-toggle, .rubric-edit .rubric-edit-actions { grid-column: 1 / -1; }
  .rubric-edit .rubric-edit-actions .btn { flex: 1; }

  /* Типы персон: по два в ряд, фото квадратом во всю ширину карточки. */
  .person-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--sp-3); }
  .person-card .person-photo { width: 100%; height: auto; aspect-ratio: 1; }
  .person-card .person-actions > .btn { flex: 1; }
  /* В режиме редактирования — на всю ширину, фото слева: в полкарточки не помещаются «Множественное число» и счётчик. */
  .person-card--edit { grid-column: 1 / -1; }
  .person-card--edit > form { display: grid; grid-template-columns: 120px minmax(0, 1fr); align-items: start; }
}
@media (hover: none) {
  /* Отклик на тап вместо наведения (:active на iOS работает с touchstart-слушателем из admin.js). */
  /* отклик на тап — раздел 25 */
}
@media (pointer: coarse) {
  /* Дашборд: чипы статусов и строки «Требует внимания» — под палец; ссылка-подпись карточки — выше зона нажатия. */
  .stat-card-chips { gap: 8px; }
  .chip { min-height: 36px; padding: 0 12px; }
  .attention-list a { min-height: 44px; padding: 10px 14px; }
  .stat-card-label a { display: inline-block; padding-block: 8px; margin-block: -8px; }
  /* Рубрики: зона нажатия тумблера и счётчиков выше (ширина прежняя — колонки совпадают с шапкой на планшете). */
  .rubric-list li.rubric > .toggle { padding-block: 11px; margin-block: -11px; }
  .rubric-counts .rubric-count { padding-block: 6px; }
}

/* 22. Экран /tasks (pages/tasks/*; пейджер — partials/pager.html, раздел 9) ------------------------------------ */
/* Раздел после 21: при равной специфичности перекрывает общие правила карточек списков (21.5). */
/* Тон статуса: tr[data-st] — полоса слева и бейдж (.badge берёт --st). */
[data-st="run"] { --st: var(--st-t-run); }
[data-st="ok"] { --st: var(--st-t-ok); }
[data-st="fail"] { --st: var(--st-t-fail); }
[data-st="cfail"] { --st: var(--st-t-cfail); }
[data-st="stuck"] { --st: var(--st-t-stuck); }

/* Панель: поиск (форма /tasks/find), период (#period, .sheet--inline), фильтры (#filters). На десктопе — строка
   поиска и периода, ниже строка селектов (содержимое шторок стоит в разметке, display: contents). */
.tasks-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.tasks-find { width: 320px; margin: 0; }
.period { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.period-chips { display: flex; flex-wrap: wrap; gap: 4px; }
a.chip-toggle { color: var(--text); }
@media (hover: hover) { a.chip-toggle:hover { text-decoration: none; } }
.chip-toggle[aria-current] { background: var(--primary-soft); border-color: var(--primary); color: var(--primary-text); font-weight: 600; }
.period-dates { display: flex; align-items: center; gap: 6px; margin: 0; }
.period-dates .input { width: 150px; }
.period-date { display: block; }
.period-date-cap { display: none; } /* подпись над полем — только в шторке до 1023px */
.period-dash { color: var(--text-faint); }
.period-caret { margin-left: 4px; font-size: 10px; color: var(--text-muted); }
.tasks-bar > .filters--tasks { flex-basis: 100%; margin: 0; }
.filters--tasks .filters-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }

/* Время страницы справа в строке пейджера сверху: «21.05 14:02 → 13:40». */
.pager-time { color: var(--text-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Сводка (#summary, _summary.html): строка здоровья, чипы статусов, частые ошибки, карточка пользователя. */
.tasks-summary { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.tasks-summary-skeleton { height: 72px; border-radius: var(--radius); background: linear-gradient(90deg, var(--surface-2), var(--surface-3), var(--surface-2)); background-size: 200% 100%; animation: skeleton 1.4s ease-in-out infinite; opacity: .7; }
@keyframes skeleton { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { .tasks-summary-skeleton { animation: none; } }
.tasks-health { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 14px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text-muted); font-size: var(--fs-s); }
.tasks-health b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.tasks-health a { color: inherit; }
.tasks-health.is-alarm { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); background: var(--danger-soft); color: var(--danger); }
.tasks-health.is-alarm b { color: var(--danger); }
.st-chips { display: flex; align-items: center; gap: 6px; overflow-x: auto; scrollbar-width: none; }
.st-chips::-webkit-scrollbar { display: none; }
.st-chip { flex: none; display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; border: 1px solid var(--border-strong); border-radius: 999px; background: var(--surface); color: var(--text-muted); white-space: nowrap; }
.st-chip b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
@media (hover: hover) { .st-chip:hover { text-decoration: none; background: var(--surface-2); } }
.st-chip.is-fail { border-color: color-mix(in srgb, var(--st-t-fail) 45%, var(--border)); }
.st-chip.is-fail, .st-chip.is-fail b { color: var(--st-t-fail); }
.st-chip.is-stuck { border-color: color-mix(in srgb, var(--st-t-stuck) 45%, var(--border)); }
.st-chip.is-stuck, .st-chip.is-stuck b { color: var(--st-t-stuck); }
.st-chip[aria-current] { background: var(--primary-soft); border-color: var(--primary); }
.st-chip[aria-current], .st-chip[aria-current] b { color: var(--primary-text); }
.st-chips-period { flex: none; font-size: var(--fs-s); }
.freq-errors { padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.freq-errors-title { margin: 0 0 2px; font-size: var(--fs); font-weight: 600; }
.freq-errors-title .muted { font-weight: 400; }
.freq-errors-list { list-style: none; display: flex; flex-direction: column; }
.freq-errors-list a { display: flex; align-items: baseline; gap: 2px 12px; padding: 5px 0; color: var(--text); }
.freq-errors-list li + li a { border-top: 1px solid var(--border); }
@media (hover: hover) { .freq-errors-list a:hover { text-decoration: none; color: var(--primary-text); } }
.freq-errors-list a[aria-current] { color: var(--primary-text); font-weight: 600; }
.freq-errors-text { flex: 1; min-width: 0; }
.freq-errors-meta { flex: none; color: var(--text-muted); font-size: var(--fs-s); font-variant-numeric: tabular-nums; white-space: nowrap; }
.task-user-card { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.task-user-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; font-size: var(--fs-l); }
.task-user-facts { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0; font-size: var(--fs-s); }
.task-user-facts div { display: flex; gap: 6px; }
.task-user-facts dt { color: var(--text-muted); }
.task-user-facts dd { margin: 0; }

/* Чипы активных фильтров (× — ссылка без параметра) и подписи под фильтрами. */
.filter-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: var(--sp-2); }
.filter-chip { display: inline-flex; align-items: center; gap: 6px; height: 28px; max-width: 100%; padding: 0 8px 0 10px; border-radius: 999px; background: var(--primary-soft); color: var(--primary-text); font-size: var(--fs-s); white-space: nowrap; overflow: hidden; }
.filter-chip-x { font-size: 15px; line-height: 1; opacity: .7; }
@media (hover: hover) {
  .filter-chip:hover { text-decoration: none; filter: brightness(.95); }
  .filter-chip:hover .filter-chip-x { opacity: 1; }
}
.filter-chips .filters-reset { font-size: var(--fs-s); }
.tasks-note { margin: 0 0 var(--sp-2); font-size: var(--fs-s); color: var(--text-muted); }

/* Таблица: полоса статуса и «вход → образец → результат» · #id, «N мин назад», длительность · статус, маркеры и чип
   кадров · что · модель, провайдеры и ₽ · пользователь · ошибка. «Что» и «Ошибка» делят остаток. */
.table--tasks .col-media { width: 256px; white-space: normal; line-height: 1.25; }
.table--tasks .col-id { width: 124px; }
.table--tasks .col-status { width: 150px; }
.table--tasks .col-model { width: 180px; }
.table--tasks .col-user { width: 170px; }
.table--tasks td.cell-media { padding-left: 14px; box-shadow: inset 4px 0 0 var(--st, transparent); }
.table--tasks td.cell-id .row-link { font-weight: 600; color: var(--primary-text); }
.task-dur { display: block; font-size: var(--fs-s); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.task-dur.is-p90 { color: var(--warning); }
.task-dur.is-p99 { color: var(--danger); font-weight: 600; }
/* Полоска строки (_row.html): вход (до 3 фото стопкой и «+N») → образец → результат, миниатюры 36×45. Вся полоска —
   ссылка на карточку; подписи частей (.task-strip-cap) — только до 1023px, на десктопе их заменяет заголовок колонки. */
.task-strip { display: flex; align-items: center; gap: 6px; color: var(--text-faint); } /* равный отступ у стрелок */
@media (hover: hover) { .task-strip:hover { text-decoration: none; } }
.task-strip-part { display: flex; flex-direction: column; min-width: 0; }
.task-strip-box { position: relative; display: flex; }
/* Части — колонками одной ширины во всех строках (результаты на одной вертикали): стопка входа прижата к стрелке. */
.task-strip-in .task-strip-box { width: auto; } /* по содержимому: без пустого поля слева у одного фото; стопка — +16px на фото */
.task-strip-sample .task-strip-box { width: 56px; }
.task-strip-cap { display: none; }
.task-strip-arrow { flex: none; font-size: 11px; line-height: 1; }
.task-slot { position: relative; flex: none; display: block; width: 56px; height: 70px; border-radius: 8px; overflow: hidden; background: var(--surface-2); }
.task-slot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.task-strip-in .task-slot + .task-slot { margin-left: -40px; box-shadow: -2px 0 0 var(--surface); }
.task-slot-more, .task-slot-extra { position: absolute; right: 2px; bottom: 2px; padding: 0 4px; border-radius: 4px; font-size: 10px; font-weight: 700; line-height: 1.5; color: #fff; background: rgba(0, 0, 0, .6); }
.task-slot-extra { display: none; } /* «+N» всех фото, кроме первого, — только до 1023px (там видно одно фото входа) */
/* Образец — рамка цвета акцента поверх картинки (inset-тень под <img> не видна). */
.task-slot.is-sample::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1.5px var(--primary); pointer-events: none; }
.task-slot.is-dim img { opacity: .5; filter: grayscale(.5); }
/* ▶ видео — ::before: ::after занят рамкой образца (у образца-видео оба класса), ::before битой — заглушкой. */
.task-slot.is-video:not(.is-broken)::before { content: "▶"; position: absolute; left: 2px; bottom: 2px; padding: 1px 3px; border-radius: 4px; font-size: 8px; line-height: 1.2; color: #fff; background: rgba(0, 0, 0, .55); }
/* Рамки без картинки: нет входа, образца (чип «свой промт»), результата («…» в работе, «✗» ошибка, «—»). */
.task-slot:is(.is-empty, .is-chip) { display: grid; place-items: center; padding: 3px; border: 1px dashed var(--border-strong); background: transparent; font-size: 10px; line-height: 1.1; text-align: center; color: var(--text-muted); overflow-wrap: anywhere; hyphens: auto; }
.task-slot.is-chip { width: 56px; }
.task-slot.is-empty:is(.is-run, .is-fail) { font-size: 14px; font-weight: 700; }
.task-slot.is-empty.is-run { color: var(--st-t-run); }
.task-slot.is-empty.is-fail { color: var(--st-t-fail); border-color: color-mix(in srgb, var(--st-t-fail) 45%, var(--border)); }
/* Превью оживления (видео) — чип «▶ превью» сплошной рамкой акцента: картинки у видео нет. */
.task-slot.is-chip.is-anim { border: 1.5px solid var(--primary); color: var(--primary-text); font-weight: 600; }
/* Не загрузилась (admin.js: img[data-fallback] → .is-broken) — рамка-заглушка. markBroken() ставит hidden, но
   `img, svg, video { display: block }` и `.task-slot img` авторские и перебивают [hidden] браузера — без этого правила
   поверх заглушки рисуется значок битой картинки (строка и карточка, 0,2,0 > 0,1,1). */
[data-fallback][hidden] { display: none; }
.task-slot.is-broken::before { content: ""; position: absolute; inset: 0; margin: auto; width: 44%; height: 32%; border: 1.5px dashed var(--border-strong); border-radius: 4px; }
/* Без Image Resizing (media.py: image_resizing — *.r2.dev, ADMIN_IMAGE_RESIZING=0) — иконка вместо миниатюры. */
.task-slot.is-off { display: grid; place-items: center; color: var(--text-muted); }
.task-slot.is-off .icon { width: 16px; height: 16px; opacity: .7; }
/* Крупный просмотр полоски (admin.js «Крупный просмотр», только мышь от 1024px): dialog.zoom — три части крупно со
   стрелками, как в строке. Высота рамок — по экрану, картинка вписывается (contain); образец — рамка акцента; несколько
   фото входа — сеткой по два; рамки без картинки — пунктир с текстом; не загрузилось — пунктир «не загрузилось». */
@media (min-width: 1024px) and (hover: hover) and (pointer: fine) {
  .task-strip[data-zoom] { cursor: zoom-in; }
}
.zoom {
  width: min(1480px, calc(100vw - 48px)); max-width: none; max-height: calc(100dvh - 48px); padding: 0; overflow: hidden;
  border: 1px solid var(--border); border-radius: 20px; background: var(--surface); color: var(--text); box-shadow: var(--shadow-up);
}
.zoom::backdrop { background: rgb(0 0 0 / .72); }
html:has(.zoom[open]) { overflow: hidden; }
.zoom-box { display: flex; flex-direction: column; gap: 12px; padding: 12px 16px 16px; }
.zoom-head { display: flex; align-items: center; gap: 12px; }
.zoom-title { margin: 0; font-size: 17px; }
.zoom-title a { color: var(--text); }
@media (hover: hover) { .zoom-title a:hover { color: var(--primary-text); text-decoration: none; } }
.zoom-hint { margin-left: auto; font-size: var(--fs-s); color: var(--text-muted); }
.zoom-close { font-size: 16px; }
.zoom-body { display: flex; align-items: center; gap: 10px; }
.zoom-arrow { flex: none; font-size: 20px; line-height: 1; color: var(--text-faint); }
.zoom-part { flex: 1 1 0; min-width: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.zoom-frames { display: grid; grid-template-rows: minmax(0, 1fr); gap: 6px; height: min(calc(100dvh - 160px), 960px); }
.zoom-frames.is-multi { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: none; grid-auto-rows: minmax(0, 1fr); }
.zoom-frame { position: relative; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; border-radius: 14px; background: var(--surface-2); overflow: hidden; }
.zoom-frame > a { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; cursor: zoom-in; }
.zoom-frame img { display: block; max-width: 100%; max-height: 100%; object-fit: contain; }
.zoom-frame video { display: block; width: 100%; height: 100%; object-fit: contain; background: #000; }
.zoom-frame.is-sample::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 2px var(--primary); pointer-events: none; }
.zoom-frame.is-empty { padding: 16px; border: 1.5px dashed var(--border-strong); background: transparent; font-size: 16px; text-align: center; color: var(--text-muted); }
.zoom-frame :is(a, video).is-broken, .zoom-frame.is-broken { cursor: default; }
.zoom-frame .is-broken::before, .zoom-frame.is-broken::before { content: "не загрузилось"; font-size: var(--fs-s); color: var(--text-muted); }
.zoom-cap { font-size: var(--fs-s); text-align: center; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-marks { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 4px; }
.task-mark { display: inline-flex; padding: 1px 6px; border-radius: 6px; font-size: 11px; line-height: 1.35; white-space: nowrap; color: var(--text-muted); background: var(--surface-2); }
.task-mark--fallback { color: var(--info); background: var(--info-soft); }
.task-mark--retry, .task-mark--no_video { color: var(--warning); background: var(--warning-soft); }
.task-mark--dry { color: var(--st-dry); background: color-mix(in srgb, var(--st-dry) 13%, var(--surface)); }
.task-what { font-weight: 500; }
.task-kind, .task-chain, .task-user-meta, .task-cost-line, .task-spent { font-size: var(--fs-s); }
.task-chain { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 2px; color: var(--text-muted); }
.task-chain-step.is-ok { color: var(--success); }
.task-chain-step.is-fail { color: var(--danger); }
.task-chain-step.is-run { color: var(--info); }
.task-chain-arrow { color: var(--text-faint); }
.task-user { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.task-user-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0 6px; }
.task-plat { display: inline-flex; padding: 0 5px; border-radius: 5px; font-size: 10px; font-weight: 700; letter-spacing: .03em; line-height: 16px; color: var(--text-muted); background: var(--surface-2); }
.task-plat--tg { color: var(--info); background: var(--info-soft); }
.task-plat--max { color: var(--st-special); background: color-mix(in srgb, var(--st-special) 13%, var(--surface)); }
.task-plat--app { color: var(--primary-text); background: var(--primary-soft); }
.credit-chip { display: inline-flex; padding: 1px 6px; border-radius: 6px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text); background: var(--surface-2); }
/* «−5 ↩5» (вернули) — обычный чип: зелёный сливался с бейджем «Готово» (владелец 27.09.2026); красный — только «↩0». */
.credit-chip--bad { color: var(--danger); background: var(--danger-soft); }
.credit-chip--muted { color: var(--text-muted); }
/* Кадры — под статусом и маркерами, себестоимость (и переплата) — внизу ячейки модели. */
.task-spent { display: flex; margin-top: 4px; }
.task-cost-line { display: flex; flex-wrap: wrap; gap: 0 6px; margin-top: 2px; white-space: nowrap; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.task-extra { color: var(--danger); font-weight: 600; }
.task-error { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: var(--fs-s); color: var(--text-muted); }
:is([data-st="fail"], [data-st="cfail"]) .task-error { color: var(--danger); }

/* Состояние вместо строк (_state.html): тяжёлый запрос, пусто, «ошибок нет». */
.table--tasks tr.row-empty td { padding: var(--sp-3); }
@media (hover: hover) { .table--tasks tr.row-empty:hover td { background: transparent; } }
.task-state { padding: var(--sp-5) var(--sp-4); border-radius: var(--radius); text-align: center; }
.task-state-title { margin: 0 0 var(--sp-1); font-size: var(--fs-l); font-weight: 600; }
.task-state-text { margin: 0; color: var(--text-muted); }
.task-state--clean .task-state-title { color: var(--success); }
.task-state--heavy { background: var(--warning-soft); }
.task-state--heavy .task-state-title { color: var(--warning); }
.task-state-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2); margin-top: var(--sp-3); }

/* Пара «‹ Новее» · «Старше ›» внизу карточки задания (detail.html: nav.kpager.task-pager; списки — .pager, раздел 9). */
.kpager { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-3); }
.kpager .is-disabled { opacity: .45; pointer-events: none; }

/* До 1023px: панель — липкая строка (поиск, чип периода, кнопка фильтров), период — шторка #period-sheet;
   строки — карточки (21.5): миниатюра слева на всю высоту, справа #id · время · длительность | статус и маркеры,
   что, модель и провайдеры, пользователь | кадры, ошибка. Карточка открывается тапом по #id (цвет ссылки) или двойным
   тапом по строке; прочие ссылки внутри — свои (раздел 25: цвет ссылки или тихое подчёркивание). */
@media (max-width: 1023.98px) {
  .tasks-bar {
    position: sticky; top: var(--top-h); z-index: 20; flex-wrap: nowrap;
    margin: 0 calc(-1 * max(var(--gutter), var(--safe-r))) var(--sp-3) calc(-1 * max(var(--gutter), var(--safe-l)));
    padding: 8px max(var(--gutter), var(--safe-r)) 8px max(var(--gutter), var(--safe-l));
    background: var(--bg); border-bottom: 1px solid var(--border);
  }
  .tasks-find { flex: 1; min-width: 0; width: auto; }
  .period, .tasks-bar > .filters--tasks { flex: none; flex-basis: auto; }
  .period-open { height: 34px; padding: 0 12px; }
  .period-sheet .period-chips { gap: 8px; }
  .period-sheet .chip-toggle { height: 40px; padding: 0 16px; font-size: var(--fs); }
  /* Даты: две равные колонки с подписями сверху, без тире. Поле даты iOS: без appearance и с min-width: 0 — иначе
     его собственная минимальная ширина выталкивала второе поле за край шторки; дата — слева и по центру высоты. */
  .period-dates { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .period-dash { display: none; }
  .period-date { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
  .period-date-cap { display: block; font-size: var(--fs-s); color: var(--text-muted); }
  .period-dates .input { width: 100%; min-width: 0; max-width: 100%; }
  .period-dates input[type="date"] { -webkit-appearance: none; appearance: none; line-height: 32px; text-align: left; }
  .period-dates input[type="date"]::-webkit-date-and-time-value { margin: 0; text-align: left; }
  .period-dates .btn { grid-column: 1 / -1; }
  main:has(.tasks-bar) #table { scroll-margin-top: calc(var(--top-h) + 72px); }
  /* Заголовок → поиск — 12px, как заголовок → первая кнопка у промтов и видео (у панели свои 8px сверху). */
  main:has(.tasks-bar) > .page-head { margin-bottom: 4px; }

  .table--tasks thead { display: none; }
  .table--tasks tbody tr {
    position: relative; min-height: 88px; padding-left: 16px;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "id status" "what status" "media media" "model model" "user user" "error error";
    gap: 3px 10px;
    box-shadow: inset 4px 0 0 var(--st, transparent);
  }
  /* Полоска — три ячейки 4:5, как миниатюры десктопа (владелец 27.09.2026: квадратные резали фото), до 120px в ширину;
     подписи — вторым рядом сетки (части — display: contents), стрелки — по центру картинок. Несколько фото входа — первое
     и «+N» (остальные — в карточке). Карточка открывается тапом по #id или двойным тапом по строке (admin.js). */
  .table--tasks td.cell-media { grid-area: media; margin: 4px 0 2px; padding: 0; box-shadow: none; }
  .task-strip { display: grid; grid-template-columns: minmax(0, 120px) 12px minmax(0, 120px) 12px minmax(0, 120px); align-items: start; gap: 3px 2px; }
  .task-strip-part { display: contents; }
  .task-strip-in .task-strip-box { grid-area: 1 / 1; }
  .task-strip-sample .task-strip-box { grid-area: 1 / 3; }
  .task-strip-result .task-strip-box { grid-area: 1 / 5; }
  .task-strip-in .task-strip-cap { grid-area: 2 / 1; }
  .task-strip-sample .task-strip-cap { grid-area: 2 / 3; }
  .task-strip-result .task-strip-cap { grid-area: 2 / 5; }
  .task-strip > .task-strip-arrow:nth-child(2) { grid-area: 1 / 2; }
  .task-strip > .task-strip-arrow:nth-child(4) { grid-area: 1 / 4; }
  .task-strip-box, .task-strip-part .task-strip-box { width: auto; height: auto; aspect-ratio: 4 / 5; }
  .task-strip-box > .task-slot { flex: 1 1 0; width: auto; min-width: 0; max-width: none; height: auto; margin: 0; border-radius: 8px; }
  .task-strip-in .task-slot + .task-slot, .task-strip-in .task-slot-more { display: none; }
  .task-slot-extra { display: block; right: 4px; bottom: 4px; padding: 1px 6px; font-size: 12px; }
  .task-slot:is(.is-empty, .is-chip) { padding: 4px; font-size: 12px; }
  .task-slot.is-empty:is(.is-run, .is-fail) { font-size: 20px; }
  .task-slot.is-video:not(.is-broken)::before { left: 4px; bottom: 4px; padding: 1px 4px; font-size: 10px; }
  .task-strip-cap { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 11px; line-height: 1.3; color: var(--text-muted); }
  .task-strip-arrow { align-self: center; text-align: center; }
  .table--tasks td.cell-id { grid-area: id; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 8px; }
  .table--tasks td.cell-id .muted, .table--tasks .task-dur { display: inline; margin: 0; }
  /* Правая колонка (статус, маркеры, чип кадров) — по содержимому и не шире 140px: маркеры переносятся, середине
     карточки остаётся место. width: auto — сброс 190px колонки статуса десктопа (раздел 8), иначе колонка всегда 140px. */
  .table--tasks td.cell-status { grid-area: status; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; width: auto; max-width: 140px; }
  .table--tasks :is(.task-marks, .task-spent) { justify-content: flex-end; margin: 0; }
  /* «Что · тип»: название шаблона или фотосессии не уступает место типу — не влезают в строку вместе, тип переносится
     ниже; оба режутся многоточием по ширине середины карточки (на 360px это ~80px, «Фото → видео: фото» — 123px). */
  .table--tasks td.cell-what { grid-area: what; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 6px; min-width: 0; }
  .table--tasks :is(.task-what, .task-model) { min-width: 0; }
  .table--tasks :is(.task-what, .task-kind) { max-width: 100%; }
  .table--tasks .task-kind { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .table--tasks .task-kind::before { content: "· "; }
  .table--tasks td:is(.cell-model, .cell-user) { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 8px; min-width: 0; }
  .table--tasks td.cell-model { grid-area: model; }
  .table--tasks td.cell-user { grid-area: user; }
  .table--tasks .task-user { display: inline; }
  .table--tasks .task-cost-line { margin: 0; }
  .table--tasks td.cell-error { grid-area: error; }
  .table--tasks td.cell-error:not(:has(.task-error)) { display: none; }
  /* пара pipeline подряд — одна склеенная карточка: видео-фаза сверху, фото-фаза снизу через пунктир */
  .table--tasks tbody tr.is-pair-top { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
  .table--tasks tbody tr.is-pair-bottom { margin-top: -10px; border-top: 1px dashed var(--border-strong); border-top-left-radius: 0; border-top-right-radius: 0; }
}
@media (max-width: 1023.98px) and (hover: hover) {
  .table--tasks tbody tr:hover { border-color: var(--border-strong); }
}
@media (max-width: 1023.98px) and (pointer: coarse) {
  .period-open { height: 44px; }
  .period-dates input[type="date"] { line-height: 42px; }
}
@media (hover: none) {
  /* отклик на тап — раздел 25; строка заданий одиночным тапом не открывается — без вспышки */
}
@media (pointer: coarse) {
  /* Под палец: соседи внизу карточки 48px, чипы 36–40px, строки частых ошибок 44px. */
  .kpager .btn { height: 48px; }
  .filter-chip { height: 36px; font-size: 14px; }
  .st-chip { height: 40px; }
  .freq-errors-list a { min-height: 44px; align-items: center; }
  .tasks-health a { padding-block: 8px; margin-block: -8px; }
}
@media (max-width: 767.98px) {
  /* Строка пейджера сверху не помещается в одну: вторая — время страницы слева, ‹ › справа. */
  .pager-meta { row-gap: 6px; }
  /* Длинное «где я» («Страница 200 из 7 941 · …») переносится внутри себя — ‹ › не уходят на вторую строку. */
  .pager-meta #pg-where { flex: 1 1 0; min-width: 0; }
  .pager-end:has(.pager-time) { flex-basis: 100%; justify-content: space-between; }
  .freq-errors-list a { flex-wrap: wrap; }
  .freq-errors-meta { flex-basis: 100%; white-space: normal; }
}

/* 23. Карточка задания /tasks/{id} (pages/tasks/{detail,detail_state,_detail_macros,_verdict,_credits,_media,_user,_request,
   _provider,_job,_live}.html) ----------------------------------------------------------------------------------------- */
/* Липкая шапка .task-top: строка .task-head («← Список», #id — кнопка копирования, статус, соседи «‹ Новее» / «Старше ›»)
   и строка времени .task-times. Прилегает к шапке сайта (−16px — верхний отступ .container), поля — как у .container.
   До 767px липкая только .task-head (.task-top — display: contents), время уезжает с прокруткой, подписи кнопок скрыты. */
.task-top {
  position: sticky; top: var(--top-h); z-index: 20;
  margin: -16px calc(-1 * max(var(--gutter), var(--safe-r))) var(--sp-3) calc(-1 * max(var(--gutter), var(--safe-l)));
  padding: 10px max(var(--gutter), var(--safe-r)) 10px max(var(--gutter), var(--safe-l));
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.task-head { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.task-back { flex: none; padding: 0 10px; }
.task-title { flex: none; margin: 0; font-size: 20px; }
.task-id-copy {
  display: inline-flex; align-items: center; gap: 4px; height: 34px; padding: 0 6px; margin-left: -2px;
  border: 0; border-radius: var(--radius); background: none; color: var(--text); font: inherit; font-weight: 700; white-space: nowrap;
}
.task-id-copy .mono { font-size: 1em; }
.task-id-copy .icon { color: var(--text-faint); }
@media (hover: hover) { .task-id-copy:hover { background: var(--surface-2); } .task-id-copy:hover .icon { color: var(--text); } }
.task-status { flex: 0 1 auto; display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
.task-head .task-badge { max-width: 100%; overflow: hidden; text-overflow: ellipsis; padding: 3px 8px; font-size: 12px; }
.task-nav { flex: none; display: flex; gap: var(--sp-2); margin-left: auto; }
.task-nav-btn.is-disabled { opacity: .45; pointer-events: none; }
/* «Ссылка»: иконка-звено; на телефоне — иконка «Поделиться» (там системное меню), подпись скрыта. */
.task-share-link, .task-share-ios { display: inline-flex; }
.task-share-ios { display: none; }
.task-times { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 14px; margin-top: 6px; font-size: var(--fs-s); color: var(--text-muted); }
.task-times b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.task-times .task-dur { display: inline; font-size: inherit; font-weight: 600; color: var(--text); }
.task-times .task-dur.is-p90 { color: var(--warning); }
.task-times .task-dur.is-p99 { color: var(--danger); }
[data-copy].is-copied, [data-copy-target].is-copied { border-color: var(--success); color: var(--success); }

/* Раскладка: до 1023px одна колонка (обёртки колонок — display: contents), порядок: вердикт, медиа, остальное по
   разметке (что просили, провайдер, кадры, пользователь, …). От 1024px — две колонки: медиа слева, факты справа. */
.task-layout { display: flex; flex-direction: column; gap: var(--sp-3); }
.task-col { display: contents; }
.task-layout .task-verdict { order: -1; }
@media (min-width: 1024px) {
  .task-layout { display: grid; grid-template-columns: minmax(320px, 5fr) minmax(0, 7fr); gap: var(--sp-5); align-items: start; }
  .task-col { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
}
.task-block { min-width: 0; padding: var(--sp-3) var(--sp-4); }
.task-block-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin: 0 0 var(--sp-2); font-size: var(--fs-l); }
.task-block-title .muted { font-weight: 400; }
.task-block-sub { margin: var(--sp-3) 0 var(--sp-2); font-size: var(--fs); }
.task-block-sub .muted { font-weight: 400; }
.task-block-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
.task-block .facts { grid-template-columns: minmax(96px, max-content) minmax(0, 1fr); font-size: var(--fs); }
.task-block .facts dd { min-width: 0; overflow-wrap: anywhere; }

/* Вердикт: полоса слева по тону (ok | warn | bad | run), признаки, «Что видел пользователь», копирование текстов. */
.task-verdict { --vt: var(--border-strong); border-left: 4px solid var(--vt); }
.task-verdict--ok { --vt: var(--success); }
.task-verdict--warn { --vt: var(--warning); }
.task-verdict--bad { --vt: var(--danger); }
.task-verdict--run { --vt: var(--info); }
.task-verdict-text { margin: 0; font-size: var(--fs-l); font-weight: 600; line-height: 1.4; }
.task-issues { list-style: none; display: flex; flex-direction: column; gap: 4px; margin-top: var(--sp-2); }
.task-issue { position: relative; padding-left: 14px; }
.task-issue::before { content: ""; position: absolute; left: 1px; top: .55em; width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint); }
.task-issue.is-bad { color: var(--danger); }
.task-issue.is-bad::before { background: var(--danger); }
.task-issue.is-warn { color: var(--warning); }
.task-issue.is-warn::before { background: var(--warning); }
.task-issue.is-info::before { background: var(--info); }
.task-saw { margin-top: var(--sp-3); padding-top: var(--sp-2); border-top: 1px solid var(--border); }
.task-saw-title { margin: 0 0 4px; font-size: var(--fs); }
.task-saw-title .muted { font-weight: 400; }
.task-saw-list { padding-left: 18px; }
.task-verdict-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
.task-live { margin: var(--sp-2) 0 0; }

/* Кадры: чип и фраза refund_state, факты, журнал (− красным, + зелёным), вероятный возврат php. */
.task-credits-state { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 8px; margin: 0 0 var(--sp-2); }
.task-credits-state .credit-chip { font-size: var(--fs); }
.task-credits-note { margin: var(--sp-2) 0 0; }
.task-ledger { list-style: none; display: flex; flex-direction: column; margin-top: 0; font-size: var(--fs-s); }
.task-ledger li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 5px 0; border-top: 1px solid var(--border); }
.task-ledger li > :last-child { margin-left: auto; }
.task-ledger-extra, .task-ledger-sep { display: inline; }
.task-ledger-delta { min-width: 28px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--danger); }
.task-ledger-delta.is-plus { color: var(--success); }
.task-probable { margin-top: var(--sp-2); padding: 8px 10px; border-radius: var(--radius); background: var(--surface-2); }
.task-probable p { margin: 0; }
.task-credits > p.small { margin: var(--sp-2) 0 0; }

/* Медиа: результат (фото — ссылка на оригинал, видео — плеер с постером), входы сеткой; не загрузилось — рамка-заглушка
   (admin.js: [data-fallback] → .is-broken). «временная ссылка провайдера» — жёлтая метка. */
.task-result { display: flex; flex-direction: column; gap: var(--sp-2); }
.task-media-frame { position: relative; display: block; overflow: hidden; border-radius: var(--radius); background: var(--surface-2); }
.task-result .task-media-frame { align-self: flex-start; max-width: 100%; }
.task-result .task-media-frame--video { align-self: stretch; }
.task-result img { width: auto; max-width: 100%; height: auto; max-height: 70vh; }
.task-result video { width: 100%; max-height: 70vh; background: #000; }
.task-media-frame.is-broken { min-width: 96px; min-height: 120px; border: 1.5px dashed var(--border-strong); background: transparent; }
.task-media-frame.is-broken::after { content: "Файл недоступен"; position: absolute; inset: 0; display: grid; place-items: center; padding: 8px; text-align: center; font-size: var(--fs-s); color: var(--text-faint); }
.task-media-links { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0; }
.task-media-empty { margin: 0; padding: var(--sp-5) var(--sp-3); border: 1px dashed var(--border-strong); border-radius: var(--radius); text-align: center; color: var(--text-muted); }
.task-temp { display: inline-flex; padding: 1px 6px; border-radius: 6px; font-size: 11px; line-height: 1.35; white-space: nowrap; color: var(--warning); background: var(--warning-soft); }
.task-inputs { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--sp-2); }
.task-input { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
.task-input .task-media-frame { width: 100%; aspect-ratio: 4 / 5; }
.task-media-frame--clip video { display: block; width: 100%; height: 100%; object-fit: cover; background: #000; }
.task-input .task-media-frame.is-broken { min-width: 0; min-height: 0; }
.task-input img { width: 100%; height: 100%; object-fit: cover; }
.task-input-label { font-size: var(--fs-s); color: var(--text-muted); }
.task-input-label.is-ref { color: var(--text); font-weight: 600; }
/* Не фото пользователя (движение, одежда, кадр фото-фазы или прошлой генерации) — курсивом; образец — последним в
   сетке, рамка цвета акцента, нет образца — пунктирный чип; под сеткой — как выбран образец. */
.task-input-label:is(.is-motion, .is-clothes, .is-phase, .is-frame, .is-legacy) { font-style: italic; }
.task-input-label.is-sample { color: var(--text); font-weight: 600; }
.task-input--sample a.task-media-frame { outline: 2px solid var(--primary); outline-offset: 2px; }
.task-media-frame:is(.task-sample-chip, .task-media-file) { display: grid; place-items: center; padding: 8px; border: 1px dashed var(--border-strong); background: transparent; text-align: center; font-size: var(--fs-s); color: var(--text-muted); }
.task-sample-note { margin: var(--sp-2) 0 0; font-size: var(--fs-s); color: var(--text-muted); }

/* Пользователь (шапка карточки — .task-user-card-head из 22), запрос: длинные тексты в <details> с копированием. */
.task-user-pii { margin-top: var(--sp-2); }
.task-user-block > p { margin: var(--sp-2) 0 0; }
.task-details { margin-top: var(--sp-2); padding-top: var(--sp-2); border-top: 1px solid var(--border); }
.task-summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; min-height: 28px; cursor: pointer; list-style: none; font-weight: 600; }
.task-summary::-webkit-details-marker { display: none; }
.task-summary::before { content: "▸"; flex: none; width: 10px; color: var(--text-faint); transition: transform .15s; }
details[open] > .task-summary::before { transform: rotate(90deg); }
.task-summary .muted { font-weight: 400; }
.task-pre { margin: var(--sp-2) 0; max-height: 50vh; overflow: auto; padding: 10px 12px; border-radius: var(--radius); background: var(--surface-2); font: 12px/1.5 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; }
.task-pre--error { max-height: 16em; overflow: auto; } /* дамп ffmpeg и т. п. не растягивает карточку */
/* Копирование внутри блока текста (ui.copy_pre): иконка справа вверху, как у блоков кода на других сайтах. float —
   первые строки обтекают её (обтекание считается по месту в потоке); sticky — при прокрутке длинного текста внутри
   блока иконка остаётся в правом верхнем углу и может лечь поверх следующих строк (владелец 27.09.2026: не страшно).
   top: 0 и без отрицательного margin-top: липкая граница считается от внутреннего отступа блока — так место, которое
   обтекает текст, и место прилипшей иконки совпадают (иначе она съезжала на третью строку). */
.pre-copy {
  float: right; position: sticky; top: 0; z-index: 1; width: 30px; height: 30px; margin: 0 -2px 4px 10px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); white-space: normal;
}
.task-block-sub .btn-icon { margin: -6px 0; }

/* Провайдер: коды карточками — код целиком (выделяется одним тапом), большие кнопки «Скопировать» и ссылка в кабинет. */
.task-codes { list-style: none; display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.task-code { padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-2); }
.task-code-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.task-code-value { display: block; margin: 6px 0 8px; font: 600 13px/1.4 var(--mono); overflow-wrap: anywhere; user-select: all; -webkit-user-select: all; }
.task-code-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.task-code-actions .btn { height: 40px; padding: 0 16px; }
.task-code > p { margin: 6px 0 0; }
.task-raw-error .task-pre { color: var(--danger); }
.task-probe:empty { display: none; }
/* Попытки (§10.8, task_attempt): .table стандартная (min-width 1180px), а колонка фактов уже —
   свой overflow-x независимо от ширины экрана (общее правило у .table-wrap срабатывает только ≤1240px). */
.task-provider .table-wrap { overflow-x: auto; }
.task-provider .table th { top: 0; } /* scroll-контейнер: иначе шапка сдвигается на var(--top-h) вниз (см. .table-wrap) */
.table--attempts tfoot td { font-weight: 600; background: var(--surface-2); }
/* Копирование кода прямо в колонке «Код» (с попытками карточки кодов не рисуются, владелец 29.09.2026). */
.btn-copy-inline { width: 22px; height: 22px; vertical-align: middle; margin-left: 2px; }
.btn-copy-inline .icon { width: 14px; height: 14px; }

/* Воркер, цепочка, все поля, нижняя пара «‹ Новее» / «Старше ›» (48px на касании — .kpager из 22). */
.task-job-errors { display: flex; flex-direction: column; gap: 4px; padding-left: 22px; font-size: var(--fs-s); }
.task-job-error { overflow-wrap: anywhere; }
.task-chain-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin: 0 0 4px; }
.task-chain-outcome { color: var(--text-muted); }
.task-chain-outcome.is-ok { color: var(--success); }
.task-chain-outcome.is-run { color: var(--info); }
.task-chain-outcome.is-bad { color: var(--danger); }
.task-chain-outcome.is-warn { color: var(--warning); }
.task-chain-block > p.small { margin: 0; }
.task-fields > .task-summary { font-size: var(--fs-l); }
.task-fields-list { margin-top: var(--sp-2); }
.task-fields-list dt, .task-fields-list dd { font-size: var(--fs-s); }
/* События ClickHouse (_events.html): строка — время, имя, чья фаза, категория · платформа · версия; ниже параметры. */
.task-events > .task-summary { font-size: var(--fs-l); }
.task-events-body { margin-top: var(--sp-2); }
.task-events-meta { margin: 0 0 var(--sp-2); }
ol.task-events { list-style: none; display: flex; flex-direction: column; margin: 0; padding: 0; } /* список, не блок details.task-events */
.task-event { padding: 8px 0; border-top: 1px solid var(--border); }
.task-event-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
.task-event-time { color: var(--text-muted); font-size: var(--fs-s); font-variant-numeric: tabular-nums; }
.task-event-name { font-weight: 600; overflow-wrap: anywhere; }
.task-event-params { display: flex; flex-wrap: wrap; gap: 2px 14px; margin: 4px 0 0; font: 12px/1.5 var(--mono); }
.task-event-params > div { display: flex; gap: 4px; min-width: 0; max-width: 100%; }
.task-event-params dt { color: var(--text-muted); }
.task-event-params dt::after { content: ":"; }
.task-event-params dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.task-event-device { margin: 4px 0 0; }
.task-pager { margin-top: var(--sp-4); }
.task-keys-hint { margin-left: auto; }
.task-find-again { width: 100%; max-width: 480px; margin: var(--sp-4) auto 0; }

@media (hover: none) {
  .task-keys-hint { display: none; }
  .task-id-copy:active { background: var(--surface-2); }
}
@media (pointer: coarse) {
  .task-id-copy { height: 44px; }
  .task-summary { min-height: 44px; align-items: center; }
  .task-code-actions .btn { height: 48px; }
  .pre-copy { width: 38px; height: 38px; }
}
/* Телефон: липкая только строка кнопок (стрелки вместо подписей), блоки плотнее, кнопки действий — на всю ширину. */
@media (max-width: 767.98px) {
  .task-top { display: contents; }
  .task-head {
    position: sticky; top: var(--top-h); z-index: 20; gap: 6px;
    margin: -16px calc(-1 * max(var(--gutter), var(--safe-r))) 0 calc(-1 * max(var(--gutter), var(--safe-l)));
    padding: 6px max(var(--gutter), var(--safe-r)) 6px max(var(--gutter), var(--safe-l));
    background: var(--bg); border-bottom: 1px solid var(--border);
  }
  .task-back { width: 40px; padding: 0; }
  .task-head :is(.task-back-text, .task-share-text) { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
  .task-title { font-size: 16px; }
  .task-head .task-mark--php { display: none; }
  /* Соседи «‹ ›» не нужны (владелец 27.09.2026): ни в шапке, ни внизу. «Поделиться» — справа, квадратная. */
  .task-nav, .task-pager { display: none; }
  .task-share { order: 1; flex: none; width: 40px; height: 40px; margin-left: auto; padding: 0; }
  .task-share .icon { width: 20px; height: 20px; }
  .task-share-link { display: none; }
  .task-share-ios { display: inline-flex; }
  .task-times { margin: 10px 0 var(--sp-3); }
  .task-block { padding: var(--sp-3); }
  .task-verdict-actions .btn, .task-block-actions .btn, .task-code-actions .btn { flex: 1 1 auto; }
  .task-verdict-actions .btn { white-space: normal; height: auto; min-height: 44px; padding-block: 8px; }
  .task-result img, .task-result video { max-height: 60vh; }
  .task-block .facts { grid-template-columns: minmax(84px, max-content) minmax(0, 1fr); }
  /* История кадров: «Δ | причина | время»; пояснение «фото-фаза #id» — второй строкой под причиной. */
  .task-ledger li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; }
  .task-ledger-extra { display: block; }
  .task-ledger-sep { display: none; }
}

/* 24. Поиск /tasks/find (pages/tasks/{find,_resolve}.html) и «Спросить провайдера» (pages/tasks/_probe.html, кнопка —
   _provider.html) ----------------------------------------------------------------------------------------------------- */
/* Поиск: строка во всю ширину (до 640px) с введённым значением, ниже — карточка состояния (_state.html) и/или совпадения
   сеткой: вся карточка — ссылка на задание или на задания пользователя, от 56px; «Что можно вставить» — при пустом итоге. */
.find-head { margin-bottom: var(--sp-3); }
.find-form { width: 100%; max-width: 640px; margin: 0 0 var(--sp-4); }
.resolve { margin-top: var(--sp-3); }
.resolve-title { margin: 0 0 var(--sp-2); font-size: var(--fs-l); }
.resolve-title .muted { font-weight: 400; }
.resolve-list { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: var(--sp-2); }
.resolve-card { display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 10px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); }
@media (hover: hover) { .resolve-card:hover { text-decoration: none; border-color: var(--border-strong); background: var(--surface-2); } }
.resolve-icon { flex: none; display: grid; place-items: center; width: 36px; height: 36px; border-radius: 50%; background: var(--surface-2); color: var(--text-muted); }
.resolve-card--user .resolve-icon { background: var(--primary-soft); color: var(--primary-text); }
.resolve-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.resolve-label { font-weight: 600; overflow-wrap: anywhere; }
.resolve-via { font-size: var(--fs-s); color: var(--text-muted); }
.resolve-go { flex: none; font-size: 20px; line-height: 1; color: var(--text-faint); }
.resolve-formats { margin-top: var(--sp-3); padding: var(--sp-3) var(--sp-4); }
.resolve-formats-title { margin: 0 0 var(--sp-2); font-size: var(--fs); }
.resolve-formats ul { display: flex; flex-direction: column; gap: 2px; padding-left: 18px; color: var(--text-muted); }

/* «Спросить провайдера»: кнопка с подписью строкой; ответ — карточка на код с полосой тона слева (ok | run | bad | warn |
   muted), состояние как у провайдера, текст ошибки, факты, ссылки на результат (временные). */
.task-probe-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: var(--sp-3) 0 0; }
.task-probe { margin-top: var(--sp-2); }
.probe-head { margin: 0 0 6px; font-weight: 600; }
.probe-head .muted { font-weight: 400; }
.probe-note { margin: 0 0 var(--sp-2); padding: 8px 10px; border-radius: var(--radius); background: var(--surface-2); }
.probe-note.is-warn { background: var(--warning-soft); color: var(--warning); }
.probe-note.is-bad { background: var(--danger-soft); color: var(--danger); }
.probe-list { list-style: none; display: flex; flex-direction: column; gap: var(--sp-2); }
.probe-item { --pt: var(--border-strong); padding: 10px 12px; border: 1px solid var(--border); border-left: 4px solid var(--pt); border-radius: var(--radius); background: var(--surface); }
.probe-item--ok { --pt: var(--success); }
.probe-item--bad { --pt: var(--danger); }
.probe-item--run { --pt: var(--info); }
.probe-item--warn { --pt: var(--warning); }
.probe-item-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.probe-outcome { font-weight: 600; color: var(--pt); }
.probe-item--muted .probe-outcome { color: var(--text-muted); }
.probe-state { font-size: var(--fs-s); color: var(--text-muted); }
.probe-code { display: block; margin: 4px 0; font: 12px/1.4 var(--mono); color: var(--text-muted); overflow-wrap: anywhere; }
.probe-msg { margin: 4px 0 0; overflow-wrap: anywhere; }
.probe-item--bad .probe-msg { color: var(--danger); }
.task-block .probe-facts { margin-top: 6px; font-size: var(--fs-s); }
.probe-links { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-top: 8px; }

@media (hover: none) {
  .resolve-card:active { filter: brightness(.96); }
}
@media (pointer: coarse) {
  /* Под палец: карточки совпадений 60px, кнопка «Спросить» 48px, ссылки на результат 44px. */
  .resolve-card { min-height: 60px; }
  .task-probe-btn { height: 48px; }
  .probe-links .btn { height: 44px; }
}
@media (max-width: 767.98px) {
  .find-form { max-width: none; }
  .task-probe-btn { flex: 1 1 100%; }
  .resolve-formats { padding: var(--sp-3); }
}

/* Задания: строки-ссылки и пары pipeline. Карточка открывается одиночным кликом по #id или двойным кликом / двойным тапом
   по строке (tr[data-href], admin.js); touch-action: manipulation — двойной тап не масштабирует страницу. */
.table tbody tr[data-href] { touch-action: manipulation; } /* задания, промты, видео — как договорились 27.09.2026 */
.task-sibling { white-space: nowrap; }
@media (min-width: 1024px) {
  .table--tasks tbody tr.is-pair-top > td { border-bottom-color: transparent; }
  .table--tasks tbody tr.is-pair-bottom > td { border-top: 1px dashed var(--border); }
  /* Детальная карточка: левая колонка (медиа) прилипает под липкой шапкой; выше экрана — прокручивается содержимое
     внутри белого блока (владелец 27.09.2026): фон и скругления блока остаются, а не уезжают вместе с контейнером. */
  .task-col--media {
    position: sticky; top: calc(var(--top-h) + var(--task-top-h, 112px) + var(--sp-3));
    max-height: calc(100dvh - var(--top-h) - var(--task-top-h, 112px) - var(--sp-3) * 2);
  }
  .task-col--media > .task-media {
    min-height: 0; max-height: 100%; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  }
}

/* 25. Что кликается (владелец 27.09.2026: «непонятно, на что можно кликнуть»; аудит всех экранов) -------------------
   Ссылка, которая что-то открывает, — цветом ссылки (--primary-text); серый и чёрный текст никуда не ведёт.
   Ссылка-фильтр в серой строке — пунктирное подчёркивание всегда (не только при наведении; владелец 27.09.2026). Кликабельная строка списка —
   стрелка «›» и подсветка. Пилюля с рамкой кликается, пилюля только с заливкой — статична. Картинка, открывающая
   оригинал, — значок «↗». На касании — отклик на нажатие (opacity у ссылок, фон у кнопок и чипов). */
:is(.tasks-health a, .task-user, .table--prompts .rubric-link, .table--videos .session-link, .rubric-counts a, .task-ledger a, .user-filter) {
  text-decoration: underline dotted; text-decoration-thickness: 1.5px; text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, currentColor 60%, transparent);
}
@media (hover: hover) {
  :is(.tasks-health a, .task-user, .table--prompts .rubric-link, .table--videos .session-link, .rubric-counts a, .task-ledger a, .user-filter):hover {
    color: var(--primary-text); text-decoration-color: currentColor;
  }
}
.tasks-health a b { color: inherit; }

/* Строки-ссылки списков: «Частые ошибки», «Требует внимания» — стрелка как у .sheet-row и подсветка. У частых
   ошибок текст переносится (на телефоне — flex-wrap) — стрелка прижата к правому краю строки, а не отдельным элементом. */
:is(.freq-errors-list a, .attention-list a)::after {
  content: ""; flex: none; align-self: center; width: 7px; height: 7px; margin: 0 4px 0 2px;
  border: solid var(--text-faint); border-width: 1.6px 1.6px 0 0; transform: rotate(45deg);
}
.freq-errors-list a { position: relative; padding: 5px 24px 5px 8px; margin-inline: -8px; border-radius: var(--radius-s); }
.freq-errors-list a::after { position: absolute; right: 10px; top: 50%; margin: -4px 0 0; }
@media (hover: hover) {
  .freq-errors-list a:hover { background: var(--surface-2); color: var(--text); }
  .attention-list a:hover { filter: none; background: color-mix(in srgb, var(--warning) 18%, var(--surface)); }
}

/* Карточки дашборда: вся карточка — ссылка на раздел (растянутая ссылка подписи), чипы — свои ссылки поверх. */
.stat-card { position: relative; }
.stat-card-label a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.stat-card-chips { position: relative; z-index: 1; }
.stat-card-go { color: var(--text-faint); }
@media (hover: hover) {
  .stat-card:has(.stat-card-label a:hover) { border-color: var(--border-strong); }
  .stat-card-label a:hover { text-decoration: none; color: var(--primary-text); }
}

/* Пилюли: ссылка-бейдж и ссылка-чип статуса — с рамкой; статичные бейджи — без. */
a.badge { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--st, var(--text-muted)) 45%, transparent); }
@media (hover: hover) { a.badge:hover { text-decoration: none; filter: none; background: color-mix(in srgb, var(--st, var(--text-muted)) 24%, var(--surface)); } }
a.chip[data-status] { border-color: color-mix(in srgb, var(--st) 35%, transparent); }

/* Иконки действий: спокойный цвет вместо полупрозрачности (та читалась как «выключено»); удаление — красным. */
:is(.row-actions, .rubric-actions) .btn-icon { color: var(--text-muted); }
.btn-icon.btn-danger { color: var(--danger); }

/* Фото карточки задания открываются крупно (галерея, 26): значок «развернуть» в углу и затемнение при наведении. */
a.task-media-frame:not(.task-media-file, .is-broken) { cursor: zoom-in; }
a.task-media-frame:not(.task-media-file, .is-broken)::after {
  content: ""; position: absolute; top: 6px; right: 6px; width: 22px; height: 22px; border-radius: 50%; pointer-events: none;
  background: rgb(0 0 0 / .45) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7'/%3E%3C/svg%3E") center / 11px no-repeat;
}
@media (hover: hover) { a.task-media-frame:hover img { filter: brightness(.9); } }
a.task-media-frame.task-media-file { border: 1px solid var(--border-strong); background: var(--surface); color: var(--primary-text); font-weight: 600; }
@media (hover: hover) { a.task-media-frame.task-media-file:hover { text-decoration: none; background: var(--primary-soft); border-color: var(--primary); } }

/* Раскрывающиеся блоки (Промпт, Анализ фото, Все поля): треугольник заметнее, при наведении — цвет ссылки. */
.task-summary::before { color: var(--text-muted); width: 14px; }
@media (hover: hover) { .task-summary:hover, .task-summary:hover::before { color: var(--primary-text); } }

/* Страницы списка — кнопками с рамкой (цифры без рамки не читались как кликабельные). */
.pager a { border: 1px solid var(--border-strong); background: var(--surface); color: var(--text); }

/* Тумблер в выключенном положении — с контуром (иначе серая полоска не похожа на переключатель). */
.toggle input:not(:checked) + .toggle-track { box-shadow: inset 0 0 0 1px var(--text-faint); }

/* Отклик на нажатие на касании (вместо brightness, почти невидимого в тёмной теме). Строка заданий открывается
   двойным тапом — одиночный её не «нажимает». */
@media (hover: none) {
  a:not(.btn, .chip, .st-chip, .filter-chip, .sheet-row, .tabbar-item, .task-media-frame, .attention-list a, .freq-errors-list a):active { opacity: .6; }
  :is(.btn, .chip-toggle, .st-chip, .filter-chip, .pager a, .pager-mini a, .tabbar-item, .theme-switch-btn, .freq-errors-list a, .task-summary):active { background: var(--surface-3); }
  .btn-primary:active { background: var(--primary-hover); }
  a.chip[data-status]:active { background: color-mix(in srgb, var(--st) 24%, var(--surface)); }
  .attention-list a:active { background: color-mix(in srgb, var(--warning) 18%, var(--surface)); }
  a.task-media-frame:active img { opacity: .8; }
}

/* 26. Галерея — крупный просмотр фото (admin.js «Галерея»): миниатюры промтов в списке, загруженные фото формы.
   dialog.gallery: фото вписано целиком, подпись и «N из M», «Оригинал ↗», ‹ › по бокам; на телефоне — во весь экран.
   Свайп вниз: окно едет за пальцем (transform — admin.js), затемнение светлеет по --gallery-fade (::backdrop наследует
   переменные от dialog, и transition тоже: пока тянем — none, отпустили — окно и затемнение за .2s доезжают до конца
   или обратно; в старых браузерах затемнение просто остаётся).
   Внутренний блок — .gallery-inner: .gallery-box занят блоком 360p формы видео (20).
   Слайдер: .gallery-body > .gallery-track — родная горизонтальная прокрутка с прилипанием (scroll-snap, по одному слайду
   за свайп, полоса прокрутки скрыта; ленте разрешён только горизонтальный жест — вертикальный ловит admin.js для закрытия)
   > .gallery-slide (100 % ширины: фото или видео целиком; .is-gray — референс на сером). ‹ › — поверх ленты, у видео на
   телефоне — ниже его панели. Шапка: длинная подпись — в одну строку с многоточием (полная — в title). */
a.thumb-slot { display: block; cursor: zoom-in; }
@media (hover: hover) { a.thumb-slot:hover img, .slot-open:hover img { filter: brightness(.9); } }
.gallery {
  width: min(1100px, calc(100vw - 48px)); max-width: none; height: calc(100dvh - 48px); max-height: none; padding: 0;
  border: 1px solid var(--border); border-radius: 20px; background: var(--surface); color: var(--text); overflow: hidden;
  transition: transform .2s ease-out, background-color .2s ease-out; /* фон окна не меняется — второе для ::backdrop */
}
.gallery::backdrop { background: rgb(0 0 0 / calc(.72 * var(--gallery-fade, 1))); transition: inherit; }
.gallery:focus { outline: none; } /* фокус на самом окне — только для клавиш, не рамка */
@media (prefers-reduced-motion: reduce) { .gallery { transition: none; } }
html:has(.gallery[open]) { overflow: hidden; }
.gallery-inner { display: flex; flex-direction: column; height: 100%; }
.gallery-head { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); touch-action: none; }
.gallery-title { flex: 0 1 auto; min-width: 0; margin: 0; font-size: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gallery-count { flex: none; white-space: nowrap; color: var(--text-muted); font-size: var(--fs-s); }
.gallery-orig { flex: none; white-space: nowrap; margin-left: auto; font-size: var(--fs-s); }
.gallery-head .btn-icon { flex: none; }
.gallery-body { position: relative; flex: 1; min-height: 0; overflow: hidden; background: var(--surface-2); }
.gallery-track {
  display: flex; height: 100%; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scrollbar-width: none; touch-action: pan-x; touch-action: pan-x pinch-zoom;
}
.gallery-track::-webkit-scrollbar { display: none; }
/* Страница увеличена щипком (.is-zoomed — admin.js по visualViewport): любой пан двигает её, лента сама не листается
   (scrollLeft сохраняется — слайд на месте; ‹ › листают scrollTo). */
.gallery.is-zoomed :is(.gallery-track, .gallery-head) { touch-action: auto; }
.gallery.is-zoomed .gallery-track { overflow-x: hidden; }
.gallery-slide {
  flex: 0 0 100%; min-width: 0; display: flex; align-items: center; justify-content: center; padding: 12px 56px;
  scroll-snap-align: center; scroll-snap-stop: always;
}
.gallery-slide :is(img, video) { display: block; max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px; }
.gallery-slide video { background: #000; }
.gallery-slide.is-gray { background: var(--ref-bg); } /* референс (PNG с альфой) — на сером, как в списке */
.gallery-nav {
  position: absolute; z-index: 1; top: 50%; width: 40px; height: 40px; margin-top: -20px; padding: 0; display: grid; place-items: center;
  border: 1px solid var(--border-strong); border-radius: 50%; background: var(--surface); color: var(--text); font-size: 22px; line-height: 1;
}
.gallery-nav--prev { left: 8px; }
.gallery-nav--next { right: 8px; }
.gallery-nav:disabled { opacity: .35; cursor: default; }
@media (hover: hover) { .gallery-nav:not(:disabled):hover { background: var(--surface-3); } }
/* Миниатюры полоски списка заданий не на компьютере открывают галерею (admin.js): iOS присылает click по тапу, только если
   у цели cursor: pointer (обработчик — на document); медиазапрос — обратный desktop в admin.js. */
@media not all and (min-width: 1024px) and (hover: hover) and (pointer: fine) {
  .task-strip[data-zoom] :is(.task-slot[data-zoom-video], .task-slot[data-zoom-src]:not(.is-broken), .task-slot-extra, .task-slot-more) { cursor: pointer; }
}
@media (pointer: coarse) { .task-strip .task-slot:is([data-zoom-src], [data-zoom-video]):active { opacity: .8; } }
@media (max-width: 767.98px) {
  .gallery { width: 100vw; height: 100dvh; border: 0; border-radius: 0; }
  .gallery-head { padding: calc(8px + var(--safe-t)) max(12px, var(--safe-r)) 8px max(12px, var(--safe-l)); }
  .gallery-slide { padding: 8px 8px calc(8px + var(--safe-b)); }
  .gallery-body.has-nav .gallery-slide.is-video { padding-bottom: calc(72px + var(--safe-b)); } /* панель плеера — над ‹ › */
  .gallery-nav { top: auto; bottom: calc(16px + var(--safe-b)); margin: 0; width: 48px; height: 48px; }
}

/* 27. Пользователи /users, /users/{id}, /users/find (pages/users/*; docs/ADMIN_USERS.md) ---------------------------------
   Панель, чипы фильтров, состояния, поиск и блоки карточки — общие с «Заданиями» (22–24): .tasks-bar, .filter-chips,
   .task-state, .resolve-*, .task-top, .task-layout, .task-block, .task-ledger, .task-event; пейджер — .pager (раздел 9);
   сегменты — .st-chips (22) и .seg (18). Здесь — только своё: сегменты и период, строка списка (аватар, работы,
   активность), метки состояния, плитки и вкладки карточки, обзор, входы, заказы, последние задания, события. */
@media (min-width: 1024px) { .table--users { min-width: 976px; } } /* 1024 − 2×24 поля = 976 */
.table--users .col-user { width: 25%; }
.table--users .col-works { width: 124px; }
.table--users .col-activity { width: 15%; }
.table--users .col-balance { width: 60px; }
.table--users .col-paid { width: 128px; }
.table--users .col-reg { width: 92px; }
.table--users .col-source { width: 12%; }
.table--users .col-invited { width: 168px; }
.table--users td { vertical-align: top; }
/* «Привёл»: по рефералке и по партнёрке — своей строкой, купившие — ссылкой-фильтром; заголовок сортируемый (8). */
.user-inv-line { display: block; white-space: nowrap; }
.user-inv-kind { color: var(--text-muted); font-size: var(--fs-s); }
.table--users .th-sub { display: block; font-weight: 400; }
.table th .sort.is-off { color: inherit; cursor: help; } /* сортировка недоступна в этом сегменте — подсказка в title */
.table th .sort.is-off::after { content: ""; }

/* Источник — комбобокс в панели фильтров (12): ширина как у селекта, список поверх таблицы. */
.filters--tasks .users-source { width: 260px; flex: none; }
.filters--tasks .users-source .input { height: 36px; }
.users-source .combobox-list { width: max(100%, 360px); }
/* Сегменты: «Все · Активные · Платящие» — чипы статусов (22), активный — акцентом; период — сегментированный контрол. */
.user-segs { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin: 0 0 var(--sp-3); }
.user-seg-list { flex-wrap: wrap; overflow: visible; }
.user-wins { gap: 2px; }
.user-wins .seg-btn { display: inline-flex; align-items: center; height: 26px; padding: 0 10px; color: var(--text-muted); text-decoration: none; }
.user-wins .seg-btn.is-active { color: var(--primary-text); }
@media (hover: hover) { .user-wins .seg-btn:hover { text-decoration: none; color: var(--text); } }

/* Строка: аватар 40×50 (портрет или фото для фотосессий; нет — значок), #id и имя — ссылки, ниже платформа и ник, метки. */
.user-cell { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.user-avatar { flex: none; display: grid; place-items: center; width: 40px; height: 50px; border-radius: 8px; overflow: hidden; background: var(--surface-2); color: var(--text-faint); }
.user-avatar img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 25%; }
.user-avatar.is-broken { background: var(--surface-2); }
.user-avatar .icon { width: 18px; height: 18px; }
.user-cell-text { min-width: 0; flex: 1; }
.user-line { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.user-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; color: var(--text); }
.user-name.is-empty { color: var(--text-muted); font-weight: 400; }
@media (hover: hover) { a.user-name:hover { color: var(--primary-text); } }
.user-sub { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; margin-top: 2px; font-size: var(--fs-s); }
.user-sub > .muted { min-width: 0; overflow-wrap: anywhere; }
.table--users td.cell-source { overflow-wrap: anywhere; } /* автоисточник называется сырым кодом без пробелов */
.user-marks { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.user-balance, .user-paid { font-variant-numeric: tabular-nums; }
.user-balance.is-zero { color: var(--text-muted); font-weight: 400; }
.user-src-line { display: block; margin-top: 2px; }

/* Работы: три последних готовых результата 36×45 внахлёст (как стопка входа в «Заданиях»), ▶ у постера видео; клик —
   галерея (26). Без Image Resizing — значок (.task-slot.is-off — 22). */
.user-works { display: inline-flex; padding-right: 4px; }
.user-works .user-work { width: 36px; height: 45px; border-radius: 6px; cursor: zoom-in; }
.user-works .user-work + .user-work { margin-left: -12px; box-shadow: -2px 0 0 var(--surface); }
.user-works .user-work.is-off { display: grid; place-items: center; color: var(--text-faint); }
.user-works .user-work.is-off .icon { width: 14px; height: 14px; }
@media (hover: hover) { .user-works .user-work:hover { z-index: 1; box-shadow: 0 0 0 2px var(--primary); } }
.user-works-none { color: var(--text-faint); }

.user-via { margin-top: 4px; } /* поиск: «найдено по: e-mail плательщика» под пользователем */
.users-found { margin-top: var(--sp-3); }
/* Активность: главная строка и подпись; в сегментах — за период. */
.user-act-main { display: block; }
.user-act-sub { display: block; margin-top: 2px; }
a.user-filter.danger { color: var(--danger); }

/* Метки состояния — статичные пилюли с заливкой (раздел 25: без рамки — не кликаются). */
.user-mark { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; font-size: 12px; line-height: 1.5; white-space: nowrap; background: var(--surface-2); color: var(--text-muted); }
.user-mark--ban { background: var(--danger-soft); color: var(--danger); font-weight: 600; }
.user-mark--bot { background: var(--warning-soft); color: var(--warning); }
.user-mark--merged { background: var(--info-soft); color: var(--info); }
.user-mark--ok { background: var(--success-soft); color: var(--success); }

/* Карточка: шапка как у задания (23) — аватар 32×40, имя после #id, «Поделиться» справа; строка под ней — платформа,
   @username, устройство, дата и метки. Плитки — ссылки на вкладки (баланс → кадры, оплаты, задания, активность → события);
   жёлтая рамка — баланс не сходится с журналом. */
.user-head-avatar { flex: none; display: grid; place-items: center; width: 32px; height: 40px; border-radius: 7px; overflow: hidden; background: var(--surface-2); color: var(--text-faint); }
.user-head-avatar a { display: block; width: 100%; height: 100%; }
.user-head-avatar img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 25%; }
.user-head-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 17px; font-weight: 600; }
.user-top .task-share { margin-left: auto; }
.user-head-meta { align-items: center; }
.user-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-3); margin-bottom: var(--sp-3); }
.user-tile { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius-l); background: var(--surface); color: var(--text); }
.user-tile-label { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; font-size: var(--fs-s); color: var(--text-muted); }
.user-tile-go { color: var(--text-faint); font-size: 16px; line-height: 1; }
.user-tile-value { font-size: 24px; font-weight: 700; line-height: 1.2; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.user-tile-value--text { font-size: 18px; }
.user-tile-sub { min-width: 0; font-size: var(--fs-s); color: var(--text-muted); overflow-wrap: anywhere; }
.user-tile.is-warn { border-color: color-mix(in srgb, var(--warning) 55%, var(--border)); }
.user-tile.is-warn .user-tile-sub { color: var(--warning); }
@media (hover: hover) {
  a.user-tile--link:hover { text-decoration: none; border-color: var(--border-strong); background: var(--surface-2); }
  a.user-tile--link:hover .user-tile-go { color: var(--primary-text); }
}
.user-warn { margin: 0 0 var(--sp-2); padding: 8px 10px; border-radius: var(--radius); background: var(--warning-soft); color: var(--warning); font-size: var(--fs-s); }

/* Вкладки карточки: полоса ссылок с подчёркиванием активной (как вкладки формы, но во всю ширину и с прокруткой на
   телефоне); счётчик — серым. Содержимое #user-tab меняется HTMX'ом, при загрузке — полупрозрачное. */
.user-tabs { display: flex; gap: 2px; margin: 0 0 var(--sp-3); border-bottom: 1px solid var(--border); overflow-x: auto; scrollbar-width: none; }
.user-tabs::-webkit-scrollbar { display: none; }
.user-tab-link { flex: none; display: inline-flex; align-items: center; gap: 6px; height: 40px; padding: 0 14px; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--text-muted); font-weight: 600; white-space: nowrap; }
.user-tab-link[aria-current] { color: var(--primary-text); border-bottom-color: var(--primary); }
@media (hover: hover) { .user-tab-link:hover { text-decoration: none; color: var(--text); background: var(--surface-2); border-radius: var(--radius-s) var(--radius-s) 0 0; } }
.user-tab-count { font-weight: 500; font-variant-numeric: tabular-nums; color: var(--text-faint); }
.user-tab-link[aria-current] .user-tab-count { color: var(--primary-text); opacity: .8; }
.user-tab.htmx-request { opacity: .5; transition: opacity .15s; }
.user-tab-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--sp-3); align-items: start; }
.user-block-more { margin-left: auto; font-size: var(--fs-s); font-weight: 500; white-space: nowrap; }
.task-block-title:has(> .user-block-more) { display: flex; align-items: baseline; gap: 8px; }
.user-totals-line { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0 0 var(--sp-2); color: var(--text-muted); }
.user-total b { margin-right: 3px; }
.user-ov-device { margin-top: var(--sp-2); }
.user-ov-related { margin-top: var(--sp-2); }

/* Значение с копированием: как #id в шапке — иконка рядом (раздел 25: кликабельное видно и на касании). */
.user-copy { display: inline; padding: 0; border: 0; background: none; color: inherit; font-size: inherit; font-weight: inherit; line-height: inherit; text-align: left; overflow-wrap: anywhere; cursor: copy; }
.user-copy.mono { font-family: var(--mono); }
.user-copy .icon { width: 13px; height: 13px; margin-left: 5px; vertical-align: -1px; color: var(--text-faint); }
@media (hover: hover) { .user-copy:hover, .user-copy:hover .icon { color: var(--primary-text); } }

/* Входы и устройства: способ · идентификатор · дата; люди (то же устройство, склейка) — список ссылок. */
.user-login-list { list-style: none; display: flex; flex-direction: column; margin: 0; }
.user-login-list li { display: grid; grid-template-columns: 76px minmax(0, 1fr) auto; align-items: baseline; gap: 2px 10px; padding: 6px 0; border-top: 1px solid var(--border); }
.user-login-list li:first-child { padding-top: 0; border-top: 0; }
.user-login-kind { color: var(--text-muted); font-size: var(--fs-s); }
.user-login-id { min-width: 0; overflow-wrap: anywhere; }
.user-ids { margin-top: var(--sp-2); }
.user-people { list-style: none; display: flex; flex-direction: column; gap: 4px; margin: 0; }
.user-hint { margin: 6px 0 0; }

/* Кадры: итоги по причинам и журнал (.task-ledger); пояснение — второй строкой; «Показать ещё» — по центру. */
.user-reason-list { list-style: none; margin: 0; font-size: var(--fs-s); }
.user-reason-list li { display: flex; align-items: baseline; gap: 10px; padding: 5px 0; border-top: 1px solid var(--border); }
.user-reason-list li:first-child { border-top: 0; padding-top: 0; }
.user-reason-name { flex: 1; min-width: 0; }
.user-ledger .task-ledger-what { flex: 1 1 0; min-width: 0; overflow-wrap: anywhere; } /* время — справа в первой строке */
.user-ledger-note { display: block; font-size: var(--fs-s); }
.task-ledger li.user-more, .user-order-list li.user-more { display: flex; justify-content: center; padding: 8px 0 0; }
.task-ledger li.user-more > :last-child { margin: 0; }

/* Оплаты: полоса статуса слева (ок — зелёная, ошибка — красная, создан — серая, возврат — жёлтая). */
.user-orders-note { margin: 0 0 var(--sp-2); }
.user-order-list { list-style: none; display: flex; flex-direction: column; margin: 0; }
.user-order { --ot: var(--border-strong); display: flex; flex-direction: column; gap: 2px; padding: 8px 0 8px 10px; border-top: 1px solid var(--border); border-left: 3px solid var(--ot); }
.user-order:first-child { border-top: 0; }
.user-order.is-ok { --ot: var(--success); }
.user-order.is-bad { --ot: var(--danger); }
.user-order.is-warn { --ot: var(--warning); }
.user-order-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
.user-order-status { font-size: var(--fs-s); font-weight: 600; color: var(--text-muted); }
.user-order-status.is-ok { color: var(--success); }
.user-order-status.is-bad { color: var(--danger); }
.user-order-status.is-warn { color: var(--warning); }
.user-order-sum { font-variant-numeric: tabular-nums; }
.user-order-time { margin-left: auto; }
.user-order-meta, .user-order-credit { overflow-wrap: anywhere; }
.user-order-error { color: var(--danger); overflow-wrap: anywhere; }
.user-sub-card { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-top: var(--sp-2); padding: 8px 10px; border-radius: var(--radius); background: var(--surface-2); }
.user-sub-card:first-child { margin-top: 0; }

/* Задания: чипы статусов переносятся (не лента), последние — карточки-ссылки с миниатюрой результата 4:5. */
.user-st-chips { flex-wrap: wrap; overflow: visible; margin-bottom: var(--sp-2); }
.user-recent { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; margin: 0; }
.user-recent-card { display: flex; align-items: center; gap: 10px; height: 100%; padding: 6px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); }
@media (hover: hover) { .user-recent-card:hover { text-decoration: none; border-color: var(--border-strong); background: var(--surface-2); } }
.user-recent-thumb { position: relative; flex: none; display: grid; place-items: center; width: 48px; height: 60px; overflow: hidden; border-radius: 6px; background: var(--surface-2); }
.user-recent-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; }
.user-recent-thumb.is-video::after { content: "▶"; position: absolute; left: 2px; bottom: 2px; padding: 1px 3px; border-radius: 4px; font-size: 8px; line-height: 1.2; color: #fff; background: rgba(0, 0, 0, .55); }
.user-recent-empty { font-size: 18px; color: var(--text-faint); }
.user-recent-empty.is-bad { color: var(--danger); }
.user-recent-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; font-size: var(--fs-s); }
.user-recent-what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-recent-status { font-weight: 600; }
.user-recent-status.is-ok { color: var(--success); }
.user-recent-status.is-bad { color: var(--danger); }
.user-recent-status.is-run { color: var(--info); }

/* Фото и анализ: сетка 4:5, крупно — галерея (26). */
.user-photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--sp-2); margin: 0 0 var(--sp-2); }
.user-photo { display: flex; flex-direction: column; gap: 4px; margin: 0; }
.user-photo .task-media-frame { aspect-ratio: 4 / 5; }
.user-photo img { width: 100%; height: 100%; object-fit: cover; }

/* События: пресеты — чипы-кнопки, ошибка — красная полоса слева, «Раньше» — следующее окно 30 дней. */
.user-ev-presets { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 var(--sp-2); }
button.chip-toggle { color: var(--text); font-family: inherit; }
.user-ev-window { margin: var(--sp-2) 0 4px; }
.user-ev-list .task-event.is-error { padding-left: 8px; border-left: 3px solid var(--danger); }
.user-ev-more { margin-top: var(--sp-2); }
.users-find-source { margin: var(--sp-3) 0 0; }

/* До 1023px: строки — карточки (21.5): аватар, #id и имя | кадры; работы и активность; оплаты, регистрация, откуда.
   Карточка: плитки 2×2, вкладки лентой, обзор и профиль — одна колонка (порядок — .task-col). */
@media (max-width: 1023.98px) {
  .table--users thead { display: none; }
  .table--users tbody tr {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "user balance" "works works" "activity activity"; /* остальное — строками во всю ширину */
    gap: 4px 10px;
  }
  .table--users td.cell-user { grid-area: user; min-width: 0; }
  .table--users td.cell-balance { grid-area: balance; text-align: right; }
  .table--users td.cell-balance::after { content: " кадр."; color: var(--text-muted); font-size: var(--fs-s); }
  .table--users td.cell-works { grid-area: works; }
  .table--users td.cell-works:has(> .user-works-none) { display: none; }
  .table--users .user-works .user-work { width: 48px; height: 60px; }
  .table--users .user-works .user-work + .user-work { margin-left: 4px; box-shadow: none; }
  .table--users td.cell-activity { grid-area: activity; font-size: var(--fs-s); }
  .table--users td.cell-activity .user-act-main, .table--users td.cell-activity .user-act-sub { display: inline; }
  .table--users td.cell-activity .user-act-sub { margin-left: 6px; }
  .table--users td:is(.cell-reg, .cell-paid, .cell-source, .cell-invited) { grid-column: 1 / -1; }
  .table--users td.cell-invited { font-size: var(--fs-s); }
  .table--users td.cell-invited:has(> .muted:only-child) { display: none; }
  .table--users .user-inv-line { display: inline; white-space: normal; }
  .table--users .user-inv-line + .user-inv-line { margin-left: 8px; }
  .table--users td.cell-reg { font-size: var(--fs-s); color: var(--text-muted); }
  .table--users td.cell-reg::before { content: "рег. "; }
  .table--users td.cell-paid { font-size: var(--fs-s); }
  .table--users td.cell-paid::before { content: "оплаты: "; color: var(--text-muted); }
  .table--users td.cell-paid .user-act-sub { display: inline; margin-left: 6px; }
  .table--users td:is(.cell-paid, .cell-source):has(> .muted:only-child) { display: none; } /* «—» */
  .table--users td.cell-source { font-size: var(--fs-s); }
  .table--users .user-src-line { display: inline; margin: 0 0 0 8px; }
  .table--users td.cell-source > .user-src-line:first-child { margin-left: 0; }
  .user-tab-grid { grid-template-columns: minmax(0, 1fr); }
  /* .task-col — display: contents (23): порядок задают блоки. Обзор: работы, кадры, оплаты, профиль, входы;
     профиль: профиль, входы, фото. */
  .user-layout .user-ov-tasks { order: -3; }
  .user-layout .user-ov-credits { order: -2; }
  .user-layout .user-ov-orders { order: -1; }
  .user-layout .user-photos { order: 1; }
}
@media (max-width: 767.98px) {
  .user-segs { gap: 8px; }
  .user-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .user-tile { padding: 10px 12px; }
  .user-tile-value { font-size: 20px; }
  .user-tile-value--text { font-size: 16px; }
  .user-head-name { font-size: 16px; }
  .user-head-avatar { display: none; } /* в узкой шапке аватару места нет — он в «Профиле» */
  .user-tabs { margin-left: calc(-1 * var(--gutter)); margin-right: calc(-1 * var(--gutter)); padding: 0 var(--gutter); }
  .user-recent { grid-template-columns: minmax(0, 1fr); }
  .user-order-time { flex-basis: 100%; margin-left: 0; }
}
@media (pointer: coarse) {
  .user-recent-card { min-height: 60px; }
  .user-ev-presets .chip-toggle { height: 36px; padding: 0 14px; }
  .user-tab-link { height: 44px; }
  .user-wins .seg-btn { height: 32px; }
}
@media (hover: none) {
  :is(.user-tile--link, .user-recent-card):active { background: var(--surface-3); }
}

/* 28. Тексты /texts (pages/texts/*; docs/ADMIN_TEXTS.md) -------------------------------------------------------------
   Шапка раздела: заголовок и переключатель «Пуши · Промо и советы» — ссылки-сегменты в стиле табов формы (.tabs-list, 18),
   подсказка — своей строкой. Пуши: карточка типа — ссылка на форму целиком (25: «›» у названия, подсветка), ключ
   кампании прижат к низу (карточки ряда — одной высоты); выключенный тип — текст и ключ приглушены. Форма пуша: aside
   шире (превью ближе к ширине уведомления на iPhone), подписи полей во вкладках языков видны (в форме промта подпись
   дублирует вкладку и скрыта), галка смены ключа, предупреждения полей. Превью «как на экране блокировки iOS»: обои —
   постоянный градиент (картинка, одна для обеих тем), уведомление — из токенов (в тёмной теме тёмное).
   До 1023px превью — шторка #push-preview-sheet (21.6); до 767px переключатель и вкладки RU / EN — во всю ширину
   поровну (21.6 делает табы сеткой 3×2), карточки — одной колонкой. «Промо и советы» и превью «как в Telegram» — ниже,
   после превью пуша; за ними — советы и сообщения после генерации (шаг C). */
.texts-head .tabs-list { margin: 0; }
.texts-head .page-hint { flex-basis: 100%; }
.texts-switch .tab { text-decoration: none; }
.texts-switch .tab[aria-current] { background: var(--primary-soft); color: var(--primary-text); font-weight: 600; }
@media (hover: hover) { .texts-switch .tab:hover { text-decoration: none; } }

.push-grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: var(--sp-3); margin: 0; }
.push-grid > li { display: flex; min-width: 0; }
.push-card {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-l); background: var(--surface); color: var(--text);
}
@media (hover: hover) {
  .push-card:hover { text-decoration: none; border-color: var(--border-strong); background: var(--surface-2); }
  .push-card:hover .push-card-go { color: var(--primary-text); }
}
.push-card-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.push-card-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-l); font-weight: 600; }
.push-card-go { flex: none; margin-left: auto; color: var(--text-faint); font-size: 18px; line-height: 1; }
.push-card-when { font-size: var(--fs-s); color: var(--text-muted); }
.push-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; margin-top: 2px; padding-left: 10px; border-left: 2px solid var(--border-strong); }
.push-card-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.push-card-body { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--text-muted); overflow-wrap: anywhere; }
.push-card-key { margin-top: auto; padding-top: 2px; font-size: 11px; color: var(--text-faint); overflow-wrap: anywhere; }
.push-card.is-off :is(.push-card-text, .push-card-key) { opacity: .55; }

@media (min-width: 1100px) { .form-layout--push { grid-template-columns: minmax(0, 1fr) 380px; } }
.push-about { margin: -2px 0 var(--sp-4); font-size: var(--fs-s); color: var(--text-muted); }
.push-about p { margin: 0; }
.push-about p + p { margin-top: 4px; }
.push-when { color: var(--text); font-size: var(--fs); font-weight: 500; }
.push-warn { display: flex; align-items: flex-start; gap: 6px; margin: 0 0 var(--sp-4); padding: 8px 10px; border-radius: var(--radius); background: var(--warning-soft); color: var(--warning); font-size: var(--fs-s); }
.push-warn .icon { flex: none; width: 14px; height: 14px; margin-top: 1px; }
.push-active { margin-bottom: var(--sp-4); }
.push-confirm { display: flex; align-items: flex-start; gap: 8px; margin: -4px 0 var(--sp-4); padding: 8px 10px; border-radius: var(--radius); background: var(--warning-soft); font-weight: 500; }
.push-confirm[hidden] { display: none; }
.push-confirm input { flex: none; margin-top: 1px; }
/* Подпись и счётчик — в строку по базовой линии (счётчик ставит texts.js в подпись). */
.push-lang .field > .field-label { position: static; display: flex; align-items: baseline; gap: 8px; width: auto; height: auto; margin: 0 0 5px; overflow: visible; clip: auto; }
.push-lang .field-label-text { flex: 1; min-width: 0; }
.push-lang .char-count { float: none; margin: 0; }
.field-hint--warn { color: var(--warning); }

/* Превью: уведомление на язык поверх «обоев»; заголовок — до 2 строк, текст — до 4 (дальше iOS обрезает).
   Ширина — как у iPhone (≤360px): иначе в шторке и под формой (768–1099px) строки длиннее и обрезка не та. */
.push-screen { display: flex; flex-direction: column; gap: 12px; max-width: 360px; margin-inline: auto; padding: 14px 8px 16px; border-radius: 14px; background: linear-gradient(165deg, #d77fcf 0%, #9a5bbd 45%, #4b3a8f 100%); }
.push-shot-lang { display: block; margin: 0 6px 5px; font-size: 11px; font-weight: 600; letter-spacing: .04em; color: rgb(255 255 255 / .92); text-shadow: 0 1px 2px rgb(0 0 0 / .25); }
.push-shot-lang [data-push-fallback] { font-weight: 500; letter-spacing: 0; }
.push-note {
  display: grid; grid-template-columns: 38px minmax(0, 1fr); gap: 10px; align-items: start; padding: 11px 12px 11px 10px;
  border-radius: 20px; background: color-mix(in srgb, var(--surface) 86%, transparent); color: var(--text);
  -webkit-backdrop-filter: blur(24px) saturate(1.5); backdrop-filter: blur(24px) saturate(1.5);
  box-shadow: 0 1px 2px rgb(0 0 0 / .12); font-size: 15px; line-height: 1.3;
}
.push-note-icon { width: 38px; height: 38px; border-radius: 9px; }
.push-note-main { min-width: 0; }
.push-note-head { display: flex; align-items: baseline; gap: 8px; }
.push-note-title { flex: 1; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; font-weight: 600; }
.push-note-time { flex: none; font-size: 13px; color: var(--text-muted); }
.push-note-body { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; white-space: pre-line; }
.push-note-body:empty { display: none; }

/* «Промо и советы» (pages/texts/{promo,_promo_form_body,_promo_preview,_tg}.html). Над полями промо — условия акции только для
   чтения (процент с бейджем состояния, даты, пояснение), как .push-about. Превью «как в Telegram» (.tg-chat, макрос _tg.html —
   им же пользуется шаг C): обои — постоянный градиент в тонах обоев Telegram по умолчанию (одни для обеих тем, как у превью
   пуша), входящее сообщение бота — пузырь из токенов (в тёмной теме тёмный) с хвостиком снизу слева (маска-эллипс); разметка
   Telegram: жирный, курсив, подчёркнутый, зачёркнутый, код, блок кода, цитата с полосой, спойлер — серая плашка (на десктопе
   раскрывается наведением), ссылки — --info. Ширина — как у iPhone (≤360px). До 1023px — шторка #promo-preview-sheet (21.6). */
@media (min-width: 1100px) { .form-layout--promo { grid-template-columns: minmax(0, 1fr) 380px; } }
.promo-about { margin: -2px 0 var(--sp-4); font-size: var(--fs-s); color: var(--text-muted); }
.promo-about p { margin: 0; }
.promo-about p + p { margin-top: 4px; }
.promo-offer { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; color: var(--text); font-size: var(--fs); font-weight: 500; }

.tg-chat { display: flex; flex-direction: column; gap: 12px; max-width: 360px; margin-inline: auto; padding: 14px 10px 16px; border-radius: 14px; background: linear-gradient(160deg, #d5d88d 0%, #88b884 50%, #6ba587 100%); }
.tg-shot { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.tg-shot-lang { display: block; margin: 0 6px 5px; font-size: 11px; font-weight: 600; letter-spacing: .04em; color: rgb(255 255 255 / .95); text-shadow: 0 1px 2px rgb(0 0 0 / .3); }
.tg-shot-lang span { font-weight: 500; letter-spacing: 0; }
.tg-shot-warn { margin: 6px 0 0 8px; padding: 3px 8px; border-radius: 999px; background: var(--surface); color: var(--danger); font-size: 11px; font-weight: 600; }
.tg-msg {
  position: relative; max-width: calc(100% - 36px); margin-left: 8px; padding: 6px 10px 7px; border-radius: 16px 16px 16px 0;
  background: var(--surface); color: var(--text); font-size: 15px; line-height: 1.35; overflow-wrap: anywhere; box-shadow: 0 1px 1px rgb(0 0 0 / .14);
}
.tg-msg::before {
  content: ""; position: absolute; left: -8px; bottom: 0; width: 9px; height: 16px; background: inherit;
  -webkit-mask: radial-gradient(9px 16px at 0 0, transparent 98%, #000 100%); mask: radial-gradient(9px 16px at 0 0, transparent 98%, #000 100%);
}
.tg-msg a { color: var(--info); text-decoration: none; }
@media (hover: hover) { .tg-msg a:hover { text-decoration: underline; } }
.tg-msg code, .tg-msg pre { border-radius: 5px; background: var(--surface-2); font-family: var(--mono); font-size: 13px; }
.tg-msg code { padding: 0 3px; }
.tg-msg pre { margin: 4px 0; padding: 6px 8px; white-space: pre-wrap; }
.tg-msg pre code { padding: 0; background: none; }
.tg-msg blockquote { margin: 4px 0; padding: 2px 8px; border-left: 3px solid var(--info); border-radius: 4px; background: var(--info-soft); }
.tg-msg .tg-spoiler { border-radius: 4px; background: var(--text-faint); color: transparent; }
@media (hover: hover) { .tg-msg .tg-spoiler:hover { background: none; color: inherit; } }
/* Плейсхолдер, который бот подставит при отправке ({channelTag} …), — плашкой цвета акцента. */
.tg-msg .tg-ph { padding: 0 3px; border-radius: 4px; background: var(--primary-soft); color: var(--primary-text); font-size: 13px; }

/* Советы и сообщения после генерации (шаг C: pages/texts/{_gen_lists,_gen_row,_gen_note,_gen_fallback}.html под формой промо;
   форма — gen_form.html, _gen_form_body.html, _gen_preview.html). Списки — под сеткой .form-layout--promo (липкое превью промо
   на них не наезжает), от 1100px — шириной колонки формы. Строки — прямо в секции, как в настройках iOS: разделители на всю ширину секции (--sec-px — её боковой отступ),
   тумблер слева поверх строки, остальная строка — ссылка на форму («›» справа, 25), выключенная — текст приглушён;
   последней строкой — «+ Добавить». Якоря возврата из формы (#advice-3) — с отступом под шапку и подсветкой :target.
   Пометка над списком (.texts-note) — как .push-warn; info — серая. Форма — как у пуша; «Удалить» — красная кнопка без рамки
   в шапке; кнопки вставки плейсхолдеров — пилюли с рамкой (кликаются). До 767px в «Промо и советы» таббар остаётся (страница
   раздела, 21.2), панель сохранения промо — плавающая карточка над ним (под ней ещё списки, лента во всю ширину легла бы
   посреди страницы), нижний отступ страницы — снова, под таббар. */
.texts-lists { min-width: 0; margin-top: var(--sp-5); }
@media (min-width: 1100px) { .texts-lists { width: calc(100% - 380px - var(--sp-5)); } }
.texts-list-sec { --sec-px: var(--sp-4); scroll-margin-top: calc(var(--top-h) + 12px); }
.texts-note { display: flex; align-items: flex-start; gap: 6px; margin: 0 0 var(--sp-3); padding: 8px 10px; border-radius: var(--radius); font-size: var(--fs-s); }
.texts-note--warn { background: var(--warning-soft); color: var(--warning); }
.texts-note--info { background: var(--surface-2); color: var(--text-muted); }
.texts-note .icon { flex: none; width: 14px; height: 14px; margin-top: 1px; }
/* Тумблер стандартных текстов — своей строкой над списком, с разделителем. */
.gen-fallback { margin: 0 calc(-1 * var(--sec-px)); padding: 0 var(--sec-px) 12px; border-bottom: 1px solid var(--border); }
.gen-fallback .toggle { gap: 12px; } /* подпись — от той же линии, что текст строк (.text-row: gap 12px) */

.text-list { list-style: none; margin: 0 calc(-1 * var(--sec-px)); padding: 0; }
.text-row { position: relative; display: flex; align-items: flex-start; gap: 12px; padding: 11px var(--sec-px); border-top: 1px solid var(--border); scroll-margin-top: calc(var(--top-h) + 12px); }
.text-row:first-child { border-top: 0; }
.text-row:target { animation: row-hl 3s ease-out; }
.text-row.row-saved { animation: row-saved 1s ease-out; } /* после :target — тумблер строки-якоря вспыхивает «сохранено» */
.text-row-toggle { position: relative; z-index: 1; flex: none; margin-top: 1px; }
.text-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding-right: 18px; color: var(--text); }
.text-row-main::before { content: ""; position: absolute; inset: 0; }
.text-row-main::after {
  content: ""; position: absolute; top: 50%; right: calc(var(--sec-px) + 2px); width: 7px; height: 7px; margin-top: -4px;
  border: solid var(--text-faint); border-width: 1.6px 1.6px 0 0; transform: rotate(45deg);
}
.text-row-meta { font-size: var(--fs-s); font-weight: 600; color: var(--text-muted); }
.text-row-text { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
.text-row.is-off :is(.text-row-meta, .text-row-text) { opacity: .55; }
.text-row--add .text-row-main { flex-direction: row; align-items: center; gap: 8px; color: var(--primary-text); font-weight: 600; }
.text-row--add .text-row-main::after { display: none; }
@media (hover: hover) {
  .text-row:has(> .text-row-main:hover) { background: var(--surface-2); }
  .text-row-main:hover { text-decoration: none; }
  .text-row-main:hover::after { border-color: var(--primary-text); }
}

@media (min-width: 1100px) { .form-layout--gen { grid-template-columns: minmax(0, 1fr) 380px; } }
/* «Кому» шире «Порядка» (число). */
.gen-grid { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.gen-active { margin-bottom: var(--sp-4); }
.gen-delete { border-color: transparent; background: transparent; color: var(--danger); }
@media (hover: hover) { .gen-delete:hover { background: var(--danger-soft); } }
.ph-list { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: -6px 0 var(--sp-4); }
.ph-list-label { margin-right: 2px; font-size: var(--fs-s); color: var(--text-muted); }
.ph-chip {
  display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border: 1px solid var(--border-strong);
  border-radius: 999px; background: var(--surface); color: var(--text-muted); font: 500 var(--fs-s)/1 var(--font); cursor: pointer;
}
.ph-chip code { font-family: var(--mono); font-size: 12px; color: var(--primary-text); }
@media (hover: hover) { .ph-chip:hover { border-color: var(--primary); color: var(--text); } }

@media (max-width: 767.98px) {
  .texts-list-sec { --sec-px: 12px; }
  .form-layout--promo .save-bar {
    margin: 16px 0 0; bottom: calc(var(--dock) + 8px); padding: 8px; border-width: 1px; border-radius: var(--radius-l); box-shadow: var(--shadow-2);
  }
  .container:has(.texts-lists) { padding-bottom: calc(var(--dock) + 16px); }
  /* Шапка формы: «← К списку» слева, «Удалить» справа — как кнопки навигационной панели iOS. */
  .gen-head .page-actions { width: 100%; justify-content: space-between; }
  .gen-head .page-actions > .btn { flex: none; }
  .ph-list-label { flex-basis: 100%; }
  .gen-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (pointer: coarse) {
  .text-row { padding-block: 13px; }
  /* Тумблер — зона нажатия шире самой дорожки; раскладка строки та же. */
  .text-row-toggle { padding: 10px 8px; margin: -10px -8px; }
  .ph-chip { height: 36px; padding: 0 12px; }
}
@media (hover: none) {
  .text-row:has(> .text-row-main:active) { background: var(--surface-3); }
  .text-row .text-row-main:active { opacity: 1; }
  .ph-chip:active { background: var(--surface-3); }
}

@media (max-width: 767.98px) {
  .texts-switch, #push-form .tabs-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* «Рассылка»: сборка и задержка — короткие числа, в ряд; ключ — во всю ширину. */
  .push-send-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-3); }
  .push-send-grid > [data-field="push_key"] { grid-column: 1 / -1; }
  .push-card { padding: 12px 14px; }
}
@media (pointer: coarse) {
  .push-confirm { padding: 10px 12px; }
}
@media (hover: none) {
  .push-card:active { background: var(--surface-3); }
}
/* Шапка на узком десктопе: с «Текстами» у root восемь разделов — от 1024 до 1279px они не влезали вместе с темой и
   «Выйти» («Типы персон» переносились, «Выйти» уезжал за край). Роли прячем, разделы плотнее; если и так не влезут —
   лента с прокруткой, как на планшете (21.1), «Выйти» остаётся на месте. */
@media (min-width: 1024px) and (max-width: 1279.98px) {
  .topbar { gap: var(--sp-3); }
  .topbar-user-roles { display: none; }
  .topbar-nav { min-width: 0; gap: 2px; margin-left: var(--sp-1); overflow-x: auto; scrollbar-width: none; }
  .topbar-nav::-webkit-scrollbar { display: none; }
  .topbar-nav a { flex: none; padding: 6px 8px; white-space: nowrap; }
}

/* 29. Модели ИИ /models, /models/providers, /models/{id} (pages/models/*, docs/AI_MODELS.md §5) — только чтение
   (этап 1). Шапка — переключатель Фото/Видео/Провайдеры как в «Тексты» (28: .tabs-list .tab[aria-current]). Строка
   курса над таблицей — .page-hint. Список без пейджера (< 60 строк): .table--models, ячейки — cell-chain/cell-price/
   cell-cost/cell-options/cell-params как в /prompts (17). Провайдеры — подзаголовок группы строкой на всю ширину. */
.models-head .tabs-list { margin: 0; }
.models-switch .tab { text-decoration: none; }
.models-switch .tab[aria-current] { background: var(--primary-soft); color: var(--primary-text); font-weight: 600; }
@media (hover: hover) { .models-switch .tab:hover { text-decoration: none; } }
.models-quote { margin-top: -4px; }
.table--models .name-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
.table--models .name-line a { color: var(--primary-text); }
.table--models .name-sub { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.table--models td.cell-options > div + div { margin-top: 2px; }
.models-group-head td { background: var(--surface-2); font-size: var(--fs-s); }

/* 30. Редакторы моделей /models/new, /models/{id}/edit, /models/{id}/chain (pages/models/form.html,
   _form_body.html, chain.html, _chain_body.html, _chain_preview.html; docs/AI_MODELS.md §5, этап 3).
   Форма модели — обычные .section/.grid-2, как у промта (17). Таблица цепочки — плотные ячейки
   (.input--sm/.textarea--sm), под каждым шагом — доп. строка .chain-row-extra с request_overrides;
   .callout — предупреждения («нет активного шага») и ошибки сборки «Проверить» без блокировки формы. */
.callout { margin-bottom: var(--sp-3); padding: 10px 14px; border-radius: var(--radius); font-size: var(--fs-s); }
.callout--warning { background: var(--warning-soft); color: var(--warning); }
.callout--danger { background: var(--danger-soft); color: var(--danger); }
.callout p { margin: 0; }
.callout p + p { margin-top: 4px; }
.table--chain-edit .select, .table--chain-edit .input { min-width: 88px; }
.table--chain-edit td { vertical-align: top; }
.input--sm { padding: 4px 6px; font-size: var(--fs-s); }
.textarea--sm { min-height: 30px; padding: 4px 6px; font-size: var(--fs-s); resize: vertical; }
.chain-row-extra td { padding-top: 0; border-top: none; }
.chain-row-extra label { display: block; margin-bottom: 2px; }
tr.chain-row.row--error > td { background: var(--danger-soft); }
.table--chain-preview td { vertical-align: top; }
.table--chain-preview tr.row--error td { background: var(--danger-soft); }

/* 31. Таблицы данных (.table--data): реестр моделей (список, провайдеры, тарифы, алиасы, карточка модели, редактор и
   предпросмотр цепочки), курс (/settings/fx) и «Попытки» в карточке задания. Это не списки-карточки (21.5), а обычные
   таблицы: ширины колонок по содержимому (table-layout: auto вместо равных долей fixed — иначе коды провайдеров налезали
   на соседнюю колонку, а цепочка переносилась в четыре строки), числа и коды без переносов; на планшете и телефоне —
   та же таблица с горизонтальной прокруткой вместо карточек без подписей (владелец 29.09.2026). */
.table--data { table-layout: auto; min-width: 0; }
.table--data th { white-space: nowrap; }
.table--data td.mono, .table--data td.nowrap { white-space: nowrap; }
.table--data td.mono.small { white-space: normal; }
.table--chain-edit .select { max-width: 100%; }
.table--chain-edit td:nth-child(2) { min-width: 240px; }
.page-hint + #form-body { margin-top: var(--sp-4); }
@media (max-width: 1023.98px) {
  .table-wrap:has(> .table--data) { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .table.table--data { display: table; min-width: 720px; border: 1px solid var(--border); background: var(--surface); }
  .table.table--data > thead { display: table-header-group; }
  .table.table--data > thead > tr { display: table-row; margin: 0; }
  .table.table--data > thead > tr > th { display: table-cell; top: 0; }
  .table.table--data > tbody { display: table-row-group; }
  .table.table--data > tbody > tr { display: table-row; padding: 0; border: 0; border-radius: 0; background: none; }
  .table.table--data > tbody > tr > td { display: table-cell; width: auto; padding: 8px 10px; border-bottom: 1px solid var(--border); }
  .table.table--data > tbody > tr:last-child > td { border-bottom: 0; }
  .table.table--data > tfoot { display: table-footer-group; }
  .table.table--data tr.row--muted { opacity: 1; }
  .table.table--data tr.row--muted > td { opacity: .6; }
}
