/*
 * f7c_design — единая точка подключения дизайн-системы ячейки C.
 * 🔴 ЭТОТ ФАЙЛ СОБРАН из index.order.txt скриптом tools/build-index.py — РУКАМИ НЕ ПРАВИТЬ.
 * Правьте компонент и пересоберите; порядок — в index.order.txt.
 * Почему один файл, а не 27 @import: цепочка импортов даёт гонку — страница успевает
 * отрисоваться БЕЗ стилей (замер 15.08: 0 правил при отданных 200).
 */

/* ═══ tokens/tokens.css ═══ */
/*
 * f7c_design — дизайн-токены ячейки C (CSS custom properties).
 * Источник истины: tokens/tokens.json. Правки — только синхронно с ним.
 * Подключение: первым, до components/*.css.
 */
:root {
  /* ---- color.brand ---- */
  --ds-color-primary: #2456d6;
  --ds-color-primary-hover: #1b44af;
  --ds-color-primary-active: #153689;
  --ds-color-on-primary: #ffffff; /* текст/иконки на primary; контраст с primary >= 4.5 */
  --ds-color-accent: #0b7c7b;

  /* ---- color.text ---- */
  --ds-text-primary: #1a1f2b;
  --ds-text-secondary: #5a6274;
  --ds-text-muted: #8b92a3;
  --ds-text-inverse: #ffffff;
  --ds-text-link: var(--ds-color-primary);

  /* ---- color.surface ---- */
  --ds-surface-page: #ffffff;
  --ds-surface-raised: #f6f7f9;
  --ds-surface-sunken: #edeff3;
  --ds-surface-inverse: #161a23;

  /* ---- color.border ---- */
  --ds-border-default: #d9dde5;
  --ds-border-strong: #b7bdca;
  --ds-border-focus: var(--ds-color-primary);
  --ds-focus-ring: color-mix(in srgb, var(--ds-color-primary) 15%, transparent);

  /* ---- color.feedback ---- */
  --ds-color-success: #188a42;
  --ds-color-warning: #b45309;
  --ds-color-error: #c0262c;
  --ds-color-info: #2456d6;

  /* ---- typography ---- */
  --ds-font-base: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --ds-font-heading: var(--ds-font-base);
  --ds-font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;

  --ds-fs-xs: 0.75rem;
  --ds-fs-sm: 0.875rem;
  --ds-fs-md: 1rem;
  --ds-fs-lg: 1.125rem;
  --ds-fs-xl: 1.375rem;
  --ds-fs-2xl: 1.75rem;
  --ds-fs-3xl: 2.25rem;
  --ds-fs-4xl: 3rem;

  --ds-fw-regular: 400;
  --ds-fw-medium: 500;
  --ds-fw-semibold: 600;
  --ds-fw-bold: 700;
  --ds-fw-black: 800;

  --ds-lh-tight: 1.2;
  --ds-lh-base: 1.5;
  --ds-lh-loose: 1.7;

  /* Голос заголовков (тема бренда задаёт значения; нейтральные дефолты = текущий вид) */
  --ds-heading-weight: var(--ds-fw-bold);
  --ds-heading-tracking: 0;
  --ds-heading-leading: var(--ds-lh-tight);

  /* ---- space (шкала 4px) ---- */
  --ds-space-1: 0.25rem;
  --ds-space-2: 0.5rem;
  --ds-space-3: 0.75rem;
  --ds-space-4: 1rem;
  --ds-space-6: 1.5rem;
  --ds-space-8: 2rem;
  --ds-space-12: 3rem;
  --ds-space-16: 4rem;
  --ds-space-24: 6rem;

  /* ---- radius / shadow ---- */
  --ds-radius-sm: 4px;
  --ds-radius-md: 8px;
  --ds-radius-lg: 16px;
  --ds-radius-xl: 24px;
  --ds-radius-full: 9999px;
  /* Форма компонентов (тема задаёт: пилюля-кнопка, мягкие карточки) */
  --ds-radius-btn: var(--ds-radius-md);
  --ds-radius-card: var(--ds-radius-lg);
  --ds-card-radius: var(--ds-radius-card);
  --ds-card-icon-inline-margin: 0 0 var(--ds-space-3) var(--ds-space-4);
  --ds-card-promo-icon-size: 58px;
  --ds-card-ladder-icon-size: 104px;   /* значок ступени; умолчание = прежнее */
  --ds-card-ladder-glyph: 26px;        /* глиф внутри плиты ступени */
  --ds-stepper-btn-font: var(--ds-fs-lg);   /* кегль знака степпера; умолчание = прежнее */   /* значок промо-плитки; умолчание = прежнее */   /* роль радиуса КАРТОЧКИ; умолчание = прежнее */

  /* Строка FAQ. Умолчания = ПРЕЖНИЙ рендер (плоская поверхность + рамка): роль поверхности
     объявляет бренд. Объявлены здесь, а не только запасным значением в компоненте, потому что
     возврат тёмной у бренда обязан возвращать К ЧЕМУ-ТО — гейт «возврат светлой» ловит именно
     это: токен есть в тёмном возврате, а базового значения в ядре нет. */
  --ds-faq-gap: var(--ds-space-4);
  --ds-faq-item-bg: var(--ds-surface-page);
  --ds-faq-item-border-width: 1.5px;
  --ds-faq-item-border-color: var(--ds-border-default);
  --ds-faq-item-radius: var(--ds-radius-card);
  --ds-faq-item-shadow: none;
  --ds-faq-item-pad-left: 62px;
  --ds-faq-q-pad: var(--ds-space-4) 0;
  --ds-section-title-gap: var(--ds-space-8);
  --ds-calc-gap-after-tabs: 0;
  --ds-calc-panel-gap: 0;        /* карточка→блок итога; действует только при ДВУХ детях */   /* зазор табы→первый шаг; умолчание = прежний рендер */
  --ds-calc-panel-pad: var(--ds-space-8);
  --ds-calc-form-pad: var(--ds-space-8);
  --ds-calc-includes-gap: 0;   /* добор ритма карточки тарифа; умолчание = прежнее */   /* поля формы; умолчание = прежнее */   /* поля панели итога; умолчание = прежнее */
  --ds-calc-sum-gap-after-caption: var(--ds-space-2);   /* подпись → сумма */
  --ds-calc-sum-gap-after-total: var(--ds-space-6);     /* сумма → строка расчёта */
  --ds-calc-sum-gap-after-line: var(--ds-space-6);      /* строка расчёта → следующий узел */
  --ds-shadow-card: none;

  --ds-shadow-sm: 0 1px 2px rgba(22, 26, 35, 0.08);
  --ds-shadow-md: 0 4px 12px rgba(22, 26, 35, 0.1);
  --ds-shadow-lg: 0 12px 32px rgba(22, 26, 35, 0.14);
  /* цветная тень глубины (акцентные оболочки: ds-calc и т.п.) */
  --ds-shadow-brand: 0 24px 60px -18px color-mix(in srgb, var(--ds-color-primary) 22%, transparent);

  /* ---- layout ---- */
  /* 🔴 ПОВЕРХНОСТЬ ОКРАШЕННОЙ СЕКЦИИ — ОТДЕЛЬНО ОТ ПОВЕРХНОСТИ КОНТРОЛА (15.08).
     Замер канона forbion показал ДВЕ разные роли под одним нашим токеном:
       секции и карточки  → #FFFFFF (23 узла, основная площадь страницы)
       НЕАКТИВНЫЕ КОНТРОЛЫ (полоса вкладок калькулятора, невыбранные ступени цены) → #ECF2F2
     Один `--ds-surface-raised` обслуживал обе; сведя его к белому, мы бы СТЁРЛИ контролы,
     а оставив серым — держим секции не как в макете. Умолчание = прежнее значение,
     поэтому izibuh и tezum не двигаются. */
  --ds-surface-section: var(--ds-surface-raised);
  /* Поверхность КАРТОЧКИ-КОНТРОЛА (опция калькулятора и родня). Отдельная роль, потому что у
     канона это его собственный `--card`, и он НЕ равен ни странице, ни приподнятой поверхности:
     светлая #FFFFFF, тёмная #27271F. Здесь умолчание ролевое, число канона — в бренд-пакете. */
  --ds-surface-card: var(--ds-surface-raised);
  /* 🔴 ВЫДЕЛЕННЫЙ НОСИТЕЛЬ — ПОДЪЁМ К БЕЛОМУ, не «добавить цвета» (15.08).
     Канон выделяет карточку и строку ИНВЕРСИЕЙ: носитель светлеет, а его внутренние метки
     теряют подложку. Умолчания = прежний вид (поверхность секции, метка без фона). */
  --ds-surface-picked: var(--ds-surface-section);
  --ds-surface-picked-chip: transparent;
  /* 🔴 ЗАЗОР ВНУТРЕННЕЙ СЕТКИ ГЕРОЯ — ТОКЕН НАЗНАЧЕНИЯ (15.08, вопрос c-tpl).
     Канон forbion держит внутри текстовой колонки героя сетку 2×3 с зазором **44px**,
     а в нашей шкале такого шага нет (…24, 32, 48…). Шкалу НЕ трогаю: 44 — число канона
     forbion, в ядре оно стало бы обязательным для izibuh и tezum.
     ⚠ И не «48 с расхождением 4px»: 44 встречается в каноне ШЕСТЬ раз — это его значение,
     а не округление нашего. Умолчание = прежний ближайший шаг, чужие бренды не двигаются. */
  --ds-hero-grid-gap: var(--ds-space-12);
  /* 🔴 ВЕС ЦЕНЫ — ТОКЕН НАЗНАЧЕНИЯ (15.08, вопрос c-tpl «шкала кончилась»).
     Канон forbion держит вес **900** на 26 узлах — это цена, крупные числа и словомарк;
     самая тяжёлая ступень шкалы ДС — `--ds-fw-black` = 800. Тот же случай, что зазор 44:
     число живёт в каноне многократно, значит это ЕГО значение, а не округление нашего.
     Шкалу не трогаю — 900 обязательным для izibuh и tezum быть не должно. */
  --ds-fw-price: var(--ds-fw-black);
  /* 🔴 ПОВЕРХНОСТЬ ВЫДЕЛЕНИЯ «выбрано / входит / выгодно» (15.08, замер канона forbion).
     У канона это отдельная роль на 6+ носителях: выделенная строка тарифа («выбирают чаще»),
     16 чипов конфигураций, АКТИВНАЯ кнопка калькулятора (плюс рамка 1px primary),
     полоса «Бесплатно при переезде», колонка «У нас в тарифе». Чернила везде --ds-text-primary.
     ⚠ У нас такой роли не было ни одним токеном: акцентная колонка сравнения — СПЛОШНОЙ primary
     с инверсным текстом, то есть другой приём. Умолчание = прежняя приподнятая поверхность. */
  --ds-surface-highlight: var(--ds-surface-raised);

  --ds-container-max: 1200px;
  --ds-container-pad: var(--ds-space-4);
  /* 🔴 ШАПКА — СВОЯ ОСЬ ШИРИНЫ (15.08). Умолчание = контейнер, поэтому izibuh и tezum
     не двигаются ни на пиксель. Заведён потому, что канон forbion держит шапку ТЕКУЧЕЙ
     (`88%`), а секции — фиксированными: одним токеном две модели не разводятся, и попытка
     сделать шапку текучей через `--ds-container-max` растащила бы все секции сайта. */
  --ds-header-max: var(--ds-container-max);
  --ds-hero-media-max: none; /* ограничение ширины hero-media композиции (тема задаёт) */
  /* вертикальный ритм секций: на мобиле уменьшается (см. media ниже) */
  --ds-section-pad-y: var(--ds-space-16);
  --ds-section-pad-normal: var(--ds-section-pad-y);   /* роль «обычная»: умолчание = прежнее */

  /* ---- градиенты (темизируемые; только декор и интерактив с проверенным контрастом) ---- */
  --ds-gradient-brand: linear-gradient(118deg, var(--ds-color-primary), var(--ds-color-accent));
  /* Градиент ДЛЯ ТЕКСТА (сквозь глифы). Отдельный от --ds-gradient-brand НАМЕРЕННО:
     brand лежит ФОНОМ под белым текстом в 9 местах ядра (кнопка, панель калькулятора,
     колонка сравнения, купон, FAQ, сегмент...) — осветлив его, мы сломали бы их контраст.
     В светлой теме совпадает с brand: дельта 0. Тёмный вариант — ниже, в тёмном блоке. */
  --ds-gradient-text: var(--ds-gradient-brand);
  --ds-gradient-soft: linear-gradient(160deg, var(--ds-surface-raised), var(--ds-surface-sunken));
  /* панель итога (ds-calc__panel и т.п.); текст на ней — on-primary:
     контраст ≥4.5 к САМОЙ СВЕТЛОЙ точке градиента — обязанность темы */
  --ds-gradient-panel: var(--ds-gradient-brand);
  /* декоративный орб (ds-section--decor, ds-calc__panel--orb): цвет и размер — параметры */
  --ds-orb-color: color-mix(in srgb, var(--ds-color-accent) 25%, transparent);
  --ds-orb-size: 280px;
  /* палитра декоративных свечений-орбов (blur-пятна за контентом; тема задаёт).
     Дефолты выведены из существующих токенов — система не знает про конкретные цвета. */
  --ds-orb-color-primary: color-mix(in srgb, var(--ds-color-primary) 16%, transparent);
  --ds-orb-color-accent: color-mix(in srgb, var(--ds-color-accent) 16%, transparent);
  --ds-orb-color-alt: color-mix(in srgb, var(--ds-color-primary) 10%, var(--ds-color-accent) 10%);
  --ds-mark-color: color-mix(in srgb, var(--ds-color-accent) 30%, transparent);
  /* интерактивные акценты контролов (тема может усилить: сегмент — градиент, ползунок — акцент) */
  --ds-segment-active-bg: var(--ds-color-primary);
  --ds-range-color: var(--ds-color-primary);
  /* карусель (ds-carousel): дефолты наследуют ядро → бренд получает тему без правок;
     точечный override — через тему при расширении контракта (письмо-ДО c-tpl) */
  --ds-carousel-dot-idle: var(--ds-border-strong);
  --ds-carousel-dot-active: var(--ds-gradient-brand);
  --ds-carousel-arrow-bg: var(--ds-surface-page);
  --ds-carousel-arrow-border: var(--ds-border-default);
  --ds-carousel-arrow-ink: var(--ds-color-primary);
  /* ---- движение (v0.5.0; темизируемые) ---- */
  --ds-motion-scale: 1; /* 0 = всё движение выключено (reduced-motion ставит 0) */
  --ds-motion-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ds-anim-rise-duration: 0.7s;
  --ds-anim-drift-duration: 14s;
  --ds-anim-float-duration: 6s;
  --ds-anim-pop-duration: 0.5s;
  --ds-anim-bump-duration: 0.3s;
  --ds-marquee-duration: 45s;
  --ds-marquee-item-width: 340px;
  --ds-steps-columns: 5;

  /* Форма разделителя секций (маска для .ds-divider); дефолт — пологая волна,
     бренд может заменить своей формой в теме */
  --ds-divider-shape: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 70" preserveAspectRatio="none"><path d="M0,35 C240,70 480,0 720,28 C960,56 1200,14 1440,42 L1440,70 L0,70 Z"/></svg>');
}

/*
 * Брейкпоинты (mobile-first, min-width) — CSS-переменные в media query не работают,
 * значения фиксируются контрактом и дублируются в компонентах как есть:
 *   sm: 640px | md: 768px | lg: 1024px | xl: 1280px
 */

/* Мобильная адаптация значений токенов (глобально; компоненты используют их) */
@media (max-width: 767px) {
  :root {
    /* 🔴 РАСХОЖДЕНИЕ С КАНОНОМ, чинить ТОЛЬКО В СВЯЗКЕ (пометка c-design-2 как держателя канона).
       Здесь 12px (space-3), а канон concept-v2.2.0:612/:636 даёт .wrap padding 18px/16px.
       Следствие уже ловили: вылет насечки купона 15px > гаттера 12px переполнял планшет
       480-767 на 3px даже у канон-верно размещённого купона (v0.44.0 закрыл СИМПТОМ через
       min(), но само расхождение живёт и всплывёт другими краевыми элементами).
       Это ВТОРОЕ расхождение метрик контейнера с каноном. Первое — рендер 1132 против
       канонных 1100 из-за отсутствия глобального box-sizing (см. components/coupon/coupon.css).
       ⚠ НЕ ПРАВИТЬ ПООДИНОЧКЕ. Обе правки двигают вид и требуют ре-визы c-design-2, поэтому
       делаются ОДНИМ окном: *{box-sizing:border-box} (1132->1100) + гаттер планшета к 16-18px.
       Одно окно = один сдвиг, одна ре-виза, один показ. Окно назначает c-design-2 ПОСЛЕ показа
       прайс-пачки владельцу (сразу после принятой победы принятый экран не двигают).
       📋 УСЛОВИЯ ИСПОЛНЕНИЯ И ЧЕК-ЛИСТ РЕ-ВИЗЫ: docs/WINDOW-container-metrics.md
       (там же: дельты НЕ аддитивны, порядок «обе правки -> один прогон -> ре-виза по итогу»,
       опорные bounding-rect стрелок ДО окна для сверки сдвига). */
    --ds-container-pad: var(--ds-space-3);   /* поля контейнера уже на планшете */
    --ds-section-pad-y: var(--ds-space-12);   /* меньше вертикали секций */
  }
}
@media (max-width: 479px) {
  :root {
    --ds-container-pad: var(--ds-space-4);    /* на телефоне вернуть комфортные поля */
    --ds-section-pad-y: var(--ds-space-8);    /* компактный ритм */
    --ds-orb-size: 200px;                     /* мельче свечения — не давят на узком */
    --ds-marquee-item-width: 280px;           /* карточка ленты влезает на 320px */
  }
}

/* Крупные тач-таргеты на сенсорных экранах (WCAG 2.5.5 ≥ 44px) */
@media (pointer: coarse) {
  .ds-segment__btn { min-height: 44px; }
  .ds-stepper__btn { min-height: 44px; }
  .ds-stepper__input { min-height: 44px; }
}

/* Базовая типографика страницы */
body {
  font-family: var(--ds-font-base);
  font-size: var(--ds-fs-md);
  line-height: var(--ds-lh-base);
  color: var(--ds-text-primary);
  background: var(--ds-surface-page);
  margin: 0;
}

/* 🔴 color: inherit, а НЕ var(--ds-text-primary). Разбор D-8 (замер c-design-2, светлая тема):
   заголовок внутри do-not-flip поверхности оставался ЧЕРНИЛЬНЫМ на брендовом градиенте —
   .ds-compare__col--gradient задаёт color: on-primary, но h3 не наследовал его, потому что
   ЯВНЫЙ цвет здесь бил наследование. Явный цвет на h1-h4 — и есть корень.
   Класс, а не случай: поверхностей, задающих собственный color, в ядре 14 (on-primary,
   text-inverse, ...). Лечили их ПЕРЕЧИСЛЕНИЕМ (.ds-section--inverse h1..h4 — прецедент в
   section.css), а для .ds-compare__col--gradient и .ds-split-panel перечислить забыли.
   Правило, которое надо не забыть, — не механизм: оно ломается ровно на следующем компоненте.
   inherit чинит все 14 сразу и будущие тоже: body задаёт --ds-text-primary, поэтому на обычной
   странице цвет ТОТ ЖЕ (дельта 0), а на цветной поверхности заголовок наследует её цвет.
   ⚠ Обратный риск проверен по эталонам: заголовок внутри контейнера с muted/secondary цветом
   стал бы приглушённым. Такой в ядре ровно один — .ds-split-panel__col--card h3, ему оставлен
   явный --ds-text-primary (см. split-panel.css). У .ds-compare__col--muted цвет висит на
   __item, а не на колонке, поэтому её заголовок не задет.
   🔴 В ТЁМНОЙ ТЕМЕ ЭТОТ ДЕФЕКТ НЕВИДИМ: text-primary там почти белый и случайно совпадает с
   верным ответом. Мы принимали страницу в тёмной — зелёный в dark не доказывал ничего про light. */
h1, h2, h3, h4 {
  font-family: var(--ds-font-heading);
  line-height: var(--ds-heading-leading);
  letter-spacing: var(--ds-heading-tracking);
  color: inherit;
  margin: 0 0 var(--ds-space-4);
}

h1 { font-size: var(--ds-fs-3xl); font-weight: var(--ds-heading-weight); }
h2 { font-size: var(--ds-fs-2xl); font-weight: var(--ds-heading-weight); }
h3 { font-size: var(--ds-fs-xl); font-weight: var(--ds-fw-semibold); }
h4 { font-size: var(--ds-fs-lg); font-weight: var(--ds-fw-semibold); }

a {
  color: var(--ds-text-link);
  text-decoration: none;
}
a:hover { text-decoration: underline; }

/* Базовый ресет картинок. Заведён 17.07 по находке c-ops (playwright@360: горизонтальный
   скролл) + c-tpl. Класс дефекта: bare <img> с атрибутом width (напр. подвальное лого
   width="400") даёт min-content = 400px -> на вьюпорте 360 горизонтальный скролл.
   Точечные правила ядра (.ds-header__logo img, .ds-card__media img) закрывали только свои
   места; этот ресет закрывает КЛАСС для любой будущей картинки.

   Проверено c-tpl СИМУЛЯЦИЕЙ на живой сборке izibuh (addStyleTag, замер ВСЕХ <img> до/после,
   vw=1400 и vw=360): визуальной дельты НЕТ ни у одного img — шапку ядро клампит специфичнее,
   подвал c-tpl зажал у себя (.tpl-footer-logo img, v0.61.2). То есть ресет = чистое усиление
   на будущее, не лечение сегодняшнего случая (тот уже закрыт на стороне шаблона).

   Почему нужен, хотя сегодня дельта нулевая (аргумент c-tpl): `heroMedia.inline` — opt-in
   ПОБРЕНДОВО. izibuh его включил (inline-SVG), но будущий бренд с heroMedia и БЕЗ inline
   получит именно <img> — и без этого ресета повторит тот же overflow.
   height:auto держит аспект при явных width/height атрибутах. Согласовано письмом-ДО с c-tpl
   (контракт: изменения ядра, затрагивающие его рендер, — до тега). */
img { max-width: 100%; height: auto; }

.ds-container {
  max-width: var(--ds-container-max);
  margin: 0 auto;
  padding: 0 var(--ds-container-pad);
}

/* ─────────────────────────────────────────────────────────────────────────
 * ТЁМНАЯ ТЕМА — наряд integrator-c 15.07 (виза владельца).
 *
 * СПЕЦИФИЧНОСТЬ = УСЛОВИЕ РАБОТЫ, а не стиль записи.
 * Порядок загрузки: tokens.css → brands/<b>/theme.css, и бренд объявляет `:root {…}`.
 * Тёмный блок вида `@media { :root {…} }` имел бы ТУ ЖЕ специфичность (0,1,0), а бренд
 * грузится ПОЗЖЕ → перебил бы его, и тёмная тема молча не работала бы ни в одном бренде,
 * то есть НИГДЕ. Селекторы ниже дают (0,2,0) и перебивают брендовый `:root`.
 * Бренду со своей тёмной палитрой нужна та же специфичность (не `:root`).
 * NB: `:where()` здесь применять НЕЛЬЗЯ — его специфичность ноль, защита обнулится.
 *
 * DO-NOT-FLIP (CELL.md п.12 «ломается ПАРА, а не значение») — здесь НЕ переопределены:
 *   --ds-color-primary/-hover/-active, --ds-color-accent — цвета бренда, не темы;
 *   --ds-color-on-primary  — СПАРЕН с primary; primary не переворачивается → и он тоже,
 *                            КАКИМ БЫ бренд его ни задал. У forbion он ЧЁРНЫЙ (#1d1d1b):
 *                            зелёный #70b62c с белым даёт 2.50. Правило «в тёмной теме
 *                            делаем его тёмным» сломало бы forbion в ОБРАТНУЮ сторону;
 *   --ds-text-inverse      — СПАРЕН с --ds-surface-inverse;
 *   --ds-surface-inverse   — фиксированная тёмная поверхность (инверс-полосы, подвал).
 * Проверяется НЕГАТИВНЫМ ТЕСТОМ в tools/check.sh, а не глазами.
 *
 * link/border-focus переопределены НАМЕРЕННО, хотя выводятся из primary: primary как
 * ЗАЛИВКА кнопки обязан остаться (пара с on-primary), но primary как ТЕКСТ на тёмном
 * не читается. Связь рвём точечно — это и есть «не трогать пару», а не «не трогать значение».
 *
 * Производные (--ds-gradient-soft, --ds-orb-color-*, --ds-mark-color, --ds-shadow-brand,
 * --ds-segment-active-bg, --ds-range-color) собраны через var()/color-mix — едут за темой
 * сами, отдельных переопределений не требуют.
 * ───────────────────────────────────────────────────────────────────────── */
:root[data-theme="dark"] {
  --ds-surface-card: var(--ds-surface-raised);   /* пара к светлой; значение бренда — в его тёмном блоке */
  --ds-surface-page:    #1a1e27;
  --ds-surface-raised:  #242a36;
  /* 🔴 15.08, ПОСЛЕ ЖИВОГО ДЕФЕКТА: `--ds-surface-section` был заведён только в светлом `:root`.
     Тёмные блоки его НЕ переопределяли — и брендовое светлое значение переехало в тёмную тему:
     на живом секции `#calc` и `#navi` стали БЕЛЫМИ при светлом тексте, 46 узлов из 55 ниже AA,
     худший контраст 1.06. Нашёл c-ops вычитанием базы «до»; глазами в светлой тег бы приняли.
     Умолчание тёмной = тёмная приподнятая поверхность, как у всех прочих. */
  --ds-surface-section: var(--ds-surface-raised);
  --ds-surface-highlight: var(--ds-surface-raised);
  --ds-surface-picked: var(--ds-surface-section);
  --ds-surface-picked-chip: transparent;
  --ds-surface-sunken:  #2f3543;
  --ds-text-primary:    #f6f8fd;
  --ds-text-secondary:  #d2d8e6;
  --ds-text-muted:      #a5adc2;
  --ds-border-default:  #39404f;
  --ds-border-strong:   #545e73;
  --ds-text-link:       #93a4f0;
  --ds-border-focus:    #93a4f0;
  --ds-color-success:   #4fd08a;
  --ds-color-warning:   #e8b366;
  --ds-color-error:     #f08c90;
  --ds-color-info:      #93a4f0;
  --ds-shadow-sm:       0 1px 2px rgba(0, 0, 0, 0.45);
  --ds-shadow-md:       0 4px 12px rgba(0, 0, 0, 0.5);
  --ds-shadow-lg:       0 12px 32px rgba(0, 0, 0, 0.6);
  --ds-focus-ring:      rgba(147, 164, 240, 0.28);
  /* Фон секций .ds-section--soft. Заведён в тёмный блок 17.07 по признаку владельца
     («тёмная тема не каскадирует на секции»). Класс тот же, что mark-color/orb: БРЕНД
     задаёт --ds-gradient-soft светлым ЛИТЕРАЛОМ в своём :root (izibuh: #f2f4fe→#fdeef7),
     а ядро тёмного варианта не имело → в тёмной секции --soft оставались СВЕТЛЫМИ
     (герой тёмный, полоса services светлая, calc тёмный — разрыв). Ядро задаёт тёмный
     дефолт: специфичность :root[data-theme=dark] (0,2,0) перебивает брендовый :root
     (0,1,0). Бренд, желающий СВОЙ тёмный градиент, задаёт :root[data-theme=dark] в
     theme.css (0,2,0, грузится позже — выиграет). Замерено на живом: единственный
     каскад-дефект секций; surface-inverse/gradient-panel уже тёмные, gradient-brand
     (кнопки) ярким и должен остаться. */
  --ds-gradient-soft:   linear-gradient(160deg, var(--ds-surface-raised), var(--ds-surface-sunken));
  /* Стрелки карусели в тёмной (признак владельца/c-design-2 17.07: стрелки сливаются).
     arrow-ink=primary (do-not-flip синий) на тёмном фоне даёт 2.5, иконка (SVG stroke=
     currentColor) почти не видна; рамка тоже тёмная. Класс тот же, что secondary/stepper/
     mark. Задаём светлые варианты: иконка -- text-primary (виден на тёмном), рамка --
     border-strong (контур читается). arrow-bg=surface-page флипается сам. */
  --ds-carousel-arrow-ink:    var(--ds-text-primary);
  --ds-carousel-arrow-border: var(--ds-border-strong);
  /* Подсветка ds-mark. Заведена в тёмный блок 17.07 по признаку владельца (обвёл «изи»
     в h1). ⚠ Диагноз наряда «тёмное на тёмном, нечитаемо» — НЕ подтвердился: проверил
     рендером на CSS СБОРКИ (DESIGN=v0.28.0, mark.css побайтно тот же) — текст наследует
     --ds-text-primary, который в тёмной СВЕТЛЫЙ, слово читается. Реальный дефект мягче:
     подложка ВЫЦВЕТАЕТ. Канон это подтверждает независимо — у него
     `:root[data-theme="dark"] mark → rgba(240,85,159,.45)` против .32 в светлой.

     КЛАСС, а не случай (прогон тёмной темы 17.07): токен с АЛЬФОЙ от брендового цвета,
     положенный на ПОВЕРХНОСТЬ. Альфа подбиралась под светлую поверхность; сам цвет не
     флипается (do-not-flip, и правильно), а поверхность под ним флипается — та же альфа
     на тёмном читается слабее. В этом классе ещё --ds-orb-color* (см. письмо).
     Токены, выведенные из ФЛИПАЮЩИХСЯ (carousel-arrow-bg → surface-page, arrow-border →
     border-default, gradient-soft → surface-raised), лечатся сами — проверено. */
  --ds-mark-color:      color-mix(in srgb, var(--ds-color-accent) 45%, transparent);
    /* Текстовый градиент в тёмной: стопы осветлены 70/30 белым. Считается из ТОКЕНОВ БРЕНДА
       (primary/accent), поэтому работает для любого бренда без знания его стопов.
       izibuh: 2.52/3.31/3.25 -> 4.70/~6.7/5.18 к фону #1a1e27 — AA(4.5) проходит.
       Дефект D-9 (замер c-design-2): брендовый градиент НЕ флипается, а фон страницы флипается —
       зеркало D-8. Там был флипающий текст на не-флипающейся поверхности, здесь наоборот. */
    --ds-gradient-text: linear-gradient(118deg,
      color-mix(in srgb, var(--ds-color-primary) 70%, white),
      color-mix(in srgb, var(--ds-color-accent) 70%, white));
    /* Свечение в ТЁМНОЙ теме — 12% вместо 22% светлой (расчёт c-design-2, подтверждён моим
       замером композита). Соотношение, а не вкус: светлая при .22 даёт ореолу контраст к фону
       1.29 — это норма заметности канона; тёмная при .12 даёт 1.16, то есть заметно тише
       светлой («приглушённое» по решению владельца 20.07), но выше порога различимости 1.08.
       Ниже .10 свечение перестаёт нести смысл и читается шумом рендера.
       🔴 Канон :41 в тёмной свечение УБИРАЛ (чёрная тень глубины) — решение владельца его
       перекрывает, и канон меняется вместе с ним (тег концепта c-design-2). Это НЕ отклонение:
       отклонение заводят, когда стенд расходится с каноном, здесь канон меняется сам. */
    --ds-shadow-brand: 0 24px 60px -18px color-mix(in srgb, var(--ds-color-primary) 12%, transparent);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ds-surface-page:    #1a1e27;
    --ds-surface-raised:  #242a36;
    --ds-surface-section: var(--ds-surface-raised);   /* см. довод в блоке [data-theme=dark] выше */
    --ds-surface-highlight: var(--ds-surface-raised);
    --ds-surface-sunken:  #2f3543;
    --ds-text-primary:    #f6f8fd;
    --ds-text-secondary:  #d2d8e6;
    --ds-text-muted:      #a5adc2;
    --ds-border-default:  #39404f;
    --ds-border-strong:   #545e73;
    --ds-text-link:       #93a4f0;
    --ds-border-focus:    #93a4f0;
    --ds-color-success:   #4fd08a;
    --ds-color-warning:   #e8b366;
    --ds-color-error:     #f08c90;
    --ds-color-info:      #93a4f0;
    --ds-shadow-sm:       0 1px 2px rgba(0, 0, 0, 0.45);
    --ds-shadow-md:       0 4px 12px rgba(0, 0, 0, 0.5);
    --ds-shadow-lg:       0 12px 32px rgba(0, 0, 0, 0.6);
    --ds-focus-ring:      rgba(147, 164, 240, 0.28);
  /* Фон секций .ds-section--soft. Заведён в тёмный блок 17.07 по признаку владельца
     («тёмная тема не каскадирует на секции»). Класс тот же, что mark-color/orb: БРЕНД
     задаёт --ds-gradient-soft светлым ЛИТЕРАЛОМ в своём :root (izibuh: #f2f4fe→#fdeef7),
     а ядро тёмного варианта не имело → в тёмной секции --soft оставались СВЕТЛЫМИ
     (герой тёмный, полоса services светлая, calc тёмный — разрыв). Ядро задаёт тёмный
     дефолт: специфичность :root[data-theme=dark] (0,2,0) перебивает брендовый :root
     (0,1,0). Бренд, желающий СВОЙ тёмный градиент, задаёт :root[data-theme=dark] в
     theme.css (0,2,0, грузится позже — выиграет). Замерено на живом: единственный
     каскад-дефект секций; surface-inverse/gradient-panel уже тёмные, gradient-brand
     (кнопки) ярким и должен остаться. */
  --ds-gradient-soft:   linear-gradient(160deg, var(--ds-surface-raised), var(--ds-surface-sunken));
  /* Стрелки карусели в тёмной (признак владельца/c-design-2 17.07: стрелки сливаются).
     arrow-ink=primary (do-not-flip синий) на тёмном фоне даёт 2.5, иконка (SVG stroke=
     currentColor) почти не видна; рамка тоже тёмная. Класс тот же, что secondary/stepper/
     mark. Задаём светлые варианты: иконка -- text-primary (виден на тёмном), рамка --
     border-strong (контур читается). arrow-bg=surface-page флипается сам. */
  --ds-carousel-arrow-ink:    var(--ds-text-primary);
  --ds-carousel-arrow-border: var(--ds-border-strong);
  /* Подсветка ds-mark. Заведена в тёмный блок 17.07 по признаку владельца (обвёл «изи»
     в h1). ⚠ Диагноз наряда «тёмное на тёмном, нечитаемо» — НЕ подтвердился: проверил
     рендером на CSS СБОРКИ (DESIGN=v0.28.0, mark.css побайтно тот же) — текст наследует
     --ds-text-primary, который в тёмной СВЕТЛЫЙ, слово читается. Реальный дефект мягче:
     подложка ВЫЦВЕТАЕТ. Канон это подтверждает независимо — у него
     `:root[data-theme="dark"] mark → rgba(240,85,159,.45)` против .32 в светлой.

     КЛАСС, а не случай (прогон тёмной темы 17.07): токен с АЛЬФОЙ от брендового цвета,
     положенный на ПОВЕРХНОСТЬ. Альфа подбиралась под светлую поверхность; сам цвет не
     флипается (do-not-flip, и правильно), а поверхность под ним флипается — та же альфа
     на тёмном читается слабее. В этом классе ещё --ds-orb-color* (см. письмо).
     Токены, выведенные из ФЛИПАЮЩИХСЯ (carousel-arrow-bg → surface-page, arrow-border →
     border-default, gradient-soft → surface-raised), лечатся сами — проверено. */
  --ds-mark-color:      color-mix(in srgb, var(--ds-color-accent) 45%, transparent);
      /* Текстовый градиент в тёмной: стопы осветлены 70/30 белым. Считается из ТОКЕНОВ БРЕНДА
       (primary/accent), поэтому работает для любого бренда без знания его стопов.
       izibuh: 2.52/3.31/3.25 -> 4.70/~6.7/5.18 к фону #1a1e27 — AA(4.5) проходит.
       Дефект D-9 (замер c-design-2): брендовый градиент НЕ флипается, а фон страницы флипается —
       зеркало D-8. Там был флипающий текст на не-флипающейся поверхности, здесь наоборот. */
    --ds-gradient-text: linear-gradient(118deg,
      color-mix(in srgb, var(--ds-color-primary) 70%, white),
      color-mix(in srgb, var(--ds-color-accent) 70%, white));
    /* Свечение в ТЁМНОЙ теме — 12% вместо 22% светлой (расчёт c-design-2, подтверждён моим
       замером композита). Соотношение, а не вкус: светлая при .22 даёт ореолу контраст к фону
       1.29 — это норма заметности канона; тёмная при .12 даёт 1.16, то есть заметно тише
       светлой («приглушённое» по решению владельца 20.07), но выше порога различимости 1.08.
       Ниже .10 свечение перестаёт нести смысл и читается шумом рендера.
       🔴 Канон :41 в тёмной свечение УБИРАЛ (чёрная тень глубины) — решение владельца его
       перекрывает, и канон меняется вместе с ним (тег концепта c-design-2). Это НЕ отклонение:
       отклонение заводят, когда стенд расходится с каноном, здесь канон меняется сам. */
    --ds-shadow-brand: 0 24px 60px -18px color-mix(in srgb, var(--ds-color-primary) 12%, transparent);
}
}

/* ═══ components/section/section.css ═══ */
/* f7c_design :: Секция страницы (обвязка + hero). Требует tokens/tokens.css. */
/* 🔴 СЕКЦИЯ ПОЗИЦИОНИРОВАНА (15.08). Базовая секция не была `relative` — только `--decor`.
   Следствие поймано на живом: сфера с местом `formats-right` (`top: 67px`) отсчиталась
   ОТ СТРАНИЦЫ и встала ВНУТРИ ШАПКИ, хотя объявлена в секции форматов правильно.
   ⚠ Виновником выглядел конфиг (`c-web-1` проверил — сфера в нужной секции с v46), а предмет
   был здесь: у носителя не было системы отсчёта. `place` даёт координату ВНУТРИ носителя —
   значит носитель обязан ею быть.
   Канон делает так же: его базовое правило `section { padding:76px 0; position:relative }`. */
/* 🔴 ВЕРТИКАЛЬНОЕ ПОЛЕ СЕКЦИИ — РОЛЬ, А НЕ ОДНО ЧИСЛО (партия 9, замер 1366).
   У канона их ШЕСТЬ, и каждое привязано к классу роли:
       0 герой · 24 калькулятор · 32 сравнение · 50 обычная · 62 «что входит»/сервер · 96 форма
   У нас **64 на всех** — отсюда систематический прирост: калькулятор +80, обычные +28,
   форма −64 (мы там КОРОЧЕ канона). В сумме по странице это около 200 из 1452.
   Модификаторы читают токены, умолчание каждого = текущее общее значение: izibuh и tezum
   не двигаются, пока потребитель не поставит класс. Значения канона — в бренд-пакете. */

.ds-section {
  /* 🔴 ДЛИННЫЕ СВОЙСТВА, А НЕ СОКРАЩЁННОЕ (находка `c-tpl`, 15.08). Здесь стояло
     `padding: var(--ds-section-pad-y) 0`, и сокращённое свойство **сбрасывало `padding-block`
     целиком** — все четыре ролевых модификатора были инертны, хотя стояли выше и с той же
     специфичностью. Правило-победитель не «сильнее», оно просто пишет ОБЕ оси. */
  /* 🔴 РОЛЬ «ОБЫЧНАЯ СЕКЦИЯ» — ИМЕНОВАННАЯ, А НЕ УМОЛЧАНИЕ ШКАЛЫ (ре-виза строки 102,
     виза integrator-c2, 15.08). Канон держит ШЕСТЬ значений поля по ролям (0 · 24 · 32 · 50 ·
     62 · 96); четыре крайних уже вышли модификаторами, пятое — «обычная 50» — до сих пор
     приходило умолчанием `--ds-section-pad-y` (64) и потому было без автора.
     ⚠ 50 в шкале (4·8·12·16·24·32) НЕТ, и шкалу под одно число я не расширяю: у величины
     должен быть свой адрес, иначе через неделю её «поправят на 48 для равномерности».
     Умолчание = прежнее: кто токен не объявляет (izibuh, tezum), получает `--ds-section-pad-y`.
     Герой не затронут по построению: у него поле задаёт сокращённое `padding` потребителя
     (`.tpl-hero--flush{padding:0}`), а оно сбрасывает `padding-block` целиком. */
  padding-block: var(--ds-section-pad-normal, var(--ds-section-pad-y));
  padding-inline: 0;
  background: var(--ds-surface-page);
  position: relative;
}

.ds-section--raised { background: var(--ds-surface-section); }

.ds-section--inverse {
  background: var(--ds-surface-inverse);
  color: var(--ds-text-inverse);
}
/* Перечисление h1-h4 здесь УДАЛЕНО в v0.45.0: с тех пор h1-h4 наследуют цвет родителя
   (tokens.css), и заголовок берёт --ds-text-inverse от самой секции. Это был ПРЕЦЕДЕНТ
   лечения перечислением — и именно его забыли повторить для .ds-compare__col--gradient
   и .ds-split-panel (дефект D-8). Оставить дубль значило бы учить копировать приём,
   который ломается на следующем компоненте. */

/* Компактный ритм для плотных страниц */
.ds-section--tight { padding: var(--ds-space-8) 0; }

/* Декоративный слой: мягкий градиентный фон (значение задаёт тема/бренд-пакет) */
.ds-section--soft { background: var(--ds-gradient-soft); }

/* Разделитель-волна между секциями. Форма — ПАРАМЕТР (--ds-divider-shape,
   маска data-uri), цвет — фон следующей секции. Бренд может заменить форму
   в своей теме, система формы не навязывает.
   🔴 15.08: ПОЙМАНО НА СЕБЕ ПОСЛЕ СВОЕЙ ЖЕ ПРАВКИ. Разделитель красился
   `--ds-surface-raised` — «фоном следующей секции». Разведя роли (секция → белая,
   контрол → #ECF2F2), я оставил здесь КОНТРОЛЬНУЮ поверхность: комментарий обещал
   фон секции, а правило брало цвет вкладок. Волна отделяла бы секцию от цвета,
   которого рядом нет — и выглядела бы как «так задумано».
   ⚠ Класс тот же, что c-tpl нашёл у себя часом раньше: правило читается как решение
   и решает не то. Своя правка ломает своё же соседнее правило ТИШЕ, чем чужое. */
.ds-divider {
  display: block;
  /* 🔴 15.08: ВЫСОТА И ЦВЕТ — ТОКЕНЫ (возврат снятого предмета, находка c-tpl).
     Замер канона: волна — **инлайновый <svg><path>** первым и последним ребёнком БЕЛОЙ секции,
     высота **32**, заливка — **цвет СТРАНИЦЫ** (#F7FAF3), ширина во всю секцию.
     У нас было 70 и заливка цветом следующей секции. Умолчания прежние. */
  height: var(--ds-divider-height, 70px);
  margin-bottom: -1px;
  background: var(--ds-divider-color, var(--ds-surface-section));
  -webkit-mask: var(--ds-divider-shape, none) center / 100% 100% no-repeat;
  mask: var(--ds-divider-shape, none) center / 100% 100% no-repeat;
}
.ds-divider--soft { background: var(--ds-gradient-soft); }
.ds-divider--flip { transform: scaleY(-1); }

/* Декоративный орб секции: цвет/размер — токены-параметры (--ds-orb-*).
   Не хардкод: бренд задаёт значения темой, конфиг сайта включает модификатором. */
.ds-section--decor {
  position: relative;
  overflow: hidden;
}

.ds-section--decor::before {
  content: "";
  position: absolute;
  width: var(--ds-orb-size);
  height: var(--ds-orb-size);
  border-radius: 50%;
  top: calc(var(--ds-orb-size) / -3);
  right: calc(var(--ds-orb-size) / -4);
  background: var(--ds-orb-color);
  filter: blur(70px);
  pointer-events: none;
}

.ds-section--decor-left::before {
  right: auto;
  left: calc(var(--ds-orb-size) / -4);
}

.ds-section--decor > * { position: relative; }

/* ── ВЕРТИКАЛЬНОЕ ПОЛЕ ПО РОЛИ СЕКЦИИ (партия 9; исправлено по замеру `c-tpl`) ──────────
   🔴 ИМЕНА РАЗВЕДЕНЫ НАМЕРЕННО: в файле уже жил `.ds-section--tight` с ДРУГИМ смыслом
   («компактный ритм плотных страниц», `--ds-space-8`). Мои роли назывались так же — и класс
   менял вид, **но не по той причине и не на то число**: калькулятор давал 32 вместо 24 и
   вместо 64. Это тише всех прочих отказов: «что-то изменилось» читается как «работает».
   ⇒ новая шкала зовётся `--pad-*`, старый `--tight` остаётся как был, ничей смысл не тронут.
   Значения канона — в бренд-пакете; умолчание каждого = текущее общее поле. */
.ds-section--pad-tight   { padding-block: var(--ds-section-pad-tight,   var(--ds-section-pad-y)); }
.ds-section--pad-compact { padding-block: var(--ds-section-pad-compact, var(--ds-section-pad-y)); }
.ds-section--pad-wide    { padding-block: var(--ds-section-pad-wide,    var(--ds-section-pad-y)); }
.ds-section--pad-final   { padding-block: var(--ds-section-pad-final,   var(--ds-section-pad-y)); }

.ds-section__inner {
  max-width: var(--ds-container-max);
  margin: 0 auto;
  padding: 0 var(--ds-container-pad);
}

.ds-section__title {
  /* 🔴 ЭТИ 32 СХЛОПЫВАЛИСЬ С ОТСТУПОМ ПОТРЕБИТЕЛЯ И ТИХО ПОБЕЖДАЛИ (нашёл `c-tpl`, 15.08).
     Он объявил `margin-top: 18` у сетки секции «что входит» — правило применялось, значение
     доехало, а на экране оставалось 32: соседние вертикальные margin'ы СХЛОПЫВАЮТСЯ в
     `max(32, 18)`. **У величины снова два автора, и второй — я.** Причём мой автор невидим
     для потребителя: он смотрит на свой отступ и видит его же в `computed`.
     Токен не меняет умолчание (прежние 32) — он даёт потребителю право снять МОЮ половину
     там, где ритм секции его. ⚠ И правильный ход у него — НЕ ставить `0` рядом со своим
     margin'ом, а провести своё число через этот токен: тогда у зазора снова один автор. */
  margin-bottom: var(--ds-section-title-gap, var(--ds-space-8));
}

/* ---- Hero: первый экран ---- */
.ds-hero {
  padding: var(--ds-section-pad-y) 0;
}

@media (min-width: 768px) {
  .ds-hero { padding: var(--ds-space-24) 0; }
}

.ds-hero__inner {
  max-width: var(--ds-container-max);
  margin: 0 auto;
  padding: 0 var(--ds-container-pad);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-6);
  align-items: flex-start;
}

.ds-hero--center .ds-hero__inner {
  align-items: center;
  text-align: center;
}

/* 🔴 КЕГЛЬ ЗАГОЛОВКА ГЕРОЯ — ТОКЕН (15.08, находка c-tpl на высоте первого экрана).
   Наш заголовок стоял фиксированным (`4xl` = 48px) на всех ширинах ≥768, а канонный ТЕЧЁТ.
   Замер канона по восьми ширинам дал точную формулу — она же лежит в его css:
     `clamp(28px, 3vw, 36px)`  → 28 на 901 · 30.72 на 1024 · 36 с 1200 и выше
     ниже 901 (мобильная раскладка) — фиксированные 34px
   Следствие фиксации: высота h1 у нас **403px против канонных 166** на 1024, и первый экран
   выходит 822 против 656. Символов при этом почти поровну (46 против 44) — **дело не в тексте,
   а в кегле**: один узел даёт бо́льшую часть разницы.
   ⚠ Умолчания прежние (`3xl` / `4xl` / `2xl`), izibuh и tezum не двигаются. */
.ds-hero__title {
  font-size: var(--ds-hero-title-size, var(--ds-fs-3xl));
  /* 🔴 ВЕС И ИНТЕРЛИНЬЯЖ ЗАГОЛОВКА ГЕРОЯ — ЛОКАЛЬНЫЕ ТОКЕНЫ, А НЕ ГЛОБАЛЬНЫЕ (16.08).
     Замер обеих сторон: кегль СХОДИТСЯ (34 @390 · 36 @1366), а вес 800 против наших 700
     и интерлиньяж 1.08 против 1.20 — при равном числе рядов (3 и 5) это +12 и +22 высоты.
     ⚠ Глобальные `--ds-heading-weight` / `--ds-heading-leading` НЕ ТРОГАЮ: они держат все
     заголовки сайта, и правка потребовала бы перемерить его целиком. Умолчания — они же. */
  font-weight: var(--ds-hero-title-weight, var(--ds-heading-weight));
  letter-spacing: var(--ds-heading-tracking);
  line-height: var(--ds-hero-title-leading, var(--ds-heading-leading));
  margin: 0;
}

@media (min-width: 768px) {
  .ds-hero__title { font-size: var(--ds-hero-title-size-md, var(--ds-fs-4xl)); }
}

/* Очень узкие экраны: заголовок не должен переполнять — на ступень мельче
   и перенос длинных слов */
@media (max-width: 479px) {
  .ds-hero__title { font-size: var(--ds-hero-title-size-sm, var(--ds-fs-2xl)); overflow-wrap: anywhere; }
}

.ds-hero__lead {
  /* Лид героя: канон ступает на 761 — 13/1.40 ниже и 14/1.60 от 761 (бисекция 16.08:
     @760 = 13px/18.2, @761 = 14px/22.4). Тот же рубеж, что у кикера и полей секции —
     третий узел на нём. Вес у канона 500 против наших 400. */
  font-size: var(--ds-hero-lead-size, var(--ds-fs-lg));
  font-weight: var(--ds-hero-lead-weight, inherit);
  color: var(--ds-text-secondary);
  line-height: var(--ds-hero-lead-leading, var(--ds-lh-loose));
  max-width: 40rem;
  margin: 0;
}

.ds-section--inverse .ds-hero__lead { color: var(--ds-text-muted); }

.ds-hero__actions {
  display: flex;
  gap: var(--ds-space-3);
  flex-wrap: wrap;
}

/* ---- Hero со слотом media: контент слева, ассет справа (снизу на узких) ---- */
.ds-hero--media .ds-hero__inner {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: var(--ds-space-12);
  align-items: center;
}

@media (max-width: 1023px) {
  .ds-hero--media .ds-hero__inner { grid-template-columns: 1fr; }
}

.ds-hero__content {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-6);
  align-items: flex-start;
}

.ds-hero__media { min-width: 0; }
.ds-hero__media img,
.ds-hero__media svg {
  display: block;
  max-width: 100%; /* не растягивать ассет выше натуральной ширины (паритет-наряд №3) */
  height: auto;
}

/* ---- Композиция media из нескольких ассетов: базовый + плавающие поверх.
   Ассеты — из бренд-пакета; позиции — модификаторами, анимация — .ds-float. ---- */
.ds-media-stack {
  position: relative;
  /* Ограничение ширины композиции — темизируемый токен (дефолт none = как раньше);
     при заданном максимуме композиция прижимается к правому краю колонки */
  max-width: var(--ds-hero-media-max, none);
  margin-left: auto;
}

/* На стеке (мобиле) композиция центрируется и не выносит декор за край */
@media (max-width: 1023px) {
  .ds-media-stack { margin-left: auto; margin-right: auto; }
}
/* 🔴 БЫЛО 559 — на пиксель мимо канонной границы `560max`. Ровно на 560 канон правило
   применяет, мы — нет. Тот же класс, что 899: граница, сдвинутая на единицу, даёт коридор
   в один пиксель и не ловится ничем, кроме прицельного замера. */
@media (max-width: 560px) {
  /* плавающие карточки декоративны и торчат за край на узком — скрываем,
     базовый ассет остаётся */
  .ds-media-stack__float { display: none; }
}

.ds-media-stack__float {
  position: absolute;
  width: 44%;
  filter: drop-shadow(0 10px 24px color-mix(in srgb, var(--ds-color-primary) 18%, transparent));
}

.ds-media-stack__float--tl { top: -4%; left: -2%; }
.ds-media-stack__float--br { bottom: -2%; right: -4%; }

/* ── ВОЛНА-КАЙМА БЕЛОЙ СЕКЦИИ (c-design, 15.08; решение по запросу integrator-c2) ──────────────
 * ЗАМЕР КАНОНА: 8 волн — ПО ДВЕ (верх и низ) у #calc, #srv, #inc, #form; высота 32, во всю
 * ширину, заливка ЦВЕТОМ СТРАНИЦЫ поверх белой секции. Доказательство парности числом:
 * низ #srv кончается на 3112, верх #inc начинается на 3116 — ДВЕ отдельные волны подряд,
 * а не одна на стыке. ⇒ это КАЙМА СЕКЦИИ, а не разделитель промежутка.
 *
 * 🔴 ПОЧЕМУ ПСЕВДОЭЛЕМЕНТЫ, А НЕ УЗЕЛ `.ds-divider`:
 *   · парность приходит даром — два псевдоэлемента вместо двух узлов и двух ключей;
 *   · шаблону нужен ОДИН булев ключ у секции, а не два элемента в разных местах;
 *   · волну нельзя применить наполовину и нельзя поставить между не теми секциями;
 *   · узел `.ds-divider` остаётся для izibuh/tezum — их модель (один, 70px) не тронута.
 * ⚠ ФОРМА — та же маска (`--ds-divider-shape`), что у `.ds-divider`: у канона `<svg><path>`,
 *   у нас маска. Механизмы разные, результат один; сводить не буду, пока не увижу расхождения
 *   в кадре — иначе это работа без предмета.
 * 🔴 НЕ СОЧЕТАТЬ С `--decor`: тот занимает `::before` под орб. Совмещение молча съест верхнюю
 *   волну — назвал здесь, потому что гейта на это нет. */
.ds-section--wave { position: relative; }

/* ── ПОЛОСЫ-ПОДЛОЖКА (`--bars`) — роль ДС, 16.08 по решению integrator-c2 ────────────────
 * Замер канона: `svg.bars` — ДВА многоугольника в коробке 100×100, заливка `var(--g)`
 * (брендовый зелёный), у каждого экземпляра свои размер, положение и прозрачность:
 *     hero 360×320 op .07 · секция сравнения 222×192 op .16 · форма 297×257 op .09
 * ⚠ ПОЧЕМУ РОЛЬЮ, А НЕ КАРТИНКОЙ: час назад мы сняли `img.tpl-pattern` из героя по доводу
 * «у канона `img` нет ни одного во всём документе». Поставить его сюда значило бы закрыть
 * строку «нет вовсе» и своей же рукой завести строку «замена носителя» — перекладывание,
 * а не прогресс (формулировка integrator-c2, и она верна).
 * ⚠ РАЗМЕТКИ НЕ ТРЕБУЕТ — тем же приёмом, что волна: псевдоэлемент + маска. Включается
 * одним классом на секции, как `wave: true` у потребителя. Умолчание — не рисовать ничего. */
.ds-section--bars {
  position: relative;
  /* 🔴 ОБРЕЗКА ОБЯЗАТЕЛЬНА, И ЭТО НАШЁЛ ГЕЙТ, А НЕ Я. Подложка вынесена за правый край
     (`right: -60px` у канона), и без обрезки это горизонтальная прокрутка на узких.
     Гейт «декоративный вынос без объявленного механизма» покраснел на первой же сборке —
     ровно та проверка, ради которой он писался после орба и стрелок. */
  overflow: hidden;
}

.ds-section--bars::after {
  /* Содержимое — токен: `none` полностью гасит псевдоэлемент. Нужно там, где декор
     у канона принадлежит НЕ секции, а узлу внутри неё (см. секцию сравнения). */
  content: var(--ds-bars-content, "");
  position: absolute;
  z-index: 0;
  pointer-events: none;
  /* 🔴 ЧЕТЫРЕ СТОРОНЫ, А НЕ ДВЕ (16.08, отзыв `c-ops` + разметка канона). У канона ТРИ
     экземпляра, и каждый посажен СВОЕЙ парой сторон:
         #topSlider  top −40  right −60   360×320  op .07
         .cmp-sec    bottom −30  right −40   220×190  op .16     ← пара нашего #honest
         #form       top −30  left −70    300×260  op .09
     ⇒ роль обязана принимать любую пару; умолчание `auto` не двигает того, кто её не задал. */
  top: var(--ds-bars-top, auto);
  right: var(--ds-bars-right, auto);
  bottom: var(--ds-bars-bottom, auto);
  left: var(--ds-bars-left, auto);
  width: var(--ds-bars-width, 360px);
  height: var(--ds-bars-height, 320px);
  opacity: var(--ds-bars-opacity, 0.07);
  background: var(--ds-bars-color, var(--ds-color-primary));
  -webkit-mask: var(--ds-bars-shape, none) center / 100% 100% no-repeat;
  mask: var(--ds-bars-shape, none) center / 100% 100% no-repeat;
}

/* ── ПОЛОСЫ ОТДЕЛЬНЫМ УЗЛОМ (`.ds-bars`) — второй носитель той же роли (c-design, 17.08) ──
 *
 * 🔴 ЗАЧЕМ ВТОРОЙ НОСИТЕЛЬ, ЕСЛИ ЕСТЬ `--bars`. У секции ровно ДВА псевдоэлемента, и волна
 * занимает оба (`::before` + `::after`). Значит на секции с волной полосы приткнуть некуда, и
 * `scaffold` отказывает на паре `bars`+`wave` — отказ верный. Обходя его, я взял чужой крючок:
 * `.ds-section--pad-final > .ds-section__inner::after`, то есть роль ОТБИВОК как адрес декора.
 * Это работает и читается как ошибка — следующий будет искать полосы там, где написано «поле».
 * ⇒ носитель, у которого своих псевдоэлементов не отнимают: ОТДЕЛЬНЫЙ УЗЕЛ.
 *
 * 🔴 И ЭТО НЕ ОБХОД, А ФОРМА КАНОНА. У канона полосы — настоящий `<svg class="bars">`, три
 * экземпляра, каждый ПРЯМОЙ ребёнок полноширинного узла. Мы воспроизводим его конструкцию,
 * а не изобретаем свою: тогда «где отсчитывается декор» перестаёт быть вопросом.
 *
 * 🔴 ЧТО ЭТИМ ЗАКРЫВАЕТСЯ ПО СУЩЕСТВУ — ЯКОРЬ. Замер 17.08 по КРАСКЕ (снимок с полосами и без,
 * разница даёт истинную коробку) на трёх наших экземплярах против канонных:
 *     #form   носитель СТАТИЧЕН ⇒ отсчёт идёт от СЕКЦИИ; краска 0…230 против канонных 0…210,
 *             левый край у обоих 0 ⇒ Δ ПО ЯКОРЮ = 0, расхождение только в РИСУНКЕ
 *     #hero   носитель `.ds-carousel__slide` ПОЗИЦИОНИРОВАН и шириной 1140 ⇒ x = 113 + 840 = 953
 *             против канонных 1066 при 1366 ⇒ Δ = −113 = 1366 − 1253. ЭТО и есть предмет
 *     #honest носитель — внутренняя колонка у обеих сторон, Δ −7
 * ⚠ В карте оба экземпляра записаны как «−113». Половина записи неверна: у формы якорь СОШЁЛСЯ,
 * и «починка» там сдвинула бы верное. Отдельный узел закрывает предмет КОНСТРУКЦИЕЙ: у прямого
 * ребёнка секции отсчёт не зависит от того, оказался ли какой-то предок позиционирован.
 *
 * ПОТРЕБИТЕЛЮ: узел ставится ПРЯМЫМ ребёнком полноширинного узла, до содержимого.
 * Разметка — зона `c-tpl`; здесь только роль и умолчания. Токены те же, что у `--bars`,
 * поэтому бренд-пакет переписывать не нужно: значения читаются одни и те же. */
.ds-bars {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  top: var(--ds-bars-top, auto);
  right: var(--ds-bars-right, auto);
  bottom: var(--ds-bars-bottom, auto);
  left: var(--ds-bars-left, auto);
  width: var(--ds-bars-width, 360px);
  height: var(--ds-bars-height, 320px);
  opacity: var(--ds-bars-opacity, 0.07);
  background: var(--ds-bars-color, var(--ds-color-primary));
  -webkit-mask: var(--ds-bars-shape, none) center / 100% 100% no-repeat;
  mask: var(--ds-bars-shape, none) center / 100% 100% no-repeat;
}

/* Носитель обязан быть позиционирован и обрезать вынос — иначе декор уедет к первому
   позиционированному предку и даст горизонтальную прокрутку (тот же довод, что у `--bars`). */
.ds-bars-host {
  position: relative;
  overflow: hidden;
}

.ds-section--wave::before,
.ds-section--wave::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: var(--ds-divider-height, 70px);
  background: var(--ds-divider-color, var(--ds-surface-page));
  -webkit-mask: var(--ds-divider-shape, none) center / 100% 100% no-repeat;
  mask: var(--ds-divider-shape, none) center / 100% 100% no-repeat;
  pointer-events: none;
}

.ds-section--wave::before { top: 0; transform: scaleY(-1); }
.ds-section--wave::after { bottom: 0; }

/* ═══ components/orb/orb.css ═══ */
/* f7c_design :: Декоративное свечение-орб (наряд c-ops «декор-слой»: атмосфера
   секций из размытых разноцветных пятен, как в прототипе izibuh .orb). Требует
   tokens.css. Ставится внутрь позиционированного контейнера (напр. .ds-section
   с position:relative); за контентом, кликами не перехватывается. Позицию задаёт
   рендер утилитами --ds-orb-x/-y (или inline у сайта — но CSP: только через
   класс-позицию). */
.ds-orb {
  position: absolute;
  z-index: 0;
  /* Собственный размер орба вынесен в переменную: на неё опираются СМЕЩЕНИЯ (ниже),
     поэтому доля видимой площади одинакова на любой ступени. */
  /* 🔴 ПОЛ 280px — МЕХАНИЗМОМ, а не гейтом-жалобой. Просьба держателя канона была «секционный
     орб <280 → красный», но статический гейт computed-диаметра не видит, а красный постфактум
     ловит уже собранное. max() делает нарушение НЕВОЗМОЖНЫМ при любом токене бренда.
     Почему именно 280: это МИНИМУМ канона (размеры 280…460, ни одного мельче). Плюс механика
     размытия — при filter: blur(70px) пик яркости в центре = 1-exp(-R²/2σ²): у орба 210px это
     68% заданной альфы, у 280px — 86%, у 450px — 99%. То есть мелкий орб не только мал, но и
     БЛЕДНЕЕ объявленного, причём computed этого не показывает: в CSS честно стоит .10, а на
     экране .068. Ниже канонного минимума орб перестаёт быть свечением и становится каймой.
     🔴 ПОЛ 290, А НЕ 280 — И ЭТО ПОПРАВКА К МОЕЙ ЖЕ ПЕРВОЙ ВЕРСИИ (находка c-web-1 до сборки).
     Я задал пол по ДИАМЕТРУ канонного минимума (280px), а критерий приёмки — ПРОИЗВЕДЕНИЕ
     площади на видимую долю (≥34 741 px²). Это разные величины, и 280 их не связывает:
        280px × 54.8% = 33 743 px² — НЕ ХВАТАЕТ 998 до порога;
        290px × 54.8% = 36 196 px² — проходит с запасом.
     Почему канону 280 хватало, а нам нет: канон держал свой минимум при доле 56.4%, наши
     смещения (-28%/-32%) дают 54.8% — чуть жёстче. Минимальный диаметр под наш критерий 284.1px.
     Отсюда норма, которую стоит запомнить: пол обязан гарантировать РЕЗУЛЬТАТ источника,
     а не воспроизводить его ЧИСЛО. 290px «не как в каноне» по диаметру — но даёт ровно то,
     что канон давал по существу.
     ⚠ Следствие, которое надо знать: при токене 300 ступень --sm упирается в пол и совпадает
     с --md. Это не дефект механизма, а честный ответ — В КАНОННОМ ДИАПАЗОНЕ МЕСТА ПОД МАЛУЮ
     СТУПЕНЬ ПРОСТО НЕТ. Шкала остаётся втроём ради читаемости API, но sm перестаёт быть
     ловушкой: он больше не может отрендериться каймой. */
  --ds-orb-w: max(290px, var(--ds-orb-size));
  width: var(--ds-orb-w);
  height: var(--ds-orb-w);
  border-radius: 50%;
  background: var(--ds-orb-color);
  filter: blur(70px);
  pointer-events: none;
}

/* Цветовые варианты — из палитры токенов (все декоративные) */
.ds-orb--primary { background: var(--ds-orb-color-primary); }
.ds-orb--accent  { background: var(--ds-orb-color-accent); }
.ds-orb--alt     { background: var(--ds-orb-color-alt); }

/* Позиции (класс, не inline — CSP style-src 'self') */
/* 🔴 СМЕЩЕНИЯ — ДОЛИ ОРБА, А НЕ ПРОЦЕНТЫ СЕКЦИИ. Это корень жалобы владельца, найденный
   c-design-2 (орбы срезаны до 25-29% площади), и он глубже размера ступеней.
   Было `top: -12%` — процент от ВЫСОТЫ СЕКЦИИ. Секции разной высоты, поэтому одна и та же
   разметка давала разный срез: на секции 400px малый орб видно на 77%, на 1200px — на 31%,
   а при высокой секции он срезается целиком. Величина, которой мы управляли, не та, которую
   хотели держать.
   Канон позиционирует орбы в ПИКСЕЛЯХ, и эти пиксели пропорциональны самому орбу: замер по
   десяти орбам канона — вертикаль 19-36% (среднее 29%), горизонталь 21-47% (среднее 36%).
   Поэтому и здесь доли ОРБА через --ds-orb-w: видимая площадь становится ПОСТОЯННОЙ и не
   зависит ни от ступени, ни от высоты секции.
   Взяты -28% / -32% (чуть мягче среднего канона): численный расчёт площади круга под срезом
   даёт 55% для УГЛОВОГО орба (срез по двум осям — худший случай) и 72% для бокового.
   Канон для сравнения: минимум 47%, среднее 59%, угловые 45-56%. То есть мы попадаем в его
   диапазон и проходим критерий «≥50% внутри», которого сам канон достигает в 9 случаях из 10.
   ⚠ --c (центральный) смещений не имеет и не срезается — проценты секции там уместны. */
.ds-orb--tl { top: calc(var(--ds-orb-w) * -0.28); left: calc(var(--ds-orb-w) * -0.32); }
.ds-orb--tr { top: calc(var(--ds-orb-w) * -0.28); right: calc(var(--ds-orb-w) * -0.32); }
.ds-orb--bl { bottom: calc(var(--ds-orb-w) * -0.28); left: calc(var(--ds-orb-w) * -0.32); }
.ds-orb--br { bottom: calc(var(--ds-orb-w) * -0.28); right: calc(var(--ds-orb-w) * -0.32); }
.ds-orb--c  { top: 30%; left: 40%; }

/* Крупнее/мельче отдельного пятна */
/* 🔴 ДРЕЙФ — канон concept-v2.3.0:103: `.orb{animation:drift 14s ease-in-out infinite alternate}`,
   БЕЗУСЛОВНО на всех орбах. Замер c-ops: у нас animation-name:none, transform неподвижен 2.5с
   при no-preference — то есть орбы статичны у ВСЕХ, а не только под запросом покоя.
   Дрейф в ядре был, но подключён к ДРУГИМ элементам: ::before секции и панели калькулятора
   (.ds-section--decor-drift / .ds-calc__panel--orb-drift). До компонента .ds-orb он не доходил.
   Это не «не реализовано вообще», а «реализовано не там» — важное различие: сам keyframes
   ds-drift рабочий и проверен, подключаем существующий механизм к нужному элементу.
   Безусловно, а не под флагом: так в каноне. Гашение покоя — универсальным правилом motion.css
   (v0.47.0), отдельного здесь не нужно; строка выше оставлена как локальная страховка. */
.ds-orb {
  animation: ds-drift var(--ds-anim-drift-duration) ease-in-out infinite alternate;
}

.ds-orb--lg { --ds-orb-w: max(290px, calc(var(--ds-orb-size) * 1.5)); }
/* 🔴 --md ЯВНОЙ СТУПЕНЬЮ, хотя размер ×1.0 и так даёт базовый класс.
   Наряд просил «добавить третью ступень», и первым ответом было «она уже есть — это .ds-orb
   без модификатора». Формально верно, но это НЕ ответ: шкала из --sm и --lg БЕЗ середины
   учит, что размер выбирается из двух, и middle читается как «забыли», а не как «по умолчанию».
   c-design-2 померила распределение канона (кластеры 311 и 407, отношение 1.31) против ступеней
   ядра (210/450, отношение 2.14 — вдвое шире) и показала, что семь орбов из десяти теряли бы
   25-34% размера. Причина промаха не в отсутствии размера, а в АСИММЕТРИИ ИМЁН.
   Поэтому --md заводится как псевдоним базы: значение то же, шкала становится читаемой втроём,
   и шаблон может эмитить ступень симметрично, не полагаясь на «отсутствие класса».
   Тот же класс, что «эталон учит употреблению»: API, где середины не видно, её и не используют. */
.ds-orb--md { --ds-orb-w: max(290px, var(--ds-orb-size)); }
.ds-orb--sm { --ds-orb-w: max(290px, calc(var(--ds-orb-size) * 0.7)); }

/* Контейнер для явных орбов: позиционирование + обрезка размытых краёв.
   (ds-section--decor тоже relative+overflow, но несёт СВОЙ ::before-орб;
   --glow — чистый контейнер под произвольный набор .ds-orb.) */
.ds-section--glow {
  position: relative;
  overflow: hidden;
}
/* 🔴 КОНТРАКТ ДЕКОРАТИВНОГО РЕБЁНКА (14.08, находка integrator-c2 на живом forbion).
   Это правило поднимает содержимое секции над орбами — и оно ЗНАЛО про декоративных детей,
   но исключало ТОЛЬКО СВОЕГО (`.ds-orb`). Чужой декор — брендовый паттерн шаблона,
   `.tpl-pattern` с `position:absolute` (0,1,0) — проигрывал этому правилу (0,2,0), падал
   в поток, занимал 285 px и сдвигал первый экран вниз. Владелец увидел «пустоту сверху».
   🔴 ДС вывела исключение для себя и не оставила контракта для чужих — чинится это
   контрактом, а не заплаткой: дописать сюда `.tpl-pattern` значило бы, что ЯДРО знает
   класс ШАБЛОНА, а поднять вес в шаблоне — гонка, которая повторится на следующем компоненте.
   ⚠ ПРИЗНАК — атрибут `data-ds-decor`, а не класс: класс потребовал бы согласовывать имя
   в двух неймспейсах, атрибут нейтрален к обоим. Помечает его ПОТРЕБИТЕЛЬ (шаблон/сборка)
   на своём декоре; ядро не знает ни одного чужого имени.
   ⚠ Список внутри ОДНОГО `:not()` — намеренно: специфичность `:not()` равна самому весомому
   аргументу, поэтому `(0,2,0)` СОХРАНЯЕТСЯ. Два раздельных `:not()` дали бы (0,3,0) и молча
   перебили бы чужие правила — правка «на всякий случай» стала бы новым дефектом. */
/* 🔴 15.08: В ЭТУ ЖЕ ЯМУ УПАЛ МОЙ СОБСТВЕННЫЙ КОМПОНЕНТ. `.ds-sphere` (0,1,0) с
   `position:absolute` проигрывал этому правилу (0,2,0), получал `position:relative`,
   переставал блокифицироваться, оставался строчным — БОКС 0×0, градиент рисовать негде.
   На живом: «в разметке 3 сферы, на экране ничего» (нашёл c-ops по пикселям, Д-7).
   ⚠ Довод шапки был про ЧУЖОЙ класс: ядро не должно знать имён шаблона. `.ds-sphere` —
   ИМЯ ЯДРА, поэтому оно здесь законно, а `data-ds-decor` остаётся контрактом для чужих.
   ⚠ Дописано ВНУТРЬ того же `:not()`: вес (0,2,0) сохраняется (см. абзац выше). */
/* 🔴 17.08: ТРЕТИЙ РАЗ ТА ЖЕ ЯМА, И НА ЭТОТ РАЗ ЕЁ НАШЁЛ ГЕЙТ ДО ВЫКЛАДКИ. Новый `.ds-bars`
   (полосы отдельным узлом) — такой же `position:absolute` (0,1,0) и проиграл бы этому (0,2,0):
   блокификация пропадает, бокс 0×0, на экране НИЧЕГО при зелёном счёте узлов. Прежние два раза
   это ловили на живом — `.tpl-pattern` глазами владельца, `.ds-sphere` по пикселям у `c-ops`.
   📌 Класс закрыт не бдительностью, а гейтом «декор ядра исключён из подъёма»: он перечисляет
   декор-классы ядра и требует, чтобы каждый стоял здесь. Третий однотипный случай — повод
   строить перечень, и перечень уже был построен; он и сработал.
   ⚠ Дописано ВНУТРЬ того же `:not()`: вес (0,2,0) сохраняется. */
.ds-section--glow > :not(.ds-orb, .ds-sphere, .ds-bars, [data-ds-decor]) { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .ds-orb { animation: none; } /* дрейф ниже — гасится вместе со всем движением */
}

/* ═══ components/sphere/sphere.css ═══ */
/* f7c_design :: СФЕРА — плотный декоративный шар. Требует tokens.css.
 *
 * ПОВОД (15.08, визуальный дифф первого экрана). В каноне между заголовком и купоном стоит
 * ПЛОТНЫЙ шар 110×110 с бликом и тенью; на живом его нет вовсе. Компонента под него не было.
 *
 * 🔴 ЭТО НЕ ОРБ, И СМЕШИВАТЬ ИХ НЕЛЬЗЯ:
 *   `ds-orb`  — мягкое пятно-подсветка: сотни пикселей, размытие, служит ФОНОМ секции;
 *   `ds-sphere` — предметный шар: сотня пикселей, чёткий край, блик и падающая тень.
 * Первое красит воздух, второе — вещь в кадре. У них разные роли, поэтому разные компоненты.
 *
 * Значения СНЯТЫ С КАНОНА (замер вычисленных стилей), а не подобраны:
 *   градиент  radial-gradient(circle at 33% 28%, блик, #A8DC6C 46%, #5F9C26 100%)
 *   тень      0 28px 46px -18px rgba(87,141,36,.5) + внутренняя подсветка сверху
 *   прозрачность .5 · радиус 50% · три размера в каноне: 110 · 89 · 71
 *
 * ⚠ Цвета шара НЕ выведены из --ds-color-primary: в каноне это своя пара (светлее и темнее
 * фирменного), и вывод из primary дал бы другой объём. Поэтому — свои токены бренда. */
.ds-sphere {
  position: absolute;
  /* 🔴 `display:block` ИЗБЫТОЧЕН при `position:absolute` — и стоит здесь именно поэтому.
     15.08 чужое правило (0,2,0) перебило `position` на `relative`; блокификация пропала,
     `span` остался строчным, ширина/высота НЕ ПРИМЕНИЛИСЬ → бокс 0×0 и НИЧЕГО НА ЭКРАНЕ.
     Провал был МОЛЧАЛИВЫМ: узлы в разметке есть, счёт даёт 3 из 3, гейты зелёные.
     С этой строкой следующая такая перебивка даст сферу НЕ ТАМ — то есть КРИЧАЩИЙ провал,
     который видно глазом и который нельзя принять за «сделано». */
  display: block;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
  width: var(--ds-sphere-size, 110px);
  height: var(--ds-sphere-size, 110px);
  opacity: var(--ds-sphere-opacity, 0.5);
  background: radial-gradient(circle at 33% 28%,
              var(--ds-sphere-hi, rgba(255, 255, 255, 0.95)),
              var(--ds-sphere-mid, #a8dc6c) 46%,
              var(--ds-sphere-low, #5f9c26) 100%);
  box-shadow: 0 28px 46px -18px var(--ds-sphere-shadow, rgba(87, 141, 36, 0.5)),
              inset 0 -12px 26px var(--ds-sphere-inner, rgba(62, 112, 19, 0.28));
}

/* Три ступени — по трём вхождениям в каноне (110 · 89 · 71), а не «на всякий случай». */
.ds-sphere--lg { --ds-sphere-size: 110px; }
.ds-sphere--md { --ds-sphere-size: 89px; }
.ds-sphere--sm { --ds-sphere-size: 71px; }

/* 🔴 ДВИЖЕНИЕ — ТОЛЬКО ПО СОГЛАСИЮ СРЕДЫ. В каноне шар плавает (`floaty` 5.2s).
 * Здесь анимация включается лишь при отсутствии запрета: у нас записано, что в моей среде
 * reduce ВКЛЮЧЁН, и «я не видел движения» — не доказательство его отсутствия. */
@media (prefers-reduced-motion: no-preference) {
  .ds-sphere--float {
    animation: ds-sphere-floaty 5.2s ease-in-out infinite alternate;
  }
}

@keyframes ds-sphere-floaty {
  from { transform: translateY(0); }
  to   { transform: translateY(-14px); }
}

/* Раскладка ТОЛЬКО для примера в sphere.html — потребитель позиционирует сам.
   Живут здесь, потому что inline style= запрещён CSP (гейт 1). */
.ds-sphere-demo { position: relative; height: 200px; }
.ds-sphere-demo__a { left: 40px;  top: 20px; }
.ds-sphere-demo__b { left: 200px; top: 40px; }
.ds-sphere-demo__c { left: 340px; top: 60px; }

/* ═══ components/eyebrow/eyebrow.css ═══ */
/* f7c_design :: Эйбр — плашка-подпись над заголовком секции. Требует tokens.css. */
.ds-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  /* размер и интерлиньяж — тоже бренд-решение: канон forbion даёт 11.5px и высоту 14,
     ядро — `--ds-fs-xs` и наследование. Умолчания прежние, чужие бренды не двигаются. */
  font-size: var(--ds-eyebrow-size, var(--ds-fs-xs));
  /* ⚠ УМОЛЧАНИЕ `inherit`, А НЕ `normal`: до правки строки не было вовсе и интерлиньяж
     НАСЛЕДОВАЛСЯ. `normal` выглядит нейтральным, но это ДРУГОЕ значение — izibuh поехал
     34→30 на первом же замере. Нейтральное умолчание — то, что воспроизводит прежний
     рендер, а не то, что нейтрально звучит. */
  line-height: var(--ds-eyebrow-lh, inherit);
  font-weight: var(--ds-fw-bold);
  letter-spacing: var(--ds-eyebrow-tracking, 0.12em);
  text-transform: uppercase;
  color: var(--ds-color-accent);
  /* 🔴 ПЛАШКА ИЛИ ГОЛАЯ ПОДПИСЬ — РЕШАЕТ БРЕНД (15.08). Канон forbion: подписи БЕЗ фона и
     БЕЗ полей — высота 14 против наших 34, то есть это не «другой отступ», а ДРУГОЙ ОБЪЕКТ.
     Умолчания оставлены прежними: izibuh и tezum остаются с плашкой. */
  background: var(--ds-eyebrow-bg, var(--ds-surface-raised));
  border-radius: var(--ds-radius-full);
  padding: var(--ds-eyebrow-pad, var(--ds-space-2) var(--ds-space-4));
  /* 🔴 ЗАЗОР ПОД КИКЕРОМ — ТОКЕН, а не число (15.08, вопрос c-tpl).
     Канон forbion-1c: `.eyebrow{margin-bottom:8px}`, высота 14 ⇒ ЗАЗОР 8, ШАГ 22.
     ⚠ Моя партия 4 отдавала «22», и это ШАГ между верхами узлов. Поставить 22 в margin —
     получить шаг 36. Число без единицы уехало бы в правку: единица здесь — ЗАЗОР.
     ⚠ И «до h2» неверно: в `#calc` следующий узел — `div.calc-head`. Правило канона —
     «до СЛЕДУЮЩЕГО УЗЛА», поэтому у секций с тихой строкой оно достижимо, а не отменяется.
     Умолчание ядра оставлено прежним (16) — izibuh и tezum не двигаются; forbion берёт 8
     в своём бренд-пакете. ОБЛАСТЬ ПРАВКИ = ОБЛАСТЬ РЕШЕНИЯ. */
  margin-bottom: var(--ds-eyebrow-gap, var(--ds-space-4));
}

/* Опциональный маркер-иконка (из набора components/icons) */
/* 🔴 ТИРЕ-ЛИНИЯ ПЕРЕД КИКЕРОМ — приём канона forbion (15.08, строка 22 диффа глазами).
   Замер: `::before` шириной 22px, высотой 2px, цвет primary. Умолчание — БЕЗ линии
   (ширина 0), поэтому izibuh и tezum не получают ничего нового. */
.ds-eyebrow::before {
  content: "";
  display: inline-block;
  width: var(--ds-eyebrow-dash, 0);
  height: 2px;
  background: var(--ds-color-primary);
  flex: 0 0 auto;
}

.ds-eyebrow__icon {
  width: 0.85em;
  height: 0.85em;
  flex-shrink: 0;
}

/* ═══ components/stat/stat.css ═══ */
/* f7c_design :: Плитки-цифры. Требует tokens.css. */
/* 🔴 КОЛОНКИ — МЕХАНИЗМ, А НЕ СОВПАДЕНИЕ ШИРИН (15.08, находка c-ops на живом).
   Было `flex-wrap`: плитки брали ширину ПО СОДЕРЖИМОМУ (160 · 256 · 166), поэтому левая
   кромка второй колонки держалась тем, что тексты первой оказались похожей длины. Замер
   показал разъезд 224 против 230 — Δ6 на всех шести комбинациях тем×ширин.
   ⚠ Дефект здесь не «6 пикселей», а ОСНОВАНИЕ: правка текста у `c-copy` сдвигала бы колонку,
   и виноват был бы текст, а не раскладка. Ряд, выровненный содержимым, — не выровненный ряд.
   Сетка с равными долями делает кромку следствием ПРАВИЛА: любой текст, любая тема, любой брейк.
   ⚠ `minmax(0, 1fr)`, а не `1fr`: у грид-элемента `min-width:auto`, и длинное слово в значении
   распирало бы колонку — тот же content-sized дефект, только через заднюю дверь.
   На узком (<480) колонка одна: две доли по 160 там не помещаются. */
.ds-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ds-space-8) var(--ds-space-12);
  align-items: start;
}

@media (max-width: 479px) {
  .ds-stats { grid-template-columns: minmax(0, 1fr); }
}

/* 🔴 БЛОК `.ds-stat` СОБСТВЕННЫХ ПРАВИЛ НЕ ИМЕЛ ВОВСЕ — и это НЕ забытое имя, а решение,
   которое до сегодня нигде не было записано (нашёл гейт `c-tpl`, 16.08, четвёртой сиротой).
   Вид плитки дают ЭЛЕМЕНТЫ (`__value`, `__label`, `__icon`) и модификатор `--plain`;
   геометрию — контейнер `.ds-stats` (грид с равными долями). Блоку принадлежать нечему.
   ⚠ Но «нечему» обязано быть НАПИСАНО: незаписанное решение неотличимо от забытого,
   и сегодня это уже стоило нам четырёх разборов. Объявляю `display: block` — ровно то,
   что и так даёт `<div>`, то есть контракт зафиксирован, а рендер не двигается.
   ⚠ ЧЕСТНО О ДОКАЗАТЕЛЬСТВЕ: узлов `.ds-stat` нет ни на одной доступной мне поверхности
   (`ts.f7cloud.ru` 0, `izibuh.ru` 0) — «рендер не двигается» здесь следует ИЗ ПОСТРОЕНИЯ
   (умолчание `<div>`), а не из замера. Если шаблон печатает блок иным тегом, чем `<div>`,
   правило станет изменением — скажите, и пересмотрю. */
.ds-stat {
  display: block;
}

.ds-stat__value {
  display: block;
  font-size: var(--ds-fs-2xl);
  font-weight: var(--ds-fw-bold);
  line-height: var(--ds-lh-tight);
  font-variant-numeric: tabular-nums;
  /* Градиент — декоративен: крупный кегль (2xl bold) допускает его по WCAG для
     largescale-текста; для мелких значений использовать модификатор --plain */
  background: var(--ds-text-link);
    background: var(--ds-gradient-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.ds-stat--plain .ds-stat__value {
  background: none;
  /* text-link, не color-primary: тот же класс R11-btn (17.07). Основной __value —
     градиент (color:transparent), а --plain красит текст брендовым синим на прозрачном
     фоне → do-not-flip primary в тёмной теме нечитаем. text-link флипается. */
  color: var(--ds-text-link);
}

/* ── ИКОНКА ПЛИТКИ (c-design 14.08) ────────────────────────────────────────────────
 * 🔴 ЗАКРЫВАЮ ОБЪЯВЛЕННЫЙ ДОЛГ ШАБЛОНА, а не завожу новое. `c-tpl` в `TEMPLATE v0.100.0`
 * добавил иконку плитке и НАЗВАЛ причину вслух: у ядрового `.ds-stat` слота иконки нет,
 * а изобретать `ds-*` из шаблона нельзя — поэтому там временный класс `tpl-stat__icon`
 * с пометкой «заведёт c-design `ds-stat__icon` — мой класс снимается».
 * Долг был объявлен ДО работы, а не после; отвечаю тем же — слот заведён, класс можно снимать.
 *
 * ⚠ Иконка здесь ДЕКОРАТИВНАЯ спутница значения, а не носитель смысла: плитка читается
 * и без неё (значение + подпись). Поэтому цвет приглушённый и размер привязан к кеглю
 * ПОДПИСИ, а не значения: у значения градиентный 2xl, и иконка в его размере спорила бы
 * с числом за внимание — а число здесь главное. */
.ds-stat__icon {
  display: block;
  width: 1.25em;
  height: 1.25em;
  margin-bottom: var(--ds-space-2);
  /* 🔴 ЦВЕТ ПРИВЕДЁН К ИДИОМЕ КАНОНА (14.08, вопрос integrator-c2). Я поставил приглушённый,
   * рассудив «иконка не должна спорить с числом». Довод мой, а не канона — а приказ владельца
   * «как в макете». Замер канона: значков со ссылкой на символ 47 штук, у 45 из них
   * stroke = rgb(62,112,19) — БРЕНДОВЫЙ ЗЕЛЁНЫЙ; приглушённых/серых нет ни одного.
   * ⚠ Плиток value/label в каноне НЕТ ВООБЩЕ, поэтому «как в каноне» задаёт не размер, а ИДИОМУ:
   * размер 18 остаётся (канонные значки 14–22 в тех же ролях), цвет уходит к брендовому.
   * Токен `--ds-text-link`, а не литерал #3E7013: последний не наш утверждённый цвет — его уже
   * сводили к бренду (#478016), и повторять чужую опечатку канона нельзя. */
  color: var(--ds-text-link);
  font-size: var(--ds-fs-sm);
}

.ds-stat__label {
  display: block;
  margin-top: var(--ds-space-1);
  font-size: var(--ds-fs-sm);
  color: var(--ds-text-secondary);
  max-width: 16rem;
}

/* ═══ components/mark/mark.css ═══ */
/* f7c_design :: Подсветка слова маркером. Требует tokens.css.
   Цвет подсветки -- токен --ds-mark-color (полупрозрачный акцент).

   --- ds-mark и color в тёмной теме (признак владельца 17.07, "изи" в h1) ---

   Тег <mark> несёт user-agent color (marktext / чёрный). Без переопределения текст
   внутри mark чёрный при любом контексте: в светлой теме не видно (заголовок и так
   тёмный), в тёмной заголовок светлый (--ds-text-primary), а mark остаётся чёрным ->
   чёрное на тёмно-розовой подложке, слово в главном h1 нечитаемо. Лечение: color:inherit
   -- mark наследует цвет контекста (h1), который флипается вместе с темой. Проверено на
   живом проде (c-design-2): inline color:inherit даёт rgb(246,248,253) в тёмной.
   -webkit-text-fill-color НЕ нужен: он не перебивает color (тот же inline-тест дал светлый,
   а не чёрный), реальный UA <mark> его отдельно не ставит.

   ПРАВИЛО РАЗМЕТКИ ЭТОГО ФАЙЛА (следствие реального бага 17.07): color:inherit стоит ПЕРВЫМ
   свойством, вплотную после '{', а комментарий -- ВНЕ тела правила (здесь, перед селектором).
   Комментарий между '{' и первым свойством в external @import CSS СЪЕДАЛ color из распарсенного
   правила (CSSOM .ds-mark оставался без color -> mark брал UA-чёрный, computed #000 в обеих
   темах). c-design-2 намерил это по CSSOM на проде; мой inline-тест в артефакте не
   воспроизводил (inline <style> != external @import). Гейт 8e держит структуру. */
.ds-mark {
  color: inherit;
  background: linear-gradient(transparent 58%, var(--ds-mark-color) 58%);
  padding: 0 0.08em;
  border-radius: 2px;
}

/* ═══ components/compare/compare.css ═══ */
/* f7c_design :: Сравнение «мы vs альтернатива» — две колонки. Требует tokens.css. */
.ds-compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* 🔴 ВОСЬМОЙ ЗА СУТКИ СЛУЧАЙ «КАНОН ОСТАВЛЯЕТ `normal` — МЫ СТАВИМ ЧИСЛО» (16.08, разложение
     `#honest`). Канонный `.cmp` держит `line-height: normal` и зазор 18; у нас `24px` и 24.
     Контрфакт @390: `lh: normal` блок 847→732, зазор 24→18 ещё −6.
     ⚠ ТРЕТИЙ ШАГ ОПЫТА Я ОТБРОСИЛ КАК СВОЙ ЖЕ ПРОМАХ: снял у канона поля `26/26` (это
     ВЕРТИКАЛЬНЫЕ) и применил их ко ВСЕМ сторонам — колонка сузилась, текст перенёсся, блок
     ВЫРОС на 65. У поля две оси, и горизонтальную я не мерил. Поля не трогаю.
     ⚠ ЗНАК: секция `#honest` у нас КОРОЧЕ канона (−171 @390), и эта правка делает её короче
     ещё на 121. Она верна по канону и обязана ехать ПОСЛЕ того, как `c-web-1`/`c-copy`
     добавят отсутствующий подзаголовок (`p.ksub` 198): иначе «ближе к канону» выйдет
     на живое как «стало хуже». Порядок разнонаправленных правок — часть правки. */
  line-height: var(--ds-compare-lh, inherit);
  gap: var(--ds-compare-gap, var(--ds-space-6));
  /* stretch (не start): колонки одного ряда тянутся к равной высоте на ВСЕХ
     ширинах. start давал «формы разного размера» — карты совпадали лишь когда
     контент случайно вышел равным (широкие вьюпорты). */
  align-items: stretch;
}

@media (max-width: 767px) {
  .ds-compare { grid-template-columns: 1fr; }
}

.ds-compare__col {
  /* Контекст позиционирования: у канона декор секции сравнения висит на КОЛОНКЕ
     (`div.cc.us`, `position: relative`), а не на секции — и отсчёт `bottom/right`
     идёт от неё. Без этого канонные числа дают другую посадку. */
  position: relative;
  background: var(--ds-surface-page);
  border: 1px solid var(--ds-border-default);
  border-radius: var(--ds-radius-card);
  /* Поля колонки — РОЛЬ (17.08): канон даёт 26, шкала ядра — 24. Умолчание прежнее. */
  padding: var(--ds-compare-col-pad, var(--ds-space-6));
  /* flex-колонка: после stretch карта равной высоты, но короткий список сел бы
     вверх с пустым хвостом. Колонка-flex позволяет списку тянуться (flex:1) и
     распределяться. Бейдж/заголовок держатся сверху естественным порядком. */
  display: flex;
  flex-direction: column;
}

/* 🔴 КОЛОНКА ВЫДЕЛЕНИЯ — приём канона forbion (15.08). Канон не заливает «нашу» колонку
   сплошным брендовым цветом: `.cc` = светло-зелёная подложка + ОБЫЧНЫЕ чернила, r=20.
   Тот же приём у выделенной строки тарифа, активной кнопки калькулятора и полосы
   «Бесплатно при переезде» — это ОДНА роль «выбрано / входит / выгодно».
   ⚠ `--accent` (сплошной primary с инверсным текстом) НЕ трогаю: это другой приём и он
   остаётся для izibuh/tezum. Класс ставит потребитель; пока не поставит — не доедет. */
.ds-compare__col--highlight {
  background: var(--ds-surface-highlight);
  color: var(--ds-text-primary);
  border-color: transparent;
}

.ds-compare__col--accent {
  background: var(--ds-color-primary);
  border-color: transparent;
  color: var(--ds-color-on-primary);
}

/* Градиентная акцентная колонка (прототип izibuh «Мы против штатного»:
   .vs-col.us = var(--grad)). Контраст on-primary к самой светлой точке
   gradient-brand — обязанность темы (izibuh: стопы 6.61/5.04/5.14). */
.ds-compare__col--gradient {
  background: var(--ds-gradient-brand);
  border-color: transparent;
  color: var(--ds-color-on-primary);
  box-shadow: var(--ds-shadow-brand);
}
.ds-compare__col--gradient .ds-compare__item::before { color: var(--ds-color-on-primary); }

/* Бейдж-ярлык над заголовком колонки (.vs-tag) */
.ds-compare__tag {
  display: inline-block;
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: var(--ds-radius-full);
  padding: var(--ds-space-1) var(--ds-space-3);
  margin-bottom: var(--ds-space-4);
  background: var(--ds-surface-raised);
  color: var(--ds-text-muted);
  /* 🔴 готча (замер c-tpl): в flex-колонке inline-block ловит cross-stretch и
     растягивается из пилюли в полосу на всю ширину. Якорим по началу оси. */
  align-self: flex-start;
}
/* 🔴 15.08: ПРАВИЛО, ПЕРЕЧИСЛЯЮЩЕЕ МОДИФИКАТОРЫ ПОИМЁННО, СТАРЕЕТ ПРИ ДОБАВЛЕНИИ НОВОГО —
   и стареет ТИХО, потому что базовое умолчание выглядит осмысленным (находка c-tpl).
   Колонка сменилась в `v0.161.0` (`--highlight`), а бейдж остался со старым набором имён:
   серый `--ds-surface-raised` на зелёной колонке.
   Замер канона: у «нашей» колонки бейдж ПРОЗРАЧНЫЙ, зелёное — под ним; чернила обычные.
   ⚠ Это тот же «отношение, а не значение»: `ds-badge--highlight` дал бы ЗЕЛЁНОЕ НА ЗЕЛЁНОМ. */
.ds-compare__col--highlight .ds-compare__tag {
  background: transparent;
  color: var(--ds-text-primary);
}

.ds-compare__col--gradient .ds-compare__tag,
.ds-compare__col--accent .ds-compare__tag {
  background: color-mix(in srgb, var(--ds-color-on-primary) 22%, transparent);
  color: var(--ds-color-on-primary);
}

/* 🔴 ЗАГОЛОВОК КОЛОНКИ — У КАНОНА ЭТО ПИЛЮЛЯ, А НЕ ЗАГОЛОВОК (16.08, решение integrator-c2:
   ЗАМЕНИТЬ, а не ставить оба). Замер канона `.cmp .cc .ct`:
       кегль 11 · вес 800 · uppercase · трекинг 1.1 · поля 5/13 · радиус 999 · чернила #1D1D1B
       фон: у ЛЕВОЙ (зелёной) колонки ПРОЗРАЧНЫЙ, у ПРАВОЙ (белой) — #ECF2F2
   у нас было: `h3` кегль 18, вес 600, во всю ширину колонки (482).
   ⚠ Регистр — через `text-transform`, а не заглавными в тексте: в исходнике канона строчные,
   капслок даёт CSS. Текст остаётся ровно тем, что написал `c-copy`.
   ⚠ Умолчания = прежний рендер: izibuh и tezum получают тот же заголовок, что и раньше. */
.ds-compare__title {
  display: var(--ds-compare-title-display, block);
  /* ⚠ `inline-block` САМ ПО СЕБЕ пилюли не даёт: колонка — флекс, и элемент растягивается
     по поперечной оси независимо от `display`. Замер после первой редакции: кегль, высота
     и фон уже канонные, а ширина осталась 482 из 482 — то есть полоса, а не пилюля.
     Ширину по содержимому даёт `align-self`, и без него правка выглядит применённой. */
  align-self: var(--ds-compare-title-self, stretch);
  font-size: var(--ds-compare-title-size, var(--ds-fs-lg));
  font-weight: var(--ds-compare-title-weight, var(--ds-fw-semibold));
  text-transform: var(--ds-compare-title-transform, none);
  letter-spacing: var(--ds-compare-title-tracking, normal);
  padding: var(--ds-compare-title-pad, 0);
  border-radius: var(--ds-compare-title-radius, 0);
  background: var(--ds-compare-title-bg, transparent);
  color: var(--ds-compare-title-ink, inherit);
  /* Отступ под заголовком — РОЛЬ (17.08): канон даёт 14 (`.ct{margin-bottom:14px}`), шкала
     ядра — 16. Два пикселя, но они входят в высоту колонки, а колонку мы сводим до пикселя. */
  margin: 0 0 var(--ds-compare-title-gap, var(--ds-space-4));
}

.ds-compare__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Зазор строк списка — РОЛЬ (17.08): канон 11, шкала ядра 12. Разница в один пиксель
     на четырёх промежутках, но это ЧИСЛО КАНОНА, а не округление. Умолчание прежнее. */
  gap: var(--ds-compare-list-gap, var(--ds-space-3));
  /* тянется на высоту карты и распределяет пункты — «пустой хвост» короткого
     списка уходит. Гейт зазора ≤30px (замер izibuh 768–1440: max 22.5px @1000).
     Если контент даст зазор >30 — вернуть к align-content:start (верх-якорь). */
  flex: 1;
  align-content: space-between;
}

/* 🔴 Кегль и вес пункта — токены (15.08, строка 55 диффа глазами). Канон: **16/600**,
   чернила основные; у нас было `--ds-fs-sm` (14) обычным весом — рядом с макетом список
   читался легче и мельче. Умолчания прежние: izibuh и tezum не двигаются. */
.ds-compare__item {
  display: flex;
  gap: var(--ds-space-3);
  align-items: flex-start;
  font-size: var(--ds-compare-item-size, var(--ds-fs-sm));
  font-weight: var(--ds-compare-item-weight, inherit);
}

/* Маркеры — текстовые глифы: наследуют токены, не требуют ассетов */
.ds-compare__item::before {
  content: "✓";
  font-weight: var(--ds-fw-bold);
  color: var(--ds-color-accent);
  flex-shrink: 0;
}

.ds-compare__col--accent .ds-compare__item::before {
  color: var(--ds-color-on-primary);
}

.ds-compare__col--muted .ds-compare__item {
  color: var(--ds-text-secondary);
}

.ds-compare__col--muted .ds-compare__item::before {
  content: "✕";
  color: var(--ds-text-muted);
}

/* Нейтральный вариант — «разделение труда», а не «мы vs они»
   (напр. «Остаётся у вашей команды» ↔ «Берёт наш главбух»): muted-колонка
   получает нейтральный маркер вместо ✕ — это не «минус», а равноправный список. */
.ds-compare--neutral .ds-compare__col--muted .ds-compare__item::before {
  content: "•";
  color: var(--ds-text-secondary);
}

/* ═══ components/trust/trust.css ═══ */
/* f7c_design :: ПАНЕЛЬ ДОВЕРИЯ — четыре проверяемых факта с источниками. Требует tokens.css.
 *
 * ПОВОД (17.08). Наряд владельца: блок доверия — дословно каноном. Текст держит `c-copy`,
 * поля конфига — `c-tpl`, форма панели — здесь. До сих пор формы не было вовсе: на сайте
 * пункты шли столбцом без панели, без заголовка и без абзаца-оговорки, и это давало
 * крупнейший остаток десктопа (`#honest` +114 @1366).
 *
 * 🔴 ЭТО НЕ «КРАСИВАЯ КОРОБКА», А НЕСУЩАЯ КОНСТРУКЦИЯ ЮР-СМЫСЛА. Панель существует, чтобы
 * рядом с каждым утверждением стоял ЕГО ИСТОЧНИК, а под ними — оговорка о том, чего мы
 * НЕ пишем и почему. Уберёшь оговорку ради компактности — останутся четыре утверждения без
 * границы, то есть ровно та форма, от которой блок и защищает. Оговорка помечена в каноне
 * как «вердикт c-legal · решение владельца 02.08»: она не украшение и не подпись вёрстки.
 *
 * ⚠ ЧЕГО ЗДЕСЬ НЕТ И ПОЧЕМУ. Ни одного числа канона: ядро объявляет РОЛИ, значения живут
 * в бренд-пакете. Умолчания взяты со шкалы токенов, а не с замера канона, — иначе второй
 * бренд получил бы чужую айдентику молча.
 *
 * ⚠ ПАРА НОСИТЕЛЕЙ (та же, что у `credential`): те же факты стоят в реквизитах первого
 * экрана, но в другой роли — там ПАСПОРТ («кто это»), здесь ВЫВОД («что из этого следует»).
 * Правишь текст в одном месте — проверь второе.
 */

.ds-trust {
  border-radius: var(--ds-trust-radius, var(--ds-radius-card));
  padding: var(--ds-trust-pad, var(--ds-space-4) var(--ds-space-6));
  margin-top: var(--ds-trust-mar-top, var(--ds-space-6));
  /* Подложка и тень — ТОКЕНАМИ, потому что у канона панель не плоская: два слоя фона
     (радиальный блик сверху слева + диагональная заливка) и пять теней, из них три
     внутренние. Плоское умолчание честнее подделки: бренд, которому нечего сказать,
     получит ровную поверхность, а не чужую светотень. */
  background: var(--ds-trust-bg, var(--ds-surface-raised));
  box-shadow: var(--ds-trust-shadow, none);
}

/* Заголовок панели — служебная строка, а не заголовок раздела: он называет РОД фактов
   («реальная компания, своя платформа»), поэтому кегль мелкий, а вес крупный. */
.ds-trust__title {
  font-size: var(--ds-trust-title-size, var(--ds-fs-xs));
  font-weight: var(--ds-trust-title-weight, var(--ds-fw-bold));
  letter-spacing: var(--ds-trust-title-tracking, normal);
  text-transform: var(--ds-trust-title-transform, none);
  /* 🔴 Интерлиньяж служебной строки — ОТДЕЛЬНАЯ роль, а не «наследуется и ладно». Первый
     прогон формы сошёлся по ВОСЕМНАДЦАТИ величинам и разошёлся ровно здесь: у канона `normal`
     (15 при кегле 13), у нас наследованные 20. Строка одна, разница 5 — и она бы уехала
     незамеченной, потому что всё вокруг совпало. Умолчание `inherit` = прежнее поведение. */
  line-height: var(--ds-trust-title-lh, inherit);
  color: var(--ds-trust-title-color, var(--ds-text-secondary));
  margin: 0 0 var(--ds-trust-title-gap, var(--ds-space-3));
}

/* 🔴 ЧИСЛО КОЛОНОК — НЕ КОНСТАНТА И НЕ МЕДИАЗАПРОС, А ТЕКУЧАЯ ВЕЛИЧИНА.
   Замер канона свипом 380…1400 через 20: ступени 1→2→3→4 приходятся на контейнер
   428→448, 648→668, 888→908. Три независимые ступени дают min ∈ (208.5 … 210.7] —
   пересечение оставляет ровно одно круглое значение. Ступень зависит от ШИРИНЫ
   КОНТЕЙНЕРА, а не окна: панель стоит внутри секции, и медиазапрос по окну промахнулся бы
   везде, где контейнер уже своего порога. */
.ds-trust__list {
  display: grid;
  grid-template-columns: var(--ds-trust-cols, repeat(auto-fit, minmax(210px, 1fr)));
  gap: var(--ds-trust-gap, var(--ds-space-3) var(--ds-space-4));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Пункт: маркер вне потока слева, поэтому левое поле — место ПОД маркер, а не отступ.
   Связь названа здесь, чтобы следующий не «убрал лишний отступ» и не посадил текст на точку. */
.ds-trust__item {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--ds-trust-item-gap, var(--ds-space-1));
  padding-left: var(--ds-trust-marker-room, var(--ds-space-6));
}

.ds-trust__item::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--ds-trust-marker-top, 0);
  width: var(--ds-trust-marker-size, var(--ds-space-3));
  height: var(--ds-trust-marker-size, var(--ds-space-3));
  border-radius: var(--ds-radius-full);
  background: var(--ds-trust-marker-bg, var(--ds-color-primary));
  /* 🔴 ВНУТРЕННИЙ БЛИК МАРКЕРА — НАШЁЛСЯ ПО ИСТОЧНИКУ, НЕ ПО РЕНДЕРУ (17.08).
     Сверка отрисовкой дала 22 совпадения из 23 — и блика среди них не было: я его не
     СПРАШИВАЛ. Зонд печатал размер, положение и число слоёв фона; тень маркера в вопрос
     не входила, поэтому и в ответе её не было.
     ⚠ И он НЕ равен блику значка карточки: у маркера `0 1px .5`, у значка `0 1.5px .45`.
     Соблазн переиспользовать готовый токен был бы ошибкой — числа близкие, но разные. */
  box-shadow: var(--ds-trust-marker-inner, none);
}

/* Утверждение и его источник — РАЗНЫЕ роли, а не крупный и мелкий текст одного абзаца:
   первое говорит, что мы заявляем, второе — чем это проверяется снаружи. */
.ds-trust__name {
  font-size: var(--ds-trust-name-size, var(--ds-fs-sm));
  font-weight: var(--ds-trust-name-weight, var(--ds-fw-bold));
  line-height: var(--ds-trust-name-lh, inherit);
  color: var(--ds-trust-name-color, var(--ds-text-primary));
}

.ds-trust__source {
  font-size: var(--ds-trust-source-size, var(--ds-fs-xs));
  font-weight: var(--ds-trust-source-weight, var(--ds-fw-medium));
  line-height: var(--ds-trust-source-lh, inherit);
  color: var(--ds-trust-source-color, var(--ds-text-secondary));
}

/* Оговорка — самая мелкая строка панели и единственная, которую нельзя выбросить.
   Отступ сверху отделяет её от списка: она относится ко ВСЕМ пунктам, а не к последнему. */
.ds-trust__disclaimer {
  font-size: var(--ds-trust-note-size, var(--ds-fs-xs));
  font-weight: var(--ds-trust-note-weight, var(--ds-fw-medium));
  line-height: var(--ds-trust-note-lh, inherit);
  color: var(--ds-trust-note-color, var(--ds-text-secondary));
  margin: var(--ds-trust-note-gap, var(--ds-space-4)) 0 0;
}

/* ═══ components/button/button.css ═══ */
/* f7c_design :: Кнопка. Требует tokens/tokens.css. */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  padding: var(--ds-space-3) var(--ds-space-6);
  font-family: var(--ds-font-base);
  font-size: var(--ds-fs-md);
  font-weight: var(--ds-fw-semibold);
  line-height: 1;
  border: 1px solid transparent;
  border-radius: var(--ds-radius-btn);
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.ds-btn:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: 2px;
}

.ds-btn--primary {
  background: var(--ds-color-primary);
  color: var(--ds-color-on-primary);
}
/* Свечение на hover — канон concept-v2.3.0:122 (.btn-solid:hover{...box-shadow:var(--shadow-pink)}).
   Это СОСТОЯНИЕ: флагом в site.json оно невыразимо в принципе — сборщик не знает,
   наведена мышь или нет. Поэтому правило в ядре, а не класс на элементе. */
.ds-btn--primary:hover { background: var(--ds-color-primary-hover); text-decoration: none;
  box-shadow: var(--ds-shadow-brand); }
.ds-btn--gradient:hover { box-shadow: var(--ds-shadow-brand); }
.ds-btn--primary:active { background: var(--ds-color-primary-active); }

.ds-btn--secondary {
  background: transparent;
  /* НЕ --ds-color-primary: он do-not-flip (не переворачивается), а фон прозрачный →
     в тёмной теме синий #2456d6 на тёмной странице даёт 2.52, ниже нормы 3 для крупного
     текста. Найдено прогоном тёмной темы на живом стенде 17.07 (замер computed, не глаз).
     --ds-text-link флипается: в светлой = var(--ds-color-primary) (ТОТ ЖЕ синий, светлая
     не меняется), в тёмной = #93a4f0 (читаемый голубой). Тот же приём, что focus-ring
     v0.25.0. Прецедент в этом же файле: .ds-btn--ghost уже красится через text-link. */
  color: var(--ds-text-link);
  border-color: var(--ds-border-strong);
}
.ds-btn--secondary:hover { border-color: var(--ds-color-primary); text-decoration: none; }

.ds-btn--gradient {
  /* Требование к теме: ВСЕ стопы --ds-gradient-brand дают ≥4.5 с on-primary */
  background: var(--ds-gradient-brand);
  color: var(--ds-color-on-primary);
}
.ds-btn--gradient:hover { filter: brightness(0.94); text-decoration: none; }
.ds-btn--gradient:active { filter: brightness(0.88); }

.ds-btn--ghost {
  background: transparent;
  color: var(--ds-text-link);
}
.ds-btn--ghost:hover { background: var(--ds-surface-raised); text-decoration: none; }

.ds-btn--sm { padding: var(--ds-space-2) var(--ds-space-4); font-size: var(--ds-fs-sm); }
/* ── КРУПНАЯ КНОПКА: числа СТАЛИ РОЛЬЮ (17.08, разбор героя при 390) ────────────────────────
 * Модификатор задавал `--ds-space-4/--ds-space-8/--ds-fs-lg` напрямую, БЕЗ брендового крючка:
 * бренд не мог назвать свои числа, даже зная канонные. Замер канона по секциям — числа у него
 * ОДНИ И ТЕ ЖЕ там, где у нас стоит `--lg`, и это делает одно значение достаточным:
 *     канон topSlider «Попробовать 15 дней бесплатно →»  14px · pad 12/22
 *     канон srv       «Получить расчёт →»                 14px · pad 12/22
 *     наше  hero      70px высотой                        18px · pad 16/32  ⇒ ПЕРЕНОС в две строки
 *     наше  srv       52px высотой                        18px · pad 16/32
 * ⚠ ОБЛАСТЬ ПРАВКИ НАЗВАНА ЗАМЕРОМ, А НЕ НА ГЛАЗ: на живом ровно 5 узлов `.ds-btn--lg`,
 * видимых 3 — hero, srv и calc. Третий уже переопределён калькулятором в 13px/11-20, что
 * совпадает с канонным `calc` 13px/11-20 ⇒ его эта правка не трогает. Двигаются ровно два,
 * и оба — в сторону канона.
 * ⚠ Умолчания = прежние значения: у izibuh и tezum не меняется ничего, пока они не назовут свои. */
.ds-btn--lg {
  padding: var(--ds-btn-lg-pad, var(--ds-space-4) var(--ds-space-8));
  font-size: var(--ds-btn-lg-font, var(--ds-fs-lg));
}

/* Растяжка на блок: канон `#calc .cr .btn` — пилюля во ВСЮ ширину блока (365 при родителе 381,
   то есть 100 % содержимого). Роль, а не размер: с любым размерным модификатором сочетается. */
.ds-btn--block {
  display: flex;
  width: 100%;
  /* 🔴 `border-box` ОБЯЗАТЕЛЕН РЯДОМ С `width: 100%`, и это поймал опыт, а не рассуждение:
     без него кнопка вышла 407 в блоке 365 — поля 20+20 прибавились СНАРУЖИ ста процентов.
     Тот же механизм, что ловил гейт 24 на контейнерах: `width` считается по content-box,
     если не сказано иначе. Ставлю только в этой роли — у остальных кнопок ширина по
     содержимому, и трогать их незачем. */
  box-sizing: border-box;
}

.ds-btn[disabled],
.ds-btn--disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* ═══ components/header/header.css ═══ */
/* f7c_design :: Шапка сайта. Требует tokens/tokens.css. */
.ds-header {
  background: var(--ds-surface-page);
  border-bottom: 1px solid var(--ds-border-default);
}

.ds-header__inner {
  /* своя ось ширины: канон forbion держит шапку текучей (88 %), секции — фиксированными.
     Умолчание токена = `--ds-container-max`, поэтому поведение прочих брендов не меняется. */
  max-width: var(--ds-header-max);
  margin: 0 auto;
  padding: var(--ds-space-4) var(--ds-container-pad);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-6);
  flex-wrap: wrap;
}

.ds-header__logo {
  font-size: var(--ds-fs-lg);
  font-weight: var(--ds-fw-bold);
  color: var(--ds-text-primary);
  text-decoration: none;
}
.ds-header__logo:hover { text-decoration: none; }

.ds-header__nav {
  display: flex;
  align-items: center;
  gap: var(--ds-space-6);
  flex-wrap: wrap;
}

/* Кегль и поля ссылок шапки — токены: канон forbion ужимает их ниже 1040
   (`12.5px`, `padding 7px`), у нас было `14px` и без полей на всех ширинах.
   Умолчания прежние — izibuh и tezum не двигаются. */
.ds-header__link {
  font-size: var(--ds-header-link-size, var(--ds-fs-sm));
  padding: var(--ds-header-link-pad, 0);
  font-weight: var(--ds-fw-medium);
  color: var(--ds-text-secondary);
}
.ds-header__link:hover { color: var(--ds-text-primary); text-decoration: none; }
.ds-header__link--active { color: var(--ds-color-primary); }

/* Липкая шапка */
.ds-header--sticky {
  position: sticky;
  top: 0;
  z-index: 30;
}

/* Стекло: единственное легальное место текста поверх размытия — подложка
   непрозрачностью ≥85% сохраняет контраст текста при любом контенте позади.
   Фолбэк без поддержки backdrop-filter — сплошной фон. */
.ds-header--glass {
  background: var(--ds-surface-page);
}

@supports (backdrop-filter: blur(12px)) {
  .ds-header--glass {
    background: color-mix(in srgb, var(--ds-surface-page) 85%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }
}

/* ── ШАПКА-«ТАБЛЕТКА» (c-design 14.08, строка A13 сверки живого с каноном) ──────────────
 * Канон несёт не «просто скруглённую шапку», а ПЛАВАЮЩУЮ панель: полоса шапки прозрачна,
 * а внутренний контейнер — пилюля со стеклом. Значения СНЯТЫ С КАНОНА, а не подобраны
 * (замер `concept-v0.30.20-draft` @ v0.90.0, вьюпорт 1440):
 *   радиус 999px · высота 63 · фон rgba(255,255,255,.88) · backdrop blur(14px) saturate(1.3)
 *   паддинги 14px 20px 14px 22px · две внутренние волосяные линии (светлая сверху, тёмная снизу)
 *
 * 🔴 ПОЧЕМУ ЭТО МОДИФИКАТОР, А НЕ ПРАВКА `.ds-header`: полосу-шапку несут izibuh и tezum,
 * им «таблетка» не заказана. Модификатор включается потребителем, умолчание не меняется.
 *
 * ⚠ СТЕКЛО НЕ ДУБЛИРУЮ: `--glass` уже даёт полупрозрачный фон и blur на самой ШАПКЕ.
 * Здесь стекло переезжает на ВНУТРЕННИЙ контейнер, а сама шапка становится прозрачной —
 * иначе стекло было бы дважды, а фон полосы выдал бы прямоугольник под пилюлей.
 * Поэтому `--pill` и `--glass` вместе бессмысленны: `--pill` уже несёт стекло.
 *
 * Волосяные линии — inset-тени, а не border: border съел бы 2px из внутренней высоты и
 * сдвинул содержимое; inset рисуется поверх фона и геометрию не трогает. */
.ds-header--pill {
  background: transparent;
  border-bottom: 0;
  padding-block: var(--ds-space-2);
}

.ds-header--pill .ds-header__inner {
  border-radius: var(--ds-radius-full);
  background: var(--ds-surface-page);
  /* 🔴 --ds-space-5 В ШКАЛЕ НЕТ (1,2,3,4,6,8,12,16,24). Первая редакция взяла его «по
   * аналогии» — объявление стало невалидным на этапе вычисления, и padding молча стал 0:
   * пилюля вышла высотой 32 вместо канонных 63. Ни один гейт не покраснел. Мой же
   * записанный класс — `var()` без фолбэка умирает молча; лечится гейтом 17. */
  padding: var(--ds-space-3) var(--ds-space-6);
  box-shadow:
    inset 0 1.5px 0 color-mix(in srgb, var(--ds-surface-page) 85%, transparent),
    inset 0 -1.5px 0 color-mix(in srgb, var(--ds-text-primary) 5%, transparent),
    var(--ds-shadow-md);
}

@supports (backdrop-filter: blur(14px)) {
  .ds-header--pill .ds-header__inner {
    background: color-mix(in srgb, var(--ds-surface-page) 88%, transparent);
    backdrop-filter: blur(14px) saturate(1.3);
    -webkit-backdrop-filter: blur(14px) saturate(1.3);
  }
}

/* 🔴 На узком экране пилюля СЪЕДАЕТ ширину дважды: поля контейнера + собственные паддинги.
 * Замер канона на 390: пилюля прижата к краям сильнее, чем на десктопе. Поэтому боковой
 * паддинг здесь уменьшается — иначе логотип и бургер сходятся в центр. */
@media (max-width: 640px) {
  .ds-header--pill .ds-header__inner {
    padding-inline: var(--ds-space-4);
  }
}

/* Вариант с CTA-кнопкой: кнопка прижимается к правому краю после навигации */
.ds-header__cta { margin-left: var(--ds-space-4); }
@media (max-width: 640px) {
  .ds-header__cta { margin-left: auto; }
}

/* ── Лого шапки: габарит приходит HTML-атрибутами width/height (шаблон рендерит
   <img width=… height=…> из logo.display.header пакета). Это ИНТРИНСИК-размер:
   на узком экране он не ужимается сам, и лого выдавливает CTA на вторую строку.

   Замерено 2026-07-16 (наряд c-web-1), а не предположено — izibuh @360px:
     без клэмпа: лого 340×78, шапка 176px, ДВЕ строки (CTA переносится);
     с клэмпом : лого 144×33, шапка  74px, одна строка, CTA цел.
   ⚠ Поправка к формулировке наряда: CTA НЕ выдавливается и не пропадает — он
   виден (119×42) и кликабелен. .ds-header__inner несёт flex-wrap:wrap, поэтому
   он ПЕРЕНОСИТСЯ. Регресс реален, но он в высоте шапки (176 против 74), а не в
   потере кнопки.

   Почему клэмп в ЯДРЕ, а не токеном бренда (замер трёх пакетов):
     izibuh  340×78 → две строки  ← заявлен
     tezum   275×70 → две строки  ← НЕ ЗАЯВЛЕН НИКЕМ, ломается так же
     forbion 160×51 → одна строка ← не ломается (лого узкий)
   Брендовый токен починил бы izibuh и оставил tezum сломанным: mobileMaxWidth
   есть ТОЛЬКО у izibuh. Дефект общий — лечится в общем месте.

   Почему 40vw, а не 150px из brand.json (подобрано замером 3×3):
     40vw → 320px:128 одна строка | 360px:144 одна строка  ← берём
     45vw → 320px:144 ДВЕ строки  | 360px:162 одна строка
     50vw → 320px:160 ДВЕ строки  | 360px:180 ДВЕ строки
   Фикс 150px держит 360px, но на 320px даёт две строки — вьюпорт-относительное
   значение выживает там, где абсолютное нет.

   Токен --ds-logo-header-mobile-max — точка расширения, НЕ требуется сегодня:
   ни один бренд его не задаёт, дефолт покрывает все три. Чтобы бренд задал его
   своим theme.css, нужен THEME_ALLOWED валидатора c-tpl — это отдельный заход,
   письмом-ДО, и только когда появится предмет.

   ⚠ Честно про две уступки (обе в письме c-web-1/c-design-2):
   1. forbion на мобиле: 160 → 144, ниже его фолбэк-минимума. Он и так помещался.
   2. izibuh при ≥414px: 40vw = 166 > mobileMaxWidth 150 из brand.json. Перенос
      не возникает (166+24+119=309 ≤ 382 usable) — нарушается эстетический потолок
      c-design-2, а не вёрстка. Точный потолок = токен = заход с c-tpl. */
.ds-header__logo img { max-width: 100%; height: auto; }
@media (max-width: 640px) {
  .ds-header__logo img { max-width: var(--ds-logo-header-mobile-max, 40vw); }
}

/* ── Лого шапки в тёмной теме ──────────────────────────────────────────────────
   Признак ВЛАДЕЛЬЦА 17.07: включил тёмную тему — логотип шапки пропал. Причина:
   шапка несёт ОДИН файл на обе темы (чернильный знак), в тёмной он чернила на
   тёмном. Подвал живёт только потому, что он ВСЕГДА тёмный и берёт fullInverse
   один раз — он не переключается.

   ПОЧЕМУ ЭТО ЯДРО, А НЕ ПРОВОДКА ШАБЛОНА (развилка integrator-c, отвечаю):
   разметку отдаёт шаблон, но РЕШЕНИЕ «который из двух виден» — каскад, и он мой.
   Ни одно в одиночку не закрывает:
     · <picture> + <source media="(prefers-color-scheme: dark)"> закрывает ТОЛЬКО
       системную тему. Ручной переключатель ставит data-theme на <html>, а медиа-
       запрос <source> его не видит — владелец нажал именно КНОПКУ;
     · JS-подмена src сломала бы no-JS и продублировала бы то, что каскад умеет сам;
     · filter:invert запрещён — weasyprint его дропает (урок c-deck), и инверсия
       ПЕРЕКРАШИВАЕТ бренд, а не подставляет утверждённый инверс-знак.
   Остаётся: в разметке ОБА <img>, в ядре — кто виден. Это ровно граница «шаблон
   даёт data-theme, ядро даёт палитру», по которой уже сделана тёмная тема v0.25.0.

   Специфичность — условие работы, а не стиль (норма из v0.25.0): (0,2,0)+класс.
   :where() здесь применять НЕЛЬЗЯ — его специфичность ноль, защита обнулится.

   АДДИТИВНО: сайт, который отдаёт ОДИН <img> без этих классов, не меняется —
   ни одно правило не матчится, лого показывается как раньше. */
.ds-header__logo-dark { display: none; }

:root[data-theme="dark"] .ds-header__logo-light { display: none; }
:root[data-theme="dark"] .ds-header__logo-dark { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ds-header__logo-light { display: none; }
  :root:not([data-theme="light"]) .ds-header__logo-dark { display: block; }
}

/* ══ РЕЖИМ БУРГЕРА (c-design, 15.08, наряд integrator-c2 по строке 80 партии 5) ══════
 * ПОВОД: на 390 шапка forbion разворачивается в ДВЕ строки высотой ~188px против ~74 у канона
 * (логотип + бургер одной строкой) — четверть первого экрана съедена навигацией. `c-tpl` замерил
 * и назвал причину точно: порог заводить НЕ НА ЧТО, компонента в ядре нет вовсе.
 *
 * ЧТО СНЯТО С КАНОНА (`concept-v0.30.20-draft.html`, правило `.burger` и блок `@media(max-width:940px)`),
 * а что моё — разделяю, потому что канон здесь описывает forbion, а компонент поедет ко всем:
 *   канон: кнопка 40×38, рамка 1px `--ln`, радиус `--r-sm`, фон градиент `card→rz`, чернила `--ink`,
 *          кегль 17, `margin-left:auto`; панель `position:fixed; inset:84px 14px auto 14px`,
 *          `z-index:65`, фон `--card`, радиус 26, тень `--deep`, колонка, поля 16/26, зазор 12,
 *          ссылки во всю ширину; открытое состояние — класс на самой панели.
 *   моё:   всё это выражено ТОКЕНАМИ ядра, а числа канона стали их умолчаниями. Бренд, у которого
 *          шапка другая, меняет значение, а не переписывает правило.
 *
 * 🔴 ПОЧЕМУ РЕЖИМ ВКЛЮЧАЕТСЯ КЛАССОМ, А ОТМЕНЯЕТСЯ МЕДИАЗАПРОСОМ — и почему не наоборот.
 * Класс из CSS не добавить: потребитель не может «включить» режим внутри своего медиазапроса,
 * как включают обычный модификатор. Поэтому режим — БАЗОВОЕ состояние (класс стоит в разметке
 * всегда), а ширина, на которой он ОТМЕНЯЕТСЯ, живёт в ОДНОМ блоке ниже. Порог там литерал —
 * иначе никак, медиазапрос переменную не читает, — но он ровно один, назван адресом канона,
 * и потребителю достаточно передекларировать этот блок под свою ширину.
 * ⚠ Это НЕ то же, что чипы, где я порог 1250 в компонент не зашивал: там порог задаёт КОМПОЗИЦИЯ
 * страницы (где лесенка помещается рядом с героем), и он у каждой раскладки свой. Здесь порог
 * задаёт САМА ШАПКА — ширина, на которой её ссылки перестают помещаться в строку, — и без
 * умолчания компонент не работает вовсе. Разница в том, ЧЕЙ предмет задаёт число.
 *
 * КОНТРАКТ С ПОТРЕБИТЕЛЕМ (то, чего ядро сделать не может):
 *   1. `.ds-header--burger` на самой шапке — постоянно, не по условию;
 *   2. кнопка `<button class="ds-header__burger" aria-label="Меню" aria-expanded="false"
 *      aria-controls="…">` — ядро не решает, каким символом её рисовать, но задаёт габарит;
 *   3. открытие — класс `.ds-header__nav--open` на панели, и `aria-expanded` на кнопке.
 *      Переключение — JS шаблона: у ядра скриптов нет и не будет. */
.ds-header__burger {
  display: none;
  flex: none;
  width: var(--ds-header-burger-size, 40px);
  height: var(--ds-header-burger-height, 38px);
  border: 1px solid var(--ds-border-default);
  border-radius: var(--ds-header-burger-radius, var(--ds-radius-sm));
  background: var(--ds-header-burger-bg, var(--ds-surface-section));
  color: var(--ds-text-primary);
  font-size: var(--ds-header-burger-glyph, 17px);
  line-height: 1;
  cursor: pointer;
  align-items: center;
  justify-content: center;
}

.ds-header--burger .ds-header__burger {
  display: flex;
  margin-left: auto;
}

/* Панель меню: выпадает под шапку и НЕ участвует в потоке — иначе она растянет шапку,
   ровно тем дефектом, ради которого режим и заводится. */
.ds-header--burger .ds-header__nav {
  display: none;
  position: fixed;
  inset: var(--ds-header-menu-top, 84px) var(--ds-header-menu-side, 14px) auto
         var(--ds-header-menu-side, 14px);
  z-index: var(--ds-header-menu-z, 65);
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ds-header-menu-gap, var(--ds-space-3));
  padding: var(--ds-header-menu-pad, 16px 26px);
  background: var(--ds-surface-section);
  border-radius: var(--ds-header-menu-radius, var(--ds-radius-xl));
  box-shadow: var(--ds-shadow-lg);
}
.ds-header--burger .ds-header__nav--open { display: flex; }
.ds-header--burger .ds-header__nav .ds-header__link { width: 100%; }

/* ── ЕДИНСТВЕННОЕ МЕСТО С ПОРОГОМ: ширина, на которой режим ОТМЕНЯЕТСЯ ──────────
   940 — замер канона forbion (`@media (max-width:940px)` — там бургер появляется, значит
   выше него шапка обычная). Потребителю с другой шапкой достаточно передекларировать
   ЭТОТ блок под свою ширину: правил внутри четыре, все возвращают базовое поведение. */
@media (min-width: 941px) {
  .ds-header--burger .ds-header__burger { display: none; }
  .ds-header--burger .ds-header__nav {
    display: flex;
    position: static;
    inset: auto;
    flex-direction: row;
    align-items: center;
    padding: 0;
    background: none;
    border-radius: 0;
    box-shadow: none;
    /* 🔴 ЭТИ ДВА ВЕРНУЛ НЕ Я, А ГЕЙТ 22 — на первом же своём прогоне.
       `gap` режима (12px) перебивал базовый зазор шапки (--ds-space-6) на ДЕСКТОПЕ, а `z-index:65`
       оставлял меню поверх страницы там, где оно обычный ряд ссылок. Ни то, ни другое не ломает
       шапку — поэтому мой замер 390/1440 их и не показал: я мерил высоту и позицию, а не зазор,
       и «нет базы ДО» не отличить от «так и должно быть». */
    gap: var(--ds-space-6);
    z-index: auto;
  }
  .ds-header--burger .ds-header__nav .ds-header__link { width: auto; }
}

/* ═══ components/card/card.css ═══ */
/* f7c_design :: Карточка. Требует tokens/tokens.css. */
.ds-card {
  /* Подложка и тень плитки — РОЛИ (17.08, разбор `wcard` слайда 3). У канона часть плиток
     несёт не плоскую заливку, а «плиту»: два слоя фона и пять теней, три из них внутренние.
     Выразить это `--ds-surface-page` нельзя — там ЦВЕТ поверхности, а здесь СОСТАВ.
     Умолчания прежние ⇒ рендер не меняется ни у одной существующей роли. */
  background: var(--ds-card-bg, var(--ds-surface-page));
  /* Рамка — тоже роль: у канонной «плиты» рамки нет вовсе (0px none), её заменяет верхний
     внутренний блик тени. Снять её `--ds-card--raised` нельзя: тот перебивает `box-shadow`
     напрямую, мимо роли, и погасил бы состав плиты. Умолчание прежнее. */
  border: var(--ds-card-border, 1px solid var(--ds-border-default));
  border-radius: var(--ds-card-radius, var(--ds-radius-card));
  box-shadow: var(--ds-card-shadow, var(--ds-shadow-card));
  /* 🔴 ПОЛЯ И ЗАЗОР — ПО РОЛЯМ, как кегли и размер значка (15.08, разбор высоты 162 против 124).
     `c-tpl` разложил разницу опытами и вывел «у канона МЕНЬШЕ кегль подписи», интегратор передал
     это дальше как наряд. **Замер канона отменяет вывод: кегль ОДИН И ТОТ ЖЕ — 12px у обоих.**
     Подпись канона — 72 знака в ТРИ строки (52 = 3×17.4), а не 82 в две, как считала гипотеза.
     Настоящий остаток — ПОЛЯ и интерлиньяж:
         канон .pcard  поля 15/16 · зазор 8  · подпись 12/17.4
         у нас         поля 24    · зазор 12 · подпись 12/18
         30 против 48 = 18 px · три строки 52 против 54 = 2 px  ⇒ ровно те 20,
         что остались у `c-tpl` после снятия значка (144 − 124).
     ⚠ Значок свои 18 px (162 → 144) отдаёт модификатором `--icon-inline`, он уже есть. */
  padding: var(--ds-card-pad, var(--ds-space-6));
  display: flex;
  flex-direction: column;
  gap: var(--ds-card-gap, var(--ds-space-3));
}

.ds-card--raised {
  border: none;
  box-shadow: var(--ds-shadow-md);
}

/* Цветная (акцентная/розовая у izibuh) тень — декор-слой прототипа:
   часть карточек светятся брендовой тенью, не только нейтральной.
   Флаг оставлен для случаев, где свечение статично и осознанно поставлено автором. */
.ds-card--glow {
  border: none;
  box-shadow: var(--ds-shadow-brand);
}

/* 🔴 ПРАВИЛА свечения по канону — вместо ручной расстановки флага в site.json.
   Замер c-web-1: в каноне свечение живёт ПРАВИЛАМИ, а в проде было флагом, который нигде
   не проставлен. Расставить руками = подделка, и вот почему это не преувеличение:
     · hover (:298, :308) — СОСТОЯНИЕ. Сборщик не знает, наведена ли мышь. Флагом
       принципиально невыразимо: получилось бы вечное свечение вместо отклика на курсор;
     · nth-child(2n) — ПОЗИЦИЯ в потоке. Флаг пришлось бы проставлять вручную через одну,
       и любая вставка/удаление карточки в site.json ломала бы чётность МОЛЧА.
   Оба свойства принадлежат рендеру, а не данным, — поэтому правила в ядре. */
/* ⚠ ОБЛАСТЬ СУЖЕНА после письма-ДО c-web-1 — моя первая версия была ШИРЕ канона.
   В каноне `.card` живёт РОВНО В ДВУХ секциях (проверил разметку, не CSS: :811-816 «what»,
   :987-991 «glavbuh-scope»). Шаги и отзывы там класса `.card` не носят вовсе.
   А в ядре `.ds-card` носят И шаги (.ds-card--step), поэтому голое nth-child(2n) засветило бы
   каждый второй ШАГ — эффект, которого в каноне нет и быть не может.
   Исключаем по РОДИТЕЛЮ (.ds-steps), а не по модификатору: так правило повторяет канонную
   структуру «карточки в сетке», а не перечисляет исключения, которые придётся дополнять. */
:not(.ds-steps) > .ds-card:nth-child(2n):hover { box-shadow: var(--ds-shadow-brand); }
/* 🔴 ПРАВИЛО ПОКА МЁРТВОЕ, и это сказано намеренно. В каноне `.card--key` объявлен в CSS
   (:307-308), но в РАЗМЕТКЕ канона его нет ни на одном элементе — я проверил, а не предположил.
   Значит и у нас оно не сработает, пока шаблон не начнёт эмитить класс, а признака «ключевая
   карточка» в site.json сегодня нет (замер c-web-1).
   Оставляю: канон его объявляет, и когда признак появится — правило уже будет на месте.
   Но никто не должен считать, что свечение ключевой карточки РАБОТАЕТ: оно объявлено, не живо. */
.ds-card--key:hover {
  box-shadow: var(--ds-shadow-brand);
  border-color: var(--ds-color-accent);
}

.ds-card__media {
  margin: calc(-1 * var(--ds-space-6)) calc(-1 * var(--ds-space-6)) var(--ds-space-3);
  border-radius: var(--ds-radius-card) var(--ds-radius-card) 0 0;
  overflow: hidden;
}
.ds-card__media img { display: block; width: 100%; height: auto; }

/* 🔴 ЗАГОЛОВОК КАРТОЧКИ — ОДИН СТИЛЬ НА ВСЕ РОЛИ, И ЭТО РАСХОЖДЕНИЕ (замер 15.08).
   Живое: `.ds-card__title` = 18/600 у ВСЕХ карточек — хаба, состава, промо, лесенки.
   Канон различает роли: хаб крупный 21/800 · хаб обычный 17/800 · лесенка 18/800 ·
   состав 16/700 · промо 13.5/700. То есть у канона заголовок карточки — не один объект,
   а пять; сведение к одному токену РОЛЬ ТЕРЯЕТ (третий такой случай за день после
   радиусов и полей секций).
   Токены ниже дают роль ТОЧКУ ВХОДА; умолчания — прежние значения, поэтому до применения
   модификатора ничего не меняется ни у одного бренда. */
.ds-card__title {
  font-size: var(--ds-card-title-size, var(--ds-fs-lg));
  font-weight: var(--ds-card-title-weight, var(--ds-fw-semibold));
  /* Интерлиньяж и верхний отступ заголовка — токены (16.08, свип `c-ops` по 360…1366).
     У канона обе роли карточки хаба дают отношение 1.14 (23.94/21 и 19.38/17) и отступ
     сверху 4; у нас было 1.20 и 0. ⚠ Обе величины ПОСТОЯННЫ на всех ширинах — медиазапрос
     не нужен, и это проверено рядом, а не предположено. */
  line-height: var(--ds-card-title-lh, inherit);
  margin: var(--ds-card-title-mar-top, 0) 0 0;
}

.ds-card__text {
  color: var(--ds-text-secondary);
  font-size: var(--ds-card-text-size, var(--ds-fs-sm));
  /* Верхний отступ абзаца — РОЛЬ (17.08, разложение `#ladder`). У канона расстояния внутри
     плитки заданы ПОЭЛЕМЕНТНО (14 · 10 · 0 · 10/2 · 8 · 16), а не одним зазором флекса;
     одинаковый `gap` шести разных расстояний воспроизвести не может в принципе.
     Умолчание 0 = прежний рендер, свойство здесь было и раньше. */
  margin: var(--ds-card-text-mar-top, 0) 0 0;
  line-height: var(--ds-card-text-lh, inherit);
  /* 🔴 РОСТ АБЗАЦА — РОЛЬ, И ЭТО МЕХАНИЗМ, А НЕ КОСМЕТИКА (17.08, `#ladder`).
     У канона `.lad .for` несёт `flex: 1`: абзац ЗАБИРАЕТ слабину растянутой плитки, и подвал
     оказывается на постоянном расстоянии от низа — замер канона даёт 27 у ВСЕХ трёх плиток и
     на обеих ширинах. У нас абзац не растёт, слабина остаётся под подвалом, и он висит на
     разной высоте: 83 · 27 · 106 @901 и 50 · 27 · 50 @1366 — подвалы соседних плиток НЕ В РЯД.
     ⚠ Высотами это не видно вовсе: плитки одинаковы, секция сходится. Видно ПОЛОЖЕНИЕМ. */
  flex: var(--ds-card-text-flex, initial);
  /* 🔴 РЕЗЕРВ ПОД ИЛЛЮСТРАЦИЮ — ТОКЕН (16.08, находка integrator-c2). Иллюстрация в карточке
     хаба лежит `absolute` и потоку места НЕ ЗАНИМАЕТ ⇒ текст идёт ПОД НЕЁ и перекрывается.
     Канон резервирует место ПОЛЕМ САМОГО АБЗАЦА: `padding-right` 186 у крупной карточки
     (илл. 170×136) и 112 у обычной (104×84); у нас было 0 при тех же иллюстрациях.
     ⚠ Это НЕ плотность и не длина текста: символов поровну (122 против 125), кегль и
     интерлиньяж совпадают ДО СОТОЙ, расходится ШИРИНА ПЕРЕНОСА — 295 против ~206.
     📌 У абзаца ТРИ размера: кегль · интерлиньяж · ширина переноса. Две сходились, третью
     не мерил ни один наш гейт — и именно она делала текст нечитаемым. */
  padding-right: var(--ds-card-text-pad-right, 0);
}

/* 🔴 ПОЛЕ ПОДВАЛА — ТОКЕН (16.08, разбор карточек хаба). Слагаемое повторяется на ПЯТИ узлах,
   и это делает его крупнее любого единичного: 29 × 5 против +8 полей и −4 значка.
   ⚠ Но замер ПАРОЙ УЗЛОВ показал, что число — только половина предмета:
       канон  span.hgo  490×15 · flex · кегль 13 · вес 800 · зелёный #3E7013 · поля 0 — ЭТО ССЫЛКА
       наше   div.ds-card__footer 487×44 · block · поле сверху 12 · внутри a.ds-btn 186×32 — КНОПКА
   ⇒ из 29 моих здесь ровно 12 (поле). Остальные 17 — РОЛЬ УЗЛА: кнопка против ссылки,
   и подменять её своим правилом я не стану: это состав, держатель `c-tpl`.
   Числа канона ему: кегль 13 · вес 800 · цвет #3E7013 · без фона и полей · стрелка 11×15. */
/* 🔴 ВЕРХНИЙ ОТСТУП ПОДВАЛА — ТОЖЕ РОЛЬ, И ЭТО НЕ КОСМЕТИКА (17.08, `#ladder`).
   `margin-top: auto` прижимает подвал к низу плитки, поэтому в растянутом ряду он ЕДЕТ:
   замер одной и той же плитки лесенки дал 0 @390 · 56 @901 · 23 @1366 — величина зависит
   от САМОЙ ВЫСОКОЙ плитки ряда, а не от своей. У канона отступ постоянный (16), и лишнее
   место остаётся снизу. ⚠ Умолчание `auto` = прежний рендер: роль появляется, поведение нет. */
.ds-card__footer {
  margin-top: var(--ds-card-footer-mar-top, auto);
  padding-top: var(--ds-card-footer-pad-top, var(--ds-space-3));
}

/* Опциональный слот иконки (набор — components/icons/icons.svg) */
/* 🔴 ЗНАЧОК КАРТОЧКИ — РАЗМЕР И ФОРМА ПО РОЛИ (15.08, строки 46 · 51 · 60 диффа глазами).
   Замер канона: значок у каждой роли СВОЙ и всегда КРУГЛЫЙ —
     промо-карточка 58 · плитка состава и «почему мы» 46 · бейдж хаба 52 · лесенка 104
   У нас один размер 48 и радиус `md` (3px у forbion) — квадрат. Единый размер = потеря роли,
   тот же случай, что радиусы (11 значений) и типографика (12 ступеней).
   Умолчания прежние: izibuh и tezum получают 48 и `--ds-radius-md`, как было. */
.ds-card__icon {
  width: var(--ds-card-icon-size, 48px);
  height: var(--ds-card-icon-size, 48px);
  border-radius: var(--ds-card-icon-radius, var(--ds-radius-md));
  /* 🔴 ПОДЛОЖКА ЗНАЧКА — ТОКЕН, потому что у канона она НЕ плоская (партия 7, строка 94).
     Замер `.lic` канона: `radial-gradient(circle at 32% 26%, --orbhi, --soft 58%, --rz 100%)`
     плюс внутренний блик `inset 0 1.5px 0 rgba(255,255,255,.45)`. Наш плоский `--ds-surface-raised`
     в СВЕТЛОЙ читается похоже, а в ТЁМНОЙ значок сливается с карточкой почти полностью:
     обе поверхности там `#27271F`. Умолчание = прежний рендер. */
  background: var(--ds-card-icon-bg, var(--ds-surface-raised));
  box-shadow: var(--ds-card-icon-inner, none);
  color: var(--ds-color-primary); /* иконка красится через currentColor */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* 🔴 РОЛЬ РАЗМЕЩЕНИЯ ЗНАЧКА: НАД заголовком или РЯДОМ с ним (партия 7, строка 97).
   У канона это РАЗНЫЕ секции, а не вкус: в хабе `.lic` стоит блоком над заголовком
   (`margin-bottom:14px`), в блоке «свой сервер» значок обтекается текстом справа
   (`float:right; margin:0 0 10px 14px`). Замер следствия: карточка сервера у канона **124**,
   у нас **184** — шестьдесят пикселей разницы дал не отступ, а именно размещение.
   Модификатор, а не токен: меняется поток, а не величина. Умолчание — прежнее (над). */
/* 🔴 ПРАВКА ЭТОГО ЖЕ ПРАВИЛА, ТОТ ЖЕ ДЕНЬ: поплавок был НЕДЕЙСТВУЮЩИМ. Замер `c-tpl` на живой
   сборке: класс встал, `computed float: right` — зелёное, а высота карточки **202 → 214**, то есть
   правка не сработала и добавила 12 px поля. Причина в моём же компоненте: `.ds-card` —
   `display: flex; flex-direction: column`, а **поплавок на флекс-элементе не действует вовсе**.
   🔴 КЛАСС, И ОН МОЙ ЖЕ, ТОЛЬКО С ДРУГОЙ СТОРОНЫ. Я сдал эту правку со словами «проверено
   СВОЙСТВО (`float`), следствие померится на живой секции» — и свойство подтвердилось,
   **будучи недействующим**. `computed` честен к CSS и молчит о пикселях; гейт «`float` стал
   `right`» был бы ЗЕЛЁНЫМ при нулевом следствии, а именно такой гейт напрашивался.
   ✅ Канон делает ровно то, что предложил `c-tpl` вариантом (а), и делает это ПОРОГОМ:
       @media (min-width: 1100px) { .pcard { display: block }
                                    .pcard > .ic { position: static; float: right; margin: 0 0 10px 14px } }
   То есть обтекание включается только там, где карточка достаточно широка; ниже 1100 канон
   держит значок над заголовком. Повторяю канон, включая порог — он свойство САМОЙ карточки
   (ширина, при которой обтекание осмысленно), а не композиции страницы.
   ⚠ И вторая половина, без которой block сломал бы ритм: у `.ds-card` вертикальные промежутки
   даёт `gap` флекса, в block-потоке его нет вовсе. Возвращаю тем же значением через поля. */
/* 🔴 ПОРОГ 1100 СНЯТ 16.08 — И ПРИЧИНА НЕ ТА, ЧТО Я ДУМАЛ, КОГДА ЕГО СТАВИЛ.
   Канонное `@media (min-width:1100px){ .pcard{display:block} .pcard>.ic{float:right} }` я прочёл
   как «обтекание включается только на широком» и завернул в порог ОБЕ половины роли.
   Замер `#srv` @390 (16.08) говорит иначе: **у канона значок обтекает и на 390** — `float: right`,
   43×43, — а `display: block` там просто УМОЛЧАНИЕ `div`, а не эффект медиазапроса.
   ⇒ канонный порог управлял тем, что и так block; обтекание у канона БЕЗУСЛОВНО.
   Цена связки на 390: значок 58 стоял В ПОТОКЕ и добавлял 66 каждой плитке — 157 против 89.
   ⚠ Урок точнее вчерашнего (`--icon-lead`, порог 900, которого у канона не было вовсе):
   **порог у канона БЫЛ, но означал не то, что я прочёл.** «Скопировал из канона» — не
   оправдание: копировать надо МЕХАНИЗМ, а он проверяется замером по обе стороны порога,
   а не чтением медиазапроса. */
.ds-card--icon-inline { display: block; }
/* 🔴 ЗДЕСЬ БЫЛ ЛИТЕРАЛ `--ds-space-3`, И ОН ОБХОДИЛ РОЛЕВОЙ ЗАЗОР (нашёл `c-tpl` опытом,
   15.08). В block-потоке промежутки даёт это правило, а не `gap` флекса — и оно брало 12
   мимо `--ds-card-gap` (у промо-плитки 8). Опыт подменой одного этого правила: 142 → 134.
   ⚠ И самое неприятное: `getComputedStyle` карточки при этом честно показывал `row-gap: 8px` —
   **значение доехало, носитель есть, связь есть, но НЕ С ТЕМ ПРАВИЛОМ**. Зазор в двух местах
   обязан браться из одного токена, иначе роль работает ровно до смены потока. */
.ds-card--icon-inline > * + * { margin-top: var(--ds-card-gap, var(--ds-space-3)); }
/* 🔴 ПОСЛЕ ОБТЕКАЕМОГО ЗНАЧКА ЗАЗОРА НЕТ — у канона его там нет вовсе (замер `#srv .pcard`):
   значок `float: right`, заголовок идёт СРАЗУ, а единственный промежуток даёт `margin-bottom`
   заголовка. Правило `> * + *` считает заголовок «вторым ребёнком» и добавляло лишние 8:
   у нас выходило два промежутка (перед заголовком и перед текстом) там, где у канона один.
   Разбор высоты: 30 полей + 8 + 32 + 8 + 52 = 130 против канонных 30 + 32 + 8 + 52 = 122. */
.ds-card--icon-inline > .ds-card__icon + * { margin-top: 0; }
.ds-card--icon-inline .ds-card__icon {
  float: right;
  /* Поля обтекаемого значка — токеном: у канона они СТУПЕНЧАТЫЕ вместе с его размером
     (замер 16.08, восемь ширин: `0 0 8px 12px` ниже 1100 и `0 0 10px 14px` от 1100).
     Умолчание = прежний рендер. */
  margin: var(--ds-card-icon-inline-margin, 0 0 var(--ds-space-3) var(--ds-space-4));
}

/* Размер глифа — РОЛЬ (17.08, разбор `wcard`). Общий 26 был литералом, и каждая роль с другим
   глифом требовала своего правила: так уже сделана лесенка (`--ds-card-ladder-glyph`, 22).
   Умолчание 26 = прежний рендер везде.
   ⚠ Правило лесенки НЕ сливаю с этим: оно специфичнее и выиграло бы в любом случае, а слияние
   двух работающих механизмов — отдельная правка с отдельным замером, а не попутная уборка. */
.ds-card__icon svg {
  width: var(--ds-card-glyph-size, 26px);
  height: var(--ds-card-glyph-size, 26px);
}

/* ── ОТСТУПЫ ПОД ЗНАЧКОМ И ПОД МЕТКОЙ В ЛЕСЕНКЕ — РОЛИ (17.08, разложение `#ladder`) ────────
 * Канон держит расстояния внутри плитки полями КАЖДОГО ребёнка, у нас их давал один общий
 * `gap`. Роли нужны обе, но объявлены они здесь УЗКО — только под `.ds-card--ladder`.
 * 🔴 ПОЧЕМУ УЗКО, А НЕ НА `.ds-card__icon` И `.ds-badge` ЦЕЛИКОМ: у этих узлов `margin`
 * РАНЬШЕ НЕ ОБЪЯВЛЯЛСЯ ВОВСЕ. Появление свойства с «нейтральным» фолбэком — не умолчание,
 * а присвоение: оно убивает чужой каскад снаружи. Ровно так 15.08 у izibuh пропали капс и
 * разрядка метки (запись в `components/badge/badge.css`). Модификатор `ladder` в бренд-пакетах
 * есть ТОЛЬКО у forbion — проверено `grep` по `brands/`, — поэтому область правки замкнута.
 * ⚠ С 1100 значок лесенки уходит из потока (`position:absolute`, бренд-пакет), и его поле
 * там перестаёт работать САМО — это свойство потока, а не наше умолчание. */
.ds-card--ladder .ds-card__icon { margin-bottom: var(--ds-card-icon-mar-bottom, 0); }
.ds-card--ladder .ds-badge { margin-bottom: var(--ds-badge-mar-bottom, 0); }

/* 🔴 ЗНАЧОК СЛЕВА, ТЕКСТ РЯДОМ — ВТОРАЯ РОЛЬ ЗНАЧКА, А НЕ ТА ЖЕ САМАЯ.
   Замер канона `#inc .iti` (15.08, 1366): плитка `display:flex; row; gap 12; pad 16px 18px`,
   значок 46 ПЕРВЫМ ребёнком слева, текст справа одним блоком. У `#srv .pcard` канон ставит
   значок СПРАВА обтеканием — это `--icon-inline` выше. **Одно слово «значок в строку» на две
   разные роли: сторона у них разная, и механизм тоже разный** (обтекание против колонки).
   Развожу именами, а не параметром: `inline` = справа, `lead` = слева и ведёт.

   ⚠ ПОЧЕМУ СЕТКА, А НЕ ФЛЕКС: у канона под значком ОДИН текстовый узел (заголовок и описание
   внутри `<span>`), у нас их ДВА (`__title` + `__text`). Во флекс-строке они встали бы тремя
   колонками. Сетка «auto 1fr» со значком на два ряда даёт канонную картинку БЕЗ правки разметки —
   слот у `c-tpl` остаётся прежним, добавляется только класс.

   🔴 И ГЛАВНОЕ ПОСЛЕДСТВИЕ, КОТОРОЕ НАДО ЗНАТЬ ДО ЗАМЕРА: значок забирает 58 px ширины,
   текст переносится ЧАЩЕ. У канона описание в 169 px идёт в три строки (63), у нас в 231 px —
   в две (41). После этой правки высота плитки упадёт МЕНЬШЕ, чем «минус значок»: часть вернётся
   переносами. Остаток до канона после неё — длина текста, держатель `c-copy`. */
/* 🔴 ПОРОГ 900 СНЯТ 16.08 — ЕГО НЕ БЫЛО В КАНОНЕ, ОН БЫЛ МОЕЙ ДОГАДКОЙ.
   Выпуская роль, я написал «роль живёт с 900 px, ниже — прежняя колонка, намеренно»:
   решил, что на узком значок рядом с текстом будет тесно. **Канон это опровергает —
   замер `#inc .iti` @390: плитка `row`, значок слева, высота 96.** У нас там оставалась
   колонка, и восемь плиток по 154/133 против канонных 96 давали **+377 только на плитках**.
   ⇒ роль безусловна, как у канона. Цена догадки, снятая опытом на живом:
       @390  сетка 1317 → 893 · `#inc` 1747 → 1323 · СТРАНИЦА 15649 → 15224 (−425)
       @1366 не изменилось ничем (роль и так действовала выше порога)
   ⚠ И признак на будущее: **порог, которого нет у канона, — это моё предположение о канонe.**
   Оно выглядит осторожностью, а стоило 425 px на самой дорогой ширине. */
.ds-card--icon-lead {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--ds-card-icon-gap, var(--ds-space-3));
  row-gap: var(--ds-card-lead-row-gap, var(--ds-card-gap, var(--ds-space-3)));
  align-items: start;
}
.ds-card--icon-lead > .ds-card__icon {
  grid-row: 1 / span 2;
  flex: 0 0 auto;
}

/* Нумерованные шаги: обёртка .ds-steps + карточки-модификаторы */
.ds-steps {
  counter-reset: ds-step;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--ds-space-4);
}

/* Фиксированная лента шагов (прототип izibuh «Неделя»: 5 в ряд).
   Число колонок — конфиг через --ds-steps-columns; minmax(0,1fr) даёт равные
   сжимаемые колонки без переполнения. На узких экранах — стек. */
.ds-steps--lane {
  grid-template-columns: repeat(var(--ds-steps-columns, 5), minmax(0, 1fr));
}
@media (max-width: 760px) {
  .ds-steps--lane { grid-template-columns: 1fr; }
}

.ds-card--step { counter-increment: ds-step; }

.ds-card--step::before {
  content: counter(ds-step);
  width: 32px;
  height: 32px;
  border-radius: var(--ds-radius-full);
  background: var(--ds-color-primary);
  color: var(--ds-color-on-primary);
  font-weight: var(--ds-fw-bold);
  font-size: var(--ds-fs-sm);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Вариант «Карточка аудита» (ds-card--audit) — слайд карусели героя izibuh.
   Заведён вариантом ds-card (правило двух использований; отдельный ds-audit —
   при втором применении письмом-ДО). Данные (title/subtitle/items/footer)
   подставляет рендер шаблона из hero.slides[].panel="audit". Новых токенов нет. */
/* Свечение статично по канону :237 (.audit-card{...box-shadow:var(--shadow-pink)}) —
   здесь оно часть самого варианта, а не отклик, поэтому правилом на классе, без флага. */
.ds-card--audit { box-shadow: var(--ds-shadow-brand); }

.ds-card__head {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
}
/* Иконка на брендовом градиенте (белая через currentColor) — в отличие от
   нейтральной .ds-card__icon (raised + primary). */
.ds-card__icon--brand {
  background: var(--ds-gradient-brand);
  color: var(--ds-color-on-primary);
}
.ds-card__sub {
  display: block;
  font-size: var(--ds-fs-xs);
  color: var(--ds-text-muted);
}
/* Чек-лист «что получите» — маркеры-галочки токеном primary (маска, не <text>). */
.ds-card__checks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--ds-space-2);
}
.ds-card__checks li {
  display: flex;
  gap: var(--ds-space-3);
  align-items: flex-start;
  font-size: var(--ds-fs-sm);
  color: var(--ds-text-muted);
}
.ds-card__checks li::before {
  content: "";
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  background: var(--ds-color-primary);
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>') center/contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>') center/contain no-repeat;
}
/* ── ПУНКТ СО СВОЕЙ МЕТКОЙ (c-design, 14.08, запрос c-tpl) ───────────────────────────────
 * ПОВОД: `audit.items[]` должны нести значок, а ядро уже рисует галочку КАЖДОМУ пункту
 * (`li::before`, CSS-маска). Значок рядом стал бы ВТОРОЙ МЕТКОЙ одной строки, а гасить
 * `::before` из шаблона — правка веса селектора, то есть та самая гонка, которую мы запретили.
 *
 * 🔴 ЭТО ТРЕТИЙ СЛУЧАЙ ОДНОГО КЛАССА, и потому правило ОБЩЕЕ, а не частное (довод c-tpl):
 * орбы — «секция предусмотрела декоративных детей и исключила только своего»; паттерн —
 * `data-ds-decor`; теперь галочка. Формулировка на класс:
 *   ЯДРО РИСУЕТ УМОЛЧАНИЕ ТОЛЬКО ТАМ, ГДЕ ПОТРЕБИТЕЛЬ НЕ ПРИНЁС СВОЁ.
 * Признак — АТРИБУТ (`data-ds-marker`), как и `data-ds-decor`: ядро не должно знать ни одного
 * класса потребителя, а потребителей у нас уже трое.
 *
 * ⚠ БАЗОВОЕ ПРАВИЛО НЕ ТРОГАЮ. Можно было написать `li:not([data-ds-marker])::before`, но это
 * подняло бы вес САМОГО базового правила (0,1,2 → 0,2,2) и молча перебило бы всё, что его
 * сегодня переопределяет по порядку. Вместо этого — отдельное правило на помеченный пункт:
 * база остаётся как была, а помеченный пункт гасит псевдоэлемент своим весом и порядком. */
.ds-card__checks li[data-ds-marker]::before {
  content: none;
}

/* Акцентный футер (цена/срок) */
.ds-card__footer--accent {
  color: var(--ds-color-accent);
  font-weight: var(--ds-fw-bold);
  font-size: var(--ds-fs-sm);
  border-top: 1px solid var(--ds-border-default);
}

/* 🔴 КАРТОЧКА ХАБА — СВОЙ РАДИУС (15.08). Канон даёт хабу 24, а не общие 26: `.hcard` ×5,
   две крупные 537×217 и три 353×232 — все 24. Замер `c-ops` на живом («узлов с 24 нет ни
   одного») был верен и вскрыл, что сведение канонного разброса к ОДНОМУ токену теряет РОЛЬ.
   ⚠ Фолбэк на общий радиус: бренды без своего значения ведут себя как раньше.
   ⚠ ТОЧКА ПОТРЕБЛЕНИЯ ЕСТЬ, НО МОДИФИКАТОР ДОЛЖЕН КТО-ТО ПОСТАВИТЬ: пока `c-tpl`/`c-web-1`
   не выдадут карточкам хаба класс `ds-card--hub`, значение не доедет — это НАЗВАННЫЙ долг,
   а не «сделано». Класс «работа объявлена и не соединена» назвала c-design-2 на этом же токене. */
.ds-card--hub { border-radius: var(--ds-radius-card-hub, var(--ds-radius-card)); }

/* Роли карточек объявляются БРЕНДОМ, а не здесь: кегли 21/17/18/16/13.5 — числа канона
   forbion, и в ядре они стали бы обязательными для izibuh и tezum. Ядро даёт только ТОЧКУ
   ВХОДА (три токена выше); значения — `brands/<бренд>/theme.css`. Область правки = область решения. */

/* 🔴 БЛОК ВЫДЕЛЕНИЯ — та же роль канона, третий носитель: полоса «Бесплатно при переезде»
   (1075×49, r=28) и выделенная строка тарифа. Подложка #EDF6E3, чернила обычные.
   Класс ставит потребитель. */
/* 🔴 ПОДЛОЖКА ВЫДЕЛЕННОЙ КАРТОЧКИ — ТОКЕН, потому что у канона она НЕ плоская (партия 7, стр. 93).
   Замер `.hcard.hi`: `radial-gradient(120% 90% at 25% 0%, rgba(255,255,255,.75), transparent 60%)`
   поверх `linear-gradient(165deg, --soft, --rz)` — то есть блик сверху слева плюс переход от
   поверхности выделения к поверхности контрола. Наш плоский `--ds-surface-highlight` даёт тот же
   ТОН и теряет объём; в тёмной разница заметнее, там перепад между `#2B331F` и `#27271F` мал.
   Умолчание = прежний рендер (плоская заливка): izibuh и tezum не двигаются. */
.ds-card--highlight {
  background: var(--ds-card-highlight-bg, var(--ds-surface-highlight));
  color: var(--ds-text-primary);
}

/* 🔴 ВЫДЕЛЕНИЕ НОСИТЕЛЯ — ЭТО ИНВЕРСИЯ, А НЕ «ДОБАВИТЬ ЗЕЛЁНОГО» (15.08, находка c-tpl,
   перепроверена мной на каноне). Замер расставил всё по местам:
     строка тарифа `tr.hi` : ЯЧЕЙКИ #EDF6E3, чипы внутри — БЕЛЫЕ
     обычные строки       : ячейки прозрачны, чипы — #EDF6E3
     карточка `.lad.hi`   : фон БЕЛЫЙ rgba(255,255,255,.72), бейдж без подложки
     обычные карточки     : фон прозрачен, бейдж — серый #ECF2F2
   ⇒ выделенный носитель и его внутренние метки МЕНЯЮТСЯ МЕСТАМИ со своим соседом.
   `--highlight` (зелёная подложка) остаётся верным для колонки сравнения, полосы и ЧИПОВ
   В ОБЫЧНОМ СОСТОЯНИИ; для «этот выбран» нужен ПОДЪЁМ К БЕЛОМУ — отдельная роль.
   ⚠ Красить `.ds-card--highlight` карточку форматов зелёным было бы ПРОТИВОПОЛОЖНО канону. */
.ds-card--picked { background: var(--ds-surface-picked, var(--ds-surface-section)); }

/* 🔴 РОЛЬ «ПЛИТА» — ОТДЕЛЬНАЯ ПОВЕРХНОСТЬ, А НЕ ОТТЕНОК ИЗ ШКАЛЫ (запрос `c-tpl` по секции
   заявки, 15.08). Он прислал меню из трёх плоских ролей и честно не выбрал: `raised` дал бы
   СЕРУЮ плиту на белом (инверсия канона), `page` — слился бы с секцией, а тенью не отделить,
   у бренда `--ds-shadow-card: none`.
   ⇒ Ни одна не подходит, потому что **у канона плита вообще не плоская**: замер `#form`
   показывает `radial-gradient(130% 90% at 18% 0%, …)` поверх `linear-gradient(165deg, …)`
   плюс многослойная тень — та же поверхность, что несут карточки тарифов и промо-плитки.
   Это РОЛЬ, и у неё есть имя в каноне (`--silk`), поэтому она заводится ролью и здесь.
   Умолчание = прежний плоский рендер: izibuh и tezum не двигаются. */
.ds-card--plate {
  /* Поля плиты — своя роль, а не ступень шкалы и не контейнерный отступ (запрос `c-tpl`, 15.08).
     Замер канона `.gifts`: `padding: 26px 28px` — по вертикали и горизонтали РАЗНЫЕ, одним
     числом не выражаются. ⚠ 26 совпадает с `--ds-container-pad`, и взять его было бы соблазном:
     это связало бы две разные роли одним числом, и следующая правка гаттера тихо поехала бы
     в плиту. Умолчание — прежний путь через `--ds-card-pad`. */
  padding: var(--ds-plate-pad, var(--ds-card-pad, var(--ds-space-6)));
  background: var(--ds-surface-silk, var(--ds-surface-section));
  box-shadow: var(--ds-shadow-silk, var(--ds-shadow-card, none));
  border-radius: var(--ds-radius-plate, var(--ds-radius-xl));
}
/* 🔴 15.08, вторая поправка от c-tpl: У ДВУХ НОСИТЕЛЕЙ ИНВЕРСИЯ РАЗНАЯ, и одним токеном
   их крыть нельзя. Замер канона:
     карточка `.lad.hi`  → метка ПРОЗРАЧНАЯ (карточка светлеет, метка исчезает)
     строка   `tr.hi`    → чипы БЕЛЫЕ (ячейка зеленеет, чип выбеливается)
   Белое пятно на белой 72 % карточке — НЕ то же, что «карточка видна сквозь метку».
   ⚠ Родня случаю `.ds-compare__tag`, но лечится иначе: там правило не знало НОВОГО
   модификатора, здесь оно знает носитель — а канон отвечает по носителю ПО-РАЗНОМУ. */
.ds-card--picked .ds-badge--highlight,
.ds-card--picked .ds-badge { background: var(--ds-surface-picked-badge, transparent); }

/* 🔴 ВЫДЕЛЕННАЯ СТРОКА ТАБЛИЦЫ — ВТОРАЯ ФОРМА ТОЙ ЖЕ ИНВЕРСИИ (15.08, запрос c-tpl).
   У карточки инверсия идёт «фон → белый, метка → без подложки» (`--picked` выше).
   У строки таблицы она ЗЕРКАЛЬНА: замер канона `tr.hi` — ЯЧЕЙКИ #EDF6E3, чипы внутри БЕЛЫЕ,
   тогда как в обычных строках ячейки прозрачны, а чипы зелёные.
   ⚠ Фон вешается на ЯЧЕЙКИ, а не на `<tr>`: у канона сам `tr` прозрачен, и это не мелочь —
   `background` на `tr` в таблице с `border-collapse` рисуется иначе и обрезается границами.
   Класс ставит потребитель на строку; носитель таблицы — шаблон, поэтому здесь только форма. */
.ds-row--picked > th,
.ds-row--picked > td { background: var(--ds-surface-highlight); }
.ds-row--picked .ds-badge,
.ds-row--picked .ds-badge--highlight { background: var(--ds-surface-picked-chip, #fff); }

/* Размер значка по ролям — числа канона; ставит потребитель тем же модификатором,
   что и типографику карточки (`--promo` / `--inc` / `--hub` / `--ladder`). */
.ds-card--promo  { --ds-card-icon-size: var(--ds-card-promo-icon-size, 58px); }
.ds-card--inc    { --ds-card-icon-size: 46px; }
.ds-card--hub    { --ds-card-icon-size: 52px; }
/* 🔴 104 БЫЛО ПОМЕЧЕНО «ЧИСЛОМ КАНОНА», А КАНОН ДАЁТ 46 (замер `.lad .lic` @390, 16.08:
   плита 45×46, глиф 22). Расхождение не описка — это другой замысел, и он прожил в ядре,
   где числа канона forbion двигают чужие бренды (ровно то, о чём предупреждает комментарий
   абзацем ниже — про свои же роли типографики я это правило вывел, а здесь не применил).
   ⇒ умолчание оставляю ПРЕЖНИМ (104), канонное значение уходит в бренд. */
.ds-card--ladder { --ds-card-icon-size: var(--ds-card-ladder-icon-size, 104px); }

/* Глиф внутри плиты у ступени канона 22 против общих 26; область — только эта роль,
   у остальных ролей глиф не мерил. */
.ds-card--ladder .ds-card__icon svg {
  width: var(--ds-card-ladder-glyph, 26px);
  height: var(--ds-card-ladder-glyph, 26px);
}

/* ⚠ ЗНАЧЕНИЯ ЭТИХ РОЛЕЙ ЖИВУТ В БРЕНД-ПАКЕТЕ, А НЕ ЗДЕСЬ (15.08). Сперва я положил их сюда,
   по прецеденту размеров значка выше — и замер сразу показал цену: у izibuh промо-плитка
   поехала 157 → и поля стали forbion-овскими. Числа канона forbion в ядре двигают чужие бренды,
   а «пауза по чужим проектам снимает наряды, но не инварианты».
   ⇒ `--ds-card-pad`, `--ds-card-gap`, `--ds-card-text-lh` объявлены в `brands/forbion/theme.css`
   на тех же ролевых классах. Прецедент со значками остаётся как есть — трогать его отдельным
   предметом, а не попутно. */


/* 🔴 ИМЯ ДС БЕЗ ПРАВИЛА — ЗНАЧИТ РЕШАЕТ БРАУЗЕР, А НЕ МЫ (нашёл гейт `c-tpl`, 15.08).
   `.ds-card__check-icon` печатался в разметке, а селектора у него не было НИГДЕ: размер
   24×24 приходит атрибутами `width`/`height` из манифеста иконок, цвет — наследованием.
   Само по себе это не дефект на экране, но **имя обещает нашу зону**, и следующий, кто
   поставит сюда правило, будет спорить с атрибутом, не зная об этом.
   ⇒ Забираю имя и записываю контракт как он есть. `color: inherit` — то же, что и было
   (иконка и раньше наследовала чернила), поэтому рендер не двигается ни на пиксель. */
.ds-card__check-icon {
  /* размер задаёт манифест атрибутами, а не ДС; цвет — окружающие чернила */
  color: inherit;
}

/* ═══ components/coupon/coupon.css ═══ */
/* f7c_design :: Купон-акция (ds-coupon). Требует tokens/tokens.css.
   Визуальный компонент слайда карусели героя (механика карусели — зона шаблона
   .tpl-carousel). Самодостаточен: к классам карусели агностичен.
   Только существующие токены — новых не вводит (валидатор не трогаем).
   Билетные «дырки» по умолчанию цвета страницы (--ds-surface-page); если купон
   лежит на не-page фоне — тему поправит сайт/шаблон (тогда письмо-ДО за токеном). */
.ds-coupon {
  position: relative;
  /* box-sizing ТОЧЕЧНО, а не глобальным ресетом ядра. Находка c-tpl (гейт исполнения
     playwright@360): без него max-width:360 + padding 2×34 + border 2×2 давали РЕАЛЬНЫЕ 428px —
     купон шире собственного max-width, горизонтальный скролл на 360.
     Замер c-tpl подтвердил фикс: 428→360 @1500 и 396→328 @360.

     ПОЧЕМУ НЕ ГЛОБАЛЬНЫЙ `*{box-sizing:border-box}` (таблица c-tpl ДО/ПОСЛЕ, 4 прогона):
     глобальный сдвинул бы .ds-hero__inner 1132→1100 и вместе с ним канон-гаттер стрелок
     izibuh (+16/−16 внутрь) — а это ПРИНЯТЫЙ владельцем первый экран. Причина сдвига важна:
     сегодняшние 1132 = объявленные 1100 + 2×16 паддинга, вылезшего наружу из-за content-box.

     🔴 ГЛАВНОЕ, чтобы не прочитали наоборот: border-box НЕ ОТВЕРГНУТ, он ОТЛОЖЕН.
     Держатель канона (c-design-2) свёл вопрос ИСХОДНИКОМ: canon concept-v2.2.0:77 несёт
     `*{box-sizing:border-box}`, а :80 — `.wrap{max-width:1100px;padding:0 24px}`. При border-box
     паддинг внутри → внешняя ширина канона РОВНО 1100. Значит:
       • токен --ds-container-max:1100 = КАНОН-ИСТИНА (изначально верен);
       • рендер 1132 = ОТКЛОНЕНИЕ +32px, следствие отсутствия border-box в ЯДРЕ.
     Т.е. глобальный border-box — это ВОЗВРАТ К КАНОНУ, а не риск для вёрстки.
     Отложен осознанно (решение c-design-2 как визирующего): владелец принял ЭТОТ рендер вчера,
     сужение колонки на 32px и сдвиг гаттера на 16px видимы и требуют ре-визы — делать это сразу
     после принятой победы нельзя. Фикс — отдельным окном (вместе со следующей layout-правкой либо
     ПЕРЕД следующим показом), с ре-визой c-design-2 ДО показа. Отклонение он фиксирует у себя,
     чтобы будущий ресет был ожидаемым, а не сюрпризом.

     РЕАЛЬНЫЙ overflow оказался ровно один (проверил: max-width как СВОЙСТВО + padding в том же
     правиле — только здесь; у прочих max-width сидит в @media, это медиазапрос, не габарит).
     Поэтому лечим точечно, а КЛАСС закрываем гейтом 8i, не глобальным ресетом. */
  box-sizing: border-box;
  /* 🔴 360 — ЖЁСТКОЕ ЧИСЛО ЯДРА ТАМ, ГДЕ КАНОН ДЕРЖИТ ТЕКУЧЕСТЬ (партия 6, строка 87).
     Замер `.price-tag` канона по четырём ширинам: 236 @1366 · 207 @1024 · 187 @941 · 186 @939 —
     карточка сжимается вместе с колонкой. У нас 360 на всех четырёх, и на 1024 она съедает
     правую колонку героя. **Четыре партии этого не поймали**, потому что на одной ширине
     360 и 236 обе читаются как «просто карточка»: расхождение видно только по РЯДУ ширин.
     Умолчание = прежний рендер (360): у izibuh купон принят владельцем в этом габарите.
     Бренду, чей канон текуч, достаточно поставить `100%` — но тогда ширину обязана задать
     КОЛОНКА раскладки: ядро даёт компонент, контентную ширину даёт layout (тот же контракт,
     что у гаттера насечек выше). Половина ядра и половина `c-tpl`, порознь не работает. */
  max-width: var(--ds-coupon-max, 360px);
  /* 🔴 МИНИМУМ — ВТОРАЯ ПОЛОВИНА ТОЙ ЖЕ ПРАВКИ, И БЕЗ НЕЁ ПЕРВАЯ НЕ РАБОТАЕТ.
     У элемента сетки `min-width` по умолчанию `auto`, то есть **min-content**, и он ПЕРЕБИВАЕТ
     любой `max-width`: колонка не может стать уже содержимого. Замер `c-tpl` на 1024 показал
     отношение колонок **1.53 вместо 1.15** — первую колонку floor-ил именно этот минимум,
     а не 360. Канон здесь даёт `min-width: 0` (computed), то есть карточке разрешено сжиматься.
     Умолчание `auto` = прежний рендер: izibuh не двигается. */
  min-width: var(--ds-coupon-min, auto);
  width: 100%;
  text-align: left;
  /* Поля купона — токен: у канона `.price-tag` 22/26, у нас 32 (`--ds-space-8`). Умолчание
     прежнее; разложение высоты см. в журнале `v0.219.0` — поля дают 20 из 207. */
  padding: var(--ds-coupon-pad, var(--ds-space-8));
  border-radius: var(--ds-radius-xl);
  box-shadow: var(--ds-coupon-shadow, var(--ds-shadow-brand));
  /* градиентная рамка через двойной фон (clip: padding-box + border-box)
     🔴 15.08, партия 5: КОЛЬЦО — НАША ВЫДУМКА, а не канон. Носитель канона для этого купона —
     `.price-tag` (карточка цены героя): `border: 0`, `background: var(--glass)`,
     `box-shadow: var(--glow)`. Вывод «рамки нет» верен в ОБЕИХ темах — поймал в
     тёмной только потому, что там яркий зелёный контур на тёмном кричит, а на светлой читался
     как акцент. Тот же класс, что стрелки карусели в этой же партии: тёмная тема не добавила
     расхождения, она ПОДСВЕТИЛА старое.
     Ширина вынесена в токен, УМОЛЧАНИЕ = прежний рендер (2px): у izibuh кольцо принято и
     остаётся. Ноль ставит только бренд, у которого канон рамки не знает — иначе это было бы
     присвоение чужому бренду, а не умолчание (запись «фолбэк в новом правиле»). */
  /* 🔴 15.08, НАЙДЕНО АРИФМЕТИКОЙ: слой `border-box` ПРОСВЕЧИВАЕТ, если фон полупрозрачен.
     У forbion `--ds-coupon-bg` = `rgba(255,255,255,.72)` (канонное стекло), а под ним лежал
     зелёный `--ds-gradient-brand` — и карточка читалась ЗЕЛЁНОЙ:
         .72 белого поверх #70B62C = (215, 235, 196) · замер живого = (215, 234, 196)
     совпадение до единицы, то есть предмет был не в декоре под картой и не в композиции.
     ⚠ Мы искали его ДВУМЯ методами и оба промахнулись: hit-test не видит `pointer-events:none`,
     обход узлов не видит псевдоэлементы — а виноват оказался СЛОЙ САМОЙ КАРТОЧКИ.
     Кольцо вынесено в токен: при нулевой рамке бренд ставит `none`, и стекло ложится на страницу,
     как у канона. Умолчание прежнее — izibuh с кольцом 2px не двигается. */
  border: var(--ds-coupon-border-width, 2px) solid transparent;
  background:
    linear-gradient(var(--ds-coupon-bg, var(--ds-surface-raised)),
                    var(--ds-coupon-bg, var(--ds-surface-raised))) padding-box,
    var(--ds-coupon-ring, var(--ds-gradient-brand)) border-box;
  /* стекло — только если бренд его попросил; умолчание `none` = прежний рендер */
  -webkit-backdrop-filter: var(--ds-coupon-blur, none);
  backdrop-filter: var(--ds-coupon-blur, none);
}

/* «дырки» билета по краям */
.ds-coupon__hole {
  position: absolute;
  top: 50%;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--ds-surface-page);
  transform: translateY(-50%);
}
/* Вылет насечки НИКОГДА не больше гаттера контейнера — переполнение исключено ПО ПОСТРОЕНИЮ.
   Канон (concept-v2.2.0:236) держит -15px на ВСЕХ ширинах без единого медиазапроса, и это
   работает не случайно: :612/:636 дают .wrap padding 16-18px, вылет уходит В ГАТТЕР, а не за
   вьюпорт. Разбор c-design-2 — канон решил вопрос ПАДДИНГОМ, а не отказом от декора.
   В ядре механизм был НАПОЛОВИНУ: --ds-container-pad = 16px на десктопе и на телефоне (<=479),
   но 12px на планшете (480-767) — там вылет 15px переполнял на 3px даже канон-верно
   размещённый купон. min() снимает это без медиазапросов и само поедет за токеном, если
   гаттер когда-нибудь поменяют. Выемка сохранена на всех ширинах — канонный вид цел.
   🔴 ПРЕДЕЛ ЭТОГО МЕХАНИЗМА (уточнение c-tpl, важнее самого min()): min() читает ТОКЕН
   --ds-container-pad, а НЕ фактическое поле родителя. Если купон стоит голым, токен всё равно
   даст 16px, min(15,16)=15 — и вылет упрётся во вьюпорт. Т.е. min() ограничивает вылет
   КАНОННЫМ гаттером, но не проверяет, что гаттер физически есть. Половина ядра и половина
   layout: порознь они не работают. Это не изъян, а граница зоны — но знать её обязан всякий,
   кто решит, что «ядро само защитилось».
   🔴 КОНТРАКТ: купон обязан лежать в контейнере ядра (.ds-section__inner / .ds-container).
   Вне контейнера гаттера нет, и вылет переполнит вьюпорт — как в замере c-tpl (373 при 360).
   Это ровно тот же класс контракта, что у гаттера стрелок карусели: ядро даёт компонент,
   контентную ширину даёт layout. Поэтому эталон coupon.html теперь показывает купон В контейнере
   (раньше показывал голым и УЧИЛ неверному употреблению — корень того замера). */
.ds-coupon__hole--l { left: calc(-1 * min(15px, var(--ds-container-pad))); }
.ds-coupon__hole--r { right: calc(-1 * min(15px, var(--ds-container-pad))); }

/* 🔴 ОТМЕНЁННЫЙ ФИКС (v0.43.0, жил один тег) — оставляю запись, чтобы не повторили.
   Уводил насечки заподлицо @media(max-width:559). Снят разбором c-design-2 ИСХОДНИКОМ:
   полный круг у края читается как ДРУГОЙ объект (дырка-перфоратор), а не как надрыв билета —
   изменённый смысл хуже отсутствующего декора. Канон насечку на 360 держит, значит и мы держим.
   Мой промах в методе: я чинил СЛЕДСТВИЕ (вылет) вместо ПРИЧИНЫ (нехватка гаттера), потому что
   не спросил канон до правки. Урок тот же, что с R11: причину даёт источник, не гипотеза.

   Старый комментарий фикса — ниже, как история решения:
   Узкие экраны: насечки НЕ выносим за край — вынос сам даёт горизонтальный скролл.
   Находка c-tpl на перемере v0.42.0: как только box-sizing починил коробку (428→328 @360),
   обнажился следующий слой — docScrollWidth 373 при vw 360 (+13px) от right:-15px.
   Класс «закрыли слой — обнажился следующий»: раньше вылет насечки маскировал больший
   overflow самой коробки.

   ПОЧЕМУ ИМЕННО ТАК (проверил ВЕСЬ класс, а не один симптом — правил с выносом вправо
   в ядре пять): орбы прикрыты overflow:hidden у предка, .ds-media-stack__float — скрытием
   <559px, стрелки карусели живут только @min-width:1200 (на узких right:6px внутрь),
   .ds-divider вертикален. Незакрытым был ровно один — этот. Так что лечим точечно,
   а КЛАСС держит гейт 8j, чтобы следующий такой вынос краснел на входе.

   Почему НЕ display:none (хотя прецедент __float именно такой): насечки — суть билетного
   вида, а на мобилке купон крупный и это его главный опознавательный знак. Уводим заподлицо:
   круг цвета страницы у самого края читается как отверстие билета. Декор сохранён, вылет ноль.
   Почему НЕ overflow:clip на самом купоне (соблазнительно — чинит без медиазапроса):
   clip обрезал бы насечки и на ДЕСКТОПЕ, а десктопный купон входит в принятый владельцем
   первый экран izibuh. Правка на узких его не трогает ни на пиксель.
   Брейк 559px взят у __float осознанно — тот же класс, тот же порог, чтобы не плодить сетку.
   [конец истории; сам медиазапрос удалён — вылет теперь ограничен гаттером выше] */

.ds-coupon__tag {
  display: inline-block;
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ds-color-on-primary);
  /* ФОН под белым текстом -> строго --ds-gradient-brand, НЕ --ds-gradient-text.
     Текстовый градиент в тёмной теме осветляется (70/30 белым), и белый текст на нём
     стал бы нечитаем. Разделение токенов существует именно ради этой границы:
     brand = поверхность под on-primary, text = глифы на фоне СТРАНИЦЫ. */
  background: var(--ds-gradient-brand);
  border-radius: var(--ds-radius-btn);
  padding: var(--ds-space-1) var(--ds-space-3);
}

.ds-coupon__off {
  /* 🔴 Кегль числа — самое крупное слагаемое разницы (129 из 207): у канона 41.663px и число
     ложится в ДВЕ строки, у нас 48px и ТРИ. Разница не в самом кегле (6 px), а в том, что кегль
     решает число строк — а строка тут стоит 64. Умолчание прежнее. */
  font-size: var(--ds-coupon-off-size, var(--ds-fs-4xl));
  /* 🔴 ВЕС — ТОКЕН, потому что канон даёт 900, а самая тяжёлая ступень нашей шкалы
     (`--ds-fw-black`) — 800. Двигать ШКАЛУ ради одного узла нельзя: она общая на три
     бренда. Умолчание прежнее, канонные 900 живут в бренд-пакете forbion. */
  font-weight: var(--ds-coupon-off-weight, var(--ds-fw-black));
  line-height: 1;
  letter-spacing: -.02em;
  margin: var(--ds-space-3) 0 var(--ds-space-1);
}
/* 🔴 РОЛЬ ЧИСЛА: ЗЕЛЁНОЕ = ВЫГОДА, ЧЕРНИЛА = ОБЯЗАТЕЛЬСТВО (15.08, правило вывел `c-web-1`
   разбором канона, я проверил независимо).
   Замер носителей `--gtxt` в каноне: `.price-tag .gift b` (строка подарка) и `.pcard .pv`
   — зелёные; а `.price-tag .num`, то есть **цена в герое**, правила цвета не имеет вовсе
   и наследует `.price-tag{color:var(--ink)}` — ЧЕРНИЛА.
   ⚠ ЧТО ТАКОЕ `.pcard`, называю точно, потому что дважды за вечер названо неверно — мной
   («карточка тарифа») и смежником («карточки подарков второго слайда»). Замер разметки:
   `.pcard` — УНИВЕРСАЛЬНАЯ плитка, встречается минимум в трёх местах (промо-сетка подарков,
   блок «свой сервер», карточки спиц, генерируемые скриптом). Зелень сидит не на плитке,
   а на её узле `.pv`, и все три его значения — **15 дней · F7 · −10 %**, то есть срок пробы,
   пакет и скидка. Ни одно не цена. А там, где в `.pcard` цена ЕСТЬ («От 22 000 ₽/мес» в блоке
   сервера), она лежит в `.pt` и зелёной НЕ красится.
   ⇒ Правило подтверждается на носителе, который обе стороны описали неверно: дело не в том,
   ЧТО за карточка, а в том, КАКУЮ РОЛЬ несёт число внутри неё.
   ⇒ Расхождение было не в оттенке, а в РОЛИ: наша градиентная зелень красила обязательство
   («сколько платить») тем же цветом, каким канон помечает выгоду («что дарим»).
   ⚠ Опасение, из-за которого я держал строку открытой («починив первый слайд, сломаю второй»),
   ОКАЗАЛОСЬ ЛОЖНЫМ, и это стоит записи: у слайдов **разные классы и разные правила**, общего
   носителя нет. Конфликт я вывел из того, что у НАС один компонент на обе роли, и приписал
   эту склейку канону. Снял его чужой замер по разметке, а не мой по кадру.
   Умолчание — прежний рендер (градиент): у izibuh купон акционный, там зелёное верно. */
.ds-coupon--price .ds-coupon__off em {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: var(--ds-coupon-number-ink, var(--ds-text-primary));
}

/* число в градиенте (клип по тексту; фолбэк — primary, если clip не поддержан) */
.ds-coupon__off em {
  font-style: normal;
  color: var(--ds-text-link);
  background: var(--ds-text-link);
    background: var(--ds-gradient-text);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* 🔴 ЕДИНИЦА ПРИ ЧИСЛЕ — МЕЛКАЯ И НЕРАЗРЫВНАЯ (разбор остатка 101, 15.08).
   Замер канона: `.price-tag .num small { font-size: 16px; font-weight: 600; letter-spacing: 0;
   white-space: nowrap }` — у канона «от 1 840» идёт крупно, а «₽/место/мес» **мелким внутри
   того же узла**, и всё вместе занимает 69. У нас единица набрана ТЕМ ЖЕ кеглем 41.663,
   поэтому строка переносится: **167 против 69, это +104 из оставшихся 101** (остальное
   уравновешено снятой офертой, −48).
   ⚠ Правило само по себе НЕ СРАБОТАЕТ, пока в разметке нет `<small>`: сейчас единица —
   голый текст. Это строка 25 партии 2 («цена крупная, единица мелкой»), она всё ещё открыта,
   🔴 ДЕРЖАТЕЛЬ ЗДЕСЬ НАЗВАН БЫЛ НЕВЕРНО, И ЭТО СТОИЛО НАРЯДА. В первой редакции стояло
   «держатель разметки — `c-web-1`». Неверно: узел `<div class="ds-coupon__off"><em>…` печатает
   **генератор шаблона**, а `c-web-1` держит только конфиг. Проверяется одной командой —
   `grep` по тому, ЧЕЙ КОД ПЕЧАТАЕТ УЗЕЛ, а не по тому, кто рядом стоит в цепочке.
   Верный держатель — **`c-tpl`**; он же и закрыл: `TEMPLATE v0.173.0` развёл два поля
   (`off` + `unit`), купон 458 → 375 при каноне 357, строка цены 167 → 83.
   ⚠ Класс: **неверно названный держатель гейтом не ловится** — наряд ждёт у неверного адресата
   ровно столько же, но числится отданным. Правило кладу заранее, чтобы ключ не ждал ядра. */
.ds-coupon__off small {
  font-size: var(--ds-coupon-unit-size, 16px);
  font-weight: var(--ds-fw-semibold);
  letter-spacing: 0;
  white-space: nowrap;
}

.ds-coupon__desc {
  color: var(--ds-text-muted);
  /* Второе слагаемое (88 из 207): канон 11.5px, у нас 14px. Умолчание прежнее. */
  font-size: var(--ds-coupon-desc-size, var(--ds-fs-sm));
  margin: 0;
}

.ds-coupon__perf {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: var(--ds-space-6);
  padding-top: var(--ds-space-4);
  border-top: 1px dashed var(--ds-border-default);
  font-size: var(--ds-fs-xs);
  color: var(--ds-text-muted);
  font-weight: var(--ds-fw-semibold);
}

/* ═══ components/review/review.css ═══ */
/* f7c_design :: Отзыв клиента. Требует tokens.css.
   Семантика рейтинга (aria-label «Оценка: N из 5») — зона шаблона; здесь визуал. */
.ds-review {
  background: var(--ds-surface-page);
  border: 1px solid var(--ds-border-default);
  border-radius: var(--ds-radius-card);
  box-shadow: var(--ds-shadow-card);
  padding: var(--ds-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
}

.ds-review__stars {
  color: var(--ds-color-accent);
  font-size: var(--ds-fs-sm);
  letter-spacing: 0.2em;
  line-height: 1;
}

.ds-review__text {
  margin: 0;
  flex: 1;
  font-size: var(--ds-fs-sm);
  color: var(--ds-text-primary);
  line-height: var(--ds-lh-base);
}

.ds-review__who {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
}

.ds-review__avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--ds-radius-full);
  background: var(--ds-color-primary);
  color: var(--ds-color-on-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--ds-fw-bold);
  /* Свечение по канону :436 (.f-ava — крупный аватар автора на градиенте). */
  box-shadow: var(--ds-shadow-brand);
  font-size: var(--ds-fs-sm);
  flex-shrink: 0;
}

.ds-review__author {
  display: block;
  font-size: var(--ds-fs-sm);
  font-weight: var(--ds-fw-semibold);
  color: var(--ds-text-primary);
}

.ds-review__role {
  display: block;
  font-size: var(--ds-fs-xs);
  color: var(--ds-text-muted);
}

/* ═══ components/carousel/carousel.css ═══ */
/* ds-carousel — контейнер карусели (ядро ДС).
   Извлечено из проверенного паттерна hero эталона izibuh (концепт c-design-2,
   письмо-ДО 2026-07-13). Универсальный не-izibuh механик → общий components/.
   Бренд-тема приходит через токены --ds-* (дефолты в tokens.css наследуют ядро,
   поэтому бренд получает свою тему без правок компонента). Данные слайдов —
   из hero.slides[] рендером шаблона (c-tpl); механик (автосмена/пауза/клавиши/
   свайп) — JS шаблона (см. carousel.html BEHAVIOR). CSP: без inline style. */

.ds-carousel { position: relative; display: grid; }

/* слайды наложены в одну грид-ячейку; показывается активный (кросс-фейд) */
.ds-carousel__slide {
  /* 🔴 ОТБИВКИ СЛАЙДА — ВТОРАЯ ПОЛОВИНА ПАРЫ К ОРГАНАМ (16.08, разбор `c-ops`).
     У канона высота первого экрана собрана так: `600 = содержимое 480 + отбивки СЛАЙДА 120`.
     У нас отбивок НЕТ ВОВСЕ, а те же ~62 давала рама карусели, которую органы как раз
     и убирают ⇒ **органы в одиночку увели бы `#hero` с −88 на −150.**
     ⚠ Пара закрывается только вместе: 450 + 120 = 570 против канонных 600, остаток −30 —
     разница СОДЕРЖИМОГО, предмет `c-copy`, не вёрстки.
     📌 И мера тут не своя половина, а СУММА СЕКЦИИ: «мой узел стал как у канона» при пустой
     второй половине — верный замер и ухудшение предмета.
     ⚠ Ступень у канона есть и снята рядом (390·760·900 → 52/96 · 901·1366 → 36/84):
     ставлю ступенью, а не средним. */
  padding: var(--ds-carousel-slide-pad, 0);
  /* 🔴 `relative` — ТОЧКА ОТСЧЁТА ДЛЯ ДЕКОРА СЛАЙДА, а не косметика (16.08).
     У канона `.slide` тоже `relative`, и его `svg.bars` лежит ВНУТРИ слайда, отсчитывая
     `top/right` от него. Без этой строки брендовый `::after` уехал бы к ближайшему
     позиционированному предку — ровно тем промахом, который я уже сделал с полосами
     секции сравнения (посадка x≈1186 вместо канонных 490): значение верное, ТОЧКА
     ОТСЧЁТА другая. Ставлю носитель ДО того, как повешу на него декор. */
  position: relative;
  /* 🔴 ОБРЕЗКА — ПАРА К `relative`, И ЭТО ЗАМЕР, А НЕ ПРЕДОСТОРОЖНОСТЬ. Декор слайда
     вынесен за правый край (`right: -60px` у канона), и без обрезки это ГОРИЗОНТАЛЬНАЯ
     ПРОКРУТКА: замер после первой правки — scrollWidth 420 при clientWidth 360, ровно +60
     на 360 · 390 · 768 · 901 (на 1366 не видно, потому что там наш слайд не во всю ширину).
     ⚠ Обрезаю СЛАЙД, а не карусель: стрелки — дети карусели и с 1200 уезжают на −54,
     обрезка там спрятала бы их. У канона свисающие 60 не видны ни на одной ширине —
     контур намеренно кроится краем, — так что клип ничего канонного не отнимает.
     ⚠ `clip`, а не `hidden`: `hidden` по одной оси делает вторую прокручиваемой. */
  overflow-x: clip;
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.55s ease, visibility 0.55s;
}
.ds-carousel__slide.is-active {
  opacity: 1; visibility: visible; pointer-events: auto;
}

/* индикаторы-точки (они же переключатели экранов) */
/* 🔴 ОРГАНЫ СЛАЙДЕРА: У КАНОНА ОНИ ВНЕ ПОТОКА, У НАС В ПОТОКЕ (16.08).
   Замер обеих сторон на трёх ширинах:
       канон  `.sctrl` — `position: absolute`, `bottom: 15`, высота 14, полей НЕТ
              (родитель `.hero.slider` — `relative`) ⇒ высоты секции НЕ ДОБАВЛЯЕТ
       наше   `.ds-carousel__dots` — `static`, высота 6, `margin-top: 30` ⇒ ДОБАВЛЯЕТ 36
   ⚠ ПРАВКА ПАРНАЯ, И ПОЛОВИНА ЕЁ ХУЖЕ ЦЕЛОГО: наш контекст позиционирования —
   `.tpl-carousel-frame` (`static`, класс `c-tpl`). Поставь я `absolute` в одиночку,
   органы поедут к ближайшему позиционированному предку, то есть неизвестно куда.
   ⇒ значения отданы ТОКЕНАМИ с умолчанием = сегодняшний рендер; включает их `c-tpl`
   тем же ходом, каким сделает рамку `relative` и снимет свой `.tpl-slabel` из потока. */
.ds-carousel__dots {
  display: flex; gap: 10px; justify-content: center;
  position: var(--ds-carousel-dots-position, static);
  bottom: var(--ds-carousel-dots-bottom, auto);
  left: 0; right: 0;
  margin-top: var(--ds-carousel-dots-gap, 30px);
}
.ds-carousel__dot {
  width: 34px; height: 6px; padding: 0;
  border: none; border-radius: var(--ds-radius-full);
  cursor: pointer;
  background: var(--ds-carousel-dot-idle);
  transition: background 0.3s;
}
.ds-carousel__dot.is-active { background: var(--ds-carousel-dot-active); }
.ds-carousel__dot:focus-visible {
  outline: 2px solid var(--ds-color-accent); outline-offset: 3px;
}

/* стрелки prev/next: десктоп — клик, тач — свайп (стрелки скрыты) */
.ds-carousel__arrow {
  position: absolute; top: 44%; transform: translateY(-50%);
  /* 🔴 Размер стрелки — токен (15.08, строка 13 диффа глазами). Канон forbion даёт круг
     60×60 с глифом 24px; у нас 46×46 и мельче глиф — на кадре рядом стрелки читаются
     как «вдвое бледнее». Умолчания прежние, izibuh и tezum не двигаются. */
  width: var(--ds-carousel-arrow-size, 46px);
  height: var(--ds-carousel-arrow-size, 46px);
  font-size: var(--ds-carousel-arrow-glyph, inherit);
  border-radius: var(--ds-radius-full);
  background: var(--ds-carousel-arrow-bg);
  /* 🔴 ШИРИНА РАМКИ — ТОКЕН, А НЕ ЦВЕТ (16.08). У канона рамки НЕТ ВОВСЕ, и опыт показал:
     `border-color: transparent` ширину НЕ убирает — прозрачное ≠ отсутствующее, коробка
     остаётся на 2 px шире. Снимать надо ШИРИНУ. */
  border: var(--ds-carousel-arrow-border-width, 1px) solid var(--ds-carousel-arrow-border);
  box-shadow: var(--ds-carousel-arrow-shadow, var(--ds-shadow-md));
  /* 🔴 ПОКАЗ СТРЕЛОК — РОЛЬ, ПОТОМУ ЧТО ПОРОГ У КАЖДОГО КАНОНА СВОЙ (17.08).
     Ядро прячет их с 640 (правило ниже), канон forbion — с 900: в полосе 641…900 мы
     показываем то, чего у канона нет вовсе. Порог — число канона, значит место ему в
     бренд-пакете; выразить его токеном можно только так — через показ, а не через ширину. */
  display: var(--ds-carousel-arrow-display, grid); place-items: center; cursor: pointer; z-index: 4;
  color: var(--ds-carousel-arrow-ink);
  transition: transform 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.ds-carousel__arrow:hover {
  border-color: var(--ds-color-accent); color: var(--ds-color-accent);
  transform: translateY(-50%) scale(1.07);
}
.ds-carousel__arrow:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--ds-color-accent) 40%, transparent);
  outline-offset: 2px;
}
.ds-carousel__arrow--prev { left: 6px; }

/* 🔴 `.ds-carousel--gutter` ОТМЕНЁН И СЕЛЕКТОРА НЕ ИМЕЕТ — ищущий, ты не ошибся файлом.
   История: в `v0.4x` десктоп-гаттер сделали опт-ином под этим классом; решение позже
   ОТМЕНЕНО, и гейт 8g теперь требует вынос стрелок БЕЗУСЛОВНО (канон izibuh v2.2.0:222).
   Правило живёт ниже, в `@media (min-width: 1200px)`.
   ⚠ Запись об опт-ине осталась в `CHANGELOG.md`, и 15.08 её прочли как ДЕЙСТВУЮЩИЙ контракт —
   шаблон печатал класс, которого в ДС нет. **CHANGELOG — журнал, а не контракт:** он верен
   на свою дату и не знает, что решение отменили. Отмену видно только здесь, поэтому она
   записана здесь. Печатать `--gutter` не нужно: он не делает ничего. */
.ds-carousel__arrow--next { right: 6px; }
/* Десктоп — стрелки в ГАТТЕР (в поля СНАРУЖИ контейнера). Совпадает с каноном izibuh v2.2.0:222
   дословно (@media(min-width:1200px){.hero-arrow.prev{left:-54px}}). Это НЕ хрупкость ядра, а
   КОНТРАКТ: гаттер-вынос требует, чтобы КОНТЕЙНЕР .ds-carousel был КОНТЕНТ-ШИРИНЫ — тогда -54
   садится в видимый гаттер между контентом и краем.
   🔴 Если макет делает .ds-carousel ПОЛНОШИРИННЫМ (izibuh hero без width-правила = 1485px @vw1500),
   -54 уезжает за край вьюпорта (prev x-54, next x1493), невидимы при ВЕРНОМ цвете. Это баг МАКЕТА
   (не сузил контейнер), не ядра. Позицию контейнера задаёт шаблон (c-tpl .tpl-carousel-frame
   сужает до контент-колонки — он это и делает для гаттера), не ядро.
   ⚠ v0.40 я ошибочно сделал -54 opt-in (дефолт внутри) ради «робастности» — но это ОТКЛОНЕНИЕ
   от канона (канон = снаружи). c-design-2 сверил исходник v2.2.0: стрелки снаружи. Откатил к
   каноническому безусловному -54 (v0.41). Робастность здесь не цель — совпадение с каноном цель. */
/* 🔴 15.08: ВЫНОС В ГАТТЕР ТРЕБОВАЛ ГАТТЕРА, НО НЕ ПРОВЕРЯЛ, ЧТО ОН ЕСТЬ.
   Нашёл `c-tpl` на 1250–1251 (горизонтальная прокрутка, `scrollWidth 1276` при 1251).
   Перемерил сам по ряду ширин на живом — **полоса шире названной и включает 1280**:
     1199 → прокрутки нет (порог не включён)   1250 → +25   1251 → +25   1280 → +10   1366 → нет
   Механизм арифметический, а не случайный: гаттер = (vw − 1192)/2, выносу нужно 54,
   значит вынос безопасен только с **vw ≥ 1300**. Порог 1200 включал его на **100 px раньше**,
   чем геометрия его выдерживает. ⚠ И у izibuh то же самое в меньшем масштабе: контейнер 1100,
   гаттер добирает 54 лишь к 1208 — восемь пикселей прокрутки жили там же и никем не мерились.
   ⇒ Лечу не порогом (порог — это снова число, взятое у одного бренда), а **самой величиной
   выноса**: она ограничена фактическим гаттером. `100%` внутри `calc` у абсолютного потомка
   считается от контейнера, поэтому `(100% − 100vw)/2` и есть «минус гаттер».
   `max()` берёт менее отрицательное: есть место — канонные −54, нет — ровно по краю.
   Выше 1300 значение снова ровно −54, канон цел (замер: 1366 → правый край 1333 = 1279 + 54).
   ✅ ГРАНИЦА СО СКРОЛЛБАРОМ ЗАКРЫТА (15.08, `TEMPLATE v0.156.0`): `100vw` включает классическую
   полосу прокрутки, клиентская область — нет, и вынос вылезал за клиентский край ровно на её
   половину. Замер `c-tpl` под Xvfb с классической полосой 15 px: правый край стрелки на 1200
   был **1192.5** при клиентских **1185** — **7.5 = 15/2**, как и следовало из арифметики.
   Лечится не здесь: `html { overflow-x: clip }` на стороне шаблона (`clip`, а не `hidden` —
   `hidden` создал бы контейнер прокрутки, убил липкую шапку и сделал `overflow-y` вычисляемым
   как `auto`). После правки полоса внизу 0 на всех ширинах 1200…1366.
   ⚠ ЧТО ОТСЮДА СЛЕДУЕТ ДЛЯ ЗАМЕРОВ, А НЕ ДЛЯ КОДА: **headless-браузер этот случай не
   воспроизводит в принципе** — у него полоса оверлейная, шириной 0. Все прежние «прокрутки
   нет», снятые headless (включая мои), были фактом О БРАУЗЕРЕ. Мерить выносы и гаттеры —
   только headful (`xvfb-run` + `headless:false`). */
@media (min-width: 1200px) {
  .ds-carousel__arrow--prev { left: max(-54px, calc((100% - 100vw) / 2)); }
  .ds-carousel__arrow--next { right: max(-54px, calc((100% - 100vw) / 2)); }
}
/* тач-экраны — стрелки прячем, переключение свайпом (механик c-tpl) */
@media (max-width: 640px) {
  /* умолчание ядра; бренд может поднять порог, объявив токен раньше по ширине */
  :root { --ds-carousel-arrow-display: none; }
}

/* доступность: не анимируем кросс-фейд при reduce (автопрокрутку глушит JS) */
@media (prefers-reduced-motion: reduce) {
  .ds-carousel__slide { transition: none; }
}

/* ═══ components/faq/faq.css ═══ */
/* f7c_design :: FAQ — сетка карточек-вопросов с номерами. Требует tokens.css.
   Издан по письму c-ops (FAQ один оставался «прозой» шаблона). Без inline-style,
   без opacity:0-ловушек; контрасты — из токенов (номер: primary на raised,
   в open — on-primary на градиенте: обязанность темы по THEMING). */
.ds-faq {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--ds-faq-gap, var(--ds-space-4));
  align-items: start;
}

@media (max-width: 819px) {
  .ds-faq { grid-template-columns: 1fr; }
}

.ds-faq__item {
  position: relative;
  /* 🔴 ПОВЕРХНОСТЬ СТРОКИ — РОЛЬ, А НЕ ОДНА ЗАЛИВКА. Замер канона `#faq` (15.08, светлая):
     строка держится НЕ рамкой, а мягкой плитой — градиент плюс пять слоёв тени (две волосяные
     inset-грани, внутренняя подсветка и два выноса). Рамки у неё нет вовсе.
     ⚠ И ловушка при снятии: `backgroundColor` канонной строки возвращает `rgba(0,0,0,0)` —
     цветной слой пуст, рисует `background-image`. Ещё немного, и я записал бы «у канона строка
     без заливки»: **computed честен к слою, а не к тому, что видно**. */
  background: var(--ds-faq-item-bg, var(--ds-surface-page));
  border: var(--ds-faq-item-border-width, 1.5px) solid
          var(--ds-faq-item-border-color, var(--ds-border-default));
  border-radius: var(--ds-faq-item-radius, var(--ds-radius-card));
  box-shadow: var(--ds-faq-item-shadow, none);
  /* 🔴 ЛЕВОЕ ПОЛЕ 62 — ЭТО РЕЗЕРВ ПОД НОМЕР, А НОМЕРА У forbion НЕТ (16.08).
     Бренд объявил `--ds-faq-num-display: none` (`v0.310.0`), узлов 8, видимых 0 — а место
     под них осталось умолчанием ядра. ⇒ **выключатель и место, которое он освобождает, —
     ОДНА правка с ДВУМЯ именами, и я объявил одно.** Наш же класс «дробление правки создаёт
     сироту», впервые где сирота — ПУСТОЕ МЕСТО.
     ⚠ ПРАВОЕ поле токена не имело вовсе и стояло литералом шкалы (24 против канонных 20):
     половина пары была параметром, половина — константой, и это не различалось на вид.
     Замер канона: у него поля лежат на ВОПРОСЕ и ОТВЕТЕ (20/20), у нас на элементе;
     для ширины содержимого это равнозначно, расходятся ЧИСЛА. */
  padding: 0 var(--ds-faq-item-pad-right, var(--ds-space-6))
           0 var(--ds-faq-item-pad-left, 62px);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.ds-faq__item:hover { border-color: var(--ds-color-primary); }

.ds-faq__item[open] {
  border-color: transparent;
  background:
    linear-gradient(var(--ds-surface-page), var(--ds-surface-page)) padding-box,
    var(--ds-gradient-brand) border-box;
  box-shadow: var(--ds-shadow-card);
}

/* 🔴 НОМЕР ВОПРОСА — ОПЦИЯ, А НЕ ЧАСТЬ КОМПОНЕНТА (16.08, решение integrator-c2).
   У канона номеров НЕТ ВОВСЕ; у нас они приехали вместе с компонентом (`v0.10.0`), и позже
   их ПОЛОЖЕНИЕ сверялось с каноном — то есть предмет проходил через глаз, и «у канона их
   нет» тогда не прозвучало. По нашему различению это «видели и пролежало», а не «не видели».
   ⚠ ПОЧЕМУ ОПЦИЯ, А НЕ СНЯТИЕ: компонент общий на три бренда, и снятие задело бы соседей,
   у которых номера — принятый вид. Умолчание `flex` = прежний рендер; forbion объявляет
   `none` и гасит их у себя. Из трёх исходов это единственный, где никто чужой не двигается. */
.ds-faq__num {
  display: var(--ds-faq-num-display, flex);
  position: absolute;
  left: 16px;
  top: 15px;
  width: 30px;
  height: 30px;
  border-radius: var(--ds-radius-full);
  background: var(--ds-surface-raised);
  color: var(--ds-color-primary);
  align-items: center;
  justify-content: center;
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold);
  font-variant-numeric: tabular-nums;
  transition: background 0.2s ease, color 0.2s ease;
}

.ds-faq__item[open] .ds-faq__num {
  background: var(--ds-gradient-brand);
  color: var(--ds-color-on-primary);
}

.ds-faq__q {
  /* 🔴 ИНТЕРЛИНЬЯЖ ВОПРОСА — ТОКЕН (16.08, разложение `#faq`). Расхождение секции сидело
     РОВНО в переносящихся вопросах: одностроч­ные сходятся с каноном до пикселя (65 = 65),
     а двухстрочные давали 85 против канонных 74. Канон держит `line-height: normal`
     (16px → ≈18.4 на строку), нам приезжает 24 от базовой типографики ⇒ +11 на каждый
     перенос. Девятый узел того же корня.
     ⚠ ОБЛАСТЬ — ТОЛЬКО ВОПРОС, И ЭТО ПРОВЕРЕНО ОПЫТОМ, А НЕ ВЫБРАНО: `normal` всему `.ds-faq`
     ПЕРЕЛЕТАЕТ (блок 860 против канонных 904 @390, 420 против 431 @1366) — значит у канона
     ответы держат СВОЁ число, и снимать умолчание им нельзя.
     Замер после: пункты 74/74/74/65/74/94/74 — тот же НАБОР, что у канона; расходится лишь
     ПОРЯДОК, потому что переносится другой по счёту вопрос (это текст, не наше число). */
  line-height: var(--ds-faq-q-lh, inherit);
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--ds-space-4);
  padding: var(--ds-faq-q-pad, var(--ds-space-4) 0);
  /* 🔴 Кегль вопроса — точка входа для бренда (15.08). Канон forbion: вопрос 17/700,
     ответ 15.5/500; у нас было 14/600 на обоих. Умолчания прежние — чужие бренды целы. */
  font-weight: var(--ds-faq-q-weight, var(--ds-fw-semibold));
  font-size: var(--ds-faq-q-size, var(--ds-fs-sm));
}

.ds-faq__q::-webkit-details-marker { display: none; }

/* 🔴 ЗНАК РАСКРЫТИЯ — РАЗМЕР И ФОРМА ТОКЕНАМИ (15.08, строка 63 диффа глазами).
   Замер канона: не шеврон, а **круг 43×43** (`span.pm`, radius 50 %) с глифом «+» цветом
   акцента; вопрос 17/700; карточка `rgba(255,255,255,.72)`, r=22, с внутренней светлой линией.
   У нас — тонкий шеврон 11×11 без подложки: на кадре рядом канон читается как КНОПКА,
   а мы как строка списка.
   ⚠ Умолчания прежние (11px, шеврон-маска) — izibuh и tezum не двигаются. */
.ds-faq__q::after {
  content: "";
  width: var(--ds-faq-toggle-size, 11px);
  height: var(--ds-faq-toggle-size, 11px);
  border-radius: var(--ds-faq-toggle-radius, 0);
  box-shadow: var(--ds-faq-toggle-ring, none);
  flex-shrink: 0;
  align-self: center;
  /* шеврон — маска, красится токеном */
  background: currentColor;
  color: var(--ds-color-accent);
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 9l7 7 7-7"/></svg>') center / contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 9l7 7 7-7"/></svg>') center / contain no-repeat;
  transition: transform 0.25s ease;
}

.ds-faq__item[open] .ds-faq__q::after { transform: rotate(180deg); }

.ds-faq__q:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: 2px;
  border-radius: var(--ds-radius-sm);
}

/* 🔴 ИНТЕРЛИНЬЯЖ ОТВЕТА — ТОКЕН (16.08). Предмет пришёл оттуда, где мы считали всё закрытым:
   `c-copy` свёл ДЛИНУ текста (267 против канонных 266), а блок остался КОРОЧЕ канонного.
   Замер трёх ширин: кегль 15.5 = 15.5 ✅, ряды 9/7 = 9/7 ✅, а интерлиньяж 23.25 против 24.8.
   ⇒ **текст сошёлся, типографика нет** — сверялись разные величины.
   📌 И это закрывает вопрос, оставшийся открытым утром: тогда `normal` всему `.ds-faq`
   ПЕРЕЛЕТАЛ, и я записал «у канона ответы держат СВОЁ число, снимать умолчание нельзя».
   Число теперь названо: **24.8 = 15.5 × 1.6** — то же отношение 1.6, что у всех восьми лидов. */
.ds-faq__a {
  margin: 0;
  line-height: var(--ds-faq-a-lh, inherit);
  padding: 0 0 var(--ds-faq-a-pad-bottom, var(--ds-space-4));
  color: var(--ds-text-secondary);
  font-size: var(--ds-faq-a-size, var(--ds-fs-sm));
}

@media (prefers-reduced-motion: reduce) {
  .ds-faq__item, .ds-faq__num, .ds-faq__q::after { transition: none; }
}

/* ═══ components/split-panel/split-panel.css ═══ */
/* f7c_design :: Тёмная split-панель «разделение» (наряд c-ops с визой руководителя:
   секция «Услуга Главбух» izibuh). Нейтральное имя — панель пригодна любому бренду.
   Фон — --ds-gradient-panel (то же правило, что у ds-calc__panel: on-primary ≥4.5
   к САМОЙ СВЕТЛОЙ точке градиента — обязанность темы; izibuh: 8.9). Требует tokens.css. */
.ds-split-panel {
  position: relative;
  overflow: hidden;
  border-radius: var(--ds-radius-xl);
  padding: var(--ds-space-12) var(--ds-space-12) var(--ds-space-8);
  background: var(--ds-gradient-panel);
  color: var(--ds-color-on-primary);
}

@media (max-width: 879px) {
  .ds-split-panel { padding: var(--ds-space-8) var(--ds-space-6); }
}

.ds-split-panel > * { position: relative; z-index: 1; }

/* Опциональный декоративный орб — тот же механизм, что у ds-calc__panel--orb */
.ds-split-panel--orb::before {
  content: "";
  position: absolute;
  z-index: 0;
  width: var(--ds-orb-size);
  height: var(--ds-orb-size);
  border-radius: 50%;
  right: calc(var(--ds-orb-size) / -3);
  top: calc(var(--ds-orb-size) / -3);
  background: var(--ds-orb-color);
  filter: blur(70px);
  pointer-events: none;
}

.ds-split-panel__tag {
  display: inline-block;
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: color-mix(in srgb, var(--ds-color-on-primary) 14%, transparent);
  color: var(--ds-color-on-primary);
  border-radius: var(--ds-radius-full);
  padding: var(--ds-space-2) var(--ds-space-4);
  margin-bottom: var(--ds-space-4);
}

/* Перечисление h1-h3 УДАЛЕНО в v0.45.0: панель задаёт color: on-primary на себе, а заголовки
   с этой версии наследуют цвет родителя (tokens.css). Стили заголовков КОЛОНОК ниже
   (__col h3, __col--card h3) — это не перекраска под поверхность, а собственный вид:
   размер, трекинг, акцент. Они остаются. */

.ds-split-panel__lead {
  color: color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent);
  max-width: 38rem;
  margin: var(--ds-space-3) 0 0;
}

/* ---- Разделение: колонка | плюс | колонка ---- */
.ds-split-panel__split {
  display: grid;
  grid-template-columns: 1fr 56px 1.15fr;
  align-items: stretch;
  margin: var(--ds-space-8) 0;
}

@media (max-width: 879px) {
  .ds-split-panel__split { grid-template-columns: 1fr; gap: var(--ds-space-3); }
}

.ds-split-panel__col {
  border-radius: var(--ds-radius-lg);
  padding: var(--ds-space-6);
  /* дефолт — «полупрозрачная» колонка (остаётся у клиента) */
  background: color-mix(in srgb, var(--ds-color-on-primary) 7%, transparent);
  border: 1px dashed color-mix(in srgb, var(--ds-color-on-primary) 30%, transparent);
  color: color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent);
}

.ds-split-panel__col--card {
  /* акцентная колонка-карточка (берём мы) */
  background: var(--ds-surface-page);
  border: none;
  color: var(--ds-text-secondary);
  box-shadow: var(--ds-shadow-lg);
}


.ds-split-panel__col h3 {
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin: 0 0 var(--ds-space-4);
  color: color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent);
}

.ds-split-panel__col--card h3 { color: var(--ds-color-accent); }

.ds-split-panel__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--ds-space-3);
  font-size: var(--ds-fs-sm);
}

.ds-split-panel__col li {
  display: flex;
  gap: var(--ds-space-3);
  align-items: flex-start;
}

.ds-split-panel__col li::before {
  content: "•";
  font-weight: var(--ds-fw-bold);
  flex-shrink: 0;
}

.ds-split-panel__col--card li::before {
  content: "✓";
  color: var(--ds-color-accent);
}

.ds-split-panel__plus {
  display: flex;
  align-items: center;
  justify-content: center;
}

.ds-split-panel__plus i {
  font-style: normal;
  width: 40px;
  height: 40px;
  border-radius: var(--ds-radius-full);
  background: var(--ds-gradient-brand);
  color: var(--ds-color-on-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--ds-fs-xl);
  font-weight: var(--ds-fw-bold);
}

/* ---- Подвал: цена + кнопка ВНУТРИ панели ---- */
.ds-split-panel__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-6);
  flex-wrap: wrap;
}

.ds-split-panel__price b {
  font-size: var(--ds-fs-2xl);
  font-weight: var(--ds-fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ds-color-on-primary);
}

.ds-split-panel__price span {
  color: color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent);
  font-size: var(--ds-fs-sm);
  margin-left: var(--ds-space-2);
}

/* Кнопка на тёмной панели — инверсная (как у ds-calc__panel) */
.ds-split-panel .ds-btn--primary {
  background: var(--ds-surface-page);
  color: var(--ds-color-primary);
}

.ds-split-panel .ds-btn--primary:hover {
  background: var(--ds-surface-raised);
}

/* ═══ components/dialog/dialog.css ═══ */
/* f7c_design :: ОКНО (диалог). Требует tokens.css.
 *
 * ПОВОД (17.08, наряд владельца — регистрация на сайте Forbion 1C). Вход в кабинет и вход
 * с сайта — ОДНА ДВЕРЬ, и разъезжаться им нельзя. До сих пор окна у нас не было вовсе:
 * каждый, кому оно требовалось, строил своё — так в макете ЛК появилась `.modal` со своими
 * числами, не связанная ни с одним нашим токеном.
 *
 * 🔴 ЧТО ЗДЕСЬ ВЫРАЖЕНО КОНСТРУКЦИЕЙ, А НЕ ПРОСЬБОЙ.
 * Окно перехватывает внимание целиком, поэтому у него два обязательства, которые легко
 * потерять при вёрстке «по картинке»:
 *   1. У окна ВСЕГДА есть выход. `.ds-dialog__actions` — единственное место для кнопок, и
 *      отменяющая стоит В НЁМ, а не «крестиком в углу вместо неё»: крестик находят глазами,
 *      кнопку — и глазами, и клавиатурой, и озвучкой.
 *   2. Завеса НЕ несёт содержимого и не подписывается. Всё, что человеку нужно прочитать,
 *      лежит внутри коробки; текст на завесе не читается озвучкой как часть окна.
 * ⚠ Роль намеренно НЕ решает, чем окно открывается (нативный `<dialog>`, атрибут или класс) —
 * это выбор потребителя. Поэтому завеса отдельным узлом: у нативного `::backdrop` свои
 * ограничения по наследованию токенов, и роль, требующая его, связала бы руки.
 */

.ds-dialog__veil {
  position: fixed;
  inset: 0;
  background: var(--ds-dialog-veil-bg, rgba(0, 0, 0, .42));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--ds-dialog-veil-pad, var(--ds-space-4));
  /* прокрутка живёт на завесе, а не на коробке: длинное окно на низком экране обязано
     доезжать до конца, иначе кнопки выхода оказываются за краем и выхода нет */
  overflow-y: auto;
}

.ds-dialog {
  width: 100%;
  max-width: var(--ds-dialog-max, 420px);
  background: var(--ds-dialog-bg, var(--ds-surface-card));
  color: var(--ds-dialog-ink, var(--ds-text-primary));
  border-radius: var(--ds-radius-card);
  padding: var(--ds-dialog-pad, var(--ds-space-6));
  box-shadow: var(--ds-dialog-shadow, none);
  /* 🔴 margin:auto, а не align-items на завесе в одиночку: при окне ВЫШЕ экрана флекс-центрирование
     обрезает ВЕРХ и обрезанное недостижимо прокруткой — заголовок и первое поле пропадают молча. */
  margin: auto;
}

.ds-dialog__title {
  margin: 0 0 var(--ds-dialog-title-gap, var(--ds-space-2));
  font-size: var(--ds-dialog-title-size, var(--ds-fs-xl));
  font-weight: var(--ds-dialog-title-weight, var(--ds-fw-black));
  line-height: var(--ds-dialog-title-lh, 1.25);
}

.ds-dialog__lead {
  margin: 0 0 var(--ds-dialog-lead-gap, var(--ds-space-4));
  color: var(--ds-dialog-lead-ink, var(--ds-text-secondary));
  font-size: var(--ds-dialog-lead-size, var(--ds-fs-sm));
}

.ds-dialog__actions {
  display: flex;
  gap: var(--ds-space-2);
  justify-content: flex-end;
  margin-top: var(--ds-dialog-actions-gap, var(--ds-space-6));
}

/* ── ВТОРОСТЕПЕННЫЕ ХОДЫ ОКНА (забыли пароль · регистрация) ──────────────────────────────────
 * 🔴 РЯДОМ, А НЕ В `__actions`. В блоке действий стоит то, что завершает окно; эти два —
 * УХОДЫ В ДРУГОЙ СЦЕНАРИЙ, и смешать их с «Войти» значит поставить три кнопки в ряд, из
 * которых две уводят. Человек, ошибшийся паролем, тянется к самой заметной — и уходит
 * регистрироваться, уже имея учётную запись.
 * ⚠ Перенос строкой на узком экране обязателен: два хода рядом на 320 px иначе слипаются
 * в одну зону касания. */
.ds-dialog__ways {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-dialog-ways-gap, var(--ds-space-3));
  margin-top: var(--ds-dialog-ways-top, var(--ds-space-4));
  font-size: var(--ds-dialog-ways-size, var(--ds-fs-sm));
}

/* ═══ components/form/form.css ═══ */
/* f7c_design :: Форма (поля, лейблы, сообщения об ошибках). Требует tokens/tokens.css. */
.ds-form {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-4);
}

.ds-field {
  /* Кегль самого поля — тот же токен, что у контролов: иначе `.ds-field` остаётся на
     наследовании от секции (16), и «13» действует только внутри `input`. */
  font-size: var(--ds-field-size, inherit);
  /* 🔴 ИНТЕРЛИНЬЯЖ СТРОКИ — ТОКЕН (16.08, разложение калькулятора по узлам). У канона строка
     опции держит `line-height: normal`, у нас с базовой типографики приезжает `24px` — и это
     ШЕСТОЙ за сутки случай формы «канон оставляет `normal`, мы ставим число». Контрфакт:
     строка со степпером 77 → 63, то есть РОВНО канон; форма 625 → 585.
     ⚠ `align-items: center` (у канона он есть, у нас `normal`) в том же опыте дал НОЛЬ —
     поэтому не ставлю: правка без измеренного следа это «объявлено и не действует».
     Умолчание `inherit` = прежний рендер, izibuh и tezum не двигаются. */
  line-height: var(--ds-field-lh, inherit);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-1);
}

/* ── ОПЦИЯ-СТРОКА: подпись слева, контрол справа (партия 9, разбор шага 2) ────────────
 * 🔴 Высоту опции держала РАСКЛАДКА, а не размеры: у канона опция — строка, у нас колонка,
 * и потому мы были выше **даже при нулевых полях**. Замер канона `#calc .copts .copt`:
 *     display:flex · row · align-items:center · justify-content:space-between
 *     gap 14 · padding 9px 12px · radius 16 · фон #FFFFFF
 *     подпись flex 0 1 auto · контрол flex 0 0 auto
 * ⚠ Поля и раскладка едут ОДНИМ тегом намеренно: порознь поля дали бы +18 на опцию, то есть
 * «ближе к канону» и «стало выше» совпали бы — единственный такой случай за смену.
 * Модификатор, а не правка `.ds-field`: колоночная раскладка — принятое поведение у izibuh. */
.ds-field--row {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-field-row-gap, var(--ds-space-4));
  padding: var(--ds-field-row-pad, 0);
  border-radius: var(--ds-field-row-radius, 0);
  background: var(--ds-field-row-bg, transparent);
}

/* 🔴 ОТРИЦАТЕЛЬНЫЙ СПИСОК СРЕЗАЛ ТЕКСТ У ПОСЕТИТЕЛЯ (нашёл `c-ops` на живом v129, 15.08:
   @390 срезано 4 подсказки из 6, до 345 px за краем; @768 — две).
   Я снял с канона верное: у канонной опции контрол НЕ сжимается (`flex: 0 0 auto`), —
   и записал это через `:not(.ds-field__label)`. Но «не подпись» это НЕ «контрол»: под то же
   правило попали `.ds-field__hint` и `.ds-checkbox`, то есть ТЕКСТ, которому запрет сжиматься
   означает уход за край. **Одно отрицание на два разных рода детей** — наш класс
   «недостаточное различение», теперь в самой дорогой форме: видно посетителю.
   ⚠ И почему не кричало: у `.ds-calc__inner` стоит `overflow-x: hidden` — прокрутки нет,
   значит и единственный крик убран. Клип не причина, но глушитель.

   Строю ПОЛОЖИТЕЛЬНЫМ списком по своему же пространству имён: жёсткие — перечисленные
   контролы ядра; всё остальное, включая незнакомых детей, сжимается. Тогда худший исход
   ошибки — перенос строки, а не исчезнувший текст.
   ⚠ `min-width: 0` обязателен рядом с `flex-shrink`: у флекс-элемента умолчание `auto` =
   min-content, и без него сжатие упирается в самое длинное слово.

   Канон 390/768/1366 (`#calc .copts .copt`): ряд остаётся РЯДОМ на всех трёх — в колонку
   не разворачиваю, у канона подпись `flex: 0 1 auto; min-width: 0`, контрол `0 0 auto`. */
.ds-field--row > * { min-width: 0; }
/* 🔴 ПОДПИСЬ НЕ СЖИМАЕТСЯ НИЖЕ СВОЕГО ДЛИННОГО СЛОВА. На живом (390 и 900, вкладка
   «Свой сервер») подпись «Ступень» была сжата В НОЛЬ и срезана на 52: `min-width: 0`,
   который я дал всем детям ряда в v0.242.0, снял у неё пол. Тогда это лечило переполнение;
   здесь оно же СОЗДАЁТ невидимый текст. **Одно свойство на две роли: контролу пол не нужен,
   тексту нужен.**
   ⚠ Цена названа и не бесплатна: на 1366 `#calc` в аренде 902 → 938 (+36) — часть подписей
   перестаёт ужиматься и переносится. Выбрал так же, как утром: **текст у посетителя дороже
   высоты**, и «стало выше» обратимо, а «слова нет» невидимо. */
.ds-field--row > .ds-field__label { flex: 0 1 auto; min-width: min-content; }

/* ⚠ НИЖЕ ПОРОГА КОНТРОЛ УСТУПАЕТ. `.ds-segment` стоит в положительном списке жёстких
   (v0.242.0) — и это верно на широком, где канон держит ряд равных ступеней. На 390 ряд
   из трёх по 155 не помещается физически: жёсткость превращалась в выход за край на 172.
   Порог 900 не новый — им уже пользуется брендовый ритм секций. */
.ds-field--row > .ds-stepper,
.ds-field--row > .ds-segment,
.ds-field--row > .ds-switch,
.ds-field--row > .ds-range,
.ds-field--row > .ds-input,
.ds-field--row > .ds-select,
.ds-field--row > .ds-badge { flex: 0 0 auto; }

/* 🔴 ПОСЛЕ СПИСКА ЖЁСТКИХ, А НЕ ДО: специфичность у них РАВНАЯ (0,2,0), решает ПОРЯДОК.
   Первая редакция стояла выше по файлу — гейты зелёные, а замер на собранном артефакте
   дал прежние 18 узлов за краем. Ниже порога контрол уступает: на 390 ряд из трёх ступеней
   по 155 в контейнере 354 невозможен физически. Порог 900 не новый — им уже пользуется
   брендовый ритм секций. */
@media (max-width: 900px) {
  .ds-field--row > .ds-segment { flex: 0 1 auto; }
}

.ds-field__label {
  /* 🔴 ЛИТЕРАЛ ШКАЛЫ ЗДЕСЬ ОСТАНАВЛИВАЛ КАНОН (находка `c-tpl`, 15.08). `--ds-field-size` я
     положил на `.ds-input`/`.ds-select`, а подпись осталась на `--ds-fs-sm` (14) — и кегль 13
     до неё **не доезжал наследованием**, потому что у неё своё явное объявление.
     У канона внутри опции калькулятора ВСЁ 13: и подпись, и значение (замер `.copts .copt`).
     ⚠ Класс тот же, что весь день: «значение есть — читателя нет», только читателем оказался
     не чужой класс, а МОЁ ЖЕ второе правило ниже по файлу. */
  font-size: var(--ds-field-label-size, var(--ds-fs-sm));
  font-weight: var(--ds-fw-medium);
  color: var(--ds-text-primary);
}

.ds-field__hint {
  font-size: var(--ds-fs-xs);
  color: var(--ds-text-muted);
}

.ds-input,
.ds-textarea,
.ds-select {
  font-family: var(--ds-font-base);
  /* Кегль контрола — токен: у канона опции калькулятора набраны **13px**, у нас `--ds-fs-md`
     (16). Разница даёт 171 из 261 в шаге 2 калькулятора вместе с зазором сетки (тот у шаблона:
     6/12 против 24). Умолчание прежнее — izibuh и tezum не двигаются.
     🔴 16.08, СВЕРКА ПОМЕТОК: ОБОСНОВАНИЕ ВЫШЕ НАЗЫВАЕТ НЕ ТОТ ПРЕДМЕТ. Замер живого: этот
     селектор накрывает РОВНО ТРИ контрола, и все три — в форме заявки; в калькуляторе
     `.ds-input/.ds-select/.ds-textarea` НЕТ НИ ОДНОГО. То есть число снято с опций калькулятора
     (`.copt > span` = 13 ✓, там оно верно) и применено к полям заявки, где канон даёт **14.5**.
     ⚠ Класс: пометка «канон» была ПРАВДОЙ О ДРУГОМ УЗЛЕ. Проверять надо не число, а на ком оно. */
  font-size: var(--ds-field-size, var(--ds-fs-md));
  color: var(--ds-text-primary);
  background: var(--ds-surface-page);
  border: 1px solid var(--ds-border-strong);
  border-radius: var(--ds-radius-md);
  /* Поля контрола — токен: канон 13/15 (`input.fld`, border-box, без min-height), у нас 12/16.
     Высота канона 45 складывается из кегля 14.5 + полей 26 + рамки 2 — то есть НЕ задана
     числом вовсе, а следует из этих трёх. ⇒ подгонять высоту нечем и не надо. */
  padding: var(--ds-field-pad, var(--ds-space-3) var(--ds-space-4));
  transition: border-color 0.15s ease;
}

.ds-input:focus,
.ds-textarea:focus,
.ds-select:focus {
  outline: none;
  border-color: var(--ds-border-focus);
  box-shadow: 0 0 0 3px var(--ds-focus-ring);
}

.ds-input::placeholder,
.ds-textarea::placeholder { color: var(--ds-text-muted); }

/* 🔴 ВЫСОТА ТЕКСТАРЕА — ТОКЕН (партия 8, строка 100; нашёл `c-tpl` на живой секции).
   У канона поле комментария **66px при двух строках** (`rows="2"`, поля 13/15), у нас `7rem`
   = 112 плюс поля, то есть **138 против 70** — вдвое выше. Разница не косметическая: у канона
   подсказка целиком помещается ВНУТРИ поля, а у нас она не влезала и была вынесена наружу,
   отчего секция и росла.
   ⚠ Высота обычного поля при этом совпадала точно (45 и там, и там) — то есть расходился
   ровно один размер, и «форма выше канона» было следствием одного числа, а не набора.
   Умолчание = прежний рендер: izibuh и tezum не двигаются. */
.ds-textarea { min-height: var(--ds-textarea-min-h, 7rem); resize: vertical; }

.ds-field--error .ds-input,
.ds-field--error .ds-textarea,
.ds-field--error .ds-select { border-color: var(--ds-color-error); }

.ds-field__error {
  font-size: var(--ds-fs-xs);
  color: var(--ds-color-error);
}

/* ── ПОЧЕМУ НЕЛЬЗЯ НАЖАТЬ ────────────────────────────────────────────────────────────────────
 * ПОВОД (17.08, наряд владельца по регистрации). Сценарий звучал как «когда заполнены все три
 * поля — ПОЯВЛЯЕТСЯ кнопка». 🔴 Дефект не в том, КАК она появляется, а в том, что до этого её
 * НЕТ: человек не знает, что она будет, сколько ещё шагов и стоит ли начинать. Форма отвечает
 * на вопрос «что тут произойдёт» вместо «что мне сделать».
 * ⇒ кнопка стоит с первой секунды неактивной, а рядом сказано, ЧЕГО НЕ ХВАТАЕТ.
 *
 * 🔴 И ТРИ ОГРАНИЧЕНИЯ, ВЫРАЖЕННЫЕ ЗДЕСЬ КОНСТРУКЦИЕЙ:
 *   1. Это УЗЕЛ, а не `title`/подсказка по наведению: на касании наведения нет вовсе, и
 *      причина, доступная только мышью, для половины людей не существует.
 *   2. Причина живёт РЯДОМ С КНОПКОЙ, а не у полей: `.ds-field__error` отвечает «что не так
 *      здесь», а этот узел — «чего не хватает, чтобы нажать». Разные вопросы, и второй нельзя
 *      собрать, читая три первых.
 *   3. Тон НЕ ошибочный. Незаполненное поле — не ошибка человека, а незаконченный шаг;
 *      красный здесь обвиняет за то, что он ещё не сделал.
 * ⚠ Узел не прячется при активной кнопке — он пустеет. Прятать значит двигать раскладку в
 * момент, когда человек целится в кнопку.
 *
 * 🔴 И ЗДЕСЬ Я СВОЁ ЖЕ УТВЕРЖДЕНИЕ СНАЧАЛА НАРУШИЛ — поймал приёмкой макета, не глазами.
 * Стояло `min-height: 1.2em` без своего `line-height`, а интерлиньяж наследовался от `body` (1.5).
 * ⇒ заполненный узел 17px, пустой 14px: **прыжок в 3px ровно в тот момент, когда кнопка
 * становится активной и человек в неё целится** — то есть ровно тот дефект, который абзац выше
 * запрещает. Резерв обязан считаться от СВОЕГО интерлиньяжа, иначе он резервирует не строку.
 * 📌 Класс: величина, выраженная в двух местах (`min-height` здесь, `line-height` у предка),
 * расходится молча — и «объявил намерение» не то же самое, что «намерение выполняется».
 *
 * ⚠ ЧЕСТНАЯ ГРАНИЦА РЕЗЕРВА: он держит ОДНУ строку. Причина в две строки сдвинет раскладку
 * снова, и резервировать две ради этого нельзя — постоянная пустота хуже редкого сдвига.
 * ⇒ это требование к ТЕКСТУ, а не к стилю: причина обязана умещаться в строку
 * («Осталось указать телефон»), а не пересказывать правила заполнения. */
.ds-blocked-reason {
  font-size: var(--ds-blocked-reason-size, var(--ds-fs-xs));
  line-height: var(--ds-blocked-reason-lh, 1.45);
  color: var(--ds-blocked-reason-ink, var(--ds-text-secondary));
  /* тот же множитель, что у интерлиньяжа выше: резерв = ровно одна своя строка */
  min-height: var(--ds-blocked-reason-min-h, 1.45em);
}

/* ── СОГЛАСИЕ НА ОБРАБОТКУ ПДн ───────────────────────────────────────────────────────────────
 * Форма регистрации собирает ПОЧТУ, ТЕЛЕФОН и ИНН — это персональные данные, и согласие
 * здесь ЧАСТЬ ФОРМЫ, а не примечание под ней.
 * 🔴 РОЛЬ НАМЕРЕННО НЕ ДАЁТ СПРЯТАТЬ ССЫЛКУ: политика лежит ВНУТРИ строки согласия, обычной
 * ссылкой, а не «подробнее» в раскрывающемся блоке. Согласие, данное на текст, которого не
 * видно, юридически слабее и по-человечески нечестно.
 * ⚠ Формулировку пишет `c-legal` — здесь только МЕСТО. И два условия к разметке, которые
 * стилем не выражаются, а нарушаются легко: галочка НЕ предустановлена, и её отсутствие
 * гасит кнопку через `.ds-blocked-reason`, а не через отдельный красный текст после нажатия. */
.ds-consent {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-consent-gap, var(--ds-space-2));
  font-size: var(--ds-consent-size, var(--ds-fs-xs));
  line-height: var(--ds-consent-lh, 1.45);
  color: var(--ds-consent-ink, var(--ds-text-secondary));
}
.ds-consent input { flex: none; margin-top: var(--ds-consent-box-top, 0.15em); }

/* ═══ components/calc/calc.css ═══ */
/* f7c_design :: Оболочка калькулятора (наряд этап 2). Требует tokens.css.
   Двухколоночный layout: поля | панель итога на --ds-gradient-panel.
   Текст панели — on-primary; тема ОБЯЗАНА дать ≥4.5 к самой светлой точке градиента. */
.ds-calc {
  position: relative;
  border-radius: var(--ds-radius-xl);
  padding: 2px; /* градиентная рамка */
  background: var(--ds-gradient-brand);
  box-shadow: var(--ds-shadow-brand);
}

.ds-calc__inner {
  display: grid;
  /* 🔴 ПРОПОРЦИЯ КОЛОНОК — ТОКЕН (15.08, наряд integrator-c2 по разрыву плотности).
     Замер канона @1366: `grid 669.5 / 418.5` — отношение **1.60**; у нас 1.1/0.9 = **1.22**,
     на живом это дало 624/511. ⚠ Утверждение наряда «двухколоночной раскладки нет вовсе,
     display:block» ПРОВЕРИЛ ПЕРЕД ПРАВКОЙ (П7) и НЕ подтверждаю: `display:block` — у внешней
     `.ds-calc` (градиентная рамка), сетка живёт в `__inner` и работает. Расходится ПРОПОРЦИЯ,
     а не наличие колонок; лечится числом, а не переписыванием раскладки. */
  grid-template-columns: var(--ds-calc-cols, 1.1fr 0.9fr);
  border-radius: calc(var(--ds-radius-xl) - 2px);
  overflow: hidden;
  background: var(--ds-surface-page);
}

/* 🔴 БЫЛО 899 — НА ПИКСЕЛЬ МИМО КАНОНА. У канона граница выражена парой `900max | 901min`
   (перечень медиазапросов, механизм `integrator-c2`), то есть РОВНО на 900 канон держит одну
   колонку, а мы держали две. Коридор шириной в один пиксель: его не найдёт ни один замер,
   кроме прицельного на 900. Это не решение и не «почти то же» — это опечатка порога. */
@media (max-width: 900px) {
  .ds-calc__inner { grid-template-columns: 1fr; }
}

.ds-calc__form {
  /* `border-box` по тому же поводу, что и у панели: у канона `#calc .cl` — `border-box`,
     у нас было `content-box`, и поля 24 прибавлялись снаружи ширины. Придержано было до
     перекладки правой колонки (v137), теперь связка снята. ⚠ Сами поля формы (канон 18/20)
     здесь НЕ трогаю: это отдельное число и отдельный предмет. */
  box-sizing: border-box;
  /* Поля формы — токен: у канона они СТУПЕНЧАТЫ на **901** (бисекция 16.08: @900 = 18px,
     @901 = 14px): `18px 20px` ниже и `14px 20px` от 901. Умолчание = прежний рендер.
     🔴 В ПЕРВОЙ РЕДАКЦИИ ЗДЕСЬ СТОЯЛО «1024», И ЭТО ЗЕРКАЛО СЛУЧАЯ С ПЛИТКОЙ `#inc`.
     Там уехало ЗНАЧЕНИЕ из-под верной пометки; здесь уехала ПОМЕТКА из-под верного
     значения: реализация давно ступает на 900/901 и сходится с каноном на обеих ширинах
     (замер: @390 18/20 · @1366 14/20, обе стороны), а текст называл порог, которого
     у канона нет вовсе (1024 отсутствует в его перечне медиазапросов).
     ⚠ Устаревшая пометка опаснее отсутствующей: следующий читатель сверит по ней и
     «подтвердит» несуществующий порог. Пометка — такой же носитель, как значение,
     и протухает отдельно от него. */
  padding: var(--ds-calc-form-pad, var(--ds-space-8));
  display: grid;
  /* 🔴 РИТМ ШАГОВ У КАНОНА — НЕ ОДНО ЧИСЛО, И ПОТОМУ ЗАЗОР КОНТЕЙНЕРА ЕГО НЕ ВЫРАЖАЕТ.
     Замер канона (`#calc .cl`, block-поток, зазора у контейнера НЕТ вовсе; расстояния несут
     поля самих элементов):
         подпись шага 1 → контрол            15
         контрол        → подпись шага 2     10
         подпись шага 2 → сетка опций         7
         сетка опций    → «Оплата за год»      0
     Четыре разных значения, убывающих. Один `gap` их не воспроизводит **по построению**:
     он одинаков для всех пар. ⇒ токен даётся, чтобы потребитель мог обнулить контейнерный
     зазор и задать ритм на элементах, как у канона. Умолчание прежнее (`--ds-space-6`). */
  gap: var(--ds-calc-form-gap, var(--ds-space-6));
  align-content: start;
}

/* Зазор ТАБЫ → первый шаг: у канона 6 (`#calc .cl`, между `.ctabs` и телом шагов),
   у нас 0 — контейнерный `gap` обнулён намеренно (ритм несут элементы, см. выше), и вместе
   с ним пропала эта единственная пара, которая у канона зазор всё-таки имеет.
   ⚠ Общим `--ds-calc-form-gap` это не лечится: он одинаков для ВСЕХ пар, а нужна одна.
   Умолчание 0 = прежний рендер; izibuh и tezum токен не объявляют и не двигаются. */
.ds-calc__form > .ds-segment + * {
  margin-top: var(--ds-calc-gap-after-tabs, 0);

  /* собственный фон колонки (не только у __inner): паритет с концептом
     и устойчивость к любым фонам секции-носителя */
  background: var(--ds-surface-page);
}

.ds-calc__panel {
  position: relative;
  overflow: hidden;
  /* 🔴 ПОЛЯ ПАНЕЛИ — ТОКЕН, И `border-box` РЯДОМ С НИМ ОБЯЗАТЕЛЕН.
     Замер канона `#calc .cr`: **16 @1366 и 12 @390**, `border-box`; у нас 32/24 и
     `content-box` — то есть поля прибавляются СНАРУЖИ, и это тот же класс, что ловил
     гейт 24 на контейнерах и что дало кнопку 407 в блоке 365.
     Умолчания прежние (`--ds-space-8`), izibuh и tezum не объявляют. */
  box-sizing: border-box;
  padding: var(--ds-calc-panel-pad, var(--ds-space-8));
  /* 🔴 ПОВЕРХНОСТЬ ПАНЕЛИ ИТОГА — ТОКЕН. Канон: **белая карточка** `rgba(255,255,255,.72)`,
     r=16, с внутренней светлой линией, чернила обычные. У нас — сплошная градиентная плита
     во всю высоту с инверсными чернилами: другой приём, тот же класс, что был у формы
     (партия 4, строка 66) и у колонки сравнения. Умолчания прежние. */
  background: var(--ds-calc-panel-bg, var(--ds-gradient-panel));
  color: var(--ds-calc-panel-ink, var(--ds-color-on-primary));
  display: flex;
  flex-direction: column;
}
/* 🔴 ЗАЗОР МЕЖДУ КАРТОЧКОЙ ТАРИФА И БЛОКОМ ИТОГА — канон 10 (`#calc .cr`, замер 15.08 на
   ОБЕИХ ширинах, подпись `bba96ebac217`). Колонка канона: `flex-column`, у первого ребёнка
   `margin-bottom: 10px`, своих зазоров у контейнера нет. Арифметика сходится ровно:
   299 + 10 + 174 + поля 16×2 = 515 = высота `.cr`.

   🔴 ПРАВИЛО НАМЕРЕННО СРАБАТЫВАЕТ ТОЛЬКО ПРИ ДВУХ ДЕТЯХ (`:nth-child(2):last-child`).
   На живом сейчас их СЕМЬ (карточка · подпись · итог · строки · сноска · пилюли · кнопка),
   и `> * + *` добавил бы зазор в ШЕСТЬ мест вместо одного. Структура в два блока приезжает
   `TEMPLATE v0.194.0`; ждать её выкладки я не стал намеренно — это вернуло бы связь порядка
   между нашими тегами, которую мы весь день снимали. **Условие в самом правиле дешевле
   договорённости о порядке.**
   Умолчание 0 = прежний рендер; izibuh и tezum токен не объявляют. */
.ds-calc__panel > :nth-child(2):last-child {
  margin-top: var(--ds-calc-panel-gap, 0);
}


.ds-calc__panel > * { position: relative; z-index: 1; }

/* Зазор ПЕРЕД кнопкой в панели итога — канон 18 (`#calc .cr a.btn { margin-top: 18 }`).
   Носитель у канона — сама кнопка, поэтому и у нас правило на кнопке внутри панели,
   а не на контейнере: контейнер (`.tpl-calc__sum`) чужой, а кнопка — роль ядра.
   Умолчание 0 = прежний рендер; izibuh и tezum токен не объявляют. */
.ds-calc__panel .ds-btn {
  margin-top: var(--ds-calc-sum-gap-before-cta, 0);
}

/* Опциональный декоративный орб панели: цвет/размер — токены-параметры */
.ds-calc__panel--orb::before {
  content: "";
  position: absolute;
  z-index: 0;
  width: var(--ds-orb-size);
  height: var(--ds-orb-size);
  border-radius: 50%;
  right: calc(var(--ds-orb-size) / -3);
  top: calc(var(--ds-orb-size) / -3);
  background: var(--ds-orb-color);
  filter: blur(70px);
  pointer-events: none;
}

.ds-calc__caption {
  font-size: var(--ds-fs-xs);
  /* Канон: подпись «Итого» даёт h=14 при кегле 12 и `line-height: normal`; у нас тот же
     кегль 12, но `lh: 18` от базовой типографики ⇒ +4. Расходится ИНТЕРЛИНЬЯЖ, не кегль. */
  line-height: var(--ds-calc-caption-lh, inherit);
  font-weight: var(--ds-fw-bold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.8;
}

.ds-calc__total {
  /* Канон `.total.pop`: кегль **32** при `lh: normal` (у нас 36 и 43.2) ⇒ +6 высоты. */
  font-size: var(--ds-calc-total-size, var(--ds-fs-3xl));
  font-weight: var(--ds-fw-bold);
  font-variant-numeric: tabular-nums;
  line-height: var(--ds-lh-tight);
  /* Ритм блока итога — ОТНОШЕНИЯМИ, а не позициями: у нас в блоке есть узел, которого
     у канона нет (дисклеймер), и «зазор №2» сместился бы вместе с ним. Замер канона
     `#calc .cr > div:last-child`: подпись→сумма **0** · сумма→строка **10** ·
     строка→пилюли **8** · пилюли→кнопка **18**. Умолчания = прежний рендер. */
  margin: var(--ds-calc-sum-gap-after-caption, var(--ds-space-2)) 0 0;
}

/* 🔴 ВТОРИЧНЫЙ ТЕКСТ ПАНЕЛИ — РОЛЬ, А НЕ «ЧЕРНИЛА НА ЗЕЛЁНОМ» (17.08, наряд Н5).
   Четыре узла панели красились `color-mix(--ds-color-on-primary 80%)`. Этот токен значит
   «чернила НА ПЕРВИЧНОЙ (зелёной) поверхности» и намеренно НЕ переворачивается по теме —
   на зелёной кнопке текст тёмный в любой теме. Но панель итога зелёной не является: замер
   краски под текстом в тёмной дал #22221E и #2F3624, то есть страницу и поверхность выделения.
   ⇒ роль «текст на зелёном» стояла как роль «приглушённый текст». В светлой это совпадало
   (тёмные чернила на светлом читаются), в тёмной дало 1.06–1.35 при пороге 4.5 — невидимый текст.
   ⚠ Ровно тот класс, что уже записан в бренд-пакете: СОВПАДЕНИЕ В СВЕТЛОЙ СКРЫЛО РАЗЛИЧИЕ РОЛЕЙ.
   Умолчание прежнее — рендер не меняется, пока бренд не объявит значение. */
.ds-calc__total small {
  /* Канон: единица внутри суммы — **13** (у нас 16). */
  font-size: var(--ds-calc-total-unit-size, var(--ds-fs-md));
  font-weight: var(--ds-fw-medium);
  /* вторичный текст на панели: 80% on-primary — у издателя пакета проверен
     контраст к самой светлой точке (см. brand.json → contrast) */
  color: var(--ds-calc-panel-ink-muted, color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent));
}

/* 🔴 ЭТОТ КЛАСС НОСИЛ ИМЯ ДС, А ПРАВИЛА У НЕГО НЕ БЫЛО ВОВСЕ (нашёл я, 15.08, разбирая ритм
   блока итога). На живом стоит `<p class="ds-calc__line">`, в ядре такого селектора нет —
   и отступы 16/16 рисовал БРАУЗЕР: умолчание `<p>` = 1em. Форма зеркальна нашей привычной:
   не «роль объявлена, носителя нет», а **носитель есть, роли нет**, и она опаснее, потому что
   результат выглядит осмысленным — 16 читается как выбранное число.
   ⚠ Из-за неё же не сработала первая редакция ритма: я переписал `.ds-calc__lines`
   (мой класс, множественное), а на живом узел `.ds-calc__line` — единственное.
   Фолбэк 16px намеренно ЛИТЕРАЛЬНЫЙ, а не ступень шкалы: он замораживает прежний рендер
   ровно тем числом, которое давал браузер; переводить его на шкалу — отдельное решение. */
.ds-calc__line {
  /* 🔴 КРУПНЕЙШЕЕ СЛАГАЕМОЕ БЛОКА ИТОГА (+45), И ОНО — «УМОЛЧАНИЕ НЕОТЛИЧИМО ОТ РЕШЕНИЯ».
     Поля этому узлу объявлены (строка ниже), а КЕГЛЬ не задан НИКЕМ: computed 16 — просто
     наследование от панели, и при ширине 223 строка ложится в три ряда по 24.
     Канон: `ul.bd li` — кегль **11**, интерлиньяж 16.5 (= 1.5), высота 27 против наших 72.
     ⚠ `c-tpl` мог поставить своё число и НЕ стал: класс в ядре, значит число моё.
     Это ровно тот случай, ради которого мы развели «нет читателя» и «читатель есть,
     значение не задано»: узел, поля и правило на месте, а размер достался браузеру. */
  font-size: var(--ds-calc-line-size, inherit);
  line-height: var(--ds-calc-line-lh, inherit);
  margin: var(--ds-calc-sum-gap-after-total, 16px) 0 var(--ds-calc-sum-gap-after-line, 16px);
}

.ds-calc__lines {
  list-style: none;
  margin: var(--ds-calc-sum-gap-after-total, var(--ds-space-6))
          0
          var(--ds-calc-sum-gap-after-line, var(--ds-space-6));
  padding: 0;
  font-size: var(--ds-fs-sm);
}

.ds-calc__lines li {
  display: flex;
  justify-content: space-between;
  gap: var(--ds-space-4);
  padding: var(--ds-space-2) 0;
  border-bottom: 1px solid color-mix(in srgb, var(--ds-color-on-primary) 22%, transparent);
  color: var(--ds-calc-panel-ink-muted, color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent));
}

.ds-calc__lines li > :last-child {
  /* 🔴 ТОТ ЖЕ ПРОМАХ РОЛИ, ЧТО У ПРИГЛУШЁННОГО ТЕКСТА, И Я ЕГО ПРОПУСТИЛ В ПРОШЛОМ ТЕГЕ.
     Правил я четыре места с `color-mix(--ds-color-on-primary 80%)` и не тронул пятое, где тот же
     токен стоит В ПОЛНУЮ СИЛУ. Сканер это и показал: два узла остались 1.06 и 1.35 при пороге 4.5.
     ⚠ Замена по ОБРАЗЦУ ТЕКСТА («найди `80%`») пропускает то же по смыслу, но иначе записанное.
     Значение колонки — первичные чернила ПАНЕЛИ, они флипаются по теме; умолчание прежнее. */
  color: var(--ds-calc-panel-ink, var(--ds-color-on-primary));
  font-weight: var(--ds-fw-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── «ЧТО ВХОДИТ»: что клиент получает за названную цену. ─────────────────────
   Заведён 2026-07-17 по признаку ВЛАДЕЛЬЦА (скриншот живого dvl: обвёл красным
   огромную пустоту в правой панели). Воспроизвёл рендером до правки — пустота
   есть: строки сверху, дисклеймер и кнопка внизу, между ними мёртвый градиент.

   ⚠ Поправка к диагнозу наряда (проверил, а не принял на слово):
   1. «.ds-section--glow — мёртвый класс» — НЕВЕРНО: у него position:relative +
      overflow:hidden с v0.13.0, включая живой v0.20.0. Сверил по трём тегам.
   2. «убрали растягивающийся элемент» — НЕТОЧНО: растягивающийся элемент в ядре
      БЫЛ — .ds-calc__lines нёс flex:1. Пустота не от сломанной раскладки, а от
      ОТСУТСТВИЯ СОДЕРЖИМОГО: строк расчёта две, а панель тянется по левой форме.
   Причина одна: в ядре нет сущности «что входит». Это не строки расчёта
   (__lines — динамический разбор цены, меняется от режима), а статичный состав
   услуги. Разные сущности, разная жизнь.

   ПОЧЕМУ ЯДРО, А НЕ IZIBUH-СПЕЦИФИКА (довод c-tpl «артефакт одного сайта не
   диктует ядру, обслуживающему N» — принят и проверен): в ядро идёт ФОРМА, а не
   содержание. Панель, которая называет цену и не говорит, что за неё дают, —
   неполный паттерн у ЛЮБОГО прайс-калькулятора. Тексты приходят из site.json.

   Раскладка: flex:1 снят со __lines и НЕ повешен сюда — низ прижимается
   через __note{margin-top:auto}. Так работает и БЕЗ слота: сайты, которые его не
   возьмут, не меняются. Проверено рендером — вёрстка без слота пиксель в пиксель
   совпала со стендом (кнопка 490, дисклеймер 446, строки 233/267). */
.ds-calc__includes {
  /* 🔴 РИТМ КАРТОЧКИ ТАРИФА У КАНОНА НЕСУТ ПОЛЯ УЗЛОВ, А НЕ ЗАЗОР КОНТЕЙНЕРА — третий раз
     та же форма (после ритма шагов калькулятора и блока итога). Замер `#calc .tcard`:
     `gap: normal`, а расстояния дают поля самих детей — подпись цены `3px 0 10px` (@1366
     `4px 0 9px`), ссылка `9px 0 0`. Итог: **имя→цена 3–4 · цена→список 9–10**.
     У нас было `gap: 12` у карточки плюс `margin-top: 16` здесь ⇒ 12 и 28.
     ⚠ Одним `gap` канон не воспроизводится ПО ПОСТРОЕНИЮ: у него две пары разные. Поэтому
     зазор карточки уходит в 4 (бренд), а разницу до 9 добирает вот это поле. */
  margin-top: var(--ds-calc-includes-gap, 0);
  /* 🔴 ИНТЕРЛИНЬЯЖ СПИСКА — ТОКЕН (16.08, разложение `#calc` по узлам на 901). Канонный `ul`
     карточки тарифа держит `line-height: normal`, а нам с базовой типографики приезжает `24px`.
     СЕДЬМОЙ за сутки случай формы «канон оставляет `normal` — мы ставим число», и здесь он
     самый дорогой: список 300 против канонных 198.
     Контрфакт по шагам @901: `lh: normal` → список 300→235, карточка 485→420, секция 1085→1058.
     ⚠ Остаток списка (235 против 198) — СОСТАВ: у канона в `ul` другое число пунктов, и там же
     живёт ссылка `.tcmore`, которой у нас нет вовсе. Держатель `c-tpl`, числа ему названы. */
  line-height: var(--ds-calc-includes-lh, inherit);
  margin-bottom: var(--ds-space-4);
}

.ds-calc__includes .ds-calc__caption {
  display: block;
  margin-bottom: var(--ds-space-3);
}

.ds-calc__includes ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--ds-space-2);
  font-size: var(--ds-fs-sm);
  color: var(--ds-calc-panel-ink-muted, color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent));
}

.ds-calc__includes li {
  display: flex;
  gap: var(--ds-space-2);
  align-items: flex-start;
}

/* Маркер — кружок, а не «✓»: галочка обещает «включено и гарантировано», а состав
   услуги задаёт сайт, и ядро за его правдивость не отвечает (норма п.11: обязательство
   несёт глагол, а не украшение). Тот же приём, что у .ds-split-panel__col li::before. */
.ds-calc__includes li::before {
  content: "";
  width: 15px;
  height: 15px;
  border-radius: 50%;
  flex-shrink: 0;
  margin-top: 2px;
  background: color-mix(in srgb, var(--ds-color-on-primary) 22%, transparent);
}

.ds-calc__note {
  font-size: var(--ds-fs-xs);
  color: var(--ds-calc-panel-ink-muted, color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent));
  /* 🔴 ЭТОТ ОТСТУП — НАШ, И ИЗ-ЗА НЕГО ЗАЗОР ПЕРЕД КНОПКОЙ НЕЛЬЗЯ ПРОСТО ПОСТАВИТЬ.
     Наряд говорил «зазор кнопка↔пилюли 16 → 18», и держателем 16 назывался шаблон. Замер:
     16 приходит ОТСЮДА, из ядра (`--ds-space-4`), а у канона дисклеймера нет вовсе и 18
     несёт сама кнопка. ⇒ поставь я 18 не сняв этих 16 — сложилось бы 34.
     Оба конца пары мои, поэтому едут ОДНИМ тегом: межрепозиторной очереди здесь нет. */
  margin: 0 0 var(--ds-calc-note-gap-after, var(--ds-space-4));
  /* Прижимает дисклеймер и кнопку к низу панели независимо от того, есть слот
     «что входит» или нет. Пришло на замену flex:1 у __lines: с флексом на строках
     слот съезжал бы вниз, а без слота результат тот же — проверено рендером. */
  margin-top: auto;
}

/* Кнопка на панели: инверсия, чтобы не слиться с градиентом */
.ds-calc__panel .ds-btn--primary {
  background: var(--ds-surface-page);
  color: var(--ds-color-primary);
}
.ds-calc__panel .ds-btn--primary:hover {
  background: var(--ds-surface-raised);
}

/* Мобильные экраны: компактные паддинги полей и панели */
@media (max-width: 479px) {
  .ds-calc__form { padding: var(--ds-calc-form-pad, var(--ds-space-6)); }
  .ds-calc__panel { padding: var(--ds-calc-panel-pad, var(--ds-space-6)); }
  .ds-calc__total { font-size: var(--ds-fs-2xl); }
}

/* ═══ components/range/range.css ═══ */
/* f7c_design :: Ползунок. Требует tokens.css.
   Только нативный input[type=range] (клавиатура/скринридер работают из коробки).
   Заполнение трека — переменная --ds-range-fill (0–100%). Выставлять ТОЛЬКО через
   CSSOM из скрипта шаблона: el.style.setProperty('--ds-range-fill', pct+'%').
   Inline-атрибут style в разметке запрещён CSP сборки (style-src 'self').
   Дефолт 0% — без JS трек честно не показывает заливку (значение видно в output). */
.ds-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  /* accent-color дублирует канон (concept:341 input[type=range]{accent-color:var(--pink)}).
     Наш ползунок красится КАСТОМНО (градиент трека + ::thumb), поэтому эффект от него не
     зависит — замер «accent-color: auto» мерил не то свойство, где живёт цвет. Оставляем как
     страховку для движков, где кастомные псевдоэлементы не применились: там ползунок станет
     брендовым, а не системно-синим. */
  accent-color: var(--ds-range-color);
  border-radius: var(--ds-radius-full);
  background: linear-gradient(
    90deg,
    var(--ds-range-color) var(--ds-range-fill, 0%),
    var(--ds-border-default) var(--ds-range-fill, 0%)
  );
  outline: none;
}

.ds-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ds-range-color);
  border: 4px solid var(--ds-surface-page);
  box-shadow: var(--ds-shadow-md);
  cursor: pointer;
}

.ds-range::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ds-range-color);
  border: 4px solid var(--ds-surface-page);
  box-shadow: var(--ds-shadow-md);
  cursor: pointer;
}

.ds-range:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: 4px;
}

/* Подпись текущего значения (кладётся в label рядом с текстом) */
.ds-range__value {
  float: right;
  font-weight: var(--ds-fw-bold);
  color: var(--ds-color-primary);
  font-variant-numeric: tabular-nums;
}

/* Опциональная шкала min/max по краям трека */
.ds-range__scale {
  display: flex;
  justify-content: space-between;
  margin-top: var(--ds-space-1);
  font-size: var(--ds-fs-xs);
  color: var(--ds-text-muted);
  font-variant-numeric: tabular-nums;
}

/* ═══ components/segment/segment.css ═══ */
/* f7c_design :: Сегментированный переключатель. Требует tokens.css.
   Состояние — aria-pressed (кнопки) либо checked (radio-вариант в обвязке шаблона).
   Закрывает и точку «табы вариантов калькулятора». */
.ds-segment {
  /* 🔴 РЯД РАВНЫХ СТУПЕНЕЙ ИЛИ ПЕРЕНОС — РЕШАЕТ БРЕНД (15.08, наряд integrator-c2).
     Замер канона: набор ступеней цены — `630×54`, **nowrap, пять детей ПО 121** (равные доли).
     У нас: перенос включён, дети по содержимому — 277 / 207 / 238, ряд ломается.
     ⚠ Это тот же класс, что плитки героя в `v0.149.0`: **ряд, выровненный содержимым, —
     не выровненный ряд**; правка текста подписи двигала бы ширину ступени.
     Умолчания прежние (`wrap`, ширина по содержимому) — izibuh и tezum не двигаются. */
  display: inline-flex;
  flex-wrap: var(--ds-segment-wrap, wrap);
  /* Дорожка — РОЛИ (17.08, табы калькулятора канона `.ctabs`): у канона свой фон, зазор 5,
     поля 5 и отбивка снизу 6. Умолчания прежние. */
  gap: var(--ds-segment-gap, 2px);
  background: var(--ds-segment-track-bg, var(--ds-surface-raised));
  border-radius: var(--ds-radius-full);
  padding: var(--ds-segment-track-pad, 4px);
  margin-bottom: var(--ds-segment-track-mar-bottom, 0);
  /* сброс UA-стилей для fieldset-варианта (radio-группа) */
  border: none;
  margin: 0;
  min-width: 0;
}

.ds-segment__btn {
  /* доля ряда: `1 1 0` даёт равные ступени, умолчание — прежняя ширина по содержимому */
  flex: var(--ds-segment-item-flex, 0 1 auto);
  min-width: 0;
  font: inherit;
  /* Кегль и вес подписи — РОЛИ: у канонного таба 13/800 против нашей шкалы. Умолчания прежние.
     🔴 Объявил я их раньше, чем соединил, — гейт «объявлено и не соединено» покраснел на обоих
     и был прав: два токена лежали в бренде и не читались ни одним `var()`. */
  font-size: var(--ds-segment-size, var(--ds-fs-sm));
  font-weight: var(--ds-segment-weight, var(--ds-fw-semibold));
  /* 🔴 ЦВЕТ НЕАКТИВНОЙ ПОДПИСИ — СВОЙ ТОКЕН, А НЕ ОБЩИЙ ПРИГЛУШЁННЫЙ. У канона это отдельная
     роль `--ctab-fg` со СВОЕЙ парой по темам (#5A6270 / #C4CABD) — она не равна ни вторичному
     тексту, ни приглушённому. Умолчание прежнее. */
  color: var(--ds-segment-ink, var(--ds-text-secondary));
  background: transparent;
  border: none;
  border-radius: var(--ds-radius-full);
  padding: var(--ds-segment-btn-pad, var(--ds-space-2) var(--ds-space-4));
  /* Двухстрочная подпись (заголовок + цена-подсказка) — состав РАЗМЕТКИ, но раскладку под неё
     даёт роль: колонка с зазором 1 и интерлиньяжем 1.25 у канона. Умолчание — прежняя строка. */
  display: var(--ds-segment-btn-display, inline-block);
  flex-direction: column;
  align-items: center;
  row-gap: var(--ds-segment-btn-row-gap, 0);
  line-height: var(--ds-segment-btn-lh, inherit);
  white-space: var(--ds-segment-btn-wrap, normal);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.ds-segment__btn:hover { color: var(--ds-text-primary); }

.ds-segment__btn[aria-pressed="true"],
.ds-segment__btn[aria-selected="true"],
.ds-segment__btn:has(input:checked) {
  /* 🔴 15.08: активная ступень — ТОКЕНЫ, а не один фон. Канон forbion выбирает активную
     кнопку калькулятора НЕ заливкой, а поверхностью выделения: #EDF6E3, чернила #1D1D1B
     и рамка 1px primary. Раньше цвет чернил был жёстко `on-primary` — то есть приём
     «сплошная заливка» был вшит, и бренд мог сменить только фон, получив белый текст
     на светлом. Умолчания прежние: izibuh и tezum не двигаются. */
  background: var(--ds-segment-active-bg);
  color: var(--ds-segment-active-ink, var(--ds-color-on-primary));
  /* ⚠ Обводка — ВНУТРЕННЕЙ тенью, а не `border`: у базовой кнопки рамки нет, и добавление
     настоящей сдвинуло бы раскладку на 2px у всех брендов. Кольцо не занимает места. */
  box-shadow:
    var(--ds-segment-active-shadow, inset 0 0 0 1px var(--ds-segment-active-border, transparent));
}

/* Вторая строка подписи — цена-подсказка. У канона свой кегль/вес и СВОЙ цвет в активном табе
   (не наследует чернила активной). Разметку даёт шаблон, роли — здесь. */
.ds-segment__btn > i {
  font-style: normal;
  font-size: var(--ds-segment-hint-size, inherit);
  font-weight: var(--ds-segment-hint-weight, inherit);
  font-variant-numeric: tabular-nums;
  color: inherit;
}
.ds-segment__btn[aria-pressed="true"] > i,
.ds-segment__btn[aria-selected="true"] > i,
.ds-segment__btn:has(input:checked) > i {
  color: var(--ds-segment-active-hint-ink, inherit);
}

/* Radio-вариант (прогрессивное улучшение: работает без JS):
   label.ds-segment__btn с визуально скрытым input[type=radio] внутри */
.ds-segment__btn input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.ds-segment__btn:has(input:focus-visible) {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: 2px;
}

.ds-segment__btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.ds-segment__btn:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: 2px;
}

/* ═══ components/badge/badge.css ═══ */
/* f7c_design :: Бейдж. Требует tokens.css. Имена вариантов нейтральные. */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold);
  line-height: 1;
  border-radius: var(--ds-radius-full);
  /* Вертикальное поле метки — РОЛЬ (17.08, `#ladder`): канон даёт 4 при высоте 20, у нас 8
     при 27. Горизонтальное НЕ трогаю — я его не мерил, а менять неизмеренное заодно значит
     выдать догадку за замер. Умолчание = прежнее значение, свойство здесь было и раньше. */
  padding: var(--ds-badge-pad-y, var(--ds-space-2)) var(--ds-space-3);
  background: var(--ds-surface-raised);
  color: var(--ds-text-secondary);
}

/* 🔴 ЧИП ВЫДЕЛЕНИЯ — роль канона «входит / выбрано» (15.08). В каноне это 16 чипов
   конфигураций внутри выделенной строки тарифа: подложка #EDF6E3, чернила #1D1D1B.
   ⚠ Базовый `.ds-badge` НЕ трогаю: он общий и служит другим ролям. Отдельный модификатор,
   как `.ds-compare__col--highlight`, — одна роль канона, один класс. */
/* 🔴 ТИПОГРАФИКА МЕТКИ ЖИВЁТ В БРЕНД-ПАКЕТЕ, А НЕ ЗДЕСЬ (15.08, регресс, нашёл c-tpl).
   Первая редакция объявила в ядре `font-size/text-transform/letter-spacing` с «нейтральными»
   фолбэками. Но раньше `.ds-badge` этих свойств НЕ СТАВИЛ ВОВСЕ — и у izibuh выигрывало то,
   что каскадировало снаружи. Появление свойства убило чужой каскад: капс и разрядка пропали.
   🔴 КЛАСС: ФОЛБЭК В НОВОМ ПРАВИЛЕ — НЕ УМОЛЧАНИЕ, А ПРИСВОЕНИЕ. «Умолчания прежние» верно
   про ЗНАЧЕНИЕ ТОКЕНА и неверно про РЕНДЕР: свойства, которого не было, теперь есть.
   ⇒ единственный способ ничего не присвоить чужому бренду — не объявлять в ядре. */
.ds-badge--highlight {
  background: var(--ds-surface-highlight);
  color: var(--ds-text-primary);
}

.ds-badge--ok {
  background: color-mix(in srgb, var(--ds-color-success) 14%, transparent);
  color: var(--ds-color-success);
}

.ds-badge--accent {
  background: color-mix(in srgb, var(--ds-color-accent) 14%, transparent);
  color: var(--ds-color-accent);
}

.ds-badge--inverse {
  background: color-mix(in srgb, var(--ds-color-on-primary) 18%, transparent);
  color: var(--ds-color-on-primary);
}

.ds-badge__icon {
  width: 0.9em;
  height: 0.9em;
  flex-shrink: 0;
}

/* ═══ components/chip/chip.css ═══ */
/*
 * f7c_design :: ПЛАВАЮЩИЙ ЧИП — пилюля поверх снимка интерфейса. Требует tokens/tokens.css.
 *
 * 🔴 ПОВОД И ОСНОВАНИЕ (15.08, решение integrator-c2 по приказу владельца).
 * Шесть чипов первого экрана были заменены двумя реквизитами (`ds-credential`) решением РОЛИ
 * (`c-copy` 13.08: три текста непроверяемы). Слова владельца на этот предмет я предъявить
 * не смог — и сказал об этом. Решение: **чипы возвращаются с их текстами, как в макете**.
 * Основание дословно: `.shot.rev` — **снимок ИНТЕРФЕЙСА клиента, иллюстрация панели, а не наше
 * обещание**; приказ «ровно как на макете, юр-аспекты игнорируем» перекрывает решение роли.
 *
 * ЗАМЕР КАНОНА (1440): фон `rgba(255,255,255,.72)` + `blur(12px)` · рамка 1px `#E2E8E4` ·
 * радиус 999 · padding 8/14 · 12px/700 · зазор до значка 8.
 * ⚠ Видны только шире 1250 — порог ставит ПОТРЕБИТЕЛЬ: компонент не знает, где его прячут.
 */
.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  padding: var(--ds-space-2) 14px;
  border-radius: var(--ds-radius-full);
  border: 1px solid var(--ds-chip-border, var(--ds-border-default));
  background: var(--ds-chip-bg, var(--ds-surface-raised));
  color: var(--ds-text-primary);
  font-size: var(--ds-chip-size, var(--ds-fs-xs));
  font-weight: var(--ds-fw-bold);
  white-space: nowrap;
}

@supports (backdrop-filter: blur(12px)) {
  .ds-chip--float {
    backdrop-filter: var(--ds-chip-blur, none);
    -webkit-backdrop-filter: var(--ds-chip-blur, none);
  }
}

/* 🔴 ЛЕСЕНКА, А НЕ СТОЛБИК. Шаг по вертикали 72 — общий; горизонталь у каждой ступени СВОЯ,
   и выравнивание их по левому краю было бы «аккуратнее макета», то есть расхождением.
   Позиции — доли ширины носителя, чтобы не зависеть от его пикселей:
   замер канона @1440 (носитель 486 шириной): 258 · 321 · 354 · 309 · 367 · 258 */
/* 🔴 КОНТРАКТ ПОЗИЦИОНИРОВАНИЯ — ПРОЦЕНТЫ СЧИТАЮТСЯ ОТ ПРАВОГО БЛОКА ЦЕЛИКОМ, НЕ ОТ КОЛОНКИ ЧИПОВ.
   Числа ниже сняты с канона и осмысленны ТОЛЬКО относительно `.shot` — правого блока первого
   экрана (**486 px @1366**), внутри которого у канона лежат И карточка цены, И шесть чипов.
   Замер канона: левые края чипов 258 · 321 · 354 · 309 · 367 · 258 из 486 = 53 · 66 · 73 · 64 · 75 · 53 %.

   🔴 ЖИВОЙ ДЕФЕКТ, ИЗ-ЗА КОТОРОГО КОНТРАКТ ЗАПИСАН (15.08, нашёл integrator-c2 на 1366):
   шаблон сделал контекстом позиционирования **колонку** чипов (`.tpl-schips`, **221 px**), а не
   правый блок. Проценты стали считаться от четверти нужной ширины, а сам чип шириной 206 —
   почти во всю колонку. Итог: `s4` вылезает за правый край страницы.
   Полоса замерена по ряду ширин, и она шире, чем видно с одного кадра:
       1250 → 0 · **1251 → 71** · 1300 → 47 · 1366 → 14 · 1392 → 1 · **1393 → 0** · 1440 → 0
   ⚠ ВАЖНО ДЛЯ ТОГО, КТО БУДЕТ ЧИНИТЬ: «прижать чипы внутрь блока» — НЕ решение.
   У канона они СПЕЦИАЛЬНО свисают наружу: третий чип кончается на 1271 при правом крае блока
   1227, то есть 44 px уходят в гаттер страницы. Клемпинг убил бы канонный вид.
   ⇒ Лечится КОНТЕКСТОМ (позиционирующий предок = правый блок целиком), а не числами здесь.
   Если контекст обязан остаться узким — проценты пересчитываются, и это отдельный замер
   с названной шириной блока, а не подгонка на глаз. */
/* 🔴 НОСИТЕЛЬ ПРОЦЕНТОВ ПОЛУЧАЕТ ИМЯ В СИСТЕМЕ, А НЕ ОПИСАНИЕ В КОММЕНТАРИИ (15.08).
   Находка `c-tpl`, и она про мой компонент: контракт был записан словами — «носитель 486» —
   а слово «носитель» потребитель прочёл как КОЛОНКУ чипов (221) и повесил `position:relative`
   на неё. `0.53 × 221` дало 415 от края блока вместо 258: лесенка ушла на 126 px за край
   своего же блока, а на 1251…1366 — за край окна. **Промах жил с `v0.154.0` и не показывался
   на широких, где снимают диффы.**
   ⇒ Класс мой же, «эталон учит употреблению»: **компонент, который позиционируется ДОЛЯМИ,
   обязан называть носителя ПРАВИЛОМ, а не прозой** — иначе выбор носителя остаётся актом
   толкования, и промах молчит.
   Поэтому носитель теперь класс: `.ds-chip-anchor` ставится на ПРАВЫЙ БЛОК первого экрана
   целиком (канонный `.shot`), внутри которого лежат И карточка цены, И лесенка. Проценты ниже
   сняты с канона именно от него: левые края 258 · 321 · 354 · 309 · 367 · 258 из 486. */
.ds-chip-anchor { position: relative; }

.ds-chip--float { position: absolute; z-index: 2; }

.ds-chip--s1 { left: 53%; top: 0; }
.ds-chip--s2 { left: 66%; top: 72px; }
.ds-chip--s3 { left: 73%; top: 144px; }
.ds-chip--s4 { left: 64%; top: 216px; }
.ds-chip--s5 { left: 75%; top: 288px; }
.ds-chip--s6 { left: 53%; top: 360px; }


/* Тот же случай, что `.ds-card__check-icon` (см. `components/card/card.css`): имя ДС без
   правила. Размер 24×24 приходит атрибутами манифеста, цвет — наследованием; забираю имя
   и записываю контракт, ничего не двигая. */
.ds-chip__icon {
  color: inherit;
}

/* ═══ components/credential/credential.css ═══ */
/* f7c_design :: РЕКВИЗИТ — проверяемый паспортный факт. Требует tokens.css и badge.css.
 *
 * ПОВОД (14.08). В каноне на первом экране шесть плавающих плиток: «Бэкап готов · 03:00»,
 * «Отчёт принят ФНС», «SQL · 12 мс», «Обновление 1С установлено»… Владелец сказал «нужны,
 * заводите» — а `c-copy` ответил третьим путём, и ответ был ЗАМЕРЕН, а не вкусовой:
 * наполнить их правдиво НЕЧЕМ. Аптайма своих цифр нет; SLA принят БЕЗ денежной компенсации,
 * значит безотказность обещать нельзя; latency и время бэкапа не мерим; «ИТС активен» —
 * состояние системы КЛИЕНТА. Его формулировка: заполнить их словами вместо замера —
 * «нарисовать приборную панель, к которой не подключены приборы».
 *
 * 🔴 РЕШЕНИЕ ВЛАДЕЛЬЦА: ставим ДВА СВОЙСТВА вместо состояний. Отсюда имя и вся форма.
 *
 * ПОЧЕМУ НЕ `status`, `live`, `pulse` И ПОЧЕМУ ЭТО НЕ ПРИДИРКА К СЛОВУ:
 * «состояние» утверждает измерение В МОМЕНТ ПОКАЗА. Реквизит утверждает ФАКТ, проверяемый
 * снаружи и не зависящий от часа. Назови компонент состоянием — и следующий добросовестно
 * доложит сюда пульсацию, время и индикатор, потому что имя ЭТОГО ПРОСИТ. Форма обещала бы
 * то, чего содержимое не несёт. Поэтому здесь НЕТ и не должно появиться: анимации «живости»,
 * относительного времени, точки-индикатора, слова «сейчас».
 *
 * 🔴 ИХ ДВА, А НЕ ШЕСТЬ — И ЭТО СВОЙСТВО ПРЕДМЕТА, А НЕ НЕДОДЕЛКА ВЁРСТКИ.
 * Третьего правдивого у нас нет. Если два элемента смотрятся пусто — это решается КОМПОЗИЦИЕЙ
 * (см. `--inline`, ширина колонки, соседство с ценой), а не третьим фактом.
 *
 * ⚠ ПАРА НОСИТЕЛЕЙ (пометка `c-copy`, пин `ac27451`): те же факты живут в блоке доверия ниже,
 * но в ДРУГОЙ РОЛИ — здесь ПАСПОРТ («кто это вообще»), там ВЫВОД («что из этого следует»).
 * Правишь текст здесь — проверь второй носитель. Формулировки сужены вместе с `c-legal`;
 * «сократить ради отсутствия дубля» = обменять юридическую точность на аккуратность списка.
 */
.ds-credentials {
  /* сброс списка: без него браузерный `padding-inline-start: 40px` сдвигает реквизиты внутрь
     колонки — замер на превью показал отступ ~40 px, которого нет ни в макете, ни в замысле */
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ds-space-2);
}

/* Ряд вместо столбца — когда колонка широкая и столбец из двух читается как обрубок списка. */
.ds-credentials--inline {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-2) var(--ds-space-3);
}

/* Реквизит наследует форму бейджа (пилюля + иконка) — новый тип был бы синонимом существующего.
 * Отличие только в РОЛИ: нейтральная поверхность, без успех-зелёного. `ds-badge--ok` здесь
 * запрещён намеренно: галочка и цвет успеха читаются как «проверено ТОЛЬКО ЧТО». */
.ds-credential {
  background: var(--ds-surface-raised);
  color: var(--ds-text-secondary);
  font-weight: var(--ds-fw-medium);
  border: 1px solid var(--ds-border-default);
  /* перенос допустим: ИНН и номер реестра рвать нельзя, а строка целиком — можно */
  line-height: var(--ds-lh-tight);
  text-align: left;
}

/* Номер — то, чем факт проверяют. Он обязан читаться, а не теряться в строке. */
.ds-credential__id {
  color: var(--ds-text-primary);
  font-weight: var(--ds-fw-bold);
  white-space: nowrap;
}

.ds-credential__icon {
  color: var(--ds-text-muted);
}

/* ═══ components/checkbox/checkbox.css ═══ */
/* f7c_design :: Чекбокс (точка согласия 152-ФЗ и др.). Требует tokens.css.
   Нативный input[type=checkbox] — a11y из коробки; стилизация accent-color. */
.ds-checkbox {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-3);
  /* 🔴 КЕГЛЬ ОТ ПОТРЕБЛЕНИЯ, А НЕ ОТ СПИСКА ИМЁН (наряд integrator-c2, 15.08).
     Прошлая правка закрыла `.ds-field__label` — ровно то, что было перечислено в наряде, —
     и внутри одной формы вышел разнобой: подписи полей 13, подписи чекбоксов 14.
     Замер канона: подписи переключателей в калькуляторе **13**, как и всё внутри опции.
     ⚠ Это наш класс «гейт строй от ПОТРЕБЛЕНИЯ, а не от списка имён», приложенный к ПРАВКЕ:
     список закрывает то, что в списке. Поэтому кегль подписи здесь берёт тот же токен,
     что и подпись поля, — величина одна, значит и источник один. */
  font-size: var(--ds-field-label-size, var(--ds-fs-sm));
  color: var(--ds-text-secondary);
  cursor: pointer;
}

/* 🔴 СНОСКА СОГЛАСИЯ — ОТДЕЛЬНАЯ РОЛЬ, А НЕ ПОДПИСЬ КОНТРОЛА (наряд integrator-c2, 15.08).
   Замер канона: подписи переключателей в калькуляторе **13**, а согласие на ПДн в форме
   заявки — **12** (узел `.pd`). Это разные роли: подпись называет контрол, сноска несёт
   юридическое утверждение. Свести их к одному числу мы за смену чуть не сделали четырежды,
   поэтому здесь отдельный токен, а не «заодно 13». Умолчание = кегль подписи. */
.ds-checkbox--note {
  font-size: var(--ds-consent-size, var(--ds-field-label-size, var(--ds-fs-sm)));
}

.ds-checkbox input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  flex-shrink: 0;
  accent-color: var(--ds-color-primary);
  cursor: pointer;
}

.ds-checkbox input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: 2px;
}

.ds-checkbox a {
  color: var(--ds-text-link);
  text-decoration: underline;
}

/* На градиентной панели/тёмной секции */
.ds-checkbox--inverse {
  color: color-mix(in srgb, var(--ds-color-on-primary) 80%, transparent);
}
.ds-checkbox--inverse a { color: var(--ds-color-on-primary); }

/* ═══ components/switch/switch.css ═══ */
/* f7c_design :: ТУМБЛЕР. Требует tokens.css.
 *
 * ПОВОД (17.08, наряд владельца Н5): в калькуляторе канона шесть тумблеров, у нас на их месте
 * галочки. `c-tpl` упирался в отсутствие компонента — класс `.ds-switch` уже упоминался в
 * раскладке ряда (`components/form/form.css`), но САМОГО КОМПОНЕНТА НЕ БЫЛО НИ СТРОКИ.
 * ⚠ То есть правило ссылалось на класс, за которым ничего не стоит: раскладка «знала» про
 * тумблер, вид не существовал. Гейты этого не видят — они судят объявленное, а не пропущенное.
 *
 * СОСТОЯНИЕ — `aria-pressed`, как у канона (`<button class="sw" aria-pressed="false"><i></i>`).
 * 🔴 Ядро НЕ владеет ролью: `aria-pressed` ставит разметка, здесь только чтение состояния.
 * Разметка обязана нести `<i>` внутри — это ручка; без неё тумблер будет пустой дорожкой.
 *
 * ⚠ ХОД РУЧКИ — НЕ ОТДЕЛЬНОЕ ЧИСЛО, А СЛЕДСТВИЕ: ширина − 2×поля − ручка. У канона
 * 46 − 6 − 20 = 20, и его `translateX(20px)` совпадает с этим ровно. Умолчание считается
 * формулой, чтобы бренд, сменивший размер, не получил уехавшую ручку молча.
 */
.ds-switch {
  position: relative;
  flex: none;
  border: 0;
  cursor: pointer;
  width: var(--ds-switch-width, 42px);
  height: var(--ds-switch-height, 24px);
  padding: var(--ds-switch-pad, 2px);
  border-radius: var(--ds-radius-full);
  background: var(--ds-switch-track-bg, var(--ds-border-default));
  box-shadow: var(--ds-switch-track-shadow, none);
  transition: background 0.25s ease, box-shadow 0.25s ease;
}

.ds-switch > i {
  display: block;
  width: var(--ds-switch-knob, 20px);
  height: var(--ds-switch-knob, 20px);
  border-radius: 50%;
  background: var(--ds-switch-knob-bg, var(--ds-surface-page));
  box-shadow: var(--ds-switch-knob-shadow, none);
  transition: transform 0.25s cubic-bezier(.25, .6, .25, 1), background 0.25s ease;
}

.ds-switch:hover { box-shadow: var(--ds-switch-hover-shadow, var(--ds-switch-track-shadow, none)); }

.ds-switch:focus-visible {
  outline: 2px solid var(--ds-color-accent);
  outline-offset: 2px;
}

.ds-switch[aria-pressed="true"] {
  background: var(--ds-switch-on-bg, var(--ds-color-primary));
  box-shadow: var(--ds-switch-on-shadow, none);
}

.ds-switch[aria-pressed="true"] > i {
  transform: translateX(var(--ds-switch-travel,
    calc(var(--ds-switch-width, 42px) - 2 * var(--ds-switch-pad, 2px) - var(--ds-switch-knob, 20px))));
  background: var(--ds-switch-on-knob-bg, var(--ds-switch-knob-bg, var(--ds-surface-page)));
}

@media (prefers-reduced-motion: reduce) {
  .ds-switch, .ds-switch > i { transition: none; }
}

/* ═══ components/state/state.css ═══ */
/* f7c_design :: СЛОВАРЬ СОСТОЯНИЙ ДАННЫХ. Требует tokens.css.
 *
 * ПОВОД (17.08, наряд владельца по ЛК 1С). У кабинета появился настоящий бэкенд, и ячейка A
 * (`ops-ai`) прислала описание API с отдельной просьбой, дословно: «мы потратили заметную часть
 * работы на то, чтобы НЕ ВЫДАВАТЬ НЕПРОВЕРЕННОЕ ЗА ОТСУТСТВУЮЩЕЕ — пожалуйста, сохраните это
 * в интерфейсе».
 *
 * 🔴 ЭТО НЕ НАБОР СТИЛЕЙ, А СЛОВАРЬ ИСТИНЫ. Интерфейс обязан УМЕТЬ СКАЗАТЬ «не знаю» так, чтобы
 * это не читалось как «ноль». Без такого словаря у верстальщика есть ровно один способ показать
 * отсутствие числа — написать `0`, и он его напишет: пустая ячейка выглядит недоделкой.
 * ⚠ Различия ниже — НЕ оттенки одного «нет данных». Каждое отвечает на свой вопрос:
 *     `--unknown`  спрашивали, не ответили      (`stale: true`) — сеансы и лицензии как «?»
 *     `--pending`  ещё не спрашивали            (`backups_known:false`, `exists:null`) — прочерк
 *     `--absent`   спросили, ответ «нет»        (`exists:false`) — предупреждение, НЕ прочерк
 * Свести их в одно «—» значит стереть три разных факта в один; свести к «0» — солгать.
 *
 * 📌 Совпадение, которое стоит записи: требование пришло СНАРУЖИ и повторило наше внутреннее
 * правило «пустота не имеет якоря» дословно. Значит это не идиосинкразия ячейки.
 */

/* ── ЗНАЧЕНИЕ, КОТОРОГО НЕТ: три разных состояния, три разных знака ──────────────────────── */
.ds-value--unknown,
.ds-value--pending,
.ds-value--absent {
  font-variant-numeric: tabular-nums;   /* чтобы «?» и «—» не сдвигали колонку чисел */
}

/* спрашивали, не ответили: показываем ЗНАК ВОПРОСА, а не ноль */
.ds-value--unknown {
  color: var(--ds-value-unknown-ink, var(--ds-text-secondary));
}
.ds-value--unknown::after { content: var(--ds-value-unknown-mark, "?"); }

/* ещё не спрашивали: прочерк. Это НЕ «нет», это «не собирали» */
.ds-value--pending {
  color: var(--ds-value-pending-ink, var(--ds-text-muted, var(--ds-text-secondary)));
}
.ds-value--pending::after { content: var(--ds-value-pending-mark, "—"); }

/* спросили, ответ «нет»: это уже ФАКТ, и он тревожный — копий действительно нет */
.ds-value--absent {
  color: var(--ds-value-absent-ink, var(--ds-color-warning, var(--ds-text-primary)));
  font-weight: var(--ds-value-absent-weight, var(--ds-fw-bold));
}

/* ── ПЛАШКА НЕПОЛНОТЫ: `degraded` обязана быть ВИДИМОЙ, а не примечанием ────────────────────
 * Источник называет её «обязательная видимая плашка»: список баз может быть НЕПОЛНЫМ, и человек,
 * не увидевший этого, сделает вывод по неполному списку и будет прав в своих действиях и неправ
 * по факту. Поэтому плашка — не украшение состояния, а условие правильного чтения таблицы. */
.ds-notice--incomplete {
  display: flex;
  gap: var(--ds-space-3);
  align-items: flex-start;
  padding: var(--ds-notice-pad, var(--ds-space-3) var(--ds-space-4));
  border-radius: var(--ds-radius-card);
  background: var(--ds-notice-incomplete-bg, var(--ds-surface-raised));
  color: var(--ds-notice-incomplete-ink, var(--ds-text-primary));
  box-shadow: inset 0 0 0 1px var(--ds-notice-incomplete-edge, var(--ds-border-default));
}
/* 🔴 МЕСТО ПОД ПРИЧИНУ — отдельным узлом, потому что источник отдаёт её отдельным полем.
 * API даёт ПАРУ: `degraded` (да/нет) и `degraded_reason` (текст). Плашка без второго узла
 * заставляет верстальщика либо вклеить причину в общую фразу, либо выбросить её — и выбросит,
 * потому что фраза уже написана. Сверка макета ЛК v0.7 (17.08) показала ровно это: плашка
 * «Часть данных не подтверждена» есть, причины в ней нет.
 * ⚠ Узел не прячется при пустой причине — это забота потребителя: пустой узел здесь честен
 * (источник может прислать `degraded_reason: ""`), а спрятанный молча съел бы непустую строку. */
.ds-notice__reason {
  display: block;
  margin-top: var(--ds-notice-reason-gap, var(--ds-space-1));
  font-size: var(--ds-notice-reason-size, var(--ds-fs-sm));
  color: var(--ds-notice-reason-ink, var(--ds-text-secondary));
}

/* ── ВОЗРАСТ ДАННЫХ. Показывается ВСЕГДА, а не только когда «старые»: иначе свежесть
 * становится невидимым свойством, и человек не знает, на что смотрит. */
.ds-freshness {
  font-size: var(--ds-freshness-size, var(--ds-fs-xs));
  color: var(--ds-freshness-ink, var(--ds-text-secondary));
  font-variant-numeric: tabular-nums;
}

/* ── ДВА РАЗНЫХ ПУСТО. Тоже не оттенки: раздел, которого ЕЩЁ НЕТ в системе, и раздел, который
 * пуст, потому что у клиента ничего нет. Первый — обещание, второй — факт о клиенте.
 * 🔴 Красивая пустая страница без этого различия УЧИТ клиента ждать функцию, которой нет.
 * Поэтому у «появится» тон служебный и сдержанный, у «пусто» — обычный текст раздела. */
.ds-empty--planned {
  color: var(--ds-empty-planned-ink, var(--ds-text-secondary));
  border: 1px dashed var(--ds-empty-planned-edge, var(--ds-border-default));
  border-radius: var(--ds-radius-card);
  padding: var(--ds-empty-pad, var(--ds-space-6));
  text-align: center;
}
.ds-empty--none {
  color: var(--ds-empty-none-ink, var(--ds-text-secondary));
  padding: var(--ds-empty-pad, var(--ds-space-6));
  text-align: center;
}

/* ── ТРИ ПРИЧИНЫ ОТКАЗА ВО ВХОДЕ — И ЭТО ТОЖЕ СЛОВАРЬ, А НЕ ЭКРАН ────────────────────────────
 * Источник API (`mail/1735` §3): отказ по логину/паролю отдаётся ВСЕГДА одной формулировкой —
 * иначе перебором выясняется, какие логины существуют. После 8 неудач учётка встаёт на паузу,
 * и об этом говорится ПРЯМО («вход приостановлен на N мин»); утечки нет, потому что пауза
 * срабатывает только после ВЕРНОГО логина.
 *
 * 🔴 ТРИ СОСТОЯНИЯ, ПОТОМУ ЧТО ТРИ РАЗНЫХ ОТВЕТА НА «ЧТО МНЕ ДЕЛАТЬ»:
 *     --input     дело во введённом      → повторяй СЕЙЧАС
 *     --timed     дело во времени        → повторяй ПОЗЖЕ, и вот когда
 *     --service   дело не в тебе         → ждать нечего, чинят на той стороне (503/502)
 * Свести их в один «красный текст» значит отнять у человека ответ на этот вопрос.
 *
 * 🔴 И ГЛАВНОЕ ОГРАНИЧЕНИЕ, КОТОРОЕ ЗДЕСЬ ВЫРАЖЕНО КОНСТРУКЦИЕЙ, А НЕ ПРОСЬБОЙ:
 * у `--input` НЕТ И НЕ БУДЕТ слота под «какое поле неверно». Постоянство формулировки —
 * свойство БЕЗОПАСНОСТИ, а не стиля: стоит дать место под уточнение, и следующий добросовестно
 * напишет «пользователь не найден» — и перебор логинов заработает через различие текстов.
 * Роль, не имеющая места для уточнения, не вводит в этот соблазн.
 */
.ds-denial {
  display: flex;
  gap: var(--ds-space-2);
  align-items: flex-start;
  font-size: var(--ds-denial-size, var(--ds-fs-sm));
  padding: var(--ds-denial-pad, var(--ds-space-2) var(--ds-space-3));
  border-radius: var(--ds-radius-md, var(--ds-radius-card));
}
/* дело во введённом: сообщение постоянное, тон — ошибка ввода
 * 🔴 17.08, ПОЙМАНО ПРИ ЧТЕНИИ СВОЕГО ЖЕ ФАЙЛА: здесь стояло `--ds-color-danger`, а такого
 * токена в системе НЕТ ВООБЩЕ (единственные два вхождения — это правило и его копия в index.css).
 * Роль молча падала в третий фолбэк `--ds-text-primary`, и самый ответственный вид отказа
 * набирался ОБЫЧНЫМ текстом. Ни один гейт не краснел: цепочка `var()` синтаксически безупречна,
 * а брендовая тема ни одного `--ds-denial-*` не объявляет, так что подмены не видно и на рендере.
 * Верное имя — `--ds-color-error`. Класс тот же, что «нейтральный фолбэк = присвоение». */
.ds-denial--input {
  color: var(--ds-denial-input-ink, var(--ds-color-error, var(--ds-text-primary)));
  background: var(--ds-denial-input-bg, transparent);
}
/* дело во времени: несёт счётчик, поэтому цифры не должны прыгать при обратном отсчёте */
.ds-denial--timed {
  color: var(--ds-denial-timed-ink, var(--ds-text-primary));
  background: var(--ds-denial-timed-bg, var(--ds-surface-raised));
  font-variant-numeric: tabular-nums;
}
/* дело не в тебе: тон служебный, без вины пользователя */
.ds-denial--service {
  color: var(--ds-denial-service-ink, var(--ds-text-secondary));
  background: var(--ds-denial-service-bg, var(--ds-surface-raised));
}

/* ── МЕЛКАЯ ЮРИДИЧЕСКАЯ СТРОКА (оговорка об оферте) ─────────────────────────────────────────
 * Замер канона `#calc .offer-note`: кегль **9.5**, интерлиньяж 13.775, **ширина ограничена 480**,
 * высота 41. У нас на том же месте 14/21 при ширине 704 и высоте 63.
 * 🔴 РАЗНИЦА НЕ В ОТСТУПАХ, А В РОЛИ: у канона это **мелкий шрифт** — юридическая сноска,
 * которую читают, когда ищут; у нас она набрана как обычный текст секции и потому спорит с
 * содержимым за внимание. Ограничение ширины — часть роли, а не украшение: длинная строка
 * мелким кеглем нечитаема, канон держит её в 480.
 * ⚠ Умолчания нейтральные: без брендовых значений роль ничего не меняет. */
.ds-legal-note {
  /* 🔴 ОТСТУПЫ — ТОЖЕ РОЛЬ (18.08, уточнение предсказания по КОРОБКЕ, а не по узлу).
   * Роль закрывала кегль, интерлиньяж, ширину и цвет — и оставляла отступы потребителю.
   * Замер коробки в потоке: канон `.offer-note.rev` несёт `14 / 9.5` = 23.5, шаблонный
   * `.tpl-section-note` — `16 / 0` = 16 ⇒ разница **7.5 px, постоянная на всех ширинах**.
   * Без этого токена правка кегля переводит нас из «+14.5 выше канона» в «−7.5 ниже» —
   * ПЕРЕЛЁТ ЧЕРЕЗ НОЛЬ, второй такой за вечер: величина ЗНАКОВАЯ, и «починили» не равно «расхождение упало».
   * ⚠ Умолчание `0`: без брендового значения роль отступов не навязывает, и потребитель,
   * применивший её к своему узлу, ничего не теряет. */
  margin: var(--ds-legal-note-mar, 0);
  font-size: var(--ds-legal-note-size, inherit);
  line-height: var(--ds-legal-note-lh, inherit);
  max-width: var(--ds-legal-note-max, none);
  color: var(--ds-legal-note-ink, var(--ds-text-secondary));
}

/* ── СПИСОК С СОБСТВЕННЫМ МАРКЕРОМ ──────────────────────────────────────────────────────────
 * Замер канона (`#calc .srvinc`): `list-style: none`, поле 0, зазор строк 8, а маркер —
 * `::before` **13×13** с радиальным градиентом (та же сфера, что у маркера панели доверия).
 * У нас: `list-style: disc`, поле 16 — то есть **браузерные кружки по умолчанию**.
 * 🔴 Это видно глазом и не ловится ни одним нашим числом: высоты сходятся, а список выглядит
 * чужим. Родня сегодняшнего класса «величина сошлась, а роль другая».
 * ⚠ Маркер ССЫЛАЕТСЯ на готовый токен подложки значка — замер показал ту же формулу, что у
 * панели доверия; копия разъехалась бы при первой правке одной из сторон. */
.ds-marked-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  row-gap: var(--ds-marked-list-gap, var(--ds-space-2));
}
.ds-marked-list > li {
  position: relative;
  padding-left: var(--ds-marked-list-room, var(--ds-space-6));
}
.ds-marked-list > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--ds-marked-list-mark-top, 0);
  width: var(--ds-marked-list-mark, var(--ds-space-3));
  height: var(--ds-marked-list-mark, var(--ds-space-3));
  border-radius: var(--ds-radius-full);
  background: var(--ds-marked-list-mark-bg, var(--ds-color-primary));
  box-shadow: var(--ds-marked-list-mark-inner, none);
}

/* ── ГОТОВНОСТЬ РАЗДЕЛА: ТРИ значения, потому что двух не хватило ────────────────────────────
 * ПОВОД (17.08, сверка макета ЛК v0.7 с API `mail/1735`). В макете такая метка уже была, но
 * ДВУЗНАЧНАЯ: `live` («API») либо `soon` («скоро» + плашка «бэкенд появится позже»). На объекте
 * из десяти разделов двузначность соврала дважды, и оба раза — в сторону обещания:
 *   · «Тарифы» получили «появится позже», хотя РАБОТАЮТ СЕГОДНЯ: их источник — наш прайс,
 *     бэкенд им не нужен вовсе. Клиенту велено ждать того, что у него уже есть;
 *   · «Поддержка» получила то же, хотя её контур ЧУЖОЙ (ячейка S). «Появится» пообещало НАШ
 *     бэкенд там, где не решено даже, чей это контур.
 * 🔴 ЗНАЧЕНИЯ ОТВЕЧАЮТ НА ВОПРОС КЛИЕНТА «могу я этим пользоваться сейчас», а не на наш вопрос
 * «чей бэкенд»:
 *     --live       да, работает здесь
 *     --planned    нет, появится           (единственное значение, которое ОБЕЩАЕТ)
 *     --elsewhere  да, но не здесь         (ведёт в другую систему; обещания не несёт)
 * ⚠ ЧЕМ обеспечен раздел — живым API, прайсом, заявкой — в КЛАССЕ не кодируется: это наша
 * внутренняя причина, она живёт в тексте метки. Закодируй мы её классом, метка снова начала бы
 * отвечать не на тот вопрос, а верстальщик выбирал бы класс по нашему устройству, а не по тому,
 * что увидит человек.
 *
 * 🔴 И ГРАНИЦА ЕДИНИЦЫ, ради которой роль вообще заведена: метка ставится ТУДА, ЧТО ОНА
 * ОПИСЫВАЕТ. В v0.7 она стояла на РАЗДЕЛЕ, а обеспеченность живёт на КАРТОЧКЕ: «Главная»
 * помечена «API», и значок читается как «все шесть плиток живые» — живая одна. Ставя метку
 * на контейнер, ты утверждаешь её про каждый элемент внутри. */
.ds-readiness {
  display: inline-flex;
  align-items: center;
  flex: none;
  padding: var(--ds-readiness-pad, var(--ds-space-1) var(--ds-space-2));
  border-radius: var(--ds-radius-full);
  font-size: var(--ds-readiness-size, var(--ds-fs-xs));
  font-weight: var(--ds-readiness-weight, var(--ds-fw-bold));
  letter-spacing: var(--ds-readiness-track, 0);
  white-space: nowrap;
}
.ds-readiness--live {
  color: var(--ds-readiness-live-ink, var(--ds-color-success));
  background: var(--ds-readiness-live-bg, transparent);
}
.ds-readiness--planned {
  color: var(--ds-readiness-planned-ink, var(--ds-color-warning));
  background: var(--ds-readiness-planned-bg, transparent);
}
.ds-readiness--elsewhere {
  color: var(--ds-readiness-elsewhere-ink, var(--ds-text-secondary));
  background: var(--ds-readiness-elsewhere-bg, transparent);
}

/* ── ВТОРОЙ СЧЁТЧИК ПРИ ЧИСЛЕ: «и ещё столько-то, но других» ─────────────────────────────────
 * ПОВОД тот же (§5 письма): `sessions` — только ЛЮДИ, `sessions_service` — фоновые задания и
 * веб-сервисы. Источник просит служебные в основной счётчик НЕ включать и отдельно предупреждает:
 * числа сеансов и лицензий «законно расходятся, не подавайте это как ошибку».
 * 🔴 Без места под второе число верстальщик выберет одно из двух, и оба выбора плохи: сложив,
 * он соврёт про людей; умолчав, он оставит расхождение с лицензиями необъяснённым — и человек
 * решит, что кабинет считает неправильно. Роль существует, чтобы у второго числа БЫЛО МЕСТО.
 * ⚠ Приписка намеренно не самостоятельна: она читается только рядом с основным числом и
 * наследует его выравнивание — иначе колонка чисел в таблице баз разъедется. */
.ds-count__aside {
  font-size: var(--ds-count-aside-size, var(--ds-fs-xs));
  font-weight: var(--ds-count-aside-weight, var(--ds-fw-regular));
  color: var(--ds-count-aside-ink, var(--ds-text-secondary));
  font-variant-numeric: tabular-nums;
}

/* ═══ components/stepper/stepper.css ═══ */
/* f7c_design :: Степпер −/+ для числовых полей. Требует tokens.css.
   Внутри нативный input[type=number]: без JS работают ввод с клавиатуры и
   стрелки Up/Down; кнопки −/+ подключает скрипт шаблона (прогрессивное
   улучшение). Спинеры браузера скрыты — их роль играют кнопки. */
/* 🔴 align-self: start — против растяжения РОДИТЕЛЕМ. Дефект нашёл владелец глазами
   («пустые тёмные прямоугольники» справа от степперов), диагностировал замером c-ops-2:
   .ds-field — flex-колонка без align-items, дефолт stretch тянул плашку степпера со 152px
   до 257px, и лишние 105px фона (radius 9999, surface-raised) читались как пустые пятна.
   ⚠ inline-flex НЕ защищает: потомок flex-контейнера блокифицируется, и stretch тянет его
   по поперечной оси. Это ровно тот случай, где свойство объявлено, а эффекта нет — только
   отменяет его не каскад, а РОДИТЕЛЬ.
   Канон concept-v2.3.0:340 держит `.stepper{display:inline-flex}` — ширина по содержимому,
   то есть дефект вносило ядро, а не замысел.
   ПОЧЕМУ ТОЧЕЧНО, А НЕ align-items У .ds-field (рекомендация c-ops-2, проверил и согласен):
   в .ds-field лежат также .ds-input и .ds-textarea, и они ДОЛЖНЫ тянуться на всю ширину поля.
   Общее правило починило бы степпер и сломало ввод — а вернувшись однажды, дефект пришёл бы
   уже как «поле ввода схлопнулось».
   Проверено на 390: медиазапросов ни у .ds-field, ни у .ds-stepper нет, поведение то же, и
   канон тоже не делает исключения по ширине — степпер остаётся по содержимому на всех экранах.
   align-self, а не width:fit-content: первое действует ТОЛЬКО внутри flex-родителя и ничего
   не меняет, если степпер стоит отдельно; второе применялось бы всегда. */
.ds-stepper {
  align-self: start;
  display: inline-flex;
  align-items: stretch;
  border-radius: var(--ds-radius-full);
  padding: var(--ds-stepper-pad, 4px);
  background: var(--ds-surface-raised);
}

.ds-stepper__btn {
  font: inherit;
  font-size: var(--ds-stepper-btn-font, var(--ds-fs-lg));
  font-weight: var(--ds-fw-bold);
  /* 🔴 `line-height: 1` — канонное значение (замер `#calc .stp button`: 17px/17px).
     Геометрия не двигается: глиф и так центрирован в кнопке фиксированных 32×32.
     ⚠ И ЧЕСТНО О ТОМ, ЧЕГО ЭТА СТРОКА НЕ ЧИНИТ. Я поставил её, объяснив себе, что
     унаследованные 27 дают `scrollHeight 38` при `clientHeight 32` и пачкают зонды.
     ЗАМЕР ПОСЛЕ ПРАВКИ: 38 осталось. Значит и по высоте, и по ширине лишнее даёт
     `::before` — зона нажатия 44×44, расширенная НАРУЖУ (см. ниже), а не интерлиньяж.
     Объяснение было правдоподобным и неверным; оставляю обе половины, чтобы следующий
     не «чинил» интерлиньяж снова.
     ⇒ ВЫВОД ДЛЯ ЗОНДОВ: признак «срез = scroll минус client» на узле с АБСОЛЮТНЫМ потомком
     ложен ПО ПОСТРОЕНИЮ — он мерит вылет декорации, а не обрезку текста. Три «среза +12»
     в наших ночных прогонах — это здесь, и дефектом они не были ни разу. */
  line-height: 1;
  /* text-link, не color-primary: тот же класс, что ds-btn--secondary. do-not-flip primary
     на прозрачном фоне нечитаем в тёмной (2.52). text-link флипается, светлую не меняет. */
  position: relative;
  color: var(--ds-text-link);
  background: var(--ds-surface-page);
  border: none;
  /* 🔴 РАЗМЕР КНОПКИ — ТОКЕН (разбор шага 2 калькулятора, 15.08). Замер канона: степпер
     целиком **32** (`.stp`), у нас 48 = 40 кнопка + 2×4 поля контейнера. Разница 16 на каждой
     опции со степпером, и она **не типографская**: кегль мы уже свели к 13, а высота держится
     контролом. Умолчание прежнее — izibuh и tezum не двигаются. */
  width: var(--ds-stepper-btn, 40px);
  height: var(--ds-stepper-btn, 40px);
  border-radius: var(--ds-radius-full);
  cursor: pointer;
  transition: transform .12s var(--ds-motion-ease);
}

/* 🔴 ЗОНА КАСАНИЯ 44×44 при видимом размере 40×40 (наряд integrator-c по замеру c-ops-2:
   коробка была 42×60, ширина < 44 — минимума тач-таргета; на мобильном это попадание пальцем,
   а не косметика). Канон-вид не трогаем: круг остаётся 40, зону добирает ПРОЗРАЧНЫЙ ::before.
   ⚠ Расширяем НАРУЖУ, а не симметрично: симметричные 44 залезли бы на 2px в поле ввода и
   отнимали бы у него клики по краю. Поэтому у «−» зона растёт влево, у «+» — вправо.
   Псевдоэлемент, а не padding+отрицательный margin: padding изменил бы поток и мог сдвинуть
   обойму, а ::before не участвует в layout вообще. */
.ds-stepper__btn::before {
  content: "";
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: max(100%, 44px);
}
.ds-stepper__btn:first-child::before { right: 0; }
.ds-stepper__btn--plus::before,
.ds-stepper__btn:last-child::before { left: 0; }

/* «+» — УТВЕРДИТЕЛЬНОЕ действие, выделено брендовым градиентом; «−» остаётся нейтральным.
   Канон concept-v2.3.0:343 (.stepper #empPlus/#kktPlus{background:var(--grad);color:#fff}).
   Смысловая пара, а не украшение: до этого обе кнопки были неразличимы (замер c-design-2).

   ДВА селектора намеренно, и порядок их появления обратный порядку надёжности:
   1) --plus — СЕМАНТИЧЕСКИЙ и основной. Различие «+»/«−» смысловое, позиция случайна;
      класс печатает шаблон (c-tpl, CONTRACT §3), контракт открыт письмом-ДО 19.07;
   2) :last-child — ПЕРЕХОДНЫЙ, для сборок на шаблоне без модификатора.
   Оба разом, чтобы не возникло ОКНА РАЗРЫВА: ядро с одним лишь --plus, приехав раньше
   шаблона, оставило бы «+» без заливки, а связка «эта версия ядра требует ту версию шаблона»
   — ровно та зависимость версий, которую мы весь день расшиваем.
   Позиционную половину снять, когда все бренды соберутся на шаблоне >= v0.69.1 (проверяемо:
   у c-tpl тест на инвариант порядка, у меня гейт 8n на эталон). До тех пор она не мешает:
   при верном порядке оба селектора указывают на одну кнопку.
   📌 Формулировка контракта (уточнение c-tpl, лучше моего): порядок «−, input, +» записан
   как ЕГО инвариант разметки, а моя привязка — как ПОТРЕБИТЕЛЬ инварианта. Поэтому снятие
   :last-child уберёт из контракта одну фразу, не тронув сам инвариант и его тест. */
.ds-stepper__btn--plus,
.ds-stepper__btn:last-child {
  background: var(--ds-gradient-brand);
  color: var(--ds-color-on-primary);
}

.ds-stepper__btn:hover { transform: scale(1.08); }

.ds-stepper__btn:disabled {
  color: var(--ds-text-muted);
  cursor: not-allowed;
}

.ds-stepper__btn:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: -2px;
}

.ds-stepper__input {
  font: inherit;
  font-weight: var(--ds-fw-bold);
  font-variant-numeric: tabular-nums;
  text-align: center;
  width: 64px;
  border: none;
  /* 🔴 ВЫСОТУ СТЕППЕРА ДЕРЖАЛО ЭТО ПОЛЕ, А НЕ КНОПКА (замер `c-tpl`, 15.08): при кнопке 26
     степпер выходил 42, потому что `__input` со своими вертикальными полями давал 36.
     У канона на этом месте `<output>` высотой 16 и без полей — то есть узел заведомо ниже
     кнопки и на высоту не влияет. Вертикальное поле вынесено в токен, умолчание прежнее. */
  padding: var(--ds-stepper-input-pad-y, var(--ds-space-2)) var(--ds-space-1);
  -moz-appearance: textfield;
  appearance: textfield;
  /* Сброс user-agent: нативный input[type=number] несёт UA background:field (белый) и
     color:fieldtext (чёрный) -- как <mark>. Без сброса «0» рендерится белым островом в
     тёмной теме (замер c-design-2/мой на живом: color #000 на bg #fff). Наследуем контекст:
     тёмная панель -> светлый текст, светлая форма -> тёмный. Проверено на живом DOM в обеих
     темах (тёмная color=246 fill=246, светлая color=#1a1f2b). webkit-text-fill следует за
     color сам (input, в отличие от <mark>, отдельного fill не несёт -- сверено computed).
     Origin дефекта -- ЯДРО (класс ds-stepper__input тут, а не в шаблоне): c-design-2 верно
     заметил core-BEM именование и попросил проверить origin. */
  background: transparent;
  color: inherit;
}

.ds-stepper__input::-webkit-outer-spin-button,
.ds-stepper__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.ds-stepper__input:focus-visible {
  outline: 2px solid var(--ds-border-focus);
  outline-offset: -2px;
}

/* ═══ components/effects/effects.css ═══ */
/* f7c_design :: Декоративные эффекты (наряд-дополнение integrator-c). Требует tokens.css. */

/* ---- Градиентный текст ----
   ДВА разных страховочных механизма, они закрывают РАЗНЫЕ отказы — не путать:
   1) @supports — от ОТСУТСТВИЯ ПОДДЕРЖКИ background-clip: движок без неё берёт сплошной
      color и текст виден;
   2) двойной `background` (сплошной, затем градиент) — от НЕПОКРАСКИ при живой поддержке.
      Разбор c-tpl (BACKLOG 19.07): @supports проходит, а заливка выбита (бренд задал пустой
      или невалидный градиент) -> background не применяется, но `color: transparent` остаётся,
      и текст становится НЕВИДИМЫМ. Молчащий отказ: разметка на месте, читателя нет.
      Лечение — каскадом самого CSS: невалидное объявление парсер ОТБРАСЫВАЕТ, оставляя
      предыдущее, поэтому глифы покрасятся сплошным вместо исчезновения.
   Фолбэк держит --ds-text-link (флипающий), а НЕ do-not-flip --ds-color-primary: в светлой
   значения совпадают (дельта 0), в тёмной primary давал бы ~2.5 на фоне страницы. */
.ds-text-gradient {
  color: var(--ds-text-link);
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .ds-text-gradient {
    background: var(--ds-text-link);
    background: var(--ds-gradient-text);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* ---- Стекло/размытие ----
   Для декоративных слоёв и панелей БЕЗ текста. Единственное исключение —
   ds-header--glass (header.css): там подложка ≥85% непрозрачности сохраняет
   контраст текста при любом контенте позади. Фолбэк — сплошной фон. */
.ds-glass {
  background: color-mix(in srgb, var(--ds-surface-page) 88%, transparent);
}

@supports (backdrop-filter: blur(12px)) {
  .ds-glass {
    background: color-mix(in srgb, var(--ds-surface-page) 65%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }
}

/* ═══ components/motion/motion.css ═══ */
/* f7c_design :: Движение (наряд «этап 3», отдельный релиз). Требует tokens.css.
   Ноль JS. Включатели — модификаторы, которые конфиг сайта задаёт флагами
   (bool → класс, рендер шаблона). reduced-motion гасится ЕДИНЫМ блоком в конце
   этого файла — тема НЕ может его отключить. Бюджет файла ≈ 3 КБ. */

@keyframes ds-drift {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(28px, -22px) scale(1.06); }
  100% { transform: translate(-16px, 14px) scale(0.97); }
}

@keyframes ds-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-12px); }
}

@keyframes ds-rise {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: none; }
}

@keyframes ds-marquee {
  to { transform: translateX(-50%); }
}

@keyframes ds-pop {
  from { opacity: 0; transform: translateY(12px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

@keyframes ds-bump {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}

/* ---- Каскадное появление hero при загрузке (hero: {"rise": true}) ---- */
.ds-hero--rise .ds-rise-1,
.ds-hero--rise .ds-rise-2,
.ds-hero--rise .ds-rise-3,
.ds-hero--rise .ds-rise-4,
.ds-hero--rise .ds-rise-5 {
  animation: ds-rise var(--ds-anim-rise-duration) var(--ds-motion-ease) both;
}
.ds-hero--rise .ds-rise-1 { animation-delay: 0.05s; }
.ds-hero--rise .ds-rise-2 { animation-delay: 0.18s; }
.ds-hero--rise .ds-rise-3 { animation-delay: 0.32s; }
.ds-hero--rise .ds-rise-4 { animation-delay: 0.46s; }
.ds-hero--rise .ds-rise-5 { animation-delay: 0.6s; }

/* ---- Дрейф декоративных орбов ({"decor_drift"/"panel_drift": true}) ---- */
.ds-section--decor-drift::before,
.ds-calc__panel--orb-drift::before {
  animation: ds-drift var(--ds-anim-drift-duration) ease-in-out infinite alternate;
}

/* ---- Появление карточек ({"pop": true}); каскад — классами-задержками.
   Базовое правило — фолбэк (отыграет при загрузке, контент виден);
   при поддержке scroll-driven animations pop ПРИВЯЗАН К ПРОКРУТКЕ —
   иначе анимация заканчивается до того, как зритель доскроллит
   (браузерный аудит c-ops: 11 карточек, 0 к скроллу). ---- */
.ds-pop,
.ds-pop-1, .ds-pop-2, .ds-pop-3 {
  animation: ds-pop var(--ds-anim-pop-duration) var(--ds-motion-ease) both;
}
.ds-pop-1 { animation-delay: 0.15s; }
.ds-pop-2 { animation-delay: 0.3s; }
.ds-pop-3 { animation-delay: 0.45s; }

@supports (animation-timeline: view()) {
  .ds-pop, .ds-pop-1, .ds-pop-2, .ds-pop-3 {
    animation-timeline: view();
    animation-delay: 0s; /* каскад при скролле — сдвигом range, не задержкой */
  }
  .ds-pop   { animation-range: entry 5% entry 40%; }
  .ds-pop-1 { animation-range: entry 8% entry 43%; }
  .ds-pop-2 { animation-range: entry 12% entry 47%; }
  .ds-pop-3 { animation-range: entry 16% entry 51%; }
}

/* ---- «Подпрыгивание» итога при пересчёте: класс перевзводится скриптом
   шаблона (remove → reflow → add), система даёт только анимацию ---- */
.ds-bump {
  animation: ds-bump var(--ds-anim-bump-duration) ease;
}

/* ---- Парение (плавающие карточки/бейджи поверх hero-media) ---- */
.ds-float {
  animation: ds-float var(--ds-anim-float-duration) ease-in-out infinite;
}

/* ---- Лента (reviews: {"marquee": true}); рендер дублирует набор карточек
   вторым экземпляром с aria-hidden="true" для бесшовного цикла.
   Едет при наведении, останавливается без него; на устройствах без hover —
   обычный ручной свайп. ---- */
.ds-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.ds-marquee__track {
  display: flex;
  width: max-content;
  animation: ds-marquee var(--ds-marquee-duration) linear infinite;
  animation-play-state: paused;
}

/* Зазор — margin'ом на КАЖДОМ элементе (включая последний), а не gap:
   только так ширина трека = ровно 2 × набор и сдвиг −50% бесшовен.
   С gap шов промахивается на gap/2 и лента дёргается раз в цикл. */
.ds-marquee__track > * {
  margin-right: var(--ds-space-6);
  /* карточки ленты — фиксированной ширины, не тянутся на контейнер
     (прототип izibuh: .review width 340px, flex-shrink:0). Ширина — токен. */
  flex: 0 0 auto;
  width: var(--ds-marquee-item-width, 340px);
}

.ds-marquee:hover .ds-marquee__track { animation-play-state: running; }

@media (hover: none) {
  .ds-marquee {
    overflow-x: auto;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .ds-marquee__track { animation: none; }
}

/* ---- Появление секций при скролле (секция: {"reveal": true}) ----
   Без поддержки scroll-driven animations контент просто виден сразу. */
@supports (animation-timeline: view()) {
  .ds-reveal {
    animation: ds-rise var(--ds-anim-rise-duration) var(--ds-motion-ease) both;
    animation-timeline: view();
    animation-range: entry 5% entry 40%;
  }
}

/* ---- Единый выключатель движения ---- */
@media (prefers-reduced-motion: reduce) {
  :root { --ds-motion-scale: 0; }

  /* 🔴 УНИВЕРСАЛЬНОЕ гашение, а не перечисление классов. Замер c-design-2: анимаций 0, но
     ПЕРЕХОДОВ 38 из 618 узлов — список ниже гасил animation у известных классов и не трогал
     transition НИГДЕ. Тот же класс, что D-8: правило, которое надо не забыть, ломается на
     следующем компоненте (а transition есть у кнопок, карточек, FAQ, сегмента, степпера...).
     Здесь запрос пользователя ОС на отсутствие движения — покрытие обязано быть полным.
     .01ms вместо 0: событие transitionend/animationend всё равно приходит, поэтому скрипты,
     ждущие его, не зависают (0s в части движков события не даёт). */
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .ds-hero--rise [class*="ds-rise-"],
  .ds-section--decor-drift::before,
  .ds-calc__panel--orb-drift::before,
  .ds-float,
  .ds-marquee__track,
  .ds-reveal,
  .ds-pop, .ds-pop-1, .ds-pop-2, .ds-pop-3,
  .ds-bump {
    animation: none;
  }
  .ds-marquee { overflow-x: auto; }
}

/* ═══ components/footer/footer.css ═══ */
/* f7c_design :: Футер сайта. Требует tokens/tokens.css.
 * 🔴 ПОВЕРХНОСТЬ ПОДВАЛА — ТОКЕН (15.08, решение integrator-c2 по приказу «ровно как на макете»).
 * Замер канона forbion: подвал ПРОЗРАЧНЫЙ, то есть лежит на светлой странице; текст #6B726C,
 * ссылки #3E7013. Тёмный подвал — НАША конструкция, макета за ней нет.
 * Практическое следствие, из-за которого это и всплыло: ссылка бренда на тёмном даёт 2.95
 * (после сведения цвета к AA стало ХУЖЕ прежних 3.51) — AA там недостижим в принципе,
 * потому что цвет ссылки один на оба фона. На светлом подвале тот же цвет даёт ≥ 4.59.
 * ⚠ Умолчания — ПРЕЖНИЕ значения: izibuh и tezum остаются с тёмным подвалом. */
/* ── ЛОГОТИП ПОДВАЛА: ПАРА ПО ТЕМЕ (c-design, 17.08, наряд владельца Н2) ────────────────
 * ПОВОД: в подвале стоял ОДИН файл — `forbion-horizontal-inverse.svg` (чернила #FFFFFF),
 * а подвал в светлой теме #F7FAF3. Белое по светлому: логотип был НЕВИДИМ. Замер обеих тем:
 *     светлая  подвал #F7FAF3 · текст #1D1D1B   ⇒ нужен файл с ЧЕРНЫМИ чернилами
 *     тёмная   подвал #22221E · текст #F4F4F0   ⇒ нужен ИНВЕРСНЫЙ
 * ⚠ Файлы РАЗНЫЕ, но одинакового размера — 10 833 байта оба: перекраска `fill` длину сохраняет.
 * Сверять содержимое, не размер (md5 9ddb10bf1335 против 7961cdd5f940).
 *
 * 🔴 ПЕРЕКЛЮЧЕНИЕ ТОЛЬКО ПО АТРИБУТУ, БЕЗ `prefers-color-scheme` — И ЭТО ЗАМЕР, А НЕ ВКУС.
 * Палитра сайта на системную схему НЕ отвечает: при системной тёмной без атрибута страница
 * остаётся светлой (#F7FAF3). Канон ведёт себя так же — проверено на нём отдельно.
 * ⇒ добавь я сюда медиаблок «как в шапке», и на системно-тёмной машине вышел бы ровно
 * исходный дефект: страница светлая, логотип белый. Механизм шапки копировать нельзя,
 * потому что там он держится чужой специфичностью бренда, а не собственной верностью.
 *
 * Специфичность (0,2,0)+класс — условие работы, а не стиль: голое `.ds-footer__logo-dark`
 * (0,1,0) проиграло бы любому `footer img{display:block}`. `:where()` применять НЕЛЬЗЯ.
 * АДДИТИВНО: подвал с ОДНИМ <img> без этих классов не меняется — ни одно правило не матчится. */
.ds-footer__logo-dark { display: none; }
:root[data-theme="dark"] .ds-footer__logo-light { display: none; }
:root[data-theme="dark"] .ds-footer__logo-dark { display: block; }

.ds-footer {
  background: var(--ds-footer-surface, var(--ds-surface-inverse));
  color: var(--ds-footer-text, var(--ds-text-inverse));
  margin-top: var(--ds-space-24);
}

/* 🔴 У ТОКЕНА ЕСТЬ ОБЛАСТЬ, И ОНА БЫЛА УЖЕ ОБЪЯВЛЕНИЯ (16.08, найдено при разборе подвала).
   `--ds-footer-gap: 28px` бренд объявил ещё 15.08 — но ЧИТАЛО его только правило внутри
   `@media (min-width: 768px)`. Ниже 768 канонные 28 молча заменялись на `--ds-space-8` = 32:
   значение есть, читатель есть, а на половине ширин их не соединяет ничто.
   ⚠ Это НЕ «объявлено и не потреблено» (наш гейт такое ловит) и не «нет читателя» — обе
   половины на месте, разошлись их ОБЛАСТИ. Гейт по именам такую форму не видит в принципе. */
.ds-footer__inner {
  max-width: var(--ds-container-max);
  margin: 0 auto;
  padding: var(--ds-footer-pad, var(--ds-space-12) var(--ds-container-pad) var(--ds-space-8));
  display: grid;
  /* 🔴 ТРЕТИЙ СЛУЧАЙ ТОЙ ЖЕ ФОРМЫ, найден гейтом 27 в тот же час, каким гейт написан.
     `--ds-footer-cols` тоже объявлен брендом безусловно, а читался только с 768 — ниже стояла
     жёсткая `1fr`. Замер канона: **@390 одна колонка, @700 и @767 ДВЕ, @768 три, @1366 четыре**
     (`auto-fit, minmax(220px, 1fr)` — число колонок следует из ширины и НЕ задано нигде).
     ⇒ в полосе 560…767 мы держали одну колонку там, где канон даёт две. Это не «безобидное
     расхождение областей», а дефект, который расхождение прятало.
     ⚠ Умолчание `1fr` = ПРЕЖНЕЕ поведение: izibuh и tezum ниже 768 остаются в один столбец,
     их `2fr 1fr 1fr` живёт в правиле ниже. Соседи не двигаются по построению, не по надежде. */
  grid-template-columns: var(--ds-footer-cols, 1fr);
  gap: var(--ds-footer-gap, var(--ds-space-8));
}

@media (min-width: 768px) {
  /* 🔴 ЧИСЛО КОЛОНОК ПОДВАЛА — ТОКЕН, и форма выбрана ЗАМЕРОМ, а не по опрятности (письмо-ДО
     `c-tpl`, 15.08). Он предложил три формы: токен-число, модификатор, `auto-fit`. Замер канона
     отвечает сам: `.cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
     gap:28px}` — то есть **колонки РАВНЫЕ, а их число не задано вовсе**: оно следует из ширины.
     Рендер канона: 1440 и 1366 → 4×251 · 1040 → 4×226 · 1000 → 3×297 · 900 → 3×264.
     Наши `2fr 1fr 1fr` — наследство, а не замер: у канона первая колонка НЕ шире прочих.
     ⇒ Ни число (модификатор `--cols-4` сломался бы на 1000), ни доли (`2fr`) канон не держит.
     Умолчание = прежний рендер: izibuh и tezum остаются на `2fr 1fr 1fr`. */
  .ds-footer__inner {
    grid-template-columns: var(--ds-footer-cols, 2fr 1fr 1fr);
    gap: var(--ds-footer-gap, var(--ds-space-8));
  }
}

.ds-footer__title {
  font-size: var(--ds-fs-sm);
  font-weight: var(--ds-fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ds-footer-text, var(--ds-text-inverse));
  margin: 0 0 var(--ds-space-3);
}

.ds-footer__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
}

.ds-footer__link {
  color: var(--ds-footer-link, var(--ds-text-muted));
  font-size: var(--ds-fs-sm);
}
.ds-footer__link:hover { color: var(--ds-footer-text, var(--ds-text-inverse)); text-decoration: none; }

.ds-footer__bottom {
  /* ⚠ Было жёстко `rgba(255,255,255,.12)` — белая линия, ВИДИМАЯ ТОЛЬКО НА ТЁМНОМ.
     На светлом подвале она исчезала бы молча: разделитель есть в коде, на экране его нет. */
  border-top: 1px solid var(--ds-footer-rule, rgba(255, 255, 255, 0.12));
  /* Канон: над строкой копирайта 28 (margin) + 16 (padding), СНИЗУ — ноль: нижнее поле
     несёт сам подвал (30). У нас было 16/16, то есть нижний отступ считался дважды. */
  padding: var(--ds-footer-bottom-pad, var(--ds-space-4) var(--ds-container-pad));
  text-align: center;
  font-size: var(--ds-fs-xs);
  color: var(--ds-footer-muted, var(--ds-text-muted));
}
