/* ═══════════════════════════════════════════════════════════════════════
   АКВАРЕЛЬНЫЙ СЛОЙ — свет и пятна
   ═══════════════════════════════════════════════════════════════════════

   ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ
   Это единственный чисто декоративный слой на сайте. Он вынесен
   целиком, чтобы его можно было посмотреть одним куском, обсудить
   одним куском и снять одной строкой в <head>, не задев ни одного
   правила утверждённой вёрстки.

   ЧТО ЭТО ЗА ЭЛЕМЕНТЫ
   Не CSS-градиенты и не размытые тени — их бриф запрещает, §2.2.
   Это ЕЁ КРАСКА: три заливки и одна марка, вырезанные из сканов её
   работ скриптом tools/make-wash-set.py. Разрешение автора получено,
   но её работы НЕ используются как контент: сайт про звук, а не про
   её живопись. Отсюда и правило — берём материал и знак, а не
   картину.

   Дизайн-система была сформулирована как «сайт — это бумага,
   акварель на ней — только её». Бумага была сделана, акварель — нет.
   Все запреты (скругление 2px, ни одного градиента, ни одной
   размытой тени, одна плотная заливка на экран) существуют ИМЕННО
   потому, что мягкость и свет должна давать акварель. Без неё
   остаётся одна строгость — три тона бежевого и линейки.

   ОТКУДА ВЗЯТЫ ПРОЗРАЧНОСТИ
   Из таблицы `python3 tools/make-wash-set.py --check`, а не с глаза.
   Она печатает, до какой прозрачности каждое пятно можно положить
   на каждый тон бумаги, не уронив самый слабый стоящий там текст
   ниже AA (4.5:1). Ограничение задаёт САМЫЙ ТЁМНЫЙ пиксель пятна.

   Это не формальность. Первая версия слоя держала cool на 0.30 —
   там --ink-soft падал с 5.04 до 3.14, то есть декорация ломала
   доступность ровно на секциях с самым мелким текстом. Числа ниже
   взяты из таблицы с запасом; пересчитать её нужно ПОСЛЕ любой
   пересборки ассетов.

   ПРАВИЛА, КОТОРЫЕ ЗДЕСЬ НЕЛЬЗЯ НАРУШАТЬ
   1. ПОД ТЕКСТОМ — ТОЛЬКО glow, и только на измеренной прозрачности.
      Проверка показала, что у cool предел под мелким текстом 0.16,
      а у sun и bowl — 0.06…0.13, то есть их там попросту не видно.
      Заглушки, где плотное пятно было допустимо, больше не
      оформляются (см. §2), поэтому sun и bowl из набора убраны.
   2. Ни одного пятна на карточке события. Там уже есть фотография
      и единственная плотная заливка --ink — кнопка. Пятно рядом
      с ней съело бы её дефицит, а на дефиците держится всё.
   3. Пятна не анимируются. Единственная бесконечная анимация
      на сайте — «дыхание» иллюстрации на /s/1…/s/5.
   4. Пути к картинкам — ОТНОСИТЕЛЬНО ЭТОГО ФАЙЛА («../img/…»),
      а не относительно страницы. В CSS url() считается от адреса
      стилей, поэтому «assets/img/…» разрешилось бы
      в /assets/css/assets/img/… и дало 404. По той же причине
      tools/build-web.py эти пути НЕ переписывает — и не должен:
      «../img/…» одинаково верен и через file://, и на сервере.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* Предел по --check указан в скобках; берём с запасом.              */
  --wash-op-cool:  .14;   /* cool на --paper-2      (предел 0.16)      */
  --wash-op-warm:  .26;   /* glow на --paper-2      (предел 0.32)      */
  --wash-op-book:  .30;   /* glow на --paper, /s/1…/s/5 (предел 0.57)  */
  --wash-op-night: .30;   /* cool на --night        (предел 0.78)      */
}

/* ═══ 1. ЗЕРНО БУМАГИ ══════════════════════════════════════════════════
   Самая дешёвая и самая заметная правка во всём слое: 26 КБ на все
   девятнадцать страниц. Отличает «бумагу» от «заливки #F6F1E7».

   ЗЕРНО ЛЕЖИТ ПОД СОДЕРЖИМЫМ, И ЭТО ИСПРАВЛЕНИЕ
   Сначала это был ФИКСИРОВАННЫЙ слой ПОВЕРХ всего, включая
   фотографии, — по рассуждению «волокно есть на всём листе».
   Рассуждение неверное. Фотография не напечатана на этой бумаге,
   она на ней ЛЕЖИТ, и волокно сквозь неё не проступает.

   Увидели это, когда в hero главной встал настоящий кадр зала:
   большая светлая area с плавными переходами — худший возможный фон
   для шума через soft-light. На бумаге волокно читается волокном,
   а на бледном небе за окном превратилось в грязь и выглядело
   пересжатым JPEG. Проверено сравнением 1:1, а не в уменьшении:
   в уменьшенном виде разницы почти не видно, и первая версия
   проскочила проверку именно так.

   Поэтому зерно теперь — background-image самих бумажных
   плоскостей, смешанный с их же тоном через background-blend-mode.
   background-blend-mode смешивает слои ФОНА ОДНОГО элемента и на
   потомков не действует вовсе: любая картинка, карточка и кнопка
   поверх остаются чистыми, а бумага под ними — бумагой.

   Плоскостей ровно четыре, потому что каждая следующая закрывает
   фон предыдущей своей заливкой: body (основной тон), .sec--paper2
   (чередующиеся секции), .ftr (подвал), .bk (страницы из книги).
   Карточкам зерна НЕ даём намеренно: они и должны читаться как
   наклеенные на лист, а разница в текстуре этому помогает.

   soft-light, а не opacity: плитка — серый шум вокруг 128, и
   soft-light делает её симметричной относительно фона (чуть светлее
   там, где волокно светлее, чуть темнее — где темнее). Через
   opacity получилась бы серая вуаль, которая гасит контраст.       */
:root {
  /* Плитка 300px отдаётся в 120: волокно её бумаги на экране должно
     быть волокном, а не рябью размером с букву. */
  --grain: url(../img/wash/grain.webp);
  --grain-size: 120px 120px;
}
@supports (background-blend-mode: soft-light) {
  body,
  .sec--paper2,
  .ftr,
  .bk {
    background-image: var(--grain);
    background-repeat: repeat;
    background-size: var(--grain-size);
    background-blend-mode: soft-light;
  }
  /* Тон плоскости обязан остаться СВОИМ: background-image сам по себе
     не задаёт цвет, а смешивать зерно не с чем. Эти три плоскости
     красились сокращённой записью background в других файлах —
     повторяем цвет явно, иначе под зерном окажется прозрачность. */
  .sec--paper2 { background-color: var(--paper-2); }
  .ftr { background-color: var(--paper-2); }
  .bk { background-color: var(--paper); }
  html.is-night .bk { background-color: var(--night); }
  /* В ночи волокно на #151824 через soft-light шумит заметно сильнее,
     чем на бумаге: светлая половина зерна вырастает. Гасим размером
     плитки помельче и меньшей амплитудой — плитка та же, а вклад
     меньше за счёт более частого рисунка. */
  html.is-night .bk { background-size: 90px 90px; }
}

/* ═══ 2. ЗАГЛУШКИ ИЛЛЮСТРАЦИЙ — НЕ ОФОРМЛЯЮТСЯ ═════════════════════════
   Здесь была самая заметная часть слоя: во все двадцать шесть заглушек
   подставлялось её пятно, и на главной это меняло полэкрана.

   СНЯТО ОСОЗНАННО, по решению заказчика: в эти слоты придёт реальный
   контент — фотографии. Оформлять их сейчас значит показывать клиенту
   и дизайнеру книги то, чего в проекте не будет, а потом убирать.
   Заглушка остаётся честной заглушкой (base.css): плоскость --paper-3,
   векторные дуги, подпись «иллюстрация · назначение · размер».

   Замер, из-за которого решение и понадобилось: на главной из 29.8%
   изменившейся площади 28.1 приходилось на заглушки — то есть почти
   всё «менее скучно» держалось на том, что заведомо временное.
   Постоянными остаются зерно, пятна на плоскостях и штрих под
   заголовком секции.

   Из этого следует, что sun и bowl больше не нужны: их единственным
   местом были заглушки и hero. Обе марки убраны из набора,
   а координаты кропов сохранены в tools/make-wash-set.py — там же
   написано, как их вернуть, если решение изменится. Набор похудел
   со 156 до 80 КБ.

   НЕ ВОЗВРАЩАЙТЕ пятна в .ph, не спросив: это не забытая доработка,
   а снятое требование.                                              */

/* ═══ 2а. ЛИНЕЙКА ПОД ЗАГОЛОВКОМ СЕКЦИИ — ЕЁ ШТРИХ ══════════════════════
   Самый частый декоративный элемент сайта: 40 штук на девятнадцать
   страниц (посчитано пробой в браузере, а не поиском по разметке).
   Был прямоугольник 32×1 из --gold. Стал штрих листовым золотом,
   снятый с венчика лучей вокруг головы на c-9 — с сужением на одном
   конце и мокрым краем на другом.

   Марка отдаётся МАСКОЙ, а не картинкой: форма её, цвет из токена.
   Иначе линейка не смогла бы жить и в --gold днём, и в --night-gold
   ночью, а два файла на два оттенка разъезжаются при первой же
   правке палитры.

   ПОЧЕМУ МАСКА ПРИХОДИТ ПЕРЕМЕННОЙ ИЗ marks.css, А НЕ ФАЙЛОМ
   Chrome НЕ ПРИМЕНЯЕТ CSS-маски к ресурсам, загруженным через
   file://. Проверено прямым опытом в браузере: одна и та же
   страница по HTTP показывает штрих, через file:// — пустое место,
   и так во всех трёх вариантах записи (mask, -webkit-mask,
   mask-mode: luminance). А работа прототипа двойным щелчком без
   сервера — жёсткое требование §3 и §12.2.

   Поэтому груз лежит вложенным data-URI в сгенерированном
   assets/css/marks.css, а оформление — здесь. Файл marks.css
   не правят руками: его пересобирает make-wash-set.py.

   ПОЧЕМУ ЗДЕСЬ @supports, А НЕ ПРОСТО ПРАВИЛО
   Без поддержки mask-image элемент остаётся тем, что он есть, —
   заливкой сплошного золота. Это заметно хуже нынешней волосяной
   линии: вместо штриха выходит золотой брусок. Геометрический
   вариант в base.css остаётся значением по умолчанию, а замена
   включается только там, где маска работает. Заодно это страхует
   от отсутствия marks.css: без переменной правило не сработает,
   и линейка останется прежней.

   ПОЧЕМУ ВЫСОТА СЧИТАЕТСЯ, А НЕ ЗАДАНА ЧИСЛОМ
   mask-size: contain вписывает маску в блок, НЕ искажая её. Если
   блок и маска разной пропорции, штрих становится короче заданной
   ширины: при первой сборке маска была 3.01:1, блок 4:1, и от
   36 px оставалось 27. Высота берётся из --mark-rule-ratio,
   который пишет тот же скрипт, — тогда пропорция совпадает
   всегда, даже если марку перевырежут из другого листа.

   ПОЧЕМУ 36 px, А НЕ КРУПНЕЕ
   Смотрели 36, 40 и 48 в натуральную величину на обоих тонах
   бумаги. От высоты 12 px штрих перестаёт читаться как линейка
   и становится пятном, спорящим с заголовком. Меняться должна
   рука, а не плотность страницы.

   ПОЧЕМУ ОТСТУП УМЕНЬШЕН
   У линии 1 px вся масса на одной высоте, у штриха — в середине.
   При том же margin-top штрих визуально провисал; --s-2
   возвращает прежний оптический просвет.                         */
@supports (mask-image: url(x)) or (-webkit-mask-image: url(x)) {
  .sec__h::after {
    width: 36px;
    height: calc(36px / var(--mark-rule-ratio, 3.3));
    margin-top: var(--s-2);
    background: var(--gold);
    -webkit-mask: var(--mark-rule) no-repeat left center / contain;
            mask: var(--mark-rule) no-repeat left center / contain;
  }
}
/* Ночного варианта здесь НЕТ намеренно. Ночной вид существует только
   на /s/1…/s/5, а там ни одного .sec__h: заголовок разворота — это
   .bk__h, и линейки под ним нет вовсе. Проверено пробой по всем
   девятнадцати страницам. Правило под --night-gold было бы
   недостижимым кодом.                                               */

/* ═══ 3. ПЛОСКОСТИ СТРАНИЦЫ ════════════════════════════════════════════
   Замеры её работ (49 сканов, кластеризация в LAB) дали неожиданное:
   13% её пигмента ХОЛОДНЫЕ — пыльно-синий #819BAD и приглушённый
   фиолетовый #6A5271. На сайте холодного не было ни пикселя. Вся
   палитра сидела в одном тёплом секторе, и отсюда ощущение
   одноцветности при формально верных токенах.

   Холодное вводится не токеном (у этих цветов контраст 1.4–2.6:1,
   текстом они быть не могут физически), а пятном на чередующихся
   секциях. Получается ритм: тёплая страница — холодный вздох —
   тёплая страница. Тихо: предел под мелким текстом — 0.16.

   ═══ ПОЧЕМУ ПЯТНО ОБРЕЗАЕТСЯ background-position, А НЕ overflow ═══
   Первая версия ставила пятну отрицательный left и вешала
   overflow: hidden на секцию, чтобы обрезать вылет. Так делать
   нельзя: overflow: hidden на секции создаёт над её содержимым
   ещё один подрезающий блок и контейнер прокрутки, и всё, что
   внутри НАМЕРЕННО выходит за край, оказывается срезано.

   Тогда это ловилось на конкретном примере: внутри .sec--paper2
   на «Об авторе» стояла .spreads — лента разворотов дневника
   со scroll-snap и отрицательным margin-right, у которой вылет
   за край экрана был частью смысла (так видно, что её можно
   тянуть). Самой ленты больше нет: блок «Рисунок в день» снят
   8 сентября 2026 (CLAUDE.md §16). Правило остаётся: горизонтально
   прокручиваемые полосы в секциях появятся снова, а обрезка
   фоном лишена этого риска в принципе.

   Поэтому сам элемент пятна ВСЕГДА внутри своего блока, а «уход
   за край» делает фоновая картинка: она смещена наружу и
   обрезается собственным блоком, как и положено фону. Ни одного
   вылета, ни одного нового контекста прокрутки.

   И ещё одно, что стоило отдельного бага. Элемент пятна занимает
   блок ЦЕЛИКОМ (inset: 0), а не его часть. Первая версия давала
   .bk::before высоту 52% — и картинка, которая выше своего блока,
   обрезалась ровно посередине страницы: через весь экран /s/1,
   прямо за кнопкой play, шла отчётливая горизонтальная линия.

   При inset: 0 обрез картинки всегда попадает на СУЩЕСТВУЮЩУЮ
   границу — край экрана, границу секции с её линейкой --line
   и сменой тона, рамку карточки. Там прямая линия и должна быть,
   поэтому её не видно. Внутри блока обрывов не бывает вовсе.

   Смещения заданы в vw, а не в процентах. Процент
   в background-position считается от РАЗНИЦЫ размеров блока
   и картинки, поэтому при картинке шире блока он меняет знак —
   значение «-25%» уезжало вправо. С длинами такого не бывает. */
.sec { position: relative; }
.sec--paper2::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url(../img/wash/cool.webp) no-repeat left -22vw top -6vw / 88vw auto;
  opacity: var(--wash-op-cool);
  pointer-events: none;
  z-index: 0;
}
/* Содержимое — над пятном. Без этого текст остаётся под ним:
   .wrap не создаёт своего контекста наложения.                       */
.sec--paper2 > .wrap { position: relative; z-index: 1; }

/* Подвал — тёплый свет у нижнего края страницы. Последнее, что
   человек видит, уходя со страницы: реквизиты на светлой бумаге,
   а не серый цоколь.                                                 */
.ftr { position: relative; }
.ftr::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url(../img/wash/glow.webp) no-repeat right -16vw bottom -18vw / 90vw auto;
  opacity: var(--wash-op-warm);
  pointer-events: none;
}
.ftr > .wrap { position: relative; z-index: 1; }

/* ═══ 4. ПУСТОЕ СОСТОЯНИЕ БЛОКА СОБЫТИЙ ════════════════════════════════
   По брифу (§8) это самый вероятный вид главной в первые месяцы,
   и блок «никогда не меняет заголовок и вес». Три ровных бежевых
   короба — ровно то, из-за чего пустое состояние читается как
   поломка. Пятно в углу отвечает за «здесь всё в порядке, просто
   дата ещё не назначена».

   Здесь именно glow: короб набит мелким текстом, а под текстом
   из всего набора проходит только он (см. правило 1 в шапке).

   Смещение в пикселях, а не в vw: короб узкий и от ширины экрана
   почти не зависит, а при vw на большом мониторе пятно уехало бы
   из него целиком.                                                */
.empty__card { position: relative; }
.empty__card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url(../img/wash/glow.webp) no-repeat right -46px top -52px / 300px auto;
  opacity: var(--wash-op-warm);
  pointer-events: none;
}
.empty__card > * { position: relative; z-index: 1; }

/* ═══ 5. СТРАНИЦЫ ИЗ КНИГИ /s/1…/s/5 ═══════════════════════════════════
   Здесь свет нужнее всего: человек читает вечером, часто перед сном,
   телефон в одной руке, книга в другой. Пятно стоит НИЗКО, за кнопкой
   play — то есть там, куда смотрят, — и не мешает верхней половине
   экрана, где по требованию §9 не должно быть вообще ничего лишнего.

   .bk — корневой блок страницы, и overflow: hidden на нём ставить
   тем более нельзя: под ним живёт вся раскладка.                  */
.bk { position: relative; }
.bk::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url(../img/wash/glow.webp) no-repeat center bottom -16vw / 150vw auto;
  opacity: var(--wash-op-book);
  pointer-events: none;
  z-index: 0;
}
.bk > * { position: relative; z-index: 1; }

/* ── Ночной вид ────────────────────────────────────────────────────────
   Тёплое пятно на #151824 светится как ночник и тянет взгляд от
   текста. Холодное ведёт себя как отражённый свет и не спорит
   с --night-gold, которым набраны время и кнопка.                    */
html.is-night .bk::before {
  background-image: url(../img/wash/cool.webp);
  opacity: var(--wash-op-night);
}

/* ═══ 6. ОТКЛЮЧЕНИЯ ════════════════════════════════════════════════════ */

/* Печать: пятна и зерно — экранный свет. На бумаге они станут
   грязью, а страницы событий печатают (адрес, дата, время).          */
@media print {
  .sec--paper2::before, .ftr::before,
  .empty__card::before, .bk::before { display: none; }
  body, .sec--paper2, .ftr, .bk { background-image: none; }
}

/* Экономия трафика: слой чисто декоративный и стоит 80 КБ.
   Кто попросил экономить — тот попросил именно про такое.            */
@media (prefers-reduced-data: reduce) {
  .sec--paper2::before, .ftr::before,
  .empty__card::before, .bk::before { display: none; }
  body, .sec--paper2, .ftr, .bk { background-image: none; }
}

/* Повышенный контраст: любое пятно под текстом — помеха, даже
   на измеренной прозрачности. Убираем всё, кроме зерна: оно
   контраста не меняет. Заглушки тоже оставляем — там под пятном
   только подпись, и она на плотной плашке.                          */
@media (prefers-contrast: more) {
  .sec--paper2::before, .ftr::before,
  .empty__card::before, .bk::before { display: none; }
}
