# Design Kit — rent-service

**Версия:** 1.2
**Дата:** 2026-04-13
**Статус:** финальный, с учётом ревью-фиксов (21-review-fixes), аудита против `project-presentation.html` и переноса финмодели на **залог-возврат** (списание депозита с последующим refund вместо authorization hold)
**Объём:** **31 компонент** · **48 микрокопи-фраз**
**Автор:** Design Systems Architect

---

## 1. Введение

**Что это.** Единый источник правды по дизайн-системе rent-service — токены, компоненты, правила. Собрано из 10 research-документов и 4 ревью, консолидировано после blockers/major фиксов.

**Для кого.**
- **Разработчики** (Flutter, Next.js, React admin) — токены, код-сниппеты, API компонентов.
- **Дизайнеры** — Figma-структура, semantic tokens, типовые составы экранов.
- **QA / А11y** — acceptance-критерии, WCAG-контрасты, motion-правила.
- **PM / заказчик** — микрокопи-словарь, принципы.

**Связь с другими документами.** Сводит `08-typography`, `09-color-motion`, `11-design-brief`, `14-mobile-screens`, `15-web-admin-screens`, `16-landing-screens`, `21-review-fixes`. При расхождении — **этот документ является финальным**.

**Версионирование:** SemVer (v1.0 = MVP ship). Breaking change — изменение semantic token или API компонента.

---

## 2. Бренд-принципы

**Позиционирование.** Утилитарный технологичный сервис аренды инструмента 24/7 с финтех-уровнем прозрачности денег. Не маркетплейс, не премиум.

**Tagline:** **«Возьмите. Верните. Залог вернётся сам.»**

**Emotion target:** *спокойно · быстро · прозрачно*.
**Anti-emotion:** *тревожно · растерянно · использованно*.

**7 принципов:**
1. **Прозрачность денег раньше кнопки** — сумма раскрыта до тапа.
2. **Залог — обещание возврата, а не штраф** — `return ↻` метафора, jade-green акценты, явный SLA refund. (Раньше был snowflake/❄ для authorization hold; механика hold не используется — деньги списываем явно и возвращаем через refund.)
3. **Симметрия Android ↔ Max** — honest degradation, без эмуляций.
4. **Time-to-rent < 90 секунд** — режем wizards, confirm-модалки.
5. **Тайминги везде, молчания нигде** — SLA на экране всегда.
6. **Плотность по контексту** — mobile воздух, admin плотно, landing hero.
7. **Motion служит ясности** — нет bounce на деньгах, нет конфетти.

---

## 3. Color Tokens

### 3.1 Primitive tokens (Tailwind-style, flat)

Палитра приведена в соответствие с утверждённой бизнес-презентацией (`docs/presentation/project-presentation.html`). Основная тема проекта — **dark** на `#0F172A` с blue/amber дуэтом. Light-варианты применяются локально для отдельных карточек.

#### Brand — Primary (Blue)

| Token | HEX | Роль |
|---|---|---|
| `primary` | `#2563EB` | Основной brand (Blue 600), CTA, акценты, bento highlights |
| `primary-dark` | `#1D4ED8` | hover / pressed |
| `primary-light` | `#DBEAFE` | subtle fill, tint-bg, light-карточки |

#### Accent — Warm (Amber)

| Token | HEX | Роль |
|---|---|---|
| `accent` | `#F59E0B` | Warm акцент (Amber 500): гради­ент-стопка, highlight-цифры, badge «Новинка» |
| `accent-dark` | `#D97706` | hover / pressed |

#### Neutral — Dark + Gray

| Token | HEX | Роль |
|---|---|---|
| `dark` | `#0F172A` | Основной фон (Slate 900) |
| `gray-900` | `#1E293B` | Поверхность карточки на dark |
| `gray-700` | `#334155` | Границы на dark, hover-surface |
| `gray-500` | `#64748B` | Secondary text |
| `gray-300` | `#CBD5E1` | Text on dark (body), muted borders on light |
| `gray-100` | `#F1F5F9` | Light subtle bg, разделитель |
| `white` | `#FFFFFF` | Primary text на dark, surface light |

#### Semantic — Success / Error

| Token | HEX | Роль |
|---|---|---|
| `green` | `#10B981` | Success (разморожено, возвращено, положительные KPI) |
| `red` | `#EF4444` | Error (системные сбои, оплата отклонена) |

#### Return (Deposit — залог с возвратом, semantic) ⭐ NEW

Финмодель проекта использует **списание залога с возвратом** (refund), а не authorization hold. Метафора «↻ вернётся» снимает тревогу: деньги уходят с карты, но визуально и в микрокопи постоянно дублируется обещание возврата.

| Token | HEX | Роль |
|---|---|---|
| `return-3` | `#D1FAE5` | Light green tint — bg карточек депозита, `bg.return-subtle` |
| `return-9` | `#30A46C` | Jade / soft-green — fill иконки ↻, акценты возврата |
| `return-11` | `#047857` | Dark green — **text-safe** для суммы залога (контраст 7+:1 на light bg) |

**Ключевое правило:** денежные суммы по умолчанию — `white` (dark-тема) или `gray-900` (light-карточки). Семантика «залог с возвратом» передаётся иконкой ↻ + tint-карточкой `return-3` + текстом «Залог … вернём после возврата». `return-9` — только fill/icon, `return-11` — только текст. Красный (`red`) — только системные ошибки, не финансовые операции.

#### Freeze (Snowflake — legacy, deprecated для финансовой логики)

Палитра `freeze` сохраняется в кодовой базе **только для статусов «временно недоступно»** (например, полка временно отключена для обслуживания). Для финансовой логики (холд) больше **не используется** — заменена `return-*` токенами выше.

| Token | HEX | Роль (deprecated в финконтексте) |
|---|---|---|
| `freeze` | `#38BDF8` | _(legacy)_ Sky 400 — fill иконки ❄ для статуса «недоступно» |
| `freeze-dark` | `#0369A1` | _(legacy)_ Sky 700 — text-safe для статуса «недоступно» |

### 3.2 Semantic tokens

Основная тема — **dark**. Light-значения применяются к отдельным «warm card» поверхностям (например, локальная light-карточка с фоном `#FAF8F5`), не к глобальному layout.

| Token | Dark (default) | Light (локальные карточки) |
|---|---|---|
| `bg.default` | `dark` (`#0F172A`) | `#FAF8F5` warm off-white |
| `bg.card` | `rgba(255,255,255,0.06)` (glass) / `gray-900` | `white` |
| `bg.elevated` | `gray-900` | `white` с `shadow.md` |
| `bg.brand` | `primary` | `primary` |
| `bg.brand-subtle` | `rgba(37,99,235,0.12)` | `primary-light` |
| `bg.return-subtle` ⭐ | `rgba(48,164,108,0.12)` | `return-3` (`#D1FAE5`) |
| `bg.freeze-subtle` _(legacy)_ | `rgba(56,189,248,0.12)` | `#E0F2FE` |
| `text.primary` | `white` | `gray-900` (`#1E293B`) |
| `text.on-dark` | `gray-300` | — |
| `text.secondary` | `gray-500` | `gray-500` |
| `text.brand` | `primary` | `primary-dark` |
| `text.return` ⭐ | `return-9` | `return-11` (`#047857`) |
| `text.freeze` _(deprecated)_ | `freeze` (только для «недоступно») | `freeze-dark` |
| `text.success` | `green` | `green` |
| `text.error` | `red` | `red` |
| `text.on-brand` | `white` | `white` |
| `icon.return` ⭐ | `return-9` | `return-9` |
| `icon.freeze` _(legacy)_ | `freeze` | `freeze` |
| `icon.success` | `green` | `green` |
| `border.default` | `gray-700` | `gray-300` |
| `border.focus` | `primary` 40% | `primary` 40% |
| `status.deposit-charged` ⭐ | `return-9` | `return-9` |
| `status.deposit-refunding` ⭐ | `return-9` | `return-9` |
| `status.deposit-refunded` ⭐ | `green` | `green` |
| `status.freeze` _(legacy)_ | `freeze` | `freeze` |
| `status.released` | `green` | `green` |
| `status.charged` | `gray-500` | `gray-500` — нейтральный, не красный |
| `status.penalty` | `accent` | `accent-dark` |
| `status.error` | `red` | `red` |

**Gradient accent (utility):** `linear-gradient(135deg, var(--primary), var(--accent))` для крупных цифр KPI и hero stats. Применение локальное — один раз на экран.

**Glassmorphism surface:** `bg.card` на dark = `rgba(255,255,255,0.06)` + `backdrop-filter: blur(12px)` + `border: 1px solid rgba(255,255,255,0.08)`. Утверждённый стиль для bento-карточек на лендинге и в презентации.

---

## 4. Typography Tokens

### 4.1 Семьи

Единственная шрифтовая семья проекта — **Inter** (variable, weight 300–900). Onest и JetBrains Mono **удалены** из дизайн-системы: унифицируемся с бизнес-презентацией. Для mono-контекста (редкие случаи: rental-id, serial) используем системный стек.

| Роль | Font | Format | Fallback |
|---|---|---|---|
| Primary UI / Body / Display | **Inter** variable 300–900 | woff2 | `-apple-system, Segoe UI, Roboto, Arial, sans-serif` |
| Mono (IDs, коды — опционально) | system mono | — | `'SF Mono', Menlo, Consolas, monospace` |
| Email-safe | Inter → Arial → sans-serif | — | system |

### 4.2 Type scale

| Token | Size/LH | Font/Weight | Применение |
|---|---|---|---|
| `display-xl` | 72/80 | Inter 900 (Black) | landing hero desktop |
| `display-lg` | 56/64 | Inter 800 | landing секционные hero, hero stats |
| `display-md` | 32/40 | Inter 800 | mobile hero, крупные суммы, KPI |
| `headline-lg` | 28/36 | Inter 700 | admin page title |
| `headline-md` | 22/28 | Inter 700 | mobile screen title, card title |
| `title-lg` | 18/24 | Inter 600 | карточка инструмента |
| `title-md` | 16/22 | Inter 600 | подзаголовок секции |
| `body-lg` | 16/24 | Inter 400 | основной текст mobile |
| `body-md` | 14/20 | Inter 400 | вторичный текст, admin rows |
| `body-sm` | 13/18 | Inter 400 | caption, admin dense |
| `label-lg` | 14/20 | Inter 500 | CTA primary, tabs |
| `label-md` | 13/16 | Inter 500 | chip, small buttons |
| `caption` | 12/16 | Inter 400 | мета, timestamp |
| `overline` | 11/14 | Inter 600 +1px tracking UPPERCASE | короткие секционные метки |
| `mono-md` | 14/20 | system mono 400 | rental-id, serial, postomat-id |
| `mono-sm` | 13/16 | system mono 400 | admin data cells |

### 4.3 Правила применения

- **Числа/суммы:** `font-variant-numeric: tabular-nums lining-nums` везде без исключений.
- **Rouble ₽:** между числом и знаком — NBSP `U+00A0`. Разделитель разрядов — NBSP или NNBSP `U+202F`. Формат `3 000 ₽`.
- **Gradient stats:** крупные цифры (≥32px) в hero/KPI допускается красить через `background: linear-gradient(135deg, #2563EB, #F59E0B); -webkit-background-clip: text; color: transparent`. Только один раз на экран.
- **Кириллица UPPERCASE:** избегаем в заголовках длиннее 2 слов; `overline` допустим для коротких маркеров (2-6 букв).
- **Кириллица line-height:** body ≥ 1.5 (vs 1.4 для латиницы).
- **Display:** Inter 800/900. Ниже 28px — Inter 600/700.
- **Dark-тема (default):** body `gray-300`, заголовки `white`.
- **Числа в статусе:** крашеные цифры (весь баланс зелёным/красным) — ЗАПРЕЩЕНО. Цвет — только на иконке/чипе рядом. Исключение — разрешённый gradient stats (brand-mood, не semantic).

---

## 5. Spacing / Sizing Tokens

**Base unit:** 8pt (допустим 4pt hairline).

| Token | Value |
|---|---|
| `space.0` | 0 |
| `space.1` | 2 |
| `space.2` | 4 |
| `space.3` | 8 |
| `space.4` | 12 |
| `space.5` | 16 |
| `space.6` | 20 |
| `space.7` | 24 |
| `space.8` | 32 |
| `space.9` | 40 |
| `space.10` | 48 |
| `space.11` | 56 |
| `space.12` | 64 |
| `space.13` | 80 |
| `space.14` | 96 |
| `space.15` | 128 |
| `space.16` | 160 |

**Контекст-defaults:**
- Mobile screen padding: 16–24.
- Mobile card padding: 16 (compact) / 24 (comfortable).
- Admin row-height: 36 default / 32 compact / 28 dense.
- Admin card padding: 8–16.
- Landing section padding-Y: 128–196, padding-X (desktop): 64–128.

**Touch targets:**
- Min 48dp (WCAG 2.5.5 AAA).
- Primary CTA mobile: 56dp height.
- Icon-button: 48×48, visual 24 iconsize.

---

## 6. Radius Tokens

| Token | Value | Применение |
|---|---|---|
| `radius.sm` | 6 | input, chip, admin card |
| `radius.md` | 12 | button, small card, mobile input |
| `radius.lg` | 16 | mobile card, bottom-sheet top |
| `radius.xl` | 24 | hero card, landing bento tile |
| `radius.2xl` | 32 | landing feature illustration |
| `radius.full` | 9999 | avatar, status dot, status pill, map cluster |

**Правило контекста:**
- Mobile → lg/xl (friendlier).
- Admin → sm/md (denser, «рабочий стол»).
- Landing → xl/2xl (воздух, premium-feel без премиальности).

---

## 7. Elevation / Shadow Tokens

| Token | Value | Применение |
|---|---|---|
| `shadow.sm` | `0 1px 2px rgba(0,0,0,0.04)` | subtle divider replacement |
| `shadow.md` | `0 2px 8px rgba(0,0,0,0.04)` | default card, bento tile |
| `shadow.lg` | `0 8px 24px rgba(0,0,0,0.08)` | modal, bottom-sheet |
| `shadow.xl` | `0 16px 48px rgba(0,0,0,0.12)` | dialog, popover, hero landing |
| `shadow.focus` | `0 0 0 3px iris-8 @ 40%` | focus ring (не заменяет outline) |

**Dark mode:** shadows заменяются на **lighter surfaces** (slate-2 → slate-3 → slate-4). Тени игнорируются (плохо читаются на тёмном).

---

## 8. Motion Tokens

### 8.1 Duration

| Token | Value | Use |
|---|---|---|
| `dur.instant` | 80ms | color hover, ripple |
| `dur.fast` | 120ms | toggle, pressed state, admin route |
| `dur.medium` | 240ms | modal, bottom-sheet, page mobile |
| `dur.slow` | 400ms | shared-element, snowflake freeze full cycle |
| `dur.slower` | 600ms | celebration (1× за сессию) |

### 8.2 Easing

| Token | Value | Use |
|---|---|---|
| `ease.standard` | `cubic-bezier(0.2, 0, 0, 1)` | универсальный M3 |
| `ease.emphasized` | `cubic-bezier(0.2, 0, 0, 1)` | важные появления |
| `ease.accelerate` | `cubic-bezier(0.3, 0, 1, 1)` | уходящий |
| `ease.decelerate` | `cubic-bezier(0, 0, 0, 1)` | входящий |
| `ease.spring.soft` | `spring(stiffness:180, damping:20)` | Flutter physical |

### 8.3 Правила

- **Базовый transition:** `transition: opacity 0.5s ease, transform 0.5s ease` (как в project-presentation). Для UI-state (hover, press) — `120ms` / `240ms`.
- **Финансовые события — без bounce.** Никаких elastic/overshoot на суммах.
- **Нет конфетти, нет fireworks.** Мы не казино.
- **Красного цвета на деньгах нет** — только на системных ошибках.
- **`prefers-reduced-motion: reduce` — ПОЛНЫЙ skip** transform, rotate, scale, parallax, shimmer, stagger. Сразу final state. Allowed: opacity-fade ≤ 80ms.
- **Deposit charge (оплата):** цифра `white → return-11` 120ms; ↻ scale 0→1 + soft pulse 200ms; haptic `impact.medium` 1× в 120ms. Никакого shimmer — деньги списались явно, а не «заморозились».
- **Return-cycle (возврат, NEW):** иконка ↻ rotate 0°→360° 600ms ease.standard → морф в ✓ `green` (cross-fade 200ms); цифра остаётся стабильной (БЕЗ зелёной подсветки самого числа); тост snackbar «Возврат 3 000 ₽ обработан · банк зачислит за 1–3 дня». Применимо в DepositChip(refunding→refunded) и landing DepositVisualization шаг 2→3.
- _(legacy, к удалению)_ Snowflake freeze/разморозка — больше не применяется в финансовой логике.
- **Celebration оплаты:** stroke-check 400ms + 1 pulse-ring 600ms. Haptic success. Один раз.

---

## 9. Icon Library

**Основа:** [Material Symbols Rounded](https://fonts.google.com/icons), weight 400, fill off (outlined default). Размеры: 20 / 24 / 32 / 40.

**Ключевые иконки и назначение:**

| Концепт | Material Symbol | Где |
|---|---|---|
| Залог (списание с возвратом) ⭐ | `replay` (↻ круговая стрелка) | DepositChip, push, landing timeline |
| Подтверждение возврата залога | `check_circle` | ReturnSuccess, DepositChip(refunded), admin accept |
| ~~Холд/заморозка~~ _(legacy)_ | `ac_unit` (❄) | только статус «полка временно недоступна» |
| Ошибка | `error` (не `cancel`) | инлайн-ошибка поля |
| Платёж / карта | `credit_card` | PaymentCard |
| QR / СБП | `qr_code_2` | CsbpQrSheet |
| Постамат | `inventory_2` (короб) | map cluster, админка |
| BLE / сигнал | `bluetooth_searching` | BleSignalIndicator |
| Камера | `photo_camera` | GuidedPhotoOverlay |
| Поддержка | `support_agent` | SupportFab |
| Таймер аренды | `timer` | RentalTimer |
| Напоминание | `schedule` | ETA unfreeze |
| Навигация: карта / аренды / каталог / профиль | `map` / `inventory` / `grid_view` / `person` | BottomNavBar |

**Return / Deposit решение:** используем Material Symbols `replay` (↻). Стилизация — `color: return-9` (`#30A46C`), размер 20 (chip) / 48 (onboarding) / 160 (hero onboarding). Кастомный SVG не вводим — избыточно. Для финального состояния «возвращено» — `check_circle` `green`. _(Snowflake `ac_unit` остаётся в библиотеке только для статуса «временно недоступно».)_

**Custom SVG исключения:** только логотипы партнёров (СБП, Max, Т-Банк) из официальных brand kit.

---

## 10. Компоненты

Каждый компонент: **назначение · API · visual · usage · a11y · Flutter + React snippet**.
**Всего: 31 компонент** (28 базовых + 3 postomat-specific: `ShelfMapView`, `ShelfStrapInstruction`, `CrossReturnBanner`).

### 10.1 PrimaryButton

**Назначение.** Основное действие экрана (CTA). Одна на screen-level view.
**API:** `label: string; onPressed; disabled?; loading?; icon?; amount?: number; fullWidth?`.
**Visual:** bg `primary` (`#2563EB`), hover `primary-dark` (`#1D4ED8`), text `white` label-lg, radius `md` (12), height 56 (mobile) / 40 (admin), px 24, shadow none.
**Usage:** `Оплатить 500 ₽`, `Войти через Max`, `Далее`.
**A11y:** min 48dp, `semanticsLabel` полный («Оплатить пятьсот рублей»), `disabled` → contrast OK, focus ring `primary` 40% 2dp offset 2.

```dart
FilledButton(
  onPressed: loading ? null : onPressed,
  style: FilledButton.styleFrom(
    backgroundColor: const Color(0xFF2563EB),
    minimumSize: const Size.fromHeight(56),
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    textStyle: Theme.of(context).textTheme.labelLarge,
  ),
  child: Text(amount != null ? 'Оплатить ${formatMoney(amount)}' : label),
);
```

```tsx
export const PrimaryButton: FC<Props> = ({label, amount, loading, ...p}) => (
  <button
    className="h-14 px-6 rounded-xl bg-[var(--primary)] hover:bg-[var(--primary-dark)]
               text-white font-medium text-[14px] leading-5 tracking-[0.01em]
               focus-visible:ring-2 focus-visible:ring-[var(--primary)] ring-offset-2
               disabled:opacity-50 transition-colors duration-[120ms]"
    disabled={loading || p.disabled} {...p}>
    {amount != null ? `Оплатить ${formatMoney(amount)}` : label}
  </button>
);
```

### 10.2 SecondaryButton

**Назначение.** Второстепенное действие (Max, Cancel).
**Visual:** bg transparent, border 1.5px `primary`, text `primary` (dark-тема: `white`), radius 12, height 56/40.
**Код (CSS):** `border-[1.5px] border-[var(--primary)] text-[var(--primary)] bg-transparent`.

### 10.3 TextButton

**Назначение.** Tертичное действие (Пропустить, Подробнее).
**Visual:** text `primary` label-lg, underline on hover, padding 8/12.

### 10.4 IconButton

**API:** `icon; onPressed; label (a11y); badge?`.
**Visual:** 48×48 tappable, icon 24, hover bg `gray-100` (light) / `gray-700` (dark), radius `full`.

### 10.5 PrimaryCta (с суммой)

**Назначение.** CTA с раскрытой суммой до тапа — прозрачность денег.
**Visual:** как PrimaryButton (`primary` blue), но label автоматически рендерит `Оплатить {amount} ₽`. Если есть deposit-сумма — `Оплатить 500 ₽ + Залог 3 000 ₽` (один CTA, две суммы через `+`); ниже caption-chip `Залог 3 000 ₽ ↻ — вернём после возврата` на `bg.return-subtle`.
**Usage:** экран M-07 оплата.

### 10.6 TextField

**API:** `label; value; onChange; placeholder?; error?; helper?; type?; autocomplete?; inputMode?`.
**Visual:** height 56, bg `gray-100` (light) / `rgba(255,255,255,0.04)` (dark), border 1px `gray-300` / `gray-700`, focus → `primary` 2px, radius `sm` (6). Label floating, error inline `red` + `error` icon.
**A11y:** `autocomplete` obligatorily (name, tel, one-time-code); `inputmode` для numeric.

### 10.7 OTPInput

**API:** `length=6; onComplete; autoFocus; flashCall?: boolean`.
**Visual:** 6 боксов 48×56, gap 8, mono-md (system mono), center-aligned. Filled bg `gray-100`/`gray-900`, active border `primary`.
**A11y:** `autocomplete="one-time-code"`, `inputmode="numeric"`, `pattern="\d{6}"`. Paste-from-SMS поддержан (split).
**Flutter:** `pinput` package с кастомной темой.

### 10.8 AmountInput

**Visual:** крупный display-md tabular-nums, суффикс ` ₽` через NBSP. Right-aligned, clear-button слева.

### 10.9 PriceBreakdown

**Назначение.** Разбивка суммы до тапа «Оплатить».
**Visual:** карточка, две строки:
- `К оплате (аренда)` left · `500 ₽` right · цвет `text.primary` · на bg `bg.brand-subtle`.
- `Залог ↻` left · `3 000 ₽` right · text `text.return` (`return-11` light / `return-9` dark) · на bg `bg.return-subtle`. Подпись ниже: «Спишем сейчас, вернём после возврата оборудования».
Divider `border.default` между. Radius `lg`. Обе суммы — фактические списания, разница в обещании refund'а у второй.

### 10.10 DepositChip ⭐ (критичный, замена FreezeChip)

**Назначение.** Индикатор залога с обещанием возврата — иконка ↻ + ТЕКСТ + статус (WCAG 1.4.1).
**API:** `amount: number; status: 'charged' | 'refunding' | 'refunded'`.
**3 состояния:**
- `charged` — списано, активная аренда. Bg `bg.return-subtle`, icon ↻ `return-9`, text `text.return` label-md: `Залог 3 000 ₽ ↻ вернём после возврата`. Тон — нейтрально-обещающий.
- `refunding` — refund в процессе после фото возврата. Та же палитра, иконка ↻ rotating (motion `return-cycle`), text: `Возвращаем залог 3 000 ₽ ↻`. SLA caption: `Банк зачислит за 1–3 дня`.
- `refunded` — возвращено. Bg `success` 12%, icon ✓ `green`, text `text.success`: `Залог 3 000 ₽ ✓ возвращён`.

**Visual:** pill radius-full, padding 12/6, label-md, иконка 16 (chip) / 20 (large).
**A11y:** `aria-label="Залог 3000 рублей, статус: списан, вернём после возврата оборудования"`. При смене status — `aria-live="polite"` announce.

```tsx
<span role="status" aria-label={`Залог ${amount} рублей, статус ${status}`}
      className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full
                 bg-[var(--return-subtle)] text-[var(--return-11)] text-[13px] font-medium">
  <Replay size={16} className={status === 'refunding' ? 'animate-spin-slow' : ''} />
  {status === 'refunded' ? `Залог ${formatMoney(amount)} ₽ возвращён`
   : status === 'refunding' ? `Возвращаем залог ${formatMoney(amount)} ₽`
   : `Залог ${formatMoney(amount)} ₽ · вернём после возврата`}
</span>
```

> Старое имя `FreezeChip` (snowflake + ❄) — **deprecated**, удалить из кода после миграции экранов M-05..M-13.

### 10.11 StatusBadge

**9 статусов:** `draft`, `booked`, `active`, `overdue`, `returning`, `under-review`, `completed`, `disputed`, `cancelled`.
**Visual:** pill radius-full, semantic bg (`primary-light`/`green`@12%/`red`@12% и т.д.), text семантический (`primary`/`green`/`red`/`accent`/`gray-500`), icon-dot 6×6 того же семантического цвета. Высота 24, px 10.

### 10.12 RentalTimer

**API:** `remainingMinutes: number; threshold: {green: 60, amber: 30, red: 5}`.
**Visual:** display-md tabular-nums «Осталось 2 ч 15 мин». Цвет цифры → `text.primary` (не крашим!); семантика через цветную pill рядом: `green` / `accent` / `red`. Update — раз в минуту (не посекундно).
**A11y:** `role="timer"`, `aria-live="off"`, milestone-announce на 5/1/0 минутах через отдельный `aria-live="polite"` region (fix M8).

### 10.13 PostomatCard

**API:** `name; distance; available: number; total: number; imageUrl; status`.
**Visual:** card radius-lg, фото-превью 72×72 left, title title-lg, meta body-md `gray-500`, availability pill справа: `green` если >5, `accent` если 1-4, `gray-500` если 0. Shape пина дублирует статус (fix M9).

### 10.14 EquipmentCard

**API:** `title; priceFrom: number; imageUrl; availableNow: boolean; isNew?`.
**Visual:** bento-style, aspect-ratio 4:3 фото, title title-lg, price display-sm «от 270 ₽/2ч» tabular-nums. Badge `Новинка` или `Доступно сейчас` (green) вместо fake rating — **никаких `★ 4.8 · 127 аренд`** (fix B1, audit-fix).

### 10.15 BentoCard

**API:** `span?: 'default'|'wide'; children; icon?; title; body`.
**Visual:** radius-xl, padding 32, **glassmorphism** на dark (`bg.card` = `rgba(255,255,255,0.06)` + `backdrop-filter: blur(12px)` + `border 1px rgba(255,255,255,0.08)`) или `white` на light-поверхностях с `shadow.md`. Иконка 40 на `primary-light` / `rgba(37,99,235,0.15)` circle 56. Title title-lg Inter 700. На landing — stagger reveal transition 0.5s ease.

### 10.16 BottomNavBar (mobile, 4 таба)

**Tabs:** Карта / Аренды / Каталог / Профиль.
**Visual:** height 72 + SafeArea, M3 NavigationBar, active indicator `primary-light` pill вокруг иконки (light) / `rgba(37,99,235,0.2)` (dark). Badge на «Аренды» если active rental.

### 10.17 Sidebar (admin)

**Width:** 240 default / 64 collapsed (⌘\).
**Items:** Dashboard · Photo queue (badge) · Постаматы · Каталог · Аренды · Клиенты · Disputes · Финансы · Бухгалтерия · Audit · Настройки.
**Active:** left-border 3dp `primary` + bg `rgba(37,99,235,0.12)` + text `white`.

### 10.18 TabBar

**Visual:** M3 primary tabs, indicator 3dp bottom `primary`, label label-lg.

### 10.19 Toast / Snackbar

**Variants:** `success`, `freeze`, `warning`, `error`, `neutral`.
**Visual:** radius-md, bg `dark` / `gray-900`, text `white`. Height 48, padding 16. Action — text-button label-md `primary-light`.
**A11y:** `role="status"` neutral / `role="alert"` error.

### 10.20 BottomSheet

**Snap-points:** 15% peek · 45% mid · 90% full.
**Visual:** radius-top-xl (24), drag-handle 32×4 `gray-300` (light) / `gray-700` (dark) сверху, padding 16, shadow-lg.
**Flutter:** `DraggableScrollableSheet` или `showModalBottomSheet`.

### 10.21 Dialog / Modal

**Visual:** width 480 desktop / full mobile, radius-lg (16), padding 24, shadow-xl. Title headline-md, actions — row-reverse primary/secondary.

### 10.22 SupportFab ⭐ (M1 fix)

**Назначение.** Persistent помощь на critical screens (M-07, M-08, M-10, M-13).
**API:** `slaMinutes: number; onPressed`.
**Visual:** bottom-right, 56×56 circle, bg `dark` (или `primary`), icon `support_agent` `white`. Label expanded on idle: `Помощь · ~3 мин`.
**A11y:** `semanticsLabel="Открыть чат поддержки, ответ около 3 минут"`.

### 10.23 GuidedPhotoOverlay ⭐ (B5 fix — 4 ракурса, audit-fix)

**Назначение.** Photo capture с рамкой-подсказкой. **Фиксируем 4 ракурса: общий · серийник · кейс · ремень закреплён.**
**Критично:** фото — **единственное юридическое доказательство возврата** (полки открытые, замков нет, датчиков веса нет). Без полного сета из 4 фото аренда не закрывается и залог не размораживается.
**API:** `angles: ['общий','серийник','кейс','ремень']; currentIndex; referenceImageUrl; onCapture`.
**Visual:** fullscreen camera, overlay: контур ожидаемого ракурса `primary` 2px dashed, progress «Фото 2 из 4» вверху, thumbnail эталона 80×80 bottom-left, CTA-shutter 72×72 bottom-center. On-device ML Kit blur detection (Android) / server-side (Max).
**Undo:** 30-sec window после capture (fix M12).

### 10.24 BleSignalIndicator

**Назначение.** **BLE используется только как proximity verification** (пользователь рядом со стеллажом) — не как источник команд на открытие. Открытие полки инициирует backend по факту подтверждённой близости.
**API:** `state: 'searching'|'near'|'found'|'failed'; distanceM?: number; failReason?: 'bt-off'|'rssi-low'|'timeout'`.
**Visual:** concentric pulse-rings `freeze` (`#38BDF8`), центр — postomat icon. Text: «Стеллаж рядом · 3 м». 3 fail-ветки (fix M7): bt-off → intent open-settings; rssi-low → tip «подойдите ближе»; timeout → fallback «Введите 6-значный код со стеллажа».

### 10.25 CsbpQrSheet

**Назначение.** СБП QR + инструкция.
**Visual:** bottom-sheet 70%, QR 240×240 center с логотипом СБП, title «Отсканируйте в банковском приложении», bank-logos strip ниже, copy-amount button.

### 10.26 DepositVisualization (landing, был FreezeVisualization)

**API:** `amount: 3000; autoplay=true`.
**Visual:** 3-step timeline: ↻ Списали залог → ↻ Запустили возврат → ✓ Банк зачислил +3 000 ₽. На mobile вертикальный, desktop горизонтальный. Motion spec — см. §8.3 (return-cycle) и 16-landing §2.4. Animation 2.4s one-time, replay on click. Тональность: «деньги ушли явно, но возврат — обещание сервиса».

### 10.27 AboutService + LegalDocs (M2 fix)

**Секция Profile tab.** ИНН, ОГРН, РКН-реестр (активная ссылка `pd.rkn.gov.ru`), адрес, версия документов оферты/ПДн с датой.

### 10.28 GranularConsents (M3 fix)

**4 раздельных чекбокса 152-ФЗ:** ПДн · маркетинг · передача эквайеру · трансграничная. Каждый — отдельный `<label>` с собственным текстом, без master-checkbox «Согласен со всем».

---

### 10.29 ShelfMapView ⭐ (postomat-specific, audit-fix)

**Назначение.** Визуализация стеллажа 1×N или 2×N **открытых полок** с подсветкой целевой полки. Заменяет устаревшую grid 6×4 «ячеек» из ранних wireframe'ов.
**Состояния полок:**
- `target` — пульсация `primary` (iris) + подсветка
- `free` — `gray-300` (slate-3, light) / `gray-700` (dark)
- `occupied` — `gray-500` (slate-7) с иконкой инструмента
- `strapFault` — `accent` (warning amber), caption «ремень требует проверки»
- `unavailable` — `gray-900` (slate-9) disabled с штриховкой
**Layout:** `linear` (1×N, mobile vertical scroll) / `dual` (2×N, 2 колонки).
**Где:** M-08 (после BLE-proximity confirm — подсвечивает целевую полку), W-04 admin shelf details (мониторинг занятости).

**API (Flutter):**
```dart
ShelfMapView(
  shelves: List<ShelfState>,
  targetIndex: int?,
  layout: ShelfLayout.linear | ShelfLayout.dual,
  onShelfTap: (int) => void,
)
```

**A11y:** каждая полка — отдельный `Semantics` узел `semanticsLabel="Полка {N}, {status}"`; `target` анонсируется через `aria-live="polite"`: «Полка 7 подсвечена».
**Reduced-motion:** пульсация → статичный `primary` fill + иконка ➜.

---

### 10.30 ShelfStrapInstruction ⭐ (postomat-specific, audit-fix)

**Назначение.** Bottom-sheet overlay с анимированной инструкцией по **ремню-фиксатору** (полки открытые, замков нет — оборудование удерживается ремнём).
**Режимы:**
- `pickup` — «Отстегните ремень и снимите [name]» (выдача)
- `return` — «Закрепите оборудование ремнём до щелчка» (возврат)
**Visual:** Lottie-анимация ремня (застёгивание/расстёгивание, loop), title headline-md, body body-lg, CTA `Готово` (primary). Bg `bg.elevated`, radius-top-xl.
**Reduced-motion fallback:** статичная иконка ремня (`lock_open` для pickup / `lock` для return) + текст, без loop.
**Где:** M-04 (выдача, после открытия/подсветки полки) · M-12 (возврат, перед съёмкой фото).

**API:**
```dart
ShelfStrapInstruction(
  mode: StrapMode.pickup | StrapMode.return,
  equipmentName: String,
  onDone: () => void,
)
```

**A11y:** `semanticsLabel` полный: «Отстегните ремень и снимите перфоратор» / «Закрепите перфоратор ремнём до щелчка». Haptic `impact.light` при открытии sheet.

---

### 10.31 CrossReturnBanner ⭐ (postomat-specific, audit-fix)

**Назначение.** Бейдж, информирующий что возврат возможен **в любой стеллаж сети** (cross-return). TTL-reserve 45 мин с момента инициации возврата — бронь свободной полки.
**Visual (mini):** pill radius-full, icon `swap_horiz` + text label-md «В любой из 12 стеллажей · TTL 45 мин». Bg `bg.brand-subtle`, text `text.brand`. Таймер TTL обновляется раз в минуту, при < 5 мин — цвет `accent`.
**Visual (full banner):** карточка в active-rental, body-md + CTA `Показать на карте`.
**Где:** M-12 (active rental → return flow) · M-15 (на карте отдельным фильтром «свободны для возврата»).
**Tap:** открывает фильтрованную карту со стеллажами, где есть свободные полки под текущий типоразмер оборудования.

**API:**
```dart
CrossReturnBanner(
  availableCount: int,
  ttlMinutes: int,   // countdown 45→0
  onTap: () => void,
)
```

**A11y:** `role="status"`, `aria-label="Возврат доступен в 12 стеллажей сети, бронь полки истекает через 45 минут"`. При TTL < 5 мин — `aria-live="assertive"` announce.

---

## 11. Микрокопи Library

| # | Ситуация | Фраза | Surface |
|---|---|---|---|
| 1 | CTA оплаты | `Оплатить 500 ₽ + Залог 3 000 ₽` ⭐ | M-07 |
| 2 | Объяснение залога ⭐ | `Залог 3 000 ₽ ↻ Спишем сейчас, вернём после возврата` | M-07 |
| 3 | Разбивка списания ⭐ | `Списано: 500 ₽ аренда + 3 000 ₽ залог` | M-07 success, M-14 |
| 4 | Дедлайн возврата залога ⭐ | `Вернём за 15 мин после фото возврата · банк зачислит 1–3 дня` | M-07, M-11 |
| 5 | Push charge ⭐ | `Списан залог 3 000 ₽ · вернём после возврата с фото` | push |
| 6 | Push refund initiated ⭐ | `Возврат 3 000 ₽ обработан · банк зачислит за 1–3 дня` | push |
| 7 | Push partial capture | `⚠ Удержали 1 200 ₽ из залога за {причина}. Остаток 1 800 ₽ вернётся` | push |
| 8 | Push payment refund | `Вернули 500 ₽ на карту ·4127` | push |
| 9 | Return success ⭐ | `Возврат залога 3 000 ₽ обработан · банк зачислит за 1–3 дня` | M-13 (fix B4 + deposit) |
| 10 | Экран выдачи | `Полка №7 — возьмите перфоратор` | M-08 |
| 11 | Timer | `Осталось 2 ч 15 мин` | M-11 |
| 12 | Взяли в | `Взяли в 14:00` (caption рядом с timer) | M-11 (fix m3) |
| 13 | Overtime | `Аренда продлена автоматически по тарифу +30%` | M-11 |
| 14 | ETA refund | `Запустим возврат залога в 15 мин после фото возврата` | M-11 (fix M6 + deposit) |
| 15 | Ошибка сети | `Не дождались ответа банка. Деньги не списаны. Попробуйте ещё раз` | M-07 |
| 16 | Decline | `Банк отклонил операцию. Позвоните в банк или попробуйте другую карту` | M-07 |
| 17 | Фото instruct | `Сфотографируйте 4 ракурса: общий, серийник, кейс, деталь` | M-10 (fix B5) |
| 18 | Ожидание проверки | `Фото на проверке. Залог вернём в течение 15 минут` | M-10 |
| 19 | FlashCall | `Сейчас позвонят — не берите трубку. Код = последние цифры номера` | onboarding |
| 20 | BLE searching | `Подойдите ближе к стеллажу` | M-08 |
| 21 | BLE bt-off | `Включите Bluetooth — иначе придётся вводить 6-значный код` | M-08 |
| 22 | Max fallback | `Для быстрой выдачи без кода — установите приложение` | Max |
| 23 | Hero лендинга | `Возьмите. Верните. Залог вернётся сам.` | landing hero |
| 24 | Sub лендинга ⭐ | `Аренда инструмента 24/7 через постаматы. Только телефон + СМС` | landing (fix B1) |
| 25 | Счётчик лендинга ⭐ | `Открываемся март 2026 · первая сеть из N постаматов` | landing (fix B1) |
| 26 | Новинка card ⭐ | `Новинка` (вместо fake rating) | M-06 (fix B1) |
| 27 | Celebration (1st return) | `Запустили возврат 3 000 ₽. Деньги придут за 1–3 дня — теперь вы знаете цикл` | M-13 first-time |
| 28 | В обработке → | `Банк получил запрос на возврат` | M-13 (fix m1) |
| 29 | Admin queue empty | `Очередь пуста. Сегодня: 127 решений, avg 11.4s` | W-03 (fix m2) |
| 30 | Admin accept | `Принято · возврат залога запущен` | W-03 snackbar |
| 31 | Undo snackbar | `Решение отменено` | W-03 |
| 32 | Bento trust #1 | `Залог виден до копейки` | landing |
| 33 | Bento trust #3 | `Чек 54-ФЗ сразу в email и приложении` | landing |
| 34 | CTA-band | `Первая аренда — первые 30 минут бесплатно` | landing |
| 35 | Onboarding slide-2 ⭐ | `Залог спишем явно — и вернём после фото возврата` | onboarding |
| 36 | Glossary first-use ⭐ | `залог (deposit с возвратом)` → далее «залог» | all |
| 37 | BLE proximity ОК ⭐ | `Стеллаж рядом. Подойдите к полке {N}` | M-08 (audit-fix) |
| 38 | Подсветка полки ⭐ | `Полка {N} — снимите оборудование` | M-09 (audit-fix) |
| 39 | Инструкция ремень pickup ⭐ | `Отстегните ремень и снимите {name}` | M-04 (audit-fix) |
| 40 | Инструкция ремень return ⭐ | `Закрепите ремнём до щелчка` | M-12 (audit-fix) |
| 41 | Cross-return badge ⭐ | `Возврат — в любой из 12 стеллажей сети` | M-15 (audit-fix) |
| 42 | Фото-валидация (must) ⭐ | `Сделайте 4 фото: общее, серийник, кейс, ремень закреплён` | M-13 (audit-fix) |
| 43 | Фото = доказательство ⭐ | `Фото — единственное подтверждение возврата. Без него аренда не закроется` | M-13 coach-mark (audit-fix) |
| 44 | Залог в истории ⭐ | `Залог 3 000 ₽: списано · возвращено` | M-14 history row |
| 45 | DepositChip charged ⭐ | `Залог 3 000 ₽ ↻ вернём после возврата` | M-11 active rental |
| 46 | DepositChip refunding ⭐ | `Возвращаем залог 3 000 ₽ ↻` | M-13, push |
| 47 | DepositChip refunded ⭐ | `Залог 3 000 ₽ ✓ возвращён` | M-13, M-14 |
| 48 | Disclaimer о банке ⭐ | `Возврат — это refund, банк не пришлёт отдельный push, баланс вырастет за 1–3 дня` | M-13 explainer |

**Итого: 48 микрокопи-фраз** (после миграции с hold-механики на deposit-refund добавлено 5 новых).

---

## 11a. Глоссарий (audit-fix)

Единый словарь терминов постаматной подсистемы. Применяется во всех поверхностях (mobile, admin, landing, push, email). Устаревшие термины «ячейка», «замок», «дверца» **не используются** — в проекте открытые полки без замков.

| Термин | Определение |
|---|---|
| **Полка** | Открытая ячейка стеллажа без замка, с **ремнём-фиксатором**, удерживающим оборудование. Нумеруется `Полка №{N}` внутри стеллажа. |
| **Стеллаж** | Металлический корпус на 16–24 полки, управляется контроллером ESP32 + BLE. **Без тачскрина** — вся интеракция через мобильное приложение. |
| **Ремень-фиксатор** | Стреп с защёлкой-«щелчком», фиксирует оборудование на открытой полке. Отстёгивается при выдаче, закрепляется при возврате (обязательно до съёмки фото). |
| **BLE proximity** | Подтверждение близости пользователя к стеллажу через Bluetooth LE. **Не команда на открытие** — только proximity verification; открытие инициирует backend. |
| **Cross-return** | Возврат в любой стеллаж сети (не обязательно тот же, где брали). TTL-reserve брони свободной полки — **45 мин** с момента инициации возврата. |
| **Фото-доказательство** | 4 обязательных ракурса при возврате (общий · серийник · кейс · ремень закреплён). **Единственное юридическое подтверждение возврата** — датчиков веса/замков на полках нет. |
| **Залог (Deposit)** ⭐ | Безусловное списание суммы (по умолчанию 3 000 ₽) с карты при оплате аренды; возвращается через `refund` после получения и валидации 4-х фото возврата оборудования. SLA refund-инициирования — 15 мин после фото; зачисление на карту — 1–3 дня (банк-зависимо). Метафора `↻` (return-cycle), палитра `return-*` (jade-green) — см. §3.1. |
| **Холд (authorization hold)** _(не используется)_ | Authorization hold через CloudPayments в проекте **не применяется**. В финмодели и UI используется механика «залог-возврат» (deposit + refund) — см. выше. Snowflake-метафора (`freeze` tokens) сохранена только для статусов «временно недоступно». |

---

## 12. Экспорт токенов

### 12.1 CSS Custom Properties

```css
:root {
  /* brand */
  --primary: #2563EB;
  --primary-dark: #1D4ED8;
  --primary-light: #DBEAFE;

  /* accent */
  --accent: #F59E0B;
  --accent-dark: #D97706;

  /* neutral */
  --dark: #0F172A;
  --gray-900: #1E293B;
  --gray-700: #334155;
  --gray-500: #64748B;
  --gray-300: #CBD5E1;
  --gray-100: #F1F5F9;
  --white: #FFFFFF;

  /* semantic status */
  --green: #10B981;
  --red: #EF4444;

  /* return / deposit (semantic — return-cycle metaphor) ⭐ */
  --return-3:  #D1FAE5;     /* bg subtle */
  --return-9:  #30A46C;     /* fill/icon */
  --return-11: #047857;     /* text-safe */

  /* freeze (legacy — только статус «недоступно») */
  --freeze: #38BDF8;
  --freeze-dark: #0369A1;

  /* surfaces (default = dark theme) */
  --bg-default: var(--dark);
  --bg-card: rgba(255, 255, 255, 0.06);
  --bg-brand-subtle: rgba(37, 99, 235, 0.12);
  --return-subtle: rgba(48, 164, 108, 0.12);   /* deposit bg */
  --freeze-subtle: rgba(56, 189, 248, 0.12);   /* legacy */
  --text-primary: var(--white);
  --text-on-dark: var(--gray-300);
  --text-secondary: var(--gray-500);
  --text-return: var(--return-9);
  --text-freeze: var(--freeze);                /* legacy */
  --border-default: var(--gray-700);

  /* radius */
  --r-sm: 6px; --r-md: 12px; --r-lg: 16px; --r-xl: 24px; --r-full: 9999px;

  /* shadows (для light-карточек) */
  --sh-md: 0 2px 8px rgba(0, 0, 0, 0.04);
  --sh-lg: 0 8px 24px rgba(0, 0, 0, 0.08);

  /* motion */
  --dur-fast: 120ms;
  --dur-medium: 240ms;
  --dur-slow: 500ms;         /* project-presentation baseline */
  --ease-std: ease;

  /* gradient utility */
  --gradient-stats: linear-gradient(135deg, var(--primary), var(--accent));
}

/* Light-карточки (локальные поверхности) */
.surface-light {
  --bg-default: #FAF8F5;
  --bg-card: var(--white);
  --text-primary: var(--gray-900);
  --text-on-dark: var(--gray-700);
  --text-return: var(--return-11);
  --text-freeze: var(--freeze-dark);
  --border-default: var(--gray-300);
}

@media (prefers-reduced-motion: reduce) {
  :root { --dur-fast: 0ms; --dur-medium: 80ms; --dur-slow: 80ms; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 80ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
```

### 12.2 Tailwind v4 config

```ts
// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: '#2563EB',
          dark: '#1D4ED8',
          light: '#DBEAFE',
        },
        accent: {
          DEFAULT: '#F59E0B',
          dark: '#D97706',
        },
        dark: '#0F172A',
        gray: {
          900: '#1E293B',
          700: '#334155',
          500: '#64748B',
          300: '#CBD5E1',
          100: '#F1F5F9',
        },
        green: '#10B981',
        red: '#EF4444',
        return: {
          3: '#D1FAE5',
          DEFAULT: '#30A46C',
          11: '#047857',
        },
        freeze: { // legacy: только статус «недоступно»
          DEFAULT: '#38BDF8',
          dark: '#0369A1',
        },
      },
      fontFamily: {
        sans: ['Inter', '-apple-system', 'Segoe UI', 'Roboto', 'Arial', 'sans-serif'],
        mono: ['"SF Mono"', 'Menlo', 'Consolas', 'monospace'],
      },
      borderRadius: { sm:'6px', md:'12px', lg:'16px', xl:'24px' },
      boxShadow: {
        md:'0 2px 8px rgba(0,0,0,.04)',
        lg:'0 8px 24px rgba(0,0,0,.08)',
      },
      transitionTimingFunction: { std:'ease' },
      transitionDuration: { fast:'120ms', medium:'240ms', slow:'500ms' },
    },
  },
};
```

### 12.3 Flutter ColorScheme + Theme Extension

```dart
final lightScheme = ColorScheme(
  brightness: Brightness.light,
  primary: const Color(0xFF5B5BD6),
  onPrimary: Colors.white,
  primaryContainer: const Color(0xFFEBEBFE),
  onPrimaryContainer: const Color(0xFF272962),
  secondary: const Color(0xFFF59E0B),
  onSecondary: Colors.white,
  surface: const Color(0xFFFAF8F5), // warm mobile
  onSurface: const Color(0xFF1C2024),
  outline: const Color(0xFFD8D9E0),
  error: const Color(0xFFE5484D),
  onError: Colors.white,
);

@immutable
class RentColors extends ThemeExtension<RentColors> {
  // deposit / return-cycle (актуальные)
  final Color returnFill;     // 30A46C (icon/accent)
  final Color returnText;     // 047857 (text-safe)
  final Color returnBgSubtle; // D1FAE5
  // legacy freeze (только для «недоступно»)
  final Color freezeFill;     // 4CC3E8
  final Color freezeText;     // 0B6E85
  final Color freezeBgSubtle; // DFF5FA
  // прочее
  final Color successFill;    // 30A46C
  final Color successText;    // 18794E
  final Color errorText;      // CD2B31
  final Color warningText;    // 9A5B00
  const RentColors({
    required this.returnFill, required this.returnText, required this.returnBgSubtle,
    required this.freezeFill, required this.freezeText, required this.freezeBgSubtle,
    required this.successFill, required this.successText,
    required this.errorText, required this.warningText,
  });
  @override RentColors copyWith({
    Color? returnFill, Color? returnText, Color? returnBgSubtle,
    Color? freezeFill, Color? freezeText, Color? freezeBgSubtle,
    Color? successFill, Color? successText, Color? errorText, Color? warningText,
  }) => RentColors(
      returnFill: returnFill ?? this.returnFill,
      returnText: returnText ?? this.returnText,
      returnBgSubtle: returnBgSubtle ?? this.returnBgSubtle,
      freezeFill: freezeFill ?? this.freezeFill,
      freezeText: freezeText ?? this.freezeText,
      freezeBgSubtle: freezeBgSubtle ?? this.freezeBgSubtle,
      successFill: successFill ?? this.successFill,
      successText: successText ?? this.successText,
      errorText: errorText ?? this.errorText,
      warningText: warningText ?? this.warningText,
    );
  @override RentColors lerp(ThemeExtension<RentColors>? other, double t) => this;
}

const rentLightColors = RentColors(
  returnFill: Color(0xFF30A46C),
  returnText: Color(0xFF047857),
  returnBgSubtle: Color(0xFFD1FAE5),
  freezeFill: Color(0xFF4CC3E8),
  freezeText: Color(0xFF0B6E85),
  freezeBgSubtle: Color(0xFFDFF5FA),
  successFill: Color(0xFF30A46C),
  successText: Color(0xFF18794E),
  errorText: Color(0xFFCD2B31),
  warningText: Color(0xFF9A5B00),
);
```

### 12.4 Figma DTCG JSON (фрагмент)

```json
{
  "color": {
    "iris": { "9": { "$value": "#5B5BD6", "$type": "color" } },
    "return": {
      "3":  { "$value": "#D1FAE5", "$type": "color", "$description": "deposit bg subtle" },
      "9":  { "$value": "#30A46C", "$type": "color", "$description": "icon/accent" },
      "11": { "$value": "#047857", "$type": "color", "$description": "text-safe" }
    },
    "freeze": {
      "9":  { "$value": "#4CC3E8", "$type": "color", "$description": "legacy: статус «недоступно»" },
      "11": { "$value": "#0B6E85", "$type": "color", "$description": "legacy text" }
    },
    "semantic": {
      "text": {
        "return": { "$value": "{color.return.11}", "$type": "color" },
        "freeze": { "$value": "{color.freeze.11}", "$type": "color" }
      }
    }
  },
  "motion": {
    "duration": {
      "fast":  { "$value": "120ms", "$type": "duration" },
      "medium":{ "$value": "240ms", "$type": "duration" },
      "slow":  { "$value": "400ms", "$type": "duration" }
    },
    "easing": {
      "standard": { "$value": "cubic-bezier(.2,0,0,1)", "$type": "cubicBezier" }
    }
  },
  "radius": {
    "md": { "$value": "12px", "$type": "dimension" },
    "xl": { "$value": "24px", "$type": "dimension" }
  }
}
```

---

## 13. Governance

### 13.1 Добавление нового токена

1. Предложение PR с обоснованием (usage-case на ≥ 2 экранах).
2. Обязательно: APCA Lc-проверка для color-token, WCAG 4.5:1 fallback.
3. Уровень `-9` (fill) **или** `-11` (text) — никогда не совмещаем.
4. Ревью design-lead + a11y-lead. Без одобрения a11y token не мерджится.

### 13.2 Добавление нового компонента

1. Компонент существует в Figma-library + в коде (Flutter + React) + в этом документе (секция 10).
2. Checklist: API · visual tokens only (не хардкод) · a11y · reduced-motion · dark (если применимо) · story в Storybook / Widgetbook.

### 13.3 Версионирование (SemVer)

- `MAJOR` — breaking: убран/переименован token/prop.
- `MINOR` — добавлены token/component.
- `PATCH` — фиксы цвета, выравнивание, микрокопи.

### 13.4 Deprecation policy

- Deprecated token/component помечается в Figma префиксом `_deprecated/` и в коде JSDoc `@deprecated`.
- Минимум 1 минорная версия (≈ 4–6 недель) до удаления.
- Migration guide в CHANGELOG с replacement.

### 13.5 Sign-off checklist (для каждого релиза)

- [ ] WCAG 2.2 AA контраст ≥ 4.5:1 весь текст (через axe-core в CI).
- [ ] Все color-status = icon + shape + text (не только цвет).
- [ ] `prefers-reduced-motion` = полный skip transform.
- [ ] OTP/tel/cc: autocomplete + inputmode.
- [ ] Snapshot-тесты всех токенов (Widgetbook golden / Storybook).
- [ ] Lighthouse A11y ≥ 95 на landing + admin.
- [ ] 0 axe-core violations в CI.

---

**Конец документа.**
Вопросы, правки, новые токены — PR в `c:\projects\rent-service\docs\design-kit.md` с ссылкой на research-источник.
