/*
 * Своё поверх Pico — только то, чего в бесклассовой сборке нет.
 *
 * Правило простое: если Pico стилизует голую семантику (form, label, input,
 * button, table, nav) сам, здесь про это не пишем. Иначе обещание «замена
 * библиотеки стоит одного <link>» перестаёт быть правдой уже к третьему экрану.
 *
 * Файл подключается после pico.classless.min.css, поэтому при равной
 * специфичности выигрывает он — этим и переопределяются переменные темы.
 *
 * Состав: акцентная палитра поверх темы Pico, типографика заголовков, шапка
 * и навигация, состояния поллинга, пустое состояние и нижняя панель действий.
 * Каждый блок появился вместе со своим потребителем — правил «на будущее»
 * здесь нет.
 */

:root {
  /* Ширина колонки контента. Pico задаёт её через свои брейкпоинты, но у нас
     один столбец текста и форм, а не сетка: держим единое значение в одном
     месте, чтобы экраны не разъезжались по ширине. */
  --dpp-content-width: 34rem;
}

/* --- Акцент: патина меди --------------------------------------------------
 *
 * Единственное цветовое решение каркаса, и взято оно из предметного мира
 * продукта, а не из палитры фреймворка: медь в доме зеленеет именно так —
 * трубы, краны, кровля. Нейтрали остаются за Pico, переопределяется ровно
 * роль «основной цвет».
 *
 * Селекторы повторяют пиковские дословно, и это обязательно: свои переменные
 * темы Pico объявляет не в голом `:root`, а в `:root:not([data-theme=dark])`
 * и `:root:not([data-theme])` — по специфичности они перебили бы простой
 * `:root`, и акцент вернулся бы к синему (в светлой теме это и случилось
 * с первой версией файла).
 */
:root:not([data-theme="dark"]),
[data-theme="light"] {
  --pico-primary: #1f6b5b;
  --pico-primary-background: #1f6b5b;
  --pico-primary-border: #1f6b5b;
  --pico-primary-hover: #17564a;
  --pico-primary-hover-background: #17564a;
  --pico-primary-hover-border: #17564a;
  --pico-primary-underline: rgba(31, 107, 91, 0.5);
  --pico-primary-hover-underline: #17564a;
  --pico-primary-focus: rgba(31, 107, 91, 0.375);
  --pico-primary-inverse: #fff;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --pico-primary: #6fc3ae;
    --pico-primary-background: #6fc3ae;
    --pico-primary-border: #6fc3ae;
    --pico-primary-hover: #8ad4c1;
    --pico-primary-hover-background: #8ad4c1;
    --pico-primary-hover-border: #8ad4c1;
    --pico-primary-underline: rgba(111, 195, 174, 0.5);
    --pico-primary-hover-underline: #8ad4c1;
    --pico-primary-focus: rgba(111, 195, 174, 0.375);
    --pico-primary-inverse: #0b1f1a;
  }
}

[data-theme="dark"] {
  --pico-primary: #6fc3ae;
  --pico-primary-background: #6fc3ae;
  --pico-primary-border: #6fc3ae;
  --pico-primary-hover: #8ad4c1;
  --pico-primary-hover-background: #8ad4c1;
  --pico-primary-hover-border: #8ad4c1;
  --pico-primary-underline: rgba(111, 195, 174, 0.5);
  --pico-primary-hover-underline: #8ad4c1;
  --pico-primary-focus: rgba(111, 195, 174, 0.375);
  --pico-primary-inverse: #0b1f1a;
}

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

/* Паспорт — это серийные номера, даты и показания счётчиков. Цифры обязаны
   стоять в колонку, иначе списки и таблицы «пляшут» на каждой строке. */
body {
  font-variant-numeric: tabular-nums;
}

/* Заголовки чуть плотнее и легче дефолтных 700/normal: крупный кегль с
   обычным трекингом на 390 px рассыпается на отдельные слова. */
h1,
h2,
h3 {
  --pico-font-weight: 650;
  letter-spacing: -0.015em;
}

h1 {
  --pico-font-size: 1.75rem;
}

@media (min-width: 768px) {
  h1 {
    --pico-font-size: 2.125rem;
  }
}

/* --- Колонка и шапка ------------------------------------------------------
 *
 * Одна колонка на всех ширинах. Pico на большом экране растягивает main до
 * 1450 px — для сплошного текста и форм это нечитаемая длина строки.
 */
body > header,
body > main {
  max-width: var(--dpp-content-width);
}

/* Шапка узкая: она подпись, а не палуба. Перенос — страховка на случай
   длинного адреса почты, чтобы полоса не выехала за экран. */
body > header {
  padding-top: calc(var(--pico-block-spacing-vertical) / 2);
  padding-bottom: 0;
}

body > header nav {
  flex-wrap: wrap;
}

/* Метка приложения набрана как шильдик на оборудовании — то самое, что этот
   продукт и оцифровывает: моноширинный капс вразрядку, мелко и тихо. */
.mark {
  color: var(--pico-contrast);
  font-family: var(--pico-font-family-monospace);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

@media (min-width: 576px) {
  .mark {
    font-size: 0.75rem;
    letter-spacing: 0.14em;
  }
}

/* Адрес вошедшего — справка, а не действие: тихий цвет, мелкий кегль и
   обрезка, чтобы длинный адрес не выдавил кнопку выхода за край экрана. */
.account {
  display: inline-block;
  overflow: hidden;
  max-width: 14ch;
  color: var(--pico-muted-color);
  font-size: 0.8125rem;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: middle;
}

/* Кнопка-ссылка: действие в шапке (выход) не спорит по весу с действием на
   странице. Pico бесклассовый красит любую button как основную. */
.link-button {
  --pico-background-color: transparent;
  --pico-border-color: transparent;
  --pico-color: var(--pico-primary);
  --pico-underline: var(--pico-primary-underline);
  padding-inline: 0;
  font-size: 0.875rem;
  text-decoration: underline 1px var(--pico-underline);
  text-underline-offset: 0.2em;
}

.link-button:is(:hover, :active, :focus) {
  --pico-background-color: transparent;
  --pico-border-color: transparent;
  --pico-color: var(--pico-primary-hover);
}

/* Сообщение об ошибке рядом с полем. Само поле Pico красит по aria-invalid,
   а подпись под ним оставляет обычным текстом — её и красим здесь.

   Отрицательный отступ сверху подтягивает подпись к своему полю: между ними
   не должно быть больше воздуха, чем до следующего поля, иначе сообщение
   читается как относящееся к нижнему. */
.error {
  margin-top: calc(var(--pico-spacing) * -0.5);
  color: var(--pico-del-color);
  font-size: 0.875rem;
}

/* --- Сообщение уровня формы ----------------------------------------------
 *
 * Отказ, не привязанный ни к какому полю: троттлинг и родня. Полосой слева,
 * а не заливкой — заливка на этом каркасе конкурировала бы по весу с кнопкой
 * действия, а сообщение обязано читаться и не кричать.
 */
.notice {
  margin-bottom: var(--pico-spacing);
  padding: calc(var(--pico-spacing) * 0.5) 0
    calc(var(--pico-spacing) * 0.5) var(--pico-spacing);
  border-left: 3px solid var(--pico-muted-border-color);
  font-size: 0.9375rem;
}

.notice-error {
  border-left-color: var(--pico-del-color);
}

/* --- Кнопка в ожидании ответа ---------------------------------------------
 *
 * htmx вешает класс htmx-request на элемент, который шлёт запрос (у нас —
 * на саму форму), на время запроса. Селекторы поэтому по вложенности: подписи
 * кнопки меняются местами, где бы она внутри формы ни лежала. Без видимой
 * реакции фоновая отправка выглядит зависанием, и человек жмёт второй раз.
 *
 * Кнопка на форме растянута во всю ширину колонки (Pico), поэтому подмена
 * подписи её не дёргает и удерживать её ширину незачем.
 */
.button-busy {
  display: none;
}

.htmx-request .button-label {
  display: none;
}

.htmx-request .button-busy {
  display: inline;
}

/* Пропуск навигации: первое, что встречает клавиатура и скринридер.
   Видим только в фокусе — иначе занимал бы место у всех остальных. */
.skip-link {
  position: absolute;
  top: 0;
  left: -9999px;
  z-index: 10;
  padding: 0.5rem 1rem;
  background: var(--pico-background-color);
}

.skip-link:focus {
  left: 0;
}

/* --- Прогресс фоновой работы ----------------------------------------------
 *
 * Элемент опрашивает сервер и подменяет сам себя, пока работа идёт (00 §7.1).
 * Восемь секунд ожидания нужно чем-то занять, и занимает их список стадий:
 * без него «идёт» и «зависло» выглядят одинаково.
 *
 * Карточка обрамлена рамкой, а не заливкой: она встаёт посреди страницы, и
 * заливка спорила бы по весу с кнопкой действия.
 */
.progress {
  margin-bottom: var(--pico-block-spacing-vertical);
  padding: var(--pico-spacing);
  border: 1px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
}

.progress-title {
  margin-bottom: calc(var(--pico-spacing) * 0.5);
  font-weight: 600;
}

/* Живой регион: одна строка «что идёт сейчас», спрятанная от глаз. Глазами
   то же самое видно по списку стадий, а скринридеру нужна именно короткая
   строка — регион объявляется целиком на каждое изменение внутри него, и
   заголовок со списком зачитывались бы заново полтора раза в секунду
   (см. views.progressStatusText).
 *
 * Прячется клипом, а не display:none или visibility:hidden: спрятанный этими
 * двумя способами регион не объявляется вовсе. */
.progress-status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Итоговая карточка помечена цветом акцента: работа закончилась, и это
   единственное событие, ради которого человек смотрел на экран.
 *
 * Селектор смотрит на родителя не от хорошей жизни: рамку рисует внешняя
 * .progress, а классом «готово» помечено подменяемое содержимое внутри неё —
 * рамка пережить подмену обязана (см. views.Progress), поэтому сменить ей
 * класс с сервера нечем. :has поддержан всеми текущими браузерами; там, где
 * его нет, теряется только цвет рамки. */
.progress:has(.progress-done) {
  border-color: var(--pico-primary-border);
}

.stages {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Стадия: маркер слева, подпись справа. Ширина маркера фиксированная, иначе
   строки съезжают по горизонтали при каждой смене состояния. */
.stage {
  position: relative;
  margin-bottom: calc(var(--pico-spacing) * 0.25);
  padding-left: 1.5rem;
  font-size: 0.9375rem;
}

.stage::before {
  position: absolute;
  left: 0;
  width: 1.5rem;
  content: "·";
}

/* Ещё не начатая стадия приглушена: список показывает, что впереди, но
   внимание удерживает текущая строка. */
.stage-pending {
  color: var(--pico-muted-color);
}

.stage-active {
  font-weight: 600;
}

.stage-active::before {
  color: var(--pico-primary);
  content: "→";
}

.stage-done::before {
  color: var(--pico-primary);
  content: "✓";
}

/* Сорвавшаяся стадия — тем же красным, что и ошибка поля: у отказа в
   приложении один цвет, где бы он ни случился. */
.stage-failed {
  color: var(--pico-del-color);
}

.stage-failed::before {
  color: var(--pico-del-color);
  content: "×";
}

/* --- Пустое состояние -----------------------------------------------------
 *
 * Первое, что видит вошедший, пока объектов нет. Рамка пунктиром, а не
 * заливкой: место обозначено как пустое и ждущее заполнения — заливка читалась
 * бы как содержимое, которого нет.
 */
.empty {
  margin-bottom: var(--pico-block-spacing-vertical);
}

.empty > p:last-child {
  margin-bottom: 0;
}

/* Табличка без гравировки — тот же шильдик, что метка в шапке, но пустой.
   Единственное место экрана, где голос продукта звучит громко; остальное
   тихо, как и договаривались с каркасом. */
.plate {
  margin-bottom: var(--pico-block-spacing-vertical);
  padding: var(--pico-block-spacing-vertical) var(--pico-spacing);
  border: 1px dashed var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
  color: var(--pico-muted-color);
  font-family: var(--pico-font-family-monospace);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-align: center;
  text-transform: uppercase;
}

@media (min-width: 576px) {
  .plate {
    font-size: 0.75rem;
    letter-spacing: 0.14em;
  }
}

/* --- Нижняя панель действий (00 §3) ---------------------------------------
 *
 * Главное действие экрана прибито к низу: телефон держат одной рукой, и
 * большой палец достаёт до нижнего края экрана, а не до верхнего.
 *
 * sticky, а не fixed, и это существенно. Fixed-панель висит поверх страницы
 * всегда: на телефоне её перекрывает всплывающая клавиатура, а содержимое под
 * ней приходится компенсировать отступом снизу, который потом забывают
 * поставить на новом экране. Sticky прилипает к низу окна, пока страница
 * длиннее экрана, и встаёт в поток, когда прокрутка кончилась, — компенсировать
 * нечего.
 *
 * Полоса под панелью — фон и граница: без них текст, проезжающий под кнопкой,
 * читается вперемешку с ней.
 */
/* Панель прибита к низу, то есть плывёт над содержимым, пока не доехала до
   своего места в потоке. Само по себе это и есть замысел (00 §3), но браузер
   об этом не знает: он считает элемент показанным, если тот попал в экран, —
   и переводя фокус по Tab, оставляет поле под панелью. Человек нажимает Tab,
   печатает и не видит куда: прогон 18.08.2026 поймал это на форме добавления
   места (docs/testing/history/2026-08-18-e21.md).

   scroll-padding объявляет нижнюю полосу непригодной для показа, и браузер
   доводит туда прокрутку сам — при переходе по Tab, по якорю и при вызове
   scrollIntoView. Ставится только там, где панель есть: :has держит правило
   ровно на тех страницах, где оно нужно, а не на всех подряд.

   Число — верхняя оценка высоты панели (поле выбора файла плюс две полосы
   по 44 px с зазорами и отступами): точную высоту CSS не измеряет, а запас
   вниз безопасен — лишняя прокрутка на пару строк заметно лучше, чем поле
   под панелью. Меняете состав панели — проверьте UI-13(б) и шаг с Tab
   в UI-11. */
html:has(.action-bar) {
  scroll-padding-block-end: 14rem;
}

.action-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  padding-top: calc(var(--pico-spacing) * 0.75);
  /* Safe area — полоса жеста на телефонах без кнопки «домой»: без неё кнопка
     оказывается ровно под ней. */
  padding-bottom: calc(var(--pico-spacing) * 0.75 + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--pico-muted-border-color);
  background: var(--pico-background-color);
}

/* Кнопка во всю ширину колонки и не мельче 44 px по высоте — минимальная зона
   нажатия пальцем. Pico своим отступам даёт примерно столько же, но «примерно»
   здесь зависит от кегля шрифта, поэтому предел записан явно. */
.action-bar :is(a[role="button"], button) {
  display: block;
  width: 100%;
  min-height: 2.75rem;
  margin-bottom: 0;
  text-align: center;
}

/* --- Галерея фотографий (E04) ---------------------------------------------
 *
 * Сетка, а не список: снимков десятки, и колонка в один столбец превращает
 * экран объекта в бесконечную ленту. Ширина колонки задана минимумом, а не
 * их числом, — на 390 px помещаются две, на планшете четыре, и ни одной
 * контрольной точки для этого не нужно.
 */
.media-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--pico-spacing);
  margin-bottom: var(--pico-block-spacing-vertical);
}

/* Карточка живёт в сетке, поэтому собственные отступы Pico ей мешают:
   снизу образуется двойной зазор. */
.media-card {
  margin-bottom: 0;
  padding: calc(var(--pico-spacing) * 0.5);
}

/* Превью занимает всю ширину карточки и не растягивается: снимки приходят
   и вертикальные, и горизонтальные. */
.media-card img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--pico-border-radius);
}

.media-card :is(p, .notice) {
  margin-bottom: 0;
}

/* Кадр в карточке подтверждения. Высота ограничена, а не ширина: с ним
   сверяют поля формы, и снимок во весь экран уводил бы саму форму под сгиб
   на телефоне — то есть прятал бы то, ради чего кадр и показан. Мелкий шрифт
   шильдика в превью всё равно не читается, поэтому картинка обёрнута ссылкой
   на снимок целиком. */
.confirm-photo {
  display: block;
  margin-bottom: var(--pico-spacing);
}

.confirm-photo img {
  display: block;
  width: 100%;
  max-height: 45vh;
  object-fit: contain;
  border-radius: var(--pico-border-radius);
  background: var(--pico-code-background-color);
}

.media-title {
  font-size: 0.9375rem;
}

/* Приглашение лежит внутри галереи и прячется, как только в ней появляется
   первая карточка: приехать она может подменой htmx, и условие в разметке
   про такое появление ничего не знает (см. views.PropertyPage).

   Ширина — во всю сетку: в одной колонке текст сжимался бы в столбик. */
.media-empty {
  grid-column: 1 / -1;
  color: var(--pico-muted-color);
}

.media-gallery:has(.media-card) .media-empty {
  display: none;
}

/* Форма загрузки стоит в нижней панели, и поле выбора файла в ней — такое же
   действие, как кнопка: во всю ширину и с той же зоной нажатия. */
.action-bar input[type="file"] {
  margin-bottom: calc(var(--pico-spacing) * 0.5);
}

/* Второй путь создания («Завести вручную») стоит в той же панели, что и
   загрузка, и оба действия занимают всю ширину. Зазор между ними задан явно:
   правило выше обнуляет margin-bottom у кнопок панели, и без него ссылка
   слипалась бы с «Загрузить» в одну полосу на 44 + 44 px. */
.action-bar a[role="button"] {
  margin-top: calc(var(--pico-spacing) * 0.5);
}

/* --- Список оборудования (E21) --------------------------------------------
 *
 * Строка, а не карточка сетки: у оборудования читают название и шильдик,
 * а обложка здесь только опознавательный знак — на 390 px две колонки
 * из текста сделали бы столбик по слову в строке.
 */
.asset-card {
  display: flex;
  gap: var(--pico-spacing);
  align-items: flex-start;
  margin-bottom: var(--pico-spacing);
  padding: calc(var(--pico-spacing) * 0.5);
}

/* Обложка ужата до значка и не растягивается: превью приходит в 640 px
   по длинной стороне, и во всю ширину строки оно оттеснило бы название
   под сгиб. Доля ширины, а не пиксели, — на узком экране значок сжимается
   вместе с ним. */
.asset-card img {
  flex: 0 0 25%;
  max-width: 6rem;
  height: auto;
  border-radius: var(--pico-border-radius);
}

/* Абзацы внутри строки идут вплотную: это подписи одной карточки,
   а не отдельные мысли. */
.asset-card :is(h3, p) {
  margin-bottom: calc(var(--pico-spacing) * 0.25);
}

.asset-card :is(h3, p):last-child {
  margin-bottom: 0;
}

.asset-card h3 {
  font-size: 1.125rem;
}

/* Описание работы — единственное поле продукта, куда человек пишет фразами
   и переводами строк (views.WorkDescriptionField, две тысячи символов).
   Без pre-line браузер схлопывает их в одну сплошную строку, и записанное
   «поменяли фильтр / подтянули клапан» читается уже не тем, чем записано.
   pre-line, а не pre: длинные строки обязаны переноситься по ширине экрана. */
.work-note {
  white-space: pre-line;
}

/* --- Записки «важно знать» (E27) ------------------------------------------
 *
 * Текст записки — второе поле продукта, куда человек пишет фразами и
 * переводами строк (views.NoteBodyField, две тысячи символов). Правило то же,
 * что у описания работы: без pre-line браузер схлопывает их в одну сплошную
 * строку, и «кран за машинкой / ключ на 19 рядом» читается уже не тем,
 * чем записано.
 */
.note-body {
  white-space: pre-line;
}

/* Фотография в строке записки ужата до значка и не растягивается: превью
   приходит в 640 px по длинной стороне, и во всю ширину оно оттеснило бы
   и текст, и подпись привязки под сгиб. Доля ширины, а не пиксели, —
   на узком экране значок сжимается вместе с ним (то же правило, что
   у обложки в .asset-card). */
.note-photo {
  display: block;
  width: 40%;
  max-width: 8rem;
  height: auto;
  border-radius: var(--pico-border-radius);
}

/* Выбор фотографии — сетка превью, а не столбик строк: снимки различают
   глазами, и двадцать подписей «Фото от 18.08.2026» друг под другом
   не отвечают на «как выглядит нужный вентиль». Колонки считает браузер
   по минимальной ширине, как в галерее объекта: на 390 px помещаются две. */
.note-photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
  gap: calc(var(--pico-spacing) * 0.5);
  align-items: start;
}

/* Легенда и сообщение группы идут во всю ширину сетки: в одной колонке
   они сжались бы в столбик по слову. */
.note-photos :is(legend, .notice) {
  grid-column: 1 / -1;
}

/* Вариант целиком — зона нажатия: палец попадает и в превью, и в подпись,
   а не только в кружок радио (UI-чеклист, 390 px). */
.note-photos label {
  display: block;
  padding: calc(var(--pico-spacing) * 0.25);
  margin-bottom: 0;
}

.note-photos img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--pico-border-radius);
}

/* Подпись под превью мельче основного текста: это дата загрузки, способ
   отличить два похожих снимка, а не содержание варианта. */
.note-photos span {
  font-size: 0.8125rem;
}

/* Превью документа-фотографии ужато тем же правилом, что и снимок в строке
   записки: конвейер строит его в 640 px по длинной стороне, и во всю ширину
   оно оттеснило бы под сгиб и название бумаги, и даты, и подпись привязки. */
.document-photo {
  display: block;
  width: 40%;
  max-width: 8rem;
  height: auto;
  border-radius: var(--pico-border-radius);
}
