/* ─────────────────────────────────────────────────────────────────────────
   ДВИЖЕНИЕ

   Отдельный слой, а не вкрапления в page.css. Причина простая: движение —
   единственная часть системы, которую можно и нужно уметь снять целиком, не
   трогая раскладку. Убери две строки из <head> — сайт останется рабочим
   документом, просто без движения.

   Правило, по которому здесь всё сделано: движение либо что-то показывает,
   либо его нет. Ни один эффект тут не украшает и ни один ничего не прячет:
   подсветка строки реестра показывает, куда ведёт сноска; полоска сверху
   показывает, сколько прочитано; метка курсора говорит, что работу можно
   открыть; лайтбокс ведёт работу из её места на странице в центр.

   Скорости. Отклик на курсор — 140 мс, иначе он ощущается как задержка.
   Крупный объект, который занимает экран, — 420–560 мс: быстрее он
   выглядит дёрганым, медленнее начинает раздражать на втором просмотре.

   Кривая одна на выход, `--ease-out` (0.23, 1, 0.32, 1): резкий старт,
   долгий вынос. Она и делает ощущение веса — объект как будто тяжёлый и
   тормозит сам. Парная кривая оставлена состояниям, которые ходят туда и
   обратно: теме и лайтбоксу.
   ───────────────────────────────────────────────────────────────────────── */

/* Токены слоя движения переехали в `tokens.css`. Здесь стоял второй
   блок `:root` с восемью значениями, пока файл токенов называл себя
   единственным источником значений для макета и для кода. Два
   источника — это не источник: правка кривой в одном не доходит до
   другого, и расхождение видно только на глаз. */

/* ── 1. Въезд первого экрана ──────────────────────────────────────────────
   Первый раздел страницы приходит так же, как приходил до 08.10.2026:
   крупные заголовки — строка за строкой из-под своей маски (строки режет
   motion.js по фактическим строчным коробкам), номер раздела — вместе с
   заголовком, лид, крошки и плиты — подъёмом с прозрачностью. На главной
   набор ждёт удара штампа штукатурки (`data-entered`, motion.js).

   Только первый раздел, и это граница, а не экономия. Слой появления при
   прокрутке — тот же приём на всех рядах ниже — снят 08.10.2026 по правкам
   Заказчицы T1 и A6: блоки ниже первого экрана прятались до входа в кадр,
   и полностраничный снимок главной выходил с пустыми разделами. Ниже
   первого раздела ничего не прячется; это держит `.reveal.mjs`, который
   меряет страницу без прокрутки. Въезд играет один раз при открытии,
   уйдя из кадра, первый раздел не прячется снова.

   Без сценария (класса `js` нет) и при `prefers-reduced-motion` первый
   раздел стоит сразу. Отказ Приложения 1 («анимация появления в первом
   экране») — расхождение, вынесенное решением Заказчице, как и до 08.10:
   identity.md §8.

   Двигается только transform у строк заголовка: текст всё это время
   отрисован. Лид и плиты проявляются прозрачностью. */
/* Строка держит под собой ступень 8 px и отдаёт её обратно отрицательным
   полем: выносные элементы («p», «y», «g») больше не срезаются кромкой
   блока, а раскладка не меняется. Стартовый сдвиг набора — на ту же
   ступень глубже, чтобы до появления из-под кромки ничего не выглядывало.
   Срез был виден на телефоне: «problem.», «Cavendish Square». 25.09.2026.
   Все правила строк — под `.js`: без скрипта слой выключается целиком. Пока
   они висели без него, снятый `js` оставлял строки сдвинутыми, а поле
   схлопывалось сквозь заголовок; гейт наложений `.overlap.mjs` мерил
   спрятанный текст и дал 979 «наездов» (25.09). */
.js .ln {
	display: block;
	overflow: hidden;
	padding-block-end: var(--s2);
	margin-block-end: calc(-1 * var(--s2));
}
/* Отрицательное поле последней строки схлопывалось сквозь заголовок и
   съедало 8 px отбивки после него. Заголовок с разрезанными строками —
   свой контекст раскладки, и поле остаётся внутри. */
.js :is(main h1, main h2):has(> .ln) { display: flow-root; }
/* Сброс курсива тега — типографика, а не движение: он действует и без
   `.js`. Под `.js` тег `i` получал курсив браузера, и гейт шрифтов нашёл
   синтезированное начертание на 1221 узле (25.09). */
.ln > i { font-style: inherit; }
.js .ln > i {
	display: block;
	transform: translateY(calc(105% + var(--s2)));
	will-change: transform;
	transition: transform var(--dur-head) var(--ease-glide);
	transition-delay: calc(var(--i, 0) * var(--stagger));
}
.is-in .ln > i { transform: none; }

/* Крупный номер раздела встаёт вместе с заголовком, а не отдельно: это
   одна строка набора, разорванная на две графы. */
.js main > .row:first-of-type .num {
	overflow: hidden;
}
.js main > .row:first-of-type .num > span {
	display: inline-block;
	transform: translateY(105%);
	transition: transform var(--dur-head) var(--ease-glide);
}
.js main > .row:first-of-type.is-in .num > span { transform: none; }

/* Начальное состояние задано структурным селектором, а не классом, который
   вешает скрипт: класс вешается после разбора документа, то есть после
   первой отрисовки, и читатель успевал бы увидеть текст до того, как его
   спрячут. Класс `js` ставится в <head>, и мигания нет ни кадра. */
.js main > .row:first-of-type > div:not(.register) > *,
.js main > .row:first-of-type > .register,
.js main > .row:first-of-type > figure.plate,
.js main > .row:first-of-type > .crumb {
	opacity: 0;
	transform: translateY(var(--rise));
}
/* Переход объявлен только у появления. Пока он стоял и у скрытого
   состояния, первый экран на каждой загрузке гас на глазах: браузер
   успевал вычислить стиль заголовка и абзаца до прихода этой таблицы —
   видимыми, — и таблица прятала их переходом (кадры владельца 26.09.2026). */
.js main > .row:first-of-type.is-in > div:not(.register) > *,
.js main > .row:first-of-type.is-in > .register,
.js main > .row:first-of-type.is-in > figure.plate,
.js main > .row:first-of-type.is-in > .crumb {
	opacity: 1;
	transform: none;
	/* Прозрачность заканчивается раньше пути: текст успевает стать
	   читаемым и ещё немного доезжает. */
	transition: opacity var(--dur-fade) var(--ease-glide),
	            transform var(--dur-reveal) var(--ease-glide);
}
/* Ступенька внутри раздела. Сорок миллисекунд — столько, чтобы порядок
   читался, и не столько, чтобы его ждать. Дальше пятого элемента ступенька
   не растёт: иначе низ раздела приезжает заметно позже верха. */
.js main > .row:first-of-type.is-in > div:not(.register) > *:nth-child(2) { transition-delay: 40ms; }
.js main > .row:first-of-type.is-in > div:not(.register) > *:nth-child(3) { transition-delay: 80ms; }
.js main > .row:first-of-type.is-in > div:not(.register) > *:nth-child(4) { transition-delay: 120ms; }
.js main > .row:first-of-type.is-in > div:not(.register) > *:nth-child(n+5) { transition-delay: 160ms; }
.js main > .row:first-of-type.is-in > .register { transition-delay: 120ms; }
/* Без движения первый раздел стоит сразу, не дожидаясь сценария: правка
   T1 — анимация только как улучшение и с учётом prefers-reduced-motion. */
@media (prefers-reduced-motion: reduce) {
	.js main > .row:first-of-type > div:not(.register) > *,
	.js main > .row:first-of-type > .register,
	.js main > .row:first-of-type > figure.plate,
	.js main > .row:first-of-type > .crumb,
	.js main > .row:first-of-type .num > span,
	.js .ln > i { opacity: 1; transform: none !important; transition: none; }
}

/* ── 2–3. Ниже первого экрана ничто не прячется ───────────────────────────
   Здесь стояли проявление блоков при подходе (прозрачность 0 и сдвиг) на
   всех рядах, лесенка строк реестра, вытирание кадра плиты и проявление её
   подписи. Всё это было спрятано до прокрутки и показывалось классом
   `is-in`. Снято 08.10.2026 по правкам Заказчицы T1 и A6: полностраничный
   снимок прокрутки не вызывает, и на нём главная выходила с пустыми
   разделами ниже первого экрана и пустой комнатой на месте плит. Так же её
   видит краулер, который учитывает видимость.

   Не возвращать ни в каком виде, где начальное состояние ряда ниже первого
   невидимо или сдвинуто. Класс `is-in` у прочих рядов зажигает только
   свечение работ (page.css, блок «Свечение работы»). Требование держит
   `.reveal.mjs`: он меряет страницу без прокрутки. */

/* ── 4. Линейки комнаты здесь нет, и это записано нарочно ────────────────
   Был эффект: волосяная линия между комнатами прочерчивается от начала
   строки, когда комната входит. Он снят, и вот почему — чтобы никто не
   вернул его обратно.

   Эффект вешал `::before` на `.row`. Но на разделе классы `row` и `room`
   стоят на одном элементе, а `.room::before` — это то, чем красится грунт
   комнаты. Псевдоэлемент один, и второе правило просто заменило первое:
   каждая комната вместо своего грунта получила линейку в один пиксель.
   Анфилада — главное, что есть у этих страниц, — исчезла целиком. На
   снимке в неудачный момент этого не видно: тело страницы покрашено
   бумагой, и разделы выглядят «просто светлыми».

   Свободных псевдоэлементов у `.room` нет: `::after` занят зерном. Значит
   эффект стоит настоящего элемента в разметке — а он декоративный, он
   ничего не показывает. Смена грунта читается и без него. Не возвращать. */

/* ── 5. Сноска и строка реестра смотрят друг на друга ─────────────────────
   Самое полезное движение на сайте и единственное, которое несёт смысл, а
   не настроение: наведи на номер сноски — подсветится строка реестра, к
   которой она ведёт, и наоборот. Аппарат перестаёт быть таблицей внизу и
   становится инструментом. Работает и с клавиатуры, по фокусу. */
.reg { transition: background-color var(--dur-fast) var(--ease-out); }
.reg.is-lit {
	background-color: var(--wash);
	box-shadow: -0.6em 0 0 var(--wash), 0.6em 0 0 var(--wash);
}
.note-n.is-lit { color: var(--fg); }
.note-n.is-lit::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-end: -0.22em;
	block-size: 1px;
	background: currentColor;
}
.note-n { position: relative; }

/* ── 6. Полоска прочитанного ──────────────────────────────────────────────
   Один пиксель приглушённым по верху окна. Не индикатор загрузки и не
   украшение: на странице в двадцать тысяч пикселей читателю нужно знать,
   сколько осталось, и это единственный способ сказать это, не занимая
   места. Ширина задаётся из скрипта, без перерисовки раскладки. */
.progress {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	block-size: 1px;
	background: var(--muted);
	transform: scaleX(0);
	transform-origin: 0 50%;
	z-index: var(--z-veil);
	pointer-events: none;
}

/* ── 7. Метка курсора над плитой ──────────────────────────────────────────
   Только для настоящего курсора. Над плитой курсор получает подпись —
   номер плиты и слово «Open». Это то место, где сайт говорит, что с
   работой можно что-то сделать, не рисуя кнопку поверх изображения. */
.cursor-chip {
	position: fixed;
	inset-block-start: 0;
	inset-inline-start: 0;
	z-index: var(--z-stage);
	pointer-events: none;
	font-family: var(--font-mono);
	font-size: var(--size-label);
	letter-spacing: var(--tracking-label);
	color: var(--paper);
	background: var(--ink);
	padding: 0.35em 0.6em;
	opacity: 0;
	transform: translate3d(-50%, -160%, 0) scale(0.94);
	transition: opacity var(--dur-fast) var(--ease-out),
	            transform var(--dur-fast) var(--ease-out);
}
.cursor-chip.is-on {
	opacity: 1;
	transform: translate3d(-50%, -160%, 0) scale(1);
}
@media (hover: none), (pointer: coarse) { .cursor-chip { display: none; } }
.js figure.plate[data-open] { cursor: zoom-in; }

/* ── 8. Лайтбокс ──────────────────────────────────────────────────────────
   Плита открывается на весь экран. Изображение едет из своего места в
   центр — считается по фактическим координатам, не «появляется по центру»:
   читатель должен видеть, что открылась та самая работа, на которую он
   нажал. Подпись остаётся внизу, потому что этикетка — часть работы.
   Закрывается по Esc, по клику и по прокрутке. */
/* Завеса — чернильная комната в миниатюре, и роли она берёт тем же
   блоком, что комната и плита. Раньше её подпись пересчитывала
   приглушённую роль своим выражением. */
.veil {
	position: fixed;
	inset: 0;
	z-index: var(--z-close);
	display: grid;
	grid-template-rows: 1fr auto;
	gap: var(--s4);
	padding: var(--s5) var(--page-pad) var(--s4);
	background: var(--scrim);
	opacity: 0;
	visibility: hidden;
	transition: opacity var(--dur-veil) var(--ease-pair),
	            visibility 0s linear var(--dur-veil);
}
.veil.is-open {
	opacity: 1;
	visibility: visible;
	transition: opacity var(--dur-veil) var(--ease-pair);
}
.veil::after {
	content: "";
	position: absolute;
	inset: 0;
	background-image: var(--grain);
	background-size: var(--grain-size) var(--grain-size);
	opacity: var(--grain-opacity);
	pointer-events: none;
}
.veil-stage {
	display: grid;
	place-items: center;
	min-block-size: 0;
}
.veil-stage img {
	max-inline-size: 100%;
	max-block-size: 100%;
	inline-size: auto;
	block-size: auto;
	display: block;
}
.veil-cap {
	font-family: var(--font-mono);
	font-size: var(--size-apparatus);
	line-height: var(--leading-tight);
	/* Приглушённая роль на чернилах объявлена блоком ролей, который завеса
	   теперь берёт целиком; здесь она пересчитывалась заново. */
	color: var(--muted);
	max-inline-size: var(--measure);
	display: grid;
	gap: var(--s1);
}
.veil-cap b { color: var(--paper); font-weight: 400; }
.veil-close {
	position: absolute;
	inset-block-start: var(--s3);
	inset-inline-end: var(--page-pad);
	font-family: var(--font-mono);
	font-size: var(--size-apparatus);
	letter-spacing: var(--tracking-label);
	color: var(--paper);
	background: none;
	border: 0;
	padding: var(--s2);
	cursor: pointer;
	min-block-size: 24px;
}
.veil-close::before { content: "■"; margin-inline-end: 0.6em; }
body.is-veiled { overflow: hidden; }

/* ── Печать ───────────────────────────────────────────────────────────────
   Скрытых состояний у слоя нет, и возвращать на бумаге нечего. Печать
   снимает только приборы экрана: полоску прочитанного, метку курсора и
   завесу лайтбокса. Проверяется `.print.mjs` вместе с остальной печатью. */
@media print {
	.progress, .cursor-chip, .veil { display: none !important; }
}

/* ── Меньше движения ──────────────────────────────────────────────────────
   Уменьшается, а не выключается: длительности схлопываются почти в ноль,
   чтобы события завершения всё же срабатывали (общий сброс — в tokens.css).
   Метка курсора не нужна вовсе: она приезжает за указателем. */
@media (prefers-reduced-motion: reduce) {
	:root { --dur-reveal: 1ms; --dur-veil: 1ms; }
	.cursor-chip { display: none; }
}

/* plaster:start ── собрано .plaster.mjs, руками не править ─────────────
   Штукатурка первого экрана главной. Холст лежит под титулом; грунт
   #definition и шапки становится прозрачным, только когда слой нарисовал
   первый кадр, и только на экране: в печати и без WebGL всё как было. */
.ground {
	position: absolute;
	inset-block-start: 0;
	inset-inline-start: 0;
	inline-size: 100%;
	block-size: var(--ground-h, 100svh);
	z-index: var(--z-ground);
	pointer-events: none;
	opacity: 0;
	transition: opacity var(--dur-invert) var(--ease-out);
	/* Маске нужна только непрозрачность: цвет — любой непрозрачный токен,
	   литерала вне tokens.css здесь быть не может (.tokens.mjs).

	   Растушёвка идёт по плавной ступеньке (3t² − 2t³), а не по прямой:
	   прямая даёт видимые начало и конец полосы, и край читался дёшево
	   (владелец 25.09.2026: «сделай более плавным и не до 100%»). Восемь
	   опорных точек — у кривой нет ни излома на входе, ни на выходе. */
	/* Ступени непрозрачности — производные от токена и объявлены по
	   одной, как --muted и --rule в page.css; имя из одних
	   букв — так объявление узнаёт .tokens.mjs. */
	--veil-top: color-mix(in srgb, var(--ink) 96%, transparent);
	--veil-high: color-mix(in srgb, var(--ink) 84%, transparent);
	--veil-upper: color-mix(in srgb, var(--ink) 68%, transparent);
	--veil-half: color-mix(in srgb, var(--ink) 50%, transparent);
	--veil-lower: color-mix(in srgb, var(--ink) 32%, transparent);
	--veil-low: color-mix(in srgb, var(--ink) 16%, transparent);
	--veil-trace: color-mix(in srgb, var(--ink) 4%, transparent);
	--gf: var(--ground-fade, 16rem);
	--ground-mask: linear-gradient(to bottom,
		var(--ink) calc(100% - var(--gf)),
		var(--veil-top) calc(100% - var(--gf) * 0.875),
		var(--veil-high) calc(100% - var(--gf) * 0.75),
		var(--veil-upper) calc(100% - var(--gf) * 0.625),
		var(--veil-half) calc(100% - var(--gf) * 0.5),
		var(--veil-lower) calc(100% - var(--gf) * 0.375),
		var(--veil-low) calc(100% - var(--gf) * 0.25),
		var(--veil-trace) calc(100% - var(--gf) * 0.125),
		transparent);
	-webkit-mask-image: var(--ground-mask);
	mask-image: var(--ground-mask);
}
@media screen {
	:root[data-ground] .ground { opacity: 1; }
	:root[data-ground] #definition::before { background-color: transparent; }
	:root[data-ground] header.masthead { background-color: transparent; }
	:root[data-ground] header.masthead::before { opacity: var(--ground-mast, 0); }
	/* Издательский знак вдавлен в штукатурку (решение владельца 24.09.2026):
	   плоский знак прячется, только когда штамп уже в стене. */
	:root[data-stamp] .titlepage::before { opacity: 0; }
	/* Плоский знак не показывается и до штампа: его место — в стене, и
	   контур, мелькнувший на бумаге до первого кадра слоя, читался сбоем
	   (запись владельца 26.09). Возвращается он, только если слою не на чем
	   рисовать: без WebGL, при отказе шейдера или если слой не пришёл вовсе —
	   тогда `data-flat` ставит сам слой или слой движения. Без сценария класса
	   `js` нет, и знак стоит как стоял. */
	.js .page:has(> canvas.ground) .titlepage::before { opacity: 0; }
	:root[data-flat] .page:has(> canvas.ground) .titlepage::before { opacity: 1; }
}
@media print { .ground { display: none; } }
/* plaster:end */
