/* =============================================================================
   ТУРБО / TOURBO — component kit
   Consumes tokens.css only. No literal colours, sizes or fonts below.
   Every block is documented: what it is, and the markup it expects.
   ============================================================================= */

/* ---------- 0. RESET / BASE ------------------------------------------------ */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-body);
  line-height: var(--lh-body);
  overflow-wrap: anywhere;
}

/* Selection, drag and the long-press callout are BROWSER furniture. On a phone
   they are what gives a web app away: a long press on a card offers to copy it,
   a press on the logo offers to save the image, and a stray drag paints half the
   screen blue. Off by default, and switched back ON below for the places where
   selecting text is the whole point. */
body {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  /* The grey flash Chrome/Safari paint over a tapped element. The app draws its
     own :active states (the plate shifts under the press), so the default only
     muddies them. */
  -webkit-tap-highlight-color: transparent;
}

/* Anything a member types into, and anything they might legitimately need to
   copy — a booking number, a receipt line, an address. Denying selection here
   is not "app-like", it is just broken. */
input, textarea, select, [contenteditable], .t-num, .t-mono, .ticket__stub, .list__val {
  -webkit-user-select: text; user-select: text;
  -webkit-touch-callout: default;
}

/* A dragged image leaves a ghost and starts a drag-and-drop the app has no use
   for; `draggable={false}` in the markup covers the rest. */
img { -webkit-user-drag: none; user-drag: none; }

h1, h2, h3, h4 { margin: 0; font-weight: var(--fw-strong); line-height: var(--lh-head); }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
img, svg { display: block; max-width: 100%; }

:focus-visible {
  outline: var(--rule) solid var(--c-focus);
  outline-offset: 2px;
}

/* ---------- 1. TYPOGRAPHY UTILITIES ---------------------------------------- */

/* .t-display — condensed uppercase poster voice. Headings, labels, numbers only.
   Never wrap a paragraph in this. */
.t-display {
  font-family: var(--font-display);
  font-weight: var(--fw-strong);
  text-transform: uppercase;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
}
/* .t-label — micro letterspaced caps: rail labels, badge text, table heads. */
.t-label {
  font-family: var(--font-display);
  font-weight: var(--fw-strong);
  text-transform: uppercase;
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  line-height: 1.1;
}
/* .t-num — tabular figures. Any number that lines up in a column. */
.t-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
/* .t-mono — ticket data: ids, receipts, countdown digits. */
.t-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.t-muted { color: var(--c-text-muted); }
.t-meta  { color: var(--c-text-meta); font-size: var(--fs-sm); }
.t-red   { color: var(--c-red); }
.t-cyan  { color: var(--c-cyan); }

/* .rail-label — vertical micro label running up the side of a block.
   Rotation is allowed here: it is ornament/label, never body copy. */
.rail-label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--font-display);
  font-weight: var(--fw-strong);
  text-transform: uppercase;
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  white-space: nowrap;
}

/* ---------- 2. RULES, DIVIDERS, ORNAMENT ----------------------------------- */

/* .rule — horizontal ink rule. Weight modifiers carry hierarchy. */
.rule { border: 0; border-top: var(--rule) solid var(--c-line); margin: 0; }
.rule--hair  { border-top-width: var(--rule-hair); border-top-color: var(--c-line-soft); }
.rule--thick { border-top-width: var(--rule-thick); }
.rule--heavy { border-top-width: var(--rule-heavy); }
.rule--red   { border-top-color: var(--c-red); }
/* .rule--double — the constructivist double bar (thick over hair). */
.rule--double {
  border-top: var(--rule-thick) solid var(--c-line);
  border-bottom: var(--rule-hair) solid var(--c-line);
  height: var(--sp-1);
}

/* .divider — labelled section break used between mockup variants on one page. */
.divider {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-10) 0 var(--sp-5);
  color: var(--c-ink-2);
}
.divider::before, .divider::after {
  content: ""; flex: 1; height: var(--rule); background: var(--c-line-soft);
}
.divider > span {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  text-transform: uppercase; font-size: var(--fs-xs); letter-spacing: var(--ls-label);
}

/* .plaque — ink block with an offset red plate: the mis-registration signature. */
.plaque {
  position: relative;
  background: var(--c-ink);
  color: var(--c-white);
  box-shadow: var(--plate-red);
}
.plaque--red   { background: var(--c-red);   box-shadow: var(--plate); }
.plaque--cyan  { background: var(--c-cyan);  box-shadow: var(--plate); }
.plaque--ochre { background: var(--c-ochre); color: var(--c-ink); box-shadow: var(--plate); }

/* .cut-br / .cut-tr — the shared diagonal corner cut. Keeps all angles parallel. */
.cut-br { clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%); }
.cut-tr { clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%); }
.cut-bl { clip-path: polygon(0 0, 100% 0, 100% 100%, var(--cut) 100%, 0 calc(100% - var(--cut))); }

/* .seal — starburst price/promo seal. Put a number inside. */
.seal {
  position: relative;
  display: grid; place-items: center;
  width: 84px; aspect-ratio: 1; border-radius: var(--r-seal);
  background: var(--c-ochre);
  color: var(--c-ink);
  text-align: center;
  z-index: var(--z-raised);
}
.seal::before {
  content: ""; position: absolute; inset: -9px; border-radius: var(--r-seal);
  background: conic-gradient(from 0deg, var(--c-ochre) 0 6deg, transparent 6deg 18deg);
  z-index: -1;
}
.seal--red { background: var(--c-red); color: var(--c-white); }

/* .seal-rail — полоса, на которой висит ценовой диск на экране тренировки.
   Высота 22px — это ровно тот кусок диска, что свешивается ниже линии
   горизонта: полоса резервирует его, поэтому следующий блок (табло мест или
   список) не оказывается под кругом, что бы за ней ни стояло.
   Диск лежит здесь, а не в `.hero`, потому что у неба есть `overflow: hidden`
   и диагональный `clip-path` — изнутри свес срезало бы. */
.seal-rail { position: relative; height: 22px; }
.seal-rail .seal { position: absolute; right: var(--gutter); top: -64px; }

.seal--red::before { background: conic-gradient(from 0deg, var(--c-red) 0 6deg, transparent 6deg 18deg); }

/* .stamp — rotated outline stamp laid over a card (sold out, cancelled, paid). */
.stamp {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%) rotate(-11deg);
  padding: var(--sp-1) var(--sp-3);
  border: var(--rule-thick) solid currentColor;
  color: var(--c-red);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  text-transform: uppercase; font-size: var(--fs-h1); letter-spacing: var(--ls-head);
  opacity: 0.9;
  z-index: var(--z-raised);
  pointer-events: none;
  white-space: nowrap;
}
.stamp[data-tone="muted"]   { color: var(--c-ink-3); }
.stamp[data-tone="positive"]{ color: var(--c-green); }

/* .ticket-edge — perforated print edge for receipts and payment blocks. */
.ticket-edge { position: relative; }
.ticket-edge::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: calc(var(--sp-2) * -1);
  height: var(--sp-2);
  background: radial-gradient(circle at 6px 0, var(--c-bg) 5px, transparent 5.5px);
  background-size: 12px var(--sp-2);
}

/* ---------- 3. APP SHELL --------------------------------------------------- */

/* .app — the phone column. Centred, capped, poster margins grow on tablet.
   Markup: <div class="app tex-grain"> [statusbar] [appbar] <main class="app__main"> … */
.app {
  position: relative;
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--c-bg);
  display: flex;
  flex-direction: column;
  border-inline: var(--rule) solid var(--c-line);
  /* .tabbar is FIXED, so it covers whatever the page ends with. Reserve its
     height (58px item + 4px top rule) plus the home-indicator inset, plus a
     gutter so the last card clears the bar instead of touching it. Without
     this the final card cannot be scrolled out from under the nav. */
  padding-bottom: calc(62px + env(safe-area-inset-bottom, 0) + var(--sp-6));
}
@media (max-width: 460px) { .app { border-inline: 0; } }

/* .fill-center — claims the page's leftover height and centres its content in
   it. For TERMINAL screens (a payment result) where nothing follows the
   message, so the eye lands on the outcome and its one action rather than on
   empty paper below them. `.app` is the flex column this grows inside. */
.fill-center {
  flex: 1;
  display: flex; flex-direction: column; justify-content: center;
  gap: var(--sp-4);
}
/* Tablet and up: the column reads as a printed sheet lying on darker stock. */
@media (min-width: 600px) {
  body { background: var(--c-paper-2); }
  .app { border-inline-width: var(--rule-thick); }
}

/* .statusbar — the OS strip. Cosmetic in mockups; keeps the header off the notch. */
.statusbar {
  height: 44px;
  flex: none;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--gutter);
  background: var(--c-ink);
  color: var(--c-white);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.04em;
}
.statusbar__dots { display: flex; gap: 3px; align-items: flex-end; }
.statusbar__dots i { display: block; width: 3px; background: var(--c-white); }
.statusbar__dots i:nth-child(1) { height: 5px; }
.statusbar__dots i:nth-child(2) { height: 8px; }
.statusbar__dots i:nth-child(3) { height: 11px; }
.statusbar__dots i:nth-child(4) { height: 14px; opacity: 0.4; }

/* .appbar — sticky header carrying the brand mark and up to two actions.
   The mockups sat it under a fake 44px .statusbar; the real app clears the notch
   with the actual inset instead, which is 0 wherever there isn't one. */
.appbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  flex: none;
  display: flex; align-items: stretch; justify-content: space-between; gap: var(--sp-3);
  background: var(--c-paper);
  border-bottom: var(--rule-heavy) solid var(--c-line);
  padding: calc(var(--sp-2) + env(safe-area-inset-top, 0)) var(--gutter) var(--sp-2);
}

/* .appbar__me — the signed-in member, as a MONOGRAM. The full name fought the
   club's plaque for the bar's one row and had to be capped, wrapped or clipped
   however it was tried; a fixed 32px square simply never competes. The name
   survives as the link's accessible label and title.
   `margin-inline-start: auto` keeps it against the right edge when a screen
   also puts its own action up there. */
/* Two classes on purpose: `.monogram` is declared further down the sheet, so a
   single-class rule here loses the size to it. */
.monogram.appbar__me {
  align-self: center; margin-inline-start: auto;
  width: 32px; height: 32px;
  font-size: var(--fs-sm);
  text-decoration: none;
  box-shadow: var(--misreg-x) var(--misreg-y) 0 0 var(--c-red);
}

/* .appbar__actions — правая группа шапки: колокольчик + монограмма.
   `margin-inline-start: auto` переехал СЮДА с монограммы: пока она была
   единственным действием, авто-отступ прижимал её сам, но рядом с
   колокольчиком тот же отступ раздвинул бы их по разным концам строки.
   Поэтому вправо уезжает ГРУППА, а внутри неё — обычный зазор. */
.appbar__actions { display: flex; align-items: center; gap: var(--sp-3); margin-inline-start: auto; }
.appbar__actions .monogram.appbar__me { margin-inline-start: 0; }

/* .appbar__bell — вход в ленту уведомлений тренера. Квадрат в 32px, как у
   монограммы: две разные высоты в одной строке шапки читаются как ошибка. */
.appbar__bell {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  color: var(--c-ink); text-decoration: none;
}
.appbar__bell svg { width: 22px; height: 22px; }
/* Точка непрочитанного. Смещена за край иконки и обведена цветом шапки —
   иначе она сливается с дужкой колокольчика. */
.appbar__bell-dot {
  position: absolute; top: 2px; right: 2px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--c-red); box-shadow: 0 0 0 2px var(--c-paper);
}

/* .brand — club mark: logo, red plaque with the club's name, latin sub-line. */
.brand { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tap-min); min-width: 0; }
/* .brand__logo — square, never shrinks: the plaque beside it is the part that
   gives ground when a club has a long name. */
.brand__logo { flex: none; width: 34px; height: 34px; object-fit: contain; }
.brand__mark {
  position: relative;
  background: var(--c-red);
  color: var(--c-white);
  padding: 2px var(--sp-2) 3px;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  line-height: 1; letter-spacing: 0.01em;
  text-transform: uppercase;
  box-shadow: 3px 3px 0 0 var(--c-ink);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%);
  /* A club name is longer than «ТУРБО» and its length is the admin's choice, so
     the plaque scales down on narrow screens and clips rather than wrapping. */
  font-size: clamp(20px, 6.4vw, 27px);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.brand__sub {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--c-ink-2);
  writing-mode: vertical-rl; line-height: 1;
}

/* .iconbtn — square 44px action in the header (filters, back, close). */
.iconbtn {
  min-width: var(--tap-min); min-height: var(--tap-min);
  display: inline-grid; place-items: center;
  background: transparent;
  border: var(--rule) solid var(--c-line);
  border-radius: var(--r-0);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.iconbtn:active { background: var(--c-ink); color: var(--c-white); }
.iconbtn--bare { border-color: transparent; }

/* .app__main — scroll area. Bottom padding clears the tab bar. */
.app__main {
  flex: 1 1 auto;
  padding-bottom: calc(var(--sp-16) + var(--sp-6));
}
.app__main--noTabbar { padding-bottom: var(--sp-10); }

/* .page-head — the screen title block: giant condensed title + rule + kicker. */
.page-head { position: relative; overflow: hidden; padding: var(--sp-5) var(--gutter) var(--sp-4); }
/* .page-head__orn — halftone disc bleeding off the top-right of the masthead.
   Pure ornament: no text, no meaning, never intercepts taps. */
.page-head__orn {
  position: absolute; top: -34px; right: -34px;
  width: 132px; height: 132px; border-radius: var(--r-seal);
  color: var(--c-red);
  pointer-events: none;
  opacity: 0.55;
  overflow: hidden;
}
.page-head__kicker {
  display: flex; align-items: center; gap: var(--sp-2);
  color: var(--c-ink-2); margin-bottom: var(--sp-2);
}
.page-head__kicker::after { content: ""; flex: 1; height: var(--rule); background: var(--c-line-soft); }
/* .page-head__row — the title line, with room for a screen-level action on the
   right. `position: relative` is load-bearing: `.page-head__orn` is absolutely
   positioned and would otherwise paint its halftone dots OVER the action. */
.page-head__row {
  position: relative;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3);
}
.page-head__title {
  min-width: 0;
  font-family: var(--font-display); font-size: var(--fs-d1);
  text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.page-head__title em { font-style: normal; color: var(--c-red); }
.page-head__rule { margin-top: var(--sp-2); border-top: var(--rule-thick) solid var(--c-red); }

/* .section — generic padded block inside the scroll area. */
.section { padding: var(--sp-5) var(--gutter); }
.section--flush { padding-inline: 0; }
.section__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.section__title {
  font-family: var(--font-display); font-size: var(--fs-h1);
  text-transform: uppercase; letter-spacing: var(--ls-head);
}

/* ---------- 4. BOTTOM TAB BAR ---------------------------------------------- */

/* .tabbar — fixed 4-item navigation. Active item is a red plaque.
   Markup: <nav class="tabbar"><a class="tabbar__item is-active">[svg]<span>…</span></a>… */
.tabbar {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 0;
  width: 100%; max-width: var(--shell-max);
  z-index: var(--z-tabbar);
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--c-ink);
  border-top: var(--rule-thick) solid var(--c-red);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.tabbar__item {
  min-height: 58px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px;
  color: var(--c-paper-3);
  padding: var(--sp-2) 2px;
  border-right: var(--rule-hair) solid rgba(241, 231, 211, 0.18);
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.tabbar__item:last-child { border-right: 0; }
.tabbar__item svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; }
.tabbar__item span {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: 0.06em; text-transform: uppercase;
  text-align: center; line-height: 1.05;
}
.tabbar__item.is-active { background: var(--c-red); color: var(--c-white); }
.tabbar__item:active { background: var(--c-red-deep); color: var(--c-white); }

/* ---------- 5. BUTTONS ------------------------------------------------------ */

/* .btn — base. Variants: --primary --secondary --ghost. Modifier: --block, --lg.
   Disabled = [disabled] or .is-disabled (striped, no plate). */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--tap-min);
  padding: var(--sp-3) var(--sp-5);
  border: var(--rule) solid var(--c-line);
  border-radius: var(--r-0);
  background: var(--c-surface);
  color: var(--c-ink);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: 0.04em;
  cursor: pointer;
  box-shadow: var(--plate);
  transition: transform var(--dur-fast) var(--ease-step),
              box-shadow var(--dur-fast) var(--ease-step),
              background var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translate(var(--misreg-x), var(--misreg-y)); box-shadow: none; }

.btn--primary { background: var(--c-red); color: var(--c-white); border-color: var(--c-ink); }
.btn--primary:hover { background: var(--c-red-deep); }

.btn--secondary { background: var(--c-surface); color: var(--c-ink); }
.btn--secondary:hover { background: var(--c-paper-2); }

.btn--ghost {
  background: transparent; border-color: transparent; box-shadow: none;
  color: var(--c-ink-2); font-size: var(--fs-body);
  text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 2px;
}
.btn--ghost:active { transform: none; }
.btn--ghost:hover { color: var(--c-red); }

.btn--lg { min-height: 56px; font-size: var(--fs-h1); padding-inline: var(--sp-6); }
.btn--block { display: flex; width: 100%; }
/* .btn--sm — компактное действие внутри строки ленты. Сохраняет 44px по высоте. */
.btn--sm {
  min-height: var(--tap-min);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label);
  box-shadow: none;
  border-width: var(--rule);
}

.btn[disabled], .btn.is-disabled {
  cursor: not-allowed;
  background: var(--c-paper-3);
  color: var(--c-ink-3);
  border-color: var(--c-ink-3);
  box-shadow: none;
  transform: none;
  background-image: repeating-linear-gradient(-45deg,
    rgba(23,19,14,0.10) 0 6px, transparent 6px 14px);
}
/* Выключенная ссылка-действие не должна становиться полосатой плашкой. */
.btn--ghost[disabled] {
  background: transparent; background-image: none; border-color: transparent;
  color: var(--c-ink-3); text-decoration: none;
}

/* .btn-row — sticky action footer above the tab bar. */
.btn-row {
  position: sticky; bottom: 0;
  display: grid; gap: var(--sp-3);
  padding: var(--sp-4) var(--gutter) var(--sp-5);
  background: var(--c-paper);
  border-top: var(--rule-thick) solid var(--c-line);
}
.btn-row--split { grid-template-columns: auto 1fr; align-items: center; }
/* .btn-row--flow — the same stack of actions, but ending a finished screen
   rather than hovering over a scrolling one. A terminal result (paid, refused)
   has nothing below it to keep reachable, and a sticky bar there just floats a
   rule across the page. */
.btn-row--flow {
  position: static;
  padding-inline: 0; padding-bottom: 0;
  border-top: 0; background: transparent;
}
/* .btn-row--inset — полоса кнопок ВНУТРИ уже отбитого контейнера (карточки
   формы), а не во всю ширину экрана. `.btn-row` несёт `--gutter` по бокам как
   раз для второго случая; внутри `.card--pad` эти поля СКЛАДЫВАЮТСЯ с полями
   карточки — на телефоне это 32px. Ровно их и не хватало: «Создать» уезжала
   за правый край экрана. Тот же счёт уже записан ниже у
   `.pkg__foot--freeze .btn-row`. */
.btn-row--inset { padding-inline: 0; }
/* И вторая половина той же беды: `auto 1fr` отдаёт «Отмене» её natural width,
   а «Создать» довольствуется остатком. Две равные колонки — ровно тот приём,
   что стоит строкой ниже для `.callout .btn-row--split`. */
.btn-row--inset.btn-row--split { grid-template-columns: 1fr 1fr; }
.btn-row--inset .btn { white-space: nowrap; padding-inline: var(--sp-2); }

/* Inside a callout the row only gets the content column, and `auto 1fr` left so
   little for the confirm button that «ОТМЕНИТЬ» broke mid-word. Two equal
   columns, and the label never breaks. */
.callout .btn-row--split { grid-template-columns: 1fr 1fr; }
.callout .btn-row--split .btn { white-space: nowrap; padding-inline: var(--sp-2); }

/* ---------- 6. CHIPS, FILTER PILLS, SEGMENTED CONTROL ---------------------- */

/* .chiprow — horizontally scrolling filter bar. Sticky under the header. */
.chiprow {
  display: flex; gap: var(--sp-2);
  overflow-x: auto;
  padding: var(--sp-2) var(--gutter);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.chiprow::-webkit-scrollbar { display: none; }
.chiprow--sticky {
  position: sticky; top: 68px; z-index: var(--z-sticky);
  background: var(--c-paper);
  border-bottom: var(--rule) solid var(--c-line);
}

/* .chip — filter pill. .is-on = selected (ink fill). [disabled] = struck out. */
.chip {
  flex: none;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: var(--tap-min); padding: var(--sp-1) var(--sp-3);
  border: var(--rule) solid var(--c-line);
  background: transparent; color: var(--c-ink);
  border-radius: var(--r-0);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.chip.is-on { background: var(--c-ink); color: var(--c-white); }
.chip.is-on.chip--accent { background: var(--c-red); border-color: var(--c-red); }
.chip__count {
  font-variant-numeric: tabular-nums;
  padding: 0 4px; background: var(--c-red); color: var(--c-white);
}
.chip.is-on .chip__count { background: var(--c-white); color: var(--c-ink); }
.chip[disabled] { opacity: 0.45; cursor: not-allowed; text-decoration: line-through; }
/* .chip.is-in — a day inside the selected date range (endpoints stay .is-on). */
.chip.is-in { background: var(--c-paper-3); border-color: var(--c-ink-2); }
/* .chiprow__sep — vertical rule splitting filter groups inside the scroller. */
.chiprow__sep { flex: none; width: var(--rule); background: var(--c-line-soft); margin-inline: var(--sp-1); }
/* Chips have a 36px box but sit in a 44px-tall scroller — the row is the target. */
.chiprow .chip { align-self: center; }
/* .chiprow--wrap / --flush — chips inside a sheet or a form wrap instead of scrolling. */
.chiprow--wrap  { flex-wrap: wrap; overflow-x: visible; }
.chiprow--flush { padding-inline: 0; }

/* .seg — two/three-way view switch (список · неделя · месяц). */
.seg {
  display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  border: var(--rule) solid var(--c-line);
  background: var(--c-surface);
  width: 100%;
}
/* .seg__price — the club's price for that length, under its label. Two lines in
   a segment, because the number is the whole reason to choose one over another. */
.seg__price {
  display: block; margin-top: 2px;
  font-size: var(--fs-micro); letter-spacing: 0; font-style: normal;
  opacity: 0.75;
}

.seg__btn {
  min-height: var(--tap-min);
  padding: var(--sp-2) var(--sp-3);
  background: transparent; border: 0;
  border-right: var(--rule) solid var(--c-line);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  text-transform: uppercase; font-size: var(--fs-xs); letter-spacing: var(--ls-label);
  cursor: pointer;
}
.seg__btn:last-child { border-right: 0; }
.seg__btn.is-on { background: var(--c-ink); color: var(--c-white); }
.seg__btn .t-num { margin-left: var(--sp-1); opacity: 0.75; }

/* .segbar — липкая полка под шапкой для сегментированного переключателя.
   Sticky stack: .appbar 0–68px, .segbar 68–128px. Markup: .segbar > .seg. */
.segbar {
  position: sticky; top: 68px; z-index: var(--z-sticky);
  padding: var(--sp-2) var(--gutter);
  background: var(--c-paper);
  border-bottom: var(--rule) solid var(--c-line);
}

/* .tapchiprow / .tapchip — крупная версия .chip для полевых экранов: цель 56px,
   строка не прокручивается (замёрзшая рука не попадает в скролл). */
.tapchiprow { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-2); }
.tapchip {
  min-height: 56px;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  color: var(--c-ink);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: 0.04em;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.tapchip.is-on { background: var(--c-ink); color: var(--c-white); box-shadow: var(--plate-red); }

/* .tapseg — двухкнопочный переключатель на всю ширину строки («был» / «не пришёл»).
   Кнопки 56px, залитое состояние читается издалека и в перчатке.
   Markup: .tapseg > button.tapseg__btn[data-id][data-tone][.is-on] */
.tapseg {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: 1fr 1fr;
  border: var(--rule) solid var(--c-line);
}
.tapseg__btn {
  min-height: 56px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-surface); border: 0;
  border-right: var(--rule) solid var(--c-line);
  color: var(--c-ink);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: 0.04em;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.tapseg__btn:last-child { border-right: 0; }
.tapseg__btn.is-on { background: var(--c-ink); color: var(--c-white); }
.tapseg__btn.is-on[data-tone="positive"] { background: var(--c-green); }
.tapseg__btn.is-on[data-tone="danger"]   { background: var(--c-red); }

/* .sortbar — sort control + result count, sits under the filter row. */
.sortbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) var(--gutter);
  border-bottom: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
/* Three items share this line — count, availability checkbox, sort. */
.sortbar__count { flex: none; }
/* The sort control must not wrap to two lines when the checkbox joins the row. */
.sortbar__btn { white-space: nowrap; }

/* .checkfilter — a checkbox sized for a dense toolbar. Same square-and-red-fill
   mark as .radio-row, because a sliding track reads as a settings control and
   this is a filter sitting next to a count and a sort. */
.checkfilter {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-width: 0; cursor: pointer;
}
.checkfilter input { position: absolute; opacity: 0; pointer-events: none; }
.checkfilter__mark {
  flex: none; width: 18px; height: 18px;
  border: var(--rule) solid var(--c-ink);
  background: var(--c-surface);
  display: grid; place-items: center;
}
.checkfilter__mark::after {
  content: ""; width: 10px; height: 10px; background: var(--c-red);
  transform: scale(0); transition: transform var(--dur-fast) var(--ease-step);
}
.checkfilter:has(input:checked) .checkfilter__mark::after { transform: scale(1); }
.checkfilter .t-label { white-space: nowrap; }

.sortbar__btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: var(--tap-min); padding: 0 var(--sp-2);
  background: transparent; border: 0; cursor: pointer;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
}
.sortbar__btn::after {
  content: ""; width: 0; height: 0;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid currentColor;
}

/* ---------- 7. DAY BAND (schedule grouping) -------------------------------- */

/* .dayband — sticky ink bar opening a day group. Giant date + halftone tail. */
/* Sticky stack: .appbar owns 0–68px, and a band sticks directly under it.
   Экран, который несёт ЕЩЁ и полосу фильтров (68–130px), помечен
   `.dayband--stacked` — и там плашка вообще не липнет, потому что третий
   липкий слой съедал четверть экрана (см. правило ниже). Раньше `--stacked`
   означало «начинается ниже», а умолчанием был как раз stacked-отступ, из-за
   чего на экранах без фильтров («Мои тренировки», «Достижения», уведомления,
   слоты тренера) зиял разрыв в 62px. */
.dayband {
  position: sticky; top: 68px; z-index: 15;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--gutter);
  background: var(--c-ink); color: var(--c-white);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 22px) 100%, 0 100%);
  overflow: hidden;
}
/* Число и месяц — `flex: none` + `nowrap`: дата не сжимается и не рвётся.
   Жалоба клиента: «13 сен» в одну строку, а «16 сен» — «1 / 6», «СЕ / Н». Дело
   было не в числе, а в соседе: у 13-го подпись «послезавтра», у 16-го —
   «среда следующей недели», и на 360px флексбокс отдавал ей место, отжимая его у
   ДАТЫ. Отжать было можно из-за `body { overflow-wrap: anywhere }`: с ним
   минимальная ширина любого слова — одна буква, и «16» законно ужималось до
   «1» над «6». Переносится пусть подпись — по словам, см. `.dayband__wd`. */
.dayband__num {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); line-height: var(--lh-tight);
  font-variant-numeric: tabular-nums;
  color: var(--c-white);
  flex: none; white-space: nowrap;
}
.dayband__mo {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ochre);
  flex: none; white-space: nowrap;
}
/* Подпись дня — единственная, кому позволено переноситься, и только МЕЖДУ
   словами: «среда / следующей недели», а не «следую / щей». `min-width: 0`
   разрешает ей сжаться во флексе, `overflow-wrap: normal` запрещает резать
   слово. Неразрывный пробел в «следующей недели» ставит `relativeDay`. */
.dayband__wd {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-paper-3);
  min-width: 0; overflow-wrap: normal;
}
.dayband__tail { flex: 1; align-self: stretch; position: relative; color: var(--c-red); min-width: 24px; }
/* `flex: none` + `nowrap` — счёт тренировок отдаёт место подписи дня, а не
   ломается сам. Подпись стала длинной («понедельник следующей недели»), и на
   375px флексбокс сперва сжимал СЧЁТ: «1 тренировка» рвалась на три куска и
   поднимала плашку с 59px до 73px. Перенос по смыслу нужен подписи, счёту —
   никогда. */
.dayband__count {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-white); padding-right: var(--sp-4);
  flex: none; white-space: nowrap;
}
.dayband--today { background: var(--c-red); }
/* .dayband--stacked — расписание участника: единственный экран, где под шапкой
   стоит ещё и полоса фильтров.
   Плашка дня здесь НЕ ЛИПНЕТ. Липла она ради ориентира при прокрутке, но
   оказывалась ТРЕТЬИМ липким слоем и вместе с двумя верхними держала
   61 + 61 + 59 = 181px из 812 — почти четверть экрана, и это на телефоне, где
   от списка и так остаётся немного.
   Ориентир при этом не теряется: дата подписана в начале каждой группы, а
   день, на который смотрит участник, выбран во вкладке недели выше.
   `top` снят вместе с липкостью — у статичного элемента он не значит ничего и
   только сбивает при чтении. `.dayband` в других местах (тренерские экраны,
   карточка тренера) остаётся липкой: там над ней одна шапка, а не две. */
.dayband--stacked { position: static; }
/* .dayband--toggle — a band that folds a section. It is a <button>, so it needs
   the same reset as button.list__row, plus room for the chevron on the right. */
button.dayband--toggle {
  width: 100%; font: inherit; color: var(--c-white); text-align: left;
  border: 0; cursor: pointer;
}
.dayband__chev { margin-left: auto; padding-left: var(--sp-3); font-size: var(--fs-micro); }
.dayband--today .dayband__mo { color: var(--c-white); }
.dayband--today .dayband__wd { color: var(--c-paper-2); }
.dayband--today .dayband__tail { color: var(--c-ink); }

/* ---------- 8. SESSION CARD ------------------------------------------------ */

/* .card — generic bordered panel with an offset ink plate. */
.card {
  position: relative;
  /* An <a class="card"> is INLINE by default, and an inline box breaks its
     border and background across line boxes: the frame collapses to a stub at
     the start of the first line, and the shadow reappears as a floating scrap
     after the last one. Every card is a block, whatever tag carries it.
     `.card--session` overrides this with `display: grid` below. */
  display: block;
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  box-shadow: var(--plate);
}

/* Карточка с содержимым БЕЗ сетки `.card--session`: у `.card` своих отступов
   нет, и текст ложится вплотную к рамке. Внутренние отступы даёт `.card__body`,
   но он рассчитан на грид-области — для простой карточки нужен свой класс. */
.card--pad { padding: var(--sp-3); }

/* .card--session — schedule row.
   Grid: [time rail | body | spots score] + full-width footer strip.
   Modifiers: .is-personal (red rail), .is-soldout, .is-cancelled. */
.card--session {
  display: grid;
  grid-template-columns: 64px 1fr auto;
  grid-template-areas: "time body score" "foot foot foot";
  align-items: start;
  margin-bottom: var(--sp-4);
  min-height: 108px;
  transition: transform var(--dur-fast) var(--ease-step), box-shadow var(--dur-fast) var(--ease-step);
}
.card--session:active { transform: translate(var(--misreg-x), var(--misreg-y)); box-shadow: none; }

.card__time {
  grid-area: time;
  align-self: stretch;
  padding: var(--sp-3) var(--sp-2);
  border-right: var(--rule) solid var(--c-line);
  background: var(--c-ink); color: var(--c-white);
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  text-align: center;
}
/* .card__where — the venue on a booking's header strip. It is the one part of
   the line allowed to give ground: the time and date are short and fixed, so a
   long venue truncates instead of pushing the calendar button off the card. */
.card__where {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.card__time b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
  white-space: nowrap; letter-spacing: -0.02em;
}
.card__time span {
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--c-paper-3);
  white-space: nowrap;
}
.card__time i {
  display: block; width: 100%; height: var(--rule-hair);
  background: var(--c-red); margin: 2px 0;
}
/* .card__rate — цена в тёмной колонке времени.
   `margin-top: auto` в flex-колонке и есть «прижата к низу»: колонка тянется
   на всю высоту карточки (`align-self: stretch`), и авто-отступ съедает весь
   остаток, уводя цену вниз. Фиксированную высоту здесь задавать нельзя —
   карточка растёт от заголовка и тегов.
   Переносим по словам, а не `nowrap` как у «90 МИН»: колонка узкая (64px),
   и «Бесплатно» в неё одной строкой не встаёт. */
.card__time .card__rate {
  margin-top: auto;
  padding-top: var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); line-height: 1.1;
  letter-spacing: 0.02em;
  color: var(--c-white);
  white-space: normal;
}
.is-soldout .card__time .card__rate,
.is-cancelled .card__time .card__rate { color: var(--c-paper-3); }

.is-personal .card__time { background: var(--c-red); }
.is-personal .card__time i { background: var(--c-ink); }

.card__body { grid-area: body; padding: var(--sp-3); min-width: 0; }
.card__kicker { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: var(--sp-2); }
.card__kicker .tag { letter-spacing: 0.06em; padding-inline: 5px; }
.card__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.card__meta { font-size: var(--fs-sm); color: var(--c-ink-2); margin-top: 2px; }
.card__coach { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-2); }

.card__score {
  grid-area: score;
  border-left: var(--rule-hair) solid var(--c-line-soft);
  padding: var(--sp-3) var(--sp-3) var(--sp-2) var(--sp-3);
  text-align: right;
  min-width: 72px;
}

.card__foot {
  grid-area: foot;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-top: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
.card__price {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums;
}
.card__note { font-size: var(--fs-xs); color: var(--c-ink-2); text-align: right; }

/* The sold-out stamp sits low-left, clear of the score column. */
.card--session .stamp { top: 64%; left: 44%; font-size: var(--fs-h2); }
.card--session.is-soldout { box-shadow: none; background: var(--c-paper-2); }
.card--session.is-soldout .card__body,
.card--session.is-soldout .card__score { opacity: 0.6; }
.card--session.is-soldout .card__foot { background: var(--c-paper-3); }
.card--session.is-cancelled { border-color: var(--c-ink-3); box-shadow: none; }
.card--session.is-cancelled .card__title { text-decoration: line-through; }

/* .sessionrow — a schedule card plus the actions that belong to it. The card is
   one big link, so anything clickable has to live beside it, not inside. */
.sessionrow { margin-bottom: var(--sp-4); }
.sessionrow .card--session { margin-bottom: 0; }

/* .quickbook — «Запись по пакету» under a schedule card: one tap, no payment.
   Reads as part of the card above it, hence the shared edges and no top rule. */
.quickbook {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: var(--rule) solid var(--c-line); border-top: 0;
  background: var(--c-paper-2);
}
/* Booked already: an ink strip reads as a mark on the row, not an offer. */
.quickbook--mine { background: var(--c-ink); color: var(--c-white); border-color: var(--c-ink); }
.quickbook--mine .btn--ghost { color: var(--c-paper-2); }
/* A hold can still be LOST, so it gets the warning colour rather than the calm one. */
.quickbook--hold { background: var(--c-ochre); border-color: var(--c-ink); }
.quickbook--hold .quickbook__mark { background: var(--c-ink); }
.quickbook__mark {
  display: grid; place-items: center; flex: none;
  width: 20px; height: 20px;
  background: var(--tone-positive); color: var(--c-white);
  font-size: var(--fs-xs); font-weight: var(--fw-strong);
}
.quickbook .t-meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.quickbook .btn--ghost { margin-inline-start: auto; }

/* .card--booking — строка записи в кабинете. Stacked, not gridded: a header
   strip, the body, then a footer that is also the card's action strip.
   Markup: <article class="card card--booking">
             <div class="card__rail">[b][i][span]…<a class="iconbtn"></div>
             <div class="card__body">… <div class="card__foot">[кнопка][номер] */
/* .booking — one card and everything welded to it. */
.booking { margin-bottom: var(--sp-5); }
/* Карточка, к которой привело уведомление. Рамка, а не заливка: статус записи
   уже говорит цветом, и второй цвет спорил бы с ним. */
.booking.is-target { outline: 2px solid var(--c-red); outline-offset: 4px; }

/* Title and status share a line; the badge keeps its width and the name takes
   the rest, wrapping under itself rather than under the badge. */
.card--booking .card__head {
  display: flex; align-items: flex-start; gap: var(--sp-3);
}
.card--booking .card__head .card__title { flex: 1; min-width: 0; }
.card--booking .card__badges {
  flex: none; display: flex; flex-wrap: wrap; justify-content: flex-end;
  gap: var(--sp-2); max-width: 45%;
}
.card--booking .card__note { max-width: 62%; margin-inline-start: auto; }

/* .card__rail — WHEN · WHERE on one line, with the calendar action at its end.
   The old rail stacked them and spent three lines saying what reads as one
   phrase. `i` is the red tick between parts. */
.card__rail {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-ink); color: var(--c-white);
  border-bottom: var(--rule) solid var(--c-line);
}
.card__rail b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); line-height: 1; font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em; white-space: nowrap;
}
.card__rail span {
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--c-paper-3);
  white-space: nowrap;
}
.card__rail i {
  flex: none; width: var(--rule-hair); height: 12px; background: var(--c-red);
}
.is-personal .card__rail { background: var(--c-red); }
.is-personal .card__rail i { background: var(--c-ink); }

/* .iconbtn — a square action inside a dark strip. Icon-only, so every use MUST
   carry an aria-label. */
.iconbtn {
  flex: none; margin-inline-start: auto;
  display: grid; place-items: center;
  width: 30px; height: 30px;
  border: var(--rule) solid currentColor;
  color: inherit; background: transparent;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.iconbtn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; }
.iconbtn:active { background: var(--c-white); color: var(--c-ink); }
/* .iconbtn--red — a screen's own quick action, standing beside a display-size
   title rather than inside a dark strip. Same red/ink pairing as .btn--primary. */
.iconbtn--red {
  background: var(--c-red); color: var(--c-white); border-color: var(--c-ink);
}
.iconbtn--red:active { background: var(--c-red-deep); color: var(--c-white); }

/* .monogram — coach avatar substitute (no remote images allowed). Initials plate. */
.monogram {
  flex: none;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  background: var(--c-ink); color: var(--c-white);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: 0.02em;
}
.monogram--lg { width: 56px; height: 56px; font-size: var(--fs-h1); box-shadow: var(--plate-red); }
.monogram--red { background: var(--c-red); }

/* ---------- 9. SCOREBOARD NUMERALS ----------------------------------------- */

/* .score — the hero number. Wrap: <div class="score"><b>4</b><span>МЕСТ</span></div>
   Modifiers: --xl (screen hero), --danger/--progress/--muted for tone. */
.score { display: flex; flex-direction: column; align-items: flex-end; line-height: 1; }
.score > b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-score); line-height: var(--lh-tight);
  letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
  color: var(--c-ink);
  /* Табло не переносится: ЧЧ:ММ:СС должно стоять одной строкой. */
  white-space: nowrap;
}
.score > span {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2); margin-top: 2px; white-space: nowrap;
}
.score--sm > b { font-size: 40px; }
.score--xl > b { font-size: var(--fs-score-l); }
.score--danger  > b { color: var(--c-red); }
.score--progress> b { color: var(--c-cyan); }
.score--muted   > b { color: var(--c-ink-3); }
.score--muted   > span { color: var(--c-ink-3); }
.score--center { align-items: center; }

/* .score-plate — scoreboard set into an ink plaque (detail screens). */
.score-plate {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--c-ink); color: var(--c-white);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--plate-red);
}
.score-plate .score > b { color: var(--c-white); }
.score-plate .score > span { color: var(--c-paper-3); }
.score-plate .score--danger > b { color: var(--c-red); }
/* Табло стоит без flex-basis, поэтому длинный текст в соседнем .grow сжимал его
   и рвал число на строки. Табло не сжимается. */
.score-plate > .score { flex: none; }

/* .tickbar — счётная шкала печатными штрихами: ряд равных ячеек по бумажной
   подложке. Одна геометрия на все счётные шкалы кита; вариант задаёт шаг, высоту,
   цвет канта (--tick-gap --tick-h --tick-edge) и семантику залитой ячейки.
   Markup: <div class="tickbar tickbar--capacity"><i class="is-taken"></i>…

   Варианты:
   --capacity  занятость: закрашено = ЗАНЯТО (обратная семантика);
   --progress  выполнение: закрашено = СДЕЛАНО (недельная норма, ступень ветки);
   --ladder    пройденные ступени сезона + новая;
   --rpe       шкала нагрузки 1–10, верхние ступени кумачом;
   --fixed     ячейки фиксированной ширины, а не в долю (ступени ветки). */
.tickbar {
  display: flex; align-items: stretch;
  gap: var(--tick-gap, 3px);
  height: var(--tick-h, 16px);
}
.tickbar > i {
  flex: 1; min-width: 0;
  background: var(--c-paper-3);
  border: var(--rule-hair) solid var(--tick-edge, transparent);
}

/* --capacity — места: занято ink, свободно кумачом. Канта нет. */
.tickbar--capacity { --tick-h: 14px; }
.tickbar--capacity > i.is-taken { background: var(--c-ink); }
.tickbar--capacity > i.is-free  { background: var(--c-red); }
/* On an ink plaque the taken ticks flip to paper so they stay visible. */
.score-plate .tickbar--capacity > i.is-taken { background: var(--c-paper-3); }

/* --progress — сделано кумачом, заморожено холодным. */
.tickbar--progress { --tick-edge: var(--c-ink-3); }
.tickbar--progress > i.is-on     { background: var(--c-red);  border-color: var(--c-red); }
.tickbar--progress > i.is-frozen { background: var(--c-cyan); border-color: var(--c-cyan); }

/* --ladder — пройденные ступени ink, только что взятая — кумачом. */
.tickbar--ladder { --tick-h: 22px; --tick-edge: var(--c-line-soft); }
.tickbar--ladder > i.is-done { background: var(--c-ink); border-color: var(--c-ink); }
.tickbar--ladder > i.is-new  { background: var(--c-red); border-color: var(--c-red); }

/* --rpe — субъективная нагрузка по отметке тренера, пик кумачом. */
.tickbar--rpe { --tick-gap: 2px; --tick-h: 22px; --tick-edge: var(--c-ink-3); }
.tickbar--rpe > i.is-on   { background: var(--c-ink); border-color: var(--c-ink); }
.tickbar--rpe > i.is-peak { background: var(--c-red); border-color: var(--c-red); }

/* --fixed — ячейки не тянутся: короткая шкала из трёх ступеней читается как
   штрихи, а не как полоса во всю ширину. */
.tickbar--fixed { --tick-edge: var(--c-ink-3); height: auto; }
.tickbar--fixed > i { flex: none; width: 16px; height: 16px; }
.tickbar--fixed > i.is-on { background: var(--c-ink); border-color: var(--c-ink); }

/* .prog — доля для больших чисел (очки). Ширина заливки приходит полем percent
   через инлайновую переменную --v. Markup: .prog[style="--v:78%"] > i */
.prog { position: relative; height: 16px; background: var(--c-paper-3); border: var(--rule-hair) solid var(--c-ink-3); }
.prog > i { position: absolute; top: 0; bottom: 0; left: 0; width: var(--v, 0%); background: var(--c-red); }
/* .prog--sm — та же полоса возле счётчика, а не под крупными очками. */
.prog--sm { height: 8px; }
.score-plate .prog { background: rgba(241, 231, 211, 0.25); border-color: var(--c-paper-3); }

/* ---------- 10. TAGS & STATUS BADGES --------------------------------------- */

/* .tag — inline micro label (level, format, session type). --fill for emphasis. */
.tag {
  display: inline-flex; align-items: center;
  padding: 2px var(--sp-2) 3px;
  border: var(--rule-hair) solid var(--c-ink-2);
  color: var(--c-ink-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  line-height: 1.2;
}
.tag--fill { background: var(--c-ink); color: var(--c-white); border-color: var(--c-ink); }
.tag--accent { background: var(--c-red); color: var(--c-white); border-color: var(--c-red); }
.tag--ochre { background: var(--c-ochre); color: var(--c-ink); border-color: var(--c-ochre); }
/* .tag.is-off — канал выключен на уровне аккаунта: подпись зачёркнута. */
.tag.is-off { text-decoration: line-through; color: var(--c-ink-3); border-color: var(--c-ink-3); }

/* .badge — booking / payment status. Tone comes from data: <span class="badge"
   data-tone="progress">. Tones: neutral positive progress warning danger muted. */
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 3px var(--sp-2) 4px;
  border: var(--rule) solid currentColor;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  line-height: 1.15;
  color: var(--tone-neutral);
  background: var(--c-surface);
}
.badge::before { content: ""; width: 8px; height: 8px; background: currentColor; flex: none; }
.badge[data-tone="positive"] { color: var(--tone-positive); }
.badge[data-tone="progress"] { color: var(--tone-progress); }
.badge[data-tone="warning"]  { color: var(--tone-warning); }
.badge[data-tone="danger"]   { color: var(--tone-danger); }
.badge[data-tone="muted"]    { color: var(--tone-muted); }
/* .badge--solid — knocked out, for hero placement. */
.badge--solid { background: currentColor; border-color: currentColor; }
.badge--solid span { color: var(--c-white); }
.badge--solid::before { background: var(--c-white); }
/* Pulsing dot for "waiting for the server" states. */
.badge--live::before { animation: badgeBlink 1.1s var(--ease-step) infinite; }
@keyframes badgeBlink { 0%, 60% { opacity: 1; } 61%, 100% { opacity: 0.15; } }

/* ---------- 11. COACH CARD ------------------------------------------------- */

/* .coach — coach row/card. .is-locked marks a стажёр who cannot take personals. */
.coach {
  position: relative;
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  box-shadow: var(--plate);
  margin-bottom: var(--sp-4);
  min-height: 84px;
}
.coach__name {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.coach__role { font-size: var(--fs-sm); color: var(--c-ink-2); }
.coach__quals { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.coach.is-locked { box-shadow: none; background: var(--c-paper-2); }
.coach.is-locked .monogram { background: var(--c-ink-3); }

/* .coach__detail — quals + specialisation get the full card width under the head. */
.coach__detail { grid-column: 1 / -1; }
.coach__spec { font-size: var(--fs-sm); color: var(--c-ink-2); margin-top: var(--sp-2); }
/* .coach__foot — footer strip: next free slot on the left, action on the right. */
.coach__foot {
  grid-column: 1 / -1;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap;
  margin: var(--sp-3) calc(var(--sp-3) * -1) calc(var(--sp-3) * -1);
  padding: var(--sp-2) var(--sp-3);
  border-top: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
.coach__slot {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums; text-transform: uppercase;
}
/* .coach__lock — band explaining why a стажёр takes no personal sessions. */
.coach__lock {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3); align-items: center;
  margin: var(--sp-3) calc(var(--sp-3) * -1) 0;
  padding: var(--sp-3);
  background: var(--c-red-wash);
  border-top: var(--rule) solid var(--c-red);
  font-size: var(--fs-sm);
}
.coach__lock b {
  font-family: var(--font-display); font-size: var(--fs-h1); line-height: 1; color: var(--c-red);
}
.coach.is-locked .coach__foot { background: var(--c-paper-3); }

/* ---------- 12. LISTS & KEY-VALUE ROWS ------------------------------------- */

/* .list — bordered stack with hairline dividers. Rows are min 44px. */
.list { border-block: var(--rule) solid var(--c-line); background: var(--c-surface); }
.list__row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  min-height: var(--tap-min);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.list__row:last-child { border-bottom: 0; }
/* .list__row--actions — строка списка, справа у которой стоят кнопки.
   `.list__row` — флекс, и у кнопок по умолчанию `flex-shrink: 1`: когда слева
   длинный текст («Крылатское · 480 мин · 5000 ₽»), сжимался не он, а КНОПКИ, и
   «ПЕРЕНЕСТИ» ломалось по букве в вертикальный столбик.
   Два правила вместо одного: `nowrap` запрещает ломать САМО слово, а перенос
   строки даёт группе кнопок уехать на свою строку, когда рядом с текстом ей
   уже не встать. Одного `nowrap` не хватило бы — кнопки просто вылезли бы за
   правый край, как это было у полосы «Создать». */
.list__row--actions { flex-wrap: wrap; row-gap: var(--sp-2); }
.list__row--actions .btn { white-space: nowrap; }
/* Группа кнопок переносится ЦЕЛИКОМ и прижимается вправо, а не растекается
   по левому краю новой строки. */
.list__row--actions > .row { flex: 0 0 auto; margin-left: auto; }
/* A row that ACTS rather than navigates. A <button> brings its own UA styling —
   auto width, centred text, its own font — so it must be told to behave like
   the anchors beside it. */
button.list__row {
  width: 100%; font: inherit; color: inherit; text-align: left;
  background: none; border: 0; border-bottom: var(--rule-hair) solid var(--c-line-soft);
  cursor: pointer;
}
button.list__row:last-child { border-bottom: 0; }
.list__key {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2); flex: none;
}
.list__val { text-align: right; font-size: var(--fs-body); }
.list__val strong { font-family: var(--font-display); font-size: var(--fs-h2); }

/* .callout — admin-authored rule text (cancellation window, package terms). */
.callout {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-paper-2);
  border-left: var(--rule-thick) solid var(--c-ink);
  font-size: var(--fs-sm);
}
.callout--warn { border-left-color: var(--c-ochre); background: var(--c-paper-2); }
.callout--danger { border-left-color: var(--c-red); background: var(--c-red-wash); }
.callout__mark {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
}

/* .setting-row — строка настройки: заголовок + пояснение слева, переключатель справа.
   .is-off — выключено участником, .is-locked — выключено на уровне аккаунта. */
.setting-row {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3);
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-surface);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.setting-row:last-child { border-bottom: 0; }
.setting-row__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.setting-row__note { font-size: var(--fs-sm); color: var(--c-ink-2); }
.setting-row__chan { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.setting-row.is-off { background: var(--c-paper-2); }
.setting-row.is-off .setting-row__title { color: var(--c-ink-3); }
.setting-row.is-locked { background: var(--c-paper-2); }

/* .optin — блок согласия: заголовок, сухое пояснение и переключатель.
   Ничего не подталкивает: выключенное состояние выглядит нормальным. */
.optin {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-paper-2);
  border-left: var(--rule-thick) solid var(--c-ink-3);
}
.optin.is-on { background: var(--c-surface); border-left-color: var(--c-red); }
.optin__title {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.optin__hint { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); margin-top: 2px; }

/* ---------- 13. HERO (detail screens) -------------------------------------- */

/* .hero — session/coach detail masthead: ink field, halftone, diagonal cut. */
.hero {
  position: relative;
  background: var(--c-ink); color: var(--c-white);
  padding: var(--sp-5) var(--gutter) var(--sp-6);
  overflow: hidden;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 26px), 0 100%);
}
.hero--red { background: var(--c-red); }
.hero__dots {
  position: absolute; inset: 0;
  color: var(--c-red);
  opacity: 0.5;
  pointer-events: none;
}
.hero--red .hero__dots { color: var(--c-ink); opacity: 0.35; }
.hero__kicker { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); position: relative; }
/* Outline tags on the dark hero need paper-weight ink to stay AA. */
.hero .tag { border-color: var(--c-paper-3); color: var(--c-paper-2); }
.hero__title {
  position: relative;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-head);
  text-transform: uppercase; letter-spacing: var(--ls-head);
}
.hero__sub { position: relative; margin-top: var(--sp-2); color: var(--c-paper-2); font-size: var(--fs-sm); }
/* .hero__sub--row — a fact that is also a control: the phone and the «Изменить»
   that changes it share one line. The ghost button is drawn for paper, so on the
   ink field it takes paper weight — same move as the install bar. */
.hero__sub--row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.hero .btn--ghost { color: var(--c-paper-2); }
.hero .btn--ghost:hover { color: var(--c-white); }
.hero__strip {
  position: relative;
  display: flex; gap: var(--sp-4); margin-top: var(--sp-4);
  padding-top: var(--sp-3); border-top: var(--rule) solid rgba(241,231,211,0.3);
}
.hero__fact b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
}
/* .hero__fact--text — факт без числа. У остальных подпись стоит ПОД крупной
   цифрой, поэтому одиночную подпись прижимаем к низу строки: иначе она
   повиснет вверху и встанет не по той линии, что соседние. */
.hero__fact--text { align-self: end; }
.hero__fact span {
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--c-paper-3);
}

/* ---------- 14. COUNTDOWN --------------------------------------------------- */

/* .countdown — the 15-minute hold. Big mono digits, cyan plate, vertical label.
   .is-urgent flips the plate to red for the last minutes. */
.countdown {
  position: relative;
  display: grid; grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: center;
  background: var(--c-ink); color: var(--c-white);
  padding: var(--sp-4);
  box-shadow: var(--misreg-x) var(--misreg-y) 0 0 var(--c-cyan);
}
.countdown__rail {
  align-self: stretch;
  display: grid; place-items: center;
  background: var(--c-cyan); color: var(--c-white);
  padding: var(--sp-2) 4px;
}
.countdown__body { text-align: center; }
.countdown__digits {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-score-l); line-height: var(--lh-tight);
  letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
}
.countdown__digits .sep { animation: colonBlink 1s var(--ease-step) infinite; }
@keyframes colonBlink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0.2; } }
.countdown__cap {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-paper-3);
}
.countdown.is-urgent { box-shadow: var(--misreg-x) var(--misreg-y) 0 0 var(--c-red); }
.countdown.is-urgent .countdown__rail { background: var(--c-red); }
.countdown.is-urgent .countdown__digits { color: var(--c-red); }
/* .countdown__track — depleting tick row under the digits. */
.countdown__track { display: flex; gap: 2px; margin-top: var(--sp-3); }
.countdown__track i { flex: 1; height: 6px; background: var(--c-cyan); }
.countdown__track i.is-spent { background: rgba(241,231,211,0.22); }
.countdown.is-urgent .countdown__track i { background: var(--c-red); }

/* ---------- 15. FORM CONTROLS ---------------------------------------------- */

.field { display: block; margin-bottom: var(--sp-4); }
.field__label {
  display: block; margin-bottom: var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}
.field__hint { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--c-ink-3); }
.field__hint--error { color: var(--c-red); }

/* .field__error — сообщение об ошибке под полем.
   Класс писали в разметке с самого начала (восемь мест: запросы, карточка
   тренера, расписание, страница тренера), а правила для него не было НИ ОДНОЙ —
   и всякий отказ показывался обычным текстом абзаца: тем же кеглем и тем же
   цветом, что подпись рядом. Читалось как пояснение, а не как «не вышло».
   Определяем через ту же геометрию, что у подсказки: разница между ними
   ровно в цвете, и держать две несвязанные записи означало бы разъехаться. */
.field__error {
  margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--c-red);
}

/* .input / .textarea / .select — flat, square, 2px ink border, no radius. */
.input, .textarea, .select {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--sp-3);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  border-radius: var(--r-0);
  font-size: var(--fs-body);
  transition: box-shadow var(--dur-fast) var(--ease-out);
}
.input:focus, .textarea:focus, .select:focus { outline: 0; box-shadow: var(--plate-cyan); }
.textarea { min-height: 96px; resize: vertical; line-height: var(--lh-body); }
.select {
  appearance: none;
  padding-right: var(--sp-8);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--c-ink) 50%),
    linear-gradient(135deg, var(--c-ink) 50%, transparent 50%);
  background-position: right 20px center, right 14px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.input[disabled], .textarea[disabled], .select[disabled] {
  background: var(--c-paper-3); color: var(--c-ink-3); cursor: not-allowed;
}

/* .radio-list — payment method chooser. Each row: label > input + content.
   .is-disabled rows keep their reason text visible. */
.radio-list { display: grid; gap: var(--sp-3); }
.radio-row {
  position: relative;
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3);
  align-items: center;
  min-height: 64px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  cursor: pointer;
  transition: box-shadow var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.radio-row input { position: absolute; opacity: 0; pointer-events: none; }
/* The mark is a square that gets an ink cross-fill when checked. */
.radio-row__mark {
  flex: none; width: 24px; height: 24px;
  border: var(--rule) solid var(--c-ink);
  display: grid; place-items: center;
}
.radio-row__mark::after {
  content: ""; width: 12px; height: 12px; background: var(--c-red); transform: scale(0);
  transition: transform var(--dur-fast) var(--ease-step);
}
.radio-row:has(input:checked) { box-shadow: var(--plate-red); background: var(--c-white); }
.radio-row:has(input:checked) .radio-row__mark::after { transform: scale(1); }
.radio-row:has(input:focus-visible) { outline: var(--rule) solid var(--c-focus); outline-offset: 2px; }
.radio-row__title {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.radio-row__note { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); margin-top: 2px; }
.radio-row .score, .radio-row .card__price, .radio-row .tag { justify-self: end; text-align: right; }
.radio-row.is-disabled {
  cursor: not-allowed; background: var(--c-paper-2); color: var(--c-ink-3);
  border-color: var(--c-ink-3); box-shadow: none;
  background-image: repeating-linear-gradient(-45deg, rgba(23,19,14,0.07) 0 6px, transparent 6px 14px);
}
.radio-row.is-disabled .radio-row__mark { border-color: var(--c-ink-3); }
.radio-row.is-disabled .radio-row__note { color: var(--c-red); }

/* .switch — freeze/transfer toggles. Square knob, no rounding. */
.switch { display: inline-flex; align-items: center; gap: var(--sp-3); min-height: var(--tap-min); cursor: pointer; }
.switch input { position: absolute; opacity: 0; }
.switch__track {
  width: 52px; height: 28px; background: var(--c-paper-3);
  border: var(--rule) solid var(--c-ink); position: relative;
}
.switch__track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  background: var(--c-ink);
  transition: transform var(--dur) var(--ease-step), background var(--dur) var(--ease-out);
}
.switch input:checked + .switch__track { background: var(--c-red); }
.switch input:checked + .switch__track::after { transform: translateX(24px); background: var(--c-white); }
/* Недоступный переключатель должен читаться как выключенный, а не как «выкл». */
.switch:has(input:disabled) { cursor: not-allowed; color: var(--c-ink-3); }
.switch input:disabled + .switch__track { opacity: 0.45; }
.switch.is-locked { cursor: not-allowed; }

/* .code — ввод одноразового кода. Один настоящий input лежит поверх ячеек,
   ячейки только рисуют цифры. Никаких паролей и токенов в UI.
   Markup: .code > input.code__input + .code__cell × 6 */
.code { position: relative; display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--sp-2); }
.code__input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; z-index: var(--z-raised);
  border: 0; background: transparent;
}
.code__cell {
  min-height: 56px;
  display: grid; place-items: center;
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); font-variant-numeric: tabular-nums;
}
.code__cell.is-filled { box-shadow: var(--plate); }
.code.is-focus .code__cell.is-next { box-shadow: var(--plate-cyan); border-color: var(--c-cyan); }
.code.is-error .code__cell { border-color: var(--c-red); background: var(--c-red-wash); box-shadow: none; }

/* .stepper — число с двумя крупными кнопками: правка подставленного значения без
   набора с клавиатуры. Поле остаётся вводимым для редких больших правок.
   Markup: .stepper > button.stepper__btn + input.stepper__val + button.stepper__btn */
.stepper {
  display: grid; grid-template-columns: 64px 1fr 64px;
  border: var(--rule) solid var(--c-line);
  background: var(--c-surface);
}
.stepper__btn {
  min-height: 64px;
  background: var(--c-ink); color: var(--c-white); border: 0;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); line-height: 1; cursor: pointer;
}
.stepper__btn:active { background: var(--c-red); }
.stepper__val {
  min-width: 0;
  border: 0; background: transparent;
  text-align: center;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-score); line-height: 1; letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
}
.stepper__val:focus { outline: 0; background: var(--c-paper-2); }

/* .rpe — ввод субъективной нагрузки 1–10 двумя рядами по пять: каждая цель 56px,
   заливка растёт вместе с числом (шкала, а не набор кнопок). Показ той же шкалы
   участнику — .tickbar--rpe. Markup: .rpe > button.rpe__btn[.is-on|.is-under] */
.rpe { display: grid; grid-template-columns: repeat(5, 1fr); gap: 2px; }
.rpe__btn {
  min-height: 56px;
  background: var(--c-surface);
  border: var(--rule-hair) solid var(--c-line);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.rpe__btn.is-on { background: var(--c-ink); color: var(--c-white); }
.rpe__btn.is-under { background: var(--c-paper-3); }

/* Закрытая тренировка: элементы ввода гаснут, но отметки остаются читаемыми. */
.tapseg__btn[disabled], .tapchip[disabled],
.rpe__btn[disabled], .stepper__btn[disabled] { cursor: not-allowed; opacity: 0.5; }
.stepper__val[disabled], .input[disabled] { color: var(--c-ink-3); }

/* .slotgrid / .slot — сетка временных плашек для персональной тренировки.
   States: free (default), .is-on (chosen), [disabled] (taken / held). */
/* .install — «поставьте приложение на домашний экран». A full sheet, because it
   is asked once and answered once; «Продолжить в браузере» is the way past it
   and is deliberately quiet, not hidden. */
.install {
  position: fixed; inset: 0; z-index: var(--z-modal, 90);
  overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-8) var(--gutter) calc(var(--sp-8) + env(safe-area-inset-bottom, 0));
  background: var(--c-paper);
  text-align: center;
}
/* Quieter than a hero's dots: this screen is mostly small text, and the raster
   competes with it at the density the posters use. */
.install__dots { position: absolute; inset: 0; color: var(--c-red); opacity: 0.10; pointer-events: none; }
.install__icon {
  position: relative; flex: none;
  width: 112px; height: 112px;
  border: var(--rule) solid var(--c-line);
  box-shadow: var(--plate);
}
.install__title {
  position: relative;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); line-height: 1; letter-spacing: var(--ls-head);
  text-transform: uppercase;
}
.install__line { position: relative; max-width: 30ch; font-size: var(--fs-sm); color: var(--c-ink-2); }
.install .btn { position: relative; }

/* Numbered because the order matters — this is a route through someone else's
   UI, not a list of features. */
.install__steps {
  position: relative; text-align: left;
  display: grid; gap: var(--sp-2);
  margin: 0; padding-inline-start: var(--sp-6);
  font-size: var(--fs-sm);
}
.install__steps li { list-style: decimal; }
.install__glyph {
  display: inline-block; vertical-align: -4px;
  width: 18px; height: 18px;
  stroke: currentColor; fill: none; stroke-width: 2;
}
.install__note {
  position: relative; max-width: 32ch;
  font-size: var(--fs-xs); color: var(--c-ink-2);
}
.install__skip { margin-top: var(--sp-2); }

/* .install--bar — «уже установлено». A strip above the tab bar rather than a
   screen, because there is no action behind it: a browser cannot open an
   installed app, so this is information, not an offer. */
.install--bar {
  inset: auto 0 0 0;
  flex-direction: row; align-items: center; justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--gutter);
  padding-bottom: calc(var(--sp-3) + 62px + env(safe-area-inset-bottom, 0));
  background: var(--c-ink); color: var(--c-white);
  text-align: left; font-size: var(--fs-xs);
}
.install--bar .btn--ghost { color: var(--c-paper-2); flex: none; }

/* .starbtn — favourite toggle on a coach card. Sits ON the name line, so it
   reads as a property of the coach rather than another action in the footer. */
.starbtn {
  border: 0; background: transparent; cursor: pointer;
  padding: 0 var(--sp-1); margin-inline-start: var(--sp-2);
  font-size: var(--fs-h1); line-height: 1; color: var(--c-ink-3);
  transition: color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-step);
}
.starbtn.is-on { color: var(--c-ochre); }
.starbtn:active { transform: scale(0.88); }
.starbtn[disabled] { opacity: 0.5; cursor: wait; }

/* .coach__seen — «you have trained with them», derived from bookings. Quiet:
   it explains the order, it is not a badge to collect. */
.coach__seen {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-green);
}

/* .dayhead — the vertical date plate with its weekday beside it. The rotated
   number is compact but unreadable at a glance; the horizontal line is what a
   member actually reads to find «завтра». */
.dayhead { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.dayhead .rail-label { margin: 0; }
.dayhead__wd {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-sm); letter-spacing: var(--ls-label); text-transform: uppercase;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dayhead__wd i { font-style: normal; color: var(--c-ink-2); }

.slotgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--sp-2); }
.slot {
  min-height: 56px;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  border-radius: var(--r-0);
  cursor: pointer; text-align: left;
  transition: transform var(--dur-fast) var(--ease-step), box-shadow var(--dur-fast) var(--ease-step);
}
.slot b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); line-height: 1; font-variant-numeric: tabular-nums;
}
.slot span {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}
.slot.is-on { background: var(--c-ink); color: var(--c-white); box-shadow: var(--plate-red); }
.slot.is-on span { color: var(--c-paper-3); }
.slot[disabled] {
  cursor: not-allowed; color: var(--c-ink-3);
  border-color: var(--c-ink-3); background: var(--c-paper-2);
  background-image: repeating-linear-gradient(-45deg, rgba(23,19,14,0.10) 0 6px, transparent 6px 14px);
}
.slot[disabled] b { text-decoration: line-through; }
.slot[disabled] span { color: var(--c-ink-3); }

/* ---------- 16. SHEET / MODAL ---------------------------------------------- */

/* .scrim — ink wash behind a sheet. Add .is-open to show. */
.scrim {
  position: fixed; inset: 0; z-index: var(--z-scrim);
  background: rgba(23, 19, 14, 0.72);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur) var(--ease-out), visibility var(--dur);
}
.scrim.is-open { opacity: 1; visibility: visible; }

/* .sheet — bottom sheet. Add .is-open to slide up. .sheet--modal centres it. */
.sheet {
  position: fixed; left: 50%; bottom: 0; transform: translate(-50%, 100%);
  width: 100%; max-width: var(--shell-max);
  z-index: var(--z-sheet);
  background: var(--c-paper);
  border-top: var(--rule-heavy) solid var(--c-red);
  transition: transform var(--dur-slow) var(--ease-out);
  max-height: 86vh; overflow-y: auto;
}
.sheet.is-open { transform: translate(-50%, 0); }
.sheet__head {
  position: sticky; top: 0;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-4) var(--gutter);
  background: var(--c-paper);
  border-bottom: var(--rule) solid var(--c-line);
}
.sheet__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.sheet__body { padding: var(--sp-4) var(--gutter) var(--sp-6); }
.sheet--modal {
  bottom: auto; top: 50%; transform: translate(-50%, -40%);
  max-width: calc(var(--shell-max) - var(--sp-8));
  border: var(--rule-thick) solid var(--c-ink);
  box-shadow: 6px 6px 0 0 var(--c-red);
  opacity: 0; visibility: hidden;
}
.sheet--modal.is-open { transform: translate(-50%, -50%); opacity: 1; visibility: visible; }

/* .sheet--full — полноэкранный лист фильтров: шапка и подвал закреплены, скроллит
   только список фасетов. Останавливается под шапкой, чтобы расписание оставалось
   видно сверху. */
.sheet--full {
  top: calc(var(--sp-16) + var(--sp-6));
  bottom: 0; max-height: none; overflow: hidden;
  display: flex; flex-direction: column;
}
.sheet--full .sheet__head { flex: none; }
.sheet--full .sheet__body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; }
/* .sheet__foot — the sheet's own action bar; same anatomy as .btn-row. */
.sheet__foot {
  flex: none;
  display: grid; gap: var(--sp-2);
  padding: var(--sp-3) var(--gutter) var(--sp-4);
  background: var(--c-paper);
  border-top: var(--rule-thick) solid var(--c-line);
}
/* .facet__head — label + live selection count above a facet's chips. */
.facet { padding-top: var(--sp-5); }
.facet + .facet { border-top: var(--rule-hair) solid var(--c-line-soft); }
.facet__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
/* Page behind a full-height sheet must not scroll. */
body.is-sheet-locked { overflow: hidden; }

/* ---------- 17. STATE BLOCKS (empty / loading / error) --------------------- */

/* .state — centred block with an ornament mark, title, copy and one action. */
.state {
  display: grid; justify-items: center; gap: var(--sp-3);
  text-align: center;
  padding: var(--sp-10) var(--gutter);
}
.state__mark {
  position: relative;
  width: 96px; height: 96px;
  display: grid; place-items: center;
  border: var(--rule-thick) solid var(--c-ink);
  color: var(--c-ink);
  background: var(--c-paper-2);
}
.state__mark span {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 44px; line-height: 1;
}
.state__mark--danger { border-color: var(--c-red); color: var(--c-red); background: var(--c-red-wash); }
.state__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.state__text { font-size: var(--fs-sm); color: var(--c-ink-2); max-width: 30ch; }

/* .skel — loading placeholder bars. Mechanical step-shimmer, not a soft pulse. */
.skel { background: var(--c-paper-3); position: relative; overflow: hidden; }
.skel::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(-45deg,
    rgba(23,19,14,0.10) 0 8px, transparent 8px 16px);
  animation: skelMove 900ms var(--ease-step) infinite;
}
@keyframes skelMove { from { transform: translateX(0); } to { transform: translateX(22px); } }
.skel--line { height: 14px; margin-bottom: var(--sp-2); }
.skel--title { height: 26px; width: 62%; margin-bottom: var(--sp-3); }
.skel--block { height: 108px; margin-bottom: var(--sp-4); }

/* .actionbar — a decision that must stay reachable. Parked ABOVE .tabbar (which
   is fixed and would otherwise cover it), so picking a slot at the top of a long
   list still leaves the confirm button on screen. Same clearance arithmetic as
   .app's bottom padding: 58px item + 4px rule + the home-indicator inset. */
.actionbar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(62px + env(safe-area-inset-bottom, 0));
  width: 100%; max-width: var(--shell-max);
  z-index: var(--z-tabbar);
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-3) var(--gutter);
  background: var(--c-paper);
  border-top: var(--rule-heavy) solid var(--c-red);
}

/* ---------- 18. TOAST / NOTIFICATION ROW ----------------------------------- */

/* .toast — fixed confirmation row. .notif-row — the same anatomy inside a feed. */
.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(58px + var(--sp-4));
  width: calc(100% - var(--gutter) * 2); max-width: calc(var(--shell-max) - var(--gutter) * 2);
  z-index: var(--z-toast);
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3); align-items: center;
  padding: var(--sp-3);
  background: var(--c-ink); color: var(--c-white);
  border-left: var(--rule-heavy) solid var(--c-red);
  box-shadow: var(--plate-red);
}
.toast[data-tone="positive"] { border-left-color: var(--c-green); }
.toast[data-tone="progress"] { border-left-color: var(--c-cyan); }
.toast__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.toast__text { font-size: var(--fs-sm); color: var(--c-paper-2); }

.notif-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3);
  padding: var(--sp-3) var(--gutter);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
  background: var(--c-surface);
  align-items: start;
}
.notif-row.is-unread { background: var(--c-paper-2); border-left: var(--rule-thick) solid var(--c-red); }
.notif-row__mark { width: 10px; height: 10px; margin-top: 6px; background: var(--c-ink); }
.notif-row[data-tone="positive"] .notif-row__mark { background: var(--c-green); }
.notif-row[data-tone="progress"] .notif-row__mark { background: var(--c-cyan); }
.notif-row[data-tone="danger"] .notif-row__mark { background: var(--c-red); }
.notif-row[data-tone="warning"] .notif-row__mark { background: var(--tone-warning); }
.notif-row[data-tone="muted"]   .notif-row__mark { background: var(--tone-muted); }
.notif-row__title { font-family: var(--font-display); font-weight: var(--fw-strong); font-size: var(--fs-h2); text-transform: uppercase; }
.notif-row__text { font-size: var(--fs-sm); color: var(--c-ink-2); }
.notif-row__time { font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--c-ink-3); white-space: nowrap; }
/* .notif-row__body / __act — вторая строка внутри уведомления: теги каналов и действие. */
/* display:block — тело бывает <a> (уведомление ведёт на свой экран), а инлайн
   якорь с <b> и <p> внутри ломает сетку строки. */
.notif-row__body { min-width: 0; display: block; }
.notif-row__act { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
.notif-row.is-read .notif-row__title { color: var(--c-ink-2); }

/* ---------- 19. WEEK STRIP & MONTH GRID ------------------------------------ */

/* .weekstrip — 7 day buttons, scrollable. .is-on = selected day. */
.weekstrip { display: grid; grid-template-columns: repeat(7, 1fr); border: var(--rule) solid var(--c-line); background: var(--c-surface); }
.weekstrip__day {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-height: 62px; padding: var(--sp-2) 2px;
  border-right: var(--rule-hair) solid var(--c-line-soft);
  background: transparent; cursor: pointer;
}
.weekstrip__day:last-child { border-right: 0; }
.weekstrip__wd { font-family: var(--font-display); font-size: var(--fs-micro); letter-spacing: 0.06em; text-transform: uppercase; color: var(--c-ink-3); }
.weekstrip__num { font-family: var(--font-display); font-weight: var(--fw-strong); font-size: var(--fs-h2); font-variant-numeric: tabular-nums; line-height: 1; }
.weekstrip__dot { width: 100%; height: 4px; background: var(--c-paper-3); }
.weekstrip__dot.has-free { background: var(--c-red); }
.weekstrip__dot.is-full { background: var(--c-ink-3); }
.weekstrip__day.is-on { background: var(--c-ink); color: var(--c-white); }
.weekstrip__day.is-on .weekstrip__wd { color: var(--c-paper-3); }

/* .weekstrip__count — сколько тренировок в этом дне. Полоса ВЫБИРАЕТ день, и
   под ней стоит только он, поэтому число здесь — единственный способ увидеть,
   что происходит в остальных днях: выбранный пустой день перестаёт быть
   тупиком. `<u>` браузер подчёркивает сам, а это цифра, не ссылка. */
.weekstrip__count {
  font-family: var(--font-display); font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums; text-decoration: none;
  color: var(--c-ink-3);
}
.weekstrip__day.is-on .weekstrip__count { color: var(--c-paper-3); }

/* «Сегодня» и «выбрано» — разные факты, а `.is-on` до сих пор означала оба:
   полоса была якорями, и выбирать в ней было нечего. Выбор — плашка, сегодня —
   красная краска на подписи дня. */
.weekstrip__day.is-today .weekstrip__wd { color: var(--c-red); }
.weekstrip__day.is-on.is-today .weekstrip__wd { color: var(--c-white); }

/* Правило `:has(.daymark)` ниже красит день «вы записаны» в зелёный и по
   специфичности сильнее плашки выбора. Подсечка снизу читается на любом фоне,
   поэтому выбранный день виден и там. */
.weekstrip__day.is-on { box-shadow: inset 0 -4px 0 var(--c-red); }

/* .monthgrid — 7-column calendar. Cells carry a load bar, not a dot cloud. */
.monthgrid { display: grid; grid-template-columns: repeat(7, 1fr); border-top: var(--rule) solid var(--c-line); border-left: var(--rule) solid var(--c-line); }
.monthgrid__head, .monthgrid__cell {
  border-right: var(--rule-hair) solid var(--c-line-soft);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
  min-height: 52px;
  padding: 3px;
  background: var(--c-surface);
}
.monthgrid__head { min-height: 26px; display: grid; place-items: center; background: var(--c-ink); color: var(--c-white); }
.monthgrid__head span { font-family: var(--font-display); font-size: var(--fs-micro); letter-spacing: 0.06em; text-transform: uppercase; }
.monthgrid__cell { display: flex; flex-direction: column; justify-content: space-between; cursor: pointer; }
.monthgrid__cell b { font-family: var(--font-display); font-weight: var(--fw-strong); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.monthgrid__cell.is-out { background: var(--c-paper-2); color: var(--c-ink-3); }   /* вне месяца */
.monthgrid__cell.is-empty { background: var(--c-paper-2); color: var(--c-ink-2); }  /* нет тренировок */
.monthgrid__cell.is-on { background: var(--c-red); color: var(--c-white); }
/* .is-past — a day already gone. The grid keeps it for orientation but mutes it
   so the eye skips ahead. Distinct from .is-out, which is a neighbouring
   month's filler cell. */
.monthgrid__cell.is-past { background: var(--c-paper-2); color: var(--c-ink-3); }
.monthgrid__cell.is-past b { opacity: 0.5; }
/* Today reuses .is-on's red — the same "you are here" the dayband uses. */
.monthgrid__cell.is-on b { color: var(--c-white); }
/* .daymark — «you are booked this day». Rendered INVISIBLY inside the day cell;
   the cell itself goes green through `:has()`. Green is the app's only
   non-poster hue and already means confirmed/paid, so it keeps that meaning
   here. The element carries the words for screen readers — the colour alone is
   not information. */
.monthgrid__cell:has(.daymark),
.weekstrip__day:has(.daymark) {
  background: var(--c-green); color: var(--c-white);
}
.monthgrid__cell:has(.daymark) b { color: var(--c-white); opacity: 1; }
/* Подписи внутри зелёного дня несли свои чернила и печатались тёмным по
   зелёному. Заметно стало, когда полоса недели превратилась в выбор дня и её
   пришлось читать, а не проматывать. */
.weekstrip__day:has(.daymark) .weekstrip__wd,
.weekstrip__day:has(.daymark) .weekstrip__count { color: rgba(255, 255, 255, 0.78); }
.monthgrid__cell:has(.daymark) .monthgrid__load i { background: var(--c-white); }
.monthgrid__cell:has(.daymark) .monthgrid__load i.is-full { background: var(--c-paper-3); }
.weekstrip__day:has(.daymark) i,
.weekstrip__day:has(.daymark) u { color: var(--c-paper-2); }
/* Today keeps its own plate — «you are here» outranks «you are booked» — but
   takes a green rule so the day is still marked. */
.monthgrid__cell.is-on:has(.daymark),
.weekstrip__day.is-on:has(.daymark) {
  background: var(--c-red); color: var(--c-white);
  box-shadow: inset 0 -4px 0 0 color-mix(in srgb, var(--c-green) 45%, white);
}
.weekstrip__day.is-on:has(.daymark) { background: var(--c-ink); }

.monthgrid__load { display: flex; gap: 2px; }
.monthgrid__load i { flex: 1; height: 5px; background: var(--c-ink); }
.monthgrid__load i.is-full { background: var(--c-paper-3); }
.monthgrid__cell.is-on .monthgrid__load i { background: var(--c-white); }

/* ---------- 20. PAYMENT RESULT & TICKET ------------------------------------ */

/* .statuspair — платёж и бронирование живут отдельно, поэтому статусы стоят рядом
   двумя равными колонками. Markup: .statuspair > div > (.t-label + .badge). */
.statuspair {
  display: grid; grid-template-columns: 1fr 1fr;
  border: var(--rule) solid var(--c-line);
  background: var(--c-surface);
  box-shadow: var(--plate);
}
.statuspair > div {
  min-width: 0;
  padding: var(--sp-3) var(--sp-2);
  border-right: var(--rule) solid var(--c-line);
  display: grid; gap: var(--sp-2); align-content: start; justify-items: start;
}
.statuspair > div:last-child { border-right: 0; }
/* Длинные статусы («Возврат инициирован») должны влезать в половину колонки. */
.statuspair .badge { max-width: 100%; font-size: var(--fs-micro); }

/* .wait-bar — неопределённое ожидание вебхука: марширующая косая заливка cyan.
   Markup: <div class="wait-bar"><i></i></div>. При reduced-motion остаётся статичной. */
.wait-bar {
  position: relative; height: 14px; overflow: hidden;
  border: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
.wait-bar > i {
  position: absolute; inset: 0 -32px;
  background-image: repeating-linear-gradient(-45deg,
    var(--c-cyan) 0 8px, transparent 8px 16px);
  animation: waitMarch 700ms linear infinite;
}
@keyframes waitMarch { from { transform: translateX(0); } to { transform: translateX(22.7px); } }

/* .ticket — билет-подтверждение: ink-шапка, слаб-цифры, перфорация и корешок с
   данными чека. Markup: .ticket > .ticket__head + .ticket__body + .ticket__perf +
   .ticket__stub, плюс абсолютная .seal. */
.ticket { position: relative; background: var(--c-surface); border: var(--rule) solid var(--c-line); box-shadow: var(--plate-red); }
.ticket__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--c-ink); color: var(--c-white);
}
.ticket__head .t-mono { font-size: var(--fs-xs); color: var(--c-paper-2); }
.ticket__body { padding: var(--sp-4); padding-right: var(--sp-6); }
.ticket__nums { display: flex; flex-wrap: wrap; gap: var(--sp-5); margin: var(--sp-3) 0; }
.ticket__num b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-tight); letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
}
.ticket__num span {
  display: block; margin-top: var(--sp-1);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}
.ticket__perf {
  position: relative; height: var(--sp-5);
  border-block: var(--rule-hair) solid var(--c-line-soft);
  background-image: radial-gradient(circle at 9px 50%, var(--c-bg) 3.5px, transparent 4px);
  background-size: 18px 100%;
}
.ticket__perf::before, .ticket__perf::after {
  content: ""; position: absolute; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: var(--r-seal); background: var(--c-bg);
}
.ticket__perf::before { left: -11px; }
.ticket__perf::after  { right: -11px; }
.ticket__stub { padding: var(--sp-3) var(--sp-4); background: var(--c-paper-2); }
.ticket__stub .list__row { padding-inline: 0; border-bottom-color: var(--c-paper-3); }

/* ---------- 21. STEP TRACKER ------------------------------------------------ */

/* .steps — four-stage tracker for the request → proposal → confirm → pay loop.
   Markup: <ol class="steps"><li class="steps__item is-done"><b class="steps__n">1</b>
           <span class="steps__cap">Запрос</span></li>… */
.steps { display: flex; gap: var(--sp-1); }
.steps__item { flex: 1; display: grid; justify-items: center; gap: var(--sp-1); text-align: center; }
.steps__n {
  width: 32px; height: 32px; display: grid; place-items: center;
  border: var(--rule) solid var(--c-ink-3); color: var(--c-ink-3);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-body); line-height: 1; font-variant-numeric: tabular-nums;
}
.steps__cap {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-3); line-height: 1.15;
}
.steps__item.is-done .steps__n { background: var(--c-ink); border-color: var(--c-ink); color: var(--c-white); }
.steps__item.is-done .steps__cap { color: var(--c-ink-2); }
.steps__item.is-now .steps__n { background: var(--c-red); border-color: var(--c-red); color: var(--c-white); box-shadow: var(--plate); }
.steps__item.is-now .steps__cap { color: var(--c-ink); }

/* ---------- 22. PACKAGE CARD ------------------------------------------------ */

/* .pkg — карточка пакета. Markup:
   .card.pkg > .pkg__head (плашка: название + статус)
             > .pkg__body (табло остатка, шкала, срок)
             > .list      (правила пакета)
             > .pkg__foot (заморозка и передача)
   Модификатор .is-off — израсходованный или истёкший пакет. */
.pkg { margin-bottom: var(--sp-6); }
.pkg__head {
  display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-ink); color: var(--c-white);
}
.pkg__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
  overflow-wrap: normal;           /* название пакета не рвём посреди слова */
}
.pkg__sub { font-size: var(--fs-sm); color: var(--c-paper-3); }
/* Бейдж статуса на чёрной плашке остаётся на бумажной подложке — иначе
   тон-цвет теряет контраст на ink. */
.pkg__head .badge { flex: none; }
.pkg__body { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-4); padding: var(--sp-4); }
/* .pkg__meter — левая колонка пакета: крупный ОСТАТОК, полоса, общее число.
   min-width держит полосу читаемой: колонка `auto` сжалась бы по ширине
   числа, и у пакета из одного занятия полоса вышла бы уже подписи. */
/* `align-content: start` обязателен: колонка тянется по высоте соседней (там
   правила пакета на несколько строк), и по умолчанию лишняя высота
   раздавалась бы между строками — число, полоса и «из 10» расползались. */
.pkg__meter {
  display: grid; gap: var(--sp-2);
  justify-items: start; align-content: start;
  min-width: 92px;
}
.pkg__meter .prog { width: 100%; }
/* Общее число встаёт под правый конец полосы: остаток слева, всего справа —
   полоса читается как отрезок между ними. Соседний селектор бьёт только по
   подписи ПОСЛЕ полосы: когда `classes_total` нет, полосы тоже нет, и
   одинокая подпись остаётся у левого края вместе с числом. */
.pkg__meter .prog + .t-meta { justify-self: end; }
.pkg__body > * { min-width: 0; }
.pkg__types { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
/* .pkg__rules — правила пакета словами клуба: подпись слева, текст слева же,
   иначе длинные предложения ломают выключку в .list. */
.pkg__rules { display: grid; gap: var(--sp-3); padding: var(--sp-4); border-top: var(--rule) solid var(--c-line); }
.pkg__rules > div { display: grid; grid-template-columns: 84px 1fr; gap: var(--sp-3); align-items: baseline; }
.pkg__rules p { font-size: var(--fs-sm); }
.pkg__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  border-top: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
/* .pkg__foot--end — for a foot holding only the commit action («Купить»), which
   belongs at the trailing edge. `space-between` alone leaves a lone child on the
   left, where a buy button reads like an afterthought. */
.pkg__foot--end { justify-content: flex-end; }
/* Действие пакета стоит справа. Двигаем именно КНОПКУ, а не выключку всего
   подвала: когда заморозка уже израсходована, вместо кнопки там объясняющая
   строка, и её место — слева, как у обычного текста.

   Селектор прямого потомка обязателен: в форме заморозки внутри подвала лежит
   ещё один `.btn-row` с «Подтвердить» и «Отмена», и потомковый селектор прижал
   бы вправо и их, сломав вёрстку формы. */
.pkg__foot > .btn { margin-left: auto; }
/* .pkg__foot--freeze — форма заморозки. Её пара кнопок стоит в СТРОКУ:
   `.btn-row` — грид в одну колонку, и подтверждение с отменой уезжали друг под
   друга на всю ширину. Флекс с переносом, а не грид в две колонки: на 320 px
   «Подтвердить заморозку» и «Отмена» рядом уже не помещаются, и там перенос
   нужнее ровной сетки. */
.pkg__foot--freeze .btn-row {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--sp-5);
  /* Поля снимаются: `.btn-row` несёт их для случая, когда полоса кнопок стоит
     сама по себе, а здесь она лежит ВНУТРИ подвала, который уже отбит. Два
     отступа подряд съедали 32px — ровно столько не хватало паре кнопок, чтобы
     встать в строку. */
  padding-inline: 0;
}
/* Отмена — подчёркнутая ссылка, поля ей ничего не дают, а строку ломают. */
.pkg__foot--freeze .btn--ghost { padding-inline: 0; }
/* Подсказка про срок встаёт РЯДОМ с полем даты, а не строкой под ним: поле
   узкое, и целая пустая строка под ним разрывала форму надвое. `flex: 1 1 0`
   с `min-width: 0` заставляет подсказку занять остаток строки и переноситься
   внутри себя, а не выталкивать себя на следующую. */
.pkg__foot--freeze .field { flex: 0 0 auto; margin-bottom: 0; }
.pkg__foot--freeze .field__hint { flex: 1 1 0; min-width: 0; margin-top: 0; }
.pkg.is-off { box-shadow: none; background: var(--c-paper-2); }
.pkg.is-off .pkg__head { background: var(--c-ink-2); }
.pkg.is-off .score > b { color: var(--c-ink-3); }

/* .usage-strip — лента движения занятий по пакету: горизонтальный скролл ячеек
   ±1 с датой. Markup: .usage-strip > .usage-strip__cell[data-tone] > b + span + em. */
.usage-strip { display: flex; gap: var(--sp-2); max-width: 100%; overflow-x: auto; padding-bottom: var(--sp-2); scrollbar-width: none; }
.usage-strip::-webkit-scrollbar { display: none; }
.usage-strip__cell {
  flex: none; width: 128px;
  padding: var(--sp-2);
  background: var(--c-surface);
  border: var(--rule-hair) solid var(--c-line);
  border-left: var(--rule-thick) solid var(--c-ink);
}
.usage-strip__cell b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
}
.usage-strip__cell span {
  display: block; margin-top: 2px;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}
.usage-strip__cell em { display: block; margin-top: 2px; font-style: normal; font-size: var(--fs-xs); color: var(--c-ink-3); }
.usage-strip__cell[data-tone="positive"] { border-left-color: var(--c-green); }
.usage-strip__cell[data-tone="positive"] b { color: var(--c-green); }
.usage-strip__cell[data-tone="progress"] { border-left-color: var(--c-cyan); }
.usage-strip__cell[data-tone="progress"] b { color: var(--c-cyan); }

/* ---------- 23. BOOKING HISTORY & RESCHEDULE -------------------------------- */

/* .timeline — история изменений записи. Rail + dot per row; tone comes from data. */
.timeline { position: relative; padding-left: var(--sp-6); }
.timeline::before {
  content: ""; position: absolute; left: 5px; top: 8px; bottom: 8px;
  width: var(--rule); background: var(--c-line-soft);
}
.timeline__row { position: relative; padding-bottom: var(--sp-4); }
.timeline__row:last-child { padding-bottom: 0; }
.timeline__dot {
  position: absolute; left: calc(var(--sp-6) * -1); top: 4px; width: 12px; height: 12px;
  background: var(--tone-neutral);
}
.timeline__row[data-tone="positive"] .timeline__dot { background: var(--tone-positive); }
.timeline__row[data-tone="progress"] .timeline__dot { background: var(--tone-progress); }
.timeline__row[data-tone="warning"]  .timeline__dot { background: var(--tone-warning); }
.timeline__row[data-tone="danger"]   .timeline__dot { background: var(--tone-danger); }
.timeline__row[data-tone="muted"]    .timeline__dot { background: var(--c-paper-3); border: var(--rule) solid var(--c-ink-3); }
.timeline__time {
  font-family: var(--font-mono); font-size: var(--fs-micro);
  color: var(--c-ink-3); letter-spacing: 0.02em;
}
.timeline__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.timeline__text { font-size: var(--fs-sm); color: var(--c-ink-2); }
.timeline__row.is-pending .timeline__title { color: var(--c-ink-3); }

/* .swap — старое время → новое время. Old cell is struck and paper-toned. */
.swap { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--sp-2); align-items: stretch; }
.swap__cell {
  background: var(--c-surface); border: var(--rule) solid var(--c-line);
  padding: var(--sp-3); min-width: 0;
}
.swap__cell.is-old {
  background: var(--c-paper-2); color: var(--c-ink-2); border-color: var(--c-ink-3);
}
.swap__cell.is-old .swap__time { text-decoration: line-through; }
.swap__cell.is-new { box-shadow: var(--plate-red); }
.swap__cell.is-empty { background: var(--c-paper-2); border-style: dashed; box-shadow: none; }
.swap__time {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
}
.swap__day {
  display: block; margin-top: 2px;
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: inherit;
}
.swap__note { display: block; margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--c-ink-2); }
.swap__arrow {
  align-self: center;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); color: var(--c-red);
}

/* ---------- 24. SIGN-IN ----------------------------------------------------- */

/* .entry-head — постерная шапка входа: красное поле, растровая точка, косой срез.
   (Печатный лист годового отчёта — это .poster, §33: другой компонент.) */
.entry-head {
  position: relative; overflow: hidden;
  background: var(--c-red); color: var(--c-white);
  padding: var(--sp-8) var(--gutter) var(--sp-10);
  text-align: center;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut-deep)), 0 100%);
}
.entry-head__dots { position: absolute; inset: 0; color: var(--c-ink); opacity: 0.35; pointer-events: none; }
.entry-head__mark {
  position: relative; display: inline-block;
  background: var(--c-white); color: var(--c-red);
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: 1; letter-spacing: 0.02em; text-transform: uppercase;
  box-shadow: var(--misreg-x) var(--misreg-y) 0 0 var(--c-ink);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%);
}
.entry-head__sub {
  position: relative; margin-top: var(--sp-4);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-white);
}
.entry-head__line {
  position: relative; margin: var(--sp-4) auto 0; max-width: 26ch;
  font-size: var(--fs-sm); color: var(--c-white);
}
.entry-head__rule { position: relative; margin: var(--sp-5) auto 0; width: 64px;
  border-top: var(--rule-heavy) solid var(--c-ink); }

/* .id-card — карточка участника: монограмма, имя, счёт выездов. */
.id-card {
  position: relative;
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-4);
  align-items: center;
  padding: var(--sp-4);
  background: var(--c-surface);
  border: var(--rule-thick) solid var(--c-line);
  box-shadow: var(--plate-red);
}
.id-card__name {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.id-card__meta { font-size: var(--fs-sm); color: var(--c-ink-2); margin-top: 2px; }
.id-card__strip {
  grid-column: 1 / -1;
  display: flex; gap: var(--sp-4); align-items: flex-end;
  padding-top: var(--sp-3); border-top: var(--rule) solid var(--c-line-soft);
}

/* ---------- 25. RANK, LADDER, NORM, STREAK, BRANCH -------------------------- */

/* .rank — плашка ступени клуба: вертикальная рейка с позицией, имя ступени
   набором в d1, растровый круг в углу. Markup:
   .rank > .rank__rail + .rank__body(.rank__cap .rank__name .rank__note) + .rank__orn */
.rank {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 34px 1fr;
  background: var(--c-red); color: var(--c-white);
  box-shadow: var(--plate);
}
.rank__rail {
  display: grid; place-items: center;
  background: var(--c-ink); color: var(--c-white);
  padding: var(--sp-3) 0;
}
.rank__body { position: relative; z-index: var(--z-raised); padding: var(--sp-3) var(--sp-4) var(--sp-4); min-width: 0; }
.rank__cap { display: block; color: var(--c-paper-2); }
.rank__name {
  display: block; margin-top: var(--sp-1);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-head);
  text-transform: uppercase; letter-spacing: var(--ls-head);
  overflow-wrap: normal;              /* имя ступени не рвём посреди слова */
}
.rank__note { display: block; margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--c-paper-2); }
.rank__orn {
  position: absolute; right: -26px; top: -26px; width: 108px; height: 108px;
  border-radius: var(--r-seal); color: var(--c-ink); opacity: 0.45;
  pointer-events: none; overflow: hidden;
}
/* Ступень новичка — та же плашка чернилами: достижения ещё нет, но место для него есть. */
.rank--start { background: var(--c-ink); box-shadow: var(--plate-red); }
.rank--start .rank__rail { background: var(--c-red); }
.rank--start .rank__orn { color: var(--c-red); opacity: 0.5; }

/* .ladder — лестница ступеней клуба, шесть строк сверху вниз.
   Markup: ol.ladder > li.ladder__row(.is-past|.is-now|.is-ahead) >
           .ladder__no + .ladder__name + .ladder__state
   (Компактная шкала пройденных ступеней — .tickbar--ladder, §9.) */
.ladder { border: var(--rule) solid var(--c-line); background: var(--c-surface); }
.ladder__row {
  display: grid; grid-template-columns: 30px 1fr auto; gap: var(--sp-3);
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.ladder__row:last-child { border-bottom: 0; }
.ladder__no {
  display: grid; place-items: center; width: 30px; height: 30px;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); line-height: 1; font-variant-numeric: tabular-nums;
  background: var(--c-paper-3); color: var(--c-ink-2);
}
.ladder__name {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
  overflow-wrap: normal;
}
.ladder__state { color: var(--c-ink-3); }
.ladder__row.is-past .ladder__no { background: var(--c-ink); color: var(--c-white); }
.ladder__row.is-now { background: var(--c-ink); color: var(--c-white); }
.ladder__row.is-now .ladder__no { background: var(--c-red); color: var(--c-white); }
.ladder__row.is-now .ladder__state { color: var(--c-ochre); }
.ladder__row.is-ahead .ladder__name { color: var(--c-ink-3); }
/* .ladder__cap — подпись под компактной шкалой .tickbar--ladder. */
.ladder__cap {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  margin-top: var(--sp-2);
}

/* .norm — норматив следующей ступени: несколько условий, у каждого своя шкала.
   Markup: .norm > .norm__row > (.row--between: .t-label + .norm__val) + шкала + .t-meta */
.norm { display: grid; gap: var(--sp-4); }
.norm__row { display: grid; gap: var(--sp-2); }
.norm__val { font-family: var(--font-display); font-weight: var(--fw-strong); font-size: var(--fs-h2); }
.norm__val em { font-style: normal; color: var(--c-ink-3); }

/* .streak — серия недель: табло, шкала текущей недели, управление заморозкой. */
.streak__week { padding: var(--sp-4); display: grid; gap: var(--sp-2); }
.streak__cnt { font-family: var(--font-display); font-weight: var(--fw-strong); font-size: var(--fs-h1); }
.streak__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  border-top: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
.streak__foot p { flex-basis: 100%; font-size: var(--fs-sm); color: var(--c-ink-2); }

/* .branch — ветка дисциплины: имя, достигнутая ступень, три штриха, остаток.
   Markup: .branch > .branch__name + .branch__steps + .branch__title + .branch__note
   Разделитель живёт на смежных ветках, а не на каждой строке: список внутри .card
   получает волосяные рули между ветками, а одиночная .card.branch (итог тренировки)
   сохраняет собственную рамку карточки. */
.branch {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-1) var(--sp-3);
  align-items: center;
  padding: var(--sp-3) var(--sp-4);
}
.branch + .branch { border-top: var(--rule-hair) solid var(--c-line-soft); }
.branch__name { color: var(--c-ink-2); }
/* Три ступени ветки — штрихи фиксированной ширины, см. .tickbar--fixed. */
.branch__steps { display: flex; gap: 3px; }
.branch__steps i { width: 16px; height: 16px; background: var(--c-paper-3); border: var(--rule-hair) solid var(--c-ink-3); }
.branch__steps i.is-on { background: var(--c-ink); border-color: var(--c-ink); }
.branch__title {
  grid-column: 1 / -1;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.branch.is-empty .branch__title { color: var(--c-ink-3); }
.branch__note { grid-column: 1 / -1; font-size: var(--fs-sm); color: var(--c-ink-2); }

/* ---------- 26. BADGES ------------------------------------------------------ */

/* .pin — эмалевый значок клуба: плоская геометрия, ободок чернилами, смещённая
   плашка. Тон приходит полем tone, никогда не выводится из id.
   Markup: <span class="pin" data-tone="ochre"><svg …></span>
           <span class="pin"><b class="pin__num">10</b></span>
   Модификаторы: --lg --xl · .is-locked — значок не получен. */
.pin {
  --pin-fill: var(--c-red);
  position: relative; flex: none;
  width: 64px; aspect-ratio: 1; border-radius: var(--r-seal);
  display: grid; place-items: center;
  background: var(--pin-fill); color: var(--c-white);
  border: var(--rule) solid var(--c-ink);
  box-shadow: 2px 2px 0 0 var(--c-ink);
}
.pin svg { width: 58%; height: 58%; fill: currentColor; }
.pin__num {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 25px; line-height: 1; letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.pin[data-tone="ink"]   { --pin-fill: var(--c-ink);   color: var(--c-white); }
.pin[data-tone="ochre"] { --pin-fill: var(--c-ochre); color: var(--c-ink); }
.pin[data-tone="cyan"]  { --pin-fill: var(--c-cyan);  color: var(--c-white); }
/* Не полученный — бумажная заготовка без эмали, перечёркнутая по общему углу. */
.pin.is-locked { --pin-fill: var(--c-paper-3); color: var(--c-ink-3); border-color: var(--c-ink-3); box-shadow: none; }
.pin.is-locked::after {
  content: ""; position: absolute; left: -4px; right: -4px; top: 50%;
  height: 3px; margin-top: -1.5px; background: var(--c-ink-3);
  transform: rotate(-45deg);
}
.pin--lg { width: 96px; border-width: var(--rule-thick); }
.pin--lg .pin__num { font-size: 38px; }
.pin--xl { width: 124px; border-width: var(--rule-thick); box-shadow: 4px 4px 0 0 var(--c-ink); }
.pin--xl .pin__num { font-size: 50px; }
/* Лучевая насечка — только на крупных значках, где она читается. */
.pin--lg::before, .pin--xl::before {
  content: ""; position: absolute; inset: -14px; border-radius: var(--r-seal);
  background: repeating-conic-gradient(from 0deg, var(--pin-fill) 0 5deg, transparent 5deg 20deg);
  -webkit-mask-image: radial-gradient(circle closest-side, transparent 0 84%, #000 84%);
          mask-image: radial-gradient(circle closest-side, transparent 0 84%, #000 84%);
  pointer-events: none;
}

/* .pin-grid — сетка значков по три в ряд. Ячейка — кнопка, открывает лист значка.
   Markup: .pin-grid > button.pin-cell > .pin + .pin-cell__name + .pin-cell__meta */
.pin-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-5) var(--sp-2); align-items: stretch; }
.pin-cell {
  display: grid; grid-template-rows: auto 1fr auto;
  justify-items: center; gap: var(--sp-2);
  min-height: var(--tap-min);
  padding: var(--sp-2) 2px;
  background: transparent; border: 0; cursor: pointer; text-align: center;
}
.pin-cell:active .pin { transform: translate(2px, 2px); box-shadow: none; }
.pin-cell__name {
  align-self: start;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: 0.04em; text-transform: uppercase;
  line-height: 1.15; overflow-wrap: normal;
}
/* Закрытый значок гасим цветом и перечёркнутой заготовкой, но не именем:
   двойное зачёркивание уже нечитаемо. */
.pin-cell.is-locked .pin-cell__name { color: var(--c-ink-2); }
.pin-cell__meta { font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--c-ink-2); line-height: 1.2; }

/* .shelf — горизонтальная полка значков; последняя ячейка ведёт на полную сетку.
   Markup: .shelf > .shelf__cell(> .pin + span) … + .shelf__more */
.shelf { display: flex; gap: var(--sp-3); overflow-x: auto; padding-bottom: var(--sp-2); scrollbar-width: none; }
.shelf::-webkit-scrollbar { display: none; }
.shelf__cell { flex: none; width: 84px; display: grid; justify-items: center; gap: var(--sp-2); text-align: center; }
.shelf__cell span {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: 0.04em; text-transform: uppercase;
  line-height: 1.15; color: var(--c-ink-2); overflow-wrap: normal;
}
.shelf__more {
  flex: none; width: 84px; min-height: 64px;
  display: grid; place-items: center; align-content: center; gap: var(--sp-1);
  border: var(--rule) solid var(--c-line); background: var(--c-paper-2);
  text-align: center; padding: var(--sp-2);
}
.shelf__more b { font-family: var(--font-display); font-weight: var(--fw-strong); font-size: var(--fs-h1); line-height: 1; }

/* .half / .half-head — две половины полки значков. Серьёзная набрана чернилами на
   бумаге, ироничная — охрой на второй бумаге: половины не путаются даже боковым
   зрением. Markup: section.half(.half--ironic) > .half-head(__title __note) > … */
.half { padding-bottom: var(--sp-6); }
.half--ironic { background: var(--c-paper-2); border-top: var(--rule-heavy) solid var(--c-ochre); }
.half-head {
  padding: var(--sp-4) var(--gutter) var(--sp-5);
  background: var(--c-ink); color: var(--c-white);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), 0 100%);
}
.half-head__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.half-head__note { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--c-paper-2); max-width: 38ch; }
.half--ironic .half-head { background: var(--c-ochre); color: var(--c-ink); }
.half--ironic .half-head__note { color: var(--c-ink); }

/* .group-head — подпись группы внутри половины: название и счёт полученных. */
.group-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  margin: var(--sp-6) 0 var(--sp-3);
  border-bottom: var(--rule) solid var(--c-line);
  padding-bottom: var(--sp-2);
}
.group-head h3 {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); text-transform: uppercase; letter-spacing: var(--ls-head);
}

/* .newbadge — кульминация экрана итога: полученный значок во весь блок, кумачовое
   поле, растр и диагональный срез. Markup:
   .newbadge > .newbadge__dots + .newbadge__rail + .pin--xl + __name + __desc + __meta */
.newbadge {
  position: relative; overflow: hidden;
  display: grid; justify-items: center; gap: var(--sp-3);
  text-align: center;
  padding: var(--sp-6) var(--gutter) var(--sp-8) calc(var(--gutter) + var(--sp-6));
  background: var(--c-red); color: var(--c-white);
  box-shadow: var(--plate);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut-deep)), 0 100%);
}
.newbadge__dots { position: absolute; inset: 0; z-index: 0; color: var(--c-ink); opacity: 0.35; pointer-events: none; }
.newbadge__rail {
  position: absolute; left: var(--sp-3); top: var(--sp-5); z-index: 2;
  color: var(--c-white);
}
.newbadge .pin,
.newbadge__name, .newbadge__desc, .newbadge__meta { position: relative; z-index: 1; }
.newbadge__name {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-head);
  text-transform: uppercase; letter-spacing: var(--ls-head);
  overflow-wrap: normal;
}
.newbadge__desc { font-size: var(--fs-sm); color: var(--c-white); max-width: 32ch; }
.newbadge__meta { color: var(--c-paper-2); }

/* .enamel — эмалевый значок сезонной награды: восьмиугольная плашка с ink-обводкой
   и внутренним кольцом, рисуется inline SVG (никаких внешних картинок). Цвет поля
   приходит полем tone значка, а не его id.
   Markup: .awards > .awards__item > (.enamel > svg) + b + span */
.enamel { width: 68px; height: 68px; flex: none; }
.enamel svg { width: 100%; height: 100%; }
.awards { display: grid; gap: var(--sp-3); }
.awards__item {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  box-shadow: var(--plate);
}
.awards__item b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.awards__item span { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); margin-top: 2px; }

/* ---------- 27. POINTS ------------------------------------------------------ */

/* .calc — разбор начисления очков. Каждая строка — множитель или слагаемое,
   подытог и итог выделены плашками. Числа приходят полями, вёрстка не считает.
   Markup: .calc > .calc__row(--sub|--total|--mult|--bonus|--zero) > span + .calc__val */
.calc { border: var(--rule) solid var(--c-line); background: var(--c-surface); }
.calc__row {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3);
  align-items: baseline;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.calc__row > span { font-size: var(--fs-sm); }
.calc__val {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Множитель — холодным цветом: его нельзя спутать со слагаемым. */
.calc__row--mult .calc__val { color: var(--c-cyan); }
.calc__row--zero .calc__val { color: var(--c-ink-3); }
.calc__row--sub { background: var(--c-paper-2); border-top: var(--rule) solid var(--c-line); }
.calc__row--bonus .calc__val { color: var(--c-red); }
.calc__row--total {
  background: var(--c-ink); color: var(--c-white);
  border-bottom: 0; padding: var(--sp-3) var(--sp-4) var(--sp-4);
  align-items: center;
}
.calc__row--total > span { font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase; }
.calc__row--total .calc__val { font-size: var(--fs-d2); color: var(--c-white); }

/* .led — строка леджера очков: причина слева, подписанное движение справа. */
.led__delta {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums; color: var(--c-red);
}
.led__reason { font-size: var(--fs-sm); }
.led__date { font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--c-ink-3); }

/* .mark-plate — отметка тренера «отлично отработал»: охряная плашка с очками. */
.mark-plate {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
}
.mark-plate b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.mark-plate span { font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); font-variant-numeric: tabular-nums; }

/* ---------- 28. LEADERBOARD ------------------------------------------------- */

/* .honour — «доска почёта»: рамка с толстым кантом, ink-шапка и три места
   передовиков недели. Markup: .honour > .honour__head + .honour__grid > .laureate */
.honour {
  position: relative;
  border: var(--rule-thick) solid var(--c-line);
  background: var(--c-surface);
  box-shadow: var(--plate-red);
}
.honour__head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-ink); color: var(--c-white);
  overflow: hidden;
}
.honour__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
  white-space: nowrap;
}
.honour__tail { flex: 1 1 auto; align-self: stretch; min-width: 16px; color: var(--c-red); }
.honour__count {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-paper-3); white-space: nowrap;
}
.honour__grid { display: grid; grid-template-columns: repeat(3, 1fr); }

/* .laureate — портрет-плашка на доске почёта: место, монограмма, фамилия, очки. */
.laureate {
  display: grid; justify-items: center; gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-2);
  border-right: var(--rule-hair) solid var(--c-line-soft);
  text-align: center;
}
.laureate:last-child { border-right: 0; }
.laureate--first { background: var(--c-paper-2); }
.laureate__place {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--c-ink-3);
}
.laureate--first .laureate__place { color: var(--c-red); }
/* Красная монограмма на красной подложке не читается — первому даём ink-плашку. */
.laureate--first .monogram { box-shadow: var(--plate); }
.laureate__name {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.laureate__pts {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); line-height: 1; font-variant-numeric: tabular-nums;
}
.laureate__cap {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}

/* .rank-row — строка рейтинга: рельс места, имя с фактами, очки.
   .is-me — своя строка; .rank-row--pin — своя строка, прижатая к низу экрана. */
.rank-row {
  display: grid; grid-template-columns: 44px 1fr auto;
  align-items: center; gap: var(--sp-3);
  min-height: 56px;
  padding-right: var(--sp-3);
  background: var(--c-surface);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.rank-row:last-child { border-bottom: 0; }
.rank-row__place {
  align-self: stretch;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  background: var(--c-paper-2);
  border-right: var(--rule) solid var(--c-line);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums; line-height: 1;
}
.rank-row__delta {
  font-family: var(--font-display); font-size: var(--fs-micro); line-height: 1;
  color: var(--c-ink-2);
}
.rank-row__delta.is-up { color: var(--c-green); }
.rank-row__name {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.rank-row__meta { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); }
.rank-row__pts {
  text-align: right;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 30px; line-height: 1; font-variant-numeric: tabular-nums;
}
.rank-row__pts span {
  display: block;
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); color: var(--c-ink-2);
}
.rank-row.is-me { background: var(--c-red-wash); }
.rank-row.is-me .rank-row__place { background: var(--c-red); color: var(--c-white); }
.rank-row--pin {
  position: sticky; bottom: 0; z-index: var(--z-sticky);
  background: var(--c-ink); color: var(--c-white);
  border-top: var(--rule-thick) solid var(--c-red);
  border-bottom: 0;
}
.rank-row--pin .rank-row__place { background: var(--c-red); color: var(--c-white); border-right-color: var(--c-red-deep); }
.rank-row--pin .rank-row__meta,
.rank-row--pin .rank-row__pts span,
.rank-row--pin .rank-row__delta { color: var(--c-paper-3); }

/* ---------- 29. TEAMS ------------------------------------------------------- */

/* .emblem — эмблема команды. Путь фигуры приходит данными (team.emblemPath),
   экран не выбирает форму по id. --sm для строк таблицы. */
.emblem {
  flex: none;
  width: 56px; height: 56px;
  display: grid; place-items: center;
  background: var(--c-ink); color: var(--c-white);
  box-shadow: var(--plate-red);
}
.emblem svg { width: 68%; height: 68%; }
.emblem--sm { width: 30px; height: 30px; box-shadow: none; }
.emblem--paper { background: var(--c-paper-2); color: var(--c-ink); box-shadow: none; }

/* .team-head — эмблема + название + подпись. Живёт внутри .hero. */
.team-head { position: relative; display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3); align-items: center; }
.team-head__name {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.team-head__sub { display: block; font-size: var(--fs-sm); color: var(--c-paper-2); margin-top: 2px; }

/* .mate-row — строка состава: монограмма, имя, норма недели и абсолютный вклад.
   Долей и внутренних мест здесь нет и быть не должно. */
.mate-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3);
  align-items: center;
  min-height: 64px;
  padding: var(--sp-3);
  background: var(--c-surface);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.mate-row:last-child { border-bottom: 0; }
.mate-row.is-me { background: var(--c-paper-2); }
.mate-row__name {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.mate-row__meta { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); }
.mate-row__norm { width: 68px; height: var(--sp-2); margin-top: var(--sp-2); }
.mate-row__pts {
  text-align: right;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 30px; line-height: 1; font-variant-numeric: tabular-nums;
}
.mate-row__pts span {
  display: block;
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}

/* .tour-row — строка недельной таблицы. Крупное число — СРЕДНЕЕ на участника;
   сумма и состав идут мелко в подписи, чтобы набор людей не выглядел тактикой. */
.tour-row {
  display: grid; grid-template-columns: 40px auto 1fr auto; gap: var(--sp-3);
  align-items: center;
  min-height: 60px;
  padding: var(--sp-2) var(--sp-3) var(--sp-2) 0;
  background: var(--c-surface);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}
.tour-row:last-child { border-bottom: 0; }
.tour-row__place {
  align-self: stretch;
  display: grid; place-items: center;
  background: var(--c-paper-2);
  border-right: var(--rule) solid var(--c-line);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums;
}
.tour-row__name {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
}
.tour-row__meta { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); }
.tour-row__avg {
  text-align: right;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 30px; line-height: 1; font-variant-numeric: tabular-nums;
}
.tour-row__avg span {
  display: block;
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}
.tour-row.is-mine { background: var(--c-red-wash); }
.tour-row.is-mine .tour-row__place { background: var(--c-red); color: var(--c-white); }

/* .teamcard — команда, которая ищет участников (состояние «вы без команды»). */
.teamcard {
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  box-shadow: var(--plate);
  margin-bottom: var(--sp-4);
}
.teamcard__name {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
  color: var(--c-red);
}
.teamcard__meta { display: block; font-size: var(--fs-sm); color: var(--c-ink-2); }

/* .teamcard__head — название + девиз одной колонкой, метка мест справа.
   Боковой отступ ТОТ ЖЕ, что у .list__row под ней (--sp-4): без него
   название висит на самом краю плашки, а цифры под ним — с отступом. */
.teamcard__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
}
/* «4 места» — счётчик, а не фраза: перенос превращает его в две строки
   и ломает выключку шапки. */
.teamcard__head .tag { white-space: nowrap; }

/* .card--form — плашка с формой. У .card своих отступов НЕТ: их дают вложенные
   .list__row и .teamcard__head. Форме их дать некому, поэтому подписи и заголовок
   упираются в край плашки. */
.card--form { padding: var(--sp-4); }

/* Строка списка, которая несёт не только значение, но и действие
   («Состав 2 / 6 · показать»). .list__val сам по себе выровнен по правому
   краю — здесь нужен ряд. */
.list__val--action { display: flex; align-items: center; gap: var(--sp-3); }

/* ---------- 30. CHALLENGE --------------------------------------------------- */

/* .goal / .goalbar — общий прогресс клуба к цели месяца с отметкой графика.
   --fill и --pace приходят данными (percentDone / percentPace), не вёрсткой. */
.goal { position: relative; padding-top: var(--sp-5); }
/* .goal__pacelab — подпись «План на сегодня». Стояла абсолютом на `left:
   var(--pace)`, но `--pace` не выставляет никто: полосу и отметку разметка
   двигает инлайновыми `width`/`left`, а переменная осталась от прежней версии.
   Без значения `left` схлопывался в ноль, а `translateX(-50%)` уводил подпись
   за левый край — текст обрезался на каждом показе. Разметка кладёт её в
   `.row--between` рядом с отставанием, то есть ждёт обычного потока. */
.goal__pacelab {
  min-width: 0;
  white-space: nowrap; color: var(--c-ink-2);
}
.goalbar {
  position: relative;
  height: 34px;
  border: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
.goalbar__fill {
  position: absolute; top: 0; bottom: 0; left: 0;
  width: var(--fill);
  background: var(--c-red);
  color: var(--c-red-deep);
}
.goalbar__pace {
  position: absolute; top: calc(var(--sp-2) * -1); bottom: calc(var(--sp-2) * -1);
  left: var(--pace);
  width: var(--rule-thick);
  margin-left: calc(var(--rule-thick) / -2);
  background: var(--c-ink);
}
.goal__scale {
  display: flex; justify-content: space-between; gap: var(--sp-3);
  margin-top: var(--sp-2);
  color: var(--c-ink-2);
}

/* .contrib — лента вкладов. Не рейтинг: порядок по времени последнего вклада,
   числа абсолютные. Горизонтальный скролл, как .chiprow. */
.contrib {
  display: flex; gap: var(--sp-3);
  overflow-x: auto;
  padding: var(--sp-1) var(--gutter) var(--sp-3);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.contrib::-webkit-scrollbar { display: none; }
.contrib__item {
  flex: none; width: 100px;
  display: grid; justify-items: center; gap: 2px;
  text-align: center;
}
.contrib__km {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); line-height: 1; font-variant-numeric: tabular-nums;
  margin-top: var(--sp-2);
}
.contrib__name { font-size: var(--fs-sm); line-height: 1.2; }
.contrib__last {
  font-family: var(--font-display); font-size: var(--fs-micro);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--c-ink-2);
}
.contrib__rest {
  flex: none; width: 120px; align-self: center;
  font-size: var(--fs-sm); color: var(--c-ink-2);
}

/* .stamp--result — печать «цель взята» в углу плаката-итога, а не по центру. */
.stamp--result {
  top: auto; left: auto;
  right: var(--gutter); bottom: var(--sp-6);
  transform: rotate(-11deg);
}
/* На красном поле зелёная печать не проходит по контрасту — выбиваем бумагой. */
.hero--red .stamp--result { color: var(--c-white); }

/* ---------- 31. SEASON WRAP ------------------------------------------------- */

/* .wrapcard — печатная карточка итога: ink-рамка, красная плита, диагональный срез
   и halftone-поле в шапке. Внутри живут только собственные элементы карточки,
   чтобы её можно было целиком отдать в шаринг.
   Markup: .wrapcard > .wrapcard__head (> .wrapcard__kicker .wrapcard__title
   .wrapcard__sub .wrapcard__strip) + .wrapcard__body. */
.wrapcard {
  position: relative;
  background: var(--c-surface);
  border: var(--rule-thick) solid var(--c-line);
  box-shadow: var(--plate-red);
}
.wrapcard__head {
  position: relative;
  overflow: hidden;
  padding: var(--sp-4) var(--sp-4) var(--sp-5);
  background: var(--c-ink);
  color: var(--c-white);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), 0 100%);
}
.wrapcard__dots {
  position: absolute; inset: 0;
  color: var(--c-red);
  opacity: 0.55;
  pointer-events: none;
}
.wrapcard__kicker {
  position: relative;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  color: var(--c-paper-3);
}
.wrapcard__title {
  position: relative;
  margin-top: var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-head);
  text-transform: uppercase; letter-spacing: var(--ls-head);
}
.wrapcard__title em { font-style: normal; color: var(--c-red); }
.wrapcard__sub { position: relative; margin-top: var(--sp-2); color: var(--c-paper-2); font-size: var(--fs-sm); }
/* Сезонный счёт стоит в шапке отдельной строкой фактов: он обнулится, и это
   должно быть видно рядом с датой закрытия, а не в теле карточки. */
.wrapcard__strip {
  position: relative;
  display: flex; flex-wrap: wrap; gap: var(--sp-5);
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: var(--rule) solid var(--c-ink-3);
}
.wrapcard__fact b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
}
.wrapcard__fact span {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-paper-3);
}
.wrapcard__body { padding: var(--sp-4); }

/* .factgrid — печатная сетка «цифры сезона»: равные ячейки с волосяными рулями.
   Markup: .factgrid > .fact > (b + span). */
.factgrid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border: var(--rule) solid var(--c-line);
  background: var(--c-surface);
}
.fact {
  min-width: 0;
  padding: var(--sp-3) var(--sp-2);
  border-right: var(--rule-hair) solid var(--c-line-soft);
  text-align: center;
}
.fact:last-child { border-right: 0; }
.fact > b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-tight); letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
}
.fact > span {
  display: block; margin-top: var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2);
}
@media (max-width: 379px) { .fact > b { font-size: var(--fs-d2); } }

/* .ledger — что переносится и что обнуляется. Блок «остаётся» печатается ink-рулём,
   блок «обнулилось» — барьерной штриховкой: разница видна раньше, чем прочитан текст.
   Markup: .ledger > .ledger__col[.is-reset] > (.ledger__head + ul > li). */
.ledger { display: grid; gap: var(--sp-3); }
.ledger__col {
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  border-left-width: var(--rule-heavy);
}
.ledger__col.is-reset {
  border-left-color: var(--c-ink-3);
  background-color: var(--c-paper-2);
  color: var(--c-ink-2);
}
.ledger__head {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.ledger__col li {
  padding: var(--sp-2) 0;
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
  font-size: var(--fs-sm);
}
.ledger__col li:last-child { border-bottom: 0; }

/* .nextseason — узкая ink-полоса «сезон, который уже идёт»: счёт сезонных очков
   начат заново, поэтому он подаётся отдельно от карточки закрытого сезона. */
.nextseason {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3); align-items: center;
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-ink); color: var(--c-white);
  box-shadow: var(--plate-cyan);
}
.nextseason b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--c-cyan-bright);
}

/* ---------- 32. YEAR POSTER ------------------------------------------------- */

/* .poster — годовой отчёт как печатный лист: тяжёлая рамка, внутренний волосяной
   контур, приводочные кресты по углам. Всё внутри рамки нарисовано собственными
   элементами постера и красится через локальные переменные (ниже), чтобы лист
   можно было напечатать в другом колорите одним классом. Значения переменных —
   только токены, ни одного нового цвета. (Шапка входа — .entry-head, §24.)

   Markup: .poster > (.poster__reg ×4) + .poster__top + .poster__yearrow
           + .poster__grid + .poster__band + .poster__streak + .poster__pair
           + .poster__scale + .poster__foot */
.poster {
  --pg: var(--c-paper);      /* бумага      */
  --pk: var(--c-ink);        /* краска      */
  --pa: var(--c-red);        /* акцент      */
  --pb: var(--c-ochre);      /* второй цвет */
  --pn: var(--c-white);      /* выворотка на акценте */
  --pm: var(--c-ink-2);      /* приглушённый текст   */

  position: relative;
  isolation: isolate;
  padding: var(--sp-5) var(--sp-4) var(--sp-4);
  background: var(--pg);
  color: var(--pk);
  border: var(--rule-heavy) solid var(--pk);
  box-shadow: var(--misreg-x) var(--misreg-y) 0 0 var(--pa);
  overflow: hidden;
}
/* Ночная печать: тот же лист, другой прогон краски. */
.poster--night {
  --pg: var(--c-ink);
  --pk: var(--c-paper);
  --pa: var(--c-ochre);
  --pb: var(--c-cyan-bright);
  --pn: var(--c-ink);
  --pm: var(--c-paper-3);
  box-shadow: var(--misreg-x) var(--misreg-y) 0 0 var(--c-red);
}
/* Внутренний волосяной контур — «поле» листа. */
.poster::before {
  content: "";
  position: absolute; inset: 5px;
  border: var(--rule-hair) solid var(--pk);
  opacity: 0.45;
  pointer-events: none;
  z-index: 1;
}
/* Приводочный крест — типографская метка совмещения плат; печатается второй
   краской (--pb), как и положено метке приводки. */
.poster__reg {
  position: absolute; width: 14px; height: 14px; z-index: 1; pointer-events: none;
  background-image:
    linear-gradient(var(--pb) 0 0), linear-gradient(var(--pb) 0 0);
  background-size: 100% var(--rule-hair), var(--rule-hair) 100%;
  background-position: 50% 50%, 50% 50%;
  background-repeat: no-repeat;
  opacity: 0.75;
}
.poster__reg--tl { top: 10px; left: 10px; }
.poster__reg--tr { top: 10px; right: 10px; }
.poster__reg--bl { bottom: 10px; left: 10px; }
.poster__reg--br { bottom: 10px; right: 10px; }

.poster__top {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
}
.poster__mark {
  background: var(--pa); color: var(--pn);
  padding: 2px var(--sp-2) 3px;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 22px; line-height: 1; letter-spacing: 0.01em; text-transform: uppercase;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%);
}
.poster__label {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--pm);
}
.poster__yearrow {
  position: relative; z-index: 2;
  display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.poster__year {
  flex: none; white-space: nowrap;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: 82px; line-height: 0.8; letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
}
.poster__owner {
  min-width: 0;
  text-align: right; font-size: var(--fs-sm); line-height: 1.25; color: var(--pm);
  padding-bottom: var(--sp-1);
}
.poster__owner b {
  display: block;
  font-family: var(--font-display); font-size: var(--fs-h2);
  text-transform: uppercase; letter-spacing: var(--ls-head); color: var(--pk);
}
.poster__rule {
  position: relative; z-index: 2;
  height: var(--rule-thick); background: var(--pa); margin: var(--sp-3) 0 var(--sp-4);
}

/* Сетка главных цифр: одна ячейка печатается акцентом — она и есть заголовок года. */
.poster__grid {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--rule);
  background: var(--pk);
  border: var(--rule) solid var(--pk);
}
.poster__cell {
  min-width: 0;
  padding: var(--sp-3);
  background: var(--pg);
}
.poster__cell--hero { background: var(--pa); color: var(--pn); }
.poster__cell b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d1); line-height: var(--lh-tight); letter-spacing: var(--ls-score);
  font-variant-numeric: tabular-nums;
}
.poster__cell span {
  display: block; margin-top: var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--pm);
}
.poster__cell--hero span { color: var(--pn); opacity: 0.85; }

/* Полоса «с чего начал → чем закончил»: диагональ между двумя состояниями. */
.poster__band {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--sp-2);
  align-items: center;
  margin-top: var(--sp-4);
}
.poster__state { min-width: 0; }
.poster__state span {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--pm);
}
.poster__state b {
  display: block; margin-top: 2px;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.poster__state i { display: block; font-style: normal; font-size: var(--fs-sm); color: var(--pm); }
.poster__state--to { text-align: right; }
.poster__arrow {
  width: 26px; height: 14px; flex: none;
  background: var(--pa);
  clip-path: polygon(0 38%, 62% 38%, 62% 0, 100% 50%, 62% 100%, 62% 62%, 0 62%);
}

/* Серия недель: год как 52 деления, серия — залитый отрезок. */
.poster__streak { position: relative; z-index: 2; margin-top: var(--sp-4); }
.poster__ticks { display: flex; gap: 1px; height: 20px; }
.poster__ticks i { flex: 1; background: var(--pk); opacity: 0.22; }
.poster__ticks i.is-on { background: var(--pa); opacity: 1; }
.poster__cap {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.poster__cap b {
  flex: none;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.poster__cap span {
  flex: 1; min-width: 0;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--pm); text-align: right;
}

/* Две одиночные строчки-факта в рамках. */
.poster__pair {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.poster__fact { border-top: var(--rule-thick) solid var(--pk); padding-top: var(--sp-2); min-width: 0; }
.poster__fact b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-d2); line-height: var(--lh-tight); font-variant-numeric: tabular-nums;
}
.poster__fact span { display: block; margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--pm); }

/* Масштабная линейка: километры года, приложенные к чему-то человеческому. */
.poster__scale { position: relative; z-index: 2; margin-top: var(--sp-5); }
.poster__ruler {
  position: relative; height: 18px;
  border-top: var(--rule) solid var(--pk);
  border-bottom: 0;
}
.poster__ruler::before, .poster__ruler::after {
  content: ""; position: absolute; top: 0; width: var(--rule); height: 12px; background: var(--pk);
}
.poster__ruler::before { left: 0; }
.poster__ruler::after  { right: 0; }
.poster__km {
  position: absolute; left: 50%; top: -12px; transform: translateX(-50%);
  background: var(--pk); color: var(--pg);
  padding: 2px var(--sp-3) 3px;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); letter-spacing: var(--ls-head); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.poster__scale b {
  display: block; margin-top: var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.poster__scale span { display: block; font-size: var(--fs-sm); color: var(--pm); }

.poster__foot {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  margin-top: var(--sp-5); padding-top: var(--sp-2);
  border-top: var(--rule-hair) solid var(--pk);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--pm);
}
/* Печать года — круглая марка в углу листа. */
.poster__seal {
  width: 54px; height: 54px; flex: none;
  border: var(--rule) solid var(--pa); border-radius: var(--r-seal);
  display: grid; place-items: center; text-align: center; line-height: 1;
  color: var(--pa);
}
.poster__seal b { display: block; font-size: var(--fs-h2); font-variant-numeric: tabular-nums; }

@media (max-width: 379px) {
  .poster__year { font-size: 66px; }
  .poster__cell b { font-size: var(--fs-d2); }
}

/* ---------- 33. COACH FIELD SCREEN ----------------------------------------- */

/* Поверхность рассчитана на замёрзшую руку на улице: цели ≥ 56px, контраст
   максимальный, ввода с клавиатуры почти нет. Крупные органы ввода этого экрана —
   .tapseg (§6), .tapchip (§6), .stepper (§15) и .rpe (§15).
   .steplabel — номер шага в ink-квадрате перед заголовком блока. */
.steplabel {
  display: inline-grid; place-items: center;
  width: 26px; height: 26px; flex: none;
  background: var(--c-ink); color: var(--c-white);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-sm); line-height: 1; font-variant-numeric: tabular-nums;
}

/* .rosterrow — строка участника: монограмма, имя, двухкнопочная отметка присутствия.
   .is-present / .is-noshow красят левый руль строки, чтобы список читался сверху.
   Markup: .rosterrow > .rosterrow__head (> .monogram + .rosterrow__name + .tapseg)
           + .rosterrow__extra */
.rosterrow {
  background: var(--c-surface);
  border: var(--rule) solid var(--c-line);
  border-left-width: var(--rule-heavy);
  border-left-color: var(--c-paper-3);
  margin-bottom: var(--sp-3);
}
.rosterrow.is-present { border-left-color: var(--c-green); }
.rosterrow.is-noshow  { border-left-color: var(--c-red); background: var(--c-paper-2); }
.rosterrow__head {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3);
}
.rosterrow__name {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); text-transform: uppercase; letter-spacing: var(--ls-head);
}
.rosterrow__note { font-size: var(--fs-sm); color: var(--c-ink-2); }
.rosterrow__extra {
  grid-column: 1 / -1;
  padding: var(--sp-3);
  border-top: var(--rule) solid var(--c-line);
  background: var(--c-paper-2);
}
.rosterrow.is-noshow .rosterrow__extra { display: none; }

/* .tally — счётчик отметок в липкой шапке блока: сколько строк уже закрыто. */
.tally {
  position: sticky; top: 68px; z-index: var(--z-sticky);
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) var(--gutter);
  background: var(--c-ink); color: var(--c-white);
  border-bottom: var(--rule-thick) solid var(--c-red);
}
.tally b {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h1); line-height: 1; font-variant-numeric: tabular-nums;
}

/* ---------- 34. LAYOUT UTILITIES ------------------------------------------- */

.stack   { display: grid; gap: var(--sp-3); }
.stack-4 { display: grid; gap: var(--sp-4); }
.row     { display: flex; align-items: center; gap: var(--sp-2); }
.row--between { justify-content: space-between; }
.row--wrap { flex-wrap: wrap; }
.grow    { flex: 1 1 auto; min-width: 0; }
.pad     { padding-inline: var(--gutter); }

/* .packbook — «записаться по пакету» над платёжной полосой. Без отступов кнопка
   прилипала к списку деталей сверху, а строка про остаток занятий — к липкой
   полосе оплаты снизу, и три разных действия читались одной кучей. */
.packbook .btn { margin-top: var(--sp-5); }
.packbook .t-meta { margin-bottom: var(--sp-5); }
.mt-3 { margin-top: var(--sp-3); }
/* Парная к .mt-3: сообщение об ошибке стоит НАД кнопками, и отбивать его
   приходится снизу. */
.mb-3 { margin-bottom: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ─────────────────────────────────────────────────────────────────────────────
   .tagstack — плашки в правом краю строки списка. Их бывает две (оплата и
   статус занятия), и на узком экране вторая должна уходить под первую, а не
   ломать строку. */
.tagstack {
  display: flex; flex-wrap: wrap; gap: var(--sp-1);
  align-items: center; justify-content: flex-end;
  flex: none; max-width: 46%;
}

/* ─────────────────────────────────────────────────────────────────────────────
   .cal — месяц одним экраном: тренеру нужно видеть загрузку, а не листать
   список. Разметка: .cal > .cal__nav + .cal__grid (7 колонок: сначала семь
   .cal__wd, затем .cal__day — пустые в начале месяца помечены .is-void).

   Клетка — квадрат: aspect-ratio держит сетку ровной при любой ширине, а
   min-height добивает её до пальца на мелких экранах. */
.cal { border: var(--rule) solid var(--c-line); background: var(--c-surface); }

.cal__nav {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: var(--rule) solid var(--c-line);
  background: var(--c-ink); color: var(--c-white);
}
.cal__title {
  font-family: var(--font-display); font-weight: var(--fw-strong);
  text-transform: uppercase; letter-spacing: var(--ls-head);
  font-size: var(--fs-sm);
}
.cal__arrow {
  min-width: var(--tap-min); min-height: 40px;
  background: none; border: 0; color: inherit;
  font-family: var(--font-display); font-size: var(--fs-h2); line-height: 1;
  cursor: pointer;
}
.cal__arrow:active { background: var(--c-red); }

.cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); }

.cal__wd {
  padding: var(--sp-2) 0;
  text-align: center;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--c-ink-3);
  background: var(--c-paper-2);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
}

.cal__day {
  position: relative;
  aspect-ratio: 1; min-height: 40px;
  display: grid; place-items: center;
  background: none; border: 0;
  border-right: var(--rule-hair) solid var(--c-line-soft);
  border-bottom: var(--rule-hair) solid var(--c-line-soft);
  font: inherit; color: inherit; cursor: pointer;
}
.cal__day:nth-child(7n) { border-right: 0; }
.cal__day.is-void { cursor: default; background: var(--c-paper-2); }

.cal__num {
  font-family: var(--font-mono); font-size: var(--fs-sm); font-style: normal;
  color: var(--c-ink-3);
}
/* День с тренировками читается сразу: тёмная цифра против серой. */
.cal__day.has-work .cal__num { color: var(--c-ink); font-weight: var(--fw-strong); }
/* ...но только пока он впереди. Прошедший день с тренировками набирался тем же
   чёрным, что и предстоящий, и месяц выглядел одинаково плотным в обе стороны
   от сегодня. Прошлое гаснет — и цифра, и счётчик. */
.cal__day.is-past.has-work .cal__num { color: var(--c-ink-3); font-weight: var(--fw-body); }
.cal__day.is-past .cal__count { background: var(--c-paper-3); color: var(--c-ink-2); }

/* Счётчик в углу, а не точка: тренеру важно, одна там тренировка или четыре. */
.cal__count {
  position: absolute; top: 2px; right: 2px;
  min-width: 14px; height: 14px; padding: 0 3px;
  display: grid; place-items: center;
  background: var(--c-red); color: var(--c-white);
  font-family: var(--font-display); font-size: 9px; font-style: normal;
  font-weight: var(--fw-strong); line-height: 1;
}

.cal__day.is-today { box-shadow: inset 0 0 0 var(--rule) var(--c-red); }
.cal__day.is-sel { background: var(--c-ink); }
.cal__day.is-sel .cal__num { color: var(--c-white); }
.cal__day.is-sel .cal__count { background: var(--c-ochre); color: var(--c-ink); }

/* .actionbar-floor — пол под липкой парой «actionbar + таббар».
   Обе панели `position: fixed`, то есть вне потока: последняя карточка списка
   уезжает под них, и дочитать её нельзя ничем, кроме как знать, что там что-то
   есть. Пустой блок в конце потока — единственный способ вернуть ей место.
   Высота = панель действия (~68px) + таббар (62px) + запас. */
.actionbar-floor { height: calc(140px + env(safe-area-inset-bottom, 0px)); }

/* .stepgrid — ряд быстрых шагов под степпером: равные колонки, чтобы «+20» не
   был вдвое шире «−5» и палец не попадал в соседний. */
.stepgrid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-2); }
.stepgrid .btn { padding-inline: 0; }

/* Плашка не переносится по слогам: «СВОБОДЕ / Н» — это не сокращение, а брак.
   В строке списка она держит свою ширину, а ужимается текст слева: у него для
   этого есть `.grow` и он переносится по словам осмысленно. */
.list__row > .tag,
.tagstack .tag { flex: none; white-space: nowrap; }

/* .coach__photo — снимок тренера на месте монограммы. Квадрат тех же размеров,
   что и `.monogram`, поэтому список не разъезжается, когда у одного тренера
   фото есть, а у другого нет. `object-fit: cover` обязателен: снимки приходят
   какие есть, и без него портрет растянуло бы по квадрату. */
.coach__photo {
  flex: none; width: 44px; height: 44px;
  object-fit: cover; object-position: center top;
  background: var(--c-paper-2);
}
.coach__photo--lg { width: 96px; height: 96px; margin-bottom: var(--sp-3); }

/* .card__coachlink — имя тренера как переход на его страницу изнутри карточки,
   которая сама целиком ссылка. Настоящий <a> сюда вложить нельзя, поэтому вид
   ссылки приходится задать здесь: без подчёркивания никто не догадается, что
   по имени можно нажать, а ради этого всё и делалось. */
.card__coachlink {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  cursor: pointer;
}
.card__coachlink .t-meta {
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-thickness: 1px; text-decoration-color: var(--c-line);
}
/* Имя тренера не режем посреди слова.
   Средняя колонка карточки на 360px — около 102px. Монограмма с отступом берёт
   34, имени остаётся 68, а «Зубриянова» — 75. `body { overflow-wrap: anywhere }`
   рвал её где придётся: «Зубрияно / ва» у клиента, «Зубриянов / а» на 360px.
   Переносы по слогам (`hyphens: auto`) пробовали и сняли: словарь русских
   переносов есть не у каждого браузера, и без него получался тот же
   «Зубриянов / а» — поведение зависело от телефона.
   Поэтому не переносим слово, а ПЕРЕНОСИМ ИМЯ ЦЕЛИКОМ: чип может свернуться в
   две строки — монограмма, под ней имя на всю ширину колонки (102px, слово
   влезает). `break-word` вместо `anywhere` — ключ: он не занижает минимальную
   ширину, поэтому флекс видит, что имя рядом с монограммой не помещается, и
   уводит его на новую строку, а не ужимает до одной буквы. Посреди слова
   `break-word` порвёт только то, что не влезает и в полную колонку. */
.card__coach,
.card__coachlink { flex-wrap: wrap; row-gap: 2px; }
.card__coach .t-meta,
.card__coachlink .t-meta { overflow-wrap: break-word; }
.card__coachlink:hover .t-meta,
.card__coachlink:focus-visible .t-meta { color: var(--c-red); text-decoration-color: currentColor; }

/* ---------- 35. ПРАЙС-ЛИСТ -------------------------------------------------- */

/* .price-cols — правая половина строки прайса: одна цена или две.
   Две — это будни и выходные. Колонка выходных появляется ТОЛЬКО когда клуб
   назвал хотя бы одну такую цену: пустой столбец с прочерками отвечает на
   вопрос «сколько стоит» хуже, чем его отсутствие. Ширина фиксирована, чтобы
   цены стояли в колонку и их можно было сравнить взглядом, а не по одной. */
.price-cols {
  display: flex; align-items: baseline; gap: var(--sp-4);
  flex: none; text-align: right;
}
.price-cols > * { min-width: 74px; }
.price-cols b {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-h2); font-variant-numeric: tabular-nums;
}
.price-cols b.is-empty { color: var(--c-ink-3); }

/* Шапка колонок — те же ширины, что и у строк, иначе подписи разъедутся. */
.price-head .price-cols > * { font-size: var(--fs-micro); }

/* Персональные — таблица: строка это ТРЕНЕР, столбцы это длительности. Ставка
   у каждого своя, и вопрос «к кому и за сколько» — это сравнение, а сравнивают
   по столбцу, а не листая карточки. Столбцов ровно столько, сколько
   длительностей кто-нибудь в клубе продаёт; у кого её нет — прочерк. */
.price-cols--3 { gap: var(--sp-2); }
.price-cols--3 > * { min-width: 60px; }

/* Имя тренера — единственная ячейка, которой позволено сжиматься и переноситься:
   цены должны стоять в колонку, а «НИКОЛАЙ ПОТЕКАЛО» на узком экране в одну
   строку не влезает. `.list__key` для этого не годится — у него `flex: none`. */
.price-row__name {
  min-width: 0;
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-sm); line-height: 1.15;
  text-transform: uppercase; letter-spacing: 0.02em;
  color: var(--c-ink);
}

/* Строка-ссылка ведёт к тренеру: прайс отвечает «сколько», а записываются на
   его карточке. Без сброса цвета анкер уносит поднятую строку в синий. */
a.list__row { color: inherit; text-decoration: none; }
a.list__row:active { background: var(--c-paper-2); }
a.list__row:hover .price-row__name { color: var(--c-red); }

/* ---------- 36. «НАЗАД» ----------------------------------------------------- */

/* .page-back — выход из экрана без своей вкладки. Стоит НАД кикером и
   названием: это первое, что нужно человеку, попавшему не туда, значит и
   встречать оно должно первым.
   Съедает верхний отступ `.page-head`, иначе шапка становится на строку выше
   на восьми экранах ради одной ссылки. Цель нажатия при этом остаётся полной:
   высота `--tap-min` набирается за счёт отрицательного margin, а не урезанием. */
.page-back {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  min-height: var(--tap-min);
  margin-top: calc(var(--sp-4) * -1);
  padding-right: var(--sp-3);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-xs); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--c-ink-2); text-decoration: none;
}
/* Шеврон крупнее подписи: в строке из мелких прописных букв он и есть то, что
   ловит глаз. Своя строчная высота, чтобы не растягивать ссылку. */
.page-back b { font-size: var(--fs-h1); line-height: 1; font-weight: var(--fw-body); }
.page-back:hover, .page-back:focus-visible { color: var(--c-red); }
.page-back:active { color: var(--c-red); transform: translateX(-1px); }

/* Свободный, но короткий старт: время НЕ занято, просто выбранная длительность
   упирается в следующую тренировку. Не зачёркиваем — зачёркнутое 9:30 клиент
   читал как чью-то тренировку в 9:30 (Доработки 14.09, пункты 7–8). Штриховка
   остаётся: выбрать нельзя. */
.slot.slot--short[disabled] b { text-decoration: none; }
.slot.slot--short[disabled] { background-image: none; border-style: dashed; }

/* Уровень значка. Цвета металла — постоянные, не токены темы: в дневной теме
   охра превращается в серый чернил, и «золото» стало бы неотличимо от
   «серебра» ровно там, где их просили различать. */
[data-tier="bronze"] { --tier: #B8692E; --tier-ink: #FFFFFF; }
[data-tier="silver"] { --tier: #8E979E; --tier-ink: #FFFFFF; }
[data-tier="gold"]   { --tier: #D9A21B; --tier-ink: #17130E; }
/* `.theme-hour .monogram` красит медаль в чернила — перебиваем специфичностью. */
body [data-tier] .monogram.badge-medal { background: var(--tier); color: var(--tier-ink); }
body .coach.is-locked[data-tier] .monogram.badge-medal { background: var(--tier); opacity: 0.45; }
.tier-tag {
  display: inline-block;
  padding: 1px var(--sp-2);
  font-family: var(--font-display); font-weight: var(--fw-strong);
  font-size: var(--fs-micro); letter-spacing: var(--ls-label); text-transform: uppercase;
  background: var(--tier, var(--c-paper-3)); color: var(--tier-ink, var(--c-ink));
}

/* Сумма рядом с кнопкой оплаты не переносится никогда. В подтверждении
   персональной записи строка оплаты стоит внутри врезки, где колонки делятся
   поровну, и «5 000 ₽» ломалось на «5 0 / 00 / ₽» (Доработки 14.09, пункт 3).
   Сумма берёт свою ширину, кнопка — остаток. */
.btn-row--split > .card__price { white-space: nowrap; }
.callout .btn-row--split:has(> .card__price) { grid-template-columns: auto 1fr; }
