/*
  Тема портала.

  Палитра из шести шагов, от почти чёрной сливы до кремового. Она
  одноцветная — весь ряд лежит на одной дуге от фиолетового к розовому,
  поэтому цветом здесь разделяются не смыслы, а планы: чем светлее, тем
  ближе к читателю. Тёмный низ — фон и рамки, светлый верх — текст и то,
  на что нужно нажать.

    #190019  подложка страницы
    #2B124C  панели
    #522B5B  линии и рамки
    #854F6C  третьестепенное: метки, декор
    #DFB6B2  акцент: ссылки, кнопки, фокус
    #FBE4D8  основной текст

  Светлой темы нет намеренно: фоновая анимация рассчитана на светлые фигуры
  по тёмному, и на белом листе она превращается в грязь.

  Слои страницы (снизу вверх):
    html            — сплошной цвет на случай, если что-то не загрузилось
    body::before    — неподвижная подсветка, рисуется один раз
    canvas#flock    — стая, см. flock.js
    содержимое      — обычный поток, поверх обоих
  Оба фоновых слоя position:fixed: при прокрутке они не перерисовываются,
  композитор просто оставляет их на месте.
*/
:root{
  --bg:#190019; --panel:#2B124C; --line:#522B5B; --mute:#854F6C;
  --ink:#FBE4D8; --dim:#B2828F; --accent:#DFB6B2;
  --danger:#E4708A; --warn:#E0B08A;
  --radius:14px; --pill:999px;
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  --sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}
*{box-sizing:border-box}
html{background:var(--bg);-webkit-text-size-adjust:100%}
body{margin:0;background:transparent;color:var(--ink);font-family:var(--sans);
  font-size:15px;line-height:1.6;-webkit-font-smoothing:antialiased;
  min-height:100dvh}

/* Подсветка снизу и сверху — статичный градиент, наследник исходной палитры.
   Отдельный неподвижный слой, а не фон body: фон элемента прокручивается
   вместе со страницей и на телефоне перерисовывается каждый кадр прокрутки. */
body::before{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:
    radial-gradient(115% 78% at 50% 116%, #56305F 0%, rgba(86,48,95,0) 60%),
    radial-gradient(85% 55% at 8% -10%, #331559 0%, rgba(51,21,89,0) 62%),
    var(--bg)}
canvas.flock{position:fixed;inset:0;z-index:-1;display:block;pointer-events:none}

h1,h2,h3{line-height:1.25;margin:0 0 .5rem;font-weight:600}
h1{font-size:1.45rem;letter-spacing:-.015em}
h2{font-size:1.02rem;letter-spacing:.005em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline;text-underline-offset:3px}
.muted,.hint{color:var(--dim)} .hint{font-weight:400;font-size:.85em}

/* --- вход --- */
/* Единственная страница, где фон виден целиком, поэтому карточка держится
   компактной и стоит по центру, оставляя стае всё поле вокруг. */
.auth{min-height:100dvh;display:grid;place-items:center;padding:1.25rem}
.auth form{background:color-mix(in srgb,var(--panel) 90%,transparent);
  border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 18px 48px rgba(9,0,12,.55);
  padding:2rem 1.9rem;width:min(23rem,100%)}
.auth h1{margin-bottom:1.25rem}
.auth label{display:block;margin:1rem 0;font-size:.88rem;color:var(--dim)}
.auth input{width:100%;margin-top:.4rem}
.auth button[type=submit]{width:100%;margin-top:.5rem}
.err{color:var(--danger);margin:.25rem 0;font-size:.9rem}
.switch{margin:1.25rem 0 0;font-size:.85rem;text-align:center}
.switch a{color:var(--dim)}
.switch a:hover{color:var(--accent)}

/* --- каркас --- */
.shell{display:grid;grid-template-columns:15rem 1fr;gap:1.25rem;
  max-width:70rem;margin:0 auto;padding:1.5rem 1.25rem 3rem;align-items:start}
@media(max-width:820px){.shell{grid-template-columns:1fr;padding-top:1rem}}
.wrap-narrow{max-width:60rem;margin:0 auto;padding:1.5rem 1.25rem 3rem}

/* Панель почти непрозрачна: под текстом движение недопустимо, но остаток
   в десятую долю не даёт листу выглядеть наклеенным поверх фона. */
.panel,.board{background:color-mix(in srgb,var(--panel) 90%,transparent);
  border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 12px 34px rgba(9,0,12,.42)}
.panel{padding:1.75rem}
@media(max-width:520px){.panel{padding:1.25rem}}
.panel.narrow{max-width:40rem;margin:3rem auto}
.panel section{margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--line)}
.panel section:first-of-type{border-top:0;padding-top:0}
/* Шапка страницы: заголовок слева, меню справа, обе части на одной строке.
   Описание под шапкой, а не внутри неё: лежа в той же ячейке, оно меняло
   высоту шапки, и меню на разных страницах оказывалось на разной высоте. */
.bar{display:flex;justify-content:space-between;align-items:baseline;
  gap:1rem;flex-wrap:wrap}
.bar h1{margin:0}
/* Подпись под шапкой. Ширина ограничена: строка во весь экран читается
   тяжело — глазу негде найти начало следующей. */
.lede{color:var(--dim);margin:.5rem 0 0;max-width:62ch}
.bar nav{display:flex;gap:1rem;align-items:center;flex-wrap:wrap;font-size:.9rem}
.bar nav a{color:var(--dim)}
.bar nav a:hover{color:var(--accent)}
/* На узком экране меню перестаёт помещаться рядом с заголовком и упирается
   в правый край. Разводим их по строкам: заголовок сверху, меню под ним. */
@media(max-width:560px){
  .bar{flex-direction:column;gap:.75rem}
  .bar nav{gap:.9rem;font-size:.88rem}
}

/* --- таблица лидеров --- */
.board{padding:1.1rem 1.25rem;position:sticky;top:1.5rem}
/* Липкая колонка имеет смысл, только пока она колонка. Когда сетка
   схлопывается в одну колонку, таблица лидеров становится обычным блоком
   сверху — и, оставаясь липкой, наезжает на содержимое при прокрутке. */
@media(max-width:820px){.board{position:static}}
.board details>summary{cursor:pointer;list-style:none;font-size:.98rem;
  font-weight:600;display:flex;align-items:center;gap:.5rem;user-select:none}
.board details>summary::-webkit-details-marker{display:none}
.board details>summary::before{content:"▸";color:var(--mute);font-size:.8em;
  transition:transform .15s ease}
.board details[open]>summary::before{transform:rotate(90deg)}
.board details>summary:hover{color:var(--accent)}
.board ol{list-style:none;margin:.9rem 0 0;padding:0}
.board li{display:grid;grid-template-columns:1.4rem 1fr auto;gap:.5rem;
  padding:.5rem 0;border-top:1px solid var(--line);font-size:.88rem}
.board li:first-child{border-top:0}
.board .rank{color:var(--mute);font-variant-numeric:tabular-nums}
.board .who{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Имя в списке — ссылка в профиль, но красить её в акцентный незачем: девять
   розовых строк подряд перестают быть списком и становятся сплошным пятном.
   Цвет наследуется, подчёркивание появляется под курсором. */
.board .who a{color:inherit}
.board .who a:hover{color:var(--accent);text-decoration:underline}
.board .amt{color:var(--dim);font-family:var(--mono);font-size:.82em}
.board li.me{color:var(--accent);font-weight:600}
.board li.me .amt{color:var(--accent)}
.board .empty{color:var(--dim);grid-template-columns:1fr}

/* --- таблицы --- */
table{width:100%;border-collapse:collapse;margin-top:.75rem}
th,td{text-align:left;padding:.7rem .5rem;border-top:1px solid var(--line);
  vertical-align:middle}
/* Шапка на --mute давала 2.6 к панели. Пока столбец был один, это сходило за
   украшение; теперь их пять, и «Время» от «Соединений» отличают именно по
   шапке — читаться она обязана. */
th{font-size:.72rem;text-transform:uppercase;letter-spacing:.08em;
  color:var(--dim);border-top:0;font-weight:600;padding-bottom:.5rem}
/* Число и единица — одно целое: без запрета переноса «48.0 GiB» на узком
   экране разрывается пополам и колонка выглядит вдвое выше остальных. */
td.num{font-family:var(--mono);font-size:.88em;font-variant-numeric:tabular-nums;
  white-space:nowrap}
tr.off{opacity:.45}
.actions{text-align:right;white-space:nowrap}

/*
  Таблица на телефоне.

  Четыре колонки с кнопками в 375 пикселей не помещаются: минимальная ширина
  строки больше экрана, и страница уезжает вбок. Горизонтальная прокрутка
  внутри таблицы это чинит, но пользоваться ей неудобно — половина строки
  всегда за кадром.

  Поэтому строка перестаёт быть строкой и становится карточкой: имя сверху,
  под ним подробности, снизу кнопки. Заголовки колонок при этом исчезают,
  поэтому значения подписываются сами — из атрибута data-l в разметке.
*/
@media(max-width:600px){
  table,tbody{display:block}
  thead{display:none}
  tr{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .75rem;
    padding:.9rem 0;border-top:1px solid var(--line)}
  tr:first-child{border-top:0}
  td{display:block;border:0;padding:0}
  td:first-child{flex:1 0 100%;font-weight:600}
  td[data-l]::before{content:attr(data-l) " ";color:var(--mute);
    font-size:.72rem;text-transform:uppercase;letter-spacing:.07em;
    font-family:var(--sans)}
  td.actions{flex:1 0 100%;text-align:left;margin-top:.4rem;
    white-space:normal;display:flex;gap:.5rem;flex-wrap:wrap}
}
/* Две ссылки на странице равноправны и подписаны по приложению. Заголовок
   у каждой обязателен: без него человек берёт первую попавшуюся. */
.cfg{margin:1.5rem 0}
.cfg h2{font-size:1rem;margin:0 0 .5rem}

/* QR читает камера, поэтому белое поле и чёрные модули заданы здесь своими
   значениями и одинаковы на обеих темах.

   Ширина важнее, чем кажется. Ссылка длинная, код выходит версии 10 — это 57
   модулей поперёк, плюс поле. На 320 пикселях на модуль приходится 4,9
   пикселя, и камера телефона, снимающая экран под углом и с бликами, такой
   код берёт с трудом. На 480 выходит 7,4 — запас, который стоит места на
   странице. Код и так свёрнут, пока его не попросят. */
.qr{background:#fff;padding:.9rem;border-radius:10px;max-width:480px;
  margin:.6rem 0}
/* `crispEdges` убирает сглаживание: по умолчанию браузер размывает края
   штрихов, и у модулей появляется серая кайма, по которой камера и
   спотыкается. */
.qr svg{display:block;width:100%;height:auto;shape-rendering:crispEdges}
/* Вложенный свёрнутый блок отступает, иначе два треугольника подряд читаются
   как один список. */
.spare .misc{margin-left:1rem}

.proto{font-size:.78rem;color:var(--dim);border:1px solid var(--line);
  padding:.12rem .55rem;border-radius:var(--pill);white-space:nowrap}
.tag{font-size:.68rem;text-transform:uppercase;letter-spacing:.07em;
  color:var(--accent);border:1px solid var(--accent);padding:.1rem .5rem;
  border-radius:var(--pill);white-space:nowrap;vertical-align:.1em}

/* --- формы --- */
input,select,textarea,button{font:inherit;color:var(--ink);
  background:color-mix(in srgb,var(--bg) 62%,transparent);
  border:1px solid var(--line);border-radius:10px;padding:.55rem .75rem}
input::placeholder{color:var(--mute)}
input:focus,select:focus,textarea:focus{outline:2px solid var(--accent);
  outline-offset:-1px;background:color-mix(in srgb,var(--bg) 80%,transparent)}
select{appearance:none;padding-right:2rem;
  background-image:linear-gradient(45deg,transparent 50%,var(--dim) 50%),
                   linear-gradient(135deg,var(--dim) 50%,transparent 50%);
  background-position:calc(100% - 1.1rem) 55%,calc(100% - .75rem) 55%;
  background-size:6px 6px,6px 6px;background-repeat:no-repeat}
button{cursor:pointer;background:var(--accent);border-color:var(--accent);
  color:#22002A;font-weight:600;border-radius:var(--pill);padding:.55rem 1.2rem}
button:hover{background:var(--ink);border-color:var(--ink)}
.row{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:.75rem}
.row input{flex:1 1 12rem}
.inline{display:inline}
.btn{display:inline-block;padding:.35rem .9rem;font-size:.85rem;font-weight:500;
  background:transparent;border:1px solid var(--line);color:var(--ink);
  border-radius:var(--pill)}
.btn:hover{border-color:var(--accent);color:var(--accent);
  background:transparent;text-decoration:none}
.btn.danger{color:var(--danger)}
.btn.danger:hover{border-color:var(--danger);color:var(--danger)}
.linklike{background:none;border:0;color:var(--dim);padding:0;font-weight:400;
  border-radius:0}
.linklike:hover{background:none;color:var(--accent);text-decoration:underline;
  text-underline-offset:3px}
.link{width:100%;font-family:var(--mono);font-size:.8rem;word-break:break-all;
  line-height:1.5}
.hints{color:var(--dim);font-size:.87rem;padding-left:1.1rem;margin-top:1rem}
.hints li{margin:.25rem 0}
.hints b{color:var(--ink);font-weight:600}
.flash{background:color-mix(in srgb,var(--accent) 10%,transparent);
  border:1px solid color-mix(in srgb,var(--accent) 45%,transparent);
  border-radius:10px;padding:.85rem 1.1rem;margin-top:1.25rem;font-size:.92rem}
.flash p{margin:.2rem 0;word-break:break-word}

/* --- запасная обложка --------------------------------------------------
   В рабочей установке `/` отдаёт nginx из site/index.html, мимо Python.
   Этот шаблон остаётся на случай запуска портала без nginx: свежая машина,
   локальная проверка, сломанный конфиг. Оформление то же, что у кабинета. */
.cover{max-width:42rem;margin:0 auto;padding:4rem 1.25rem}
.cover header{margin-bottom:2.5rem}
.cover .sub{color:var(--dim)}
.cover article{padding:1.5rem 0;border-top:1px solid var(--line)}
.cover footer{margin-top:3rem;padding-top:1rem;border-top:1px solid var(--line);
  display:flex;justify-content:space-between;color:var(--dim);font-size:.85rem}
.quiet{color:var(--dim);opacity:.4;padding:0 .5rem}
.quiet:hover{opacity:1;text-decoration:none}

/* --- часы и ранги ------------------------------------------------------
   Единственное место в портале, где появляется цвет вне палитры: материал
   ступени. Он приходит из ranks.py через inline-переменную --tier, потому
   что тот же цвет нужен и SVG-циферблату, а два источника правды разошлись
   бы на первой правке. Всё остальное здесь по-прежнему на палитре. */
/* Размер приходит из макроса инлайном (style="width:Npx"), но местами он
   обязан уступить: у героя 176 px не помещаются рядом с текстом на узком
   экране, а в списке участников и в лестнице свой размер. Поэтому здесь и
   ниже стоит потолок, а не размер, и `!important` нужен именно чтобы
   перебить инлайн.
   Высоту макрос больше не задаёт вовсе — её доделывает aspect-ratio, иначе
   при перебитой ширине циферблат переставал быть квадратным. */
.dial{display:block;height:auto;aspect-ratio:1}
.mine>.dial{width:min(11rem,42vw)!important}
.dial-link .dial{width:min(7.5rem,30vw)!important}

/* Циферблат как кнопка: открывает лестницу окном. Кнопка, а не ссылка, —
   никуда не ведёт, а браузеру и озвучке надо это сообщить. */
.dial-link{display:block;flex:0 0 auto;padding:0;border:0;background:none;
  cursor:pointer;line-height:0}
.dial-link:hover{filter:brightness(1.12);background:none}
.dial-link:focus-visible{outline:2px solid var(--accent);outline-offset:4px;
  border-radius:50%}

/* Блик по металлу и дыхание мифрила — из пакета дизайнера. Значков на
   странице до сорока, и анимировать их все на одном ядре нельзя: остаются
   только крупные, в герое и в окне. */
.dial .polish{opacity:.18}
.mine>.dial .polish,.sheet .dial .polish{animation:sheen 10s ease-in-out infinite}
.mine>.dial .halo,.sheet .dial .halo{animation:breathe 7s ease-in-out infinite}
@keyframes sheen{0%,84%{opacity:.18}90%{opacity:.5}100%{opacity:.18}}
@keyframes breathe{0%,100%{opacity:.18}50%{opacity:.5}}
@media(prefers-reduced-motion:reduce){
  .dial .polish,.dial .halo{animation:none!important}
}

/* Циферблат и текст рядом; на телефоне — друг под другом по центру. */
.mine{display:flex;gap:1.5rem;align-items:center;flex-wrap:wrap}
.mine>.dial,.mine>.dial-link{flex:0 0 auto}
.mine-text{flex:1 1 15rem;min-width:0}
.mine-text h2{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
.mine .big{font-size:1.9rem;line-height:1.1;margin:.2rem 0 .6rem;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.mine.compact .big{font-size:1.35rem}
@media(max-width:520px){
  /* Циферблат по центру, текст слева: центрированный абзац в четыре строки
     читается заметно хуже — глазу негде найти начало следующей. */
  .mine{gap:1rem;justify-content:center}
  .mine>.dial{width:min(9rem,52vw)!important}
}

.badge{font-size:.7rem;text-transform:uppercase;letter-spacing:.09em;
  font-weight:600;color:var(--tier);border:1px solid var(--tier);
  padding:.14rem .6rem;border-radius:var(--pill);white-space:nowrap;
  vertical-align:.15em}
.tier-mithril{box-shadow:0 0 12px -2px var(--tier)}

/* Полоса пути до следующей ступени. Дорожка своим цветом, а не прозрачностью:
   поверх подвижной стаи полупрозрачный трек мерцал бы. */
.bar-track{height:.45rem;border-radius:var(--pill);overflow:hidden;
  background:color-mix(in srgb,var(--line) 80%,var(--bg));margin:.1rem 0 .55rem}
.bar-track span{display:block;height:100%;border-radius:var(--pill);
  transition:width .4s ease}

/* Ступени: шесть силуэтов в ряд. Образец материала заменён самим
   циферблатом — у каждой ступени свой корпус, и по нему ступень опознаётся
   раньше цвета: круг, октагон, круг с заклёпками, монета, солнце, цветок. */
.ladder{list-style:none;display:flex;gap:.5rem;padding:0;margin:.9rem 0 0;
  flex-wrap:wrap}
.ladder li{flex:1 1 6rem;border:1px solid var(--line);border-radius:10px;
  padding:.7rem .5rem;display:flex;flex-direction:column;gap:.3rem;
  align-items:center;text-align:center;
  background:color-mix(in srgb,var(--bg) 35%,transparent)}
.ladder li.now{border-color:var(--tier);
  background:color-mix(in srgb,var(--tier) 12%,transparent)}
/* Закрытая ступень приглушена, но её силуэт виден: понятно, что впереди. */
.ladder li.off{opacity:.55}
.ladder .dial{width:3.4rem!important}
.ladder .nm{font-size:.85rem;font-weight:600;color:var(--tier)}
.ladder li.off .nm{color:var(--dim)}
.ladder .th{font-size:.75rem;color:var(--dim);font-family:var(--mono)}

/* Список участников. Живёт в боковой колонке кабинета: сюда переехала
   лестница часов, потому что отдельной страницей она показывала тех же людей
   и вела в те же профили. Часы крупно, трафик мелко под ними — это разные
   вещи, и раньше их приходилось сличать глазами между двумя страницами. */
.people{list-style:none;padding:0;margin:.75rem 0 0}
.people li{display:flex;align-items:center;gap:.6rem;padding:.5rem 0;
  border-top:1px solid var(--line)}
.people li:first-child{border-top:0}
/* --dim, а не --mute: на панели тот давал 3.32:1 при норме 4.5, и номер
   места, ступень и трафик читались хуже всего остального в колонке. */
.people .pos{width:1.2rem;color:var(--dim);font-variant-numeric:tabular-nums;
  flex:0 0 auto;font-size:.8rem}
.people .dial{width:2.4rem!important;flex:0 0 auto}
.people .who{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}
.people .who a,.people .who>*:first-child{overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.people .who i{font-style:normal;font-size:.72rem;color:var(--dim)}
.people .amt{color:var(--ink);font-family:var(--mono);font-size:.82em;
  white-space:nowrap;flex:0 0 auto;text-align:right;display:flex;
  flex-direction:column;gap:.1rem}
.people .amt i{font-style:normal;color:var(--dim);font-size:.9em}
.people li.me .who a{color:var(--accent);font-weight:600}
.people li.me .amt{color:var(--accent)}
.people .empty{color:var(--dim)}

/* Столбики по суткам. Высота задаётся в разметке — она вычислена из данных. */
/* Сутки. Черта на половине высоты нарисована фоном самой рамки: она нужна,
   чтобы шкала читалась без наведения — на телефоне подсказки нет вовсе. */
.days{display:flex;align-items:flex-end;gap:2px;height:4.5rem;margin-top:.75rem;
  padding:.35rem;border:1px solid var(--line);border-radius:10px;
  /* Черта на половине СУТОК, а не на половине рамки: столбик рисуется от
     4% до 100%, и 12 часов приходятся на 52%. Подпись — обещание. */
  background-image:linear-gradient(to bottom,transparent 48%,
                   var(--mute) 48%,var(--mute) calc(48% + 1px),
                   transparent calc(48% + 1px));
  background-clip:content-box}
.days i{flex:1 1 0;min-width:2px;border-radius:2px 2px 0 0}
/* Сбора ещё не было: столбика нет, есть пунктир по низу. Отсутствие данных
   обязано выглядеть отсутствием, а не малой величиной.

   Цвет --mute (2.58 к панели), а не --line: на --line с opacity .7 выходило
   1.27, то есть не видно вовсе, и оба пустых состояния сливались в пустоту.
   Здесь низкий контраст осмыслен — это и есть «ничего», — но он должен
   существовать. */
.days i.none{height:100%;background:none;border-radius:0;
  border-bottom:1px dashed var(--mute)}
/* Сутки были, VPN не включался: полоска. Это ИЗМЕРЕНИЕ, а не фон, поэтому
   --dim (5.01) и три пикселя: на --line (1.43) и двух пикселях подпись
   «тонкая полоска — не включался ни разу» обещала то, чего не видно. */
.days i.zero{height:3px;background:var(--dim);opacity:1;border-radius:1px}
.days-axis{display:flex;justify-content:space-between;gap:.5rem;
  margin-top:.35rem;color:var(--dim);font-size:.8rem}
.days-axis .mid{text-align:center}
@media (max-width:32rem){.days-axis .mid{display:none}}

/* Окно. Ступени и разбор одного сайта показываются здесь, а не отдельной
   страницей: человек смотрит их на секунду и возвращается туда же, откуда
   открыл. <dialog> нативный — фокус, Esc и подложку браузер держит сам. */
.sheet{border:1px solid var(--line);border-radius:var(--radius);
  background:var(--panel);color:var(--ink);padding:1.5rem;
  width:min(46rem,92vw);max-height:86vh;overflow:auto}
.sheet::backdrop{background:rgba(12,0,14,.78)}
.sheet h2{margin-top:0}
.sheet-close{display:flex;justify-content:flex-end;margin:-.6rem -.6rem .2rem 0}
.sheet-close button{background:none;border:0;color:var(--dim);cursor:pointer;
  font-size:1.4rem;line-height:1;padding:.2rem .5rem}
.sheet-close button:hover{color:var(--accent);background:none}

/* Вручение ступени. Полтора удара сердца, только transform и opacity —
   на одном ядре всё остальное дёргается. У каждого материала своё прочтение
   одного и того же момента: дерево растёт, камень скалывается, бронза
   отливается, серебро полируется, золото чеканится, мифрил загорается. */
.ceremony{border:0;background:none;padding:1.75rem;max-width:100vw;
  max-height:100vh;width:100%;height:100%;overflow:hidden;
  display:flex;align-items:center;justify-content:center;color:var(--ink)}
.ceremony::backdrop{background:rgba(12,0,14,.94)}
.ceremony-box{display:flex;flex-direction:column;align-items:center;
  gap:.75rem;text-align:center}
.ceremony-dial{position:relative;margin-bottom:.75rem}
.ceremony-dial .dial{width:min(15rem,60vw)!important}
.ceremony .flare{position:absolute;inset:-7%;border-radius:50%;
  border:2px solid var(--mat);animation:flare 1.4s ease-out both}
.ceremony .flare2{border-width:1px;animation-delay:.22s}
.kicker{font-size:.7rem;letter-spacing:.24em;text-transform:uppercase;
  color:var(--mute);margin:0}
.ceremony-name{font-size:clamp(2rem,7vw,3.2rem);line-height:1;margin:0;
  font-weight:600}
.ceremony .btn{margin-top:.75rem}
.ceremony-box>*:not(.ceremony-dial){animation:rise .55s .5s ease both}

.ceremony-dial{animation:pop 1.4s cubic-bezier(.2,.8,.2,1) both}
.ceremony--wood    .ceremony-dial{transform-origin:50% 96%;
  animation:grow 1.5s cubic-bezier(.22,.9,.25,1) both}
.ceremony--stone   .ceremony-dial{animation:chip 1.4s cubic-bezier(.2,.85,.2,1) both}
.ceremony--bronze  .ceremony-dial{animation:cast 1.5s cubic-bezier(.2,.8,.2,1) both}
.ceremony--silver  .ceremony-dial{animation:buff 1.4s cubic-bezier(.25,.9,.25,1) both}
.ceremony--gold    .ceremony-dial{animation:strike 1.4s cubic-bezier(.2,.85,.2,1) both}
.ceremony--mithril .ceremony-dial{animation:ignite 1.8s cubic-bezier(.3,.7,.2,1) both}
@keyframes pop{0%{opacity:0;transform:scale(.82)}42%{opacity:1;transform:scale(1.05)}72%{transform:scale(.99)}100%{opacity:1;transform:scale(1)}}
@keyframes flare{from{opacity:.9;transform:scale(.6)}to{opacity:0;transform:scale(1.6)}}
@keyframes rise{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
@keyframes grow{0%{opacity:0;transform:scaleY(.24) scaleX(.86)}55%{opacity:1;transform:scaleY(1.06) scaleX(1.02)}100%{opacity:1;transform:scale(1)}}
@keyframes chip{0%{opacity:0;transform:scale(.74) rotate(-9deg)}40%{opacity:1;transform:scale(1.04) rotate(3deg)}62%{transform:scale(.99) rotate(-1.5deg)}100%{opacity:1;transform:scale(1) rotate(0)}}
@keyframes cast{0%{opacity:0;transform:scale(.62) translateY(14px)}50%{opacity:1;transform:scale(1.07) translateY(0)}75%{transform:scale(.98)}100%{opacity:1;transform:scale(1)}}
@keyframes buff{0%{opacity:0;transform:scale(1.14)}30%{opacity:1;transform:scale(1.02)}55%{transform:scale(.985)}78%{transform:scale(1.008)}100%{opacity:1;transform:scale(1)}}
@keyframes strike{0%{opacity:0;transform:scale(.9) rotate(6deg)}28%{opacity:1;transform:scale(1.08) rotate(-2deg)}55%{transform:scale(.99) rotate(.6deg)}100%{opacity:1;transform:scale(1) rotate(0)}}
@keyframes ignite{0%{opacity:0;transform:scale(.52)}35%{opacity:.85;transform:scale(.94)}68%{opacity:1;transform:scale(1.05)}100%{opacity:1;transform:scale(1)}}
@media(prefers-reduced-motion:reduce){
  .ceremony *{animation-duration:.01ms!important;animation-iteration-count:1!important}
}

/* --- сайты --- */
.sites td.host{font-family:var(--mono);font-size:.86em;word-break:break-all;
  font-weight:400}
h3.dev{margin:1.25rem 0 .35rem;font-size:.92rem;display:flex;gap:.5rem;
  align-items:center}
.hosts{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;gap:.4rem}
.hosts li{display:flex;gap:.45rem;align-items:baseline;border:1px solid var(--line);
  border-radius:var(--pill);padding:.2rem .7rem;font-size:.82rem}
.hosts .host{font-family:var(--mono);word-break:break-all}
/* Число обращений — не декор, а единственная цифра в строке, и в открытом
   профиле она и есть содержание. На --mute у неё выходило 2.6 к фону панели:
   читается как серая тень. --dim даёт 5.1, столько же, сколько у остального
   второстепенного текста. */
.hosts .amt{color:var(--dim);font-variant-numeric:tabular-nums}

/* Имя сайта — кнопка: открывает разбор по конфигам окном. Выглядит как текст,
   потому что это и есть текст строки.

   Гасить фон нужно И в покое, И под курсором. Глобальное правило кнопок
   красит их в акцентный и скругляет в пилюлю, а `button:hover` меняет фон на
   светлый — сбросив только первое, мы получали при наведении светлую пилюлю
   ШИРЕ текста (её скруглённые концы и торчали слева и справа) с бледно-розовой
   надписью вместо инверсии. Здесь инверсия настоящая: фон акцентный, буквы
   цвета подложки. */
.hostbtn{background:none;border:0;border-radius:3px;padding:0;margin:0;
  font:inherit;color:inherit;cursor:pointer;text-align:left;
  max-width:100%;word-break:break-all;
  transition:background .12s ease,color .12s ease,box-shadow .12s ease}
/* Подложку растягивает ТЕНЬ, а не отступы. Отступы уменьшали ширину
   содержимого (max-width:100% + padding), и длинное имя вроде
   `store.steampowered.com` переносилось на вторую строку ровно в тот момент,
   когда на него наводишь. Тень на вёрстку не влияет вовсе: строка стоит на
   месте, а подложка облегает буквы с запасом в три пикселя. */
/* Домен рядом с именем службы: он нужен тому, кто хочет проверить, и не
   должен мешать тому, кто просто читает список. */
.hostbtn .dom{margin-left:.45rem;color:var(--dim);font-size:.82em;
  white-space:nowrap}
/* Узко — домен уходит: имя службы человек читает, домен проверяет. Первое
   важнее, и разорванное посреди слова `windowsupdat e.com` не помогает
   никому. */
@media (max-width:44rem){.hostbtn .dom{display:none}}
.hostbtn:hover .dom,.hostbtn:focus-visible .dom{color:inherit;opacity:.72}
.hostbtn:hover,.hostbtn:focus-visible{background:var(--accent);color:var(--bg);
  box-shadow:0 0 0 3px var(--accent);border-color:transparent;
  text-decoration:none;outline:none}
.hosts .hostbtn{display:flex;gap:.45rem;align-items:baseline}
/* Внутри капсулы «по конфигам» инвертируется вся капсула вместе со счётчиком:
   иначе цифра осталась бы приглушённой на светлом и пропала. */
.hosts .hostbtn:hover .amt,.hosts .hostbtn:focus-visible .amt{color:var(--bg)}

/* Звёздочка витрины. Погасшая — обычная рамка списка, зажжённая — акцент:
   разница видна и на 42 пикселях, и в чёрно-белом. */
/* --dim, а не --mute: это орган управления, а не украшение. На --mute
   контраст к панели выходил 2.6 — звёздочку было не отличить от тени, и
   человек не понял бы, что на неё можно нажать. */
.pin{background:none;border:0;cursor:pointer;color:var(--dim);font-size:1.05rem;
  line-height:1;padding:.15rem .35rem}
/* Фон гасится и под курсором: без этого глобальное `button:hover` красило
   звёздочку светлой пилюлей — тот же случай, что и с именем сайта. */
.pin:hover{color:var(--accent);background:none}
.pin.on{color:var(--warn)}
.sites td.pin-col,.sites th.pin-col{text-align:right;width:3rem;
  white-space:nowrap}
tr.pinned td.host{color:var(--ink)}

/* Витрина в профиле: те же капсулы, но со временем и без действий. */
.showcase li{border-color:color-mix(in srgb,var(--accent) 35%,var(--line))}
.hosts li.on{border-color:color-mix(in srgb,var(--warn) 45%,var(--line))}
.hosts li.on .linklike{color:var(--mute);font-size:.9rem}
.hosts li.on .linklike:hover{color:var(--danger)}

/* «Прочее» — свёрнутая часть списка. Служебные узлы стучатся сами каждую
   минуту и занимали первые места; здесь они видны, но не мешают. */
.misc>summary{cursor:pointer;font-weight:600;padding:.5rem 0;
  list-style:none;display:flex;align-items:center;gap:.5rem}
.misc>summary::-webkit-details-marker{display:none}
.misc>summary::before{content:"▸";color:var(--mute);font-size:.8em;
  transition:transform .15s ease}
.misc[open]>summary::before{transform:rotate(90deg)}
.misc>summary:hover{color:var(--accent)}
.misc table{opacity:.82}

/* Выключатели профиля: что из своего человек показывает остальным.
   Строка на пункт, кнопка справа — так видно, что выключателей несколько и
   каждый сам по себе, а не один общий «открыть профиль». */
.switches{list-style:none;padding:0;margin:.9rem 0 0}
.switches li{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;
  padding:.7rem 0;border-top:1px solid var(--line)}
.switches li:first-child{border-top:0}
.switches .nm{flex:1 1 12rem;min-width:0;display:flex;gap:.5rem;
  align-items:baseline;flex-wrap:wrap;font-weight:600}
/* Открытый пункт помечен цветом заголовка, а не галочкой: подпись на кнопке
   говорит, что произойдёт при нажатии, и без метки «Скрыть» читалось бы как
   «сейчас скрыто». */
.switches li.on .nm{color:var(--accent)}

/* Разовый показ логина нового пользователя. Красным по краю — не украшение:
   это единственный экран, где секрет виден, и второй раз его не покажут. */
.flash.secret{border-color:color-mix(in srgb,var(--warn) 55%,transparent);
  background:color-mix(in srgb,var(--warn) 10%,transparent)}
.flash.secret .link{margin-top:.6rem;text-align:center;letter-spacing:.06em}

/* Журнал действий в админке. Свёрнут: он нужен при разборе происшествия,
   а не каждый день. */
.journal{font-size:.85rem}
.journal td.det{color:var(--dim);word-break:break-word}
.journal td.num{white-space:nowrap;color:var(--dim)}
@media(max-width:600px){
  .journal td:first-child{flex:0 0 auto;font-weight:400}
  .journal td.det{flex:1 0 100%}
}
