/* Слой компонентов «Комнаты забега» поверх tokens/*.css.
   Здесь только классы: ни одного собственного цвета — всё через var().
   Спеки перенесены один в один из дизайн-системы Running Helper
   (components/core, components/race, components/charts, ui_kits/miniapp). */

/* --- интенциональные добавления к токенам ------------------------------- */
:root {
  /* Затемнение под диалогом. Оно всегда тёмное: на светлой теме тёмное даёт
     --text, на тёмной — сам --bg. Одной переменной обе темы не покрыть. */
  --scrim: color-mix(in srgb, var(--text) 55%, transparent);

  /* Буква на метке участника белая в ОБЕИХ темах: цвета слотов --p-* насыщенные
     и там, и там, а человек должен узнавать свою метку одинаково. */
  --marker-ink: rgb(255 255 255);

  /* Высота развёрнутой шторки. Переменной, а не числом в правиле: её же читает
     app.js, когда шторку тянут пальцем, — иначе два места разошлись бы. */
  --sheet-open: 76%;
}

html.dark,
:root[data-theme="dark"] {
  --scrim: color-mix(in srgb, var(--bg) 72%, transparent);
}

*,
*::before,
*::after { box-sizing: border-box; }

/* Атрибут hidden обязан побеждать display компонента. Правило браузера по
   умолчанию слабее любого авторского, поэтому у .stack (display:flex), .signal
   и .screen спрятанный блок оставался бы на экране. */
[hidden] { display: none !important; }

html,
body {
  height: 100%;
  /* страница целиком не скроллится: скроллят свои контейнеры */
  overflow-x: hidden;
}

body {
  display: flex;
  flex-direction: column;
  overscroll-behavior-y: none;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}

/* ======================================================================== */
/* Утилиты                                                                  */
/* ======================================================================== */

.dim { color: var(--text-dim); }
.mute { color: var(--text-mute); }

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}

.stack--1, .cluster--1 { gap: var(--sp-1); }
.stack--2, .cluster--2 { gap: var(--sp-2); }
.stack--3, .cluster--3 { gap: var(--sp-3); }
.stack--4, .cluster--4 { gap: var(--sp-4); }
.stack--5, .cluster--5 { gap: var(--sp-5); }
.stack--6, .cluster--6 { gap: var(--sp-6); }

.cluster--between { justify-content: space-between; }
.cluster--end { justify-content: flex-end; }
.cluster--grow > * { flex: 1 1 0; min-width: 0; }

/* ======================================================================== */
/* Кнопка                                                                   */
/* ======================================================================== */

.btn {
  --btn-veil: transparent;
  --btn-ring: 0 0 0 0 transparent;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  border: var(--border-w) solid transparent;
  border-radius: var(--r-md);
  background: var(--accent-ink);
  color: var(--text-on-accent);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: 1;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transform: none;
  transition: transform 90ms var(--ease-out);
  box-shadow: var(--btn-ring), inset 0 0 0 999px var(--btn-veil);
}

.btn > svg { flex: none; }

.btn--main {
  background: var(--mainbutton-bg);
  color: var(--mainbutton-fg);
}

.btn--primary {
  background: var(--accent-ink);
  color: var(--text-on-accent);
}

.btn--secondary {
  background: var(--surface-2);
  color: var(--text);
}

.btn--ghost {
  background: transparent;
  color: var(--accent-ink);
}

.btn--outline {
  background: transparent;
  color: var(--text);
  border-color: var(--line);
}

.btn--danger {
  background: transparent;
  color: var(--dnf);
  border-color: var(--line);
}

.btn--lg {
  min-height: 50px;
  padding: 0 var(--sp-5);
  font-size: 16px;
  border-radius: var(--r-md);
}

.btn--md {
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  font-size: var(--fs-body);
  border-radius: var(--r-md);
}

.btn--sm {
  position: relative;
  min-height: 32px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-caption);
  border-radius: var(--r-sm);
}

/* Мелкая кнопка ниже зоны тапа по высоте — добираем невидимым полем. */
.btn--sm::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: var(--tap-min);
  transform: translateY(-50%);
}

.btn--full {
  display: flex;
  width: 100%;
}

.btn--bare {
  min-height: 0;
  padding: 0;
  border: none;
  /* Без заливки: голая кнопка — текстовая ссылка, а не чип. Без этих двух строк
     она наследует акцентный фон .btn и при нулевых полях читается ярко-зелёным
     пятном в 13 px высотой — «еле видна». */
  background: transparent;
  color: var(--accent-ink);
}

/* Голая мелкая кнопка ростом с текст: невидимое поле .btn--sm::before торчало бы
   выше и ниже её на 15 px и перехватывало тапы у соседа сверху. Даём ей
   собственную высоту вместо фантомной — зона тапа та же, чужого не занимает. */
.btn--bare.btn--sm {
  min-height: var(--tap-min);
}

.btn--bare.btn--sm::before { content: none; }

@media (hover: hover) {
  .btn:hover:not([disabled]) { --btn-veil: var(--hover-veil); }
}

.btn:active:not([disabled]) {
  --btn-veil: var(--press-veil);
  transform: scale(var(--press-scale));
}

.btn:focus-visible {
  --btn-ring: var(--focus-ring);
  outline: none;
}

.btn[disabled] {
  --btn-veil: transparent;
  opacity: var(--disabled-opacity);
  cursor: default;
  transform: none;
}

/* ======================================================================== */
/* Карточка                                                                 */
/* ======================================================================== */

.card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
  padding: var(--sp-4);
  border: var(--border-w) solid var(--card-border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
}

/* Вложенный блок: заливка --surface-2, ни тени, ни рамки. */
.card--nested {
  background: var(--surface-2);
  border-color: transparent;
  box-shadow: none;
}

.card--pad-sm { padding: var(--sp-3); }
.card--flat { box-shadow: none; }

.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-width: 0;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
}

.card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
}

/* ======================================================================== */
/* Чип статуса                                                              */
/* ======================================================================== */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--text-dim);
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  line-height: 1.3;
  white-space: nowrap;
}

.chip__dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.chip--accent {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

/* Лайм редкий: рекорд и «БЕЖИМ», а не рядовой статус. */
.chip--lime {
  background: var(--lime-soft);
  color: var(--lime);
}

.chip--warn {
  background: color-mix(in srgb, var(--warn) 14%, var(--surface));
  color: var(--warn);
}

.chip--dnf {
  background: color-mix(in srgb, var(--dnf) 14%, var(--surface));
  color: var(--dnf);
}

/* ======================================================================== */
/* Строка списка                                                            */
/* ======================================================================== */

.row {
  --row-veil: transparent;
  --row-ring: 0 0 0 0 transparent;
  --row-elev: var(--shadow);

  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-width: 0;
  min-height: var(--tap-min);
  padding: 10px 14px;
  border: var(--border-w) solid var(--card-border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-sans);
  text-align: left;
  text-decoration: none;
  transform: none;
  transition: transform 90ms var(--ease-out);
  box-shadow: var(--row-ring), var(--row-elev), inset 0 0 0 999px var(--row-veil);
}

.row--nested {
  --row-elev: 0 0 0 0 transparent;
  background: var(--surface-2);
  border-color: transparent;
}

/* Участник в лобби лежит прямо на фоне экрана, без своей поверхности. */
.row--bare {
  --row-elev: 0 0 0 0 transparent;
  background: transparent;
  border-color: transparent;
  padding: var(--sp-2) var(--sp-1);
}

.row__lead {
  flex: none;
  display: flex;
  align-items: center;
}

.row__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Блок, а не flex: многоточие умеет только блочный контейнер, а на flex-родителе
   text-overflow не действует — длинное название рубилось посреди буквы.
   Значки внутри строки всё равно инлайновые и встают в ряд сами. */
.row__title {
  display: block;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row__sub {
  display: block;
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Строка забега в истории — это цифры результата, а не подпись: «25 км —
   2:08:1…» обрезало ровно то, ради чего строка есть. Переносим. */
.row__sub--wrap { white-space: normal; }

.row__tail {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

a.row,
button.row,
.row[role="button"],
.row.is-tap { cursor: pointer; }

@media (hover: hover) {
  a.row:hover,
  button.row:hover,
  .row[role="button"]:hover,
  .row.is-tap:hover { --row-veil: var(--hover-veil); }
}

a.row:active,
button.row:active,
.row[role="button"]:active,
.row.is-tap:active {
  --row-veil: var(--press-veil);
  transform: scale(var(--press-scale));
}

.row:focus-visible {
  --row-ring: var(--focus-ring);
  outline: none;
}

/* ======================================================================== */
/* Сегментированный переключатель (и выбор роли)                            */
/* ======================================================================== */

.seg {
  display: flex;
  gap: var(--sp-1);
  width: 100%;
  padding: var(--sp-1);
  border-radius: var(--r-md);
  background: var(--surface-2);
  font-family: var(--font-sans);
}

.seg__item {
  flex: 1;
  min-width: 0;
  min-height: var(--tap-min);
  padding: 6px var(--sp-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-dim);
  font-size: 14px;
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  white-space: nowrap;
  cursor: pointer;
}

.seg__item.is-active {
  background: var(--accent-ink);
  color: var(--text-on-accent);
  font-weight: var(--fw-semibold);
}

.seg__item:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.seg__item[disabled] {
  opacity: var(--disabled-opacity);
  cursor: default;
}

.seg__hint {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--text-mute);
  font-variant-numeric: var(--numeric-tabular);
}

.seg__item.is-active .seg__hint {
  color: inherit;
  opacity: .75;
}

/* Четвёртый пункт («Своя») в одну строку на узком экране не влезает:
   .seg__item — nowrap, а «Полумарафон» и так длинный. Разрешаем перенос. */
.seg--wrap { flex-wrap: wrap; }
.seg--wrap .seg__item { flex: 1 1 calc(50% - var(--sp-1)); }

/* Роль — это форма, поэтому переключатель роли повторяет форму метки.
   Поля и кегль мельче обычных: три подписи со значками в один ряд на экране
   320 px иначе не помещаются, а nowrap не даёт им сжаться — переключатель
   вылезал за карточку. */
.seg--role .seg__item {
  flex-direction: row;
  gap: 6px;
  font-size: 12px;
  padding: 6px var(--sp-2);
}

.seg__shape {
  flex: none;
  width: 13px;
  height: 13px;
  background: currentColor;
}

.seg__shape--runner { border-radius: 50%; }
.seg__shape--helper { border-radius: 5px; }

/* ======================================================================== */
/* Поле ввода                                                               */
/* ======================================================================== */

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  font-family: var(--font-sans);
}

.field__label {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
}

.field__input {
  width: 100%;
  min-height: var(--tap-min);
  padding: 0 var(--sp-3);
  border: var(--border-w) solid transparent;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  outline: none;
}

textarea.field__input {
  padding: 10px var(--sp-3);
  resize: vertical;
}

.field__input::placeholder { color: var(--text-placeholder); }

.field__input:focus { box-shadow: var(--focus-ring); }

.field__input[disabled] {
  opacity: var(--disabled-opacity);
  cursor: default;
}

.field__hint {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-mute);
  line-height: 1.35;
}

/* Именно border-color, а не тень: .field__input:focus специфичнее и перебил бы
   box-shadow, а рамка спокойно живёт рядом с кольцом фокуса. */
.field__input--bad { border-color: var(--dnf); }
.field__hint--bad { color: var(--dnf); }

/* ======================================================================== */
/* Диалог                                                                   */
/* ======================================================================== */

/* Родитель обязан быть position: relative — диалог накрывает экран мини-апа,
   а не всю страницу. */
.dialog {
  position: absolute;
  /* Четыре стороны, а не inset: его понимает только Chrome 87 и новее, а на
     старом WebView диалог подтверждения ложился бы вне экрана. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 20;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--sp-4);
}

.dialog[hidden] { display: none; }

.dialog__backdrop {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0;
  border: none;
  background: var(--scrim);
}

.dialog__panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 340px;
  padding: 18px;
  border: var(--border-w) solid var(--card-border);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.dialog__title {
  font-size: 17px;
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
}

.dialog__text {
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  line-height: 1.4;
}

.dialog__actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
}

/* ======================================================================== */
/* Тост                                                                     */
/* ======================================================================== */

.toast {
  --toast-tone: var(--ok);

  display: flex;
  align-items: flex-start;
  gap: 10px;
  max-width: 420px;
  padding: var(--sp-3) 14px;
  border: var(--border-w) solid var(--card-border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
  font-family: var(--font-sans);
}

/* Цветной полосы слева у карточек в этой системе нет: тон несёт точка. */
.toast::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  margin-top: 6px;
  border-radius: 50%;
  background: var(--toast-tone);
}

.toast--ok { --toast-tone: var(--ok); }
.toast--warn { --toast-tone: var(--warn); }
.toast--dnf { --toast-tone: var(--dnf); }
.toast--lime { --toast-tone: var(--lime); }
.toast--neutral { --toast-tone: var(--text-mute); }

.toast__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.toast__title {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: 1.3;
}

.toast__text {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  line-height: 1.35;
}

.toast__action {
  position: relative;
  flex: none;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--accent-ink);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.toast__action::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: var(--tap-min);
  transform: translateY(-50%);
}

.toast__action:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* ======================================================================== */
/* Показатель и секундомер                                                  */
/* ======================================================================== */

/* Число доминирует, единица подпирает. Подпись всегда с новой строки — её
   переносит flex-basis: 100%, отдельная обёртка для этого не нужна. */
.metric {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1px 3px;
  min-width: 0;
  font-family: var(--font-sans);
}

.metric__v {
  flex: none;
  font-size: var(--fs-metric);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: var(--text);
  font-variant-numeric: var(--numeric-tabular);
  white-space: nowrap;
}

.metric__unit {
  flex: none;
  font-size: var(--fs-unit);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  white-space: nowrap;
}

.metric__k {
  flex-basis: 100%;
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  line-height: 1.2;
  white-space: nowrap;
}

.metric--right {
  justify-content: flex-end;
  text-align: right;
}

.metric--center {
  justify-content: center;
  text-align: center;
}

.metric--sm .metric__v { font-size: 18px; }
.metric--lg .metric__v { font-size: 26px; }

/* Лайм здесь — только личный рекорд, не украшение. */
.metric--lime .metric__v { color: var(--lime); }
.metric--warn .metric__v { color: var(--warn); }
.metric--dnf .metric__v { color: var(--dnf); }

.stopwatch {
  font-family: var(--font-numeric);
  font-size: var(--fs-stopwatch);
  font-weight: var(--fw-semibold);
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--text);
  font-variant-numeric: var(--numeric-tabular);
}

.stopwatch--sm { font-size: 38px; }
.stopwatch--dim { color: var(--text-dim); }

.stopwatch__label {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
}

/* ======================================================================== */
/* Отсчёт                                                                   */
/* ======================================================================== */

.countdown {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-6);
  padding: var(--sp-6) var(--screen-pad);
  background: var(--bg);
  text-align: center;
}

/* Единственное место, где лайма много, и это правильно. */
.countdown__digit {
  font-family: var(--font-sans);
  font-size: var(--fs-countdown);
  font-weight: var(--fw-heavy);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--lime);
  font-variant-numeric: var(--numeric-tabular);
  animation: rh-digit var(--dur-digit) var(--ease-out);
}

.countdown__digit--go {
  font-size: calc(var(--fs-countdown) * 0.42);
  letter-spacing: 0.02em;
}

.countdown__cap {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-mute);
  line-height: var(--lh-snug);
}

.countdown__cap--lead {
  font-size: var(--fs-body);
  color: var(--text-dim);
}

@keyframes rh-digit {
  from { transform: scale(1.25); }
  to { transform: scale(1); }
}

/* ======================================================================== */
/* Шторка                                                                   */
/* ======================================================================== */

/* Родитель обязан быть position: relative — шторка живёт внутри экрана карты.
   Плотная поверхность, не матовое стекло: карта под ней и так перерисовывается
   каждые две секунды. */
.sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: var(--sheet-peek);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  color: var(--text);
  border-top: var(--border-w) solid var(--card-border);
  border-top-left-radius: var(--r-lg);
  border-top-right-radius: var(--r-lg);
  box-shadow: var(--shadow-sheet);
  font-family: var(--font-sans);
  transition: max-height var(--dur-sheet) var(--ease-out);
}

/* Развёрнутая шторка. Не 100%: карта должна остаться видимой сверху — иначе
   человек теряет из виду то, ради чего экран и открыт. */
.sheet.is-expanded { max-height: var(--sheet-open); }

/* Пока шторку тянут, высоту ведёт палец: переход только отставал бы от него.
   На отпускании класс снимается, и защёлкивание уже анимирует переход. */
.sheet.is-dragging { transition: none; }

/* Полоска хвата в развёрнутом виде ярче: это единственный способ свернуть. */
.sheet.is-expanded .sheet__grab::after { background: var(--text-mute); }

/* Полоска хвата узкая, но зону тапа элемент держит сам: невидимое поле сверху
   срезал бы overflow шторки, а снизу оно воровало бы тапы у содержимого. */
.sheet__grab {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--sp-2) 0 var(--sp-1);
  border: none;
  background: transparent;
  cursor: pointer;
  /* Вертикальный жест на хвате — перетаскивание шторки, а не прокрутка:
     без этого браузер забирает палец себе и шлёт pointercancel. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.sheet__grab::after {
  content: "";
  width: 36px;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--line);
}

.sheet__grab:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.sheet__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Снизу добавляем зону жеста «домой»: без неё последняя строка списка уезжает
     под индикатор и её не дочитать. */
  padding: var(--sp-1) 14px calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
}

/* План набран: результат записан, а бег идёт дальше. Плашка лаймовая —
   это тот самый момент, ради которого лайм и держат редким. Текст не лаймом:
   на --lime-soft он не дотягивает до 4.5:1, лайм здесь — только кромка. */
.planbar {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: var(--sp-2) 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--lime);
  border-radius: var(--r-sm);
  background: var(--lime-soft);
  color: var(--text);
}

.planbar__title {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  font-variant-numeric: var(--numeric-tabular);
}

.planbar__text {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--text-dim);
}

/* В карточке участника плашка стоит в стопке — свои поля ей не нужны. */
.screen__scroll > .planbar { margin: 0; }

/* ======================================================================== */
/* Состояние связи (§17)                                                    */
/* ======================================================================== */

.signal {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--text-dim);
  font-family: var(--font-sans);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  line-height: 1.3;
  white-space: nowrap;
}

/* display у .signal перебивает атрибут hidden, поэтому гасим его явно:
   полосу связи в index.html прячут именно им. */
.signal[hidden] { display: none; }

/* «Всё хорошо» не занимает места на экране. */
.signal--live { display: none; }

/* Тишина 90 с — человек мог встать на светофоре, тон нейтральный. */
.signal--quiet {
  background: var(--surface-2);
  color: var(--text-dim);
}

.signal--lost,
.signal--expired {
  background: color-mix(in srgb, var(--dnf) 14%, var(--surface));
  color: var(--dnf);
}

/* Конец трансляции известен заранее — это состояние важнее потери связи. */
.signal--expiring {
  background: color-mix(in srgb, var(--warn) 14%, var(--surface));
  color: var(--warn);
}

.signal--back {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.signal--lost::before,
.signal--expiring::before,
.signal--back::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

/* ======================================================================== */
/* Метка участника в списках                                                */
/* ======================================================================== */

.avatar {
  --avatar-size: var(--marker-size);
  --avatar-color: var(--p-1);

  position: relative;
  flex: none;
  width: var(--avatar-size);
  height: var(--avatar-size);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--marker-radius-runner);
  border: var(--marker-ring) solid var(--surface);
  background: var(--avatar-color);
  color: var(--marker-ink);
  box-shadow: var(--shadow);
  font-family: var(--font-sans);
  font-size: calc(var(--avatar-size) * 0.44);
  font-weight: var(--fw-heavy);
  line-height: 1;
  text-transform: uppercase;
  user-select: none;
}

/* Слот выдаётся при входе в комнату и принадлежит человеку, а не строке. */
.avatar--p1 { --avatar-color: var(--p-1); }
.avatar--p2 { --avatar-color: var(--p-2); }
.avatar--p3 { --avatar-color: var(--p-3); }
.avatar--p4 { --avatar-color: var(--p-4); }
.avatar--p5 { --avatar-color: var(--p-5); }
.avatar--p6 { --avatar-color: var(--p-6); }
.avatar--p7 { --avatar-color: var(--p-7); }
.avatar--p8 { --avatar-color: var(--p-8); }

/* Роль — это форма: бегун круг, помощник скруглённый квадрат. */
.avatar--helper { border-radius: var(--marker-radius-helper); }

/* Наблюдатель метки не имеет: он ничего не отдаёт, поэтому и цвета нет. */
.avatar--none {
  background: var(--surface-2);
  color: var(--text-mute);
  border-color: transparent;
  box-shadow: none;
}

.avatar--none > svg {
  width: 56%;
  height: 56%;
}

.avatar--sm { --avatar-size: 22px; }
.avatar--lg { --avatar-size: 40px; }

/* «Я» — лаймовое кольцо и лёгкая пульсация. */
.avatar--self::after {
  content: "";
  position: absolute;
  top: -4px;
  right: -4px;
  bottom: -4px;
  left: -4px;
  border-radius: inherit;
  border: 2px solid var(--marker-self-ring);
  animation: rh-self-pulse var(--pulse-self) var(--ease-out) infinite;
}

/* Потерялся: цвет глушится, но остаётся — человека всё ещё надо узнавать. */
.avatar.is-stale {
  filter: saturate(var(--marker-stale-saturation));
  outline: 2px dashed var(--text-mute);
  outline-offset: 2px;
}

@keyframes rh-self-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--lime) 55%, transparent); }
  70% { box-shadow: 0 0 0 10px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ======================================================================== */
/* Таблица гонки и протокол                                                 */
/* ======================================================================== */

.results {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-family: var(--font-sans);
  font-variant-numeric: var(--numeric-tabular);
}

/* Четыре ячейки: место, «кто» (метка вместе с именем), значение, хвост.
   Раньше колонок было пять, а метка и имя приходили отдельными детьми — имя
   попадало в узкую числовую колонку, а метка налезала на соседнюю. */
.results__row {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) 62px 64px;
  gap: 6px;
  align-items: center;
}

/* Ни одна ячейка не имеет права вылезти к соседу: у чисел и чипов nowrap, и
   без этого «1:58:03» ложилось поверх «25,00 км». */
.results__row > * { min-width: 0; }

/* Число в строке таблицы — данные, а не витрина: витринные 18 px в свою
   колонку на 320 px не помещаются. */
.results__row .metric__v { font-size: var(--fs-body); }

/* Подписи в узких колонках на шаг мельче: «помощник» и «потерялся» иначе
   рубятся посреди слова. Что не влезет и так (например «трансляция кончается»),
   аккуратно уедет в многоточие, а не ляжет поверх соседа. */
.results__row .signal,
.results__row .row__sub {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--fs-unit);
}

.results__row .signal { padding: 3px 6px; }

/* Заголовок секции — подпись, а не строка таблицы: своей сетки ему не нужно. */
.results__head {
  padding: 0 10px 6px;
  font-size: var(--fs-unit);
  font-weight: var(--fw-medium);
  color: var(--text-mute);
}

.results__row {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--sp-2) 10px;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text);
  font-size: var(--fs-body);
  text-align: left;
}

.results__row:nth-child(even) { background: var(--surface-2); }

/* Колонки данных прижаты вправо — разметке для этого класс не нужен. */
.results__row > :nth-child(n + 3) {
  text-align: right;
  font-weight: var(--fw-medium);
  color: var(--text-dim);
}

.results__row > :nth-child(3) {
  color: var(--text);
  font-weight: var(--fw-semibold);
}

/* Метка в таблице мельче, чем в списках: на 320 px каждый пиксель отнят у
   имени, а узнаётся метка по цвету и форме, а не по размеру. */
.results__who {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.results__who .avatar { --avatar-size: 26px; }

/* Обрезать может только блочный контейнер: на самом flex-родителе
   text-overflow не работает, и длинное имя рубилось посреди буквы. */
.results__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: var(--fw-medium);
}

.results__place {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--text-dim);
}

a.results__row,
button.results__row,
.results__row.is-tap { cursor: pointer; }

.results__row:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* За кем следит карта — тот подсвечен в таблице. Заливка системная, зелёная:
   цвет участника здесь занят его меткой. */
.results__row.is-active { background: var(--accent-soft); }

/* Своя строка обводится лаймом, а не заливается. */
.results__row.is-self {
  outline: var(--border-w) solid var(--lime);
  outline-offset: calc(var(--border-w) * -1);
}

/* Сошедший теряет место и уходит в --text-mute. */
.results__row.is-dnf { color: var(--text-mute); }
.results__row.is-dnf .results__place { color: var(--dnf); }
.results__row.is-dnf > :nth-child(n + 3) { color: var(--text-mute); }
.results__row.is-dnf > :nth-child(5) { color: var(--dnf); }

/* Вторая строка протокола — вся пробежка под результатом за план. Своя
   строка сетки на все колонки после места: в 62 px «всего 27,0 км за 2:18:40»
   не лезет, а обрезать цифры нельзя. */
.results__row > .results__more {
  grid-column: 2 / -1;
  margin-top: -2px;
  text-align: left;
  font-size: var(--fs-unit);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--text-dim);
  white-space: normal;
}

/* «план» в шторке — чип, но в колонке 64 px: поля и кегль на шаг мельче. */
.results__row .chip {
  justify-self: end;
  padding: 3px 6px;
  font-size: var(--fs-unit);
}

/* ======================================================================== */
/* Раскладка по километрам                                                  */
/* ======================================================================== */

.splits {
  display: flex;
  flex-direction: column;
  gap: var(--chart-bar-gap);
  min-width: 0;
  font-family: var(--font-sans);
  font-variant-numeric: var(--numeric-tabular);
}

/* Длина бара приходит разметкой в процентах: процент считается от колонки. */
.split {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) 44px;
  gap: var(--sp-2);
  align-items: center;
}

.split__km {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--text-mute);
  text-align: right;
}

.split__bar {
  --split-fill: var(--chart-3);

  display: block;
  width: 100%;
  min-width: 6px;
  height: var(--chart-bar-h);
  justify-self: start;
  border-radius: 0 var(--chart-bar-r) var(--chart-bar-r) 0;
  background: var(--split-fill);
}

/* Величина, а не категории: один хвойный оттенок светлее → темнее. */
.split__bar--1 { --split-fill: var(--chart-1); }
.split__bar--2 { --split-fill: var(--chart-2); }
.split__bar--3 { --split-fill: var(--chart-3); }
.split__bar--4 { --split-fill: var(--chart-4); }
.split__bar--5 { --split-fill: var(--chart-5); }

.split__time {
  font-size: var(--fs-unit);
  font-weight: var(--fw-semibold);
  color: var(--text);
  text-align: right;
}

.split.is-best .split__bar { --split-fill: var(--chart-best); }
.split.is-worst .split__bar { --split-fill: var(--chart-worst); }

/* Оценочный километр: штриховка под 45°, в лучший и худший он не играет. */
.split.is-est .split__bar {
  --split-fill: var(--surface-2);
  background-image: var(--chart-hatch);
  box-shadow: inset 0 0 0 1px var(--line);
}

.split.is-est .split__time { color: var(--text-mute); }

/* Подпись над графиком на экране истории. */
.splits__head {
  margin-top: var(--sp-4);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-dim);
}

.splits__head:first-child { margin-top: 0; }

.splits__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: 6px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--text-mute);
}

.splits__key {
  display: flex;
  align-items: center;
  gap: 5px;
}

.splits__key::before {
  content: "";
  flex: none;
  width: 12px;
  height: 8px;
  border-radius: 2px;
  background: var(--surface-2);
}

.splits__key--best::before { background: var(--chart-best); }
.splits__key--worst::before { background: var(--chart-worst); }

.splits__key--est::before {
  background: var(--surface-2);
  background-image: var(--chart-hatch);
  box-shadow: inset 0 0 0 1px var(--line);
}

/* ======================================================================== */
/* Оболочка — постоянные узлы index.html                                    */
/* ======================================================================== */

/* Высота обязана быть определённой на всём пути до .screen: иначе
   .screen__scroll не прокручивается, а шторка ложится по низу содержимого
   вместо низа карты. */
#screen {
  flex: 1;
  min-height: 0;
}

/* Полоса связи — обычная строка в потоке: она сдвигает экран вниз, а не
   накрывает шапку, и тапы по шапке остаются тапами по шапке. Фиксированной она
   лежала поверх всей 48-пиксельной .screen__head — и «Назад», и заголовка — и,
   не имея pointer-events:none, съедала по ним тапы. Фраза длинная, поэтому
   здесь, в отличие от чипа, строка переносится. */
#conn {
  flex: none;
  margin: calc(var(--sp-2) + env(safe-area-inset-top, 0px)) var(--sp-3) 0;
  justify-content: center;
  white-space: normal;
  text-align: center;
  box-shadow: var(--shadow);
  pointer-events: none;
}

/* Тосты всплывают над экраном и не перехватывают тапы по карте под ними. */
.toasts {
  position: fixed;
  z-index: 40;
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  left: var(--sp-3);
  right: var(--sp-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  pointer-events: none;
}

.toasts > * { pointer-events: auto; }

/* Карта во весь экран: габариты холста map.js читает из CSS. */
.racemap__wrap {
  position: relative;
  flex: 1;
  min-height: 0;
}

.racemap {
  display: block;
  width: 100%;
  height: 100%;
}

/* ======================================================================== */
/* Экран                                                                    */
/* ======================================================================== */

.screen {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
}

.screen[hidden] { display: none; }

.screen__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: var(--sp-1) 14px;
  background: var(--bg);
  border-bottom: var(--border-w) solid var(--line);
}

/* Шапка — три слота в ряд: «назад», заголовок с подписью, хвост.
   Растягивается только средний, поэтому flex живёт на обёртке, а не на самом
   заголовке. */
.screen__titles {
  flex: 1 1 auto;
  min-width: 0;
}

.screen__back {
  flex: none;
  align-self: center;        /* высоту зоны тапа задаёт .btn--bare.btn--sm */
  padding: 0 var(--sp-2) 0 0;
  font-size: var(--fs-body);
}

/* Кнопка в шапке — дублёр системной. Есть системная — своя лишняя. */
html.has-native-back .screen__back,
html.has-native-back .screen__back--float { display: none; }

/* .screen позиционирован relative — кнопке на карте есть от чего считаться. */
.screen__back--float {
  position: absolute;
  z-index: 10;
  top: calc(var(--sp-3) + env(safe-area-inset-top, 0px));
  left: var(--sp-3);
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow);
}

.screen__title {
  min-width: 0;
  font-size: var(--fs-title);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.screen__sub {
  font-size: var(--fs-unit);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.screen__pad { padding: var(--sp-4) var(--screen-pad) var(--screen-pad); }

/* Скроллит только этот контейнер: страница по горизонтали не едет никогда. */
.screen__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* Гонка и отсчёт — full-bleed: карта во весь экран, поверх неё только метки,
   чипы и шторка. */
.screen--bare .screen__scroll { overflow: hidden; }

/* ======================================================================== */
/* Пусто и ожидание                                                         */
/* ======================================================================== */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-8) var(--sp-4);
  text-align: center;
  color: var(--text-dim);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.empty__title {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.empty__text {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-mute);
}

.spinner {
  flex: none;
  width: 20px;
  height: 20px;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: rh-spin 700ms var(--ease-linear) infinite;
}

.spinner--lg {
  width: 28px;
  height: 28px;
  border-width: 3px;
}

@keyframes rh-spin {
  to { transform: rotate(360deg); }
}

/* ======================================================================== */
/* Меньше движения                                                          */
/* ======================================================================== */

@media (prefers-reduced-motion: reduce) {
  .btn,
  .row,
  .sheet { transition: none; }

  .btn:active:not([disabled]),
  a.row:active,
  button.row:active,
  .row[role="button"]:active,
  .row.is-tap:active { transform: none; }

  .countdown__digit { animation: none; }
  .avatar--self::after { animation: none; }

  /* Крутилка не замирает: это не украшение, а единственный признак того, что
     ответа ещё ждём. Поэтому не выключаем, а замедляем. */
  .spinner { animation-duration: 1600ms; }
}

/* ======================================================================== */
/* Запасные цвета для старых вебвью                                         */
/* ======================================================================== */

/* color-mix() понимает Chrome 111 и новее. На Android мини-ап открывается в
   системном WebView, а он часто заметно старее. Все семь мест, где мы им
   пользуемся, оборачивают var(), поэтому старый движок не отбрасывает
   объявление при разборе, а обнуляет его при вычислении — и «запасное»
   объявление строкой выше не спасает: оно уже перекрыто. Помогает только
   @supports: сюда движок войдёт лишь тогда, когда color-mix ему не по зубам.

   Без этого блока подложка диалога, чипы предупреждений и полосы состояния
   становятся прозрачными: текст читается, но фон пропадает. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  :root { --scrim: rgb(10 31 20 / .55); }
  html.dark,
  :root[data-theme="dark"] { --scrim: rgb(4 12 8 / .72); }

  .chip--warn,
  .signal--expiring { background: var(--surface-2); }

  .chip--dnf,
  .signal--lost,
  .signal--expired { background: var(--surface-2); }

  /* Пульс своей метки — кольцом видимого цвета вместо полупрозрачного. */
  @keyframes rh-self-pulse {
    0% { box-shadow: 0 0 0 0 var(--lime); }
    70% { box-shadow: 0 0 0 12px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
  }
}

/* Карта пройденного в карточке забега. Высота фиксированная: карточка
   прокручивается целиком, и карте нельзя тянуть её вниз бесконечно. */
.cardmap {
  display: block;
  width: 100%;
  height: 220px;
  border-radius: var(--r-md);
  background: var(--surface-2);
}

/* Вторая строка блока результата: вся пробежка. */
.result__total {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
}

/* Слои карты «Как бежал»: «По маршруту» и «Реальный трек». Чип — и
   переключатель, и подпись: образец линии в нём той же толщины и цвета, что
   на карте. Выключенный слой бледнеет, но не пропадает — его видно, чтобы
   включить обратно. */
.layers {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: calc(var(--sp-2) - var(--sp-5));
}

.layer {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 36px;
  padding: 0 var(--sp-3);
  border: var(--border-w) solid var(--line);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-mute);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

.layer.is-on {
  border-color: transparent;
  background: var(--surface-2);
  color: var(--text);
}

.layer:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.layer__swatch {
  flex: none;
  width: 18px;
  height: var(--route-w);
  border-radius: var(--r-pill);
  background: var(--accent);
  opacity: .35;
}

.layer__swatch--track {
  height: var(--path-w);
  background: var(--p-1);
}

.layer.is-on .layer__swatch { opacity: 1; }
