/**
 * Витрина. Светлая бумага, антиква в заголовках и один насыщенный цвет на весь
 * магазин. Цветное здесь — сами цветы: чем спокойнее вокруг, тем громче кадр.
 *
 * Антиква — Cormorant Garamond, лежит своя в /fonts. Раньше тут стояла PT Serif
 * без единого @font-face: на этом маке она есть, а на телефоне покупателя её
 * нет, и весь заголовок молча падал в системный Times. Шрифт, который есть
 * только у разработчика, — это не выбранный шрифт.
 */

/* Кириллица, кыргызская кириллица и латиница разными кусками: браузер
   скачивает только тот, который нужен странице. Русской витрине латинский
   кусок нужен ровно на «maryam flowers» в шапке — тянуть его целиком ради
   двух слов незачем, а кыргызский она не тронет вовсе.

   Кусков кириллицы ДВА, и это не запас. В `cyrillic` (U+0400–045F) входит
   русский алфавит и только он; кыргызские ү, ө, ң лежат в `cyrillic-ext`.
   Без второго куска браузер честно рисовал их системной антиквой — и
   «Бүгүн дүкөндө» выходило набранным двумя гарнитурами внутри одного слова.
   Заметно это только на кыргызской странице, поэтому и вылезло не сразу.

   display: swap — текст показывается сразу системной антиквой и переверстается,
   когда файл придёт. Умолчание (block) прячет заголовок на три секунды: на
   медленном мобильном интернете первый экран выглядит пустым. */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-cyrillic-400-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-cyrillic-400-italic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-cyrillic-600-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-cyrillic-ext-400-normal.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-cyrillic-ext-400-italic.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-cyrillic-ext-600-normal.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122, U+2212;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-latin-400-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122, U+2212;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/cormorant-garamond-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122, U+2212;
}

:root {
  color-scheme: light;
  /* Бумага была #f4f1ea — плотный грязновато-бежевый. Розовый и белый букет на
     нём сереют: у кадра и фона почти одна светлота, и снимок перестаёт быть
     ярким пятном. Бумага поднята почти до белого, а тёплый подтон оставлен —
     чистый #fff рядом с цветами выглядит канцелярским. */
  --paper: #fdfaf6;
  /* Тройкой, а не цветом: из неё собираются полупрозрачные слои — приклеенная
     шапка и растяжки по краям кадра. Записанные готовым rgba(), они держали
     бумагу числом и на тёмной теме оставались светлыми пятнами. */
  --paper-rgb: 253, 250, 246;
  --paper-2: #f6efe9;
  --paper-3: #ece2da;
  --ink: #2b211b;
  /* Второй и третий слой были подобраны на глаз и оба не дотягивали до порога
     читаемости: третьим набраны состав букета, «осталось два», надчёркивания и
     все надписи в 10 прописными — на бумаге они давали 2.75:1 при норме 4.5,
     а в подвале, где бумага темнее, 2.47. Это не придирка: так выглядит текст
     для человека с обычным возрастным зрением на солнце. Слои сдвинуты вниз
     оба сразу, чтобы разница между ними осталась видна. */
  --ink-2: #6a5b4f;
  /* Витрину перекрасили в тёплое, а кабинет остался на холодном угле — рядом
     это читалось как две разные программы. Слои взяты с витрины и проверены
     заново: у кабинета есть своя тёмная бумага (`--paper-3`), которой на
     витрине нет, и на ней витринный #786a5b давал 4.10 при норме 4.5.
     Третий слой чуть темнее витринного: 4.63 на самой тёмной подложке. */
  --ink-3: #6f6254;
  --line: rgba(74, 48, 34, 0.14);
  --line-soft: rgba(74, 48, 34, 0.07);
  /* Волосяная линия по срезу кадра, она же под курсором, и тень отрыва. */
  --edge: rgba(74, 48, 34, 0.07);
  --edge-on: rgba(74, 48, 34, 0.16);
  --drop: rgba(74, 48, 34, 0.42);
  /* Затемнение под шторкой. */
  --scrim: rgba(43, 33, 27, 0.42);
  /* Как ложится подпись на кадр: на светлой бумаге умножением (строка темнеет
     по светлому снимку), на тёмной так она бы пропала — см. тёмную тему. */
  --cap-blend: multiply;
  /* Поправка к снимкам. На бумаге не нужна; на тёмной теме гасит студийный
     белый фон, чтобы карточка не светила в глаза. */
  --shot: none;
  /* Насколько виден размытый цветочный фон. */
  --blooms: 1;
  /* Поля вокруг снимка нет: кадр заполняет свою плитку целиком и читается
     плоскостью, а не картинкой в паспарту. Поле осталось ровно там, где без
     него нельзя: у подписи героя (иначе строка ляжет на обрез страницы) и у
     кадра в шторке (прижатый к краю, он читается обрезанным). */
  --mat: clamp(22px, 2.4vw, 38px);
  /* Два разных смысла, которые раньше носил один цвет.
     `--hot` — «это действует»: кнопка, выбранный ярлык, идущий показ. Он берёт
     цвет магазина с витрины, и владелец меняет его в кабинете; здесь стоит то
     же значение, что и по умолчанию на витрине.
     `--alarm` — «тут беда»: отказ формы, поле с ошибкой, «нет связи». Пока это
     был один токен, перекрасить кабинет в золото значило написать золотом
     сообщение об ошибке. Заливкой годятся оба (белым по золоту 5.2, по розе
     6.1); текстом — только роза: золото на самой тёмной подложке кабинета
     даёт 4.05, поэтому золотом ничего не набирается. */
  --hot: #8e6534;
  /* Тёмная полоса-якорь. В светлой версии темнее бумаги, в тёмной — светлее:
     смысл не в «чёрном», а в том, что это другая плоскость. При #14100d по
     бумаге #17130f граница между ними пропадала, и полоса переставала делить
     страницу. */
  --band: #221a13;
  /* Что написано поверх розы. Единственный жёстко заданный #fff в проекте стоял
     именно здесь — и на тёмной теме, где заливка светлеет, белый по ней давал
     2.5:1: сообщение об ошибке было наименее читаемым местом всей витрины. */
  --on-hot: #fff;
  --alarm: #9e4557;
  --on-alarm: #fff;
  /* Высота шапки: от неё считается и просвет под ней. На телефоне 84px — это
     десятая часть экрана, отданная строке из двух слов. */
  --bar: clamp(62px, 5.8vw, 84px);
  --serif: "Cormorant Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  /* Поле в долях экрана — и до 1920px это верно: страница дышит вместе с
     монитором. Верхнего предела не было вовсе, и дальше она просто тянулась:
     на 3440px снимок в витрине выходил метровым, ползунок бюджета — длиной в
     полтора экрана, а заголовок упирался в свой потолок 90px и повисал в
     пустом углу. Полоса содержимого теперь не шире 1760px, а всё, что сверх
     неё, уходит в поля. Число выбрано так, чтобы на 1920px не поменялось
     ровно ничего: (1920 − 1760) / 2 = 80px — это и есть прежние 4.2vw. */
  --measure: 1760px;
  /* Нижний предел был 22px, и на телефоне он и работал всегда: 4.2vw обгоняют
     его только на 524px, то есть уже на планшете. Выходило, что кегли на
     телефоне растут вместе с экраном, а поле стоит намертво — и на 430px
     строка подходила к борту вплотную. Теперь предел 26px: на глаз это
     разница между «текст в рамке страницы» и «текст, прижатый к стеклу», а
     заголовку на 320px остаётся 268px — прежние 33px в него проходят с
     запасом. Выше 620px всё как было, там правит доля экрана. */
  --gutter: max(26px, 4.2vw, (100vw - var(--measure)) / 2);
  /* Поля с учётом выреза. `viewport-fit=cover` пускает страницу под скруглённые
     углы и «чёлку», и в альбомной ориентации айфона край съедается — поэтому
     каждое поле берёт максимум из своего отступа и безопасной зоны. */
  --gut-l: max(var(--gutter), env(safe-area-inset-left));
  --gut-r: max(var(--gutter), env(safe-area-inset-right));
}

/* ─────────────────────────── тёмная версия ───────────────────────────
   Не «инверсия», а вечерний зал: тёплый уголь с коричневым подтоном, тёплый
   же белый вместо чистого. Синий дарк на цветочном магазине читается панелью
   разработчика, а не витриной.

   Тёмная включается ТОЛЬКО по кнопке, за системой не идёт. Раньше шло: стояло
   `@media (prefers-color-scheme: dark)`, и у любого, у кого телефон или мак в
   тёмном режиме — а это почти все, — цветочный магазин открывался угольным.
   Букет на угле сереет, а сам тон читается вечерним баром, не витриной.
   Умолчание для магазина — бумага; уголь остаётся тем, кто его выбрал сам.

   Два состояния:
   1. выбора не было или выбрали светлую — работает :root выше;
   2. выбрали тёмную — `data-theme="dark"`.
   Ставит метку встроенный скрипт в <head> до первой отрисовки, иначе у тех,
   кто выбрал уголь, страница успевает моргнуть бумагой. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #17120e;
  --paper-rgb: 23, 18, 14;
  --paper-2: #201a14;
  --paper-3: #2a231b;
  --ink: #f2e9df;
  --ink-2: #b9a998;
  /* На угле третий слой поднимаем, а не опускаем: 4.4:1 в подвале превращалось
     в 4.07. Тёплая версия — 4.64 на самой светлой из тёмных подложек. */
  --ink-3: #9a8a79;
  --line: rgba(242, 233, 223, 0.18);
  --line-soft: rgba(242, 233, 223, 0.09);
  --edge: rgba(242, 233, 223, 0.1);
  --edge-on: rgba(242, 233, 223, 0.24);
  --drop: rgba(0, 0, 0, 0.62);
  --scrim: rgba(0, 0, 0, 0.66);
  /* Оба цвета с бумаги на угле почти черны — поднимаем до светлых, иначе
     кнопка гаснет, а «последний день» перестаёт быть тревогой. Надпись по ним
     тогда наоборот тёмная: 8.4 по золоту, 7.3 по розе. */
  --hot: #d6ab6d;
  --on-hot: #211608;
  --alarm: #e8859d;
  --on-alarm: #17120e;
  --cap-blend: screen;
  /* Снимки сняты в светлой студии: фон у них почти белый, и на угле каждая
     карточка светит фонарём. Гасим настолько, чтобы ушла резь, но букет
     остался ярче страницы — он тут главный. */
  --shot: brightness(0.9) saturate(1.04);
  --blooms: 0.5;
  --band: #2b2318;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
/* Прокрутка по ссылкам в шапке. `scroll-padding-top` — потому что шапка
   приклеена: без него якорь доводит раздел ровно под неё, и заголовок, ради
   которого нажимали, оказывается закрыт. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--bar) + 18px);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.7;
  /* Цифры — прописные, во всю высоту. У Cormorant по умолчанию минускульные:
     красивые в наборе, но у них разная высота и хвосты уходят под строку. В
     сводке кабинета «1 заявка · 0 доставок · 6 букетов» это читалось как
     «I o 6», а в цене и телефоне такие цифры просто тормозят чтение. Свойство
     наследуется, поэтому строки достаточно одной; гротеск и так так набирает,
     для него это ничего не меняет. */
  font-variant-numeric: lining-nums;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
button { font: inherit; cursor: pointer; background: none; border: none; padding: 0; color: inherit; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; }

/* ─────────────────────────── движение ───────────────────────────
   Слой фона лежит под содержимым, но над заливкой страницы. Заливка задана
   только на body, у секций её нет — поэтому фон видно насквозь. */
.blooms {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
}
header, main, footer { position: relative; z-index: 1; }

/* Проход и дыхание — на обёртках, размытие — на самой картинке и без анимации.
   Размытие в полсотни пикселей по полэкрана браузер считает один раз и дальше
   просто двигает готовое; стоит анимировать сам размытый слой — и он начнёт
   пересчитываться каждый кадр. */
.blooms .drift {
  position: absolute;
  animation: drift ease-in-out infinite alternate;
  /* Ход от прокрутки — отдельным свойством: transform занят анимацией прохода,
     и в одном свойстве их было бы не сложить. */
  translate: 0 var(--by, 0px);
  will-change: transform;
}
.blooms .pulse {
  position: absolute;
  inset: 0;
  animation: breathe-bloom ease-in-out infinite alternate;
  will-change: transform;
}
.blooms .bloom {
  position: absolute;
  inset: 0;
  background-size: cover;
  /* Размытие подобрано по размеру самого цветка, а не «побольше»: за полсотни
     пикселей от бутонов не остаётся формы, и пятно читается цветной кляксой.
     На тридцати круглые головки ещё угадываются — это и делает фон цветами.
     Насыщенность поднята потому, что расфокус её съедает. */
  filter: blur(30px) saturate(1.3);
  /* На тёмной бумаге те же пятна светятся вдвое сильнее — гасим темой. */
  opacity: var(--blooms);
  /* Края у пятна быть не должно ни при какой непрозрачности: маска уводит его
     в ничто задолго до границы своего квадрата. */
  --fade: radial-gradient(closest-side, #000 8%, rgba(0, 0, 0, 0.6) 44%, transparent 76%);
  -webkit-mask-image: var(--fade);
  mask-image: var(--fade);
}
@keyframes drift {
  from { transform: translate3d(-3.5vw, 2.5vh, 0); }
  to { transform: translate3d(3.5vw, -2.5vh, 0); }
}
/* Дыхание, а не пульс: ход меньше седьмой доли и держится полминуты. Всё, что
   быстрее, перестаёт быть фоном и начинает отвлекать от товара. */
@keyframes breathe-bloom {
  from { transform: scale(1); }
  to { transform: scale(1.16); }
}

/* Выход блоков. Держим на непрозрачности и сдвиге: они считаются композитором,
   и от них не дёргается раскладка соседей. */
.motion [data-rise] {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
}
.motion [data-rise].shown {
  opacity: 1;
  transform: none;
  transition:
    opacity 1s var(--ease-soft) var(--d, 0s),
    transform 1.15s var(--ease) var(--d, 0s);
}

/* Заголовок выходит не блоком, а строками из-под обреза — как строка, которая
   выезжает из-под линейки. Общий подъём для него снят: два движения на одном
   элементе читаются рябью, а не выходом. */
.motion .hero h1[data-rise] { opacity: 1; transform: none; }
.hero h1 .ln {
  display: block;
  overflow: hidden;
  /* Обрез срезал бы хвосты у «у», «д», «р». Даём строке запас снизу и тут же
     снимаем его отступом, чтобы межстрочник не разъехался. */
  padding-bottom: 0.11em;
  margin-bottom: -0.11em;
}
.hero h1 .ln > i { display: block; font-style: inherit; }
.motion .hero h1 .ln > i { transform: translate3d(0, 112%, 0); }
.motion .hero h1.shown .ln > i {
  transform: none;
  transition: transform 1.25s var(--ease) var(--ld, 0s);
}
.motion .hero h1.shown .ln:nth-child(2) > i { --ld: 0.1s; }
:focus-visible { outline: 1px solid var(--ink); outline-offset: 4px; }
::selection { background: var(--ink); color: var(--paper); }

/* Второй голос вёрстки: всё служебное говорит прописными вполголоса.
   Ключевое слово — служебное. Отсюда убраны `.chip` и `.scroll-cue`: поводы —
   главный способ выбрать букет, а «смотреть витрину» — единственное действие
   первого экрана. Набранные десятью прописными в разрядку, они выглядели
   сноской, а весь раздел подбора — стеной мелких капсов, которую не читают.
   Прописные остаются там, где текст и правда подпись, а не действие.

   10.5px было мелко даже для подписей: это уже не «вполголоса», а шёпот.
   Поднято до 11.5 — разница видна на пределе, но именно на нём эти строки и
   читаются. */
.eyebrow,
nav a,
.callbtn,
.pill,
.state,
.left,
.pane label,
.help,
.fineprint,
.close {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.5;
}
.eyebrow { color: var(--ink-3); display: block; }

/* ─────────────────────────── шапка ─────────────────────────── */

.top {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  transition: background 0.5s var(--ease-soft), border-color 0.5s var(--ease-soft);
  border-bottom: 1px solid transparent;
}
/* Пока под шапкой идёт кадр первого экрана, меню набрано белым: под ним
   лежит затемнение снимка, и обычный тёмный набор в нём пропадает. Класс
   ставит app.js по высоте героя. Цвета прямые, не из токенов: затемнение
   тёмное в обеих темах. */
/* Своя растяжка под шапкой, а не затемнение верха снимка. Шапка приклеена, а
   кадр под ней уезжает: тёмная полоса, нарисованная на фотографии, остаётся
   внизу, и через сотню пикселей прокрутки меню оказывается на светлых
   лепестках. Растяжка, привязанная к самой шапке, едет вместе с ней. */
.top.over {
  background: linear-gradient(to bottom, rgba(12, 9, 7, 0.62) 0, rgba(12, 9, 7, 0.28) 62%, rgba(12, 9, 7, 0) 100%);
}
.top.over a,
.top.over button { color: rgba(255, 255, 255, 0.92); }
.top.over .brand { color: #fff; }
.top.over .brand small { color: rgba(255, 255, 255, 0.6); }
@media (hover: hover) {
  .top.over nav a:hover { color: #fff; }
}
/* Кнопка стоит в правом верхнем углу — там растяжка шапки самая слабая, а
   снимок как раз светлый. Своя полупрозрачная заливка добирает контраст и
   заодно делает её похожей на кнопку, а не на рамку поверх фотографии. */
.top.over .callbtn {
  border-color: rgba(255, 255, 255, 0.5);
  color: #fff;
  background: rgba(12, 9, 7, 0.34);
  backdrop-filter: blur(6px);
}
@media (hover: hover) {
  .top.over .callbtn:hover { background: #fff; color: var(--ink); }
}
@media (hover: hover) {
  .top.over .themebtn:hover { background: rgba(255, 255, 255, 0.14); color: #fff; }
}
.top.stuck {
  background: rgba(var(--paper-rgb), 0.86);
  backdrop-filter: blur(16px) saturate(1.2);
  border-bottom-color: var(--line);
}
.bar {
  display: flex;
  align-items: center;
  gap: 30px;
  height: var(--bar);
  padding: 0 var(--gut-r) 0 var(--gut-l);
}
.brand {
  font-family: var(--serif);
  font-size: 23px;
  letter-spacing: -0.015em;
  line-height: 1;
  margin-right: auto;
}
.brand small {
  display: block;
  font-family: var(--sans);
  font-size: 9.5px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: 7px;
}
nav { display: flex; gap: 32px; }
nav a { color: var(--ink-2); transition: color 0.4s var(--ease-soft); }
@media (hover: hover) {
  nav a:hover { color: var(--ink); }
}
.callbtn {
  border: 1px solid var(--ink);
  padding: 13px 22px;
  transition: background 0.5s var(--ease-soft), color 0.5s var(--ease-soft);
}
@media (hover: hover) {
  .callbtn:hover { background: var(--ink); color: var(--paper); }
}

/* Переключатель темы. Иконка без подписи намеренно: солнце и месяц читаются
   на любом языке, а строка «тёмная тема» в шапке из двух слов стоила бы места
   больше, чем весит. Поле вокруг доводит цель до пальца — сама иконка 18px. */
.themebtn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin: -11px -10px -11px 0;
  color: var(--ink-2);
  border-radius: 50%;
  transition: color 0.4s var(--ease-soft), background 0.4s var(--ease-soft);
}
@media (hover: hover) {
  .themebtn:hover { color: var(--ink); background: var(--line-soft); }
}
.themebtn svg { width: 18px; height: 18px; overflow: visible; }
.themebtn .t-sun { fill: currentColor; }
.themebtn .t-rays { stroke: currentColor; stroke-width: 1.5; fill: none; }
.themebtn .t-moon { fill: currentColor; opacity: 0; }
/* На тёмной теме показываем месяц: кнопка говорит, что включено сейчас, а не
   что случится по нажатию. Второе читается наоборот у половины людей. */
.themebtn[aria-pressed="true"] .t-sun,
.themebtn[aria-pressed="true"] .t-rays { opacity: 0; }
.themebtn[aria-pressed="true"] .t-moon { opacity: 1; }
.themebtn .t-sun,
.themebtn .t-rays,
.themebtn .t-moon { transition: opacity 0.45s var(--ease-soft); }

/* ─────────────────────────── герой ─────────────────────────── */

/* Разворот вместо двух колонок. Раньше первый экран делился пополам: слева
   текст по бумаге, справа кадр в 42vw. Две половины одинакового веса не
   спорят и не складываются — выходил первый экран без первого впечатления.
   Теперь снимок занимает экран целиком, а текст лежит на нём: появляется то,
   ради чего разворот и делают, — одна фотография во всю полосу. */
.hero {
  position: relative;
  /* Кадр уходит за отрицательный z-index. Свой слой обязателен: без него
     фотография провалилась бы под фон страницы и исчезла. */
  isolation: isolate;
  display: grid;
  justify-items: start;
  align-content: end;
  /* Высота от живой высоты экрана, а не от `vh`: на телефоне `vh` считается по
     окну с убранной адресной строкой, и «во весь экран» оказывается выше
     экрана — низ уезжает под строку браузера. `--vh` пишет app.js. */
  min-height: min(calc(var(--vh, 100vh) * 0.94), 1000px);
  padding: calc(var(--bar) + 24px) var(--gut-r) clamp(34px, 4vw, 66px) var(--gut-l);
}

.hero-shot {
  position: absolute;
  inset: 0;
  margin: 0;
  z-index: -1;
  overflow: hidden;
}
/* Волосяная рамка есть у плитки витрины — там кадр лежит на бумаге. Здесь он
   уходит в край экрана, и обводить нечего. */
.hero-shot .win { box-shadow: none; }

/* Текст по фотографии читается только на затемнении, и оно тут не подложка,
   а свет: снизу плотное — там лежит весь текст, сверху лёгкое — под ним идёт
   прозрачная шапка, и белое меню на светлом углу снимка иначе пропадает.
   Раньше на кадр натекала бумага и строки набирались тёмным; так было можно,
   пока снимок стоял отдельной колонкой. Заголовок в сто с лишним пунктов
   поверх живой фотографии тёмным не удержать — ни на одном снимке нет
   гарантированно светлого угла. */
.hero-shot::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background:
    linear-gradient(to bottom, rgba(12, 9, 7, 0.58) 0, rgba(12, 9, 7, 0.2) 120px, rgba(12, 9, 7, 0) 260px),
    /* Косая растяжка слева — то, чем в журнале сажают текст на светлый
       снимок: тёмный угол ровно под колонкой, правая половина кадра остаётся
       открытой. Одной нижней растяжки не хватало: подпись под заголовком
       ложилась на светлый бутон и пропадала. */
    linear-gradient(to right, rgba(12, 9, 7, 0.86) 0, rgba(12, 9, 7, 0.4) 46%, rgba(12, 9, 7, 0) 74%),
    linear-gradient(to top, rgba(12, 9, 7, 0.72) 0, rgba(12, 9, 7, 0.3) 34%, rgba(12, 9, 7, 0) 68%);
}

.hero-shot img {
  width: 100%;
  height: 100%;
  filter: var(--shot);
  /* Запас сверху и снизу — под проезд кадра внутри рамки (--py из motion.js).
     Без него рамка на прокрутке показала бы из-под снимка свой фон. */
  position: absolute;
  inset: -6% 0;
  height: 112%;
  object-fit: cover;
  /* Чуть выше середины: на снимке букета цветы почти всегда в верхней половине,
     а низ — стебли, ваза или стол. Ровный центр отдаёт им лишнюю треть кадра. */
  object-position: 50% 40%;
  display: block;
  opacity: 0;
  /* translate и scale — отдельными свойствами, а не одним transform: у проезда
     переход не нужен (он идёт за прокруткой и от перехода мажется), у масштаба
     нужен. В общем transform их не разделить. */
  translate: 0 var(--py, 0px);
  scale: 1.04;
  transform-origin: 50% 40%;
  transition: opacity 1.5s var(--ease-soft), scale 2.6s var(--ease);
}
.hero-shot img:not(.ready) { scale: 1.1; }
/* Кадр очень медленно дышит: полминуты на ход, амплитуда меньше десятой доли.
   Это и есть «живой» — не движение цветка, а движение воздуха вокруг него. */
.motion .hero-shot img.ready {
  animation: breathe 38s var(--ease-soft) 1.6s infinite alternate;
}
@keyframes breathe {
  from { scale: 1.04; }
  to { scale: 1.115; }
}
.hero-shot img.ready { opacity: 1; }

/* Подпись к кадру — на своём месте в журнале: мелкая строка у нижнего правого
   угла полосы, как подпись к фотографии на развороте. Режим смешивания снят:
   под ней теперь тёмная растяжка, и умножение просто съело бы строку. */
.hero-shot figcaption {
  position: absolute;
  right: var(--gut-r);
  bottom: clamp(34px, 4vw, 66px);
  z-index: 2;
  max-width: 34ch;
  text-align: right;
  color: rgba(255, 255, 255, 0.82);
  font-family: var(--sans);
  font-size: 11.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.6;
}

/* ─── текст поверх кадра ───
   Все цвета здесь заданы прямо белым, а не через токены темы. Токены меняются
   вместе с темой страницы, а под текстом лежит не бумага, а затемнённая
   фотография — она тёмная и в светлой версии, и в тёмной. */
/* Своя мягкая подложка под колонкой текста.
   Растяжки на кадре привязаны к рамке: низ плотный, верх лёгкий. Но верхняя
   строка колонки стоит примерно на середине кадра, где растяжки уже почти
   нет, — и стоит ли она на стебле или на подсвеченном лепестке, решает
   фотография, а не вёрстка. Магазин будет менять снимок первого экрана, и
   подгонять под каждый проценты в градиентах никто не станет.
   Поэтому затемнение, которое ездит вместе с текстом: размытое пятно чуть
   шире самой колонки, без краёв и без формы. На снимке оно читается тенью, а
   не подложкой, зато держит контраст при любом кадре. */
.hero-type::before {
  content: '';
  position: absolute;
  /* шире и ниже самой колонки — чтобы у пятна не было видимой границы */
  inset: -20% -18% -34% -24%;
  z-index: -1;
  pointer-events: none;
  /* Центр пятна — у ВЕРХА колонки, а не у середины. Низ текста и так лежит
     на самой плотной части нижней растяжки кадра; помощь нужна верхней
     строке, которая стоит примерно на середине фотографии. */
  background: radial-gradient(
    92% 104% at 24% 18%,
    rgba(12, 9, 7, 0.7) 0,
    rgba(12, 9, 7, 0.4) 54%,
    rgba(12, 9, 7, 0) 88%
  );
}
.hero-type {
  position: relative;
  z-index: 2;
  /* Ширина колонки — в пикселях, не в `ch`. Единица `ch` считается по шрифту
     самого элемента, а у обёртки это обычный текстовый кегль: «19ch» вышли в
     165 пикселей, и заголовок в сто тридцать пунктов рубился по слогам —
     «Сего… / в / зале… / пят… / буке…». Потолок взят по длине самой длинной
     строки заголовка, доля — чтобы на узком экране колонка ужималась вместе
     с полосой. */
  max-width: min(920px, 72%);
  color: #fff;
}
/* Надзаголовок — чистым белым, без приглушения. Иерархию здесь держат кегль
   и разрядка, а не полупрозрачность: на 11.5 пунктах поверх живого снимка
   каждая сотая альфы — это разница между «читается» и «угадывается». */
.hero .eyebrow { color: #fff; }

/* Кегль вырос вдвое против прежнего. На бумаге рядом с колонкой текста такой
   заголовок кричал бы; поверх фотографии во всю полосу он ровно тот, каким
   набирают заголовок разворота, — и только он делает первый экран событием.
   Cormorant — антиква с низким ростом строчных: при одном кегле она читается
   мельче привычных, поэтому предел выше, чем взяли бы для PT Serif. */
.hero h1 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(48px, 8vw, 132px);
  line-height: 0.94;
  letter-spacing: -0.02em;
  margin: 20px 0 26px;
}
.hero h1 em { font-style: italic; }
.hero .lede {
  max-width: 44ch;
  color: rgba(255, 255, 255, 0.84);
  font-size: clamp(15px, 1.1vw, 18px);
  line-height: 1.65;
  margin: 0 0 30px;
}

.hero-row { display: flex; flex-wrap: wrap; gap: 12px; }
.pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--line);
  padding: 11px 17px;
  color: var(--ink-2);
}
.pill .dot {
  width: 5px;
  height: 5px;
  background: var(--ink);
  flex: none;
  border-radius: 50%;
}
.pill.occ {
  border-color: var(--ink);
  color: var(--ink);
  transition: background 0.5s var(--ease-soft), color 0.5s var(--ease-soft);
}
@media (hover: hover) {
  .pill.occ:hover { background: var(--ink); color: var(--paper); }
}
/* Те же плашки, но по фотографии: линия темы на снимке не видна, а тёмный
   текст на затемнении не читается. */
.hero .pill { border-color: rgba(255, 255, 255, 0.34); color: rgba(255, 255, 255, 0.88); }
.hero .pill .dot { background: #fff; }
.hero .pill.occ { border-color: #fff; color: #fff; }
@media (hover: hover) {
  .hero .pill.occ:hover { background: #fff; color: var(--ink); }
}

/* Кнопка первого экрана — единственное цветное пятно, кроме самих цветов. */
.scroll-cue {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  margin-top: clamp(26px, 3vw, 40px);
  padding: 0 28px;
  background: var(--hot);
  color: var(--on-hot);
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.02em;
  border-radius: 999px;
  transition: transform 0.4s var(--ease), filter 0.4s var(--ease-soft);
}
/* Стрелка нарисована псевдоэлементом, а не набрана в тексте ссылки: иначе
   читалка проговаривала бы её вслух. */
.scroll-cue::after {
  content: '→';
  font-size: 16px;
  transition: transform 0.4s var(--ease);
}
@media (hover: hover) {
  .scroll-cue:hover { filter: brightness(1.08); }
}
@media (hover: hover) {
  .scroll-cue:hover::after { transform: translateX(4px); }
}

/* ─────────────────────────── тёмная полоса ───────────────────────────

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

   Цвета здесь тоже прямые, а не из токенов: полоса тёмная в обеих темах —
   иначе в тёмной версии она сольётся с бумагой и якоря не останется. */
.band {
  background: var(--band);
  color: rgba(255, 255, 255, 0.9);
  padding: clamp(58px, 7vw, 112px) var(--gut-r) clamp(58px, 7vw, 112px) var(--gut-l);
}
.band-in {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 96px);
  align-items: end;
}
.band-say {
  font-family: var(--serif);
  font-size: clamp(28px, 3.4vw, 54px);
  line-height: 1.16;
  letter-spacing: -0.012em;
  margin: 0;
  /* Перенос по смыслу, а не по остатку строки: без этого на телефоне от
     второй фразы на третью строку уезжало одно слово «так». */
  text-wrap: balance;
}
.band-say em {
  font-style: italic;
  /* Не `--hot`: в светлой версии это тёмно-вишнёвый по бумаге, и по чёрному
     он почти не виден. Полоса тёмная в обеих темах, значит и цвет один. */
  color: #e8859d;
}
.band-facts {
  display: grid;
  gap: clamp(18px, 2vw, 26px);
  margin: 0;
}
.band-facts div {
  display: grid;
  gap: 6px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.band-facts dt {
  font-family: var(--sans);
  font-size: 11.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.52);
}
.band-facts dd {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(20px, 1.7vw, 27px);
  line-height: 1.2;
}

/* ─────────────────────────── подбор ─────────────────────────── */

/* Полоса отбора — строка под заголовком витрины, а не отдельный розовый
   блок. Тон бумаги, которым прежний блок отделялся от страницы, больше не
   нужен: отделяет его сама витрина, начинающаяся сразу под ним. */
.filters {
  padding-bottom: clamp(26px, 2.8vw, 40px);
  border-bottom: 1px solid var(--line);
  margin-bottom: clamp(30px, 3vw, 44px);
}
/* Поводы. Раньше это были шесть строк десятью прописными в разрядку, и к
   каждой — пояснение десятым же кеглем серым по бежевому. Шесть пар мелкого
   текста в два столбца читались сплошной серой сеткой: понять, что здесь
   вообще что-то нажимается, было нельзя, а выбранный отличался от остальных
   волосяной линией снизу.

   Теперь настоящие таблетки: слово целиком, строчными, в палец высотой.
   Пояснение ушло в `title` — на него наводят, когда оно нужно, а не читают
   все шесть сразу. */
.chips { display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 20px;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 450;
  line-height: 1.2;
  color: var(--ink-2);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  transition: color 0.35s var(--ease-soft), border-color 0.35s var(--ease-soft),
    background 0.35s var(--ease-soft);
}
@media (hover: hover) {
  .chip:hover { color: var(--ink); border-color: var(--edge-on); }
}
/* Выбранный повод красится цветом магазина — тем же, что кнопка первого
   экрана. На странице ровно один такой цвет, и он всегда значит «это сейчас
   действует». */
.chip[aria-pressed="true"] {
  background: var(--hot);
  border-color: transparent;
  color: var(--on-hot);
}
@media (hover: hover) {
  .chip[aria-pressed="true"]:hover { color: var(--on-hot); border-color: transparent; }
}

.budget {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-top: clamp(20px, 2vw, 28px);
}
.budget .eyebrow { flex: none; }
.budget b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 21px;
  letter-spacing: -0.01em;
  min-width: 148px;
  text-align: right;
}
/* Видно волосяную линию, а нажимается полоса в палец высотой. Само поле высотой
   в пиксель мышью ещё берётся, а пальцем — нет: попасть в линию толщиной в
   пиксель на ходу невозможно. Поэтому линию рисует фон по центру поля, а высоту
   поле держит настоящую. */
#budget {
  flex: 1;
  /* Ползунку flex-basis не поможет: у поля есть своя наименьшая ширина, и без
     этой строки он отказывается ужиматься, а строка уезжает за край экрана. */
  min-width: 0;
  appearance: none;
  height: 44px;
  background: linear-gradient(var(--line), var(--line)) center / 100% 1px no-repeat;
  outline: none;
  cursor: pointer;
  /* Протяжка вдоль — бегунку, поперёк — странице. Запрет всех жестов разом
     бегунок чинил, но взамен клал поперёк витрины глухую полосу: дорожка
     занимает 253 пикселя из 411, и палец, начавший прокрутку на ней, просто
     упирался — каталог не двигался с места. `pan-y` разделяет жесты по
     направлению: вертикальную протяжку браузер по-прежнему отдаёт странице,
     а всё остальное, включая движение вдоль дорожки, достаётся ползунку. */
  touch-action: pan-y;
}
#budget::-webkit-slider-runnable-track { height: 44px; background: none; border: none; }
#budget::-webkit-slider-thumb {
  appearance: none;
  width: 11px;
  height: 11px;
  /* бегунок сам не центрируется в высокой дорожке — сажаем его на середину */
  margin-top: 16.5px;
  border-radius: 50%;
  background: var(--ink);
  transition: transform 0.35s var(--ease);
}
@media (hover: hover) {
  #budget::-webkit-slider-thumb:hover { transform: scale(1.35); }
}
#budget::-moz-range-track { height: 44px; background: none; border: none; }
#budget::-moz-range-thumb {
  width: 11px;
  height: 11px;
  border: none;
  border-radius: 50%;
  background: var(--ink);
}

/* ─────────────────────────── витрина ─────────────────────────── */

.showcase { padding: 0 var(--gut-r) clamp(58px, 6.2vw, 100px) var(--gut-l); }
.section-head {
  display: flex;
  align-items: baseline;
  gap: 24px;
  padding: clamp(46px, 5.2vw, 76px) 0 clamp(18px, 1.8vw, 26px);
}
/* Правый край заголовка раздела отталкивает пояснение к самому краю полосы.
   Раньше это делал `margin-right:auto` у самого h2 — теперь h2 лежит внутри
   блока с подписью и до строки флексов не достаёт. */
.section-title { max-width: 56ch; margin-right: auto; }
.section-head .sub {
  color: var(--ink-2);
  margin: 12px 0 0;
  max-width: 46ch;
}
.section-head h2 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(34px, 4.4vw, 64px);
  /* Сжатие снято почти полностью: оно подбиралось под PT Serif, а Cormorant и
     без того узкая — при -0.03em её буквы начинали слипаться. */
  letter-spacing: -0.012em;
  margin: 0 auto 0 0;
}

/* Три в ряд, а не два. В два столбца плитка вырастает до шестисот пикселей:
   один букет занимает пол-экрана, и витрина перестаёт быть витриной — по ней
   уже не пробежать глазами, её приходится листать. */
/* Асимметрия вместо трёх одинаковых квадратов. Ряд из равных плиток с
   подписью внизу — заводская раскладка любого интернет-магазина, и никакая
   типографика её не спасает: глаз не находит, с чего начать, потому что
   начинать негде. Шесть колонок и чередование «крупный кадр — мелкий кадр»
   дают развороту ритм: в каждом ряду есть главный и второстепенный, а через
   ряд они меняются местами, и витрина читается сверху вниз зигзагом.

   Цикл на четыре карточки: 4+2, потом 2+4. Сумма ряда всегда шесть, поэтому
   дыр в сетке не бывает при любом числе букетов. Соотношение сторон у крупных
   и мелких разное (4/3 против 4/5) — иначе разница осталась бы только в
   ширине, а это читается как «одну картинку растянули». */
.grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  /* Раньше к просвету прибавлялись два поля венка, и бумаги между снимками
     было втрое больше самого числа. Теперь кадры стоят голыми, и весь воздух
     держит только просвет — поэтому он и вырос. Взято около трёх четвертей
     прежнего разрыва, а не весь: по краю голого прямоугольника воздух читается
     плотнее, чем по краю цветов. По вертикали больше: под кадром идут ещё
     название и цена, и ряд от ряда должен отделяться заметнее плитки. */
  gap: clamp(52px, 5vw, 80px) clamp(30px, 3vw, 48px);
}
/* Выход карточки — общий для всей страницы `data-rise` (см. «движение»).
   Своего наблюдателя у витрины больше нет: он сообщал о пересечении, а не о
   положении, и переход по ссылке из шапки проскакивал карточки насквозь. */
.card { cursor: pointer; }
.grid .card:nth-child(4n + 1),
.grid .card:nth-child(4n) { grid-column: span 4; }
.grid .card:nth-child(4n + 2),
.grid .card:nth-child(4n + 3) { grid-column: span 2; }
/* Карточки в ряду разной высоты — в этом и смысл. Без этого grid растянул бы
   низкую до высоты соседки, и вся асимметрия ушла бы в пустое поле под
   подписью. */
.grid .card { align-self: start; }
.grid .card:nth-child(4n + 1) .ph,
.grid .card:nth-child(4n) .ph { aspect-ratio: 4 / 3; }
.grid .card:nth-child(4n + 2) .ph,
.grid .card:nth-child(4n + 3) .ph { aspect-ratio: 4 / 5; }
/* Карточки стоят ровным рядом, без ступенек. Плитка у всех одна и та же
   квадратная, а вот подписи разной длины — от них ряд и разъезжался. Поэтому
   карточка тянется на всю высоту ряда, а строка с ценой прижата к низу: цены
   в ряду встают на одну линию независимо от того, в одну строку название или
   в две. */
.grid .card {
  display: flex;
  flex-direction: column;
}
.card .body {
  display: flex;
  flex-direction: column;
  flex: 1;
}
.card .row { margin-top: auto; }

/* Плитка квадратная, а снимки портретные — cover срезает треть высоты. Срез
   уводим вниз: цветы на фотографии букета почти всегда в верхней половине. */
/* Носитель кадра. Сам он ничего не рисует — внутри окно со снимком (`.win`),
   которое и режет: и обрез при раскрытии, и `overflow` под проезд снимка
   (см. `--py` в motion.js) живут на окне. */
.ph {
  position: relative;
  aspect-ratio: 1 / 1;
}

/* Окно, в которое смотрит снимок. Занимает плитку целиком: поля вокруг кадра
   нет — кадр и есть плитка. */
.win {
  position: absolute;
  inset: 0;
  overflow: hidden;
  transition:
    box-shadow 0.6s var(--ease-soft),
    clip-path 1.25s var(--ease);
}
/* Волосяная линия по срезу окна: студийный фон у снимков почти белый, и без
   линии кадр растворяется в бумаге страницы. Под курсором отпечаток отлипает —
   тень мягкая и почти без смещения, иначе выходит рекламная подложка, а не
   лист в альбоме. */
.win { box-shadow: 0 0 0 1px var(--edge); }
/* Снимка нет или он не пришёл. Раньше окно оставалось прозрачной дырой —
   и, что хуже, вовсе не раскрывалось: раскрытие висело на событии загрузки,
   которого в этом случае не будет никогда. Заливка бумагой потемнее читается
   как «кадр ещё не сняли», а не как поломка вёрстки. */
.win.nophoto {
  background:
    linear-gradient(160deg, var(--paper-2), var(--paper-3));
}
@media (hover: hover) {
  .card:hover .win {
    box-shadow:
      0 0 0 1px var(--edge-on),
      0 18px 42px -26px var(--drop);
  }
}

/* Кадр не проявляется, а раскрывается сверху вниз — так его открывают, когда
   вынимают из обёрточной бумаги. Сверху, потому что цветы в кадре сверху: они и
   должны показаться первыми, а стебли последними. Метку ставит app.js по
   загрузке снимка (`whenReady`), а не прокрутка.

   Отступы по бокам и снизу отрицательные: обрез ровно по окну срезал бы линию
   и тень — они лежат снаружи. */
.motion .win { clip-path: inset(0 -60px 100% -60px); }
.motion .lit .win { clip-path: inset(0 -60px -60px -60px); }

.ph img {
  width: 100%;
  height: 100%;
  filter: var(--shot);
  position: absolute;
  inset: -6% 0;
  height: 112%;
  object-fit: cover;
  object-position: 50% 38%;
  display: block;
  opacity: 0;
  translate: 0 var(--py, 0px);
  scale: var(--ps, 1.03);
  transition: opacity 1.1s var(--ease-soft), scale 1.4s var(--ease);
}
.ph img.ready { opacity: 1; }
@media (hover: hover) {
  .card:hover .ph img { --ps: 1.08; }
}
/* Витрина открывается и с клавиатуры — значит, видно должно быть, где ты
   сейчас стоишь. Только :focus-visible: обводка после нажатия мышью выглядит
   как поломка и раньше была причиной убрать её вовсе. */
.card:focus { outline: none; }
.card:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 6px;
}
/* ─── полоса срока ───
   Скоропорт — единственное, чем этот магазин отличается от всех остальных, и
   до сих пор он стоял серой строкой петитом под фотографией: «простоит ещё
   пять дней». Строку никто не читал. Полоса под кадром показывает остаток
   срока длиной: она видна раньше, чем текст, и видна сразу у всей витрины —
   по одному взгляду понятно, какой букет свежий, а какой доживает.

   Не поверх фотографии: белая линия по снимку пропадает на светлых лепестках,
   а тёмная — на стеблях. На бумаге она читается всегда. */
.life {
  position: relative;
  height: 2px;
  margin-top: clamp(16px, 1.4vw, 22px);
  background: var(--line);
}
.life i {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--left, 1) * 100%);
  background: var(--ink);
  /* Ширина оживает после появления карточки: сама полоса приезжает вместе с
     ней, а заполнение доезжает следом — иначе движение читается как загрузка,
     а не как «столько осталось». */
  transition: width 1.1s var(--ease);
}
.card[data-stage='discount'] .life i { background: var(--alarm); }
.card[data-stage='lastcall'] .life i { background: var(--alarm); }
.card[data-stage='lastcall'] .life { background: rgba(152, 48, 75, 0.2); }

.card .body { padding-top: clamp(16px, 1.4vw, 22px); }
.state { color: var(--ink-3); display: block; margin-bottom: 10px; }
.state.hot { color: var(--alarm); }
/* Кегли подняты под Cormorant: у неё низкий рост строчных, и прежние 19px
   названия при ней читались как подпись под фотографией, а не как имя товара. */
.card h3 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(22px, 1.8vw, 28px);
  letter-spacing: -0.012em;
  line-height: 1.15;
  margin: 0 0 7px;
}
.card .comp { color: var(--ink-2); font-size: 14px; line-height: 1.5; }
/* Крупной карточке — крупное имя. Если под кадром в девятьсот пикселей и под
   кадром в четыреста стоит одна и та же строка, разница в размерах читается
   не замыслом, а тем, что картинки просто разные. Кегль подхватывает ритм
   сетки и делает главный букет ряда главным и в тексте. */
.grid .card:nth-child(4n + 1) h3,
.grid .card:nth-child(4n) h3 { font-size: clamp(26px, 2.5vw, 38px); }
.grid .card:nth-child(4n + 1) .comp,
.grid .card:nth-child(4n) .comp { font-size: 15px; }
.card .row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--line-soft);
}
/* Цена и «осталось два» стояли одним кеглем и почти одним цветом — строка
   читалась ровной серой парой, и главное число карточки в ней терялось.
   Цена тяжелее и крупнее, остаток остаётся подписью. */
.price {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 25px;
  letter-spacing: -0.01em;
  line-height: 1;
  margin-right: auto;
}
.price s { color: var(--ink-3); font-weight: 400; font-size: 16px; margin-left: 10px; }
.left { color: var(--ink-3); }

.empty {
  border-top: 1px solid var(--line);
  padding-top: 34px;
  color: var(--ink-2);
  max-width: 42ch;
}

/* Витрина не загрузилась. Отдельно от «ничего не подошло»: там магазин
   работает и просто нет подходящего, здесь — не дозвонились до магазина. */
.offline { color: var(--alarm); }
.retry {
  display: block;
  margin-top: 16px;
  padding: 13px 22px;
  background: none;
  border: 1px solid var(--line);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: border-color 0.3s var(--ease-soft), background 0.3s var(--ease-soft);
}
@media (hover: hover) {
  .retry:hover { border-color: var(--edge-on); background: var(--paper-2); }
}
.retry:disabled { opacity: 0.55; cursor: default; }

/* ─────────────────────────── подвал ─────────────────────────── */

footer {
  border-top: 1px solid var(--line);
  background: var(--paper-2);
}
.foot {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 32px;
  padding:
    clamp(46px, 5.2vw, 76px) var(--gut-r)
    calc(clamp(46px, 5.2vw, 76px) + env(safe-area-inset-bottom)) var(--gut-l);
  color: var(--ink-2);
  font-size: 13.5px;
}
.foot .eyebrow { margin-bottom: 16px; }

/* Вход в кабинет — для хозяина магазина, поэтому он в подвале и в тоне бумаги,
   а не в шапке рядом с «Витриной». Покупателю он ничего не говорит; хозяин
   знает, куда идти, и ему хватает одной строки. Поле под палец — как у всего
   остального нажимаемого. */
.owner {
  display: block;
  width: fit-content;
  margin-top: 22px;
  padding: 11px 17px;
  border: 1px solid var(--line);
  color: var(--ink-3);
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 10px;
  font-weight: 500;
  line-height: 1.5;
  transition: border-color 0.4s var(--ease-soft), color 0.4s var(--ease-soft);
}
@media (hover: hover) {
  .owner:hover { border-color: var(--ink); color: var(--ink); }
}

/* ─────────────────────────── карточка букета ─────────────────────────── */

.sheet-bg {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.7s var(--ease-soft);
  z-index: 90;
}
.sheet-bg.on { opacity: 1; pointer-events: auto; }

.sheet {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(1180px, 96vw);
  background: var(--paper);
  z-index: 100;
  transform: translateX(101%);
  transition: transform 0.85s var(--ease);
  overflow: hidden;
}
.sheet.on { transform: none; }
.sheet-inner { display: grid; grid-template-columns: 1.06fr 1fr; height: 100%; }
/* Высота, отданная под кадр: вся шторка минус поля вокруг венка. На широком
   экране кадр стоит столбцом во всю высоту — здесь и считаем от неё. */
.sheet { --stage: calc(var(--vh, 100vh) - (var(--mat) + 10px) * 2); }
.close {
  position: absolute;
  top: max(26px, env(safe-area-inset-top));
  right: max(26px, env(safe-area-inset-right));
  z-index: 3;
  /* Крестик — единственный выход из шторки на телефоне, и попасть в него надо
     пальцем: сама надпись 10px, поле добирает до пальца. */
  padding: 10px 2px 7px;
  margin: -10px -2px -7px;
  color: var(--ink-2);
  padding-bottom: 7px;
  border-bottom: 1px solid transparent;
  transition: color 0.4s var(--ease-soft), border-color 0.4s var(--ease-soft);
}
@media (hover: hover) {
  .close:hover { color: var(--ink); border-bottom-color: var(--ink); }
}

/* Здесь букет наконец показывают целиком — вместе с упаковкой и сроком. */
/* Форму задаёт сам снимок: он ужимается по ячейке (`max-width`/`max-height` —
   браузер считает это сам и точно), опора облегает снимок.
   Здесь, в отличие от плитки, кадр не режем: пришли смотреть букет целиком,
   вместе с упаковкой — по ней и судят, за что платят. */
.viewer {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 0;
  min-width: 0;
  /* Просвет, чтобы кадр не упирался в обрез шторки: прижатый к краю, он
     читается обрезанным. Ровно это же число вычитает `--stage` — разойдутся,
     и снизу останется мёртвая полоса бумаги. */
  padding: calc(var(--mat) + 10px);
}
.mount { position: relative; max-width: 100%; max-height: 100%; }
.viewer .win {
  position: static;
  max-width: 100%;
  max-height: 100%;
}
.viewer .win img {
  display: block;
  filter: var(--shot);
  max-width: 100%;
  /* Высоту ограничиваем сценой, а не окном: окно само высоты не держит — она
     приходит от снимка, и доля от неё замкнулась бы сама на себя. `--stage`
     задаёт каждая раскладка шторки отдельно, потому что высота ячейки под
     кадр у них разная. */
  max-height: var(--stage, 70vh);
  width: auto;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}
/* Ракурсы лежат на самом снимке, а не под ним: полоса под кадром съела бы
   высоту, которой в шторке и так впритык. */
.angles {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 18px;
  display: flex;
  gap: 8px;
  z-index: 1;
}
/* Ракурсы — те же отпечатки, только мельче. */
.angle {
  width: 46px;
  height: 46px;
  padding: 2px;
  border: 1px solid var(--line-soft);
  background: var(--paper);
  cursor: pointer;
  overflow: hidden;
  opacity: 0.55;
  transition: opacity 0.4s var(--ease-soft), border-color 0.4s var(--ease-soft);
}
.angle img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 78%; display: block; }
@media (hover: hover) {
  .angle:hover { opacity: 0.85; }
}
.angle.on { opacity: 1; border-color: var(--ink); }

.pane {
  overflow-y: auto;
  padding:
    clamp(54px, 5vw, 82px)
    max(clamp(24px, 3.2vw, 54px), env(safe-area-inset-right))
    calc(48px + env(safe-area-inset-bottom))
    max(clamp(24px, 3.2vw, 54px), env(safe-area-inset-left));
  -webkit-overflow-scrolling: touch;
  /* Прокрутка шторки не должна тянуть за собой страницу под ней. */
  overscroll-behavior: contain;
}
.pane > * { animation: rise 0.9s var(--ease) both; }
.pane > *:nth-child(2) { animation-delay: 0.05s; }
.pane > *:nth-child(3) { animation-delay: 0.1s; }
.pane > *:nth-child(n + 4) { animation-delay: 0.15s; }
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
/* Имя букета в заявке — крупнейшая строка всей покупки. Правило было написано
   на `h3`, а разметка отдаёт `<h2 id="sheet-title">` (на него ссылается
   `aria-labelledby` шторки), и заголовок доставался браузеру: 22px жирным
   гротеском вместо антиквы. Тег в разметке правильный — исправлен селектор. */
.pane h2 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(30px, 3vw, 44px);
  letter-spacing: -0.012em;
  line-height: 1.1;
  margin: 0 0 10px;
}
.pane .comp { color: var(--ink-2); font-size: 14px; }

.stage-note {
  margin: 30px 0 34px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 13.5px;
  line-height: 1.7;
}
.stage-note b {
  display: block;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 9px;
}
.stage-note.hot b { color: var(--alarm); }

.field { margin-bottom: 24px; }
.field.two { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
.pane label { display: block; color: var(--ink-3); margin-bottom: 9px; }
/* Поля — гротеском, а не антиквой. Прежняя PT Serif в полях ещё читалась: это
   рабочая книжная антиква с обычными цифрами. Cormorant — шрифт заголовков, у
   неё низкий рост строчных и минускульные цифры (те, что с хвостами вниз).
   Для цены «4 200 сом» это украшение, а вот телефон 0700 123 456 в наборе
   таким шрифтом читается с запинкой — а это поле, по которому заказ находят.
   Антиква остаётся там, где текст показывают, гротеск — там, где вводят. */
.pane input,
.pane select,
.pane textarea {
  width: 100%;
  background: none;
  border: none;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  padding: 9px 0 12px;
  transition: border-color 0.4s var(--ease-soft);
}
.pane textarea { font-size: 16px; line-height: 1.6; resize: vertical; min-height: 78px; }
.pane input::placeholder, .pane textarea::placeholder { color: var(--ink-3); }
.pane input:focus, .pane select:focus, .pane textarea:focus {
  outline: none;
  border-bottom-color: var(--ink);
}
.pane select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: right 6px center, right 1px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  /* Уголок нарисован фоном и текст его не видит: без отбивки справа
     «В черте города +350» заезжало прямо под стрелку. */
  padding-right: 16px;
  cursor: pointer;
}
.pane .pill { margin-bottom: 26px; }

.help {
  color: var(--ink-2);
  margin-top: 13px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  transition: color 0.4s var(--ease-soft), border-color 0.4s var(--ease-soft);
}
@media (hover: hover) {
  .help:hover { color: var(--ink); border-bottom-color: var(--ink); }
}
/* Пока подсказок не запросили, список пуст — но отбивку сверху он занимал
   всегда: восемнадцать пикселей пустоты между «не знаю, что написать» и
   ценой, которых никто не задумывал. */
.variants { display: flex; flex-direction: column; gap: 1px; margin-top: 18px; }
.variants:empty { display: none; }
.variant {
  text-align: left;
  font-family: var(--serif);
  /* Cormorant ниже ростом прежней антиквы: тот же кегль читается мельче,
     поэтому 15.5 здесь пришлось поднять — это текст, который выбирают. */
  font-size: 17.5px;
  line-height: 1.5;
  color: var(--ink-2);
  padding: 14px 0;
  border-top: 1px solid var(--line-soft);
  transition: color 0.4s var(--ease-soft);
}
@media (hover: hover) {
  .variant:hover { color: var(--ink); }
}

.total {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 42px 0 22px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
}
.sum {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(30px, 3vw, 40px);
  letter-spacing: -0.012em;
}
.fee { color: var(--ink-3); font-size: 13px; }
/* Кнопка, ради которой существует весь магазин. Была контурной рамкой с
   надписью в десять прописных — тем же приёмом, которым на этой же странице
   набраны «доставка» и «как это работает», то есть подписи. Заливка цветом
   магазина: на всей шторке это единственное цветное место, и промахнуться
   мимо него нельзя. */
.go {
  display: block;
  width: 100%;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--hot);
  color: var(--on-hot);
  padding: 19px 22px;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.02em;
  transition: filter 0.4s var(--ease-soft);
}
@media (hover: hover) {
  .go:hover { filter: brightness(1.08); }
}
.go[disabled] { opacity: 0.4; cursor: default; }
.fineprint { color: var(--ink-3); margin-top: 18px; text-align: center; letter-spacing: 0.14em; }

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom));
  max-width: min(420px, calc(100vw - 2 * var(--gutter)));
  transform: translate(-50%, 200%);
  background: var(--ink);
  color: var(--paper);
  padding: 15px 26px;
  font-size: 13.5px;
  z-index: 120;
  opacity: 0;
  pointer-events: none;
  transition: transform 0.6s var(--ease), opacity 0.35s var(--ease-soft);
}
.toast.on { transform: translate(-50%, 0); opacity: 1; }
/* Плохая весть отличается не только словами: тот же тёмный прямоугольник с
   другим текстом человек за прилавком пролистывает глазами не читая. */
.toast.bad {
  background: var(--alarm);
  color: var(--on-alarm);
}
/* Плашка со ссылкой должна ловить палец — обычная нажатия не принимает
   намеренно, чтобы не перехватывать его у витрины под собой. */
.toast.tap {
  pointer-events: auto;
}
.toast a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}

/* ─── стопка плашек (кабинет) ───
   На витрине плашка одна: посетитель делает одно действие и уходит. За
   прилавком действий подряд много, и сообщение об успехе не должно исчезать
   от того, что следом пришла ошибка про другое. Поэтому здесь контейнер, а
   сообщения — его дети, каждое со своим сроком. */
.toasts {
  position: fixed;
  left: 50%;
  bottom: max(28px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  width: min(420px, calc(100vw - 2 * var(--gutter)));
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 120;
  pointer-events: none;
}
.toast-msg {
  background: var(--ink);
  color: var(--paper);
  padding: 14px 22px;
  font-size: 13.5px;
  line-height: 1.5;
  opacity: 0;
  transform: translateY(18px);
  transition: transform 0.5s var(--ease), opacity 0.3s var(--ease-soft);
}
.toast-msg.on { opacity: 1; transform: none; }
.toast-msg.bad { background: var(--alarm); color: var(--on-alarm); }

/* Поле, на котором споткнулись. Красим линию, а не фон: фон здесь бумага, и
   заливка читается отдельным блоком, а не пометкой. Метка садится на самую
   тесную обёртку — в строке из двух полей иначе краснели бы оба.
   Стоит ниже `.pane input:focus` намеренно: при той же весе побеждает
   последнее, а подсветка не должна пропадать, как только в поле встал курсор. */
.pane .bad input,
.pane .bad select,
.pane .bad textarea {
  border-bottom-color: var(--alarm);
}
.pane .bad label {
  color: var(--alarm);
}

/* ─────────────────────────── очень широкие ─────────────────────────── */

/* Порог не выдуман: поле перестаёт расти в долях экрана и начинает съедать
   излишек ровно там, где (100vw − 1760) / 2 обгоняет 4.2vw, то есть на
   1921px. Ниже этой черты ничего из блока не срабатывает, и всё, что уже
   проверено на 1440 и 1920, остаётся нетронутым. */
/* Очень широкие экраны отдельного правила больше не просят: кадр героя
   намеренно уходит в стекло, а текст на нём держат те же поля `--gut`, что и
   всю остальную полосу. Прежний блок сужал столбец снимка до доли меры —
   столбца больше нет. */

/* ─────────────────────────── узкие экраны ─────────────────────────── */

/* Ступеней у витрины три: три в ряд, два и один. Порог поставлен по ширине
   плитки, а не по названию устройства: ниже 1180px третий столбец сжимает
   снимок так, что от букета остаётся сердцевина. */
@media (max-width: 1180px) {
  /* Плитка на этой ступени заметно крупнее, а просвет в долях экрана — тот же.
     Два больших снимка сошлись бы почти встык, поэтому разрыв поднимаем
     вместе с величиной кадра. */
  .grid { gap: clamp(56px, 5.4vw, 84px) clamp(34px, 3.4vw, 52px); }
}

/* Планшет: шести колонок мало — мелкая карточка на них ужимается до трети
   экрана, и в ней уже не помещается строка «цена · осталось». Колонок
   четыре, распорки те же: крупный кадр занимает всю полосу, два мелких
   встают парой. Ряд по-прежнему всегда полный. */
@media (max-width: 1000px) and (min-width: 621px) {
  .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (max-width: 1000px) {
  /* Поле справа возвращается герою целиком. На большом экране его нет
     намеренно: кадр стоит отдельным столбцом и уходит в край страницы. Но
     здесь столбец один, кадр встал ПОД текстом во всю строку — и уходил в
     край уже в одиночку: слева 22px, справа ноль. Рядом с ровными полями у
     всего остального (заголовки, плитки витрины, подвал) это читалось не
     приёмом, а перекосом — единственное место во всей вёрстке, где поле
     задано с одной стороны. Теперь кадр героя стоит ровно по тем же линиям,
     что и плитки витрины под ним. */
  /* Кадр остаётся во всю ширину, но экран ниже и уже: держать под него почти
     весь экран незачем — под первым экраном должна быть видна кромка тёмной
     полосы, иначе телефон показывает фотографию и ничего больше. */
  .hero { min-height: min(calc(var(--vh, 100vh) * 0.9), 780px); }
  .hero-type { max-width: 100%; }
  /* Планшет: экран уже, но текст всё ещё занимает меньше половины полосы —
     косая растяжка слева остаётся и добирается выше, а нижняя становится
     плотнее: заголовок здесь стоит примерно на середине кадра, где нижней
     растяжки почти нет, и на светлом бутоне он пропадал. */
  .hero-shot::after {
    background:
      linear-gradient(to bottom, rgba(12, 9, 7, 0.52) 0, rgba(12, 9, 7, 0) 190px),
      linear-gradient(to right, rgba(12, 9, 7, 0.72) 0, rgba(12, 9, 7, 0.42) 58%, rgba(12, 9, 7, 0) 86%),
      linear-gradient(to top, rgba(12, 9, 7, 0.82) 0, rgba(12, 9, 7, 0.5) 52%, rgba(12, 9, 7, 0.1) 84%, rgba(12, 9, 7, 0) 100%);
  }
  .band-in { grid-template-columns: 1fr; align-items: start; }
  /* Заголовок раздела и пометка справа перестают быть одной строкой: на узком
     экране они складываются в столбик и без переноса лезли друг на друга. */
  .section-head { flex-wrap: wrap; gap: 14px; }
  .section-title { margin-right: 0; }
  .budget { margin-top: 30px; }
  .sheet { width: 100%; }
  /* Шторка на телефоне прокручивается целиком, одним движением.
     Было иначе: снимок занимал 54% экрана намертво, сверху ещё 98 пикселей
     пустой бумаги под надпись «Закрыть», а вся заявка — полторы тысячи
     пикселей содержимого — ездила во врезанном окошке высотой 290. Палец на
     верхних двух третях экрана не двигал ничего вовсе: страница выглядела
     заевшей. Строки теперь по содержимому, прокрутка одна и живёт на
     `.sheet-inner`; `.pane` своей прокрутки не держит. */
  .sheet-inner {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* Прокрутка шторки не тянет за собой страницу под ней — это правило
       уехало сюда вместе с прокруткой. */
    overscroll-behavior: contain;
  }
  /* Поля кадра — те же 24 пикселя, что у текста заявки. Раньше кадр стоял в
     64 и не совпадал с колонкой ни одной линией.

     И кадр здесь — обычный блок, а не ячейка сетки. Сеткой строка под него
     считалась по опоре с процентным потолком: круг «высота строки → потолок
     опоры → высота строки» замыкался в ноль, строка выходила восемнадцати
     пикселей высотой, и снимок рисовался поверх заявки. Блок такого круга не
     заводит — он просто растёт под содержимое. */
  .viewer {
    display: block;
    /* И самое коварное: `min-height: 0` с большого экрана. Там он нужен —
       кадр сжимается в свою колонку. Здесь строка сетки берёт начальный
       размер как раз из этого минимума, то есть ноль, а расти ей некуда:
       заявка под ней уже выше экрана, свободного места нет. Строка так и
       оставалась в восемнадцать пикселей. */
    min-height: auto;
    padding: 18px 24px 0;
  }
  .mount,
  .viewer .win { max-height: none; }
  /* Мера кадра — ширина колонки, и только она. Потолок по высоте ломал
     ровно то, ради чего всё и затевалось: снимок выше, чем два к трём,
     упирался в потолок раньше, чем в колонку, ужимался по ширине и вставал
     на десять пикселей уже текста под собой. Снимок 374×665 давал кадр в
     323 при колонке в 342 — заметно с первого взгляда.

     Потолок остаётся страховкой на снимок в половину ширины и уже: такой
     обрежется по высоте, но колонку держать не перестанет. */
  .viewer .win img {
    width: 100%;
    height: auto;
    max-height: calc(var(--vh, 100vh) * 0.95);
    object-fit: cover;
  }
  .pane { overflow: visible; padding-top: 30px; }
  .foot { grid-template-columns: 1fr; gap: 30px; }
  nav { display: none; }
}

/* Телефон боком — и вообще любое окно, у которого ширины вдоволь, а высоты
   нет. Условие сменилось с «узкий И боком» на «низкий И не уже 620px»: мерить
   надо то, чего не хватает. Прежнее ловило телефон, но пропускало короткое
   окно браузера на мониторе, а там разваливалось ровно то же самое.

   Все правила ниже — отмена того, что наложила ступень ≤1000px. Она права,
   пока экран узкий и высокий: складывает две колонки в одну. На боку это ровно
   наоборот — колонки есть куда поставить, а складывать некуда. */
@media (min-width: 620px) and (max-height: 560px) {
  /* Первый экран на боку был стеной текста: кадр ушёл под заголовок, кнопка
     «Смотреть витрину» — за нижний край, и на 844×390 не помещалось ничего,
     кроме двух строк и абзаца. Возвращаем кадр рядом с текстом и сжимаем
     вертикаль: на боку читают меньше, а смотрят больше. */
  /* Телефон боком: экран ниже 560 пикселей. Кадр остаётся во всю ширину, но
     занимает его целиком — на такой высоте «почти весь экран» и «весь экран»
     отличаются на полоску бумаги, которая читается ошибкой вёрстки. */
  .hero { min-height: calc(var(--vh, 100vh) - 1px); padding-bottom: 3.2vh; }

  /* Шапка на боку — не десятая часть экрана, а шестая: 62px при 360px высоты.
     Столько строке из двух слов не нужно ни при какой ориентации. */
  :root { --bar: 52px; }
  /* Весь вертикальный ритм героя — в долях высоты, а не в пикселях. Пиксели
     одинаковы на 360px и на 560px, и внизу либо пусто, либо кнопка за краем;
     доли сами разводят текст ровно на то, что есть. */
  .hero-type { max-width: min(760px, 78%); }
  /* Кегль тоже от высоты, а не от ширины: боком её и не хватает. Потолок тот
     же, что и стоя, — 46px. */
  .hero h1 { font-size: clamp(28px, 9.4vh, 62px); margin: 1.4vh 0 2vh; }
  /* Мера заметно шире, чем стоя: боком колонка и так низкая, а 44ch загоняли
     абзац в четыре строки — вместе с ними за нижний край уезжала кнопка. */
  .lede { font-size: 14.5px; line-height: 1.5; margin: 0 0 2.6vh; max-width: 64ch; }
  /* Кадр тянется по высоте строки, а не по пропорции: с 4/3 он на таком экране
     либо вылезал за край, либо оставлял под собой пустую бумагу. Отбивку от
     шапки не трогаем: без неё снимок уходит под неё, и кнопка темы становится
     белым значком по розовым лепесткам. */
  .hero .lede { margin-bottom: 2.4vh; }
  .scroll-cue { margin-top: 2.4vh; min-height: 44px; }
  /* Экран лежит на боку: он снова широкий, и текст занимает меньше половины
     полосы — косая растяжка слева опять работает и возвращается. Правило
     узких экранов её сняло, потому что там колонка текста во всю ширину. */
  .hero-shot::after {
    background:
      linear-gradient(to bottom, rgba(12, 9, 7, 0.5) 0, rgba(12, 9, 7, 0) 150px),
      linear-gradient(to right, rgba(12, 9, 7, 0.8) 0, rgba(12, 9, 7, 0.42) 52%, rgba(12, 9, 7, 0) 78%),
      linear-gradient(to top, rgba(12, 9, 7, 0.7) 0, rgba(12, 9, 7, 0.3) 46%, rgba(12, 9, 7, 0) 82%);
  }
  /* Подписи к кадру здесь места нет: сверху её накрывает шапка, снизу — текст
     во всю высоту экрана. */
  .hero-shot figcaption { display: none; }
  /* Прокрутка возвращается заявке. Стоя шторка едет целиком одним листом,
     а боком листу некуда ехать: кадр стоит своим столбцом во всю высоту, и
     прокручивать надо только правую колонку. */
  .sheet-inner {
    grid-template-columns: 1.06fr 1fr;
    grid-template-rows: 100%;
    overflow: hidden;
  }
  .pane { overflow-y: auto; }
  .sheet { --stage: calc(var(--vh, 100vh) - (var(--mat) + 10px) * 2); }
  /* Кадр снова стоит своим столбцом, «закрыть» ушло на заявку — отбивка сверху
     под надпись больше не нужна, а с ней кадр не влезал в высоту.

     Здесь же отменяем всё, чем стоячая ступень разбирала кадр в обычный
     блок: боком он снова ячейка во всю высоту колонки, и высота у неё
     своя, а не по содержимому. */
  .viewer {
    display: grid;
    place-items: center;
    min-height: 0;
    padding: calc(var(--mat) + 10px);
  }
  .mount,
  .viewer .win { max-height: 100%; }
  /* И кадр снова вписывается в ячейку целиком, а не тянется в ширину
     колонки: боком высота — то, чего не хватает, и обрезать по ней нельзя. */
  .viewer .win img {
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: var(--stage, 70vh);
    object-fit: fill;
  }
  /* «Закрыть» стоит над заявкой, и имя букета уезжало прямо под эту надпись.
     Отбивать заголовок справа плохо: он сжимается до 184 пикселей и рвётся
     на две строки с одиноким словом внизу. Поэтому надписи отводим свою
     полосу сверху — ровно как на стоячем телефоне над кадром. */
  .pane { padding-top: 62px; }
  /* Две колонки полей в заявке шириной в треть экрана дают по 127 пикселей —
     в них не помещается ни «В черте города +350», ни «сегодня к 10:30».
     Боком у нас мало не ширины страницы, а ширины этого столбца. */
  .field.two { grid-template-columns: 1fr; gap: 18px; }
}

@media (max-width: 620px) {
  .grid { grid-template-columns: 1fr; gap: 34px; }
  /* Подпись к кадру убираем вовсе. Наверху она спорит с названием магазина в
     шапке, внизу — с кнопкой «Витрина», а третьего места на 390 пикселях
     нет. Букет из кадра всё равно назван в витрине ниже. */
  .hero-shot figcaption { display: none; }
  /* Косая растяжка слева здесь только мешает: колонка текста идёт во всю
     ширину полосы, и её правый край выходит туда, где градиента уже нет.
     Затемнение одно, снизу вверх, и самое плотное из всех. */
  .hero-shot::after {
    background:
      linear-gradient(to bottom, rgba(12, 9, 7, 0.52) 0, rgba(12, 9, 7, 0) 190px),
      linear-gradient(to top, rgba(12, 9, 7, 0.92) 0, rgba(12, 9, 7, 0.78) 52%, rgba(12, 9, 7, 0.26) 86%, rgba(12, 9, 7, 0) 100%);
  }
  /* Распорки обязательно снять. `grid-column: span 2` в сетке из одной
     колонки её не сужает, а ДОБАВЛЯЕТ неявную вторую: телефон показывал две
     узких полосы вместо ленты, и в них наезжали друг на друга цена и остаток.
     Селекторы повторены один в один — у правил выше тот же вес, и побеждает
     то, что стоит ниже по файлу. */
  .grid .card:nth-child(4n + 1),
  .grid .card:nth-child(4n),
  .grid .card:nth-child(4n + 2),
  .grid .card:nth-child(4n + 3) { grid-column: auto; }
  /* Чередование пропорций остаётся: лента из одинаковых кадров — это тот же
     список, из-за которого всё и затевалось. */
  .grid .card:nth-child(4n + 1) .ph,
  .grid .card:nth-child(4n) .ph { aspect-ratio: 5 / 4; }
  .grid .card:nth-child(4n + 2) .ph,
  .grid .card:nth-child(4n + 3) .ph { aspect-ratio: 4 / 5; }
}

@media (max-width: 560px) {
  /* Портрет, а не полоса: букет стоит вертикально, и в широком кадре на всю
     ширину телефона от него остаётся середина стеблей. Пропорция портретная
     только здесь — на планшете кадр во всю ширину ушёл бы за полтора экрана. */
  /* Заголовок на телефоне живёт своим размером: доля экрана, посчитанная для
     монитора, на 320px даёт строку шире экрана — «Сегодня в зале» не влезает.
     Доля та же, что и выше по ширине (7.2vw), меняется только нижний предел:
     с 9.2vw на 560px заголовок выходил КРУПНЕЕ, чем на 561px, и при повороте
     телефона строка прыгала в обратную сторону. */
  /* Кегль на телефоне поднят: заголовок лежит поверх кадра во весь экран,
     и прежние 33 пункта рядом с ним читались подписью, а не заголовком. */
  .hero h1 { font-size: clamp(36px, 11.5vw, 62px); margin: 16px 0 20px; }
  .field.two { grid-template-columns: 1fr; gap: 22px; }
  /* Подпись, ползунок и сумма в одну строку на телефон не встают: сумма
     фиксированной ширины плюс подпись съедают всё, и ползунка не остаётся.
     Подпись уходит своей строкой, а сумма перестаёт держать ширину. */
  .budget { flex-wrap: wrap; gap: 16px 18px; }
  .budget .eyebrow { width: 100%; }
  .budget b { min-width: 0; font-size: 19px; }
  .section-head { flex-wrap: wrap; gap: 6px 20px; }
  /* Заявку отправляют большим пальцем в самом низу — кнопка не должна упираться
     в полосу жеста. */
  .go { padding: 21px 22px; }
}

/* ─── шапка на телефоне ───
   В строке шапки три несжимаемых предмета: название, переключатель темы и
   «Написать». Меню на этой ширине уже скрыто, но оставшимся трём нужно 383px —
   на 375 и уже кнопка уезжала за правый край. Отступ по краям не трогаем: по
   нему название выровнено со всем, что идёт ниже, и сдвинуть его — значит
   получить кривую левую границу на всю страницу. Ужимаем то, что кривизны не
   даёт: просветы между предметами и поля внутри кнопки. */
@media (max-width: 430px) {
  .bar { gap: 16px; }
  .callbtn { padding: 13px 16px; letter-spacing: 0.14em; }
}

/* Самые узкие экраны (320–360). Здесь и укороченной кнопки не хватает —
   уменьшаем сам логотип: 20px против 23px на глаз почти не отличить, а строке
   это отдаёт полтора десятка пикселей. Высота кнопки остаётся 44px: сжимается
   поле по бокам, а не цель для пальца. */
@media (max-width: 360px) {
  .bar { gap: 12px; }
  .brand { font-size: 20px; }
  .callbtn { padding: 13px 12px; letter-spacing: 0.1em; }
}

/* ─── палец вместо курсора ───
   Размер цели задаёт не ширина экрана, а то, чем по ней попадают: на планшете
   в 800px строка в 26px мышью берётся, а пальцем — нет. Поэтому условие двойное:
   узкий экран (там точно палец) или устройство без наведения. Надписи набраны
   в 10px намеренно — растёт не буква, а поле вокруг неё, и отрицательный
   отступ не даёт ему раздвинуть соседей.

   Порог по ширине взят по самому широкому планшету в портрете, а не по
   телефону: планшет в 1024px мимо «телефонного» порога проходит, а палец у
   него тот же. На широком мониторе от лишних десяти пикселей поля ничего не
   портится, а промах пальцем стоит заказа. */
@media (max-width: 1180px), (pointer: coarse) {
  /* Поле добираем только там, где вокруг надписи ничего не нарисовано.
     У кого есть своя коробка — заливка таблетки, рамка повода — боковое поле
     трогать нельзя: оно и есть расстояние от буквы до краски. Обнуление
     прилепляло надпись к борту на любом сенсорном экране, включая широкие,
     а у поводов буквы попросту вылезали за рамку. Палец этим двоим и так
     есть куда ставить: у повода min-height 44px, у таблетки 52px.

     Вместе с полем ушёл и раздутый до 26px зазор между поводами. Он подменял
     собой недостающее поле внутри, пока поводы выглядели голым текстом; у
     настоящих таблеток он разносил надписи на 66px и гнал лишний ряд. Теперь
     и повод, и таблетка на телефоне ровно те же, что на мониторе. */
  nav a { padding: 11px 0; }
  .help { padding: 11px 0 8px; }
  .variant { padding: 16px 0; }
  .angle { width: 50px; height: 50px; }

  /*
   * Палец, а не курсор. Отступов выше хватало до 34–40px — по такой цели
   * промахиваются, а это выбор повода, суммы и кнопка заявки: те самые места,
   * где промах стоит покупателя. Ставим 44 явно и выравниваем по центру,
   * чтобы подчёркивание не уезжало от текста.
   *
   * Повода и таблетки в списке нет намеренно: у них своя высота в основном
   * правиле (44 и 52), и 44 здесь работало бы не полом, а потолком — главная
   * кнопка витрины оказывалась на телефоне ниже, чем на мониторе.
   */
  .owner,
  .brand,
  .themebtn,
  nav a,
  .help,
  .callbtn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .themebtn { min-width: 44px; justify-content: center; }
  .callbtn { justify-content: center; }
  /* Строкой ниже, а не сбоку от телефона. В подвале «Кабинет» идёт сразу
     за номером, и на мониторе он стоит отдельной строкой — так задумано:
     `display: block`. Общее правило под палец выше подменяло это на
     `inline-flex`, и на любом сенсорном экране рамка кабинета уезжала
     вплотную к номеру, в одну строку с ним. `flex` — тот же блок, только
     с выравниванием: и палец, и своя строка, и отбивка в 22px на месте. */
  .owner { display: flex; }

  /* Подчёркивание — часть надписи, а не дно коробки. У остальных в списке
     выше рамка идёт по всем четырём сторонам, и «по центру» для них верно:
     буквы стоят посреди таблетки. У «не знаю, что написать» черта только
     снизу, и центрирование отодвинуло её от букв на двенадцать пикселей
     вместо шести — строка перестала читаться ссылкой и превратилась в
     пустое поле ввода, третье подряд после текста открытки. Ставим буквы
     на дно коробки: запас под палец уходит вверх, черта остаётся под
     словами ровно там же, где на мониторе. Верхнюю отбивку на те же
     шесть пикселей и укорачиваем — иначе строка отъедет от открытки. */
  .help { align-items: flex-end; padding: 11px 0 6px; margin-top: 8px; }

  /* Поля заявки — самая важная форма во всём магазине: через неё приходят
     заказы. Подчёркнутая строка в 41px пальцем берётся через раз, особенно
     список города и времени. Рисунок не меняется — растёт только поле. */
  .pane input,
  .pane select,
  .pane textarea {
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 12px;
  }
  /* Сафари увеличивает страницу, когда палец попадает в поле мельче 16px:
     человек начинает писать открытку, а вёрстка прыгает и уезжает вбок.
     Полпикселя на глаз не видно, скачка не будет. */
  .pane textarea { min-height: 84px; font-size: 16px; }
  /* Выход из шторки один — в него надо попадать с первого раза. */
  .close { padding: 15px 8px; margin: -15px -8px; }
  /* Подпись, ползунок и сумма в одну строку на телефон не влезают — пусть
     подпись встаёт над ними, а не сдавливает ползунок до огрызка. */
  .budget { flex-wrap: wrap; }
  .brand { display: inline-flex; flex-direction: column; justify-content: center; align-items: flex-start; }
  /* Бегунок бюджета. Ловить касание есть чем — дорожка настоящей высоты в
     44px, и нажатие в любую её точку переносит бегунок туда же. А вот видеть
     под пальцем точку в 11px нечем: палец её целиком и закрывает. Под курсором
     эта же точка — намеренно тихая деталь, поэтому крупнее она только там, где
     работают пальцем. */
  #budget::-webkit-slider-thumb { width: 18px; height: 18px; margin-top: 13px; }
  #budget::-moz-range-thumb { width: 18px; height: 18px; }
  /* :hover пальцем не наступает — отклик на нажатие даём через :active. */
  #budget:active::-webkit-slider-thumb { transform: scale(1.25); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .card { opacity: 1; transform: none; }
  .hero-shot img { opacity: 1; transform: none; scale: 1.04; }
  /* Фон не «замедляем», а не создаём вовсе — см. motion.js. Здесь только
     страховка на случай, если слой уже построен, а настройку сменили после. */
  .blooms { display: none; }
  [data-rise] { opacity: 1 !important; transform: none !important; }
  /* Строки заголовка и раскрытие окна держатся не на переходе, а на начальном
     положении — оно бы так и осталось, сколько переход ни укорачивай. */
  .hero h1 .ln > i { transform: none !important; }
  .win { clip-path: none !important; }
}

/* ═══════════════════ «Закрыть» на стоячем телефоне ═══════════════════
   Плашка живёт в самом конце листа намеренно. Ступень «палец вместо
   курсора» ниже по файлу возвращает надписи прежние поля с отрицательными
   отступами — из ступени ≤1000px плашка от них съезжала на пиксель выше
   края экрана.

   Условие повторяет ровно ту раскладку, где шторка встаёт в одну колонку и
   снимок уезжает под надпись: узко — и при этом не «телефон боком». */
@media (max-width: 1000px) and (min-height: 561px), (max-width: 619px) {
  /* Снимок уезжает под надпись, поэтому надписи нужна своя подложка:
     бумага шторки и волосяная линия по краю. На белёсом студийном фоне
     плашку держит именно линия — заливка там почти сливается со снимком. */
  .close {
    top: max(14px, env(safe-area-inset-top));
    right: max(14px, env(safe-area-inset-right));
    /* 13, а не 12: с двенадцатью плашка выходила 43.3 — ровно на семь
       десятых ниже пальца. */
    padding: 13px 16px;
    margin: 0;
    border: 1px solid var(--line-soft);
    border-radius: 999px;
    background: var(--paper);
  }
}
