/* SafeNation Admin — единственный слой стилей админки.
 *
 * До этого их было пять: admin-shell.css, admin-mobile.css, admin.css,
 * rebuild.css и rebuild-responsive.css. Они описывали одно и то же — топбар,
 * боковое меню, панели — но с разными точками перелома (761 против 860) и
 * двумя наборами переменных, поэтому каждый следующий перебивал предыдущий
 * силой !important. Тысяча с лишним объявлений !important на триста строк.
 *
 * Так и появился баг, из-за которого форма входа и форма кода подтверждения
 * показывались одновременно: правило раскладки перебивало сокрытие.
 *
 * Здесь всё держится на специфичности: разметка админки помечена классом
 * admin-app, и селектор .admin-app .panel сильнее базового .panel без всякой
 * силы. !important остаётся только там, где перебивается инлайн-стиль.
 */

/* ---------- токены ---------- */
.admin-app{
  --sn-bg:#101318;
  --sn-surface:#171c23;
  --sn-surface-2:#1d232c;
  --sn-elevated:#1b222b;
  --sn-border:#2a313b;
  --sn-text:#f5f7fa;
  --sn-muted:#8f98a6;
  /* Приглушённый цвет не проходил по контрасту ни на одной поверхности
     админки: 3.14–3.61 при норме 4.5 для обычного текста. #828e9e даёт
     4.75–5.46 везде. */
  --sn-muted-soft:#828e9e;
  /* Шрифты: пропорциональный — для всего, что читают словами; моноширинный
     остаётся кодам, ссылкам и числовым колонкам, где важна ширина знака. */

  /* Шкала кегля. Размеров было больше двадцати — от 8px до 2rem, вразнобой:
     .64, .66, .67, .68, .7, .72, .74, .76, .78, .8, .82, .85… Глаз не может
     опереться на такую сетку, и вся страница читается одинаково серой.
     Нижняя граница 12px: восьмипиксельная кириллица нечитаема в принципе. */
  --t-xs:12px;     /* подписи полей, мелкие метки — меньше не бывает */
  --t-sm:13px;     /* вторичный текст: мета, единицы */
  --t-md:14px;     /* пояснения и примечания */
  --t-base:15px;   /* основной текст карточек и заголовки разделов */
  --t-lg:17px;     /* заголовок карточки */
  --t-xl:22px;     /* число в плитке */
  --t-2xl:32px;    /* крупное число в обзоре */
  /* Моноширинный набор админки. На него уже ссылалось правило ниже, а
     объявления не было: шрифт молча наследовался от родителя, и цифры в
     строке времени не выстраивались столбиком. */
  --sn-mono:"JetBrains Mono",ui-monospace,"SFMono-Regular",monospace;
  --sn-accent:#ff4d2d;
  --sn-accent-tint:rgba(255,77,45,.12);
  --sn-accent-line:rgba(255,77,45,.28);
  /* Дорожка меры — тот же тон на ступень темнее заливки, а не серый: состояние
     тогда читается по всей длине полосы. Пара #7a3f2c → #ff4d2d проверена
     валидатором палитры: монотонность по светлоте, шаг ΔL ≥ 0.06, светлый
     конец 2.15:1 к поверхности плитки, один тон. */
  --sn-meter-track:#7a3f2c;
  /* Состояния данных. Четыре значения, проверены валидатором палитры на
     поверхности плитки: полоса светлоты, порог цветности и различимость по
     всем парам при дальтонизме (худшая 9.3 при протанопии, при обычном
     зрении 17.4 между красным и брендовым).
     Красный тёмный намеренно: он отличим от брендового оранжевого, но его
     контраст к плитке 2.66 — поэтому он работает заливкой с белым текстом
     (6.13), а не цветом текста. Цвет нигде не идёт один: рядом всегда
     слово — при печати и дальтонизме остаётся только оно. */
  --sn-ok:#279b61;   --sn-ok-bg:rgba(39,155,97,.13);   --sn-ok-line:rgba(39,155,97,.35);
  --sn-wait:#458bd0; --sn-wait-bg:rgba(69,139,208,.13); --sn-wait-line:rgba(69,139,208,.35);
  --sn-bad:#b42318;  --sn-bad-ink:#ffffff;
  --sn-radius:14px;
  --sn-sidebar:248px;
  --sn-top:76px;
  --sn-gutter:28px;
}

/* ---------- полотно ---------- */
.admin-app,
html:has(.admin-app){background:var(--sn-bg)}
/* Весь текст админки по умолчанию был моноширинным: body в base.css задаёт
   var(--mono) на весь сайт, и админка это наследовала. На сайте так задумано,
   в панели управления — нет: экран из мелкой моноширинной кириллицы читается
   как распечатка лога. Пропорциональный шрифт здесь по умолчанию, а
   моноширинный остаётся кодам, ссылкам и числовым колонкам. */
.admin-app{color:var(--sn-text);min-width:0;overflow-x:hidden;
  font-family:var(--display);font-size:var(--t-md);line-height:1.6}

/* ---------- экраны входа и подтверждения ---------- */
.admin-app #loginView,
.admin-app #mfaView{
  min-height:100dvh;
  width:100%;
  display:grid;
  place-items:center;
  padding:24px 16px;
  box-sizing:border-box;
}
/* Скрытый экран остаётся скрытым: сокрытие сильнее раскладки. */
.admin-app #loginView[hidden],
.admin-app #mfaView[hidden],
.admin-app #dashView[hidden]{display:none}

.admin-app .login{
  width:min(460px,100%);
  max-width:460px;
  margin:0 auto;
  padding:38px;
  box-sizing:border-box;
  background:linear-gradient(180deg,#232831,#1e2229);
  border:1px solid var(--sn-border);
  border-radius:26px;
  box-shadow:0 24px 70px -48px rgba(0,0,0,.9);
}
/* 42px не помещались в карточку 460px, и «//» съезжал на отдельную строку,
   выглядя как обрывок вёрстки. Для формы входа хватает и меньшего кегля. */
.admin-app .login h1{font-size:clamp(26px,3vw,32px);line-height:1.12;letter-spacing:-.03em;color:var(--sn-text);text-wrap:balance}
.admin-app .login h1 .slash{white-space:nowrap}
.admin-app .login .sub{line-height:1.65;color:var(--sn-muted);margin:12px 0 24px}
/* Поля шли вплотную друг к другу и к кнопке: форма читалась как один блок. */
.admin-app #loginView form,
.admin-app #mfaView form{display:flex;flex-direction:column;gap:14px}
.admin-app #loginView input,
.admin-app #mfaView input,
.admin-app #loginView button,
.admin-app #mfaView button{min-height:48px}
.admin-app #loginView button[type=submit],
.admin-app #mfaView button[type=submit]{width:100%;margin-top:4px;justify-content:center}
.admin-app #mfaView #mfaCancel{width:100%;justify-content:center}
.admin-app #mfaView #mfaCode{text-align:center;font-size:1.5rem;letter-spacing:.4em;text-indent:.4em}
/* Сообщение об ошибке входа выводилось обычным текстом абзаца и терялось
   среди подписей. Оформляем как на остальных страницах платформы. */
.admin-app .login-err{margin-top:4px;padding:12px 14px;border-radius:12px;border:1px solid var(--accent-line);background:var(--accent-tint);color:#ffb9a6;line-height:1.5;font-size:.82rem;overflow-wrap:anywhere}
.admin-app .login-err:empty{display:none}
.admin-app .login-forgot{
  align-self:flex-start;
  margin-top:-4px;
  font-family:var(--mono);
  font-size:.78rem;
  color:var(--sn-muted);
  text-decoration:underline;
  text-underline-offset:3px;
}
.admin-app .login-forgot:hover{color:var(--sn-text)}
.admin-app .login-forgot:focus-visible{outline:2px solid var(--sn-accent);outline-offset:3px;border-radius:4px}

/* ---------- оболочка панели ---------- */
.admin-app #dashView{
  position:relative;
  width:100%;
  min-height:100vh;
  padding-top:var(--sn-top);
  background:radial-gradient(circle at top right,rgba(255,77,45,.08),transparent 32%),var(--sn-bg);
}
.admin-app .admin-wrap{max-width:none;width:100%;margin:0;padding:0}

.admin-app .topbar{
  position:fixed;
  left:var(--sn-sidebar);
  right:0;
  top:0;
  height:var(--sn-top);
  min-height:var(--sn-top);
  z-index:250;
  margin:0;
  padding:0 var(--sn-gutter);
  box-sizing:border-box;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:20px;
  background:rgba(16,19,24,.94);
  backdrop-filter:blur(14px);
  border-bottom:1px solid var(--sn-border);
}
.admin-app .topbar .brand{display:flex;align-items:center;gap:11px;min-width:0}
.admin-app .topbar .mark{width:34px;height:34px;flex:none}
.admin-app .topbar .wm{font-family:var(--display);font-weight:800;font-size:.95rem;letter-spacing:.02em;color:var(--sn-text)}
.admin-app .topbar .wm b{color:var(--sn-accent)}
.admin-app .who{display:flex;align-items:center;gap:10px;color:var(--sn-muted);min-width:0}
.admin-app .who a{color:var(--sn-muted)}
.admin-app .role-badge{color:var(--sn-accent);border-color:var(--sn-accent-line);background:var(--sn-accent-tint)}

/* ---------- боковое меню ---------- */
.admin-app .tabs{
  position:fixed;
  left:0;
  top:var(--sn-top);
  bottom:0;
  width:var(--sn-sidebar);
  z-index:220;
  margin:0;
  padding:20px 14px 24px;
  box-sizing:border-box;
  display:flex;
  flex-direction:column;
  gap:3px;
  background:#12171f;
  border-right:1px solid var(--sn-border);
  overflow:hidden;
}
.admin-app .tabs>.admin-drawer-scroll{
  display:flex;
  flex-direction:column;
  gap:3px;
  min-height:0;
  overflow-y:auto;
  overflow-x:hidden;
  padding-bottom:18px;
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
}
.admin-app .nav-section-label{
  font-family:var(--display);
  font-weight:600;
  font-size:var(--t-xs);
  letter-spacing:.15em;
  color:var(--sn-muted-soft);
  margin:14px 10px 7px;
}
.admin-app .tab{
  display:flex;
  align-items:center;
  gap:11px;
  width:100%;
  min-height:43px;
  margin:0;
  padding:9px 12px;
  box-sizing:border-box;
  border:1px solid transparent;
  border-radius:10px;
  background:transparent;
  color:#a5adba;
  font-family:var(--display);
  font-weight:500;
  font-size:var(--t-base);
  text-align:left;
}
.admin-app .tab:hover{background:var(--sn-elevated);color:var(--sn-text)}
.admin-app .tab.active{
  background:var(--sn-accent-tint);
  border-color:var(--sn-accent-line);
  color:#fff;
  box-shadow:inset 3px 0 0 var(--sn-accent);
}
.admin-app .nav-ico{display:grid;place-items:center;flex:0 0 19px}
.admin-app .nav-ico svg{width:18px;height:18px}
.admin-app .nav-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.admin-app .mobile-menu{display:none}
.admin-app .mobile-nav-backdrop{display:none}

/* ---------- контентная колонка ---------- */
/* Панели размечены тегом section, поэтому селектор по div[id^="pane"] не
   совпадал ни с одной из двенадцати: отступ не применялся, и содержимое
   каждой панели уезжало под боковое меню. Заголовок и строка доступа
   отодвигались правильно — у них селекторы по id и классу, — из-за чего
   поломка выглядела как «съехало только содержимое». Селектор без тега. */
.admin-app .cab-intro,
.admin-app #adminDashHeading,
.admin-app #adminSubtabs,
.admin-app #dashView>[id^="pane"]{
  margin-left:calc(var(--sn-sidebar) + var(--sn-gutter));
  margin-right:var(--sn-gutter);
  width:auto;
  max-width:none;
}
.admin-app #adminDashHeading{
  display:flex;
  align-items:flex-end;
  justify-content:space-between;
  gap:18px;
  margin-top:0;
  padding:30px 0 20px;
  border-bottom:1px solid var(--sn-border);
}
.admin-app #adminDashHeading h1{
  margin:0;
  font-family:var(--display);
  font-size:2rem;
  line-height:1.05;
  font-weight:800;
  color:var(--sn-text);
}
.admin-app #adminDashHeading p{margin:7px 0 0;color:var(--sn-muted);font-size:var(--t-md);line-height:1.5;max-width:70ch}
/* Строка про уровень доступа стояла без правила размера и наследовала
   базовый шрифт: она выходила крупнее подзаголовка раздела, рядом с
   которым стоит, и читалась важнее его. Это служебная подпись — и
   выглядеть должна как подпись. */
/* margin-top, а не сокращённое margin: сокращённая запись обнуляет и левый
   отступ, которым строка отодвигается из-под закреплённого меню, — она
   уезжала под него, и от фразы оставался хвост «, управление доступами». */
.admin-app .cab-intro{margin-top:6px;color:var(--sn-muted);font-size:var(--t-md);line-height:1.55;max-width:70ch}
.admin-app .role-chip{
  background:#151b23;
  border:1px solid var(--sn-border);
  color:#b0b8c5;
  border-radius:999px;
  padding:6px 11px;
  font-family:var(--mono);
  font-size:.67rem;
  white-space:nowrap;
}

/* ---------- компоненты ---------- */
.admin-app .panel{
  background:linear-gradient(180deg,#1a2028,#171c23);
  border:1px solid var(--sn-border);
  border-radius:var(--sn-radius);
  /* Тень на каждой панели уравнивала их все в весе: пятнадцать одинаково
     приподнятых прямоугольников читаются как пятнадцать равнозначных
     вещей. Иерархию держат отступы и заголовки, граница обозначает край. */
  padding:22px;
  color:var(--sn-text);
}
/* Заголовок панели и приписка рядом с ним были отдельными элементами, а
   отступ между ними держала рамка .pill. Рамку сняли — и счётчик прилип к
   слову: «Итоги обучения3 мероприятия». Расстояние теперь задаёт раскладка. */
.admin-app .panel-h{margin-bottom:16px;display:flex;flex-wrap:wrap;
  align-items:center;gap:8px 12px}
/* Кнопки в шапке панели держатся вместе. Раньше каждая переносилась сама
   по себе: на узком экране «Новый тест» оставался прижатым вправо, а
   «Экспорт» уходил на следующую строку к левому краю — между ними
   получалась дыра во всю ширину. Теперь на перенос уходит вся группа
   целиком и встаёт по левому краю под заголовком. */
.admin-app .panel-h>.btn{margin-left:0}
.admin-app .panel-h>.btn:first-of-type{margin-left:auto}
@media(max-width:720px){
  .admin-app .panel-h>.btn:first-of-type{margin-left:0}
  .admin-app .panel-h>.pill{flex:1 0 100%}
  .admin-app .panel-h>.btn{flex:1 1 auto}
}
/* Заголовок раздела. Из base.css .pill приходит моноширинным капсом с
   разрядкой, а админка ужимала его до 10 пикселей — то есть название
   каждого блока было десятипиксельной кириллицей вразрядку в рамке.
   Здесь это заголовок: обычный регистр, пропорциональный шрифт, размер
   основного текста. Рамка не нужна — вес и место в панели и так говорят,
   что это заголовок. */
.admin-app .pill{
  background:transparent;
  border:0;
  padding:0;
  color:var(--sn-text);
  font-family:var(--display);
  font-weight:700;
  font-size:var(--t-base);
  line-height:1.3;
  letter-spacing:-.005em;
  text-transform:none;
}
.admin-app .muted-note,
.admin-app .item-s,
.admin-app .gen,
.admin-app .exp-l,
.admin-app .psep{color:var(--sn-muted)}
/* Кегль поясняющим подписям не задавал никто: они брали браузерные 16px и
   рисовались вровень с заголовками. Моноширинный шрифт при равном кегле
   выглядит крупнее пропорционального, поэтому в админке 16px моно читаются
   как «гигантский шрифт» — отсюда сплошная серая стена в «Кампаниях» и
   «Аналитике». Размеры приведены к 12–13px, как у остального текста. */
/* Моноширинный в админке стоял на всём подряд: примечания, подписи,
   мета-строки, названия в списках. Кириллица в моноширинном при мелком
   кегле читается заметно хуже пропорциональной, а страница целиком в нём
   выглядит распечаткой лога. Оставляем моноширинный там, где он работает:
   коды, ссылки, числовые колонки. Всё остальное — обычным шрифтом. */
.admin-app .muted-note,
.admin-app .item-s,
.admin-app .gen,
.admin-app .exp-l,
.admin-app .psep,
.admin-app .kpi-lbl,
.admin-app .mkpi__l,
.admin-app .row-l,
.admin-app .bar-l,
.admin-app .cmp__meta,
.admin-app .trn__meta,
.admin-app .trn__note,
.admin-app .cmp-hint{font-family:var(--display)}
.admin-app .muted-note{font-size:var(--t-md);line-height:1.6}
.admin-app .gen,
.admin-app .exp-l,
.admin-app .psep{font-size:var(--t-sm)}
/* .weakest приходит из base.css сайта: там свой масштаб, здесь нужен наш. */
.admin-app .weakest{font-size:var(--t-md)}
.admin-app .weakest b{font-size:var(--t-md)}
/* Пустое состояние: отступов не задавал ни один прежний слой, поэтому текст
   лип к краям и выглядел как системная полоса во всю ширину. */
.admin-app .empty{
  background:var(--sn-surface);
  border:1px dashed var(--sn-border);
  color:var(--sn-muted);
  border-radius:var(--sn-radius);
  padding:22px 24px;
  font-family:var(--display);
  font-size:var(--t-md);
  line-height:1.65;
}

/* Требуется действие — это не пустота, и выглядеть должно иначе. */
.admin-app .notice{
  display:flex;
  align-items:flex-start;
  gap:13px;
  padding:18px 20px;
  border:1px solid var(--sn-accent-line);
  border-radius:var(--sn-radius);
  background:rgba(255,77,45,.06);
  color:var(--sn-text);
  font-family:var(--display);
  font-size:var(--t-md);
  line-height:1.65;
}
.admin-app .notice:before{
  content:"";
  flex:0 0 18px;
  height:18px;
  margin-top:1px;
  background:var(--sn-accent);
  -webkit-mask:center/contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M12 3.5 5.5 6v5.4c0 4 2.7 7.5 6.5 8.6 3.8-1.1 6.5-4.6 6.5-8.6V6L12 3.5ZM12 9v4M12 16h.01'/%3E%3C/svg%3E");
  mask:center/contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M12 3.5 5.5 6v5.4c0 4 2.7 7.5 6.5 8.6 3.8-1.1 6.5-4.6 6.5-8.6V6L12 3.5ZM12 9v4M12 16h.01'/%3E%3C/svg%3E");
}

.admin-app .kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
/* Три плитки, а не четыре: «Обзор» показывает четыре показателя, «Аналитика» — три. */
.admin-app .kpis-3{grid-template-columns:repeat(3,minmax(0,1fr));max-width:820px}
/* Между сводкой и журналом нужен воздух, иначе они читаются одним куском. */
.admin-app .att-panel-h{margin-top:22px}
.admin-app .kpi{
  background:linear-gradient(145deg,#1b212a,#151a21);
  border:1px solid var(--sn-border);
  border-radius:13px;
  padding:18px;
  min-width:0;
}
.admin-app .kpi-num{font-family:var(--display);font-weight:700;color:var(--sn-text);font-size:var(--t-2xl);line-height:1.1}
/* Подпись у числа: «балл» вплотную к «-1» читалось как одно слово «-1балл».
   У «/100» разделителем работала сама косая черта, поэтому проблему видно
   было только там, где подпись начинается с буквы. */
.admin-app .kpi-num small{font-size:.5em;margin-left:.45em;color:var(--sn-muted)}
.admin-app .kpi-lbl{color:var(--sn-muted);font-size:var(--t-sm);line-height:1.45}
/* Состояние числа. Цвет достаётся только той плитке, которую человек должен
   заметить, — остальные остаются нейтральными, иначе не выделяется ничего.
   Слева от плитки полоса того же тона: на телефоне, где плитки идут в
   столбик, она видна раньше, чем строка под подписью. */
.admin-app .kpi{position:relative;overflow:hidden}
.admin-app .kpi--ok .kpi-num{color:var(--sn-ok)}
.admin-app .kpi--wait .kpi-num{color:var(--sn-wait)}
/* Тёмный красный не проходит по контрасту как цвет крупных букв на плитке,
   поэтому число остаётся белым, а красным идут полоса и точка состояния. */
.admin-app .kpi--bad .kpi-num{color:var(--sn-text)}
.admin-app .kpi--ok::before,.admin-app .kpi--wait::before,.admin-app .kpi--bad::before{
  content:"";position:absolute;left:0;top:0;bottom:0;width:3px}
.admin-app .kpi--ok::before{background:var(--sn-ok)}
.admin-app .kpi--wait::before{background:var(--sn-wait)}
.admin-app .kpi--bad::before{background:var(--sn-bad)}
.admin-app .kpi-st{
  display:flex;align-items:center;gap:7px;margin-top:9px;
  font-family:var(--sn-mono);font-size:var(--t-xs);font-weight:700}
.admin-app .kpi-st::before{content:"";width:7px;height:7px;border-radius:50%;flex:none;background:currentColor}
.admin-app .kpi-st--ok{color:var(--sn-ok)}
.admin-app .kpi-st--wait{color:var(--sn-wait)}
/* Красный как цвет текста тут проходит: 12px bold на плитке — мелкая метка,
   и рядом с ним стоит слово, а не только цвет. Осветлён до читаемого. */
.admin-app .kpi-st--bad{color:#e4614f}

/* Диаграммы — это SVG с viewBox: кегль внутри задан в его единицах и на
   экране растягивается вместе с картинкой. Своего размера у подписей не было,
   они брали браузерные 16px и после растяжения рисовались 25-ю — отсюда
   «гигантские шрифты» в обзоре. Размеры подобраны так, чтобы на экране
   получалось 12–14px, как у остального текста админки. */
.admin-app .chart{background:#141a21;border:1px solid var(--sn-border);border-radius:12px;padding:12px;box-sizing:border-box;width:100%;height:auto;max-width:760px}
.admin-app .ch-v{font-family:var(--mono);font-size:9px;font-weight:700;fill:var(--sn-text)}
.admin-app .ch-fill{fill:var(--sn-accent)}
/* Базовая линия сплошная и на тон от поверхности: пунктир читался бы как
   порог или прогноз, а это просто ось. */
.admin-app .ch-base{stroke:var(--sn-border);stroke-width:1}
.admin-app .ch-bar{cursor:default}
.admin-app .ch-x{font-family:var(--mono);font-size:8px;fill:var(--sn-muted)}
.admin-app .donut-c{font-family:var(--display);font-size:19px;font-weight:800;fill:var(--sn-text)}
/* ---------- лента последних прохождений ----------
   Балл слева тем же блоком, что и во вкладке «Прохождения»: одна и та же
   цифра должна выглядеть одинаково в обоих местах, иначе их читают как
   разные. */
/* Отметка «когда в последний раз кто-то проходил» в шапке ленты.
   Отвечает на вопрос, с которым сюда заходят чаще всего, не заставляя
   разворачивать график активности и вычитать даты в уме. */
.admin-app .feed-last{
  margin-left:10px;padding:4px 10px;border-radius:var(--sn-r-pill,999px);
  font-size:12px;line-height:1.2;white-space:nowrap;
  background:rgba(101,211,145,.12);color:var(--sn-ok);
}
.admin-app .feed-last--quiet{background:rgba(228,97,79,.12);color:#e4614f}
@media (max-width:520px){ .admin-app .feed-last{margin-left:0;order:3} }

.admin-app .feed{display:flex;flex-direction:column;gap:8px}
.admin-app .feed-i{display:flex;align-items:center;gap:12px;
  padding:8px 0;border-bottom:1px solid var(--sn-border)}
.admin-app .feed-i:last-child{border-bottom:0}
.admin-app .feed-main{flex:1 1 auto;min-width:0}
.admin-app .feed-t{color:var(--sn-text);font-size:var(--t-md);line-height:1.35;overflow-wrap:anywhere}
.admin-app .feed-m{color:var(--sn-muted-soft);font-size:var(--t-xs);margin-top:2px;overflow-wrap:anywhere}
.admin-app .feed-rep{flex:0 0 auto}

/* ---------- «Что сделать сейчас» ----------
   Список дел, а не плашек тревоги: красный фон на каждой строке превращает
   экран в аварийный щит, и через неделю его перестают читать. Отличает строку
   полоса слева и кнопка, которая ведёт туда, где это чинят. */
.admin-app .alerts{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
.admin-app .alert-i{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:12px 14px;border:1px solid var(--sn-border);border-radius:var(--sn-radius);
  background:var(--sn-surface);box-shadow:inset 3px 0 0 var(--sn-accent,#ff5533)}
.admin-app .alert-t{flex:1 1 260px;min-width:0;color:var(--sn-text);
  font-size:var(--t-md);line-height:1.45;overflow-wrap:anywhere}
.admin-app .alert-go{flex:0 0 auto}

/* ---------- до → после ----------
   Три числа в ряд и стрелка между ними: «было 48, стало 71» читается за один
   взгляд, а «+23» само по себе не говорит, с чего начали. */
.admin-app .pp{display:flex;align-items:center;gap:clamp(10px,3vw,28px);flex-wrap:wrap}
.admin-app .pp-c{min-width:78px}
.admin-app .pp-n{font-family:var(--sn-mono);font-size:var(--t-2xl);line-height:1.1;
  color:var(--sn-text);font-variant-numeric:tabular-nums}
.admin-app .pp-l{font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.05em;
  color:var(--sn-muted-soft);margin-top:2px}
.admin-app .pp-arrow{font-size:var(--t-xl);color:var(--sn-muted-soft)}
.admin-app .pp-d--ok .pp-n{color:#5fd08a}
.admin-app .pp-d--bad .pp-n{color:#ff7a5c}

/* ---------- таблица раздач за период ----------
   Числовые колонки моноширинные и с табличными цифрами: в столбце из
   процентов и баллов разрядность должна совпадать, иначе глаз не сравнивает
   строки, а перечитывает каждую. Обёртка со своей прокруткой: на телефоне
   шесть колонок в ширину экрана не встают, а ломать их в столбик — значит
   потерять само сравнение, ради которого таблицу и смотрят. */
.admin-app .fn-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.admin-app .fn{width:100%;border-collapse:collapse;font-size:var(--t-md)}
.admin-app .fn th{
  text-align:right;padding:8px 10px;white-space:nowrap;
  font-weight:600;font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.04em;
  color:var(--sn-muted-soft);border-bottom:1px solid var(--sn-border)}
.admin-app .fn th:first-child{text-align:left}
.admin-app .fn td{padding:10px;border-bottom:1px solid var(--sn-border);color:var(--sn-text)}
.admin-app .fn tbody tr:last-child td{border-bottom:0}
.admin-app .fn-t{min-width:180px;overflow-wrap:anywhere}
.admin-app .fn-n{text-align:right;white-space:nowrap;
  font-family:var(--sn-mono);font-variant-numeric:tabular-nums}
/* Подсветка — слева полосой, а не заливкой строки: заливка спорит с наведением
   и делает таблицу пёстрой, когда проблемных раздач много. */
.admin-app .fn-row--weak td{background:rgba(255,85,51,.07)}
.admin-app .fn-row--weak td:first-child{box-shadow:inset 3px 0 0 var(--sn-accent,#ff5533)}
/* На узком экране название раздачи остаётся на месте, пока числа уезжают
   вбок: иначе, доскроллив до «доходимости», уже не помнишь, чья это строка,
   а сравнение строк — единственное, ради чего таблицу и открывают.
   Подложка обязательна: без неё уезжающие числа проступают сквозь название. */
@media(max-width:899px){
  .admin-app .fn-t{position:sticky;left:0;z-index:1;background:#191f27}
  .admin-app .fn th:first-child{position:sticky;left:0;z-index:1;background:#191f27}
  .admin-app .fn-row--weak .fn-t{background:
    linear-gradient(rgba(255,85,51,.07),rgba(255,85,51,.07)),#191f27}
}
.admin-app .rows{display:flex;flex-direction:column;gap:10px}
.admin-app .row{display:flex;align-items:center;gap:12px}
/* Колонка подписи занимала 40% ширины панели: на широком экране между словом
   «45+» и началом полосы оставалось четыреста пустых пикселей, и глаз шёл
   через провал. Ограничиваем — остаток забирает полоса. */
.admin-app .row-l{flex:1 1 40%;max-width:260px;min-width:0;color:#e8ebef;font-size:var(--t-md);line-height:1.4;overflow-wrap:anywhere}
.admin-app .row-r{flex:0 0 auto;font-family:var(--sn-mono);font-size:var(--t-sm);white-space:nowrap;font-variant-numeric:tabular-nums}
.admin-app .row-bar{flex:1 1 30%;height:8px;border-radius:999px;overflow:hidden;background:#282f38}
/* Конец полосы, который показывает величину, скруглён; начало прижато к нулю
   дорожкой. Скругление с обеих сторон отрывало бы полосу от начала отсчёта. */
.admin-app .row-bar span{display:block;height:100%;border-radius:0 4px 4px 0;background:var(--sn-accent)}
/* Одна величина в ряду — один цвет. Раскраска каждой строки своим цветом
   дублировала длину полосы тоном и отдавала цвет номеру строки, а не теме:
   после фильтра выжившие перекрашивались. */
.admin-app .row[title]{cursor:help}
/* «Устройства» рисует другой компонент, чем остальные полосы сводки: своя
   ширина подписи, своя дорожка, свои торцы. В одной панели это читается как
   две разные таблицы. Выравниваем внутри админки, не трогая тот же компонент
   на сайте. */
.admin-app .bar-row{grid-template-columns:minmax(0,260px) 1fr auto}
.admin-app .bar-track{background:#282f38}
.admin-app .bar-fill{border-radius:0 4px 4px 0}

/* Кнопки приходят из base.css капсом вразрядку. На сайте это уместно —
   их там единицы; в админке кнопок десятки в одном экране, и капс
   превращает их в сплошной шум, в котором не найти нужную. */
.admin-app .btn{border-radius:10px;text-transform:none;letter-spacing:0;
  font-weight:600;font-size:var(--t-md)}
/* Палец в мелкую кнопку не попадает. Замерено на экране 390px: девять целей
   были по 40px — «Копировать ссылку», «QR-код», «Статистика», «Удалить»,
   «Найти», «Выйти», «Добавить категорию» и кнопка меню. Порог 44 — правило
   из Human Interface Guidelines, и оно не зависит от платформы: это размер
   подушечки пальца, а не размер иконки. Кнопка остаётся зрительно мелкой —
   растёт только область нажатия. */
.admin-app .btn-sm{font-size:var(--t-sm);min-height:44px}
.admin-app .mobile-menu{min-height:44px;min-width:44px}
.admin-app .btn-primary{background:var(--sn-accent);border-color:var(--sn-accent);color:#111}
.admin-app .btn-outline{background:transparent;border-color:var(--sn-border);color:var(--sn-text)}
.admin-app .fld::placeholder{color:#626c79}
.admin-app .fld:focus{border-color:var(--sn-accent);box-shadow:0 0 0 3px rgba(255,77,45,.12)}
.admin-app .pbtn.active{color:#fff;background:rgba(255,77,45,.1);border-color:rgba(255,77,45,.4)}
.admin-app .linkfld{background:#11161d}
.admin-app .made{background:rgba(255,77,45,.06);border-color:rgba(255,77,45,.25)}
.admin-app .mfa-secret{background:#11161d;border-color:var(--sn-border);color:#fff}
/* ---------- выбор тестов для схемы ---------- */
/* Адрес теста вписывали руками, и ошибка в нём никак не проявлялась: кнопка
   на странице схемы просто не появлялась. Список с галочками ошибиться не даёт. */
.admin-app .pick{
  border:1px solid var(--sn-border);
  border-radius:14px;
  padding:14px 16px;
  background:#151a21;
}
.admin-app .pick .muted-note{margin:6px 0 12px}
.admin-app .pick-list{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr));
  gap:8px;
  max-height:260px;
  overflow:auto;
}
.admin-app .pick-item{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  align-items:center;
  gap:4px 10px;
  padding:8px 10px;
  border:1px solid var(--sn-border);
  border-radius:10px;
  cursor:pointer;
  min-width:0;
}
.admin-app .pick-item:hover{border-color:var(--sn-accent-line)}
.admin-app .pick-item input{width:16px;height:16px;accent-color:var(--sn-accent);grid-row:1/3}
.admin-app .pick-t{
  font-family:var(--display);font-weight:600;font-size:.85rem;
  color:var(--sn-text);overflow-wrap:anywhere;
}
.admin-app .pick-s{font-size:var(--t-sm);color:var(--sn-muted-soft);overflow-wrap:anywhere}
/* Набрано десять — остальные гаснут, но чужую галочку за человека не снимаем. */
.admin-app .pick-off{opacity:.45;cursor:not-allowed}
.admin-app .pick-off:hover{border-color:var(--sn-border)}

/* ---------- редактор изображения ---------- */
/* Раскладки у этого окна не было ни в одном файле после сведения стилей —
   остались только два правила с цветами. Без position оно не всплывало над
   страницей, а падало в конец потока: кнопка «Применить» оказывалась на
   1780px, за пределами экрана, и её закрывало боковое меню. Со стороны это
   выглядело так, будто загрузка картинок перестала работать — нажимаешь и
   ничего не происходит.
   z-index здесь тоже был бесполезен: у неспозиционированного элемента он не
   действует вовсе.

   Правило написано на класс, а не на один идентификатор: второе такое окно
   (редактор аудио) наступило на ту же граблю — разметка с классом .imed,
   раскладка только у #imgEditor, и окно снова падало в конец потока. */
.admin-app .imed{
  position:fixed;
  inset:0;
  z-index:1000;
  align-items:center;
  justify-content:center;
  padding:20px;
  box-sizing:border-box;
  background:rgba(8,11,15,.78);
  overflow:auto;
}
/* Осциллограмме нужна ширина: на 560 пикселях ярлычки стоят вплотную и
   выбрать по ней границу нельзя. */
.admin-app #audEditor .imed-box{width:min(760px,100%)}
.admin-app .imed-box{
  width:min(560px,100%);
  max-height:100%;
  display:flex;
  flex-direction:column;
  min-height:0;
  background:var(--sn-surface);
  border:1px solid var(--sn-border);
  border-radius:16px;
  overflow:hidden;
  box-shadow:0 30px 80px -40px rgba(0,0,0,.95);
}
.admin-app .imed-head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:14px 18px;
  border-bottom:1px solid var(--sn-border);
  font-family:var(--display);
  font-weight:600;
  font-size:.92rem;
  color:var(--sn-text);
}
.admin-app .imed-x{
  background:none;border:0;cursor:pointer;
  color:var(--sn-muted);font-size:1rem;line-height:1;
  padding:4px 8px;border-radius:8px;
}
.admin-app .imed-x:hover{color:var(--sn-text)}
.admin-app .imed-x:focus-visible{outline:2px solid var(--sn-accent);outline-offset:2px}
/* Сцена может не поместиться по высоте на невысоком экране — пусть прокрутится
   она, а не уедут кнопки. */
.admin-app .imed-stage{
  position:relative;
  padding:14px 18px;
  min-height:0;
  overflow:auto;
  background:#0c1015;
}
/* Холст рисуется в 640px шириной: на узком экране его надо ужать по месту. */
.admin-app .imed-stage canvas{
  display:block;
  max-width:100%;
  height:auto;
  margin:0 auto;
  border-radius:10px;
  touch-action:none;
  cursor:grab;
}
.admin-app .imed-stage canvas:active{cursor:grabbing}
.admin-app .imed-hint{
  margin-top:8px;
  font-family:var(--mono);
  font-size:.66rem;
  line-height:1.5;
  color:var(--sn-muted-soft);
  text-align:center;
}
.admin-app .imed-ctrls{display:flex;flex-wrap:wrap;gap:14px;padding:12px 18px 0}
/* Предпросмотр рядом с кадром: в самом кадре видно, что попадёт в картинку,
   а здесь — как она ляжет на страницу, в том же размере и с тем же эффектом. */
.admin-app .imed-prev-row{display:flex;align-items:center;gap:14px;padding:12px 18px 0;flex-wrap:wrap}
.admin-app .imed-prev-l{font-size:var(--t-sm);color:var(--sn-muted)}
.admin-app .imed-prev{width:180px;border:1px solid var(--sn-border);border-radius:10px;overflow:hidden;background:#12141a}
.admin-app .imed-prev canvas{width:100%;height:auto;display:block}
.admin-app .imed-l{
  flex:1 1 180px;
  display:flex;align-items:center;gap:10px;min-width:0;
  font-family:var(--mono);font-size:.7rem;color:var(--sn-muted);
}
/* Основа 0: ползунок иначе требует своей ширины и ломает пару в одну строку. */
.admin-app .imed-l input[type=range]{flex:1 1 0;min-width:0;accent-color:var(--sn-accent)}
.admin-app .imed-actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;padding:14px 18px 0}
.admin-app #imedMsg{padding:10px 18px 16px}

/* На узком экране окно занимает экран целиком: половинчатая карточка с
   прокруткой внутри неудобна пальцем. */
@media (max-width:560px){
  .admin-app #imgEditor{padding:12px}
  .admin-app .imed-box{width:100%}
  .admin-app .imed-actions .btn{flex:1 1 140px}
}

/* ---------- формы ---------- */
/* Этого слоя не было ни в одном файле после сведения стилей: у .form, .list,
   .item-main, .chk и .msg не осталось ни одного правила раскладки. Поля текли
   строкой как inline-блоки — разной ширины, многострочные посреди строки, —
   а миниатюры списков рисовались в натуральную величину. Раскладку видно
   только с данными, поэтому проверки на пустой панели этого не показывали. */
.admin-app .form{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));
  gap:12px;
  align-items:start;
  max-width:1080px;
}
.admin-app .form>textarea.fld,
.admin-app .form>.form-row,
.admin-app .form>.cover-edit,
.admin-app .form>.link-row,
.admin-app .form>.chk,
.admin-app .form>.msg,
.admin-app .form>.fld-label,
.admin-app .form>.muted-note,
.admin-app .form>p,
.admin-app .form>#qWrap,
.admin-app .form>#simSteps,
.admin-app .form>#simSlots,
.admin-app .form>.qblock,
.admin-app .form>button{grid-column:1/-1}
/* Кнопка занимает строку сетки, но не растягивается на всю её ширину. */
.admin-app .form>button{justify-self:start}
.admin-app .form>.fld{width:100%}
.admin-app .form-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.admin-app .form-row>.fld{flex:1 1 240px}
/* Одно объявление поля: и цвета, и размеры. Ширину select задаёт самый длинный
   пункт списка, поэтому длинное название организации растягивало его до 693px
   и выносило форму кампаний за край экрана. */
.admin-app .fld{
  min-height:44px;
  padding:10px 13px;
  max-width:100%;
  min-width:0;
  box-sizing:border-box;
  background:#11161d;
  border:1px solid var(--sn-border);
  border-radius:10px;
  color:var(--sn-text);
  font-family:var(--display);
  font-size:var(--t-md);
}
.admin-app textarea.fld{min-height:92px;resize:vertical;line-height:1.55}
/* Подпись поля была 9px вразрядку моноширинным. Это меньше любого порога
   читаемости, а подпись — единственное, что объясняет, что в поле вводят. */
.admin-app .fld-label{display:block;font-family:var(--display);font-weight:500;
  font-size:var(--t-xs);letter-spacing:0;line-height:1.4;color:var(--sn-muted)}
.admin-app .fld-label .opt{color:var(--sn-muted-soft)}
.admin-app .chk{display:inline-flex;align-items:center;gap:9px;color:var(--sn-text);font-size:var(--t-md)}
.admin-app .link-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.admin-app .msg{font-size:var(--t-md);line-height:1.6;color:var(--sn-muted)}
.admin-app .msg:empty{display:none}
/* .msg.is-err ниже задаёт display:flex и по специфичности перебивает
   правило выше: без этой строки пустая плашка ошибки всё равно рисуется. */
.admin-app .msg.is-err:empty{display:none}
/* Отказ формы должен быть виден. Раньше причина отказа писалась тем же
   приглушённым серым, что и «Готово ✓», строкой ниже кнопки: человек жал
   «Сохранить», получал серую строчку под кнопкой и уходил с ощущением, что
   не произошло вообще ничего. */
.admin-app .msg.is-err{color:#ff6b6b;display:flex;align-items:flex-start;gap:8px;
  background:rgba(255,107,107,.1);border:1px solid rgba(255,107,107,.34);
  border-radius:10px;padding:10px 13px;margin-top:10px}
.admin-app .msg.is-err::before{content:"!";flex:none;width:17px;height:17px;border-radius:50%;
  background:#ff6b6b;color:#1a1d24;font-weight:700;font-size:.68rem;display:grid;place-items:center;margin-top:1px}
.admin-app .ok-line{color:#7ad19a}

/* ---------- редактор вопроса ---------- */
/* Блок вопроса собирается скриптом и виден только в форме теста, поэтому его
   раскладка пропала вместе с остальным слоем незаметно: поля вариантов
   налезали на переключатель, а пояснение шло уже другой ширины. */
.admin-app .qblock{display:grid;gap:9px;padding:14px;margin:6px 0;border:1px solid var(--sn-border);border-radius:12px;background:#141a21}
.admin-app .qblock-h{font-family:var(--display);font-weight:600;font-size:.9rem;color:var(--sn-text)}
.admin-app .qblock .fld{width:100%}
.admin-app .opt-row{display:flex;align-items:center;gap:10px}
.admin-app .opt-row .rdo{display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;font-size:var(--t-sm);color:var(--sn-muted)}
.admin-app .opt-row .fld{flex:1 1 auto;min-width:0}
.admin-app .q-preview:empty{display:none}
.admin-app .q-preview img,
.admin-app .q-preview video{max-width:260px;border-radius:10px}

/* ---------- списки ---------- */
.admin-app .list{display:flex;flex-direction:column}
.admin-app .item{display:flex;align-items:center;gap:14px;padding:12px 0;border-bottom:1px solid var(--sn-border)}
.admin-app .item:last-child{border-bottom:0}
/* Основа 0, а не auto: иначе длинный заголовок требует ширины содержимого и
   выдавливает колонку кнопок в столбец даже там, где места хватает. */
.admin-app .item-main{flex:1 1 0;min-width:0}
.admin-app .item-t{font-family:var(--display);font-weight:600;font-size:var(--t-lg);line-height:1.35;overflow-wrap:anywhere}
.admin-app .item-s{font-size:var(--t-sm);line-height:1.55;overflow-wrap:anywhere}

/* Раздел «Страницы»: блок текста сайта с его правкой. Блоков на странице
   много, поэтому каждый отделён рамкой — иначе абзацы сливаются в стену и
   непонятно, где чей текст. */
.admin-app .pg-item{border:1px solid var(--sn-border);border-radius:12px;padding:14px 16px;margin-bottom:12px;display:flex;flex-direction:column;gap:10px}
.admin-app .pg-flag{color:var(--sn-accent)}
.admin-app .pg-orig{font-family:var(--mono);font-size:.72rem;line-height:1.55;color:var(--sn-muted);overflow-wrap:anywhere}
.admin-app .pg-media{max-width:320px}
.admin-app .pg-media img,.admin-app .pg-media video{width:100%;height:auto;display:block;border-radius:10px}
.admin-app .pg-item-msg:empty{display:none}

/* Слоты картинок-рангов у теста: три равных карточки в ряд, на узком экране
   складываются в колонку. */
.admin-app .rank-slots{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:12px}
.admin-app .rank-slot{border:1px solid var(--sn-border);border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:8px}
.admin-app .rank-prev{aspect-ratio:3/4;border-radius:8px;overflow:hidden;background:var(--sn-surface-2,rgba(255,255,255,.04));display:grid;place-items:center}
.admin-app .rank-prev img,.admin-app .rank-prev video{width:100%;height:100%;object-fit:contain;display:block}
.admin-app .rank-slot-t{font-size:var(--t-sm);color:var(--sn-text,inherit)}
@media(max-width:720px){.admin-app .rank-slots{grid-template-columns:1fr}}

/* Предпросмотр страницы. Высоту ограничиваем: страница целиком выше экрана,
   и без рамки список блоков уезжал бы на несколько экранов вниз. */
/* Страница под панелью — рабочая поверхность, а не картинка: в ней и правят.
   Раньше тексты правились списком одинаковых полей с подписями «H2» и «P», а
   увидеть результат можно было только отдельной кнопкой предпросмотра. */
.admin-app .pg-preview{margin-top:14px;max-height:calc(100vh - 260px);min-height:320px;overflow:auto;
  border:1px solid var(--sn-border);border-radius:12px;background:#1c2029}
.admin-app .pg-panel{padding-bottom:10px}
/* Панель кнопок правки: висит над блоком поверх всего. Живёт в админке, а не
   в теневом дереве страницы, — иначе её стили пришлось бы навязывать чужой
   разметке. */
.admin-app .pg-bar{position:fixed;z-index:80;display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:8px;border-radius:12px;border:1px solid var(--sn-border);background:var(--sn-surface);
  box-shadow:0 18px 40px -22px rgba(0,0,0,.95)}
/* Скрытие атрибутом обязано пересиливать display из класса: правило
   [hidden]{display:none} у браузера слабее любого нашего, и панель кнопок
   оставалась на экране после отмены правки. */
.admin-app .pg-bar[hidden]{display:none}
.admin-app .pg-bar__msg{font-size:12px;color:var(--sn-muted);max-width:240px}
.admin-app .pg-bar__msg.is-err{color:#ff8ba6}
@media (max-width:640px){
  /* На телефоне панель не пляшет за блоком, а стоит внизу экрана: иначе она
     перекрывала бы сам текст, который в этот момент правят. */
  .admin-app .pg-bar{left:8px!important;right:8px;top:auto!important;bottom:calc(var(--sn-bottom-nav,64px) + 8px)}
  .admin-app .pg-bar .btn{flex:1 1 40%}
  .admin-app .pg-preview{max-height:none}
}
.admin-app .tag-off{color:var(--sn-accent);font-size:var(--t-sm);font-weight:600}
/* Плакаты и обложки приходят полноразмерными: без рамки строка списка
   растягивалась на высоту картинки. */
/* Подгон кадра в карточке аудитории: слева предпросмотр в пропорциях самой
   карточки на сайте, справа ручки. Пропорции здесь не украшение — если
   показывать квадрат, владелец подгонит не то, что увидит посетитель. */
/* Редактор аудио: дорожка с двумя ярлычками.
   Запись к вопросу приносят целиком — с гудками, «алло, алло» и тишиной в
   конце. Слушать это человеку под обратный отсчёт незачем, а обрезать
   владельцу было нечем: файл уходил в хранилище как есть. */
.admin-app .aud-stage{position:relative;margin:14px 0 10px;border-radius:12px;overflow:hidden;
  background:#0f141b;border:1px solid var(--sn-border);touch-action:none;user-select:none}
.admin-app .aud-stage canvas{display:block;width:100%;height:150px}
/* Отрезаемое затемняем, а не прячем: видно, сколько именно уходит. */
.admin-app .aud-mask{position:absolute;top:0;bottom:0;background:rgba(10,14,20,.72);pointer-events:none}
.admin-app .aud-mask--l{left:0}
.admin-app .aud-mask--r{right:0}
/* Ярлычок ставим по центру своей позиции и не даём выйти за дорожку:
   с отрицательным полем крайний левый наполовину срезался обрезкой блока и
   поймать его мышью было нечем. */
.admin-app .aud-grip{position:absolute;top:0;bottom:0;width:16px;transform:translateX(-50%);
  cursor:ew-resize;display:grid;place-items:center;touch-action:none}
.admin-app .aud-grip::before{content:"";width:4px;height:100%;border-radius:2px;background:var(--sn-accent);
  box-shadow:0 0 0 1px rgba(0,0,0,.5)}
/* Без этой строки ярлычки выреза остаются на дорожке, даже когда выреза
   нет: display у класса сильнее атрибута [hidden]. */
.admin-app .aud-grip[hidden]{display:none}
.admin-app .aud-grip:focus-visible{outline:none}
.admin-app .aud-grip:focus-visible::before{box-shadow:0 0 0 2px #fff}
/* Вырезаемая середина: штриховка, а не просто цвет. Сплошная заливка
   читалась бы как «вот это и останется» — ровно наоборот. */
.admin-app .aud-cut{position:absolute;top:0;bottom:0;pointer-events:none;
  background:repeating-linear-gradient(135deg,rgba(255,77,45,.40) 0 8px,rgba(255,77,45,.12) 8px 16px)}
.admin-app .aud-grip--cut::before{background:#ffcf6b}
.admin-app .aud-playhead{position:absolute;top:0;bottom:0;width:2px;background:#7fdc9a;pointer-events:none}
.admin-app .aud-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.admin-app .aud-times{font-family:var(--sn-mono);font-size:12px;color:var(--sn-muted)}
.admin-app .aud-times b{color:var(--sn-text);font-weight:600}

/* Редактор видео: дорожка времени и лента кусков монтажа.
   Ролик приносят целиком — с разгоном в начале, дублем посередине и рукой у
   камеры в конце. Обрезать его владельцу было нечем: файл уходил в
   хранилище как есть, а склеить два ролика было нельзя вовсе. */
/* В окне видео содержимого больше, чем в окне картинки: под сценой ещё
   дорожка, лента кусков и настройки. Прокручиваться должно всё окно целиком —
   иначе на телефоне сцена сжималась в полоску, а кнопки уезжали за край. */
.admin-app #vidEditor .imed-box{width:min(900px,100%);overflow:auto;padding-bottom:14px}
/* Бегунки дорожки: капсула с насечками, а не волосок на тёмном фоне. Палец
   должен попадать в них без прицеливания, поэтому зона захвата шире самой
   ручки и вынесена в ::before — сама ручка от этого не толстеет. */
.admin-app .ved-strip .aud-grip{width:14px;transform:translateX(-50%);z-index:3}
.admin-app .ved-strip .aud-grip::before{content:"";position:absolute;left:50%;top:0;bottom:0;
  width:44px;transform:translateX(-50%);background:none;box-shadow:none;border-radius:0}
.admin-app .ved-strip .aud-grip::after{content:"";width:14px;height:100%;border-radius:8px;
  background:linear-gradient(180deg,#ff6f4f,var(--sn-accent));
  box-shadow:0 2px 10px -2px rgba(0,0,0,.8), inset 0 0 0 1px rgba(255,255,255,.35)}
.admin-app .ved-strip .aud-grip:focus-visible::after{box-shadow:0 0 0 2px #fff}
/* Подпись времени над ручкой: видно, куда именно поставили рез. */
.admin-app .ved-track{padding-top:18px}
.admin-app .ved-tip{position:absolute;top:0;transform:translateX(-50%);
  padding:2px 6px;border-radius:6px;background:var(--sn-accent);color:#101318;
  font-family:var(--sn-mono);font-size:10px;font-weight:700;pointer-events:none;white-space:nowrap;z-index:4}
/* Подписи концов дорожки: если кадры ещё не отрисовались, полоса не выглядит
   пустой и сломанной. */
.admin-app .ved-ends{display:flex;justify-content:space-between;
  font-family:var(--sn-mono);font-size:10px;color:var(--sn-muted-soft);margin-top:4px}
@media (max-width:640px){
  /* Телефон: кнопки в ряд, настройки в две колонки, подпись над полем.
     Раньше каждая кнопка занимала свою строку во всю ширину, а три селекта с
     подписями сбоку — три строки: до кнопки «Собрать» приходилось листать
     пол-экрана. */
  .admin-app #vidEditor .aud-row{gap:8px}
  .admin-app #vidEditor .aud-row .btn{flex:1 1 0;min-width:0}
  .admin-app #vidEditor .aud-times{flex:1 1 100%;order:3}
  .admin-app #vidEditor .imed-ctrls{display:grid;grid-template-columns:1fr 1fr;gap:10px 12px}
  .admin-app #vidEditor .imed-l{flex-direction:column;align-items:stretch;gap:4px}
  .admin-app #vidEditor .imed-l .fld{max-width:none;width:100%}
  .admin-app #vidEditor .imed-actions{position:sticky;bottom:0;background:var(--sn-surface);
    padding-top:10px;padding-bottom:6px;margin-top:6px;border-top:1px solid var(--sn-border)}
  .admin-app #vidEditor .imed-actions .btn{flex:1 1 40%}
  .admin-app #vidEditor .ved-clip{width:calc(50% - 5px)}
  .admin-app .ved-help-long{display:none}
}
.admin-app #vidEditor .imed-stage{overflow:visible;flex:none}
/* Кадр не должен съедать экран: на невысоком мониторе сцена в полный рост
   выталкивала дорожку и кнопки за нижний край. */
.admin-app #vidEditor .imed-stage canvas{width:auto;max-height:46vh}
.admin-app #vidEditor .ved-track,
.admin-app #vidEditor .aud-row,
.admin-app #vidEditor .ved-clips,
.admin-app #vidEditor .link-row,
.admin-app #vidEditor .muted-note{padding-left:18px;padding-right:18px}
.admin-app .ved-track{position:relative;margin:14px 0 10px}
.admin-app .ved-strip{position:relative;height:64px;border-radius:12px;overflow:hidden;
  background:#0f141b;border:1px solid var(--sn-border);touch-action:none;user-select:none;
  background-image:repeating-linear-gradient(90deg,rgba(255,255,255,.07) 0 1px,transparent 1px 40px)}
/* Лента кусков: каждый кусок — карточка с кадром, длиной и своими ручками.
   Порядок в ленте и есть порядок в готовом ролике, поэтому стрелки стоят на
   самой карточке, а не в отдельном меню. */
/* Кадры на дорожке: по ним и видно, где в ролике что происходит. Пустая
   полоса заставляла резать вслепую — просмотреть, запомнить секунду, попасть
   в неё ярлычком. */
.admin-app .ved-film{position:absolute;inset:0;width:100%;height:100%;display:block;opacity:.85}
.admin-app .ved-clips{display:flex;gap:10px;flex-wrap:wrap;margin:12px 0 10px}
.admin-app .ved-clip{position:relative;display:flex;flex-direction:column;gap:6px;width:132px;padding:8px;
  border:1px solid var(--sn-border);border-radius:12px;background:var(--sn-panel);cursor:pointer;
  text-align:left;color:var(--sn-text)}
.admin-app .ved-clip.is-on{border-color:var(--sn-accent);box-shadow:0 0 0 1px var(--sn-accent) inset}
.admin-app .ved-clip video,.admin-app .ved-clip canvas{width:100%;aspect-ratio:16/9;object-fit:cover;
  border-radius:8px;background:#0f141b;display:block}
.admin-app .ved-clip__t{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.admin-app .ved-clip__s{font-family:var(--sn-mono);font-size:11px;color:var(--sn-muted)}
.admin-app .ved-clip__acts{display:flex;gap:4px}
.admin-app .ved-clip__acts button{flex:1 1 0;min-height:30px;border-radius:8px;border:1px solid var(--sn-border);
  background:transparent;color:var(--sn-muted);font-size:12px;cursor:pointer}
.admin-app .ved-clip__acts button:hover{color:var(--sn-text);border-color:var(--sn-accent)}
.admin-app .ved-clip__acts button[disabled]{opacity:.4;cursor:default}
@media (max-width:640px){
  .admin-app .ved-clip{width:calc(50% - 5px)}
}
/* На касании ручка дорожки должна ловиться пальцем: четыре пикселя ширины
   хватает глазу, но не пальцу. */
@media (hover:none){
  .admin-app .ved-strip .aud-grip{width:44px}
}

.admin-app .sim-fit{display:flex;gap:14px;flex-wrap:wrap;margin-top:12px;align-items:flex-start}
.admin-app .sim-fit__stage{flex:0 0 132px}
.admin-app .sim-fit__box{width:132px;aspect-ratio:4/5;border-radius:12px;overflow:hidden;
  background:#11161d;border:1px solid var(--sn-border);display:grid;place-items:center;padding:6px;text-align:center}
.admin-app .sim-fit__media{width:100%;height:100%;display:block}
.admin-app .sim-fit__side{flex:1 1 220px;min-width:0;display:grid;gap:8px}
.admin-app .sim-fit__row{display:grid;grid-template-columns:110px minmax(0,1fr);gap:10px;align-items:center;
  font-family:var(--sn-mono);font-size:12px;color:var(--sn-muted)}
.admin-app .sim-fit__row input[type=range]{width:100%;accent-color:var(--sn-accent)}
.admin-app .sim-fit__row select{padding:6px 8px}
.admin-app .sim-fit__row b{color:var(--sn-text);font-weight:600}
@media(max-width:640px){
  .admin-app .sim-fit__row{grid-template-columns:1fr;gap:4px}
}

/* ---------- кампании ----------
   Вкладка отвечает на два вопроса: «сделать ссылку на тест» и «как идут уже
   сделанные». Метрики раздачи лежали одной серой строкой мелким моноширинным,
   где число не отличить от подписи, а код кампании — то единственное, что её
   отличает и что диктуют по телефону, — не показывался нигде. */
.admin-app .cmp-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.admin-app .cmp-f{display:grid;gap:6px;min-width:0}
.admin-app .cmp-f>.fld{width:100%;max-width:none}
/* Строка пояснения во всю ширину панели — это 140 знаков: глаз теряет начало
   следующей строки. Держим меру строки в районе 80 знаков. */
.admin-app .cmp-lead,
.admin-app .cmp-hint{max-width:82ch}
.admin-app .cmp-hint{margin-top:14px}
.admin-app .cmp-filter{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr) auto;gap:12px;align-items:end}

.admin-app .cmp{border:1px solid var(--sn-border);border-radius:var(--sn-radius);
  background:var(--sn-surface-2);padding:16px 18px;margin-bottom:12px}
.admin-app .cmp__head{display:flex;gap:18px;justify-content:space-between;align-items:flex-start;flex-wrap:wrap}
.admin-app .cmp__t{font-family:var(--display);font-weight:700;font-size:var(--t-lg);line-height:1.3;overflow-wrap:anywhere}
.admin-app .cmp__meta{font-size:var(--t-sm);color:var(--sn-muted);margin-top:5px}
.admin-app .cmp__sep{margin:0 .5em;color:var(--sn-muted-soft)}
.admin-app .cmp__code-box{text-align:right;flex:0 0 auto}
/* Код набирают с экрана и диктуют вслух: разрядка и моноширинный нужны, чтобы
   не спутать ноль с «О», а единицу с «I». */
.admin-app .cmp-code{font-family:var(--sn-mono);font-weight:700;font-size:1.2rem;
  letter-spacing:.16em;color:var(--sn-text);line-height:1.3}
.admin-app .cmp-code--sm{font-size:.95rem}
.admin-app .mkpi-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:15px}
.admin-app .mkpi{background:#141a21;border:1px solid var(--sn-border);border-radius:11px;padding:11px 13px;min-width:0}
/* Крупное отдельное число — пропорциональными цифрами: tabular-nums даёт
   каждой цифре ширину нуля, и «118» на таком кегле выглядит разреженным.
   Табличные цифры нужны там, где числа стоят столбиком. */
.admin-app .mkpi__v{font-family:var(--display);font-weight:700;font-size:var(--t-xl);line-height:1.1;
  color:var(--sn-text);font-variant-numeric:proportional-nums}
.admin-app .mkpi__v small{font-size:.52em;color:var(--sn-muted);margin-left:.15em}
.admin-app .mkpi__l{font-size:var(--t-sm);line-height:1.4;color:var(--sn-muted);margin-top:6px}
.admin-app .mkpi-meter{display:block;height:5px;border-radius:999px;overflow:hidden;
  margin-top:9px;background:var(--sn-meter-track)}
.admin-app .mkpi-meter>span{display:block;height:100%;background:var(--sn-accent);border-radius:999px}
/* Ссылку читают глазами и выделяют целиком, поэтому моноширинный и
   user-select:all. Одна типографика на список и на карточку «Готово»: в
   карточке класс .cmp-link когда-то остался без правил вовсе и рисовался
   браузерным шрифтом — ровно так же, как .made-row без правил рисовала QR
   и текст в столбик. */
.admin-app .cmp-link,
.admin-app .cmp__link{font-family:var(--sn-mono);font-size:.72rem;color:var(--sn-muted);
  line-height:1.55;overflow-wrap:anywhere;user-select:all}
.admin-app .cmp__link{margin-top:14px}
.admin-app .cmp__acts{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.admin-app .cmp__del:hover,.admin-app .cmp__del:focus-visible{border-color:#8c2f2a;color:#ff8a76}
.admin-app .cmp__drawer:not(:empty){margin-top:15px;padding-top:15px;border-top:1px solid var(--sn-border)}
.admin-app .cmp__qr{display:flex;flex-direction:column;align-items:flex-start;gap:10px}
.admin-app .cmp-cats{margin-top:18px}
.admin-app .cmp-cats>summary{display:flex;align-items:center;gap:10px}
.admin-app .cmp-cats__n:not(:empty){background:#11161d;border:1px solid var(--sn-border);border-radius:999px;
  padding:2px 9px;font-size:.68rem;color:var(--sn-muted)}

/* Карточка «Готово ✓» после создания: QR слева, код и ссылка справа.
   .made-row не имела правил вовсе — только мобильную отмену направления. */
.admin-app .made{margin-top:16px}
.admin-app .made-row{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start}
.admin-app .made-qr{flex:0 0 auto;display:flex;flex-direction:column;align-items:flex-start}
.admin-app .made-info{flex:1 1 280px;min-width:0}
.admin-app .made-info .fld-label{display:block;margin-bottom:5px}

@media(max-width:900px){
  .admin-app .cmp-form,
  .admin-app .cmp-filter{grid-template-columns:1fr}
  .admin-app .mkpi-row{grid-template-columns:repeat(2,minmax(0,1fr))}
  .admin-app .cmp__code-box{text-align:left}
}
/* На телефоне код то вставал рядом с названием, то падал под него — в
   зависимости от длины названия. Ставим его на одно и то же место всегда:
   искать глазами колонку, которая переезжает, тяжелее, чем лишнюю строку. */
@media(max-width:620px){
  .admin-app .cmp__head{flex-direction:column;gap:10px}
}

/* ---------- обучение ----------
   Вкладка шла против сценария: первым экраном сводка и три панели охвата, в
   двух из которых по одной строке, затем развёрнутая форма создания, две
   кнопки выгрузки — и только седьмым блоком сами мероприятия, ради которых
   на вкладку и заходят. */
.admin-app .trn-head{display:flex;gap:16px;justify-content:space-between;align-items:flex-start;flex-wrap:wrap}
.admin-app .trn-head__r{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.admin-app .trn-filter{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:12px;align-items:end;margin-top:14px}
.admin-app .trn-f{display:grid;gap:6px;min-width:0}
.admin-app .trn-f>.fld{width:100%;max-width:none}

.admin-app .trn{border:1px solid var(--sn-border);border-radius:var(--sn-radius);
  background:var(--sn-surface-2);padding:16px 18px;margin-bottom:12px}
.admin-app .trn__head{display:flex;gap:18px;justify-content:space-between;align-items:flex-start;flex-wrap:wrap}
.admin-app .trn__id{min-width:0}
.admin-app .trn__t{font-family:var(--display);font-weight:700;font-size:var(--t-lg);line-height:1.3;overflow-wrap:anywhere}
.admin-app .trn__meta{font-size:var(--t-sm);color:var(--sn-muted);margin-top:5px;line-height:1.5}
.admin-app .trn__sep{margin:0 .5em;color:var(--sn-muted-soft)}
.admin-app .trn__code-box{text-align:right;flex:0 0 auto}
/* Код диктуют классу вслух — моноширинный с разрядкой, как в «Кампаниях». */
.admin-app .trn-code{font-family:var(--sn-mono);font-weight:700;font-size:1.1rem;
  letter-spacing:.16em;color:var(--sn-text);line-height:1.3}
.admin-app .trn__note{font-size:var(--t-sm);color:var(--sn-muted);margin-top:12px;line-height:1.5}
.admin-app .trn__acts{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.admin-app .trn-pp{font-family:var(--display);font-weight:700;font-size:.95rem;color:var(--sn-text);white-space:nowrap}

/* Этап занятия — состояние, а не просто подпись: идёт ли занятие прямо
   сейчас, видно по чипу, а не по чтению слова. Цвет идёт вместе со словом,
   не вместо него. */
.admin-app .trn-st{display:inline-block;margin-top:9px;font-size:var(--t-xs);font-weight:600;
  letter-spacing:.06em;padding:4px 10px;border-radius:999px;border:1px solid var(--sn-border);color:var(--sn-muted)}
.admin-app .trn-st--live{border-color:var(--sn-accent-line);color:#ff8f74;background:var(--sn-accent-tint)}
.admin-app .trn-st--pre,.admin-app .trn-st--post{border-color:#3d5a72;color:#8fc0e0;background:rgba(58,160,255,.10)}
.admin-app .trn-st--done{color:var(--sn-muted-soft)}

.admin-app .trn-new{padding:0;margin-bottom:14px}
.admin-app .trn-new__sum{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:16px 18px;cursor:pointer}
.admin-app .trn-new__body{padding:0 18px 18px}
.admin-app .trn-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:14px 0}
.admin-app .trn-venue{border-top:1px solid var(--sn-border);padding-top:14px;margin-top:4px}
/* Блок нового места прячут атрибутом — правило по классу обязано ему уступить. */
.admin-app .trn-venue[hidden]{display:none}
.admin-app .trn-cov{margin-top:6px}
.admin-app .trn-cov>summary{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.admin-app .trn-cov__n{font-family:var(--sn-mono);font-size:.68rem;color:var(--sn-muted-soft)}
.admin-app .trn-cov__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}
.admin-app .trn-trust{margin-top:14px}

@media(max-width:900px){
  .admin-app .trn-filter,
  .admin-app .trn-form,
  .admin-app .trn-cov__grid{grid-template-columns:1fr}
}
@media(max-width:620px){
  .admin-app .trn__head{flex-direction:column;gap:10px}
  .admin-app .trn__code-box{text-align:left}
}

/* ---------- доступность с клавиатуры ----------
   Ни у одной кнопки, вкладки меню и свёрнутого блока не было своего
   состояния фокуса: браузер рисовал тонкую системную рамку, а на оранжевой
   основной кнопке её не видно вовсе. Идя по Tab, человек не понимал, где
   находится. Кольцо цветом текста читается и на панели, и на акценте. */
.admin-app .btn:focus-visible,
.admin-app .tab:focus-visible,
.admin-app .pbtn:focus-visible,
.admin-app .chip:focus-visible,
.admin-app summary:focus-visible,
.admin-app a:focus-visible{
  outline:2px solid var(--sn-text);
  outline-offset:2px;
  border-radius:10px;
}
.admin-app .tab:focus-visible{outline-offset:-2px}

/* Строка прозы шла во всю ширину панели — до 129 знаков. На такой длине
   глаз теряет начало следующей строки. Держим меру около 72. */
.admin-app .muted-note,
.admin-app .item-s,
.admin-app .msg,
.admin-app .gen{max-width:72ch}
/* Заголовок не должен ронять в последнюю строку одно слово. */
.admin-app .pill,
.admin-app .item-t,
.admin-app .cmp__t,
.admin-app .trn__t{text-wrap:balance}

.admin-app .sl-thumb{flex:0 0 60px;width:60px;height:60px;object-fit:cover;border-radius:10px;background:#11161d;border:1px solid var(--sn-border)}
.admin-app .sl-thumb-lg{flex:0 0 104px;width:104px;height:74px}
.admin-app .sl-thumb-empty{display:grid;place-items:center;padding:4px;font-family:var(--mono);font-size:8px;line-height:1.3;color:var(--sn-muted-soft);text-align:center}
.admin-app .sl-prev{max-width:180px;border-radius:10px}
/* Кнопки строки списка: своя колонка по ширине содержимого, но сжимаемая —
   в «Тестах» их пять, и на 1024 они иначе выносят строку за край экрана. */
.admin-app .camp-side,
.admin-app .test-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end;flex:0 1 auto;min-width:0}
.admin-app .usr-dates{font-family:var(--mono);font-size:.66rem;color:var(--sn-muted-soft)}

/* ---------- обложка теста в форме редактирования ---------- */
/* Обложка правилась отдельной кнопкой в списке, и результат замены был не
   виден: картинка менялась где-то там, а подтверждения не было никакого.
   Здесь она рядом с остальными полями теста, и превью показывает, что
   именно сейчас стоит. */
.admin-app .cover-edit{
  display:flex;
  flex-wrap:wrap;
  gap:18px;
  align-items:flex-start;
  padding:16px;
  border:1px solid var(--sn-border);
  border-radius:14px;
  background:#151a21;
}
.admin-app .cover-prev{
  flex:0 0 260px;
  width:260px;
  aspect-ratio:16/9;
  border-radius:10px;
  overflow:hidden;
  background:var(--sn-bg);
  border:1px solid var(--sn-border);
}
.admin-app .cover-prev img{width:100%;height:100%;object-fit:cover;display:block}
.admin-app .cover-empty{
  width:100%;height:100%;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--mono);font-size:.7rem;color:var(--sn-muted-soft);
}
/* Основа 0: иначе длинное пояснение требует ширины содержимого и выталкивает
   превью на отдельную строку даже там, где места хватает. */
.admin-app .cover-side{flex:1 1 240px;min-width:0}
.admin-app .cover-side .muted-note{margin:6px 0 12px}
/* Полоска этапов загрузки. Неподвижное «Загружаем…» не отличить от зависшего,
   а полоска показывает, что дело движется, и на каком оно шаге. */
.admin-app .prog{
  height:4px;
  border-radius:999px;
  background:var(--sn-border);
  overflow:hidden;
  margin:0 0 6px;
  max-width:320px;
}
.admin-app .prog-bar{
  height:100%;
  background:var(--sn-accent);
  border-radius:inherit;
  transition:width .25s ease;
}
@media (prefers-reduced-motion:reduce){
  .admin-app .prog-bar{transition:none}
}

@media (max-width:560px){
  .admin-app .cover-prev{flex-basis:100%;width:100%}
}

/* ---------- журнал попыток ---------- */
/* Плоский список одинакового веса не читался: балл тонул в заголовке, дата
   повторялась в каждой строке. Здесь три колонки постоянной ширины, поэтому
   баллы и доли верных ответов выстраиваются в вертикали, по которым глаз
   ведёт сам, а даты вынесены в подзаголовки дня. */
/* Строка не должна растягиваться на всю ширину экрана: иначе колонка «верно»
   уезжает к дальнему краю и глазу приходится идти через пустоту. */
.admin-app .att-list{max-width:940px}
.admin-app .att-head{
  display:grid;
  grid-template-columns:2.6rem minmax(0,1fr) 5.5rem;
  gap:16px;
  padding-bottom:8px;
  border-bottom:1px solid var(--sn-border);
  font-family:var(--mono);
  font-size:.6rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--sn-muted-soft);
}
.admin-app .att-head span:first-child{text-align:right}
.admin-app .att-head span:last-child{text-align:right}
/* Идентификатор участника. Анонимный: за ним нет ни имени, ни почты — но
   именно по нему видно, что человек вернулся. В выгрузке он был всегда, на
   экране его не было, и сверить строку с выгрузкой было нечем.
   Показан короткий вид, полный копируется нажатием. */
.admin-app .att-uid{
  justify-self:end;
  min-height:44px;
  padding:0 8px;
  background:transparent;
  border:1px solid transparent;
  border-radius:8px;
  color:var(--sn-muted-soft);
  font-family:var(--sn-mono);
  font-size:var(--t-sm);
  cursor:pointer;
}
.admin-app .att-uid:hover{color:var(--sn-text);border-color:var(--sn-border)}
.admin-app .att-uid.is-copied{color:var(--sn-ok);border-color:var(--sn-ok-line)}
@media(max-width:640px){
  .admin-app .att-head,
  .admin-app .att{grid-template-columns:2.6rem minmax(0,1fr) 4.5rem;gap:10px}
}
.admin-app .att-day{
  margin:16px 0 2px;
  font-family:var(--mono);
  font-size:.63rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--sn-muted-soft);
}
.admin-app .att-head + .att-day{margin-top:10px}
.admin-app .att{
  display:grid;
  grid-template-columns:2.6rem minmax(0,1fr) 5.5rem;
  align-items:center;
  gap:16px;
  padding:9px 0;
  border-bottom:1px solid var(--sn-border);
}
.admin-app .att:last-child{border-bottom:0}
/* Табличные цифры: без них разряды пляшут и колонка перестаёт быть колонкой. */
.admin-app .att-score{
  font-family:var(--display);
  font-weight:800;
  font-size:1.15rem;
  line-height:1;
  text-align:right;
  font-variant-numeric:tabular-nums;
}
.admin-app .att-low{color:#ff6b57}
.admin-app .att-mid{color:#f5b301}
.admin-app .att-high{color:#7ad19a}
.admin-app .att-none{color:var(--sn-muted-soft)}
.admin-app .att-main{min-width:0}
.admin-app .att-t{
  font-family:var(--display);
  font-weight:600;
  font-size:.92rem;
  line-height:1.3;
  color:var(--sn-text);
  overflow-wrap:anywhere;
}
.admin-app .att-m{
  margin-top:2px;
  font-family:var(--mono);
  font-size:.67rem;
  line-height:1.5;
  color:var(--sn-muted);
  font-variant-numeric:tabular-nums;
  overflow-wrap:anywhere;
}
.admin-app .att-sep{color:var(--sn-muted-soft);margin:0 1px}

@media (max-width:560px){
  .admin-app .att{grid-template-columns:2.2rem minmax(0,1fr);gap:11px}
  .admin-app .att-t{font-size:.85rem}
}

/* ---------- журнал действий ---------- */
/* Строка была сплошной: код действия, цель, дата, автор и дамп JSON шли через
   точку одной серой лентой. Здесь три колонки — время, событие, автор, — а
   дата вынесена в подзаголовок дня, как в журнале попыток. */
.admin-app .aud-list{max-width:960px}
.admin-app .aud-head{
  display:grid;
  grid-template-columns:3.2rem minmax(0,1fr) minmax(0,13rem);
  gap:16px;
  padding-bottom:8px;
  border-bottom:1px solid var(--sn-border);
  font-family:var(--mono);
  font-size:.6rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--sn-muted-soft);
}
.admin-app .aud-head + .att-day{margin-top:10px}
.admin-app .aud{
  display:grid;
  grid-template-columns:3.2rem minmax(0,1fr) minmax(0,13rem);
  align-items:baseline;
  gap:16px;
  padding:9px 0;
  border-bottom:1px solid var(--sn-border);
}
.admin-app .aud:last-child{border-bottom:0}
/* Табличные цифры: иначе время не встаёт в колонку. */
.admin-app .aud-time{
  font-family:var(--mono);
  font-size:.72rem;
  color:var(--sn-muted);
  font-variant-numeric:tabular-nums;
  white-space:nowrap;
}
.admin-app .aud-main{min-width:0}
.admin-app .aud-t{
  font-family:var(--display);
  font-weight:600;
  font-size:.92rem;
  line-height:1.3;
  color:var(--sn-text);
  overflow-wrap:anywhere;
}
/* Красим не всё подряд, а только то, что ищут глазами: удаления и отказы. */
.admin-app .aud-danger{color:#ff6b57}
.admin-app .aud-warn{color:#f5b301}
.admin-app .aud-m{
  margin-top:2px;
  font-family:var(--mono);
  font-size:.67rem;
  line-height:1.6;
  color:var(--sn-muted);
  overflow-wrap:anywhere;
}
.admin-app .aud-k{color:var(--sn-muted-soft)}
.admin-app .aud-m .att-sep{margin:0 6px}
.admin-app .aud-id{border-bottom:1px dotted var(--sn-muted-soft);cursor:help}
.admin-app .aud-who{
  font-family:var(--mono);
  font-size:.7rem;
  line-height:1.4;
  color:var(--sn-muted);
  overflow-wrap:anywhere;
}
.admin-app .aud-sys{color:var(--sn-muted-soft)}
.admin-app .aud-role{margin-top:2px;font-size:.63rem;color:var(--sn-muted-soft)}

/* Колонка автора занимает 13rem под длинные адреса. Когда списку остаётся
   меньше ~600px, событию не хватает места и заголовки начинают ломаться —
   тогда автор уходит под событие, оставаясь во второй колонке. */
@media (max-width:900px){
  .admin-app .aud-head,
  .admin-app .aud{grid-template-columns:3.2rem minmax(0,1fr);gap:11px}
  .admin-app .aud-head span:last-child{display:none}
  .admin-app .aud-who{grid-column:2;margin-top:4px}
}

/* ---------- строка периода ---------- */
.admin-app .period{display:flex;flex-wrap:wrap;align-items:center;gap:9px;margin-bottom:16px}
/* Кнопки периода рисовались голым текстом в тонкой рамке: у .pbtn были только
   цвета, а размеров не задавал никто. */
.admin-app .pbtn{
  padding:8px 13px;
  background:#151b22;
  color:var(--sn-muted);
  border:1px solid var(--sn-border);
  border-radius:999px;
  font-family:var(--mono);
  font-size:.72rem;
  line-height:1;
  cursor:pointer;
}
.admin-app .period-range{display:flex;flex-wrap:wrap;align-items:center;gap:9px}
/* Поле даты: у русской локали подпись «дд.мм.гггг» шире английской, и значок
   календаря вставал вплотную к рамке. Ширину задаём с запасом, а значку
   оставляем воздух — иначе он читается как наползающий на край. */
.admin-app .period .fld{min-height:38px;width:auto}
.admin-app .period .pdate{min-width:168px;padding-right:8px}
.admin-app .period .pdate::-webkit-calendar-picker-indicator{margin-left:6px;opacity:.65;cursor:pointer}
.admin-app .period .pdate::-webkit-calendar-picker-indicator:hover{opacity:1}
.admin-app .exp-wrap{display:flex;flex-direction:column;gap:10px;margin:14px 0}
.admin-app .exp{display:flex;flex-wrap:wrap;gap:8px}
.admin-app .stat-acc{border:1px solid var(--sn-border);border-radius:var(--sn-radius);background:var(--sn-surface);margin-top:12px}
.admin-app .stat-acc>summary{padding:15px 18px;cursor:pointer;font-family:var(--display);font-weight:600;font-size:var(--t-base);color:var(--sn-text)}
.admin-app .stat-acc-body{padding:0 18px 18px}

/* Нижняя навигация появляется только на узком экране. */
.admin-app .admin-bottom-nav{display:none}

/* ---------- планшет: сайдбар уже, отступы меньше ---------- */
@media (min-width:721px) and (max-width:860px){
  .admin-app{--sn-sidebar:218px;--sn-top:68px;--sn-gutter:20px}
  .admin-app .topbar{padding:0 16px}
  .admin-app .topbar .brand{display:none}
  .admin-app .kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .admin-app .kpis-3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .admin-app .kpis-3>.kpi:last-child{grid-column:1/-1}
}

/* ---------- мобильный: меню выезжает поверх ---------- */
@media (max-width:720px){
  .admin-app{--sn-sidebar:0px;--sn-top:64px;--sn-gutter:14px}
  .admin-app .admin-wrap{padding:0 14px 32px;box-sizing:border-box}
  .admin-app #dashView{padding-bottom:0}
  .admin-app .topbar{left:0;padding:0 14px}
  .admin-app .topbar .brand{display:flex;max-width:calc(100% - 52px)}
  .admin-app .topbar .wm{font-size:.78rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .admin-app .who .role-badge,
  .admin-app .who #adminEmail,
  .admin-app .who>a,
  .admin-app .who #pwdBtn{display:none}

  .admin-app .mobile-menu{
    display:inline-grid;
    place-items:center;
    width:40px;
    height:40px;
    flex:0 0 40px;
    margin-right:8px;
    padding:0;
    background:#171d25;
    border:1px solid var(--sn-border);
    border-radius:10px;
  }
  .admin-app .mobile-menu span{display:block;width:17px;height:2px;margin:2px 0;background:#f4f6f8;border-radius:2px}

  .admin-app .tabs{
    width:min(320px,88vw);
    height:auto;
    padding:18px 13px 24px;
    transform:translate3d(-110%,0,0);
    transition:transform .22s ease;
    box-shadow:26px 0 80px -45px #000;
  }
  .admin-app .tabs.mobile-open{transform:translate3d(0,0,0)}
  .admin-app .mobile-nav-backdrop{
    position:fixed;
    inset:var(--sn-top) 0 0 0;
    z-index:210;
    display:none;
    background:rgba(0,0,0,.58);
  }
  .admin-app .mobile-nav-backdrop.show{display:block}

  .admin-app .cab-intro,
  .admin-app #adminSubtabs,
  .admin-app #adminDashHeading,
  .admin-app #dashView>[id^="pane"]{margin-left:0;margin-right:0}
  .admin-app #adminDashHeading{display:block;padding:22px 0 16px}
  .admin-app .kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .admin-app .kpis-3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .admin-app .kpis-3>.kpi:last-child{grid-column:1/-1}
}

/* ---------- узкий экран: одна колонка и нижняя навигация ---------- */
@media (max-width:560px){
  .admin-app .topbar .wm{font-size:.72rem}
  .admin-app .cab-intro{display:none}
  .admin-app #adminDashHeading h1{font-size:1.6rem}
  .admin-app #adminDashHeading p{font-size:.68rem;max-width:100%}
  .admin-app .role-chip{display:none}
  .admin-app .kpis{grid-template-columns:1fr;gap:10px}
  .admin-app .kpi{padding:16px}
  .admin-app .kpi-num{font-size:1.8rem}
  .admin-app .panel{padding:16px;border-radius:12px}
  /* Колонка меняет главную ось: базис 240px из строчной раскладки превращается
     в ВЫСОТУ, и каждое поле становится 240 пикселей ростом. Сбрасываем. */
  .admin-app .form-row{flex-direction:column;align-items:stretch}
  .admin-app .form-row>.fld{flex:0 0 auto}
  .admin-app .form-row .fld,
  .admin-app .form-row button,
  .admin-app .link-row button{width:100%;max-width:none}
  .admin-app .item{flex-wrap:wrap}
  .admin-app .camp-side,
  .admin-app .test-actions{width:100%;max-width:none;justify-content:flex-start}
  .admin-app .tabs .tab{min-height:45px}
  .admin-app #dashView{padding-bottom:72px}

  .admin-app .admin-bottom-nav{
    position:fixed;
    left:0;
    right:0;
    bottom:0;
    z-index:260;
    display:grid;
    grid-template-columns:repeat(5,1fr);
    height:64px;
    padding:6px 8px calc(6px + env(safe-area-inset-bottom));
    box-sizing:border-box;
    background:rgba(14,18,23,.97);
    border-top:1px solid var(--sn-border);
    backdrop-filter:blur(18px);
  }
  .admin-app .admin-bottom-nav__item{
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:3px;
    min-width:0;
    border:0;
    border-radius:9px;
    background:transparent;
    color:#7f8896;
    font-family:var(--mono);
    font-size:9px;
  }
  .admin-app .admin-bottom-nav__item.is-active{color:var(--sn-accent);background:rgba(255,77,45,.08)}
  .admin-app .admin-bottom-nav__ico{display:grid;place-items:center;min-height:18px;font-size:13px;line-height:1}
  .admin-app .admin-bottom-nav__ico svg{width:18px;height:18px}
}

@media (prefers-reduced-motion:reduce){
  .admin-app .tabs{transition:none}
}

/* ---------- экран занятия ---------- */
/* Преподавателю во время занятия нужна одна цифра, видимая с задней парты,
   и QR рядом с ней — поэтому экран крупный и делится надвое. */
.admin-app .live-grid{display:grid;grid-template-columns:auto auto;gap:44px;align-items:center;justify-content:center;padding:26px 0}
.admin-app .live-qr{display:flex;flex-direction:column;align-items:center;gap:10px}
.admin-app .live-qr canvas{background:#fff;border-radius:16px;padding:14px}
.admin-app .live-code{font-family:var(--mono);font-weight:700;font-size:2rem;letter-spacing:.3em;text-indent:.3em;color:var(--sn-text)}
.admin-app .live-count{text-align:center}
.admin-app .live-num{font-family:var(--display);font-weight:800;font-size:clamp(64px,12vw,132px);line-height:1;color:var(--sn-accent)}
@media(max-width:720px){
  .admin-app .live-grid{grid-template-columns:1fr;gap:18px}
  .admin-app .live-num{font-size:clamp(56px,22vw,96px)}
}

/* ---------- Симулятор свиданий ---------- */
/* Шаг переписки — тот же блок, что у вопроса теста, но с отметкой опасной
   просьбы: по ней взглядом видно, какие шаги попадут в итоговый разбор, а
   какие останутся обычным разговором. */
/* Шаг переписки выглядит перепиской.

   Прежде это был блок вопроса теста: семь одинаковых полей в столбик. Но в
   тесте пишут вопрос и варианты, а здесь — диалог: её реплика, две его
   возможные фразы и два её ответа на них. В столбике не видно ни того, кто
   говорит, ни того, что развилки ровно две. Поэтому слева её пузырь, справа
   две ветки, под каждой — её ответ именно на этот выбор. */
/* Доступ к ссылке: словами и выбором, а не галочкой.
   Код печатают в QR и рассылают заранее, поэтому «открыта или нет» — это то,
   что владелец должен видеть с первого взгляда на карточку. */
.admin-app .cmp__access{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;margin:12px 0 4px}
.admin-app .cmp__state{align-self:center;padding:5px 12px;border-radius:999px;
  font-family:var(--sn-mono);font-size:11px;border:1px solid var(--sn-border);color:var(--sn-muted)}
.admin-app .cmp__state--on{color:#7fdc9a;border-color:rgba(127,220,154,.45)}
.admin-app .cmp__state--off{color:#ff8ba6;border-color:rgba(255,107,157,.45)}
.admin-app .cmp__state--wait{color:#ffcf6b;border-color:rgba(255,207,107,.45)}
.admin-app .cmp__pick{display:flex;flex-direction:column;gap:4px;min-width:0}
.admin-app .cmp__pick[hidden]{display:none}
.admin-app .cmp__pick .fld{min-width:210px}
.admin-app .accMsg.is-err{color:#ff8ba6}
@media (max-width:560px){
  .admin-app .cmp__access{flex-direction:column;align-items:stretch}
  .admin-app .cmp__state{align-self:flex-start}
  .admin-app .cmp__pick .fld{min-width:0;width:100%}
}

/* Замер «было → стало» в сводке раздачи.

   Голое «+23» не говорит, выросли с 40 до 63 или с 70 до 93, — а разговор с
   заказчиком идёт именно об этом. Поэтому оба балла стоят рядом, и видно,
   на скольких участниках этот замер держится. */
.admin-app .prepost{margin-top:14px;padding:16px 18px;border-radius:14px;
  border:1px solid var(--sn-border);background:var(--sn-panel)}
.admin-app .prepost__h{font-size:12px;color:var(--sn-muted);margin-bottom:12px}
.admin-app .prepost__row{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.admin-app .prepost__cell{text-align:center}
.admin-app .prepost__n{font-family:var(--display);font-weight:800;font-size:34px;line-height:1;color:var(--sn-text)}
.admin-app .prepost__l{font-family:var(--sn-mono);font-size:11px;color:var(--sn-muted);margin-top:4px}
.admin-app .prepost__arrow{font-size:22px;color:var(--sn-muted)}
.admin-app .prepost__delta{margin-left:auto;font-family:var(--display);font-weight:800;font-size:22px;
  padding:6px 14px;border-radius:999px}
.admin-app .prepost__delta.up{color:#7fdc9a;background:rgba(127,220,154,.12)}
.admin-app .prepost__delta.down{color:#ff8ba6;background:rgba(255,107,157,.12)}
@media (max-width:560px){ .admin-app .prepost__delta{margin-left:0;width:100%;text-align:center} }

/* Лента шагов — оглавление сценария. */
.admin-app .sim-rail{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 4px}
.admin-app .sim-railchip{padding:6px 10px;min-height:32px;border-radius:999px;cursor:pointer;
  border:1px solid var(--sn-border);background:transparent;color:var(--sn-muted);
  font-family:var(--sn-mono);font-size:11px}
.admin-app .sim-railchip:hover{color:var(--sn-text);border-color:var(--sn-accent)}
.admin-app .sim-railchip.is-danger{color:#ff8ba6;border-color:rgba(255,107,157,.45)}
@media (hover:none){ .admin-app .sim-railchip{min-height:44px} }
.admin-app .sim-step{display:grid;gap:12px;padding:14px;margin:10px 0;
  border:1px solid var(--sn-border);border-radius:14px;background:#141a21}
.admin-app .sim-step__head{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.admin-app .sim-step__name{font-family:var(--display);font-weight:600;font-size:.9rem;color:var(--sn-text)}
.admin-app .sim-step__day{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--sn-muted)}
.admin-app .sim-step__acts{margin-left:auto}
.admin-app .sim-chat{display:grid;gap:12px}
/* Поля шага занимают свою строку целиком. Раньше это давало правило
   .qblock .fld — вместе с видом вопроса теста, от которого шаг и уходит. Без
   собственного правила поле разбора сжалось до ширины по умолчанию. */
.admin-app .sim-step .fld{width:100%}
.admin-app .sim-step__day .fld{width:130px}
.admin-app .sim-who{font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--sn-muted);margin-bottom:4px}
.admin-app .sim-who--sub{margin-top:8px}
/* Пузыри: её слева, его справа — как в самой переписке. Цвет несёт смысл, но
   не остаётся один: над каждым пузырём написано, кто говорит. */
.admin-app .sim-bubble{border-radius:14px;padding:8px 10px;border:1px solid var(--sn-border)}
.admin-app .sim-bubble--her{background:#1b2128;border-bottom-left-radius:6px}
.admin-app .sim-bubble--me{background:rgba(255,77,45,.10);border-color:rgba(255,77,45,.35);border-bottom-right-radius:6px}
.admin-app .sim-bubble .fld{width:100%;border:0;background:transparent;padding:6px 4px;min-height:0}
.admin-app .sim-bubble .fld:focus{outline:none;box-shadow:none}
.admin-app .sim-bubble:focus-within{border-color:var(--sn-accent)}
.admin-app .sim-bubble textarea.fld{resize:vertical}
.admin-app .sim-turn{max-width:min(620px,100%)}
.admin-app .sim-attach{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px}
.admin-app .sim-attach .fld{max-width:130px}
.admin-app .sim-photo-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:10px;
  font-size:12px;color:var(--sn-muted)}
.admin-app .sim-photo-row .fld{max-width:240px}
.admin-app .sim-hint{margin-top:6px}
/* Две ветки рядом: видно, что выбор ровно один и он двойной. На узком экране
   встают друг под друга — иначе каждая ветка сжимается в колонку по слову. */
.admin-app .sim-fork{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.admin-app .sim-branch{padding:10px;border-radius:12px;border:1px dashed var(--sn-border);background:#11161c}
.admin-app .sim-branch--yes{border-color:rgba(127,220,154,.35)}
.admin-app .sim-branch--no{border-color:rgba(255,207,107,.35)}
.admin-app .sim-branch .sim-bubble--me{margin-left:auto;max-width:96%}
.admin-app .sim-why-box{padding:10px 12px;border-radius:12px;
  border:1px solid rgba(255,107,157,.45);background:rgba(255,46,99,.07)}
@media (max-width:720px){
  .admin-app .sim-fork{grid-template-columns:1fr}
  .admin-app .sim-step__acts{margin-left:0;width:100%}
  .admin-app .sim-step__acts .btn{flex:1 1 0}
}
.admin-app .sim-step.is-danger{border-color:rgba(255,107,157,.5);background:rgba(255,46,99,.06)}
.admin-app .sim-step.is-danger .sim-step__name::after{content:" · опасная просьба";font-family:var(--mono);
  font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;color:#ff8ba6}
.admin-app .media-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.admin-app .media-row .sim-mprev img,.admin-app .media-row .sim-mprev video,
.admin-app .sim-attach .sim-mprev img,.admin-app .sim-attach .sim-mprev video{display:block;border:1px solid var(--sn-border)}
/* Слот под фотографию персонажа: квадратный кадр для лиц, вертикальный для
   карточки. Слоты стоят по два в ряд, чтобы подпись под каждым помещалась в
   одну-две строки, а не в шесть. */
/* Четыре слота в ряд, верхние края выровнены. Превью у всех квадратные —
   вертикальную карточку показываем целиком внутри квадрата (object-fit
   уже contain), иначе первый слот выше остальных и ряд выглядит сломанным. */
.admin-app #simSlots{grid-template-columns:repeat(4,minmax(0,1fr));align-items:start}
.admin-app #simSlots .rank-prev{aspect-ratio:1}
.admin-app #simSlots .rank-slot{justify-content:flex-start}
.admin-app #simSlots .rank-slot .link-row{margin-top:auto}
@media(max-width:1100px){.admin-app #simSlots{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:620px){.admin-app #simSlots{grid-template-columns:1fr}}

/* Статистика симулятора: своя вкладка и своё оформление.
   Раньше это была строка цифр на общем фоне — её взгляд проскакивал. Здесь
   у каждого числа своя карточка с цветом смысла: сколько распознали схему —
   зелёным, сколько опасных просьб выполнили — оранжевым. */
/* Статистика симулятора рисуется теми же кирпичами, что и «Аналитика»:
   .period, .kpis, .charts, .stat-acc, .panel с .rows/.row. Своих правил ей не
   нужно — админка должна выглядеть одинаково, в какую вкладку ни зайди. */

/* Предпросмотр вложения к реплике: видно сам кадр, а не то, что файл есть. */
.admin-app .sim-mprev{display:block;flex-basis:100%}
.admin-app .sim-mprev__file{display:block;width:100%;max-width:260px;max-height:170px;
  object-fit:cover;border-radius:10px;border:1px solid var(--sn-border);background:#11161d}

/* Галерея анкеты: плитки с превью и кнопка добавления последней в ряду.
   Превью здесь важнее подписей — владелец узнаёт фотографию в лицо, а не по
   имени файла, и порядок в ленте видит сразу. */
/* Форма персонажа — грид из колонок по 260px, и без этой строки галерея
   занимала ОДНУ колонку: двенадцать плиток по 37 пикселей, в которых ничего
   не видно. Лента фотографий обязана идти во всю ширину. */
.admin-app .form>.sim-gal-edit,.admin-app .form>#simGalMsg{grid-column:1/-1}
.admin-app .sim-gal-edit{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px;margin-top:10px}
.admin-app .sim-gal-cell{position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;
  border:1px solid var(--sn-border);background:var(--sn-surface-2,rgba(255,255,255,.04))}
.admin-app .sim-gal-cell img,.admin-app .sim-gal-cell video{width:100%;height:100%;object-fit:cover;display:block}
.admin-app .sim-gal-x,.admin-app .sim-gal-mv{position:absolute;top:5px;width:22px;height:22px;
  border:none;border-radius:50%;background:rgba(0,0,0,.62);color:#fff;font-size:.72rem;line-height:1;
  cursor:pointer;display:grid;place-items:center}
.admin-app .sim-gal-x{right:5px}
.admin-app .sim-gal-mv{left:5px}
.admin-app .sim-gal-x:hover{background:#d93025}
.admin-app .sim-gal-mv:hover{background:rgba(0,0,0,.85)}
.admin-app .sim-gal-add{aspect-ratio:1;border:1px dashed var(--sn-border);border-radius:10px;
  background:none;color:inherit;font-family:var(--mono);font-size:.74rem;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px}
.admin-app .sim-gal-add small{opacity:.6;font-size:.68rem}
.admin-app .sim-gal-add:hover{border-color:var(--sn-accent,#ff5533)}
.admin-app .sim-gal-full{grid-column:1/-1}
@media(max-width:720px){.admin-app .sim-gal-edit{grid-template-columns:repeat(3,minmax(0,1fr))}}

/* Дерево назначений для кнопки слайда.
   Владелец не помнит адресов вида test.html?test=phishing-basics и не обязан:
   он помнит «тест про фишинг». Поэтому вместо поля для ссылки — дерево из
   того, что на сайте уже есть.

   Ветви нарисованы линиями, а не отступом: по отступу глаз восстанавливает
   вложенность сам, но медленно, а линия показывает её сразу. Вертикаль ведёт
   вдоль детей, горизонталь отходит к каждому; у последнего ребёнка вертикаль
   обрывается — иначе линия уходит в пустоту и обещает продолжение. */
.admin-app .sl-cta{display:grid;gap:10px;margin-top:10px}
.admin-app .sl-cta__pick{display:flex;gap:10px;align-items:center}
.admin-app .sl-cta__pick .fld{flex:1;font-family:var(--mono);font-size:.78rem}
.admin-app .sl-cta__pick .fld:read-only{opacity:.85;cursor:default}
.admin-app .sl-tree{border:1px solid var(--sn-border);border-radius:12px;
  padding:12px 14px;max-height:380px;overflow:auto;font-family:var(--mono)}

.admin-app .sl-root{display:flex;align-items:center;gap:8px;font-size:.8rem;color:var(--sn-text)}
.admin-app .sl-root__dot{width:7px;height:7px;border-radius:50%;background:var(--sn-accent,#ff5533)}

/* Каждый уровень: вертикаль слева, горизонталь к ребёнку. */
.admin-app .sl-tree{--sl-branch:rgba(255,255,255,.3)}
/* Ветви светлее обычных рамок намеренно: рамка отделяет блоки, а эта линия
   несёт смысл — по ней читают вложенность. В цвете рамки (#2a313b на #171c23)
   она есть в разметке, но глазом не видна. */
.admin-app .sl-kids{margin-left:3px;padding-left:17px;position:relative}
.admin-app .sl-kids::before{content:"";position:absolute;left:0;top:0;bottom:0;
  width:1px;background:var(--sl-branch)}
.admin-app .sl-kids>*{position:relative}
.admin-app .sl-kids>*::before{content:"";position:absolute;left:-17px;top:17px;
  width:13px;height:1px;background:var(--sl-branch)}
/* Хвост вертикали после последнего ребёнка закрываем фоном панели. */
.admin-app .sl-kids>*:last-child::after{content:"";position:absolute;left:-17px;top:18px;bottom:0;
  width:1px;background:var(--sn-surface,#171c23)}
.admin-app .sl-kids--top>.sl-node:last-child::after{bottom:-12px}

.admin-app .sl-node>summary{cursor:pointer;list-style:none;display:flex;align-items:baseline;gap:8px;
  padding:8px 6px;font-size:.8rem;border-radius:8px}
.admin-app .sl-node>summary:hover{background:var(--sn-surface-2,rgba(255,255,255,.04))}
.admin-app .sl-node>summary::-webkit-details-marker{display:none}
.admin-app .sl-node__name::before{content:"▸ ";color:var(--sn-muted);display:inline-block;
  transition:transform .18s;transform-origin:center}
.admin-app .sl-node[open]>summary .sl-node__name::before{content:"▾ "}
.admin-app .sl-node__n{font-size:.7rem;color:var(--sn-muted)}

/* Лист — строка во всю ширину: так их можно читать столбиком, а не искать
   глазами среди плиток разной длины. */
.admin-app .sl-leaf{display:flex;align-items:baseline;justify-content:space-between;gap:14px;width:100%;
  text-align:left;cursor:pointer;background:none;color:inherit;border:1px solid transparent;
  border-radius:8px;padding:7px 10px;font-family:inherit;font-size:.78rem;
  transition:background .15s,border-color .15s}
.admin-app .sl-leaf:hover{background:var(--sn-surface-2,rgba(255,255,255,.04));border-color:var(--sn-border)}
.admin-app .sl-leaf__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Когда строка не помещается, отступает адрес, а не название: «Симулятор
   зн…» ничего не говорит, а «simulator.h…» — говорит достаточно. */
.admin-app .sl-leaf__href{flex:0 2 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:.68rem;color:var(--sn-muted-soft,#6b7280);opacity:.8}
.admin-app .sl-leaf.is-on{background:var(--sn-accent,#ff5533);border-color:transparent;color:#fff}
.admin-app .sl-leaf.is-on .sl-leaf__href{color:rgba(255,255,255,.82)}
@media(max-width:720px){
  .admin-app .sl-cta__pick{flex-direction:column;align-items:stretch}
  .admin-app .sl-leaf{flex-direction:column;gap:2px}
  .admin-app .sl-leaf__href{font-size:.64rem}
}

/* ====== Список тестов: карточки ======
   Размеры взяты из макета .design/Testy.dc.html один в один: обложка 132px,
   заголовок 15px/700, мета 13px, метка 13px с иконкой, поле фильтра 44px.
   Прежняя версия была набрана на полступени крупнее, и четыре кнопки .btn-sm
   занимали полкарточки.

   Два цвета меток пришлось поднять относительно макета: на собственной
   плашке зелёный #279b61 давал 3.77, синий #458bd0 — 3.70 при норме 4.5.
   Тон тот же, ink светлее: 4.82 и 4.71. Оранжевый из макета проходит как
   есть — 6.26. */
.admin-app .tfilters{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:18px}
.admin-app .tfilters .fld{
  flex:1 1 190px;min-width:0;margin:0;min-height:44px;padding:10px 13px;
  font-family:var(--display);font-size:14px;border-radius:10px}
.admin-app .tfilters .fld[type="search"]{flex:2 1 280px}
.admin-app .tfilters__n{
  flex:none;font-family:var(--sn-mono);font-size:12px;color:var(--sn-muted-soft);
  white-space:nowrap;font-variant-numeric:tabular-nums}

.admin-app .tgrid{display:grid;gap:14px;grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}

.admin-app .tcard{
  display:flex;flex-direction:column;overflow:hidden;
  background:var(--sn-surface-2);border:1px solid var(--sn-border);border-radius:12px}
/* Черновик приглушён целиком: он ещё не работает, и это видно до того, как
   человек дочитает метки. */
.admin-app .tcard--draft{opacity:.72}
.admin-app .tcard--draft:hover,.admin-app .tcard--draft:focus-within{opacity:1}

.admin-app .tc-cover{
  position:relative;height:132px;flex:none;max-width:100%;overflow:hidden;
  border-radius:11px 11px 0 0;
  background-size:cover;background-position:center;background-color:#232833}
/* Подпись стоит на кадре: на светлой фотографии белые буквы без подложки
   исчезают, поэтому низ кадра затемняется градиентом. */
.admin-app .tc-cover::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(16,19,24,0) 45%,rgba(16,19,24,.78))}
.admin-app .tc-cover--none{display:flex;align-items:center;justify-content:center;
  border-bottom:1px dashed var(--sn-border)}
.admin-app .tc-cover--none::after{content:none}
.admin-app .tc-none{font-family:var(--sn-mono);font-size:12px;color:var(--sn-muted-soft)}
.admin-app .tc-cat{
  position:absolute;left:13px;bottom:11px;z-index:1;
  font-size:12px;font-weight:600;color:#fff;opacity:.92}

.admin-app .tc-body{display:flex;flex-direction:column;gap:8px;padding:13px 14px 14px;flex:1}
/* Заголовок карточки — название теста, а не рубрика: капслок из общего
   правила для h3 превращал «Аста ла виста, детка!» в крик. */
.admin-app .panel .tc-t,.admin-app .tc-t{
  margin:0;font-family:var(--display);font-size:15px;font-weight:700;
  line-height:1.3;letter-spacing:0;text-transform:none;
  color:var(--sn-text);text-wrap:balance}
.admin-app .tc-s{margin:0;font-size:13px;color:var(--sn-muted)}
/* Метки идут сразу за мета-строкой. Раньше на них стоял margin-top:auto —
   он прижимал метки к низу карточки, и у теста с коротким названием
   между текстом и метками зияла дыра: текст выглядел приклеенным к
   верху. К низу теперь прижата строка действий: она есть у всех
   карточек одинаковая, и пустота над ней читается как разделитель. */
.admin-app .tc-marks{display:flex;flex-wrap:wrap;gap:6px;padding-top:2px}

/* Метка: значок и слово. Цвет нигде не идёт один — при печати и дальтонизме
   остаётся и то и другое. */
.admin-app .tmark{
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--display);font-size:12px;font-weight:600;white-space:nowrap;
  padding:4px 9px;border-radius:999px;border:1px solid var(--sn-border);
  color:var(--sn-muted-soft)}
.admin-app .tmark svg{flex:none}
.admin-app .tmark--hard{color:#ff8f74;background:var(--sn-accent-tint);border-color:var(--sn-accent-line)}
.admin-app .tmark--timed,.admin-app .tmark--draft{
  color:#5c9fdd;background:rgba(69,139,208,.13);border-color:rgba(69,139,208,.35)}
.admin-app .tmark--live{color:#33b072;background:rgba(39,155,97,.13);border-color:rgba(39,155,97,.35)}

/* ====== Действия карточки ======
   Сначала здесь стояли четыре .btn-sm — они занимали полкарточки, и
   «Удалить» весило столько же, сколько «Редактировать». Потом я убрал их в
   строку текстовых подписей, и стало хуже: на телефоне это читается как
   мета-информация, а не как то, что можно нажать.

   Теперь кнопки, но по малому размеру из макета (.btn-s: 13px, отступы
   9/14, высота 38px, радиус 10px). Видимая высота 38px, зона нажатия 44px —
   подушкой ::after сверху и снизу: палец попадает по зоне, которой на глаз
   нет, и кнопка при этом не раздувается.

   Вес распределён: главное действие залито акцентом, соседние — обводкой,
   удаление тоже обводкой и красным только под курсором. Необратимое
   действие не должно тянуть руку к себе в покое. */
.admin-app .tc-acts{
  /* Сетка, а не перенос по строке. Раньше это был flex-wrap: кнопки имели
     разную ширину по длине слова, «Удалить» прижималось вправо, и второй
     ряд получался рваным — «Открыть» у левого края, «Удалить» у правого,
     между ними дыра. Две равные колонки ставят все четыре кнопки в один
     блок с общими краями. */
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;
  margin-top:auto;padding-top:12px;border-top:1px solid var(--sn-border)}
.admin-app .tact{
  position:relative;z-index:1;display:flex;align-items:center;justify-content:center;width:100%;
  appearance:none;cursor:pointer;text-decoration:none;white-space:nowrap;
  padding:9px 14px;min-height:38px;border-radius:10px;
  border:1px solid var(--sn-border);background:transparent;
  font-family:var(--display);font-size:13px;font-weight:600;line-height:1.2;
  color:var(--sn-text);
  transition:color .15s,background .15s,border-color .15s}
/* Подушка добирает высоту до 44px, не меняя вид кнопки. */
.admin-app .tact::after{
  content:"";position:absolute;left:0;right:0;top:50%;height:44px;transform:translateY(-50%)}
.admin-app .tact:hover{background:var(--sn-surface);border-color:var(--sn-muted-soft)}
.admin-app .tact:focus-visible{outline:2px solid var(--sn-accent);outline-offset:2px}
/* Главное действие — единственное залитое: остальные читаются как выбор. */
.admin-app .tact--main{
  background:var(--sn-accent);border-color:var(--sn-accent);color:var(--sn-bg);font-weight:700}
.admin-app .tact--main:hover{background:#ff6a4d;border-color:#ff6a4d;color:var(--sn-bg)}
.admin-app .tact--del:hover{
  background:rgba(228,97,79,.12);border-color:rgba(228,97,79,.5);color:#e4614f}


@media(max-width:720px){
  .admin-app .tfilters .fld,.admin-app .tfilters .fld[type="search"]{flex:1 1 100%}
}

.admin-app .panel[hidden]{display:none}

/* ====== Пять разделов вместо пятнадцати ======
   Пятнадцать пунктов в столбик — это не меню, а список: чтобы найти нужное,
   его приходилось прочитывать целиком каждый раз. Разделов теперь пять, по
   вопросу «что я сейчас делаю», а прежние пункты стали вкладками внутри.
   Сами панели не тронуты — меняется только то, как до них добираются. */
.admin-app .nav-section{
  display:flex;align-items:center;gap:12px;width:100%;
  appearance:none;background:none;border:1px solid transparent;
  font-family:var(--display);font-size:15px;font-weight:600;text-align:left;
  color:var(--sn-muted);cursor:pointer}
.admin-app .nav-section[hidden]{display:none}
/* Прежние пятнадцать пунктов остаются в разметке — панели открываются
   именно ими, — но из меню уходят. Правило [hidden]{display:none} у
   браузера самое слабое, а класс .tab задаёт свой display и перебивает
   его: без этой строки спрятанные пункты продолжали рисоваться, и меню
   показывало оба набора разом. */
.admin-app .tabs .tab[hidden]{display:none}

/* Вкладки раздела. Полоса появляется только там, где вкладок больше одной:
   единственная вкладка ничего не сообщает и лишь занимает строку. */
.admin-app .subtabs{
  display:flex;flex-wrap:wrap;gap:6px;
  margin-top:16px;margin-bottom:6px}
.admin-app .subtabs[hidden]{display:none}
.admin-app .subtab{
  appearance:none;background:none;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px solid transparent;border-radius:999px;
  padding:9px 15px;min-height:38px;
  font-family:var(--display);font-size:14px;font-weight:600;line-height:1.2;
  color:var(--sn-muted);transition:color .15s,background .15s,border-color .15s}
.admin-app .subtab[hidden]{display:none}
.admin-app .subtab:hover{color:var(--sn-text);background:var(--sn-surface-2)}
.admin-app .subtab:focus-visible{outline:2px solid var(--sn-accent);outline-offset:2px}
/* Выбранная вкладка залита акцентом: обводки мало — среди семи вкладок
   рамка читается как ещё одна кнопка, а не как «вы здесь». */
.admin-app .subtab.active{
  color:var(--sn-accent);background:var(--sn-accent-tint);border-color:var(--sn-accent-line)}

@media(hover:none),(max-width:720px){
  /* Сначала полоса была горизонтальным скроллером. Это давало сразу две
     беды. Первая: overflow-x:auto вынуждает браузер вычислить и
     overflow-y:auto, контейнер высотой ровно в кнопку начинал резать
     содержимое по вертикали — вкладки выглядели обрезанными пополам.
     Вторая: половина вкладок уезжала за край, и о «Симуляторе» с
     «Ресурсами» нельзя было догадаться — горизонтальную прокрутку на
     телефоне не видно, пока её не нащупаешь.

     Вкладки переносятся по строкам, как на большом экране. Семь штук дают
     три ряда — это меньше ста пикселей и видно всё сразу. */
  .admin-app .subtab{min-height:44px}
}

/* Поле с подписью над ним. Раньше в .form-row лежали голые input'ы, и
   подпись было некуда деть: она встала бы слева от поля и сломала ряд.
   Обёртка-колонка держит подпись и поле вместе и растягивается наравне с
   соседями, поэтому ряд из трёх полей остаётся рядом из трёх. */
.admin-app .fld-col{display:flex;flex-direction:column;gap:6px;flex:1 1 0;min-width:0}
.admin-app .fld-col>.fld{margin:0}
.admin-app .fld-col>.fld-label{margin:0}

/* ---------- уборка хранилища ----------
   Список не должен выглядеть страшно: это не ошибки, а следы работы. Поэтому
   спокойная сетка «что · где · сколько лежит · сколько весит», тяжёлое сверху,
   и только кнопка выделена цветом действия. */
.admin-app .sweep-bar{height:8px;border-radius:999px;background:rgba(255,107,53,.22);overflow:hidden;margin:4px 0 8px}
.admin-app .sweep-bar__fill{height:100%;border-radius:999px;background:var(--sn-accent)}
.admin-app .sweep-lead{margin:12px 0 8px}
.admin-app .sweep-list{max-height:280px;overflow:auto;border:1px solid var(--sn-line);border-radius:12px}
.admin-app .sweep-row{display:grid;grid-template-columns:78px 1fr auto auto;gap:10px;align-items:center;
  padding:7px 12px;border-bottom:1px solid var(--sn-line);font-size:13px}
.admin-app .sweep-row:last-child{border-bottom:0}
.admin-app .sweep-row__k{font-size:11px;text-transform:uppercase;letter-spacing:.04em;opacity:.75}
.admin-app .sweep-row__k--video{color:#ffcf6b;opacity:1}
/* Путь длинный и справа у него самое важное — имя файла: обрезаем слева. */
.admin-app .sweep-row__p{display:flex;min-width:0;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;opacity:.8}
/* Папка говорит, откуда файл, и она короткая — её не режем никогда. Режется
   только хвост имени, и слева: в конце у него расширение, ради которого
   имя и читают. */
.admin-app .sweep-row__f{flex:0 0 auto;opacity:.65}
.admin-app .sweep-row__n{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.admin-app .sweep-row__a{opacity:.6;font-size:12px;white-space:nowrap}
.admin-app .sweep-row__s{font-variant-numeric:tabular-nums;white-space:nowrap;min-width:64px;text-align:right}
.admin-app .sweep-row--more{grid-template-columns:1fr;opacity:.6;justify-items:center}
.admin-app .sweep-auto{display:flex;align-items:center;gap:8px;margin:16px 0 4px;cursor:pointer}
@media (max-width:640px){
  /* На телефоне четыре колонки не помещаются: вид и возраст уходят под путь. */
  .admin-app .sweep-row{grid-template-columns:1fr auto;grid-template-areas:"path size" "kind age";row-gap:2px}
  .admin-app .sweep-row__p{grid-area:path}
  .admin-app .sweep-row__s{grid-area:size}
  .admin-app .sweep-row__k{grid-area:kind}
  .admin-app .sweep-row__a{grid-area:age;text-align:right}
  .admin-app .sweep-row--more{grid-template-columns:1fr;grid-template-areas:none}
}

/* Состояние доступа к сайту — рядом с названием карточки, чтобы было видно
   без чтения текста: закрытый сайт легко забыть открыть обратно. */
.admin-app .quiet-state{margin-left:auto;padding:5px 12px;border-radius:999px;
  border:1px solid var(--sn-line);font-size:12px;letter-spacing:.02em}
.admin-app .quiet-state--on{color:#7fdc9a;border-color:rgba(127,220,154,.45)}
.admin-app .quiet-state--off{color:#ffcf6b;border-color:rgba(255,207,107,.45)}

/* Предпросмотр фона заставки: показываем его в той же вертикальной пропорции,
   в какой он ляжет на телефон, — иначе широкий кадр выглядит здесь удачно, а
   на экране у ребёнка обрезается по краям. */
/* Два слота фона рядом: вертикальный кадр телефона и горизонтальный кадр
   компьютера. Видно сразу оба — и то, что второй не загружен. */
.admin-app .intro-slots{display:flex;flex-wrap:wrap;gap:18px;margin-top:10px}
.admin-app .intro-slot{flex:1 1 220px;min-width:0}
.admin-app .intro-prev-desktop{max-width:260px;aspect-ratio:16/9}
.admin-app .intro-prev{margin-top:12px;max-width:150px;aspect-ratio:9/16;border-radius:10px;
  overflow:hidden;border:1px solid var(--sn-line);background:var(--sn-bg-2);
  display:grid;place-items:center;text-align:center;padding:8px}
.admin-app .intro-prev img,.admin-app .intro-prev video{width:100%;height:100%;object-fit:cover;display:block}

/* ---------- квест «Детектив: Кто мошенник?» ---------- */
/* Шесть показателей, а не четыре: плитки квеста мельче обычных KPI, потому
   что на обзоре они идут вторым рядом под платформенными цифрами и не должны
   спорить с ними за внимание. */
.admin-app .q-kpis{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:12px;margin-top:4px}
.admin-app .q-kpi{
  background:linear-gradient(145deg,#1b212a,#151a21);
  border:1px solid var(--sn-border);
  border-radius:13px;
  padding:14px 16px;
}
.admin-app .q-kpi b{display:block;font-size:26px;line-height:1.1;margin-top:4px}
@media (max-width:900px){ .admin-app .q-kpis{grid-template-columns:repeat(3,minmax(0,1fr))} }
@media (max-width:520px){ .admin-app .q-kpis{grid-template-columns:repeat(2,minmax(0,1fr))} }

/* ---------- правка точки квеста ---------- */
/* base.css задаёт .form input{width:100%;height:54px} — правило ловит и
   галочки с кружками, и вместо отметки получается синий прямоугольник во всю
   строку. Отметкам нужен свой размер. */
.admin-app .form .chk input[type="checkbox"],
.admin-app .form .q-opt input[type="radio"]{
  width:18px;height:18px;flex:none;margin:0;padding:0;
  accent-color:var(--sn-accent);
}
.admin-app .form>.q-quest{grid-column:1/-1;display:grid;gap:10px}
/* Кнопка занимает строку сетки, но не растягивается на всю её ширину —
   то же правило, что у кнопок самой формы. */
.admin-app .q-quest>button{justify-self:start}
.admin-app .q-opts{display:grid;gap:8px}
/* Строка варианта: кружок «правильный», текст, крестик. Текст тянется, края
   не прыгают — иначе список вариантов читается как лесенка. */
.admin-app .q-opt{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:center}
.admin-app .q-opt-pick{display:inline-flex;align-items:center;gap:8px;cursor:pointer}
.admin-app .q-opt-key{
  display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;border-radius:8px;
  background:var(--sn-panel-2,#1b212a);border:1px solid var(--sn-border);
  font-family:var(--mono,monospace);font-size:13px;color:var(--sn-muted);
}
/* Отмеченный вариант видно сразу, не вглядываясь в кружок. */
.admin-app .q-opt-pick input:checked+.q-opt-key{
  background:var(--sn-accent);border-color:var(--sn-accent);color:#fff;
}
.admin-app .q-opt-del{padding:0 12px;min-width:44px}
/* Уточнение рядом с подписью поля — не вторая подпись, а примечание к ней. */
.admin-app .fld-label .muted-note{font-weight:400;margin-left:6px;opacity:.75}
.admin-app .q-attempts{max-width:220px}
@media (max-width:520px){
  .admin-app .q-opt{grid-template-columns:auto 1fr auto;gap:8px}
  .admin-app .q-opt-del{min-width:38px;padding:0 8px}
}

/* Слот медиа точки: превью слева, кнопки справа — как у обложки теста. */
.admin-app .form>.q-media{grid-column:1/-1;display:grid;gap:8px}
.admin-app .q-media-body{display:grid;grid-template-columns:minmax(0,220px) minmax(0,1fr);gap:14px;align-items:start}
.admin-app .q-media-prev{
  aspect-ratio:16/9;border:1px solid var(--sn-border);border-radius:12px;overflow:hidden;
  background:var(--sn-panel-2,#1b212a);display:flex;align-items:center;justify-content:center;
}
.admin-app .q-media-prev img,.admin-app .q-media-prev video{width:100%;height:100%;object-fit:cover;display:block}
.admin-app .q-media-empty{color:var(--sn-muted);font-size:var(--t-sm,13px);text-align:center;padding:0 10px}
.admin-app .q-media-side{display:grid;gap:8px}
/* Попытки и секунды — два коротких числа рядом, а не два поля во всю строку. */
.admin-app .q-nums{display:grid;grid-template-columns:repeat(2,minmax(0,200px));gap:12px}
@media (max-width:640px){
  .admin-app .q-media-body{grid-template-columns:1fr}
  .admin-app .q-nums{grid-template-columns:1fr 1fr}
}

/* Карточка мероприятия с обложкой: миниатюра, название, кнопки. */
.admin-app .list-row.q-ev{display:grid;grid-template-columns:auto 1fr auto;gap:14px;align-items:center}
.admin-app .q-ev-cover{width:96px;aspect-ratio:16/9;object-fit:cover;border-radius:10px;border:1px solid var(--sn-border);display:block}
.admin-app .q-ev-cover--empty{
  display:flex;align-items:center;justify-content:center;text-align:center;
  background:var(--sn-panel-2,#1b212a);color:var(--sn-muted);font-size:11px;padding:0 6px;
}
/* Откуда берётся вопрос: два взаимоисключающих ответа, а не список настроек. */
.admin-app .q-src{display:flex;flex-wrap:wrap;gap:10px}
.admin-app .q-src-i{
  display:inline-flex;align-items:center;gap:9px;cursor:pointer;
  padding:10px 14px;border:1px solid var(--sn-border);border-radius:12px;
  background:var(--sn-panel-2,#1b212a);
}
.admin-app .q-src-i:has(input:checked){border-color:var(--sn-accent);background:rgba(255,85,51,.10)}
.admin-app .form .q-src-i input[type="radio"]{width:18px;height:18px;flex:none;margin:0;padding:0;accent-color:var(--sn-accent)}
@media (max-width:520px){ .admin-app .list-row.q-ev{grid-template-columns:auto 1fr} .admin-app .list-row.q-ev .link-row{grid-column:1/-1} }

/* Лампа служб в верхней строке.

   Её смотрят мельком и с любой вкладки, поэтому она компактная: точка плюс
   два слова. На узком экране слова убираются, точка остаётся — цвет и есть
   то, за чем смотрят, а подпись доступна через title и в поповере.

   Поповер привязан к верхней строке, а не к кнопке: шапка на мобильном
   прижата к краю, и всплывающее окно у кнопки уехало бы за экран. */
/* [hidden] браузер объявляет на своём, самом слабом уровне, и любое правило
   с классом его перебивает. Без этой строки лампа оставалась бы на экране у
   учителя, хотя разметка помечает её скрытой. Ошибка в этом проекте уже
   случалась трижды, поэтому её и сторожит проверка в CI. */
.admin-app .sv-top[hidden]{display:none}
.admin-app .sv-top{
  display:inline-flex;align-items:center;gap:7px;
  padding:5px 10px;border-radius:var(--sn-r-pill,999px);
  background:var(--sn-panel-2,#1b212a);border:1px solid var(--sn-border);
  font:inherit;font-size:12px;color:var(--sn-muted);cursor:pointer;
  transition:border-color .2s ease,background .2s ease;
}
.admin-app .sv-top:hover{border-color:var(--sn-accent,#ff5533)}
.admin-app .sv-top--warn{border-color:rgba(233,178,80,.45)}
.admin-app .sv-top--fail{border-color:rgba(228,97,79,.55);color:#e4614f}
.admin-app .sv-top--ok .sv-lamp{background:var(--sn-ok)}
.admin-app .sv-top--warn .sv-lamp{background:var(--sn-wait)}
.admin-app .sv-top--fail .sv-lamp{background:#e4614f}
/* До первого ответа лампа серая и молчит: зелёная была бы ложью, красная —
   ложной тревогой. */
.admin-app .sv-top .sv-lamp{background:var(--sn-muted)}
.admin-app .sv-top-w{white-space:nowrap}

.admin-app .sv-pop{
  position:absolute;right:16px;top:100%;z-index:60;
  width:min(520px,calc(100vw - 32px));max-height:70vh;overflow:auto;
  padding:14px;border-radius:14px;
  background:var(--sn-panel,#141a22);border:1px solid var(--sn-border);
  box-shadow:0 18px 48px rgba(0,0,0,.45);
}
.admin-app .sv-pop-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px}
.admin-app .sv-pop-h .btn{margin-left:auto}
@media (max-width:720px){
  .admin-app .sv-top-w{display:none}
  .admin-app .sv-pop{right:8px;width:calc(100vw - 16px)}
}

/* Светофор служб в «Безопасности».

   Лампа — не украшение: цвет здесь единственное, что читается за полсекунды,
   а это единственное, что нужно за минуту до выступления. Поэтому у строки
   есть и лампа, и слово («работает», «не отвечает»): цветом одним полагаться
   нельзя — его не различит каждый десятый мужчина в зале, да и проектор врёт.

   Инструкция лежит в <details>. У красной она раскрыта разметкой: в красный
   день лишнее нажатие — это лишняя секунда. */
.admin-app .sv-state{
  margin-left:10px;padding:4px 10px;border-radius:var(--sn-r-pill,999px);
  font-size:12px;line-height:1.2;white-space:nowrap;
}
.admin-app .sv-state--ok{background:rgba(101,211,145,.12);color:var(--sn-ok)}
.admin-app .sv-state--warn{background:rgba(233,178,80,.14);color:var(--sn-wait)}
.admin-app .sv-state--fail{background:rgba(228,97,79,.14);color:#e4614f}

.admin-app .sv-list{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.admin-app .sv-i{
  padding:12px 14px;border-radius:12px;
  background:var(--sn-panel-2,#1b212a);border:1px solid var(--sn-border);
  border-left-width:3px;
}
.admin-app .sv-i--ok{border-left-color:var(--sn-ok)}
.admin-app .sv-i--warn{border-left-color:var(--sn-wait)}
.admin-app .sv-i--fail{border-left-color:#e4614f}

.admin-app .sv-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.admin-app .sv-lamp{
  width:10px;height:10px;border-radius:50%;flex:none;
  box-shadow:0 0 0 3px rgba(255,255,255,.04);
}
.admin-app .sv-i--ok .sv-lamp{background:var(--sn-ok)}
.admin-app .sv-i--warn .sv-lamp{background:var(--sn-wait)}
.admin-app .sv-i--fail .sv-lamp{background:#e4614f}
.admin-app .sv-t{font-weight:600;font-size:14px}
.admin-app .sv-w{font-size:12px;color:var(--sn-muted);font-family:var(--mono)}
.admin-app .sv-d{color:var(--sn-muted);font-size:13px;margin-left:auto;overflow-wrap:anywhere}

.admin-app .sv-det{margin-top:10px;border-top:1px solid var(--sn-hairline);padding-top:8px}
.admin-app .sv-det>summary{cursor:pointer;font-size:13px;color:var(--sn-muted)}
.admin-app .sv-imp{font-size:13px;line-height:1.5;margin:8px 0 6px;max-width:72ch}
.admin-app .sv-fix{margin:0;padding-left:20px;font-size:13px;line-height:1.6;color:var(--sn-muted)}
.admin-app .sv-fix li{margin:3px 0;max-width:72ch}
@media (max-width:520px){
  .admin-app .sv-d{margin-left:0;flex-basis:100%}
  .admin-app .sv-state{margin-left:0}
}

/* Самопроверка платформы в «Безопасности».
   Находка с замечанием обязана отличаться от находки без него с одного
   взгляда — иначе список читается как ровная стена, и «сломано» теряется
   среди «всё хорошо». */
.admin-app .sc-state{
  margin-left:10px;padding:4px 10px;border-radius:var(--sn-r-pill,999px);
  font-size:12px;line-height:1.2;white-space:nowrap;
}
.admin-app .sc-state--ok{background:rgba(101,211,145,.12);color:var(--sn-ok)}
.admin-app .sc-state--warn{background:rgba(233,178,80,.14);color:var(--sn-wait)}
.admin-app .sc-state--fail{background:rgba(228,97,79,.14);color:#e4614f}

.admin-app .sc-list{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.admin-app .sc-i{
  display:flex;align-items:center;gap:12px;
  padding:12px 14px;border-radius:12px;
  background:var(--sn-panel-2,#1b212a);border:1px solid var(--sn-border);
  border-left-width:3px;
}
.admin-app .sc-i--ok{border-left-color:var(--sn-ok)}
.admin-app .sc-i--warn{border-left-color:var(--sn-wait)}
.admin-app .sc-i--fail{border-left-color:#e4614f}

/* Недельный отчёт. Пересказ — главное на блоке, и набран он крупнее списка:
   его читают целиком, а прошлые недели пробегают глазами. */
.admin-app .rp-last{font-family:var(--mono);font-size:var(--t-sm,13px);color:var(--sn-muted);margin:14px 0 6px}
.admin-app .rp-sum{font-size:1.05rem;line-height:1.55;margin:0 0 10px;max-width:70ch}
.admin-app .rp-ch{margin:0 0 4px}
.admin-app .rp-prev{margin-top:18px;border-top:1px solid var(--sn-hairline);padding-top:14px}
.admin-app .sc-main{flex:1;min-width:0}
.admin-app .sc-t{font-weight:600;font-size:14px}
.admin-app .sc-d{color:var(--sn-muted);font-size:13px;line-height:1.45;margin-top:3px;overflow-wrap:anywhere}
@media (max-width:520px){
  .admin-app .sc-i{flex-wrap:wrap}
  .admin-app .sc-i .btn{width:100%}
  .admin-app .sc-state{margin-left:0;order:3}
}

/* Шапка открытого мероприятия квеста: обложка слева от названия. Раньше на
   этом экране картинок не было вовсе — ни обложки, ни медиа точек, — хотя
   загрузить их можно было с самого начала. */
.admin-app .q-ev-head{display:flex;align-items:center;gap:14px;min-width:0}

/* Надписи квеста: что ребёнок читает на экране. Правятся у точки и у всего
   мероприятия, поэтому блок один и тот же в обеих формах. Подсказка под
   полем говорит, когда эту строку увидят, — без неё непонятно, что правишь. */
.admin-app .q-texts{border-top:1px solid var(--sn-border);padding-top:16px;margin-top:4px}
.admin-app .q-text-row{margin-top:14px}
.admin-app .q-text-row .fld-label{margin-bottom:6px}
.admin-app .q-text-row .muted-note{margin-top:5px;display:block}

/* Проверка участника квеста по коду с его экрана: экран выдачи подарков.
   Открывают его с телефона, стоя у стола, поэтому строки крупные, а исход
   каждой точки читается значком, цветом и словом сразу. */
.admin-app .qc-card{margin-top:14px;border:1px solid var(--sn-border);border-radius:14px;padding:16px}
.admin-app .qc-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:10px}
.admin-app .qc-row{display:flex;gap:12px;padding:11px 0;border-top:1px solid var(--sn-border)}
.admin-app .qc-mark{flex:none;width:26px;height:26px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;font-weight:700}
.admin-app .qc-mark.ok{background:rgba(101,211,145,.14);color:#65d391}
.admin-app .qc-mark.bad{background:rgba(255,138,114,.16);color:#ff8a72}
.admin-app .qc-mark.idle{background:var(--sn-panel-2,#1b212a);color:var(--sn-muted)}
.admin-app .qc-tx{min-width:0}
.admin-app .qc-tx b{display:block}
.admin-app .qc-prize{margin:12px 0 0;padding-top:12px;border-top:1px solid var(--sn-border)}
