SUS — Design Tokens: шрифты, цвета, иконки, темы
Архитектура дизайн-токенов (design tokens) для нового SUS.
Единственный источник правды для тем, шрифтов и иконок в SUS (каскад StyleSheet + Resources). Принцип: всё через CSS-каскад иvar()— компонент не знает конкретных значений, только семантические токены.
![]() Цвета, типографика и иконки — Dark | ![]() Те же токены — Light |
Содержание
- Общая архитектура (вкл. рестайл без C#)
- Шрифты
- Цвета: трёхслойная система токенов
- Иконки: Phosphor Icons
- Темы: переключение Light/Dark
- Адаптивные breakpoints
- Bootstrap: как всё собирается вместе
- Структура файлов
- Исторический чеклист
- Приложение A: сводная таблица токенов
- Приложение B: ключевые правила и паттерны
1. Общая архитектура
1.1 Принцип
The component writes: And receives:
color: var(--sus-text-primary) → rgba(255,255,255,1) (in dark theme)
font-size: var(--sus-font-size-body) → 14px (semantic size from _font.uss)
-unity-font: var(--sus-font-body) → Montserrat (family bridge)Токены резолвятся через CSS-каскад. Переменные задаются в :root USS-файлах, которые подключаются прямым вызовом root.styleSheets.Add().
1.2 Три слоя токенов
LAYER 1 — Raw values (ground truth)
_palette.uss — --base-color-*, --base-space-*, --base-font-*, --base-radius-*
_font.uss — --font-family-*, --font-heading, --font-body
LAYER 2 — Theme aliases (light/dark switch)
_theme.uss (:root + .theme-dark) — --thm-* (dark default)
_theme.uss (.theme-light) — --thm-* (light override)
LAYER 3 — Semantic UI tokens
design-tokens.uss — --sus-* (delegates to --thm-*)
_icon.uss — .sus-icon-bg utility classПорядок загрузки (каскад контейнера через SusBootstrap.LoadTokenCascade / SusApp): _palette → _font → _theme → design-tokens → _icon → зарегистрированные extras (L4/L5) → OverlayHost.
Panel-level TSS (SusDefault.tss / ApplyDefaultTSS) также включает _palette, _font и опциональный _global — _global не входит в каскад контейнера.
Тема переключается добавлением/снятием .theme-dark / .theme-light на корне каскада (SusThemeService.Instance.SetTheme(root, theme)).
1.3 Как это работало в старой схеме
SusDataManager.LoadUIResolutionThemes() (legacy) создавал несколько пресетов темы. В SUS это упрощено — только Dark/Light через классы .theme-*; адаптация под размер экрана — только breakpoints (SusBreakpointService + переопределения токенов .breakpoint-*). См. 06-responsive.md.
1.4 Рестайл без правки C#
Каскад токенов — не только правило внутренней гигиены: это поддерживаемый способ сделать контролы похожими на вашу игру без правки C#:
- Токены (
--sus-*) — переопределите семантические токены (или L1/L2-значения, на которые они делегируют) на корне каскада. Все потребители этих токенов перекрасятся вместе: одно изменение листа — весь UI. - Классы — добавьте проектное USS-правило на класс компонента или элемента для разового вида (кнопка, панель, ряд) без форка скрипта компонента.
- Состояния как классы — hover / active / disabled / selected (и похожие) ведутся USS- классами или UITK pseudo-classes. C# только переключает членство (
AddToClassList/RemoveFromClassListили аналог Sharq). Рестайл состояния — та же работа, что и рестайл базы: меняйте лист, не скрипт.
Политика vs долг. Look-and-feel, который buyer должен уметь переопределить, живёт в USS. Геометрия и motion, которые обязаны следовать runtime props, могут оставаться в C#. Inline-запись appearance из C# (.style.backgroundColor, .style.color, шрифты, radii и подобное) перебивает любой селектор — известный остаток таких call site ещё есть и мигрируется в USS/классы. Не считайте эти call site API темизации; предпочитайте токены и классы. Именованные исключения (motion tweens, font service, drag ghosts и подобное) намеренны и задокументированы в коде.
2. Шрифты
2.1 Текущее состояние
Уже сделано: Montserrat (6 начертаний) поставляется с sus-core:
sus-core/Runtime/Resources/SusRuntime/
├── _font.uss
└── Fonts/Montserrat/
├── Montserrat-Regular.ttf (400)
├── Montserrat-Medium.ttf (500)
├── Montserrat-Bold.ttf (700)
├── Montserrat-Black.ttf (900)
├── Montserrat-Italic.ttf
└── Montserrat-Light.ttf (300)_font.uss задаёт :root { -unity-font: url("Fonts/Montserrat/Montserrat-Regular.ttf"); } и CSS-переменные --font-family-regular, --font-family-medium, --font-family-bold, --font-family-black, --font-family-italic, --font-family-light.
SusBootstrap.Mount<T>() автоматически загружает Resources/SusRuntime/_font.uss при каждом mount.
2.2 Семантические шрифты (поставляются)
_font.uss определяет:
- Слоты семейств (Family slots)
--font-family-*(по умолчанию Montserrat). Опциональные проектные override используют fallback-паттерн из заголовка_font.uss(var(…, url(...))) — эти override-слоты не входят в опубликованный инвентарь из 61 токена L3. - Семантические размеры (L3) — компоненты используют их (не сырые
--base-font-Nи не числовые px-алиасы с префиксом--sus-):
--sus-font-size-caption /* ← --base-font-size-xs (10px) */
--sus-font-size-small /* 12px */
--sus-font-size-body /* 14px */
--sus-font-size-subtitle /* 16px */
--sus-font-size-heading3 /* 20px */
--sus-font-size-heading2 /* 24px */
--sus-font-size-heading1 /* 32px */
--sus-font-size-hero /* 48px */design-tokens.uss также экспонирует мосты семейств (family bridges) для -unity-font:
--sus-font-body · --sus-font-label · --sus-font-heading · --sus-font-italic
2.2.1 Сырые размеры шрифта L1 (палитра)
В _palette.uss по-прежнему есть сырые --base-font-10 … --base-font-32. Считайте их внутренностями палитры. Компоненты и buyer-сэмплы должны использовать восемь токенов --sus-font-size-caption … --sus-font-size-hero выше.
2.2.2 Пользовательский override
Создайте Assets/Resources/SusRuntime/_font.uss — Resources.Load предпочитает копию проекта. Код не нужен. Открытые пробелы по начертаниям (Thin / ExtraLight / …) ведутся во внутреннем roadmap и не входят в этот пакет.
3. Цвета: трёхслойная система токенов
3.1 Слой 1 — сырые значения (в _palette.uss)
Файл, который идёт с sus-core в Resources/. Базовые цвета без семантики:
/* sus-core/Runtime/Resources/SusRuntime/_palette.uss — Layer 1 */
:root {
/* Greys */
--base-color-Grey98: rgb(250, 250, 250);
--base-color-Grey94: rgb(240, 240, 240);
--base-color-Grey90: rgb(229, 229, 229);
--base-color-Grey80: rgb(204, 204, 204);
--base-color-Grey60: rgb(153, 153, 153);
--base-color-Grey40: rgb(102, 102, 102);
--base-color-Grey30: rgb( 77, 77, 77);
--base-color-Grey24: rgb( 61, 61, 61);
--base-color-Grey16: rgb( 41, 41, 41);
--base-color-Grey12: rgb( 31, 31, 31);
--base-color-Grey08: rgb( 20, 20, 20);
--base-color-Grey04: rgb( 10, 10, 10);
--base-color-Grey02: rgb( 5, 5, 5);
/* Primary (Blue) */
--base-color-Primary90: rgb(205, 225, 255);
--base-color-Primary70: rgb(102, 163, 255);
--base-color-Primary50: rgb( 0, 102, 255);
--base-color-Primary30: rgb( 0, 61, 153);
--base-color-Primary10: rgb( 0, 20, 51);
/* Accent / Success / Warning / Error */
--base-color-Success: rgb( 76, 175, 80);
--base-color-Warning: rgb(255, 193, 7);
--base-color-Error: rgb(244, 67, 54);
}3.2 Слой 2 — тематические алиасы (Dark/Light в _theme.uss)
L2 определяет только --thm-* (без выдуманных --thm-bg / --thm-fail / --thm-text-inverse). Полный список — в USS-файле; ключевые группы ниже совпадают с as-is.
Тема Dark (по умолчанию):
/* sus-core/Runtime/Resources/SusRuntime/_theme.uss — Layer 2 Dark (excerpt) */
:root,
.theme-dark {
/* Surface */
--thm-bg-page: var(--base-color-Grey04);
--thm-bg-surface: var(--base-color-Grey08);
--thm-bg-surface-raised: var(--base-color-Grey12);
--thm-bg-surface-overlay: var(--base-color-Grey16);
--thm-bg-surface-variant: var(--base-color-Grey18);
--thm-bg-disabled: var(--base-color-Grey40);
/* Text */
--thm-text-primary: var(--base-color-Grey94);
--thm-text-secondary: var(--base-color-Grey80);
--thm-text-disabled: var(--base-color-Grey60);
--thm-text-on-primary: var(--base-color-Grey98);
/* Border */
--thm-border: var(--base-color-Grey24);
--thm-border-hover: var(--base-color-Grey40);
--thm-border-focus: var(--base-color-Primary50);
/* Brand / status */
--thm-primary: var(--base-color-Primary50);
--thm-primary-hover: var(--base-color-Primary70);
--thm-primary-pressed: var(--base-color-Primary30);
--thm-secondary: var(--base-color-Grey24);
--thm-secondary-hover: var(--base-color-Grey30);
--thm-success: var(--base-color-Success);
--thm-success-hover: var(--base-color-SuccessHover);
--thm-warning: var(--base-color-Warning);
--thm-warning-hover: var(--base-color-WarningHover);
--thm-error: var(--base-color-Error);
--thm-error-hover: var(--base-color-ErrorHover);
--thm-info: var(--base-color-Info50);
--thm-info-hover: var(--base-color-Info70);
/* Scrim / overlays */
--thm-scrim: var(--base-color-BlackT30);
--thm-hover-overlay: var(--base-color-WhiteT5);
--thm-selected-overlay: var(--base-color-WhiteT10);
--thm-disabled-overlay: var(--base-color-WhiteT5);
}Тема Light (.theme-light — инвертируемые шаги серого зеркалятся; brand/status в основном те же --base-*):
/* sus-core/Runtime/Resources/SusRuntime/_theme.uss — Layer 2 Light (excerpt) */
.theme-light {
--thm-bg-page: var(--base-color-Grey96);
--thm-bg-surface: var(--base-color-Grey92);
--thm-bg-surface-raised: var(--base-color-Grey88);
--thm-bg-surface-overlay: var(--base-color-Grey84);
--thm-bg-surface-variant: var(--base-color-Grey81);
--thm-bg-disabled: var(--base-color-Grey60);
--thm-text-primary: var(--base-color-Grey06);
--thm-text-secondary: var(--base-color-Grey20);
--thm-text-disabled: var(--base-color-Grey40);
--thm-text-on-primary: var(--base-color-Grey98);
--thm-border: var(--base-color-Grey76);
--thm-border-hover: var(--base-color-Grey60);
--thm-border-focus: var(--base-color-Primary50);
--thm-secondary: var(--base-color-Grey76);
--thm-secondary-hover: var(--base-color-Grey70);
--thm-hover-overlay: var(--base-color-BlackT10);
--thm-selected-overlay: var(--base-color-BlackT18);
--thm-disabled-overlay: var(--base-color-BlackT10);
--thm-scrim: var(--base-color-BlackT30);
/* primary / success / warning / error / info — same --base-* as dark */
}Полный блок — в _theme.uss (включая игровые utility --thm-rare / --thm-heal / … — только L2, без зеркала в --sus-*).
3.3 Слой 3 — семантические UI-токены (design-tokens.uss + размеры шрифта)
Публичный L3 API: 61 определение --sus-* — 53 в design-tokens.uss плюс 8 семантических font-size токенов в _font.uss. Копируйте из этих файлов; не выдумывайте компонентные цветовые алиасы (кнопки, inputs, tooltips, scrollbars) — собирайте из L3.
/* sus-core/Runtime/Resources/SusRuntime/design-tokens.uss — Layer 3 (as-is) */
:root {
/* ── Surface ── */
--sus-bg-page: var(--thm-bg-page);
--sus-bg-surface: var(--thm-bg-surface);
--sus-bg-surface-raised: var(--thm-bg-surface-raised);
--sus-bg-overlay: var(--thm-bg-surface-overlay);
/* ── Text ── */
--sus-text-primary: var(--thm-text-primary);
--sus-text-secondary: var(--thm-text-secondary);
--sus-text-disabled: var(--thm-text-disabled);
--sus-text-on-primary: var(--thm-text-on-primary);
/* ── Border ── */
--sus-border: var(--thm-border);
--sus-border-hover: var(--thm-border-hover);
--sus-border-focus: var(--thm-border-focus);
--sus-divider: var(--thm-border);
/* ── Scrim ── */
--sus-scrim: var(--thm-scrim);
/* ── Surfaces ── */
--sus-bg-surface-variant: var(--thm-bg-surface-variant);
--sus-bg-disabled: var(--thm-bg-disabled);
/* ── Primary ── */
--sus-primary: var(--thm-primary);
--sus-primary-hover: var(--thm-primary-hover);
--sus-primary-pressed: var(--thm-primary-pressed);
/* ── Secondary ── */
--sus-secondary: var(--thm-secondary);
--sus-secondary-hover: var(--thm-secondary-hover);
/* ── Semantic colors ── */
--sus-success: var(--thm-success);
--sus-success-hover: var(--thm-success-hover);
--sus-warning: var(--thm-warning);
--sus-warning-hover: var(--thm-warning-hover);
--sus-error: var(--thm-error);
--sus-error-hover: var(--thm-error-hover);
--sus-info: var(--thm-info);
--sus-info-hover: var(--thm-info-hover);
/* ── Overlay accents (hover/selected/disabled states) ── */
--sus-hover-overlay: var(--thm-hover-overlay);
--sus-selected-overlay: var(--thm-selected-overlay);
--sus-disabled-overlay: var(--thm-disabled-overlay);
/* ── Opacity states ── */
--sus-opacity-hover: 0.04;
--sus-opacity-focus: 0.12;
--sus-opacity-selected: 0.08;
--sus-opacity-disabled: 0.38;
/* ── Font family bridges (-unity-font) ── */
--sus-font-body: var(--base-font-body);
--sus-font-label: var(--base-font-label);
--sus-font-heading: var(--base-font-heading);
--sus-font-italic: var(--base-font-italic);
/* ── Spacing ── */
--sus-space-0: 0px;
--sus-space-4: var(--base-space-4);
--sus-space-8: var(--base-space-8);
--sus-space-12: var(--base-space-12);
--sus-space-16: var(--base-space-16);
--sus-space-24: var(--base-space-24);
--sus-space-32: var(--base-space-32);
--sus-space-48: var(--base-space-48);
--sus-space-64: var(--base-space-64);
/* ── Radius ── */
--sus-radius-sm: var(--base-radius-sm);
--sus-radius-md: var(--base-radius-md);
--sus-radius-lg: var(--base-radius-lg);
--sus-radius-xl: var(--base-radius-xl);
--sus-radius-full: var(--base-radius-full);
}Размеры шрифта (тоже L3; задаются в _font.uss, не в design-tokens.uss):
/* sus-core/Runtime/Resources/SusRuntime/_font.uss — semantic sizes */
:root {
--sus-font-size-caption: var(--base-font-size-xs);
--sus-font-size-small: var(--base-font-size-sm);
--sus-font-size-body: var(--base-font-size-md);
--sus-font-size-subtitle: var(--base-font-size-lg);
--sus-font-size-heading3: var(--base-font-size-xl);
--sus-font-size-heading2: var(--base-font-size-2xl);
--sus-font-size-heading1: var(--base-font-size-3xl);
--sus-font-size-hero: var(--base-font-size-4xl);
}Композиция (не токены L3): кнопки/inputs/tooltips/scrollbars собирают токены выше — например primary-кнопка background-color: var(--sus-primary); подпись color: var(--sus-text-on-primary); hover var(--sus-primary-hover); затемнение модального backdrop var(--sus-scrim); приподнятая поверхность панели var(--sus-bg-overlay) или var(--sus-bg-surface-raised).
Использование шкалы space в вёрстке: держите зазоры между sibling не меньше --sus-space-8, выравнивайте ряды, избегайте обрезанного контента и используйте только токены (без сырых px).
3.4 Использование в компонентах
<!-- Card.sharq — compose L3 tokens (no component-level color aliases) -->
<style scoped>
.card {
background-color: var(--sus-bg-surface);
border-width: 1px;
border-color: var(--sus-border);
border-radius: var(--sus-radius-md);
color: var(--sus-text-primary);
padding: var(--sus-space-16);
}
.card__title {
font-size: var(--sus-font-size-heading3);
-unity-font: var(--sus-font-heading);
color: var(--sus-text-primary);
}
.card__body {
font-size: var(--sus-font-size-body);
color: var(--sus-text-secondary);
margin-top: var(--sus-space-8);
}
.card__cta {
background-color: var(--sus-primary);
color: var(--sus-text-on-primary);
}
.card__cta:hover {
background-color: var(--sus-primary-hover);
}
.card__link {
color: var(--sus-primary); /* links: brand color, not a separate link token */
}
</style>4. Иконки: Phosphor Icons
SUS поставляет Phosphor Icons (MIT) как VectorImage SVG — не Bootstrap Icons. Дерева Icons/bootstrap/ больше нет.
| Набор | Путь | Размер | Роль |
|---|---|---|---|
| Core | Resources/SusRuntime/Icons/core/{regular,fill}/ | ~127 SVG | Встроенный набор, всегда присутствует: всё, что компоненты и downstream UI-пакеты используют по умолчанию |
| Phosphor | Samples~/PhosphorIcons/Resources/SusRuntime/Icons/phosphor/{thin,light,regular,bold,fill,duotone}/ | 1512 имён × 6 weights ≈ 9000 SVG | Опциональная полная библиотека, импортируется как сэмпл Phosphor Icon Set |
Weights (SusIconWeight): Thin, Light, Regular (по умолчанию), Bold, Fill, Duotone.
Статус: ✅ runtime + editor автоматически регистрируют оба провайдера; downstream UI-пакеты используют тот же registry.
Полный набор намеренно вынесен за пределы пакета: папка Resources попадает в каждый player build и не вырезается, поэтому 9000 SVG стоили бы каждому потребителю ~19 MB независимо от того, нужны они или нет. PhosphorIconProvider читает из любой папки Assets/**/Resources/SusRuntime/Icons/phosphor/, поэтому и импорт сэмпла, и копирование только нужных иконок в свой Resources — оба варианта работают. Без него SusIconRegistry резолвит через core-подмножество и для «длинного хвоста» возвращает null вместо падения.
4.1 Раскладка на диске
sus-core/Runtime/Resources/SusRuntime/Icons/
└── core/
├── regular/ star.svg, gear.svg, …
└── fill/ star-fill.svg, … (weight folder + optional -fill suffix)
sus-core/Samples~/PhosphorIcons/Resources/SusRuntime/Icons/ (optional sample)
└── phosphor/
├── thin/
├── light/
├── regular/ {name}.svg
├── bold/ {name}-bold.svg
├── fill/ {name}-fill.svg
└── duotone/ {name}-duotone.svgКонвенция загрузчика (ResourcesFolderIconProvider): resources pathSusRuntime/Icons/{collection}/{weightFolder}/{fileName}
где fileName — {name} для regular, иначе {name}-{weight}.
Два конструктора:
new ResourcesFolderIconProvider("app")— project-local перегрузка (без package id). Отдаёт иконки из любой папкиAssets/**/Resources/SusRuntime/Icons/app/…(включая wizardCustomization/Icons/Resources/…). Используйте для собственных иконок приложения.new ResourcesFolderIconProvider("com.my.game", "app")— packaged перегрузка. Первый аргумент — UPM package id, в чьёмRuntime/Resources/SusRuntime/Icons/app/…лежат иконки (editor scan + починка.metaрезолвятся относительно этого пакета). Используйте только когда иконки поставляются внутри вашего пакета. Не передавайте"com.sharq-it.sus.core"для проектных иконок — сканирование уйдёт в пакет core.
4.2 Как работает поиск иконки
SusIcon / SusIconRegistry.Load(alias, weight)
→ resolve semantic aliases (e.g. "settings" → "gear")
→ optional name suffix "-fill" / "-bold" / … overrides weight
→ query ISusIconProvider list (first hit wins):
1. Project providers (SusApp.UseIcons / RegisterProvider highest priority)
2. CoreIconProvider (Icons/core)
3. PhosphorIconProvider (any Assets/**/Resources/…/Icons/phosphor, auto via PhosphorIconBootstrap)PhosphorIconBootstrap регистрирует Phosphor на SubsystemRegistration / загрузке editor с asHighestPriority: false, поэтому core + проектные иконки сохраняют приоритет при совпадающих именах.
4.3 API (текущий)
// Weight enum — not the old SusIconStyle Outline/Filled
public enum SusIconWeight { Thin, Light, Regular, Bold, Fill, Duotone }
// Core primitive (also used under the hood)
var icon = new SusIcon("gear"); // Regular
var star = new SusIcon("star", SusIconWeight.Fill);
icon.Name.Value = "x"; // reactive
VectorImage img = SusIconRegistry.Load("star", SusIconWeight.Fill);
SusIconRegistry.AddAlias("settings", "gear");
SusIconRegistry.RegisterProvider(myProvider, asHighestPriority: true);
// SusApp — project icons win over Phosphor
SusApp.Create(uiDocument)
.UseIcons(new ResourcesFolderIconProvider("com.my.game", "app"))
.Mount<HomeScreen>();Downstream UI-пакеты могут экспонировать иконку более высокого уровня:
<sus:SusIcon Icon="star" Weight="Fill" Size="md" />Kind=phosphor|game|portrait|auto — game/portrait используют consumer media bridge; см. документацию вашего опционального пакета компонентов.
4.4 Настройки импорта SVG (ВАЖНО)
Каждый .svg.meta должен использовать UI Toolkit Vector Image + antialiased tessellation:
svgType: 3 # UI Toolkit Vector Image
tessellationMode: 1 # Antialiased Arc Encoding (NOT 0)
textureSize: 256
sampleCount: 4Без tessellationMode: 1 кривые выглядят пиксельными.
4.5 Вспомогательный USS
_icon.uss — глобальный класс для обычного VisualElement / core SusIcon (.sus-icon-bg):
.sus-icon-bg {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
-unity-background-image-tint-color: rgb(255, 255, 255);
flex-shrink: 0;
}Загружается вместе с каскадом токенов (SusBootstrap / SusApp).
4.6 USS vs C# для иконок
| Что | Где | Почему |
|---|---|---|
background-size/position/repeat, flex-shrink | USS (.sus-icon-bg / companion) | Статика |
| Default tint | USS | Тема может переопределить |
backgroundImage | C# через SusIconRegistry.Load | Runtime-ассет |
| Size / weight / name | Props / C# | Динамика |
4.7 Кастомные / проектные иконки
Не кладите файлы в вымышленный Icons/bootstrap/custom/. Предпочтительнее:
ResourcesFolderIconProvider, указывающий на вашу коллекцию подResources/SusRuntime/Icons/{yourCollection}/{weight}/, затемSusApp.UseIcons(...)илиSusIconRegistry.RegisterProvider(...).- Или
SusIconSetAsset+SusApp.UseIcons(iconSet). - Семантические алиасы:
SusIconRegistry.AddAlias("my-settings", "gear").
Scaffolding Setup Project использует Customization/Icons/.../app/ + ResourcesFolderIconProvider("app") (project-local перегрузка) как слой проектного override.
5. Темы: переключение Light/Dark
5.1 Как это работало в предыдущей схеме
UIResolutionThemes— ScriptableObject с массивом из 6StyleTheme(Dark/Light/CustomLight × High/Low)panel.themeStyleSheet=ThemeStyleSheet(.tss), который через@importтянет цепочку USSPollScreenWidth()вUpdate— переключает High/Low при пересечении 1600pxSetStyleTheme()— переключает Dark/Light/CustomLight
5.2 SUS — текущий API
Нет ThemeStyleSheet для переключения темы — USS-листы добавляются на контейнер через LoadTokenCascade / SusApp. Варианты темы — CSS-классы на корне каскада.
Файлы слоёв:
_palette.uss— L1--base-*(сырые значения)_theme.uss— L2--thm-*для.theme-dark/.theme-light
Runtime API (SusTheme — readonly struct; сервис — singleton):
// sus-core/Runtime/SusTheme.cs + Runtime/Services/SusThemeService.cs
namespace Sharq.Core
{
public readonly struct SusTheme
{
public string Name { get; }
public SusTheme(string name);
public static SusTheme Dark => new("dark");
public static SusTheme Light => new("light");
public string CssClass => $"theme-{Name}"; // "theme-dark", "theme-light", …
}
public class SusThemeService
{
public static SusThemeService Instance { get; }
public static Prop<SusTheme> Current { get; }
// Applies .theme-{name} on the cascade root + OverlayHost
public void SetTheme(VisualElement root, SusTheme theme);
}
}Использование:
SusThemeService.Instance.SetTheme(root, SusTheme.Dark);
SusThemeService.Instance.SetTheme(root, SusTheme.Light);
SusThemeService.Instance.SetTheme(root, new SusTheme("midnight")); // custom → .theme-midnight
Watch(SusThemeService.Current, (_, next) => AdaptToTheme(next));CSS-переключение через класс на корне — уже в _theme.uss (см. 3.2).
5.3 SusBootstrap / SusApp
SusApp.Create(...).UseTheme(...).Mount/Run применяет тему последней, чтобы OverlayHost получил theme-класс. Предпочитайте этот путь ручному styleSheets.Add.
SusBootstrap.Mount<T>() / LoadTokenCascade загружают полный каскад (_palette → _font → _theme → design-tokens → _icon → extras + OverlayHost).
6. Адаптивные breakpoints
Адаптация под размер экрана — только SusBreakpointService (классы .breakpoint-sm … .breakpoint-2xl на корне каскада). Нет оси High/Low resolution и нет автоматического масштаба UI, привязанного к размеру монитора.
Ширина читается из cascadeRoot.resolvedStyle.width на geometry changes (тот же путь подачи, что у удалённого SusResolutionService), с fallback на panel / Screen только когда root ещё не выложен. См. 06-responsive.md.
Token-листы downstream UI-пакетов могут переопределять пакетные токены под этими классами (высоты, spacing, шрифты). Компоненты, которые уже потребляют эти vars, подхватывают активный breakpoint без per-component C#.
PanelSettings сэмплов/wizard используют ConstantPixelSize, чтобы ширина breakpoint совпадала с панелью (без авто-масштаба Unity ScaleWithScreenSize).
7. Bootstrap: как всё собирается вместе
7.1 Порядок загрузки (предпочитайте SusApp)
SusApp.Create(uiDocument)
├─ ApplyDefaultTSS (panel: _palette + _font + _global via SusDefault.tss)
├─ EnsureEventSystem
├─ UseIcons → SusIconRegistry
├─ LoadTokenCascade (container):
│ _palette → _font → _theme → design-tokens → _icon → extras
│ + Breakpoint/Density/Scale services + OverlayHost
├─ EnsureWorldSpacePanel (if UseWorldSpace && playing)
├─ UseFonts / UseCustomStyles
├─ Configure callbacks (router / manual UI)
├─ Mount<T> (optional)
└─ SusThemeService.Instance.SetTheme(root, theme) ← lastВажно: каскад контейнера всегда включает _palette и _theme (не только design-tokens). Предпочитайте SusApp самодельному root.styleSheets.Add(...).
Оговорка про world-space panel.
EnsureWorldSpacePanelдаёт world-панели собственный каскад токенов (_palette → _font → _theme → design-tokens), ноUseFonts,UseCustomStyles(брендинг) иSetThemeприменяются только к screen root + OverlayHost — не к world panel. Поэтому world UI (healthbars/nameplates из downstream UI-пакета) рендерится с дефолтной темой Dark, дефолтными шрифтами и без branding override. Если нужны brand-цвета / light-тема / кастомные шрифты на world UI, примените их кapp.WorldPanel/WorldSpaceService.Default.WorldSpacePanelсами (напримерSusThemeService.Instance.SetTheme(worldRoot, theme)и загрузите branding USS на него).
7.2 Что определяет каждый слой
_palette.uss — L1: --base-color-*, --base-space-*, --base-font-*, --base-radius-*
_font.uss — font families (--font-family-*) + optional family override slots + --sus-font-size-caption…hero
_theme.uss — L2: --thm-* for .theme-dark / .theme-light
design-tokens.uss— L3: --sus-* semantic tokens (colors, space, radius, font family bridges)
_icon.uss — .sus-icon-bg utility7.3 Минимальный пример
public class AppEntry : MonoBehaviour
{
public UIDocument uiDocument;
void Start()
{
SusApp.Create(uiDocument)
.UseTheme(SusTheme.Dark)
.Mount<App>();
// Fonts, colors, icons, OverlayHost, theme — all wired.
}
}Низкоуровневая альтернатива (ручной каскад):
void Start()
{
SusBootstrap.ApplyDefaultTSS(uiDocument);
var root = uiDocument.rootVisualElement;
SusBootstrap.LoadTokenCascade(root);
SusBootstrap.Mount<App>(root);
SusThemeService.Instance.SetTheme(root, SusTheme.Dark);
}8. Структура файлов
8.1 В sus-core (идёт с пакетом)
sus-core/
├── Docs/
│ └── DESIGN_TOKENS.md ← this document
│
├── Runtime/
│ ├── Resources/SusRuntime/
│ │ ├── _palette.uss ← L1 --base-* ✅
│ │ ├── _font.uss ← Fonts (Montserrat) ✅
│ │ ├── _theme.uss ← L2 --thm-* ✅
│ │ ├── design-tokens.uss ← L3 --sus-* ✅
│ │ ├── _icon.uss ← .sus-icon-bg ✅
│ │ ├── Fonts/Montserrat/ ← 6 .ttf
│ │ └── Icons/
│ │ └── core/{regular,fill}/
│ │ (full phosphor/{thin,light,regular,bold,fill,duotone}/
│ │ ships as the optional Samples~/PhosphorIcons sample)
│ ├── SusTheme.cs / Services/SusThemeService.cs
│ ├── SusBreakpointService.cs
│ ├── SusIconRegistry.cs + icon providers
│ ├── SusIcon.cs
│ ├── SusApp.cs
│ └── SusBootstrap.csЗамечание по версионированию: версия пакета ведётся в package.json. При каждом изменении .cs в sus-core поднимайте package.json (pre-push hook требует этого).
8.2 В проекте пользователя (опциональный override)
Assets/Resources/SusRuntime/
├── _font.uss ← font override
├── _palette.uss ← L1 palette override (optional)
├── _theme.uss ← L2 theme override (optional)
└── Icons/
└── {yourCollection}/{regular|fill|…}/ ← via ResourcesFolderIconProviderИли используйте SusApp.UseIcons(...) / SusIconSetAsset — см. §4.7.
8.3 SUS Theme Editor (инструмент Editor)
Окно Editor для визуального редактирования _palette.uss вместо ручного набора rgb(): свотчи цвета на каждый токен (--base-color-*), полоса превью с живым contrast ratio и генератор hover/pressed-вариантов из исходного цвета. Сохраняет обратно в проектный override _palette.uss.

9. Исторический чеклист
Фазы B–F (palette/theme/L3-токены, breakpoints, Phosphor icons, каскад SusApp, доки) сделаны. Эта buyer-страница — не roadmap.
Открытая работа по начертаниям (Thin / ExtraLight / SemiBold / ExtraBold и связанные расширения _font.uss) ведётся во внутреннем roadmap и не входит в этот пакет.
| Фаза | Результат | Статус |
|---|---|---|
| A — Шрифты (поставляемое подмножество) | Montserrat 6 styles + семантические font-size токены + family bridges | ✅ (открытые faces → internal plan) |
| B — Цвета | _palette / _theme / design-tokens + SusThemeService | ✅ |
| C — Размеры / breakpoints | spacing/radius L1; SusBreakpointService | ✅ |
| D — Иконки | Core-подмножество + опциональный сэмпл Phosphor + registry | ✅ |
| E — Bootstrap | SusApp / LoadTokenCascade | ✅ |
| F — Документация | эта страница + проекция в пакет | ✅ |
Приложение A: сводная таблица токенов
Публичный инвентарь L3 (61 --sus-*). Источник: design-tokens.uss + _font.uss (font-size токены).
Surfaces / text / border / scrim
| Токен | Роль | Слой |
|---|---|---|
--sus-bg-page | Фон страницы | Color L3 |
--sus-bg-surface | Карточка / панель | Color L3 |
--sus-bg-surface-raised | Приподнятая поверхность | Color L3 |
--sus-bg-overlay | Overlay-поверхность (--thm-bg-surface-overlay) | Color L3 |
--sus-bg-surface-variant | Вариант поверхности | Color L3 |
--sus-bg-disabled | Заливка disabled | Color L3 |
--sus-text-primary | Основной текст | Color L3 |
--sus-text-secondary | Вторичный текст | Color L3 |
--sus-text-disabled | Текст disabled | Color L3 |
--sus-text-on-primary | Текст на залитом primary | Color L3 |
--sus-border | Рамка по умолчанию | Color L3 |
--sus-border-hover | Рамка hover | Color L3 |
--sus-border-focus | Focus ring | Color L3 |
--sus-divider | Разделитель | Color L3 |
--sus-scrim | Затемнение modal / overlay | Color L3 |
Brand / status / overlays / opacity
| Токен | Роль | Слой |
|---|---|---|
--sus-primary, --sus-primary-hover, --sus-primary-pressed | Brand | Color L3 |
--sus-secondary, --sus-secondary-hover | Нейтральный акцент | Color L3 |
--sus-success, --sus-success-hover | Success | Color L3 |
--sus-warning, --sus-warning-hover | Warning | Color L3 |
--sus-error, --sus-error-hover | Error | Color L3 |
--sus-info, --sus-info-hover | Info | Color L3 |
--sus-hover-overlay | Hover wash | Color L3 |
--sus-selected-overlay | Selected wash | Color L3 |
--sus-disabled-overlay | Disabled wash | Color L3 |
--sus-opacity-hover, --sus-opacity-focus, --sus-opacity-selected, --sus-opacity-disabled | Числовые opacity | Color L3 |
Шрифты
| Токен | Роль | Слой |
|---|---|---|
--sus-font-body, --sus-font-label, --sus-font-heading, --sus-font-italic | Family bridges (-unity-font) | Font L3 |
--sus-font-size-caption | 10px | Size L3 |
--sus-font-size-small | 12px | Size L3 |
--sus-font-size-body | 14px | Size L3 |
--sus-font-size-subtitle | 16px | Size L3 |
--sus-font-size-heading3 | 20px | Size L3 |
--sus-font-size-heading2 | 24px | Size L3 |
--sus-font-size-heading1 | 32px | Size L3 |
--sus-font-size-hero | 48px | Size L3 |
Spacing / radius
| Токен | Роль | Слой |
|---|---|---|
--sus-space-0 … --sus-space-64 (0,4,8,12,16,24,32,48,64) | Шкала spacing | Size L3 |
--sus-radius-sm … --sus-radius-full (sm,md,lg,xl,full) | Скругление углов | Size L3 |
L1-хелперы, часто рядом с L3 (не входят в 61): --font-family-regular, --main-font-family, --base-space-*, --base-radius-*.
Приложение B: ключевые правила и паттерны (выявленные в процессе)
B.1 Статика в USS, динамика в C#
┌─────────────┬────────────────────────┬──────────────────────────┐
│ Where │ Examples │ Rule │
├─────────────┼────────────────────────┼──────────────────────────┤
│ USS (static)│ background-size, │ Do not change in runtime │
│ │ flex-shrink, │ from props - write in USS │
│ │ margin-left, color, │ │
│ │ font-size (layout) │ │
├─────────────┼────────────────────────┼──────────────────────────┤
│ C# (dynamics) │ width/height from Size, │ Change from props or │
│ │ tint from TintColor, │ runtime logic - in code │
│ │ backgroundImage │ │
│ │ (Resources.Load) │ │
└─────────────┴────────────────────────┴──────────────────────────┘Мотивация: главный источник изменений — props. Меняя props, мы меняем в C# только динамический атрибут. Статика лежит в USS и не размазана по коду. Так проще читать и отлаживать.
Пример (хорошо):
// C# - only dynamic from props
el.style.width = size; // ← from prop Size
el.style.height = size;
el.AddToClassList("icon-row"); // ← statics in USS
/* USS:
.icon-row { flex-direction: row; align-items: center; margin-bottom: 6px; } */Пример (плохо — антипаттерн):
el.style.flexDirection = FlexDirection.Row; // ← static in C# (migrate to USS)
el.style.alignItems = Align.Center;
el.style.color = new StyleColor(Color.white);B.2 Sharq bare <style> — как работают селекторы
Source (.sharq) Compiled (.uss) Does root match?
─────────────────────────────────────────────────────────────────────
<style> .sus-icon { ✅ YES
flex-shrink: 0; flex-shrink: 0;
</style> }
<style> .sus-icon .sus-icon { ❌ NO
.sus-icon { flex-shrink: 0; (.sus-icon inside .sus-icon)
flex-shrink: 0; }
}
</style>
<style> .sus-icon .child { ✅ YES (if child is inside)
.child { ... }
...
}
</style>Правило: если нужно стилизовать ROOT компонента, пишите свойства прямо в <style> без селектора. Если нужно стилизовать дочерние элементы — используйте их классы (без префикса компонента).
В особых случаях (self-target, container-target — см. правило sharq-css-scoping.mdc) — перенесите правило в companion .uss как unscoped.
B.3 Паттерн re-assert backgroundImage
UI Toolkit Known Issue: VectorImage иногда не рендерится на первом кадре. Проверенный паттерн (из старого SizedIcon):
el.style.backgroundImage = new StyleBackground(vec);
el.MarkDirtyRepaint();
el.schedule.Execute(() =>
{
el.style.backgroundImage = new StyleBackground(vec);
el.MarkDirtyRepaint();
}).ExecuteLater(0);
el.schedule.Execute(() =>
{
el.style.backgroundImage = new StyleBackground(vec);
el.MarkDirtyRepaint();
}).ExecuteLater(16); // next frameПереустановка backgroundImage трижды: сразу, через 0 кадров, через ~1 кадр. Так VectorImage не «потеряется» при пересборке дерева.
B.4 SVG Import: tessellationMode
| Параметр | Значение | Зачем |
|---|---|---|
svgType | 3 | UI Toolkit Vector Image (не Texture2D) |
tessellationMode | 1 | Antialiased Arc Encoding — гладкие края на дугах |
textureSize | 256 | Достаточно для иконок до 48px |
sampleCount | 4 | Multisampling для subpixel AA |
Никогда не используйте tessellationMode: 0 (Basic Triangulation) — даёт пиксельные/зубчатые края на SVG-кривых.
B.5 Загрузка USS: предпочитайте SusApp / LoadTokenCascade
// ✅ Correct: SusApp (or LoadTokenCascade) on the document root
SusApp.Create(uiDocument)
.UseTheme(SusTheme.Dark)
.Mount<App>();
// Cascade order: _palette → _font → _theme → design-tokens → _icon → extras + OverlayHost
// Panel TSS (_global) via ApplyDefaultTSS / SusDefault.tss
// ✅ Lower-level
SusBootstrap.ApplyDefaultTSS(uiDocument);
SusBootstrap.LoadTokenCascade(uiDocument.rootVisualElement);
// ❌ Incorrect: hand-add only design-tokens without _palette/_theme
root.styleSheets.Add(designTokensSheet);B.6 Версионирование sus-core
При любом изменении .cs в sus-core:
- Поднимите версию в
sus-core/package.json(patch) - Закоммитьте с version tag
- Push → кэш в Unity Package Manager
- Очистите
Library/PackageCache/com.sharq-it.sus.core/у клиента - Удалите
Packages/packages-lock.json - Refresh Unity → пакет резолвится заново
Изменения скриптов (.uss, .svg, .meta) не требуют ручного сброса кэша.

