/* ============================================================
   Единая система UI-компонентов (кнопки, модалки, формы, тосты,
   таблицы, пустые состояния) — заменяет 5+ параллельных реализаций,
   выросших независимо на разных страницах (.dm-btn, .btn-light/dark,
   .btn-login, .sr-action-btn/.table-btn-action, confirmModal и т.д.)

   Визуальный язык частично портирован с Velora UI (velora.colorlib.com,
   MIT) на чистый CSS — transition-кривая, shimmer/glow-эффекты,
   паттерн модалки. Velora сама не даёт готовых таблиц/форм/дашбордов
   (чисто лендинг-набор), поэтому компоненты спроектированы с нуля под
   админ-панель, Velora дала только тон: премиальный тёмный, сдержанные
   анимации, никакого кислотного неона.

   Подключать ПОСЛЕ main.css и custom.css на каждой странице — этот
   файл обязан выигрывать каскад у старых разрозненных стилей.
   ============================================================ */

:root {
  /* Единая transition-кривая (как у Velora) - используется везде ниже,
     вместо разных ease/ease-in-out/линейных переходов на разных страницах. */
  --ease-premium: cubic-bezier(0.2, 0.8, 0.2, 1);
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 280ms;
}

/* ---------- 1. Кнопки ----------
   Одна база .btn + модификаторы роли + модификаторы размера.
   Заменяет: .btn-custom-*, голый .btn-light/.btn-dark, .dm-btn-*,
   .btn-login, .btn-quick-action, .btn-date-picker, .btn-copy-script,
   .btn-confirm-cancel/-ok. */

.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 18px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font: 600 13.5px/1 var(--bs-body-font-family);
  letter-spacing: -0.005em;
  color: var(--text-main);
  background: transparent;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: background-color var(--dur-base) var(--ease-premium),
              border-color var(--dur-base) var(--ease-premium),
              color var(--dur-base) var(--ease-premium),
              transform var(--dur-fast) var(--ease-premium),
              box-shadow var(--dur-base) var(--ease-premium);
}
.ui-btn i, .ui-btn .bi { font-size: 15px; line-height: 1; }
.ui-btn:active:not(:disabled) { transform: scale(0.97); }
.ui-btn:focus-visible { outline: 2px solid var(--brand-lime); outline-offset: 2px; }
.ui-btn:disabled { opacity: 0.4; cursor: default; }

/* Размеры */
.ui-btn-sm { height: 32px; padding: 0 13px; font-size: 12.5px; gap: 6px; }
.ui-btn-lg { height: 46px; padding: 0 24px; font-size: 14.5px; }
.ui-btn-icon { width: 36px; height: 36px; padding: 0; border-radius: var(--radius-md); }
.ui-btn-icon.ui-btn-sm { width: 30px; height: 30px; }
.ui-btn-icon.ui-btn-lg { width: 44px; height: 44px; border-radius: var(--radius-lg); }

/* ---------- Роли ----------
   Визуальная масса распределена явно по важности действия, а не одной
   шкалой "насыщенность рамки": primary - единственная сплошная заливка на
   экране (взгляд сразу находит основное действие); secondary - видимая,
   но нейтральная поверхность для парных действий ("Сохранить + Отмена"
   одного веса, когда оба допустимы); ghost - вообще без фона/рамки, чистый
   текст тише окружения (используется для некритичных действий в рядах
   таблиц и как "Отмена" рядом с primary - не конкурирует с ним за взгляд);
   danger - отдельный акцент, не пересекается по весу с primary. */
.ui-btn-primary {
  background: var(--brand-lime);
  color: #0a0a0a;
  font-weight: 700;
  box-shadow: 0 1px 0 rgba(255,255,255,.4) inset, 0 6px 16px rgba(0,0,0,.28);
}
.ui-btn-primary:hover:not(:disabled) { background: var(--brand-lime-hover); box-shadow: 0 1px 0 rgba(255,255,255,.5) inset, 0 8px 22px rgba(229,229,229,.18); transform: translateY(-1px); }
.ui-btn-primary:active:not(:disabled) { transform: translateY(0) scale(0.97); }

.ui-btn-secondary {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-main);
}
.ui-btn-secondary:hover:not(:disabled) { background: rgba(255, 255, 255, 0.14); }

/* Ghost - текстовая кнопка без фона и рамки. Не "более тёмная кнопка",
   а намеренно другая визуальная категория: читается как ссылка на
   действие, не как форма, с которой primary должна была бы конкурировать
   по весу. Подчёркивание на hover - единственный сигнал интерактивности. */
.ui-btn-ghost {
  background: transparent;
  color: var(--text-muted-green);
  padding-left: 10px;
  padding-right: 10px;
}
.ui-btn-ghost:hover:not(:disabled) { color: var(--text-main); background: rgba(255, 255, 255, 0.055); }

.ui-btn-danger {
  background: transparent;
  color: var(--sys-red);
  padding-left: 10px;
  padding-right: 10px;
}
.ui-btn-danger:hover:not(:disabled) { background: var(--sys-red-bg); }
/* Иконка-кнопка danger (удаление строки в таблице) - нужен видимый hover
   без текста рядом, чистый прозрачный фон слишком незаметен в таблице. */
.ui-btn-danger.ui-btn-icon { color: var(--text-muted-green); }
.ui-btn-danger.ui-btn-icon:hover:not(:disabled) { color: var(--sys-red); background: var(--sys-red-bg); }

.ui-btn-success {
  background: var(--sys-green-bg);
  color: var(--sys-green);
}
.ui-btn-success:hover:not(:disabled) { background: var(--sys-green); color: #0a2a12; }

/* Loading-состояние (Velora Stateful Button: спиннер вместо текста, не меняя размер кнопки) */
.ui-btn.is-loading { color: transparent !important; pointer-events: none; position: relative; }
.ui-btn.is-loading::after {
  content: "";
  position: absolute;
  width: 15px; height: 15px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  color: var(--text-main);
  animation: ui-spin 0.7s linear infinite;
}
.ui-btn-primary.is-loading::after { color: #0a0a0a; }
@keyframes ui-spin { to { transform: rotate(360deg); } }

/* Shimmer-эффект (Velora Shimmer Button) - только на primary, сдержанно,
   запускается hover, не постоянно (владелец просил без лишней анимации). */
.ui-btn-primary {
  position: relative;
  overflow: hidden;
}
.ui-btn-primary::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 30%, rgba(255,255,255,.45) 50%, transparent 70%);
  transform: translateX(-120%);
  transition: none;
}
.ui-btn-primary:hover:not(:disabled)::before { animation: ui-shimmer 900ms var(--ease-premium); }
@keyframes ui-shimmer { to { transform: translateX(120%); } }
@media (prefers-reduced-motion: reduce) {
  .ui-btn-primary:hover:not(:disabled)::before { animation: none; }
}

/* ---------- 2. Модалки ----------
   Единый открыватель/закрыватель через JS (см. components.js),
   фиксированная структура: .ui-modal-overlay > .ui-modal > header/body/footer.
   Заменяет: голый Bootstrap modal, confirmModal(), кастомные domainModal/insp-modal. */

.ui-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1050;
  place-items: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-premium);
}
.ui-modal-overlay.is-open { display: grid; opacity: 1; }

.ui-modal {
  width: min(600px, 100%);
  /* calc(100vh - ...) здесь НЕ годится: custom.css ставит html { zoom:
     1.1875 } на весь сайт, а Chrome считает vh от немасштабированного
     layout viewport, завышая её в те же 1.1875 раза относительно
     реального экрана - модалка вылезала за низ/верх окна (баг, тот же
     принцип уже документирован для .infra-embed в infra-styles.css,
     решался через fixed+top/bottom; здесь модалка подвижной высоты,
     поэтому делим константу на известный zoom-фактор явно). */
  max-height: min(860px, calc((100vh - 48px) / 1.1875));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--card-background);
  border: 1px solid var(--border-light);
  border-radius: 20px;
  box-shadow: 0 24px 64px rgba(0,0,0,.5), 0 2px 0 rgba(255,255,255,.03) inset;
  transform: translateY(14px) scale(0.985);
  opacity: 0;
  transition: transform var(--dur-slow) var(--ease-premium), opacity var(--dur-slow) var(--ease-premium);
}
.ui-modal-overlay.is-open .ui-modal { transform: none; opacity: 1; }
/* infra-app.js рендерит свои модалки через замену innerHTML (элемент либо
   есть в DOM целиком, либо отсутствует) - там нет отдельного .is-open
   переключателя, как в остальном сайте. .overlay - его собственный класс
   оверлея (infra-styles.css), сразу считаем .ui-modal внутри открытым. */
.overlay .ui-modal { transform: none; opacity: 1; }
.ui-modal-sm { width: min(440px, 100%); }
.ui-modal-lg { width: min(800px, 100%); }
.ui-modal-xl { width: min(1080px, 100%); }

/* Шапка без нижней линии - разделение заголовка и тела идёт через
   пространство (32px до первого поля), не через видимую границу. Линия
   на каждом шве модалки - то самое "перегружено", о чём шла речь: здесь
   остаётся ровно одна граница (внешний контур модалки), а не три. */
.ui-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 22px 28px 0;
  flex: none;
}
.ui-modal-title { margin: 0; font: 700 18.5px/1.3 var(--display-font); color: var(--text-main); letter-spacing: -0.015em; }
.ui-modal-subtitle { margin: 4px 0 0; font-size: 13px; color: #a3a3a3; line-height: 1.45; }
.ui-modal-close {
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255, 95, 87, .55); border-radius: 10px;
  background: rgba(255, 95, 87, .2); color: #ff6b63; font-size: 15px;
  cursor: pointer; flex: none;
  margin-top: -2px; margin-right: -4px;
  transition: background-color var(--dur-fast) var(--ease-premium), color var(--dur-fast) var(--ease-premium);
}
/* Красный крестик - единственный (вместе с «Отмена») способ закрыть окно. */
.ui-modal-close:hover { background: #ef4444; border-color: #ef4444; color: #fff; }
.ui-modal-close:focus-visible { outline: 2px solid #f87171; outline-offset: 2px; }

.ui-modal-body { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 28px 20px; }

/* Футер без верхней линии - приподнят лёгким градиентом-«тенью» от
   контента сверху, который виден только пока есть что скроллить (вместо
   постоянной жёсткой линии). Сама граница убрана - действия просто
   получают больше воздуха снизу и явную асимметрию primary/ghost. */
.ui-modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 28px 20px;
  flex: none;
  position: relative;
}
.ui-modal-footer::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: -14px;
  height: 14px;
  background: linear-gradient(to bottom, transparent, rgba(0,0,0,.14));
  pointer-events: none;
}
.ui-modal-footer.is-split { justify-content: space-between; }

/* Danger-модалка (подтверждения удаления) - тот же .ui-modal, акцент на заголовке */
.ui-modal-danger .ui-modal-title { color: var(--sys-red); }

/* ---------- 3. Поля ввода / select / textarea ----------
   Единый .ui-field на замену .form-control-custom/.dns-in/.login-input/.sx-input.
   Лейбл - обычный текст, не КАПС с letter-spacing (это главный маркер
   "админской" типографики - у продуктовых форм лейбл такого же регистра,
   как и остальной интерфейс, просто чуть тише по цвету и меньше по кеглю). */

.ui-label {
  display: block;
  margin-bottom: 5px;
  font: 500 13px/1.3 var(--bs-body-font-family);
  color: #b4b4b4;
}

.ui-field {
  display: block;
  width: 100%;
  height: 40px;
  padding: 0 14px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text-main);
  font: 500 14.5px/1.4 var(--bs-body-font-family);
  transition: border-color var(--dur-base) var(--ease-premium),
              box-shadow var(--dur-base) var(--ease-premium),
              background-color var(--dur-base) var(--ease-premium);
}
textarea.ui-field { height: auto; min-height: 60px; padding: 10px 14px; resize: vertical; }
.ui-field::placeholder { color: #767676; opacity: 1; }
.ui-field:hover:not(:focus):not(:disabled) { border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.05); }
.ui-field:focus {
  outline: none;
  border-color: var(--brand-lime);
  background: rgba(255, 255, 255, 0.055);
  box-shadow: 0 0 0 4px rgba(229, 229, 229, 0.1);
}
.ui-field.is-invalid { border-color: var(--sys-red); }
.ui-field.is-invalid:focus { box-shadow: 0 0 0 4px rgba(248, 113, 113, 0.14); }
.ui-field:disabled { opacity: 0.5; cursor: not-allowed; }

.ui-field-error {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12.5px;
  color: var(--sys-red);
}

select.ui-field {
  appearance: none;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23f5f5f5' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 10px;
  padding-right: 38px;
}

.ui-field-group { position: relative; }
.ui-field-group .ui-field { padding-left: 40px; }
.ui-field-group-icon {
  position: absolute;
  left: 14px; top: 50%;
  transform: translateY(-50%);
  color: var(--text-sidebar-muted);
  pointer-events: none;
  font-size: 15px;
}

/* Чекбокс - на замену нативным <input type="checkbox"> без стилей
   (браузерный виджет поверх тёмной темы выглядит инородно). Разметка:
   <label class="ui-check"><input type="checkbox"><span class="ui-check-box"></span><span>текст</span></label> */
.ui-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
  user-select: none;
}
.ui-check input[type="checkbox"] { position: absolute; width: 1px; height: 1px; opacity: 0; }
.ui-check-box {
  flex: none;
  width: 20px; height: 20px;
  margin-top: 1px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: var(--nested-block-bg);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color var(--dur-fast) var(--ease-premium),
              border-color var(--dur-fast) var(--ease-premium),
              box-shadow var(--dur-fast) var(--ease-premium);
}
.ui-check-box::after {
  content: "";
  width: 11px; height: 7px;
  border-left: 2px solid #0a0a0a;
  border-bottom: 2px solid #0a0a0a;
  transform: rotate(-45deg) scale(0);
  transition: transform var(--dur-fast) var(--ease-premium);
}
.ui-check input:checked ~ .ui-check-box {
  background: var(--brand-lime);
  border-color: var(--brand-lime);
}
.ui-check input:checked ~ .ui-check-box::after { transform: rotate(-45deg) scale(1); }
.ui-check input:focus-visible ~ .ui-check-box { outline: 2px solid var(--brand-lime); outline-offset: 2px; }
.ui-check:hover .ui-check-box { border-color: rgba(255, 255, 255, 0.4); }
.ui-check-label { font-size: 13.5px; color: var(--text-main); line-height: 1.4; }
.ui-check-label small, .ui-check-hint { display: block; margin-top: 2px; font-size: 12px; color: var(--text-muted-green); font-weight: 400; }

/* Инфо-плашки (замена плоских однотонных .dm-info/metric-грид по всем
   модалкам) - лёгкая поверхность на тон светлее тела модалки + тонкая
   граница, чтобы не сливаться с фоном и друг с другом. */
.ui-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}
.ui-info-cell {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-md);
  padding: 10px 13px;
  transition: background-color var(--dur-base) var(--ease-premium), border-color var(--dur-base) var(--ease-premium);
}
.ui-info-cell:hover { background: rgba(255, 255, 255, 0.065); border-color: rgba(255, 255, 255, 0.12); }
.ui-info-label { display: block; font-size: 12px; font-weight: 500; color: #9b9b9b; margin-bottom: 4px; }
.ui-info-value { font-size: 13.5px; font-weight: 600; color: var(--text-main); word-break: break-word; }

/* Группа полей внутри модалки/страницы - заменяет .ui-section с рамкой.
   Иерархия "модалка → группа → поле" теперь строится ЦЕЛИКОМ отступом и
   типографикой, без единой дополнительной рамки внутри тела - рамка
   есть только у самой модалки снаружи. Это и есть принципиальная разница
   с прошлой итерацией: не "рамка светлее/темнее", а рамки нет вовсе. */
.ui-form-group + .ui-form-group { margin-top: 12px; }
.ui-form-group-title {
  margin: 0 0 10px;
  font-size: 12.5px;
  font-weight: 700;
  color: #9b9b9b;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ui-form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 12px; }
.ui-form-row + .ui-form-row { margin-top: 10px; }
.ui-form-row.cols-2 { grid-template-columns: 1fr 1fr; }
.ui-form-row.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
.ui-form-row.cols-8-4 { grid-template-columns: 2fr 1fr; }
@media (max-width: 560px) {
  .ui-form-row.cols-2, .ui-form-row.cols-3, .ui-form-row.cols-8-4 { grid-template-columns: 1fr; }
}
.ui-form-hint { margin: 6px 0 0; font-size: 12.5px; color: #8a8a8a; line-height: 1.5; }

/* Вкладки внутри модалки - для форм, которые физически не помещаются без
   скролла одним экраном (установка ноды и т.п.): контент распределяется
   по шагам вместо того, чтобы ужимать всё в один длинный скролл. Все поля
   остаются в DOM на неактивных вкладках (просто hidden), чтобы сбор
   формы через querySelectorAll('[data-field]') не терял значения. */
.ui-modal-tabs {
  display: flex;
  gap: 4px;
  padding: 0 28px;
  margin-top: 14px;
  border-bottom: 1px solid var(--border-light);
  flex: none;
}
.ui-modal-tab {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 4px;
  margin-bottom: -1px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: #8a8a8a;
  font: 600 13px/1 var(--bs-body-font-family);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-premium), border-color var(--dur-fast) var(--ease-premium);
}
.ui-modal-tab + .ui-modal-tab { margin-left: 14px; }
.ui-modal-tab i { font-size: 13.5px; }
.ui-modal-tab:hover { color: var(--text-main); }
.ui-modal-tab.on { color: var(--text-main); border-bottom-color: var(--brand-lime); }

/* ---------- 4. Тосты / алерты ----------
   Заменяет: toast() с инлайновыми хардкод-цветами, .dm-err/.dm-warn,
   .action-popover, голый text-danger. Все цвета теперь --sys-* переменные. */

.ui-toast-stack {
  position: fixed;
  bottom: 20px; right: 20px;
  z-index: 1100;
  display: flex; flex-direction: column; gap: 10px;
  max-width: min(360px, calc(100vw - 40px));
}
.ui-toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--card-background);
  border: 1px solid var(--border-light);
  box-shadow: var(--shadow-md);
  font-size: 13px;
  color: var(--text-main);
  transform: translateX(8px);
  opacity: 0;
  animation: ui-toast-in var(--dur-slow) var(--ease-premium) forwards;
}
@keyframes ui-toast-in { to { transform: none; opacity: 1; } }
.ui-toast.is-leaving { animation: ui-toast-out var(--dur-base) var(--ease-premium) forwards; }
@keyframes ui-toast-out { to { transform: translateX(8px); opacity: 0; } }
.ui-toast-icon { flex: none; font-size: 16px; line-height: 1.2; }
.ui-toast-ok { border-color: rgba(74, 222, 128, 0.3); }
.ui-toast-ok .ui-toast-icon { color: var(--sys-green); }
.ui-toast-error { border-color: rgba(248, 113, 113, 0.3); }
.ui-toast-error .ui-toast-icon { color: var(--sys-red); }
.ui-toast-warn { border-color: rgba(251, 191, 36, 0.3); }
.ui-toast-warn .ui-toast-icon { color: var(--sys-orange); }

/* Постоянный alert-блок внутри страницы/модалки (замена .dm-err/.dm-warn,
   теперь единый для всего сайта, не только domains.html) */
.ui-alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  font-size: 13px;
  line-height: 1.5;
  margin-bottom: 14px;
}
.ui-alert-icon { flex: none; font-size: 16px; margin-top: 1px; }
.ui-alert-error { background: var(--sys-red-bg); color: var(--sys-red); }
.ui-alert-warn { background: var(--sys-orange-bg); color: var(--sys-orange); }
.ui-alert-success { background: var(--sys-green-bg); color: var(--sys-green); }
.ui-alert-actions { margin-top: 8px; }

/* ---------- 5. Пустые состояния ----------
   Заменяет 4 разных паттерна (<tr><td>, <p>, голый text, text-danger). */

.ui-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 48px 20px;
  text-align: center;
  color: var(--text-muted-green);
}
.ui-empty-icon { font-size: 34px; color: var(--text-sidebar-muted); opacity: 0.6; }
.ui-empty-title { font-size: 14px; font-weight: 700; color: var(--text-main); }
.ui-empty-hint { font-size: 13px; color: var(--text-muted-green); }
.ui-empty.is-error .ui-empty-icon,
.ui-empty.is-error .ui-empty-title { color: var(--sys-red); }

/* ---------- 6. Карточки с hover-акцентом (Velora Spotlight/Border Beam,
   упрощённый vanilla-CSS перенос) - используется на карточках нод/серверов. */

.ui-card-spotlight {
  position: relative;
  overflow: hidden;
}
.ui-card-spotlight::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--spot-angle, 0deg), transparent 0deg, rgba(229,229,229,.35) 20deg, transparent 60deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-premium);
  pointer-events: none;
}
.ui-card-spotlight:hover::before { opacity: 1; animation: ui-beam-rotate 3.5s linear infinite; }
@keyframes ui-beam-rotate { to { --spot-angle: 360deg; } }
@media (prefers-reduced-motion: reduce) {
  .ui-card-spotlight:hover::before { animation: none; opacity: 0; }
}
@property --spot-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }

/* ---------- 7. Карточки страниц (Settings/Billing/Servers и т.п.) ----------
   Базовый .card (main.css) даёт только фон+радиус+тень, без границы - на
   фоне страницы (#050505) и с полями ярче самой карточки (--nested-block-bg
   #202020 > --card-background #111111) контраст выходит слабым и иерархия
   "страница → карточка → поле" не читается. Добавляем тонкую границу и
   чуть более выраженную тень, не трогая сам токен --card-background (он
   используется в других местах за пределами форм). */
.card {
  border: 1px solid rgba(255, 255, 255, 0.06);
}
.card .card-title { letter-spacing: -0.01em; }

/* ---------- 8. Кастомный select (замена нативного) ----------
   Нативный <select> открывает список опций браузерным рендером ОС - на
   тёмной теме получается яркий синий хайлайт на белом фоне (видно на
   скриншоте домена), который невозможно перекрасить из CSS. Combobox-
   паттерн: кнопка .ui-select-btn + список .ui-select-list, управляется
   JS (ui-select.js), полностью доступен с клавиатуры (role=listbox,
   стрелки, Enter, Escape, type-ahead), под капотом держит синхронный
   нативный <select> (hidden, не display:none) для форм/сборщиков
   querySelectorAll('[data-field]') - переключать его JS не нужно. */
.ui-select { position: relative; }
.ui-select select { position: absolute; opacity: 0; pointer-events: none; width: 1px; height: 1px; }
.ui-select-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  height: 40px;
  padding: 0 14px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text-main);
  font: 500 14px/1.4 var(--bs-body-font-family);
  cursor: pointer;
  text-align: left;
  transition: border-color var(--dur-base) var(--ease-premium), background-color var(--dur-base) var(--ease-premium);
}
.ui-select-btn .ui-select-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-select-btn .ui-select-value.is-placeholder { color: #767676; }
.ui-select-btn i { flex: none; font-size: 11px; color: #9b9b9b; transition: transform var(--dur-fast) var(--ease-premium); }
.ui-select-btn:hover { border-color: rgba(255, 255, 255, 0.26); background: rgba(255, 255, 255, 0.05); }
.ui-select.is-open .ui-select-btn { border-color: var(--brand-lime); box-shadow: 0 0 0 4px rgba(229, 229, 229, 0.1); }
.ui-select.is-open .ui-select-btn i { transform: rotate(180deg); }
.ui-select-btn:focus-visible { outline: 2px solid var(--brand-lime); outline-offset: 2px; }
.ui-select-list {
  position: absolute;
  z-index: 60;
  left: 0; right: 0; top: calc(100% + 6px);
  max-height: 260px;
  overflow-y: auto;
  padding: 6px;
  background: #161616;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-md);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
  display: none;
}
.ui-select.is-open .ui-select-list { display: block; animation: ui-select-in 120ms var(--ease-premium); }
@keyframes ui-select-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.ui-select-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 8px;
  color: var(--text-main);
  font-size: 13.5px;
  cursor: pointer;
}
.ui-select-opt:hover, .ui-select-opt.is-active { background: rgba(255, 255, 255, 0.08); }
.ui-select-opt.is-selected { color: var(--brand-lime); font-weight: 600; }
.ui-select-opt.is-selected::after { content: "\f633"; font-family: "bootstrap-icons"; margin-left: auto; font-size: 12px; }

/* ---------- 9. Карточки выбора (протокол, роль и т.п.) ----------
   Заменяет безжизненные .choice (eле видная рамка, нет hover/focus,
   подпись --muted #858585 почти сливается с фоном карточки --card-2). */
.ui-choice-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 560px) { .ui-choice-grid { grid-template-columns: 1fr; } }
.ui-choice-card {
  display: block;
  text-align: left;
  width: 100%;
  padding: 14px 16px;
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.035);
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-premium),
              background-color var(--dur-base) var(--ease-premium),
              box-shadow var(--dur-base) var(--ease-premium),
              transform var(--dur-fast) var(--ease-premium);
}
.ui-choice-card:hover { border-color: rgba(255, 255, 255, 0.28); background: rgba(255, 255, 255, 0.06); }
.ui-choice-card:active { transform: scale(0.99); }
.ui-choice-card:focus-visible { outline: 2px solid var(--brand-lime); outline-offset: 2px; }
.ui-choice-card.on {
  border-color: var(--brand-lime);
  background: rgba(229, 229, 229, 0.07);
  box-shadow: 0 0 0 1px var(--brand-lime);
}
.ui-choice-title { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; font-size: 14px; font-weight: 700; color: var(--text-main); }
.ui-choice-card.on .ui-choice-title::after { content: "\f633"; font-family: "bootstrap-icons"; margin-left: auto; color: var(--brand-lime); font-size: 15px; }
.ui-choice-text { font-size: 12.5px; line-height: 1.5; color: #a3a3a3; }

/* ---------- 10. Пошаговый мастер (step indicator) ----------
   "Шаг N из M" + названия шагов с прогрессом - для длинных форм, которые
   не умещаются одним экраном даже при компактной вёрстке (установка
   ноды: сервер → протокол → публикация → проверка). Живёт внутри
   .ui-modal, между .ui-modal-header и .ui-modal-body - сам не скроллится,
   скроллится только body под ним. */
.ui-modal-wizard .ui-modal-body { padding-top: 18px; }
.ui-steps { padding: 14px 28px 0; flex: none; }
.ui-steps-kicker { display: block; margin-bottom: 10px; font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #9b9b9b; }
.ui-steps-track { display: flex; align-items: center; }
.ui-steps-item { display: flex; align-items: center; gap: 8px; flex: none; }
.ui-steps-dot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%;
  font-size: 12px; font-weight: 700;
  background: rgba(255, 255, 255, 0.06);
  color: #9b9b9b;
  border: 1px solid rgba(255, 255, 255, 0.14);
  transition: background-color var(--dur-base) var(--ease-premium), border-color var(--dur-base) var(--ease-premium), color var(--dur-base) var(--ease-premium);
}
.ui-steps-label { font-size: 13px; font-weight: 600; color: #9b9b9b; white-space: nowrap; }
.ui-steps-item.on .ui-steps-dot { background: var(--brand-lime); border-color: var(--brand-lime); color: #0a0a0a; }
.ui-steps-item.on .ui-steps-label { color: var(--text-main); }
.ui-steps-item.done .ui-steps-dot { background: rgba(229, 229, 229, 0.16); border-color: rgba(229, 229, 229, 0.4); color: var(--brand-lime); }
.ui-steps-item.done .ui-steps-label { color: #c9c9c9; }
.ui-steps-line { flex: 1; height: 1px; background: rgba(255, 255, 255, 0.12); margin: 0 10px; min-width: 16px; }
@media (max-width: 680px) { .ui-steps-label { display: none; } }

/* Кнопка-инфо рядом с заголовком группы (напр. "Балансировка хоста ⓘ") */
.ui-info-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  margin-left: 6px;
  border: 0; border-radius: 50%;
  background: transparent;
  color: #9b9b9b;
  cursor: pointer;
  font-size: 13px;
  transition: color var(--dur-fast) var(--ease-premium), background-color var(--dur-fast) var(--ease-premium);
}
.ui-info-btn:hover { color: var(--brand-lime); background: rgba(255, 255, 255, 0.08); }
.ui-info-btn:focus-visible { outline: 2px solid var(--brand-lime); outline-offset: 2px; }

/* ---------- Доступность ---------- */
@media (prefers-reduced-motion: reduce) {
  .ui-btn, .ui-modal, .ui-modal-overlay, .ui-toast, .ui-field, .ui-btn-primary::before, .ui-select-list, .ui-choice-card {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}
