/* Дизайн-система Edge — единственный источник токенов.
   Зона Донека (design/): остальные зоны читают, но никогда не правят.

   Подключение — со своего origin, файл физически один:
       <link rel="stylesheet" href="/design/edge.css">
   На каждом домене /design/ — alias на /opt/edge/design/ (как /fonts/).
   Один абсолютный URL не годится: CSP `style-src 'self'` режет кросс-домен.

   Тема одна, светлая — тёмной не будет (решение 14.08.2026).
   Чисто белого в системе нет: самая светлая поверхность — steel-100.
*/

/* Шрифт проекта. Диапазоны весов — та же конвенция, что уже стоит у клиента
   (server/public-client/index.html:13-15): одно семейство, начертание выбирается весом.
   Regular, Heavy и HeavySlanted лежат в public-client/fonts и раздаются как /fonts/
   со всех трёх доменов — это чужая зона, поэтому ссылаюсь, но не трогаю.

   Thin положен в свою: design/fonts. Он нужен только системе (тонкие разделители «|»
   в карточках и в кнопке оплаты), в клиенте его не было вовсе. Заодно это проба
   схемы, к которой всё равно придём: шрифты системы едут вместе с её CSS. */
@font-face { font-family: "PP Object Sans"; src: url("/fonts/PPObjectSans-Regular.woff2") format("woff2");
             font-weight: 100 650; font-style: normal; font-display: swap; }

/* Thin — ОТДЕЛЬНЫМ семейством, а не весом внутри «PP Object Sans».
   Через вес не заработало: файл на телефон приходил (в логах 304), глифы в нём есть,
   но после догрузки Safari всё равно рисовал разделители Regular — то есть промахивался
   мимо начертания при подборе. Отдельное имя выбор делает однозначным: одно семейство —
   один файл, подбирать нечего. Внутри у файла usWeightClass 250, не 100 — возможно,
   в этом и дело, но проверить вживую в этой сессии было нечем. */
@font-face { font-family: "PP Object Sans Thin"; src: url("/design/fonts/PPObjectSans-Thin.woff2") format("woff2");
             font-weight: 400; font-style: normal; font-display: swap; }
/* Medium — тоже ОТДЕЛЬНЫМ семейством, и по той же причине, что Thin: Regular выше
   объявлен диапазоном 100–650, то есть вес 500 достаётся ему, и подбор по весу до
   Medium просто не дойдёт. Взят из полного семейства PP Object Sans v2.4
   (usWeightClass 500), переведён в woff2. Нужен цифрам счётчика в профиле:
   в макете они набраны именно им. */
@font-face { font-family: "PP Object Sans Medium"; src: url("/design/fonts/PPObjectSans-Medium.woff2") format("woff2");
             font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "PP Object Sans"; src: url("/fonts/PPObjectSans-Heavy.woff2") format("woff2");
             font-weight: 651 900; font-style: normal; font-display: swap; }
@font-face { font-family: "PP Object Sans"; src: url("/fonts/PPObjectSans-HeavySlanted.woff2") format("woff2");
             font-weight: 651 900; font-style: italic; font-display: swap; }

:root {
  color-scheme: light;

  /* ── Steel — нейтральная шкала, 17 ступеней (решено 03.08.2026) ────────────
     База. Семантика ниже ссылается сюда, разметка берёт семантику, а не ступень. */
  --edge-steel-100: #F8F9FA;
  --edge-steel-150: #F0F2F4;
  --edge-steel-200: #E9ECEF;
  --edge-steel-250: #E3E7EA;
  --edge-steel-300: #DEE2E6;
  --edge-steel-350: #D6DBE0;
  --edge-steel-400: #CED4DA;
  --edge-steel-450: #BDC4CB;
  --edge-steel-500: #ADB5BD;
  --edge-steel-550: #8C949C;
  --edge-steel-600: #6C757D;
  --edge-steel-650: #5A626A;
  --edge-steel-700: #495057;
  --edge-steel-750: #3E454B;
  --edge-steel-800: #343A40;
  --edge-steel-850: #2A2F34;
  --edge-steel-900: #212529;

  /* ── Поверхности ───────────────────────────────────────────────────────────
     Фон страниц — общий для всех поверхностей: клиент, панель, лендинг, телек.
     Задан 17.08.2026 (steel-200). */
  --edge-bg-page: var(--edge-steel-200);

  /* ── Текст ─────────────────────────────────────────────────────────────────
     Из макета EDGE-MS, узел 848:2671: активный пункт #212529, спокойные #ADB5BD.
     Оба хекса уже есть в шкале — steel-900 и steel-500, новых значений не заводим. */
  --edge-text-strong: var(--edge-steel-900);
  --edge-text-muted:  var(--edge-steel-500);
  /* Второй план внутри карточек — характеристики, единицы, цена (#8C949C = steel-550,
     узел 848:3169). Отличается от muted на одну ступень и живёт на светлом и на
     тёмном фоне одинаково, поэтому имя отдельное. */
  --edge-text-secondary: var(--edge-steel-550);
  /* Цена и статус занятой станции — steel-400 (узлы 867:3603, 868:3606). */
  --edge-text-faint: var(--edge-steel-400);
  /* Единственное чисто белое в системе — надписи поверх кадра на телеке (узлы
     915:3187, 915:3188). На фотографии steel-100 читается как грязноватый серый,
     поэтому здесь белый настоящий; на поверхностях его по-прежнему нет. */
  --edge-text-on-photo: #FFFFFF;

  /* ── Панель оператора ──────────────────────────────────────────────────────
     У панели своя пара ступеней, и это не прихоть: её плашки лежат на steel-100,
     а не на фоне страницы, и весь её язык тише гостевого — сведения, а не речь.
     Пересобрано 26.08 по узлу 1000:6515: Даниил развёл цвета не по красоте, а по
     РОЛЯМ, и роли теперь читаются с одного взгляда.

     Главное различие — между «есть данные» и «нечего показать». Основное потемнело
     до steel-800, а неизвестное ушло в steel-200, почти в фон: погашенный значок
     больше не спорит за внимание с живым, он просто отсутствует.

     Второй план (идентификатор, MAC, единицы) поднялся с steel-400 до steel-500 —
     он стал читаемым, оставшись вторым. */
  --edge-text-panel:      var(--edge-steel-800);
  --edge-text-panel-dim:  var(--edge-steel-500);
  --edge-icon-panel:      var(--edge-steel-800);
  --edge-icon-panel-dim:  var(--edge-steel-200);

  /* ── То, что можно нажать ──────────────────────────────────────────────────
     Отдельная тройка, и это не прихоть: кнопка в панели ЛЕГЧЕ текста рядом с ней
     (steel-500 против steel-800) — иначе ряд кнопок перетягивал бы взгляд с самих
     станций. Тяжелеет она только под курсором и под нажатием.

     Недоступная кнопка красится не своим цветом, а общим «неизвестным» steel-200:
     нечего нажимать — не за что и цепляться глазу. */
  --edge-icon-action:        var(--edge-steel-500);
  --edge-icon-action-hover:  var(--edge-steel-700);
  --edge-icon-action-active: var(--edge-steel-900);
  /* Значок «всё в порядке» — зелёный из системы (--edge-green, он же у личного
     времени). Пары значков на карточке читаются и цветом, и формой. */
  --edge-icon-ok:         var(--edge-green);

  /* ── Линии ─────────────────────────────────────────────────────────────────
     Разделители внутри карточек и тонкие «|» между значениями — steel-300. */
  --edge-line: var(--edge-steel-300);

  /* ── Тень карточек ────────────────────────────────────────────────────────
     Нужна там, где карточка лежит не на ровном фоне, а на фотографии (экран
     телека, узел 915:2834): без тени светлая плашка сливается со светлыми
     местами кадра. Цвет — тот же steel-900 в 8%, а не отдельный серый. */
  --edge-shadow-card: 0 4px 16px color-mix(in srgb, var(--edge-steel-900) 8%, transparent);

  /* ── Цвета сигналов ────────────────────────────────────────────────────────
     Три значения вне шкалы Steel, заданы Даниилом 17.08. Базовый слой — сами
     цвета, ниже смысловые имена: разметка берёт смысл, а не «фиолетовый». */
  --edge-violet: #B06BFF;
  --edge-red:    #FF5D6C;
  --edge-green:  #2ED573;
  /* Жёлтый добавлен 26.08 четвёртым. Он не подобран на глаз: в OKLCH три первых
     стоят на H 302 / 18 / 152, и 85 — ровно середина между красным и зелёным
     (шаги 66.5 и 66.7), так что светофорная тройка встаёт равными долями, а
     фиолетовый остаётся особняком — он про рекорды, а не про состояние.

     Насыщенность ниже остальных (C .164 против .19–.21) не по недосмотру: жёлтого
     такой яркости в sRGB нет вовсе — он существует там только на светлоте .87 и
     выше, где на steel-100 его уже не видно. Взят максимум, доступный на H 85.

     Светлота .800 выбрана так, чтобы он звучал вровень с зелёным: контраст к
     steel-100 1.80 против 1.83 — рядом на карточке ни один не перетягивает взгляд.

     ⚠️ Смысла у него пока нет намеренно: применение придёт следующим макетом. */
  --edge-yellow: #EEB300;

  /* Времена в телеметрии, та же логика, что в GT7 и в формуле: фиолетовый — лучшее
     время, зелёный — личный рекорд или улучшение, красный — медленнее. */
  --edge-time-best:     var(--edge-violet);
  --edge-time-personal: var(--edge-green);
  --edge-time-slower:   var(--edge-red);

  /* «Выбрать» в карточке станции (узел 867:3577) носит тот же фиолетовый. Это
     применение, а не значение, поэтому имя у него своё. */
  --edge-action: var(--edge-violet);

  /* ── Шрифт ─────────────────────────────────────────────────────────────────
     Системные — запасные: пока PP Object Sans не подгрузился, страница должна
     остаться читаемой, а не пустой. */
  --edge-font-sans: "PP Object Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Сколько места занимает прибитое снизу меню: ряд 49 (8 + строка 33 + 8) плюс его
     нижний отступ. Экран отводит на это место сам — меню больше не в потоке. */
  --edge-tabs-space: calc(49px + max(8px, env(safe-area-inset-bottom)));
}

/* ═══ Компоненты ══════════════════════════════════════════════════════════════ */

/* ── Ряд с листанием (.edge-rail) ──────────────────────────────────────────────
   Общая механика всего, что листается по горизонтали. Меню и карточки станций
   отличаются только содержимым, а ведут себя одинаково: прокрутка без ползунка,
   фейд по краям, доводка активного до середины (edge-tabs.js цепляется сюда же).

   Фейд в макете не нарисован, задан Даниилом 17.08: он показывает, что ряд листается.
   Ширину задаёт --edge-rail-fade, стороны включает скрипт — только там, где реально
   есть скрытое. Без скрипта фейда просто нет, ряд рабочий. */
@property --edge-rail-fade-l { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --edge-rail-fade-r { syntax: "<length>"; inherits: false; initial-value: 0px; }

.edge-rail {
  --edge-rail-fade: 24px;

  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;                 /* ползунка в макете нет */
  -webkit-overflow-scrolling: touch;

  -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 var(--edge-rail-fade-l),
      #000 calc(100% - var(--edge-rail-fade-r)), transparent 100%);
          mask-image: linear-gradient(to right,
      transparent 0, #000 var(--edge-rail-fade-l),
      #000 calc(100% - var(--edge-rail-fade-r)), transparent 100%);
  transition: --edge-rail-fade-l .18s linear, --edge-rail-fade-r .18s linear;
}

.edge-rail::-webkit-scrollbar { display: none; }

/* Атрибут hidden против авторского display бессилен: правило «.edge-segments {
   display: flex }» перебивает браузерное «display: none». Поэтому скрытие
   объявляем сами — иначе спрятанное остаётся на экране. */
.edge-rail[hidden],
.edge-segments[hidden],
.edge-stepper[hidden] { display: none; }

/* ── Слайдер-меню (макет EDGE-MS, узел 848:3351) ───────────────────────────────
   Листается по горизонтали, активный пункт тёмный, остальные спокойные.
   Числа — из макета: gap 16, паддинг 8, кегль 24, межстрочный normal.

   Перенесено вниз 17.08 — под палец. В макете ряд стоит на y=809 при высоте 49,
   то есть в 16 от нижнего края. Отступ берём через max() с safe-area: на айфоне
   снизу живёт жестовая полоса, и 16 по макету завели бы меню прямо под неё.

   Два слоя, и это не украшательство. Меню прибито, значит содержимое экрана
   проезжает под ним и подложка обязана его закрывать. Но фейд по краям сделан
   маской, а маска гасит элемент целиком — вместе с его фоном. Поэтому подложка
   (.edge-tabs) и ряд (.edge-tabs__row) разделены: закрывает собой первая,
   затухает второй. */
.edge-tabs {
  --edge-tabs-blur: 14px;                /* блюр у самого низа */
  --edge-tabs-blur-tail: 24px;           /* насколько размытие продолжается выше подложки */

  /* Прибито к низу экрана, но НЕ через fixed. В iOS Safari с нижней адресной строкой
     fixed-элемент считается от layout viewport, а видимая область меньше на высоту
     панели — низ уезжает под неё, и меню исчезает целиком. Поэтому позиция абсолютная
     внутри экрана, а сам экран обязан быть ровно в высоту окна и не прокручиваться
     (прокручивается содержимое внутри него). Экран для этого держит position: relative
     и оставляет снизу --edge-tabs-space. */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  padding-bottom: max(8px, env(safe-area-inset-bottom));

  /* Вуаль вместо сплошной заливки: у низа почти плотная, к верху сходит в ноль.
     Первая строка — запасная для браузеров без color-mix: там подложка просто
     останется непрозрачной. */
  background: var(--edge-bg-page);
  background: linear-gradient(to top,
      color-mix(in oklab, var(--edge-bg-page) 88%, transparent) 0%,
      color-mix(in oklab, var(--edge-bg-page) 55%, transparent) 60%,
      transparent 100%);
}

/* Прогрессивный блюр. backdrop-filter не умеет затухать по градиенту, поэтому
   прогрессия набирается двумя слоями: сильный держит низ и гаснет к середине,
   слабый подхватывает и сходит в ноль. Оба выпущены выше подложки на
   --edge-tabs-blur-tail — иначе размытие обрывалось бы по её кромке ровной линией. */
.edge-tabs::before,
.edge-tabs::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(-1 * var(--edge-tabs-blur-tail));
  bottom: 0;
  pointer-events: none;
}

.edge-tabs::before {
  -webkit-backdrop-filter: blur(var(--edge-tabs-blur));
          backdrop-filter: blur(var(--edge-tabs-blur));
  -webkit-mask-image: linear-gradient(to top, #000 0, #000 40%, transparent 72%);
          mask-image: linear-gradient(to top, #000 0, #000 40%, transparent 72%);
}

.edge-tabs::after {
  -webkit-backdrop-filter: blur(calc(var(--edge-tabs-blur) / 3));
          backdrop-filter: blur(calc(var(--edge-tabs-blur) / 3));
  -webkit-mask-image: linear-gradient(to top, transparent 30%, #000 62%, transparent 100%);
          mask-image: linear-gradient(to top, transparent 30%, #000 62%, transparent 100%);
}

.edge-tabs__row {
  position: relative;                    /* пункты поверх блюр-слоёв */
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;                     /* сетка экрана — 16 от краёв */
}

.edge-tabs__item {
  flex: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  touch-action: manipulation;          /* без этого двойной тап зумит страницу */
  -webkit-touch-callout: none;         /* долгое нажатие иначе зовёт системное меню */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  font-family: var(--edge-font-sans);
  font-size: 24px;
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;   /* перечёркнутый ноль и дроби — из макета */
  color: var(--edge-text-muted);
  transition: color .18s linear;
}

/* Активный — разметкой, а не отдельным классом: состояние и так надо объявить
   для скринридера, второй источник правды ни к чему. */
.edge-tabs__item[aria-current] { color: var(--edge-text-strong); }

/* ── Заголовок экрана (макет EDGE-MS, узел 875:3878) ────────────────────────────
   24px, тёмный. Переноса в макете нет, но и не запрещаю его: на экранах уже 375
   текст в одну строку впритык, а на 320 просто вылез бы за края. */
.edge-screen-title {
  margin: 0;
  text-align: center;

  font-family: var(--edge-font-sans);
  font-size: 24px;
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
  color: var(--edge-text-strong);
}

/* ── Карточки выбора станции (макет EDGE-MS, узел 867:3569) ─────────────────────
   Переделаны 17.08: горизонтального листания больше нет, карточки идут стопкой во
   всю ширину. Значит ни .edge-rail, ни фейда, ни доводки здесь — обычный поток.

   Числа из макета: стопка с зазором 8 и отступом 16 от краёв экрана (370 при 402),
   карточка — радиус 24, паддинг 16, тень 0/4/16 от steel-900 на 8%, внутренний
   зазор 8. Крупное — 32, характеристики и техника — 12. */
.edge-cards {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 16px;
}

.edge-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  border-radius: 24px;
  background: var(--edge-steel-100);
  box-shadow: 0 4px 16px rgba(33, 37, 41, .08);
  overflow: clip;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

.edge-card__top {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Шапка и подвал держат одну раскладку: слева своё, справа своё, крупным кеглем. */
.edge-card__head,
.edge-card__foot {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  font-size: 32px;
  white-space: nowrap;
}

.edge-card__title { color: var(--edge-text-strong); }

/* Значений справа может быть несколько — у «Моушн» это «15 Н•м | 4DOF». */
.edge-card__meta {
  display: flex;
  gap: 4px;
  color: var(--edge-text-secondary);
}

.edge-card__specs {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 12px;
  color: var(--edge-text-secondary);
}

/* Разделители в макете отданы картинками, но это линия 0.4px цветом steel-300
   шириной ровно в контентную часть карточки. Границей она точнее и не тянет за
   собой ассет на каждую строку. */
.edge-card__specs li + li {
  margin-top: 4px;
  padding-top: 4px;
  border-top: .4px solid var(--edge-line);
}

/* Строка техники: PS5 · 4K · 120 Гц через тонкие вертикальные черты. */
.edge-card__tech {
  display: flex;
  gap: 4px;
}

/* Черта набрана Thin — своим семейством, см. @font-face наверху. */
.edge-card__sep {
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
  font-weight: 400;
  color: var(--edge-line);
}

/* «Выбрать» — единственное действие карточки, поэтому именно кнопка. У занятой
   станции это не действие, а состояние: тот же слот, но текстом и без нажатия. */
.edge-card__action {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--edge-action);
  cursor: pointer;
  touch-action: manipulation;          /* без этого двойной тап зумит страницу */
  -webkit-touch-callout: none;         /* долгое нажатие иначе зовёт системное меню */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* Станцию нельзя взять. Причин у этого несколько — занята, не в сети, класс не
   подтверждён (EDGE-168), — но гостю показывается ОДНА: «Занята» (решение 435 от
   19.08). Отдельных слов и отдельного вида для остальных причин нет намеренно:
   гостю всё равно, почему именно сейчас нельзя, а разбираться с этим у стойки. */
.edge-card__action--off {
  color: var(--edge-text-faint);
  cursor: default;
}

.edge-card__price { color: var(--edge-text-faint); }

/* ── Шторка выбора времени и оплаты (макет EDGE-MS, узлы 868:3734, 868:3737) ────
   Сделана на <dialog>: модальность, Esc и возврат фокуса на кнопку, с которой её
   открыли, браузер берёт на себя — руками это повторяется плохо.

   Числа из макета: углы 40 сверху, паддинг 16/16/40, зазор между блоками 24,
   тень вверх 0/-4/16 от steel-900 на 8%. Подложка — steel-100 на 8% и блюр 8:
   экран не затемняется, а выцветает. */
.edge-sheet {
  width: 100%;
  max-width: 100%;
  max-height: 100dvh;
  margin: auto auto 0;                   /* прижата к низу экрана */
  box-sizing: border-box;
  padding: 16px 16px 24px;
  border: 0;
  border-radius: 40px 40px 0 0;
  background: var(--edge-steel-100);
  box-shadow: 0 -4px 16px rgba(33, 37, 41, .08);

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

.edge-sheet[open] {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}

.edge-sheet::backdrop {
  background: color-mix(in oklab, var(--edge-steel-100) 8%, transparent);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
}

@keyframes edge-sheet-in   { from { transform: translateY(100%); } }
@keyframes edge-sheet-out  { to   { transform: translateY(100%); } }
@keyframes edge-veil-in    { from { opacity: 0; } }
@keyframes edge-veil-out   { to   { opacity: 0; } }

.edge-sheet[open]            { animation: edge-sheet-in .28s cubic-bezier(.2, .7, .3, 1); }
.edge-sheet[open]::backdrop  { animation: edge-veil-in  .28s linear; }
.edge-sheet[data-closing]           { animation: edge-sheet-out .2s cubic-bezier(.4, 0, .8, .4) forwards; }
.edge-sheet[data-closing]::backdrop { animation: edge-veil-out  .2s linear forwards; }

@media (prefers-reduced-motion: reduce) {
  .edge-sheet[open],
  .edge-sheet[open]::backdrop,
  .edge-sheet[data-closing],
  .edge-sheet[data-closing]::backdrop { animation-duration: .01s; }
}

/* Голова шторки (название и пояснение) и блок времени идут одной пачкой с зазором
   16, а до кнопки оплаты остаётся общий зазор шторки в 24 — так в макете разведены
   «о чём речь» и «что нажать». */
.edge-sheet__body,
.edge-sheet__head,
.edge-sheet__group {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.edge-sheet__body  { gap: 16px; }
.edge-sheet__head  { gap: 4px; }
.edge-sheet__group { gap: 4px; }

/* В поминутном режиме подпись отстоит на 8, а не на 4 — так в макете (868:3853):
   кнопки на 40 выше пилюли, и прежний зазор к ним прижимался. */
.edge-duration:has(.edge-stepper:not([hidden])) { gap: 8px; }

/* Заголовок и пояснение одинаковые в шторке и в нижней панели (узлы 868:3739 и
   915:2777) — правило общее, чтобы кегль правился в одном месте. */
.edge-sheet__title,
.edge-panel__title {
  margin: 0;
  font-size: 32px;
  font-weight: 400;
  color: var(--edge-text-strong);
  white-space: nowrap;
}

.edge-sheet__note,
.edge-panel__note {
  margin: 0;
  font-size: 12px;
  text-align: center;
  color: var(--edge-text-secondary);
}

/* ── Нижняя панель (макет EDGE-MS, узел 915:2775) ───────────────────────────────
   Светлая карточка у нижнего края экрана — то же место и та же начинка, что у
   шторки, но панель никуда не открывается и не закрывается: она часть экрана и
   видна всё время. На экране заезда в ней продлевают аренду.

   Отступ снизу в макете 8, но на телефоне под ним живёт системный индикатор,
   поэтому берётся большее из двух — иначе кнопка оказывается под ним. */
.edge-panel {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  width: 386px;
  max-width: calc(100% - 16px);
  margin: 0 auto max(8px, env(safe-area-inset-bottom));
  padding: 16px 8px;
  border-radius: 24px;
  background: var(--edge-steel-100);

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* Заголовок с выбором идут одной пачкой (зазор 16), а до кнопки остаётся общий
   зазор панели в 24 — та же разводка «о чём речь» и «что нажать», что в шторке. */
.edge-panel__body,
.edge-panel__group {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.edge-panel__body  { gap: 16px; }
.edge-panel__group { gap: 8px; }


/* ── Переключатель длительности (узел 868:3752) ─────────────────────────────────
   Ряд сделан листающимся: пять значений при кегле 24 занимают почти всю ширину
   402 и на экранах поуже просто не поместятся. Пока помещаются — фейда нет, ряд
   выглядит обычным. */
.edge-segments {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  width: 100%;
}

.edge-segments__item {
  flex: none;
  margin: 0;
  padding: 4px 12px;
  border: 0;
  border-radius: 12px;
  background: none;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;          /* без этого двойной тап зумит страницу */
  -webkit-touch-callout: none;         /* долгое нажатие иначе зовёт системное меню */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  font: inherit;
  font-size: 24px;
  color: var(--edge-text-strong);
  transition: background-color .18s linear, color .18s linear;
}

/* Пакет, который не помещается до закрытия: виден, но не выбирается. Гасим цветом,
   а не прячем — исчезнувшая кнопка увела бы ряд под пальцем в момент нажатия. */
.edge-segments__item:disabled {
  color: var(--edge-text-faint);
  cursor: default;
}

.edge-segments__item[aria-current] {
  background: var(--edge-steel-900);
  color: var(--edge-steel-100);
  border-radius: 16px;                   /* у выбранного скругление крупнее — из макета */
}

/* Единица подписывается только к выбранному значению: в макете активное — «30 мин»,
   остальные просто числа. Единицу несёт атрибут, а не правило, потому что она есть
   не у всех: «Свой» подписывать нечем. Пробел неразрывный — иначе на узком экране
   «мин» может оторваться от числа. */
.edge-segments__item[aria-current][data-unit]::after {
  content: "\00A0" attr(data-unit);
}

/* ── Поминутный выбор (макет EDGE-MS, узел 868:3855) ────────────────────────────
   Второй режим того же выбора длительности: включается по «Свой» и заменяет собой
   ряд готовых значений. Значение — та же тёмная пилюля, что и выбранный пресет,
   слева и справа кнопки на 40, зазор 4.

   Кнопка возврата к готовым значениям стоит отдельно, у левого края — в макете она
   выведена из ряда абсолютной позицией, чтобы «минус — значение — плюс» осталось
   ровно по центру, а не съехало из-за четвёртого элемента. */
.edge-stepper {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
}

.edge-stepper__btn {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  touch-action: manipulation;          /* без этого двойной тап зумит страницу */
  -webkit-touch-callout: none;         /* долгое нажатие иначе зовёт системное меню */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.edge-stepper__btn img {
  display: block;
  width: 32.5px;
  height: 32.5px;
}

.edge-stepper__btn:disabled {
  opacity: .35;
  cursor: default;
}

.edge-stepper__back {
  position: absolute;
  left: 0;
}

.edge-stepper__value {
  padding: 4px 12px;
  border-radius: 16px;
  background: var(--edge-steel-900);
  color: var(--edge-steel-100);
  font-size: 24px;
  white-space: nowrap;
}

/* ── Кнопка-протягивание (узлы 888:2831, 888:2927, 888:3023, 888:3222) ─────────
   Одно и то же действие в двух местах: оплатить в шторке и начать заезд на экране
   подготовки. Механика одинаковая — тянут светлую пилюлю, — поэтому компонент один,
   а чем он занят, говорит разметка (data-action) и содержимое.

   Отступы выровнены по восьмёрочной сетке 17.08; знак внутри пилюли — 28. */
.edge-pull {
  --edge-pull-handle-min: 76px;         /* 24 + знак 28 + 24 */
  --edge-pull-handle-max: 280px;        /* вся кнопка за вычетом отступов по 4 */

  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  width: 288px;
  height: 60px;
  box-sizing: border-box;
  padding: 4px 16px 4px 4px;
  border: 0;
  border-radius: 48px;
  background: var(--edge-steel-900);
  overflow: clip;
  touch-action: manipulation;          /* без этого двойной тап зумит страницу */
  -webkit-touch-callout: none;         /* долгое нажатие иначе зовёт системное меню */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* Светлая пилюля — то, что тянут. Она не едет ползунком, а РАСТЯГИВАЕТСЯ: левый край
   стоит на месте, правый уходит за пальцем, знак карты держится у правого края.
   Отсюда justify-content: flex-end — в покое знак оказывается ровно по центру пилюли,
   а при растягивании остаётся под пальцем.
   touch-action: none обязателен: иначе жест перехватит прокрутка. */
.edge-pull__handle {
  position: absolute;
  left: 4px;
  top: 4px;
  bottom: 4px;
  width: var(--edge-pull-handle-min);
  z-index: 1;

  display: flex;
  align-items: center;
  justify-content: flex-end;
  box-sizing: border-box;
  margin: 0;
  padding: 0 24px;
  border: 0;                           /* пилюля — <button>, сбрасываем рамку браузера */
  border-radius: 44px;
  background: var(--edge-steel-100);

  touch-action: none;
  cursor: grab;
  transition: width .3s cubic-bezier(.2, .7, .3, 1), left .3s cubic-bezier(.2, .7, .3, 1);
}

.edge-pull__handle img {
  display: block;
  width: 28px;
  height: 28px;
  pointer-events: none;
}

/* Знак, у которого вектор меньше своего бокса (флаг — 21×21.87 в боксе 28×28 и
   внутри него стоит не по центру). Держим обе геометрии раздельно, как в макете,
   а не растягиваем картинку на весь бокс — иначе поедут пропорции. */
.edge-pull__glyph {
  position: relative;
  display: block;
  flex: none;
  width: 28px;
  height: 28px;
  pointer-events: none;
}

.edge-pull__glyph img {
  position: absolute;
  inset: 12.51% 9.38% 9.37% 15.63%;
  display: block;
  width: auto;
  height: auto;
}

/* Пока тянут — без анимации: пилюля должна идти ровно за пальцем, без догоняния. */
.edge-pull[data-dragging] .edge-pull__handle {
  transition: none;
  cursor: grabbing;
}

/* Оплата пошла: пилюля схлопывается до исходной ширины, но уже у правого края —
   правый край остаётся на месте, левый догоняет, — и освобождает место под надпись. */
/* Справа резервируем место под саму пилюлю: она позиционирована абсолютно и в
   раскладке не участвует, поэтому без этого надпись центровалась бы по всей кнопке,
   а не по свободной от пилюли части (в макете её центр — 106 из 288). */
.edge-pull[data-state="done"] {
  padding-right: calc(4px + var(--edge-pull-handle-min));
}

.edge-pull[data-state="done"] .edge-pull__handle {
  left: calc(100% - var(--edge-pull-handle-min) - 4px);
  width: var(--edge-pull-handle-min);
  cursor: default;
}

.edge-pull__status {
  display: none;
  flex: 1;
  text-align: center;
  font-size: 20px;
  color: var(--edge-steel-600);
}

.edge-pull[data-state="done"] .edge-pull__info   { display: none; }
.edge-pull[data-state="done"] .edge-pull__status { display: block; }

/* Справа от пилюли: у оплаты это «30 мин | 700 ₽», у старта — одно слово «Начать».
   Размер и цвет заданы здесь, чтобы простой текст выглядел как в макете; составной
   переопределяет их своими частями. */
.edge-pull__info {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-size: 20px;
  color: var(--edge-steel-100);
}

.edge-pull__time  { font-size: 20px; color: var(--edge-steel-600); }
.edge-pull__price { font-size: 20px; color: var(--edge-steel-100); }

.edge-pull__sep {
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
  font-size: 24px;
  font-weight: 400;
  color: var(--edge-steel-800);
}


/* ── Отсчёт (макет EDGE-MS, узлы 888:3242 и 915:2794) ───────────────────────────
   Крупное число и пояснение под ним. Два применения одного компонента: секунды до
   старта на экране подготовки и остаток аренды на экране заезда. Отличаются кеглем
   и тем, как подпись подтянута вверх, — обе величины из макета и вынесены в
   переменные, чтобы модификатор менял их, а не переписывал правила.

   Отрицательный отступ снизу — тоже из макета: у крупного кегля собственный
   межстрочный интервал уводит подпись слишком низко. */
.edge-countdown {
  --edge-countdown-size: 160px;
  --edge-countdown-lift: 24px;

  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 100%;
  text-align: center;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* Число не переносится НИКОГДА. 320 из макета — ширина подписи, а не числа: цифры
   стоят в ячейках по 0.7em, и при кегле 160 три из них дают 336 — в 320 они не
   влезали, и «115» вставало как «11» и «5» одно под другим. */
.edge-countdown__value {
  margin-bottom: calc(-1 * var(--edge-countdown-lift));
  font-size: var(--edge-countdown-size);
  color: var(--edge-text-strong);
  white-space: nowrap;
}

.edge-countdown__note {
  margin: 0;
  width: 320px;                        /* из макета: 888:3119 */
  max-width: 100%;
  font-size: 12px;
  color: var(--edge-text-secondary);
}

/* Часы:минуты:секунды — остаток аренды на экране заезда (узел 915:2794). */
.edge-countdown--clock {
  --edge-countdown-size: 80px;
  --edge-countdown-lift: 8px;
  width: auto;
}

/* Цифры на сетке. В PP Object Sans цифры пропорциональные и разброс огромный:
   «1» — 398 единиц, «0» — 728. Живой посекундный отсчёт от этого дёргался бы
   на десятки пикселей при каждой смене знака, а табличных цифр в шрифте нет
   (в ss02 на месте цифр лежат стрелки). Поэтому каждая цифра ставится в ячейку
   одной ширины, а двоеточия остаются обычным текстом — они не меняются.

   0.7em — не на глаз: самое широкое чёрное у цифр 605 единиц, в ячейку оно
   входит с зазором ~95, а средний просвет между цифрами в этом шрифте ~100.
   То есть ритм строки остаётся шрифтовым, ничего не слипается и не разрежается.
   Плата — строка шире макетной: равная ячейка не может быть уже самого широкого
   знака (313 против 274 при кегле 80). */
.edge-countdown__cell {
  display: inline-block;
  width: .7em;
}

/* ── Подсказки (макет EDGE-MS, узлы 895:3318, 895:3319) ─────────────────────────
   Лента слайдов: листается сама, но её можно и пролистать пальцем. Свайп сделан
   не перетаскиванием, а нативной прокруткой со scroll-snap — так жест ощущается
   системным, работает инерция и ничего не нужно ловить руками; автолистание в
   edge-tips.js просто прокручивает ленту к следующему слайду.

   Порядок слайдов перемешивается при открытии экрана, поэтому номер в подписи —
   это номер самого совета, а не позиция в ленте: они идут вразнобой намеренно. */
.edge-tips {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.edge-tips::-webkit-scrollbar { display: none; }

.edge-tip {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;                             /* из макета: 17 подписи + 24 = арт на 41 */

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
  color: var(--edge-text-secondary);
  font-size: 12px;
  text-align: center;
}

.edge-tip__label { white-space: nowrap; }

.edge-tip__text {
  margin: 0;
  width: 320px;
  max-width: 100%;
}

/* Сцена рисунка. Размеры у советов разные (254×232 и 348×232), поэтому задаются
   разметкой — тут только правила игры для выносок поверх. */
.edge-tip__stage {
  position: relative;
  flex: none;
}

/* Рисунок приходит из макета не картинкой, а МАСКОЙ: PNG задаёт форму, цвет берётся
   из системы. Значит рисунок перекрашивается вместе с палитрой и не тащит за собой
   собственный оттенок. */
.edge-tip__art {
  width: 100%;
  height: 100%;
  background: var(--edge-steel-900);
  opacity: var(--edge-tip-art-opacity, 1);

  -webkit-mask: var(--edge-tip-art) center / contain no-repeat;
          mask: var(--edge-tip-art) center / contain no-repeat;
}

/* Выноска на рисунке: подпись к кнопке руля. Паддинг из макета несимметричный —
   снизу 4, сверху 0: так подпись оптически садится по центру плашки. */
.edge-tip__pin {
  position: absolute;
  padding: 0 12px 4px;
  border-radius: 10px;
  background: var(--edge-steel-900);
  color: var(--edge-steel-100);
  font-size: 16px;
  white-space: nowrap;
}

.edge-tip__ring,
.edge-tip__mark {
  position: absolute;
  display: block;
}


/* ══ ЭКРАН ТЕЛЕКА ═══════════════════════════════════════════════════════════════
   Другой зритель и другое расстояние. Телефон держат в руке, телек смотрят из-за
   руля и от двери клуба — поэтому кегли здесь свои (44 и 24 против 32 и 12 в
   приложении), а не те же самые, увеличенные.

   Экран статичный: он ничего не спрашивает и ни на что не отвечает. Его работа —
   ответить издалека на два вопроса: что это за станция и свободна ли она.
   Управление остаётся на телефоне, поэтому единственный «интерфейс» тут — QR.

   Размеры даны как в макете (1920×1080). Вписывание в другой экран — забота
   страницы, а не компонента: телек показывает 1:1. */

/* ── Сцена (макет EDGE-MS, узел 915:2827) ─────────────────────────────────────
   Экран телека — ОВЕРЛЕЙ поверх игры, а не картинка на своём фоне. Своего фона у
   него нет и быть не должно: под ним идут настоящие кадры с приставки. В макетах
   кадр из игры лежит для наглядности — это подложка Даниила, а не часть экрана,
   и в стендах она тоже подложка, объявленная отдельно. */
.edge-tv {
  position: relative;
  width: 1920px;
  height: 1080px;
  overflow: clip;
  background: transparent;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* Знак стоит по центру макетного бокса (1026, 540), а не по координате угла:
   у наклонного знака рамка в Figma шире самого рисунка, и привязка по углу
   увела бы его вбок. */
.edge-tv__logo {
  position: absolute;
  left: 1026px;
  top: 540px;
  translate: -50% -50%;
  display: block;
  width: 845px;
  height: auto;                        /* пропорции считает браузер — исказить нельзя */

  /* Знак стоит своими пропорциями — тем размером, в котором его выгрузили.
     Растяжения здесь больше нет: оно было следствием СТАРОГО вектора. Прямой знак
     имел пропорцию 2.49, и чтобы занять макетный бокс 866×314 (2.76), его
     приходилось тянуть. Актуальный наклонный — 845×315, то есть 2.68, и по высоте
     он совпадает с макетным боксом почти точно (315 против 314); шире бокс на 21 —
     это поля рамки вокруг наклонного рисунка, а не сам знак. Место занимает то же:
     центр бокса (1026, 540) остался центром знака. */
}

/* Колонка карточек: слева, во всю высоту за вычетом полей. */
.edge-tv__side {
  /* Скругления «пачкой» — свойство МЕСТА плитки в колонке, а не самой плитки:
     та же плитка стоит на телефоне поодиночке и остаётся там круглой на 24.
     Поэтому углы задаёт колонка, а не модификаторы на каждой карточке — иначе
     разметку пришлось бы править всякий раз, когда плиток станет две или четыре. */
  --edge-pack-outer: 40px;             /* внешние углы всей пачки */
  --edge-pack-inner: 16px;             /* углы внутри неё */

  position: absolute;
  left: 32px;
  top: 32px;
  bottom: 32px;
  width: 370px;

  display: flex;
  flex-direction: column;
  gap: 4px;
}

.edge-tv__side > .edge-tile { border-radius: var(--edge-pack-inner); }

.edge-tv__side > .edge-tile:first-child {
  border-top-left-radius: var(--edge-pack-outer);
  border-top-right-radius: var(--edge-pack-outer);
}

.edge-tv__side > .edge-tile:last-child {
  border-bottom-left-radius: var(--edge-pack-outer);
  border-bottom-right-radius: var(--edge-pack-outer);
}

/* Плитка — светлая плашка с тенью. Одна и та же на телефоне (итоги заезда) и на
   телеке (станция, статус, QR): и там, и там она лежит поверх кадра или фона и
   должна от него отделяться. */
.edge-tile {
  box-sizing: border-box;
  padding: 16px;
  border-radius: 24px;
  background: var(--edge-steel-100);
  box-shadow: var(--edge-shadow-card);
  overflow: clip;

  /* Шрифт задаётся здесь, а не наследуется от страницы: плитка живёт и на телефоне,
     и на телеке, и в обоих местах должна выглядеть одинаково без помощи снаружи. */
  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* Плитка, в которой вместо содержимого — одна фраза («итогов нет»). Место в
   колонке она сохраняет: если сжать её по тексту, статус и QR уедут вверх, и
   экран станет выглядеть сломанным сильнее, чем от самого сообщения. Поэтому
   плитка остаётся во всю высоту, а фраза встаёт по центру — так это читается
   как сообщение, а не как список, из которого всё пропало. */
.edge-tile--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* Кегль тот же, что у подписей на телеке: с двери клуба это ещё читается, а
   крупнее делать нечего — новость не радостная, привлекать к ней внимание
   размером не нужно. */
.edge-tile__message {
  margin: 0;
  max-width: 280px;
  font-size: 24px;
  color: var(--edge-text-secondary);
}

/* ── Карточка станции (узел 915:2834) ────────────────────────────────────────
   Занимает всё, что осталось от колонки, и разводит своё содержимое по краям:
   что за станция — вверху, сколько стоит — внизу. */
.edge-tv-rig {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.edge-tv-rig__top,
.edge-tv-rig__specs {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.edge-tv-rig__top   { gap: 8px; }    /* по макету 20.08: Frame 119 стоит на y=69 при голове 61 */
.edge-tv-rig__specs { gap: 4px; }

.edge-tv-rig__head {
  display: flex;
  justify-content: space-between;
  font-size: 44px;
  white-space: nowrap;
}

.edge-tv-rig__title  { color: var(--edge-text-strong); }
.edge-tv-rig__torque { color: var(--edge-text-secondary); }

.edge-tv-rig__spec {
  margin: 0;
  font-size: 20px;                     /* по макету 20.08 (узлы 915:2840, 915:2842, 915:2861) */
  color: var(--edge-text-secondary);
}

.edge-tv-rig__tech {
  display: flex;
  gap: 4px;
  white-space: nowrap;
}

/* «|» на светлом фоне — цвета линий, а не тёмный, как на кнопке оплаты.
   Начертание то же, тонкое; кегль наследуется от строки, в которой стоит. */
.edge-sep {
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
  color: var(--edge-line);
}

.edge-rule {
  width: 100%;
  height: 0;
  margin: 0;
  border: 0;
  border-top: 1px solid var(--edge-line);
}

/* ── Прайс (узел 915:2881) ──────────────────────────────────────────────────
   Минуты слева тёмные, рубли справа спокойные: издалека сначала читается, за
   сколько времени речь, и только потом цена. */
.edge-tv-price {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.edge-tv-price__row {
  display: flex;
  justify-content: space-between;
  font-size: 44px;
  white-space: nowrap;
}

.edge-tv-price__row > :first-child { color: var(--edge-text-strong); }
.edge-tv-price__row > :last-child  { color: var(--edge-text-muted); }

/* ── Статус (узел 915:3088) ─────────────────────────────────────────────────
   Цвет задаётся один раз на карточке, знак и надпись берут его через
   currentColor: сменить состояние — значит сменить один цвет, а не два. */
.edge-tv-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--edge-green);
}

.edge-tv-status--busy { color: var(--edge-red); }

.edge-tv-status__text {
  font-size: 44px;
  white-space: nowrap;
}

/* Знак у флага меньше своего бокса и стоит в нём не по центру — та же геометрия,
   что на кнопке старта. Рисунок подаётся маской, а не картинкой, потому что цвет
   у него меняется вместе с состоянием. */
.edge-tv-status__mark {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
}

.edge-tv-status__mark::before {
  content: "";
  position: absolute;
  inset: 12.51% 9.38% 9.37% 15.63%;
  background: currentColor;

  -webkit-mask: url("/design/icons/flag-fill.svg") center / contain no-repeat;
          mask: url("/design/icons/flag-fill.svg") center / contain no-repeat;
}

/* ── Онлайн-аренда (узел 915:2990) ──────────────────────────────────────────── */
.edge-tv-qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.edge-tv-qr__code {
  display: block;
  width: 124px;
  height: 124px;
}

.edge-tv-qr__label {
  margin: 0;
  font-size: 24px;
  color: var(--edge-text-secondary);
}


/* ── Итоги заезда (макеты EDGE-MS, узлы 915:2934 и 915:3221) ─────────────────
   Показатели заезда: значение и подпись под ним. Один компонент на два экрана —
   телефонный и телечный, — потому что это одни и те же три числа, показанные
   гостю в телефоне и на экране станции. Отличаются только кегли и то, что на
   телеке между показателями стоят линии: расстояние до зрителя разное, смысл — нет. */
.edge-stats {
  --edge-stat-size: 32px;              /* значение */
  --edge-stat-label: 12px;             /* подпись */
  --edge-stat-gap: 0px;                /* между значением и подписью */
  --edge-stat-inner: 8px;              /* между частями значения: «42 круга | 120 км» */

  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* На телеке те же показатели читают из-за руля и от двери, поэтому крупнее. */
.edge-stats--tv {
  --edge-stat-size: 44px;
  --edge-stat-label: 24px;
  --edge-stat-gap: 4px;
  --edge-stat-inner: 4px;

  flex: 1;
  min-height: 0;
}

.edge-stat {
  display: flex;
  flex-direction: column;
  gap: var(--edge-stat-gap);
  width: 100%;
}

.edge-stat__value {
  display: flex;
  gap: var(--edge-stat-inner);
  font-size: var(--edge-stat-size);
  color: var(--edge-text-strong);
  white-space: nowrap;
}

.edge-stat__label {
  margin: 0;
  font-size: var(--edge-stat-label);
  color: var(--edge-text-secondary);
}

/* ── Конец аренды (макет EDGE-MS, узел 915:3185) ─────────────────────────────
   Отсчёт в середине кадра: сколько ещё есть, чтобы встать и забрать вещи. Это
   тот же компонент отсчёта, что на экране подготовки, только очень крупный и
   белым по фотографии — поэтому и знак, и подпись берут цвет с родителя. */
.edge-countdown--tv {
  --edge-countdown-size: 400px;
  --edge-countdown-lift: 46px;

  position: absolute;
  left: 50%;
  top: 264px;
  translate: -50% 0;
  width: auto;
  color: var(--edge-text-on-photo);
}

.edge-countdown--tv .edge-countdown__value { color: currentColor; }

.edge-countdown--tv .edge-countdown__note {
  font-size: 44px;
  color: currentColor;
}

/* ── Плашка «время на исходе» (макет EDGE-MS, узел 915:3289) ─────────────────
   Единственное, что мы позволяем себе показать поверх идущей игры, — и потому она
   маленькая, стоит сверху по центру и уходит сама. Ни кнопок, ни действий: гость
   за рулём, руки заняты, продлевать он будет с телефона.

   Скругление здесь 36, а не 24 как у карточек: плашка ниже их вдвое, и на её
   высоте прежний радиус читался бы как угол. */
.edge-tv-notice {
  position: absolute;
  left: 50%;
  top: 32px;
  translate: -50%;

  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 40px;
  border-radius: 36px;
  background: var(--edge-steel-100);
  box-shadow: var(--edge-shadow-card);

  font-size: 44px;
  white-space: nowrap;
}

.edge-tv-notice__value { color: var(--edge-text-strong); }
.edge-tv-notice__text  { color: var(--edge-text-secondary); }


/* ══ ВХОД ═══════════════════════════════════════════════════════════════════════
   Экран входа существует давно (server/public-client), но нарисован до системы:
   своя палитра, оливковая кнопка, свои кегли. Здесь он пересобран на токенах и
   компонентах — поведение и порядок экранов те же, что в работающем клиенте.

   Кнопки-плашки в системе до сих пор не было: все действия в макетах — либо
   протягивание пилюли, либо текстовая ссылка. Геометрию беру у пилюли, чтобы
   главное действие везде выглядело одинаково. */

.edge-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 288px;
  max-width: 100%;
  height: 60px;
  margin: 0;
  padding: 0 24px;
  border: 0;
  border-radius: 48px;
  background: var(--edge-steel-900);
  color: var(--edge-steel-100);
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  font-size: 20px;
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
  transition: opacity .15s linear;
}

.edge-button:disabled {
  opacity: .35;
  cursor: default;
}

/* Второстепенное действие: то же место, но без плашки — «Отправить ещё раз». */
.edge-button--quiet {
  height: 44px;
  background: none;
  color: var(--edge-text-secondary);
  font-size: 16px;
}

/* Третий уровень — то, что нужно единицам: «Не приходит SMS?». */
.edge-button--link {
  height: 36px;
  background: none;
  color: var(--edge-text-faint);
  font-size: 14px;
}

/* ── Знак ────────────────────────────────────────────────────────────────────
   Подаётся маской, а не картинкой: на светлом экране входа он тёмный, на кадре
   игры — белый, и это один и тот же файл. Цвет берётся с родителя. */
.edge-logo {
  display: block;
  height: var(--edge-logo-height, 28px);
  width: auto;
  aspect-ratio: 845 / 315;             /* размеры знака, ширину считает браузер */
  background: currentColor;

  -webkit-mask: url("/design/brand/edge-logo.svg") center / contain no-repeat;
          mask: url("/design/brand/edge-logo.svg") center / contain no-repeat;
}

/* ── Ввод цифр (номер телефона, код из SMS) ──────────────────────────────────
   Цифры стоят в ячейках одной ширины — на той же сетке, что отсчёты: набирая
   номер, гость не должен видеть, как строка ёрзает под пальцем. Пустые места
   показаны заранее, чтобы было видно, сколько ещё вводить.

   Поле ввода настоящее и лежит поверх — так работают клавиатура, вставка кода
   из SMS и автозаполнение; видно его не будет. */
.edge-digits {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: baseline;

  font-family: var(--edge-font-sans);
  font-weight: 400;
  font-size: var(--edge-digits-size, 32px);
  line-height: normal;
  font-feature-settings: "zero" 1, "frac" 1;
  color: var(--edge-text-strong);
  cursor: text;
}

.edge-digits__prefix { margin-right: .3em; }

.edge-digits__slot {
  position: relative;
  display: inline-block;
  width: .7em;
  text-align: center;
  color: transparent;                     /* пока не набрано — место держит точка */
}

.edge-digits__slot[data-filled] { color: var(--edge-text-strong); }

/* Пустое место — точка, а не ноль: ноль в бледном сером читается как уже набранная
   цифра. Ячейка при этом не меняет ширину — под точкой по-прежнему стоит знак,
   просто невидимый, иначе строка дёргалась бы на каждой набранной цифре. */
.edge-digits__slot:not([data-filled])::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: .16em;
  height: .16em;
  translate: -50% -50%;
  border-radius: 50%;
  background: var(--edge-text-faint);
}

/* Группы номера читаются глазом, а не пересчитываются: +7 999 123 45 67. */
.edge-digits__slot[data-gap] { margin-right: .34em; }

.edge-digits__field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  opacity: 0;
  font-size: 16px;                        /* меньше — и iOS зумит страницу при фокусе */
  color: transparent;
  caret-color: transparent;
}

/* ── Экран входа целиком ─────────────────────────────────────────────────────
   Знак и ввод сверху, действия внизу под пальцем — та же раскладка, что у
   остальных экранов телефона. */
.edge-auth {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100%;
  min-height: 100dvh;
  box-sizing: border-box;
  padding: 48px 24px max(16px, env(safe-area-inset-bottom));

  font-family: var(--edge-font-sans);
  font-weight: 400;
  line-height: normal;
}

.edge-auth__top {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.edge-auth__foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-top: auto;
  padding-top: 24px;
}

.edge-auth__hint {
  margin: 14px 0 0;
  max-width: 320px;
  text-align: center;
  font-size: 12px;
  color: var(--edge-text-secondary);
}

/* Ошибка ввода — тем же спокойным тоном: на экране входа краснота пугает больше,
   чем помогает, а других красных сообщений в системе нет. */
.edge-auth__msg {
  margin: 12px 0 0;
  min-height: 17px;
  text-align: center;
  font-size: 12px;
  color: var(--edge-text-secondary);
}

.edge-auth__legal {
  margin: 13px 6px 0;
  max-width: 320px;
  text-align: center;
  font-size: 11px;
  color: var(--edge-text-faint);
}


/* ══ ПРОФИЛЬ ════════════════════════════════════════════════════════════════════
   Экран гостя: кто он и сколько накатал. Пока пустой — всё, что есть, это имя,
   снимок и счётчик на нулях (макет EDGE-MS, узел 958:3503). */

/* ── Снимок ────────────────────────────────────────────────────────────────── */
.edge-avatar {
  display: block;
  width: var(--edge-avatar-size, 80px);
  height: var(--edge-avatar-size, 80px);
  border-radius: 50%;
  object-fit: cover;                   /* кадрирует, а не сплющивает */
  background: var(--edge-steel-300);   /* видно место, пока снимка нет */
}

/* ── Заголовок раздела внутри экрана (узел 958:3653) ─────────────────────────
   Крупнее заголовка экрана (24) — потому что это не подпись сверху, а начало
   отдельного блока, под которым идёт свой список. Переносится: «История заездов»
   коротко, но название раздела может оказаться длиннее, и обрезать его нельзя. */
.edge-section-title {
  margin: 0;
  font-family: var(--edge-font-sans);
  font-weight: 400;
  font-size: 32px;
  line-height: normal;
  color: var(--edge-text-strong);
}

/* ── Когда это было (узел 958:3695) ─────────────────────────────────────────
   Дата и время заезда в подвале карточки истории. Цвет самый спокойный из
   текстовых: это не показатель, а метка — по ней находят нужный заезд, но
   читают её последней. */
.edge-when {
  margin: 0;                           /* это <p>: без сброса браузер добавит свои отступы */
  display: flex;
  gap: 4px;
  align-items: baseline;
  font-size: 12px;
  color: var(--edge-text-faint);
}

/* ── Счётчик-барабан (узел 958:3599) ────────────────────────────────────────
   Девять цифр, разбитых по три, — как на одометре. Каждая цифра живёт в своей
   тёмной плашке и прокручивается, а не подменяется: смена числа должна читаться
   как «накрутил», иначе счётчик ничем не отличается от обычного текста.

   Ведущие нули приглушены (steel-700), значащие цифры светлые (steel-100) — так
   в макете и так на настоящих одометрах: ноль, который ещё ничего не значит,
   не должен спорить с цифрами, которые уже значат. */
.edge-odometer {
  /* Шаг ленты = высота строки цифры. Отсюда же берётся высота плашки: 31 + 4 + 4
     даёт макетные 39. В макете соседние цифры барабана нарисованы с шагом 29, но
     это застывший кадр «на глаз»: строка при кегле 24 занимает 31, и на меньшем
     шаге цифры наезжали бы друг на друга при прокрутке. */
  --edge-odometer-step: 31px;

  display: flex;
  gap: 8px;
  align-items: center;

  font-family: "PP Object Sans Medium", var(--edge-font-sans);
  font-weight: 400;
  font-size: 24px;
  line-height: var(--edge-odometer-step);
  font-feature-settings: "zero" 1, "frac" 1;
}

.edge-odometer__group {
  display: flex;
  gap: 4px;
  align-items: center;
}

/* Ширина плашки постоянная и считается по самой широкой цифре — по нулю. В макете
   у части плашек она меньше (27 против 30), но это следствие пропорциональных
   цифр в застывшем кадре: у живого барабана от этого прыгала бы вся строка при
   каждом обороте. Та же причина, по которой цифры отсчётов стоят на сетке. */
.edge-odometer__cell {
  position: relative;
  overflow: clip;
  width: 30px;                         /* 6 + цифра 18 + 6 — по эталону 966:3757 */
  height: var(--edge-odometer-step);
  padding: 4px 0 3px;                  /* сверху 4, снизу 3: плашка 38, а не 39 */
  border-radius: 8px;
  background: var(--edge-steel-900);
  color: var(--edge-steel-100);
  text-align: center;
}

/* Ведущий ноль — ещё не число, а место под него. */
.edge-odometer__cell[data-idle] { color: var(--edge-steel-700); }

.edge-odometer__reel {
  display: block;
  transition: transform .6s cubic-bezier(.2, .7, .3, 1);
}

.edge-odometer__reel > span {
  display: block;
  height: var(--edge-odometer-step);
}

/* Просьбу уменьшить движение уважаем: барабан перестаёт крутиться, цифра просто
   встаёт на место. Счётчик от этого не ломается — он и так показывает число. */
@media (prefers-reduced-motion: reduce) {
  .edge-odometer__reel { transition: none; }
}

/* ─── Карточка станции в панели оператора ─────────────────────────────────────
   Узел макета 979:2980. Колонка шириной 200: столько занимает станция в ряду,
   и в этой ширине помещается MAC — самая длинная строка карточки.

   Устройство карточки — ПАЧКИ. Плашки одного смысла (устройство и его адреса,
   консоль с пультом) лежат вплотную через 1px и скругляются как одно тело:
   16 по внешним углам, 4 по внутренним. Между пачками 8. Поэтому глазу не нужны
   заголовки — границу смысла рисует сама геометрия.

   Радиус задаётся ПО МЕСТУ В ПАЧКЕ (:first-child/:last-child/:only-child), а не
   классом на каждой плашке: иначе порядок строк нельзя менять разметкой — пришлось
   бы каждый раз переставлять и классы, а это ровно тот вид расхождения, который
   замечают последним. */

.edge-station {
  box-sizing: border-box;
  width: 320px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--edge-font-sans);
  font-weight: 400;
  /* Высота строки задана числом, а не `normal`: у `normal` её решает файл шрифта, и
     пока он не подгрузился, плашки считаются по запасному — ряд станций собирается
     на одних числах, а потом переезжает на другие. Числа взяты из макета: 12→17. */
  font-size: 16px;
  line-height: 22px;
  font-variant-numeric: slashed-zero;
  font-feature-settings: "zero" 1, "frac" 1;
}

/* ─── Ряд станций ─────────────────────────────────────────────────────────────
   Станции читают не по одной, а поперёк: «у кого нет сети», «у кого не заведён
   класс». Поэтому ряд — таблица: одноимённые блоки соседних карточек стоят на
   одном уровне, даже когда содержимое разной высоты (у одной станции
   идентификатор в две строки, у другой прочерк).

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

   Число строк названо числом, потому что оно и есть договор о составе карточки:
   станция, телевизор, игровой узел (консоль с пультом и рулём), платформа.
   Появится блок аренды — меняется здесь, в одном месте. */
.edge-rack {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  --edge-rack-rows: 4;
}

@supports (grid-template-rows: subgrid) {
  .edge-rack {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 320px;
    grid-template-rows: repeat(var(--edge-rack-rows), auto);
  }

  .edge-rack > .edge-station {
    display: grid;
    grid-row: 1 / -1;
    grid-template-rows: subgrid;
    width: auto;
  }

  /* Блок садится в строку ПО РОЛИ, а не по очереди. Без этого у свободной станции
     шапка занимала бы строку аренды, и ряд переставал быть таблицей ровно там, где
     она нужнее всего: когда часть станций занята, а часть нет.
     Роль проставляется только там, где состав карточек разный; где он одинаков,
     хватает обычного порядка, и разметка остаётся без лишних атрибутов. */
  .edge-rack > .edge-station > [data-row="rent"]     { grid-row: 1; }
  .edge-rack > .edge-station > [data-row="head"]     { grid-row: 2; }
  .edge-rack > .edge-station > [data-row="tv"]       { grid-row: 3; }
  .edge-rack > .edge-station > [data-row="game"]     { grid-row: 4; }
  .edge-rack > .edge-station > [data-row="platform"] { grid-row: 5; }
}

.edge-station__pack {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.edge-station__row {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  border-radius: 12px;
  background: var(--edge-steel-100);
}

/* Радиус говорит, насколько близко соседняя плашка: 20 по краям всей карточки,
   12 на стыках блоков (зазор 4), 4 внутри пачки (зазор 2). Чем теснее стык, тем
   мельче угол — карточка от этого читается как одно тело с внутренним делением,
   а не как стопка одинаковых плиток. */
.edge-station__pack > .edge-station__row              { border-radius: 4px; }
.edge-station__pack > .edge-station__row:first-child { border-radius: 12px 12px 4px 4px; }
.edge-station__pack > .edge-station__row:last-child  { border-radius: 4px 4px 12px 12px; }
.edge-station__pack > .edge-station__row:only-child  { border-radius: 12px; }

/* Внешние углы карточки. Первым и последним блоком может быть и одиночная плашка,
   и пачка, поэтому правило написано на оба случая — иначе угол терялся бы ровно
   тогда, когда состав карточки меняется (у станции без платформы последний блок
   другой). */
.edge-station > .edge-station__row:first-child,
.edge-station > .edge-station__pack:first-child > .edge-station__row:first-child {
  border-top-left-radius: 32px;
  border-top-right-radius: 32px;
}

.edge-station > .edge-station__row:last-child,
.edge-station > .edge-station__pack:last-child > .edge-station__row:last-child {
  border-bottom-left-radius: 32px;
  border-bottom-right-radius: 32px;
}

/* Внутри карточки все абзацы свои поля отдают вёрстке: браузерный margin у <p>
   уже разъезжался на карточке истории (+22px), второй раз наступать незачем. */
.edge-station p { margin: 0; }

/* ── Слово состояния ──────────────────────────────────────────────────────────
   Единственная крупная строка карточки. Она отвечает на вопрос, с которым к
   станции подходят: что с ней сейчас. */
.edge-station__state {
  font-size: 32px;
  line-height: 44px;
  color: var(--edge-text-panel);
}

/* ─── Аренда ──────────────────────────────────────────────────────────────────
   Блок идущего заезда: кто едет, сколько осталось, за что заплачено, чем занят
   сейчас. Стоит ПЕРВЫМ в карточке — это ответ на вопрос, с которым к стойке
   подходят чаще всего («сколько ещё?»), и искать его под перечнем железа значит
   заставлять оператора листать глазами в разговоре с гостем.

   Собран из узла 983:4151 и переложен на нынешние правила карточки: поля 8/12,
   радиусы пачки, набор 12/17 и 20/28. */

.edge-station__guest {
  flex-direction: column;
  align-items: flex-start;
  gap: 0;                    /* имя и телефон — одно сведение о человеке */
}

.edge-station__guest b { font-weight: 400; color: var(--edge-text-panel); }
.edge-station__guest span { color: var(--edge-text-panel-dim); }

.edge-station__rent {
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

/* Первая строка аренды: слева что идёт, справа сколько осталось. */
.edge-station__rentline {
  display: flex;
  justify-content: space-between;
  width: 100%;
  color: var(--edge-text-panel);
}

/* Окно аренды и оплата — вторым планом: их читают, когда уже ответили на
   главный вопрос. Обе строки набраны одинаково, поэтому и правило одно. */
.edge-station__window,
.edge-station__pay {
  display: flex;
  gap: 6px;
  color: var(--edge-text-panel-dim);
}

.edge-station__window i,
.edge-station__pay i {
  font-style: normal;
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
}

/* Чем занят гость прямо сейчас: на трассе, в меню, между заездами. Строка своя,
   а не приписка к аренде: аренда идёт по часам, а это меняется каждую минуту. */
.edge-station__mode { color: var(--edge-text-panel); }

/* Плашка паузы. Кнопка по центру, и под ней — место под причину.

   ⚠️ Причина ЗАЛОЖЕНА, но не нарисована: вид за макетом. Важно здесь одно —
   плашка растёт вниз, а не обрезает текст. Причина это свободная фраза («ждём
   решения по возврату», «сломалась платформа»), и длинные как раз объясняют,
   почему время встало; посади их в строку фиксированной высоты — и пропадут
   именно они. */
.edge-station__pauserow {
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.edge-station__reason {
  width: 100%;
  white-space: normal;
  text-align: center;
  color: var(--edge-text-panel-dim);
}

/* ── Шапка станции ─────────────────────────────────────────────────────────── */
.edge-station__head { align-items: flex-start; }

.edge-station__ident {
  flex: 1 0 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.edge-station__name {
  font-size: 32px;
  line-height: 44px;
  color: var(--edge-text-panel);
}

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

   Место под него держится ВСЕГДА в две строки — столько занимает настоящий UUID
   в этой колонке. Прочерк занимает столько же: иначе у станции без идентификатора
   класс и цена уезжают вверх, и в ряду соседние карточки перестают читаться
   построчно. Поле держит место, даже когда данных нет, — ровно как ячейка таблицы. */
.edge-station__id {
  min-height: calc(2 * 22px);
  color: var(--edge-text-panel-dim);
  word-break: break-word;
}

.edge-station__meta {
  display: flex;
  gap: 4px;
  color: var(--edge-text-panel-dim);
  white-space: nowrap;
}

/* Разделитель «|» между классом и ценой набран тонким начертанием — оно живёт своим
   семейством, см. @font-face наверху (Regular объявлен диапазоном 100–650 и иначе
   перехватил бы вес). */
.edge-station__meta i {
  font-style: normal;
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
}

.edge-station__acts {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ── Строка устройства ───────────────────────────────────────────────────────
   Плашка устройства бывает двух видов: с адресом и без. Без адреса — обычная
   строка «иконка слева, отметки справа». С адресом — та же строка плюс адрес
   под ней, в одной плашке: адрес принадлежит устройству, и разлучать их
   отдельной плашкой значит заставлять глаз связывать их заново. */
.edge-station__dev { justify-content: space-between; }

.edge-station__unit {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.edge-station__devline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

/* Иконки одного устройства держатся вместе слева: сначала что это, потом что с ним
   можно сделать. Значки состояния уходят направо — это не действия, а отметки. */
.edge-station__tools {
  display: flex;
  align-items: center;
  gap: 13px;
}

.edge-station__flags {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ── Адрес ─────────────────────────────────────────────────────────────────────
   Одна строка: последний октет и MAC. Полный адрес не нужен — сеть у клуба одна,
   и первые три числа у всех устройств одинаковые; в колонке 176 они занимали бы
   треть строки, ничего не различая. Последний октет — это и есть то, чем
   устройства отличаются друг от друга.

   MAC рядом, приглушённо: он не меняется, поэтому служит не для чтения, а для
   сверки — когда октет уехал, а устройство то же. */
.edge-station__addr {
  display: flex;
  gap: 4px;
  white-space: nowrap;
}

.edge-station__ip  { color: var(--edge-text-panel); }
.edge-station__mac { color: var(--edge-text-panel-dim); }

.edge-station__addr i {
  font-style: normal;
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
  color: var(--edge-text-panel-dim);
}

/* ── Пульт консоли ───────────────────────────────────────────────────────────
   Раскладка кнопок повторяет геймпад: крестовина слева, служебные в центре,
   лицевые ромбом справа. Ромб выложен точками из макета, а не сеткой на глаз:
   каждая кнопка 32 в поле 64, смещения 0/16/32. */
/* Пульт занимает плашку во всю ширину: боковых полей нет, крестовина и лицевые
   кнопки прижаты к краям — так они лежат и на самом геймпаде. */
.edge-station__padrow {
  align-items: flex-start;
  justify-content: space-between;
  gap: 0;                /* общий зазор плашки здесь мешает: три группы по 128 плюс
                            два зазора не помещаются в 400, и ряд переполняется.
                            Разводит их space-between — щели выходят по 8, как в макете */
  padding: 12px 0;
}

.edge-pad__mid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.edge-pad__top { display: flex; align-items: center; }

.edge-pad__face {
  position: relative;
  width: 96px;
  height: 96px;
}

.edge-pad__face > .edge-ic { position: absolute; }
.edge-pad__face > .edge-ic--circle   { left: 0;    top: 24px; }
.edge-pad__face > .edge-ic--cross    { left: 24px; top: 48px; }
.edge-pad__face > .edge-ic--square   { left: 48px; top: 24px; }
.edge-pad__face > .edge-ic--triangle { left: 24px; top: 0; }

/* ─── Подсказка о питании ─────────────────────────────────────────────────────
   Всплывает у значка молнии и рассказывает цепочку целиком: `220 В — розетка |
   вилка — 96 Вт`. Она отвечает не на один вопрос, а на три сразу — есть ли
   напряжение в сети, замкнуто ли реле, берёт ли устройство ток, — и потому
   разбирает беду до места: «ничего не работает» превращается в «выбило автомат»,
   «реле выключено» или «устройство спит».

   Цвет проводов и вилок и есть ответ. Зелёное — ток идёт, красное — нет; обрыв
   виден там, где зелёное кончается. Ватты краснеют вместе с правым проводом:
   ноль при живой розетке — это не поломка, а выключенное устройство, но его
   стоит заметить.

   Вторая строка — сама розетка: адрес, MAC и время снятия. Время не для порядка:
   розетка отвечает закэшированным анонсом и на холостом ходу может не обновлять
   его подолгу, так что «96 Вт» без отметки способно быть получасовой давности и
   выглядеть свежим. Вся строка приглушена — это не показания, а опознание.

   Тень здесь единственная во всей панели: подсказка лежит ПОВЕРХ карточки, и
   без неё не читалось бы, что это отдельный слой, а не ещё одна плашка.

   Узел 998:5985. */
.edge-hint {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 20px;
  border-radius: 20px;
  background: var(--edge-steel-100);
  filter: drop-shadow(4px 4px 8px color-mix(in oklab, var(--edge-steel-900) 16%, transparent));
  font-family: var(--edge-font-sans);
  font-size: 16px;
  line-height: 22px;
  white-space: nowrap;
}

.edge-hint p { margin: 0; }

/* Когда розетка не отвечает, вольты и ватты не прячутся в ноль, а исчезают:
   нуля мы не измеряли, у нас просто нет числа. Флексы иначе перебивают [hidden]. */
.edge-hint [hidden] { display: none; }

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

   Появляется по наведению на значок питания. Спрятана `display: none`, а не
   прозрачностью: невидимая, но существующая подсказка перехватывала бы курсор у
   соседних значков — они стоят через шесть пикселей. */
.edge-station__hintwrap {
  position: relative;
  display: inline-flex;
}

.edge-station__hintwrap > .edge-hint {
  display: none;
  position: absolute;
  bottom: calc(100% + 6px);      /* НАД значком — так в макете: подсказка занимает
                                    505.8–579.8, значок начинается на 586.4 */
  left: 50%;
  transform: translateX(-50%);   /* по центру значка: центр подсказки 407.5 против
                                    центра значка 408 */
  z-index: 2;
}

.edge-station__hintwrap:hover > .edge-hint { display: flex; }

/* Состояния без подсказки — «нет связи» у телеметрии, значок без розетки у питания:
   показывать там нули значило бы выдавать отсутствие данных за измерение. Правило
   стоит после ховера и с большей точностью, иначе наведение его перебьёт. */
.edge-station__hintwrap > .edge-hint[hidden] { display: none; }

/* Первая строка: напряжение, цепочка, потребление. */
.edge-hint__head {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Величина с единицей. Единица приглушена: в строке важно число, а «Вт» и «В»
   лишь называют, чего оно. Зазор 2 — они читаются одним словом. */
.edge-hint__num {
  display: flex;
  align-items: center;
  gap: 2px;
  color: var(--edge-text-panel);
}

.edge-hint__num u {
  text-decoration: none;
  color: var(--edge-text-panel-dim);
}

/* Ноль при живой розетке — красный: цепь цела, а тока нет. */
.edge-hint__num--zero { color: var(--edge-red); }

/* Подсказка телеметрии устроена иначе, чем у питания: не одна строка с цепочкой, а
   две однотипные — темп пакетов и доля потерь. Они выключены влево, потому что
   читаются столбиком: взгляд идёт по значкам сверху вниз, и центрирование ломало
   бы этот столбик (узел 998:6430). */
.edge-hint--rows { align-items: flex-start; }

.edge-hint__line {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Цепочка: провод — розетка|вилка — провод. Зазора между звеньями нет намеренно,
   иначе провод не выглядит соединённым с вилкой. */
.edge-hint__chain {
  display: flex;
  align-items: center;
}

/* Вторая строка — опознание розетки, вся приглушённая. */
.edge-hint__addr {
  display: flex;
  gap: 4px;
  color: var(--edge-text-panel-dim);
}

.edge-hint__addr i {
  font-style: normal;
  font-family: "PP Object Sans Thin", var(--edge-font-sans);
}

/* ─── Иконки панели ───────────────────────────────────────────────────────────
   Каждая иконка — маска из своего файла, выгруженного из макета. Цвет и размер
   задаются снаружи, поэтому одна и та же иконка служит и активной, и погашенной:
   заводить второй файл ради другого оттенка незачем.

   Размер по умолчанию 24 — им набрана карточка; исключения проставлены явно
   рядом с теми иконками, где макет требует другого. */
.edge-ic {
  display: block;
  flex: none;
  width: var(--edge-ic-size, 36px);
  height: var(--edge-ic-size, 36px);
  background: var(--edge-ic-color, var(--edge-icon-panel));
  -webkit-mask: var(--edge-ic-src) center / contain no-repeat;
          mask: var(--edge-ic-src) center / contain no-repeat;
}

/* Кнопка с иконкой: та же геометрия, без наследия <button>. */
button.edge-ic {
  padding: 0;
  border: 0;
  cursor: pointer;
  appearance: none;
}

/* Кнопка живёт своей тройкой цветов; наведение и нажатие меняют только её вес,
   а не глиф — значок остаётся тем же, чтобы ряд не «моргал» под курсором. */
button.edge-ic          { --edge-ic-color: var(--edge-icon-action); }
button.edge-ic:hover    { --edge-ic-color: var(--edge-icon-action-hover); }
button.edge-ic:active   { --edge-ic-color: var(--edge-icon-action-active); }

button.edge-ic[disabled] {
  cursor: default;
  --edge-ic-color: var(--edge-icon-panel-dim);
}

/* ── Кнопка станции: нажатие включает, удержание выключает ────────────────────
   Разная цена ошибки — разный жест. Включить станцию не страшно, поэтому включение
   идёт обычным нажатием. Выключить — значит погасить оверлей под гостем, который
   может сидеть в заезде, поэтому там удержание две секунды.

   Кольцо вокруг кнопки — не украшение, а ответ на вопрос «сколько ещё держать»:
   без него удержание неотличимо от зависшего интерфейса. Оно и появляется только
   на время жеста: прозрачность считается от самого прогресса, поэтому кольцо
   проступает с началом удержания и тает при откате, без отдельного правила.

   Прогресс приходит из `edge-hold.js` одной переменной `--edge-hold` (0…1). Вся
   картинка собрана вокруг неё: угол заливки, цвет кнопки и видимость кольца. */
.edge-hold {
  position: relative;
  display: inline-flex;
  --edge-hold: 0;
}

/* Кольцо и кнопка меняют цвет ОДНОВРЕМЕННО — это один предмет, а не два: дуга
   догорает ровно в тот оттенок, в котором окажется кнопка. Поэтому цвет считается
   один раз здесь и берётся обоими. */
.edge-hold {
  /* Под удержанием зелёный перетекает в КРАСНЫЙ — тот самый, каким кнопка станет
     после выключения. К концу жеста она уже выглядит так, как будет выглядеть.

     Смешивание идёт в oklch по короткой дуге тона, а не в oklab: прямая между
     зелёным и красным проходит через грязно-коричневый, а дуга — через жёлтый и
     оранжевый. Получается знакомый светофор, и середина жеста не выглядит как
     испорченный цвет. */
  --edge-hold-tone: color-mix(in oklch shorter hue,
    var(--edge-red) calc(var(--edge-hold) * 100%), var(--edge-green));
}

/* Кольцо — SVG-дуга, а не conic-gradient: у градиента концы всегда рубленые, а
   дуга должна догорать скруглённым краем. Рисует его сам `edge-hold.js`, поэтому
   в разметке карточки лишнего нет.

   Геометрия: кнопка 36, кольцо вплотную к её краю, толщина 3 — значит окружность
   радиусом 19.5 в поле 42. Зазор нулевой не случайно: у самого глифа внутри своего
   квадрата есть поле, и на глаз просвет всё равно остаётся. Поворот на -90° ставит
   начало дуги наверх, чтобы она шла по часовой от двенадцати. */
.edge-hold__ring {
  position: absolute;
  inset: -3px;
  width: 42px;
  height: 42px;
  transform: rotate(-90deg);
  pointer-events: none;
  opacity: calc(var(--edge-hold) * 4);
}

.edge-hold__ring circle {
  fill: none;
  stroke: var(--edge-hold-tone);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 122.5;
  stroke-dashoffset: calc(122.5 * (1 - var(--edge-hold)));
}

/* ── Кнопка питания говорит цветом, а не подписью (узел 1003:7309) ───────────
   Зелёная — станция работает. Красная — выключена, но включить её можно прямо
   сейчас. Серая — включить нельзя вовсе: телевизор не в сети, оверлею взяться
   неоткуда, и нажатие ничего бы не дало.

   Красный тут не тревога, а состояние: станция выключена — это нормальный режим,
   просто заметный. Тревожиться оператор должен, когда кнопка серая. */
.edge-hold > button.edge-ic { --edge-ic-color: var(--edge-red); }
.edge-hold.is-on > button.edge-ic { --edge-ic-color: var(--edge-hold-tone); }

/* Ховер и нажатие цвет не трогают: он здесь несёт состояние станции, а не
   отзывчивость кнопки. Вес под курсором добавлять некуда — она и так цветная. */
.edge-hold > button.edge-ic:hover,
.edge-hold > button.edge-ic:active { --edge-ic-color: var(--edge-red); }
.edge-hold.is-on > button.edge-ic:hover,
.edge-hold.is-on > button.edge-ic:active { --edge-ic-color: var(--edge-hold-tone); }

/* Серым остаётся ровно один случай — нечего включать. */
.edge-hold > button.edge-ic[disabled],
.edge-hold > button.edge-ic[disabled]:hover {
  --edge-ic-color: var(--edge-icon-panel-dim);
}

/* Сжатие — по факту жеста, а не по прогрессу: кнопка «уходит под палец» сразу и
   возвращается, когда всё кончилось, чем бы ни кончилось. */
.edge-hold > button.edge-ic { transition: transform 180ms ease; }
.edge-hold.is-holding > button.edge-ic { transform: scale(0.92); }

/* Команда ушла, ответа ещё нет. Кнопка на это время серая и пульсирует — не
   «нажимается», а показывает, что занята: нажать второй раз и получить две
   команды подряд станция не должна. Мигание плавное и медленное; резкое на
   панели, где карточек четыре, читалось бы как тревога. */
.edge-hold.is-busy > button.edge-ic,
.edge-hold.is-busy > button.edge-ic:hover {
  --edge-ic-color: var(--edge-red);
  animation: edge-hold-busy 1200ms ease-in-out infinite;
  pointer-events: none;
  cursor: default;
}

.edge-hold.is-busy .edge-hold__ring { opacity: 0; }

@keyframes edge-hold-busy {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .edge-hold > button.edge-ic { transition: none; }
  .edge-hold.is-busy > button.edge-ic { animation: none; opacity: 0.6; }
}

/* ── Кнопка разблокировки: плей и стоп ───────────────────────────────────────
   Это админский жест, а не управление заездом гостя (узел 1007:3475). Плей
   снимает оверлей и открывает станцию, стоп возвращает оверлей на место.

   Стоп красный не потому, что опасен, а потому что описывает состояние: оверлея
   сейчас НЕТ, станция открыта всем. Такое на карточке должно быть видно издалека,
   иначе открытую станцию заметят, только когда за неё кто-то сядет. */
.edge-ride > button.edge-ic { --edge-ic-color: var(--edge-icon-action); }
.edge-ride > button.edge-ic:hover  { --edge-ic-color: var(--edge-icon-action-hover); }
.edge-ride > button.edge-ic:active { --edge-ic-color: var(--edge-icon-action-active); }

.edge-ride.is-open > button.edge-ic,
.edge-ride.is-open > button.edge-ic:hover,
.edge-ride.is-open > button.edge-ic:active { --edge-ic-color: var(--edge-red); }

.edge-ride > button.edge-ic[disabled],
.edge-ride > button.edge-ic[disabled]:hover { --edge-ic-color: var(--edge-icon-panel-dim); }

/* Ожидание ответа — тот же язык, что у кнопки питания: серое мигание не годится,
   красное держит связь с остальными состояниями панели. */
.edge-ride.is-busy > button.edge-ic,
.edge-ride.is-busy > button.edge-ic:hover {
  --edge-ic-color: var(--edge-red);
  animation: edge-hold-busy 1200ms ease-in-out infinite;
  pointer-events: none;
  cursor: default;
}

/* Значок, который не кнопка, но обозначает доступное действие — так набран
   пульт: сам он погашен, а дистанционная игра горит цветом действия (1000:6555). */
.edge-ic--act { --edge-ic-color: var(--edge-icon-action); }

/* Приглушённая иконка — недоступное действие и погасшее состояние. */
.edge-ic--dim { --edge-ic-color: var(--edge-icon-panel-dim); }

/* Иконка в порядке: сеть отвечает, питание идёт, телеметрия приходит. */
.edge-ic--ok { --edge-ic-color: var(--edge-icon-ok); }

/* Выключено намеренно — рукой или командой, а не поломкой (узел 998:6217). */
.edge-ic--warn { --edge-ic-color: var(--edge-yellow); }

/* Ток не идёт: устройство не берёт его или розетка не отвечает вовсе. */
.edge-ic--bad { --edge-ic-color: var(--edge-red); }

/* ── Шесть состояний значка питания ───────────────────────────────────────────
   Цвет говорит, что происходит, перечёркивание — идёт ли ток. Узел 998:5985.

   С розеткой (у значка есть подсказка с цепочкой):

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

   Жёлтый в этой четвёрке ровно один и означает «выключено намеренно». Всё, где
   ток не идёт сам по себе, красное — и целое, и перечёркнутое.

   Без розетки (подсказки нет, брать числа неоткуда):

     зелёный контур       — розетки нет, но устройство отвечает в сети: питание
                            есть, а измерить его нечем;
     погашенная целая     — про сеть ничего не известно.

   ⚠️ Погашенная теперь ЦЕЛАЯ, а не перечёркнутая: перечёркивание закреплено за
   «ток не идёт», а незнание — не утверждение о токе. Перечёркнутая погашенная
   молния говорила бы «питания нет» там, где мы просто не смотрели. */

.edge-ic--power    { --edge-ic-src: url("/design/icons/power-circle-fill.svg"); }
.edge-ic--play     { --edge-ic-src: url("/design/icons/play-circle-fill.svg"); }
.edge-ic--pause    { --edge-ic-src: url("/design/icons/pause-circle-fill.svg"); }
.edge-ic--stop     { --edge-ic-src: url("/design/icons/stop-circle-fill.svg"); }
.edge-ic--monitor  { --edge-ic-src: url("/design/icons/monitor-fill.svg"); }
.edge-ic--console  { --edge-ic-src: url("/design/icons/game-controller-fill.svg"); }
.edge-ic--wheel    { --edge-ic-src: url("/design/icons/steering-wheel-fill.svg"); }
.edge-ic--platform { --edge-ic-src: url("/design/icons/seat-fill.svg"); }
.edge-ic--pc       { --edge-ic-src: url("/design/icons/app-window-fill.svg"); }
.edge-ic--remote   { --edge-ic-src: url("/design/icons/arrow-up-right.svg"); }

/* Значки состояния — 16. Каждое состояние это ПАРА иконок одного глифа: целая и
   перечёркнутая. Одним глифом с двумя цветами обойтись нельзя: цвет различает
   состояние только у того, кто смотрит внимательно, а перечёркивание видно и
   краем глаза.

   Сеть читается тем же языком, что и питание, но БЕЗ подсказки: скорость канала
   никто не отдаёт, показывать в ней нечего (узел 998:6273):

     зелёная целая        — устройство отвечает;
     красная перечёркнутая— не отвечает: это беда, а не режим;
     погашенная целая     — про сеть ничего не известно.

   ⚠️ Перечёркнутая сеть теперь КРАСНАЯ, а не погашенная. Погашенная перечёркнутая
   говорила бы «сети нет» там, где её никто не проверял, — а это разные вещи. */
.edge-ic--net      { --edge-ic-src: url("/design/icons/network-fill.svg");         --edge-ic-size: 24px; }
.edge-ic--netoff   { --edge-ic-src: url("/design/icons/network-slash-fill.svg");   --edge-ic-size: 24px; }

/* Связь приложения станции с сервером — только у телевизоров (узлы 1003:7684,
   1003:7706). Рядом с сетью, но отвечает на другой вопрос, и путать их нельзя:

     значок сети — телевизор виден роутеру, то есть жив как железо;
     этот значок — приложение станции держит связь с сервером.

   Расходятся они постоянно: телек включён и в сети, а приложение упало или не
   запустилось — оверлея не будет, хотя «сеть есть». Обратное тоже бывает при
   разрыве канала: приложение молчит секунды, телевизор при этом никуда не делся.

   Зелёный — связь держится, красный — нет. Полутонов здесь не бывает: соединение
   либо живо, либо нет. */
.edge-ic--cast     { --edge-ic-src: url("/design/icons/screencast-fill.svg");      --edge-ic-size: 24px; }

/* Туннель — только у консолей и только рядом с сетью (узел 998:6273). Сеть и
   туннель это разные вопросы: кабель может быть жив, а PSN всё равно недоступна,
   потому что Sony отбивает российские адреса. Поэтому значка два, и второй
   отвечает ровно на один вопрос — идёт ли трафик консоли через VPS.

     зелёная воронка        — консоль ходит через туннель;
     красная с крестиком    — идёт напрямую, PSN её отобьёт.

   Пара «сеть красная + туннель красный» — обычное следствие: нет сети, нет и
   туннеля. Обратное не работает: туннель падает и при живой сети. */
.edge-ic--tunnel    { --edge-ic-src: url("/design/icons/funnel-fill.svg");         --edge-ic-size: 24px; }
.edge-ic--tunneloff { --edge-ic-src: url("/design/icons/funnel-x-fill.svg");       --edge-ic-size: 24px; }
.edge-ic--bolt     { --edge-ic-src: url("/design/icons/lightning-fill.svg");       --edge-ic-size: 24px; }

/* Та же молния КОНТУРОМ — для случая, когда розетки нет, а устройство отвечает в
   сети (узел 1003:7284). Цвет говорит «питание есть», незалитый контур — «знаем
   косвенно, числа не будет». Раньше здесь стояла обычная тёмная молния, и она
   читалась как полноценное измерение, которого у нас нет. */
.edge-ic--boltline { --edge-ic-src: url("/design/icons/lightning-line.svg");       --edge-ic-size: 24px; }
.edge-ic--poweroff { --edge-ic-src: url("/design/icons/lightning-slash-fill.svg"); --edge-ic-size: 24px; }
/* Перечёркнутая КОНТУРОМ — «тока нет, и знаем мы это косвенно». Пара к --boltline
   ровно так же, как перечёркнутая залитая относится к залитой: форма говорит,
   откуда знание, а перечёркивание — что цепь не работает (EDGE-405). */
.edge-ic--boltslash { --edge-ic-src: url("/design/icons/lightning-slash-line.svg"); --edge-ic-size: 24px; }
/* Звенья цепочки в подсказке о питании. Провод одноцветный — обычная маска,
   цвет ей задаёт состояние. Пара «розетка | вилка» бывает и двухцветной: левая
   половина живая, правая уже нет — такую маской не выразить, и она единственный
   значок панели, который рисуется картинкой. Узлы 998:6113 и 998:6162. */
/* Стрелка вниз — доля потерянных пакетов в подсказке телеметрии (узел 998:6437). */
.edge-ic--down     { --edge-ic-src: url("/design/icons/arrow-down.svg");            --edge-ic-size: 24px; }

.edge-ic--wire     { --edge-ic-src: url("/design/icons/wire.svg");                  --edge-ic-size: 24px; }
.edge-ic--plugs    { --edge-ic-src: url("/design/icons/plugs-connected-fill.svg");   --edge-ic-size: 24px; }
.edge-ic--plugshalf {
  --edge-ic-size: 24px;
  background: url("/design/icons/plugs-connected-split.svg") center / contain no-repeat;
  -webkit-mask: none;
          mask: none;
}

/* Телеметрия (узел 998:6426). Здесь значок красится не по факту «идёт или нет», а
   по КАЧЕСТВУ потока, и пороги заданы Даниилом:

     зелёный  — 59 п/с и больше, потерь 0%;
     жёлтый   — 31–58 п/с или потери 1–4%;
     красный  — 30 п/с и меньше или потери от 5%;
     перечёркнутый красный — 0 п/с, связи нет.

   Состояние — ХУДШЕЕ из двух: полный темп при пяти процентах потерь всё равно
   рваный поток. GT7 шлёт 60 Гц, поэтому 59 и есть «всё хорошо», а не придирка. */
.edge-ic--tele     { --edge-ic-src: url("/design/icons/package-fill.svg");        --edge-ic-size: 24px; }
.edge-ic--teleoff  { --edge-ic-src: url("/design/icons/package-slash-fill.svg");  --edge-ic-size: 24px; }

.edge-ic--dpad     { --edge-ic-src: url("/design/icons/pad-dpad.svg");     --edge-ic-size: 96px; }
.edge-ic--home     { --edge-ic-src: url("/design/icons/pad-home.svg");     --edge-ic-size: 48px; }
.edge-ic--options  { --edge-ic-src: url("/design/icons/pad-options.svg");  --edge-ic-size: 48px; }
/* Вместо кнопки питания на пульте — значок дистанционной игры: по этой кнопке
   станция подключается к Remote Play, а не включается (решение Даниила 26.08,
   узел 1000:6555). Временный вид, потом заменим на свой. */
.edge-ic--remoteplay { --edge-ic-src: url("/design/icons/broadcast.svg"); }
.edge-ic--circle   { --edge-ic-src: url("/design/icons/pad-circle.svg");   --edge-ic-size: 48px; }
.edge-ic--cross    { --edge-ic-src: url("/design/icons/pad-cross.svg");    --edge-ic-size: 48px; }
.edge-ic--square   { --edge-ic-src: url("/design/icons/pad-square.svg");   --edge-ic-size: 48px; }
.edge-ic--triangle { --edge-ic-src: url("/design/icons/pad-triangle.svg"); --edge-ic-size: 48px; }
