/* ─── Alert — встроенное уведомление ──────────────────────────────────────────
   Единственный источник компонентного CSS.
   Значения: site/tokens.css (--ds-surface-{info,pos,warn}-low · --ds-surface-neutral-low
             · --ds-stroke-{info,pos,warn,neg}-high · --ds-surface-{info,pos,warn,neg}-icon
             · --ds-text-primary · --ds-radius-xl · --ds-space-*)
   Потребители: site/preview/alert.html
   Префикс: ds-alert
   Состояния: только data-атрибуты
     data-intent='info|positive|warning|negative' — Figma: моды коллекции ❖ Alert

   ГРАУНД-ТРУС, снят Plugin API 2026-09-06 с мастера Alert 2599:41684:
     контейнер   padding 16 (Padding/pad-200) · radius 16 (Radius/rad-200)
                 border 1 (Stroke/stroke-12,5) · тени НЕТ (effects: 0)
                 min-width 600, max-width 1320 (↔ Alert/Container/{Min,Max} width,
                 мод Desktop; мод Mobile даёт 320/800 — см. «Мобильный режим» ниже)
     заливка     ❖ Alert/Surface  → Static/Surface/<Intent>/Low
     рамка       ❖ Alert/Border   → Static/Border/<Intent>/High
     иконка      ❖ Alert/Icon/Color → Static/Icon/<Intent>/Accent, 24×24
                 (icon/Size/size-300), stroke-width 2
     Body        HORIZONTAL, gap 8 (Gap/gap-100): иконка + Core
     Core        VERTICAL, gap 12 (Gap/gap-150): Title · Description · слот · кнопки
     Title       Inter Medium 18/24  → --ds-l0-size / --ds-l0-lh
     Description Inter Regular 14/20 → --ds-l2-size / --ds-l2-lh
     оба текста  Static/Text/Neutral-Blue/Primary → --ds-text-primary
     закрытие    Tertiary IconOnly MD 40×40 (radius 12, иконка 20), АБСОЛЮТНО:
                 x=546 при ширине 600 и y=14 → top 14, right 14
     кнопки      Buttons Group SM (высота 32): Secondary High Contrast=On
                 (наш data-variant='outline') + Tertiary Semantic=Info
                 (наш data-variant='ghost' data-intent='info'), gap 16

   ЧЕТЫРЕ СЕМАНТИКИ — ЭТО МОДЫ ПЕРЕМЕННЫХ, А НЕ ВАРИАНТЫ. У мастера один узел;
   Info/Positive/Warning/Negative живут модами коллекции ❖ Alert и переключают
   сразу три привязки (Surface, Border, Icon/Color) плюс четыре булевых флага
   видимости иконок. Поэтому в коде это data-intent на контейнере, а не отдельные
   классы: одна ось, одно место переключения.

   НЕГАТИВ ЗАЛИТ СЕРЫМ, И ЭТО НЕ ОШИБКА. Static/Surface/Negative/Low цепочкой через
   Main/Neutral резолвится в нейтральную ступень (#f7f8fb в Light), а не в красную —
   Negative Anomaly, у негатива Low/Medium/High нейтральные, красный только в Accent.
   Поэтому фон здесь --ds-surface-neutral-low, а НЕ --ds-surface-neg-low: последний
   держит полупрозрачный Fill/Soft/Default и с макетом не совпадает ни в одной теме.
   Проверено отпечатком по 4 темам против всех 856 имён tokens.css, 2026-09-06.

   ТЁМНЫХ ПЕРЕОПРЕДЕЛЕНИЙ НЕТ И НЕ ДОЛЖНО БЫТЬ. Все восемь цветов — ролевые токены,
   темы они переключают сами. Это тот же законный третий путь, что у badge.css:
   компонент целиком на токенах, поэтому парного dark-паттерна тут не появляется.

   МОБИЛЬНЫЙ РЕЖИМ. В макете Viewport — мод коллекций L2 - Semantic Dimensions и
   Viewport: на Mobile меняются ширины (320/800), заголовок 16 вместо 18, описание
   12/16 вместо 14/20 и трекинг описания 0.5.
   ЗДЕСЬ СТОЯЛА МОЯ ОШИБКА, и она записана как ошибка, а не стёрта. Я написал, что
   «наши токены этой оси НЕ несут», и на этом основании оставил фиксированные
   --ds-l0/--ds-l2. Ось несут: на тот день site/css/typography.css:151 объявлял мобильный мод
   (с 2026-09-17 он в site/tokens.css, @media max-width: 639px),
   и Sub2 там 16/24, Body 2 — 12/16, ровно как в макете. Нашёл стенд, не я;
   это тот же класс, что уже записан в память — названная граница вместо взгляда
   в соседний файл. Типографика переведена на --ds-type-*.
   ЧЕГО ДЕЙСТВИТЕЛЬНО НЕТ — одно значение: трекинга у Body 2 нет ни в каком моде
   (в шкале нет ни одного --ds-type-body-*-tracking), поэтому мобильные
   0.5 не выражены. Это узкая дыра, а не отсутствие оси.
   Ещё одно правило мобильного режима из документации, которое кодом не выражается:
   на мобильных запрещено отключать крестик.
   ────────────────────────────────────────────────────────────────────────────── */

.ds-alert {
  --_alert-bd: calc(var(--ds-comp-unit) * 0.25);   /* 1 — Stroke/stroke-12,5 */

  position: relative;
  display: flex;
  box-sizing: border-box;
  min-width: calc(var(--ds-comp-unit) * 150);  /* 600 — ↔ Alert/Container/Min width, Desktop */
  max-width: calc(var(--ds-comp-unit) * 330);  /* 1320 — ↔ Alert/Container/Max width, Desktop */
  padding: var(--ds-space-4);                  /* 16 — Padding/pad-200 */
  border: var(--_alert-bd) solid transparent;
  border-radius: var(--ds-radius-xl);          /* 16 — Radius/rad-200 */
  font-family: var(--ds-font);
}

/* Кнопка закрытия вынута из потока: в макете это ABSOLUTE-ребёнок контейнера
   с constraints MAX/MIN, то есть прижата к правому верхнему углу. Инсет 14, а не 16,
   потому что у самой кнопки внутренний padding 10 — визуальный крестик садится
   на те же 24 от края, что и остальной контент.

   СЕЛЕКТОР СОСТАВНОЙ НАМЕРЕННО, и это не косметика. Одиночный .ds-alert__close имеет
   ту же специфичность (0,1,0), что и .ds-btn{position:relative} из button.css, а на
   странице последним грузится бандл site/js/alert-explorer.css, в который вкомпилирована
   копия button.css. При равной специфичности выигрывает тот, кто позже, — и кнопка
   оставалась в потоке. Замер поймал: position=relative, инсет сверху 31 вместо 14,
   справа 562 вместо 14. Составной селектор (0,2,0) ставит правило выше копии в бандле. */
.ds-alert .ds-alert__close {
  position: absolute;
  /* 14 от ВНЕШНЕГО края, как в макете. Абсолютная привязка считается от внутреннего
     края блока-контейнера, то есть уже за рамкой, — поэтому её толщину вычитаем.
     Без вычитания замер давал 15: ошибка ровно в одну рамку, и увидеть её можно
     только измерением, глазом такая разница неотличима. */
  top: calc(var(--ds-comp-unit) * 3.5 - var(--_alert-bd));
  right: calc(var(--ds-comp-unit) * 3.5 - var(--_alert-bd));
}

.ds-alert__body {
  display: flex;
  flex: 1 1 auto;
  gap: var(--ds-space-2);   /* 8 — Gap/gap-100 */
  min-width: 0;
}

/* Иконка статуса. Цвет приходит через currentColor от data-intent контейнера —
   так один SVG обслуживает все четыре семантики без дублирования правил. */
.ds-alert__icon {
  flex: 0 0 auto;
  width: calc(var(--ds-comp-unit) * 6);   /* 24 — icon/Size/size-300 */
  height: calc(var(--ds-comp-unit) * 6);
}

.ds-alert__icon svg {
  display: block;
  width: 100%;
  height: 100%;
  stroke: currentColor;
  stroke-width: 2;   /* иконка ровно 24px, viewBox 24 → масштаб 1:1 */
  fill: none;
}

/* Резерв справа под абсолютную кнопку закрытия: 40 ширина кнопки + 8 разрядка.
   Без него длинный заголовок уезжает под крестик. */
.ds-alert__core {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--ds-space-3);   /* 12 — Gap/gap-150 */
  min-width: 0;
}

/* Резерв берётся от НАЛИЧИЯ кнопки, а не от параллельного атрибута. Здесь стояло
   правило на [data-close='off'], и стенд назвал его хрупким по существу: разметка
   без кнопки, но без атрибута оставляла пустые 48, а атрибут при живой кнопке пускал
   текст под крестик. Два источника правды об одном факте расходятся, как только
   разметку соберёт кто-то помимо нашего React. :has() спрашивает сам факт. */
.ds-alert:has(> .ds-alert__close) .ds-alert__core {
  padding-right: calc(var(--ds-comp-unit) * 12);   /* 48 = кнопка 40 + 8 */
}

/* Типографика взята из ВЬЮПОРТНОГО ряда --ds-type-*, а не из фиксированного --ds-l*.
   Ряд объявлен в site/tokens.css (с 2026-09-17; прежде — в site/css/typography.css, удалён
   2026-09-22) и сам несёт мобильный мод: Sub2 18/24 → 16/24, Body 2 14/20 → 12/16 при ширине
   до 639 (@media max-width: 639px). Это ровно те числа, что стоят в макете у мода Mobile, —
   подставлять их вручную не нужно.
   ЗАВИСИМОСТЬ: страница обязана подключить ../tokens.css. Так же устроен
   Notification — token-слой подключает страница, а не тянет компонент.
   Стенд назвал это дефектом интеграции: потребитель, подключивший ОДИН alert.css,
   получит неразрешённые var() у размеров. Зависимость от слоя у Alert ЕСТЬ, и
   отрицать её нельзя — здесь стояла формулировка «это не про Alert», и стенд
   справедливо назвал её слишком широкой. Точная: ни один компонент в src/components
   не импортирует site/tokens.css напрямую (проверено грепом), оба слоя приходят
   через общий src/styles/tokens.css, который импортирует site/tokens.css (с 2026-09-22 —
   его одного: второй импорт снят вместе с удалённым typography.css). То есть слои подключает
   приложение, и Alert зависит от типографического слоя ровно так же, как от цветового.
   ТОЧНОСТЬ МЕЖСТРОЧНОГО названа честно: ряд хранит межстрочный ОТНОШЕНИЕМ, и оно
   округлено. Замер даёт 23.994 вместо 24 у заголовка и 20.02 вместо 20 у описания
   на десктопе, 15.96 вместо 16 на мобильном. Это свойство общего слоя, не Alert:
   тем же живут Notification и остальные его потребители. Сотые доли пикселя
   раскладку не двигают, но число в отчёте должно совпадать с числом на экране. */
.ds-alert__title {
  margin: 0;
  font-size: var(--ds-type-sub2-size);      /* 18 desktop / 16 mobile — ✦ Typography/Subtitle/Sub2 */
  line-height: var(--ds-type-sub2-lh);      /* 24 в обоих модах */
  font-weight: var(--ds-type-sub2-weight);  /* 500 — Typography/Weight/medium */
  color: var(--ds-text-primary);            /* Static/Text/Neutral-Blue/Primary */
}

.ds-alert__text {
  margin: 0;
  /* Макет 2599:41684: блок Description несёт paddingTop 2 (L1 Padding/pad-25), текст в нём с
     y = 2 — от заголовка до описания 14, а не голый зазор Core 12. Без отступа витрина ставила
     текст на 2px выше макета (замер 2026-09-24, строка COMP-UNIT-GEOMETRY-UNMEASURED). */
  padding-top: calc(var(--ds-comp-unit) * 0.5);
  font-size: var(--ds-type-body-2-size);      /* 14 desktop / 12 mobile — ✦ Typography/Body/Body 2 */
  line-height: var(--ds-type-body-2-lh);      /* 20 desktop / 16 mobile */
  font-weight: var(--ds-type-body-2-weight);  /* 400 — Typography/Weight/regular */
  color: var(--ds-text-primary);
}

/* Слот произвольного содержимого. Собственного оформления не несёт намеренно:
   в макете рамка и радиус у Placeholder — это заглушка слота, а не хром компонента. */
.ds-alert__slot {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* ── Семантика: моды коллекции ❖ Alert ─────────────────────────────────────── */

.ds-alert[data-intent='info'] {
  background: var(--ds-surface-info-low);   /* Static/Surface/Info/Low */
  border-color: var(--ds-stroke-info-high); /* Static/Border/Info/High */
  color: var(--ds-surface-info-icon);       /* Static/Icon/Info/Accent — только иконке */
}

.ds-alert[data-intent='positive'] {
  background: var(--ds-surface-pos-low);
  border-color: var(--ds-stroke-pos-high);
  color: var(--ds-surface-pos-icon);
}

.ds-alert[data-intent='warning'] {
  background: var(--ds-surface-warn-low);
  border-color: var(--ds-stroke-warn-high);
  color: var(--ds-surface-warn-icon);
}

.ds-alert[data-intent='negative'] {
  background: var(--ds-surface-neutral-low);  /* Negative Anomaly — см. шапку */
  border-color: var(--ds-stroke-neg-high);
  color: var(--ds-surface-neg-icon);
}

/* ── Узкий экран ───────────────────────────────────────────────────────────────
   Жёсткая минимальная ширина макета (600) на узком экране физически не помещается,
   а кнопки должны переноситься. Типографика здесь НЕ трогается: её мобильный мод
   приходит сам из --ds-type-*.
   Порог 639, а не 640: ровно так записан мобильный мод шкалы (site/tokens.css, @media max-width: 639px), и два
   разных порога на одной оси разошлись бы на один пиксель — на 640 типографика уже
   десктопная, а раскладка была бы мобильной. Стенд поймал это расхождение. */
@media (max-width: 639px) {
  .ds-alert {
    min-width: 0;
    max-width: 100%;
  }

  .ds-alert .ds-buttons-group { flex-wrap: wrap; }
}
