# Umbra by Aid Beisembin: полный контекст Подключение: в components.json продукта "registries": { "@umbra": "https://aid-design-lovat.vercel.app/r/{name}.json" }, затем npx shadcn@latest add @umbra/<имя> ================================================================================ --- version: alpha name: Umbra description: "Umbra by Aid Beisembin, единая дизайн-система продуктов Aid Beisembin: бенто из плиток, цвет только с ролью, APCA, стекло только поверх. Сгенерировано из tokens/ (DTCG 2025.10) скриптом pnpm design-md." colors: window: "#ECEEEB" tile: "#FFFFFF" nested: "#F3F3F1" popover: "#FFFFFF" text: "#111111" text-muted: "#4D4D4D" border: "#E4E4E2" nav-active: "#333333" nav-active-foreground: "#FFFFFF" loader: "#666666" skeleton: "#EEEEEC" indigo: "#B49DF8" indigo-ink: "#6C4BD7" indigo-soft: "#D1C5FD" giants: "#EA6E38" giants-soft: "#FBC0A9" giants-ink: "#A64217" record: "#D92D20" record-foreground: "#FFFFFF" destructive: "#B6251B" destructive-foreground: "#FFFFFF" on-role: "#111111" primary: "#E4FB77" primary-foreground: "#111111" primary-ink: "#576E00" primary-edge: "#98B620" ring: "#576E00" window-dark: "#1C1C1C" tile-dark: "#0A0A0A" nested-dark: "#1A1A1A" popover-dark: "#212121" text-dark: "#FFFFFF" text-muted-dark: "#CFCFCF" border-dark: "#242424" nav-active-dark: "#383838" loader-dark: "#A3A3A3" skeleton-dark: "#1F1F1F" indigo-ink-dark: "#C3B1F9" giants-ink-dark: "#F1A17E" destructive-dark: "#D92D20" primary-ink-dark: "#E4FB77" primary-edge-dark: "#E4FB77" ring-dark: "#E4FB77" portfolio-primary: "#3044D8" portfolio-primary-foreground: "#FFFFFF" portfolio-primary-ink: "#3044D8" portfolio-primary-ink-dark: "#C4CBFF" portfolio-primary-edge: "#3044D8" portfolio-primary-edge-dark: "#C4CBFF" typography: display: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 36px fontWeight: 700 lineHeight: 40px letterSpacing: -0.02em title: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 22px fontWeight: 700 lineHeight: 28px letterSpacing: -0.01em heading: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 20px fontWeight: 600 lineHeight: 28px letterSpacing: -0.01em timer: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 32px fontWeight: 700 lineHeight: 36px fontFeature: '"tnum" 1' body: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 15px fontWeight: 400 lineHeight: 22px body-web: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 16px fontWeight: 400 lineHeight: 24px label: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 14px fontWeight: 500 lineHeight: 20px caption: fontFamily: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', Inter, 'Helvetica Neue', system-ui, sans-serif fontSize: 13px fontWeight: 400 lineHeight: 18px mono: fontFamily: "'SF Mono', ui-monospace, Menlo, 'IBM Plex Mono', monospace" fontSize: 12px fontWeight: 400 lineHeight: 16px letterSpacing: 0.02em rounded: frame: 56px panel: 40px tile: 24px nested: 16px chip: 8px pill: 9999px window: 17px spacing: "4": 4px "8": 8px "12": 12px "16": 16px "20": 20px "24": 24px "32": 32px tile-gap: 8px tile-pad: 20px inner-gap: 8px components: tile: backgroundColor: "{colors.tile}" textColor: "{colors.text}" rounded: "{rounded.tile}" padding: "{spacing.tile-pad}" tile-nested: backgroundColor: "{colors.nested}" textColor: "{colors.text}" rounded: "{rounded.nested}" padding: "{spacing.16}" tile-primary: backgroundColor: "{colors.primary}" textColor: "{colors.primary-foreground}" rounded: "{rounded.tile}" padding: "{spacing.tile-pad}" tile-giants-soft: backgroundColor: "{colors.giants-soft}" textColor: "{colors.on-role}" rounded: "{rounded.tile}" padding: "{spacing.tile-pad}" tile-indigo-soft: backgroundColor: "{colors.indigo-soft}" textColor: "{colors.on-role}" rounded: "{rounded.tile}" padding: "{spacing.tile-pad}" button-primary: backgroundColor: "{colors.primary}" textColor: "{colors.primary-foreground}" typography: "{typography.label}" rounded: "{rounded.pill}" height: 48px button-secondary: backgroundColor: "{colors.nested}" textColor: "{colors.text}" typography: "{typography.label}" rounded: "{rounded.pill}" height: 48px button-ghost: textColor: "{colors.text}" typography: "{typography.label}" rounded: "{rounded.pill}" height: 48px button-destructive: backgroundColor: "{colors.destructive}" textColor: "{colors.destructive-foreground}" typography: "{typography.label}" rounded: "{rounded.pill}" height: 48px button-icon: rounded: "{rounded.pill}" size: 56px button-hero: backgroundColor: "{colors.record}" rounded: "{rounded.pill}" size: 88px chip: backgroundColor: "{colors.nested}" textColor: "{colors.text}" typography: "{typography.label}" rounded: "{rounded.pill}" height: 40px list-row: textColor: "{colors.text}" typography: "{typography.label}" list-row-meta: textColor: "{colors.text-muted}" typography: "{typography.caption}" sidebar: backgroundColor: "{colors.tile}" rounded: "{rounded.tile}" width: 72px sidebar-item-active: backgroundColor: "{colors.nav-active}" textColor: "{colors.nav-active-foreground}" rounded: "{rounded.pill}" size: 56px notice-card: backgroundColor: "{colors.popover}" textColor: "{colors.text}" rounded: "{rounded.tile}" toast: backgroundColor: "{colors.popover}" textColor: "{colors.text}" rounded: "{rounded.tile}" width: 380px field: backgroundColor: "{colors.nested}" textColor: "{colors.text}" typography: "{typography.body}" rounded: "{rounded.pill}" height: 48px skeleton: backgroundColor: "{colors.skeleton}" rounded: "{rounded.nested}" spinner: textColor: "{colors.loader}" mono-label: textColor: "{colors.text-muted}" typography: "{typography.mono}" rounded: "{rounded.chip}" --- # Umbra by Aid Beisembin: ДНК и правила ## Overview Единая визуальная система для всех продуктов Aid Beisembin: приложений (ShadeNotes), сайта-портфолио и всего, что будет дальше. Собрана из фактических решений ShadeNotes (ветка `aid`, `CLAUDE.md`, `globals.css`, `docs/case/decisions.md`) и портфолио (PR #6 на Vercel, `globals.css`, `page.tsx`) на 7 ноября 2026. Ничего в самих продуктах этим документом не меняется: он фиксирует, что уже решено, и предлагает единую основу для нового Как читать. Файл в формате DESIGN.md (спецификация Google Stitch): YAML-шапка с токенами для агентов и инструментов, затем стандартные разделы Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components, Do's and Don'ts. Это правила, которым следует любой новый продукт. После них дополнительные разделы: «ДНК: восемь принципов», «Движение», «Иконки», «Стекло и движение», «Слой продукта» (что продукт меняет, а что нет), «Расхождения портфолио и ShadeNotes» (с предложением, как их свести) и «Источники». Слово «Предложение» помечает то, что ещё не принято YAML-шапка генерируется из `tokens/` (W3C Design Tokens 2025.10) скриптом `pnpm design-md` и проверяется в CI; руками её не править. В шапке светлая тема и акцент ShadeNotes под основными именами, тёмная тема с суффиксом `-dark`, акцент портфолио с префиксом `portfolio-`. Тело файла правится руками и содержит принятые решения --- ## Colors Имена даны в CSS. В Tailwind они доступны через `@theme` (Tailwind 4) или `theme.extend` (Tailwind 3.4), см. `tokens.css` ### Нейтрали: две темы Тёмная тема это светлая наоборот: светлая держит белые плитки на сером окне, тёмная чёрные плитки на сером окне. Плитка всегда контрастнее окна, вложенная поверхность ближе к окну | Токен | Роль | Светлая | Тёмная | |---|---|---|---| | `--window` | фон окна (рамка вокруг плиток) | `#ECEEEB` | `#1C1C1C` | | `--tile` | плитка | `#FFFFFF` | `#0A0A0A` | | `--nested` | вложенная поверхность внутри плитки, чипы, поля | `#F3F3F1` | `#1A1A1A` | | `--popover` | меню, подсказки, сплошной вариант стекла | `#FFFFFF` | `#212121` | | `--text` | текст | `#111111` | `#FFFFFF` | | `--text-muted` | подписи, второстепенное (по APCA не ниже Lc 75 на окне, плитке, вложенном и поповере) | `#4D4D4D` | `#CFCFCF` | | `--border` | только для полей ввода и редких разделителей | `#E4E4E2` | `#242424` | | `--nav-active` | выбранный пункт сайдбара (таблетка, белая иконка); на витрине сайта Umbra вместо таблетки стеклянная капля (см. «Стекло и движение») | `#333333` | `#383838` | | `--loader` | дуга спиннера | `#666666` | `#A3A3A3` | | `--skeleton` | скелетоны | `#EEEEEC` | `#1F1F1F` | Портфолио сейчас только тёмное и живёт на `#2A2A2A` (рамка) / `#0A0A0A` (основная поверхность) / `#1E1E1E` (карточки) / `#404040` (приподнятое) / текст `#9B9B9B`. Это та же трёхслойная модель окно, плитка, вложенное; в единой системе значения берутся из таблицы ### Роли цвета (общие для всех продуктов) | Роль | Токен | Цвет | Чернила (`-ink`: иконки и текст роли на нейтральной плитке, мелкий текст только если пара проходит Lc 75) | Что значит | |---|---|---|---|---| | ИИ | `--indigo` | `#B49DF8` | светлая `#6C4BD7`, тёмная `#C3B1F9` | итоги, всё, что делает модель | | Предупреждение | `--giants` | `#EA6E38` | светлая `#A64217`, тёмная `#F1A17E` | «не распознано», внимание | | Запись | `--record` | `#D92D20` | тот же | кнопка «Остановить», точка записи; знак и текст на нём `--record-foreground` (белый в обеих темах) | | Опасное | `--destructive` | светлая `#B6251B`, тёмная `#D92D20` (красный записи) | тот же | удаление | | Текст на роли | `--on-role` | `#111111` | | текст на акценте, Indigo и Giants | На красном текст белый: на `--record` и на `--destructive` (в тёмной теме опасное тот же красный `#D92D20`, Lc 77). На насыщенных Indigo и Giants текста нет, только иконки и знаки (белый на Giants даёт 3,09:1, `#111` на Indigo Lc 58). Текст роли стоит на мягком тоне: `--indigo-soft` это Indigo 60 % с белым (Lc 76 с `#111`), `--giants-soft` это Giants 45 % с белым (Lc 76,5): абзацы и списки итогов и предупреждений стоят на них. Чернила `-ink` на нейтральной плитке: иконки (не ниже Lc 45) и крупный полужирный текст от 18 px 600 (не ниже Lc 60). Мелкий текст чернилами разрешён, если пара проходит Lc 75 в `scripts/apca.ts`: решает скрипт, а не цвет. Сейчас проходят `--primary-ink` во всех продуктах и темах и `--indigo-ink`, `--giants-ink` в светлой теме; в тёмной Indigo и Giants `-ink` только крупный полужирный и иконки Зелёного в системе нет: успех показывается галочкой цветом акцента и формой (плитка «загорается»), а не отдельным цветом. Статусные зелёные портфолио (`#22C55E`, `#DCE95E`) в единую систему не входят (см. «Расхождения портфолио и ShadeNotes», Р6) ### Акцент продукта Единственное, что продукт задаёт сам. Три токена, остальное выводится: | Токен | Что это | ShadeNotes | |---|---|---| | `--primary` | цвет акцента: главное действие, выбор, фокус | лайм `#E4FB77` | | `--primary-foreground` | текст на акценте, по APCA не ниже Lc 75 | `#111111` | | `--primary-ink` | акцент как текст и иконка на нейтральной плитке | светлая `#576E00`, тёмная `#E4FB77` | | `--primary-edge` | рамка выбранного чипа на белом, где сам акцент не виден | светлая `#98B620`, тёмная `#E4FB77` | Правило вывода: если акцент светлый (лайм, жёлтый, мятный), текст на нём `#111`, а `-ink` в светлой теме это тот же оттенок, затемнённый до Lc 75 на белом. Если акцент тёмный (кобальт, бордо), текст на нём белый, `-ink` в светлой теме сам акцент, в тёмной акцент осветляется до Lc 75 на `#0A0A0A` Пример для кобальта портфолио `#3044D8`: `--primary-foreground: #FFFFFF` (Lc 89), `--primary-ink` светлая `#3044D8` (Lc 84 на белом), тёмная `#C4CBFF` (Lc 77 на `#0A0A0A`) --- ## Typography Шрифт: SF Pro через системный стек `-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text'`, запасной Inter (latin + cyrillic) для Windows, Linux и веба в не-Apple браузерах. SF Pro в продукт не встраивается (лицензия Apple). Моноширинный для коротких системных подписей: `SF Mono, ui-monospace, Menlo`, запасной IBM Plex Mono. Вес через `font-weight`, не через разные семейства | Роль | Размер / интерлиньяж | Вес | Трекинг | Где | |---|---|---|---|---| | Display | 36 / 40 | 700 | −0.02em | заголовок экрана («Встречи», название встречи) | | Hero (`--type-hero`) | `clamp(28px, 5.6 % ширины плитки, 76px)` / 1.06, одна строка | 600 | −0.028em | только первый экран витрины сайта Umbra; в продуктах запрещён (см. ниже) | | Title | 22 / 28 | 700 | −0.01em | заголовок раздела, шаг онбординга | | Heading | 20 / 28 | 600 | −0.01em | заголовки в вебе и длинных текстах | | Timer | 32 / 36 | 700 | tabular | таймер записи, крупные числа | | Body | 15 / 22 | 400 | 0 | основной текст приложений | | Body web | 16 / 24 | 400 | 0 | чтение на сайте | | Label | 14 / 20 | 500 | 0 | подписи кнопок, строк, чипы | | Caption | 13 / 18 | 400 | 0 | мета, второстепенное | | Mono | 12 / 16 | 400 | +0.02em | системные метки (дата, место, статус), всегда `--text-muted` | Display 36/40 это потолок для всех экранов продуктов и сайта. Единственное исключение `--type-hero`: SF Pro 600, `clamp(28px, 5.6cqi, 76px)`, интерлиньяж 1.06, трекинг −0.028em, по центру, цвет `--text`. Каждая фраза в одну строку без переноса: кегль подобран так, чтобы самая длинная фраза влезала в колонку; место под одну строку (`min-height: 1.06em`), ниже ничего не прыгает при печати. Он только на первом экране витрины сайта Umbra (интро главной с печатью фраз по кругу), в продуктах запрещён. Причина: первый экран витрины работает как обложка, у него нет рабочего содержимого, и заголовок 36 на целой пустой высоте плитки теряется. Размер считается от ширины плитки (`cqi`), а не окна: у сайта есть сайдбар, и 5.6vw окна давали лишний перенос. Токен задан в CSS сайта, а не в `tokens/`: в продукт он не попадает, классов Tailwind у него нет Колонка текста до 760 px. Заголовки, подсказки и короткие статусы с `text-wrap: balance` (колонка около 30 символов для заголовков), абзацы с `text-wrap: pretty` (до 52 символов в плитках). Имя продукта и предлоги не отрываются: неразрывный пробел. Текст на цветном фоне не приглушается (ни `muted`, ни прозрачность) и не тоньше 500 --- ## Layout ### Сетка и отступы Сетка 8 px. Внутри группы «заголовок плюс описание» 8–12, между группами 24–32 | Токен | Значение | Что это | |---|---|---| | `--tile-gap` | 8 | между плитками и от края окна | | `--tile-pad` | 20 | внутренний отступ плитки (во всём продукте один) | | `--inner-gap` | 8 | между плиткой и вложенным | Контролы: обычные 48 px, главные действия 56 px (иконка 24 px без подложки), мелкие 40 px (вкладки, чипы). Главная круглая кнопка продукта 88 px (запись в ShadeNotes), иконка 32. Круглые кнопки-иконки 56 px. Миниатюры состояния в списке 48 px. Сайдбар 72 px, логотип в нём 48 px с радиусом 12 (концентрично), пункты 48 px с иконкой 24. Миниатюры состояния круглые **Внутреннее меньше внешнего** (правило 13, по М. Ильяхову). Расстояние между связанными элементами меньше, чем от них до соседней группы и до края контейнера. Группы задаёт пустота, а не рамки и линии | Ступень | Что | Отступ | |---|---|---| | Пара | подпись и поле, заголовок и описание, иконка и текст | 4–12 | | Пункты одной группы | поля формы, строки, карточки с подписью | 8–16 | | Группы | разделы страницы, блоки плитки | 24–32 | | Край плитки | от содержимого до края | 20 (`--tile-pad`) | Каждая ступень заметно больше предыдущей, соседние ступени не равны: если подпись стоит в 8 от поля, следующее поле не ближе 16 Исключение: залитые контейнеры (плитки бенто, чипы, кнопки, строки на `--nested`). Их край сам отделяет группу, поэтому зазор между ними может быть меньше их внутреннего отступа: плитки с отступом 20 стоят через 8. Правило тогда действует внутри контейнера: содержимое ближе друг к другу, чем к краю Частые ошибки: | Плохо | Хорошо | |---|---| | Подпись поля ближе к предыдущему полю, чем к своему (поля через 8, подпись в 8 от поля) | Подпись в 8 от своего поля, поля через 16–24 | | Заголовок раздела на равном расстоянии от текста сверху и снизу | Заголовок в 8–12 от своего текста, от предыдущего раздела 32 | | Описание карточки дальше от её заголовка, чем от соседней карточки (подпись в 12, карточки через 8) | Подпись в 8–12, незалитые карточки с подписью через 24 | | Иконка в кнопке дальше от текста, чем от края | Иконка в 8 от текста, от края 16–24 | Проверяет `scripts/check-spacing.ts` в `pnpm check`: в собранном сайте подпись ближе к своему полю, заголовок раздела к своему тексту, подпись карточки к своей карточке, чем к соседнему блоку. Живые примеры на странице сайта «Отступы и сетка» (`/layout`) Оптические поправки: у карточки с кнопкой-таблеткой справа правый отступ на 4 px меньше; обложка-иллюстрация заполнена на 60–70 % и стоит чуть выше геометрического центра **Композиция без дыр.** Одна ширина колонки на странице: карточки, код, таблицы и примеры одной ширины, ни один блок не шире соседей, справа не остаётся пустой полосы; абзацы внутри колонки до 760 px. Ряды сетки полные: карточки по 2 в ряд (по 1 на узком окне), в ряду одной высоты; нечётный последний элемент на всю ширину широкой плиткой, а не обрубком (`.umbra-fill` в `base.css`). Пустая колонка допустима только как намеренный отступ по шкале **Выравнивание подписей.** Подпись поля, подсказка и ошибка выравниваются по левому краю поля; текст внутри поля начинается с отступа 20. Так же у селектов, переключателей и карточек: подпись и контрол на одной левой линии, одинаково на всех экранах ### Каркас экрана - **Рамка.** Приложение: окно `--window`, плитки от края через `--tile-gap`. Сайт: на десктопе внешняя рамка `--window` с отступом 8 px и основная поверхность с радиусом 56; на телефоне рамки нет, отступ 16 - **Сайдбар** 72 px отдельной плиткой слева: логотип сверху, служебное внизу (настройки, приватность, тема таблеткой). На macOS над сайдбаром подложка кнопок окна 72 × 26 - **Страница** одной плиткой: липкая шапка (Display, подпись, действия круглыми кнопками 56, чипы-фильтры), при прокрутке шапка становится стеклом, край растворяется. Прокрутка обрезана по радиусу плитки, полоса прокрутки 4 px цветом `--text` 22 %, видна при движении и скрывается через 0,7 с после остановки - **Док действия** внизу по центру поверх контента (запись в ShadeNotes). Снизу у контента запас под него, только если контент до него доходит - **Сетка сайта:** одна колонка до 1024, две от 1024 (320 px сайдбар плюс лента), три от 1280 (сайдбар, лента, виджет 320) --- ## Elevation & Depth Глубина задаётся тремя плоскими слоями, а не тенями: окно `--window`, плитка `--tile`, вложенное `--nested` (см. Colors). Плитка всегда контрастнее окна, вложенная поверхность ближе к окну. Приподнятого слоя в бенто нет (Р9). Над контентом поднимается только стекло: липкая шапка, капсула записи, меню, подсказки, диалоги (принцип 5 в «ДНК»); параметры стекла и его тень в разделе «Стекло и движение». Карточки при наведении на витрине сайта поднимаются на 2 px с мягкой тенью `--glass-shadow`, но остаются плотными. Градиентов и свечений на поверхностях нет --- ## Shapes | Токен | Значение | Что это | |---|---|---| | `--radius-frame` | 56 | внешняя рамка сайта на десктопе (портфолио) | | `--radius-panel` | 40 | крупная панель внутри рамки (чат-виджет портфолио) | | `--radius-tile` | 24 | плитка | | `--radius-nested` | 16 | вложенное = плитка минус `--inner-gap` (концентрично) | | `--radius-chip` | 8 | мелкие моно-метки, миниатюры 40 px | | `--radius-pill` | 9999 | кнопки, поля, чипы, таблетки | | `--window-radius` | 17 | скругление окна macOS (обрезка контента по углу) | --- ## Components Каталог для реестра. У каждого: что это, состояния, из чего собран. Все уже есть в коде ShadeNotes или портфолио, кит их обобщает | Компонент | Описание | Состояния | |---|---|---| | `Tile` | плитка: `--tile`, радиус 24, отступ 20; вариант `nested` (радиус 16, отступ 16, `--nested`), `role` (indigo / giants / primary, мягкие indigo-soft / giants-soft; в коде проп `tone`) | обычная, цветная роли, интерактивная (hover: плитка тон `--nested`, вложенное тон `--border`, primary и indigo 90 % непрозрачности) | | `Page` | колонка страницы: липкая шапка, стекло при прокрутке, edge-fade, запас под док, обрезка по радиусу | шапка плоская / стекло | | `Sidebar` | 72 px, логотип 48, пункты 48 круглые с иконкой 24, активный `--nav-active` (вариант витрины сайта: стеклянная капля `MorphIndicator`, перетекает между пунктами) | | | `WindowBar` | подложка кнопок macOS 72 × 26 | только macOS | | `Button` | таблетка; `primary` (акцент, текст `--primary-foreground`), `secondary` (`--nested`), `ghost` (текст), `destructive`; размеры 40 / 48 / 56; `icon` круглая | hover (primary и destructive 90 % непрозрачности, secondary тон `--border`, ghost подложка `--nested`), active (`translateY(1px)`), disabled нейтральный серый (никогда приглушённый акцент), focus-visible кольцо `--primary-ink` внутри | | `Chip` | фильтр и вкладка, 40 px таблетка на `--nested`; выбранный: рамка 2 px `--primary-edge`, лёгкое стекло `.glass-soft`, `font-medium` | on / off, со счётчиком | | `SearchToggle` | круглая кнопка 56 с лупой, поле выезжает влево до 280 px, лупа поворотом становится крестиком | закрыт, открыт, с текстом | | `ListRow` | строка списка без вложенной карточки: круглая миниатюра состояния 48, заголовок Label 500, мета Caption, шеврон; две колонки от 1100 px | hover, выбрана | | `NoticeCard` | карточка-уведомление у дока: плотное стекло, радиус 24, знак в круге 44 слева, заголовок одной строкой, одно главное действие, тихие второстепенные; появляется из размытия, уходит за `--t-exit` (0.28 с) по `--ease-in`; знак info нейтральный на `--nested`, Indigo только для того, что делает ИИ | info, warning (Giants), с иллюстрацией-подсказкой | | `Dock` | капсула состояния поверх контента (запись): стекло, таймер tabular, полосы уровней 0–1 (источник задаёт продукт; в ShadeNotes это сегменты детектора речи), пауза 56, стоп красный со знаком `--record-foreground` | покой (кнопка 88, иконка 32), активна, пауза | | `Glass` | материал стекла `@umbra/glass`: `.umbra-glass`, плотное `-dense`, `-soft`, индикатор `-select`, преломление `-refract` (Chromium), нажатие `.umbra-press`; обёртка `Glass` с `dense`, `soft`, `pop`, `refract`, `press` | | | `MorphIndicator` | `@umbra/motion`: капля выбора перетекает к выбранной вкладке, чипу, пункту, бегунку; вытягивается по направлению и садится пружиной | покой, движение | | `Spinner`, `Skeleton` | дуга без подложки; скелетоны `--skeleton` | | | `Toast` | уведомление Sonner в стекле, радиус 24, ширина 380 | success, error (знак без красного), warning (Giants) | | `Toggle`, `Select`, `Field` | по shadcn на Radix, таблетки, фокус внутри | | | `Onboarding` | плитка до 600 px по центру окна, точки шага (текущая `--primary-edge`), обложка 8:3 с живым показом, шаги размытием и сдвигом; до трёх шагов, каждый в 1100 × 700 | шаг появляется, уходит; уход экрана в приложение | | `Showcase` | показ работы продукта на первом экране (шаг знакомства): обложка 8:3, сцены по очереди, итоговый кадр держится 1,7 с и гаснет перед повтором (`useDemoScenes`), 60–70 % заполнения, оптический центр; повтор разрешён только здесь | по сценам | | `ProcessScreen` | экран процесса (обработка, импорт, загрузка): сверху текущий шаг 22/28 со спиннером по реальному статусу, ниже настоящее содержимое процесса, факты одной строкой подписью; финал «Готово» с галочкой и явными действиями; автопереход не раньше 3 с без действия человека; без иллюстраций и выдуманного прогресса | идёт, готово, сбой | | `Counter` | число считается вверх 0.5 с ease-out при появлении | | | `Composer` | поле ввода ИИ-чата с живой аурой и кнопкой отправки (портфолио) | idle, busy (дыхание) | | `Mascot` | персонаж продукта: статичный знак в приложениях, живой (настроения, взгляды, жесты) только в портфолио; у каждого продукта свой персонаж | см. «Слой продукта», «Персонажи» | --- ## Do's and Don'ts ### Правила, которые проверяются 1. **APCA.** Текст до 16 px на цвете не ниже Lc 75, крупный полужирный (от 18 px 600) не ниже Lc 60, иконки и знаки не ниже Lc 45. Лайм с `#111` даёт Lc ≈ 96. На насыщенных Indigo и Giants текста нет, только иконки; текст роли на мягких тонах `--indigo-soft` и `--giants-soft`. Мелкий текст чернилами `-ink` только там, где пара проходит Lc 75 в скрипте (в тёмной теме Indigo и Giants `-ink` только крупный полужирный). Проверяет `pnpm apca` на всех парах обеих тем и всех продуктов, исключений нет 2. **Две плитки.** Не больше двух цветных плиток на экране. Indigo вложен в показ, и это не считается третьей 3. **Отступ плитки один** во всём продукте. Шкала 8 / 12 / 16 / 20 / 24 / 32, других значений нет 4. **Без рамок и подложек.** Границы задаёт поверхность. Рамка только у выбранного чипа (2 px акцентом) и у полей ввода 5. **Текст.** Заголовок одна строка или две ровные; одиночных слов нет; текст на цвете не приглушается; неактивная кнопка серая, а не блёклый акцент 6. **Движение.** Одна кривая на появление, одна на уход; у слоя управления (индикатор выбора, бегунок, нажатие, перетекание стекла) пружины `--spring-snappy` и `--spring-smooth`, других кривых нет; суммарная искусственная задержка до 3 с; `reduced-motion` даёт статичный итог; живых элементов на экране не больше одного 7. **Окно.** Каждый экран приложения помещается в 1100 × 700 без прокрутки, если это экран-шаг (онбординг, обработка) 8. **Честность.** Нарисованный показ системного окна стилизован и уменьшен, никогда не стоит рядом с настоящим. Статус в интерфейсе только из реального источника 9. **Копирайт.** Короткие фразы: что случилось и что сделать. Кавычки «ёлочки», короткие тире, без длинных тире. Термин поясняется при первом упоминании. Абзацы, подписи, описания карточек, тосты, подсказки и пункты списков заканчиваются без точки; точки между предложениями внутри остаются. Исключения: код, цитаты, сокращения («т. д.»). Проверяет `scripts/check-periods.ts` в `pnpm check` по собранному сайту и `llms.txt` 10. **Экран процесса показывает реальное содержимое, а не иллюстрацию.** Статус сверху по реальному источнику, ниже то, что процесс производит (транскрипт, файлы), финал «Готово» с явными действиями 11. **Композиция без дыр:** одна ширина колонки на странице; ряды сетки полные; нечётный последний элемент на всю ширину. Карточки в ряду одной высоты, пустая колонка только как намеренный отступ по шкале 12. **Подпись по левому краю поля** (а не по тексту внутри): одинаково у полей, селектов, переключателей и карточек 13. **Внутреннее меньше внешнего:** связанные элементы ближе друг к другу, чем к соседней группе и к краю контейнера; ступени пара 4–12, пункты 8–16, группы 24–32, край плитки 20, соседние ступени не равны. Залитые контейнеры (плитки, чипы, кнопки) могут стоять ближе своего внутреннего отступа (Layout, «Сетка и отступы») ### Запреты - Градиенты и свечения на поверхностях, у кнопок, у плиток. (Исключение: одно живое существо продукта, см. «Слой продукта», «Одно живое существо».) - Приглушённый акцент как неактивное состояние («грязная олива») - Иконки с подложкой, второй набор иконок, спиннеры из иконок - Любой текст на насыщенных Indigo и Giants: на них только иконки и знаки. Мелкий текст чернилами `-ink` там, где пара не проходит Lc 75 - Декоративный цвет без роли, зелёный «для статуса» - Красный для ошибки: ошибка показывается знаком Giants, красный только запись и удаление - Ролик по кругу, фальшивый прогресс, задержка ради анимации. Повтор показа только на шаге знакомства (`Showcase`). На первом экране витрины сайта Umbra допустимы печать фраз по кругу (пауза вне вида и на скрытой вкладке, при `reduced-motion` первая фраза целиком) и подсказка прокрутки (шеврон, покачивание на 4 px с периодом `--t-loop`) - Треугольник вместо шеврона (`Caret*` в заливке) - Стрелки `Arrow*` запрещены, направление только шевроном `Caret*` bold - Текст крупнее Display 36/40. Единственное исключение `--type-hero` на первом экране витрины сайта Umbra; в продуктах запрещено - Стекло на стекле, стекло без контента под ним, `backdrop-filter` у плитки и карточки (предупреждение `umbra/glass-layer`); преломление у больших панелей - Внутреннее не меньше внешнего: подпись ближе к чужому полю, чем к своему; заголовок посередине между разделами; описание карточки ближе к соседней карточке; иконка в кнопке дальше от текста, чем от края - Пустая правая треть, блоки разной ширины на одной странице, неполные ряды разной ширины (ряды по 4, потом 3 растянутых); карточки разной высоты в одном ряду - Иллюстрация вместо содержимого на экране процесса: там настоящий статус и настоящее содержимое (`ProcessScreen`) - Название из первой фразы транскрипта и любые данные пользователя в интерфейсе до того, как они проверены - Реальные встречи, записи, ключи и личные данные в фикстурах и репозиториях --- ## ДНК: восемь принципов 1. **Бенто.** Экран состоит из плиток на фоне окна. Плитка это единица смысла: одна плитка, одна задача. Между плитками воздух, а не линии. Рамок и разделителей нет: границы задают цвет поверхности и расстояние 2. **Цвет только с ролью.** Нейтральная основа, и каждый цвет на экране что-то значит: акцент продукта это главное действие и выбор, Indigo это ИИ, Giants это предупреждение, красный это запись и удаление. Декоративного цвета нет. На экране не больше двух цветных плиток одновременно 3. **Контраст по восприятию.** Читаемость текста на цвете проверяется по APCA (Lc, модель контраста по восприятию; WCAG 2 переоценивает тёмный текст на средне-светлом цвете), а не на глаз и не по WCAG 2 4. **Текст кирпичиком.** Заголовки и подсказки переносятся ровными строками, без лесенки и висящих слов. Текст короткий: что случилось и что сделать. Объяснять показом, а не абзацем 5. **Стекло только поверх.** Полупрозрачное стекло в духе Liquid Glass macOS 26 бывает только у слоя управления, который лежит над контентом: шапка при прокрутке, индикатор выбора, переключатели, капсула записи, меню, подсказки, тосты, диалоги. Поверхности плоские. Градиентов и свечений на поверхностях нет (раздел «Стекло и движение») 6. **Движение объясняет.** Появление из размытия, сдвиг на 8 px, одна кривая ease-out. Длительности 0,2–0,35 с. Анимация показывает, что произошло, и никогда не держит человека ради красоты: нет роликов по кругу и выдуманных процентов. `prefers-reduced-motion` уважается всегда: статичный итоговый кадр 7. **Одно живое существо.** У продукта может быть один живой элемент, который дышит, двигается и реагирует: маскот в портфолио, показ работы на первом экране ShadeNotes. Всё остальное спокойно. Два живых элемента на экране спорят друг с другом 8. **Честность состояний.** Интерфейс показывает только настоящий статус. Неуверенность не трактуется как успех: «Проверим при первой записи», а не «Доступ есть». Нарисованный показ системного окна не должен быть похож на настоящее --- ## Движение | Токен | Значение | Где | |---|---|---| | `--ease-out` | `cubic-bezier(0.22, 1, 0.36, 1)` | всё появление и сдвиги | | `--ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | уход | | `--t-fast` | 0.14–0.2 с | уход, крестики, смена состояния | | `--t-base` | 0.22–0.32 с | появление, смена экрана (0.24), карточка у кнопки (0.32) | | `--t-exit` | 0.28 с | уход карточки-уведомления (NoticeCard), с `--ease-in` | | `--t-slow` | 0.4–0.6 с | крупные панели, счётчики чисел (0.5), логотип | | `--t-stagger` | 120–180 мс | шаг между элементами списка | | `--spring-snappy` | `linear()`, перелёт 2 % | индикатор выбора, бегунок, нажатие (`--t-spring-snappy` 0.35 с) | | `--spring-smooth` | `linear()`, перелёт 0.2 % | перетекание крупного: плавающая панель, шапка, смена темы (`--t-spring-smooth` 0.5 с) | Появление: `opacity 0 → 1`, `scale 0.96 → 1`, `blur 6–8px → 0`, иногда `translateY(8px)`. Циклы живых элементов 1.4–2.6 с, `ease-in-out`. Итоговый кадр показа держится 1.5–2 с, перед повтором затухание, без резкого сброса. Искусственная задержка на весь путь не больше 3 с; когда результат готов, его можно открыть сразу (клик, Enter). При `prefers-reduced-motion`: 0 анимаций, показы стоят на итоговом кадре --- ## Иконки Только `@phosphor-icons/react`, вес `fill` везде. Исключения с весом `bold`: все шевроны `Caret*` (`CaretRight`, `CaretLeft`, `CaretDown`, `CaretUp`), галочка `Check`, крестик `X`, лупа `MagnifyingGlass` (в заливке они рисуются плашкой или диском). Стрелки `Arrow*` запрещены, направление только шевроном `Caret*` bold: ни `ArrowDown`, `ArrowRight`, `ArrowUpRight`, `ArrowLeft`, ни иконок со стрелкой внутри (`CloudArrowUp`, `ArrowsClockwise`); это проверяет правило `umbra/no-arrow-icons`. Шеврон только линией `bold` (2–3 px), 20 px в поле: треугольник (`Caret*` в заливке) запрещён везде, в том числе у `Select`; это проверяет `@umbra/eslint`. Нельзя: варианты `*Circle` и `*Square`, всё, что в заливке становится сплошной плашкой (`Subtitles`, `QuestionMark`). Размеры: 24 в кнопках с текстом, главных действиях, круглых кнопках-иконках 56 px и пунктах сайдбара 48 px; 32 в главной круглой кнопке 88; 16 и 20 в мелких местах. Иконки без подложек. Индикатор ожидания только дуга-спиннер без подложки, цвет `--loader` --- ## Стекло и движение Стекло и движение в духе Liquid Glass macOS 26 и iOS 26. Живые примеры каждого пункта на странице сайта «Стекло и анимация» (`/glass-motion`), код в реестре: `@umbra/glass` (материал, `styles/umbra/glass.css`) и `@umbra/motion` (утилиты и `MorphIndicator`, `styles/umbra/motion.css`). Эталон материала: `.aid-glass` в ShadeNotes ### Два слоя Контент плотный: плитки, карточки, текст, образцы. Стекло только у слоя управления, который плавает поверх: шапка при прокрутке, индикатор выбора, переключатели, меню, подсказки, тосты, диалоги, плавающие панели. Стекло на стекло не кладётся: внутри стеклянной капсулы бегунок и кнопки плотные. Стекло без контента под ним не ставится: капсула над пустой плиткой это просто серая таблетка. Проверяет `umbra/glass-layer` (предупреждение): `backdrop-filter` у плитки и стекло внутри стекла по классам ### Материал `.umbra-glass`: тон `--glass-tint` с `--glass-alpha`, `backdrop-filter: blur(var(--glass-blur)) saturate(180%)`, блик по верхней кромке `inset 0 1px 0 rgb(255 255 255 / var(--glass-rim))`, тонкая кромка `--glass-edge`, мягкая тень `0 12px 32px -12px var(--glass-shadow)` | | Светлая | Тёмная | |---|---|---| | `--glass-tint` / `--glass-alpha` | `#FFFFFF` / 0.70 | `#141414` / 0.72 | | `--glass-alpha-dense` | 0.97 | 0.97 | | `--glass-rim` | 0.85 | 0.16 | | `--glass-edge` | `rgba(0,0,0,.06)` | `rgba(255,255,255,.08)` | | `--glass-shadow` | `rgba(0,0,0,.14)` | `rgba(0,0,0,.5)` | Варианты: обычное `.umbra-glass` и плотное `.umbra-glass-dense` (для текста поверх пёстрого). Лёгкое `.umbra-glass-soft` без тени и края. Индикатор выбора `.umbra-glass-select`: тон `--text` 8 % вместо `--glass-tint`, чтобы капля читалась на однотонной плитке. Базовые `.glass`, `.glass-soft`, `.glass-dense` из `theme-base` это тот же материал без преломления и нажатия. Нижний край шапки растворяется полосой 28 px (`.edge-fade`, scroll edge effect), линии нет. Снизу так же: где прокрутка уходит под плавающее управление (капсула темы внизу сайдбара, док), её нижний край закрыт полосой размытия и затемнения тоном плитки (`backdrop-filter` и `--tile` 72 %), растворённой вверх; линии нет, растворение маской у самой прокрутки там не нужно. При `prefers-reduced-transparency` полоса сплошная `--tile`. Широкие полосы на всю ширину плитки (шапка при прокрутке) берут половину `--glass-blur`: полное размытие поверх маски прокрутки заметно дороже, а на тексте под шапкой разницы не видно ### Преломление Лупа по краю: SVG `feDisplacementMap` в `backdrop-filter` (фильтр `#umbra-refract`, ставит ``), класс `.umbra-glass-refract`. Только Chromium, проверка `@supports`; в Safari и Firefox только blur и saturate. Только у мелких элементов: индикатор выбора, бегунок, тост. У больших панелей нет: дорого ### Концентричность Радиус внутреннего равен внешнему минус отступ: плитка 24, отступ 8, вложенное 16. У управления таблетки: внутренняя таблетка с отступом 4 повторяет внешнюю ### Доступность - `prefers-reduced-transparency` (и `data-transparency="reduce"` на ``): сплошной фон темы без размытия (`--popover`, индикатор выбора `--nested`) - `prefers-reduced-motion`: без перетекания и пружин, только смена прозрачности (длительности токенов 0) - Контраст текста на стекле по APCA проверяется на самом пёстром фоне под ним; `pnpm apca` считает худший случай, когда под стеклом сплошной цвет (текст, акцент, Indigo, Giants, красный записи, поверхности). Итог: мелкий текст (до 16 px) и подписи `--text-muted` только на плотном стекле; на обычном крупный полужирный от 18 px 600 и иконки; мелкий `--text` на обычном стекле только в тёмной теме (светлое обычное стекло над `#111` даёт Lc 65) ### Движение Пружины только у слоя управления; появление контента в потоке остаётся на `--ease-out` | Токен | Значение | Где | |---|---|---| | `--spring-snappy` | `linear()`, перелёт 2 %, `--t-spring-snappy` 0.35 с | индикатор выбора, бегунок, нажатие | | `--spring-smooth` | `linear()`, перелёт 0.2 %, `--t-spring-smooth` 0.5 с | плавающая панель, шапка, появление поверх, смена темы | Перелёт любой пружины не больше 4 %. В `tokens/` у пружин есть `$value` cubic-bezier для инструментов без `linear()`, в CSS точная кривая - **Перетекание (morph).** Индикатор выбора не появляется на новом месте, а перетекает каплей (`MorphIndicator`): во время движения вытягивается по направлению (scale по оси около 1.08), садится пружиной `--spring-snappy` - **Появление поверх.** Из `blur(8px)`, `scale 0.96` и прозрачности (`.umbra-pop-in`); уход быстрее появления, за `--t-exit` по `--ease-in` (`.umbra-pop-out`) - **Нажатие.** `scale 0.97` и усиленный блик кромки на время нажатия (`.umbra-press`) - **Смена страниц.** View Transitions: контент проявляется из `blur(6px)` за `--t-page`, заголовок страницы (`.umbra-vt-title`) перетекает, если он есть на обеих. Только если новая страница готова сразу (данные уже на клиенте): переход замораживает экран до отрисовки, и при загрузке по сети он ощущается долгим. Если страница грузится, выбор в навигации меняется сразу по клику, а контент появляется следом быстрым проявлением из прозрачности и сдвига 4 px за `--t-fast`, без размытия (так на сайте Umbra) - **Смена темы.** View Transition круговым раскрытием от кнопки (`umbraThemeTransition`); при `reduced-motion` мгновенно ### Витрина сайта Umbra На сайте правила применены так: выбранный пункт сайдбара стеклянная капля, которая перетекает между пунктами (в продуктах выбранный пункт остаётся таблеткой `--nav-active`); вкладки и чипы продуктов со стеклянным индикатором; тема стеклянной капсулой с перетекающим бегунком; капля совпадает с пунктом (тот же отступ, что у наведения, текст с отступом 12), наведение только `--nested` без стекла, на выбранном пункте наведение не рисуется; при прокрутке верхний край плитки контента уходит под полосу стекла с размытием и затемнением тоном плитки (`--tile` 80 %): она проявляется постепенно по первым 96 px и растворяется книзу длинным переходом; когда заголовок страницы уходит под край, его текст проявляется на полосе; пока полоса видна, нижнее растворение плитки снято; в интро тег автора и круглые кнопки на плотном стекле; фон интро: плывущий шум точечным дизерингом Байера 8 × 8 (пиксель 2, 4 уровня, точки `--text` 70 % на `--tile`), курсор вдавливает шум вокруг себя; показывается сразу, пауза вне вида и на скрытой вкладке, при `reduced-motion` неподвижный кадр. Вместе с печатью это одно живое существо витрины: в продуктах так нельзя; тост «Скопировано» стеклянный и появляется из размытия; карточки и образцы плотные, при наведении подъём на 2 px и мягкая тень; блоки ниже первого экрана проявляются `umbra-rise` с шагом 60 мс --- ## Слой продукта Продукт меняет только то, что перечислено здесь. Всё остальное берётся из базы без правок ### Что задаёт продукт (`product.css`) ```css :root { --product-name: "ShadeNotes"; --primary: #E4FB77; --primary-foreground: #111111; --primary-ink: #576E00; --primary-edge: #98B620; } .dark { --primary-ink: #E4FB77; --primary-edge: #E4FB77; } ``` Портфолио задаёт `--product-name: "Aidyn Beisembin"` и кобальт `#3044D8` (Colors, «Акцент продукта») Плюс логотип (SVG, плитка цвета акцента, знак на ней) и иконка приложения. Радиус плитки логотипа 52/256 (macOS сетка 824/1024) ### Одно живое существо У продукта один живой элемент. В ShadeNotes это показ работы на первом экране (экран обработки показывает настоящий статус и содержимое, без живого показа). В портфолио это персонаж-ассистент в чате: настроения, взгляды, жесты, ответ на действия, и его аура в композере. Аура это единственное место, где разрешён мягкий градиент, потому что это не поверхность, а свет от живого существа. Второй живой элемент в тот же продукт не добавляется ### Персонажи Семья персонажей Umbra: у каждого продукта свой персонаж, а не один на все. Персонажи разные, их объединяет общая ДНК. Исходные файлы в `brand/` (перенесены из продуктов без изменений), показ на странице сайта «Знаки продуктов» (`/characters`, раздел «Персонажи и логотипы») **Общая ДНК:** - чёрный силуэт (`#0A0A0A` в знаке); - белые глаза; - акцент продукта: плитка под знаком или свечение у живого персонажа; - свои детали у каждого продукта, по ним персонажа узнают **Персонажи продуктов:** | Продукт | Персонаж | Свои детали | Вид | |---|---|---|---| | ShadeNotes | знак `brand/shadenotes/mark.svg` | острый силуэт, глаза-полумесяцы, искра цветом акцента | статичный знак на лаймовой плитке, радиус 52/256; без эмоций, анимации и градиентов (решено в ShadeNotes 6 ноября 2026) | | Портфолио | живой маскот `brand/portfolio/mascot.svg` | круглая голова с ушами, вертикальные глаза-эллипсы `#FFFEF5`, без искры | живой: это «одно живое существо» портфолио, он и есть продукт | Искра есть только у ShadeNotes; в портфолио её не добавлять **Живой или знак.** Правило «одно живое существо» остаётся: живой персонаж только там, где он и есть продукт (портфолио). В приложениях персонаж статичный знак (логотип, иконка, заставка, сайдбар): анимированный персонаж в инструменте отвлекает. Живой компонент живёт в продукте (`assistant-avatar` портфолио) и в реестр не идёт **Живой персонаж портфолио:** - Рисунок: тело тёмное с радиальными градиентами (`#050507` к `#1A132B`), снизу фиолетовый свет (`#5930AF`), мягкий край. Градиенты и свечение ауры разрешены только у живого персонажа: это свет существа, а не поверхность интерфейса. Цвета персонажа не роли интерфейса и в токены не входят - Настроения (8), по приоритету сверху вниз: | Настроение | Когда | Как выглядит | |---|---|---| | `error` | ошибка | наклон, короткая дрожь, капля пота | | `thinking` | запрос ушёл, ответа ещё нет | наклон и покачивание, глаза разного размера, три точки загораются по очереди | | `speaking` | идёт ответ | сжатие и растяжение с маленьким прыжком, пульсируют звуковые дуги | | `happy` | ответ готов, «спасибо» | прыжок с растяжением, искры, румянец | | `wink` | приветствие | подмигивание, кивок, звёздочка у глаза | | `curious` | человек печатает или задержался на приветствии | наклон, взгляд ищет, глаза к полю ввода, «?» | | `sleepy` | 40 с без действий | голова опущена, глубокое дыхание, поднимается «z»; любое действие будит с любопытством | | `idle` | покой | моргание, дыхание, редкие взгляды | - Движения (7), одной фигурой от нижнего центра: `turn`, `lean`, `hop`, `stretch`, `shake`, `settle`, `twitch`. Одно событие за раз; между событиями только моргание, дыхание и взгляды - Взгляд: моргание (иногда двойное), микросаккады, осмотр в восьми направлениях - Размеры: 24 и 40 (глаза и брови крупнее, без аксессуаров и циклов), 64 и 120 (аксессуары настроений, сцены только в 120) - `prefers-reduced-motion`: событий и циклов нет, персонаж стоит - Возможно позже: взгляд за курсором. Сейчас в коде портфолио слежение выключено, глаза двигаются только с настроениями, осмотром и взглядами **Знак для мелких размеров.** У живого персонажа есть плоский знак для 16–24 px: силуэт без градиентов, белые глаза, на плитке акцента продукта с тем же радиусом 52/256. Для портфолио это предложение `brand/portfolio/mark-proposal.svg` (кобальтовая плитка), в продукт пока не поставлен **Как сделать персонажа новому продукту (чек-лист по ДНК):** - [ ] свой силуэт, чёрный, узнаётся по контуру без глаз и деталей; - [ ] белые глаза, читаются в 16 px (в мелких размерах глаза крупнее, как у живого персонажа портфолио); - [ ] одна своя деталь, по которой персонажа отличают от остальных; чужие детали (искра ShadeNotes, уши портфолио) не брать; - [ ] знак на плитке акцента продукта, радиус 52/256 (сетка иконок macOS 824/1024); - [ ] в знаке без градиентов, теней, обводок и эмоций; - [ ] читается в 16, 24, 28 и 48 px на окне и на плитке в обеих темах; - [ ] живой вариант только если персонаж и есть продукт; тогда градиенты ауры как свет существа и `prefers-reduced-motion` останавливает его ### Компоненты продукта Специфичные компоненты (капсула записи, обложка кейса, виджет чата) живут в продукте, но собираются из базовых: плитка, таблетка, стекло, роли цвета, токены движения. Новый компонент продукта не вводит новых цветов, радиусов и кривых --- ## Расхождения портфолио и ShadeNotes Что сейчас разное и как свести. Портфолио и ShadeNotes не трогаем; при следующей крупной правке портфолио подтягивается к базе | # | Тема | ShadeNotes | Портфолио | Предложение | |---|---|---|---|---| | Р1 | Шрифт | SF Pro, запасной Inter | IBM Plex Sans и Mono | **Решено:** SF Pro везде, Inter запасной; моно SF Mono, запасной Plex Mono | | Р2 | Акцент | лайм, текст `#111` | кобальт `#3044D8`, текст белый | У каждого продукта свой. Портфолио остаётся на кобальте, если это осознанный выбор; тогда `-ink` в тёмной теме осветлить до Lc 75: кобальт на `#0A0A0A` даёт Lc 18, как текст и иконка он не читается, годится только как заливка кнопки с белым текстом | | Р3 | Иконки | Phosphor fill | lucide | Phosphor fill; lucide в портфолио заменить при следующей правке | | Р4 | Рамки | нет, границы поверхностью | `rgba(255,255,255,.1)` на чипах и карточках | Убрать рамки, оставить поверхность `--nested`; рамка только у полей | | Р5 | Радиусы | 24 / 16 / таблетки | 56 / 40 / 12 / 8 / 6 | Одна шкала (Shapes): 56 рамка, 40 панель, 24 плитка, 16 вложенное, 8 моно-метка, таблетки. 6 и 12 уходят | | Р6 | Статусные цвета | роли: Indigo, Giants, red | `#22C55E` зелёный, `#DCE95E` «позитив», `#EC4362` негатив | Роли из «Роли цвета». Зелёный убрать: успех галочкой акцента. `#EC4362` заменить на `--destructive` | | Р7 | Градиенты | запрещены | аура композера, стеклянная сфера кнопки отправки, свечение внутри панели чата | Аура остаётся как свет живого существа («Слой продукта», «Одно живое существо»). Стеклянная сфера и внутреннее свечение панели: убрать, кнопка отправки обычная таблетка акцента | | Р8 | Темы | светлая и тёмная | только тёмная | Сайт может быть только тёмным, но токены берёт из таблицы «Нейтрали: две темы», чтобы светлую можно было включить без перекраски | | Р9 | Поверхности | `#1C1C1C` / `#0A0A0A` / `#1A1A1A` | `#2A2A2A` / `#0A0A0A` / `#1E1E1E` / `#404040` | Значения из «Нейтрали: две темы»; `#404040` как «приподнятое» не нужно: приподнятого слоя в бенто нет | | Р10 | Типографика | 36/40, 22/28, 15 | 30/36 600, 20/28 600, 16/24 | Одна шкала Typography: заголовок экрана 36/40 700 и на сайте тоже; 30/36 уходит. Единственное исключение: `--type-hero` (600, до 76 px, `clamp` от 34) на первом экране витрины сайта Umbra (Typography), в продуктах запрещено | | Р11 | Моно-метки | нет | дата, место, статус моно 12/16 | Принять в базу как роль Mono (Typography): полезно для системных меток и в приложениях (таймер, даты) | | Р12 | Кнопка shadcn | таблетки 40/48/56 | shadcn base-nova: 24–36 px, радиус `lg` | Размеры и форма из Layout и Shapes; shadcn-варианты пересобираются под них в ките | | Р13 | Контраст тёмной темы | `--text-muted` `#B3B3B3`, `--indigo-ink` `#B49DF8`, `--giants-ink` `#EA6E38`, `--destructive` `#F97167` с чёрным текстом | текст `#9B9B9B` | **Решено** (APCA, aid-design PR #1): `--text-muted` `#CFCFCF`, `--indigo-ink` `#C3B1F9`, `--giants-ink` `#F1A17E`, `--destructive` `#D92D20` с белым, добавлен `--giants-soft`. ShadeNotes пока на `#B3B3B3` и старых `-ink`, переходит при следующей правке | | Р14 | Персонажи | знак: острый силуэт, глаза-полумесяцы, искра | живой маскот: круглая голова с ушами, вертикальные глаза | **Решено:** у каждого продукта свой персонаж, общая ДНК (чёрный силуэт, белые глаза, акцент продукта). Знак ShadeNotes не упрощение маскота портфолио. Логотипы и иконка приложения не меняются | | Р15 | Взгляд персонажа | статичный знак (решено 6 ноября 2026); `DESIGN.md` в репозитории ShadeNotes устарел: там маскот в сайдбаре живой | глаза за курсором не следят (выключено) | **Решено:** DESIGN.md приведён к коду, взгляд за курсором записан как «возможно позже» | --- ## Источники - ShadeNotes: `frontend/CLAUDE.md` (разделы «Стек интерфейса»), `frontend/src/aid/styles/globals.css`, `docs/case/decisions.md`, PR #17, #18, #19 (ветка `aid`, 7 ноября 2026) - Портфолио: PR #6 `claude/responsive-layout` (включает #4 и `main`), `src/app/globals.css` (токены Paper, 43 штуки), `src/app/page.tsx`, `src/components/ui/button.tsx`, `src/app/layout.tsx` ================================================================================ # Тема: база (@umbra/theme-base) Установка: npx shadcn@latest add @umbra/theme-base Токены и base.css Umbra: нейтрали двух тем, роли цвета, геометрия, типографика, движение, стекло, reduced-motion и reduced-transparency. Ставится первым в любой продукт, один раз. Без него компоненты @umbra не работают. Акцент продукта ставится отдельно: theme-shadenotes или theme-portfolio ## Что это Основа системы: CSS-переменные (`tokens.css`), базовые стили (`base.css`) и мост в Tailwind. Файлы ложатся в `styles/umbra/` - `tokens.css`: окно, плитка, вложенное, текст, роли цвета, стекло, геометрия, шрифт, типографика, движение. Светлая тема в `:root`, тёмная в `.dark` - `base.css`: шрифт SF Pro системным стеком (Inter запасной), `text-wrap: balance` у заголовков и `pretty` у абзацев, полоса прокрутки 4 px цветом `--text` 22 %, `.glass`, `.glass-soft`, `.glass-dense`, `.glass-pop`, `.edge-fade`, `.umbra-fill` (карточки по 2 в ряд, по 1 на узком окне; нечётный последний на всю ширину), утилиты движения (`.umbra-appear`, `.umbra-rise`, `.umbra-leave`, `.umbra-exit`, `.umbra-stagger`, `.umbra-transition`, `.umbra-spin`), правила `prefers-reduced-motion` и `prefers-reduced-transparency` - `tailwind-v4.css`: `@theme inline` и `@utility` для Tailwind 4 - `tailwind-v3-preset.cjs`: пресет для Tailwind 3.4. Классы те же, что в v4, один в один ## Когда брать Всегда, первым шагом в новом продукте и перед любым компонентом @umbra ## Когда не брать Если `tokens.css` уже подключён в продукте вручную. Второй раз не ставить: переменные задаются в одном месте ## Подключение Tailwind 4 (`app/globals.css`): ```css @import "tailwindcss"; @import "../styles/umbra/tokens.css"; @import "../styles/umbra/product.css"; @import "../styles/umbra/tailwind-v4.css"; @import "../styles/umbra/base.css"; ``` Tailwind 3.4 (`tailwind.config.js` и `globals.css`): ```js module.exports = { presets: [require("./styles/umbra/tailwind-v3-preset.cjs")], content: ["./src/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"], }; ``` ```css @import "../styles/umbra/tokens.css"; @import "../styles/umbra/product.css"; @import "../styles/umbra/base.css"; @tailwind base; @tailwind components; @tailwind utilities; ``` Тёмная тема: класс `dark` на `` ## Классы токенов Одинаковы в Tailwind 3.4 и 4 - Цвета: `bg-window`, `bg-tile`, `bg-nested`, `bg-popover`, `text-foreground`, `text-muted`, `bg-primary`, `text-primary-foreground`, `text-primary-ink`, `border-primary-edge`, `bg-indigo`, `bg-indigo-soft`, `text-indigo-ink`, `bg-giants`, `bg-giants-soft`, `text-giants-ink`, `bg-record`, `text-record-foreground`, `bg-destructive`, `text-destructive-foreground`, `text-on-role`, `bg-nav-active`, `text-nav-active-foreground`, `bg-skeleton`, `text-loader`, `ring-ring`, `border-border` - Отступы: `p-tile` (20), `px-tile`, `py-tile`, `pt-tile`, `pr-tile`, `pb-tile`, `pl-tile`, `gap-tile` (8, между плитками), `gap-inner` и `p-inner` (8), `p-edge` (8, от края окна). Остальное по шкале Tailwind: 2, 3, 4, 5, 6, 8 - Размеры: `h-control` (48), `h-control-main` (56), `h-control-sm` (40), `size-control-hero` (88), `w-sidebar` (72), `pb-dock-space` (136), `h-edge-fade` (28), `max-w-text-column` (760) - Радиусы: `rounded-tile` (24), `rounded-nested` (16), `rounded-chip` (8), `rounded-pill`, `rounded-panel` (40), `rounded-frame` (56), `rounded-window` (17) - Текст: `text-display`, `text-title`, `text-heading`, `text-timer`, `text-body`, `text-body-web`, `text-label`, `text-caption`, `text-mono` (размер, интерлиньяж, вес и трекинг сразу); `font-ui`, `font-mono` - Движение: `ease-out`, `ease-in`, `duration-fast`, `duration-base`, `duration-page`, `duration-exit`, `duration-slow` ## Можно - Брать цвет только через роль: `bg-tile`, `text-muted`, `bg-primary` - Текст роли на мягких тонах `bg-indigo-soft`, `bg-giants-soft`; на насыщенных `bg-indigo`, `bg-giants` только иконки - Чернила `text-*-ink`: иконки и текст. Мелкий текст только там, где пара проходит Lc 75 в `pnpm apca`: `text-primary-ink` везде, `text-indigo-ink` и `text-giants-ink` только в светлой теме; в тёмной они только для крупного полужирного (`text-title`, `text-heading`) - Стекло только у того, что лежит поверх контента: липкая шапка, меню, подсказки, диалоги, док ## Нельзя - Сырые цвета, `p-[13px]`, `rounded-lg`, модификаторы прозрачности (`bg-primary/50`): их ловит `@umbra/eslint` - Переписывать `tokens.css` в продукте. Продукт меняет только акцент в `product.css` - Модификатор `dark:` для цветов: тема переключается переменными, классы те же - Смешивать классы `base.css` (`.umbra-transition`, `.glass`) с утилитами того же свойства на одном элементе (`umbra-transition duration-base`): в Tailwind 4 `base.css` вне слоёв и побеждает утилиты, в 3.4 проигрывает. Нужна другая длительность: `transition-all duration-base ease-out` без `.umbra-transition` ================================================================================ # Тема: ShadeNotes (@umbra/theme-shadenotes) Установка: npx shadcn@latest add @umbra/theme-shadenotes Слой продукта ShadeNotes: акцент лайм #E4FB77, текст на нём #111, чернила #576E00 в светлой теме. Ставится после theme-base в ShadeNotes. В другом продукте не брать: у каждого продукта свой акцент ## Что это `product.css` ShadeNotes. Задаёт только акцент: `--primary`, `--primary-foreground`, `--primary-ink`, `--primary-edge` и имя продукта. Всё остальное берётся из базы | Токен | Светлая | Тёмная | |---|---|---| | `--primary` | `#E4FB77` | `#E4FB77` | | `--primary-foreground` | `#111111` | `#111111` | | `--primary-ink` | `#576E00` | `#E4FB77` | | `--primary-edge` | `#98B620` | `#E4FB77` | ## Когда брать В ShadeNotes, один раз, после `@umbra/theme-base`. Подключить в глобальном CSS после `tokens.css` ## Когда не брать В портфолио (там `@umbra/theme-portfolio`) и в новом продукте: новому продукту нужен свой `product.css` по правилу вывода из DESIGN.md (Colors, «Акцент продукта») ## Нельзя - Добавлять в `product.css` что-то кроме акцента, логотипа и имени ================================================================================ # Тема: портфолио (@umbra/theme-portfolio) Установка: npx shadcn@latest add @umbra/theme-portfolio Слой продукта портфолио: акцент кобальт #3044D8, текст на нём белый, чернила в тёмной теме осветлены до #C4CBFF (Lc 77 на #0A0A0A). Ставится после theme-base в портфолио. В другом продукте не брать ## Что это `product.css` портфолио, имя продукта `--product-name: "Aidyn Beisembin"`. Кобальт годится как заливка кнопки с белым текстом (Lc 89), но как текст и иконка на `#0A0A0A` он не читается (Lc 18), поэтому `--primary-ink` в тёмной теме осветлён | Токен | Светлая | Тёмная | |---|---|---| | `--primary` | `#3044D8` | `#3044D8` | | `--primary-foreground` | `#FFFFFF` | `#FFFFFF` | | `--primary-ink` | `#3044D8` | `#C4CBFF` | | `--primary-edge` | `#3044D8` | `#C4CBFF` | Сайт сейчас только тёмный (класс `dark` на ``), но светлые значения заданы, чтобы светлую тему можно было включить без перекраски ## Когда брать В портфолио, один раз, после `@umbra/theme-base` ## Когда не брать В ShadeNotes и в новом продукте ================================================================================ # Движение (@umbra/motion) Установка: npx shadcn@latest add @umbra/motion Пружины --spring-snappy и --spring-smooth (CSS linear(), перелёт не больше 4 %), появление поверх из размытия, перетекающий индикатор выбора MorphIndicator, нажатие, смена страниц и темы через View Transitions. Брать для слоя управления: индикатор вкладок и сайдбара, бегунок, меню, тост, смена темы. Не брать для появления контента в потоке (это umbra-rise из theme-base) ## Что это Движение слоя управления в духе Liquid Glass. Токены пружин уже в `tokens.css` (`theme-base`): `--spring-snappy` и `--spring-smooth` через `linear()`, длительности `--t-spring-snappy` 0.35 с и `--t-spring-smooth` 0.5 с; при `prefers-reduced-motion` длительности 0. Здесь утилиты (`styles/umbra/motion.css`, после `base.css`) и компонент `MorphIndicator` - `.umbra-pop-in` / `.umbra-pop-out`: появление поверх из `blur(8px)`, `scale 0.96` и прозрачности; уход быстрее, за `--t-exit` по `--ease-in` - `MorphIndicator`: капля выбора не появляется на новом месте, а перетекает; во время движения вытягивается по направлению (около 1.08), садится пружиной `--spring-snappy` - `.umbra-vt-title` и правила `::view-transition-*`: контент новой страницы проявляется из `blur(6px)` за `--t-page`, заголовок перетекает, если он есть на обеих страницах - `umbraThemeTransition(apply, origin)`: смена темы круговым раскрытием от кнопки ## Подключение ```css @import "../styles/umbra/motion.css"; /* после base.css */ ``` ## Когда брать - Индикатор выбора вкладок, чипов, пунктов сайдбара, бегунок переключателя - Меню, подсказка, тост, диалог: появление поверх - Смена страниц и темы ## Когда не брать - Появление контента в потоке: `umbra-rise` и `umbra-appear` из `theme-base` - Живой персонаж и показы: у них свой цикл ## Варианты | Проп `MorphIndicator` | Что | По умолчанию | |---|---|---| | `containerRef` | контейнер с `position: relative` | обязательно | | `value` | значение, меняющееся с выбором | обязательно | | `selector` | выбранный элемент | `[aria-selected="true"], [aria-checked="true"], [aria-current="page"]` | | `stretch` | вытягивание по направлению | `1.08` | ## Состояния - Покой: капля под выбранным - Движение: `.is-moving`, вытянута по оси движения - `prefers-reduced-motion`: перескок без перетекания и пружин, только смена прозрачности ## Можно - Материал капли из `@umbra/glass`: `umbra-glass-select`, с `umbra-glass-refract` у мелких - Пружины только у слоя управления ## Нельзя - Перелёт больше 4 %, свои кривые вместо `--spring-*` - Пружины у контента в потоке и у текста ## Пример ```tsx import { MorphIndicator } from "@/components/umbra/morph-indicator"; const ref = React.useRef(null);
{tabs.map((t) => ( ))}
``` ================================================================================ # Склейка классов (cn) (@umbra/cn) Установка: npx shadcn@latest add @umbra/cn Склейка классов для компонентов Umbra: clsx плюс tailwind-merge, которому объяснены классы токенов (text-display, text-muted, p-tile, rounded-tile). Ставится сам как зависимость компонентов. Отдельно брать, только если пишете свой компонент из классов Umbra и передаёте className снаружи ## Что это Нужны `clsx` и `tailwind-merge`: они есть в любом проекте shadcn. Реестр их не ставит намеренно, чтобы не поднять tailwind-merge до 3 в проекте на Tailwind 3.4 (там нужен 2) Обычный `cn` из shadcn не знает классов токенов и склеивает `text-label` и `text-muted` в одну группу: один класс теряется. `cn` из Umbra знает группы токенов. Работает с tailwind-merge 2 (Tailwind 3.4) и 3 (Tailwind 4) ## Когда брать Ставится автоматически с любым компонентом. Вручную: если собираете свой компонент продукта из классов Umbra ## Когда не брать Не заменяет `@/lib/utils` продукта: тот остаётся для компонентов shadcn ## Пример ```tsx import { cn } from "@/components/umbra/cn";

; ``` ================================================================================ # Icon (@umbra/icon) Установка: npx shadcn@latest add @umbra/icon Иконка Umbra: обёртка над @phosphor-icons/react, вес fill по умолчанию, bold сам у всех шевронов Caret*, Check, X, MagnifyingGlass. Брать для любой иконки в интерфейсе. Не брать для логотипа и маскота (это SVG продукта) и для индикатора ожидания (это Spinner) ## Что это `` рисует иконку Phosphor с правильным весом. Передавать `weight` не нужно и нельзя: вес выбирается по имени иконки ## Когда брать Для всех иконок интерфейса: в кнопках, строках списка, чипах, сайдбаре ## Когда не брать - Логотип и маскот: это SVG продукта - Индикатор ожидания: только дуга-спиннер без подложки (Spinner) ## Размеры | Размер | Где | |---|---| | 24 | кнопки с текстом, главные действия, круглые кнопки-иконки 56 px, пункты сайдбара 48 px (по умолчанию) | | 32 | главная круглая кнопка 88 (Dock) | | 16, 20 | мелкие места | ## Варианты - `fill`: все иконки - `bold`: все шевроны `Caret*`, `Check`, `X`, `MagnifyingGlass` (в заливке шеврон становится треугольником, остальные плашкой или диском). Треугольник запрещён везде, шеврон в поле 20 px ## Можно - `label="Записать"`, если иконка единственное содержимое и смысл не подписан рядом - Цвет иконки через текст родителя: `text-primary-ink`, `text-indigo-ink`, `text-muted`. Чернила `-ink` для иконок проходят Lc 45 во всех темах ## Нельзя - Варианты `*Circle` и `*Square`, `Subtitles`, `QuestionMark`: в заливке становятся сплошной плашкой - Иконки с подложкой, второй набор иконок (lucide и др.), спиннер из иконки - Стрелки `Arrow*` (и `CloudArrowUp`, `ArrowsClockwise`): направление только шевроном `Caret*` bold. Ловит `umbra/no-arrow-icons` ## Пример ```tsx import { Microphone, CaretRight } from "@phosphor-icons/react"; import { Icon } from "@/components/umbra/icon"; // fill, 24 // bold сам ``` В серверных компонентах Next импортировать иконки из `@phosphor-icons/react/dist/ssr` ================================================================================ # ESLint Umbra (@umbra/eslint) Установка: npx shadcn@latest add @umbra/eslint Правила ESLint Umbra для продукта: запрет сырых цветов, произвольных значений Tailwind вне шкалы, радиусов вне шкалы, чужих наборов иконок и Phosphor без fill. Брать в каждый продукт вместе с theme-base: правила работают только если их проверяют ## Что это Один файл `eslint.umbra.mjs` без зависимостей. Плоский конфиг ESLint 9 (и ESLint 8.57 с `eslint.config.js`) | Правило | Что ловит | |---|---| | `umbra/no-raw-color` | `#fff`, `rgb()`, `bg-[#fff]`, `text-zinc-500` в классах и `style` | | `umbra/no-arbitrary-value` | `p-[13px]`, `rounded-[10px]`; разрешено `w-[var(--sidebar)]` | | `umbra/spacing-scale` | отступы вне шкалы 8 / 12 / 16 / 20 / 24 / 32 (`p-7`, `gap-2.5`) | | `umbra/radius-scale` | `rounded-lg`, `rounded` и другие радиусы вне шкалы | | `umbra/no-opacity-modifier` | `bg-primary/50`: в Tailwind 3.4 не работает с токенами | | `umbra/no-foreign-icons` | `lucide-react`, `react-icons`, `@heroicons/react` и другие | | `umbra/no-arrow-icons` | стрелки Phosphor `Arrow*` и любые `*Arrow*` (`CloudArrowUp`): направление только шевроном `Caret*` bold | | `umbra/glass-layer` | предупреждение: `backdrop-filter` и стекло у плитки, стекло внутри стекла (классы `umbra-glass*`, `glass*`) | | `umbra/phosphor-weight` | Phosphor без `weight="fill"`, шеврон `Caret*` в заливке (треугольник), запрещённые `*Circle`, `*Square` | ## Подключение ```js // eslint.config.mjs import umbra from "./eslint.umbra.mjs"; export default [ // ...конфиг продукта ...umbra.configs.recommended, ]; ``` ## Когда брать В каждый продукт. Для постепенного перехода правила можно временно перевести в `warn` для старых папок ## Когда не брать Не нужен в проектах без интерфейса ================================================================================ # Навык: экран по Umbra (@umbra/skill-umbra-ui) Установка: npx shadcn@latest add @umbra/skill-umbra-ui Навык Claude Code «собрать экран по Umbra»: порядок работы (каркас, плитки, роли цвета, правило двух плиток, APCA, движение, проверка в 1100 × 700) и чек-лист отчёта. Ставится в .claude/skills/umbra-ui/ продукта. Брать в каждый продукт, где агент делает интерфейс. Не заменяет DESIGN.md: навык задаёт порядок, правила в DESIGN.md ## Что это Навык Claude Code (`.claude/skills/umbra-ui/SKILL.md`): пошаговый порядок сборки экрана по Umbra и чек-лист проверки. Агент подхватывает его сам, когда задача трогает интерфейс ## Когда брать - В каждый продукт Aid Beisembin, где агент делает или правит интерфейс - Вместе с ним в `CLAUDE.md` продукта вставить сниппет из `docs/agents/claude-md-snippet.md` (страница «Как подключить агента») ## Когда не брать - В проектах без интерфейса - Как замену DESIGN.md: навык ссылается на него и не повторяет все правила ## Порядок в навыке 0. Прочитать DESIGN.md, проверить, что стоят `theme-base`, слой продукта и `eslint` 1. Каркас: окно, Sidebar, Page, Dock; экран-шаг плиткой по центру 2. Плитки: одна задача, шкала отступов, плотные, без рамок; тень только у стекла и при наведении на карточку 3. Роли цвета: акцент, Indigo, Giants, запись, удаление, нейтрали; стекло только у слоя управления поверх контента, мелкий текст на плотном стекле 4. Правило двух цветных плиток 5. APCA: только пары из таблицы, новая пара это вопрос 6. Движение токенами; у слоя управления пружины и перетекание; одно живое существо 7. Текст: коротко, «ёлочки», честный статус, без точки в конце абзаца, без стрелок, `--type-hero` только на витрине сайта 8. Проверка: ESLint, `tsc`, сборка, скриншоты 1100 × 700 в двух темах, чек-лист в отчёте ## Пример ```bash npx shadcn@latest add @umbra/skill-umbra-ui ``` ================================================================================ # Tile (@umbra/tile) Установка: npx shadcn@latest add @umbra/tile Плитка бенто: --tile, радиус 24, отступ 20, без рамок и теней; вложенная: радиус 16, отступ 16. Одна плитка, одна задача. Брать для любого смыслового блока на окне и для вложенной поверхности внутри плитки (variant=nested). Не брать для того, что лежит поверх контента (это Glass, NoticeCard, Toast) и для строк списка (это ListRow без вложенной карточки) ## Что это Единица экрана. Экран состоит из плиток на фоне окна, между плитками воздух `gap-tile` (8), а не линии. Границы задают цвет поверхности и расстояние ## Когда брать - Смысловой блок на окне: список встреч, итоги, настройки, карточка кейса - `variant="nested"`: поверхность внутри плитки (поле, чип-группа, превью). Радиус 16 концентричен плитке 24 при зазоре 8 - `tone`: цветная плитка роли, когда цвет что-то значит: `primary` (главное действие, выбор), `indigo-soft` (итоги ИИ, текст), `giants-soft` (предупреждение, текст), `indigo` и `giants` (насыщенные: только иконка или знак, текста нет) - `interactive`: вся плитка ссылка или кнопка ## Когда не брать - Поверх контента (меню, уведомление, док): стекло, `Glass` или `NoticeCard` - Строки списка: `ListRow` без вложенной карточки - Плитка в плитке ради рамки: вложенное только как `variant="nested"`, не вторая плитка ## Варианты | Проп | Значения | По умолчанию | |---|---|---| | `variant` | `tile` (отступ 20), `nested` (радиус 16, отступ 16) | `tile` | | `tone` | `primary`, `indigo`, `indigo-soft`, `giants`, `giants-soft` | нет | | `interactive` | `true` / `false` | `false` | | `flush` | без внутреннего отступа (для обложек и списков) | `false` | | `asChild` | отрисовать ссылку или кнопку с видом плитки | `false` | В DESIGN.md вариант роли называется `role`; в коде это `tone`, потому что `role` занят ARIA ## Состояния Обычная, цветная роли, интерактивная: наведение (плитка тон `--nested`, вложенное тон `--border`, `primary` и `indigo` 90 % непрозрачности), фокус кольцом `--ring` внутри, нажатие сдвигом на 1 px ## Можно - Одна плитка, одна задача. Отступ плитки один во всём продукте: `p-tile` (20) - Текст роли на `indigo-soft` и `giants-soft`, а на насыщенных `indigo` и `giants` только иконка ## Нельзя - Больше двух цветных плиток на экране (Indigo внутри показа не считается) - Рамки, тени, градиенты на плитке - Приглушённый текст (`text-muted`, прозрачность) на цветной плитке - Любой текст на насыщенных `indigo` и `giants` - Свой отступ плитки (`p-6`, `p-4` вместо `p-tile`) ## Пример ```tsx import { Tile } from "@/components/umbra/tile";

Встречи

12 записей за неделю

Вложенное

Итоги

Решили перенести запуск на вторник.

``` ================================================================================ # Page (@umbra/page) Установка: npx shadcn@latest add @umbra/page Колонка страницы одной плиткой: липкая шапка (Display, подпись, круглые действия 56, чипы-фильтры), при прокрутке шапка становится стеклом с растворённым краем, прокрутка обрезана по радиусу. Брать для основного экрана раздела рядом с Sidebar. Не брать для экрана-шага (онбординг, обработка: там Tile по центру) и для блока внутри экрана (это Tile) ## Что это Основная колонка экрана: плитка, внутри которой прокручивается содержимое. Шапка липкая; пока страница наверху, она плоская, при прокрутке становится стеклом (`.glass`), нижний край растворяется полосой `--edge-fade` (28 px), линии нет. Полоса прокрутки 4 px видна только при движении и скрывается через 0,7 с после остановки ## Когда брать - Экран раздела: «Встречи», «Настройки», лента кейсов - `dock`: на экране есть Dock и контент доходит до него ## Когда не брать - Экран-шаг (онбординг, обработка): плитка до 600 px по центру окна, без прокрутки, помещается в 1100 × 700 - Блок внутри экрана: `Tile` - Диалог и меню: стекло поверх (`Glass`) ## Варианты | Проп | Что | По умолчанию | |---|---|---| | `title` | заголовок экрана, Display 36/40 | обязателен | | `subtitle` | подпись, Caption | нет | | `actions` | круглые кнопки 56 (`Button icon size="lg"`), `SearchToggle` | нет | | `filters` | чипы-фильтры (`Chip`) | нет | | `dock` | запас снизу `--dock-space` (136) под док | `false` | ## Состояния Шапка плоская (наверху) и стекло (при прокрутке). Полоса прокрутки скрыта и видна при движении ## Можно - Одна страница на экран, рядом Sidebar - Действия шапки только круглыми кнопками 56 и SearchToggle ## Нельзя - Линия или тень под шапкой: край растворяется сам - Вторая прокрутка внутри страницы - Запас под док, если контент до него не доходит ## Пример ```tsx import { Page } from "@/components/umbra/page"; import { Chip } from "@/components/umbra/chip"; import { SearchToggle } from "@/components/umbra/search-toggle"; } filters={<>ВсеС итогами} dock > {/* строки ListRow */} ``` ================================================================================ # Button (@umbra/button) Установка: npx shadcn@latest add @umbra/button Кнопка-таблетка: primary (акцент), secondary (--nested), ghost (текст), destructive; размеры 40 / 48 / 56; icon для круглой кнопки-иконки. Брать для действия, у которого есть подпись или понятная иконка. Не брать для фильтров и вкладок (это Chip), для навигации сайдбара (это Sidebar) и для поиска (это SearchToggle) ## Что это Таблетка с подписью Label (14 / 500) или круглая кнопка-иконка. Без рамок и подложек, цвет только по роли ## Когда брать - `primary`: одно главное действие на экране или в плитке («Начать запись», «Сохранить») - `secondary`: обычное действие рядом с главным («Отмена», «Экспорт») - `ghost`: тихое второстепенное действие («Позже», «Подробнее») - `destructive`: удаление. Только для необратимого - `icon`: круглая кнопка-иконка, когда смысл понятен без подписи (в шапке страницы: кнопка 56, иконка 24). Подпись в `aria-label` ## Когда не брать - Фильтр или вкладка: `Chip` - Пункт навигации: `Sidebar` - Поиск: `SearchToggle` - Остановка записи: красная кнопка дока (`Dock`), а не `destructive` - Ссылка в тексте: обычная ссылка. Кнопка-ссылка через `asChild` ## Варианты | Проп | Значения | По умолчанию | |---|---|---| | `variant` | `primary`, `secondary`, `ghost`, `destructive` | `primary` | | `size` | `sm` (40), `md` (48), `lg` (56) | `md` | | `icon` | круглая кнопка-иконка | `false` | | `asChild` | отрисовать ссылку с видом кнопки | `false` | Иконка внутри: 20 в `sm`, 24 в `md` и `lg`. Иконку передавать через `Icon` ## Состояния - Наведение: `primary` и `destructive` 90 % непрозрачности, `secondary` тон `--border`, `ghost` подложка `--nested` - Нажатие: сдвиг вниз на 1 px - Фокус с клавиатуры: кольцо 2 px `--ring` внутри - Неактивная (`disabled` или `aria-disabled`): нейтральный серый `--nested` и `--text-muted`, никогда не приглушённый акцент ## Можно - Одна `primary` на плитку или экран - Подпись: глагол и что сделать («Начать запись»), коротко ## Нельзя - Приглушённый акцент как неактивное состояние («грязная олива») - Градиент, свечение, тень у кнопки - Своя высота или радиус (`h-9`, `rounded-lg`): только 40 / 48 / 56 и таблетка - Кнопка-иконка без `aria-label` ## Пример ```tsx import { Microphone, Export } from "@phosphor-icons/react"; import { Button } from "@/components/umbra/button"; import { Icon } from "@/components/umbra/icon";
``` ================================================================================ # Chip (@umbra/chip) Установка: npx shadcn@latest add @umbra/chip Фильтр и вкладка: таблетка 40 px на --nested, выбранный с рамкой 2 px --primary-edge, лёгким стеклом и font-medium, может нести счётчик. Брать для 2–6 видимых вариантов фильтра или вкладок. Не брать для действия (это Button), для длинного списка вариантов (это Select) и для «вкл / выкл» (это Toggle) ## Что это Таблетка-переключатель. Выбранный чип отмечен формой: рамкой 2 px `--primary-edge` (там, где сам акцент не виден на белом), лёгким стеклом `.glass-soft` (тон, блик кромки, размытие, без тени) и весом 500. Рамка у чипа один из двух случаев, где она разрешена ## Когда брать - Фильтры над списком: «Все», «С итогами», «Не распознано» - Вкладки раздела: `role="tab"` у чипов и `role="tablist"` у группы ## Когда не брать - Действие («Экспорт», «Удалить»): `Button` - Больше шести вариантов или длинные названия: `Select` - «Вкл / выкл»: `Toggle` ## Варианты | Проп | Что | По умолчанию | |---|---|---| | `selected` | выбран | `false` | | `count` | счётчик справа, tabular, `--text-muted` | нет | | `role` | `tab` для вкладок (тогда `aria-selected`), иначе кнопка с `aria-pressed` | кнопка | ## Состояния Выкл, вкл, наведение (тон `--border`), фокус кольцом `--ring` внутри, нажатие сдвигом на 1 px, неактивный (текст `--text-muted`) ## Можно - Один выбранный в группе вкладок; несколько в группе фильтров ## Нельзя - Заливка акцентом у выбранного чипа: только рамка - Чипы разной высоты; иконка вместо подписи ## Пример ```tsx import { Chip } from "@/components/umbra/chip";
Все С итогами Не распознано
``` ================================================================================ # ListRow (@umbra/list-row) Установка: npx shadcn@latest add @umbra/list-row Строка списка без вложенной карточки: круглая миниатюра состояния 48, заголовок Label 500, мета Caption, шеврон; List даёт две колонки от 1100 px. Брать для списков записей, встреч, кейсов внутри Page или Tile. Не брать для одиночного блока (это Tile) и для пунктов меню (это Select или стеклянное меню) ## Что это Строка без своей карточки: у каждой строки нет фона, пока на неё не навели или её не выбрали. Тогда тон `--nested` с радиусом вложенного. `List` раскладывает строки в одну колонку и в две от 1100 px Отступы по правилу «внутреннее меньше внешнего» (DESIGN.md, правило 13): миниатюра и текст 8, от края строки 12. До aid-design PR 8 было 16 и 8: содержимое стояло дальше друг от друга, чем от края. Строка стала выше на 8 px (72 вместо 64) ## Когда брать - Список встреч, записей, кейсов - Строка-ссылка: `asChild` и `` внутри ## Когда не брать - Одиночный смысловой блок: `Tile` - Карточка в карточке: строки списка не оборачиваются в `Tile variant="nested"` - Пункты выбора: `Select` ## Варианты | Проп | Что | По умолчанию | |---|---|---| | `title`, `meta` | заголовок (Label) и мета (Caption, `--text-muted`) | `title` обязателен | | `thumbnail` | круглая миниатюра состояния 48 × 48 | нет | | `trailing` | что-то справа вместо шеврона | нет | | `chevron` | шеврон `CaretRight` | `true` | | `selected` | выбрана (`aria-current`) | `false` | | `asChild` | отрисовать ссылку с видом строки | `false` | ## Состояния Обычная, наведение (тон `--nested`), выбрана (тон `--nested`), фокус кольцом `--ring` внутри ## Можно - Мета коротко: дата, длительность, статус - В миниатюре настоящий статус записи (иконка, Spinner, Skeleton) ## Нельзя - Разделительные линии между строками: воздух вместо линий - Заголовок в две строки: обрезается многоточием ## Пример ```tsx import Link from "next/link"; import { List, ListRow } from "@/components/umbra/list-row"; ``` ================================================================================ # NoticeCard (@umbra/notice-card) Установка: npx shadcn@latest add @umbra/notice-card Карточка-уведомление у дока: плотное стекло, радиус 24, знак в круге 44, заголовок одной строкой, одно главное действие и тихие второстепенные; появляется из размытия. Брать для статуса, который ждёт действия человека (нет доступа к микрофону, не распознано). Не брать для короткого подтверждения (это Toast) и для содержимого страницы (это Tile) ## Что это Уведомление поверх контента рядом с доком. Стекло плотное (`.glass-dense`, 0,97), текст читается на любом фоне. Слева знак в круге 44: `info` нейтральный на `--nested`, `warning` на насыщенном Giants (только знак, текста на нём нет). Indigo не для info: он только для того, что делает ИИ ## Когда брать - Статус, который требует действия: «Нет доступа к микрофону» и «Открыть настройки» - Предупреждение о качестве: «Речь не распознана» - С иллюстрацией-подсказкой: показать, куда нажать ## Когда не брать - Короткое подтверждение («Сохранено»): `Toast` - Постоянный блок экрана: `Tile` - Неуверенный статус как успех: «Проверим при первой записи», а не «Доступ есть» ## Варианты | Проп | Что | По умолчанию | |---|---|---| | `tone` | `info`, `warning` | `info` | | `icon` | знак `` | обязателен | | `title` | заголовок одной строкой | обязателен | | `description` | что сделать, Caption | нет | | `action` | одно главное действие (`Button size="sm"`) | нет | | `secondary` | тихие второстепенные (`Button variant="ghost" size="sm"`) | нет | | `illustration` | иллюстрация-подсказка | нет | | `state` | `open` (появление) или `closed` (уход за `--t-exit` 0,28 с по `--ease-in`) | `open` | ## Состояния Появление (`umbra-appear`: из размытия, масштаб 0,96), уход (`umbra-exit`: `--t-exit`, `--ease-in`). При `prefers-reduced-motion` без анимации; при `prefers-reduced-transparency` стекло сплошное ## Можно - Одна карточка за раз; заголовок: что случилось, описание: что сделать ## Нельзя - Два главных действия - Красный знак: красный это запись и удаление ## Пример ```tsx import { MicrophoneSlash } from "@phosphor-icons/react"; import { NoticeCard } from "@/components/umbra/notice-card"; import { Button } from "@/components/umbra/button"; import { Icon } from "@/components/umbra/icon"; } title="Нет доступа к микрофону" description="Разрешите доступ в настройках системы." action={} secondary={} /> ``` ================================================================================ # Glass (@umbra/glass) Установка: npx shadcn@latest add @umbra/glass Стекло в духе Liquid Glass macOS 26 / iOS 26: материал .umbra-glass (обычное и плотное), индикатор выбора, преломление по краю в Chromium с фолбэками, нажатие. Брать только для слоя управления поверх контента: шапка при прокрутке, индикатор выбора, переключатели, меню, подсказки, тосты, диалоги, плавающие панели. Не брать для плиток и карточек в потоке: они плотные (это Tile) ## Что это Материал стекла (`styles/umbra/glass.css`, подключается после `base.css`) и обёртка `Glass`. Правила в DESIGN.md, «Стекло и движение» - `.umbra-glass`: тон `--glass-tint` с `--glass-alpha`, `backdrop-filter: blur(var(--glass-blur)) saturate(180%)`, блик по верхней кромке, тонкий край `--glass-edge`, мягкая тень `--glass-shadow` - `.umbra-glass-dense`: плотное (`--glass-alpha-dense`) для мелкого текста поверх пёстрого - `.umbra-glass-soft`: без тени и края; `.umbra-glass-select`: индикатор выбора на однотонной плитке (тон `--text` 8 %) - `.umbra-glass-refract`: преломление по краю (лупа) через SVG `feDisplacementMap` в `backdrop-filter`, только Chromium (проверка `@supports`); Safari и Firefox получают только blur и saturate. Нужен `` один раз на странице. Только у мелких элементов: дорого - `.umbra-press`: нажатие, `scale 0.97` и усиленный блик кромки - `prefers-reduced-transparency` и `data-transparency="reduce"` на ``: сплошной фон темы без размытия Компонент `Glass` ставит базовые классы `.glass*` из `theme-base` (тот же материал), пропы `refract` и `press` добавляют классы из `glass.css` ## Подключение ```css @import "../styles/umbra/glass.css"; /* после base.css */ ``` ## Когда брать - Поповер, меню, подсказка, диалог, капсула над контентом - `dense`: карточка-уведомление, где текст должен читаться на любом фоне - `pop`: появление меню и диалогов из размытия ## Когда не брать - Плитка, вложенное, поле: поверхности плоские - Шапка страницы: стекло включает `Page` - Уведомление: готовый `NoticeCard` или `Toast` ## Варианты | Проп | Что | По умолчанию | |---|---|---| | `dense` | плотное стекло 0,97 | `false` | | `soft` | лёгкое стекло `.glass-soft`: без тени и края | `false` | | `pop` | появление из размытия | `false` | | `refract` | преломление по краю (Chromium), только мелкие элементы | `false` | | `press` | нажатие: `scale 0.97`, усиленный блик | `false` | | `radius` | `tile` (24), `pill`, `nested` (16), `none` | `tile` | | `asChild` | стекло на дочернем элементе | `false` | ## Контраст APCA считается на худшем случае, когда под стеклом сплошной цвет (`pnpm apca`, пары «стекло / …»): на плотном стекле мелкий текст и подписи проходят Lc 75 поверх чего угодно; на обычном только крупный полужирный (от 18 px 600) и иконки, мелкий текст на обычном стекле только в тёмной теме ## Нельзя - Стекло на стекле (предупреждение `umbra/glass-layer`) - `backdrop-filter` у плитки (предупреждение `umbra/glass-layer`) - Преломление у больших панелей - Градиент или свечение вместо стекла - Стекло у элемента в потоке страницы ## Пример ```tsx import { Glass } from "@/components/umbra/glass"; {/* пункты меню */} ``` ================================================================================ # Spinner (@umbra/spinner) Установка: npx shadcn@latest add @umbra/spinner Индикатор ожидания: дуга без подложки цветом --loader, размеры 16 / 20 / 24. Брать, когда идёт настоящая работа без известного процента. Не брать для заглушки будущего содержимого (это Skeleton), для выдуманного прогресса и вместо иконки ## Что это Дуга в четверть круга, которая вращается за `--loader-speed` (0,9 с). Без подложки и без трека. При `prefers-reduced-motion` дуга стоит ## Когда брать - Идёт загрузка или обработка, процент неизвестен - В миниатюре строки списка, пока запись обрабатывается ## Когда не брать - Место будущего содержимого: `Skeleton` - Известен прогресс: показать настоящий прогресс, не спиннер - Ради анимации: задержек «для красоты» нет ## Варианты | Проп | Что | По умолчанию | |---|---|---| | `size` | 16, 20, 24 | 20 | | `label` | что ждём, для скринридера | «Загрузка» | ## Нельзя - Спиннер из иконки (`CircleNotch` и т. п.), подложка или трек под дугой - Спиннер дольше нужного: когда результат готов, его открывают сразу ## Пример ```tsx import { Spinner } from "@/components/umbra/spinner"; ``` ================================================================================ # Skeleton (@umbra/skeleton) Установка: npx shadcn@latest add @umbra/skeleton Скелетон: место будущего содержимого поверхностью --skeleton, формы block, text, circle; статичный, без бегущего блика. Брать, когда известна форма содержимого, которое загружается. Не брать, когда форма неизвестна или идёт долгая обработка (это Spinner) ## Что это Заглушка той же формы, что и будущее содержимое. Цвет `--skeleton`. Без анимации: бегущий блик это декоративное движение, а движение в Umbra только объясняет ## Когда брать - Строки списка, превью, текст, пока данные грузятся ## Когда не брать - Неизвестная форма или долгая обработка: `Spinner` - Пустое состояние: текст «что случилось и что сделать» ## Варианты | `shape` | Что | |---|---| | `block` | прямоугольник с радиусом вложенного (16), по умолчанию | | `text` | строка-таблетка высотой 16 | | `circle` | круг, ширина задаёт размер | ## Нельзя - Бегущий блик и пульсация - Скелетон дольше загрузки и вместо настоящего пустого состояния ## Пример ```tsx import { Skeleton } from "@/components/umbra/skeleton";
``` ================================================================================ # Field (@umbra/field) Установка: npx shadcn@latest add @umbra/field Поле ввода: Input таблеткой 48 px и Textarea на --nested с рамкой --border, фокус кольцом внутри; Field связывает подпись, подсказку и ошибку. Брать для ввода текста. Не брать для выбора из списка (это Select), для «вкл / выкл» (это Toggle) и для поиска в шапке (это SearchToggle) ## Что это `Input` (таблетка 48 px), `Textarea` (радиус вложенного) и `Field` (подпись, поле, подсказка или ошибка). Подпись, подсказка и ошибка по левому краю поля; текст внутри начинается с отступа 20. Поле один из двух случаев, где в Umbra есть рамка: `--border` 1 px ## Когда брать - Название, заметка, ключ API, адрес - `Field` вокруг любого поля, чтобы подпись и ошибка были связаны с ним для скринридера ## Когда не брать - Выбор из списка: `Select` - «Вкл / выкл»: `Toggle` - Поиск в шапке страницы: `SearchToggle` ## Варианты | Компонент | Что | |---|---| | `Input` | таблетка 48 px, текст Body 15 | | `Textarea` | радиус 16, по умолчанию 4 строки, без ручного растягивания | | `Field` | `label`, `hint`, `error`; ошибка заменяет подсказку | ## Состояния Пустое (плейсхолдер `--text-muted`), заполненное, фокус (кольцо `--ring` 2 px внутри), ошибка (рамка `--giants-ink`, знак Giants и текст ошибки), неактивное (текст `--text-muted`) ## Можно - Ошибка: что случилось и что сделать («Ключ не подошёл. Проверьте, что скопирован целиком») ## Нельзя - Красный для ошибки: красный это запись и удаление; предупреждение это Giants - Плейсхолдер вместо подписи ## Пример ```tsx import { Field, Input } from "@/components/umbra/field"; ``` ================================================================================ # Toggle (@umbra/toggle) Установка: npx shadcn@latest add @umbra/toggle Переключатель «вкл / выкл» на Radix Switch: таблетка, включённый цветом акцента, выключенный тоном --border. Брать для настройки, которая действует сразу. Не брать для выбора одного из нескольких (это Chip или Select) и для действия с подтверждением (это Button) ## Что это Переключатель 56 × 32: дорожка таблеткой, ручка 24 цветом плитки. Включён: дорожка `--primary`. Выключен: `--border`. Работает с клавиатуры (пробел), отдаёт `role="switch"` ## Когда брать - Настройка, которая применяется сразу: «Сохранять записи на устройстве» ## Когда не брать - Один из нескольких вариантов: `Chip` или `Select` - Действие, которое нужно подтвердить («Удалить всё»): `Button` ## Варианты | Проп | Что | |---|---| | `label` | подпись справа, кликабельная; без неё нужен `aria-label` | | `checked`, `defaultChecked`, `onCheckedChange` | как у Radix Switch | | `disabled` | неактивный: дорожка `--border` и во включённом положении | ## Состояния Выкл, вкл, фокус (кольцо `--ring`), неактивный ## Нельзя - Приглушённый акцент у неактивного включённого переключателя - Переключатель без подписи ## Пример ```tsx import { Toggle } from "@/components/umbra/toggle"; ``` ================================================================================ # Sidebar (@umbra/sidebar) Установка: npx shadcn@latest add @umbra/sidebar Сайдбар приложения: отдельная плитка 72 px слева, логотип сверху, пункты круглыми кнопками 48 с иконкой 24, служебное внизу, выбранный пункт таблеткой --nav-active. Брать для навигации между разделами приложения. Не брать для сайта (там сайдбар 320 с лентой), для действий на странице (это Button) и для фильтров (это Chip) ## Что это `Sidebar` (плитка 72 px) и `SidebarItem` (круглая кнопка 48 с иконкой 24). Подпись пункта уходит в `aria-label` и подсказку `title`. На macOS над сайдбаром стоит `WindowBar` ## Когда брать - Разделы приложения: «Встречи», «Итоги», внизу «Настройки», «Приватность», тема ## Когда не брать - Сайт: сетка сайта с сайдбаром 320 и лентой - Действие на странице: `Button` ## Варианты | Проп | Что | |---|---| | `Sidebar.logo` | логотип продукта 48 × 48 | | `Sidebar.footer` | служебные пункты внизу | | `SidebarItem.label` | подпись (обязательна) | | `SidebarItem.active` | выбранный раздел (`aria-current="page"`) | | `SidebarItem.asChild` | пункт-ссылка | ## Состояния Обычный (иконка `--text`), наведение (тон `--nested`), выбранный (таблетка `--nav-active`, иконка `--nav-active-foreground`), фокус кольцом `--ring` внутри ## Нельзя - Подписи текстом под иконками и бейджи цветом - Выбранный пункт цветом акцента: только `--nav-active` ## Пример ```tsx import { House, Sparkle, Gear } from "@phosphor-icons/react"; import { Sidebar, SidebarItem } from "@/components/umbra/sidebar"; import { Icon } from "@/components/umbra/icon"; } footer={}> ``` ================================================================================ # WindowBar (@umbra/window-bar) Установка: npx shadcn@latest add @umbra/window-bar Подложка под кнопки окна macOS 72 × 26 над сайдбаром, зона перетаскивания окна в Electron и Tauri. Брать только в настольном приложении на macOS. Не брать в браузере, на Windows и Linux и как декоративную шапку ## Что это Пустая зона 72 × 26 под светофором окна macOS. Без цвета, окно видно сквозь неё. Помечена `-webkit-app-region: drag` (Electron) и `data-tauri-drag-region` (Tauri) ## Когда брать - Настольное приложение на macOS с окном без системной шапки: над `Sidebar` ## Когда не брать - Браузер, Windows, Linux: `macos={false}`, компонент ничего не рендерит - Нарисованный показ системного окна в интерфейсе: он стилизован и уменьшен и не притворяется настоящим ## Варианты | Проп | Что | |---|---| | `macos` | рендерить ли; передавать настоящую платформу | ## Пример ```tsx import { WindowBar } from "@/components/umbra/window-bar";
{/* … */}
``` ================================================================================ # SearchToggle (@umbra/search-toggle) Установка: npx shadcn@latest add @umbra/search-toggle Поиск в шапке: круглая кнопка 56 с лупой, поле выезжает влево до 280 px, лупа поворотом становится крестиком. Брать для поиска по списку страницы. Не брать для полей формы (это Field) и для постоянной строки поиска, если поиск главное действие экрана ## Что это Кнопка-лупа 56 в ряду действий `Page`. По нажатию поле 280 px выезжает влево (`--t-base`, `--ease-out`), фокус уходит в поле, лупа поворачивается и становится крестиком. Крестик и Escape закрывают и очищают поиск ## Когда брать - Поиск по списку страницы: встречи, кейсы ## Когда не брать - Поле формы: `Field` - Поиск главное действие экрана: обычное поле `Input` на виду ## Варианты Управляемый и неуправляемый: `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `placeholder` ## Состояния Закрыт (кнопка), открыт (поле и крестик), с текстом ## Нельзя - Поле шире 280 px и поиск без кнопки закрытия ## Пример ```tsx import { SearchToggle } from "@/components/umbra/search-toggle"; ``` ================================================================================ # Dock (@umbra/dock) Установка: npx shadcn@latest add @umbra/dock Док действия продукта внизу по центру поверх контента (запись в ShadeNotes): в покое круглая кнопка 88 цвета записи с иконкой 32, при записи стеклянная капсула с таймером, полосами сегментов, паузой 56 и красной кнопкой «Остановить». Брать для одного главного длительного действия продукта. Не брать для обычных действий (это Button) и для уведомлений (это NoticeCard) ## Что это Главное действие продукта, которое длится: запись. Состояния приходят из настоящего источника: таймер и уровни сигнала передаёт приложение, компонент их не выдумывает. Белые знаки `--record-foreground` на красном `--record` (Lc 77). В покое иконка 32 ## Когда брать - Запись в ShadeNotes; другое одно длительное действие продукта - Положение задаёт продукт: обычно `absolute inset-x-0 bottom-6` внутри окна, и у `Page` проп `dock` ## Когда не брать - Обычное действие на странице: `Button` - Сообщение рядом с доком: `NoticeCard` ## Варианты | Проп | Что | |---|---| | `state` | `idle` (кнопка 88), `recording` (капсула), `paused` (капсула, полосы гаснут, «Продолжить») | | `time` | время строкой, tabular, Timer 32 | | `levels` | уровни 0–1, источник задаёт продукт; пусто: полос нет. В ShadeNotes это сегменты детектора речи: уровня звука ядро не отдаёт | | `onStart`, `onPause`, `onResume`, `onStop` | действия | ## Нельзя - Фальшивые полосы и таймер не из записи - Второй живой элемент рядом: на экране одно живое существо - Кнопка «Остановить» цветом `destructive`: это красный записи ## Пример ```tsx import { Dock } from "@/components/umbra/dock"; ``` ================================================================================ # Toast (@umbra/toast) Установка: npx shadcn@latest add @umbra/toast Короткое уведомление Sonner в стекле: радиус 24, ширина 380, виды success (галочка акцента), error, warning (знак Giants). Брать для короткого подтверждения или сбоя после действия. Не брать для статуса, который ждёт действия человека (это NoticeCard), и для ошибок поля (это Field) ## Что это `Toaster` (поставить один раз в корне) и `toast` из Sonner. Внешний вид Sonner выключен (`unstyled`), вместо него стекло Umbra. Появление из размытия внизу по центру ## Когда брать - «Сохранено», «Ссылка скопирована», «Не удалось сохранить. Проверьте соединение» ## Когда не брать - Нужно действие человека и статус держится: `NoticeCard` - Ошибка ввода: `Field` с `error` ## Варианты | Вызов | Знак | |---|---| | `toast.success()` | галочка цветом `--primary-ink` | | `toast.warning()` | знак Giants (`--giants-ink`) | | `toast.error()` | крестик цветом текста | | `toast()` | без знака | ## Нельзя - Красный знак ошибки: красный это запись и удаление - Тост дольше нескольких секунд с важным действием внутри ## Пример ```tsx // app/layout.tsx import { Toaster } from "@/components/umbra/toast"; // где угодно import { toast } from "@/components/umbra/toast"; toast.success("Сохранено"); ``` ================================================================================ # Select (@umbra/select) Установка: npx shadcn@latest add @umbra/select Выбор одного значения из списка на Radix Select: триггер таблеткой 48 px как поле, меню в стекле с появлением из размытия, галочка у выбранного. Брать для выбора из 6 и больше вариантов или длинных названий. Не брать для 2–5 видимых вариантов (это Chip) и для «вкл / выкл» (это Toggle) ## Что это Обёртка над Radix Select: `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`. Триггер выглядит как `Input`, меню это стекло (`.glass .glass-pop`) с радиусом плитки, пункты таблетками 40 px ## Когда брать - Язык распознавания, микрофон, формат экспорта ## Когда не брать - Мало вариантов и их стоит видеть сразу: `Chip` - «Вкл / выкл»: `Toggle` ## Состояния Триггер: пустой (плейсхолдер `--text-muted`), выбран, фокус, ошибка (рамка `--giants-ink`), неактивный. Пункт: наведение (тон `--nested`), выбран (галочка `--primary-ink`, вес 500), неактивный ## Нельзя - Меню без стекла или с тенью вместо стекла - Пункты с иконками разных наборов ## Пример ```tsx import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/umbra/select"; import { Field } from "@/components/umbra/field"; ``` ================================================================================ # Counter (@umbra/counter) Установка: npx shadcn@latest add @umbra/counter Число, которое считается вверх от 0 при появлении за --t-slow (0,5 с) по кривой --ease-out; при reduced-motion сразу итог. Брать для крупных настоящих чисел в момент их появления (итоги, статистика). Не брать для прогресса, таймера (это Dock) и чисел, которые часто меняются ## Что это Счёт от 0 до значения при монтировании и при смене значения. Длительность читается из `--t-slow`, поэтому `prefers-reduced-motion` (длительность 0) сразу ставит итог. Цифры tabular, формат ru-RU. Скринридер читает итоговое значение ## Когда брать - «12 записей», «3 ч 40 мин» в момент появления итогов ## Когда не брать - Прогресс и проценты: только настоящие, без счёта - Таймер записи: `Dock` - Число, которое обновляется каждую секунду ## Варианты | Проп | Что | |---|---| | `value` | итоговое число | | `format` | `Intl.NumberFormatOptions`, по умолчанию целые | ## Нельзя - Считать до числа, которого ещё нет: выдуманный прогресс ## Пример ```tsx import { Counter } from "@/components/umbra/counter";

``` ================================================================================ # Onboarding (@umbra/onboarding) Установка: npx shadcn@latest add @umbra/onboarding Первый запуск продукта: одна плитка до 600 px по центру окна, точки шага, шаги сменяются размытием и сдвигом; OnboardingTitle и OnboardingHint держат колонку текста. Брать для первого запуска и коротких мастеров до трёх шагов. Не брать для настроек (это Page), для одиночного уведомления (это NoticeCard) и для экрана обработки (это Showcase на плитке) ## Что это Каркас первого запуска: фон окна, сверху точки шага, ниже плитка до 600 px с содержимым шага. Шаг держит одну задачу: обложка (`Showcase` 8:3), заголовок, подсказка, одно главное действие. Логику шагов (что проверять, когда идти дальше) задаёт продукт, компонент отвечает за вид и смену ## Когда брать - Первый запуск: знакомство с живым показом, доступ (микрофон), «Почти готово» с настоящим прогрессом - Короткий мастер до трёх шагов, где каждый шаг это одно решение человека ## Когда не брать - Настройки и длинные формы: `Page` - Одно сообщение: `NoticeCard` - Экран обработки после действия: плитка по центру с `Showcase` 2:1 и фактами ## Состав | Часть | Что | |---|---| | `Onboarding` | каркас: `step`, `count`, `stepKey`, содержимое шага, `top` (WindowBar), `note` (сбой), `exiting` (уход в приложение) | | `StepDots` | точки шага: текущая вытянута цветом `--primary-edge`; при одном шаге не показываются | | `OnboardingTitle` | Title 22/28, ровные строки, колонка около 30 символов | | `OnboardingHint` | Body, `--text-muted`, без висящих слов, до 52 символов | Главное действие шага: `Button size="lg" className="w-full"`; тихое «Позже»: `Button variant="ghost"` ## Состояния Шаг появляется (`umbra-rise`: сдвиг 8 px и размытие), уходит (`umbra-leave`, `--t-fast`); весь экран уходит в приложение по `exiting`. При `prefers-reduced-motion` без движения ## Можно - Загрузку и проверки запускать уже на первом шаге, пока человек читает - Неуверенный статус честно: «Проверим при первой записи», а не «Доступ есть» - «ShadeNotes» и предлоги не отрывать: неразрывный пробел в строках ## Нельзя - Больше трёх шагов, прокрутку внутри шага, шаг, который не помещается в 1100 × 700 - Выдуманный прогресс и задержку ради анимации: показывается только настоящий статус - Неактивную главную кнопку блёклым акцентом: она нейтрально-серая ## Пример ```tsx import { Onboarding, OnboardingTitle, OnboardingHint } from "@/components/umbra/onboarding"; import { Button } from "@/components/umbra/button"; {cover} Нажмите «Записать», остальное сделает ShadeNotes Запись и итоги остаются на этом компьютере. ``` ================================================================================ # Showcase (@umbra/showcase) Установка: npx shadcn@latest add @umbra/showcase Показ работы продукта на первом экране: обложка 8:3, сцены по очереди, итоговый кадр держится 1,7 с и гаснет перед повтором; содержимое 60–70 % обложки по оптическому центру. Брать только для шага знакомства в Onboarding. Не брать для экранов процесса с настоящим статусом (это ProcessScreen), для прогресса и для декоративной анимации в остальном интерфейсе ## Что это Демо продукта на шаге знакомства: что будет, когда человек нажмёт главную кнопку. `useDemoScenes` проигрывает сцены по очереди, итоговый кадр держится 1,7 с (1,5–2 с), затем обложка гаснет и показ повторяется без резкого сброса. Повтор разрешён только здесь: в остальном интерфейсе роликов по кругу нет ## Когда брать - Первый шаг `Onboarding`: «Записать → капсула → реплики → итоги» - Стилизованный и уменьшенный показ системного окна (запрос доступа), никогда не похожий на настоящее ## Когда не брать - Экран процесса с настоящим статусом (обработка, импорт, загрузка): `ProcessScreen`, там настоящее содержимое, а не иллюстрация - Прогресс и проценты - Анимация ради красоты вне первого экрана ## Состав | Часть | Что | |---|---| | `Showcase` | `scene`, `scenes` (рисунок каждой сцены), `label` (что показывает демо, для скринридера), `ratio` (8:3 по умолчанию), `fading` | | `useDemoScenes(order, sceneMs = 1200, holdMs = 1700)` | сцена и признак затухания; при `prefers-reduced-motion` сразу итоговая сцена без повтора | ## Нельзя - Показ вместо настоящего статуса - Нарисованное системное окно, похожее на настоящее - Второй живой элемент рядом ## Пример ```tsx import { Showcase, useDemoScenes } from "@/components/umbra/showcase"; const ORDER = ["button", "capsule", "lines", "summary"] as const; const { scene, fading } = useDemoScenes(ORDER, 1100, 1700); , capsule: , lines: , summary: }} /> ``` ================================================================================ # ProcessScreen (@umbra/process-screen) Установка: npx shadcn@latest add @umbra/process-screen Экран процесса: сверху текущий шаг 22/28 со спиннером строго по реальному статусу, ниже настоящее содержимое процесса (например, дописывающийся транскрипт), факты одной строкой; финал «Готово» с галочкой и явными действиями, автопереход не раньше 3 с без действий человека. Брать для обработки, импорта, сохранения, загрузки. Не брать для демо на первом экране (это Showcase) и для коротких действий до секунды (хватит Spinner в кнопке) ## Что это Статус главный, ниже настоящее содержимое. Шаг берётся только из реального источника (`current`). Иллюстраций, выдуманного прогресса и процентов нет. Финал явный: «Готово» с галочкой цветом `--primary-ink` и действиями. Сбой: знак Giants, что случилось, и действия ## Когда брать - Обработка после записи: «Останавливаю запись» → «Дописываю реплики» → «Сохраняю встречу» → «Готово», ниже транскрипт, который дописывается - Импорт файла, сохранение, загрузка модели (настоящие мегабайты фактами) ## Когда не брать - Демо на первом экране: `Showcase` - Действие короче секунды: `Spinner` в кнопке - Уведомление поверх экрана: `NoticeCard` ## Состав | Проп | Что | |---|---| | `steps` | шаги по порядку: `{ id, label }` | | `current` | id текущего шага из реального статуса | | `state` | `running`, `done`, `failed` | | `facts` | одной строкой подписью: «32 мин · 4 120 слов · 186 реплик» | | `actions` | явные действия финала или сбоя | | `onAutoContinue`, `autoContinueMs` | автопереход после «Готово»: не раньше 3000 мс и отменяется любым действием человека | | `doneTitle`, `failedTitle`, `stepLabel` | тексты | | `children` | настоящее содержимое процесса | ## Состояния Идёт (спиннер, «шаг n из m», заголовок шага появляется сдвигом), готово (галочка, действия), сбой (знак Giants, действия). Заголовок в `aria-live` ## Можно - Содержимое настоящее: последние реплики без галлюцинаций, настоящие числа фактов (можно `Counter`) - Открыть результат сразу, как только он готов: явное действие, а не ожидание ## Нельзя - Иллюстрация вместо содержимого, выдуманные проценты, шаг по таймеру - Автопереход раньше 3 с или когда человек уже что-то делает ## Пример ```tsx import { ProcessScreen } from "@/components/umbra/process-screen"; import { Button } from "@/components/umbra/button"; Открыть встречу} onAutoContinue={openMeeting} > ``` ================================================================================ # BrandMark (@umbra/brand-mark) Установка: npx shadcn@latest add @umbra/brand-mark Статичный знак продукта: картинка персонажа продукта на плитке акцента нужного размера (16–128), с альтернативным текстом. Брать для логотипа в Sidebar, на заставке и в шапке. Не брать для живого маскота (он в портфолио) и для иконок интерфейса (это Icon) ## Что это `` знака с фиксированными размерами и запретом перетаскивания. Знак берётся из `public/brand/` (элемент бренда продукта) ## Когда брать - Логотип в `Sidebar` (48), заставка запуска (88), мелкие места (16, 24, 28) ## Когда не брать - Живой маскот с настроениями: компонент портфолио - Иконки действий: `Icon` ## Варианты | Проп | Что | |---|---| | `src` | путь к знаку: `/brand/shadenotes.svg` | | `name` | альтернативный текст; пустой, если имя продукта написано рядом | | `size` | 16, 24, 28, 48, 88, 128 | ## Нельзя - Анимация, эмоции, тень и обводка у знака ## Пример ```tsx import { BrandMark } from "@/components/umbra/brand-mark"; import { Sidebar } from "@/components/umbra/sidebar"; }>{/* … */} ``` ================================================================================ # Знак ShadeNotes (@umbra/brand-shadenotes) Установка: npx shadcn@latest add @umbra/brand-shadenotes Знак ShadeNotes: персонаж ShadeNotes (острый силуэт #0A0A0A, глаза-полумесяцы, лаймовая искра) на лаймовой плитке с радиусом 52/256, SVG и PNG, и иконка приложения 512. Брать в ShadeNotes для логотипа в сайдбаре, заставки и иконок. Не брать в другом продукте и не анимировать: знак статичный ## Что это Персонаж ShadeNotes в семье персонажей Umbra: общая ДНК (чёрный силуэт, белые глаза, плитка акцента) и свои детали (острый силуэт, глаза-полумесяцы, искра). Статичный знак. Тот же рисунок, что в иконке приложения. Файлы ложатся в `public/brand/` ## Когда брать - Логотип в сайдбаре 48 px: `` - Заставка запуска, шапка письма, страница загрузки ## Когда не брать - В портфолио и новом продукте: у них свой акцент и свой знак - Как живого персонажа: знак без эмоций и анимации ## Можно - Размеры от 16 до 1024; в 16–24 px знак не дополнять деталями ## Нельзя - Перекрашивать плитку не в акцент продукта, добавлять тень, градиент, обводку - Менять иконку приложения без решения автора ================================================================================ # Знак портфолио (@umbra/brand-portfolio) Установка: npx shadcn@latest add @umbra/brand-portfolio Персонаж портфолио: статичный кадр живого маскота (круглая голова с ушами, вертикальные глаза #FFFEF5, тело с радиальными градиентами) и иконка сайта 180. Брать в портфолио для превью и иконки сайта. Не брать в других продуктах: у каждого продукта свой персонаж. Живой маскот (настроения и движения) остаётся компонентом портфолио и в реестр не входит ## Что это Статичный кадр живого персонажа портфолио. Градиенты тела и свет ауры разрешены только у него: это свет живого существа, «одно живое существо» портфолио. Спецификация персонажей в DESIGN.md («Слой продукта», «Персонажи») и на странице «Знаки продуктов» Для 16–24 px предложен плоский знак `brand/portfolio/mark-proposal.svg`: тот же силуэт без градиентов, белые глаза на кобальтовой плитке. Это предложение: в элемент реестра он не входит и в портфолио не поставлен ## Когда брать - Иконка сайта портфолио, превью ссылок ## Когда не брать - В приложениях и других продуктах: у каждого продукта свой персонаж - Как живого персонажа в другом продукте: живое существо у продукта одно, и у портфолио это он ## Нельзя - Добавлять персонажу портфолио искру: она деталь ShadeNotes - Анимировать кадр в другом продукте