/* oscript-ui — базовый лист UI-кита: правила его собственных примитивов в облике
 * Material Design 3 (m3.material.io): цвет ролями, шрифт шкалой, скругления формами,
 * тени уровнями возвышения, наведение и нажатие слоями состояний — всё токенами
 * --md-sys-*, которые печатает ТокеныТемы.
 *
 * ДВИЖЕНИЕ — ПО ОБРАЗЦУ MUI. Слой состояний, плавающая подпись поля, рамка фокуса
 * поля, раскрытие меню и окна, выезд шторки, скольжение индикатора вкладок — CSS.
 * Рябь от точки нажатия, пустота поля и тень шапки при прокрутке — скрипт кита
 * (oscript-ui.js). Без скрипта всё работает и выглядит законченным: подпись поля просто
 * стоит наверху, а ряби нет.
 *
 * САМОДОСТАТОЧЕН. Библиотека, поставленная в чужой проект, выглядит прилично без
 * единой строки CSS от потребителя: этот лист печатает КаркасСтраницы первым тегом
 * <link>, спрашивая адрес у АктивыСтатики.АдресБазовогоЛиста().
 *
 * ПЕРЕОПРЕДЕЛЕНИЕ — КАСКАДОМ. Лист инсталляции идёт вторым, поэтому её правило той же
 * специфичности побеждает без !important. Как проект перебивает правило, подменяет файл
 * и меняет палитру — раздел «Переопределение» в README.
 *
 * ХЕШ СОДЕРЖИМОГО В АДРЕСЕ. Файл лежит на диске БЕЗ версии в имени, а отдаётся по адресу
 * /static/oscript-ui-<хеш>.css, где хеш — префикс SHA-256 этого файла (считает
 * ОтпечаткиСтатики). Любая правка сама меняет адрес: «правка без сброса кэша» невозможна.
 *
 * НИ ОДНОГО ЦВЕТА. Все значения берутся из CSS-переменных, которые сервер печатает
 * инлайном в <head> (ТокеныТемы): тема, акцент, плотность, радиусы. Поэтому смена темы
 * не требует ни пересборки этого файла, ни сброса его кэша.
 *
 * Порядок разделов: сброс → слой состояний и рябь → каркас → типографика → компоненты →
 * страницы (формы, отборы, свёртки) → каркас настроек → движение.
 *
 * ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ — С ПОДЧЁРКИВАНИЕМ. --_bg, --_fg, --_state и им подобные объявляет
 * сам лист у своего компонента: это входы общих правил состояния, а не токены темы.
 * Всё остальное, что лист читает через var(), объявляет ТокеныТемы.
 */

/* --- сброс --- */

*, *::before, *::after { box-sizing: border-box; }

body {
	margin: 0;
	font: var(--type-body-large);
	letter-spacing: var(--md-sys-typescale-body-large-tracking);
	color: var(--md-sys-color-on-surface);
	background: var(--md-sys-color-surface);
	-webkit-font-smoothing: antialiased;
	-webkit-tap-highlight-color: transparent;
}

a { color: var(--md-sys-color-primary); text-decoration: none; }
a:hover { text-decoration: underline; }
/* Ссылка в тексте подчёркнута всегда: в абзаце, пункте списка, описании, лиде, подсказке
   под полем и в прозе документа её нельзя отличить от текста одним цветом — primary к
   тексту 2.67:1 в светлой теме и 1.32:1 в тёмной при норме 3:1 (WCAG 1.4.1; аудит M3, S4).
   Поднять контраст цветом не выйдет: в тёмной теме и текст, и primary светлые, а ссылка,
   тёмная настолько, чтобы отличаться от текста, перестала бы читаться на фоне. Ссылка вне
   текста — пункт навигации, кнопка, ячейка таблицы, имя карточки — отличается местом и
   формой, а не цветом: у неё есть класс или она стоит одна, и подчёркивания ей не нужно.
   :not([class]) — у ссылок-компонентов кита всегда есть класс. Проза документа (.prose)
   отдельно не названа: её текст — те же абзацы и пункты, а ссылка в ячейке таблицы README
   стоит одна, как в любой таблице. */
p a:not([class]), li a:not([class]), dd a:not([class]), blockquote a:not([class]),
figcaption a:not([class]), .card__lead a:not([class]),
.section__lead a:not([class]), .modal__lead a:not([class]), .field__hint a:not([class]),
.cmd__note a:not([class]), .toast a:not([class]) {
	text-decoration: underline;
	text-decoration-thickness: from-font;
	text-underline-offset: .2em;
}
/* Список ссылок в навигации — меню, а не текст: подчёркивание только под указателем. */
nav li a:not([class]) { text-decoration: none; }
nav li a:not([class]):hover { text-decoration: underline; }
/* Индикатор фокуса Material: обводка 3px цветом secondary с зазором. Скругление у обводки
   своё не задаётся — она идёт по форме самого элемента, и пилюля остаётся пилюлей. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible {
	outline: 3px solid var(--md-sys-color-secondary);
	outline-offset: 2px;
}

code, pre, kbd, samp { font-family: var(--md-ref-typeface-mono); }

/* --- слой состояний и рябь ---------------------------------------------------------
   Наведение, фокус и нажатие в Material — не новый цвет, а СЛОЙ: поверх заливки элемента
   ложится его же цвет содержимого с прозрачностью из токенов (8% наведение, 10% фокус
   и нажатие). Здесь слой считается одной функцией: фон = color-mix(содержимое --_state,
   заливка --_bg). Компонент объявляет только --_bg и --_fg, правила состояний у всех одни,
   и прозрачная заливка (кнопка-текст, пункт меню) получает слой тем же путём.

   Рябь — круг от точки нажатия, который расходится и гаснет, как в MUI. Её кладёт скрипт
   кита (oscript-ui.js) внутрь элемента; правила ниже — её вид. Элемент, внутри которого
   рябь, держит её в своих границах: контейнер ряби повторяет его скругление. */
.ripple {
	position: absolute;
	inset: 0;
	overflow: hidden;
	border-radius: inherit;
	pointer-events: none;
}
.ripple__wave {
	position: absolute;
	border-radius: 50%;
	background: currentColor;
	opacity: .2;
	transform: scale(0);
	animation: ui-ripple var(--md-sys-motion-duration-long1) var(--md-sys-motion-easing-standard) forwards;
}
.ripple__wave--out {
	opacity: 0;
	transition: opacity var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-standard);
}

/* --- каркас --- */

.wrap {
	max-width: var(--layout-width);
	margin: 0 auto;
	padding: 0 var(--space-5);
}

/* ⚠ Высоту этой полосы повторяет отступ выпадающей панели меню (.hubmenu:popover-open
   в медиа-блоке широкого экрана): панель висит ПОД шапкой и собрана из тех же величин.
   Меняешь отступы полосы или высоту знака — поправь расчёт и там. Линия снизу — 1px
   outline-variant в обеих темах: у верхней панели Material разделителя нет, но без него
   шапка цвета поверхности в тёмной теме сливалась с содержимым под ней.

   Верхняя панель Material: цвета поверхности, пока страница наверху, и цвета контейнера
   с тенью, когда содержимое уехало под неё. Признак прокрутки (data-scrolled) ставит
   скрипт кита; без скрипта панель остаётся цвета поверхности — разница только в тени. */
.top {
	background: var(--md-sys-color-surface);
	color: var(--md-sys-color-on-surface);
	border-bottom: 1px solid var(--md-sys-color-outline-variant);
	padding: var(--space-3) 0;
	position: sticky;
	top: 0;
	z-index: 20;
	transition: background-color var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-standard),
	            box-shadow var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-standard);
}
.top[data-scrolled] {
	background: var(--md-sys-color-surface-container);
	box-shadow: var(--md-sys-elevation-level2);
}

.top .wrap { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }

.brand {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	font: var(--type-title-large);
	color: var(--md-sys-color-on-surface);
}
.brand:hover { text-decoration: none; color: var(--md-sys-color-primary); }
/* Высота фиксирована, ширина считается по СОБСТВЕННЫМ пропорциям картинки: знак бывает
   и квадратным (поставка), и горизонтальным с нарисованным названием (загруженный).
   Квадратный от этого не меняется — 28 на 28 он и есть. Потолок ширины держит очень
   длинный логотип, чтобы тот не выдавил навигацию; object-fit не даёт потолку растянуть
   картинку — вместо искажения остаются поля. */
.brand__mark { height: 28px; width: auto; max-width: 200px; object-fit: contain; flex: none; }

.top__nav { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* Ход в шапке — кнопка-текст Material: пилюля, слой состояния на наведении. */
.top__link {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	min-height: 36px;
	padding: 0 var(--space-3);
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-on-surface-variant);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	transition: background-color var(--motion-fast) var(--motion-ease), color var(--motion-fast) var(--motion-ease);
}
.top__link:hover { --_state: var(--state-hover); color: var(--md-sys-color-on-surface); text-decoration: none; }
.top__link:active { --_state: var(--state-pressed); }

.who {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-large);
}

main.wrap { padding-top: var(--space-6); padding-bottom: var(--space-8); min-height: 60vh; }

/* Подвал — у низа окна (аудит M3 №39, дев-сборка 8): на короткой странице он висел посреди
   окна, под ним — пустой фон. Тело — столбик во всю высоту окна, основное забирает остаток,
   и подвал стоит внизу; длинная страница прокручивается, как прежде. Ширину основного и
   полос с полями (.wrap) держит width: 100% — без неё автоматические поля в столбике сжали
   бы их по содержимому. Окна, поповеры и тосты лежат в верхнем слое или закреплены — столбик
   их не двигает. */
body { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
body > main { flex: 1 0 auto; }
body > main, body > .wrap { width: 100%; }

/* --- единое меню инсталляции: шапка, бургер, панель -----------------------------------
   Навигация инсталляции живёт в ОДНОМ меню. На широком экране шапка остаётся прежней —
   знак, дороги, оформление, учётка справа, — и учётка же открывает панель; на телефоне
   полоса ходов прячется целиком, а слева появляется бургер. Открывают они одну и ту же
   панель, второй копии пунктов в разметке нет. Открытие, крест, Esc и щелчок мимо —
   работа движка по атрибутам popover/popovertarget: JS в ките нет ни строки.

   Открывашки печатаются всегда, а видны ровно там, где панель открывается: движку без
   верхнего слоя кнопка ничего не откроет, и мёртвых кнопок на экране быть не должно. */
.hubmenu__opener { display: none; }
.hubmenu__user { display: none; }
.hubmenu__close { display: none; }

/* Панель БЕЗ верхнего слоя — обычный блок в потоке, до содержимого страницы: разделы
   видны, каждая дорога достижима. Здесь же снимается раскладка, которую движок даёт
   любому popover (по центру экрана, своей рамкой на своей заливке): `inset: auto` стоит
   ПЕРЕД остальными, сокращённая запись обнулила бы их. */
.hubmenu {
	display: block;
	position: static;
	inset: auto;
	width: auto;
	max-width: none;
	height: auto;
	margin: 0 0 var(--space-5);
	padding: var(--space-3);
	border: 0;
	border-bottom: 1px solid var(--md-sys-color-outline-variant);
	border-radius: 0;
	overflow: visible;
	background: var(--md-sys-color-surface-container-low);
	color: var(--md-sys-color-on-surface-variant);
}

.hubmenu__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	margin-bottom: var(--space-4);
}
.hubmenu__brand { display: inline-flex; align-items: center; min-width: 0; }

/* Блок вошедшего — он же ход в кабинет, поэтому кликабелен целиком: пункт ящика
   навигации Material с аватаром — пилюля во всю ширину, слой состояния на наведении. */
.hubmenu__account {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: flex;
	align-items: center;
	gap: var(--space-3);
	min-height: 56px;
	padding: var(--space-2) var(--space-4);
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-on-surface);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.hubmenu__account:hover { --_state: var(--state-hover); text-decoration: none; }
.hubmenu__account:active { --_state: var(--state-pressed); }
.hubmenu__avatar {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	flex: none;
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-primary-container);
	color: var(--md-sys-color-on-primary-container);
	font: var(--type-title-medium);
}
.hubmenu__who { display: flex; flex-direction: column; min-width: 0; }
.hubmenu__login { font: var(--type-title-small); letter-spacing: var(--md-sys-typescale-title-small-tracking); overflow-wrap: anywhere; }
.hubmenu__role { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-small); }

.hubmenu__section { margin-top: var(--space-4); }
/* Разделы текущего экрана — первая группа под учёткой: на широком их прячет медиа-блок —
   там они уже стоят колонкой на самой странице. На телефоне колонки нет, и это
   единственная дорога к ним. */
.hubmenu__section--screen { display: block; }
/* Дороги инсталляции: на широком их прячет медиа-блок — там они стоят в самой шапке.
   На телефоне полоса ходов спрятана, и шторка — единственное место, где они есть. */
.hubmenu__section--roads { display: block; }
/* Рубрика внутри группы разделов — ступенью ниже имени группы (дев-сборка 9, Х4б П6):
   имя группы — title-small, рубрика — label-medium обычным регистром, приглушённо, левым
   краем — с текстом пунктов: у пунктов со значком — за значком (поле пункта, значок 24
   и зазор до подписи), без значка — с полем пункта. Одного начертания с именем группы
   рубрика «Доступ» под «Настройками аккаунта» читалась соседней группой, а не частью дома.
   Прописные — стиль M2, их здесь нет (аудит M3, S10). */
.hubmenu__rubric {
	margin: 0;
	padding: var(--space-3) var(--space-4) var(--space-1);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
.hubmenu__rubric:has(+ .button > .icon) { padding-left: calc(var(--space-4) + 24px + var(--space-3)); }
/* Строка переключателя оформления: на широком её прячет медиа-блок — там переключатель
   стоит в самой шапке, и вторая такая же строка в меню была бы дублем. На телефоне полоса
   ходов спрятана, и это единственное место, где тему можно переключить. */
.hubmenu__theme { display: block; }
/* Действия инсталляции в подвале панели — по той же причине: на телефоне полоса ходов
   спрятана, и панель единственное место, где их видно. На широком они стоят в шапке. */
.hubmenu__actions { display: flex; flex-direction: column; }
/* Заголовок группы в ящике навигации Material — title-small приглушённым цветом. */
.hubmenu__title {
	margin: 0;
	padding: var(--space-4) var(--space-4) var(--space-2);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
}

/* Пункт меню — пункт ящика навигации Material: строка во всю ширину и под палец (56px),
   пилюлей, без рамки и заливки; текущий ход — индикатор цвета secondary-container. Вид
   задаёт ящик, какой бы вариант кнопки ни пришёл: кнопка кита сама по себе ужимается
   по содержимому и выравнивается по центру. */
.hubmenu .button {
	--_bg: transparent;
	--_fg: var(--md-sys-color-on-surface-variant);
	width: 100%;
	min-height: 56px;
	padding: 0 var(--space-4);
	justify-content: flex-start;
	gap: var(--space-3);
	text-align: left;
	border-color: transparent;
	box-shadow: none;
}
.hubmenu .button[aria-current] {
	--_bg: var(--md-sys-color-secondary-container);
	--_fg: var(--md-sys-color-on-secondary-container);
}
.hubmenu .button .icon { width: 24px; height: 24px; }
/* Опасный раздел — цветом ошибки, значок тоже; текущий опасный залит error-container,
   а не secondary: индикатор не должен выдавать необратимое за обычный раздел. */
.hubmenu .button--danger { --_fg: var(--md-sys-color-error); }
.hubmenu .button--danger .icon { color: var(--md-sys-color-error); }
.hubmenu .button--danger[aria-current] {
	--_bg: var(--md-sys-color-error-container);
	--_fg: var(--md-sys-color-on-error-container);
}
.hubmenu .button--danger[aria-current] .icon { color: var(--md-sys-color-on-error-container); }

.hubmenu__foot {
	display: flex;
	flex-direction: column;
	gap: 0;
	margin-top: var(--space-3);
	padding-top: var(--space-3);
	border-top: 1px solid var(--md-sys-color-outline-variant);
}

/* Шапка страницы с единым меню — прежняя шапка: знак слева, ходы справа. Бургер стоит
   первым и виден только на телефоне, поэтому на широком экране полоса выглядит ровно
   так же, как у инсталляции без меню. */
.top--menu .wrap { gap: var(--space-3); }

/* Верхний слой знает не всякий движок, поэтому ВСЁ, что без него бессмысленно, живёт
   здесь: и открывашки, и всплывающая панель. */
@supports selector(:popover-open) {

	.hubmenu__user { display: inline-flex; }
	.hubmenu__close { display: inline-flex; }

	.hubmenu:not(:popover-open) { display: none; }

	/* Открытая панель прокручивается сама: самый длинный дом в неё не влезает ни на одной
	   ширине, а тянуть за собой страницу она не должна. */
	.hubmenu:popover-open {
		display: flex;
		flex-direction: column;
		position: fixed;
		margin: 0;
		border: 0;
		overflow-y: auto;
		overscroll-behavior: contain;
		box-shadow: var(--md-sys-elevation-level1);
	}

	/* Панель — колонка, и содержимого в ней бывает больше, чем высоты: без этого браузер
	   ужимает строки, чтобы всё влезло, и учётка с рубриками наезжают друг на друга.
	   Не ужимаем — прокручиваем. */
	.hubmenu:popover-open > * { flex: none; }

}

/* Панель широкого экрана висит под шапкой у ПРАВОГО края — там же, где стоит кнопка,
   которая её открыла. Позиция обычная, `position: fixed`: привязка к якорю (anchor())
   живёт не во всех движках, а меню, которое в одном браузере выпадает у кнопки, а в другом
   прилипает к левому верхнему углу, — хуже предсказуемого. `inset: auto` снимает раскладку
   UA-стиля (`inset: 0` вместе с `margin: auto` ставит popover по центру экрана), поэтому
   стоит ПЕРЕД top/right. Отступ сверху — высота липкой шапки плюс зазор. */
@media (min-width: 901px) {

	/* Шапка панели на широком не нужна вовсе: знак стоит в двух шагах, в самой шапке
	   страницы, а закрывают выпадающее меню щелчком мимо и клавишей Esc — так жило
	   прежнее меню учётки. Крест — привычка телефона, там он и остаётся. */
	.hubmenu__head { display: none; }

	/* Вид — меню Material: компактный дропдаун цвета surface-container на уровне
	   возвышения 2, скругление extra-small, а не мобильная шторка. */
	.hubmenu {
		padding: var(--space-2) 0;
		border: 0;
		border-radius: var(--md-sys-shape-corner-extra-small);
		background: var(--md-sys-color-surface-container);
		box-shadow: var(--elevation-ring-level2);
	}

	/* Пункт — строка меню Material (48px, без скругления, слой состояния на наведении):
	   рамка-коробка у каждого пункта дробила бы список на кнопки, а это список. */
	.hubmenu .button {
		min-height: 48px;
		width: 100%;
		padding: 0 var(--space-3);
		border-radius: 0;
		--_fg: var(--md-sys-color-on-surface);
	}
	.hubmenu .button .icon { width: 20px; height: 20px; color: var(--md-sys-color-on-surface-variant); }

	/* Рубрика — мелкая приглушённая подпись над тонким разделителем, а не заголовок:
	   в дропдауне секции разделяют, а не объявляют. */
	.hubmenu__section {
		margin-top: var(--space-2);
		padding-top: var(--space-2);
		border-top: 1px solid var(--md-sys-color-outline-variant);
	}
	.hubmenu__title {
		padding: var(--space-1) var(--space-3);
		font: var(--type-label-medium);
		letter-spacing: var(--md-sys-typescale-label-medium-tracking);
	}

	/* Учётка — такая же строка, как пункты: мелкий кружок, имя и роль под ним. Высота
	   собственная, а не нулевая: с min-height: 0 двухэтажная строка съезжала на аватар. */
	.hubmenu__account {
		min-height: 56px;
		gap: var(--space-3);
		padding: var(--space-2) var(--space-3);
		margin-top: 0;
		border-radius: 0;
	}
	.hubmenu__avatar { width: 32px; height: 32px; font: var(--type-label-large); }
	.hubmenu__who { line-height: 1.25; }

	/* Разделы текущего экрана на широком стоят колонкой на самой странице, дороги
	   и переключатель оформления — в шапке: второй раз в меню они не нужны. В выпадающем
	   меню остаются учётка, дела и выход. */
	.hubmenu__section--screen { display: none; }
	.hubmenu__section--roads { display: none; }
	.hubmenu__theme { display: none; }
	.hubmenu__actions { display: none; }

	/* Скрим под дропдауном не нужен: прежнее меню учётки страницу не затемняло. */
	.hubmenu::backdrop { background: transparent; }

	.hubmenu__foot {
		margin-top: var(--space-2);
		padding-top: var(--space-2);
		gap: 0;
	}

	@supports selector(:popover-open) {

		.hubmenu:popover-open {
			inset: auto;
			/* Отступ сверху — не число на глаз: это высота липкой шапки, собранная из тех же
			   величин, что и она сама (её вертикальные отступы, знак в 28px и линия снизу),
			   плюс зазор. Меняется плотность — панель едет вместе с шапкой. */
			top: calc(var(--space-3) * 2 + 28px + 1px + var(--space-5));
			/* Отступ справа считается от ПОЛОСЫ содержимого, а не от края экрана: кнопка,
			   которая открыла панель, стоит у правого края .wrap, а полоса на широком
			   мониторе центрирована и уже экрана. Проценты, а не 100vw: у фиксированного
			   элемента процент берётся от области просмотра БЕЗ полосы прокрутки, и панель
			   не съезжает на её половину. Экран уже полосы — остаётся прежний отступ. */
			right: max(var(--space-5), calc(50% - var(--layout-width) / 2 + var(--space-5)));
			width: min(92vw, 280px);
			max-height: min(80vh, 640px);
			border: 0;
			border-radius: var(--md-sys-shape-corner-extra-small);
			box-shadow: var(--elevation-ring-level2);
		}

	}

}

.bottom {
	border-top: 1px solid var(--md-sys-color-outline-variant);
	color: var(--md-sys-color-on-surface-variant);
	padding: var(--space-5) 0;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	margin-top: var(--space-8);
}

/* --- типографика --- */

/* Заголовки — ступени шкалы Material: h1 — headline-medium, h2 — title-large,
   h3 — title-medium. Подчёркивания у h2 нет: в Material раздел отделяет пространство
   и поверхность, а не линия под заголовком. */
h1, h2, h3, h4 { font-family: var(--md-ref-typeface-brand); font-weight: 400; }
h1 { font: var(--type-headline-medium); margin: 0 0 var(--space-3); }
h2 { font: var(--type-title-large); margin: var(--space-6) 0 var(--space-3); }
h3 {
	font: var(--type-title-medium);
	letter-spacing: var(--md-sys-typescale-title-medium-tracking);
	margin: var(--space-5) 0 var(--space-2);
}
.muted { color: var(--md-sys-color-on-surface-variant); }

/* --- компонент: иконка --- */

.icon {
	width: 1em;
	height: 1em;
	flex: none;
	vertical-align: -0.125em;
}

/* --- компонент: карточка --- */

/* Карточка Material, приподнятая (elevated): поверхность --surface-card (surface-container-low,
   в тёмной теме на тон выше), кольцо 1px outline-variant поверх тени уровня 1 — контур
   виден и там, где тень на тёмном фоне пропадает; скругление large (16px) — у самой крупной
   поверхности экрана самый мягкий угол. */
.card {
	background: var(--surface-card);
	color: var(--md-sys-color-on-surface);
	border-radius: var(--md-sys-shape-corner-large);
	box-shadow: var(--elevation-ring-level1);
	padding: var(--space-5);
	margin-bottom: var(--space-4);
}
.card__title {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	margin: 0 0 var(--space-2);
	font: var(--type-title-large);
	border: 0;
	padding: 0;
}
.card__title .icon { color: var(--md-sys-color-primary); }
/* Заголовок-ссылка (Адрес) со значком (аудит M3 №16, дев-сборка 8): значок и подпись — внутри
   ссылки, и зазор заголовка до них не доставал — «⬡Склад» слипалось. Ссылка — строка флекса
   с тем же зазором. */
.card__title > a, .section__title > a { display: inline-flex; align-items: center; gap: var(--space-2); }
.card__lead {
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	margin: 0 0 var(--space-4);
}
/* Ритм внутри карточки — та же обязанность, что у .section__body. Тело карточки держит
   произвольное доверенное содержимое, в том числе поля кита, и раньше ритм давали
   маргины самих полей: в документации кита восемь полей подряд в .card__body слиплись
   в ноль пикселей. Маргин, а не flex+gap — по той же причине, что и у .section__body:
   в потоке маргины детей схлопываются, а не складываются с зазором.

   ОХВАТ ПРАВИЛА ПРОВЕРЕН ЧИСЛАМИ.
   Правило кладёт margin-top ВСЕМ не-первым детям тела карточки, поэтому вопрос «не
   поехала ли вёрстка» законный. Шаг сетки — 4dp Material: 4/8/12/16/20/32
   (ТокеныТемы.ШагСетки, плотность 1; пятая ступень плотнее спецификации), то есть
   --space-4 = 16px при кегле body-large 15px.
   Считаем по факту, помня две вещи: правило трогает только ВЕРХНИЙ маргин, а маргины
   соседей СХЛОПЫВАЮТСЯ (зазор = max, а не сумма):
     * поля и тумблеры (собственных маргинов у них нет): 0 -> 16px — собственно
       починенный дефект;
     * <p> + <p> без класса: 16px. Правило переопределяет margin-top второго абзаца,
       а margin-bottom первого — браузерный 1em = 15px — схлопывается с ним:
       max(15, 16) = 16px;
     * pre.code (margin-bottom space-3 = 12px) перед <p>: max(12, 16) = 16px;
     * первый и последний ребёнок: внешние маргины сняты, чтобы не удваивать padding
       карточки (space-5 = 20px).
   ЗАЗОР ПОД ЗАГОЛОВКОМ СТАНОВИТСЯ ШИРЕ — и это единственное место, где правило не
   только нормализует. У заголовков собственный нижний маргин МЕНЬШЕ 16px, а верхний
   больше, так что правило подтягивает их сверху и раздвигает снизу:
     * h3 (margin: space-5 0 space-2 = 20/8) — реальный непосредственный ребёнок
       .card__body (заголовки блоков страницы): сверху 20 -> 16,
       снизу max(8, 16) = 16 (+8). Заголовок становится равноудалён от соседей
       сверху и снизу — практически это ровнее, а не поломка;
     * h1 (margin-bottom space-3 = 12) и h2 (space-6 0 space-3 = 32/12) — так же:
       снизу 12 -> 16.
   АТОМАРНОМУ ИНЛАЙНУ (inline-block, inline-flex — такова .tag) ЭТОТ МАРГИН УХОДИТ
   В ВЫСОТУ СТРОЧНОГО БОКСА и опускает всю строку вместе с собой; настоящий inline
   вертикальный маргин игнорирует. Поэтому ряд инлайновых элементов печатается ОДНИМ
   ребёнком тела (.tag-row), а не несколькими. Одиночный атомарный инлайн после
   заголовка — законный случай (образец документации): там маргин отбивает образец от подписи
   своего блока.
   Сужать правило нельзя: «> * + *» владеет интервалом между ЛЮБЫМИ соседями тела,
   а «> .field + .field» не покрывает даже пару .field + .toggle — стоит поставить
   тумблер между двумя полями, и зазор в этом месте пропадёт. */
.card__body > * + * { margin-top: var(--space-4); }
.card__body > :first-child { margin-top: 0; }
.card__body > :last-child { margin-bottom: 0; }
.card__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin-top: var(--space-4);
	padding-top: var(--space-4);
	border-top: 1px solid var(--md-sys-color-outline-variant);
}
/* Карточка В ЯЧЕЙКЕ СЕТКИ — колонка на всю высоту ряда; лекарство то же, что у .objcard.
   Ячейка сетки высоту ряда получает (align-items: stretch), а карточка внутри неё росла
   только под своё содержимое: в ряду карточек описание в три строки опускало разделитель
   и кнопку на 23 px ниже, чем у соседей, и ряд разъезжался. height: 100% выравнивает
   коробки, margin-top: auto ставит полосу действий на одну линию у всех.
   Собственный нижний маргин карточки в сетке лишний вдвойне: интервал между рядами
   держит gap сетки, а внутри ячейки он не давал карточке дойти до низа.
   Зазор над разделителем не теряется: маргины флекс-элементов не схлопываются, и
   нижний маргин .card__lead (space-4) даёт ровно тот же отступ, что схлопывание давало
   в потоке. Вне сетки правило молчит — одиночной карточке не во что тянуться. */
.grid__cell > .card {
	display: flex;
	flex-direction: column;
	height: 100%;
	margin-bottom: 0;
}
.grid__cell > .card > .card__actions { margin-top: auto; }

/* --- компонент: поле ---

   ИНТЕРВАЛ МЕЖДУ ПОЛЯМИ ДАЁТ КОНТЕЙНЕР, А НЕ ПОЛЕ.
   Было: у .field и .toggle стоял margin-bottom: space-4, и он СКЛАДЫВАЛСЯ с gap
   контейнера — формы дышали двойным ритмом 16+12 px, причём величина зависела
   от того, в какой контейнер поле попало. Заодно это ломало выравнивание в .filters:
   align-items: flex-end равняет МАРГИН-БОКСЫ, поэтому нижний отступ поля опускал его
   контрол на 16 px выше базовой линии кнопки рядом. Один владелец интервала
   (контейнер) лечит и то, и то.

   ПОЛНОТА ДЕРЖИТСЯ НЕ СПИСКОМ.
   Первая редакция этого правила называла три контейнера (.form-stack, .filters,
   .from-config) и этим создала впечатление полноты: тело секции настроек
   (.section__body), где лежит большинство полей приложения, в список не попало
   и осталось без ритма — три поля подряд слиплись в ноль пикселей. Обязанность
   формулируется поэтому не перечнем, а правилом: КАЖДЫЙ элемент, внутри которого
   печатается больше одного .field/.toggle, обязан задать интервал сам. Новый
   контейнер подпадает под правило сразу, без правки этого комментария.

   Контейнер, в котором поле ОДНО по конструкции (.filters, .from-config), интервалом
   между полями не владеет, но gap ему всё равно нужен: он разводит поле с кнопкой
   отбора и с подписью источника. Задавая gap, не забудь про display: flex — без
   flex-контекста gap инертен, и правило выглядит рабочим, ничего не делая. */

/* ТЕКСТОВОЕ ПОЛЕ MATERIAL, ЗАЛИТОЕ (filled), но в рамке. Контейнер
   surface-container-highest, скругление small (8px) по всем углам и рамка 1px
   цветом outline — роль Material для «важной границы, такой как рамка текстового поля»:
   контраст рамки с заливкой поля и с карточкой — не меньше 3:1 в обеих темах при любом
   акценте (WCAG 1.4.11). Рамка outline-variant давала 1.2–1.8:1, и в светлой теме поле на
   карточке почти пропадало (аудит M3, S3). Подпись лежит ВНУТРИ контейнера. На фокусе рамка — 2px primary:
   вторым пикселем служит кольцо внутрь (inset box-shadow), поэтому содержимое не
   сдвигается. Плоский низ и линия-индикатор залитого поля M3 сняты: рядом с карточками
   в 16px и пилюлями-плашками поле с прямыми нижними углами читалось «квадратно», а линия
   без рамки на тёмной теме терялась.
   Подпись плавает: у пустого поля без фокуса она стоит на месте значения во весь кегль,
   у заполненного или в фокусе — уменьшается и поднимается к верхнему краю. Пустоту
   отмечает скрипт кита атрибутом data-empty; без скрипта атрибута нет, и подпись
   ВСЕГДА стоит наверху — законченный вид, а не сломанный. Подпись первой в разметке,
   контрол вторым, поэтому верх .field и есть верх контейнера: подпись встаёт по нему
   абсолютно. Пояснение и ошибка — вспомогательный текст ПОД контейнером, с отступом
   16px по Material. */
.field { position: relative; display: flex; flex-direction: column; gap: var(--space-1); }
.field__label {
	position: absolute;
	top: 11px;
	left: 17px; /* рамка 1px и отступ 16px контрола: подпись стоит ровно над значением */
	z-index: 1;
	max-width: calc(100% - 32px);
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	font: var(--type-body-large);
	letter-spacing: var(--md-sys-typescale-body-large-tracking);
	color: var(--md-sys-color-on-surface-variant);
	pointer-events: none;
	/* Поле плотное, 44 px: подпись наверху — 12 px (4/5 от 15) в трёх пикселях от верхнего
	   края, значение под ней. Опущенная подпись стоит по середине поля, на месте значения. */
	transform: translateY(-8px) scale(.8);
	transform-origin: left top;
	transition: transform var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
	            color var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
	            max-width var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
/* Подпись наверху — уменьшенная до 4/5, поэтому ей и места в строке на 5/4 больше. */
.field:not([data-empty]) > .field__label,
.field:focus-within > .field__label { max-width: calc((100% - 32px) * 5 / 4); }
.field[data-empty]:not(:focus-within) > .field__label { transform: none; }
.field:focus-within > .field__label { color: var(--md-sys-color-primary); }
/* Плейсхолдер прячется, только пока на его месте стоит опущенная подпись: у поля без
   подписи он единственная подсказка и виден всегда. */
.field[data-empty]:not(:focus-within):has(> .field__label) > .field__control::placeholder { color: transparent; }
.field__control {
	--_bg: var(--md-sys-color-surface-container-highest);
	--_state: 0%;
	--_line: var(--md-sys-color-primary);
	width: 100%;
	min-height: 44px;
	padding: 17px 16px 3px;
	font: var(--type-body-large);
	letter-spacing: var(--md-sys-typescale-body-large-tracking);
	color: var(--md-sys-color-on-surface);
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--_state), var(--_bg));
	border: 1px solid var(--md-sys-color-outline);
	border-radius: var(--md-sys-shape-corner-small);
	outline: none;
	caret-color: var(--md-sys-color-primary);
	transition: background-color var(--motion-fast) var(--motion-ease),
	            box-shadow var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease);
}
.field__control:hover:not([disabled]) { --_state: var(--state-hover); border-color: var(--md-sys-color-on-surface-variant); }
.field__control:focus, .field__control:focus-visible {
	border-color: var(--_line);
	box-shadow: inset 0 0 0 1px var(--_line);
	outline: none;
}
/* Поле без подписи: значению наверху делать нечего, оно садится посередине. :has() обёрнут
   в :is(): движок без :has() выбросил бы и всё остальное правило. */
.field:is(:not(:has(> .field__label))) > .field__control { padding-top: 10px; padding-bottom: 10px; }
/* Выпадающий список — свой шеврон справа: системная стрелка снята, и место под ней тоже. */
select.field__control { -webkit-appearance: none; appearance: none; padding-right: 48px; cursor: pointer; }
.field:has(> select.field__control)::after {
	content: "";
	position: absolute;
	top: 17px;
	right: 20px;
	width: 7px;
	height: 7px;
	border-right: 2px solid var(--md-sys-color-on-surface-variant);
	border-bottom: 2px solid var(--md-sys-color-on-surface-variant);
	transform: rotate(45deg);
	pointer-events: none;
}
.field__control[disabled] {
	--_bg: color-mix(in srgb, var(--md-sys-color-on-surface) 4%, transparent);
	color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
	border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-container), transparent);
	cursor: not-allowed;
}
.field:has(> .field__control[disabled]) > .field__label {
	color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
}
.field__control[readonly] { color: var(--md-sys-color-on-surface-variant); cursor: not-allowed; }
.field__hint {
	padding: 0 var(--space-4);
	font: var(--type-body-small);
	letter-spacing: var(--md-sys-typescale-body-small-tracking);
	color: var(--md-sys-color-on-surface-variant);
}
/* Источник значения — справка о значении, а не о поле: мелкая приглушённая надпись справа
   в строке подсказки. Строка с источником — flex: подсказка слева, источник прижат вправо
   и переносится внутри себя (ключ конфига бывает длиннее строки); на телефоне он уходит
   под подсказку медиа-блоком ниже. */
.field__hint--origin, .toggle__hint--origin {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	column-gap: var(--space-3);
}
.field__origin {
	margin-left: auto;
	min-width: 0;
	text-align: end;
	overflow-wrap: anywhere;
	font: var(--type-label-small);
	letter-spacing: var(--md-sys-typescale-label-small-tracking);
	/* цвет вспомогательного текста, а не outline: мелкому кеглю нужен полный контраст */
	color: var(--md-sys-color-on-surface-variant);
}
@media (max-width: 640px) {
	.field__hint--origin, .toggle__hint--origin { flex-direction: column; align-items: flex-start; }
	.field__origin { margin-left: 0; text-align: start; }
}
.field__error {
	padding: 0 var(--space-4);
	font: var(--type-body-small);
	letter-spacing: var(--md-sys-typescale-body-small-tracking);
	color: var(--md-sys-color-error);
}
.field--error .field__control { --_line: var(--md-sys-color-error); border-color: var(--md-sys-color-error); caret-color: var(--md-sys-color-error); }
.field--error > .field__label,
.field--error:focus-within > .field__label { color: var(--md-sys-color-error); }
/* Флажок — сетка в две колонки: галка слева, подпись справа, пояснение и отказ — под
   подписью во второй колонке. Строкой (flex-row) пояснение вставало справа от подписи
   и сжимало её в столбик из слов. */
.field--check {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	align-items: start;
	column-gap: var(--space-2);
	row-gap: 2px;
}
/* Подпись флажка — обычный текст строки, а не плавающая подпись поля: правила подписи
   выше здесь сняты целиком. */
.field--check .field__check { margin: 3px 0 0; }
.field--check > .field__label {
	position: static;
	max-width: none;
	white-space: normal;
	transform: none;
	pointer-events: auto;
	color: var(--md-sys-color-on-surface);
}
.field--check .field__hint,
.field--check .field__error { grid-column: 2; padding: 0; }
/* ФЛАЖОК MATERIAL. Системная галка снята, коробка 18px с рамкой 2px рисуется заново;
   отмеченная заливается primary, а сама галочка — два отрезка градиентами цветом
   on-primary (--_mark): рисунок картинкой не взял бы цвет темы, маска срезала бы обводку
   фокуса. «Частично» — короткая черта тем же цветом. Тот же флажок у дерева галок. */
.field__check, .checktree__box {
	--_mark: var(--md-sys-color-on-primary);
	-webkit-appearance: none;
	appearance: none;
	width: 18px;
	height: 18px;
	margin: 0;
	flex: none;
	border: 2px solid var(--md-sys-color-on-surface-variant);
	border-radius: calc(var(--md-sys-shape-corner-extra-small) / 2);
	background: transparent;
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease);
}
.field__check:hover, .checktree__box:hover { border-color: var(--md-sys-color-on-surface); }
.field__check:checked, .checktree__box:checked {
	border-color: var(--md-sys-color-primary);
	background: linear-gradient(45deg, transparent calc(50% - 1px), var(--_mark) 0 calc(50% + 1px), transparent 0)
			1px 5px / 5px 5px no-repeat,
		linear-gradient(-45deg, transparent calc(50% - 1px), var(--_mark) 0 calc(50% + 1px), transparent 0)
			4px 1px / 9px 9px no-repeat,
		var(--md-sys-color-primary);
}
.field--error .field__check:not(:checked) { border-color: var(--md-sys-color-error); }
.field__check[disabled], .checktree__box[disabled] {
	border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
	cursor: not-allowed;
}
.field__check[disabled]:checked, .checktree__box[disabled]:checked {
	--_mark: var(--md-sys-color-surface);
	border-color: transparent;
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
}
textarea.field__control { min-height: calc(var(--space-8) * 1.5); resize: vertical; }

/* Группа полей <fieldset><legend> — тоже КОНТЕЙНЕР полей, а значит владелец интервала:
   без этого правила три флажка подряд внутри группы слипаются в ноль пикселей.
   Правило висит на ТЕГЕ, а не на классе: fieldset группирует поля семантикой,
   и требовать от разметки лишний класс не за что.
   Маргин, а не flex+gap: display:flex у fieldset выбивает <legend> из его собственного
   способа отрисовки (легенда становится обычным flex-элементом и уезжает из рамки). */
fieldset > * + * { margin-top: var(--space-2); }

/* --- поля список и цвет (выпуск 0.12, шаги 14–15) ------------------------------------
   СПИСОК. Без скрипта .chips-in — просто обёртка, и поле выглядит обычным текстовым:
   подпись лежит поверх ввода, как у любого поля (позиционирована от .field), плашки
   спрятаны атрибутом hidden. Скрипт ставит data-chips-live, и рамкой поля становится
   .chips-in: та же заливка и линия снизу, что у контрола, подпись наверху, плашки встают
   перед строкой ввода (order), ввод берёт остаток строки. В разметке ввод идёт ПЕРВЫМ:
   подпись <label> связана с ним вложением, а крестики-кнопки лежат вне подписи.
   Подпись здесь не прямой ребёнок .field, поэтому её состояния (фокус, отказ,
   блокировка) повторены для вложенной. */
.field--chips:focus-within .field__label { color: var(--md-sys-color-primary); }
.field--chips.field--error .field__label { color: var(--md-sys-color-error); }
.field--chips:has(.chips-in__input[disabled]) .field__label {
	color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
}
.chips-in__list[hidden] { display: none; }
.field--chips[data-chips-live] .chips-in {
	--_bg: var(--md-sys-color-surface-container-highest);
	--_state: 0%;
	--_line: var(--md-sys-color-primary);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-1) var(--space-2);
	min-height: 44px;
	padding: 17px 12px 3px 16px;
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--_state), var(--_bg));
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-small);
	cursor: text;
	transition: background-color var(--motion-fast) var(--motion-ease),
	            box-shadow var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease);
}
.field--chips[data-chips-live] .chips-in:hover { --_state: var(--state-hover); border-color: var(--md-sys-color-on-surface-variant); }
.field--chips[data-chips-live] .chips-in:focus-within { border-color: var(--_line); box-shadow: inset 0 0 0 1px var(--_line); }
.field--chips.field--error[data-chips-live] .chips-in {
	--_line: var(--md-sys-color-error);
	border-color: var(--md-sys-color-error);
}
.field--chips[data-chips-live] .chips-in__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1);
	order: -1;
	min-width: 0;
	max-width: 100%;
}
.field--chips[data-chips-live] .chips-in__list:empty { display: none; }
.field--chips[data-chips-live] .chips-in__entry { display: flex; flex: 1 1 8rem; min-width: 6rem; }
.field--chips[data-chips-live] .chips-in__input {
	min-height: 22px;
	padding: 0;
	background: none;
	border: 0;
	border-radius: 0;
}
/* Плашка значения — входной чип Material, уменьшенный под рамку поля: высота 28px,
   скругление small, подпись label-large, крестик 24px со своим слоем состояния. Заливка
   secondary-container отличает значение от строки ввода в любой теме. */
.chips-in__chip {
	display: inline-flex;
	align-items: center;
	gap: 2px;
	min-height: 28px;
	max-width: 100%;
	padding: 0 2px 0 var(--space-3);
	border-radius: var(--md-sys-shape-corner-small);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	color: var(--md-sys-color-on-secondary-container);
	background: var(--md-sys-color-secondary-container);
	cursor: default;
}
.chips-in__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chips-in__remove {
	--_state: 0%;
	display: inline-grid;
	place-items: center;
	flex: none;
	width: 24px;
	height: 24px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	color: inherit;
	background: color-mix(in srgb, currentColor var(--_state), transparent);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.chips-in__remove:hover { --_state: var(--state-hover); }
.chips-in__remove:active { --_state: var(--state-pressed); }
.chips-in__remove .icon { width: 18px; height: 18px; }
.chips-in__remove[disabled] { display: none; }

/* ЦВЕТ. Группа <fieldset>: подпись — <legend> обычным текстом над рядом (плавающей
   подписи у группы нет), ряд образцов — радиокнопки-кружки, «свой» — кружок со скрытым
   поверх него системным выбором цвета, под рядом — код и имя выбранного. Без скрипта ряд
   спрятан, и остаётся поле кода. Цвет кружка — пользовательское свойство --_swatch из
   разметки (только проверенный код #rrggbb). Отмеченный кружок обведён двойным кольцом:
   зазор цветом поверхности и кольцо on-surface — видно и на светлом, и на тёмном
   образце. Свой цвет отмечен, когда кружок «свой» показывает код, а ни одна
   радиокнопка не отмечена. */
.field--color { display: block; min-width: 0; margin: 0; padding: 0; border: 0; }
.field.field--color > .field__label {
	position: static;
	max-width: none;
	margin-bottom: var(--space-2);
	padding: 0;
	white-space: normal;
	transform: none;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.swatches { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.swatches[hidden] { display: none; }
.swatches__item {
	position: relative;
	display: inline-grid;
	place-items: center;
	width: 44px;
	height: 44px;
	border-radius: 50%;
	cursor: pointer;
}
.swatches__radio, .swatches__dot {
	/* цвет образца: разметка перекрывает его своим style, у кружка без цвета — заливка */
	--_swatch: var(--md-sys-color-surface-container-highest);
	-webkit-appearance: none;
	appearance: none;
	display: block;
	width: 32px;
	height: 32px;
	margin: 0;
	border-radius: 50%;
	background: var(--_swatch);
	box-shadow: inset 0 0 0 1px var(--md-sys-color-outline-variant);
	cursor: pointer;
	transition: box-shadow var(--motion-fast) var(--motion-ease);
}
.swatches__radio:checked,
.field--color:not(:has(.swatches__radio:checked)) .swatches__dot[style] {
	box-shadow: 0 0 0 2px var(--md-sys-color-surface), 0 0 0 4px var(--md-sys-color-on-surface);
}
/* «По умолчанию» — пустой кружок, перечёркнутый наискось: своего цвета у него нет. */
.swatches__item--default .swatches__radio {
	background: linear-gradient(to top right, transparent calc(50% - 1px),
			var(--md-sys-color-outline) 0 calc(50% + 1px), transparent 0),
		var(--md-sys-color-surface-container-highest);
}
/* «Свой» без своего цвета — кружок с плюсом из двух отрезков цветом on-surface-variant. */
.swatches__item--own .swatches__dot:not([style]) {
	background: linear-gradient(var(--md-sys-color-on-surface-variant), var(--md-sys-color-on-surface-variant))
			center / 14px 2px no-repeat,
		linear-gradient(var(--md-sys-color-on-surface-variant), var(--md-sys-color-on-surface-variant))
			center / 2px 14px no-repeat,
		var(--md-sys-color-surface-container-highest);
}
/* Системный выбор цвета лежит поверх кружка невидимым: нажатие открывает палитру движка,
   а обводку фокуса за невидимое поле рисует кружок. */
.swatches__picker {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	padding: 0;
	border: 0;
	opacity: 0;
	cursor: pointer;
}
.swatches__item--own:has(.swatches__picker:focus-visible) .swatches__dot {
	outline: 3px solid var(--md-sys-color-secondary);
	outline-offset: 2px;
}
.swatches__item:has(:disabled) { cursor: not-allowed; }
.swatches__item:has(:disabled) > * {
	cursor: not-allowed;
	filter: grayscale(1);
	opacity: .38;
}
.swatches__current { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.field--color .swatches__code {
	flex: 0 1 10rem;
	width: 10rem;
	min-width: 8rem;
	padding-top: 10px;
	padding-bottom: 10px;
	font-family: var(--md-ref-typeface-mono);
}
.swatches__name {
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	color: var(--md-sys-color-on-surface-variant);
}

/* ЗНАЧОК (дев-сборка 9). Группа <fieldset>, как у цвета: подпись — <legend> над сеткой,
   сетка — ячейки 40×40 с радиокнопкой поверх значка 24 px, разделы «Глифы» и «Бренды» —
   мелкой подписью label-medium. Выбранная ячейка — по правилам M3 выбранного: заливка
   secondary-container и кольцо primary 2 px внутрь; наведение — слой состояния; фокус —
   кольцо secondary снаружи, как у любого элемента. Радиокнопка невидима и растянута
   поверх ячейки: щелчок и клавиатура (стрелки ходят по группе) — её. Строка поиска —
   поле со значком-лупой в рамке справа; без скрипта она спрятана (искать нечем), сетка
   целиком. Прячет найденное скрипт атрибутом hidden. */
.field--icons { display: block; min-width: 0; margin: 0; padding: 0; border: 0; }
.field.field--icons > .field__label {
	position: static;
	max-width: none;
	margin-bottom: var(--space-2);
	padding: 0;
	white-space: normal;
	transform: none;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.iconpicker__search { position: relative; max-width: 20rem; }
.iconpicker__search[hidden] { display: none; }
.iconpicker__search > .field > .field__control { padding-right: 40px; }
.iconpicker__glass {
	position: absolute;
	top: 10px;
	right: var(--space-3);
	width: 24px;
	height: 24px;
	color: var(--md-sys-color-on-surface-variant);
	pointer-events: none;
}
.iconpicker__group[hidden], .iconpicker__item[hidden] { display: none; }
.iconpicker__title {
	margin: 0 0 var(--space-1);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
.iconpicker__grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, 40px);
	gap: var(--space-1);
}
/* «без значка» — одна ячейка своей строкой над разделами; со своим значком пустого варианта
   (дев-сборка 10) — и видимая подпись справа от ячейки («По издателю») */
.iconpicker__grid--none { grid-template-columns: 40px auto; align-items: center; justify-content: start; }
.iconpicker__caption {
	padding-left: var(--space-2);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
/* свой значок пустого варианта — приглушённый: это «подберётся само», а не выбранный значок */
.iconpicker__item .iconpicker__default { opacity: .6; }
.iconpicker__item:has(> .iconpicker__radio:checked) .iconpicker__default { opacity: 1; }
.iconpicker__item {
	--_state: 0%;
	--_bg: transparent;
	position: relative;
	display: inline-grid;
	place-items: center;
	width: 40px;
	height: 40px;
	border-radius: var(--md-sys-shape-corner-small);
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--_state), var(--_bg));
	color: var(--md-sys-color-on-surface-variant);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease), box-shadow var(--motion-fast) var(--motion-ease);
}
.iconpicker__item:hover { --_state: var(--state-hover); }
.iconpicker__item .icon { width: 24px; height: 24px; }
.iconpicker__radio {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	opacity: 0;
	cursor: pointer;
}
.iconpicker__item:has(> .iconpicker__radio:checked) {
	--_bg: var(--md-sys-color-secondary-container);
	color: var(--md-sys-color-on-secondary-container);
	box-shadow: inset 0 0 0 2px var(--md-sys-color-primary);
}
.iconpicker__item:has(> .iconpicker__radio:focus-visible) {
	outline: 3px solid var(--md-sys-color-secondary);
	outline-offset: 2px;
}
.iconpicker__item:has(> .iconpicker__radio:disabled) { cursor: not-allowed; opacity: .38; }
/* «без значка» — перечёркнутый круг: пустая ячейка выглядела бы несработавшей загрузкой */
.iconpicker__nothing {
	width: 20px;
	height: 20px;
	border-radius: var(--md-sys-shape-corner-full);
	box-shadow: inset 0 0 0 2px currentColor;
	background: linear-gradient(45deg, transparent calc(50% - 1px), currentColor 0 calc(50% + 1px), transparent 0);
}

/* --- компонент: тумблер (поле из двух состояний) ---
   Без JS: всё держится на :checked у настоящего <input type="checkbox">. Флажок НЕ
   скрыт через display:none — иначе он выпал бы из порядка фокуса и тумблер перестал
   бы переключаться пробелом; он прозрачен и растянут поверх дорожки. */

/* интервал снизу не ставим — его даёт контейнер (см. комментарий у .field) */
.toggle { display: flex; flex-direction: column; gap: var(--space-1); }
.toggle__box { display: flex; align-items: center; gap: var(--space-3); cursor: pointer; }
/* Переключатель Material: дорожка 52×32 пилюлей, бегунок 16px у выключенного и 24px
   у включённого, при нажатии — 28px. Бегунок и растёт, и едет — одним переходом размеров
   и места: так он и движется в Material. Вокруг бегунка на наведении — круг слоя
   состояния (тень без размытия цветом содержимого). */
.toggle__input {
	position: absolute;
	width: 52px; height: 32px;
	margin: 0;
	opacity: 0;
	cursor: pointer;
}
.toggle__track {
	position: relative;
	flex: 0 0 auto;
	width: 52px; height: 32px;
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-surface-container-highest);
	border: 2px solid var(--md-sys-color-outline);
	transition: background-color var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease);
}
.toggle__thumb {
	position: absolute;
	top: 6px; left: 6px;
	width: 16px; height: 16px;
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-outline);
	box-shadow: 0 0 0 0 transparent;
	transition: top var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-emphasized-decelerate),
	            left var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-emphasized-decelerate),
	            width var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-emphasized-decelerate),
	            height var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-emphasized-decelerate),
	            background-color var(--motion-fast) var(--motion-ease),
	            box-shadow var(--motion-fast) var(--motion-ease);
}
.toggle__box:hover .toggle__thumb { background: var(--md-sys-color-on-surface-variant); box-shadow: 0 0 0 12px color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-hover), transparent); }
.toggle__input:active + .toggle__track .toggle__thumb { top: 0; left: 0; width: 28px; height: 28px; }
.toggle__input:checked + .toggle__track { background: var(--md-sys-color-primary); border-color: var(--md-sys-color-primary); }
.toggle__input:checked + .toggle__track .toggle__thumb { top: 2px; left: 22px; width: 24px; height: 24px; background: var(--md-sys-color-on-primary); }
.toggle__box:hover .toggle__input:checked + .toggle__track .toggle__thumb {
	background: var(--md-sys-color-primary-container);
	box-shadow: 0 0 0 10px color-mix(in srgb, var(--md-sys-color-primary) var(--state-hover), transparent);
}
.toggle__input:checked:active + .toggle__track .toggle__thumb { top: 0; left: 20px; width: 28px; height: 28px; }
.toggle__input:focus-visible + .toggle__track { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 2px; }
.toggle__input[disabled] { cursor: not-allowed; }
.toggle__input[disabled] + .toggle__track { opacity: .38; }
.toggle__input[disabled] ~ .toggle__text { color: var(--md-sys-color-on-surface-variant); }
.toggle__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.toggle__label { color: var(--md-sys-color-on-surface); }
.toggle__state { font: var(--type-body-small); color: var(--md-sys-color-on-surface-variant); }
.toggle__hint { font: var(--type-body-small); color: var(--md-sys-color-on-surface-variant); }

/* --- компонент: проводник (шаги с отметкой пройденного и текущего) --- */

.guide__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
/* Шаги — карточки Material, выделенные обводкой (outlined): пройденный узнаётся значком
   цвета primary, текущий — обводкой primary и лёгкой заливкой его же цветом. */
.guide__step {
	display: flex;
	gap: var(--space-3);
	padding: var(--space-4);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	background: var(--md-sys-color-surface-container-low);
	transition: border-color var(--motion-fast) var(--motion-ease), background-color var(--motion-fast) var(--motion-ease);
}
.guide__step--done { border-color: var(--md-sys-color-outline-variant); }
.guide__step--current {
	border-color: var(--md-sys-color-primary);
	background: color-mix(in srgb, var(--md-sys-color-primary) var(--state-hover), var(--md-sys-color-surface-container-low));
}
.guide__mark { flex: 0 0 auto; color: var(--md-sys-color-on-surface-variant); }
.guide__step--done .guide__mark { color: var(--md-sys-color-primary); }
.guide__body { min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.guide__title { font: var(--type-title-medium); letter-spacing: var(--md-sys-typescale-title-medium-tracking); }
.guide__hint { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); }
.guide__progress { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); margin: 0 0 var(--space-3); }

pre.code {
	margin: 0 0 var(--space-3);
	padding: var(--space-3) var(--space-4);
	overflow-x: auto;
	background: var(--md-sys-color-surface-container-highest);
	color: var(--md-sys-color-on-surface);
	border: 0;
	border-radius: var(--md-sys-shape-corner-small);
	font-size: var(--md-sys-typescale-body-medium-size);
	line-height: var(--md-sys-typescale-body-medium-line-height);
}

/* --- компонент: поле из конфига --- */

.from-config {
	/* контейнер сам держит интервал между полем, источником и ENV-сниппетом:
	   раньше это делал margin-bottom у вложенного .field, и величина отступа зависела
	   от порядка правил, а не от того, что внутри */
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	border: 1px dashed var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	padding: var(--space-4);
	margin-bottom: var(--space-4);
	background: var(--md-sys-color-surface-container);
}
.from-config__source {
	display: inline-flex;
	/* в flex-колонке .from-config элемент растянулся бы на всю ширину: подпись
	   источника должна занимать ровно свой текст */
	align-self: flex-start;
	align-items: center;
	gap: var(--space-1);
	font: var(--type-body-medium);
	color: var(--md-sys-color-on-surface-variant);
}
.from-config__env {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	padding: var(--space-2) var(--space-3);
	background: var(--md-sys-color-surface-container-highest);
	border-radius: var(--md-sys-shape-corner-small);
	overflow-x: auto;
}
.from-config__env code { font: var(--type-body-medium); white-space: nowrap; }

/* --- источник значения настройки: дефолт / база / конфигурация --- */

.note-origin {
	/* строка идёт СРАЗУ ПОД своим полем и подписана ключом настройки: у каждого
	   значения виден его собственный источник, а не «источник где-то рядом» */
	display: flex;
	align-items: center;
	gap: var(--space-2);
	flex-wrap: wrap;
	margin: calc(-1 * var(--space-2)) 0 var(--space-4);
	font: var(--type-body-small);
	color: var(--md-sys-color-on-surface-variant);
}
.text-key code { font: var(--type-body-medium); white-space: nowrap; }

/* Ключ настройки на телефоне: сначала он уходит на свою строку под плашку источника
   (перенос ряда выше), а ключ длиннее строки переносится внутри себя — иначе он уводит
   вбок всю страницу, а с ней и лист меню, прибитый к краям окна. */
@media (max-width: 640px) {
	.text-key code { white-space: normal; overflow-wrap: anywhere; }
}

/* Показ загруженной картинки оформления: те же правила, что и у знака в шапке, только
   крупнее — высота фиксирована, ширина по пропорциям, потолок держит очень длинный
   логотип, чтобы он не растянул таблицу и не увёл колонку с кнопкой за край экрана. */
.image-preview {
	height: 32px;
	width: auto;
	max-width: 240px;
	object-fit: contain;
	vertical-align: middle;
	margin-right: var(--space-2);
}

/* --- компонент: таблица --- */

/* Таблица данных — по образцу MUI: лист цвета surface-container-low в обводке
   outline-variant, шапка — title-small приглушённым цветом, строки body-medium через
   разделитель, наведение — слой состояния на всю строку. Ряд плотный, как dense у MUI:
   ячейка 10/12px по вертикали и горизонтали, строка body-medium — ряд около 40px, шапка
   той же высоты. Отступы — от ступеней шага, чтобы плотность оформления их тоже двигала. */
.table-scroll {
	overflow-x: auto;
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	box-shadow: none;
}
table, .table {
	border-collapse: collapse;
	width: 100%;
	background: var(--md-sys-color-surface-container-low);
	color: var(--md-sys-color-on-surface);
}
th, td {
	text-align: left;
	padding: calc(var(--space-3) - 2px) var(--space-3);
	border-bottom: 1px solid var(--md-sys-color-outline-variant);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
th {
	height: 40px;
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
	color: var(--md-sys-color-on-surface-variant);
	background: var(--md-sys-color-surface-container);
}
tr:last-child td { border-bottom: none; }
tbody tr { transition: background-color var(--motion-fast) var(--motion-ease); }
tbody tr:hover { background: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-hover), transparent); }
caption { text-align: left; padding: calc(var(--space-3) - 2px) var(--space-3); color: var(--md-sys-color-on-surface-variant); }
.note-empty { color: var(--md-sys-color-on-surface-variant); font-style: italic; }

/* Строка-разворот: печатается следующим за записью <tr> во всю ширину таблицы.
   Пара «запись + разворот» — ОДНА запись: разделитель между ними снят, отступ сверху
   у разворота тоже, и подсветка записи забирает обе строки. */
.table__record > td { border-bottom: none; }
.table__expand > td { padding-top: 0; }
tbody tr.table__record:hover + .table__expand { background: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-hover), transparent); }

/* Выделенная запись: одна строка списка отвечает на вопрос, ради которого список открыли,
   и заливка находит её раньше, чем человек начнёт читать колонки. Оттенок берётся у дальней
   роли «выбранного» в Material — secondary-container с его же цветом текста, — и наведение
   кладёт поверх неё обычный слой состояния. */
tbody tr.row-accent { background: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
tbody tr.row-accent:hover { background: color-mix(in srgb, var(--md-sys-color-on-secondary-container) var(--state-hover), var(--md-sys-color-secondary-container)); }
tbody tr.row-accent + .table__expand { background: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
tbody tr.row-accent.table__record:hover + .table__expand { background: color-mix(in srgb, var(--md-sys-color-on-secondary-container) var(--state-hover), var(--md-sys-color-secondary-container)); }

/* Узкий столбец: сжат по содержимому и не переносится — значок, галочка, счётчик.
   Соседние столбцы забирают освободившуюся ширину себе. */
.col-narrow, .cell-narrow { width: 1%; white-space: nowrap; }

/* Колонка чисел (вид «число» у колонки и ячейки): число и заголовок над ним — по правому
   краю, цифры одной ширины (tabular-nums) — разряды встают друг под другом, и 1 204 сравнивается
   с 87 взглядом, без чтения. Строку число не рвёт. Стрелка сортировки у такого заголовка —
   слева от подписи, чтобы подпись стояла над числами, а не сдвигалась стрелкой. */
.col-num, .cell-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.col-num > .table__sort { flex-direction: row-reverse; }

/* --- компонент: тост --- */

/* Тост — сообщение в потоке страницы, как Alert у MUI: тональный контейнер статуса,
   значок цветом самого статуса, текст цветом «на контейнере». Появляется въездом снизу. */
.toast {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	min-height: 48px;
	padding: var(--space-2) var(--space-4);
	border-radius: var(--md-sys-shape-corner-small);
	margin: var(--space-3) 0;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	border: 0;
	background: var(--md-sys-color-surface-container-high);
	color: var(--md-sys-color-on-surface);
	animation: ui-toast var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate);
}
.toast .icon { width: 22px; height: 22px; }
.toast__title { font: var(--type-title-small); letter-spacing: var(--md-sys-typescale-title-small-tracking); }
.toast__text { flex: 1; }
.toast__close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	margin-right: calc(-1 * var(--space-2));
	background: none;
	border: 0;
	border-radius: var(--md-sys-shape-corner-full);
	color: inherit;
	cursor: pointer;
	padding: 0;
}
.toast__close:hover { background: color-mix(in srgb, currentColor var(--state-hover), transparent); }
/* Закрытый тост уходит, как Snackbar у MUI: гаснет и сжимается; убирает его скрипт кита. */
.toast--closing {
	opacity: 0;
	transform: translateY(8px);
	transition: opacity var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized-accelerate),
	            transform var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized-accelerate);
}
.toast--ok { background: var(--md-sys-color-success-container); color: var(--md-sys-color-on-success-container); }
.toast--ok .icon { color: var(--md-sys-color-success); }
.toast--warn { background: var(--md-sys-color-warning-container); color: var(--md-sys-color-on-warning-container); }
.toast--warn .icon { color: var(--md-sys-color-warning); }
.toast--err { background: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container); }
.toast--err .icon { color: var(--md-sys-color-error); }
.toast--info { background: var(--md-sys-color-info-container); color: var(--md-sys-color-on-info-container); }
.toast--info .icon { color: var(--md-sys-color-info); }

/* --- компонент: плашка --- */

/* Плашка — малый чип Material: высота 22px (плотнее 24 спецификации, чтобы плашка в строке
   текста не раздвигала её), пилюля, подпись label-medium.
   Нейтральная — контейнер surface-container-highest, статусные — тональные контейнеры
   своих ролей: цвет узнаётся заливкой, а текст на ней остаётся читаемым. */
.tag {
	--_bg: var(--md-sys-color-surface-container-highest);
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	min-height: 22px;
	padding: 0 var(--space-2);
	border-radius: var(--md-sys-shape-corner-full);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	border: 0;
	color: var(--md-sys-color-on-surface-variant);
	margin-right: var(--space-1);
	transition: background-color var(--motion-fast) var(--motion-ease);
}
a.tag:hover { --_state: var(--state-hover); text-decoration: none; }
.tag--accent { --_bg: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }
.tag--ok { --_bg: var(--md-sys-color-success-container); color: var(--md-sys-color-on-success-container); }
.tag--warn { --_bg: var(--md-sys-color-warning-container); color: var(--md-sys-color-on-warning-container); }
.tag--err { --_bg: var(--md-sys-color-error-container); color: var(--md-sys-color-on-error-container); }
/* Ряд плашек — ОДИН элемент, а не соседние дети контейнера. Плашка — атомарный инлайн
   (inline-flex), и вертикальный маргин, который контейнер ритма даёт своим детям как
   блокам (.card__body > * + *), уходит ей в высоту строчного бокса: строка опускается,
   а контейнер растёт. Зазор внутри ряда — тот же, что у .objcard__chips и .objhead__chips. */
.tag-row { display: flex; flex-wrap: wrap; gap: var(--space-1); }
/* Плашка с крестиком тихой формой (дев-сборка 8, слово Снять): форма — строчный блок вокруг
   плашки, в ряду плашек она встаёт, как сама плашка, и перенос строки её не рвёт. */
.tag-form { display: inline-flex; margin: 0; max-width: 100%; }

/* --- компонент: сводка (запись списка одним взглядом) ---
   Название первым и крупнее всего, знаки состояния — плашками рядом с ним, подпись —
   приглушённой строкой под ним, сведения — подписанными строками: на широком экране
   подпись и значение колонками, на узком подпись встаёт над значением. Вся разметка
   строчная (span): сводка живёт и в <summary>, куда блочные теги класть нельзя. */

.brief { display: flex; flex-direction: column; gap: var(--space-1); flex: 1 1 auto; min-width: 0; }
.brief__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); }
.brief__head .tag { margin-right: 0; }
.brief__name { font: var(--type-title-medium); letter-spacing: var(--md-sys-typescale-title-medium-tracking); color: var(--md-sys-color-on-surface); overflow-wrap: anywhere; }
.brief__sub { font: var(--type-body-medium); color: var(--md-sys-color-on-surface-variant); overflow-wrap: anywhere; }
.brief__facts {
	display: grid;
	grid-template-columns: max-content minmax(0, 1fr);
	gap: 2px var(--space-3);
	margin-top: var(--space-1);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.brief__fact { display: contents; }
.brief__label { color: var(--md-sys-color-on-surface-variant); }
.brief__value { color: var(--md-sys-color-on-surface); overflow-wrap: anywhere; }
.brief__value--warn { color: var(--md-sys-color-warning); }
@media (max-width: 640px) {
	.brief__facts { grid-template-columns: minmax(0, 1fr); gap: 0; }
	.brief__fact + .brief__fact .brief__label { margin-top: var(--space-1); }
}

/* Значок бренда (Кит.Иконка("бренд:…"), дев-сборка 8) — логотип цветом бренда (атрибут fill).
   Тёмный («чернильный») логотип — GitHub, Keycloak, Mattermost — на тёмной поверхности не
   виден, и в тёмной теме он рисуется цветом текста, как разрешают правила этих брендов.
   В строке флекса логотип не сжимается: искажённый знак правила брендов запрещают. */
.icon--brand { flex: none; }
:root:not([data-theme="light"]) .icon--brand-ink { fill: var(--md-sys-color-on-surface); }

/* --- компонент: дерево галок (выбор с трёхпозиционной галкой у ветки) ---
   Узел — строка «галка + подпись + приписка + плашки»; ветка — нативная раскрывашка,
   чей <summary> — один шеврон слева от строки. Строка стоит в разметке после ветки,
   а на экране первой (order): шеврон в порядке фокуса идёт раньше галки и в кнопку
   раскрытия ничего не вложено. Вложенность показывают отступ и линия слева.
   «Частично» рисуется знаком «—» в самой галке и без скрипта: по aria-checked="mixed",
   который печатает сервер, и по :indeterminate, который ставит скрипт. */

.checktree, .checktree__children { list-style: none; margin: 0; padding: 0; }
.checktree__node { position: relative; display: flex; flex-direction: column; }
.checktree__row {
	order: -1;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-1) var(--space-2);
	min-height: 28px;
	padding: 2px 0 2px 24px;
}
.checktree__item { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; min-width: 0; }
.checktree__label { color: var(--md-sys-color-on-surface); overflow-wrap: break-word; }
.checktree__note { font: var(--type-body-small); color: var(--md-sys-color-on-surface-variant); }
.checktree__marks { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); }
.checktree__marks .tag { margin-right: 0; }
.checktree__toggle {
	position: absolute;
	top: 2px; left: 0;
	width: 22px; height: 24px;
	display: flex; align-items: center; justify-content: center;
	list-style: none;
	cursor: pointer;
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-on-surface-variant);
}
.checktree__toggle::-webkit-details-marker { display: none; }
.checktree__toggle::before {
	content: "";
	width: 7px; height: 7px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(-45deg);
	transition: transform var(--motion-fast) var(--motion-ease);
}
.checktree__branch[open] > .checktree__toggle::before { transform: rotate(45deg); }
.checktree__toggle:hover { color: var(--md-sys-color-on-surface); background: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-hover), transparent); }
.checktree__toggle:focus-visible { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 0; }
.checktree__children {
	margin: 0 0 var(--space-1) 10px;
	padding-left: 14px;
	border-left: 1px solid var(--md-sys-color-outline-variant);
}
.checktree__children > .checktree__node::before {
	content: "";
	position: absolute;
	top: 16px; left: -14px;
	width: 10px;
	border-top: 1px solid var(--md-sys-color-outline-variant);
}
.checktree__more { margin: var(--space-1) 0 var(--space-1) 34px; font: var(--type-body-small); color: var(--md-sys-color-on-surface-variant); }
/* Галка дерева — флажок Material (правило у .field__check). «Частично» — черта вместо
   галочки: по aria-checked="mixed" от сервера и по :indeterminate от скрипта. */
.checktree__box[aria-checked="mixed"]:checked, .checktree__box:indeterminate {
	border-color: var(--md-sys-color-primary);
	background: linear-gradient(var(--_mark), var(--_mark)) center / 10px 2px no-repeat,
		var(--md-sys-color-primary);
}
.checktree__box[disabled] + .checktree__label { color: var(--md-sys-color-on-surface-variant); }

/* --- дерево галок (выпуск 0.13): тысяча пунктов --- */

/* Обёртка дерева с полями 0.13: поиск над деревом, дерево (или ответ поиска), ряд правок
   под ним — над кнопками формы. «Показать ещё» — ссылка-кнопка и счёт показанного в конце
   ветки. Ветка с режимами вместо галки несёт малые сегменты «все / выбранные / никакие»;
   при «все» и «никакие» её дети показаны тем, что решил режим: галки отмеченными или
   пустыми, приглушённо, и указатель их не правит (клавиатуру уводит скрипт — inert).
   Режим лист узнаёт по значению радиокнопки, а не по месту сегмента: набор режимов у ветки
   бывает свой, и «все, кроме» (дев-сборка 8) правится, как «выбранные». */
.checktree-set { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.checktree__search { display: flex; align-items: center; gap: var(--space-2); }
.checktree__search > .field { flex: 1 1 auto; min-width: 0; }
/* Поиск дерева значком (дев-сборка 8) — разметка строки поиска кита: лупа внутри рамки
   поля (правила .search-bar ниже), строка во всю ширину над деревом. */
.checktree__search > .search-bar { position: relative; flex: 1 1 auto; min-width: 0; }
.checktree__page { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); }
.checktree__count { color: var(--md-sys-color-on-surface-variant); }
.checktree-set[aria-busy="true"] .checktree__search .button,
.checktree__page .button[aria-busy="true"] { cursor: progress; opacity: .6; }
span.checktree__item { cursor: default; }
.checktree__modes { margin-left: auto; }
/* Строка ветки над страницей её детей (дев-сборка 10): подпись и сегменты режима, без места
   под шеврон — раскрывать на странице ветки нечего. */
.checktree__head { padding-left: 0; }
.checktree__node:has(> .checktree__row > .checktree__modes > .segments__item > [value="все"]:checked) > .checktree__branch > .checktree__children,
.checktree__node:has(> .checktree__row > .checktree__modes > .segments__item > [value="никакие"]:checked) > .checktree__branch > .checktree__children {
	pointer-events: none;
}
.checktree__node:has(> .checktree__row > .checktree__modes > .segments__item > [value="все"]:checked) > .checktree__branch > .checktree__children .checktree__box {
	--_mark: var(--md-sys-color-surface);
	border-color: transparent;
	background: linear-gradient(45deg, transparent calc(50% - 1px), var(--_mark) 0 calc(50% + 1px), transparent 0)
			1px 5px / 5px 5px no-repeat,
		linear-gradient(-45deg, transparent calc(50% - 1px), var(--_mark) 0 calc(50% + 1px), transparent 0)
			4px 1px / 9px 9px no-repeat,
		color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
}
.checktree__node:has(> .checktree__row > .checktree__modes > .segments__item > [value="никакие"]:checked) > .checktree__branch > .checktree__children .checktree__box {
	border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
	background: transparent;
}
.checktree__node:has(> .checktree__row > .checktree__modes > .segments__item > [value="все"]:checked) > .checktree__branch > .checktree__children .checktree__label,
.checktree__node:has(> .checktree__row > .checktree__modes > .segments__item > [value="никакие"]:checked) > .checktree__branch > .checktree__children .checktree__label {
	color: var(--md-sys-color-on-surface-variant);
}
.checktree__found { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.checktree__found-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); }
.checktree__found-count { margin: 0; font: var(--type-body-medium); color: var(--md-sys-color-on-surface-variant); }
.checktree__found-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* совпадения — плоский список: места под шеврон ветки им не нужно */
.checktree--found > .checktree__node > .checktree__row { padding-left: 0; }
.checktree__edits { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); }
/* Отмеченное вне показанного (дев-сборка 8): ряд «Выбрано: …» над деревом — плашки тем же
   чипом, что правки. Крестик — настоящая галка формы под значком «закрыть»: щелчок по нему
   отмечает снятие, и без скрипта плашка зачёркивается — снятие уйдёт с формой. Фокус
   с клавиатуры рисуется кольцом вокруг значка. */
.checktree__picked { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); }
.checktree__picked-label { font: var(--type-label-medium); color: var(--md-sys-color-on-surface-variant); }
.checktree__picked-list { display: flex; flex-wrap: wrap; gap: var(--space-1); min-width: 0; }
.checktree__pick { max-width: 100%; }
/* хвост ряда за пределом (дев-сборка 10): «ещё 120» — текстом или ссылкой на продолжение */
.checktree__picked-more { font: var(--type-label-medium); color: var(--md-sys-color-on-surface-variant); }
a.checktree__picked-more { color: var(--md-sys-color-primary); }
.checktree__pick--off { color: var(--md-sys-color-on-error-container); background: var(--md-sys-color-error-container); }
.checktree__unpick {
	--_state: 0%;
	position: relative;
	display: inline-grid;
	place-items: center;
	flex: none;
	width: 24px;
	height: 24px;
	border-radius: 50%;
	color: inherit;
	background: color-mix(in srgb, currentColor var(--_state), transparent);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.checktree__unpick:hover { --_state: var(--state-hover); }
.checktree__unpick:active { --_state: var(--state-pressed); }
.checktree__unpick .icon { width: 18px; height: 18px; }
.checktree__unpick-box {
	position: absolute;
	inset: 0;
	margin: 0;
	opacity: 0;
	cursor: pointer;
}
.checktree__unpick:has(> .checktree__unpick-box:focus-visible) { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 0; }
.checktree__pick:has(.checktree__unpick-box:checked) > .chips-in__text { text-decoration: line-through; opacity: .6; }
.checktree__edits[hidden] { display: none; }
.checktree__edits-label { font: var(--type-label-medium); color: var(--md-sys-color-on-surface-variant); }
.checktree__edits-list { display: flex; flex-wrap: wrap; gap: var(--space-1); min-width: 0; }
.checktree__edit { max-width: 100%; }
.checktree__edit--on { color: var(--md-sys-color-on-success-container); background: var(--md-sys-color-success-container); }
.checktree__edit--off { color: var(--md-sys-color-on-error-container); background: var(--md-sys-color-error-container); }
/* Сводка правок (дев-сборка 8) — строка body-medium у кнопок формы; без правок спрятана. */
.checktree__changes { margin: 0; font: var(--type-body-medium); color: var(--md-sys-color-on-surface-variant); }
.checktree__changes[hidden] { display: none; }
@media (max-width: 640px) {
	.checktree__modes { margin-left: 0; }
	.checktree__found-head { flex-direction: column; align-items: flex-start; }
}

/* --- компонент: дерево разделов --- */

.tree { min-width: 0; }
.tree__list { list-style: none; margin: 0; padding: 0; }
.tree--row > .tree__list { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.tree--column > .tree__list { display: flex; flex-direction: column; gap: 2px; }
.tree__item { min-width: 0; }
/* Ход дерева — пункт навигации Material: пилюля, label-large, слой состояния на
   наведении; текущий раздел — индикатор цвета secondary-container. */
.tree__link {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 36px;
	padding: 0 var(--space-4);
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-on-surface-variant);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	transition: background-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}
.tree__link:hover { --_state: var(--state-hover); color: var(--md-sys-color-on-surface); text-decoration: none; }
.tree__link:active { --_state: var(--state-pressed); }
.tree__link--active, .tree__link--active:hover {
	--_bg: var(--md-sys-color-secondary-container);
	color: var(--md-sys-color-on-secondary-container);
}
/* Колонка разделов дома — плотная, 36px, как ход в шапке: пальцевую высоту ей даёт медиа-блок
   дома настроек на узком экране, а на широком под курсором хватает и этой. */
.tree--column .tree__link { display: flex; min-height: 36px; }
.tree--column .tree__list--1 { margin-left: var(--space-4); }
/* Рубрика — подпись над первым разделом своей группы: заголовок секции ящика навигации
   Material — title-small обычным регистром, приглушённо и с отступом сверху, чтобы группа
   читалась отдельно (было label-medium прописными, стиль M2; аудит M3, S10). В ряду шапки
   рубрик нет: их там не печатает компонент. */
.tree__rubric {
	padding: var(--space-3) var(--space-4) var(--space-1);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
}
/* Опасный раздел — цветом ошибки и на наведении; текущий опасный залит error-container. */
.tree__link--danger, .tree__link--danger:hover { color: var(--md-sys-color-error); }
.tree__link--danger.tree__link--active, .tree__link--danger.tree__link--active:hover {
	--_bg: var(--md-sys-color-error-container);
	color: var(--md-sys-color-on-error-container);
}
/* Ряд ходов в шапке дышит шире колонки: соседние дороги там читаются как отдельные,
   а не как список. */
.tree--top > .tree__list { gap: var(--space-3); }
.office-nav {
	margin: 0 0 var(--space-5);
	padding-bottom: var(--space-3);
	border-bottom: 1px solid var(--md-sys-color-outline-variant);
}

/* --- компонент: панель сохранения ---
   Тень — только у прилипшей панели (аудит M3 №12, дев-сборка 8): Material поднимает
   поверхность, когда под ней прокручивается содержимое, а панель в конце короткой формы
   с тенью уровня 2 выглядела плавающим блоком посреди формы. Прилипла ли она, знает только
   движок: скрипт ставит data-stuck (панель прилипла к низу экрана — на пиксель ниже края,
   отсюда bottom: -1px). Без скрипта признака нет, и прилипшую панель отделяет от полей
   под ней кольцо outline-variant (дев-сборка 10, ревью ui-брендирование-2): фон панели —
   фон карточки, и без края поля срезались под ней на полуслове. Слой выше подписей полей
   (z-index 1 у плавающей подписи и справки): иначе подпись поля под прилипшей панелью
   просвечивала поверх неё. */

.savebar {
	position: sticky;
	bottom: -1px;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	flex-wrap: wrap;
	margin-top: var(--space-5);
	padding: var(--space-3) var(--space-4);
	background: var(--md-sys-color-surface-container);
	border-top: 0;
	border-radius: var(--md-sys-shape-corner-large);
	box-shadow: none;
	transition: box-shadow var(--motion-medium) var(--motion-ease),
		background-color var(--motion-medium) var(--motion-ease);
}
.savebar[data-stuck] { box-shadow: var(--md-sys-elevation-level2); }
@media (scripting: none) {
	.savebar { box-shadow: 0 0 0 1px var(--md-sys-color-outline-variant); }
}
.savebar__note { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); }
/* Несохранённые правки: класс ставит скрипт кита на первом же вводе в форму, а снимает
   ответ сервера — после сохранения панель приходит без него. */
.savebar--dirty { background: var(--md-sys-color-secondary-container); }
.savebar--dirty .savebar__note { color: var(--md-sys-color-on-secondary-container); }
.savebar__actions { display: flex; gap: var(--space-2); align-items: center; }

/* --- компонент: кнопка ---
   Вид задаётся модификатором, а не наличием элемента. Ловушка, которую это лечит:
   `.button--primary` печатался разметкой, но правила для него НЕ БЫЛО — класс молча
   падал на базовый `.button`, и кнопка выглядела правильно ровно потому, что базовый
   вид совпадал с первичным. Поэтому у каждого варианта есть собственное правило:
   класса без правила и правила без класса в этом листе не бывает.

   Кнопки — общие кнопки Material 3: пилюля, подпись label-large. Высота плотная — 36px,
   а не 40 спецификации (малая — 28, значок — круг 36): в строке с полем 44px и в ряду
   действий карточки кнопка не должна быть самым высоким, что там стоит.
   Вариант задаёт пару «заливка и содержимое» (--_bg, --_fg), а слой состояния считается
   поверх неё одним правилом: залитая (основная, опасная) на наведении ещё и приподнимается
   на уровень 1, как у MUI. Варианты кита ложатся на виды Material так:
   основная — filled, вторичная — outlined, опасная — filled цветом error, ссылка — text. */
.button {
	--_bg: var(--md-sys-color-primary);
	--_fg: var(--md-sys-color-on-primary);
	--_lift: var(--md-sys-elevation-level1);
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	min-height: 36px;
	padding: 0 var(--space-5);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	border-radius: var(--md-sys-shape-corner-full);
	border: 1px solid transparent;
	background-color: color-mix(in srgb, var(--_fg) var(--_state), var(--_bg));
	color: var(--_fg);
	box-shadow: none;
	cursor: pointer;
	white-space: nowrap;
	-webkit-user-select: none;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
	transition: background-color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
	            box-shadow var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
	            border-color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
	            color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
}
.button:hover {
	--_state: var(--state-hover);
	box-shadow: var(--_lift);
	text-decoration: none;
}
.button:focus-visible { --_state: var(--state-focus); }
.button:active { --_state: var(--state-pressed); box-shadow: none; }
.button[disabled], .button[aria-disabled="true"] {
	--_bg: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-container), transparent);
	--_fg: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
	--_state: 0%;
	box-shadow: none;
	cursor: not-allowed;
}
.button--primary { --_bg: var(--md-sys-color-primary); --_fg: var(--md-sys-color-on-primary); }
.button--ghost {
	--_bg: transparent;
	--_fg: var(--md-sys-color-primary);
	--_lift: none;
	border-color: var(--md-sys-color-outline);
}
.button--ghost:focus-visible { border-color: var(--md-sys-color-primary); }
.button--ghost:is([disabled], [aria-disabled="true"]) {
	--_bg: transparent;
	border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-container), transparent);
}
.button--danger { --_bg: var(--md-sys-color-error); --_fg: var(--md-sys-color-on-error); }
.button--link {
	--_bg: transparent;
	--_fg: var(--md-sys-color-primary);
	--_lift: none;
	padding-left: var(--space-3);
	padding-right: var(--space-3);
}
.button--link:is([disabled], [aria-disabled="true"]) { --_bg: transparent; }
.button--sm {
	min-height: 28px;
	padding: 0 var(--space-4);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
.button--link.button--sm { padding: 0 var(--space-2); }
.button--lg {
	min-height: 56px;
	padding: 0 var(--space-6);
	font: var(--type-title-medium);
	letter-spacing: var(--md-sys-typescale-title-medium-tracking);
}
/* Кнопка во всю ширину живёт в вертикальном перечне, а не в ряду действий: подпись
   здесь переносится, иначе на узком экране длинная надпись выезжает за контейнер. */
.button--block { display: flex; width: 100%; justify-content: center; white-space: normal; }
/* Занятая кнопка — круговой индикатор Material перед подписью: дуга цветом содержимого
   вращается, пока сервер работает. */
.button--busy { cursor: progress; }
.button--busy::before {
	content: "";
	width: 18px;
	height: 18px;
	flex: none;
	border-radius: var(--md-sys-shape-corner-full);
	border: 2px solid currentColor;
	border-right-color: transparent;
	animation: ui-spin var(--motion-pulse) linear infinite;
}
/* Кнопка-иконка: подпись у неё есть, но живёт в aria-label — значит нужен круг под
   иконку, а не пилюля под текст. Кнопка-значок Material: круг 40px со значком 24px;
   вторичная и ссылка — стандартная кнопка-значок без заливки и рамки, цветом
   on-surface-variant, основная — залитая. */
/* Картинка кнопки (произвольный значок вместо глифа набора) занимает РОВНО ту же
   коробку, что иконка набора (.icon): сторона в 1em и flex: none — иначе она тянется
   вместе с подписью, перерастает текст и меняет высоту кнопки. */
.button__pic { width: 1em; height: 1em; flex: none; vertical-align: -0.125em; }

.button--icon { width: 36px; min-height: 36px; padding: 0; }
.button--icon .icon { width: 24px; height: 24px; }
.button--icon.button--sm { width: 32px; min-height: 32px; }
.button--icon.button--sm .icon { width: 20px; height: 20px; }
/* :where() держит специфичность этого правила на уровне варианта: пункт меню, который
   перебивает варианты, перебивает и его. */
.button--icon:where(.button--ghost, .button--link) {
	--_fg: var(--md-sys-color-on-surface-variant);
	border-color: transparent;
}

/* Счётчик на кнопке — сколько ждёт человека за её панелью. Фон и цвет у пилюли
   СОБСТВЕННЫЕ, а не унаследованные от кнопки: приглушённый вариант делает надпись
   кнопки бледной, и счётчик на ней потерялся бы вместе с надписью. Ширина в 1.4em
   держит однозначное число круглым, а не сплюснутым. */
/* Бейдж Material — большой: 16px высотой, цвета error, подпись label-small. На любой
   заливке кнопки он остаётся красным пятном: значит «ждёт человека». */
.button__count {
	flex: none;
	min-width: 16px;
	height: 16px;
	padding: 0 var(--space-1);
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-error);
	color: var(--md-sys-color-on-error);
	font: var(--type-label-small);
	letter-spacing: var(--md-sys-typescale-label-small-tracking);
	text-align: center;
}
/* У кнопки-значка бейдж садится на угол значка, а не встаёт рядом и не раздвигает круг. */
.button--icon .button__count { position: absolute; top: 2px; right: 2px; }

/* Шеврон раскрытия — подсказка, а не второй значок: он мельче значка кнопки и приглушён,
   иначе спорит с ним за внимание. Коробку значка (.icon) он и так не растягивает. */
.button__chevron { width: 18px; height: 18px; opacity: .8; margin-right: calc(-1 * var(--space-2)); transition: transform var(--motion-medium) var(--md-sys-motion-easing-emphasized); }
.button[popovertarget]:has(+ :popover-open) .button__chevron,
.menu:has(:popover-open) .button__chevron { transform: rotate(180deg); }

/* --- меню строки и учётки -----------------------------------------------------------
   Панель меню лежит в ВЕРХНЕМ СЛОЕ браузера: у панели атрибут popover, у кнопки —
   popovertarget с её идентификатором. Из потока панель вынута, поэтому строка таблицы
   от неё не растёт и соседние строки не сдвигаются; предок с overflow (обёртка прокрутки
   таблицы) её не обрезает. Открытие, закрытие щелчком мимо и по Esc, возврат фокуса
   на кнопку — работа движка: JS здесь нет вовсе. Браузер, который верхнего слоя не знает,
   атрибут popover игнорирует — панель остаётся видимой на своём месте в потоке, то есть
   деградирует в прежнее поведение, а не в пустой экран. */

/* Открывашка — кнопка-значок без текста, базовой линии у неё нет: по умолчанию она садилась
   низом на строку текста соседа (кнопки «Отозвать» в той же ячейке) и торчала выше неё.
   Середина к середине. */
.menu { display: inline-block; vertical-align: middle; }

/* Вид панели — безусловный, место — только там, где браузер знает верхний слой. Без него
   панель остаётся ОБЫЧНЫМ блоком в потоке ячейки: видна на месте со всеми пунктами, как
   раскрытая раскрывашка до перехода. Вырви её из потока безусловно — старый движок
   показал бы её обрезанной обёрткой прокрутки или вовсе за краем экрана. */
/* Меню Material: surface-container на уровне возвышения 2, скругление extra-small, поля
   8px сверху и снизу; пункты — строки 48px во всю ширину. */
.menu__panel {
	margin: var(--space-1) 0 0 0;
	width: max-content;
	min-width: 112px;
	max-width: 280px;
	padding: var(--space-2) 0;
	flex-direction: column;
	gap: 0;
	background: var(--md-sys-color-surface-container);
	color: var(--md-sys-color-on-surface);
	border: 0;
	border-radius: var(--md-sys-shape-corner-extra-small);
	box-shadow: var(--elevation-ring-level2);
}

/* Подпись меню — заголовок листа на телефоне: лист встаёт у нижнего края окна, вдали
   от строки, и без подписи не видно, чьи это действия. На широком панель висит у самой
   кнопки, подпись была бы дублем — там она спрятана. */
.menu__title {
	display: none;
	margin: 0 0 var(--space-2);
	padding: 0 var(--space-4);
	font: var(--type-title-medium);
	letter-spacing: var(--md-sys-typescale-title-medium-tracking);
	overflow-wrap: anywhere;
}

/* Пункт — строка во всю ширину панели, как в выпадающей панели единого меню: рамка-коробка
   у каждого пункта дробила бы список на кнопки. Вид пункта задаёт МЕНЮ, а не вызывающий:
   ссылка, кнопка окна и форма из одной кнопки выглядят одинаково, какой бы вариант кнопки
   ни пришёл. Форма с полями — не пункт, а маленькая форма, и её кнопка остаётся кнопкой.
   Кнопки окна, открытого из меню, лежат глубже и строкой не становятся. :has() обёрнут
   в :is(): движок без :has() выбросил бы из-за него весь список вместе со ссылками,
   а так теряет только форму. */
.menu__panel > .button,
.menu__panel > form:is(:not(:has(.field))) > .button {
	--_bg: transparent;
	--_fg: var(--md-sys-color-on-surface);
	--_lift: none;
	width: 100%;
	min-height: 48px;
	justify-content: flex-start;
	gap: var(--space-3);
	padding: 0 var(--space-3);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	text-align: left;
	white-space: normal;
	border-color: transparent;
	border-radius: 0;
}
.menu__panel > .button .icon,
.menu__panel > form:is(:not(:has(.field))) > .button .icon { width: 24px; height: 24px; color: var(--md-sys-color-on-surface-variant); }
/* Опасный пункт узнаётся цветом подписи, а не заливкой: залитая кнопка перекрикивала бы
   весь список. */
.menu__panel > .button--danger,
.menu__panel > form:is(:not(:has(.field))) > .button--danger { --_fg: var(--md-sys-color-error); }
.menu__panel > .button--danger .icon,
.menu__panel > form:is(:not(:has(.field))) > .button--danger .icon { color: var(--md-sys-color-error); }
/* Форма с полями внутри меню — маленькая форма, не пункт: ей нужны поля по краям. */
.menu__panel > form:has(.field) { padding: var(--space-2) var(--space-3); }

@supports selector(:popover-open) {

	/* Место панели — под кнопкой, которая её открыла: кнопка, названная в popovertarget,
	   и есть неявный якорь панели. Но движок берёт его, только когда попросили: при
	   начальном `position-anchor: normal` значения anchor() якоря не находят, и панель
	   ложилась в левый верхний угол ДОКУМЕНТА — на прокрученной странице за край экрана.

	   Позиция fixed, а не absolute: у кнопки привязка та же, а запасное значение
	   anchor(…, запас) считается от окна — движок, не нашедший якоря, покажет панель
	   в экране, а не в начале документа. `inset: auto` снимает раскладку UA-стиля (`inset: 0`
	   вместе с `margin: auto` ставит popover по центру экрана), поэтому стоит ПЕРЕД top/right.

	   Панель растёт ВЛЕВО от правого края кнопки: меню стоит в последней колонке таблицы.
	   Не хватит места снизу или слева — браузер отзеркалит её сам; выше окна она не вырастет
	   и прокручивается сама. */
	.menu__panel {
		position: fixed;
		inset: auto;
		position-anchor: auto;
		top: anchor(bottom, 30vh);
		right: anchor(right, var(--space-5));
		max-height: min(70vh, 32rem);
		overflow-y: auto;
		overscroll-behavior: contain;
		position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
	}

	/* display достаётся ТОЛЬКО открытой панели: авторское правило сильнее UA-стиля
	   `[popover]:not(:popover-open) { display: none }`, и безусловный display оставил бы
	   панель раскрытой навсегда. */
	.menu__panel:popover-open { display: flex; }

	/* Движок знает верхний слой, но не знает привязки к якорю: панель встаёт по центру
	   экрана — всё так же поверх таблицы и всё так же по одной за раз. */
	@supports not (top: anchor(bottom)) {
		.menu__panel { position: fixed; inset: auto; top: 30vh; left: 50%; translate: -50% 0; }
	}

}

.menu__form { display: flex; flex-direction: column; gap: var(--space-2); }

/* Набор полей одного варианта: невыбранный уезжает под `disabled hidden`, поэтому
   собственной рамки и отступов у набора нет — в колонке полей он прозрачен
   для раскладки. */
.field-set { border: 0; padding: 0; margin: 0; min-width: 0; }
.field-set[hidden] { display: none; }

/* Крестик снятия печатается ВНУТРИ плашки (компонент «Плашка», поле Действие): их
   связку держит рамка самой плашки, поэтому перенос строки не может оторвать крестик
   от своей метки. Интервал до крестика даёт gap самой плашки. */
.tag__action { display: inline-flex; align-items: center; }

/* Крестик — служебная деталь ПРИ плашке, а не самостоятельная кнопка рядом с ней.
   Кит даёт ему полный набор кнопки (рамка, свой акцентный цвет, глиф 18px, поворот
   на наведении) — здесь всё это снимается: без такого сброса связка читалась как
   «плашка и случайно оказавшаяся рядом кнопка».

   Цвет НЕ наследуется от плашки сознательно: у вариантов плашки свои цвета, а крестик
   служебный и обязан оставаться контрастным в обеих темах — поэтому он берёт цвет
   содержимого плашки: тональная заливка у каждого вида своя, и «на ней» читается только
   её же пара. */
.tag__action .button {
	--_bg: transparent;
	--_fg: currentColor;
	--_lift: none;
	width: 18px;
	min-height: 18px;
	padding: 0;
	margin-right: calc(-1 * var(--space-1));
	border: 0;
	border-radius: var(--md-sys-shape-corner-full);
	color: inherit;
	/* .7, а не «поглуше»: на поверхности карточки более низкое значение уводит глиф
	   ниже 3:1, а это нетекстовый элемент управления. */
	opacity: .7;
	transition: opacity var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}

/* 12px вместо кнопочных 18px: прежний глиф был шире всей подписи короткой плашки
   и спорил с её текстом за внимание. */
.tag__action .button .icon { width: 12px; height: 12px; }

.tag__action .button:hover { opacity: 1; box-shadow: none; }

/* Фокус обязателен: это кнопка, по ней ходят с клавиатуры. Обводка от глобального
   правила, здесь только прижатый offset — вокруг 12-пиксельного глифа зазор в 2px
   выглядит нимбом. */
.tag__action .button:focus-visible { opacity: 1; outline-offset: 1px; }

/* Поворот крестика на наведении читается как «закрыть окно», а не «снять плашку». */
.tag__action .button:hover .icon { transform: none; }

/* --- студия: поля слева, образец справа -----------------------------------------
   Образец — серверный рендер того же компонента при любой перерисовке страницы: это
   и есть путь без скрипта. Прогрессивное улучшение лишь перерисовывает тот же образец
   по вводу; разметку и цвета печатает сервер.

   flex-wrap, а не grid с двумя колонками: колонка образца обязана уезжать ПОД поля
   на узком экране, а без медиазапроса grid этого не умеет. Интервал между полями
   даёт .studio__work — он и есть тот контейнер-владелец интервала, о котором говорит
   комментарий у .field. */
.studio { display: flex; flex-wrap: wrap; gap: var(--space-5); align-items: flex-start; }

.studio__work {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	flex: 1 1 20rem;
	min-width: 0;
}

.studio__sample {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	flex: 0 1 14rem;
	padding: var(--space-4);
	background: var(--md-sys-color-surface-container);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
}

/* Ряд, прижатый к правому краю: вторичное действие формы стоит там же, где кнопки
   панели сохранения. Отбивку сверху даёт ритм контейнера (.section__body > * + *). */
.row-end { display: flex; justify-content: flex-end; }

/* Нативная пипетка рисует сплошную заливку во всю площадь ввода: у поля кита
   с его паддингом она выглядела бы серой рамкой вокруг цветного пятна. */
input[type="color"].field__control { padding: 17px 12px 3px; height: 44px; cursor: pointer; }

/* Подпись только для скринридера: видимая часть меню — символ «⋯», и без текстовой
   пары кнопка безымянна (та же беда, что у кнопки-иконки в ките). */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* --- страница входа (дев-сборка 9) ---
   Узкая страница — одна колонка 360 px по центру с отступом сверху, как вход GitHub: шапка
   колонки (знак 48 px, заголовок headline-small и подпись по центру), форма в столбик,
   разделитель «или», кнопки провайдеров во всю ширину и рамочная строка под формой. На
   телефоне колонка во всю ширину с полями страницы (их даёт .wrap). Блоки колонки — с
   ритмом --space-4, столбик кнопок — --space-2. */
.narrow {
	width: 100%;
	max-width: 360px;
	margin: var(--space-6) auto var(--space-8);
}
.narrow > * { margin-top: 0; margin-bottom: 0; }
/* у блока со своим нулевым маргином (рамочная строка) ритм колонки всё равно старше */
.narrow > :not(:first-child) { margin-top: var(--space-4); }
.narrow .form-stack { max-width: none; }
.narrow .form-stack > .button { align-self: stretch; }
.narrow__head {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-2);
	text-align: center;
}
.narrow__head > img, .narrow__head > .icon { width: auto; height: 48px; }
.narrow__head > .icon { width: 48px; }
.narrow__head > h1 {
	margin: var(--space-2) 0 0;
	font: var(--type-headline-small);
	letter-spacing: var(--md-sys-typescale-headline-small-tracking);
	color: var(--md-sys-color-on-surface);
}
/* Подпись шапки — лид ворот (дев-сборка 10): абзац длиннее строки переносится по словам по
   центру, не шире колонки; строки выравниваются по длине (balance), а длинное слово без
   пробелов (адрес, логин) переносится внутри себя, а не раздвигает колонку. */
.narrow__head > p { margin: 0; max-width: 100%; text-wrap: balance; overflow-wrap: anywhere; }
.stack { display: flex; flex-direction: column; gap: var(--space-2); }
.stack > .button { width: 100%; }
/* Разделитель: линия outline-variant; с текстом — две линии по бокам текста label-medium. */
.divider {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	margin: var(--space-4) 0;
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
hr.divider { display: block; height: 0; border: 0; border-top: 1px solid var(--md-sys-color-outline-variant); }
div.divider::before, div.divider::after { content: ""; flex: 1 1 0; border-top: 1px solid var(--md-sys-color-outline-variant); }
.divider__text { flex: none; }
/* Рамочная строка под формой: «Нет аккаунта? Зарегистрироваться» — текст по центру в рамке
   outline-variant со скруглением medium. */
.note-frame {
	margin: 0;
	padding: var(--space-4);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	text-align: center;
	color: var(--md-sys-color-on-surface);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
/* Сноска (дев-сборка 10): самый мелкий петит страницы — обязательная атрибуция базы
   данных («IP to Country by DB-IP»), лицензия, строка о правах. body-small цветом
   пояснения, без рамки и своих маргинов (ритм ставит контейнер); ссылка внутри того же
   кегля, а не крупнее текста вокруг. В узкой колонке — по центру, как всё в ней, в обычной
   странице — слева. */
.note-footnote {
	margin: 0;
	font: var(--type-body-small);
	letter-spacing: var(--md-sys-typescale-body-small-tracking);
	color: var(--md-sys-color-on-surface-variant);
}
.note-footnote a { font: inherit; letter-spacing: inherit; }
.narrow > .note-footnote { text-align: center; }
/* Ссылка над полем справа — в строке подписи, label-large цветом ссылки. */
.field-aside { display: flex; flex-direction: column; gap: var(--space-1); }
.field-aside__link { align-self: flex-end; font: var(--type-label-large); letter-spacing: var(--md-sys-typescale-label-large-tracking); }

/* --- переключатель темы ---
   Пилюля с бегунком, как у сайта oscript.io (VitePress; дев-сборка 9): кнопка отправки
   формы темы с ролью переключателя — форма POST-ит смену, поэтому переключатель работает
   без единой строки JS. Дорожка 44×24 с кольцом outline-variant, бегунок 18 px заливкой
   surface-container-lowest (белый в светлой, самый тёмный в тёмной — как у VitePress)
   с тенью уровня 1, в бегунке солнце (светлая) или луна (тёмная).
   Положение бегунка и значок лист берёт у ТЕМЫ СТРАНИЦЫ (html[data-theme]), а не у
   aria-checked: скрипт перекрашивает страницу в тот же миг, что и щёлкнули, и бегунок
   едет вместе с ней, не дожидаясь ответа. Без атрибута темы страница тёмная — как палитры
   кита. Строка меню — та же кнопка вариантом пункта: подпись словами, пилюля в конце. */

.theme-toggle { display: inline-flex; margin: 0; }
.theme-switch {
	position: relative;
	display: inline-flex;
	align-items: center;
	flex: none;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: var(--md-sys-shape-corner-full);
	background: none;
	color: var(--md-sys-color-on-surface-variant);
	cursor: pointer;
}
/* зона касания 48 px вокруг пилюли 44×24 — видимый размер прежний */
.theme-switch::before { content: ""; position: absolute; inset: -12px -2px; }
.theme-switch__track {
	position: relative;
	display: inline-block;
	flex: none;
	width: 44px;
	height: 24px;
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-surface-container-highest);
	box-shadow: inset 0 0 0 1px var(--md-sys-color-outline-variant);
	transition: box-shadow var(--motion-fast) var(--motion-ease);
}
.theme-switch:hover .theme-switch__track,
.theme-switch--row:hover .theme-switch__track { box-shadow: inset 0 0 0 1px var(--md-sys-color-outline); }
.theme-switch__thumb {
	position: absolute;
	top: 3px;
	left: 3px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 18px;
	height: 18px;
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-surface-container-lowest);
	color: var(--md-sys-color-on-surface-variant);
	box-shadow: var(--md-sys-elevation-level1);
	transform: translateX(20px);
	transition: transform var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
.theme-switch__sun, .theme-switch__moon { display: inline-flex; }
.theme-switch__sun { display: none; }
.theme-switch__thumb .icon { width: 12px; height: 12px; }
html[data-theme="light"] .theme-switch__thumb { transform: none; }
html[data-theme="light"] .theme-switch__sun { display: inline-flex; }
html[data-theme="light"] .theme-switch__moon { display: none; }
/* строка меню — пункт ящика (вид у .hubmenu .button), пилюля прижата к правому краю */
.theme-switch--row > .theme-switch__track { margin-left: auto; }
@media (prefers-reduced-motion: reduce) {
	.theme-switch__thumb { transition: none; }
}

/* --- тихая отправка ---
   Форма с data-quiet уходит запросом в фоне (скрипт кита), и пока ответ в пути, она
   помечена aria-busy: курсор говорит «сервер работает», а страница не перезагружается. */

form[data-quiet][aria-busy="true"] { cursor: progress; }

/* --- компонент: блок команды --- */

.cmd__note { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); margin: 0 0 var(--space-2); }
.cmd {
	display: flex;
	/* flex-start, а не center: у многострочного сниппета центрирование уводило
	   кнопку копирования на середину блока */
	align-items: flex-start;
	gap: var(--space-2);
	background: var(--md-sys-color-surface-container-highest);
	color: var(--md-sys-color-on-surface);
	border: 0;
	border-radius: var(--md-sys-shape-corner-small);
	padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
	overflow-x: auto;
}
/* ОДНО правило на .cmd code, и в нём pre-wrap.
   Регрессия, которую это лечит: правил было ДВА — первое ставило pre-wrap ради
   многострочного сниппета, второе (ниже по файлу, та же специфичность) перебивало его
   на nowrap. Побеждало второе, и многострочный сниппет печатался одной строкой.
   Не «поправить второе», а слить в одно: два правила на один селектор — это и есть
   механизм, которым дефект вернётся. */
/* Строка кода стоит на высоте кнопки копирования (32px) за счёт полей, а не интерлиньяжа:
   у многострочного сниппета интерлиньяж 32px развёл бы строки вдвое. */
/* Код сжимается, а не кнопка: min-width 0 снимает с флекс-элемента ширину самого длинного
   слова, а overflow-wrap: anywhere рвёт строку без пробелов (адрес, путь) у края блока.
   Было: на 390 «opm install localhost:3412/пул/пакет» держал ширину целиком, и кнопка
   «Копировать» уезжала за край блока (аудит M3, S7). */
.cmd code {
	flex: 1 1 auto;
	min-width: 0;
	padding: 6px 0;
	font-size: var(--md-sys-typescale-body-medium-size);
	line-height: var(--md-sys-typescale-body-medium-line-height);
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	tab-size: 4;
}
/* На телефоне кнопка встаёт под код, когда рядом с ним не помещается: ряд переносится по
   полной ширине кода (основа auto), и короткая команда остаётся в одну строку с кнопкой. */
@media (max-width: 600px) {
	.cmd { flex-wrap: wrap; }
}
.cmd .copy, .from-config__env .copy {
	flex: none;
	margin-left: auto;
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	position: relative;
	min-height: 32px;
	border: 1px solid var(--md-sys-color-outline);
	background: transparent;
	color: var(--md-sys-color-primary);
	border-radius: var(--md-sys-shape-corner-full);
	padding: 0 var(--space-3);
	cursor: pointer;
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
	white-space: nowrap;
	transition: background-color var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease);
}
.cmd .copy:hover, .from-config__env .copy:hover { background: color-mix(in srgb, var(--md-sys-color-primary) var(--state-hover), transparent); }

/* --- компонент: табы ---
   Вкладка — обычная ссылка на свой URL, активная помечена aria-current="page":
   подсветка берётся из атрибута, а не из отдельного состояния на JS. */

/* Основные вкладки Material: высота 48px, подпись title-small, текущая — цвета primary
   с индикатором 3px шириной в подпись и скруглённым верхом. Индикатор — псевдоэлемент
   ссылки: у текущей он развёрнут, у остальных сжат в ноль, поэтому смена вкладки внутри
   страницы (без перехода) разворачивает его движением.
   Переход по вкладке — это смена страницы, и индикатор, как в MUI, ЕДЕТ со старого места
   на новое: скрипт кита называет индикатор старой и новой страницы одним именем перехода
   (атрибут data-vt), и движок браузера анимирует его между документами (View Transitions).
   Без поддержки переходов или без скрипта страница меняется как обычно. */
.tabs { margin: var(--space-4) 0; border-bottom: 1px solid var(--md-sys-color-surface-variant); }
.tabs__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0; }
.tabs__item { min-width: 0; }
.tabs__link {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	min-height: 48px;
	padding: 0 var(--space-4);
	color: var(--md-sys-color-on-surface-variant);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
	border-bottom: 0;
	margin-bottom: 0;
	transition: color var(--motion-fast) var(--motion-ease),
	            background-color var(--motion-fast) var(--motion-ease);
}
.tabs__link::after {
	content: "";
	position: absolute;
	left: var(--space-4);
	right: var(--space-4);
	bottom: 0;
	height: 3px;
	border-radius: var(--md-sys-shape-corner-extra-small) var(--md-sys-shape-corner-extra-small) 0 0;
	background: var(--md-sys-color-primary);
	transform: scaleX(0);
	transition: transform var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized);
}
.tabs__link:hover { --_state: var(--state-hover); color: var(--md-sys-color-on-surface); text-decoration: none; }
.tabs__link:active { --_state: var(--state-pressed); }
.tabs__link--active, .tabs__link--active:hover { color: var(--md-sys-color-primary); }
.tabs__link--active::after { transform: none; }
.tabs__link[data-vt]::after { view-transition-name: oscript-ui-tabs-indicator; }
.tabs__link--plain { cursor: default; }
.tabs__link--plain:hover { --_state: 0%; }
.tabs--settings { margin-top: 0; }
.tabs__count {
	min-width: 16px;
	padding: 0 var(--space-1);
	border-radius: var(--md-sys-shape-corner-full);
	font: var(--type-label-small);
	text-align: center;
	background: var(--md-sys-color-surface-container-highest);
	color: var(--md-sys-color-on-surface-variant);
}
.tabs__link--active .tabs__count { background: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }

/* --- компонент: пустое состояние --- */

.empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-2);
	text-align: center;
	padding: var(--space-7) var(--space-5);
	border: 0;
	border-radius: var(--md-sys-shape-corner-large);
	background: var(--md-sys-color-surface-container-low);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.empty__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 64px;
	height: 64px;
	margin-bottom: var(--space-2);
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-surface-container-highest);
	font-size: 32px;
	line-height: 1;
	color: var(--md-sys-color-on-surface-variant);
}
.empty__title { margin: 0; font: var(--type-title-medium); letter-spacing: var(--md-sys-typescale-title-medium-tracking); color: var(--md-sys-color-on-surface); }
.empty__text { margin: 0; max-width: 60ch; }
.empty__action { margin-top: var(--space-2); }
/* Два пути из пустоты (дев-сборка 9, пробел хаба П4) — рядом одной группой по центру,
   главный первым; на телефоне столбиком во всю ширину пустоты: две кнопки рядом на 358 px
   сжимали подписи. */
.empty__actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-2);
	margin-top: var(--space-2);
}
@media (max-width: 600px) {
	.empty__actions { flex-direction: column; align-self: stretch; }
	.empty__actions > .button { width: 100%; }
}
.empty--compact { padding: var(--space-4) var(--space-3); gap: var(--space-1); }
.empty--compact .empty__icon { width: 40px; height: 40px; margin-bottom: 0; font-size: 20px; }
/* Ритм у корня (дев-сборка 9, П4): пустое состояние — такой же блок страницы, как карточка
   и секция, и держит ту же отбивку снизу; без неё пустота и карточка под ней стояли
   вплотную, рамка к рамке (аудит превью 3, А3). Последнему ребёнку контейнера отбивка
   не нужна — её даёт поле контейнера; компактному в ячейке — тоже. Специфичность нулевая
   (:where): контейнер со своим зазором (флекс с gap), снимающий маргины детей, старше. */
:where(.empty:not(.empty--compact):not(:last-child)) { margin-bottom: var(--space-4); }

/* --- компонент: опасная зона ---
   Формы компонент не печатает (вложенная форма — невалидный HTML): он живёт внутри
   формы секции настроек и добавляет к ней подтверждение и опасную кнопку. */

.danger-zone {
	border: 1px solid var(--md-sys-color-error);
	border-radius: var(--md-sys-shape-corner-medium);
	background: color-mix(in srgb, var(--md-sys-color-error) var(--state-hover), transparent);
	padding: var(--space-5);
	margin: var(--space-4) 0;
}
.danger-zone__head { margin-bottom: var(--space-3); }
.danger-zone__title {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	margin: 0;
	font: var(--type-title-large);
	color: var(--md-sys-color-error);
}
.danger-zone__text { margin: var(--space-2) 0 0; color: var(--md-sys-color-on-surface); max-width: 70ch; }
.danger-zone__body > :first-child { margin-top: 0; }
.danger-zone__confirm { margin-top: var(--space-3); max-width: 420px; }
.danger-zone__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* --- компонент: шапка объекта --- */

.objhead { margin-bottom: var(--space-5); }
.objhead__top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.objhead__title { display: flex; align-items: baseline; gap: var(--space-2); margin: 0; flex-wrap: wrap; }
.objhead__title .icon { color: var(--md-sys-color-primary); align-self: center; }
/* Признак у имени — значок размером с букву имени, по средней линии строки. Последний
   символ имени и значки — неразрывный кусок: значок не уходит на строку один, а перенос
   внутри самого имени остаётся прежним. */
.objhead__tail { white-space: nowrap; }
.objhead__mark { margin-left: var(--space-1); }
.objhead__mark .icon { width: 1em; height: 1em; vertical-align: middle; }
.objhead__kind {
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	color: var(--md-sys-color-on-surface-variant);
}
.objhead__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.objhead__chips { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-2); }
.objhead__lead { color: var(--md-sys-color-on-surface-variant); margin: var(--space-2) 0 0; max-width: 70ch; }
.objhead__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1) var(--space-3);
	margin-top: var(--space-2);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
}
/* Разделитель — флекс-элемент строки, поэтому к его margin добавляется gap строки:
   вместе они дают тот же отступ, что был у ряда без значков. */
.objhead__meta-item { display: inline-flex; align-items: center; gap: var(--space-1); }
.objhead__meta-item + .objhead__meta-item::before { content: "·"; margin-right: var(--space-2); }
.objhead__cmd { margin-top: var(--space-4); }

/* --- компонент: карточка объекта --- */

.objcard-list { display: flex; flex-direction: column; gap: var(--space-3); }
/* Три части в колонку: заголовок, описание, метаданные. Метаданные прижаты к низу
   (margin-top: auto), поэтому в ряду карточек они стоят на одной линии независимо от
   длины описания. height: 100% работает только в ячейке сетки (там высота ряда задана);
   в .objcard-list высоты нет, и правило молчит. */
.objcard {
	display: flex;
	flex-direction: column;
	height: 100%;
	background: var(--surface-card);
	border-radius: var(--md-sys-shape-corner-large);
	box-shadow: var(--elevation-ring-level1);
	padding: var(--space-3) var(--space-4);
	transition: box-shadow var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-standard);
}
.objcard:hover { box-shadow: var(--elevation-ring-level2); }
/* Шапка карточки: имя, плашки, действия. Имя — главное в карточке (M3 Cards: заголовок
   первым), поэтому сжимается последним и не уже 8–9 знаков: основа 0 и min-width 9ch, а
   плашки и действия держат свою ширину (flex: none). Не хватает места на всё в строке —
   ряд переносится, а не давит имя в ноль, как было (аудит M3, S1: в узкой карточке сетки
   с плашкой и кнопкой «Настройки» имя пропадало целиком).
   В узкой шапке (меньше 28rem — карточка в колонке сетки, телефон) плашки уходят второй
   строкой ПОД имя, а действия остаются справа от него: так имя получает всю ширину, кроме
   кнопки. Шапка — контейнер запросов: ширину решает сама карточка, а не окно, поэтому
   одна и та же карточка в широком списке и в сетке в три колонки ведёт себя по-своему.
   Контейнер — только по ширине (inline-size): высота шапки по-прежнему растёт под ряд. */
.objcard__head {
	display: flex;
	align-items: center;
	gap: var(--space-2) var(--space-3);
	flex-wrap: wrap;
	container-type: inline-size;
}
/* Имя — блок со строкой в одну линию и многоточием, а не flex: многоточие умеет только
   блочный контейнер, и так оно работает и у имени-ссылки, и у имени-текста. Подсказку с
   именем целиком у обрезанного имени ставит скрипт кита (title на наведении). */
.objcard__name {
	display: block;
	margin: 0;
	font: var(--type-title-medium);
	letter-spacing: var(--md-sys-typescale-title-medium-tracking);
	flex: 1 1 0;
	min-width: min(9ch, 100%);
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
.objcard__name .icon { color: var(--md-sys-color-primary); margin-right: var(--space-2); }
.objcard__chips { display: flex; flex-wrap: wrap; gap: var(--space-1); flex: none; max-width: 100%; }
.objcard__actions { margin-left: auto; display: flex; gap: var(--space-2); flex: none; }
@container (max-width: 28rem) {
	.objcard__chips { order: 1; flex: 1 0 100%; }
}
/* Описание — ровно две строки с многоточием: карточка перестаёт расти под текст, а ряд
   остаётся ровным. Обрезает БРАУЗЕР по строкам, сервер отдаёт описание целиком. */
.objcard__desc {
	color: var(--md-sys-color-on-surface-variant);
	margin: var(--space-1) 0 0;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
}
.objcard__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1) var(--space-3);
	margin-top: auto;
	padding-top: var(--space-2);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-small);
	letter-spacing: var(--md-sys-typescale-body-small-tracking);
}
/* Значок вместо слова-подписи: пара «значок + значение» не разрывается переносом, а сам
   ряд переносится по элементам. Цвет значка — унаследованный muted ряда, своего у него
   нет; размер чуть крупнее текста ряда, иначе контур не читается. Слово-подпись остаётся
   в разметке визуально скрытой парой (.visually-hidden) — её читает скринридер. */
.objcard__meta-item {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	white-space: nowrap;
}
.objcard__meta-item .icon,
.objhead__meta-item .icon { width: 14px; height: 14px; }

/* --- компонент: пагинация --- */

.pagination {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	margin-top: var(--space-5);
	flex-wrap: wrap;
}
/* Шаг пагинации — кнопка Material с обводкой: пилюля 40px, подпись label-large цветом
   primary, слой состояния на наведении. */
.pagination__link {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 40px;
	padding: 0 var(--space-4);
	border: 1px solid var(--md-sys-color-outline);
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-primary);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.pagination__link:hover { --_state: var(--state-hover); text-decoration: none; }
.pagination__link:active { --_state: var(--state-pressed); }
.pagination__link--prev { margin-right: auto; }
.pagination__link--next { margin-left: auto; }
.pagination__status { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); }

/* --- компонент: плитка показателя ---
   Плитка кликабельна целиком (вариант .tile--link), а не только её значение: показатель,
   от которого нельзя перейти к стоящим за ним данным, — украшение, а не навигация. */

.tile {
	display: flex;
	flex-direction: column;
	gap: 2px;
	position: relative;
	background: var(--surface-card);
	border: 0;
	border-radius: var(--md-sys-shape-corner-large);
	box-shadow: var(--elevation-ring-level1);
	padding: var(--space-4) var(--space-5);
	min-width: 140px;
	color: var(--md-sys-color-on-surface);
	transition: box-shadow var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-standard),
	            background-color var(--motion-fast) var(--motion-ease);
}
.tile__value {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	font: var(--type-headline-medium);
}
.tile__value .icon { color: var(--md-sys-color-primary); font-size: 24px; }
.tile__label { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); }
.tile__hint { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-small); }
.tile--link:hover { text-decoration: none; box-shadow: var(--elevation-ring-level2); }
.tile--ok .tile__value { color: var(--md-sys-color-success); }
.tile--warn .tile__value { color: var(--md-sys-color-warning); }
.tile--err .tile__value { color: var(--md-sys-color-error); }
.tile--accent .tile__value { color: var(--md-sys-color-primary); }

/* --- ряд плашек в строку и его свёрнутый хвост ---
   Раскладку даёт .tag-row, здесь — только «в строку» и сама свёртка: хвост прячется
   в <details>, раскрытие без единой строки JS, маркер <summary> убран, чтобы счётчик
   выглядел плашкой. Так показывают длинный ряд: важные плашки видны всегда, остальные
   лежат за счётчиком и не разгоняют строку на пол-экрана. */

.tag-row--inline { display: inline-flex; align-items: center; }
.tag-row__more { display: inline-flex; align-items: center; }
.tag-row__count {
	cursor: pointer;
	list-style: none;
	font: var(--type-label-medium);
	color: var(--md-sys-color-on-surface-variant);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-small);
	padding: 2px var(--space-3);
}
.tag-row__count::-webkit-details-marker { display: none; }
.tag-row__count:hover { color: var(--md-sys-color-on-surface); background: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-hover), transparent); }
.tag-row__rest { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); margin-left: var(--space-1); }

/* --- компонент: сетка ---
   Одна раскладка на все случаи: россыпь частных сеток по-разному ведёт себя на узком
   экране, и предсказать её поведение по имени класса нельзя. Число колонок — по
   allowlist, поэтому набор классов конечен и полностью покрыт правилами. */

.grid { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.grid__cell { flex: 1 1 auto; min-width: 0; }
.grid--auto > .grid__cell { flex: 0 1 auto; }
/* Колонки — НАСТОЯЩАЯ сетка, а не flex-grow. На flex ширину ячейки решал остаток строки,
   поэтому неполный последний ряд раздувался: две карточки занимали место шести. Ширину
   задаёт трек, и она одинакова в любом ряду; высота выравнивается сама (align-items:
   stretch), а карточка тянется на всю ячейку своим height: 100%.
   Раскладки в 1–2 колонки — это ДВЕ КОЛОНКИ СОДЕРЖИМОГО (текст рядом с текстом, список
   рядом со списком): им нужен auto-fit, чтобы пара блоков занимала ширину целиком,
   как на flex.
   Раскладки в 3–4 колонки — СЕТКА КАРТОЧЕК, и число в имени — ПОТОЛОК: колонок не больше
   трёх (четырёх) на любой ширине, а уже экрана их меньше по точкам перелома. Прежний
   auto-fill (колонки по 280 и 220 px, сколько влезет) на широком мониторе ставил в ряд
   «трёх колонок» четыре карточки, и шесть карточек ложились рядом 4 + 2. Теперь шесть
   карточек в .grid--3 заполняют ряды всегда: 3 + 3, 2 + 2 + 2 или по одной.
   minmax(0, 1fr): длинное слово в карточке не раздвигает свою колонку шире соседних. */
.grid--1 { display: grid; grid-template-columns: 1fr; }
.grid--2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); }
.grid--3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .grid--4 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 900px) { .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 760px) { .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .grid--3 { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 520px) { .grid--4 { grid-template-columns: minmax(0, 1fr); } }
.grid--tight { gap: var(--space-2); }
/* Полные ряды (.grid--full-rows, слово ПолныеРяды): на каждой ширине ячейки неполного
   последнего ряда не показываются. Число карточек лист не знает, поэтому ячейка находит себя
   сама: nth-child — её место в ряду, nth-last-child — сколько ячеек после неё. Первая ячейка
   ряда, за которой меньше, чем колонок, — начало хвоста; вторая и третья — по тому же счёту.
   Первый ряд не прячется никогда (счёт с 4n+5, 3n+4, 2n+3): три карточки в четырёх колонках
   остаются тремя, а не пустой сеткой. Восемь карточек в четырёх колонках дают 4 + 4, в трёх — 3 + 3 (две прячутся), в двух —
   четыре ряда, в одной — все восемь. Каждое правило действует только на своём отрезке ширин:
   на узком экране правило широкого спрятало бы не тот хвост. Отрезки — те же точки
   перелома, что у потолка колонок выше. */
@media (min-width: 1001px) {
	.grid--4.grid--full-rows > .grid__cell:nth-child(4n+5):nth-last-child(-n+3),
	.grid--4.grid--full-rows > .grid__cell:nth-child(4n+6):nth-last-child(-n+2),
	.grid--4.grid--full-rows > .grid__cell:nth-child(4n+7):nth-last-child(1) { display: none; }
}
@media (min-width: 761px) and (max-width: 1000px) {
	.grid--4.grid--full-rows > .grid__cell:nth-child(3n+4):nth-last-child(-n+2),
	.grid--4.grid--full-rows > .grid__cell:nth-child(3n+5):nth-last-child(1) { display: none; }
}
@media (min-width: 521px) and (max-width: 760px) {
	.grid--4.grid--full-rows > .grid__cell:nth-child(2n+3):nth-last-child(1) { display: none; }
}
@media (min-width: 901px) {
	.grid--3.grid--full-rows > .grid__cell:nth-child(3n+4):nth-last-child(-n+2),
	.grid--3.grid--full-rows > .grid__cell:nth-child(3n+5):nth-last-child(1) { display: none; }
}
@media (min-width: 601px) and (max-width: 900px) {
	.grid--3.grid--full-rows > .grid__cell:nth-child(2n+3):nth-last-child(1) { display: none; }
}

/* --- страницы: формы, отборы, свёртки --- */

/* Вертикальная форма: стопка полей кита с одинаковым зазором. Поверхности не рисует —
   её даёт карточка или секция настроек, внутри которых форма и живёт. */
.form-stack { display: flex; flex-direction: column; gap: var(--space-3); max-width: 560px; }
/* Раздел длинной формы — группа полей с подписью-заголовком. Рамки нет: раздел от раздела
   отделяет черта сверху. Легенда плавает на всю ширину — так она остаётся первым ребёнком
   fieldset и при этом не рвёт черту. Вложенные группы внутри раздела — без рамок, их
   подпись — как подпись поля. */
.form-section { border: 0; margin: 0; padding: var(--space-4) 0 0; min-width: 0; border-top: 1px solid var(--md-sys-color-outline-variant); }
.form-section:first-child { border-top: 0; padding-top: 0; }
.form-section > legend {
	float: left;
	width: 100%;
	padding: 0;
	margin: 0 0 var(--space-3);
	font: var(--type-title-medium);
	letter-spacing: var(--md-sys-typescale-title-medium-tracking);
	color: var(--md-sys-color-on-surface);
}
/* clear — у каждого, а не у соседа легенды: соседом бывает скрытое поле, а у него обтекание
   не действует, и легенда вложенной группы уезжала вправо за край. */
.form-section > :not(legend) { clear: both; }
.form-section > legend + * { margin-top: 0; }
.form-section > * + * { margin-top: var(--space-3); }
.form-section fieldset { border: 0; margin-left: 0; margin-right: 0; padding: 0; min-width: 0; }
.form-section fieldset > legend { padding: 0; margin-bottom: var(--space-1); font: var(--type-body-medium); color: var(--md-sys-color-on-surface-variant); }
/* Примечание внутри раздела формы — пояснение к полям, а не основной текст: размер подсказки
   поля, чтобы не спорить с подписью раздела. */
.form-section p.muted { font: var(--type-body-medium); }
.form-stack > .button { align-self: flex-start; }

/* Форма-строка внутри ячейки таблицы: кнопка (и иногда пара полей) в одну линию. */
.form-inline { display: inline-flex; align-items: center; gap: var(--space-2); margin: 0; }

/* Полоса отборов над списком: поле поиска + кнопка в одну строку.

   flex-end равняет МАРГИН-БОКСЫ элементов, поэтому кнопка вставала по нижней границе
   отступа поля, а не по нижней границе его контрола — визуально на 16 px ниже.
   Починка не здесь, а у .field: интервалы больше не живут на самих полях, и низ
   маргин-бокса поля совпал с низом ввода.
   С полем Material правило стало flex-start: контейнер поля — 44px, кнопка — 36px, и низом
   к низу она садилась ниже середины поля, а с пояснением под полем — и вовсе к пояснению.
   По верху и со сдвигом на половину разницы высот кнопка стоит ровно по центру контрола,
   что бы ни печаталось под ним. baseline по-прежнему не годится: равняло бы по ТЕКСТУ. */
.filters { display: flex; align-items: flex-start; gap: var(--space-2); margin: var(--space-4) 0; }
.filters > .field { flex: 1; min-width: 0; }
.filters > .button { margin-top: var(--space-1); }
/* Строка поиска со значком (0.13, дев-сборка 6): кнопка-значок «поиск» внутри рамки поля,
   справа — trailing icon строки поиска Material. Поле занимает всю строку, ввод отступает
   справа под значок, значок стоит по центру контрола (44 − 36 = по 4 сверху и снизу), что
   бы ни печаталось под полем. На телефоне ничего не переносится: кнопки снаружи нет. */
.filters > .search-bar { position: relative; flex: 1 1 auto; min-width: 0; }
.search-bar > .field > .field__control { padding-right: 48px; }
.search-bar > .button { position: absolute; top: var(--space-1); right: var(--space-1); }
/* На телефоне поля отбора встают каждое во всю строку, а кнопка — под ними: в одну строку
   три поля и кнопка сжимались до нечитаемого. */
@media (max-width: 640px) {
	.filters { flex-wrap: wrap; }
	.filters > .field { flex: 1 1 100%; }
	.filters > .button { margin-top: 0; }
}
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Чип фильтра Material: высота 28px (плотнее 32 спецификации — ряд чипов над списком не
   должен быть выше строки списка), пилюля, обводка outline-variant;
   выбранный — заливка secondary-container без обводки и галочка перед подписью. */
.chip {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 28px;
	padding: 0 var(--space-4);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-full);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	text-decoration: none;
	transition: background-color var(--motion-fast) var(--motion-ease), padding var(--motion-fast) var(--motion-ease);
}
.chip:hover { --_state: var(--state-hover); text-decoration: none; }
.chip:active { --_state: var(--state-pressed); }
.chip--active {
	--_bg: var(--md-sys-color-secondary-container);
	color: var(--md-sys-color-on-secondary-container);
	border-color: transparent;
	padding-left: var(--space-2);
}
.chip--active::before {
	content: "";
	width: 5px;
	height: 10px;
	margin: -3px var(--space-1) 0 var(--space-1);
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate(45deg);
}

/* Раскладку чипов держит .tag-row внутри, здесь — только отбивка от списка ниже. */
.chips-bar { margin-bottom: var(--space-3); }

/* Свёрнутая группа списка: раскрытие без JS. */
.fold {
	background: var(--surface-card);
	border-radius: var(--md-sys-shape-corner-large);
	box-shadow: var(--elevation-ring-level1);
	padding: var(--space-3) var(--space-4);
	margin-top: var(--space-4);
}
.fold__head { cursor: pointer; color: var(--md-sys-color-on-surface-variant); font: var(--type-label-large); }
.fold__head:hover { color: var(--md-sys-color-on-surface); }
/* Шапка врезки и подробности со значком и плашками (Раскрывашка.Шапка(Иконка, Текст, Плашка,
   Плашка) — 0.13, дев-сборка 7). Свёрнутая строка — строчная (list-item ради маркера), и дети
   шапки слипались: значок прилипал к подписи, плашки — к ней и друг к другу. Теперь шапка —
   строка флекса с зазором 8 px, как шапка записи реестра: зазор встаёт и между текстом и
   узлами (текст — свой анонимный элемент строки), тесная строка переносится. Флекс снимает
   нативный маркер, поэтому маркер свой — тот же треугольник впереди, повёрнутый у открытой. */
.fold > .fold__head,
.fold-detail > summary {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-1) var(--space-2);
	list-style: none;
}
/* Маркер — шеврон, как у дерева галок и свёрнутой секции (аудит M3 №34, дев-сборка 8): один
   набор значков на весь интерфейс, а не треугольник браузера. Простая раскрывашка (вид
   «простая» — details без классов) получает тот же шеврон: у неё оставался системный «▸». */
details:not([class]) > summary:not([class]) {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-1) var(--space-2);
	list-style: none;
	cursor: pointer;
}
.fold > .fold__head::-webkit-details-marker,
.fold-detail > summary::-webkit-details-marker,
details:not([class]) > summary:not([class])::-webkit-details-marker { display: none; }
.fold > .fold__head::before,
.fold-detail > summary::before,
details:not([class]) > summary:not([class])::before {
	content: "";
	flex: none;
	width: .48em;
	height: .48em;
	margin: 0 .1em 0 .05em;
	border: solid currentColor;
	border-width: 0 1.5px 1.5px 0;
	transform: rotate(-45deg);
	transition: transform var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
.fold[open] > .fold__head::before,
.fold-detail[open] > summary::before,
details:not([class])[open] > summary:not([class])::before { transform: rotate(45deg); }
/* плашке в строке текста отступ справа нужен, в строке флекса зазор даёт gap */
.fold > .fold__head > .tag,
.fold-detail > summary > .tag { margin-right: 0; }

/* Отрендеренный README: чужой (санитизированный) HTML, которому нужны свои отступы. */
.prose { overflow-x: auto; }
.prose pre {
	background: var(--md-sys-color-surface-container-highest);
	padding: var(--space-3) var(--space-4);
	border-radius: var(--md-sys-shape-corner-small);
	overflow-x: auto;
}
/* Подсветка кода README (shiki, темы GitHub — цвета печатаются в разметку блока) в светлой теме
   (аудит M3 №3, дев-сборка 8): github-light рассчитана на белый фон, и её оранжевый и красный
   токены не дотягивали до 4.5:1 — #e36209 на фоне блока 2.7:1, на белом 3.5:1. Цвета токенов
   приходят в атрибуте style, переписать их листом нельзя — содержимое блока притемняется
   фильтром: цвет токена 0.7 от своего, и самый бледный токен на белом — 6.3:1, на
   surface-container-highest — 4.9:1. Оттенки остаются те же; тёмная тема не меняется. */
html[data-theme="light"] .prose pre.shiki > code { filter: brightness(.7); }
.prose img { max-width: 100%; }
.prose table { box-shadow: none; }

/* --- каркас настроек: дом → дерево разделов → секции → sticky-панель --- */

.settings { display: block; }
.settings__head { margin-bottom: var(--space-5); }
.settings__title {
	font: var(--type-headline-medium);
	margin: 0;
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	flex-wrap: wrap;
}
.settings__object {
	font-family: var(--md-ref-typeface-mono);
	font-size: var(--md-sys-typescale-body-large-size);
	color: var(--md-sys-color-primary);
}
.settings__lead { color: var(--md-sys-color-on-surface-variant); margin: var(--space-2) 0 0; max-width: 70ch; }
/* Имя и значок текущего раздела под заголовком дома. На широком экране не нужно — там
   колонка разделов с подсвеченным пунктом; видно оно на телефоне с единым меню, где
   колонку заменяет группа разделов в шторке (см. медиа-блок дома настроек). */
.settings__current {
	display: none;
	align-items: center;
	gap: var(--space-2);
	margin: var(--space-3) 0 0;
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
}
.settings__current .icon { width: 20px; height: 20px; }

/* align-items у колонки — поперечный размер, то есть ШИРИНА: с flex-start панелям хватало
   ширины по содержимому, и широкая таблица уносила вбок всю страницу вместо того, чтобы
   прокручиваться внутри своей .table-scroll. На узком экране раскладка становится колонкой
   и выравнивание меняется на stretch — см. медиа-блок дома настроек. */
.settings__layout { display: flex; align-items: flex-start; gap: var(--space-6); }
.settings__panes { flex: 1; min-width: 0; }

.settings__nav { flex: 0 0 220px; position: sticky; top: var(--space-4); }

.tree--settings > .tree__list { gap: 2px; }

.crumbs { margin-bottom: var(--space-3); }
.crumbs__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	color: var(--md-sys-color-on-surface-variant);
}
.crumbs__item + .crumbs__item::before { content: "/"; margin-right: var(--space-2); opacity: .6; }
/* Крошка-ссылка — того же тона, что и строка крошек: путь читается подряд, а не пёстрой
   цепочкой ссылок; ссылкой её выдаёт подчёркивание под указателем и фокусом. */
.crumbs__link { color: inherit; text-decoration: none; }
.crumbs__link:hover, .crumbs__link:focus-visible { color: var(--md-sys-color-primary); text-decoration: underline; }

/* Секция настроек — карточка Material, залитая (filled) поверхностью карточки --surface-card:
   на странице настроек секций много, и тень у каждой рябила бы. Контур даёт кольцо 1px
   outline-variant без тени — в тёмной теме секция иначе сливалась с фоном. */
.section {
	background: var(--surface-card);
	border-radius: var(--md-sys-shape-corner-large);
	box-shadow: var(--elevation-ring);
	padding: var(--space-5);
	margin-bottom: var(--space-4);
}

/* Вложенная поверхность — кольцом, без своей тени (аудит M3 №11, дев-сборка 8). Карточка,
   карточка объекта, плитка и свёртка внутри карточки или секции в светлой теме клали свою
   тень уровня 1 в тень родителя: две тени друг в друге читались «карточкой в карточке».
   Material держит карточки на уровне 0–1 и отличает вложенное тоном и контуром, а не тенью:
   внутри остаётся кольцо outline-variant, а на наведении — тень уровня 1, на ступень ниже,
   чем у карточки на странице. Тёмная тема не меняется: тень там и так не видна. */
.card .card, .card .objcard, .card .tile, .card .fold,
.section .card, .section .objcard, .section .tile, .section .fold { box-shadow: var(--elevation-ring); }
.card .objcard:hover, .section .objcard:hover,
.card .tile--link:hover, .section .tile--link:hover { box-shadow: var(--elevation-ring-level1); }
.section__head { margin-bottom: var(--space-4); }
.section__title {
	font: var(--type-title-large);
	margin: 0;
	display: flex;
	align-items: center;
	gap: var(--space-2);
	border: 0;
	padding: 0;
}
.section__title .icon { color: var(--md-sys-color-primary); }
.section__lead { color: var(--md-sys-color-on-surface-variant); margin: var(--space-2) 0 0; max-width: 70ch; }
/* Шапка с действиями: кнопка «Добавить» стоит в одном месте на всех экранах — справа от
   заголовка и лида, по верху. В разметке слот идёт ПОСЛЕ них, поэтому место справа даёт
   сетка, а не порядок: на узком экране слот без перестановки встаёт под лидом, и порядок
   чтения совпадает с порядком на глаз. Шапка без действий остаётся обычным блоком. */
.section__head--actions {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	column-gap: var(--space-4);
	align-items: start;
}
.section__head--actions > :not(.section__actions) { grid-column: 1; }
.section__actions {
	grid-column: 2;
	grid-row: 1 / span 2;
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	align-items: center;
	gap: var(--space-2);
}
.section__actions > form { margin: 0; }
@media (max-width: 640px) {
	/* Под лидом во всю ширину: справа от заголовка кнопка сжала бы его до пары слов. */
	.section__head--actions { grid-template-columns: minmax(0, 1fr); }
	.section__actions { grid-column: 1; grid-row: auto; margin-top: var(--space-3); }
	.section__actions > * { flex: 1 1 100%; }
	.section__actions .button { width: 100%; }
}
/* Ритм внутри секции настроек. Тело секции — тот самый контейнер, в котором лежит
   большинство полей приложения: РендерерНастроек.СекцияHTML всегда заворачивает Тело
   секции в .section__body, а поля туда кладут дома настроек. Собственных маргинов
   у .field/.toggle нет, поэтому держать ритм здесь больше нечему.

   Почему НЕ flex+gap, как у .form-stack: тело секции держит произвольное содержимое —
   таблицы, списки, вложенные блоки, у каждого свои маргины. Во flex-боксе маргины
   детей СКЛАДЫВАЮТСЯ с gap (ровно та двойная величина, от которой уходили, снимая
   интервал с самих полей), а в обычном потоке margin-top соседа СХЛОПЫВАЕТСЯ с его
   собственным — владелец интервала остаётся один, и таблицы не растягиваются
   по align-items: stretch. */
.section__body > * + * { margin-top: var(--space-4); }
.section__body > :first-child { margin-top: 0; }
.section__body > :last-child { margin-bottom: 0; }
/* .section__form сам полей НЕ содержит: между формой и полями всегда стоит
   .section__body (см. РендерерНастроек.ФормаСекции), поэтому ритм ему не нужен. */
.section__form { margin: 0; }
/* Кат секции: секция действия уходит под нативную <details> целиком, вместе со своей
   формой. Раскрывает браузер — скрипта здесь нет. Поверхность даёт .section, а подпись
   ката обязана читаться КНОПКОЙ: серая строчка того же цвета, что лид секции выше,
   не говорит, что на неё можно нажать. Нативный маркер снят (кнопочной формы у него нет),
   взамен — шеврон, глядящий вниз у закрытого ката и вверх у раскрытого. */
.section__fold > .fold__head {
	--_bg: transparent;
	--_state: 0%;
	list-style: none;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 36px;
	padding: 0 var(--space-4) 0 var(--space-5);
	border: 1px solid var(--md-sys-color-outline);
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-primary);
	background-color: color-mix(in srgb, currentColor var(--_state), var(--_bg));
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.section__fold > .fold__head::-webkit-details-marker { display: none; }
.section__fold > .fold__head:hover { --_state: var(--state-hover); }
.section__fold > .fold__head::after {
	content: "";
	width: .45em;
	height: .45em;
	margin-top: -.2em;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate(45deg);
	transition: transform var(--motion-medium) var(--md-sys-motion-easing-emphasized),
	            margin-top var(--motion-medium) var(--md-sys-motion-easing-emphasized);
}
.section__fold[open] > .fold__head::after { margin-top: .2em; transform: rotate(-135deg); }
.section__fold[open] > .fold__head { margin-bottom: var(--space-4); }

.section--danger { box-shadow: inset 0 0 0 1px var(--md-sys-color-error); }
.section--danger .section__title { color: var(--md-sys-color-error); }
.section--danger .section__title .icon { color: var(--md-sys-color-error); }

.list-note { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); margin: var(--space-2) 0 0; padding-left: var(--space-5); }

.section table, .section .table-scroll { margin-bottom: var(--space-4); }

/* --- раскрывашка-панель и надписи внутри её шапки --------------------------------
   Печатает эти классы кит (род «раскрывашка», виды надписи и картинки), поэтому имена
   в них ничего не знают о предмете: панель — нативная раскрывашка <details>, свёрнутая
   строка отвечает «что это», развёрнутая отдаёт содержимое, и раскрытие работает без
   единой строки JS. */

/* Панель — карточка Material с обводкой (outlined), раскрывается как аккордеон MUI:
   высота содержимого едет, шеврон поворачивается (правила движения внизу листа). */
.panel {
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	background: var(--md-sys-color-surface-container-low);
	overflow: clip;
}

.panel + .panel { margin-top: var(--space-2); }

.panel__head {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2);
	min-height: 48px;
	padding: var(--space-2) var(--space-4);
	cursor: pointer;
	line-height: 1.6;
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--_state), var(--_bg));
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.panel__head::-webkit-details-marker { display: none; }
.panel__head:hover { --_state: var(--state-hover); }
/* Признак раскрытия панели — шеврон справа, как у аккордеона Material/MUI (пробел хаба П3
   ui-хвост-единообразие, дев-сборка 8): нативный маркер снят флексом, а своего у панели не
   было — свёрнутая панель в форме не выглядела раскрываемой. Вниз — у свёрнутой, вверх —
   у открытой. У записи реестра (шапка-строка без раскрытия) и шага со скриптом его нет. */
.panel > .panel__head:not(.entry__head)::after {
	content: "";
	flex: none;
	width: .5em;
	height: .5em;
	margin: -.25em .25em 0 auto;
	border: solid currentColor;
	border-width: 0 1.5px 1.5px 0;
	color: var(--md-sys-color-on-surface-variant);
	transform: rotate(45deg);
	transition: transform var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
.panel[open] > .panel__head:not(.entry__head)::after { margin-top: .25em; transform: rotate(-135deg); }

.image-icon { width: 20px; height: 20px; flex: none; }
.text-name code { white-space: nowrap; }
.text-hint { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); }
.text-note code { font-size: var(--md-sys-typescale-body-medium-size); color: var(--md-sys-color-on-surface-variant); }
.panel__body {
	padding: var(--space-4);
	border-top: 1px solid var(--md-sys-color-outline-variant);
}

.panel__body > * + * { margin-top: var(--space-4); }

/* --- реестр: немногие записи — сводка, действия снаружи, тело с кнопками ------------
   Печатает эти классы узел Кит.Реестр (шаг 11). Запись — пункт списка с раскрывашкой-
   панелью внутри: свёрнутая строка отвечает «что это и в каком оно состоянии», тело —
   подробности и кнопки. Раскрытие — нативный <details>, без единой строки JS.

   Действия записи (тумблер, стрелки порядка, меню ⚙) лежат ВНЕ <summary> — иначе щелчок
   по тумблеру раскрывал бы запись — и стоят в разметке перед раскрывашкой. Они прижаты
   вправо обтеканием (float): свёрнутая строка — flex-контейнер, а значит свой контекст
   форматирования, и она сужается ровно на ширину действий, сколько бы их ни было. Ни
   расчёта ширины на сервере, ни скрипта, ни налезания длинного имени на тумблер. */

.entries {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* flow-root держит обтекание внутри своей записи: действия первой записи не цепляются
   за вторую. */
.entry { display: flow-root; }

.entry__actions {
	position: relative;
	z-index: 1;
	float: right;
	display: flex;
	align-items: center;
	gap: var(--space-1);
	/* высота — высота свёрнутой строки (min-height 48px под рамкой в 1px): обтекание
	   кончается вместе с ней и тело раскрытой записи не сужает */
	min-height: 48px;
	margin: 1px var(--space-3) 0 var(--space-2);
}
.entry__actions > form { display: flex; align-items: center; gap: var(--space-1); margin: 0; }
.entry__actions .toggle__box { gap: 0; }
/* Подпись тумблера в ряду действий не видна — место у свёрнутой строки, — но остаётся
   его именем для скринридера: тумблер без имени прочёлся бы «флажок, отмечен». */
.entry__actions .toggle__text,
.entry__actions .toggle__hint {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Тумблер с СохранятьСразу отправляет свою форму скриптом по изменению. Без скрипта
   отправить её нечем, кроме её же кнопки, — поэтому кнопка формы печатается всегда,
   а прячется только там, где скрипт есть. Других кнопок в форме тумблера не бывает.
   Не только в действиях записи реестра: где бы ни стояла форма тумблера — в теле секции,
   в карточке, сама по себе, — запасная кнопка отправки при работающем скрипте не видна.
   Прячется она только у формы, где, кроме тумблеров с СохранятьСразу, полей нет: у формы
   с другим полем кнопка — единственный способ отправить это поле. Движок без :has()
   правило выбросит, и кнопка останется — лишняя, но рабочая. */
/* Запасную кнопку тумблеру «сохранять сразу» в форме без своей кнопки печатает кит (0.13,
   дев-сборка 7): тумблер и кнопка-значок — в одной строке, при работающем скрипте кнопки нет
   — по тому же правилу, что у своей кнопки ниже: в форме с другим полем она остаётся, иначе
   то поле не отправить. Движок без :has() кнопку оставит — лишнюю, но рабочую. */
.toggle-save { display: flex; align-items: flex-start; gap: var(--space-2); min-width: 0; }
@media (scripting: enabled) {
	form:not(:has(input:not([type="hidden"]):not(.toggle__input[data-autosubmit]), select, textarea)) .toggle__save { display: none; }
}
@media (scripting: enabled) {
	.entry__actions form:has([data-autosubmit]) .button { display: none; }
	form:has(.toggle__input[data-autosubmit]):not(:has(input:not([type="hidden"]):not(.toggle__input[data-autosubmit]), select, textarea)) button.button[type="submit"] { display: none; }
}

/* У раскрытой записи её действия — кнопки внизу тела, на виду; меню ⚙ с теми же
   действиями было бы дублем. Без :has() меню остаётся видимым — дублем, а не дырой. */
.entry:has(> .entry__panel[open]) .entry__gear { display: none; }

/* Нижний предел ширины свёрнутой строки: если действия (без скрипта — с кнопкой формы
   тумблера) не оставили ей места, строка уходит под них целиком, а не сжимается в столбик
   по букве. */
.entry__head { flex-wrap: nowrap; align-items: flex-start; gap: var(--space-3); min-width: min(10rem, 100%); }
/* Номер и значок встают по строке имени, а не по середине многострочной сводки. */
.entry__num {
	flex: none;
	min-width: 1.5em;
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	line-height: var(--md-sys-typescale-title-medium-line-height);
	color: var(--md-sys-color-on-surface-variant);
	font-variant-numeric: tabular-nums;
}
.entry__icon,
.entry__image {
	flex: none;
	width: 24px;
	height: 24px;
}
.entry__icon { color: var(--md-sys-color-on-surface-variant); }
.entry__image { object-fit: contain; border-radius: var(--md-sys-shape-corner-extra-small); }
/* Свёрнутая строка без тела не раскрывается: курсор и слой наведения ей ни к чему. */
div.entry__head { cursor: default; }
div.entry__head:hover { --_state: 0%; }

/* Кнопки внизу тела — те же действия, что в меню ⚙, отбитые чертой от подробностей. */
.entry__buttons {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2);
	padding-top: var(--space-4);
	border-top: 1px solid var(--md-sys-color-outline-variant);
}
.panel__body > .entry__buttons:first-child { padding-top: 0; border-top: 0; }
.entry__buttons > form { display: flex; margin: 0; }

/* На телефоне кнопки встают столбиком во всю ширину — под палец, как действия секции. */
@media (max-width: 640px) {
	.entry__actions { margin-right: var(--space-2); }
	.entry__head { gap: var(--space-2); }
	.entry__buttons { flex-direction: column; align-items: stretch; }
	.entry__buttons > .button,
	.entry__buttons > form > .button { width: 100%; }
}

/* --- касание: цели не меньше 48 px ---------------------------------------------------
   Material требует под палец область 48×48 dp, а плотный облик кита рисует кнопку-значок
   в 36 (малую — в 32), крестик плашки списка — в 24 и кружок акцента — в 44 (аудит M3,
   S8: на 390 таких целей сотни). Видимый размер — решение плотности, его не трогаем:
   область касания расширяет невидимый псевдоэлемент, и только там, где указатель — палец
   (pointer: coarse). У кнопки — ::after: ::before у неё занят индикатором занятости. Значок
   «?» подсказки расширен всегда (правило .help::before). Ссылка, которая одна в ячейке
   таблицы, растягивается на всю высоту ряда и не ниже 48. */
@media (pointer: coarse) {
	.button--icon::after,
	.chips-in__remove::before,
	.swatches__item::after {
		content: "";
		position: absolute;
		left: 50%;
		top: 50%;
		width: 48px;
		height: 48px;
		transform: translate(-50%, -50%);
	}
	.chips-in__remove { position: relative; }
	td > a:only-child:not([class]) {
		display: flex;
		align-items: center;
		min-height: 48px;
		margin-block: calc(-1 * (var(--space-3) - 2px));
	}
}

/* --- телефон: карточка в карточке -------------------------------------------------
   На compact (до 600 px) вложенные поверхности не вкладываются: Material на узком экране
   держит одну панель и поля 16 px. Было: поле страницы 20 + поле секции 20 + кольцо и
   поле вложенной карточки или таблицы 16 — содержимому оставалось около 270 px из 390, и
   в светлой теме тени лежали одна в другой (аудит M3, S6).
   Теперь: боковое поле страницы и поле карточки и секции — 16 px (compact margin M3), а
   карточка, секция и карточка объекта ВНУТРИ карточки или секции становятся частью
   родителя — без тени, кольца, своей заливки и боковых полей; соседей разделяет черта
   outline-variant, как пункты списка. Таблица внутри — без своей обводки. Содержимое
   вложенного блока получает всю ширину родителя: на 390 — 326 px. Карточки объектов сетки
   — исключение (правило ниже): плоской становится родитель, а они остаются карточками.
   На широком экране всё как было: вложенная карточка там — карточка. */
@media (max-width: 600px) {
	.wrap { padding-left: var(--space-4); padding-right: var(--space-4); }
	.card, .section { padding: var(--space-4); }
	.card .card, .card .section, .card .objcard,
	.section .card, .section .section, .section .objcard {
		background: transparent;
		box-shadow: none;
		border-radius: 0;
		padding: var(--space-3) 0 0;
		margin-bottom: 0;
		border-top: 1px solid var(--md-sys-color-outline-variant);
	}
	.card .objcard:hover, .section .objcard:hover { box-shadow: none; }
	.card .grid, .section .grid { gap: var(--space-3); }
	.card .table-scroll, .section .table-scroll { border: 0; border-radius: 0; }
}

/* Секция со списком карточек на телефоне (0.13, дев-сборка 7). Правило выше делало плоскими
   карточки объектов СЕТКИ внутри карточки или секции: список «Недавно обновлённые» на 390
   разваливался в полосы с чертами — без рамки и фона, плашки торчали справа от имени (слово
   владельца к превью 3). Одна поверхность на compact — верная цель, но плоской становится
   ВНЕШНЯЯ карточка или секция, в которой лежит сетка (или список) карточек объектов: без
   кольца, заливки, тени и боковых полей, её заголовок и пояснение стоят на поле страницы 16.
   Карточки объектов внутри — карточки, как на широком экране: кольцо outline-variant,
   заливка поверхности карточки (surface-container в тёмной теме), скругление 16, свои поля,
   во всю ширину (на 390 — 358 px), зазор сетки --space-3, без черты между ними. Вложенная
   таблица и одиночная вложенная карточка — по правилу выше. Движок без :has() правила не
   применит — останется прежний вид, а не карточки в карточке.
   Дев-сборка 8: сетка простых карточек (.card в ячейках сетки — обзор групп, пулов) —
   тоже список карточек: на превью ui.4 блок пулов на 390 остался в рамке, а карточки пулов
   в нём стали полосами. Правило то же: плоская — внешняя, карточки сетки — карточки. */
@media (max-width: 600px) {
	@supports selector(:has(*)) {
		.card:has(> .card__body > .grid .objcard),
		.card:has(> .card__body > .objcard-list > .objcard),
		.section:has(> .section__body > .grid .objcard),
		.section:has(> .section__body > .objcard-list > .objcard),
		.card:has(> .card__body > .grid > .grid__cell > .card),
		.section:has(> .section__body > .grid > .grid__cell > .card) {
			background: transparent;
			box-shadow: none;
			border-radius: 0;
			padding-left: 0;
			padding-right: 0;
		}
		.card .grid > .grid__cell > .card,
		.section .grid > .grid__cell > .card {
			background: var(--surface-card);
			box-shadow: var(--elevation-ring-level1);
			border-radius: var(--md-sys-shape-corner-large);
			padding: var(--space-4);
			border-top: 0;
		}
		.card .grid > .grid__cell > .objcard,
		.section .grid > .grid__cell > .objcard,
		.card .objcard-list > .objcard,
		.section .objcard-list > .objcard {
			background: var(--surface-card);
			box-shadow: var(--elevation-ring-level1);
			border-radius: var(--md-sys-shape-corner-large);
			padding: var(--space-3) var(--space-4);
			border-top: 0;
		}
		.card .grid > .grid__cell > .objcard:hover,
		.section .grid > .grid__cell > .objcard:hover,
		.card .objcard-list > .objcard:hover,
		.section .objcard-list > .objcard:hover { box-shadow: var(--elevation-ring-level2); }
	}
}

/* --- модальное окно формы -------------------------------------------------------
   Открытый <dialog> уходит в верхний слой браузера: он не в потоке, поэтому ничего
   на странице не двигает, и его не обрезает предок с overflow. Открытие, закрытие
   по Esc и щелчку мимо, удержание фокуса и гашение фона — работа движка, скрипта
   здесь нет. Движок постарше покажет содержимое на месте: это ровно сегодняшний вид,
   а не пустой экран.
   Вид — диалог Material: surface-container-high, скругление extra-large (28px), уровень
   возвышения 3, скрим 32%. Появление — проявление с небольшим ростом, как Dialog у MUI
   (правила движения внизу листа). */

/* Ширина — базового диалога Material (280–560 dp): 35rem на широком экране, а на узком —
   экран без полей по 16 px. Широкое окно (шаги мастера, таблица) — 40rem своим видом. */
/* Заголовок окна без полей принимает фокус при открытии (скрипт, дев-сборка 8): он не элемент
   управления, и кольцо на нём не нужно — скринридер прочтёт его и без кольца. */
.modal__title[tabindex="-1"]:focus { outline: none; }
.modal {
	padding: 0;
	max-width: min(35rem, calc(100vw - var(--space-6)));
	width: 100%;
	border: 0;
	border-radius: var(--md-sys-shape-corner-extra-large);
	background: var(--md-sys-color-surface-container-high);
	color: var(--md-sys-color-on-surface);
	box-shadow: var(--md-sys-elevation-level3);
}
/* Модальное окно — по центру экрана, где бы его ни напечатали. Окно в теле карточки или
   секции — не первый ребёнок, и правило ритма контейнера (.card__body > * + *, у секции и
   записи реестра то же) давало ему margin-top 16px при прочих нулях: центрирование верхнего
   слоя пропадало, окно вставало у верхнего края поверх шапки (аудит M3, S2). */
.modal:modal { margin: auto; }
/* фон гасится скримом Material — тем же, что под шторкой и листом меню */
.modal::backdrop { background: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); }

/* Окно, напечатанное открытым без скрипта (?окно=…, отбитая форма), — не модальное: в верхний
   слой его поднимает только showModal(), а это скрипт. Без правила оно стояло position:
   absolute с z-index: auto, и следующая секция или запись реестра (у свёрнутой строки записи
   position: relative) рисовалась поверх окна и забирала себе щелчок в его поле. Теперь такое
   окно ведёт себя, как модальное: закреплено по центру экрана (margin: auto, как у :modal),
   выше шапки (z-index 20) и секций, ниже тостов (30); длинное — прокручивается само.
   Подложка: ::backdrop без showModal() не рисуется, поэтому затемнение — тень окна со спредом
   во весь экран (тень не перекрашивает само окно, как перекрасил бы его псевдоэлемент под ним),
   а щелчок мимо окна ловит прозрачный ::before во весь экран — страница под окном не
   нажимается, но прокручивается колесом и пальцем: замок прокрутки без скрипта не ставится.
   Закрывает окно крестик (команда close — движок, который её знает) или ссылка приложения на
   адрес без ?окно. Конечное состояние появления (.modal[open] ниже) от :modal не зависит.
   Движок без :modal правило выбросит — окно останется на месте в потоке, как раньше. */
.modal[open]:not(:modal) {
	position: fixed;
	inset: 0;
	z-index: 25;
	margin: auto;
	max-height: calc(100% - var(--space-6));
	overflow: auto;
	box-shadow: var(--md-sys-elevation-level3),
	            0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
}
.modal[open]:not(:modal)::before {
	content: "";
	position: fixed;
	inset: 0;
	z-index: -1;
}
.modal__box { padding: var(--space-5); }
.modal__box > h2:first-child, .modal__box > h3:first-child { font: var(--type-headline-small); margin: 0; }
.modal__box > * + * { margin-top: var(--space-4); }

.modal__actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: var(--space-2);
	margin-top: var(--space-5);
}

/* --- окно со слотами, подтверждение, окно по адресу (выпуск 0.11) ----------------
   Узел Кит.Окно: шапка (заголовок и крестик), лид, тело, действия. Рамка та же, что у окна
   объявленной формы выше. Подтверждение — окно вида «опасное»: заголовок цветом ошибки,
   последствия читаются в полный цвет текста, эхо-поле и кнопка цвета ошибки. Кнопка
   подтверждения выглядит недоступной, пока эхо-поле не совпало с образцом: без скрипта
   это делает лист (:has(:invalid)), а отправку с несовпавшим полем не пускает сам браузер
   (pattern + required); скрипт ещё и ставит кнопке disabled. */

.modal__head { display: flex; align-items: flex-start; gap: var(--space-2); }
.modal__title {
	flex: 1;
	min-width: 0;
	margin: 0;
	font: var(--type-headline-small);
	color: var(--md-sys-color-on-surface);
}
/* крестик — кнопка-значок кита; в угол шапки его ставит отрицательный отступ, чтобы
   слой состояния не упирался в заголовок */
.modal__head > .button { flex: none; margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 0; }
.modal__box > .modal__lead {
	margin-top: var(--space-2);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.modal__body > :first-child { margin-top: 0; }
.modal__body > * + * { margin-top: var(--space-4); }

.modal--wide { max-width: min(40rem, calc(100vw - var(--space-6))); }
.modal--danger .modal__title { color: var(--md-sys-color-error); }
.modal--confirm .modal__box > .modal__lead { color: var(--md-sys-color-on-surface); font: var(--type-body-large); }
/* Длинное слово без пробелов — адрес апстрима, ключ, хеш — в тексте окна переносится по знакам
   (пробел хаба П-пул-1, дев-сборка 8): со скриптом его обрезал край окна, без скрипта окно
   раздвигало страницу шире экрана. Обычные слова переносятся по пробелам, как прежде. */
.modal__title, .modal__lead, .modal__body, .modal__echo { overflow-wrap: anywhere; }
/* Эхо-поле подтверждения: имя, которое нужно набрать, — в подписи поля. Плавающая подпись
   в одну строку обрезала длинное имя многоточием, и его не было видно целиком: у эха подпись
   стоит над полем обычной строкой и переносится, а образец виден в пустом поле. */
.modal__echo > .field > .field__label {
	position: static;
	transform: none;
	max-width: none;
	overflow: visible;
	white-space: normal;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
}
.modal__echo > .field > .field__control { padding-top: 10px; padding-bottom: 10px; }
.modal__echo > .field[data-empty]:not(:focus-within):has(> .field__label) > .field__control::placeholder {
	color: var(--md-sys-color-on-surface-variant);
}
.modal__echo { max-width: 420px; }
.modal--confirm:has(.modal__echo :invalid) .modal__actions [name="confirmed"] {
	--_bg: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-container), transparent);
	--_fg: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-disabled-content), transparent);
	box-shadow: none;
	cursor: not-allowed;
}

/* Оболочка окна по адресу пуста, пока скрипт не положил в неё окно; тост ошибки загрузки
   стоит рядом с ней и всплывает снизу экрана поверх содержимого. */
.window-error {
	position: fixed;
	z-index: 30;
	left: var(--space-4);
	right: var(--space-4);
	bottom: var(--space-4);
	display: flex;
	justify-content: center;
	pointer-events: none;
}
.window-error[hidden] { display: none; }
.window-error > .toast { pointer-events: auto; max-width: 40rem; margin: 0; box-shadow: var(--md-sys-elevation-level3); }

/* На телефоне (до 640 px) базовое окно остаётся базовым: по центру, шириной в экран без
   полей по 16 px, действия под телом. Полноэкранный диалог Material — только для сложной
   задачи: окно со словом ВоВесьЭкран (.modal--full) и окно с шагами мастера ([data-steps]).
   Было: листом во весь экран вставало любое окно, и подтверждение из поля и двух кнопок
   оставляло огромную пустоту с действиями внизу (аудит M3, S5).
   Лист: прокручивается само окно, а действия прилипают к его низу и всегда под пальцем.
   Только у модального (:modal): окно, которое сервер напечатал открытым без скрипта,
   листом не становится — оно базовое по центру экрана и прокручивается само. Движок без :has() выбросит правила листа
   целиком — и любое окно у него останется базовым: законченный вид, а не сломанный. */
@media (max-width: 640px) {
	.modal:modal { width: calc(100% - var(--space-6)); max-width: calc(100% - var(--space-6)); }
	.modal--full:modal, .modal:modal:has([data-steps]) {
		width: 100%;
		max-width: 100%;
		height: 100%;
		max-height: 100%;
		margin: 0;
		border-radius: 0;
	}
	.modal--full:modal .modal__box, .modal:modal:has([data-steps]) .modal__box { display: flex; flex-direction: column; min-height: 100%; padding-bottom: 0; }
	.modal--full:modal .modal__body, .modal:modal:has([data-steps]) .modal__body { flex: 1 0 auto; }
	.modal--full:modal .modal__actions, .modal:modal:has([data-steps]) .modal__actions {
		position: sticky;
		bottom: 0;
		margin: var(--space-5) calc(-1 * var(--space-5)) 0;
		padding: var(--space-3) var(--space-5) calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
		background: var(--md-sys-color-surface-container-high);
		border-top: 1px solid var(--md-sys-color-outline-variant);
	}
	/* у прежнего окна формы последним идёт форма, а подвал — внутри неё */
	.modal--full:modal .modal__box > form:last-child, .modal:modal:has([data-steps]) .modal__box > form:last-child { flex: 1 0 auto; display: flex; flex-direction: column; }
	.modal--full:modal .modal__box > form:last-child > .modal__actions, .modal:modal:has([data-steps]) .modal__box > form:last-child > .modal__actions { margin-top: auto; }
}

/* --- врезка: шаг ВНУТРИ формы, а не своя секция ---------------------------------
   Пунктир и означает «часть формы»: карточка рисует сплошную рамку и читалась бы
   как самостоятельный блок. Так собирают подготовительный шаг — собственная кнопка
   врезки отправляет ту же <form> с другими name/value и заполняет поля ниже. */

.inset {
	padding: var(--space-4);
	border: 1px dashed var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	background: var(--md-sys-color-surface-container);
}

.inset > * + * { margin-top: var(--space-3); }

.inset__title {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
}
.inset__lead {
	display: block;
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
}

.choice-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Выбранная кнопка ряда обозначена ОДНИМ признаком — aria-current. Он же переносится
   скриптом при мгновенном переключении, поэтому вид кнопки и её доступное имя разъехаться
   не могут: вариант кнопки у всех ссылок ряда одинаковый. */
.choice-row .button[aria-current] {
	--_bg: var(--md-sys-color-secondary-container);
	--_fg: var(--md-sys-color-on-secondary-container);
	border-color: transparent;
}

/* Взаимоисключающий набор полей. Сервер печатает ВСЕ наборы, а невыбранные — с `disabled`
   и `hidden`: отключённая группа не отправляется, не ловит фокус и не держит `required`.
   Рамки у обёртки нет — она группирует поля для браузера, а не рисует рамку;
   интервал между полями внутри даёт общее правило `fieldset > * + *` выше. */
.choice-set { border: 0; padding: 0; margin: 0; min-width: 0; }
.choice-set[hidden] { display: none; }

/* --- движение --- */

@keyframes ui-toast {
	from { opacity: 0; transform: translateY(8px); }
	to { opacity: 1; transform: none; }
}

/* Рябь расходится от точки нажатия до размера, который скрипт задал кругу. */
@keyframes ui-ripple {
	from { transform: scale(0); }
	to { transform: scale(1); }
}

@keyframes ui-spin {
	to { transform: rotate(360deg); }
}

/* Раскрывашки — как аккордеон и Collapse у MUI: высота содержимого едет от нуля до своей.
   Анимировать до height: auto умеет только движок с interpolate-size и ::details-content;
   остальные открывают мгновенно, как раньше. */
@supports (interpolate-size: allow-keywords) and selector(::details-content) {
	details { interpolate-size: allow-keywords; }
	details::details-content {
		height: 0;
		overflow: clip;
		transition: height var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized),
		            content-visibility var(--md-sys-motion-duration-medium2) allow-discrete;
	}
	details[open]::details-content { height: auto; }
}

/* Диалог появляется, как Dialog у MUI: проявляется и чуть вырастает, скрим набирает
   плотность вместе с ним. Закрытый <dialog> снимается из верхнего слоя дискретно, поэтому
   overlay и display названы в переходе отдельно; движок, который так не умеет, открывает
   и закрывает окно мгновенно. */
.modal {
	opacity: 0;
	transform: scale(.92);
	transition: opacity var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
	            transform var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate),
	            overlay var(--md-sys-motion-duration-medium2) allow-discrete,
	            display var(--md-sys-motion-duration-medium2) allow-discrete;
}
.modal[open] { opacity: 1; transform: none; }
@starting-style {
	.modal[open] { opacity: 0; transform: scale(.92); }
}
.modal::backdrop {
	transition: background-color var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-standard),
	            overlay var(--md-sys-motion-duration-medium2) allow-discrete,
	            display var(--md-sys-motion-duration-medium2) allow-discrete;
}
@starting-style {
	.modal[open]::backdrop { background-color: transparent; }
}

/* Выпадающие панели широкого экрана растут из угла у кнопки, как Grow у MUI: проявляются
   и разворачиваются из уменьшенных. На телефоне у них свой выезд — листом и шторкой. */
@supports selector(:popover-open) {
	@media (min-width: 901px) {
		.menu__panel, .hubmenu {
			opacity: 0;
			transform: scale(.85, .7);
			transform-origin: top right;
			transition: opacity var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
			            transform var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-emphasized-decelerate),
			            overlay var(--md-sys-motion-duration-medium1) allow-discrete,
			            display var(--md-sys-motion-duration-medium1) allow-discrete;
		}
		.menu__panel:popover-open, .hubmenu:popover-open { opacity: 1; transform: none; }
		@starting-style {
			.menu__panel:popover-open, .hubmenu:popover-open { opacity: 0; transform: scale(.85, .7); }
		}
	}
}

/* Индикатор вкладок едет между страницами (имя перехода ставит скрипт кита), а сама
   страница меняется сразу: двигается только индикатор, как у Tabs в MUI. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
::view-transition-group(oscript-ui-tabs-indicator) {
	animation-duration: var(--md-sys-motion-duration-medium2);
	animation-timing-function: var(--md-sys-motion-easing-emphasized);
}
/* Псевдоэлементы перехода живут вне дерева документа, и общее правило «*» их не гасит. */
@media (prefers-reduced-motion: reduce) {
	::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}

/* --- компонент: полоса заполнения ---
   Показание, а не орган управления: ширина берётся классом-ступенью, инлайновых стилей
   в ките нет. Цвет — вторичный носитель смысла, процент всегда напечатан текстом. */

.meter {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	min-width: 0;
}
/* Линейный индикатор Material: дорожка 4px пилюлей цвета surface-container-highest,
   заполнение — цветом роли. */
.meter__track {
	position: relative;
	height: 4px;
	border-radius: var(--md-sys-shape-corner-full);
	background: var(--md-sys-color-surface-container-highest);
	border: 0;
	overflow: hidden;
}
.meter__fill {
	height: 100%;
	min-width: 4px;
	border-radius: inherit;
	background: var(--md-sys-color-primary);
	transition: width var(--md-sys-motion-duration-long2) var(--md-sys-motion-easing-emphasized-decelerate);
}
.meter--warn .meter__fill { background: var(--md-sys-color-warning); }
.meter--err .meter__fill { background: var(--md-sys-color-error); }
.meter--ok .meter__fill { background: var(--md-sys-color-success); }
.meter__value {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1) var(--space-2);
	margin: 0;
	font: var(--type-body-medium);
	color: var(--md-sys-color-on-surface-variant);
}
.meter__label { color: var(--md-sys-color-on-surface); }
.meter--compact { gap: 0; }
.meter--compact .meter__track { height: 4px; min-width: 56px; }

.meter__fill--0 { width: 0%; }
.meter__fill--5 { width: 5%; }
.meter__fill--10 { width: 10%; }
.meter__fill--15 { width: 15%; }
.meter__fill--20 { width: 20%; }
.meter__fill--25 { width: 25%; }
.meter__fill--30 { width: 30%; }
.meter__fill--35 { width: 35%; }
.meter__fill--40 { width: 40%; }
.meter__fill--45 { width: 45%; }
.meter__fill--50 { width: 50%; }
.meter__fill--55 { width: 55%; }
.meter__fill--60 { width: 60%; }
.meter__fill--65 { width: 65%; }
.meter__fill--70 { width: 70%; }
.meter__fill--75 { width: 75%; }
.meter__fill--80 { width: 80%; }
.meter__fill--85 { width: 85%; }
.meter__fill--90 { width: 90%; }
.meter__fill--95 { width: 95%; }
.meter__fill--100 { width: 100%; }

/* Уважение к настройке системы: анимации отключаются ПОЛНОСТЬЮ. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}
}

/* --- единое меню на узком экране ------------------------------------------------------
   Полосе ходов на телефоне места нет: её роль забирает бургер слева, и открывает он ту же
   панель — шторкой на всю высоту, из-за левого края. */
@media (max-width: 900px) {

	.top--menu .top__nav { display: none; }

	/* На телефоне в шапке остаются двое — бургер и знак, — и стоять они обязаны рядом,
	   слева. Базовое «по краям» растащило бы их по противоположным углам экрана. */
	.top--menu .wrap { justify-content: flex-start; }

	@supports selector(:popover-open) {

		.hubmenu__opener { display: block; }

		/* Закрытая шторка стоит за левым краем: оттуда выезжает, туда и уходит. Плавность —
		   на движении; display и верхний слой снимаются дискретно, поэтому названы отдельно.
		   Движок, который так не умеет, отбрасывает объявление целиком и открывает шторку
		   мгновенно — поведение то же, просто без анимации. */
		.hubmenu {
			translate: -100% 0;
			transition: translate var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate),
			            overlay var(--md-sys-motion-duration-medium2) allow-discrete,
			            display var(--md-sys-motion-duration-medium2) allow-discrete;
		}

		/* Ящик навигации Material: surface-container-low, скругление large у свободного
		   края, поля 12px. */
		.hubmenu:popover-open {
			inset: 0 auto 0 0;
			width: min(85vw, 360px);
			height: auto;
			max-height: none;
			padding: var(--space-3);
			border-bottom: 0;
			border-radius: 0 var(--md-sys-shape-corner-large) var(--md-sys-shape-corner-large) 0;
			translate: 0 0;
		}

		@starting-style {
			.hubmenu:popover-open { translate: -100% 0; }
		}

		/* Затемнение — признак того, что страница за шторкой сейчас не работает. */
		.hubmenu::backdrop { background: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); }

	}

}

/* --- строчное меню на узком экране ----------------------------------------------------
   На телефоне панель к кнопке не привязана: шестерёнка стоит в последней колонке, у края
   экрана и внутри прокручиваемой обёртки таблицы, и панель у такой кнопки уезжала за край.
   Здесь она встаёт ЛИСТОМ у нижнего края окна — там её видно целиком, в какой бы строке
   и на какой прокрутке ни нажали, и туда дотягивается палец. Лист — пара шторке единого
   меню: выезжает от своего края, под ним тот же скрим, пункты той же пальцевой высоты. */
@media (max-width: 900px) {

	.menu__title { display: block; }

	/* Пункт листа — строка списка Material под палец: 56px, пилюлей, подпись body-large. */
	.menu__panel > .button,
	.menu__panel > form:is(:not(:has(.field))) > .button {
		min-height: 56px;
		padding: 0 var(--space-4);
		font: var(--type-body-large);
		letter-spacing: var(--md-sys-typescale-body-large-tracking);
		border-color: transparent;
		border-radius: var(--md-sys-shape-corner-full);
	}

	/* Кнопка формы с полями в листе — та же цель под палец. Форма тянет её во всю ширину,
	   и подпись встаёт по центру, как у кнопки, а не у строки: строкой она не стала. */
	.menu__panel > form:has(.field) > .button { min-height: 48px; justify-content: center; }

	@supports selector(:popover-open) {

		/* Место листа — края окна, а не кнопка: inset задаёт все четыре стороны разом
		   и тем перекрывает и привязку к якорю, и запасной центр движка без якорей,
		   а position-try-fallbacks: none не даёт браузеру отзеркалить лист. Ширина — во всё
		   окно телефона, на планшете — полоса по центру: лист во всю ширину планшета
		   с тремя строчками читался бы пустым полем. */
		.menu__panel {
			position: fixed;
			inset: auto 0 0 0;
			width: 100%;
			max-width: 32rem;
			margin: 0 auto;
			max-height: 80vh;
			max-height: 80dvh;
			padding: var(--space-2) var(--space-3) var(--space-5);
			border-width: 0;
			border-radius: var(--md-sys-shape-corner-extra-large) var(--md-sys-shape-corner-extra-large) 0 0;
			background: var(--md-sys-color-surface-container-low);
			box-shadow: var(--md-sys-elevation-level1);
			translate: 0 100%;
			position-try-fallbacks: none;
			transition: translate var(--md-sys-motion-duration-medium2) var(--md-sys-motion-easing-emphasized-decelerate),
			            overlay var(--md-sys-motion-duration-medium2) allow-discrete,
			            display var(--md-sys-motion-duration-medium2) allow-discrete;
		}

		/* Ручка нижнего листа Material: полоска 32×4 вверху по центру — знак того, что лист
		   выехал снизу и туда же уйдёт. */
		.menu__panel::before {
			content: "";
			flex: none;
			align-self: center;
			width: 32px;
			height: 4px;
			margin: var(--space-2) 0 var(--space-4);
			border-radius: var(--md-sys-shape-corner-full);
			background: var(--md-sys-color-on-surface-variant);
			opacity: .4;
		}

		.menu__panel:popover-open { translate: 0 0; }

		@starting-style {
			.menu__panel:popover-open { translate: 0 100%; }
		}

		/* Затемнение — признак того, что страница за листом сейчас не работает;
		   щелчок по нему закрывает лист, как и щелчок мимо шторки. */
		.menu__panel::backdrop { background: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); }

	}

}

/* --- дом настроек на узком экране ----------------------------------------------------
   Меню на телефоне одно — бургер: разделы дома едут в его шторку первой группой, под
   учёткой, с рубриками (см. панель выше), а на странице под заголовком дома остаётся имя
   текущего раздела. Своего выдвижного меню у дома нет: два выдвижных меню на одном
   экране спорили бы друг с другом. У инсталляции без единого меню колонка разделов
   встаёт над секциями и читается пальцем. */
@media (max-width: 900px) {

	.settings__layout { flex-direction: column; align-items: stretch; }
	.settings__nav { position: static; flex: 1 1 auto; }

	/* Страница с единым меню несёт те же разделы секцией меню, и на телефоне колонка
	   была бы вторым их списком подряд. Прячется она ТОЛЬКО там, где меню есть: панель
	   стоит перед содержимым, поэтому «страница с меню» выражается соседством, а не
	   классом-маркером на теле. У инсталляции без слота колонка — единственный вход
	   в соседний раздел, и она остаётся. */
	.hubmenu ~ main .settings__nav { display: none; }

	/* Где стоит человек, говорит строка под заголовком: колонки с подсвеченным пунктом
	   на этой странице нет. Без меню колонка на месте, и вторая подсказка не нужна. */
	.hubmenu ~ main .settings__current { display: flex; }

	/* Пункт под палец: 44 px — нижняя граница, ниже которой в него не попадают. */
	.settings__nav .tree__link {
		min-height: 44px;
		padding: var(--space-2) var(--space-3);
		font: var(--type-body-large);
	}

}

@media (max-width: 640px) {
	/* Прежняя шапка на совсем узком встаёт колонкой: в ней ряд ходов, тема и учётка,
	   в строку они не влезают. Шапке с единым меню это не нужно и вредно — там всего
	   двое, бургер и знак, и колонка уводила бы знак на вторую строку. */
	.top:not(.top--menu) .wrap { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
	/* Шапке узкой страницы (вход, ворота) колонка не нужна: в ней знак и пилюля темы (у
	   вошедшего — ещё учётка), и вторая строка под знаком только удлиняла шапку (дев-сборка
	   10). Знак слева, справа — то, что в навигации, одной строкой на любой ширине; длинное
	   имя знака сжимается многоточием, а не сталкивает пилюлю вниз. Разметка шапки та же:
	   узкую страницу лист узнаёт по колонке в main. */
	body:has(> main > .narrow) > .top .wrap { flex-direction: row; align-items: center; justify-content: space-between; }
	body:has(> main > .narrow) > .top .brand { min-width: 0; }
	body:has(> main > .narrow) > .top .brand > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
	body:has(> main > .narrow) > .top .top__nav { flex: none; flex-wrap: nowrap; }
	main.wrap { padding-top: var(--space-5); }
	.savebar { position: static; }
}

/* --- двухэтажная ячейка: сверху строка «значок + значение», снизу приписка --- */

.cell-stack { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.row-line { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
/* Действия записи одним рядом: кнопка формы и кнопки соседних форм (каждая — своя форма
   из одной кнопки) стоят в строку и переносятся вместе. */
.actions-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.image-icon--small { width: 16px; height: 16px; }

/* --- таблица карточками: длинный список читается без горизонтальной прокрутки --- */

.cell-value { white-space: nowrap; color: var(--md-sys-color-on-surface-variant); }

/* Машинное значение в такой таблице не переносится: разорванный путь читается как два.
   На узком экране запрет снимается — там строка уже стала карточкой во всю ширину. */
.table-cards code { white-space: nowrap; }

/* На узком экране строка перестаёт быть рядом ячеек и становится записью-карточкой.
   Шапки у записи нет, поэтому ячейка называет свою колонку сама: подпись колонки (кит
   кладёт её в data-label) стоит приглушённой строкой над значением. Заглавная ячейка,
   ячейка с меню строки и колонка, чьи значения называют себя сами, подписи не несут.

   Ячейка записи — строка во всю ширину карточки: ширина и запрет переноса узкого столбца
   (.cell-narrow) здесь снимаются, иначе ячейка ужимается до 1% и число встаёт столбиком
   по букве. Пустую ячейку кит помечает классом, и места в записи она не занимает. */
@media (max-width: 720px) {
	.table-cards, .table-cards tbody, .table-cards tr, .table-cards td { display: block; }
	.table-cards thead { display: none; }
	.table-cards tr { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--md-sys-color-outline-variant); }
	.table-cards tr:last-child { border-bottom: none; }
	.table-cards td { width: auto; white-space: normal; border-bottom: none; padding: var(--space-1) 0; }
	/* в карточке число — обычная строка под подписью колонки: справа оно оторвалось бы от неё */
	.table-cards td.cell-num { text-align: left; }
	.table-cards td.cell-empty { display: none; }
	.table-cards td[data-label]::before {
		content: attr(data-label);
		display: block;
		color: var(--md-sys-color-on-surface-variant);
		font: var(--type-body-medium);
	}
	.table-cards code { white-space: normal; overflow-wrap: anywhere; }
}

.text-below { display: block; margin-top: var(--space-1); color: var(--md-sys-color-on-surface-variant); font: var(--type-body-medium); }

/* --- подробность в ячейке: содержимое раскрывается на месте ----------------------
   Свёрнутая строка не мешает читать список, развёрнутая отвечает «что именно».
   Без единой строки JS. */

.fold-detail > summary {
	cursor: pointer;
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-medium);
}

/* Свойства объекта — сетка «имя | значение». Потолок ширины — мера ЧИТАЕМОСТИ, как
   у вводных абзацев: строка в половину экрана читается не лучше, чем строка в два
   символа. Ширина у списка есть: он стоит строкой во всю таблицу, а не ячейкой в ряду. */
.props {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: var(--space-1) var(--space-3);
	margin-top: var(--space-2);
	font: var(--type-body-medium);
	max-width: 70ch;
}

.props dt { color: var(--md-sys-color-on-surface-variant); white-space: nowrap; }
/* anywhere — ради хешей и адресов: слово длиннее колонки переносится, а не вылезает из
   таблицы. Обычные слова им не режутся: ширину даёт строка во всю таблицу. */
.props dd { margin: 0; overflow-wrap: anywhere; }

/* --- шаги: длинная форма по одному экрану за раз (выпуск 0.11, шаг 10) ---------------
   Без скрипта шаги — панели подряд (те же .panel, что у раскрывашки), открыт один,
   кнопка отправки под ними: форма видна и уходит целиком. Скрипт кита ставит корню
   data-steps-live: тогда видна одна панель без своей строки-шапки, над ней — полоса
   номеров, под ней — «Назад / Дальше». Полоса — как Stepper у MUI в облике Material 3:
   круг 24px с номером, у текущего и пройденных залит primary, у пройденного вместо
   номера галка, между кругами — линия. До 640 px подписи шагов, кроме текущего, уходят
   из вида (скринридер их читает), остаются номера. */

.steps > * + * { margin-top: var(--space-4); }
.steps__bar[hidden], .steps__act[hidden], .steps__panel[hidden] { display: none; }

.steps__bar {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	align-items: center;
	gap: var(--space-2);
}
.steps__item { display: flex; align-items: center; gap: var(--space-2); flex: 1 1 0; min-width: 0; }
.steps__item:last-child { flex: 0 1 auto; }
/* линия к следующему шагу; у пройденного — цвета primary: путь уже пройден */
.steps__item:not(:last-child)::after {
	content: "";
	flex: 1 1 var(--space-4);
	min-width: var(--space-4);
	height: 1px;
	background: var(--md-sys-color-outline-variant);
}
.steps__item[data-steps-done]::after { background: var(--md-sys-color-primary); }

.steps__go {
	--_bg: transparent;
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-width: 0;
	min-height: 40px;
	padding: var(--space-1) var(--space-3) var(--space-1) var(--space-1);
	border: 0;
	border-radius: var(--md-sys-shape-corner-full);
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--_state), var(--_bg));
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}
.steps__go:hover { --_state: var(--state-hover); }
.steps__go:active { --_state: var(--state-pressed); }
.steps__item[aria-current] .steps__go, .steps__item[data-steps-done] .steps__go { color: var(--md-sys-color-on-surface); }

.steps__mark {
	flex: none;
	display: inline-grid;
	place-items: center;
	width: 24px;
	height: 24px;
	border: 1px solid var(--md-sys-color-outline);
	border-radius: 50%;
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
	transition: background-color var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}
.steps__mark .icon { display: none; width: 16px; height: 16px; }
.steps__item[aria-current] .steps__mark, .steps__item[data-steps-done] .steps__mark {
	border-color: var(--md-sys-color-primary);
	background: var(--md-sys-color-primary);
	color: var(--md-sys-color-on-primary);
}
.steps__item[data-steps-done] .steps__num { display: none; }
.steps__item[data-steps-done] .steps__mark .icon { display: block; }
.steps__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Строка-шапка панели без скрипта: номер шага кругом, подпись — заголовком строки. */
.steps__head { font: var(--type-title-small); letter-spacing: var(--md-sys-typescale-title-small-tracking); }
/* длинная подпись шага переносится в строке-шапке, а не раздвигает её */
.steps__title { min-width: 0; overflow-wrap: anywhere; }
.steps__head .steps__mark { border-color: var(--md-sys-color-primary); color: var(--md-sys-color-primary); }
/* Без скрипта отказ поля в свёрнутой панели браузеру показать негде — отправка просто
   не идёт. После попытки отправки такое поле :user-invalid, и шапка его панели берёт цвет
   ошибки: видно, какой шаг открыть. */
.steps__panel:has(:user-invalid) > .steps__head { color: var(--md-sys-color-error); }
.steps__panel:has(:user-invalid) > .steps__head .steps__mark { border-color: var(--md-sys-color-error); color: var(--md-sys-color-error); }

/* Шаги со скриптом: панель одна, её шапку заменяет полоса номеров. Фокус на панель
   ставит скрипт при смене шага — обводка только с клавиатуры. Рамки и своей заливки у
   единственной панели нет (аудит M3 №13, дев-сборка 8): в окне и в секции она была
   поверхностью в поверхности — обведённой карточкой вокруг полей; содержимое шага стоит
   прямо под полосой номеров, как у Stepper в MUI. Без скрипта панели — раскрывашки
   с рамкой: им она нужна. */
.steps[data-steps-live] > .steps__panel > .panel__head { display: none; }
.steps[data-steps-live] > .steps__panel {
	border: 0;
	border-radius: 0;
	background: transparent;
	overflow: visible;
}
.steps[data-steps-live] > .steps__panel > .panel__body { border-top: 0; padding: 0; }
.steps[data-steps-live] > .steps__panel:focus { outline: none; }
.steps[data-steps-live] > .steps__panel:focus-visible {
	outline: 3px solid var(--md-sys-color-secondary);
	outline-offset: 2px;
}

/* «Назад» слева, «Дальше» и отправка справа — как у Stepper в MUI. */
.steps__nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-2); }
.steps__act { display: inline-flex; }
.steps__act--back { margin-right: auto; }
/* «Отмена» окна шагами (АдресЗакрытия, дев-сборка 8) — первой слева, «Назад» — рядом с ней. */
.steps__act--cancel { display: inline-flex; margin-right: auto; }
.steps__act--cancel:has(~ .steps__act--back:not([hidden])) { margin-right: 0; }

/* Вид «вкладки»: правка уже заполненного — любой шаг открывается сразу. Полоса — основные
   вкладки Material: подпись без кругов и линий, текущая — цвета primary с индикатором
   3px снизу. */
.steps--tabs .steps__bar { gap: 0; border-bottom: 1px solid var(--md-sys-color-surface-variant); }
.steps--tabs .steps__item { flex: 0 1 auto; }
.steps--tabs .steps__item::after { display: none; }
.steps--tabs .steps__go {
	min-height: 48px;
	padding: 0 var(--space-4);
	border-radius: 0;
	border-bottom: 3px solid transparent;
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
	transition: background-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease),
	            border-color var(--motion-fast) var(--motion-ease);
}
.steps--tabs .steps__item[aria-current] .steps__go {
	color: var(--md-sys-color-primary);
	border-bottom-color: var(--md-sys-color-primary);
}
@media (min-width: 641px) {
	.steps--tabs .steps__bar .steps__mark { display: none; }
}

@media (max-width: 640px) {
	.steps__go { padding-right: var(--space-1); }
	/* место в полосе — подписи текущего: остальные шаги сжаты до номера */
	.steps__item[aria-current] { flex: 1 0 auto; }
	.steps__item[aria-current] .steps__go { flex-shrink: 0; padding-right: var(--space-2); }
	.steps__item:not([aria-current]):not(:last-child) { flex: 1 1 0; }
	.steps__item:not([aria-current]) .steps__label {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
	.steps--tabs .steps__go { padding: 0 var(--space-2); }
	/* вкладки стоят у левого края, а не растянуты линиями мастера */
	.steps--tabs > .steps__bar > .steps__item { flex: 0 1 auto; }
}

/* --- сегменты и подсказка (выпуск 0.12) ---------------------------------------------
   Сегменты — сегментированные кнопки Material 3: пилюля с рамкой outline, сегменты через
   линию, выбранный залит secondary-container и несёт галку. Под капотом радиокнопки
   одного имени: выбор держит :checked, скрипта нет. Радиокнопка не спрятана display:none —
   иначе выпала бы из порядка фокуса и стрелок; она прозрачна и растянута по сегменту. */
.segments {
	display: inline-grid;
	grid-auto-flow: column;
	/* сегмент шириной в свою подпись. Равные колонки резали бы на телефоне самую длинную
	   подпись — а у выбранного она ещё и с галкой; здесь же не хватит места — короткие
	   сегменты останутся целыми, многоточие получит длинный */
	grid-auto-columns: minmax(0, auto);
	max-width: 100%;
	vertical-align: middle;
	border: 1px solid var(--md-sys-color-outline);
	border-radius: var(--md-sys-shape-corner-full);
}
.segments__item { position: relative; display: flex; min-width: 0; cursor: pointer; }
.segments__item + .segments__item { border-left: 1px solid var(--md-sys-color-outline); }
.segments__item:has(:focus-visible) { z-index: 1; }
.segments__input {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	opacity: 0;
	cursor: pointer;
}
.segments__label {
	--_bg: transparent;
	--_fg: var(--md-sys-color-on-surface);
	--_state: 0%;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	min-width: 0;
	min-height: 40px;
	padding: 0 var(--space-3);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	color: var(--_fg);
	background-color: color-mix(in srgb, var(--_fg) var(--_state), var(--_bg));
	transition: background-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}
.segments__item:first-child > .segments__label { border-radius: var(--md-sys-shape-corner-full) 0 0 var(--md-sys-shape-corner-full); }
.segments__item:last-child > .segments__label { border-radius: 0 var(--md-sys-shape-corner-full) var(--md-sys-shape-corner-full) 0; }
.segments__item:only-child > .segments__label { border-radius: var(--md-sys-shape-corner-full); }
.segments__text { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* Галка выбранного въезжает слева и сдвигает подпись, как в Material; у невыбранного места
   под неё нет — подпись стоит посередине. */
.segments__check {
	display: inline-flex;
	flex: none;
	width: 0;
	overflow: hidden;
	transition: width var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
	            margin var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
}
.segments__check .icon { flex: none; width: 18px; height: 18px; }
/* Значок сегмента (дев-сборка 10): перед подписью, 18 px, не сжимается — на телефоне подпись
   переносится, а значок остаётся; галка выбранного встаёт перед ним. */
.segments__icon { flex: none; width: 18px; height: 18px; margin-right: var(--space-2); }
.segments--small .segments__icon { width: 16px; height: 16px; margin-right: var(--space-1); }
.segments__item:hover > .segments__label { --_state: var(--state-hover); }
.segments__item:active > .segments__label { --_state: var(--state-pressed); }
.segments__input:checked + .segments__label {
	--_bg: var(--md-sys-color-secondary-container);
	--_fg: var(--md-sys-color-on-secondary-container);
}
.segments__input:checked + .segments__label .segments__check { width: 18px; margin-right: var(--space-2); }
.segments__input:focus-visible + .segments__label { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 2px; }
.segments__input[disabled] { cursor: not-allowed; }
.segments__input[disabled] + .segments__label { opacity: .38; }
/* Малые — для строки списка и записи реестра: та же пилюля, ниже и плотнее. */
.segments--small .segments__label {
	min-height: 32px;
	padding: 0 var(--space-2);
	font: var(--type-label-medium);
	letter-spacing: var(--md-sys-typescale-label-medium-tracking);
}
.segments--small .segments__input:checked + .segments__label .segments__check { margin-right: var(--space-1); }
/* На телефоне подпись сегмента не обрезается многоточием, а переносится во вторую строку:
   три сегмента с подписями в два-три слова на 390 резали каждую до «Свет…» (пробел хаба
   dev.4, п. 5). Пилюля растёт в высоту, скругление full остаётся круглым.
   Группа встаёт во всю ширину равными колонками: при колонках по содержимому выбранный
   сегмент с галкой оставался самым узким и рвал слово посередине.
   Колонка не уже самого длинного слова своей подписи (0.13, дев-сборка 7): у равных
   колонок minmax(0, 1fr) и overflow-wrap: break-word однословная подпись, которой не
   хватило трети пилюли, рвалась посреди слова — «Вручну / ю», «GitH / ub». Теперь нижний
   предел колонки — min-content (самое длинное слово, у выбранного — с галкой), а остаток
   ширины делится поровну: где слова влезают, колонки равные, как прежде; длинное слово
   делает свою колонку шире соседей, а не рвётся. Переносится подпись только между словами. */
@media (max-width: 600px) {
	.segments { display: grid; grid-auto-columns: minmax(min-content, 1fr); width: 100%; }
	.segments__label { padding: var(--space-1) var(--space-2); }
	.segments__text { white-space: normal; text-overflow: clip; overflow-wrap: normal; text-align: center; }
}

/* Сегменты в окне (пробел хаба П5 ui-хвост-единообразие, дев-сборка 8). Окно уже экрана —
   560 px, на телефоне 358, — а правило выше включалось по ширине ЭКРАНА: на 1280 в окне
   тега пять сегментов резали подписи многоточием («Стабильн…», «Устар…»). В окне сегменты на
   любой ширине держатся, как на телефоне: во всю ширину, колонка не уже самого длинного
   слова, подпись переносится между словами. Пять и больше однословных подписей в 518 px
   окна (на телефоне — четыре и больше в 326) всё равно не встают — тогда группа встаёт
   столбиком: сегмент на строку, пилюля — прямоугольник со скруглением 16, между сегментами —
   черта. Сегментов в окне по Material не больше пяти. */
.modal .segments { display: grid; grid-auto-columns: minmax(min-content, 1fr); width: 100%; }
.modal .segments__label { padding: var(--space-1) var(--space-2); }
.modal .segments__text { white-space: normal; text-overflow: clip; overflow-wrap: normal; text-align: center; }
@supports selector(:has(*)) {
	.modal .segments:has(> :nth-child(5)) {
		grid-auto-flow: row;
		grid-auto-columns: 100%;
		border-radius: var(--md-sys-shape-corner-large);
	}
	.modal .segments:has(> :nth-child(5)) > .segments__item + .segments__item {
		border-left: 0;
		border-top: 1px solid var(--md-sys-color-outline);
	}
	.modal .segments:has(> :nth-child(5)) > .segments__item > .segments__label { border-radius: 0; }
	.modal .segments:has(> :nth-child(5)) > .segments__item:first-child > .segments__label {
		border-radius: var(--md-sys-shape-corner-large) var(--md-sys-shape-corner-large) 0 0;
	}
	.modal .segments:has(> :nth-child(5)) > .segments__item:last-child > .segments__label {
		border-radius: 0 0 var(--md-sys-shape-corner-large) var(--md-sys-shape-corner-large);
	}
}
@media (max-width: 600px) {
	@supports selector(:has(*)) {
		.modal .segments:has(> :nth-child(4)) {
			grid-auto-flow: row;
			grid-auto-columns: 100%;
			border-radius: var(--md-sys-shape-corner-large);
		}
		.modal .segments:has(> :nth-child(4)) > .segments__item + .segments__item {
			border-left: 0;
			border-top: 1px solid var(--md-sys-color-outline);
		}
		.modal .segments:has(> :nth-child(4)) > .segments__item > .segments__label { border-radius: 0; }
		.modal .segments:has(> :nth-child(4)) > .segments__item:first-child > .segments__label {
			border-radius: var(--md-sys-shape-corner-large) var(--md-sys-shape-corner-large) 0 0;
		}
		.modal .segments:has(> :nth-child(4)) > .segments__item:last-child > .segments__label {
			border-radius: 0 0 var(--md-sys-shape-corner-large) var(--md-sys-shape-corner-large);
		}
	}
}

/* Сегменты режима у строки дерева галок (дев-сборка 10, ревью ui-получатели-2 П4) — строкой на
   любой ширине. Правила выше (телефон — во всю ширину с переносом подписей, окно — столбиком
   от четырёх-пяти сегментов) для выбора в форме, а у строки дерева сегменты — её короткое
   действие: столбиком они удлиняли каждую строку в 3–4 раза, и окно получателей на 38 веток
   становилось втрое длиннее. Здесь пилюля — малыми сегментами своей ширины, подписи не
   переносятся (не влезли — многоточие у длинной), строка ветки переносит пилюлю под подпись
   целиком. Правила стоят после правил окна и телефона и не слабее их: побеждают порядком. */
.checktree__row > .segments.checktree__modes {
	display: inline-grid;
	grid-auto-flow: column;
	grid-auto-columns: minmax(0, auto);
	width: auto;
	max-width: 100%;
	border-radius: var(--md-sys-shape-corner-full);
}
.checktree__row > .segments.checktree__modes > .segments__item + .segments__item {
	border-top: 0;
	border-left: 1px solid var(--md-sys-color-outline);
}
.checktree__row > .segments.checktree__modes > .segments__item > .segments__label { padding: 0 var(--space-2); border-radius: 0; }
.checktree__row > .segments.checktree__modes > .segments__item:first-child > .segments__label {
	border-radius: var(--md-sys-shape-corner-full) 0 0 var(--md-sys-shape-corner-full);
}
.checktree__row > .segments.checktree__modes > .segments__item:last-child > .segments__label {
	border-radius: 0 var(--md-sys-shape-corner-full) var(--md-sys-shape-corner-full) 0;
}
.checktree__row > .segments.checktree__modes .segments__text {
	white-space: nowrap;
	text-overflow: ellipsis;
	text-align: start;
}

/* Источник значения сегментов (слово Источник, 0.13 дев-сборка 6) — справка о значении, как
   у поля и тумблера: мелко и приглушённо. Сервер печатает его перед группой, и он стоит
   справа на строке подписи группы: подпись (<legend>) встаёт влево обтеканием, источник —
   вправо, группа — под обоими. Не влез рядом — уходит под подпись, у правого края. Без
   подписи группы источник — своей строкой над пилюлей, справа. */
.segments__origin {
	display: block;
	min-width: 0;
	text-align: end;
	overflow-wrap: anywhere;
	font: var(--type-label-small);
	letter-spacing: var(--md-sys-typescale-label-small-tracking);
	color: var(--md-sys-color-on-surface-variant);
}
fieldset > legend:has(+ .segments__origin) { float: left; padding: 0; }
fieldset > legend + .segments__origin {
	float: right;
	max-width: 100%;
	margin: 0 0 0 var(--space-3);
	/* строка источника той же высоты, что строка подписи: базовые линии почти совпадают */
	line-height: inherit;
}
fieldset > legend + .segments__origin + * { clear: both; }
/* clear действует только на блок: строчная пилюля (inline-grid) встала бы рядом с подписью,
   поэтому здесь она — блочная сетка своей ширины (на телефоне — во всю, как всегда там) */
fieldset > legend + .segments__origin + .segments { display: grid; width: fit-content; }
@media (max-width: 600px) {
	fieldset > legend + .segments__origin + .segments { width: 100%; }
}

/* Отказ у сегментов (дев-сборка 10): строка field__error под пилюлей, рамка пилюли и черты
   между сегментами — цветом ошибки, как контур отбитого поля. В наборе с панелями строка
   стоит после панелей (лист считает панели по номеру), а под пилюлю её ставит порядок флекса. */
.segments__error { display: block; margin-top: var(--space-1); }
.segments[aria-invalid="true"] { border-color: var(--md-sys-color-error); }
.segments[aria-invalid="true"] > .segments__item + .segments__item { border-left-color: var(--md-sys-color-error); }
.segments-set > .segments { order: -2; }
.segments-set > .segments__error { order: -1; margin-top: calc(var(--space-1) - var(--space-4)); }

/* Наборы полей под сегментами. Панель номер N видна, пока отмечен сегмент номер N: лист
   сопоставляет их по номеру, правила перечислены до восьми сегментов (столько же
   проверяет Сегменты.НаборовНаЛисте). Движок без :has() показывает все наборы сразу —
   поля не теряются, лишние отбрасывает сервер. Пустая панель места не занимает. */
.segments-set { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-4); }
.segments-set > .segments__panel { align-self: stretch; display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.segments-set > .segments__panel:empty { display: none; }
@supports selector(:has(*)) {
	.segments-set > .segments__panel { display: none; }
	.segments-set:has(> .segments > :nth-child(1) > :checked) > .segments__panel:nth-child(2):not(:empty),
	.segments-set:has(> .segments > :nth-child(2) > :checked) > .segments__panel:nth-child(3):not(:empty),
	.segments-set:has(> .segments > :nth-child(3) > :checked) > .segments__panel:nth-child(4):not(:empty),
	.segments-set:has(> .segments > :nth-child(4) > :checked) > .segments__panel:nth-child(5):not(:empty),
	.segments-set:has(> .segments > :nth-child(5) > :checked) > .segments__panel:nth-child(6):not(:empty),
	.segments-set:has(> .segments > :nth-child(6) > :checked) > .segments__panel:nth-child(7):not(:empty),
	.segments-set:has(> .segments > :nth-child(7) > :checked) > .segments__panel:nth-child(8):not(:empty),
	.segments-set:has(> .segments > :nth-child(8) > :checked) > .segments__panel:nth-child(9):not(:empty) { display: flex; }
}

/* Панель сегмента вне его набора (Кит.ПанельСегмента, Сегменты.Панель — 0.13, дев-сборка 7).
   Та же панель набора, только стоит не под группой, а где угодно в той же форме — в другом
   шаге мастера, в другой группе. Видна, пока в форме отмечен сегмент номер N группы с тем же
   местом (data-segments-slot): CSS не сравнит имя поля панели с именем радиокнопок, поэтому
   сервер нумерует группы с панелями в форме, а правила перечислены по месту (до четырёх
   групп, Сегменты.МестПанелейНаЛисте) и номеру сегмента (до восьми). Движок без :has()
   показывает все панели — поля не теряются, лишние отбрасывает сервер. */
.segments-panel { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.segments-panel:empty { display: none; }
@supports selector(:has(*)) {
	.segments-panel { display: none; }
	form:has(.segments[data-segments-slot="1"] > :nth-child(1) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="1"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(2) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="2"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(3) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="3"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(4) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="4"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(5) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="5"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(6) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="6"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(7) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="7"]:not(:empty),
	form:has(.segments[data-segments-slot="1"] > :nth-child(8) > :checked) .segments-panel[data-segments-slot="1"][data-segments-number="8"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(1) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="1"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(2) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="2"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(3) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="3"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(4) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="4"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(5) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="5"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(6) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="6"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(7) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="7"]:not(:empty),
	form:has(.segments[data-segments-slot="2"] > :nth-child(8) > :checked) .segments-panel[data-segments-slot="2"][data-segments-number="8"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(1) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="1"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(2) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="2"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(3) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="3"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(4) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="4"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(5) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="5"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(6) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="6"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(7) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="7"]:not(:empty),
	form:has(.segments[data-segments-slot="3"] > :nth-child(8) > :checked) .segments-panel[data-segments-slot="3"][data-segments-number="8"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(1) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="1"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(2) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="2"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(3) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="3"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(4) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="4"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(5) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="5"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(6) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="6"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(7) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="7"]:not(:empty),
	form:has(.segments[data-segments-slot="4"] > :nth-child(8) > :checked) .segments-panel[data-segments-slot="4"][data-segments-number="8"]:not(:empty) { display: flex; }
}

/* Подсказка — значок «?» и поповер при нём (rich tooltip Material): контейнер
   surface-container, второй уровень возвышения, текст body-medium. Открывает и закрывает
   поповер движок (popovertarget, Esc, щелчок мимо), скрипта нет. Значок 24px, а зона
   нажатия — 48px: псевдоэлемент выходит за него на 12px с каждой стороны. */
.help {
	--_state: 0%;
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 24px;
	height: 24px;
	margin: 0 0 0 var(--space-1);
	padding: 0;
	vertical-align: middle;
	border: 0;
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-on-surface-variant);
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) var(--_state), transparent);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}
.help::before { content: ""; position: absolute; inset: -12px; }
.help:hover { --_state: var(--state-hover); color: var(--md-sys-color-on-surface); }
.help:active { --_state: var(--state-pressed); }
.help:focus-visible { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 2px; }
.help__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 18px;
	height: 18px;
	border: 2px solid currentColor;
	border-radius: var(--md-sys-shape-corner-full);
	font: var(--type-label-medium);
	font-weight: 700;
	line-height: 1;
}
.help__pop {
	max-width: min(312px, calc(100vw - 2 * var(--space-4)));
	padding: var(--space-3) var(--space-4);
	border: 0;
	border-radius: var(--md-sys-shape-corner-medium);
	background: var(--md-sys-color-surface-container);
	color: var(--md-sys-color-on-surface-variant);
	box-shadow: var(--md-sys-elevation-level2);
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	font-weight: normal;
	text-align: start;
	text-transform: none;
	white-space: normal;
	overflow-wrap: anywhere;
}
/* Поповер проявляется — дискретно снимаются только верхний слой и display, после
   движения; движок, который так не умеет, открывает его сразу. */
@supports selector(:popover-open) {
	.help__pop {
		opacity: 0;
		transition: opacity var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard),
		            overlay var(--md-sys-motion-duration-short4) allow-discrete,
		            display var(--md-sys-motion-duration-short4) allow-discrete;
	}
	.help__pop:popover-open { opacity: 1; }
	@starting-style {
		.help__pop:popover-open { opacity: 0; }
	}
}
/* Место поповера — под значком, который его открыл: кнопка из popovertarget и есть его
   неявный якорь (см. панель меню). Не хватит места справа или снизу — браузер отзеркалит
   его сам. Движок без привязки к якорю ставит поповер по центру экрана (стиль UA), и
   ширина в экран с полями 16px не пускает его за край и там. */
@supports (top: anchor(bottom)) {
	.help__pop {
		inset: auto;
		position-anchor: auto;
		top: anchor(bottom);
		left: anchor(left);
		margin: var(--space-1) 0 0;
		position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
	}
	/* У поля значок стоит у правого края — поповер растёт от него влево, над полем. */
	.field-help > .help__pop { left: auto; right: anchor(right); }
	/* На телефоне поповер шириной в экран с полями 16px — под значком или над ним: так
	   он не уходит за край, где бы значок ни стоял. */
	@media (max-width: 640px) {
		.help__pop, .field-help > .help__pop {
			left: var(--space-4);
			right: var(--space-4);
			max-width: none;
			position-try-fallbacks: flip-block;
		}
	}
}

/* Поле со справкой: значок стоит в строке подписи у правого края поля — там, где
   у текстового поля Material живёт замыкающий значок; ни значение, ни подпись не уходят
   под него. У выпадающего списка значок левее шеврона. У флажка значок идёт сразу за
   подписью. */
.field-help { position: relative; display: flex; flex-direction: column; min-width: 0; }
.field-help > .help { position: absolute; z-index: 1; top: 10px; right: var(--space-3); margin: 0; }
.field-help > .field > .field__control { padding-right: 48px; }
.field-help > .field > select.field__control { padding-right: 76px; }
.field-help:has(> .field > select.field__control) > .help { right: 44px; }
.field-help > .field > .field__label { max-width: calc(100% - 72px); }
.field-help > .field:not([data-empty]) > .field__label,
.field-help > .field:focus-within > .field__label { max-width: calc((100% - 72px) * 5 / 4); }
.field-help:has(> .field--check) { flex-direction: row; align-items: flex-start; }
.field-help > .field--check { flex: 0 1 auto; }
/* Значок флажка и тумблера стоит в потоке, но остаётся позиционированным (position: relative,
   дев-сборка 8): зона касания 48 px — псевдоэлемент .help::before с position: absolute —
   отсчитывается от самого значка. С position: static она растягивалась на ближайшего
   позиционированного предка — всю строку .field-help, — и щелчок по флажку и дорожке
   тумблера открывал справку, а не переключал (слово владельца к ui.4, с dev.5). */
.field-help:has(> .field--check) > .help { position: relative; margin: -2px 0 0 var(--space-1); }
/* Тумблер со справкой: «?» стоит справа от подписи, по верху ряда, как у флажка. */
.field-help:has(> .toggle) { flex-direction: row; align-items: flex-start; }
.field-help > .toggle { flex: 0 1 auto; min-width: 0; }
.field-help:has(> .toggle) > .help { position: relative; margin: 4px 0 0 var(--space-1); }
/* Справка у списка и цвета (выпуск 0.12): строка ввода списка не прямой ребёнок поля,
   поэтому место под значок оставляют её рамка (со скриптом) и сам ввод (без него) —
   иначе значок лёг бы на плашки и текст. У цвета рамки нет: значок стоит в строке
   подписи, над рядом образцов, которые иначе дотянулись бы до него. */
.field-help > .field--chips:not([data-chips-live]) .field__control { padding-right: 48px; }
.field-help > .field--chips[data-chips-live] .chips-in { padding-right: 48px; }
.field-help:has(> .field--color) > .help { top: -2px; right: 0; }

/* --- проверка до отправки и тост (выпуск 0.12, шаги 18 и 19) ----------------------------
   Отказ до отправки скрипт пишет в тот же field__error и ставит тот же field--error, что
   печатает сервер: правила поля выше уже его красят, своих здесь нет. Тост из ответа тихой
   отправки скрипт поднимает из потока страницы (data-toast-live) и показывает внизу экрана
   по одному, как Snackbar у Material: ждущий очереди не виден. */
.toast[data-toast-live] {
	position: fixed;
	z-index: 30;
	left: var(--space-4);
	right: var(--space-4);
	bottom: var(--space-4);
	width: fit-content;
	max-width: 40rem;
	margin: 0 auto;
	box-shadow: var(--md-sys-elevation-level3);
}
.toast[data-toast-live="wait"] { display: none; }

/* --- картинка и образец (выпуск 0.12): слот картинки -----------------------------
   Узел Кит.СлотКартинки: предпросмотр на шахматке, подпись и строка состояния, кнопка
   «Загрузить / Заменить» и меню ⚙. Слот — своя форма (и форма сброса рядом), формы стоят
   display: contents — их дети раскладываются одной строкой слота; на узком экране
   кнопки переносятся под подпись. Без скрипта видно системное файловое поле и кнопку
   отправки: только так форму и отправить. Со скриптом выбор файла отправляет форму сам —
   поле уходит из вида (но не из порядка обхода: фокус с клавиатуры встаёт на него, и
   обводку рисует кнопка-подпись над ним), кнопка отправки прячется. */

.imgslot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3) var(--space-4);
}
.imgslot__upload, .imgslot__reset { display: contents; }

/* Шахматка — фон «прозрачного» из редакторов картинок: на ней видно, где у логотипа
   прозрачные края, в обеих темах. */
.imgslot__preview {
	--_a: var(--md-sys-color-surface-container-highest);
	--_b: var(--md-sys-color-surface-container-low);
	flex: none;
	display: grid;
	place-items: center;
	width: 72px;
	height: 72px;
	overflow: hidden;
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	background: repeating-conic-gradient(var(--_a) 0 25%, var(--_b) 0 50%) 0 0 / 16px 16px;
	transition: opacity var(--motion-fast) var(--motion-ease);
}
.imgslot__image { display: block; max-width: 100%; max-height: 100%; object-fit: contain; }
/* пока файл в пути, предпросмотр приглушён: форма слота помечена aria-busy */
.imgslot:has(.imgslot__upload[aria-busy="true"]) .imgslot__preview { opacity: 0.6; }

.imgslot__text { flex: 1 1 12rem; min-width: 0; }
.imgslot__label {
	margin: 0;
	font: var(--type-title-small);
	letter-spacing: var(--md-sys-typescale-title-small-tracking);
	color: var(--md-sys-color-on-surface);
}
.imgslot__state {
	margin: var(--space-1) 0 0;
	font: var(--type-body-medium);
	letter-spacing: var(--md-sys-typescale-body-medium-tracking);
	color: var(--md-sys-color-on-surface-variant);
	overflow-wrap: anywhere;
}

/* Отказ сервера под строкой состояния (слово Ошибка, 0.13 дев-сборка 6) — как отказ под
   полем: мелко, цветом error; рамку слот не перекрашивает — отказ касается файла, а не места. */
.imgslot__error {
	margin: var(--space-1) 0 0;
	font: var(--type-body-small);
	letter-spacing: var(--md-sys-typescale-body-small-tracking);
	color: var(--md-sys-color-error);
	overflow-wrap: anywhere;
}

.imgslot__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.imgslot__pick { display: inline-flex; position: relative; min-width: 0; }
.imgslot .imgslot__face { display: none; }
.imgslot__file { max-width: 100%; font: var(--type-body-medium); color: var(--md-sys-color-on-surface); }
.imgslot__gear { display: inline-flex; }
/* окно сброса, напечатанное открытым без скрипта, в движке без :modal стоит в потоке
   страницы — во всю строку; там, где :modal есть, оно поверх страницы (см. окно формы) */
.imgslot > .modal[open] { flex: 1 1 100%; }

@media (scripting: enabled) {
	.imgslot .imgslot__face { display: inline-flex; cursor: pointer; }
	.imgslot__file {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
		border: 0;
		opacity: 0;
	}
	.imgslot__actions > .button { display: none; }
	.imgslot__pick:has(.imgslot__file:focus-visible) .imgslot__face {
		--_state: var(--state-focus);
		outline: 3px solid var(--md-sys-color-secondary);
		outline-offset: 2px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.imgslot__preview { transition: none; }
}

/* --- картинка и образец (выпуск 0.12): образец --------------------------------------
   Узел Кит.Образец: подпись, переключатель темы и рамка, в которой лежит любое дерево.
   Тема рамки — data-theme на .preview__frame; палитры для неё скрипт кита собирает из блока
   токенов страницы, а здесь рамка лишь берёт фон и текст своей палитры. Без скрипта
   рамка в теме страницы, а переключатель спрятан: кнопка, которая ничего не меняет, хуже,
   чем никакой. Зеркало значения (data-mirror) кладёт цвет поля в свойство --mirror-<имя>
   узла: им пользуется лист приложения. Своё правило у кита одно — зеркало поля accent
   (ниже): узлы кита в образце перекрашиваются акцентом сами. */

.preview {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: var(--space-2) var(--space-3);
	margin: 0;
}
.preview__label {
	min-width: 0;
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	color: var(--md-sys-color-on-surface-variant);
}
.preview__themes { display: none; gap: var(--space-1); }
.preview__theme {
	--_state: 0%;
	min-height: 32px;
	padding: 0 var(--space-3);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-small);
	background-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--_state), transparent);
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease),
	            color var(--motion-fast) var(--motion-ease);
}
.preview__theme:hover { --_state: var(--state-hover); }
.preview__theme:active { --_state: var(--state-pressed); }
.preview__theme[aria-pressed="true"] {
	border-color: transparent;
	background-color: var(--md-sys-color-secondary-container);
	color: var(--md-sys-color-on-secondary-container);
}
.preview__frame {
	grid-column: 1 / -1;
	min-width: 0;
	padding: var(--space-4);
	border: 1px solid var(--md-sys-color-outline-variant);
	border-radius: var(--md-sys-shape-corner-medium);
	background: var(--md-sys-color-surface);
	color: var(--md-sys-color-on-surface);
}
/* в студии (поля слева, образец справа) образец делит строку с полями и уезжает под них
   на узком экране — как .studio__sample, но во всю свою колонку */
.studio > .preview { flex: 1 1 18rem; min-width: 0; }
.preview__frame > :first-child { margin-top: 0; }
.preview__frame > :last-child { margin-bottom: 0; }

@media (scripting: enabled) {
	.preview__themes { display: inline-flex; }
}

/* Зеркало акцента (0.13, дев-сборка 6). Узел образца, на котором зеркало поля accent напечатало
   (сервер) или написало (скрипт) свойство --mirror-accent, пересобирает акцентные роли своей
   палитры из него: основная кнопка, плашка и чип вида accent, ссылка, контурная кнопка внутри
   берут выбранный цвет, как возьмут его на сайте. Контейнер — тот же цвет на 30 % поверх
   поверхности рамки, текст на нём — on-surface; текст на самом акценте — белый или чёрный
   по светлоте цвета (порог OKLCH 0,6 — там, где белый и чёрный дают равный контраст),
   где движок умеет относительный цвет; иначе остаётся on-primary палитры. Селектор по
   атрибуту style: свойство без значения правило не включает — пустое поле («по умолчанию»)
   возвращает узлу акцент рамки. Зеркало на самой рамке образца перекрашивает рамку целиком. */
.preview__frame [style*="--mirror-accent:"],
.preview[style*="--mirror-accent:"] > .preview__frame {
	--md-sys-color-primary: var(--mirror-accent);
	--md-sys-color-primary-container: color-mix(in srgb, var(--mirror-accent) 30%, var(--md-sys-color-surface));
	--md-sys-color-on-primary-container: var(--md-sys-color-on-surface);
}
@supports (color: oklch(from red l c h)) {
	.preview__frame [style*="--mirror-accent:"],
	.preview[style*="--mirror-accent:"] > .preview__frame {
		--md-sys-color-on-primary: oklch(from var(--mirror-accent) clamp(0, (0.6 - l) * 1000, 1) 0 0);
	}
}

@media (prefers-reduced-motion: reduce) {
	.preview__theme { transition: none; }
}

/* --- таблица карточками и свойства (выпуск 0.13) ------------------------------------
   Запись-карточка на узком экране: заглавная ячейка (слово Заглавная у колонки, иначе
   первая) встаёт первой строкой и крупнее — по ней запись узнают в лицо, в каком бы месте
   ряда она ни стояла на широком экране. Ячейка с меню строки уходит в правый верхний угол:
   там шестерёнку ищут глазом, и действия не путаются со значениями. Лист действий меню
   по-прежнему выезжает у нижнего края окна.

   Сетка из двух колонок, а не порядок flex: заглавная и меню делят первую строку, прочие
   ячейки идут под ними во всю ширину и в порядке разметки. Без меню вторая колонка пуста
   и ширины не берёт. На широком экране оба класса ничего не меняют: там у таблицы шапка. */
@media (max-width: 720px) {
	.table-cards tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; }
	.table-cards td { grid-column: 1 / -1; min-width: 0; }
	.table-cards td.cell-title {
		grid-row: 1;
		grid-column: 1;
		align-self: center;
		font: var(--type-title-medium);
		letter-spacing: var(--md-sys-typescale-title-medium-tracking);
	}
	/* Отрицательное поле ставит кнопку меню в угол карточки, а не в угол её полей —
	   как действие в шапке карточки Material. */
	.table-cards td.cell-menu {
		grid-row: 1;
		grid-column: 2;
		padding: 0 0 0 var(--space-2);
		margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 0;
	}
}

/* Свойство со словами: значение, кнопки сразу за ним и примечание строкой ниже. Кнопка
   у края списка, оторванная от значения, читалась бы действием над всем списком. */
.props__value { display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--space-2); min-width: 0; }
.props__text { min-width: 0; overflow-wrap: anywhere; }
/* полоса заполнения — показание во всю строку: ужатая до содержимого, она стала бы точкой */
.props__text:has(.meter) { flex: 1 1 12rem; }
.props__note {
	flex-basis: 100%;
	color: var(--md-sys-color-on-surface-variant);
	font: var(--type-body-small);
	letter-spacing: var(--md-sys-typescale-body-small-tracking);
}

/* Кнопка копирования — кнопка-иконка Material: круг 32px, слой состояния на наведении.
   Подпись «Копировать» — для скринридера и всплывающей подсказки (title). */
.props__copy {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 32px;
	height: 32px;
	padding: 0;
	border: 0;
	border-radius: var(--md-sys-shape-corner-full);
	background: transparent;
	color: var(--md-sys-color-on-surface-variant);
	cursor: pointer;
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.props__copy:hover { background: color-mix(in srgb, var(--md-sys-color-on-surface) var(--state-hover), transparent); }
.props__copy svg { width: 18px; height: 18px; }

/* Секрет без скрипта: галка «Показать» стоит в разметке ПЕРЕД значением, и правило
   «:checked ~» снимает точки и открывает значение — работает и там, где :has ещё нет.
   Сама галка спрятана, но фокус в неё приходит, и кольцо фокуса рисует её подпись.
   Скрытое значение не читает и скринридер: display: none убирает его из дерева. */
.props__reveal-box {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	opacity: 0;
}
.props__mask { color: var(--md-sys-color-on-surface-variant); letter-spacing: .12em; }
.props__value--secret .props__text { display: none; }
.props__reveal-box:checked ~ .props__text { display: block; }
.props__reveal-box:checked ~ .props__mask { display: none; }
.props__reveal {
	position: relative;
	display: inline-flex;
	align-items: center;
	min-height: 32px;
	padding: 0 var(--space-2);
	border-radius: var(--md-sys-shape-corner-full);
	color: var(--md-sys-color-primary);
	font: var(--type-label-large);
	letter-spacing: var(--md-sys-typescale-label-large-tracking);
	cursor: pointer;
	user-select: none;
	transition: background-color var(--motion-fast) var(--motion-ease);
}
.props__reveal:hover { background: color-mix(in srgb, var(--md-sys-color-primary) var(--state-hover), transparent); }
.props__reveal-box:focus-visible ~ .props__reveal { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 2px; }
.props__reveal-hide { display: none; }
.props__reveal-box:checked ~ .props__reveal .props__reveal-show { display: none; }
.props__reveal-box:checked ~ .props__reveal .props__reveal-hide { display: inline; }

/* Кнопка рядом со значением выше строки текста, и строка значения встаёт по её середине:
   имя напротив опускается на ту же середину, иначе оно глядит выше значения. */
@media (min-width: 641px) {
	.props dt:has(+ dd > :is(.props__copy, .props__reveal)) { padding-top: 6px; }
}

/* До 640px двум колонкам тесно: имя встаёт над значением, пара отбита от соседней. */
@media (max-width: 640px) {
	.props { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
	.props dt { white-space: normal; }
	.props dd + dt { margin-top: var(--space-3); }
/* --- страницы и отбор без перезагрузки (выпуск 0.13, шаг 20) -----------------------------
   Диапазон «41–60 из 137» стоит рядом с номером страницы и звучит тише его. Заголовок
   сортируемой колонки — ссылка цветом заголовка со стрелкой порядка: у колонки, по которой
   список отсортирован (aria-sort), стрелка видна и смотрит по порядку — вверх по возрастанию,
   вниз по убыванию; у прочих проявляется на наведении и фокусе — знак, что колонка
   сортируется. Пока тихий переход в пути, навигация и таблица помечены aria-busy. */
.pagination__count { color: var(--md-sys-color-on-surface-variant); font: var(--type-body-small); }
.table__sort {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	color: inherit;
	border-radius: var(--md-sys-shape-corner-extra-small);
}
.table__sort::after {
	content: "";
	width: 6px; height: 6px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
	opacity: 0;
	transition: opacity var(--motion-fast) var(--motion-ease), transform var(--motion-fast) var(--motion-ease);
}
.table__sort:hover { color: var(--md-sys-color-on-surface); text-decoration: none; }
.table__sort:hover::after, .table__sort:focus-visible::after { opacity: .5; }
.table__sort:focus-visible { outline: 3px solid var(--md-sys-color-secondary); outline-offset: 2px; }
th[aria-sort] { color: var(--md-sys-color-on-surface); }
th[aria-sort] > .table__sort::after { opacity: 1; }
th[aria-sort="ascending"] > .table__sort::after { transform: translateY(2px) rotate(-135deg); }
.pagination[aria-busy="true"], table[aria-busy="true"] { cursor: progress; }
/* на узком экране с диапазоном шаги стоят первой строкой, номер страницы и диапазон — под ними:
   втроём с состоянием им в 390 px не уместиться, и «Вперёд» уехал бы строкой ниже один */
@media (max-width: 640px) {
	.pagination:has(.pagination__count) > .pagination__status,
	.pagination:has(.pagination__count) > .pagination__count { order: 1; flex-basis: 100%; text-align: center; }
	.pagination:has(.pagination__count) { row-gap: var(--space-1); }
}
