:root{
  --bg:#12141a; --panel:#191c24; --panel2:#1f2430; --line:#2a3040;
  --text:#e6e9f0; --dim:#8d95a8; --accent:#ffd45e; --up:#5fd97a;
  --down:#ff6b5e; --link:#7fb4ff;
}
*{box-sizing:border-box}
body{
  margin:0; background:var(--bg); color:var(--text);
  font:15px/1.5 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
  -webkit-text-size-adjust:100%;
  /* Ничего не должно уезжать вбок: горизонтально ездят только таблицы, и
     каждая внутри своей обёртки. */
  overflow-x:hidden;
}
.wrap{max-width:1080px; margin:0 auto; padding:0 18px}
.hidden{display:none !important}
a{color:var(--link); text-decoration:none}
a:hover{text-decoration:underline}
code{background:var(--panel2); padding:1px 5px; border-radius:4px; font-size:12px}

header{
  border-bottom:1px solid var(--line); background:var(--panel);
  position:sticky; top:0; z-index:5;
  /* Тень нужна именно из-за липкости: содержимое проезжает под шапкой, и без
     тени оно об неё стукается. */
  box-shadow:0 6px 14px -12px rgba(0,0,0,.9);
}
/* Шапке даём больше ширины, чем колонкам страницы: вкладок шесть, и в 1080
   они жались друг к другу. */
header .wrap{max-width:1400px}
.head{display:flex; align-items:center; gap:14px; height:58px}
.brand{
  font-weight:800; letter-spacing:.5px;
  color:var(--text); text-decoration:none;
}
.brand span{color:var(--accent)}
nav{display:flex; gap:13px; white-space:nowrap}
nav a{color:var(--dim); padding:4px 2px; border-bottom:2px solid transparent}
nav a.active{color:var(--text); border-bottom-color:var(--accent)}
nav a:hover{text-decoration:none; color:var(--text)}
.spacer{flex:1}
input[type=search],input[type=password],input[type=text]{
  background:var(--panel2); border:1px solid var(--line); color:var(--text);
  border-radius:7px; padding:7px 10px; outline:none; min-width:180px;
}
#search{flex:0 1 210px; min-width:130px}
input:focus{border-color:var(--accent)}
button{
  background:var(--panel2); border:1px solid var(--line); color:var(--text);
  border-radius:7px; padding:7px 12px; cursor:pointer;
}
button:hover{border-color:var(--accent)}
button.ghost{color:var(--dim)}

.hint{color:var(--dim); font-size:13px}
main.wrap{padding-top:34px; padding-bottom:48px}
.lead{color:var(--dim); max-width:70ch}

.totals{display:flex; gap:22px; flex-wrap:wrap; margin-bottom:16px; color:var(--dim); font-size:13px}
.totals b{color:var(--text); font-size:16px; display:block}

table.grid{width:100%; border-collapse:collapse}
.grid th{
  text-align:left; font-size:12px; text-transform:uppercase; letter-spacing:.6px;
  color:var(--dim); font-weight:600; padding:8px 10px; border-bottom:1px solid var(--line);
}
.grid td{padding:9px 10px; border-bottom:1px solid #21252f}
.grid tbody tr:hover{background:var(--panel)}
.num{text-align:right}
.rank{color:var(--dim); width:46px}
.elo{font-weight:700; font-variant-numeric:tabular-nums}
.nick{font-weight:600}

.tier{
  display:inline-block; min-width:34px; text-align:center; padding:2px 8px;
  border-radius:6px; font-weight:700; font-size:12px; background:var(--panel2);
  border:1px solid var(--line); color:var(--dim);
}
.tier.t-Splus{background:#4a0f16; border-color:#a0303c; color:#ff9b9b}
.tier.t-S{background:#3b1418; border-color:#7b2630; color:#ff8a8a}
.tier.t-Aplus,.tier.t-A,.tier.t-Aminus{background:#3a3110; border-color:#7a6a1e; color:var(--accent)}
.tier.t-Bplus,.tier.t-B,.tier.t-Bminus{background:#122a3c; border-color:#22557a; color:#7fc4ff}
.tier.t-Cplus,.tier.t-C,.tier.t-Cminus{background:#1a2a1c; border-color:#2f5c35; color:#8fd79a}
.tier.t-Dplus,.tier.t-D,.tier.t-Dminus{background:#242424; border-color:#3d3d3d; color:#b0b0b0}
.tier.t-Eplus,.tier.t-E{background:#1c1a22; border-color:#332f3d; color:#8b869a}
.unplayed{opacity:.55}
.unplayed .elo{color:var(--dim)}

.flag{color:var(--accent); font-weight:700; cursor:help}
.badge{
  display:inline-block; margin-left:8px; padding:1px 7px; border-radius:5px;
  font-size:11px; background:var(--panel2); border:1px solid var(--line);
  color:var(--dim); cursor:help;
}
.badge.up{color:var(--up); border-color:#2f5c35}
.badge.down{color:var(--down); border-color:#6b2a24}
.badge.calib{color:#e0b64a; border-color:#6b5420; background:#2b2415}
.calib{color:#e0b64a; font-size:12px; cursor:help}
.subtle{color:var(--dim); font-size:13px; margin:10px 0 0}

.bandsbox{
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:14px 16px; margin-bottom:18px;
}
.bandshead{display:flex; align-items:baseline; gap:12px; margin-bottom:10px; font-size:13px}
.dist{width:100%; height:168px; display:block}
.dist .distarea{fill:rgba(255,212,94,.13)}
.dist .distline{fill:none; stroke:var(--accent); stroke-width:2; stroke-linejoin:round}
.dist .bandcell{opacity:.85}
.dist .bandcell:hover{opacity:1}
.dist .banddiv{stroke:#5a6272; stroke-width:1; stroke-dasharray:3 3; opacity:.7}
.dist .bandlabel{fill:#12141a; font-size:12px; font-weight:800}
.dist .bandtick{fill:var(--dim); font-size:11px; font-variant-numeric:tabular-nums}
.glyph{
  height:1.05em; width:auto; vertical-align:-0.15em; margin:0 1px;
  image-rendering:auto;
}
.playerhead h1 .glyph,.heroname h1 .glyph{height:0.85em}
.ico{
  display:inline-block; vertical-align:middle; margin-right:8px; flex:none;
  background-size:contain; background-repeat:no-repeat; background-position:center;
}
.namecell{display:flex; align-items:center}
.guess{color:var(--dim); font-size:12px; font-style:italic; cursor:help}

/* --- «сейчас на сервере» ---
   Кружок сразу за ником — в таблице эло и в тир-листе. Пустой он у всех, кого
   сейчас нет: место под него держится всегда, иначе строка дёргалась бы на
   каждый чужой заход. Мигание мягкое, на два счёта: это метка, а не тревога. */
.pdot{width:8px; height:8px; border-radius:50%; flex:none}
.namecell .pdot{margin-left:8px}
/* В тир-листе строку до правого края растягивал ник; теперь за ником идёт
   кружок, и распорка живёт в нём — иначе он уехал бы вправо, к рейтингу. */
.tierrow .pdot{margin-right:auto}
.pdot.on{
  background:var(--up); cursor:help;
  animation:pdotpulse 1.8s ease-in-out infinite;
}
/* Зритель подключён, но не играет — тот же кружок, только полый. Кольцо рисуем
   рамкой, а не тенью: тень занята миганием и перебила бы его. */
.pdot.on.watching{background:transparent; border:2px solid var(--up)}
@keyframes pdotpulse{
  0%,100%{opacity:1; box-shadow:0 0 0 3px rgba(95,217,122,.16)}
  50%{opacity:.35; box-shadow:0 0 0 3px rgba(95,217,122,0)}
}
@media (prefers-reduced-motion: reduce){
  .pdot.on{animation:none; box-shadow:0 0 0 3px rgba(95,217,122,.16)}
}

.tiergroup{margin:18px 0 26px}
.tierhead{display:flex; align-items:baseline; gap:12px; margin-bottom:8px}
.tierhead h2{margin:0; font-size:18px}
.tierhead .meta{color:var(--dim); font-size:13px}
.tierrow{
  display:flex; align-items:center; gap:12px; padding:7px 10px;
  border-bottom:1px solid #21252f;
}
.tierrow:hover{background:var(--panel)}
.tierrow .nick{min-width:0}
.tierrow .exact{min-width:38px; margin-right:2px}
.event .by{color:var(--dim); font-size:12px; width:110px; text-align:right}
.dim{color:var(--dim)}
.tierrow .elo{width:70px; text-align:right}

.card{background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:18px; margin-bottom:16px}
.playerhead{display:flex; align-items:center; gap:16px; flex-wrap:wrap}
.playerhead h1{margin:0; font-size:26px}
.pb{color:var(--dim); font-size:12px; font-family:ui-monospace,monospace}
.statline{display:flex; gap:26px; flex-wrap:wrap; margin-top:14px}
.statline div{color:var(--dim); font-size:13px}
.statline b{display:block; color:var(--text); font-size:19px; font-variant-numeric:tabular-nums}

.chart{width:100%; height:200px; display:block}
.chart .line{fill:none; stroke:var(--accent); stroke-width:2}
.chart .area{fill:rgba(255,212,94,.10)}
.chart .base{stroke:#39404f; stroke-width:1; stroke-dasharray:4 4}
.chart text{fill:var(--dim); font-size:11px}
/* Калибровка: рейтинг там ходил вчетверо резче, и этот кусок читается иначе. */
.chart .calibzone{fill:rgba(224,182,74,.1)}
.chart .calibline{stroke:#e0b64a; stroke-width:1; stroke-dasharray:3 3; opacity:.75}
.chart .calibtext{fill:#e0b64a; font-size:10px; opacity:.8}

/* Соперник в строке события — таким же, каким он выглядит везде. */
.evwho{display:inline-flex; align-items:center; gap:2px; vertical-align:-4px}
.evwho .ico{margin-right:0}
.event .what{display:flex; align-items:center; gap:5px; flex-wrap:wrap}
.event .score{
  font-variant-numeric:tabular-nums; font-weight:700; font-size:13px;
  background:var(--panel2); border:1px solid var(--line); border-radius:5px;
  padding:0 6px; cursor:help;
}
.event .gap{color:var(--dim); font-size:12px; cursor:help}

.events{max-height:420px; overflow:auto}
.event{display:flex; gap:12px; padding:6px 0; border-bottom:1px solid #21252f; font-size:14px}
.event .when{color:var(--dim); width:120px; flex:none; font-size:12px}
.event .what{flex:1}
.event .d{width:64px; text-align:right; font-variant-numeric:tabular-nums; font-weight:600}
.up{color:var(--up)} .down{color:var(--down)}

.suggest{background:#241f14; border:1px solid #3a3018; border-radius:8px; padding:10px 14px; margin-top:12px}

footer{color:var(--dim); font-size:12px;
       display:flex; flex-direction:column; gap:10px}
footer.wrap{padding-top:16px; padding-bottom:40px}
/* Документы и поддержка — первой строкой подвала и за чертой: они относятся
   ко всему сайту, а не к таблице, под которой стоят. */
.footdocs{
  display:flex; gap:8px 20px; flex-wrap:wrap; align-items:baseline;
  border-top:1px solid var(--line); padding-top:16px; font-size:13px;
}
.footdocs a{color:var(--dim)}
.footdocs a:hover{color:var(--text)}
/* Поддержку ищут в беде — единственная приметная ссылка в подвале. */
.footdocs .support{color:var(--link)}
.footline{display:flex; gap:16px; flex-wrap:wrap; align-items:baseline}
/* Телеграм — вторая опора сообщества, поэтому в подвале, а не в шапке:
   заметно, но не спорит с навигацией. */
.tglink{margin-left:auto}

/* На телефоне ссылки встают столбиком: в строку они всё равно не помещаются,
   а перенос вразнобой читается хуже списка. Отступ по вертикали крупнее
   обычного — чтобы пальцем не попадать в соседнюю. */
@media (max-width: 560px) {
  .footdocs{flex-direction:column; gap:11px; font-size:14px}
  .footline{gap:8px}
  .tglink{margin-left:0}
}
.empty{color:var(--dim); padding:24px 0}

/* --- страницы документов: /privacy и /terms ---
   Колонка уже основной: сплошной текст в 1080 читать нечем — глаз теряет
   начало строки. Ссылка «на сайт» в шапке своя, не .backlink: у того нижний
   отступ, и в ряду по центру он поднимал бы ссылку над остальным. */
.docbody{max-width:760px; line-height:1.65}
.docbody h1{font-size:26px; margin:0 0 6px}
.docdate{color:var(--dim); font-size:13px; margin:0 0 24px}
.docbody h2{font-size:17px; margin:30px 0 10px}
.docbody p{margin:10px 0}
.docbody ul{margin:10px 0; padding-left:22px}
.docbody li{margin:5px 0}
/* Пересказ своими словами перед сводом пунктов: его и прочитают. */
.docsum{
  background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--accent);
  border-radius:10px; padding:14px 18px; margin-bottom:8px;
}
.docsum p{margin:0; color:var(--dim)}
.tosite{color:var(--dim); font-size:13px; white-space:nowrap}

/* --- онлайн и чат --- */
.ocount{
  display:none; margin-left:6px; padding:0 6px; border-radius:9px;
  font-size:11px; font-weight:700; background:#1d3a24; color:var(--up);
  border:1px solid #2f5c35; vertical-align:1px;
}
.ocount.live{display:inline-block}

.servers{display:flex; flex-direction:column; gap:12px; margin-bottom:16px}
.server{background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:14px 16px}
.serverhead{display:flex; align-items:center; gap:10px; flex-wrap:wrap}
.serverhead .dot{
  width:8px; height:8px; border-radius:50%; background:var(--up); flex:none;
  box-shadow:0 0 0 3px rgba(95,217,122,.15);
}
.serverhead .sname{font-size:15px}
.serverhead .meta{color:var(--dim); font-size:13px}
.serverhead .count{
  margin-left:auto; font-variant-numeric:tabular-nums; font-weight:700;
  color:var(--dim);
}
.server.off{opacity:.5}
.server.off .dot{background:#4a5060; box-shadow:none}

/* Адрес сервера: моноширинный ярлык-кнопка. Моноширинным нарочно — его
   сверяют по цифрам с тем, что набрали в игре, а в пропорциональном шрифте
   единицы и двойки сливаются. */
.addr{
  padding:2px 9px; border-radius:6px; cursor:pointer;
  background:var(--panel2); border:1px solid var(--line); color:var(--dim);
  font:12px/1.5 ui-monospace,monospace; white-space:nowrap;
}
.addr:hover{border-color:var(--accent); color:var(--text)}
.addr.copied{border-color:#2f5c35; color:var(--up); background:#16241a}
.addr.failed{border-color:#5c2f2f; color:var(--down); background:#2a1a1a}

.chips{display:flex; flex-wrap:wrap; gap:8px; margin-top:12px}
.chip{
  display:flex; align-items:center; gap:6px; padding:4px 10px 4px 6px;
  background:var(--panel2); border:1px solid var(--line); border-radius:20px;
}
.chip .ico{margin-right:0}
.chip .nick{font-weight:600; font-size:14px}
.chip .tier{min-width:0; padding:1px 6px; font-size:11px}
.chipelo{color:var(--dim); font-size:12px; font-variant-numeric:tabular-nums}
.chip.watching{opacity:.55}
.chips .empty{padding:2px 0; font-size:13px}

.panehead{flex:none; padding:11px 14px 10px; border-bottom:1px solid var(--line)}
.panehead h2{
  margin:0; font-size:13px; font-weight:700; color:var(--dim);
  text-transform:uppercase; letter-spacing:.8px;
}

/* Лента ездит только вверх-вниз. Вбок её растягивало длинное слово и широкая
   строка события — и под чатом появлялась горизонтальная полоса во всю ширину. */
.feed{
  flex:1; min-height:0; padding:8px 10px 10px;
  overflow-y:auto; overflow-x:hidden; overflow-wrap:anywhere;
  overscroll-behavior:contain;
  scrollbar-width:thin; scrollbar-color:#333a4a transparent;
}
.feed::-webkit-scrollbar{width:8px}
.feed::-webkit-scrollbar-thumb{background:#333a4a; border-radius:4px}
.feed::-webkit-scrollbar-thumb:hover{background:#41495c}
.feed::-webkit-scrollbar-track{background:transparent}
.feed .empty{margin:0; padding:10px 4px; font-size:13px}

.msg{display:flex; gap:9px; align-items:baseline; padding:4px 8px; font-size:14px;
     border-radius:8px}
.msg .when{color:var(--dim); font-size:11px; flex:none; font-variant-numeric:tabular-nums}
.msg .srvtag{
  flex:none; font-size:11px; color:var(--dim); background:var(--panel2);
  border:1px solid var(--line); border-radius:5px; padding:0 5px;
}
.msg .who{font-weight:600; flex:none; max-width:190px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.msg .who::after{content:':'; color:var(--dim)}
.msg .text{flex:1; min-width:0}
/* Написанное на сайте — тёплым фоном вместо значка: значок стоял в каждой
   такой строке, отъедал место и читался как часть текста. */
.msg.fromsite{background:rgba(255,212,94,.075); box-shadow:inset 2px 0 0 var(--accent)}

/* --- график в профиле --- */
.chart{cursor:crosshair}
.chart .tierband{opacity:.16}
.chart .tierbandlabel{fill:var(--text); font-size:10px; font-weight:700; opacity:.55}
.chart .peakline{stroke:var(--up); stroke-width:1; stroke-dasharray:5 4; opacity:.8}
.chart .peaktext{fill:var(--up); font-size:11px; font-weight:600}
.chart .guide{stroke:var(--text); stroke-width:1; opacity:.5}
.chart .guidedot{fill:var(--accent)}
.chart .guidetext{fill:var(--text); font-size:11px; font-weight:600;
                  paint-order:stroke; stroke:var(--panel); stroke-width:3px}

/* --- изменение тиров --- */
select{
  background:var(--panel2); border:1px solid var(--line); color:var(--text);
  border-radius:7px; padding:5px 8px; outline:none; font:inherit; font-size:13px;
}
select:focus{border-color:var(--accent)}
button.primary{border-color:#7a6a1e; color:var(--accent); background:#2b2415}
button.primary:disabled,button.commit:disabled{
  opacity:.4; cursor:default; border-color:var(--line); color:var(--dim); background:var(--panel2);
}

.revhead{display:flex; align-items:center; gap:12px; flex-wrap:wrap}
.revhead h1{margin:0; font-size:24px}
.revhead button{margin-left:auto; padding:4px 10px; font-size:13px}
.revstats{margin:10px 0 0; color:var(--dim); font-size:13px}
.revstats b{color:var(--text); font-size:15px; font-variant-numeric:tabular-nums}
.revtools{display:flex; gap:10px; flex-wrap:wrap; margin-top:14px}
.revstart{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:12px}

/* Колонок много (по одной на модератора) — на узком экране таблица едет вбок,
   а не ломает вёрстку страницы. */
.revtablewrap{overflow-x:auto}
.revtable{min-width:900px}
.revtable td{vertical-align:middle}
.revtable tr.done{opacity:.62}
/* Не отыграл сотни событий с прошлого изменения — судить не по чему. Строка
   остаётся на месте, но гаснет целиком, вместе с цветом тира: цветной бейдж в
   ней читался бы как «тут ещё решают». Обесцвечиваем ячейки, а не строку:
   filter на tr браузеры применяют по-разному. */
.revtable tr.idle{opacity:.42}
.revtable tr.idle td{filter:grayscale(1)}
.revtable tr.idle:hover{opacity:.72}
.idlenote{color:var(--dim); font-size:12px; cursor:help}
.revtable tr.expand:hover{background:transparent}
.revtable tr.expand td{padding:6px 14px 18px}
.revtable .namecell{gap:2px}
.revtable .nick{margin-left:2px}

.expander{
  background:none; border:none; color:var(--dim); padding:0 6px 0 0;
  font-size:12px; line-height:1; cursor:pointer;
}
.expander:hover{color:var(--accent)}
.votecol{white-space:nowrap}
.votecol.me{color:var(--accent)}
.votecell{white-space:nowrap}
.votecell .vote{min-width:132px}
.vote.todo{border-color:#7a6a1e}
/* Стрелки и «равно» — рисованные, чтобы в колонке они были одного веса. */
.vdir{
  width:13px; height:13px; flex:none; display:inline-block; margin-left:5px;
  vertical-align:-2px; fill:currentColor;
}
.vdir.same{color:var(--accent)}
.vdir.up{color:var(--up)}
.vdir.down{color:var(--down)}
.revnum{color:var(--dim); font-weight:800; margin-right:9px}
.finalcell{white-space:nowrap}

/* «Судить самому»: видно, что человек решил, но не что именно. Глаз в клетке
   открывает ровно её — чужое мнение не лезет в глаза само. */
.revbar{display:flex; justify-content:flex-end; margin:0 0 8px}
.revbar button{display:flex; align-items:center; gap:7px; padding:5px 11px;
               font-size:13px; color:var(--dim)}
.revbar button.on{color:var(--accent); border-color:#7a6a1e; background:#2b2415}
.revbar .peek{width:15px; height:15px}
.peekbtn{
  padding:2px 6px; line-height:0; border-color:transparent;
  background:transparent; color:var(--dim);
}
.peekbtn:hover{color:var(--accent); border-color:var(--line)}
.peek{width:16px; height:16px; display:block}
.peeked{cursor:pointer; display:inline-flex; align-items:center}
/* Пока решили не все — итог предварительный, и видно, что он ещё поедет. */
.finalcell .prelim{opacity:.5; border-style:dashed}
.commitcell{white-space:nowrap}
.commit{padding:3px 10px; font-weight:700; line-height:1.2}

/* --- настройки --- */
.tokenline{display:flex; gap:10px; align-items:center; margin-top:14px; flex-wrap:wrap}
.settings h2{margin:0 0 6px}

/* --- голоса в истории тира --- */
.histitem{border-bottom:1px solid #21252f}
.histitem .event{border-bottom:none}
.revline{
  display:flex; align-items:center; gap:7px; flex-wrap:wrap;
  padding:0 0 8px 132px; font-size:12px; color:var(--dim);
}
.revno{color:var(--accent)}
.votechip{
  display:inline-flex; align-items:center; gap:2px; padding:1px 7px;
  background:var(--panel2); border:1px solid var(--line); border-radius:20px;
  cursor:help;
}
.votechip .vname{color:var(--text)}
.votechip .vdir{margin-left:2px}

/* --- метки в шапке профиля ---
   Страна, пол, возраст и «когда был» — такими же карточками, как ники и
   игроки в онлайне: одна визуальная грамматика на весь сайт. */
.metachips{margin-top:12px; font-size:13px}
.metachips .chip{gap:7px; padding:4px 11px; color:var(--dim); cursor:help}
.metachips .chip.g-w{border-color:#5c2f45} .metachips .chip.g-m{border-color:#284567}
.flag-icon{font-size:17px; line-height:1}
.gender{width:15px; height:15px; flex:none; display:block}
.g-w .gender{color:#ff8ac0} .g-m .gender{color:#7fb4ff}
.metachips .chip .dot{
  width:7px; height:7px; border-radius:50%; background:#4a5060; flex:none;
}
.metachips .chip.online{color:var(--text); border-color:#2f5c35; background:#16241a}
.metachips .chip.online .dot{background:var(--up); box-shadow:0 0 0 3px rgba(95,217,122,.15)}

/* =========================================================================
   Телефон и планшет
   =========================================================================
   Порог 700px совпадает с NARROW в app.js: по нему же там выбирается система
   координат графиков и решается, перерисовывать ли всё после поворота
   экрана. Меняете здесь — поменяйте и там.  */

@media (max-width: 860px) {
  .wrap{padding:0 12px}

  /* Шапка в две строки: сверху имя, поиск и вход, снизу — сами вкладки.
     Они не переносятся, а едут вбок: их пять, и на узком экране в строку
     они всё равно не встанут. */
  /* Боковой отступ тот же, что у .wrap: иначе имя сайта и кнопка входа
     прилипают к краям экрана. Задавать его надо здесь — сокращённая запись
     padding обнуляет боковые, унаследованные от .wrap. */
  .head{height:auto; padding:9px 12px; gap:9px; flex-wrap:wrap}
  .spacer{display:none}
  #search{flex:1 1 120px; min-width:0}
  #loginBtn{max-width:42%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  /* Вкладки по центру, но safe: когда они шире экрана и едут вбок, обычный
     center прячет первую за левым краем без возможности домотать. */
  nav{order:5; width:100%; gap:16px; overflow-x:auto; scrollbar-width:none;
      justify-content:center; justify-content:safe center}
  nav::-webkit-scrollbar{display:none}
  nav a{white-space:nowrap}

  /* На телефоне iOS увеличивает страницу, если у поля шрифт меньше 16px. */
  input[type=search],input[type=password],input[type=text],select{font-size:16px}
  button{padding:8px 12px}

  main.wrap{padding-top:24px; padding-bottom:34px}
  .card{padding:14px; border-radius:10px}
  .totals{gap:14px; font-size:12px}
  .totals b{font-size:15px}
  .statline{gap:16px}
  .statline b{font-size:17px}
  .playerhead h1{font-size:21px}
  .playerhead .ico{width:40px !important; height:40px !important}
  .bandsbox{padding:12px}
  .bandshead{flex-wrap:wrap; gap:4px}
  .event .when{width:78px; font-size:11px}
  .event .by{width:auto; max-width:90px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .msg .who{max-width:110px}
  /* На телефоне пластина ужимается по бокам, но не по воздуху
     внутри секций: тесниться должен экран, а не содержимое. */
  .authsect{padding:20px}
  .authsect.who{padding:16px 20px}
}

@media (max-width: 700px) {
  /* Второстепенные колонки таблицы эло прячем: без них строка помещается
     целиком, а киллы и смерти есть в карточке игрока. */
  .grid .col-n,.grid .col-k,.grid .col-d,.grid .col-a{display:none}
  .grid th,.grid td{padding:8px 6px}
  .grid th{font-size:11px; letter-spacing:.3px}
  .rank{width:34px; font-size:12px}
  .tier{min-width:28px; padding:2px 6px}
  .dist{height:150px}
  .chart{height:180px}

  .tierrow{gap:8px; padding:7px 4px}
  .tierrow .exact{min-width:32px}
  .tierrow .elo{width:52px}
  .tierhead h2{font-size:16px}

  .server{padding:12px}
  /* Адрес на телефоне — своей строкой во всю ширину: в строку с именем,
     режимом и счётчиком он не встаёт, а рвать его переносом нельзя. */
  .serverhead .addr{order:9; flex:1 1 100%; text-align:center; padding:6px 9px}
  .revhead h1{font-size:20px}
  .revhead button{margin-left:0}
  .revtools{gap:8px}
  .revtools button{flex:1 1 auto}
  .revline{padding-left:0}
}

@media (max-width: 760px) {
  /* Изменение тиров: девять колонок в строку не влезут никак, поэтому
     каждая строка разворачивается в карточку и подписывает свои поля сама
     (data-label на ячейках). Горизонтальная прокрутка была бы честнее по
     коду, но голосовать в ней с телефона невозможно. */
  .revtablewrap{overflow-x:visible}
  .revtable{min-width:0}
  .revtable thead{display:none}
  .revtable,.revtable tbody,.revtable tr,.revtable td{display:block; width:100%}
  .revtable tr{
    display:flex; flex-wrap:wrap; align-items:center; column-gap:10px;
    background:var(--panel); border:1px solid var(--line); border-radius:10px;
    padding:9px 12px; margin-bottom:10px;
  }
  .revtable tbody tr:hover{background:var(--panel)}
  .revtable tr.done{opacity:.7}
  /* На карточке серая строка сливалась бы с фоном — гасим мягче. */
  .revtable tr.idle{opacity:.55}
  .revtable td{border:none; padding:3px 0; display:flex; align-items:center; gap:8px}
  .revtable td[data-label]::before{
    content:attr(data-label); color:var(--dim); font-size:12px;
    flex:0 0 92px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  /* Первая строка карточки — место, игрок и рейтинг: то, по чему её узнают. */
  .revtable td.rank{width:auto; padding:0; color:var(--dim); font-size:12px}
  /* Базис в процентах, а не по содержимому: перенос строк flexbox считает по
     нему, и без этого у коротких ников на первую строку запрыгивало ещё и
     соседнее поле — карточки выходили все разной формы. */
  .revtable td.namecell{flex:1 1 60%; width:auto; padding:0; font-size:16px}
  /* Рейтинг забирает остаток первой строки: так за него не цепляется
     следующее поле, и шапка карточки всегда выглядит одинаково. */
  .revtable td.elo{flex:1 1 auto; width:auto; padding:0; font-size:16px;
                   justify-content:flex-end}
  .revtable td.elo::before{display:none}
  /* Дальше — по два поля в ряд: иначе карточка растягивается на пол-экрана. */
  .revtable td.nowcell,.revtable td.guesscell,.revtable td.votecell{
    width:calc(50% - 5px);
  }
  .revtable td.nowcell::before,.revtable td.guesscell::before,
  .revtable td.votecell::before{flex-basis:auto; max-width:50%}
  .revtable td.votecell.mine{width:100%; padding-top:5px}
  .revtable td.finalcell{width:auto; flex:1 1 auto; padding-top:5px}
  .revtable td.commitcell{width:auto; padding-top:5px}
  .revtable .vote{flex:1; min-width:0}
  /* Развёрнутая статистика — не карточка, а продолжение своей строки. */
  .revtable tr.expand{
    background:transparent; border:none; border-radius:0;
    padding:0 2px; margin:-8px 0 12px;
  }
  .revtable tr.expand td{display:block; padding:0}
}

@media (max-width: 460px) {
  .grid .col-kd{display:none}
  .brand{font-size:14px}
  nav{gap:13px; font-size:14px}
  .playerhead{gap:10px}
  .statline{gap:12px}
  .statline div{font-size:12px}
  .revtable td[data-label]::before{flex-basis:82px}
}

/* --- динамика: полотно на весь экран --- */
body.wide main.wrap{max-width:none; padding:0}
body.wide footer{display:none}
body.wide{overflow:hidden}

.linewrap{display:flex; flex-direction:column; height:calc(100vh - 59px)}
.linestage{flex:1; display:flex; min-height:0}
/* width:100% тут нельзя: canvas требовал бы всю ширину полосы и выдавливал
   табло за экран. Ширину даёт flex, а canvas берёт её через clientWidth. */
.linecanvas{flex:1 1 auto; min-width:0; display:block; height:100%; cursor:crosshair}

.lineboard{
  width:290px; flex:none; border-left:1px solid var(--line); padding:12px 10px;
  background:var(--panel); overflow:hidden;
}
.boardhead{display:flex; align-items:baseline; gap:10px; margin-bottom:10px}
.boardhead .clock{color:var(--accent); font-weight:800; font-variant-numeric:tabular-nums}
.boardhead .dim{font-size:12px}
.boardrows{position:relative; transition:height .3s}
/* Строки не пересоздаются, а переезжают: обгон должен читаться движением. */
.boardrow{
  position:absolute; left:0; right:0; height:40px; display:flex; align-items:center;
  gap:9px; padding:0 8px; border-radius:9px; cursor:pointer;
  transition:transform .5s cubic-bezier(.32,.72,0,1), opacity .3s, background .2s;
}
.boardrow:hover{background:var(--panel2)}
.boardrow.on{background:#2b2415}
.boardrow .place{color:var(--dim); font-size:12px; width:26px; flex:none;
                 font-variant-numeric:tabular-nums}
.boardrow .ico{margin-right:0}
.boardrow .who{flex:1; min-width:0; display:flex; flex-direction:column; line-height:1.15}
.boardrow .nick{font-size:14px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.boardrow .sub{font-size:10px; align-self:flex-start; min-width:0; padding:0 5px}
.boardrow .elo{font-weight:700; font-variant-numeric:tabular-nums}

.linebar{
  height:56px; flex:none; border-top:1px solid var(--line); background:var(--panel);
  display:flex; align-items:center; gap:14px; padding:0 16px;
}
.playbtn{
  width:38px; height:38px; flex:none; border-radius:50%; padding:0; font-size:14px;
  border-color:#7a6a1e; color:var(--accent); background:#2b2415;
}
.track{flex:1; height:26px; position:relative; cursor:pointer; display:flex; align-items:center}
.trackline{position:absolute; left:0; right:0; height:4px; background:var(--line); border-radius:2px}
.trackfill{position:absolute; left:0; height:4px; background:var(--accent); border-radius:2px}
.trackknob{
  position:absolute; width:14px; height:14px; margin-left:-7px; border-radius:50%;
  background:var(--accent); box-shadow:0 0 0 5px rgba(255,212,94,.15);
}
.speeds,.ranges{display:flex; gap:6px; flex:none}
.speeds button,.ranges button{padding:5px 9px; font-size:12px; color:var(--dim)}
.speeds button.on,.ranges button.on{color:var(--accent); border-color:#7a6a1e}

/* --- писать в игровой чат прямо с сайта ---
   Поле с кнопкой отдельной строкой, подсказка и остаток лимита — под ними:
   в одну строку поле сжималось до пары слов. */
.chatsay{flex:none; border-top:1px solid var(--line); padding:10px 12px}
.sayrow{display:flex; gap:8px}
.sayrow input{flex:1; min-width:0}
.sayfoot{display:flex; align-items:baseline; gap:12px; margin-top:7px}
.sayfoot .hint{flex:1; min-width:0}
.quota{flex:none; font-size:12px; color:var(--dim); font-variant-numeric:tabular-nums}
.quota.wait{color:var(--accent)}

@media (max-width: 860px) {
  .wrap{padding:0 12px}

  /* Шапка в две строки: сверху имя, поиск и вход, снизу — сами вкладки.
     Они не переносятся, а едут вбок: их пять, и на узком экране в строку
     они всё равно не встанут. */
  /* Боковой отступ тот же, что у .wrap: иначе имя сайта и кнопка входа
     прилипают к краям экрана. Задавать его надо здесь — сокращённая запись
     padding обнуляет боковые, унаследованные от .wrap. */
  .head{height:auto; padding:9px 12px; gap:9px; flex-wrap:wrap}
  .spacer{display:none}
  #search{flex:1 1 120px; min-width:0}
  #loginBtn{max-width:42%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  /* Вкладки по центру, но safe: когда они шире экрана и едут вбок, обычный
     center прячет первую за левым краем без возможности домотать. */
  nav{order:5; width:100%; gap:16px; overflow-x:auto; scrollbar-width:none;
      justify-content:center; justify-content:safe center}
  nav::-webkit-scrollbar{display:none}
  nav a{white-space:nowrap}

  /* На телефоне iOS увеличивает страницу, если у поля шрифт меньше 16px. */
  input[type=search],input[type=password],input[type=text],select{font-size:16px}
  button{padding:8px 12px}

  main.wrap{padding-top:24px; padding-bottom:34px}
  .card{padding:14px; border-radius:10px}
  .totals{gap:14px; font-size:12px}
  .totals b{font-size:15px}
  .statline{gap:16px}
  .statline b{font-size:17px}
  .playerhead h1{font-size:21px}
  .playerhead .ico{width:40px !important; height:40px !important}
  .bandsbox{padding:12px}
  .bandshead{flex-wrap:wrap; gap:4px}
  .event .when{width:78px; font-size:11px}
  .event .by{width:auto; max-width:90px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .msg .who{max-width:110px}
  /* На телефоне пластина ужимается по бокам, но не по воздуху
     внутри секций: тесниться должен экран, а не содержимое. */
  .authsect{padding:20px}
  .authsect.who{padding:16px 20px}
}

@media (max-width: 700px) {
  /* Второстепенные колонки таблицы эло прячем: без них строка помещается
     целиком, а киллы и смерти есть в карточке игрока. */
  .grid .col-n,.grid .col-k,.grid .col-d,.grid .col-a{display:none}
  .grid th,.grid td{padding:8px 6px}
  .grid th{font-size:11px; letter-spacing:.3px}
  .rank{width:34px; font-size:12px}
  .tier{min-width:28px; padding:2px 6px}
  .dist{height:150px}
  .chart{height:180px}

  .tierrow{gap:8px; padding:7px 4px}
  .tierrow .exact{min-width:32px}
  .tierrow .elo{width:52px}
  .tierhead h2{font-size:16px}

  .server{padding:12px}
  /* Адрес на телефоне — своей строкой во всю ширину: в строку с именем,
     режимом и счётчиком он не встаёт, а рвать его переносом нельзя. */
  .serverhead .addr{order:9; flex:1 1 100%; text-align:center; padding:6px 9px}
  .revhead h1{font-size:20px}
  .revhead button{margin-left:0}
  .revtools{gap:8px}
  .revtools button{flex:1 1 auto}
  .revline{padding-left:0}
}

@media (max-width: 760px) {
  /* Изменение тиров: девять колонок в строку не влезут никак, поэтому
     каждая строка разворачивается в карточку и подписывает свои поля сама
     (data-label на ячейках). Горизонтальная прокрутка была бы честнее по
     коду, но голосовать в ней с телефона невозможно. */
  .revtablewrap{overflow-x:visible}
  .revtable{min-width:0}
  .revtable thead{display:none}
  .revtable,.revtable tbody,.revtable tr,.revtable td{display:block; width:100%}
  .revtable tr{
    display:flex; flex-wrap:wrap; align-items:center; column-gap:10px;
    background:var(--panel); border:1px solid var(--line); border-radius:10px;
    padding:9px 12px; margin-bottom:10px;
  }
  .revtable tbody tr:hover{background:var(--panel)}
  .revtable tr.done{opacity:.7}
  /* На карточке серая строка сливалась бы с фоном — гасим мягче. */
  .revtable tr.idle{opacity:.55}
  .revtable td{border:none; padding:3px 0; display:flex; align-items:center; gap:8px}
  .revtable td[data-label]::before{
    content:attr(data-label); color:var(--dim); font-size:12px;
    flex:0 0 92px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  /* Первая строка карточки — место, игрок и рейтинг: то, по чему её узнают. */
  .revtable td.rank{width:auto; padding:0; color:var(--dim); font-size:12px}
  /* Базис в процентах, а не по содержимому: перенос строк flexbox считает по
     нему, и без этого у коротких ников на первую строку запрыгивало ещё и
     соседнее поле — карточки выходили все разной формы. */
  .revtable td.namecell{flex:1 1 60%; width:auto; padding:0; font-size:16px}
  /* Рейтинг забирает остаток первой строки: так за него не цепляется
     следующее поле, и шапка карточки всегда выглядит одинаково. */
  .revtable td.elo{flex:1 1 auto; width:auto; padding:0; font-size:16px;
                   justify-content:flex-end}
  .revtable td.elo::before{display:none}
  /* Дальше — по два поля в ряд: иначе карточка растягивается на пол-экрана. */
  .revtable td.nowcell,.revtable td.guesscell,.revtable td.votecell{
    width:calc(50% - 5px);
  }
  .revtable td.nowcell::before,.revtable td.guesscell::before,
  .revtable td.votecell::before{flex-basis:auto; max-width:50%}
  .revtable td.votecell.mine{width:100%; padding-top:5px}
  .revtable td.finalcell{width:auto; flex:1 1 auto; padding-top:5px}
  .revtable td.commitcell{width:auto; padding-top:5px}
  .revtable .vote{flex:1; min-width:0}
  /* Развёрнутая статистика — не карточка, а продолжение своей строки. */
  .revtable tr.expand{
    background:transparent; border:none; border-radius:0;
    padding:0 2px; margin:-8px 0 12px;
  }
  .revtable tr.expand td{display:block; padding:0}
}

@media (max-width: 460px) {
  .grid .col-kd{display:none}
  .brand{font-size:14px}
  nav{gap:13px; font-size:14px}
  .playerhead{gap:10px}
  .statline{gap:12px}
  .statline div{font-size:12px}
  .revtable td[data-label]::before{flex-basis:82px}
}

/* --- динамика: все игроки на одном графике --- */
.linecard{display:flex; gap:16px; align-items:flex-start}
.linechart{flex:1; min-width:0}
.bigchart{display:block; width:100%; cursor:crosshair; touch-action:pan-y}
.bigchart .grid{stroke:#242a36; stroke-width:1}
.bigchart .axis{fill:var(--dim); font-size:11px; font-variant-numeric:tabular-nums}
.bigchart .pline{stroke-width:1.3; opacity:.5; fill:none;
                 stroke-linejoin:round; stroke-linecap:round}
.bigchart .pline.lead{stroke-width:2; opacity:.85}
/* Выделенная линия выходит вперёд, остальные уходят в фон — иначе в полусотне
   кривых не разглядеть ни одной. */
.bigchart .pline.hot{stroke-width:3; opacity:1}
.bigchart .pline.faded{opacity:.13}
.bigchart .plabel{font-size:11px; font-weight:700; opacity:.85}
.bigchart .plabel.hot{opacity:1; font-size:12px}
.bigchart .guide{stroke:var(--text); stroke-width:1; opacity:.45}
.bigchart .zoomzone{fill:rgba(255,212,94,.14); stroke:var(--accent); stroke-width:1}

.standings{
  width:230px; flex:none; background:var(--panel2); border:1px solid var(--line);
  border-radius:10px; padding:10px 12px; max-height:430px; overflow:auto;
}
.standhead{color:var(--accent); font-size:12px; font-weight:700; margin-bottom:6px}
.standrow{display:flex; align-items:center; gap:7px; padding:3px 0; font-size:13px;
          cursor:pointer}
.standrow:hover{color:var(--text)}
.standrow.on{background:#242a36; border-radius:6px; margin:0 -6px; padding:3px 6px}
.standrow .place{color:var(--dim); font-size:11px; width:26px; flex:none}
.standrow .nick{flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.standrow .elo{font-variant-numeric:tabular-nums; font-weight:700}
.linedot{width:8px; height:8px; border-radius:50%; flex:none}

@media (max-width: 760px) {
  .linecard{flex-direction:column}
  .standings{width:100%; max-height:260px}
}
.bigchart .linespot{stroke:var(--bg); stroke-width:1.5}
.bigchart .linetag{
  font-size:12px; font-weight:800; paint-order:stroke;
  stroke:var(--panel); stroke-width:3.5px; pointer-events:none;
}

@media (max-width: 860px) {
  /* На телефоне полотно и табло встают друг под другом, транспорт — снизу. */
  .linewrap{height:calc(100vh - 104px)}
  .linestage{flex-direction:column}
  .linecanvas{height:60%; min-height:220px}
  .lineboard{width:100%; border-left:none; border-top:1px solid var(--line);
             overflow-y:auto; flex:1}
  .linebar{height:auto; padding:8px 12px; flex-wrap:wrap; gap:8px}
  .track{order:5; flex-basis:100%}
}

/* --- график онлайна за сутки на карточке сервера --- */
.serverhead .watchbtn{
  padding:4px 13px; font-size:13px; color:var(--accent);
  border-color:#7a6a1e; background:#2b2415;
}
.server.off .watchbtn{color:var(--dim); border-color:var(--line); background:var(--panel2)}

.spark{margin-top:12px}
.sparkhead{
  display:flex; justify-content:space-between; align-items:baseline;
  gap:12px; color:var(--dim); font-size:11px; margin-bottom:3px;
}
.sparkstats{display:flex; gap:14px; flex-wrap:wrap; justify-content:flex-end}
.sparkstat{display:inline-flex; align-items:baseline; gap:4px; cursor:help}
.sparkstat b{color:var(--text); font-size:12px; font-variant-numeric:tabular-nums}
.sparkbody{position:relative; height:64px; touch-action:pan-y}
/* Полотно растянуто по ширине: сутки должны занимать всю карточку, а не свою
   пропорцию. Столбики от этого только шире — они и так прямоугольники. */
.sparksvg{display:block; width:100%; height:100%}
.sparkbar{fill:var(--accent); opacity:.85}
/* Ноль — короткий огрызок у земли: сервер работал, просто никого не было.
   Там, где он не отвечал, столбика нет вовсе, и это видно как пропуск. */
.sparkbar.empty{fill:#3f4759; opacity:.9}
.sparkbase{stroke:#2a3040; stroke-width:1}
.sparkrule,.sparktip{
  position:absolute; opacity:0; pointer-events:none; transition:opacity .12s;
}
.sparkbody.hover .sparkrule,
.sparkbody.hover .sparktip{opacity:1}
.sparkrule{top:0; bottom:0; width:1px; background:#41495c}
.sparktip{
  top:0; transform:translateX(-50%); white-space:nowrap;
  background:var(--panel2); border:1px solid var(--line); border-radius:6px;
  padding:1px 7px; font-size:11px; font-variant-numeric:tabular-nums;
}
.sparktip.right{transform:translateX(-100%)}
.server.off .spark{opacity:.45}

/* --- просмотр одного сервера: чат и события во всю ширину --- */
.watch{
  display:flex; flex-direction:column; padding:0 18px 16px;
  height:calc(100vh - 59px); height:calc(100dvh - 59px);
}
.watchbar{display:flex; align-items:center; gap:12px; flex-wrap:wrap; padding:12px 2px}
.watchbar .dot{
  width:8px; height:8px; border-radius:50%; background:var(--up); flex:none;
  box-shadow:0 0 0 3px rgba(95,217,122,.15);
}
.watchbar .dot.off{background:#4a5060; box-shadow:none}
.watchbar .wname{font-size:16px}
.watchbar .meta{color:var(--dim); font-size:13px}
.watchbar .count{
  margin-left:auto; font-variant-numeric:tabular-nums; font-weight:700;
  color:var(--dim);
}

.watchcols{flex:1; display:flex; gap:16px; min-height:0}
.pane{
  flex:1 1 0; min-width:0; display:flex; flex-direction:column; overflow:hidden;
  background:var(--panel); border:1px solid var(--line); border-radius:12px;
}
.chatpane{flex:1.3 1 0}

.ev{display:flex; align-items:center; gap:8px; padding:5px 8px; border-radius:8px;
    border-bottom:1px solid #21252f; font-size:13px}
.ev:last-child{border-bottom:none}
.ev .when{color:var(--dim); font-size:11px; flex:none; font-variant-numeric:tabular-nums}
.ev .evmark{color:var(--dim); width:14px; text-align:center; flex:none}
/* Строка переносится, а не уезжает вбок: ленте разрешено быть в две строки,
   горизонтальная прокрутка в ней — нет. */
.ev .evtext{flex:1; display:flex; align-items:center; gap:5px; min-width:0; flex-wrap:wrap}
.ev .evwho{min-width:0; gap:3px}
.ev .evwho .nick{overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:150px}
.ev .evtext .dim{flex:none; font-size:12px}
.ev .d{font-variant-numeric:tabular-nums; font-weight:600; flex:none}
.ev .score{font-variant-numeric:tabular-nums; font-weight:700; font-size:12px;
           background:var(--panel2); border:1px solid var(--line); border-radius:5px; padding:0 5px}
.ev-join .evmark{color:var(--up)}
.ev-left .evmark{color:var(--down)}
.ev-duel .evmark,.ev-mvp .evmark{color:var(--accent)}
.ev .evwho .tier{min-width:0; padding:0 5px; font-size:10px}
.ev .nick{font-size:12px}

/* Сообщение, которое ещё летит в игру. */
.msg.pending{opacity:.55}
.msg .evwho{gap:4px}
.msg .evwho .nick::after{content:':'; color:var(--dim)}
.msg .evwho .tier{min-width:0; padding:0 5px; font-size:10px}
.spin{
  width:11px; height:11px; flex:none; border-radius:50%;
  border:2px solid var(--line); border-top-color:var(--accent);
  animation:spin .8s linear infinite; display:inline-block; vertical-align:-1px;
}
@keyframes spin{to{transform:rotate(360deg)}}

/* Команда и её вывод: вывод свёрнут, разворачивается по кнопке. */
.msg.cmd .cmdtag{color:var(--accent); border-color:#7a6a1e}
.msg.cmd .text b{font-family:ui-monospace,monospace}
.msg.cmd .toggle{
  margin-left:8px; padding:0 7px; font-size:11px; color:var(--dim);
  background:transparent;
}
.cmdout{
  margin-top:5px; padding:6px 9px; background:var(--panel2); border-radius:7px;
  font-family:ui-monospace,monospace; font-size:12px; white-space:pre-wrap;
  color:var(--dim); max-height:260px; overflow:auto;
}

@media (max-width: 860px) {
  /* На телефоне ленты встают друг под другом: две колонки по полэкрана
     нечитаемы. Высоту делят между собой, страница по-прежнему не скроллится.
     Шапка тут в две строки — те же 104px, что и у полотна динамики. */
  .watch{padding:0 12px 12px;
         height:calc(100vh - 104px); height:calc(100dvh - 104px)}
  .watchbar{gap:8px; padding:10px 2px}
  .watchcols{flex-direction:column; gap:12px}
  .pane,.chatpane{flex:1 1 0}
  .sparkbody{height:56px}
}

/* --- калибровка: картинка вместо графика --- */
.calibart{display:block; width:100%; height:auto; margin-top:6px}
.calibart .calibtarget{
  stroke:var(--line); stroke-width:1.5; stroke-dasharray:2 9;
  stroke-linecap:round;
}
.calibart .calibwave{
  fill:none; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round;
}
/* Слева блёкло, справа — в полный цвет: место ещё не найдено, но уже видно. */
.calibart .c0{stop-color:#5a4f2a}
.calibart .c1{stop-color:#b99433}
.calibart .c2{stop-color:var(--accent)}
.calibart .calibdot{fill:var(--accent)}
.calibart .calibend{fill:var(--accent); stroke:var(--panel); stroke-width:2.5}

/* --- список прошедших изменений тиров --- */
.revlist{display:flex; flex-direction:column; gap:6px; margin-top:10px}
.revitem{
  display:flex; align-items:baseline; gap:12px; flex-wrap:wrap; text-align:left;
  padding:8px 12px; background:var(--panel2); border:1px solid var(--line);
  border-radius:8px; font-size:13px; color:var(--text);
}
.revitem .revnum{color:var(--accent); font-weight:700; font-variant-numeric:tabular-nums}
.revitem .dim{margin-left:auto; font-size:12px}
.revitem.on{border-color:var(--accent)}

/* --- карточка игрока: главное крупно, служебное убрано --- */
.hero{padding:20px 20px 16px}
.herorow{display:flex; align-items:flex-start; gap:18px; flex-wrap:wrap}
.herowho{display:flex; align-items:flex-start; gap:14px; flex:1; min-width:0}
.herotitle{min-width:0}
.heroname{display:flex; align-items:center; gap:10px; flex-wrap:wrap}
.heroname h1{margin:0; font-size:26px; overflow-wrap:anywhere}
.heroname .tier{font-size:13px; padding:3px 10px}
.heroname .badge{margin-left:0}
/* Рейтинг — то, ради чего страницу и открывают. Он один набран крупно. */
.heroscore{text-align:right; margin-left:auto; line-height:1.15}
.heroscore b{
  display:block; font-size:40px; font-weight:800;
  font-variant-numeric:tabular-nums; color:var(--accent);
}
.heroscore span{color:var(--dim); font-size:13px}
.hero .metachips{margin-top:10px}

/* Остальные числа — мелкой строкой: нужны, но не они главные. */
.facts{
  display:flex; flex-wrap:wrap; gap:8px 22px; margin-top:16px;
  padding-top:14px; border-top:1px solid var(--line); font-size:13px;
}
.fact{display:flex; align-items:baseline; gap:6px; color:var(--dim)}
.fact b{color:var(--text); font-size:15px; font-variant-numeric:tabular-nums}

/* Свёрнутое служебное: pb-id, адреса, правка анкеты. */
.more{margin-top:14px; border-top:1px solid var(--line); padding-top:10px}
.more > summary{
  cursor:pointer; color:var(--dim); font-size:13px; list-style:none;
  display:inline-flex; align-items:center; gap:6px;
}
.more > summary::-webkit-details-marker{display:none}
.more > summary::before{content:'▸'; font-size:11px}
.more[open] > summary::before{content:'▾'}
.more > summary:hover{color:var(--text)}
.morebody{display:flex; flex-direction:column; gap:14px; margin-top:12px}
.morerow{display:flex; gap:14px; align-items:flex-start; flex-wrap:wrap}
.morerow > .dim{flex:0 0 92px; font-size:13px; padding-top:6px}
.iplist{display:flex; flex-direction:column; gap:5px; min-width:0}
.iprow{display:flex; align-items:center; gap:9px; font-size:13px; flex-wrap:wrap}
.iprow code{font-size:12px}
.iprow .dim{font-size:12px}
/* Устройство — приглушённее адреса: это подсказка, а не то, что ищут глазами. */
.iprow code.device{color:var(--dim); font-size:11px}
/* А в списке устройств оно как раз главное и стоит целиком: сорок символов
   должны переноситься, а не растягивать карточку вбок. */
.iprow code.devid{word-break:break-all}
.iprow .shared{color:var(--accent); font-size:12px; cursor:help}
/* Связи: по чему совпало. Устройство — почти наверняка тот же человек, и
   помечено ярче; адрес — только повод присмотреться. */
.linkrow .why{color:var(--dim); font-size:12px; cursor:help}
.linkrow .why.hard{color:var(--accent)}

/* --- страница входа ---------------------------------------------------------
   Весь сайт — плотные таблицы; здесь наоборот: одна колонка, много воздуха и
   ровно одно дело на экран. Заголовок называет это дело, а всё остальное
   молчит. Единственное яркое пятно — талон с кодом: его уносят в игру. */

.authpage{
  max-width:420px; margin:0 auto; padding:36px 0 48px;
  /* Вертикально по центру экрана: страница одна и делает одно дело, прижимать
     её к шапке незачем. На телефоне содержимое выше экрана — центрирование
     там само собой отключается. */
  min-height:min(76vh, 700px); display:flex; flex-direction:column;
  justify-content:center;
}
.authtitle{
  font-size:clamp(30px,7vw,42px); line-height:1.02; font-weight:800;
  letter-spacing:-1px; margin:0 0 12px;
}
/* Подпись по ширине пластины: рваный правый край уже читался как небрежность. */
.authlead{color:var(--dim); font-size:14.5px; line-height:1.65; margin:0 0 28px}

/* Одна пластина с волосяными перегородками вместо трёх коробок: границ втрое
   меньше, а разделение читается так же. */
.authplate{
  background:var(--panel); border:1px solid var(--line); border-radius:18px;
  overflow:hidden;
  /* Волосяной блик сверху и мягкая тень: пластина лежит на странице, а не
     нарисована на ней. */
  box-shadow:inset 0 1px 0 rgba(255,255,255,.045), 0 18px 40px -28px #000;
}
.authsect{padding:24px 24px 26px}
.authsect + .authsect{border-top:1px solid var(--line)}
/* Путь для новичка приглушён: у большинства он уже позади. */
.authsect.quiet{background:#161920}

.authlabel{
  font-size:11px; text-transform:uppercase; letter-spacing:.14em;
  color:var(--dim); font-weight:600; margin:0 0 14px;
}
.authhint{color:var(--dim); font-size:13px; line-height:1.6; margin:0 0 16px}
.authnote{color:var(--dim); font-size:12px; line-height:1.55; margin:14px 0 0; opacity:.8}

.authsect input[type=text],.authsect input[type=password]{
  display:block; width:100%; min-width:0; margin-bottom:10px;
  padding:11px 13px; border-radius:9px;
}
.authrow{display:flex; gap:8px}
.authrow input{flex:1; margin-bottom:0}

/* Поле имени: значок V2 внутри слева и выпадающие подсказки под ним. Отступ
   держит обёртка, а не само поле, — иначе список бы отрывался от него. */
.namefield{position:relative; margin-bottom:10px}
/* Селектор с типом обязателен: правило выше задано как .authsect input[type=…]
   и без этого перебивает отступ — значок наезжал бы на первые буквы. */
.authsect .namefield input[type=text]{margin-bottom:0; padding-left:42px}
.namemark{
  position:absolute; left:14px; top:50%; transform:translateY(-50%);
  height:16px; width:auto; opacity:.85; pointer-events:none;
}
/* Имя не `suggest`: так называется плашка про тир на карточке игрока (правило
   выше), и общее имя утаскивало её сюда — в absolute поверх страницы. */
.suggestbox{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:6;
  max-height:262px; overflow:auto;
  background:var(--panel2); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 20px 36px -20px #000;
}
.suggestitem{
  display:flex; align-items:center; gap:8px; padding:9px 12px; cursor:pointer;
}
.suggestitem + .suggestitem{border-top:1px solid #21252f}
.suggestitem:hover,.suggestitem.on{background:var(--panel)}
.suggestitem .ico{margin-right:0}
.suggestitem .tier{margin-left:auto}
button.wide{width:100%; padding:12px; font-weight:700; margin-top:6px}
.authsect button.primary{
  background:var(--accent); border-color:var(--accent); color:#171308;
}
.authsect button.primary:hover{background:#ffdd7d; border-color:#ffdd7d}
/* Второй способ — тихой кнопкой во всю ширину под полем, а не рядом с ним:
   так обе секции читаются одной колонкой. */
#codeForm button{width:100%; margin-top:10px}
#codeForm .authrow{display:block}

:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.authstate{margin:16px 4px 0; font-size:13px; min-height:18px; text-align:center}
.authstate.up{color:var(--up)}
.authstate.down{color:var(--down)}

/* Кто вошёл. Строка, а не карточка: это справка, а не действие. */
.authsect.who{display:flex; align-items:center; gap:10px; padding:18px 24px}
/* «Выйти» — не действие этой страницы, а запасной выход: текстом, без рамки. */
.authsect.who button{background:none; border-color:transparent; padding:6px 2px}
.authsect.who button:hover{border-color:transparent; color:var(--text)}
.wholine{display:flex; align-items:center; gap:8px; flex:1; min-width:0}
.whoname{font-weight:600}

/* Ждущий заход — единственное, что требует действия прямо сейчас. */
.authsect.pending{background:#1b1a13}
.authfacts{display:flex; gap:10px; margin:0 0 18px}
.authfact{
  flex:1; min-width:0; display:flex; flex-direction:column; gap:5px;
  padding:11px 13px; background:var(--bg); border:1px solid var(--line);
  border-radius:10px;
}
.authfactname{
  font-size:10px; text-transform:uppercase; letter-spacing:.12em; color:var(--dim);
}
.authfact code{
  background:none; padding:0; font-size:13px; color:var(--text);
  overflow:hidden; text-overflow:ellipsis;
}

/* Талон с кодом. Пунктир по контуру — он и правда отрывной: его уносят
   со страницы в игровой чат. */
.ticket{
  display:flex; align-items:baseline; gap:12px; cursor:pointer;
  padding:16px 18px; border-radius:12px;
  background:var(--bg); border:1px dashed #5a4a18;
  transition:border-color .15s, background .15s;
}
.ticket:hover{border-color:var(--accent); background:#191712}
.ticketcmd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
           font-size:15px; color:var(--dim)}
.ticketcode{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:30px; font-weight:600; letter-spacing:.14em; color:var(--accent);
  line-height:1;
}
.ticket.copied{border-style:solid; border-color:var(--up)}
.ticket.copied::after{content:'скопировано'; margin-left:auto; align-self:center;
                      font-size:11px; color:var(--up)}

/* Вкладка появляется, только когда заход и правда ждёт, — и должна попадаться
   на глаза, поэтому цветом, а не как остальные. */
nav a.warn{color:var(--accent)}

/* Та же вкладка на телефоне никуда не годится: вкладки там едут вбок, и её
   попросту не видно, а человек в этот момент стоит на сервере и не может ни
   играть, ни писать. Поэтому на узком экране вопрос встаёт во весь экран сам —
   не полупрозрачным окошком поверх страницы, а вместо неё: дело ровно одно, и
   заниматься чем-то ещё в эту секунду всё равно нельзя. */
.pendsheet{
  position:fixed; inset:0; z-index:20; overflow-y:auto;
  background:var(--bg); padding:22px 14px 28px;
  display:flex; flex-direction:column; justify-content:center;
  animation:sheetrise .22s ease-out;
}
.pendsheetbox{width:100%; max-width:460px; margin:0 auto}
.pendsheettitle{margin:0 0 6px; font-size:26px; line-height:1.15}
.pendsheetlead{margin:0 0 18px; color:var(--dim); font-size:14px; line-height:1.5}
.pendsheet .authsect.pending{border:1px solid var(--line); border-radius:14px}
.pendsheet button.wide{margin-top:14px}
/* Страница под плашкой не должна ездить: прокрутка там уже ни к чему. */
body.sheetopen{overflow:hidden}
@keyframes sheetrise{from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:none}}
@media (prefers-reduced-motion: reduce){ .pendsheet{animation:none} }

/* Появление страницы: одно движение на весь экран, а не эффект на каждом
   элементе. */
#view-login:not(.hidden) .authtitle,
#view-login:not(.hidden) .authlead,
#view-login:not(.hidden) .authplate{animation:authrise .32s ease-out both}
#view-login:not(.hidden) .authlead{animation-delay:.04s}
#view-login:not(.hidden) .authplate{animation-delay:.08s}
@keyframes authrise{from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:none}}
@media (prefers-reduced-motion: reduce){
  #view-login:not(.hidden) .authtitle,
  #view-login:not(.hidden) .authlead,
  #view-login:not(.hidden) .authplate{animation:none}
  .ticket{transition:none}
}

/* --- правка страны и даты рождения --- */
.aboutedit{display:flex; gap:12px; flex-wrap:wrap; align-items:flex-end}
.field{display:flex; flex-direction:column; gap:4px}
.field > span{color:var(--dim); font-size:12px}
.aboutedit select,.aboutedit input{min-width:170px; font-size:13px; padding:6px 9px}
.aboutedit input[type=date]{color-scheme:dark}

@media (max-width: 700px) {
  /* На телефоне рейтинг встаёт под ником, а не жмётся к краю. */
  .hero{padding:16px}
  .heroname h1{font-size:22px}
  .heroscore{text-align:left; margin-left:0; width:100%}
  .heroscore b{font-size:32px}
  .facts{gap:8px 16px}
  .morerow > .dim{flex-basis:100%; padding-top:0}
  .aboutedit select,.aboutedit input{min-width:0; width:100%}
  .field{flex:1 1 100%}
}

/* Флаг из игры: тот же значок, что видно на сервере. */
.flag-img{height:14px; width:auto; display:block; border-radius:2px}
.metachips .flag-img{height:15px}

/* ------------------------------------------------------------- наказания */
/* Фильтр по виду наказания: те же чипы, что и везде, но кликабельные. */
.banfilter{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:14px}
.banchip{
  padding:5px 12px; border-radius:20px; cursor:pointer;
  background:var(--panel2); border:1px solid var(--line);
  color:var(--dim); font:inherit; font-size:13px;
}
.banchip:hover{color:var(--text)}
.banchip.on{color:var(--text); border-color:#3d4657; background:var(--panel)}
/* Атаки на сервер. Адрес и id устройства — моноширинным, чтобы столбцы
   читались ровно; строка захода и подробности — мелким и приглушённым: они
   справочные. Значок вида стоит первым в ячейке — левый отступ у него снимаем. */
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:13px}
td > .badge:first-child{margin-left:0}
.attinfo{color:var(--dim); font-size:13px; max-width:360px}
.attspec{display:block; margin-top:2px; color:#6b7280; font-size:12px;
  font-family:ui-monospace,monospace; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; max-width:340px}
/* Истёкшее не прячем, но и не выпячиваем: оно уже ни на что не влияет. */
.grid.bans tr.over td{opacity:.45}
.grid.bans tr.clickable{cursor:pointer}
.bandetails td{background:var(--panel2); padding:10px 14px}
.bandetail{display:flex; flex-direction:column; gap:6px; font-size:13px}
.banline{display:flex; gap:10px; align-items:flex-start}
.bankey{color:var(--dim); flex:none; min-width:120px}
.banvals{word-break:break-word}
.bandim{color:var(--dim)}
@media (max-width: 760px){
  .banline{flex-direction:column; gap:2px}
  .bankey{min-width:0}
}

/* --- кастомизация: монеты, магазин, модерация ------------------------- */

/* Счётчик монет в шапке — тот же значок, что у онлайна, только золотой. */
.ocount.coin{
  background:#2b2415; color:var(--accent); border-color:#7a6a1e;
  display:none; align-items:center; gap:3px;
}
.ocount.coin.live{display:inline-flex}
/* Число и значок монеты рядом — везде, где показывают деньги. */
.coinval{display:inline-flex; align-items:center; gap:4px;
         font-variant-numeric:tabular-nums; white-space:nowrap}
.coinval .shopsvg, .ocount.coin .shopsvg, .coinamount .shopsvg,
.shopmoney .shopsvg{stroke-width:1.7; opacity:.95}
.shopmoney .shopsvg{color:var(--accent)}

/* Шапка магазина — одна строка: слева деньги, справа вкладки. Всё остальное
   уехало во вкладку «моё»: в витрине это шум, за которым не видно товара. */
.shopbar{
  display:flex; align-items:center; justify-content:space-between;
  gap:12px 16px; flex-wrap:wrap; margin-bottom:18px;
}
.shopmoney{display:flex; align-items:center; gap:7px}
.coinsum{
  font-size:26px; font-weight:700; color:var(--accent);
  font-variant-numeric:tabular-nums; line-height:1;
}
.coinword{color:var(--dim)}
.coinday{color:var(--up); font-size:12px}
.privline{margin:0; color:var(--accent); font-size:13px}

.shoptabs{display:flex; gap:8px; flex-wrap:wrap}
.shoptab{font-size:14px}
.shoptab.on{border-color:var(--accent); color:var(--accent)}

.shopnote{
  margin:0 0 14px; padding:9px 12px; border-radius:8px; font-size:14px;
  background:#1d3a24; border:1px solid #2f5c35; color:var(--up);
}
.shopnote.bad{background:#3a1d1d; border-color:#5c2f2f; color:var(--down)}
/* Не беда и не радость, а положение дел: зелёный тут читался бы как «готово».
   Тем же жёлтым, что и остальные предупреждения на сайте. */
.shopnote.wait{background:#241f14; border-color:#3a3018; color:var(--accent)}

/* Ряд витрины: тихий заголовок и плитки. Рамок и панелей вокруг нет нарочно —
   товар и так в рамках, вторая рамка вокруг ряда добавляет шума. */
.shoprow{margin-bottom:26px}
.rowhead{display:flex; align-items:baseline; gap:10px; margin-bottom:10px}
.rowhead h2{margin:0; font-size:15px; font-weight:600}
.rowdim{color:var(--dim); font-size:12px}

/* Плитки: на телефоне две в ряд, дальше сколько влезет. auto-fill, а не
   auto-fit, — иначе одна позиция в ряду растянулась бы на всю ширину. */
.tiles{
  display:grid; gap:10px;
  grid-template-columns:repeat(auto-fill, minmax(132px, 1fr));
}
/* Плитка. Ступень видна тремя способами сразу: цветом свечения под обликом,
   тонкой чертой под именем и подписью. Одного цвета мало — его не назовёшь
   вслух и не различишь при цветовой слепоте; одной подписи мало — её не
   видно боковым зрением. */
.tile{
  position:relative; overflow:hidden;
  display:flex; flex-direction:column; align-items:center; gap:6px;
  padding:14px 8px 11px; border-radius:14px; text-decoration:none;
  color:var(--text);
  background:
    radial-gradient(120% 70% at 50% -10%,
      color-mix(in srgb, var(--rare) 22%, transparent) 0%, transparent 70%),
    var(--panel2);
  border:1px solid color-mix(in srgb, var(--rare) 35%, var(--line));
  transition:transform .12s, border-color .12s, box-shadow .12s;
}
.tile:hover{
  transform:translateY(-2px);
  border-color:color-mix(in srgb, var(--rare) 70%, var(--line));
  box-shadow:0 10px 24px -16px var(--rare);
}
/* Свечение под обликом — то же, чем ступени различаются в игре. */
.tileart{
  display:flex; align-items:center; justify-content:center;
  border-radius:50%;
  background:radial-gradient(closest-side,
             color-mix(in srgb, var(--rare) 32%, transparent) 0%,
             transparent 100%);
}
/* Подпись ступени и черта под именем — тем же цветом. */
.tilerare{
  font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--rare); position:relative; padding-top:6px;
}
.tilerare::before{
  content:''; position:absolute; top:0; left:50%; transform:translateX(-50%);
  width:26px; height:2px; border-radius:2px; background:var(--rare);
  opacity:.75;
}
.tileart .ico{margin:0}
.tilename{
  font-size:13px; font-weight:600; text-align:center; line-height:1.2;
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.tilefoot{display:flex; align-items:center; justify-content:center;
          gap:8px; min-height:20px}
.tileworn{
  font-size:11px; color:#0e1a12; background:var(--up);
  padding:1px 8px; border-radius:20px; font-weight:700;
}
.tileown{font-size:11px; color:var(--dim)}

/* Цена — то, ради чего в карточку и смотрят: крупнее остального, но не
   жирнее — жирная она забивала собой всю плитку. */
.price{
  color:var(--accent); font-size:15px; font-weight:500;
  font-variant-numeric:tabular-nums;
}
.price.big{font-size:19px}

/* Ступени редкости. Цвета — те же, что у сундуков в игре: мятный, синий,
   бирюзовый и фиолетовый с золотом. */
.r-common{--rare:#93a1b5}
.r-rare{--rare:#5fd39d}
.r-epic{--rare:#b06dff}
.r-legend{--rare:#ffae3d}
/* Кастомные ступеней не имеют — у них своя подпись и свой цвет, тёплый, чтобы
   не путался с лесенкой. */
.r-custom{--rare:#ffab5e}
/* У привилегий ступени нет вовсе: рамка нейтральная. */
.tile:not([class*=" r-"]), .r-{--rare:var(--line)}
/* У легендарного сверх цвета — золотая искра: вся ступень одной масти. */
.r-legend .tileart{
  background:
    radial-gradient(closest-side, rgba(255,205,90,.28) 0%, transparent 62%),
    radial-gradient(closest-side, color-mix(in srgb, var(--rare) 34%, transparent) 0%,
                    transparent 100%);
}
.r-legend .tilerare{color:#ffd45e}
.r-legend .tilerare::before{background:linear-gradient(90deg, var(--rare), #ffd45e)}
/* Страница предмета. Одна карточка на всё: слева сама вещь — демка или
   значок, — справа имя, ступень, оценка и цена. Заголовков внутри нет ни
   одного: каждый из них был подписью к очевидному, а вместе они занимали
   больше места, чем то, что подписывали. */
.backlink{display:inline-block; margin-bottom:12px; color:var(--dim); font-size:13px}
.itemcard{
  display:flex; gap:20px; align-items:flex-start; flex-wrap:wrap;
  border-top:2px solid var(--rare, var(--line));
}
.itemart{
  display:flex; align-items:center; justify-content:center; flex:none;
  border-radius:50%;
  background:radial-gradient(closest-side,
             color-mix(in srgb, var(--rare, transparent) 22%, transparent) 0%,
             transparent 100%);
}
/* С демкой это уже не кружок со значком, а картинка: занимает столько,
   сколько дадут, но не настолько много, чтобы вытолкнуть цену за экран. */
.itemart.hasdemo{flex:1 1 300px; max-width:420px; background:none;
                 border-radius:12px}

/* Фотографии облика — оба кадра рядом и целиком: по одному виду облик не
   читается, у половины из них вся разница как раз сбоку. */
.itemart.hasshots{
  flex:1 1 300px; max-width:420px; background:none; border-radius:12px;
  display:flex; gap:8px; align-items:flex-start;
}
/* Кадр показываем целиком, не обрезая по рамке: обрезка съедает как раз края,
   а у половины обликов всё интересное — крылья, хвост, шляпа — по краям. */
.itemshot{
  flex:1 1 0; min-width:0; width:100%; height:auto; display:block;
  border-radius:12px; border:1px solid var(--line); background:#000;
}

/* Инвентарь на странице игрока: список вещей, а не витрина. Плитки тут были бы
   вторым магазином на чужой странице — здесь важно «что у него есть», а не
   «сколько это стоит». */
.card h2 .rowdim{margin-left:8px; font-weight:400}
.invlist{display:flex; flex-direction:column; gap:6px; margin-top:12px}
.invitem{
  display:flex; align-items:center; gap:10px; padding:7px 10px;
  border-radius:10px; text-decoration:none; color:var(--text);
  background:var(--panel2);
  border:1px solid color-mix(in srgb, var(--rare) 30%, var(--line));
}
.invitem:hover{border-color:color-mix(in srgb, var(--rare) 60%, var(--line))}
.invtext{display:flex; flex-direction:column; min-width:0; flex:1}
.invname{font-weight:600; font-size:14px; overflow:hidden;
         text-overflow:ellipsis; white-space:nowrap}
.invkind{font-size:11px; color:var(--dim)}
.invitem .ico{margin:0; flex:none}

/* Фотография вместо нарисованного значка: в плитке — то же окошко, что и у
   значков, только заполнено кадром из боя. */
.ico.shopico.photoico{padding:0; overflow:hidden; background:#0e1015}
.photoico img{width:100%; height:100%; object-fit:cover; display:block}
.itemart .ico{margin:0}
.itembody{flex:1 1 240px; min-width:220px;
          display:flex; flex-direction:column; gap:10px}
.itemname{margin:0; font-size:22px; line-height:1.15}
/* Ступень и род вещи — одной строкой, мелким и вразрядку: это подпись, а не
   заголовок. Ступень своим цветом, остальное серым. */
.itemmeta{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  font-size:11px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--dim);
}
.metarare{color:var(--rare, var(--dim)); font-weight:700}
.itemabout{margin:0; color:var(--dim)}
.itemauthor{margin:0; font-size:12px; color:var(--dim)}
/* Из чего складывается стиль строки над головой — списком, а не строкой:
   слагаемых бывает три, и в строку они читаются как одно длинное слово. */
.stylelist{margin:0; padding-left:18px; color:var(--dim); font-size:13px}
.stylelist li{margin:2px 0}
/* Что открывает привилегия — списком: у неё нет ни облика, ни демки, и это
   единственное, по чему её выбирают. */
.perklist li{margin:3px 0}

/* Команды привилегии: имя моноширинным ярлыком, подпись рядом. Не таблицей —
   таблица из двух колонок на телефоне съезжает, а строка просто переносится. */
.cmdlist{display:flex; flex-direction:column; gap:7px; margin-top:12px}
.cmdrow{display:flex; gap:10px; align-items:baseline; flex-wrap:wrap}
.cmdname{
  flex:none; color:var(--accent); background:var(--panel2);
  border:1px solid var(--line); border-radius:6px; padding:2px 8px;
  font:12px/1.5 ui-monospace,monospace;
}
.cmdwhat{color:var(--dim); font-size:13px; min-width:0}

/* Молния денег не стоит — на месте цены у неё словами то, почему. Тем же
   кеглем, что и цена: это её строка, просто в ней не число. */
.freenote{font-size:15px; color:var(--dim)}

/* Цена и кнопка — отдельным «этажом» под тонкой чертой: до неё написано,
   что это за вещь, после неё — что с ней можно сделать. */
.itemact{
  display:flex; align-items:center; gap:14px;
  margin-top:2px; padding-top:12px; border-top:1px solid var(--line);
}
.shopbuy.big{padding:8px 18px; font-size:15px}
.coinsvg{flex:none; display:block}
.shopbuy{padding:5px 10px; font-size:13px}
.warnchip{
  padding:1px 7px; border-radius:20px; font-size:11px;
  background:#2b2415; border:1px solid #7a6a1e; color:var(--accent);
}
/* Значок для того, у чего игровой картинки нет вовсе: эффекты, префиксы,
   привилегии, кастомные облики. Рисунок внутри — svg одним цветом, поэтому
   вся раскраска живёт здесь, а не в разметке. */
.ico.shopico{
  display:flex; align-items:center; justify-content:center;
  background:var(--panel); border:1px solid var(--line); border-radius:8px;
  color:var(--dim);
}
.shopsvg{fill:none; stroke:currentColor; stroke-width:1.6;
         stroke-linecap:round; stroke-linejoin:round}

/* Звёзды. Единственный залитый значок в разделе: строка из пяти обводок в
   ряд читается решёткой, и «сколько закрашено» по ней не поймёшь. */
.starsvg{fill:currentColor; stroke:none; display:block}

/* Средняя оценка: два ряда друг поверх друга — пустой снизу, залитый сверху
   и обрезанный по ширине. Отсюда и берётся неполная последняя звезда. */
.stars{position:relative; display:inline-block; line-height:0; flex:none}
.starsback{display:flex; gap:3px; color:var(--line)}
.starsfront{
  position:absolute; left:0; top:0; bottom:0; overflow:hidden;
  display:flex; gap:3px; color:var(--accent);
}
.starsfront .starsvg{flex:none}

/* Свои звёзды: те же, но нажимаются. Незакрашенные держим чуть заметнее
   пустых в средней — по ним видно, что тут можно ткнуть. */
.starpick{display:flex; gap:3px; flex:none}
.starbtn{
  border:0; background:none; padding:2px; margin:0; cursor:pointer;
  color:#3a4150; line-height:0; border-radius:4px;
  transition:color .1s, transform .1s;
}
.starbtn.on{color:var(--accent)}
.starbtn:hover{transform:scale(1.12)}
.starbtn:focus-visible{outline:1px solid var(--accent); outline-offset:1px}

/* Строка оценки: звёзды и словами то, что за ними стоит. */
.itemrate{display:flex; align-items:center; gap:10px; flex-wrap:wrap;
          min-height:26px}
.ratenote{font-size:12px; color:var(--dim)}
.ratenum{color:var(--text); font-size:13px; font-variant-numeric:tabular-nums}

/* Оценка в плитке — одной звездой и числом, рядом с ценой. */
.tilestars{
  display:flex; align-items:center; gap:3px;
  font-size:11px; color:var(--dim); font-variant-numeric:tabular-nums;
}
.tilestars .starsvg{color:var(--accent)}

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

   Слоя два нарочно. Иконка персонажа приезжает картинкой без прозрачности
   (в файле RGB, персонаж нарисован на чёрном — в игре его вырезает отдельная
   маска characterIconMask). Срезать её маской можно только отдельно от
   кнопки: одна маска на весь узел съела бы и саму кнопку. */
.charframe{
  display:inline-flex; align-items:center; justify-content:center;
  flex:none; margin-right:8px;
  /* Кнопка — та самая текстура из игры (buttonSquare), только уменьшенная и
     покрашенная один раз в тон сайта: в игре она серая и красится цветом
     кнопки, а нам нужен один тон. */
  background:url(/static/ui/button.png) center/100% 100% no-repeat;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));
}
.ico.charmask{
  margin:0; background-size:contain;
  /* Режем ровно тем, чем режет движок: characterIconMask. Скруглённый
     квадрат с небольшим радиусом — приближение кругом срезало пол-иконки. */
  -webkit-mask:url(/static/ui/charmask.png) center/contain no-repeat;
  mask:url(/static/ui/charmask.png) center/contain no-repeat;
}
/* Цвет по роду товара: глазами группа читается раньше, чем подпись. */
.ico.shopico.k-fatality{color:#ff8b7a; border-color:#4a2a28}
.ico.shopico.k-death{color:#9aa7ff; border-color:#2b3050}
.ico.shopico.k-prefix{color:var(--accent); border-color:#4a411f}
.ico.shopico.k-priv{color:#ffcf5e; border-color:#4a411f}
.ico.shopico.k-skin{color:#7fd6a8; border-color:#25412f}
.ico.shopico.k-particles{color:#7fe0e6; border-color:#22434a}
.ico.shopico.k-decor{color:#f0a86a; border-color:#4a3322}
/* Точки у «искр» и общего значка рисуются кружками, а не линиями. */
.k-particles .shopsvg{stroke-linecap:round; stroke-width:2}

/* Описание эффекта под названием: одна строка, чем он и отличается. */
.shopabout{font-size:12px; color:var(--dim); margin-top:1px;
           overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.authorlink{color:var(--link)}

/* Демка — на странице предмета, без звука и по кругу: это короткая петля, а
   не ролик, и запускать её вручную незачем. */
.demovideo{
  width:100%; max-height:60vh; border-radius:12px;
  background:#000; border:1px solid var(--line); display:block;
}

/* Наборы монет: крупная сумма, под ней рубли. Выгода набора — уголком, как
   ценник со скидкой; у самого маленького её нет вовсе. */
.packs{
  display:grid; gap:10px; margin-bottom:16px;
  grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));
}
.pack{
  position:relative; display:flex; flex-direction:column; align-items:center;
  gap:4px; padding:16px 10px 12px; border-radius:14px; cursor:pointer;
  background:linear-gradient(180deg, #2a2416 0%, var(--panel2) 70%);
  border:1px solid #4a411f; color:var(--text);
}
.pack:hover{border-color:var(--accent); transform:translateY(-2px)}
/* Набор, который сейчас не купить: гаснет и перестаёт отзываться на мышь —
   иначе он подмигивает рамкой и обещает то, чего не будет. */
.pack:disabled{opacity:.5; cursor:default}
.pack:disabled:hover{border-color:var(--line); transform:none}
/* Строка для незашедшего над витриной: тем же тоном, что и подсказки, но с
   кнопкой — за ней сюда и пришли. */
.guestbar{
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:12px 16px; margin-bottom:16px; color:var(--dim); font-size:14px;
}
.packsum{
  display:flex; align-items:center; gap:6px;
  font-size:22px; font-weight:700; color:var(--accent);
  font-variant-numeric:tabular-nums;
}
.packrub{font-size:15px; color:var(--dim)}
.packbonus{
  position:absolute; top:8px; right:8px; font-size:11px; font-weight:700;
  color:#0e1a12; background:var(--up); padding:1px 7px; border-radius:20px;
}

.contractform{
  display:flex; flex-wrap:wrap; gap:8px 12px; align-items:flex-end;
}
.contractfield{display:flex; flex-direction:column; gap:2px; font-size:12px}
.contractfield input{padding:3px 6px; font-size:12px}
.contractform .hint{flex-basis:100%; margin:0; font-size:11px}

.wornrow{
  display:flex; align-items:center; gap:10px; padding:8px 0;
  border-bottom:1px solid var(--line);
}
.wornrow:last-child{border-bottom:none}
.wornslot{width:120px; color:var(--dim); font-size:13px; flex:none}
.wornname{flex:1; min-width:0}
button.small{padding:3px 9px; font-size:12px}

.coinrow{
  display:flex; align-items:center; gap:12px; padding:5px 0;
  font-size:13px; border-bottom:1px solid var(--line);
}
.coinrow:last-child{border-bottom:none}
.coinamount{
  display:inline-flex; align-items:center; justify-content:flex-end; gap:3px;
  width:66px; flex:none; color:var(--up); font-weight:700;
  font-variant-numeric:tabular-nums;
}
.coinamount.minus{color:var(--down)}
.coinwhy{flex:1; min-width:0}

/* --- заявки авторов --- */
.shopfield{
  display:block; width:100%; max-width:420px; margin-top:10px;
  background:var(--panel2); border:1px solid var(--line); color:var(--text);
  border-radius:8px; padding:9px 11px; font:inherit;
}
.shopcode{
  display:block; width:100%; margin-top:10px; min-height:130px;
  background:var(--panel2); border:1px solid var(--line); color:var(--text);
  border-radius:8px; padding:9px 11px;
  font:12px/1.45 ui-monospace,monospace; resize:vertical;
}
.shopnum{
  width:80px; background:var(--panel2); border:1px solid var(--line);
  color:var(--text); border-radius:7px; padding:4px 8px; font:inherit;
  font-size:13px;
}

.subrow{
  padding:12px 0; border-bottom:1px solid var(--line);
}
.subrow:last-child{border-bottom:none}
.subhead{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.subtitle{font-weight:600}
.schip{
  padding:1px 8px; border-radius:20px; font-size:11px;
  background:var(--panel2); border:1px solid var(--line); color:var(--dim);
}
.schip.bad{border-color:#5c2f2f; color:var(--down)}
.subreason{color:var(--down); font-size:13px; margin-top:4px}
/* Находки разбора — красным и в столбик: их читают глазами перед тем, как
   решить судьбу чужого кода, и проскочить мимо они не должны. */
.subflag{
  color:var(--down); font-size:13px; margin-top:4px;
  font-family:ui-monospace,monospace;
}
.subok{color:var(--up); font-size:13px; margin-top:4px}
.subtools{display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-top:10px}
.subcode{
  margin-top:10px; padding:12px; max-height:420px; overflow:auto;
  background:#0e1015; border:1px solid var(--line); border-radius:8px;
  font:12px/1.5 ui-monospace,monospace; color:#cbd2e0; white-space:pre;
}

/* =========================================================================
   Магазин на телефоне
   =========================================================================
   Раздел рисовался под широкий экран, и на телефоне рассыпался весь сразу:
   плитки вставали по три в ряд шириной в значок, имена в них обрезались на
   третьей букве, вкладки переносились рваными строками, а страница предмета
   оставляла кнопку «купить» в правом углу узкой колонки.

   Порог один — 700px, тот же, что у всей остальной страницы (NARROW в
   app.js). Второго не заводим: ширину плиток держит auto-fill, и от 360px до
   700px число колонок меняется само.  */

@media (max-width: 700px) {
  /* Шапка в две строки: деньги сверху, вкладки под ними. В одну строку они
     не встают никогда — вкладок пять, и flex-wrap раскладывал их как придётся,
     то есть по-разному на каждой странице. */
  .shopbar{flex-direction:column; align-items:stretch; gap:12px;
           margin-bottom:14px}
  .coinsum{font-size:30px}
  /* Вкладки едут вбок, а не переносятся — как и вкладки самого сайта.
     Полосу прокрутки прячем: она тут только шумит, а то, что строка едет,
     видно по обрезанной вкладке у края. */
  .shoptabs{flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
            gap:6px; margin:0 -12px; padding:0 12px}
  .shoptabs::-webkit-scrollbar{display:none}
  .shoptab{white-space:nowrap; flex:none}

  /* Две плитки в ряд вместо трёх. 140px — это ровно тот порог, при котором на
     360px их становится две: значок 56px перестаёт быть половиной плитки, а
     имя помещается целиком. */
  .tiles{grid-template-columns:repeat(auto-fill, minmax(140px, 1fr))}
  .shoprow{margin-bottom:20px}
  /* Имя в две строки, а не в одну с обрезкой: «Пасхальный кролик» и
     «золотое яйцо» на телефоне не помещаются ни в какую ширину, а обрезанное
     имя товара — это товар, который не узнали. */
  .tilename{
    white-space:normal; display:-webkit-box; -webkit-box-orient:vertical;
    -webkit-line-clamp:2; line-clamp:2; min-height:2.4em;
  }

  /* Страница предмета — в столбик: значок (или демка) во всю ширину и по
     центру, под ним всё остальное. Раньше значок оставался слева отдельной
     колонкой в 108px, и рядом с ним оставалась щель, в которую ничего не
     помещалось. */
  .backlink{padding:6px 0}                 /* палец должен во что-то попасть */
  .itemcard{gap:14px}
  .itemart{width:100%; justify-content:center}
  .itemart.hasdemo, .itemart.hasshots{max-width:none}
  .itembody{flex:1 1 100%; min-width:0}
  .itemname{font-size:20px}
  .demovideo{max-height:44vh}
  /* Цена и кнопка: цена строкой, кнопка во всю ширину под ней. В строку они
     помещались, но кнопка выходила в палец шириной и жалась к правому краю. */
  .itemact{flex-wrap:wrap; gap:10px}
  .itemact .shopbuy.big{flex:1 1 100%; padding:11px 18px}

  /* «Моё»: подпись слота уже, имя обрезается, кнопка остаётся на строке.
     Переносить слот на свою строку не стали — шесть слотов превратились бы в
     двенадцать строк. */
  .wornslot{width:auto; flex:0 0 74px; font-size:12px}
  .wornname{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .coinamount{width:56px}
  .coinrow{gap:8px; font-size:12px}
  .coinwhy{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}

  .packs{grid-template-columns:repeat(auto-fill, minmax(140px, 1fr))}

  /* Заявки авторов. Поля — во всю ширину и кеглем 16: меньший iOS считает
     поводом увеличить всю страницу, и после первого касания сайт остаётся
     разъехавшимся. Базовое правило про 16px ловит только input[type=text],
     а тут select и textarea. */
  .shopfield, .shopcode{max-width:none; font-size:16px}
  .shopcode{min-height:110px}
  .subcode{max-height:260px; font-size:11px}
  .contractform{flex-direction:column; align-items:stretch}
  .contractfield input, .shopnum{width:100%}
}
/* Выдача наказания: форма в одну строку, на телефоне — в столбик. */
.banform{
  display:flex; flex-wrap:wrap; gap:12px; align-items:flex-end;
  padding:14px; margin-bottom:14px; border:1px solid var(--line);
  border-radius:10px; background:var(--panel2);
}
.banfield{display:flex; flex-direction:column; gap:4px; font-size:12px; color:var(--dim)}
.banfield input, .banfield select{
  background:var(--panel); color:var(--text); border:1px solid var(--line);
  border-radius:6px; padding:6px 9px; font:inherit; font-size:13px; min-width:170px;
}
.banform .primary{
  padding:7px 16px; border-radius:6px; cursor:pointer; font:inherit; font-size:13px;
  background:#2f3a4d; color:var(--text); border:1px solid #3d4657;
}
.banform .primary:hover{background:#394759}
.banstatus{font-size:13px; color:var(--dim); align-self:center}
.banstatus.bad{color:var(--down)}
.banacts{white-space:nowrap}
.banact{
  padding:3px 9px; margin-left:6px; border-radius:6px; cursor:pointer;
  background:var(--panel2); color:var(--dim); border:1px solid var(--line);
  font:inherit; font-size:12px;
}
.banact:hover{color:var(--text); border-color:#3d4657}
@media (max-width: 760px){
  .banform{flex-direction:column; align-items:stretch}
  .banfield input, .banfield select{min-width:0; width:100%}
}
/* Широкие таблицы ездят вбок внутри себя: страница от этого не разъезжается,
   и на телефоне видно последнюю колонку, а не её край. */
.tablescroll{overflow-x:auto; -webkit-overflow-scrolling:touch}
.tablescroll > .grid{min-width:760px}

/* -------- экономика (вкладка владельца) -------- */
.econpie { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
.econcircle {
  width: 180px; height: 180px; border-radius: 50%;
  flex: 0 0 auto;
  border: 1px solid var(--line);
}
.econlegend { display: flex; flex-direction: column; gap: 6px; min-width: 220px; }
.econrow { display: flex; align-items: center; gap: 8px; }
.econdot {
  width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto;
  display: inline-block;
}
.econtable { border-collapse: collapse; width: 100%; }
.econtable td { padding: 4px 10px 4px 0; border-bottom: 1px solid var(--line); }
.econtable td.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.econtable td.minus { color: var(--down); }

/* Настройка касс в админке. Строка на кассу: имя, выключатель, вес и доля.
   Доля прижата вправо и набрана моноширинными цифрами — по ней проверяют, что
   сумма сошлась в сто, а прыгающая ширина цифр этому мешает. */
.payrow{
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:10px 0; border-bottom:1px solid var(--line); font-size:14px;
}
.payrow:last-child{border-bottom:none}
.payname{font-weight:700; min-width:104px}
.paytoggle,.payweight{display:inline-flex; align-items:center; gap:6px}
.payrow input[type=text]{width:74px; margin:0; padding:6px 8px; text-align:right}
.payshare{
  margin-left:auto; font-weight:700; color:var(--accent);
  font-variant-numeric:tabular-nums;
}
.payurl{
  font-size:13px; color:var(--dim); word-break:break-all;
  background:var(--bg); border:1px solid var(--line);
  border-radius:6px; padding:3px 7px;
}
.payfoot{display:flex; align-items:center; gap:10px; margin:14px 0 0}

/* На телефоне доля не должна улетать на свою строку одна: перенос ловит её
   вместе с весом, а имя кассы остаётся в первой строке. */
@media (max-width:520px){
  .payshare{margin-left:0}
  .payname{min-width:100%}
}
