Цвет и тема
Цвет в ките — это роли Material Design 3: у каждой своё назначение и своя пара для текста. Всю схему, светлую и тёмную, сервер выводит из одного цвета-акцента.
Роли вместо цветов#
Роль называет назначение, а не оттенок: primary — главное действие, surface — фон, error — ошибка. Компоненты кита берут только роли, поэтому смена акцента или темы перекрашивает их все без единой правки.
Роли идут парами: заливка и текст на ней — primary и on-primary, surface и on-surface. Пара держит контраст не ниже 4,5:1 в обеих темах при любом акценте.
Каждая роль — переменная --md-sys-color-<роль>. Сервер печатает их в <head> страницы, лист кита читает только их.
Акцент
Статусы
Поверхности
Обводки
Плитки нарисованы переменными темы: переключите тему в шапке — они перекрасятся на месте. Внизу плитки — значения обеих тем для текущего акцента.
Акцент#
primary— главное действие и активное состояние: основная кнопка, ссылка, подпись и линия поля в фокусе, значок в заголовке карточки.secondary— обводка фокуса с клавиатуры.secondary-container— выбранное: текущий пункт навигации и дерева разделов, выбранный чип, отмеченная строка таблицы.tertiary— третий акцент, оттенок на 60° дальше затравки. Сам кит его не берёт: он оставлен экрану для меток и графиков, которым нельзя спорить сprimary.*-container— тональная заливка той же семьи, слабее основной. Плашка вида «accent» лежит наprimary-container.
Статусы#
error — роль самой Material. success, warning и info кит добавил тем же устройством, как «пользовательские цвета» M3: четыре роли на семью. От акцента статусы не зависят — красный остаётся красным при любой затравке.
Плашки статусов — контейнер и текст своей семьи
HTML = Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "активна", "ok"))
+ Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "истекает", "warn"))
+ Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "отозвана", "err"))
+ Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "бета", "accent"))
+ Набор.Тег().Отрисовать("черновик");Тосты — контейнер статуса, значок цветом самого статуса
HTML = Набор.Тост().Отрисовать(Новый Структура("Текст, Вид", "Проект сохранён.", "ok"))
+ Набор.Тост().Отрисовать(Новый Структура("Текст, Вид", "Ключ истекает через три дня.", "warn"))
+ Набор.Тост().Отрисовать(Новый Структура("Текст, Вид", "Не удалось связаться с сервером.", "err"))
+ Набор.Тост().Отрисовать(Новый Структура("Текст, Вид", "Индекс перестраивается.", "info"));Поверхности#
surface — фон страницы. Пять ступеней контейнера, от surface-container-lowest до surface-container-highest, дают глубину без теней: карточка лежит на -low (в тёмной теме — на surface-container, токен кита --surface-card: между фоном и -low там всего три тона), меню на surface-container, диалог на -high, поле и плашка — на -highest. Текст на любой ступени — on-surface, второстепенный — on-surface-variant.
inverse-surface — обратная поверхность: тёмная в светлой теме и светлая в тёмной.
Обводки#
outline — граница управления: рамка вторичной кнопки и поля ввода. outline-variant — разделитель: черта над действиями карточки, линия между строками таблицы.
Остальные роли#
Схема печатает и роли, которых нет на плитках: *-fixed (одинаковые в обеих темах), surface-dim, surface-bright, background, inverse-primary, surface-tint, shadow и scrim. Скрим под диалогом и шторкой — scrim с прозрачностью 32%.
Схема из одного акцента#
Акцент — один цвет вида #rrggbb. По умолчанию это базовый цвет Material 3 #6750a4. Инсталляция задаёт свой в оформлении, посетитель — своим выбором, и сервер пересчитывает обе темы.
Выберите цвет и нажмите «Перекрасить»: акцент уйдёт в cookie оформления, и этот сайт перекрасится целиком — роли выше тоже. «Базовый акцент» возвращает #6750a4.
Как выводится схема#
Из затравки строятся тональные палитры. primary берёт её оттенок и хрому целиком, поэтому выбранный цвет узнаётся. secondary — тот же оттенок, приглушённый. tertiary — оттенок на 60° дальше. Нейтральные палитры почти серые, но с оттенком затравки: поверхности чуть тёплые или чуть холодные вслед за акцентом.
Роль — это тон палитры. Тон — светлота L* CIELAB, как в самой Material: primary — тон 40 в светлой теме и 80 в тёмной.
| Роль | Светлая | Тёмная |
|---|---|---|
primary, secondary, tertiary, статусы |
40 | 80 |
on-primary и другие on- |
100 | 20 |
*-container |
90 | 30 |
on-*-container |
10 | 90 |
surface |
98 | 6 |
on-surface |
10 | 90 |
surface-container-lowest … -highest |
100 … 90 | 4 … 22 |
outline |
50 | 60 |
outline-variant |
80 | 30 |
Яркость цвета зависит только от L*, поэтому контраст пары определяют тоны, а не затравка. Контраст держит таблица тонов, а не удачно выбранный цвет.
Не всякий тон есть в sRGB при нужной хроме: насыщенного жёлтого тона 10 не существует. Тогда схема уменьшает хрому, а тон оставляет — и контраст вместе с ним. Тест ТемаMaterial проверяет пары контраста на девяти акцентах, среди них белый, чёрный и жёлтый.
ЦветоваяСхема.Роли()src/Классы/оформление/ЦветоваяСхема.os
Роли цвета схемы.
Параметры:
Затравка - Строка - цвет #rgb либо #rrggbb, уже прошедший проверку формата.
Тёмная - Булево - Истина для тёмной темы.
Пресет - Структура, Неопределено - (необязательный, дев-сборка 9) поправки пресета темы
для этой темы: Ключ (Строка — для кэша), Нейтраль (Структура: Оттенок, Хрома,
ХромаВарианта — нейтральные палитры не от затравки; Неопределено — от неё),
Тоны (Соответствие: роль -> тон L*, поверх таблицы ролей). Без пресета — схема
Material, как прежде.
Возвращаемое значение:
Массив из Структура - Роль (имя без префикса, например "primary", "on-surface"),
Цвет (#rrggbb). Порядок постоянный: схема печатается байт в байт одинаково.Из чего выбирать акцент, кит тоже предлагает: палитра образцов ТокеныТемы.ОбразцыАкцента() — шесть цветов, которые поле цвета печатает само словом ОбразцыТемы().
ТокеныТемы.ОбразцыАкцента()src/Классы/оформление/ТокеныТемы.os
Образцы акцента — палитра кружков поля цвета, из которой посетитель или администратор выбирает акцент: шесть цветов, на которых схема Material держит контраст ролей в обеих темах. Палитра одна на все экраны выбора акцента — так её цвета не расходятся между оформлением посетителя и брендированием инсталляции. Имён цветов кит не знает (своих строк у него нет): имя для человека — текст словаря по ключу «field.color.swatch.<цвет>». Поле печатает палитру само — словом Кит.Поле(…).ОбразцыТемы(). Возвращаемое значение: Массив из Структура - Цвет (Строка — код #rrggbb), Ключ (Строка — ключ словаря с именем цвета: field.color.swatch.lavender | blue | green | amber | red | grey).
Пресеты темы#
Пресет — готовый набор цвета целиком: затравка акцента обеих тем, нейтраль поверхностей (с оттенком акцента, как у Material, синеватая или серая) и тоны поверхностей. Кит знает три пресета:
| Ключ | Имя в словаре | Что это |
|---|---|---|
classic |
theme.preset.classic — «Как было» |
кит 0.8: затравка акцента #5b4bbd в светлой и #9184d9 в тёмной, синеватые поверхности. Печатается: акцент #5d4cbf и #a092e9 (тон 65 — ссылке нужен контраст), тёмная — фон #171925, карточка #232532; светлая — фон #f3f3f9, карточка #ffffff |
material |
theme.preset.material — «Новая» |
по умолчанию: схема Material от затравки #6750a4, тёмные поверхности помягче (фон L* 10) |
oscript |
theme.preset.oscript — «oscript» |
цвета сайта oscript.io: затравка акцента #2563eb в светлой и #60a5fa в тёмной, серые поверхности. Печатается: акцент #1e60e8 (тон 45) и #61a6fb (тон 67) — тоны под контраст ссылки на всех поверхностях, светлая — фон #f9f9f9 и белая карточка, тёмная — фон #1b1b1e и карточка #212124 |
Пресет задаёт поле Пресет оформления: ТокеныТемы.Переменные(Новый Структура("Пресет", "oscript")). Свой акцент ложится поверх затравки пресета (Новый Структура("Пресет, Акцент", "classic", "#2bb673")): нейтраль и тоны остаются пресета. Без поля и с неизвестным ключом печатается material — та же схема, что до пресетов, байт в байт. Инсталляция отдаёт пресет по умолчанию полем Пресет своего оформления, посетитель — ключом пресет в cookie оформления.
Затравка — цвет, от которого строится схема; печатается тон схемы, ближайший к затравке, поэтому hex на странице может отличаться от затравки на единицу-другую в канале. Сверяйте с ТокеныТемы.ЦветаПресета(Ключ, Тёмная), а не с литералом. Тоны пресетов подобраны так, чтобы каждая пара «текст на поверхности» держала 4.5:1, а обводка поля — 3:1, в обеих темах; контраст зависит только от тонов, поэтому держится и со своим акцентом. Это сверяет тест ТемаMaterial.
Перечень для формы выбора даёт ТокеныТемы.Пресеты(): ключ, ключ словаря с именем и затравки. Рамка образца показывает пресет словом Пресет(Ключ, ИмяПоля): с именем поля она зеркалит выбор — сегменты ниже переключают пресет рамки без сервера, а переключатель «Светлая / Тёмная» на рамке показывает его во второй теме.
Три пресета: выбор сегментами и образец
Проект «Альфа»
HTML = Кит.Холст(
Кит.Сегменты("пресет",
Кит.Сегмент("classic", "Как было"),
Кит.Сегмент("material", "Новая"),
Кит.Сегмент("oscript", "oscript")).Подпись("Пресет темы").Выбрано("material"),
Кит.Образец("Пресет на сайте",
Кит.Карточка("Проект «Альфа»", Кит.Надпись("Карточка на поверхности пресета."),
Кит.Ряд("действия", Кит.Кнопка("Сохранить").Основная(), Кит.Кнопка("Отмена").Вторичная(),
Кит.Плашка("стабильный").Вид("accent")),
Кит.Ссылка("/проекты/альфа", "Открыть проект")),
Кит.Поле("имя", "Имя проекта").Значение("Альфа"))
.Пресет("material", "пресет")).Рендер();Без имени поля рамка показывает один пресет — так удобно сравнить их рядом:
Пресет «Как было» в тёмной теме
Проект «Альфа»
HTML = Кит.Образец("Как было",
Кит.Карточка("Проект «Альфа»", Кит.Надпись("Поверхности кита 0.8."),
Кит.Кнопка("Сохранить").Основная()))
.Пресет("classic").Тема("тёмная").Рендер();Пресет «oscript» в светлой теме
Проект «Альфа»
HTML = Кит.Образец("oscript",
Кит.Карточка("Проект «Альфа»", Кит.Надпись("Цвета сайта oscript.io."),
Кит.Кнопка("Сохранить").Основная()))
.Пресет("oscript").Тема("светлая").Рендер();ТокеныТемы.Пресеты()src/Классы/оформление/ТокеныТемы.os
Пресеты темы — готовые наборы цвета, из которых приложение даёт выбрать (слово владельца: «та, что была, та, что будет, и тема oscript с цветами сайта»). Пресет задаёт затравку акцента обеих тем, нейтраль поверхностей и тоны поверхностей; свой акцент оформления ложится поверх затравки пресета, нейтраль и тоны остаются его. Имён для человека у кита нет — имя пресета приходит из словаря по ключу. Возвращаемое значение: Массив из Структура - Ключ (classic | material | oscript — значение поля Пресет оформления), КлючСловаряИмени (theme.preset.<ключ>), Акцент (затравка светлой темы #rrggbb), АкцентТёмной (затравка тёмной), ПоУмолчанию (Булево — пресет оформления без поля Пресет). Порядок — порядок показа.
ТокеныТемы.ЦветаПресета()src/Классы/оформление/ТокеныТемы.os
Цвета одной темы пресета без тега <style> и без селектора: роли --md-sys-color-*, рампы, поверхность карточки, кольцо и прежние имена-ссылки на роли. Нужны тем, кто показывает пресет НЕ на всей странице, — рамке образца выбора пресета (Кит.Образец(…).Пресет). Параметры: Ключ - Строка - ключ пресета (см. Пресеты); неизвестный — пресет по умолчанию. Тёмная - Булево - Истина для тёмной темы. Акцент - Строка - (необязательный) свой акцент поверх затравки пресета. Возвращаемое значение: Строка - CSS-объявления, начиная с color-scheme.
Тёмная и светлая тема#
Тема по умолчанию — тёмная. Обе палитры печатаются в <head> сразу: тёмная — в :root, светлая — в html[data-theme="light"]. Какая из них действует, решает атрибут data-theme у <html>, его ставит сервер.
Переключатель в шапке — пилюля с бегунком (в бегунке солнце или луна) внутри формы. Форма отправляется без скрипта, сервер записывает выбор в cookie оформления и печатает страницу в новой теме. Кука хранит тему, акцент, плотность, радиус и пресет, живёт год, имя ей даёт инсталляция (по умолчанию ui). Выбор посетителя ложится поверх оформления инсталляции.
ТокеныТемы.ПоУмолчанию()src/Классы/оформление/ТокеныТемы.os
Оформление по умолчанию. Возвращаемое значение: Структура - Тема, Акцент, Плотность, Радиус.
Прежние имена#
Лист, написанный под прежний словарь кита, продолжает работать: старые переменные стали псевдонимами ролей.
| Прежнее имя | Роль |
|---|---|
--color-bg |
surface |
--color-surface |
surface-container-low |
--color-surface-alt |
surface-container-highest |
--color-text |
on-surface |
--color-text-muted |
on-surface-variant |
--color-accent, --color-accent-contrast |
primary, on-primary |
--color-divider |
outline-variant |
--color-success, --color-warning, --color-danger |
success, warning, error |
--color-success-bg, --color-warning-bg, --color-danger-bg |
их контейнеры |
Рампы --color-accent-100 … 900 и --color-neutral-100 … 900 тоже выводятся из затравки. Ступень 100 всегда ближе к тексту, 900 — ближе к фону, в обеих темах. В новом коде берите роли --md-sys-color-*.
Гайдлайны#
Код
HTML = "<span class=""tag"" style=""background:var(--md-sys-color-tertiary-container);"
+ "color:var(--md-sys-color-on-tertiary-container)"">бета</span>";Код
HTML = "<span class=""tag"" style=""background:#e8def8;color:#4a4458"">бета</span>";Код
HTML = "<span class=""tag"" style=""background:var(--md-sys-color-primary);"
+ "color:var(--md-sys-color-on-primary)"">primary и on-primary</span>";Код
HTML = "<span class=""tag"" style=""background:var(--md-sys-color-primary);"
+ "color:var(--md-sys-color-on-surface)"">primary и on-surface</span>";Код
HTML = Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "новое", "accent"))
+ Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "отозвана", "err"));Код
HTML = Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "новое", "err"))
+ Набор.Тег().Отрисовать(Новый Структура("Текст, Вид", "отозвана", "err"));Доступность#
- Пары ролей держат контраст WCAG AA (4,5:1) в обеих темах при любом акценте — это проверяет тест
ТемаMaterial. - Контраст гарантирован только внутри пары. Текст
on-surfaceнаprimaryилиon-primaryнаsurfaceкит не проверяет. - Цвет не должен быть единственным признаком. Тост несёт значок и текст, плашка — слово: «отозвана», а не просто красный прямоугольник.
- Тема видна браузеру: схема печатает
color-scheme, и полосы прокрутки, календарь и флажки системы рисуются в тон.