/* --- Шрифты ----------------------------------------------------------
   Те же, что в веб-приложении LAER: Plus Jakarta Sans основной, Inter —
   запасной для кириллицы (в Plus Jakarta её нет, браузер добирает
   недостающие буквы из следующего шрифта стека).
   Файлы лежат у нас (public/fonts), Google Fonts CDN не используется:
   немецкая фирма, передача IP на серверы Google — нарушение DSGVO, плюс
   приложение должно работать без интернета до нашего сервера. */
/* optional вместо swap у двух основных начертаний: они забираются заранее
   (preload в index.html) и почти всегда готовы к первой отрисовке. Если
   всё-таки опоздают — страница останется на системном шрифте до следующего
   открытия, но НЕ дёрнется на полстроки посреди загрузки */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(fonts/plusjakartasans-latin.woff2) format('woff2');
}

/* cyrillic-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(fonts/inter-1.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic — тоже нужен с первой отрисовки: интерфейс по умолчанию русский */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: optional;
  src: url(fonts/inter-2.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(fonts/inter-6.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(fonts/inter-7.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --bg: #0b1020;
  --panel: #141c30;
  --panel-2: #1b2742;
  --text: #f8fafc;
  --muted: #9aa8bf;
  --accent: #4f8cff;
  --accent-dark: #2563eb;
  --green: #22c55e;
  --amber: #f59e0b;
  --red: #ef4444;
  --line: rgba(255,255,255,.09);

  /* Шрифт как в LAER Web: Plus Jakarta Sans + Inter для кириллицы */
  --font-sans: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, Arial, system-ui, sans-serif;

  /* Цвет закреплён за модулем и одинаков везде: заставка, меню, дашборд,
     списки, план объекта. Красный — спикеры, фиолетовый — камеры */
  --mod-home: #4f8cff;
  --mod-video: #7c3aed;
  --mod-speakers: #e11d48;
  --mod-meters: #0ea5e9;
  --mod-barriers: #f59e0b;
  --mod-users: #475569;
}

* { box-sizing: border-box; }

/* Атрибут hidden должен побеждать display у классов. Иначе скрытый элемент
   остаётся на экране: у .ghost-button стоит inline-flex, у .chips — flex,
   и hidden их не пересиливает. Ловилось трижды, поэтому правило общее */
[hidden] { display: none !important; }

/* Подписи ждут своего языка: место под текст остаётся (visibility, не display),
   поэтому при появлении ничего не сдвигается. Класс снимается сразу после
   первой подстановки переводов — счёт идёт на десятки миллисекунд */
.i18n-pending [data-i18n] { visibility: hidden; }

/* --- Заставка запуска ------------------------------------------------ */

.splash {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background:
    radial-gradient(circle at top right, #19315c 0, transparent 42%),
    var(--bg);
  transition: opacity .45s ease, visibility .45s ease;
}

/* Приложение готово — заставка мягко растворяется и убирается совсем */
.splash-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.splash[hidden] { display: none; }

/* Значки модулей в ряд: каждый со своим наклоном — вид «разложенных карточек» */
.splash-marks {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}

.splash-mark {
  /* Наклон держим переменной: иначе анимация появления его затирает */
  --tilt: 0deg;
  position: relative;
  width: 62px;
  height: 62px;
  display: grid;
  place-items: center;
  color: #fff;
  border-radius: 20px;
  transform: rotate(var(--tilt));
  animation: splashMark .55s cubic-bezier(.2, .8, .3, 1) both;
}

.splash-mark svg { width: 30px; height: 30px; }

/* Цвета берём из общего набора модулей (:root), чтобы заставка, меню и
   дашборд не разъезжались: правим цвет в одном месте */
.mark-video {
  --tilt: -9deg;
  --ring: var(--mod-video);
  background: var(--mod-video);
  box-shadow: 0 14px 32px rgba(124, 58, 237, .35);
  animation-delay: .05s;
}

.mark-speakers {
  --tilt: 5deg;
  --ring: var(--mod-speakers);
  width: 74px;
  height: 74px;
  border-radius: 24px;
  background: var(--mod-speakers);
  box-shadow: 0 16px 38px rgba(225, 29, 72, .4);
  animation-delay: .15s;
}

.mark-speakers svg { width: 36px; height: 36px; }

.mark-meters {
  --tilt: -5deg;
  --ring: var(--mod-meters);
  background: var(--mod-meters);
  box-shadow: 0 14px 32px rgba(14, 165, 233, .32);
  animation-delay: .25s;
}

.mark-barriers {
  --tilt: 9deg;
  --ring: var(--mod-barriers);
  background: var(--mod-barriers);
  box-shadow: 0 14px 32px rgba(245, 158, 11, .32);
  animation-delay: .35s;
}

/* Расходящееся кольцо у каждого значка — волной, своим цветом */
.splash-ring {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--ring, #e11d48);
  animation: splashRing 3.4s ease-out infinite;
}

.mark-video .splash-ring { animation-delay: .6s; }
.mark-speakers .splash-ring { animation-delay: .95s; }
.mark-meters .splash-ring { animation-delay: 1.3s; }
.mark-barriers .splash-ring { animation-delay: 1.65s; }

.splash-title {
  margin: 0;
  font-size: 21px;
  font-weight: 700;
  animation: splashText .5s ease .45s both;
}

.splash-subtitle {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
  letter-spacing: .04em;
  animation: splashText .5s ease .55s both;
}

/* Полоска показывает, что идёт работа, а не зависание */
.splash-bar {
  width: 132px;
  height: 3px;
  margin-top: 14px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255,255,255,.1);
  animation: splashText .5s ease .65s both;
}

/* На узком экране значки чуть теснее, чтобы ряд помещался целиком */
@media (max-width: 380px) {
  .splash-marks { gap: 8px; }
  .splash-mark { width: 54px; height: 54px; border-radius: 17px; }
  .splash-mark svg { width: 26px; height: 26px; }
  .mark-speakers { width: 64px; height: 64px; border-radius: 20px; }
  .mark-speakers svg { width: 31px; height: 31px; }
}

/* Полоска заполняется слева направо один раз — как загрузка, без беготни */
.splash-bar i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  transform-origin: left center;
  animation: splashBar 2.3s cubic-bezier(.25, .7, .3, 1) .3s both;
}

@keyframes splashMark {
  from { opacity: 0; transform: rotate(var(--tilt)) translateY(14px) scale(.86); }
  to { opacity: 1; transform: rotate(var(--tilt)); }
}

@keyframes splashRing {
  0% { opacity: .5; transform: scale(1); }
  55% { opacity: .18; transform: scale(1.32); }
  80% { opacity: 0; transform: scale(1.5); }
  100% { opacity: 0; transform: scale(1.5); }
}

@keyframes splashText {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

@keyframes splashBar {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* --- Значки модулей --------------------------------------------------
   Один набор на всё приложение: заставка, боковое меню, карточки на
   главной, заглушка неготового модуля. Значок сплошного цвета с белым
   глифом — так модуль узнаётся по цвету, а не по подписи.
   Размер задаётся переменной --size, скругление и глиф считаются от неё */
.mod-icon {
  --size: 44px;
  width: var(--size);
  height: var(--size);
  flex: none;
  display: grid;
  place-items: center;
  color: #fff;
  border-radius: calc(var(--size) / 3.1);
  background: var(--mod-color, var(--accent));
  box-shadow: 0 10px 22px var(--mod-shadow, rgba(79, 140, 255, .3));
}

.mod-icon svg { width: 58%; height: 58%; }

.mod-icon-sm { --size: 32px; box-shadow: none; }
/* Значок внутри строки-плашки: тень на таком размере превращается в грязь */
.mod-icon-xs { --size: 16px; box-shadow: none; }
.mod-icon-lg { --size: 76px; }

.mod-home { --mod-color: var(--mod-home); --mod-shadow: rgba(79, 140, 255, .3); }
.mod-video { --mod-color: var(--mod-video); --mod-shadow: rgba(124, 58, 237, .32); }
.mod-speakers { --mod-color: var(--mod-speakers); --mod-shadow: rgba(225, 29, 72, .32); }
.mod-meters { --mod-color: var(--mod-meters); --mod-shadow: rgba(14, 165, 233, .3); }
.mod-barriers { --mod-color: var(--mod-barriers); --mod-shadow: rgba(245, 158, 11, .3); }
/* Раздел администратора — не модуль объекта, поэтому нейтральный сине-серый */
.mod-users { --mod-color: var(--mod-users); --mod-shadow: rgba(71, 85, 105, .32); }

/* До первой отрисовки анимации выключены: иначе при запуске приложения
   успевает проиграться выезд бокового меню — экран будто уезжает влево.
   Заставку это не касается: она и должна ожить сразу */
body:not(.ready) .app,
body:not(.ready) .app *,
body:not(.ready) .menu,
body:not(.ready) .menu * {
  transition: none !important;
  animation: none !important;
}

body {
  margin: 0;
  min-height: 100vh;
  color: var(--text);
  font-family: var(--font-sans);
  background:
    radial-gradient(circle at top right, #19315c 0, transparent 32%),
    var(--bg);
  padding-bottom: env(safe-area-inset-bottom);
  -webkit-text-size-adjust: 100%;
}

button { font: inherit; }
input { font: inherit; }

.app {
  width: min(920px, 100%);
  margin: auto;
  padding: 18px;
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}

/* Название и подпись сжимаются с многоточием: раньше при нехватке места
   они лезли под значок режима (LIVE/DEMO) и текст накладывался */
.topbar h1,
.topbar p {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.topbar h1 { font-size: 25px; }
.topbar p { margin-top: 5px; color: var(--muted); }

/* Значки справа не сжимаются — им всегда хватает своей ширины */
.topbar-right {
  display: flex;
  align-items: center;
  flex: none;
  gap: 10px;
}

.topbar-left {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.topbar-left > div { min-width: 0; }

/* --- Гамбургер ----------------------------------------------------- */

.burger {
  width: 42px;
  height: 42px;
  flex: none;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 5px;
  padding: 0;
  cursor: pointer;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.05);
}

.burger span {
  display: block;
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: var(--text);
  transition: transform .18s ease, opacity .18s ease;
}

.burger:hover { border-color: var(--accent); }
.burger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger.open span:nth-child(2) { opacity: 0; }
.burger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --- Боковое меню модулей ------------------------------------------ */

.menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(4, 8, 20, .6);
  backdrop-filter: blur(2px);
}

.menu {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 50;
  width: min(320px, 86vw);
  overflow-y: auto;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  transform: translateX(-102%);
  transition: transform .22s ease;
  border-right: 1px solid var(--line);
  background: linear-gradient(180deg, #131c31, #0d1424);
  box-shadow: 24px 0 48px rgba(0,0,0,.35);
}

.menu.open { transform: translateX(0); }

.menu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.menu-head strong { font-size: 16px; }

.menu-close {
  width: 32px;
  height: 32px;
  flex: none;
  cursor: pointer;
  color: var(--muted);
  border-radius: 10px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.04);
}

.menu-close:hover { color: var(--text); border-color: var(--accent); }

/* Заголовок группы меню. Прописные задаются стилем, а не в словарях:
   немецкое и русское написание остаётся обычным и годится для чтения вслух */
.menu-group {
  margin: 14px 0 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Первая группа стоит сразу под шапкой — лишний отступ там читался бы
   как пустая строка */
.menu-group:first-child { margin-top: 0; }

/* minmax(0, 1fr) обязателен: без него длинное слово («Видеонаблюдение»)
   растягивает колонку шире панели, и кнопки вылезают за её край */
.menu-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-width: 0;
  padding: 12px 13px;
  cursor: pointer;
  text-align: left;
  color: var(--text);
  border-radius: 13px;
  border: 1px solid transparent;
  background: rgba(255,255,255,.04);
  transition: background .15s ease, border-color .15s ease;
}

.menu-item:hover { background: rgba(255,255,255,.08); }

.menu-item.active {
  border-color: rgba(79,140,255,.45);
  background: rgba(79,140,255,.14);
}

.menu-item.pending .menu-item-name { color: var(--muted); }

.menu-item-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  font-size: 15px;
  overflow-wrap: anywhere;
}

.menu-item-soon {
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 999px;
  color: #fcd34d;
  background: rgba(245,158,11,.12);
}

/* Подпись идёт сразу за списком: прижатая к низу, она оставляла
   полпанели пустотой на высоком экране */
.menu-foot {
  margin: 2px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

/* Версия продукта и хэш сборки — двумя строками: первая для человека,
   вторая для разбора («та ли сборка приехала на телефон») */
.menu-about {
  display: grid;
  gap: 2px;
}

.menu-release { color: var(--text); font-weight: 600; }

.menu-build { font-size: 11px; opacity: .75; }

/* --- Заглушка неготового модуля ------------------------------------ */

.stub-card {
  max-width: 460px;
  margin: auto;
  text-align: center;
  display: grid;
  justify-items: center;
  gap: 12px;
}

.stub-card h2 { margin: 0; }

/* Обёртка без своего оформления: внутри лежит общий значок модуля */
.stub-icon {
  display: grid;
  place-items: center;
}

/* Выбор языка: кнопка с текущим кодом + выпадающий список названий */
.lang-select {
  position: relative;
  flex: none;
}

/* Кнопка языка — такой же значок, как выход: только глобус, без подписи */
.lang-trigger {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.05);
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}

.lang-trigger:hover { color: var(--text); border-color: var(--accent); }
.lang-trigger[aria-expanded="true"] {
  color: var(--text);
  border-color: var(--accent);
  background: rgba(79,140,255,.12);
}

.lang-globe { width: 18px; height: 18px; }

.lang-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  min-width: 184px;
  padding: 6px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: linear-gradient(180deg, #16203a, #111a2e);
  box-shadow: 0 18px 38px rgba(0,0,0,.45);
}

.lang-menu[hidden] { display: none; }

.lang-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-width: 0;
  padding: 9px 10px;
  cursor: pointer;
  text-align: left;
  color: var(--text);
  border: 0;
  border-radius: 10px;
  background: transparent;
  transition: background .15s ease;
}

.lang-option:hover { background: rgba(255,255,255,.07); }

.lang-name {
  flex: 1;
  min-width: 0;
  font-size: 14px;
}

.lang-tag {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--muted);
}

.lang-check {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--accent);
  opacity: 0;
}

.lang-option.active { background: rgba(79,140,255,.14); }
.lang-option.active .lang-tag { color: var(--accent); }
.lang-option.active .lang-check { opacity: 1; }

/* Кнопка выхода в шапке — компактная, рядом с переключателем языка */
.icon-button {
  width: 38px;
  height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.05);
  transition: color .15s ease, border-color .15s ease;
}

.icon-button svg { width: 18px; height: 18px; }
.icon-button:hover { color: #fca5a5; border-color: rgba(239,68,68,.4); }

/* Размер значков шапки на телефоне задан один раз — в общем блоке @media внизу файла */

.view { display: none; }
.view.active { display: block; }

.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 18px;
}

.stat, .card {
  border: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.025));
  border-radius: 20px;
}

.stat { padding: 18px; }
.stat strong { display: block; font-size: 28px; }

/* Плитка-кнопка (состояние системы): выглядит как обычная плитка,
   но когда есть проблемы — открывает разбор, что именно молчит */
button.stat {
  width: 100%;
  text-align: left;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color .15s ease, transform .12s ease;
}

button.stat:disabled { cursor: default; }
button.stat:not(:disabled):hover { border-color: rgba(239,68,68,.45); transform: translateY(-1px); }

/* Нижняя строка плитки: значки или подпись со стрелкой — на одной линии.
   На телефоне плитка ужимается до ~110 px, и подпись со значками там не
   помещается: переносим её на свою строку, а не обрезаем и не прячем */
.stat-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
}

.stat-icons { display: inline-flex; gap: 4px; flex: none; }

/* Плитка, которую есть смысл нажать, говорит об этом стрелкой. Раньше
   подсказка жила только в title — на телефоне её не существует */
.stat-go {
  flex: none;
  display: grid;
  place-items: center;
  color: #fca5a5;
}

.stat-go svg { width: 16px; height: 16px; transform: rotate(-90deg); }

.stat-action { border-color: rgba(239,68,68,.3); }

/* Плитка статуса: словами и с цветной точкой, а не безликое «OK» */
.stat strong.state-ok,
.stat strong.state-bad {
  font-size: 22px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.stat strong.state-ok { color: #8ef0ad; }
.stat strong.state-bad { color: #fca5a5; }
.stat span, .muted { color: var(--muted); }
.small { font-size: 12px; }

.card { padding: 20px; }
.card + .card { margin-top: 14px; }

/* Шапка карточки: заголовок держится в одну строку, значок при нехватке
   места переносится под него, а не наезжает на текст */
.section-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 16px;
}

.section-title h2 {
  margin: 0;
  min-width: 0;
  flex: 1 1 auto;
  font-size: 19px;
  /* Заголовок всегда одной строкой: не хватает места — вниз уезжает значок */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Заголовок с подписью (название объекта + адрес): сжимается сам,
   подпись переносится — значок остаётся в строке заголовка */
.section-title > div {
  flex: 1 1 0;
  min-width: 0;
}

/* Есть подпись под заголовком — значок держим на линии заголовка, а не по центру блока */
.section-title:has(> div) { align-items: flex-start; }

/* Карточка записи выровнена по центру: заголовок стоит ровно по центру
   карточки, значок состояния — у правого края.
   Обычной строкой это не сходится: h2 растягивается на остаток ширины и
   текст центруется в нём, а не в карточке — заголовок уезжает влево на
   половину значка, тем сильнее, чем длиннее подпись (от «online» до
   «nicht konfiguriert»). Поэтому три колонки 1fr | auto | 1fr: слева
   пустая колонка-двойник, и центр заголовка не зависит от значка */
.recorder-card .section-title {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
}

/* Пустая колонка слева — двойник плашки: тот же текст теми же буквами, но
   невидимый. Поэтому левая колонка ровно такой же ширины, как правая со
   значком, и заголовок стоит по центру при любой подписи. Текст двойника
   кладёт скрипт в data-badge (см. mirrorBadge в public/app.js) */
.recorder-card .section-title::before {
  content: attr(data-badge);
  justify-self: start;
  visibility: hidden;
  padding: 6px 10px;
  border: 1px solid transparent;
  font-size: 12px;
  white-space: nowrap;
}

.recorder-card .section-title .badge { justify-self: end; }

.section-title .badge { flex: none; }

/* Пока значку нечего показать, пустая плашка не занимает место
   (под центрированным заголовком она особенно заметна) */
.section-title .badge:empty { display: none; }

.badge {
  color: #8ef0ad;
  background: rgba(34,197,94,.12);
  border: 1px solid rgba(34,197,94,.24);
  padding: 6px 10px;
  border-radius: 999px;
  font-size: 12px;
  white-space: nowrap;
}

.badge-neutral {
  color: #cbd5e1;
  background: rgba(255,255,255,.06);
  border-color: var(--line);
}

/* Счётчики в цвете своего модуля: сколько чего есть — синий у объектов,
   красный у спикеров, фиолетовый у камер и так далее.
   ⛔ Состояния («3 из 4 на связи», «ошибок 2») этими классами НЕ красим:
   там зелёный и жёлтый уже значат «всё хорошо» и «есть потери», а красный
   цвет спикеров прочитался бы как авария */
.badge-site {
  color: #bfd4ff;
  background: rgba(79,140,255,.14);
  border-color: rgba(79,140,255,.3);
}

.badge-speakers {
  color: #fda4af;
  background: rgba(225,29,72,.15);
  border-color: rgba(225,29,72,.3);
}

.badge-video {
  color: #c4b5fd;
  background: rgba(124,58,237,.17);
  border-color: rgba(124,58,237,.32);
}

.badge-meters {
  color: #a5e4ff;
  background: rgba(14,165,233,.15);
  border-color: rgba(14,165,233,.3);
}

.badge-barriers {
  color: #fcd9a0;
  background: rgba(245,158,11,.14);
  border-color: rgba(245,158,11,.3);
}

/* Разделы администратора не модули объекта — сине-серый, как их значок */
.badge-users {
  color: #cbd5e1;
  background: rgba(71,85,105,.35);
  border-color: rgba(148,163,184,.28);
}

.badge-warn {
  color: #fcd34d;
  background: rgba(245,158,11,.12);
  border-color: rgba(245,158,11,.26);
}

.badge-error {
  color: #fca5a5;
  background: rgba(239,68,68,.12);
  border-color: rgba(239,68,68,.26);
}

.badge-mode { font-weight: 600; }

.grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}

.park-card, .speaker-card {
  width: 100%;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 16px;
  transition: border-color .15s ease, background .15s ease, transform .1s ease;
}

.park-card:hover, .speaker-card:hover {
  border-color: var(--accent);
  background: #202f50;
}

.park-card:active, .speaker-card:active { transform: scale(.99); }

.row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.park-card h3, .speaker-card h3 { margin: 0; }
.park-card p, .speaker-card small { color: var(--muted); }

/* Карточка объекта: иконка, название с адресом, метрики снизу */
.park-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.park-title { flex: 1; min-width: 0; }
.park-title p { margin: 4px 0 0; font-size: 13px; }

.park-metrics {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.metric {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 999px;
  background: rgba(255,255,255,.06);
}

.metric-warn { color: #fcd34d; background: rgba(245,158,11,.12); }

/* Глиф модуля внутри плашки: цвет наследует от неё, поэтому
   предупреждение остаётся жёлтым целиком */
.metric-glyph {
  display: inline-grid;
  place-items: center;
  width: 15px;
  height: 15px;
  color: inherit;
}

.metric-glyph svg { width: 100%; height: 100%; }

.park-metrics .open-link { margin-left: auto; }

.dot {
  width: 9px;
  height: 9px;
  flex: none;
  background: var(--green);
  border-radius: 50%;
  box-shadow: 0 0 0 5px rgba(34,197,94,.12);
}

.dot-offline { background: var(--red); box-shadow: 0 0 0 5px rgba(239,68,68,.12); }
.dot-unknown { background: var(--muted); box-shadow: 0 0 0 5px rgba(154,168,191,.12); }
.dot-warn { background: var(--amber); box-shadow: 0 0 0 5px rgba(245,158,11,.12); }

.open-link { color: #80aaff; font-size: 13px; }

/* Потери рядом с ключевой цифрой модуля: жёлтым, как везде в приложении */
.text-warn { color: #fcd34d; }

.speaker-list { display: grid; gap: 10px; }

/* Камера — та же карточка, но не кликабельная */
.speaker-card.static { cursor: default; }
.speaker-card.static:hover { border-color: var(--line); background: var(--panel-2); }
.speaker-card.static:active { transform: none; }

/* Чипы выбора парка в модуле видеонаблюдения */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.chip {
  padding: 8px 14px;
  cursor: pointer;
  color: var(--muted);
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.04);
}

.chip:hover { color: var(--text); border-color: var(--accent); }
.chip.active { color: #fff; border-color: transparent; background: var(--accent); }

/* Характеристики регистратора */
.info-grid {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 8px 16px;
  margin: 0 0 14px;
}

.info-grid dt { color: var(--muted); font-size: 13px; }
.info-grid dd { margin: 0; font-size: 14px; overflow-wrap: anywhere; }

.storage-list { display: grid; gap: 12px; }

.storage-item .row { margin-bottom: 6px; }
.storage-item small { color: var(--muted); }
.storage-item .limit-bar { max-width: none; margin: 0; }

.speaker-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

/* Устройство ещё не заведено — карточку не приглушаем: о состоянии говорят
   серый значок и подпись справа, а нажатие просто не работает */
.speaker-card[disabled] { cursor: not-allowed; }

/* Номер устройства — один и тот же в списке и на плане объекта */
.device-no {
  width: 22px;
  flex: none;
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.speaker-info {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.speaker-icon {
  width: 42px;
  height: 42px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 12px;
  /* Как маркер на плане: сплошной красный, значок белый */
  color: #fff;
  background: #e11d48;
}

.speaker-icon svg { width: 20px; height: 20px; }

/* Камеры — тот же приём, но свой цвет: модули различаются взглядом */
.speaker-icon.cam {
  color: #fff;
  background: #7c3aed;
}

/* Нет связи или устройство не заведено — значок серый, как маркер на плане */
.speaker-icon.off { background: #6b7280; }

.speaker-meta { display: grid; gap: 3px; min-width: 0; }
.speaker-meta h3 { font-size: 16px; }

.speaker-side {
  display: grid;
  justify-items: end;
  gap: 6px;
  text-align: right;
}

.port {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #cbd5e1;
  font-size: 13px;
}

.state-line {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * ОДНО ПРАВИЛО ПАНЕЛИ ДЕЙСТВИЙ — одинаково на всех экранах:
 *   • панель стоит сразу под шапкой, до содержимого;
 *   • кнопки равной ширины и высоты, занимают всю ширину контента,
 *     ровно как карточки под ними;
 *   • нечётная последняя кнопка растягивается на строку, а не висит
 *     половинкой;
 *   • подпись всегда в одну строку.
 * Новый экран = <div class="toolbar"> с кнопками, без своих отступов.
 * ------------------------------------------------------------------ */

.back-button, .ghost-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 46px;
  padding: 10px 14px;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.05);
  transition: border-color .15s ease, background .15s ease;
}

.back-button:hover, .ghost-button:hover { border-color: var(--accent); }

/* Идёт обновление: значок крутится, кнопка не нажимается */
.ghost-button.is-busy .btn-icon svg { animation: statusSpin 1s linear infinite; }
.ghost-button:disabled { opacity: .7; cursor: progress; }

/* Подпись кнопки обрезается многоточием, а значок остаётся целым:
   иначе на узком экране первым исчезает он, а не лишние буквы */
.back-button > span:not(.btn-icon),
.ghost-button > span:not(.btn-icon) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Значок в кнопке: одного роста с текстом, цвет наследует */
.btn-icon {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 17px;
  height: 17px;
  opacity: .85;
}

.btn-icon svg { width: 100%; height: 100%; }

/* Широкий экран: все кнопки одной строкой, поровну на всю ширину контента */
.toolbar {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 10px;
  margin: 0 0 18px;
}

.recorder-card {
  max-width: 620px;
  margin: auto;
  text-align: center;
}

.login-card { max-width: 460px; margin: auto; }

.form { display: grid; gap: 14px; margin-top: 16px; }

.field { display: grid; gap: 6px; text-align: left; }
.field span { color: var(--muted); font-size: 13px; }

.field input,
.field select {
  width: 100%;
  padding: 13px 14px;
  color: var(--text);
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.04);
}

/* Поле с кнопкой внутри (показать пароль). Кнопка лежит поверх правого
   края, поэтому набранное под неё не заезжает — у поля свой отступ */
.field-with-button { position: relative; display: block; }

.field-with-button input { padding-right: 46px; }

.field-button {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--muted);
  border-radius: 9px;
  border: 1px solid transparent;
  background: transparent;
}

.field-button:hover { color: var(--text); background: rgba(255,255,255,.06); }
.field-button.on { color: var(--accent); }
.field-button svg { width: 18px; height: 18px; }

/* Ответ поля прямо под ним: пароли сошлись или нет — видно при наборе.
   Цвет задан через .field: у общего правила `.field small` тот же вес,
   и приглушённый серый побеждал бы по порядку в файле */
.field .field-note { font-size: 12px; }
.field .field-note-bad { color: #fca5a5; }
.field .field-note-good { color: #8ef0ad; }

.field input.field-bad { border-color: rgba(239,68,68,.55); }
.field input.field-good { border-color: rgba(34,197,94,.45); }

/* --- Свой выпадающий список ------------------------------------------
   Системный <select> рисует список силами Windows: белая полоса поверх
   тёмного экрана. Сам список браузер стилям не отдаёт, поэтому у нас своя
   кнопка со списком, а <select> остаётся хозяином значения и прячется. */
.select { position: relative; }

.select-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 13px 14px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text);
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.04);
}

.select-trigger:hover { border-color: rgba(255,255,255,.2); }
.select.open .select-trigger { border-color: var(--accent); }

.select-value { flex: 1; min-width: 0; overflow-wrap: anywhere; }

.select-arrow {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--muted);
}

.select-arrow svg { width: 18px; height: 18px; transition: transform .18s ease; }
.select.open .select-arrow svg { transform: rotate(180deg); }

/* Сам список: поверх соседних полей, поэтому со своей тенью и слоем */
.select-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  display: grid;
  gap: 2px;
  padding: 6px;
  max-height: 260px;
  overflow: auto;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  box-shadow: 0 18px 40px rgba(0,0,0,.45);
}

.select-list[hidden] { display: none; }

.select-option {
  padding: 10px 12px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text);
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
}

.select-option:hover, .select-option:focus-visible { background: rgba(255,255,255,.07); outline: none; }

.select-option.active {
  border-color: rgba(79,140,255,.45);
  background: rgba(79,140,255,.14);
}

.field input:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
  background: rgba(79,140,255,.08);
}

/* Подсказка под полем — не подпись, поэтому не серо-мелкая как span */
.field small { color: var(--muted); font-size: 12px; }

.selected {
  color: var(--muted);
  margin: 12px 0 20px;
}

.selected strong {
  display: block;
  color: var(--text);
  font-size: 19px;
  margin-top: 5px;
}

.selected small {
  display: block;
  margin-top: 7px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.record-button {
  width: 138px;
  height: 138px;
  /* inline-grid, а не grid: блочная кнопка перестаёт центрироваться
     text-align'ом карточки и прижимается к левому краю */
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  cursor: pointer;
  color: white;
  border: 8px solid rgba(225,29,72,.18);
  background: linear-gradient(145deg, #e11d48, #9f1239);
  box-shadow: 0 18px 44px rgba(225,29,72,.32);
  touch-action: manipulation;
}

.record-button svg { width: 52px; height: 52px; }
.record-button:disabled { opacity: .5; cursor: not-allowed; }

/* Идёт запись: ярче, с пульсацией, значок меняется на «стоп» */
.record-button.recording {
  background: linear-gradient(145deg, #fb7185, #e11d48);
  animation: pulse 1.2s infinite;
}

@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(239,68,68,.4); }
  70% { box-shadow: 0 0 0 25px rgba(239,68,68,0); }
  100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); }
}

.timer {
  margin: 15px 0 7px;
  font-size: 30px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.timer.warning { color: var(--amber); }
.timer.danger { color: var(--red); }

.limit-bar {
  height: 4px;
  margin: 12px auto 6px;
  max-width: 320px;
  border-radius: 999px;
  background: rgba(255,255,255,.08);
  overflow: hidden;
}

.limit-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width .25s linear, background .25s linear;
}

/* Цвет полосы — классом: инлайновый style="background:…" запрещён политикой CSP */
.limit-bar span.bar-ok { background: var(--green); }
.limit-bar span.bar-warn { background: var(--amber); }
.limit-bar span.bar-crit { background: var(--red); }

audio {
  width: 100%;
  margin: 12px 0;
}

/* --- Готовые объявления --------------------------------------------- */

.preset-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px;
}

/* Подсказка про беззвучный запуск: спокойная, не как ошибка */
.preset-hint {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  text-align: left;
}

.preset-hint p { margin: 0; }
.preset-hint[hidden] { display: none; }

/* Запасной выход — открыть сайт в браузере */
.hint-link {
  display: inline-block;
  margin-top: 8px;
  color: var(--accent);
  font-size: 13px;
  text-decoration: none;
  border-bottom: 1px solid rgba(79, 140, 255, .4);
}

.hint-link:hover { color: #fff; border-bottom-color: currentColor; }

/* Одна карточка на объявление: слева отправка на спикер, справа —
   прослушивание у себя. Рамка и фон общие, внутри только разделитель */
.preset-item {
  display: flex;
  min-width: 0;
  overflow: hidden;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.05);
  transition: border-color .15s ease;
}

.preset-item:hover { border-color: var(--accent); }

/* Файл ещё качается — карточка приглушена, кнопка прослушивания ждёт.
   Готово: значок прослушивания становится обычным, можно нажимать */
.preset-item:not(.preset-ready) .preset-preview {
  color: var(--muted);
  opacity: .45;
}

.preset-item.preset-ready .preset-preview { opacity: 1; }



.preset-button {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-height: 56px;
  min-width: 0;
  padding: 10px 14px;
  cursor: pointer;
  text-align: left;
  color: var(--text);
  border: 0;
  border-radius: 0;
  background: transparent;
  transition: background .15s ease;
}

.preset-button:hover:not(:disabled) { background: rgba(79,140,255,.12); }
.preset-button:disabled { opacity: .5; cursor: progress; }

.preset-icon {
  width: 34px;
  height: 34px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 10px;
  color: #fff;
  background: #e11d48;
}

.preset-icon svg { width: 17px; height: 17px; }

.preset-name {
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Прослушивание: звук только у оператора, поэтому кнопка спокойная, не красная */
.preset-preview {
  width: 48px;
  flex: none;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--muted);
  border: 0;
  border-left: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  transition: color .15s ease, background .15s ease;
}

.preset-preview svg { width: 18px; height: 18px; }
.preset-preview:hover { color: var(--text); background: rgba(255,255,255,.07); }

.preset-preview.playing {
  color: #fff;
  background: var(--accent);
}


/* --- Подтверждение --------------------------------------------------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: rgba(4, 8, 20, .6);
  backdrop-filter: blur(2px);
}

.modal {
  position: fixed;
  z-index: 80;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(420px, calc(100vw - 32px));
  padding: 22px;
  border-radius: 20px;
  border: 1px solid var(--line);
  background: linear-gradient(180deg, #16203a, #111a2e);
  box-shadow: 0 24px 60px rgba(0,0,0,.5);
}

.modal[hidden], .modal-backdrop[hidden] { display: none; }

.modal h3 { margin: 0 0 10px; font-size: 18px; }
.modal p { margin: 0 0 18px; }

.modal-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.modal-actions .primary-button,
.modal-actions .ghost-button { margin-top: 0; }

/* Окно-остановка: значок, разговор и одна кнопка — всё по центру.
   Кнопка у края читалась как «одна из двух», хотя выбор здесь один */
.modal-notice {
  display: grid;
  justify-items: center;
  gap: 4px;
  text-align: center;
  padding: 26px 22px 22px;
}

.modal-notice h3 { margin: 6px 0 0; }
.modal-notice p { margin: 0 0 8px; }

.modal-notice .primary-button {
  width: auto;
  min-width: 160px;
  margin-top: 6px;
}

/* Значок разговора: круг в цвет предупреждения, глиф внутри */
.notice-icon {
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  color: #fcd34d;
  border-radius: 999px;
  background: rgba(245,158,11,.14);
  box-shadow: inset 0 0 0 1px rgba(245,158,11,.26);
}

.notice-icon svg { width: 26px; height: 26px; }

/* Кнопки записи одна под другой на любой ширине: «Отправить» сверху,
   «Перезаписать» под ней. В строку они помещались впритык и наезжали */
.button-row {
  display: flex;
  flex-direction: column-reverse;
  gap: 10px;
  margin-top: 18px;
}

.button-row .primary-button,
.button-row .ghost-button {
  width: 100%;
  margin-top: 0;
}

.primary-button {
  width: 100%;
  margin-top: 18px;
  padding: 14px;
  border: 0;
  border-radius: 14px;
  color: white;
  font-weight: 700;
  background: var(--accent);
  cursor: pointer;
}

.primary-button:disabled {
  opacity: .45;
  cursor: not-allowed;
}

/* --- Громкость спикера ------------------------------------------------ */

.volume-box {
  margin: 4px 0 18px;
  padding: 14px 16px;
  text-align: left;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.04);
}

.volume-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 8px;
  font-size: 14px;
  color: var(--muted);
}

.volume-head strong {
  font-size: 18px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.volume-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.volume-mark {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 16px;
  height: 16px;
  opacity: .7;
}

/* Правый значок (громко) крупнее левого — так шкала читается сразу */
.volume-mark:last-of-type { width: 19px; height: 19px; }
.volume-mark svg { width: 100%; height: 100%; }

.volume-box input[type="range"] {
  flex: 1;
  min-width: 0;
  height: 30px;
  cursor: pointer;
  accent-color: var(--accent);
  background: transparent;
}

.volume-box p { margin: 8px 0 0; }

/* Остановить объявление: заметная, но появляется только пока клип звучит */
.danger-button {
  width: 100%;
  margin-top: 10px;
  padding: 14px;
  cursor: pointer;
  font-weight: 700;
  color: #fff;
  border: 0;
  border-radius: 14px;
  background: #e11d48;
  transition: background .15s ease;
}

.danger-button:hover:not(:disabled) { background: #be123c; }
.danger-button:disabled { opacity: .5; cursor: progress; }
.danger-button[hidden] { display: none; }

.status {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin-top: 12px;
  padding: 12px;
  color: var(--muted);
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: rgba(255,255,255,.03);
  text-align: left;
  word-break: break-word;
}

/* display:flex перебивает атрибут hidden — возвращаем его силой,
   иначе скрытые плашки состояния видны всегда */
.status[hidden] { display: none; }

/* Плашка на весь экран (под верхней панелью) — ответ на то, что нажали
   там же. Отступ уходит вниз: сверху его уже дала панель, а без нижнего
   карточка прилипала бы к сообщению */
.view > .status { margin: 0 0 18px; }

/* Значок вида сообщения: успех, ошибка, ожидание */
.status-glyph {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 1px;
}

.status-glyph svg { width: 100%; height: 100%; }

/* Ожидание: значок медленно крутится, текст остаётся на месте */
.status.busy .status-glyph svg { animation: statusSpin 1.1s linear infinite; }

@keyframes statusSpin {
  to { transform: rotate(360deg); }
}

.status.ok {
  color: #8ef0ad;
  border-color: rgba(34,197,94,.26);
  background: rgba(34,197,94,.08);
}

.status.error {
  color: #fca5a5;
  border-color: rgba(239,68,68,.26);
  background: rgba(239,68,68,.08);
}

.status.busy {
  color: #bfdbfe;
  border-color: rgba(79,140,255,.28);
  background: rgba(79,140,255,.08);
}

.stages {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  gap: 8px;
  text-align: left;
}

.stages li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 13px;
  color: var(--muted);
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(255,255,255,.03);
}

.stages li strong { color: var(--text); font-weight: 600; }
.stages li.done { border-color: rgba(34,197,94,.24); }
.stages li.failed { border-color: rgba(239,68,68,.26); color: #fca5a5; }

/* --- План объекта с маркерами --------------------------------------- */

.map-viewer {
  position: relative;
  overflow: auto;
  border-radius: 16px;
  background: #fff;
  line-height: 0;
  /* Размеры маркеров считаются от ширины плана, а не экрана: иначе на
     телефоне чертёж ужимается, а маркер остаётся прежним и закрывает пол-плана */
  container-type: inline-size;
}

.map-viewer img {
  display: block;
  width: 100%;
  height: auto;
}

.map-markers {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Сектор направления звука — под маркером, не перехватывает нажатия.
   Цвет тот же, что у маркера: красный = устройство на связи, серый = молчит */
.map-cone {
  position: absolute;
  width: 34%;
  aspect-ratio: 1;
  pointer-events: none;
  color: #e11d48;
}

.map-cone-off { color: #6b7280; }
.map-cone svg { width: 100%; height: 100%; display: block; }

/* Капсула поворачивается целиком — вместе с рупором и цифрой, одним куском */
.map-marker {
  position: absolute;
  transform: translate(-50%, -50%) rotate(var(--turn, 0deg));
  display: flex;
  align-items: center;
  font-size: clamp(7px, 1.15cqw, 11px);
  gap: .4em;
  padding: .2em .42em;
  cursor: pointer;
  pointer-events: auto;
  line-height: 1;
  white-space: nowrap;
  color: #fff;
  border: max(1px, .17em) solid #fff;
  border-radius: 999px;
  background: #e11d48;
  box-shadow: 0 4px 14px rgba(0,0,0,.35);
  transition: transform .12s ease;
}

/* Поворот держим и при наведении — иначе капсула на миг встаёт ровно */
.map-marker:hover { transform: translate(-50%, -50%) rotate(var(--turn, 0deg)) scale(1.06); }

/* Нет связи — маркер гаснет: цвет занят под сам факт «это спикер» */
.map-marker-off {
  background: #6b7280;
  opacity: .75;
}

.map-dot {
  width: 1.75em;
  height: 1.75em;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(255,255,255,.22);
}

.map-dot svg { width: 1.15em; height: 1.15em; }

/* Содержимое доворачивается обратно ОДНИМ блоком: тогда рупор и цифра не
   просто остаются ровными, а вообще не двигаются с места — крутится только
   сама капсула вокруг них */
/* Содержимое поворачивается вместе с капсулой — своего доворота у него нет */
.map-body {
  display: flex;
  align-items: center;
  gap: .4em;
}


/* Пульсирующее кольцо: маркер заметен на плотном чертеже */
.map-marker::before {
  content: '';
  position: absolute;
  left: 1.08em;
  top: 50%;
  width: 1.95em;
  height: 1.95em;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 2px solid currentColor;
  opacity: .45;
  animation: mapPing 2.4s ease-out infinite;
}

@keyframes mapPing {
  0% { transform: translate(-50%, -50%) scale(.9); opacity: .5; }
  70% { transform: translate(-50%, -50%) scale(1.9); opacity: 0; }
  100% { opacity: 0; }
}

/* На плане у устройства только значок и номер: названия читаются в списке */
.map-no {
  font-size: 1.15em;
  font-weight: 800;
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  padding-right: .25em;
}

.journal { display: grid; gap: 10px; }

.journal-item {
  display: grid;
  gap: 4px;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-2);
}

/* ---------------------------------------------------------------- */
/* Пользователи и права                                              */
/* ---------------------------------------------------------------- */

.user-list { display: grid; gap: 10px; }

.user-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-2);
}

/* Выключенная учётка — приглушена, но читаема: это состояние, а не авария */
.user-card.off { opacity: .62; }

.user-info { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* Значок человека: круглый и в своём цвете. Цвет считается из логина
   (userColorClass) — значит, у одного человека он всегда один и тот же, а
   хранить его нигде не нужно. Тень убрана: цветное свечение под каждым в
   списке из десяти учёток превращается в кашу */
.user-avatar {
  --size: 38px;
  border-radius: 999px;
  box-shadow: none;
}

/* Оттенки взяты не из модульных: там цвет означает «спикеры» или «камеры»,
   и мешать два языка не стоит */
.user-color-0 { --mod-color: #6366f1; }
.user-color-1 { --mod-color: #14b8a6; }
.user-color-2 { --mod-color: #d946ef; }
.user-color-3 { --mod-color: #f97316; }
.user-color-4 { --mod-color: #0891b2; }
.user-color-5 { --mod-color: #84cc16; }
.user-color-6 { --mod-color: #8b5cf6; }
.user-color-7 { --mod-color: #ec4899; }
.user-meta { display: grid; gap: 3px; min-width: 0; }
.user-meta h3 { margin: 0; font-size: 15px; }
.user-meta small { color: var(--muted); overflow-wrap: anywhere; }

.user-side { display: grid; justify-items: end; gap: 8px; }
.user-actions { display: flex; gap: 6px; }

/* Кнопка-значок в строке списка. Своё имя, не .icon-button: тот уже занят
   шапкой приложения, и общий класс перебил бы ей цвет наведения и размер.
   Размер держим 40px и на телефоне — по строке надо попадать пальцем */
.row-action {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  color: var(--text);
  cursor: pointer;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--panel);
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}

/* Кнопка удаления в панели: обычная по форме, но по цвету видно, что она опасная */
.ghost-button.danger-text { color: #fca5a5; }
.ghost-button.danger-text:hover { border-color: var(--mod-speakers); color: var(--mod-speakers); }

.row-action:hover { border-color: var(--accent); color: var(--accent); }
.row-action.danger:hover { border-color: var(--mod-speakers); color: var(--mod-speakers); }
.row-action .btn-icon { width: 18px; height: 18px; }

/* --- Расстановка устройств на плане --------------------------------- */

/* Числовая подгонка — отдельный блок с воздухом и чертой сверху: иначе
   подсказка про план и подписи полей сливаются в одну кашу */
.placement-controls {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.placement-controls p { margin: 10px 0 0; }

/* Кнопка стоит на месте поля, а подписи у неё нет. Пустая подпись всё равно
   должна занимать строку: без неё ряд выходит выше своего содержимого, и
   кнопка растягивается на всю высоту — рядом с инпутом это сразу видно */
.field span:empty::before { content: '\00a0'; }

/* И тот же рост, что у соседнего инпута: те же отступы, та же рамка */
.field > .ghost-button { min-height: 0; padding: 13px 14px; }

/* Опасное действие отделяем от подсказки: вплотную к тексту оно читается
   как часть абзаца, а не как отдельная кнопка */
.placement-controls + .toolbar { margin-top: 18px; }

/* В режиме правки палец должен двигать маркер, а не прокручивать страницу */
.map-viewer.placement { touch-action: none; cursor: crosshair; }

/* Выбранный маркер выделяем кольцом: он же и слушается перетаскивания */
.map-marker.picked { box-shadow: 0 0 0 3px rgba(79, 140, 255, .55); }

/* Ручка направления. Кнопка 44 px и прозрачная — в неё надо попадать пальцем;
   видимая точка меньше, чтобы не закрывать план, и рисуется внутри.
   Растягивать область нажатия псевдоэлементом нельзя: у кнопки он обрезается */
.map-handle {
  position: absolute;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: grab;
  border: 0;
  background: none;
  transform: translate(-50%, -50%);
  /* Слой маркеров нажатия не принимает — их включает каждый элемент сам */
  pointer-events: auto;
}

.map-handle::after {
  content: '';
  width: 22px;
  height: 22px;
  border: 2px solid #fff;
  border-radius: 50%;
  background: var(--mod-speakers);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .45);
}

/* Вторая ручка — поворот самого маркера. Цвет другой, потому что и смысл
   другой: красная крутит звук, синяя — железку */
.map-handle-icon::after {
  width: 18px;
  height: 18px;
  background: var(--accent);
}

.map-handle:active { cursor: grabbing; }

/* Устройство ещё не на плане — чип приглушён, но выбирается */
/* ---------------------------------------------------------------- */
/* Объекты: список и разделы одного объекта                          */
/* ---------------------------------------------------------------- */

/* «Объявление на все» — первая строка списка спикеров. Выделена рамкой в цвет
   модуля: это не ещё один спикер, а действие сразу на всех */
.speaker-card-all {
  border-color: var(--mod-speakers);
  background: rgba(225, 29, 72, .08);
}

.speaker-card-all .speaker-icon { background: var(--mod-speakers); color: #fff; }

/* Подзаголовок группы полей внутри одной формы: длинная форма заведения
   иначе читается сплошной лентой, а тут видно, где кончается объект и
   начинается железо. Черта сверху — у всех, кроме первой группы */
.form-group {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 8px 0 0;
  padding-top: 14px;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  border-top: 1px solid var(--line);
}

/* Значок группы мельче, чем у раздела: подпись рядом одной строкой */
.form-group .mod-icon { --size: 28px; box-shadow: none; }

/* Спикеров в карточке заведения может быть несколько. Блоки разделяем
   чертой, чтобы поля второго не читались продолжением первого */
.speaker-block { display: grid; gap: 14px; }
.speaker-block + .speaker-block {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
}

.speaker-block-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 0;
}

.speaker-block-head .block-title { color: var(--muted); font-size: 13px; }

.form-group:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* Заголовок экрана объекта — без карточки: он только называет, что открыто */
.view-head { margin: 0 0 14px; }
.view-head h2 { margin: 0; font-size: 20px; }
.view-head p { margin: 2px 0 0; }

/* Карточка объекта в списке. Кнопка целиком: нажимается любой её угол */
.park-card {
  display: grid;
  gap: 10px;
  width: 100%;
  text-align: left;
  padding: 14px 15px;
  cursor: pointer;
  color: inherit;
  font: inherit;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--panel-2);
  transition: transform .15s ease, border-color .15s ease;
}

.park-card:hover { transform: translateY(-2px); border-color: var(--accent); }
.park-info { display: flex; align-items: center; gap: 12px; min-width: 0; }
.park-meta { min-width: 0; }
.park-meta h3 { margin: 0; font-size: 15px; overflow-wrap: anywhere; }
.park-meta small { color: var(--muted); overflow-wrap: anywhere; }
.park-pills { display: flex; flex-wrap: wrap; gap: 6px; }

.pill {
  font-size: 12px;
  color: var(--muted);
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(255,255,255,.05);
}

/* Плашка со значком модуля: цвет говорит, о чём строка, ещё до чтения.
   Подпись рядом со значком светлее обычной — приглушённая рядом с ярким
   пятном выглядит потухшей */
.pill-mod {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 4px;
  color: #dbe4f3;
}

/* Того, чего у объекта нет, цветом не отмечаем: подпись и значок остаются
   приглушёнными. Это не авария — красным не красим */
.pill-mod-off { color: var(--muted); }
.mod-off { --mod-color: rgba(255,255,255,.1); color: var(--muted); }

/* Раздел, раскрывающийся на месте. Закрытый показывает только суть:
   что это и сколько там всего — экран начинается с обзора, а не с полей */
.card.collapsible { padding: 0; overflow: hidden; }

.collapse-head {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  cursor: pointer;
  color: inherit;
  font: inherit;
  text-align: left;
  border: 0;
  background: none;
}

.collapse-head:hover { background: rgba(255,255,255,.03); }
.collapse-name { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600; }

/* Значение прижато к стрелке и уступает место названию, а не наоборот */
.collapse-value {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--muted);
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.collapse-chevron { flex: none; display: grid; place-items: center; color: var(--muted); }
.collapse-chevron svg { width: 18px; height: 18px; transition: transform .18s ease; }
.collapsible.open .collapse-chevron svg { transform: rotate(180deg); }

/* Отступы содержимого — те же, что у обычной карточки */
.collapse-body { padding: 0 16px 16px; }

.chip.chip-empty { opacity: .65; }

/* Тип устройства на экране расстановки. Активная вкладка красится цветом
   модуля, а не общим акцентом: цвет здесь уже означает «спикеры/камеры» */
.placement-kinds { margin-bottom: 10px; }
.chip.chip-kind.active { background: var(--mod-speakers); }
.chip.chip-kind[data-kind="camera"].active { background: var(--mod-video); }

/* Камера на плане — тот же маркер, что у спикера, только цвет модуля:
   красный занят под спикеры, фиолетовый под видео — везде в приложении */
.map-marker.map-cam { background: var(--mod-video); }
.map-cone.map-cam { color: var(--mod-video); }
.map-handle.map-cam::after { background: var(--mod-video); }

/* Нет связи — гаснет и камера. Цвет модуля задан парой классов и потому
   сильнее одиночного .map-*-off: без этих двух строк камера светилась бы
   фиолетовым, даже когда молчит */
.map-marker.map-cam.map-marker-off { background: #6b7280; }
.map-cone.map-cam.map-cone-off { color: #6b7280; }

/* Чужой тип виден, чтобы не поставить маркер на маркер, но не хватается —
   иначе его двигали бы, метя в свой */
.map-marker.map-idle { opacity: .45; cursor: default; pointer-events: none; }
.map-cone.map-idle { opacity: .4; }

/* На плане в модуле камер нажимать нечего — не изображаем кнопку */
.map-marker.map-static { cursor: default; pointer-events: none; }

/* Превью плана объекта: показываем целиком, не обрезая — по нему сверяют,
   тот ли файл загрузили. Высоту ограничиваем, чтобы карточка не разъезжалась */
.map-preview {
  margin: 4px 0 14px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-2);
  text-align: center;
}

/* Рамка ровно по картинке: слой маркеров должен совпасть с планом, а не
   с карточкой — картинка вписана по высоте и уже своего блока.
   container-type — чтобы маркеры считали размер от ширины плана, как в
   редакторе: иначе на превью они выглядели бы великанами */
/* Рамка во всю ширину карточки — как план в редакторе.
   ⛔ Ужимать её по картинке нельзя: container-type: inline-size означает
   «ширину считать без оглядки на содержимое», и у ужимающегося блока она
   выходит нулевой — вместе с картинкой. Ловилось на живом объекте:
   превью схлопывалось в полоску, а маркеры сваливались в одну точку.
   container-type нужен, чтобы маркеры считали размер от ширины плана. */
.map-preview-frame {
  position: relative;
  display: block;
  line-height: 0;
  container-type: inline-size;
}

.map-preview img {
  /* Блочная картинка во всю ширину: у строчной снизу остаётся место под
     буквы, и слой маркеров оказывался выше плана */
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  /* План часто чёрно-белый чертёж: на тёмной карточке он тонет без подложки */
  background: #fff;
}

/* Два поля в строку (порт и протокол, логин и пароль). На узком экране
   остаются два столбца: поля короткие, в один столбец форма растянулась бы */
.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

/* Координаты точки на плане: четыре коротких поля */
.field-row.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 480px) {
  .field-row.four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Галочки модулей: в столбец, чтобы длинные названия не ломали строку */
.module-checks { display: grid; gap: 8px; margin-top: 8px; }

.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
}

.check-row input { width: 18px; height: 18px; flex: none; accent-color: var(--accent); }

/* «Скоро» у неготового модуля — та же плашка, что в меню */
.check-soon {
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 999px;
  color: #fcd34d;
  background: rgba(245,158,11,.12);
}

/* Внутри формы панель кнопок не должна прилипать к последнему полю */
.form .toolbar { margin-top: 18px; }

/* Главная кнопка внутри панели живёт по правилам панели, а не сама по себе:
   иначе в одном ряду оказываются кнопки 44, 49 и 67 px — последняя потому,
   что длинная подпись переносится на две строки */
.toolbar .primary-button {
  width: auto;
  margin-top: 0;
  min-height: 46px;
  padding: 10px 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 650px) {
  .toolbar .primary-button { min-height: 44px; font-size: 13px; }
}

.journal-item .row { align-items: flex-start; }
.journal-item h3 { margin: 0; font-size: 15px; }
.journal-item small { color: var(--muted); }

.journal-status {
  font-size: 12px;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
}

.journal-status.ok { color: #8ef0ad; background: rgba(34,197,94,.12); }
.journal-status.error { color: #fca5a5; background: rgba(239,68,68,.12); }

.empty {
  padding: 22px;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--line);
  border-radius: 14px;
}

.skeleton {
  height: 86px;
  border-radius: 16px;
  background: linear-gradient(90deg, rgba(255,255,255,.04), rgba(255,255,255,.08), rgba(255,255,255,.04));
  background-size: 200% 100%;
  animation: shimmer 1.2s infinite linear;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@media (max-width: 650px) {
  .app { padding: 14px; }
  .stats, .grid { grid-template-columns: 1fr; }
  .stats { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .stat { padding: 14px 12px; }
  .stat strong { font-size: 22px; }
  .stat span { font-size: 12px; }
  /* Шапка одной строкой: слева бургер и название, справа значки режима,
     языка и выхода. Чтобы название влезло на 375px, всё в шапке компактнее */
  .topbar { gap: 8px; }
  .topbar-left { gap: 8px; }
  .topbar-right { gap: 8px; }
  .burger { width: 36px; height: 36px; }
  .topbar h1 { font-size: 17px; }
  /* Подпись показываем целиком — на узком экране она переносится, а не обрезается */
  .topbar p { font-size: 12px; white-space: normal; }
  /* Значки языка и выхода — одного размера */
  .lang-trigger, .icon-button { width: 32px; height: 32px; }
  .lang-globe { width: 16px; height: 16px; }
  .icon-button svg { width: 16px; height: 16px; }
  .badge-mode { padding: 4px 7px; font-size: 10px; }
  .section-title { align-items: center; }
  /* Телефон: значок остаётся в углу, поэтому плашка мельче, а заголовку
     разрешено переноситься на вторую строку — в одну строку с двумя
     колонками под значок он на 375px уже не помещается */
  .recorder-card .section-title { gap: 8px; }

  .recorder-card .section-title h2 {
    font-size: 17px;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  /* Плашка и её двойник слева меняются вместе — иначе колонки разъедутся */
  .recorder-card .section-title .badge,
  .recorder-card .section-title::before { padding: 4px 7px; font-size: 10px; }
  /* Телефон: в строку помещаются две кнопки, нечётная последняя — во всю ширину.
     Шрифт мельче, чтобы подписи оставались в одну строку на 375px */
  .toolbar {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    margin-bottom: 14px;
  }
  .toolbar > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  .back-button, .ghost-button { min-height: 44px; padding: 8px 10px; font-size: 13px; }
  .record-button { width: 152px; height: 152px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
