oscript-ui
oscript-ui

Цвет и тема

Цвет в ките — это роли Material Design 3: у каждой своё назначение и своя пара для текста. Всю схему, светлую и тёмную, сервер выводит из одного цвета-акцента.

Роли вместо цветов#

Роль называет назначение, а не оттенок: primary — главное действие, surface — фон, error — ошибка. Компоненты кита берут только роли, поэтому смена акцента или темы перекрашивает их все без единой правки.

Роли идут парами: заливка и текст на ней — primary и on-primary, surface и on-surface. Пара держит контраст не ниже 4,5:1 в обеих темах при любом акценте.

Каждая роль — переменная --md-sys-color-<роль>. Сервер печатает их в <head> страницы, лист кита читает только их.

Акцент

primaryon-primaryсветлая #6750a4 · тёмная #d2bbff
primary-containeron-primary-containerсветлая #e9ddff · тёмная #4c3889
secondaryon-secondaryсветлая #635a74 · тёмная #ccc2df
secondary-containeron-secondary-containerсветлая #e9defb · тёмная #4b435b
tertiaryon-tertiaryсветлая #864e5c · тёмная #f6b5c3
tertiary-containeron-tertiary-containerсветлая #ffd9e1 · тёмная #6c3644

Статусы

erroron-errorсветлая #b4271f · тёмная #ffb4a5
error-containeron-error-containerсветлая #ffdad3 · тёмная #93000b
successon-successсветлая #1a6d23 · тёмная #8bd986
success-containeron-success-containerсветлая #a6f5a1 · тёмная #005311
warningon-warningсветлая #9c4400 · тёмная #ffb68b
warning-containeron-warning-containerсветлая #ffdbc8 · тёмная #773200
infoon-infoсветлая #00639a · тёмная #9ccaff
info-containeron-info-containerсветлая #d0e4ff · тёмная #004a75

Поверхности

surfaceon-surfaceсветлая #fbf8ff · тёмная #1d1a21
surface-container-loweston-surfaceсветлая #ffffff · тёмная #151219
surface-container-lowon-surfaceсветлая #f6f2fb · тёмная #232027
surface-containeron-surfaceсветлая #f0ecf6 · тёмная #29272e
surface-container-highon-surfaceсветлая #eae7f0 · тёмная #322f36
surface-container-higheston-surfaceсветлая #e4e1ea · тёмная #3b383f
surface-varianton-surface-variantсветлая #e6e0f0 · тёмная #494552
inverse-surfaceinverse-on-surfaceсветлая #322f36 · тёмная #e4e1ea

Обводки

outlinesurfaceсветлая #7a7583 · тёмная #948e9d
outline-varianton-surfaceсветлая #cac4d3 · тёмная #4e4956

Плитки нарисованы переменными темы: переключите тему в шапке — они перекрасятся на месте. Внизу плитки — значения обеих тем для текущего акцента.

Акцент#

  • 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", "пресет")).Рендер();

Без имени поля рамка показывает один пресет — так удобно сравнить их рядом:

Пресет «Как было» в тёмной теме

Как было

Проект «Альфа»

Поверхности кита 0.8.
HTML = Кит.Образец("Как было", Кит.Карточка("Проект «Альфа»", Кит.Надпись("Поверхности кита 0.8."), Кит.Кнопка("Сохранить").Основная())) .Пресет("classic").Тема("тёмная").Рендер();

Пресет «oscript» в светлой теме

oscript

Проект «Альфа»

Цвета сайта oscript.io.
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>";
primary и on-primary
ДелайтеТекст на заливке роли — её on-парой
Код
HTML = "<span class=""tag"" style=""background:var(--md-sys-color-primary);"
	+ "color:var(--md-sys-color-on-primary)"">primary и on-primary</span>";
primary и on-surface
Не делайтеТекст на заливке — цветом другой пары: контраст не гарантирован
Код
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, и полосы прокрутки, календарь и флажки системы рисуются в тон.