/* Оформление наблюдателя. Палитра Lavender + Violet — та же, что у Aura,
   morally.site и приложения Mora (скилл morally-design). Это внутренний
   инструмент того же хозяйства, и выглядеть он должен своим, а не чужой админкой. */

:root {
    /* Тёмная тема для системных элементов браузера: полей ввода, полос
       прокрутки, автозаполнения. Иначе они рисуются светлыми. */
    color-scheme: dark;

    --bg: linear-gradient(135deg, #0B0712 0%, #130D1D 100%);
    --panel: rgba(255, 255, 255, 0.03);
    --panel-2: rgba(255, 255, 255, 0.05);
    --line: rgba(255, 255, 255, 0.08);
    --text: #F4F0FA;
    --muted: #B3A9C4;
    --dim: #8E849F;
    --primary: #6E4FB0;
    --primary-hover: #5A3F96;
    --accent: #D2C3F6;
    /* Главное действие экрана — лавандовое с тёмным текстом. */
    --cta: #D2C3F6;
    --cta-hi: #E3D9FA;
    --on-cta: #1A1028;
    --ok: #10b981;
    --warn: #f59e0b;
    --crit: #ef4444;
    --radius: 16px;
    --glass: blur(24px) saturate(180%);
    /* Высота шапки: от неё считается отступ липкого меню. */
    --topbar-h: 62px;
}

* { box-sizing: border-box; }

/* Атрибут hidden скрывает элемент через display:none из стиля браузера, и
   любое авторское правило display его перебивает. Блоков с display:flex тут
   много — без этой строки «скрытое» остаётся на виду. */
[hidden] { display: none !important; }

body {
    margin: 0;
    min-height: 100vh;
    background: var(--bg);
    color: var(--text);
    font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
}

body.centered {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

h1 { font-size: 22px; margin: 0; font-weight: 700; letter-spacing: -.01em; }

h2 {
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--muted);
    margin: 0 0 14px;
    font-weight: 600;
}

.hidden { display: none; }
.muted { color: var(--muted); font-size: 13px; }

/* ------------------------------------------------------------ шапка -- */

.top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    height: var(--topbar-h);
    padding: 0 24px;
    border-bottom: 1px solid var(--line);
    background: rgba(22, 16, 34, 0.6);
    backdrop-filter: var(--glass);
    position: sticky;
    top: 0;
    z-index: 5;
}

.brand { display: flex; align-items: center; gap: 13px; min-width: 0; }

.brand-mark { width: 34px; height: 34px; flex: none; }

.top-right { display: flex; align-items: center; gap: 14px; }

main {
    display: flex;
    flex-direction: column;
    gap: 30px;
}

/* ----------------------------------------------------------- кнопки -- */

/* Умолчание для кнопки — нейтральное. Раньше здесь стояла фиолетовая
   заливка, и каждая новая кнопка рождалась кричащей, пока её не перебьёшь
   своим правилом. Стоило стилям отстать от скрипта — и в меню появлялась
   фиолетовая плашка вместо пункта списка. */
button {
    font: inherit;
    font-weight: 500;
    cursor: pointer;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: none;
    color: var(--text);
    padding: 10px 20px;
    transition: background .2s, border-color .2s, color .2s;
}

button:hover:not(:disabled) { background: rgba(255, 255, 255, .05); }
button:disabled { opacity: .55; cursor: default; }

/* Заливка — только у главного действия на экране. */
button.primary {
    background: var(--cta);
    border-color: transparent;
    color: var(--on-cta);
}

button.primary:hover:not(:disabled) { background: var(--cta-hi); }

button.ghost { color: var(--muted); }
button.ghost:hover { border-color: var(--primary); color: var(--text); }

/* ------------------------------------------------------------- вход -- */

.card {
    background: var(--panel);
    backdrop-filter: var(--glass);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 32px 28px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, .4);
}

.login { display: flex; flex-direction: column; gap: 16px; width: 320px; }

.login .brand { margin-bottom: 6px; }

.login input {
    font: inherit;
    padding: 12px 14px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: rgba(0, 0, 0, .3);
    color: var(--text);
    transition: border-color .2s;
}

.login input::placeholder { color: var(--muted); }
.login input:focus { outline: none; border-color: var(--primary); }

.error { color: var(--crit); font-size: 13px; margin: 0; min-height: 18px; }

.login-foot { margin: 4px 0 0; font-size: 12px; color: var(--muted); text-align: center; }

/* --------------------------------------------------------- тревоги -- */

.alert {
    display: flex;
    gap: 12px;
    align-items: baseline;
    padding: 13px 16px;
    border-radius: 12px;
    border: 1px solid var(--line);
    background: var(--panel);
    margin-bottom: 8px;
    /* Полоса слева читается быстрее текста: цвет виден раньше, чем слова. */
    border-left: 3px solid var(--muted);
}

.alert.critical { border-left-color: var(--crit); background: rgba(239, 68, 68, .07); }
.alert.warning  { border-left-color: var(--warn); background: rgba(245, 158, 11, .07); }
.alert .who { font-weight: 600; min-width: 190px; }
.alert .what { flex: 1; min-width: 0; }
.alert .when { margin-left: auto; color: var(--muted); font-size: 13px; white-space: nowrap; }

/* --------------------------------------------------------- объекты -- */

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 16px;
}

/* -------------------------------------------------------- боковое меню -- */

.layout {
    display: grid;
    grid-template-columns: 232px minmax(0, 1fr);
    gap: 30px;
    max-width: 1400px;
    margin: 0 auto;
    padding: 26px 24px 60px;
}

.layout main { padding: 0; max-width: none; margin: 0; }

/* Липкое и во всю высоту экрана: список групп прокручивается внутри себя,
   история с настройками прибиты к низу. При длинном списке иначе не
   переключиться, не прокрутив страницу, а нижние пункты уезжают из виду
   ровно тогда, когда нужны. */
.side {
    display: flex;
    flex-direction: column;
    align-self: start;
    position: sticky;
    top: calc(var(--topbar-h) + 26px);
    height: calc(100vh - var(--topbar-h) - 52px);
}

.side-groups {
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* Место под полосу прокрутки, чтобы она не наезжала на счётчики. */
    padding-right: 4px;
}

.side-foot {
    display: flex;
    flex-direction: column;
    gap: 3px;
    margin-top: 10px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}

/* Класс, а не button: общее правило для кнопок красит их в фиолетовый. */
.side-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    border-radius: 9px;
    border: 1px solid transparent;
    background: none;
    color: var(--muted);
    font-size: 14px;
    font-weight: 500;
    text-align: left;
    cursor: pointer;
    transition: background .15s, color .15s;
}

.side-item:hover { background: rgba(255, 255, 255, .04); color: var(--text); }

.side-item.on {
    background: rgba(110, 79, 176, .2);
    border-color: rgba(110, 79, 176, .5);
    color: var(--text);
}

/* Вложенные — с отступом и мельче: видно, что это часть ветки выше, а не
   ещё одна группа того же уровня. */
.side-item.sub { margin-left: 14px; font-size: 13.5px; padding-left: 10px; }

.side-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: none;
    background: var(--ok);
}

.side-item.warning  .side-dot { background: var(--warn); }
.side-item.critical .side-dot { background: var(--crit); }

.side-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.side-count { font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums; }

.side-item.on .side-count { color: var(--muted); }

/* История и настройки — не группы объектов: у них нет точки состояния,
   поэтому текст начинается там же, где у остальных, без пустого места
   от кружка. */
.side-extra { padding-left: 12px; }

/* ------------------------------------------------------------ настройки -- */

.panel-form {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 22px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    max-width: 760px;
}

.f-row { display: flex; gap: 14px; }
.f-row.two > .f { flex: 1; min-width: 0; }

.f { display: flex; flex-direction: column; gap: 7px; flex: 1; min-width: 0; position: relative; }
.f > span { font-size: 13px; color: var(--muted); font-weight: 500; }
.f > small { font-size: 11.5px; color: var(--dim); }

.f input {
    font: inherit;
    padding: 11px 13px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: rgba(0, 0, 0, .3);
    color: var(--text);
}

.f input::placeholder { color: var(--dim); }
.f input:focus { outline: none; border-color: var(--primary); }

/* Переключатель типа: три состояния всегда на виду, без выпадающих списков —
   выбор здесь меняет остальные поля, и прятать его нельзя. */
.seg { display: flex; gap: 6px; flex-wrap: wrap; }

.seg-b {
    padding: 9px 14px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: none;
    color: var(--muted);
    font-size: 13.5px;
    font-weight: 500;
}

.seg-b:hover { background: rgba(255, 255, 255, .04); color: var(--text); }
.seg-b.on { background: rgba(110, 79, 176, .2); border-color: var(--primary); color: var(--text); }

.suggest {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 20;
    margin: 4px 0 0;
    padding: 5px;
    list-style: none;
    background: #201833;
    border: 1px solid var(--line);
    border-radius: 10px;
    max-height: 200px;
    overflow-y: auto;
    box-shadow: 0 12px 30px rgba(0, 0, 0, .5);
    font-size: 13.5px;
}

.suggest li { padding: 9px 11px; border-radius: 7px; cursor: pointer; }
.suggest li:hover { background: rgba(255, 255, 255, .06); }

/* -------------------------------------------------------------- токен -- */

.token-box {
    margin-top: 18px;
    max-width: 760px;
    background: rgba(110, 79, 176, .12);
    border: 1px solid var(--primary);
    border-radius: var(--radius);
    padding: 20px 22px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.token-head { margin: 0; font-weight: 600; }
.token-cmd { margin: 6px 0 0; font-size: 12.5px; color: var(--muted); }

.token-box code {
    display: block;
    background: rgba(0, 0, 0, .4);
    border-radius: 8px;
    padding: 11px 13px;
    font-size: 13px;
    word-break: break-all;
    user-select: all;
}

.token-box button { align-self: flex-start; margin-top: 6px; }

/* ------------------------------------------------------- список объектов -- */

.obj-list { display: flex; flex-direction: column; gap: 8px; }

/* Последняя колонка фиксированной ширины — под полный набор кнопок, даже
   если у этой строки их меньше. На auto строки получались разной раскладки:
   у сайтов нет «Токена», у объектов без периода нет «Продлил», и адрес с
   группой съезжали от строки к строке. */
.obj {
    display: grid;
    grid-template-columns: minmax(140px, 1.2fr) 110px minmax(0, 1fr) 418px;
    align-items: center;
    gap: 14px;
    padding: 12px 16px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 12px;
    font-size: 13.5px;
}

.obj.off { opacity: .5; }

.obj-l { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.obj-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.obj-slug { font-size: 11.5px; color: var(--dim); }
.obj-grp { color: var(--muted); font-size: 12.5px; }
.obj-addr { color: var(--dim); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Кнопки прижаты вправо, и «Выключить» у всех строк оказывается на одной
   вертикали — независимо от того, есть ли у объекта токен. */
.obj-acts { display: flex; gap: 6px; justify-content: flex-end; }

/* Кнопок в строке до шести, поэтому они компактнее обычных: иначе ряд не
   помещается и наезжает на адрес. */
.mini {
    padding: 6px 10px;
    border-radius: 7px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    background: none;
    border: 1px solid var(--line);
    color: var(--muted);
}

.mini:hover { background: rgba(255, 255, 255, .05); color: var(--text); }
.mini.danger:hover { border-color: var(--crit); color: var(--crit); background: rgba(239, 68, 68, .08); }

/* ---------------------------------------------------------- своё окно -- */

/* Вместо confirm и prompt: системные нельзя объяснить, нельзя оформить и
   человек может их отключить в браузере — тогда действие пройдёт молча. */
.ask-back {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(0, 0, 0, .65);
    backdrop-filter: blur(3px);
}

.ask {
    width: 100%;
    max-width: 430px;
    background: #201833;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .6);
}

.ask h3 { margin: 0; font-size: 17px; }
.ask p { margin: 0; color: var(--muted); font-size: 13.5px; }

.ask input {
    font: inherit;
    padding: 11px 13px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: rgba(0, 0, 0, .3);
    color: var(--text);
}

.ask input:focus { outline: none; border-color: var(--primary); }

.ask-f { display: flex; flex-direction: column; gap: 6px; }
.ask-f > span { font-size: 13px; color: var(--muted); }

.ask-hint { font-size: 12px; color: var(--dim); }

.ask-acts { display: flex; gap: 10px; justify-content: flex-end; }

/* Сплошной красный — только у подтверждения в окне, где нажатие необратимо.
   В списке объектов такая же кнопка стоит в каждой строке, и десяток алых
   плашек подряд превращает предупреждение в фон. */
.ask-acts button.danger {
    background: var(--crit);
    border-color: transparent;
    color: #fff;
}

.ask-acts button.danger:hover:not(:disabled) { background: #c73434; }

@media (max-width: 760px) {
    .f-row, .f-row.two { flex-direction: column; }
    .obj { grid-template-columns: 1fr; gap: 8px; }
    .obj-acts { flex-wrap: wrap; }
}

/* На узком экране меню ложится строкой над содержимым и прокручивается вбок:
   колонка в 232 пикселя на телефоне съела бы половину ширины. */
@media (max-width: 900px) {
    .layout { grid-template-columns: 1fr; gap: 18px; }

    /* На телефоне всё ложится одной строкой: группы прокручиваются вбок,
       история с настройками остаются справа за разделителем. */
    .side {
        position: static;
        height: auto;
        flex-direction: row;
        gap: 8px;
    }

    .side-groups {
        flex-direction: row;
        overflow-x: auto;
        overflow-y: visible;
        padding: 0 0 4px;
        gap: 7px;
    }

    .side-foot {
        flex-direction: row;
        gap: 7px;
        margin: 0;
        padding: 0 0 0 8px;
        border-top: none;
        border-left: 1px solid var(--line);
    }

    .side-item { width: auto; flex: none; }
    .side-item.sub { margin-left: 0; }
}

.tile {
    position: relative;
    /* Не hidden: подсказка выше карточки, и обрезка съедала её верхние
       строки. Полоска состояния вместо этого не доходит до скруглённых
       углов — см. ::before ниже. */
    overflow: visible;
    background: var(--panel);
    backdrop-filter: var(--glass);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 18px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, .25);
    transition: border-color .2s, transform .2s;
}

/* z-index при наведении: иначе подсказка уходит под соседнюю плитку,
   которая идёт следующей в потоке. */
.tile:hover { border-color: rgba(255, 255, 255, .14); transform: translateY(-1px); z-index: 3; }

/* Полоса по верхнему краю появляется только у проблемных плиток. В норме
   украшательства нет вовсе — иначе оно перестаёт что-либо значить, и на
   сетке из девяти карточек сломанная ничем не выделяется. */
.tile.warning::before,
.tile.critical::before {
    content: '';
    position: absolute;
    /* Отступ по бокам вместо обрезки по контуру: полоска не доходит до
       скруглённых углов и не торчит из них квадратными концами. */
    inset: 0 14px auto;
    height: 2px;
    border-radius: 0 0 2px 2px;
}

.tile.warning::before  { background: var(--warn); }
.tile.critical::before { background: var(--crit); }

.tile.critical { border-color: rgba(239, 68, 68, .35); }
.tile.warning  { border-color: rgba(245, 158, 11, .3); }

.tile-head { display: flex; align-items: center; gap: 10px; }

.dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    flex: none;
    background: var(--muted);
}

/* Свечение вокруг точки: на плитке это единственный элемент, который надо
   заметить боковым зрением, не читая. */
.dot.ok       { background: var(--ok);   box-shadow: 0 0 0 3px rgba(16, 185, 129, .15); }
.dot.warning  { background: var(--warn); box-shadow: 0 0 0 3px rgba(245, 158, 11, .15); }
.dot.critical { background: var(--crit); box-shadow: 0 0 0 3px rgba(239, 68, 68, .18); }

.tile-name { font-weight: 600; }
.tile-kind { margin-left: auto; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }

/* Ручка перетаскивания. Проявляется при наведении на плитку: постоянно
   видимая, она в сетке из двадцати карточек превращается в рябь. */
.grip {
    width: 14px;
    height: 16px;
    flex: none;
    cursor: grab;
    opacity: 0;
    transition: opacity .15s;
    /* Шесть точек, как принято у перетаскиваемых элементов. Рисуем фоном,
       чтобы не заводить ни картинку, ни шрифт значков. */
    background-image: radial-gradient(circle, var(--muted) 1.1px, transparent 1.2px);
    background-size: 5px 5px;
    background-position: 1px 1px;
}

.tile:hover .grip { opacity: .55; }
.grip:hover { opacity: 1 !important; }
.grip:active { cursor: grabbing; }

/* Перетаскиваемая плитка приподнята и полупрозрачна — видно, что именно
   несёшь и куда оно встанет. */
.tile.dragging {
    opacity: .45;
    border-color: var(--primary);
}

/* На сенсорном экране перетаскивание мышью не работает, а ручка только
   занимает место. */
@media (hover: none) {
    .grip { display: none; }
}

/* ---------------------------------------------------- загрузка и график -- */

.hero { display: flex; align-items: flex-end; gap: 11px; }

.hero b {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -.03em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* Цифры выравниваем по разрядам везде, где они стоят столбцом или меняются
   на месте: иначе значение дёргается при каждом обновлении. */
.num { font-variant-numeric: tabular-nums; }

.hero b.bad { color: var(--crit); }

/* Единица чуть мельче числа — ровно настолько, чтобы не спорить с ним за
   внимание. Сильнее уменьшать не стоит: «мс» превращается в сноску. */
.hero b i {
    font-size: 20px;
    font-weight: 650;
    font-style: normal;
    letter-spacing: -.02em;
    color: var(--muted);
    margin-left: 4px;
}

.hero b.bad i { color: inherit; opacity: .75; }

/* Словом вместо числа — у объектов, где измерять нечего. Кегль меньше:
   «на связи» в тридцать пикселей занимает всю плитку. */
.hero b.word { font-size: 21px; letter-spacing: -.01em; }

.hero span { font-size: 12px; color: var(--muted); padding-bottom: 3px; }


/* График выходит за отступы плитки и касается её краёв. Так линия читается
   частью карточки, а не вставленной картинкой, и под неё не тратится место
   по бокам. Отрицательные поля равны padding у .tile. */
/* overflow: hidden — страховка: у плитки скругления и включённый overflow
   visible ради подсказки, и без этого график вылезал бы за углы карточки. */
.spark-box { position: relative; height: 44px; margin: 0 -18px; overflow: hidden; }

/* Верх шкалы подписан: без него непонятно, три процента это у самого низа
   или у потолка. Шкала подстраивается под сервер, и число меняется. */
.spark-top {
    position: absolute;
    top: 0;
    right: 18px;
    font-size: 10px;
    color: var(--dim);
    font-variant-numeric: tabular-nums;
    pointer-events: none;
    /* Тень цветом подложки: даже когда пик подходит вплотную, цифра остаётся
       читаемой и не сливается с линией. */
    text-shadow: 0 0 4px #150a1c, 0 0 6px #150a1c;
}

/* preserveAspectRatio=none растягивает график по ширине плитки; толщину линии
   при этом держит vector-effect, иначе она размазывается вместе с картинкой. */
.spark { display: block; width: 100%; height: 44px; }

.spark-empty {
    height: 44px;
    display: flex;
    align-items: center;
    padding: 0 18px;
    font-size: 12px;
    color: var(--dim);
}

.pair { display: flex; justify-content: space-between; font-size: 12.5px; color: var(--muted); }
.pair .bad { color: var(--crit); }

/* ---------------------------------------------------- отметки связи -- */

/* Занимает столько же места, сколько график у соседних плиток, и так же во
   всю ширину — клеток всегда двенадцать, по одной на каждые пять минут часа. */
.beats {
    display: flex;
    align-items: center;
    gap: 5px;
    height: 44px;
}

.beats i {
    flex: 1 1 0;
    height: 15px;
    border-radius: 3px;
}

/* Отметки не было. */
.beats i.off { height: 5px; background: rgba(255, 255, 255, .09); }

/* Объекта тогда ещё не наблюдали — это не перерыв, и выглядеть должно иначе. */
.beats i.none { height: 3px; background: rgba(255, 255, 255, .04); }
.beats i.on { background: var(--ok); }
.beats i.on.warning { background: var(--warn); }
.beats i.on.critical { background: var(--crit); }

/* Подвал плитки: слева свежесть и аптайм, справа — самое заметное.
   Одна строка вместо таблицы: карточка ниже, а взгляду есть за что
   зацепиться, потому что справа появляется только отклонение. */
.foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: 11.5px;
    color: var(--dim);
    padding-top: 12px;
    border-top: 1px solid var(--line);
    margin-top: auto;
    cursor: default;
}

.foot-l { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.foot-l .sep { opacity: .5; }

.foot-r {
    text-align: right;
    white-space: nowrap;
    /* Длинная причина отказа обрезается, а не переносит строку: целиком
       видно в подсказке по наведению. */
    overflow: hidden;
    text-overflow: ellipsis;
}

.foot .bad  { color: var(--crit); }
.foot .warn { color: var(--warn); }

/* ------------------------------------------------------------ подсказка -- */

/* Своя, а не браузерный title: та появляется с задержкой, пропадает от
   любого движения мыши и рисуется системной белой плашкой поверх тёмной
   страницы. Эта возникает сразу и выглядит частью карточки.
   Живёт внутри плитки, поэтому overflow:hidden её не режет. */
.hint {
    position: absolute;
    left: 10px;
    right: 10px;
    bottom: 10px;
    z-index: 2;
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 6px 14px;
    padding: 12px 14px;
    border-radius: 12px;
    background: #201833;
    border: 1px solid var(--line);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .55);
    font-size: 12px;
    opacity: 0;
    visibility: hidden;
    transform: translateY(4px);
    transition: opacity .13s, transform .13s, visibility .13s;
}

.tile.has-hint:hover .hint {
    opacity: 1;
    visibility: visible;
    transform: none;
}

.hint .hk { color: var(--muted); }

.hint .hv {
    text-align: right;
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hint .hv.bad { color: var(--crit); }

/* На сенсорном экране наведения нет, и подсказка либо не открывается,
   либо залипает после тапа. Проще не показывать её вовсе. */
@media (hover: none) {
    .hint { display: none; }
}

/* --------------------------------------------------------- история -- */

.history { display: flex; flex-direction: column; gap: 6px; }

.history-row {
    display: flex;
    gap: 12px;
    font-size: 13px;
    padding: 10px 14px;
    border-radius: 10px;
    background: var(--panel);
    border: 1px solid var(--line);
}

.history-row .who { font-weight: 600; min-width: 190px; }
.history-row .when { margin-left: auto; color: var(--muted); white-space: nowrap; }

/* ------------------------------------------------------- прокрутка -- */

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 5px; }
::-webkit-scrollbar-track { background: transparent; }

@media (max-width: 640px) {
    main { padding: 18px 14px 40px; }
    .top { padding: 12px 14px; }
    .alert .who, .history-row .who { min-width: 0; }
    .alert, .history-row { flex-wrap: wrap; }
    .login { width: 100%; max-width: 320px; }
}

/* ------------------------------------------------------ оплата серверов -- */

.pay-totals { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 18px; }
.pay-stat {
    display: flex; flex-direction: column; gap: 4px;
    padding: 16px 18px; border-radius: var(--radius);
    background: var(--panel); border: 1px solid var(--line);
}
.pay-stat-l { font-size: 12.5px; color: var(--muted); }
.pay-stat b { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pay-stat small { font-size: 12px; color: var(--dim); }

.pay-table { display: flex; flex-direction: column; gap: 8px; }
.pay-row {
    display: grid;
    /* Колонка кнопок фиксированной ширины: у строки без ссылки на хостинг
       кнопок меньше, и на auto колонки соседних строк расходились */
    grid-template-columns: minmax(160px, 1.4fr) minmax(110px, .8fr) minmax(130px, .9fr) 290px;
    align-items: center; gap: 14px;
    padding: 12px 16px; border-radius: 12px;
    background: var(--panel); border: 1px solid var(--line);
}
.pay-row > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pay-row b { font-weight: 600; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pay-row small { font-size: 12px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pay-due.warn b { color: var(--warn); }
.pay-due.bad b { color: var(--crit); }
.pay-row .obj-acts { flex-direction: row; }
a.mini { display: inline-flex; align-items: center; text-decoration: none; color: inherit; }
.mini.primary-mini { background: var(--cta); border-color: transparent; color: var(--on-cta); }
.mini.primary-mini:hover { background: var(--cta-hi); color: var(--on-cta); }

.pay-log .history-row b { font-variant-numeric: tabular-nums; }

@media (max-width: 760px) {
    .pay-totals { grid-template-columns: minmax(0, 1fr); }
    .pay-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .pay-row .obj-acts { grid-column: 1 / -1; justify-content: flex-start; flex-wrap: wrap; }
}
