/* ─── Upload ────────────────────────────────────────────────────────────────────
   Values: site/tokens.css — новых токенов нет
   Primary consumers: site/preview/upload.html + src/components/Upload/Upload.tsx
   Figma: Components isVYOT7tW0RTaIofg7FAqM, страница «↳ Upload» 2949:65099:
     Upload / Box   2961:70891 — 10 вариантов, оси State · Type Small|Big · Validation · Disabled
     Upload / File  2951:70155 — 5 вариантов, оси Status · Progress type · Disabled, булевы Button_1 · Button_2
     Upload / Group 2973:75562 — 3 варианта, оси Type · Small element, слот Files
   States: data-attributes only (data-state hover|pressed ставит React)

   РАЗМЕР. В коде одна ось size: md | lg. md — зона Type=Small и строки файла в моде MD
   коллекции «❖ Upload» 2950:69630; lg — зона Type=Big и строки в моде LG. Так их и
   собирает Group: у Type=Small строки в MD, у Type=Big — в LG (замер Plugin API 2026-09-15).

   ЦВЕТА — привязки макета и их токены. Сверка обратным поиском по значениям всех 4 мод:
   для зоны — живой замер Figma 2026-09-15, для строки и группы — tokens.figma-truth.json.
     Border/Neutral-Blue/Default · Hover · Pressed      → --ds-nba-border · -hover · -pressed
     Border/Disable/Neutral (Blue)                      → --ds-nba-border-disabled
     Fill/Neutral-Blue/Outline/High Contrast (Default)  → --ds-fill-neutral-outline-hc — 4/4 с 2026-09-21
       (до того расходилось в Light Invert и Dark, как у Collapse; починено одной правкой в
       tokens.css, строка FILL-NEUTRAL-OUTLINE-HC-DRIFT-INVERT-DARK). Ролевой токен, а не
       подстановка токена с совпавшими числами.
     Fill/Neutral-Blue/Outline/Hover · Pressed          → --ds-nba-outline-hover · -pressed
     Static/Border/Negative/Medium                      → --ds-stroke-neg-mid
     Static/Border/Neutral-Blue/Low                     → --ds-stroke-low
     Text/Neutral-Blue/Outline/Default (Hover, Pressed — те же значения) → --ds-text-neutral-outline
     Text/Info/Outline/Default                          → --ds-info-soft-text
     Text/Negative-Blue/Ghost/Default                   → --ds-neg-soft-text (значения 4/4; в имени роль soft)
     Static/Text/Neutral-Blue/Primary · Secondary       → --ds-text-primary · --ds-text-secondary
     Static/Text/Negative/Accent · Static/Icon/Negative/Accent → --ds-surface-neg-text · --ds-surface-neg-icon
     Static/Icon/Neutral-Blue/Tertiary                  → --ds-icon-tertiary
     Text/Disable/Neutral (Blue) · Icon/Disable/…       → --ds-disabled-text · --ds-disabled-icon
   Все эти токены сами переключаются по темам, включая инверт, поэтому тёмного
   переопределения здесь нет — тот же путь, что у collapse.css и badge.css.

   ОТСТУПЛЕНИЯ ОТ МАКЕТА — решение владельца 2026-09-15 («бери рекомендации»):
     · прозрачность наведения и нажатия зоны — токеном как есть, без множителя слоя из
       макета (0.2 и 0.4 поверх и без того полупрозрачной переменной). Тем же доводом снят
       множитель 0.8 у выключенной рамки — он в рекомендации не назван, отмечено отдельно;
     · индикатор загрузки строки — вращающийся Spinner, а не статичная иконка макета;
     · радиус зоны Small — 16, как в новом макете (прежняя спека по Pixso говорила 12);
     · элемент «File downloded» 2961:71948 в сборку не входит, пока дизайнер не опишет роль.
   ─────────────────────────────────────────────────────────────────────────── */

/* ══ Upload / Box ═══════════════════════════════════════════════════════════ */
.ds-upload-box {
  /* MD — Type=Small */
  --_pad:        calc(var(--ds-comp-unit) * 2.5);   /* Padding/pad-125 — 10 */
  --_gap:        calc(var(--ds-comp-unit) * 2.5);   /* Gap/gap-125 — 10 */
  --_radius:     calc(var(--ds-comp-unit) * 4);     /* Radius/rad-200 — 16, у обеих ступеней */
  --_title-fs:   var(--ds-l2-size);                 /* Label L2 — 14 */
  --_title-lh:   var(--ds-l2-lh);                   /* 20 */

  position:        relative;
  display:         flex;
  align-items:     center;
  gap:             var(--_gap);
  box-sizing:      border-box;
  width:           100%;
  padding:         var(--_pad);
  border-radius:   var(--_radius);
  background:      var(--ds-fill-neutral-outline-hc);
  font-family:     var(--ds-font);
  color:           var(--ds-text-neutral-outline);
  transition:      background-color var(--ds-dur-1) var(--ds-ease);
}

/* LG — Type=Big: колонка по центру, текст выровнен по центру (textAlignHorizontal CENTER). */
.ds-upload-box[data-size='lg'] {
  --_pad:          calc(var(--ds-comp-unit) * 6);     /* Padding/pad-300 — 24 */
  --_gap:          calc(var(--ds-comp-unit) * 4);     /* Gap/gap-200 — 16 */
  --_title-fs:     var(--ds-l1-size);                 /* Label L1 — 16 */
  --_title-lh:     var(--ds-l1-lh);                   /* 24 */
  flex-direction:  column;
  justify-content: center;
  text-align:      center;
}

/* Пунктир — отдельным SVG-слоем, а не border-style: dashed. Макет задаёт штрих [2, 2] и
   внутреннюю обводку 1 px. Рисунок dashed браузер выбирает сам, и штрих получается длиннее
   макетного, а border к тому же прибавил бы 2 px к высоте 60 / 144. Слой вдавлен на
   полпикселя, чтобы линия толщиной 1 px легла ровно внутрь края; радиус поэтому на
   полпикселя меньше радиуса зоны. */
.ds-upload-box__frame {
  position:        absolute;
  top:             calc(var(--ds-comp-unit) * 0.125);
  left:            calc(var(--ds-comp-unit) * 0.125);
  width:           calc(100% - var(--ds-comp-unit) * 0.25);
  height:          calc(100% - var(--ds-comp-unit) * 0.25);
  overflow:        visible;
  pointer-events:  none;
  color:           var(--ds-nba-border);
}
.ds-upload-box__frame rect {
  rx:                calc(var(--_radius) - var(--ds-comp-unit) * 0.125);
  ry:                calc(var(--_radius) - var(--ds-comp-unit) * 0.125);
  fill:              none;
  stroke:            currentColor;
  stroke-width:      calc(var(--ds-comp-unit) * 0.25);                       /* Stroke/stroke-12,5 — 1 */
  stroke-dasharray:  calc(var(--ds-comp-unit) * 0.5) calc(var(--ds-comp-unit) * 0.5); /* dashPattern [2, 2] */
}

/* Выбор файла — сам <input type="file">, растянутый на всю зону и прозрачный. Клик в любую
   точку, Enter и пробел с клавиатуры и перетаскивание файла на зону работают средствами
   браузера, без своего обработчика открытия диалога. Слой над содержимым, поэтому текст
   зоны не выделяется — это кнопка, а не абзац. */
.ds-upload-box__input {
  position:        absolute;
  inset:           0;
  z-index:         1;
  width:           100%;
  height:          100%;
  margin:          0;
  padding:         0;
  opacity:         0;
  cursor:          pointer;
}
.ds-upload-box__input::file-selector-button {
  cursor:          pointer;
}
.ds-upload-box__input:disabled,
.ds-upload-box__input:disabled::file-selector-button {
  cursor:          default;
}

.ds-upload-box > .ds-upload-box__icon {
  flex:            none;
}

.ds-upload-box__text {
  display:         flex;
  flex-direction:  column;
  flex:            1 1 auto;
  min-width:       0;
}
.ds-upload-box[data-size='lg'] > .ds-upload-box__text {
  flex:            none;
  width:           100%;
}

/* Title — перенос разрешён (textTruncation DISABLED у ↳ Label Item). */
.ds-upload-box__title {
  font-size:       var(--_title-fs);
  line-height:     var(--_title-lh);
  font-weight:     500;                              /* ✦ Typography/Label/Weight — Medium */
  color:           var(--ds-text-neutral-outline);
  overflow-wrap:   anywhere;
}
/* Акцентная часть заголовка — «Click to upload» у Big: отдельный отрезок текста с цветом
   Text/Info/Outline/Default. Это не ссылка: кликабельна вся зона. */
.ds-upload-box__accent {
  color:           var(--ds-info-soft-text);
}
/* Caption — до двух строк с многоточием (textTruncation ENDING, maxLines 2). */
.ds-upload-box__desc {
  display:            -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow:           hidden;
  font-size:          var(--ds-field-caption-fs);    /* ✦ Typography/Caption/size — 12 */
  line-height:        var(--ds-field-caption-lh);    /* 16 */
  font-weight:        400;
  color:              var(--ds-text-secondary);
}

/* Цвет иконки — переопределение на ЭКЗЕМПЛЯРЕ Avatar в макете: у самого Avatar с High
   contrast иконка --ds-icon-primary, а в зоне на ней стоит Static/Icon/Neutral-Blue/Tertiary.
   Селектор тяжелее правила avatar.css `.ds-avatar[data-fill][data-hc] .ds-avatar-icon`
   (0,4,0) на один класс, чтобы не зависеть от порядка подключения файлов. */
.ds-upload-box > .ds-avatar.ds-upload-box__icon[data-type='icon'] .ds-avatar-icon {
  stroke:          var(--ds-icon-tertiary);
}

/* ── Наведение и нажатие ─────────────────────────────────────────────────── */
/* Только у зоны без ошибки: вариантов Error × Hover и Error × Pressed в макете нет. */
.ds-upload-box[data-state='hover']:not([data-error]) {
  background:      var(--ds-nba-outline-hover);
}
.ds-upload-box[data-state='hover']:not([data-error]) > .ds-upload-box__frame {
  color:           var(--ds-nba-border-hover);
}
.ds-upload-box[data-state='pressed']:not([data-error]) {
  background:      var(--ds-nba-outline-pressed);
}
.ds-upload-box[data-state='pressed']:not([data-error]) > .ds-upload-box__frame {
  color:           var(--ds-nba-border-pressed);
}

/* ── Ошибка ──────────────────────────────────────────────────────────────── */
/* Красные рамка, заголовок и иконка; Caption остаётся Static/Text/Neutral-Blue/Secondary. */
.ds-upload-box[data-error] > .ds-upload-box__frame {
  color:           var(--ds-stroke-neg-mid);
}
.ds-upload-box[data-error] .ds-upload-box__title {
  color:           var(--ds-neg-soft-text);
}
.ds-upload-box[data-error] > .ds-avatar.ds-upload-box__icon[data-type='icon'] .ds-avatar-icon {
  stroke:          var(--ds-surface-neg-icon);
}

/* ── Выключено ───────────────────────────────────────────────────────────── */
/* У выключенной зоны в макете нет заливки, подложки у иконки тоже нет (Avatar Fiil=Off). */
.ds-upload-box[data-disabled] {
  background:      transparent;
}
.ds-upload-box[data-disabled] > .ds-upload-box__frame {
  color:           var(--ds-nba-border-disabled);
}
.ds-upload-box[data-disabled] :is(.ds-upload-box__title, .ds-upload-box__accent, .ds-upload-box__desc) {
  color:           var(--ds-disabled-text);
}
.ds-upload-box[data-disabled] > .ds-avatar.ds-upload-box__icon[data-type='icon'] .ds-avatar-icon {
  stroke:          var(--ds-disabled-icon);
}

/* ── Фокус ───────────────────────────────────────────────────────────────── */
/* Фокуса в макете нет — кольцо системное, по форме зоны. [data-focus-visible] ставит
   компонент и тумблер Focus в State Explorer; :has — для статичных демо. */
.ds-upload-box[data-focus-visible],
.ds-upload-box:has(> .ds-upload-box__input:focus-visible) {
  box-shadow:      var(--ds-focus-ring);
}

/* ══ Upload / File ══════════════════════════════════════════════════════════ */
.ds-upload-file {
  /* MD — мод MD коллекции «❖ Upload» */
  --_py:           calc(var(--ds-comp-unit) * 1.5);   /* Top-Bottom → Padding/pad-75 — 6 */
  --_px:           calc(var(--ds-comp-unit) * 2);     /* Left-Right → Padding/pad-100 — 8 */
  --_gap:          calc(var(--ds-comp-unit) * 2);     /* Gap → Gap/gap-100 — 8 */
  --_radius:       calc(var(--ds-comp-unit) * 3);     /* Radius → Radius/rad-150 — 12 */
  --_title-fs:     var(--ds-l2-size);                 /* Label L2 — 14 */
  --_title-lh:     var(--ds-l2-lh);                   /* 20 */
  --_stroke:       var(--ds-stroke-low);

  display:         flex;
  align-items:     center;
  gap:             var(--_gap);
  box-sizing:      border-box;
  width:           100%;
  min-width:       0;
  padding:         var(--_py) var(--_px);
  border-radius:   var(--_radius);
  background:      var(--ds-fill-neutral-outline-hc);
  /* Рамка — внутренней тенью, а не border: обводка макета внутренняя и высоту 48 / 60 не
     прибавляет. Смена цвета в ошибке меняет только отрисовку. */
  box-shadow:      inset 0 0 0 1px var(--_stroke);
  font-family:     var(--ds-font);
  color:           var(--ds-text-primary);
}

.ds-upload-file[data-size='lg'] {
  --_py:           calc(var(--ds-comp-unit) * 2.5);   /* Top-Bottom → Padding/pad-125 — 10 */
  --_px:           calc(var(--ds-comp-unit) * 2.5);   /* Left-Right → Padding/pad-125 — 10 */
  --_gap:          calc(var(--ds-comp-unit) * 2.5);   /* Gap → Gap/gap-125 — 10 */
  --_radius:       calc(var(--ds-comp-unit) * 4);     /* Radius → Padding/pad-200 — 16 */
  --_title-fs:     var(--ds-l1-size);                 /* Label L1 — 16 */
  --_title-lh:     var(--ds-l1-lh);                   /* 24 */
}

.ds-upload-file[data-status='error'] {
  --_stroke:       var(--ds-stroke-neg-mid);
}

/* Иконка — Avatar без заливки (Fiil=Off): цвет содержимого задаёт корень аватара, а в
   строке макет ставит на иконку Static/Icon/Neutral-Blue/Tertiary. */
.ds-upload-file > .ds-upload-file__icon {
  flex:            none;
  color:           var(--ds-icon-tertiary);
}
.ds-upload-file[data-status='error'] > .ds-upload-file__icon {
  color:           var(--ds-surface-neg-icon);
}
.ds-upload-file[data-disabled] > .ds-upload-file__icon {
  color:           var(--ds-disabled-icon);
}

/* contain: inline-size — текст строки не задаёт её минимальную ширину. Имя и подпись идут в
   одну строку, и без этого их полная длина становилась минимальной шириной строки, группы и
   колонки сетки, куда группу положили: колонка раздвигалась и наезжала на соседнюю
   (найдено на витрине 2026-09-15, раздел «Группа», три колонки). Ширину тексту даёт flex. */
.ds-upload-file__text {
  display:         flex;
  flex-direction:  column;
  flex:            1 1 auto;
  min-width:       0;
  contain:         inline-size;
}
.ds-upload-file__text > .ds-pb {
  width:           100%;
}

/* Имя и подпись — в одну строку с многоточием (textTruncation ENDING, maxLines 1). */
.ds-upload-file__name,
.ds-upload-file__caption {
  overflow:        hidden;
  white-space:     nowrap;
  text-overflow:   ellipsis;
}
.ds-upload-file__name {
  font-size:       var(--_title-fs);
  line-height:     var(--_title-lh);
  font-weight:     500;                              /* ✦ Typography/Label/Weight — Medium */
  color:           var(--ds-text-primary);
}
.ds-upload-file__caption {
  font-size:       var(--ds-field-caption-fs);       /* 12 */
  line-height:     var(--ds-field-caption-lh);       /* 16 */
  font-weight:     400;
  color:           var(--ds-text-secondary);
}

.ds-upload-file[data-status='error'] .ds-upload-file__name {
  color:           var(--ds-surface-neg-text);
}
.ds-upload-file[data-disabled] :is(.ds-upload-file__name, .ds-upload-file__caption) {
  color:           var(--ds-disabled-text);
}

/* ══ Upload / Group ═════════════════════════════════════════════════════════ */
.ds-upload {
  --_gap:          calc(var(--ds-comp-unit) * 3);     /* Type=Small itemSpacing — 12 */
  --_list-gap:     calc(var(--ds-comp-unit) * 1.5);   /* слот Files — 6 */

  display:         flex;
  flex-direction:  column;
  gap:             var(--_gap);
  box-sizing:      border-box;
  width:           100%;
  font-family:     var(--ds-font);
}
.ds-upload[data-size='lg'] {
  --_gap:          calc(var(--ds-comp-unit) * 4);     /* Type=Big itemSpacing — 16 */
  --_list-gap:     calc(var(--ds-comp-unit) * 2);     /* слот Files — 8 */
}

/* Small element=On: вместо зоны — кнопка «Add file» шириной по содержимому. */
.ds-upload > .ds-btn {
  align-self:      flex-start;
}

/* ── Иконки в кнопках ─────────────────────────────────────────────────────── */
/* Толщина глифа — токеном кнопки по её размеру (❖ Button Icon/Stroke). button.css даёт её
   только [data-lucide] и шеврону, а у встроенных глифов строки и у иконок, которые передаёт
   потребитель, атрибута нет — без правила они рисовались бы толщиной 1 (замер витрины
   2026-09-15: 1px при 16 и 20 против 2.25 / 2.4). Спиннер загрузки — кольцо из рамки, не svg. */
.ds-upload-file .ds-btn[data-size='sm'] :is(.ds-btn-icon-left, .ds-btn-icon-right, .ds-btn-icon-content) svg,
.ds-upload > .ds-btn[data-size='sm'] :is(.ds-btn-icon-left, .ds-btn-icon-right, .ds-btn-icon-content) svg {
  stroke-width:    var(--ds-btn-sm-stroke);
}
.ds-upload-file .ds-btn[data-size='md'] :is(.ds-btn-icon-left, .ds-btn-icon-right, .ds-btn-icon-content) svg,
.ds-upload > .ds-btn[data-size='md'] :is(.ds-btn-icon-left, .ds-btn-icon-right, .ds-btn-icon-content) svg {
  stroke-width:    var(--ds-btn-md-stroke);
}

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

/* Скрытые узлы: поле выбора у кнопочного варианта и живой регион строки. */
.ds-upload__sr {
  position:        absolute;
  width:           1px;
  height:          1px;
  margin:          -1px;
  padding:         0;
  overflow:        hidden;
  clip:            rect(0, 0, 0, 0);
  white-space:     nowrap;
  border:          0;
}

/* ── Reduced motion ─────────────────────────────────────────────────────────── */
/* Анимируется только цвет наведения зоны; при выключенном движении — мгновенно. */
@media (prefers-reduced-motion: reduce) {
  .ds-upload-box {
    transition:    none;
  }
}
