# Маппинг токенов в Tailwind (для frontend-dev)

Источник значений — `design/tokens.css` (единственный источник истины).
Ниже — как подключить их в `tailwind.config.ts` проекта `frontend/` через
`tailwind.config` + CSS-переменные, без дублирования значений в JS.

## 1. Подключение

1. Скопировать (или импортировать) `design/tokens.css` в `frontend/src/styles/tokens.css`,
   подключить в корневом `index.css` до Tailwind-директив:
   ```css
   @import './tokens.css';
   @tailwind base;
   @tailwind components;
   @tailwind utilities;
   ```
2. В `tailwind.config.ts` **не хардкодить hex** — ссылаться на CSS-переменные,
   чтобы тема комнаты (`[data-theme="room"]`) и тёмная тема оболочки
   (`[data-theme="dark"]` / `prefers-color-scheme: dark`, см. ниже §1a)
   переопределяли значения без пересборки Tailwind:

```ts
import type { Config } from 'tailwindcss'

export default {
  darkMode: ['selector', '[data-theme="room"]'], // room-тема — не системная OS dark, а локальный скоуп
  content: ['./src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        bg: 'var(--color-bg)',
        'bg-alt': 'var(--color-bg-alt)',
        surface: 'var(--color-surface)',
        border: {
          DEFAULT: 'var(--color-border)',
          strong: 'var(--color-border-strong)',
          pill: 'var(--color-border-pill)',
        },
        ink: {
          900: 'var(--color-ink-900)',
          700: 'var(--color-ink-700)',
          500: 'var(--color-ink-500)',
          400: 'var(--color-ink-400)',
          300: 'var(--color-ink-300)',
        },
        // Акцент не обязан быть зелёным — см. DESIGN_SYSTEM.md §1.1.
        accent: {
          DEFAULT: 'var(--color-accent)',
          hover: 'var(--color-accent-hover)',
          active: 'var(--color-accent-active)',
          text: 'var(--color-accent-text)',
        },
        success: { DEFAULT: 'var(--color-success)', bg: 'var(--color-success-bg)', dot: 'var(--color-success-dot)' },
        danger: { DEFAULT: 'var(--color-danger)', bg: 'var(--color-danger-bg)', dot: 'var(--color-danger-dot)' },
        warning: { DEFAULT: 'var(--color-warning)', bg: 'var(--color-warning-bg)', dot: 'var(--color-warning-dot)' },
        focus: 'var(--color-focus-ring)',

        // Тема комнаты — доступна везде как room-* (реально применяется только
        // внутри [data-theme="room"], т.к. сами переменные там переопределены)
        room: {
          bg: 'var(--color-room-bg)',
          surface: 'var(--color-room-surface)',
          'surface-raised': 'var(--color-room-surface-raised)',
          tile: 'var(--color-room-tile)',
          'tile-border': 'var(--color-room-tile-border)',
          'tile-hover': 'var(--color-room-tile-hover)',
          'text-primary': 'var(--color-room-text-primary)',
          'text-secondary': 'var(--color-room-text-secondary)',
          'text-tertiary': 'var(--color-room-text-tertiary)',
          'mic-on': 'var(--color-room-mic-on)',
          'mic-off': 'var(--color-room-mic-off)',
          'speaker-ring': 'var(--color-room-speaker-ring)',
          danger: 'var(--color-room-danger)',
          'danger-bg': 'var(--color-room-danger-bg)',
          'danger-bg-hover': 'var(--color-room-danger-bg-hover)',
          focus: 'var(--color-room-focus-ring)',
        },
      },
      fontFamily: {
        display: ['Unbounded', 'Arial Rounded MT Bold', '-apple-system', 'Segoe UI', 'sans-serif'],
        body: ['Inter', '-apple-system', 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', 'sans-serif'],
        mono: ['JetBrains Mono', 'SFMono-Regular', 'Menlo', 'Consolas', 'monospace'],
      },
      fontSize: {
        'display-2xl': ['64px', { lineHeight: '1.05', fontWeight: '700' }],
        'display-xl': ['48px', { lineHeight: '1.1', fontWeight: '700' }],
        'display-lg': ['34px', { lineHeight: '1.2', fontWeight: '600' }],
        h1: ['28px', { lineHeight: '1.25', fontWeight: '700' }],
        h2: ['22px', { lineHeight: '1.3', fontWeight: '600' }],
        h3: ['18px', { lineHeight: '1.35', fontWeight: '600' }],
        'body-lg': ['16px', { lineHeight: '1.5' }],
        body: ['14px', { lineHeight: '1.5' }],
        caption: ['12px', { lineHeight: '1.4', letterSpacing: '.04em', fontWeight: '600' }],
        'mono-sm': ['13px', { lineHeight: '1.4', fontWeight: '500' }],
      },
      spacing: {
        1: 'var(--space-1)', 2: 'var(--space-2)', 3: 'var(--space-3)',
        4: 'var(--space-4)', 5: 'var(--space-5)', 6: 'var(--space-6)',
        8: 'var(--space-8)', 10: 'var(--space-10)', 12: 'var(--space-12)',
        16: 'var(--space-16)', 20: 'var(--space-20)', 24: 'var(--space-24)',
      },
      borderRadius: {
        sm: 'var(--radius-sm)', md: 'var(--radius-md)', lg: 'var(--radius-lg)',
        xl: 'var(--radius-xl)', '2xl': 'var(--radius-2xl)', '3xl': 'var(--radius-3xl)',
        full: 'var(--radius-full)',
      },
      boxShadow: {
        sm: 'var(--shadow-sm)', md: 'var(--shadow-md)', lg: 'var(--shadow-lg)',
        glass: 'var(--shadow-glass)',
        'speaker-glow': 'var(--shadow-speaker-glow)',
        'room-panel': 'var(--shadow-room-panel)',
      },
      screens: {
        // desktop-first: минимальная поддерживаемая ширина — 1280px
        min: { raw: '(min-width: 1280px)' },
      },
      transitionDuration: {
        fast: '120ms',
        base: '200ms',
      },
    },
  },
  plugins: [],
} satisfies Config
```

## 1a. Тёмная тема оболочки — контракт для frontend

Механизм (детали и обоснование — `design/DESIGN_SYSTEM.md` §0.1/§1.2b,
реализация значений — `design/tokens.css`):

- Атрибут `data-theme="dark"` / `data-theme="light"` на `<html>` — явный
  выбор пользователя (persist в localStorage/профиле); без атрибута — дефолт
  по `@media (prefers-color-scheme: dark)`.
- Оба триггера переопределяют **те же самые** CSS-переменные, что и светлый
  `:root` (`--color-bg`, `--color-ink-900`, `--color-accent`, …). Так как
  весь маппинг Tailwind-цветов в §1 уже идёт через `var(--color-*)`, а не
  хардкод, **подавляющему большинству компонентов не нужна отдельная
  `dark:`-логика** — те же классы (`bg-bg`, `text-ink-900`, `bg-accent`)
  автоматически перекрашиваются вместе со сменой `data-theme`.
- Существующий Tailwind `dark:`-вариант (`darkMode: ['selector', '[data-theme="room"]']`)
  остаётся зарезервирован **только за комнатой** (см. комментарий в конфиге
  §1) — не путать с тёмной темой оболочки, у них разный контракт (комната не
  переключается, оболочка — переключается и по умолчанию следует ОС).
- Для редких мест, где нужен именно Tailwind-вариант под тёмную оболочку
  (например, компонентные исключения §1.2b DESIGN_SYSTEM.md — glass-обводка,
  focus/error-glow, если их проще выразить `class`, а не `var()`) — завести
  отдельный кастомный вариант `shell-dark:` через `addVariant`, с тем же
  двойным условием, что и в `tokens.css`:

```ts
import plugin from 'tailwindcss/plugin'

export default {
  // ...
  plugins: [
    plugin(({ addVariant }) => {
      addVariant('shell-dark', [
        '&[data-theme="dark"]',
        '@media (prefers-color-scheme: dark) { &:not([data-theme]) }',
      ])
    }),
  ],
} satisfies Config
```

Использование: `shell-dark:border-white/10` и т.п. — только для случаев,
которые принципиально нельзя выразить через `var()` (см. категории 3–4 в
§1.2b DESIGN_SYSTEM.md); для всех обычных цветов компонентов — `var()`-токены,
`shell-dark:` не нужен.

## 2. shadcn/ui

`components.json` продолжает использовать CSS-переменные (`cssVariables: true`),
но вместо стандартной shadcn-палитры (`--primary`, `--secondary`, …) — алиасить
их на токены проекта в `globals.css`:

```css
:root {
  --primary: var(--color-ink-700);
  --primary-foreground: var(--color-surface);
  --secondary: var(--color-bg-alt);
  --destructive: var(--color-danger);
  --destructive-foreground: #FFFFFF;
  --ring: var(--color-focus-ring);
  --radius: 24px; /* соответствует --radius-2xl, база для shadcn radius-cascade */
}

[data-theme='room'] {
  --primary: var(--color-room-mic-on);
  --destructive: var(--color-room-danger-bg);
  --ring: var(--color-room-focus-ring);
}
```

Тёмную тему оболочки (`[data-theme='dark']`) здесь отдельно объявлять не
нужно: блок `:root` выше уже целиком построен на `var(--color-*)`, а эти
переменные сами переопределяются в `tokens.css` под `[data-theme='dark']`/
`prefers-color-scheme` — shadcn-компоненты перекрашиваются вместе с оболочкой
автоматически, без дублирования правил.

`Button` variant `default` переопределить на pill (`rounded-full`) + `bg-accent
text-accent-text hover:bg-accent-hover active:bg-accent-active`
вместо стандартного `bg-primary`. Variant `outline` — `border-border-pill
text-ink-700 rounded-full`.

## 3. Иконки

`lucide-react` устанавливается как обычная npm-зависимость (не CDN),
`stroke-width={2}` по умолчанию через обёртку `<Icon />` в `frontend/src/components/ui/icon.tsx`,
цвет — через `currentColor` (наследуется от текстового токена родителя).
Список иконок по экранам — раздел 6 `DESIGN_SYSTEM.md`.

## 4. Паттерны динамических конференций — для frontend-dev

Макеты `design/mockups/lobby.html`, `calendar.html`, `my-conferences.html`,
`join.html` вводят компоненты, которых нет в статичном списке комнат (см.
`DESIGN_SYSTEM.md` §4.12–4.15). При переносе в `frontend/`:

- **Раскрытие тумблером («Закрытая по паролю», «Закрепить постоянную
  конференцию») и radio-группа повторений** в HTML-макете сделаны чистым
  CSS через `:has()` (без JS/зависимостей) — во frontend это обычный
  **controlled-компонент** (`useState<boolean>` для тумблера,
  `useState<RecurrenceType>` для radio-группы), `:has()` не переносится.
- **Пилюли «ссылка/номер» с копированием** (§4.15) — обёртка над
  `navigator.clipboard.writeText`, состояние «скопировано» — таймер
  `setTimeout` ~1.5s (как в статическом макете `my-conferences.html`), плюс
  `aria-live="polite"` для тултипа «Скопировано» (в макете — визуальный
  `opacity`-переход, без `aria-live`, добавить во frontend).
- **Бейдж статуса конференции** (§4.6) заменяет прежний `Badge` для комнат:
  варианты `scheduled` / `pinned` / `live` / `ended` / `cancelled`; закрытость
  паролем — отдельная иконка `Lock` рядом с заголовком, не вариант `Badge`.
- **Карточки-действия хаба** (§4.12) — фиксированная высота (не `min-height`)
  обязательна для симметрии сетки 2×2; в CSS-grid Tailwind — `grid-rows-2`
  с явной высотой строки (`grid-template-rows: repeat(2, 340px)` через
  `[grid-template-rows:repeat(2,340px)]` или отдельный `h-[340px]` на карточке).
