artefact-design-system 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,46 @@
1
+ # AGENTS.md — using `artefact-design-system`
2
+
3
+ Ты работаешь в проекте, который установил `artefact-design-system` как зависимость (`npm install artefact-design-system`). Этот файл — про то, как этой системой пользоваться в ЭТОМ проекте.
4
+
5
+ ## Два файла правды — при любой задаче на UI сначала сюда
6
+
7
+ Оба лежат прямо в этом пакете, в `node_modules/artefact-design-system/context/`:
8
+
9
+ | Вопрос | Файл |
10
+ |---|---|
11
+ | Какой компонент уже есть, что он принимает, брать его или писать свой | `context/COMPONENTS.md` |
12
+ | Какое значение (цвет/отступ/радиус/шрифт) использовать, какие есть правила («не больше одной accent-кнопки на экран» и т.п.) | `context/DESIGN.md` |
13
+
14
+ Порядок: **`COMPONENTS.md`** (есть ли уже готовое) → **`DESIGN.md`** (каким токеном это красить) → код. Не изобретай инлайновый цвет/отступ, если для него есть токен — `DESIGN.md` перечисляет их все.
15
+
16
+ Эти два файла обновляются вместе с версией пакета — после `npm update` они у тебя тоже актуальные, не нужно искать их где-то отдельно от кода. В самом пакете (не у тебя в проекте, а при разработке `artefact-design-system`) оба проверяются автоматически перед каждым релизом — линтером формата и скриптом, сверяющим их с реальным кодом токенов/компонентов. Из-за этого в `DESIGN.md`'s frontmatter могут встретиться на первый взгляд странные, но осознанные вещи — не чини их сам:
17
+ - Контраст `button-accent` (белый текст на оранжевом) ниже общего порога 4.5:1 — это ок, порог для крупного/полужирного текста кнопок ниже (3:1), а не баг.
18
+ - Многие цвета в `colors` не привязаны к конкретному пункту в `components` того же файла — это не значит, что они не используются в реальных компонентах (используются, просто `components`-блок — небольшая репрезентативная выборка, не полное отображение кода).
19
+
20
+ ## Подключение
21
+
22
+ ```tsx
23
+ import { Button, Surface, Box, Flex } from "artefact-design-system"
24
+ import "artefact-design-system/style.css" // один раз, в корне приложения
25
+ ```
26
+
27
+ Стили — уже скомпилированный CSS (Tailwind собран при публикации пакета), настраивать Tailwind у себя не нужно. `react`/`react-dom` — peer-зависимости, должны уже быть в проекте (см. `package.json` пакета за точными версиями).
28
+
29
+ ## Как устроена система — что можно строить сверху
30
+
31
+ Компоненты идут четырьмя тирами, снизу вверх: **Foundation** (`Box`/`Flex`/`Grid`/`Stack`/`Surface` — чистое поведение и токены, без идентичности) → **Primitives** (`Icon`/`Text`/`Button`/`Input` и т.п. — атомарные контролы) → **Components** (`Toolbar`/`AdviceCard`/чарты — сборки из примитивов) → **Sections** (`SidebarNav`/`AssistantDock` — готовые bespoke-блоки). Если строишь что-то новое в своём проекте — собирай это ИЗ них (`Box`+токен, не сырой `<div style={{padding: 16}}>`), тогда оно визуально не расходится с остальной системой без дополнительных усилий.
32
+
33
+ `Sections` — намеренно короткий список (сейчас всего два компонента). Пакет обслуживает несколько продуктов сразу, поэтому туда попадает только то, что реально повторяется в нескольких из них, а не любой удобный блок с одной страницы — если тебе нужен похожий bespoke-блок именно в твоём проекте, собери его у себя из Foundation/Primitives/Components, а не жди, что он появится в пакете.
34
+
35
+ Список вариантов/токенов (например, вариантов `Surface`, семантических цветовых ролей) — всегда смотри в реестре, который экспортирует сам пакет (`SURFACE_VARIANTS`, `semanticRoles`, `chartKeys`, `ICON_NAMES` и т.п.), а не угадывай по `COMPONENTS.md`/`DESIGN.md` — реестр гарантированно совпадает с тем, что реально работает в этой версии пакета.
36
+
37
+ ## CSS-специфичность — если переопределяешь стили компонента
38
+
39
+ Инлайн `style` на React-элементе побеждает внешний CSS-класс всегда, независимо от специфичности CSS-селектора. Если пытаешься переопределить внешний вид компонента из пакета через свой CSS-класс, а компонент внутри уже задаёт то же CSS-свойство инлайново — твой класс не сработает молча, без ошибки. Проверяй computed-стиль в браузере, если переопределение не подействовало, а не считай это багом пакета.
40
+
41
+ ## Ограничения
42
+
43
+ - **Не редактируй файлы внутри `node_modules/artefact-design-system`.** Если чего-то не хватает или что-то ведёт себя не так — это повод завести задачу на обновление самого пакета, а не патчить установленную копию (правки потеряются при следующем `npm install`).
44
+ - **Не изобретай второй способ задать цвет/отступ.** Если нужного токена нет в `DESIGN.md` — это либо повод расширить пакет (отдельная задача, не эта), либо использовать нейтральное значение проекта вне этой системы — но не выдумывать хардкод, который выглядит как токен, но им не является.
45
+ - **Не собирай компонент, который уже есть в `COMPONENTS.md`, заново.** Если существующий не подходит по API — сначала посмотри, можно ли обернуть его, а не дублировать логику с нуля.
46
+ - **Обязательные пропсы без дефолта — это осознанно, не недосмотр.** Например, `SidebarNav`'s `items` ничего не подставляет сам за тебя — пакет обслуживает несколько продуктов, у него нет «одного правильного» набора пунктов навигации по умолчанию.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gpogrebnyak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # artefact-design-system
2
+
3
+ Дизайн-система «Артефакт»: Foundation/Primitives/Components/Sections на базе shadcn/ui + Radix, публикуемая как обычный npm-пакет.
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ npm install artefact-design-system
9
+ ```
10
+
11
+ ```tsx
12
+ import { Button, Surface, SidebarNav } from "artefact-design-system"
13
+ import "artefact-design-system/style.css" // один раз, в корне приложения
14
+ ```
15
+
16
+ Стили — один скомпилированный файл (`dist/style.css`), собранный при публикации пакета через Tailwind CLI. Устанавливать/настраивать Tailwind у себя не нужно — все нужные классы уже внутри.
17
+
18
+ ## Разработка
19
+
20
+ ```bash
21
+ npm install
22
+ npm run storybook # витрина — читает src/ напрямую, живой reload
23
+ npm run build # lint:design → check:context → typecheck → dist/*.js+*.d.ts → dist/style.css
24
+ ```
25
+
26
+ ## Документация
27
+
28
+ - Для агента, который ИСПОЛЬЗУЕТ этот пакет в другом проекте — [`/AGENTS.md`](./AGENTS.md).
29
+ - Токены/правила и каталог компонентов — [`context/DESIGN.md`](./context/DESIGN.md), [`context/COMPONENTS.md`](./context/COMPONENTS.md) (оба публикуются вместе с пакетом).
30
+ - Устройство самого формата `DESIGN.md` — [`context/README.md`](./context/README.md).
@@ -0,0 +1,90 @@
1
+ # COMPONENTS.md — каталог
2
+
3
+ Что здесь, а что в `DESIGN.md`: `DESIGN.md` — токены и правила визуального языка (Foundation-уровень). Этот файл — конкретные React-компоненты: что это, какой API, когда брать, когда — нет. Значения (цвета/отступы/радиусы) не повторяются здесь — они указаны по имени токена, расшифровка — в `DESIGN.md`.
4
+
5
+ Источник правды — код (`src/foundation`, `src/primitives`, `src/components`, `src/sections`). Этот файл описывает его состояние на момент написания; если он расходится с кодом — прав код, этот файл нужно поправить.
6
+
7
+ **Обозначения тира:**
8
+ - 🟢 **своё** — написано в этом проекте, полный контроль над API.
9
+ - 🔵 **shadcn/vendored** — стандартный shadcn-компонент (`components/ui/*`),
10
+ используется как есть + иногда токен-оверрайд. Полный список пропсов — в
11
+ документации shadcn (не переписываем её здесь, она устареет быстрее кода).
12
+ - ⚠️ **демо, не компонент** — существует только как демонстрационный код,
13
+ ничего не экспортируется для импорта. Если нужно использовать —
14
+ сначала превратить в реальный компонент.
15
+
16
+ ---
17
+
18
+ ## Foundation
19
+
20
+ Чистое поведение, без цвета/идентичности. Всё выше строится на этом.
21
+
22
+ | Компонент | Тир | Что делает | Когда брать |
23
+ |---|---|---|---|
24
+ | `Box` | 🟢 | Базовый контейнер: `p/px/py`, `radius`, `width/maxWidth/minWidth`, `grow`. Всё остальное (включая `onClick`, `aria-*`) прокидывается на элемент напрямую. | Всегда, когда нужен `<div>` с паддингом/радиусом из шкалы — никогда не пиши `style={{padding: 16}}` вручную. |
25
+ | `Flex` | 🟢 | `Box` + `display:flex`, `direction`, `gap`, `align`, `justify`, `wrap` (по умолчанию `true` — оборачивается сам). | Ряд элементов, которым можно перенестись на новую строку. |
26
+ | `Stack` | 🟢 | `Flex` с `direction="column"` по умолчанию. | Вертикальная колонка — самый частый кейс, есть отдельным именем, чтобы не писать `direction="column"` каждый раз. |
27
+ | `Grid` | 🟢 | `Box` + `display:grid`, `auto-fit`/`minmax` реflow по `minColWidth` (default 280) ИЛИ фиксированное число `columns`. | Сетка карточек, которая сама решает, сколько колонок влезает — не считай breakpoints руками. |
28
+ | `Container` | 🟢 | `Box` + `max-width` (default 1280) + `margin-inline:auto`. | Центрированная колонка контента заданной ширины. |
29
+ | `Surface` | 🟢 | `Box` + один из 5 background-вариантов (`glass/paper/muted/plain/scrim`, реестр `SURFACE_VARIANTS` в `surface.tsx`). | Любая поверхность с фоном из системных токенов. См. `DESIGN.md` → Elevation про разницу `glass` и `scrim`. |
30
+ | `TitledRow` | 🟢 | Секция title-rail + main + опциональный side-rail (`rail`/`gap` в px). Настоящий CSS (`row.css`), не инлайн-стили — брейкпоинт ~1120px требует media query. | Заголовок раздела страницы с (опционально) правой колонкой-советом. Не давай `side`, если контента для side нет — есть модификатор `--no-side`. |
31
+ | `AppShell` | 🟢 | `{sidebar, children}` → флекс сайдбар + колонка `max-width:1280, padding:26px 24px 90px`. Инвариант для всех страниц продукта, не одноразовое решение. | Корень любой реальной страницы. Не пересобирай эти значения руками на уровне страницы — это ровно тот баг, который уже был (сайдбар прилипал не туда). |
32
+
33
+ ## Primitives
34
+
35
+ ### Свои (полный контроль)
36
+
37
+ | Компонент | Что делает | Когда брать / не брать |
38
+ |---|---|---|
39
+ | `Icon` | `<Icon name="..." size weight color label />`. Имя — ключ из governed-реестра `REGISTRY` (Phosphor-глифы; полный список в `ICON_NAMES`). | Всегда через `name`, никогда не импортируй Phosphor-глиф напрямую в компонент выше — тогда список иконок остаётся одним управляемым словарём. Если нужной иконки нет в `REGISTRY` — добавь её туда, не инлайни SVG на месте использования. |
40
+ | `Image` | `<Image src alt width height fit radius loading />`. Настоящий пробел ни у Radix, ни у shadcn (Avatar покрывает только круглый fallback-кейс, не фото/иллюстрацию). | Любое `<img>` в продукте. `alt` обязателен — decorative-картинка получает `alt=""` явно, а не отсутствие атрибута. |
41
+ | `Text` | `<Text as size color weight align truncate />`. `size` — шаг шкалы типографики (`footnote…display`), резолвится в px+lineHeight из `tokens.ts`. | Любая строка текста в компоненте выше должна идти через `Text`, не через сырой `<span className="text-sm">` — единственное место, которое знает, что означает `caption` в px. `weight` не запечён в шаг шкалы — ставь явно (600 — единственное другое реально самохостящееся начертание, см. `DESIGN.md`). |
42
+
43
+ ### shadcn/vendored (стандартный API — см. shadcn-документацию)
44
+
45
+ `Avatar`, `Badge`, `Button`, `Checkbox`, `Input`, `RadioGroup`, `Select`, `Switch`, `Toggle`/`ToggleGroup`, `Tooltip`, `NavigationMenu`, `Progress`, `Separator`, `Dialog`.
46
+
47
+ Используются как есть, без правки исходников. Наши оверрайды — на уровне темы/атрибутов, не пропсов:
48
+ - `[data-slot="button"]`/`[data-slot="input"]`/`[data-slot="select-trigger"]` — высота/радиус пересчитаны в `brand-overrides.css` (см. `DESIGN.md` → Components).
49
+ - Если конкретному месту нужно другое поведение (например, `Select`, который умеет очищаться крестиком) — это уже не «просто shadcn Select», это новый Components-тир композит (см. `FilterSelect` ниже), а не проп на самом `Select`.
50
+
51
+ **⚠️ `Sparkline`/`ScoreRing` — не компоненты.** Демонстрационный код в `src/stories/Chart.stories.tsx` показывает, что Recharts+`ChartContainer` покрывает то, что раньше было кастомным SVG (mini-график, круговой индикатор прогресса). Ничего не экспортируется — если понадобится настоящий спарклайн-компонент, его нужно сначала выделить в реальный файл.
52
+
53
+ ## Components
54
+
55
+ Primitives + Foundation, собранные в конкретную повторяемую сущность.
56
+
57
+ ### Свои композиты
58
+
59
+ | Компонент | Props | Когда брать |
60
+ |---|---|---|
61
+ | `AdviceCard` | `icon, tone("ink"\|"accent"), value, text, note?` | Карточка-совет в side-rail (`TitledRow`'s `side`) — иконка + крупное значение + текст. `tone="accent"` — редкость, для настоящего хайлайта (новый рекорд и т.п.), не дефолт. Не путать с `Alert` (тот — плоское уведомление, без слота под значение) и `EmptyState` (тот — про отсутствие иконки-как-идентичности). |
62
+ | `FilterSelect` | `label, placeholder, value?, onValueChange?, onClear?, options[]` | Пилюля-селект, которая обзаводится крестиком-очисткой при выбранном значении. Обычный `Select` не умеет очищаться — не пытайся добавить это пропом на `Select`, это отдельный композит. |
63
+ | `Toolbar` + `ToolbarGroup` + `ToolbarToggleGroup` + `ToolbarToggleItem` + `ToolbarSeparator` + `ToolbarButton` | `Toolbar({orientation, bare})`; `ToolbarToggleGroup({value, onValueChange})` | Построено на `radix-ui`'s `Toolbar` напрямую (у shadcn нет тулбар-рецепта) — roving-focus между кнопками/тоглами. **`bare=true` обязателен**, если внутри ровно одна уже самоокрашенная группа (`ToolbarToggleGroup` рисует свою pill-плашку) — иначе получится пилюля-в-пилюле (реальный баг, который уже чинили). |
64
+ | `Chip` | Все пропсы `<button>` (`ButtonHTMLAttributes`) | Кликабельная пилюля на `glass`-поверхности — сработала только после того, как `Box` начал прокидывать `onClick` (см. Foundation). Не для статичных тегов без реакции — см. правило «выглядит кликабельным → обязано реагировать» из `DESIGN.md`. |
65
+ | `MessageBubble` | `from("user"\|"bot")`, children | Пузырь чата с «зарезанным» уголком (5px) в сторону отправителя. `user` = тёмная заливка, `bot` = paper. |
66
+ | `TypingIndicator` | без пропсов | Три пульсирующих точки — статус «ассистент думает» перед ответом. |
67
+ | `FormField` (Field.tsx) | `label, htmlFor?, description?, error?, children` | Обёртка shadcn-примитивов `Field/FieldLabel/FieldDescription/FieldError` вокруг любого контрола (Input/Select/Checkbox). Используй это, а не собирай label+control+error руками на месте — тогда ошибка/описание всегда в одном месте разметки. |
68
+ | `Search` | `placeholder?, value?, onChange?, className?` | `InputGroup` (shadcn) + `Icon` как leading addon. Всегда для полей поиска — не собирай Input + абсолютно-позиционированную иконку вручную. |
69
+ | `BarChart` / `LineChart` / `AreaChart` | `data, xKey, series: {key,label,color}[], height?, showLegend?` (+`stacked?` у Area) | Настоящий Recharts через shadcn's `ChartContainer`. `showLegend` по умолчанию `series.length > 1`. Grid/оси — `color.input`, никогда `color.border` (тот прозрачный по правилу). |
70
+ | `DonutChart` | `data: {key,label,value,color}[], height?, showLegend?` | Категориальная разбивка (Pie, `innerRadius:"60%"`). |
71
+ | `DistributionChart` | `groups: {name,total,segments:{label,count,widthPct}[]}[], rowHeight?` | Горизонтальный stacked-bar на группу; если сегменты не сумма в 100% — остаток дорисовывается приглушённым `rest`. Заменил старый самописный `SegmentedBar` (визуально выглядел дешевле настоящих чартов). |
72
+
73
+ ### shadcn/vendored, но на уровне Components (не Primitives)
74
+
75
+ `Card`, `EmptyState` (`empty.tsx`), `Tabs`, `Alert`, `Menu` (`dropdown-menu.tsx`), `Pagination`, `Table`, `Notification` (`sonner.tsx`).
76
+
77
+ Это тоже стандартные shadcn-компоненты, но они — композиционные оболочки (слоты Header/Content/Footer, список пунктов и т.п.), а не атомарный контрол — поэтому классифицированы как Components, не Primitives. API — см. shadcn-документацию.
78
+
79
+ **⚠️ Карточка-метрика (`Card` + `Text` + `LineChart`) — не компонент.** Демонстрационный код в `src/stories/ChartAdvanced.stories.tsx` показывает такую композицию вручную — не экспортируется, есть только как образец, скопируй руками, если нужен такой паттерн.
80
+
81
+ ⚠️ **Известный пробел:** `Notification` (`sonner.tsx`) использует `next-themes`, но `ThemeProvider` в приложении не смонтирован — тема тостов всегда резолвится в `"system"`. Не чинилось, низкий приоритет.
82
+
83
+ ## Sections
84
+
85
+ Секции — намеренно немногочисленные (стоящее правило: `Sections` остаётся компактным, а не превращается во вторую копию `Components`; странично-специфичный блок без повторяющегося прецедента на нескольких реальных экранах продукта не пакуется в новую секцию раньше времени — он остаётся кодом конкретной страницы в потребляющем приложении).
86
+
87
+ | Компонент | Props | Что это |
88
+ |---|---|---|
89
+ | `SidebarNav` | `active?, items (обязателен, NavItem[]), avatarInitials?, avatarTitle?, showSettings?, clientLabel?, clientInitial?` | Левый рейл приложения. `items` — обязательный проп, без дефолта (пакет обслуживает несколько продуктов/клиентов Артефакта — ни один конкретный набор пунктов не может быть «тем самым» дефолтом). `clientLabel` — опциональный чип «какой это клиент»; чип не рендерится вообще, если его не передать, никогда не показывает чьё-то конкретное имя по умолчанию. `EXAMPLE_NAV_ITEMS` экспортирован как пример/фикстура, не как рабочий дефолт. |
90
+ | `AssistantDock` | `title?, intro?, chips?(ChipQuestion[]), fallbackAnswer?, placeholder?` | Плавающий AI-ассистент. Настоящий стейт-мачин (`idle → open → orb`), не статичный снэпшот — `orb` включается, если пользователь скроллит вниз и закрывает панель (`window.scrollY > 120`). `intro`/`fallbackAnswer` — placeholder-дефолты без привязки к конкретному продукту/клиенту, передавай реальный текст явно. Единственный наравне с `SidebarNav` «органический» (bespoke) компонент — блюр-панель/градиентная кнопка/спиннер-иконка — реальный CSS в `AssistantDock.css`, не выражен через Foundation-примитивы (не всё честно сводится к токенам без выдумывания). |
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: Артефакт
3
+ version: "1.0"
4
+ description: >
5
+ Дизайн-система «Артефакт» для аналитических продуктов. Тёплый, спокойный,
6
+ без визуального шума интерфейс поверх shadcn/Radix — токенами и одним
7
+ override-слоем, без правки исходников компонентов. Источник правды для
8
+ этого файла: src/index.css + src/foundation/tokens.ts +
9
+ src/brand-overrides.css.
10
+ colors:
11
+ # --- страница / поверхности ---
12
+ background: "#f9f6f0"
13
+ foreground: "#2c2c2a"
14
+ card: "rgba(255, 255, 255, 0.6)"
15
+ card-foreground: "#2c2c2a"
16
+ popover: "#fffdf8"
17
+ popover-foreground: "#2c2c2a"
18
+ ink-3: "#a3a094"
19
+
20
+ # --- контролы ---
21
+ primary: "#2c2c2a"
22
+ primary-foreground: "#f9f6f0"
23
+ secondary: "#ffffff"
24
+ secondary-foreground: "#2c2c2a"
25
+ muted: "#f1ece1"
26
+ muted-foreground: "#6c6a63"
27
+ border: "transparent"
28
+ input: "#e4e2da"
29
+ ring: "#f75506"
30
+ destructive: "#b23a2e" # shadcn-слот, всегда == danger, отдельного значения нет
31
+
32
+ # --- brand + semantic roles (каждая: base/foreground/hover/soft/soft-foreground) ---
33
+ accent: "#f75506"
34
+ accent-foreground: "#ffffff"
35
+ accent-hover: "color-mix(in srgb, #f75506 85%, black)"
36
+ accent-soft: "#fdeee5"
37
+ accent-soft-foreground: "#f75506"
38
+
39
+ green: "#1f7a54"
40
+ green-foreground: "#ffffff"
41
+ green-hover: "color-mix(in srgb, #1f7a54 85%, black)"
42
+ green-soft: "#eaf2ea"
43
+ green-soft-foreground: "#1f7a54"
44
+
45
+ warn: "#b8862f"
46
+ warn-foreground: "#2c2c2a"
47
+ warn-hover: "color-mix(in srgb, #b8862f 85%, black)"
48
+ warn-soft: "#f6edda"
49
+ warn-soft-foreground: "#b8862f"
50
+
51
+ plum: "#7a4d7d"
52
+ plum-foreground: "#ffffff"
53
+ plum-hover: "color-mix(in srgb, #7a4d7d 85%, black)"
54
+ plum-soft: "#f1e6f0"
55
+ plum-soft-foreground: "#7a4d7d"
56
+
57
+ danger: "#b23a2e"
58
+ danger-foreground: "#ffffff"
59
+ danger-hover: "color-mix(in srgb, #b23a2e 85%, black)"
60
+ danger-soft: "#f6e1df"
61
+ danger-soft-foreground: "#b23a2e"
62
+
63
+ # --- data-viz ---
64
+ chart-1: "#f75506"
65
+ chart-2: "#1f7a54"
66
+ chart-3: "#c9805e"
67
+ chart-4: "#b3a48f"
68
+ chart-5: "#6c6a63"
69
+ chart-surface: "#fbfaf7"
70
+
71
+ # --- оверлеи ---
72
+ scrim: "rgba(0, 0, 0, 0.1)"
73
+ typography:
74
+ footnote:
75
+ fontFamily: "Struve, system-ui, sans-serif"
76
+ fontSize: 12px
77
+ fontWeight: "400"
78
+ lineHeight: 1.45
79
+ caption:
80
+ fontFamily: "Struve, system-ui, sans-serif"
81
+ fontSize: 14px
82
+ fontWeight: "400"
83
+ lineHeight: 1.45
84
+ body:
85
+ fontFamily: "Struve, system-ui, sans-serif"
86
+ fontSize: 16px
87
+ fontWeight: "400"
88
+ lineHeight: 1.55
89
+ subhead:
90
+ fontFamily: "Struve, system-ui, sans-serif"
91
+ fontSize: 18px
92
+ fontWeight: "400"
93
+ lineHeight: 1.35
94
+ title:
95
+ fontFamily: "Struve, system-ui, sans-serif"
96
+ fontSize: 20px
97
+ fontWeight: "400"
98
+ lineHeight: 1.3
99
+ headline:
100
+ fontFamily: "Struve, system-ui, sans-serif"
101
+ fontSize: 24px
102
+ fontWeight: "400"
103
+ lineHeight: 1.25
104
+ display:
105
+ fontFamily: "Struve, system-ui, sans-serif"
106
+ fontSize: 32px
107
+ fontWeight: "400"
108
+ lineHeight: 1.15
109
+ rounded:
110
+ sm: 8.4px
111
+ md: 11.2px
112
+ lg: 14px
113
+ xl: 19.6px
114
+ 2xl: 25.2px
115
+ pill: 9999px
116
+ spacing:
117
+ none: 0px
118
+ xs: 4px
119
+ sm: 8px
120
+ md: 12px
121
+ base: 16px
122
+ lg: 20px
123
+ xl: 24px
124
+ 2xl: 32px
125
+ 3xl: 40px
126
+ 4xl: 56px
127
+ components:
128
+ surface-glass:
129
+ backgroundColor: "{colors.card}"
130
+ rounded: "{rounded.xl}"
131
+ surface-paper:
132
+ backgroundColor: "{colors.secondary}"
133
+ rounded: "{rounded.xl}"
134
+ surface-muted:
135
+ backgroundColor: "{colors.muted}"
136
+ rounded: "{rounded.xl}"
137
+ surface-plain:
138
+ backgroundColor: transparent
139
+ rounded: "{rounded.xl}"
140
+ surface-scrim:
141
+ backgroundColor: "{colors.scrim}"
142
+ rounded: "{rounded.xl}"
143
+ control:
144
+ height: 40px
145
+ rounded: "{rounded.lg}"
146
+ button-primary:
147
+ backgroundColor: "{colors.primary}"
148
+ textColor: "{colors.primary-foreground}"
149
+ rounded: "{rounded.pill}"
150
+ height: 40px
151
+ padding: 0 16px
152
+ button-accent:
153
+ backgroundColor: "{colors.accent}"
154
+ textColor: "{colors.accent-foreground}"
155
+ rounded: "{rounded.pill}"
156
+ height: 40px
157
+ padding: 0 16px
158
+ # Единственный градиент во всей системе — сюда, а не в `colors`: та карта
159
+ # проверяется линтером как чистые CSS-цвета, градиент туда не проходит
160
+ # (`design.md lint` кидал ошибку на colors.accent-gradient). В коде это
161
+ # тот же токен, `color.accentGradient` в tokens.ts — здесь он просто
162
+ # переехал под конкретный компонент, который его использует.
163
+ assistant-dock-btn:
164
+ backgroundColor: "linear-gradient(135deg, #ffb88c 0%, #f75506 50%, #ff5e62 100%)"
165
+ textColor: "{colors.accent-foreground}"
166
+ rounded: "{rounded.pill}"
167
+ height: 40px
168
+ toolbar-toggle-active:
169
+ backgroundColor: "{colors.foreground}"
170
+ textColor: "{colors.background}"
171
+ assistant-dock-panel:
172
+ backgroundColor: "{colors.scrim}"
173
+ rounded: "{rounded.2xl}"
174
+ ---
175
+
176
+ # Артефакт — правила дизайна
177
+
178
+ Токены во frontmatter выше дают агенту точные значения. Ниже — *почему* они такие и как их применять. Меняешь систему на уровне значений — правишь frontmatter; меняешь правило — правишь прозу. Страницы правишь только после того, как поправлен этот файл, а не параллельно с ним.
179
+
180
+ ## Overview
181
+
182
+ Тёплый, спокойный интерфейс с минимумом визуального шума: кремовая нейтральная база, тёмно-серый (не чёрный) текст, и **один** громкий акцентный цвет — оранжевый, используемый редко и целенаправленно (одна filled-CTA такого цвета на экран, не больше). Каждый токен либо реально используется где-то в системе, либо явно помечен как осознанное расширение (см. `danger`, `accent-gradient`, `scrim` ниже) — никаких «запасных» значений на будущее.
183
+
184
+ Технически система лежит **на shadcn/ui + Radix**, не заменяет их: токены и один override-слой (`src/brand-overrides.css`) дают наш вид без единой правки исходников компонентов — так система остаётся синхронизируемой с апстримом shadcn, а не форкает его.
185
+
186
+ ## Colors
187
+
188
+ - **Нейтральная база — тёплая, не серая.** `background`/`card`/`popover` — оттенки cream/paper, не белый и не серый. `card` — 60%-стекло (`rgba(255,255,255,.6)`), а не сплошной белый: острова на странице должны просвечивать фон.
189
+ - **`primary` (дефолтная filled-кнопка) — тёмный ink, не оранжевый.** Обычное заметное действие — тёмное. Оранжевый — это отдельная, более редкая роль.
190
+ - **`accent` — редкость: одна на экран, максимум пара.** Каждая brand/semantic-роль (`accent`, `green`, `warn`, `plum`, `danger`) несёт полный набор из пяти токенов — `base/foreground/hover/soft/soft-foreground` — никогда просто `base` + произвольный фон.
191
+ - `foreground` подобран по **измеренному** WCAG-контрасту, не по предположению «всегда белый» (у `warn` это тёмный ink, а не белый — белый давал хуже контраст).
192
+ - `hover` всегда через `color-mix()` от `base` — пересчитывается сам, никогда не второй захардкоженный hex, который может разойтись с базой.
193
+ - `soft`/`soft-foreground` — бледная плашка-беджик (светлый фон + текст цветом базы).
194
+ - **`danger` (красный) — осознанное расширение палитры.** Подобран так, чтобы читаться как отдельный цвет, а не «грустный оранжевый» (другой тон и насыщенность, приземлённая к семье `accent/green/warn`, а не к выбивающейся насыщенности `accent`).
195
+ - **Единственный градиент во всей системе** — `components.assistant-dock-btn` (кнопка AI-ассистента). Живёт в `components`, не в `colors` — градиент не проходит как CSS-цвет (см. `Do's and Don'ts`). Не заводи второй градиент без такой же осознанной причины — иначе это быстро превращается в декоративный шум.
196
+ - **`scrim` — тёмная подложка низкой прозрачности позади тяжёлого блюра** (панель ассистента). Отличается от `card` (светлое стекло) — не путать.
197
+ - **`border: transparent` — правило, не забытое значение.** Разделение поверхностей — прозрачностью/тенью фона, а не рамкой. Если разделитель реально нужен видимым (инпут, `<hr>`-подобный разделитель) — используй `input` (единственный «настоящий» hairline-токен), никогда не включай `border` обратно.
198
+ - **Известный пробел: тёмная тема не оформлена.** В коде она существует (`.dark` в `index.css`), но это дефолтные нейтральные серые shadcn, не тема бренда — их пока не трогали, при работе с dark mode это нужно сделать осознанно, а не считать готовым.
199
+
200
+ ## Typography
201
+
202
+ - **Один семейный шрифт — Struve**, self-hosted (`public/fonts/`, `src/brand-overrides.css`), подключён ПОВЕРХ дефолтного пресета Geist (Geist — то, что даёт `shadcn`/Tailwind-тема по умолчанию; Struve грузится позже и выигрывает каскад через `--font-sans`). На данный момент самохостятся только два начертания — 400 и 600 (полужирный); отдельного Medium (500) файла нет (CDN-ссылка отдавала 404) — не проси третье начертание, пока файл не появится.
203
+ - Начертание **не** запечено в шкалу размеров — по умолчанию 400, полужирный (600) ставится на конкретных местах через `weight` пропу примитива `Text`, а не как атрибут самого шага шкалы.
204
+ - Глобальная фича шрифта: `font-feature-settings: "ss10" 1` (одноэтажная «a») — включена на `body`, это правило бренда, не убирать.
205
+ - **Шкала — 7 шагов** (`footnote/caption/body/subhead/title/headline/display`, 12–32px). `body` (16px) — «самый важный текст блока»: если решаешь показать что-то крупнее — сначала проверь, действительно ли его смысловой вес выше, чем у остального `body`-текста на этой же поверхности, а не просто «чтобы выделялось».
206
+ - `subhead/title/headline` — реальные, используемые шаги (`Foundation/Row`, страничные шапки, `AssistantDock`), а не проектные заготовки на будущее.
207
+
208
+ ## Layout & Spacing
209
+
210
+ - Шкала отступов (`spacing` во frontmatter) — единый согласованный шаг (0/4/8/12/16/20/24/32/40/56) — бери значение из шкалы, не изобретай промежуточный пиксель под конкретный случай.
211
+ - Радиус (`rounded`) — все шаги производные от одного `--radius` (14px, роль `lg`): `sm`/`md` — доли базового, `xl`/`2xl` — кратные. `pill` — это настоящий pill (9999px), не отдельное число — используется на CTA-кнопках и переключателях.
212
+ - Основная страничная сетка — сайдбар слева (см. `Foundation/AppShell`) + колонка контента `max-width: 1280px`, `padding: 26px 24px 90px` — это инвариант для ЛЮБОЙ страницы продукта, не одноразовое решение под одну конкретную страницу.
213
+
214
+ ## Elevation & Depth
215
+
216
+ - **Тени выключены глобально**, кроме фокус-кольца. Реализовано не через `box-shadow: none` (это убило бы и ring — в Tailwind v4 drop-shadow и focus-ring компилируются в один и тот же `box-shadow` через разные CSS-переменные), а точечно: `--tw-shadow: 0 0 #0000 !important` на `[data-slot]`, оставляя `--tw-ring-shadow` живым.
217
+ - **Фокус всегда виден**: focus-ring — `ring` (брендовый оранжевый). Не убирай ring вместе с тенями — это разные слои одного CSS-свойства.
218
+ - **Стеклянные острова**: `card`-поверхности получают `backdrop-filter: blur(10px) saturate(1.1)` — это про физическую многослойность (полупрозрачный остров над фоном), не декоративный эффект сам по себе.
219
+ - **`scrim` (панель ассистента)** — гораздо сильнее: `blur(40px) saturate(1.4)` на тёмной, почти прозрачной (`rgba(0,0,0,.1)`) подложке — самый глубокий уровень в системе, использовать только для оверлея, который реально перекрывает контент под собой.
220
+
221
+ ## Shapes
222
+
223
+ - Скругления консервативные и последовательные (`sm→2xl`, одна прогрессия от одного базового числа) — не смешивай острые (`radius:0`) и pill-элементы на соседних по смыслу поверхностях.
224
+ - **Pill-радиус — только для действий/переключателей** (кнопки, toggle-групп), не для контентных карточек — карточки используют `xl`/`2xl`.
225
+
226
+ ## Components
227
+
228
+ Компоненты строятся на Radix-примитивах (через `shadcn`, где для задачи есть готовый рецепт, либо напрямую через пакет `radix-ui`, где рецепта нет — например, `Toolbar`) плюс токены этого файла, применяемые СНАРУЖИ через тему и `data-slot`-атрибуты, без правки исходников самих компонентов:
229
+
230
+ - `[data-slot="button"]`/`[data-slot="input"]`/`[data-slot="select-trigger"]` — высота и радиус подтянуты к нашим 40px/pill и 40px/14px соответственно (shadcn ставит собственную шкалу — 32px, туже — по умолчанию, мы её переопределяем на уровне атрибута, не в разметке).
231
+ - `[data-slot="separator"]` — единственное место, где `border` намеренно подменяется на `input`: иначе все внутренние разделители (в `DropdownMenu`, `Select`, `Field`) стали бы невидимыми из-за правила «border transparent».
232
+ - Полный перечень готовых Primitives/Components/Sections, что каждый принимает и когда его брать/не брать — не здесь, это отдельный каталог (`context/COMPONENTS.md`), этот файл описывает только токены и правила их применения.
233
+
234
+ ## Do's and Don'ts
235
+
236
+ - **Don't** — заводить второй захардкоженный hex-цвет там, где нужен brand или semantic оттенок. Всегда через `color.<role>` — единственное исключение — `danger`, который явно помечен как добавленный, а не извлечённый.
237
+ - **Don't** — возвращать `box-shadow` на карточки/острова. Единственный штатный «глубокий» эффект — `backdrop-filter` на `glass`/`scrim`.
238
+ - **Don't** — включать `border` обратно ради видимого разделителя. Нужен видимый — бери `input`.
239
+ - **Don't** — заводить второй градиент без такой же осознанной причины, как у `assistant-dock-btn`; сейчас в системе он ровно один. И держи градиент в `components`, не в `colors` — линтер (`npm run lint:design`) не пропускает нецветовые значения в `colors`.
240
+ - **Don't** — использовать больше одной `accent`-кнопки одновременно на экране — это обесценивает акцент.
241
+ - **Do** — для тёмной темы: явно спроектировать её значения, а не полагаться на дефолтные серые shadcn, которые сейчас там лежат нетронутыми.
242
+ - **Do** — при добавлении нового цвета/варианта сразу добавить его в соответствующий реестр (`SURFACE_VARIANTS`, `semanticRoles`/`chartKeys` и т.п. в `foundation/`), а не только использовать в JSX где-то — иначе он невидим для всего, что читает реестр программно.