.logo { display: block; }

.card {
    background: var(--surface);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}

.back-link {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.85rem;
    display: inline-block;
    margin-bottom: 20px;
}
.back-link:hover { color: var(--accent); }

.field { margin-bottom: 16px; }
.field label {
    display: block;
    font-size: 0.85rem;
    color: var(--text-muted);
    margin-bottom: 6px;
}
.field input {
    width: 100%;
    background: var(--surface-strong);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px 14px;
    color: var(--text-primary);
    font-size: 0.95rem;
    outline: none;
    transition: border-color 0.2s ease;
}
.field input:focus { border-color: var(--accent); }
.field textarea {
    width: 100%;
    background: var(--surface-strong);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px 14px;
    color: var(--text-primary);
    font-size: 0.9rem;
    font-family: inherit;
    outline: none;
    resize: none;
    transition: border-color 0.2s ease;
}
.field textarea:focus { border-color: var(--accent); }
/* input[type=file] рисует свою кнопку нативным виджетом ОС — background/
   color/border на самом <input> её не красят, только контейнер вокруг.
   ::file-selector-button (и старый вебкитовый alias для Safari) нужно
   стилизовать отдельно, иначе на тёмной теме получаем белую системную
   кнопку поверх тёмного поля. */
.field input[type="file"] {
    padding: 8px;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.field input[type="file"]::file-selector-button,
.field input[type="file"]::-webkit-file-upload-button {
    margin-right: 12px;
    padding: 8px 14px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--surface-strong);
    color: var(--text-primary);
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
    transition: all 0.2s ease;
}
.field input[type="file"]:hover::file-selector-button,
.field input[type="file"]:hover::-webkit-file-upload-button {
    border-color: rgba(255, 255, 255, 0.22);
}
.field select {
    width: 100%;
    background: var(--surface-strong);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 12px 14px;
    color: var(--text-primary);
    font-size: 0.95rem;
    outline: none;
}
.field select:focus { border-color: var(--accent); }
/* Браузер применяет color к <option> (наследуется), но НЕ применяет
   background открывающегося попапа — тот рисуется нативно поверх ОС.
   Без явного цвета на самих <option> получаем белый текст на белом
   нативном фоне попапа. background здесь — сплошной, не полупрозрачный
   var(--surface-strong): попап рисуется вне нашего тёмного фона
   страницы, полупрозрачность её не подхватит. */
.field select option {
    background: var(--background);
    color: var(--text-primary);
}
.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; }
.field-hint { font-size: 0.78rem; color: var(--text-muted); margin-top: 4px; }
/* Ссылки внутри подсказок (.field-hint/.ssh-config-hint — обе просто
   приглушённый текст, ссылка внутри до сих пор нигде не стилизовалась
   и падала в дефолт браузера — синий, подчёркнутый, выбивался из темы,
   живой репорт). Тот же цвет/поведение, что у .back-link. */
.field-hint a, .ssh-config-hint a {
    color: var(--text-secondary);
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 2px;
}
.field-hint a:hover, .ssh-config-hint a:hover {
    color: var(--accent);
    text-decoration-color: var(--accent);
}

.switch { position: relative; width: 42px; height: 24px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
    position: absolute; cursor: pointer; inset: 0;
    background: var(--surface-strong);
    border: 1px solid var(--border);
    border-radius: 24px;
    transition: 0.2s;
}
.slider::before {
    content: ""; position: absolute; height: 18px; width: 18px;
    left: 2px; bottom: 2px;
    background: var(--text-muted);
    border-radius: 50%;
    transition: 0.2s;
}
.switch input:checked + .slider { background: rgba(20, 184, 166, 0.5); border-color: var(--accent); }
.switch input:checked + .slider::before { transform: translateX(18px); background: var(--accent); }

.btn {
    display: inline-block;
    text-align: center;
    padding: 13px;
    border-radius: 10px;
    font-weight: 700;
    font-size: 0.95rem;
    /* <button> и <a> по умолчанию наследуют РАЗНЫЙ line-height (у <a> —
       унаследованный от body/theme.css, у <button> — свой, платформенный)
       — без явного значения тут <a class="btn"> визуально выше, чем
       <button class="btn"> рядом с тем же padding. Фиксируем одно
       значение для обоих типов элементов. */
    line-height: 1.2;
    font-family: inherit;
    cursor: pointer;
    border: 1px solid var(--border);
    background: var(--surface-strong);
    color: var(--text-primary);
    text-decoration: none;
    transition: all 0.2s ease;
}
.btn:hover { border-color: rgba(255, 255, 255, 0.22); transform: translateY(-1px); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn-primary {
    background: rgba(20, 184, 166, 0.85);
    border-color: rgba(20, 184, 166, 0.6);
    color: #0b0c10;
    box-shadow: 0 8px 24px -8px rgba(20, 184, 166, 0.6);
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); box-shadow: none; }
.btn-ghost {
    background: transparent;
    border-color: var(--border);
    color: var(--accent);
}
.btn-ghost:hover {
    background: var(--surface);
    /* border-color совпадает с color (оба --accent-hover, не --accent)
       — иначе текст уходит в синий (--accent-hover), а обводка остаётся
       бирюзовой (--accent), два разных акцентных оттенка одновременно
       не сочетаются. .btn-primary:hover рядом уже делает так же (и
       background, и border-color — один и тот же --accent-hover). */
    border-color: var(--accent-hover);
    color: var(--accent-hover);
}
.btn-block { display: block; width: 100%; }
.btn-sm { padding: 7px 12px; font-size: 0.8rem; border-radius: 8px; }
.btn-danger { color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: rgba(255, 90, 90, 0.1); border-color: var(--danger); color: var(--danger); }
.btn-row { display: flex; gap: 12px; margin-top: 28px; }
.btn-row .btn { flex: 1; }

.error-msg {
    color: var(--danger);
    font-size: 0.85rem;
    margin-top: 14px;
    display: none;
}

.ssh-config-hint { font-size: 0.82rem; color: var(--text-muted); margin-bottom: 16px; }

/* Опциональная строка-чекбокс в hk-dialog (hkConfirmCheckbox, dialog.js)
   — своя лёгкая пара классов, а не .toggle-row/.switch (те определены
   только в node_server.php, а dialog.js подключается и на других
   страницах, см. докблок файла — им бы этот стиль не достался). */
.hk-dialog-checkbox-row {
    display: flex; align-items: center; gap: 10px;
    margin: 4px 0 20px; font-size: 0.9rem; color: var(--text-secondary); cursor: pointer;
}
.hk-dialog-checkbox-row input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; flex-shrink: 0; }

/* Модалка поверх страницы — используется как страничными "удалить
   сервер/пользователя" модалками, так и общим диалогом dialog.js
   (замена confirm()/prompt()/alert(), см. этот файл). */
.user-modal-overlay {
    position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6);
    display: flex; align-items: center; justify-content: center;
    padding: 20px; z-index: 100;
}
.user-modal { width: 100%; max-width: 420px; }
/* Прогресс-диалог (hkProgress — лог apt/SSE) шире обычного confirm/
   prompt/alert: короткий текст подтверждения нормально смотрится на
   420px, а строки лога на такой ширине переносятся посреди слова. */
.user-modal.hk-progress-modal { max-width: 680px; }
/* TrafficGuard-модалка (node_server.php, #tg-modal) — своя кастомная
   модалка (textarea, тумблер, select, статус), 420px было тесно для
   textarea с URL блок-листов (живой репорт). */
#tg-modal { max-width: 600px; }
/* "Быстрые действия" (node_server.php, #fw-quick-modal) — три бывших
   отдельных блока (ICMP/политика/Серверы проекта) внутри одной модалки,
   каждый со своей формой. 640px всё ещё ломало строку .fw-policy-row
   (лейбл + бейдж + select 220px + кнопка "Применить" не помещались —
   живой репорт), увеличено с запасом. */
#fw-quick-modal { max-width: 760px; }
.user-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 10px 0; }
.user-modal-header h3 { font-size: 1.05rem; color: var(--text-primary); }
.user-modal-close {
    background: none; border: none; color: var(--text-muted); font-size: 1.1rem; cursor: pointer; line-height: 1; padding: 4px;
    /* Крестик — не текст, кликом/драгом по нему не должно выделяться "✕"
       как строка (реальный баг, найден на живом тесте). */
    user-select: none; -webkit-user-select: none;
}
.user-modal-close:hover { color: var(--text-primary); }
.user-modal-body { padding: 8px 10px 10px; }

/* Появление/закрытие — привязано к классу .hk-anim-overlay, а не ко ВСЕМ
   .user-modal-overlay: у остальных модалок на страницах (создание
   Linux-пользователя, пока не переведена) своя логика показа (JS
   выставляет display впрямую), их это не задевает. Класс, а не id
   #hk-dialog-overlay (как было раньше) — чтобы этой же анимацией мог
   пользоваться любой другой оверлей через hkShowOverlay/hkHideOverlay
   (dialog.js), не только общий confirm/prompt/alert (например,
   TrafficGuard-модалка с кастомными полями, которые не влезают в
   hkPrompt).
   Раньше было на @keyframes-animation, запускавшейся самим переключением
   display:none→flex — у этого подхода два реальных бага (живой тест):
   1) первый кадр рендерился уже в конечном (тёмном) состоянии ДО начала
      анимации (браузер применяет "невоспроизведённую" анимацию как
      конечное состояние на первом синхронном кадре), потом резко "сбрасывал"
      в 0 и проигрывал анимацию — визуально выглядело как "тёмный, потом
      резко прозрачный, потом снова затемняется";
   2) анимации закрытия не было вообще — display:none выставлялся мгновенно.
   Заменено на transition + явный класс .hk-dialog-visible, который JS
   добавляет на следующий кадр после display:flex (см. dialog.js) —
   гарантированно стартует с opacity:0 и одинаково хорошо работает что на
   открытии, что на закрытии (класс просто снимается, transition играет
   в обратную сторону, JS ждёт её конца и только тогда прячет display).

   Третий реальный баг (живой тест, после первой правки выше — та правка
   САМА ПО СЕБЕ не помогла, см. четвёртый пункт): карточка диалога — это
   .user-modal.card, а .card даёт backdrop-filter: blur() (glass-эффект).
   Пока opacity этого элемента анимируется дробным значением, браузер не
   пересчитывает backdrop-filter покадрово — фон под карточкой рендерится
   сплошным тёмным, и только когда opacity долетает до 1, в один кадр
   "включается" настоящий блюр. Фикс — opacity больше не трогаем на самой
   карточке, только transform (scale+translate).

   Четвёртый реальный баг (тот же живой тест) — этого было НЕДОСТАТОЧНО:
   проблема не только в opacity самой карточки, а в opacity ЛЮБОГО ПРЕДКА.
   #hk-dialog-overlay анимировал свою opacity (0→1) — а дробная opacity
   на предке заставляет браузер сплющивать всё поддерево (включая
   потомка с backdrop-filter) в один изолированный compositing-слой,
   backdrop-filter в этом слое не может сэмплить настоящий фон страницы
   позади и тоже рендерится тёмным до конца анимации. Поэтому оверлей
   больше не анимирует opacity вообще — вместо этого анимируется
   background-color (сам rgba(0,0,0,0.6) едет от прозрачного к
   непрозрачному через transition на background-color, а не через
   opacity всего элемента) — визуально идентично, но не создаёт
   изолирующий слой и не ломает backdrop-filter потомка.

   Пятый реальный баг (живой тест): раз карточке нельзя было анимировать
   opacity (см. выше), на закрытии её "исчезновение" было ЧИСТО через
   transform + жёсткий display:none в JS — карточка оставалась полностью
   непрозрачной до самого обрыва. Даже подогнав таймер тютелька-в-тютельку
   под конец transition (см. dialog.js), это ощущалось как "карточка
   секунду висит неподвижно, потом резко пропадает" — глаз читает конец
   easing-кривой (скорость движения стремится к нулю) как "замерла", а
   резкий обрыв БЕЗ фейда — это отдельный, тоже заметный скачок сразу
   следом. Два "стоп-кадра" подряд.

   Шестой (он же настоящий фикс) — проверено вживую через Web Animations
   API (эмуляция очень медленной анимации + заморозка на конкретном кадре,
   скриншот на opacity≈0.15): предположение "opacity ЛЮБОЙ ценой ломает
   backdrop-filter" оказалось неполным — `will-change: opacity, transform`
   на самой карточке даёт браузеру заранее создать для неё стабильный
   compositing-слой, и backdrop-filter пересчитывается корректно кадр за
   кадром ДАЖЕ при дробной opacity этого элемента (на скриншоте с
   opacity≈0.15 — честная полупрозрачная карточка с блюром, не сплошной
   чёрный). Раньше (баги 3-4) этого хинта не было — отсюда и чёрный флэш.
   Теперь карточка снова анимирует opacity (настоящее растворение, не
   только сдвиг+масштаб) — исчезает вместе с фоном, а не зависает. */
.hk-anim-overlay {
    background-color: rgba(0, 0, 0, 0);
    transition: background-color 0.16s ease;
}
.hk-anim-overlay.hk-dialog-visible { background-color: rgba(0, 0, 0, 0.6); }
.hk-anim-overlay .user-modal {
    will-change: transform, opacity;
    opacity: 0;
    transform: scale(0.85) translateY(16px);
    transition: transform 0.16s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.16s ease;
}
.hk-anim-overlay.hk-dialog-visible .user-modal { opacity: 1; transform: none; }

.spinner {
    width: 36px; height: 36px;
    border: 3px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    margin: 0 auto 16px;
    animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Спиннер + текст — тот же composition, что на экране входа (index.php,
   ожидание подтверждения в Telegram, .waiting), переиспользуется везде,
   где раньше был просто текст "⏳ Загружаю...": списки правил/пакетов/
   файлов/пользователей/бэкапов и т.п. Отдельно от .empty-state (та — для
   настоящих "тут пусто" сообщений без спиннера, живёт в node_server.php
   локально; .loading-state — здесь, в общем слое, раз .spinner тоже тут). */
.loading-state { text-align: center; padding: 40px 20px; color: var(--text-muted); }
.loading-state p { margin: 0; }

.log-box {
    background: #000;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 14px;
    font-family: 'SF Mono', Consolas, monospace;
    font-size: 0.78rem;
    color: #b5f5ea;
    height: 400px;
    overflow-y: auto;
    white-space: pre-wrap;
    word-break: break-all;
}
.log-status { text-align: center; margin-bottom: 14px; font-size: 0.95rem; }
.log-status.success { color: var(--success); }
.log-status.failed { color: var(--danger); }

.pill {
    display: inline-block;
    background: var(--surface-strong);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 2px 8px;
    font-size: 0.75rem;
    color: var(--text-muted);
}
