@charset "utf-8";
/* preview-overlay.css — overlay поверх PDP в момент генерации превью.
   Открывается через JS (app.js submit), URL меняется через history.pushState
   на /<slug>/preview?order=<id>, navigation НЕ происходит → XHR upload фото
   не прерывается.

   Вёрстка — ДС «Экслибрис» (эталон layouts-web/preview.html, токены theme.css):
   бумага --skz-bg, книжные объекты без радиуса с «матовой» тенью
   (0 1px 0 line-deep + мягкое 24px-облако), secondary-кнопки на карточном
   фоне, латунные подписи-«каталожные строки» над разворотами.
   ⚠️ Стили .preview-ov__spread-text / __thumb-text — зеркало ПЕЧАТНОЙ вёрстки
   (server/pipeline/buildBlockHtml.js): их цвета/шрифты (Lora, #6E5316, #EFE7D2)
   НЕ переводить на UI-токены — меняются только вместе с печатью. */

/* Lora — self-hosted, ТЕ ЖЕ файлы, что в печатном PDF (server/fonts/, копии
   в public/fonts/): превью и печать рендерятся одной версией шрифта (v37).
   Веса как в печати: 400 Regular / 600 SemiBold (strong, буквица, heading) /
   400 italic. Bold 700 в превью не используется. Google Fonts для Lora
   больше не подключается (pdp.html). */
@font-face {
  font-family: "Lora";
  src: url("/static/fonts/Lora-Regular.ttf") format("truetype");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Lora";
  src: url("/static/fonts/Lora-SemiBold.ttf") format("truetype");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Lora";
  src: url("/static/fonts/Lora-Italic.ttf") format("truetype");
  font-weight: 400; font-style: italic; font-display: swap;
}

/* Оверлей превью — В ПОТОКЕ страницы (НЕ position:fixed): сверху остаётся
   сквозная sticky-шапка .skz-topbar ({{SITE_HEADER_HTML}}), а сразу ПОД оверлеем
   в разметке — сквозной подвал .site-foot ({{SITE_FOOTER_HTML}}). В preview-mode
   прячем ТОЛЬКО лендинг PDP: шапку магазина .site-header, секции .section-skz и
   сквозной pre-footer CTA. overflow:hidden с body убран — иначе документ не проскроллить до
   подвала, а sticky-футер флоу и sticky-шапка не к чему «прилипать». */
body.preview-mode .site-header,
body.preview-mode .section-skz,
body.preview-mode .site-prefooter { display: none !important; }

.preview-ov {
  /* relative БЕЗ z-index — не создаёт stacking context: модалки-descendants
     (position:fixed, z-index:1000) остаются над sticky-шапкой (z-50). */
  position: relative;
  background: var(--skz-bg, #F7F1E4);
  display: flex;
  flex-direction: column;
}
.preview-ov[hidden] { display: none; }

/* Top-stepper .flow-steps (разметка в pdp.html, базовый стиль в styles.css) —
   внутри overlay ему нужна своя подложка-«бумага» и линия-отбивка, как у
   бара шагов в эталоне. */
.preview-ov .flow-steps {
  background: var(--skz-bg, #F7F1E4);
  border-bottom: 1px solid var(--skz-line, #DCD1B8);
}

/* Клавиатурный фокус — рецепт ДС (system.md §4): синее кольцо 2px. */
.preview-ov a:focus-visible,
.preview-ov button:focus-visible,
.preview-ov__dedi-overlay button:focus-visible,
.preview-ov__approve-overlay button:focus-visible,
.preview-ov__approve-consent input:focus-visible {
  outline: 2px solid var(--skz-coral, #24406E);
  outline-offset: 2px;
}

/* ── Header ──────────────────────────────────────────────────────────────── */
.preview-ov__header {
  background: var(--skz-bg, #F7F1E4);
  padding: 20px 24px 22px;
  text-align: center;
  border-bottom: 1px solid var(--skz-line, #DCD1B8);
  position: relative;
}
/* «К книге» — secondary-кнопка «Экслибриса» (эталон .btn-sec). */
.preview-ov__back {
  position: absolute;
  left: 24px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--skz-surface, #FCF8EE);
  color: var(--skz-chocolate, #232E47);
  text-decoration: none;
  font: 500 13px/1 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  padding: 9px 14px;
  border: 1px solid var(--skz-line, #DCD1B8);
  border-radius: 6px;
  white-space: nowrap;
  transition: border-color .2s;
}
.preview-ov__back:hover { border-color: var(--skz-light-tan, #C9B87E); }
.preview-ov__back svg { width: 13px; height: 13px; color: var(--skz-gray-warm, #545D70); }
.preview-ov__title {
  font-family: var(--skz-font-serif, "Playfair Display"), Georgia, serif;
  font-size: clamp(26px, 3vw, 36px);
  line-height: 1.18;
  font-weight: 800;
  margin: 0;
  text-wrap: balance;
  color: var(--skz-chocolate, #232E47);
}
.preview-ov__meta {
  margin: 6px 0 0;
  color: var(--skz-gray-warm, #545D70);
  font-size: .9rem;
}

/* ── Stage (интро + обложка + разворот 1) ───────────────────────────────── */
.preview-ov__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 24px 24px 36px;
  max-width: 1100px;
  margin: 0 auto;
  width: 100%;
  gap: 30px;
}
.preview-ov__intro {
  text-align: center;
  color: var(--skz-gray-warm, #545D70);
  font-size: 15.5px;
  line-height: 1.6;
  margin: 0 auto;
  width: min(960px, 100%);
  max-width: none;
}
.preview-ov__intro p { margin: 0 0 10px; }
.preview-ov__intro p:last-child { margin-bottom: 0; }
/* Иконка-«искра» в тексте — референс кнопки «Перерисовать», текстовое золото. */
.preview-ov__intro-icon {
  display: inline-flex;
  vertical-align: -4px;
  width: 18px; height: 18px;
  color: var(--skz-gold-hover, #6E5316);
  margin: 0 2px;
}
.preview-ov__intro-icon svg { width: 100%; height: 100%; }
.preview-ov__intro-support {
  margin-top: 14px !important;
  padding-top: 10px;
  border-top: 1px solid var(--skz-line, #DCD1B8);
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px 18px;
  font-size: .9rem;
}
.preview-ov__intro-support a {
  color: var(--skz-gold-hover, #6E5316);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}
.preview-ov__intro-support a:hover { text-decoration-color: var(--skz-gold, #A67C2E); }
.preview-ov__intro-order {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  white-space: nowrap;
  font: inherit;
  color: inherit;
}
.preview-ov__intro-order strong {
  color: var(--skz-chocolate, #232E47);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Обложка-блок + spread-блок — общий контейнер: карточка сверху, кнопка regen снизу */
.preview-ov__block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
  /* Высота подписи-«каталожной строки» (::before ниже): 12px строка + 12px
     flex-gap блока. На неё же смещается .preview-ov__pano-hint (моб. панорама). */
  --ov-sphead-h: 24px;
}

/* ── Подписи разворотов «Обложка / Разворот NN» + статус справа ──────────────
   Эталон: .sp-head/.sp-name/.sp-st (layouts-web/preview.html). Рендерятся
   ЧИСТЫМ CSS (счётчик + :has по data-status хоста) — JS-шаблоны не тронуты:
   ::before — каталожная строка слева, ::after — статус по правому краю карточки.
   Без поддержки :has() статус просто не показывается (мягкая деградация),
   само состояние дублируют замок/прелоадер на карточке. */
#ovSpreadsList { counter-reset: ovspread; }
#ovSpreadsList > .preview-ov__block { counter-increment: ovspread; }
.preview-ov__stage > .preview-ov__block::before,
#ovSpreadsList > .preview-ov__block::before {
  font: 500 10.5px/12px var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--skz-gold-hover, #6E5316);
  text-align: left;
  margin: 0; /* отбивку до карточки даёт flex-gap блока (12px) */
}
.preview-ov__stage > .preview-ov__block::before {
  content: "Обложка";
  width: min(560px, 92vw);
}
#ovSpreadsList > .preview-ov__block::before {
  content: "Разворот " counter(ovspread, decimal-leading-zero);
  width: min(960px, 100%);
}
.preview-ov__stage .preview-ov__block::after {
  position: absolute;
  top: 0;
  font: 500 10px/12px var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--skz-gray-warm, #545D70);
  pointer-events: none;
}
.preview-ov__stage > .preview-ov__block::after { right: calc((100% - min(560px, 92vw)) / 2); }
#ovSpreadsList > .preview-ov__block::after { right: calc((100% - min(960px, 100%)) / 2); }
/* Статусы: данные (data-status) → текст. Только чтение, никакой механики. */
.preview-ov__block:has(> .preview-ov__spread[data-status="locked"])::after { content: "После оплаты"; }
.preview-ov__block:has(> .preview-ov__spread[data-status="pending"])::after,
.preview-ov__block:has(> .preview-ov__cover[data-status="pending"])::after { content: "Создаём…"; }
.preview-ov__block:has(> .preview-ov__spread[data-status="ready"])::after {
  content: "Готов";
  color: var(--skz-gold-hover, #6E5316);
}
.preview-ov__block:has(> .preview-ov__cover[data-status="ready"])::after {
  content: "Готова";
  color: var(--skz-gold-hover, #6E5316);
}
.preview-ov__block:has(> .preview-ov__spread[data-status="failed"])::after,
.preview-ov__block:has(> .preview-ov__cover[data-status="failed"])::after {
  content: "Не получилось";
  color: var(--skz-warning, #B4472A);
}
/* Read-only просмотр готовой книги: статусы «Готов» не несут смысла.
   Селектор длиннее :has-правил по специфичности (3 класса + 2 элемента). */
body.preview-mode--view .preview-ov__stage .preview-ov__block::after { content: none; }

/* Обложка крупно (квадрат) — «лежащая книга»: тонкая линия + матовая тень,
   без радиуса (книга прямоугольна, system.md §3). */
.preview-ov__cover {
  position: relative;
  width: min(560px, 92vw);
  aspect-ratio: 1;
  border-radius: 0;
  overflow: hidden;
  background: var(--skz-sand, #EFE7D2);
  border: 1px solid var(--skz-line, #DCD1B8);
  box-shadow: 0 1px 0 var(--skz-line-deep, #C9BFA9), 0 8px 24px rgba(35, 46, 71, 0.06);
}

/* Список 14 разворотов — vertical stack с gap */
#ovSpreadsList {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 30px;
  width: 100%;
}

/* Разворот: image + text. Обе страницы — квадраты 1:1, разворот = 2:1.
   container-type: спред — база контейнерных единиц (cqw) для кегля текста:
   кегль задаётся в % ширины спреда и масштабируется с ним НЕПРЕРЫВНО на любом
   вьюпорте (раньше на десктопе был фикс-px: между 720 и 960px страница
   сжималась, а текст — нет, пропорции плыли). */
.preview-ov__spread {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: min(960px, 100%);
  aspect-ratio: 2 / 1;
  border-radius: 0;
  overflow: hidden;
  background: var(--skz-surface, #FCF8EE);
  border: 1px solid var(--skz-line, #DCD1B8);
  box-shadow: 0 1px 0 var(--skz-line-deep, #C9BFA9), 0 8px 24px rgba(35, 46, 71, 0.06);
  container-type: inline-size;
}
/* Разворот без текста (Коронация) — панорамная 2:1 иллюстрация раскладывается
   на две квадратные страницы 1:1, левая половина + правая половина, стыкуются
   на корешке разворота. Layout grid тот же 2col, обе колонки — image. */
.preview-ov__spread-page {
  position: relative;
  overflow: hidden;
}
.preview-ov__spread-page--image { background: var(--skz-sand, #EFE7D2); }
.preview-ov__spread-page--image img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* Текстовая страница — зеркало печатной (server/pipeline/buildBlockHtml.js
   .page--text): поля 17% ширины страницы (как в печати: ≈40 мм на странице
   233 мм), текстовый блок по центру по вертикали, фон — кремовый #EFE7D2 +
   орнамент page-bg-text.svg. Меняешь тут — поменяй и в buildBlockHtml.js. */
.preview-ov__spread-page--text {
  position: relative;
  padding: 17%;
  display: flex;
  /* Вертикально по центру рабочей зоны — как в печати. */
  align-items: center;
  justify-content: flex-start;
  background-color: #EFE7D2;
  background-image: url("/static/img/page-bg-text.svg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-left: 1px solid var(--skz-line, #DCD1B8);
  overflow: hidden;
}
/* Текстовая страница СЛЕВА в развороте (макеты text_left_image_right и
   text_left_text_right, JS ставит её first-child): линия корешка — справа. */
.preview-ov__spread-page--text:first-child {
  border-left: none;
  border-right: 1px solid var(--skz-line, #DCD1B8);
}
/* Две текстовые страницы (text_left_text_right): линия корешка одна — от левой. */
.preview-ov__spread-page--text:first-child + .preview-ov__spread-page--text {
  border-left: none;
}

/* Тело текста: Lora, leading 1.5, выключка влево, без переносов — 1:1 с печатью.
   Кегль: --spread-font-pt ставит JS из меты разворота (font_size_pt в БД;
   фолбэки 11pt сюжет / 16pt пожелание-финал — на сервере). px-запись — fallback
   для браузеров без container queries; рабочий масштаб — в @supports ниже. */
.preview-ov__spread-text {
  margin: 0;
  font-family: "Lora", Georgia, serif;
  font-weight: 400;
  font-size: calc(var(--spread-font-pt, 11) * 1px);
  line-height: 1.5;
  color: #232E47;
  max-width: 100%;
  width: 100%;
  text-align: left;
  hyphens: none;
}
/* Кегль в % ширины спреда (контейнер .preview-ov__spread = 2 страницы).
   Печать: 11pt на странице 233 мм = 1.67% ширины страницы. Экран сознательно
   крупнее ради читаемости («решение: единый стиль, читаемый кегль»):
   11px при странице 480px = 2.2917% ширины страницы = 1.14583% ширины спреда
   → 1pt = 0.104167cqw. Пропорции полей/буквицы/отбивок — 1:1 с печатью,
   переносы строк на экране от печатных отличаются (кегль относительно
   страницы другой) — это осознанный компромисс, зато web одинаков на
   десктопе/планшете/мобиле. */
@supports (container-type: inline-size) {
  .preview-ov__spread-text {
    font-size: calc(var(--spread-font-pt, 11) * 0.104167cqw);
  }
}
.preview-ov__spread-text p {
  margin: 0 0 0.42em;   /* отбивка 0.42em между абзацами — как в печати */
  text-indent: 0;        /* без красной строки */
  text-wrap: pretty;     /* против висячих строк; печать (Chromium) делает так же */
}
.preview-ov__spread-text p:last-child { margin-bottom: 0; }
/* Спейсер: доп. пустая строка автора = один интервал (см. buildBlockHtml.js). */
.preview-ov__spread-text .spacer { height: 1.5em; margin: 0; padding: 0; }
/* MD-разметка в тексте: **акцент** (Lora 600 охра), *курсив* (#6E5316),
   # Заголовок (по центру, охра, ~1.5×). Классы .heading/.quote/.dropcap-target
   приходят с сервера (единый рендер server/lib/spreadHtml.js) — те же, что
   в печатном PDF (buildBlockHtml.js), стили — зеркало печатных. */
.preview-ov__spread-text strong { font-weight: 600; color: #6E5316; }
.preview-ov__spread-text em { font-style: italic; color: #6E5316; }
.preview-ov__spread-text .heading {
  font-family: Lora, Georgia, serif;
  font-weight: 600;
  color: #6E5316;
  text-align: center;
  font-size: 1.5em;
  line-height: 1.25;
  margin: 0 0 0.6em;
}
/* Blockquote `> ...` — вопрос-якорь по центру, италик, цвет #6E5316. */
.preview-ov__spread-text .quote {
  font-style: italic;
  color: #6E5316;
  text-align: center;
}
/* Clearfix для float буквицы */
.preview-ov__spread-text::after { content: ""; display: block; clear: both; }

/* Буквица: Lora SemiBold 600, охра #6E5316, ~2 строки. (стандарт §6)
   Целевой абзац помечает СЕРВЕРНЫЙ парсер классом dropcap-target (реплики
   и кавычки её не получают); мета dropcap=false разворота вешает модификатор
   --no-dropcap на контейнер. */
.preview-ov__spread-text:not(.preview-ov__spread-text--no-dropcap) .dropcap-target::first-letter {
  float: left;
  font-family: "Lora", Georgia, serif;
  font-weight: 600;
  font-size: 2.8em;
  line-height: 0.88;
  padding: 0.04em 0.12em 0 0;
  color: #6E5316;
}

/* Текст-слой печатной вёрстки (renderTextPageImage.js): прозрачный WebP с
   буквами, отрендеренный из ТОГО ЖЕ HTML-шаблона, что печатный PDF — переносы
   строк в превью идентичны книге. Лежит поверх CSS-фона страницы (фон и
   орнамент — те же ассеты, что в печати).
   Пока точный слой грузится, серверный HTML уже виден. После load класс
   is-print-ready показывает WebP и скрывает HTML без SVG-заглушки. */
.preview-ov__spread-textimg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.preview-ov__spread-page--text .preview-ov__spread-text { visibility: visible; }
.preview-ov__spread-page--text.is-print-ready .preview-ov__spread-text { visibility: hidden; }
.preview-ov__spread-page--text.is-print-ready .preview-ov__spread-textimg { opacity: 1; }

/* Locked-разворот:
   - левая страница — уже «запечённый» 64×64 WebP-плейсхолдер, растянутый до квадрата
     браузерной интерполяцией (никакого CSS-filter — нечего блюрить, картинка уже мягкая);
   - сверху — canvas-overlay со звёздной пылью (анимация частиц);
   - правая страница — обычный текст книги. */
.preview-ov__spread[data-status="locked"] .preview-ov__spread-page--image img {
  /* image-rendering: auto — браузер сам сделает плавную bicubic интерполяцию 64→480. */
  image-rendering: auto;
}
.preview-ov__stardust {
  position: absolute; inset: 0;
  z-index: 2;
  background: rgba(0, 0, 0, 0.4);
  pointer-events: auto;
  touch-action: none;
  cursor: crosshair;
}
.preview-ov__stardust canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* Тач-устройства (мобилка/планшет): приоритет СКРОЛУ. Палец проходит сквозь спойлер
   (pointer-events:none) — не двигает частицы и не блокирует прокрутку (touch-action
   больше не none). Эффект следования частиц за курсором остаётся только на десктопе. */
@media (pointer: coarse) {
  .preview-ov__stardust { pointer-events: none; touch-action: auto; cursor: auto; }
}
/* «Странички спят…» — поверх stardust, на иллюстрации */
.preview-ov__locked-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px 22px;
  gap: 12px;
  text-align: center;
  z-index: 3;
  pointer-events: none;
}
/* Панорама (full-spread) в кратком превью: замок + подпись «Странички спят» —
   ОДИН на весь разворот, монтируется на .spread--full (splitPanoramaSpread в
   app.js) и центрируется по всей 2:1 через базовый inset:0. relative нужен, чтобы
   overlay якорился к развороту, а не к странице-половине (overflow:hidden на
   половине резал растянутую подпись по шву). */
.preview-ov__spread--full { position: relative; }
/* Панорама — одна иллюстрация: spoiler/caption живут прямыми детьми spread и
   перекрывают обе страницы единым слоем. Старые постраничные слои скрываем,
   чтобы при переходах состояний не появлялся шов или caption на полстраницы. */
.preview-ov__spread--full > .preview-ov__spread-page--image > .preview-ov__stardust,
.preview-ov__spread--full > .preview-ov__spread-page--image > .preview-ov__caption {
  display: none;
}
/* Панорама на ДЕСКТОПЕ — одна цельная картинка поверх двух фоновых половин:
   половины (setPanoBackground 200%/0|100%) на дробной ширине разворота и DPR 1
   стыкуются с суб-пиксельным швом по центру; цельный слой убирает его физически.
   На мобиле слой скрыт — там свайп по двум половинам (pan-to-explore). */
.preview-ov__pano-full { display: none; }
@media (min-width: 721px) {
  .preview-ov__spread--full .preview-ov__pano-full {
    display: block;
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    z-index: 1;
  }
}
.preview-ov__locked-icon {
  width: 38px; height: 38px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.86);
  color: var(--skz-chocolate, #232E47);
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 14px rgba(35, 46, 71, 0.28);
  flex: none;
}
.preview-ov__locked-icon svg { width: 18px; height: 18px; }
.preview-ov__locked-text {
  margin: 0;
  font-family: var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  font-style: normal;
  font-weight: 500;
  font-size: clamp(.88rem, 1.05vw, 1rem);
  line-height: 1.45;
  color: var(--skz-text-on-primary, #FCF8EE);
  text-shadow: 0 2px 8px rgba(30, 18, 16, 0.55);
  max-width: 88%;
}

/* Кнопка «Перерисовать страницу» — ВНЕ карточки, под ней, одинаково для cover/spread.
   Появляется только в .preview-ov__block (т.е. для ready), отсутствует у locked.
   Рецепт secondary «Экслибриса» (эталон .btn-sec): карточный фон, тонкая линия,
   hover — латунная кромка. Искра-иконка — латунь. */
.preview-ov__regen-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 14px;
  background: var(--skz-surface, #FCF8EE);
  color: var(--skz-chocolate, #232E47);
  border: 1px solid var(--skz-line, #DCD1B8);
  border-radius: 6px;
  font: 500 13px/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  cursor: pointer;
  transition: border-color .2s;
}
.preview-ov__regen-btn:hover:not(:disabled) { border-color: var(--skz-light-tan, #C9B87E); }
.preview-ov__regen-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.preview-ov__regen-btn svg { width: 18px; height: 18px; flex: 0 0 18px; display: block; color: var(--skz-gold, #A67C2E); }

/* Ряд кнопок под разворотом: сетка 1:1 совпадает с .preview-ov__spread —
   левая ячейка ↔ страница с лицом ребёнка (Перерисовать), правая ↔ страница
   с текстом (Редактировать пожелание, только для спреда 1 — пожелание). */
.preview-ov__actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: min(960px, 100%);
  gap: 0;
  align-items: flex-start;
}
.preview-ov__action-cell {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  min-height: 1px; /* удерживает сетку даже когда одна ячейка пустая */
}
/* Кнопка «Редактировать пожелание» — тот же secondary-рецепт, что и regen;
   отдельный класс для будущих вариаций. Карандаш — латунь. */
.preview-ov__text-edit-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 14px;
  background: var(--skz-surface, #FCF8EE);
  color: var(--skz-chocolate, #232E47);
  border: 1px solid var(--skz-line, #DCD1B8);
  border-radius: 6px;
  font: 500 13px/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  cursor: pointer;
  transition: border-color .2s;
}
.preview-ov__text-edit-btn:hover { border-color: var(--skz-light-tan, #C9B87E); }
.preview-ov__text-edit-btn svg { width: 15px; height: 15px; flex: 0 0 15px; display: block; color: var(--skz-gold, #A67C2E); }

/* Модал «Редактировать пожелание» — поверх всего overlay.
   Рецепт «Попап-диалог» ДС: карточка r8 + shadow-pop, чернильный backdrop. */
.preview-ov__dedi-overlay {
  position: fixed;
  inset: 0;
  background: rgba(35, 46, 71, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
  backdrop-filter: blur(2px);
}
.preview-ov__dedi-overlay[hidden] { display: none; }
.preview-ov__dedi-modal {
  background: var(--skz-surface, #FCF8EE);
  width: min(520px, 100%);
  border-radius: 8px;
  padding: 24px 22px 20px;
  box-shadow: 0 24px 64px rgba(35, 46, 71, 0.16);
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
  color: var(--skz-chocolate, #232E47);
}
.preview-ov__dedi-kicker {
  margin: 0 0 -6px;
  color: var(--skz-gold-hover, #6E5316);
  font: 500 10px/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.preview-ov__dedi-modal h2 {
  margin: 0;
  font-family: var(--skz-font-serif, "Playfair Display"), Georgia, serif;
  font-size: 22px;
  line-height: 1.25;
  font-weight: 800;
  text-wrap: balance;
}
.preview-ov__dedi-hint {
  margin: 0;
  color: var(--skz-gray-warm, #545D70);
  font-size: .94rem;
  line-height: 1.5;
  text-wrap: pretty;
}
.preview-ov__dedi-modal label {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.preview-ov__dedi-modal label > span {
  font: 500 14px/1.3 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  color: var(--skz-chocolate, #232E47);
}
.preview-ov__dedi-modal input,
.preview-ov__dedi-modal textarea {
  font: inherit;
  font-size: 1rem;
  padding: 10px 12px;
  border: 1px solid var(--skz-line, #DCD1B8);
  border-radius: 6px;
  background: var(--skz-surface, #FCF8EE);
  color: var(--skz-chocolate, #232E47);
  resize: vertical;
  width: 100%;
  box-sizing: border-box;
}
.preview-ov__dedi-modal textarea { min-height: 92px; line-height: 1.45; }
.preview-ov__dedi-modal input:focus,
.preview-ov__dedi-modal textarea:focus {
  outline: none;
  border-color: var(--skz-gold, #A67C2E);
  box-shadow: 0 0 0 3px rgba(166, 124, 46, 0.18);
}
.preview-ov__dedi-counter {
  font-size: .76rem;
  color: var(--skz-gray-warm, #545D70);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.preview-ov__dedi-note,
.preview-ov__dedi-error {
  margin: 0;
  padding: 10px 12px;
  border-radius: 6px;
  font-size: .82rem;
  line-height: 1.45;
  text-wrap: pretty;
}
.preview-ov__dedi-note {
  background: var(--skz-sand, #EFE7D2);
  color: var(--skz-gray-warm, #545D70);
}
.preview-ov__dedi-error {
  background: rgba(140, 59, 46, 0.08);
  color: #7B3328;
}
.preview-ov__dedi-note[hidden],
.preview-ov__dedi-error[hidden] {
  display: none;
}
.preview-ov__dedi-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, auto));
  gap: 10px;
  justify-content: flex-end;
  margin-top: 4px;
}
.preview-ov__dedi-btn {
  min-height: 44px;
  padding: 10px 20px;
  border-radius: 6px;
  border: 0;
  cursor: pointer;
  touch-action: manipulation;
  font: 600 .95rem/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
}
.preview-ov__dedi-btn--ghost { background: transparent; color: var(--skz-gray-warm, #545D70); }
.preview-ov__dedi-btn--ghost:hover { color: var(--skz-chocolate, #232E47); background: var(--skz-sand, #EFE7D2); }
.preview-ov__dedi-btn--primary { background: var(--skz-coral, #24406E); color: var(--skz-text-on-primary, #FCF8EE); font-weight: 600; transition: background .2s, box-shadow .2s; }
.preview-ov__dedi-btn--primary:hover { background: var(--skz-coral-hover, #1B3355); box-shadow: inset 0 0 0 1px var(--skz-light-tan, #C9B87E); }
.preview-ov__dedi-btn:active { transform: scale(.98); }
.preview-ov__dedi-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

/* ── Модал «Утвердить и отправить в печать» ─────────────────────────────
   Рецепт «Попап-диалог» ДС; чек-лист — латунные галочки, согласие — плашка
   на форзаце. */
.preview-ov__approve-overlay {
  position: fixed;
  inset: 0;
  background: rgba(35, 46, 71, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
  backdrop-filter: blur(2px);
}
.preview-ov__approve-overlay[hidden] { display: none; }
.preview-ov__approve-modal {
  background: var(--skz-surface, #FCF8EE);
  width: min(560px, 100%);
  border-radius: 8px;
  padding: 28px 26px 22px;
  box-shadow: 0 24px 64px rgba(35, 46, 71, 0.16);
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  color: var(--skz-chocolate, #232E47);
}
.preview-ov__approve-modal h2 {
  margin: 0;
  font-family: var(--skz-font-serif, "Playfair Display"), Georgia, serif;
  font-size: 26px;
  font-weight: 800;
  line-height: 1.2;
  text-wrap: balance;
}
.preview-ov__approve-lede {
  margin: 0;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--skz-chocolate, #232E47);
}
.preview-ov__approve-lede strong { color: var(--skz-coral, #24406E); font-weight: 600; }
.preview-ov__approve-checklist-title {
  margin: 4px 0 0;
  font-size: .92rem;
  font-weight: 600;
  color: var(--skz-chocolate, #232E47);
}
.preview-ov__approve-checklist {
  margin: 0;
  padding-left: 22px;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--skz-chocolate, #232E47);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.preview-ov__approve-checklist li { list-style: "✓  "; }
.preview-ov__approve-checklist li::marker { color: var(--skz-gold-hover, #6E5316); font-weight: 600; }

.preview-ov__approve-consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  background: var(--skz-sand, #EFE7D2);
  border: 1px solid var(--skz-line, #DCD1B8);
  border-radius: 6px;
  font-size: .98rem;
  line-height: 1.4;
  cursor: pointer;
  user-select: none;
}
.preview-ov__approve-consent input[type="checkbox"] {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
  margin-top: 1px;
  accent-color: var(--skz-coral, #24406E);
  cursor: pointer;
}
.preview-ov__approve-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
  margin-top: 4px;
}
.preview-ov__approve-btn {
  padding: 12px 22px;
  border-radius: 6px;
  border: 0;
  cursor: pointer;
  font: 500 .98rem/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
}
.preview-ov__approve-btn--ghost { background: transparent; color: var(--skz-gray-warm, #545D70); }
.preview-ov__approve-btn--ghost:hover { color: var(--skz-chocolate, #232E47); background: var(--skz-sand, #EFE7D2); }
.preview-ov__approve-btn--primary { background: var(--skz-coral, #24406E); color: var(--skz-text-on-primary, #FCF8EE); font-weight: 600; transition: background .2s, box-shadow .2s; }
.preview-ov__approve-btn--primary:hover:not(:disabled) { background: var(--skz-coral-hover, #1B3355); box-shadow: inset 0 0 0 1px var(--skz-light-tan, #C9B87E); }
.preview-ov__approve-btn--primary:disabled { opacity: 0.45; cursor: not-allowed; }

/* Универсальная "карточка" — для skel + caption у Обложки */
.preview-ov__card { display: none; } /* legacy селектор больше не используем */

.preview-ov__pic {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Изначально показываем master-blur (ставится в ovShow) под stardust.
     Когда AI cover приходит через ovSwapCover — src меняется на AI URL
     и status="ready" убирает stardust. Раньше pic стартовал с opacity:0
     и был виден только при ready — master-blur не показывался вовсе. */
  opacity: 1;
  transition: opacity .55s ease;
}
/* Skel — легаси-нода в pdp.html (master-blur заменил shimmer): всегда скрыт. */
.preview-ov__skel { display: none; }
.preview-ov__cover[data-status="ready"] .preview-ov__caption { opacity: 0; pointer-events: none; }

.preview-ov__caption {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--skz-text-on-primary, #FCF8EE);
  text-align: center;
  padding: 16px;
  pointer-events: none;
  transition: opacity .35s ease;
  z-index: 2;
}
/* Искра генерации — латунь в белом кружке (эталон .plq-i), кружок пульсирует. */
.preview-ov__sparkles {
  width: 38px; height: 38px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.86);
  color: var(--skz-gold, #A67C2E);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(35, 46, 71, 0.28);
  margin-bottom: 12px;
  font-size: 1.15rem; /* фолбэк-глиф ✦, если разметка старая */
  line-height: 1;
  animation: ov-sparkle 2.5s ease-in-out infinite;
}
.preview-ov__sparkles svg { width: 18px; height: 18px; display: block; fill: currentColor; }
@keyframes ov-sparkle {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.18); }
}
@media (prefers-reduced-motion: reduce) {
  .preview-ov__sparkles { animation: none; }
}
.preview-ov__caption-title {
  font-family: var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  font-size: 15.5px;
  line-height: 1.45;
  font-weight: 500;
  margin: 0 0 4px;
  text-shadow: 0 2px 8px rgba(30, 18, 16, 0.55);
}
.preview-ov__caption-counter {
  font-size: 13px;
  margin: 0;
  opacity: .88;
  text-shadow: 0 2px 8px rgba(30, 18, 16, 0.55);
  font-variant-numeric: tabular-nums;
}
.preview-ov__caption-tip {
  margin: 12px auto 0;
  max-width: 84%;
  font-size: 13px;
  line-height: 1.45;
  color: var(--skz-text-on-primary, #FCF8EE);
  opacity: 0;
  text-shadow: 0 2px 8px rgba(30, 18, 16, 0.55);
  min-height: 2.6em;
  transform: translateY(4px);
  transition: opacity .35s ease, transform .35s ease;
}
.preview-ov__caption-tip.is-visible {
  opacity: .88;
  transform: translateY(0);
}

/* Caption над spread в режиме generating: тот же sparkle+title+tip, что у
   cover, поверх master-картинки под stardust. При смене status на "ready"
   (AI пришёл) — stardust и caption плавно растворяются вместе. */
.preview-ov__caption--spread {
  background: linear-gradient(180deg, rgba(35, 46, 71, 0) 0%, rgba(35, 46, 71, 0.55) 100%);
}
.preview-ov__spread[data-status="ready"] .preview-ov__stardust,
.preview-ov__spread[data-status="ready"] .preview-ov__caption,
.preview-ov__spread[data-status="ready"] .preview-ov__locked-overlay,
.preview-ov__cover[data-status="ready"] .preview-ov__stardust {
  opacity: 0;
  pointer-events: none;
  transition: opacity .45s ease;
}

/* ── «Не получилось создать иллюстрации» ─────────────────────────────────────
   data-status="failed" (ovShowGenerationFailed в app.js): прелоадер-caption и
   stardust гаснут, поверх — плашка с кнопкой «Попробовать ещё раз». */
.preview-ov__spread[data-status="failed"] .preview-ov__stardust,
.preview-ov__spread[data-status="failed"] .preview-ov__caption,
.preview-ov__cover[data-status="failed"] .preview-ov__stardust,
.preview-ov__cover[data-status="failed"] .preview-ov__caption {
  opacity: 0;
  pointer-events: none;
}
.preview-ov__fail {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 16px;
  gap: 8px;
  color: var(--skz-text-on-primary, #FCF8EE);
  /* .78 — кремовый текст держит AA (≥4.5:1) даже на белоснежной иллюстрации. */
  background: rgba(35, 46, 71, 0.78);
  z-index: 3;
}
.preview-ov__fail-title {
  font-family: var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  font-weight: 600;
  margin: 0;
  text-shadow: 0 2px 8px rgba(30, 18, 16, 0.55);
}
.preview-ov__fail-text {
  margin: 0;
  max-width: 84%;
  font-size: .92rem;
  line-height: 1.45;
  opacity: .9;
  text-shadow: 0 2px 8px rgba(30, 18, 16, 0.55);
}
.preview-ov__fail-btn {
  margin-top: 10px;
  padding: 12px 22px;
  border-radius: 6px;
  border: 0;
  cursor: pointer;
  font: 600 .95rem/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  background: var(--skz-coral, #24406E);
  color: var(--skz-text-on-primary, #FCF8EE);
  transition: background .2s, box-shadow .2s;
}
.preview-ov__fail-btn:hover:not(:disabled) { background: var(--skz-coral-hover, #1B3355); box-shadow: inset 0 0 0 1px var(--skz-light-tan, #C9B87E); }
.preview-ov__fail-btn:disabled { opacity: 0.55; cursor: wait; }

/* ── Footer (price + Continue) — sticky bottom, всегда видим ─────────────── */
.preview-ov__footer {
  position: sticky;
  bottom: 0;
  background: var(--skz-surface, #FCF8EE);
  border-top: 1px solid var(--skz-line, #DCD1B8);
  padding: 16px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  z-index: 50;
  box-shadow: 0 -6px 24px rgba(35, 46, 71, 0.08);
}
/* Stepper — легаси (разметки #ovStep1..4 в pdp.html нет), правила оставлены
   на случай возврата узкой полоски; приведены к токенам. */
.preview-ov__stepper {
  position: sticky;
  top: 0;
  display: flex;
  background: var(--skz-surface, #FCF8EE);
  border-bottom: 1px solid var(--skz-line, #DCD1B8);
  z-index: 40;
  padding: 0;
}
.preview-ov__stepper-item {
  flex: 1;
  padding: 10px 12px;
  text-align: center;
  font-size: .82rem;
  font-weight: 500;
  color: var(--skz-gray-warm, #545D70);
  border-bottom: 3px solid transparent;
  text-decoration: none;
}
.preview-ov__stepper-item--active {
  color: var(--skz-chocolate, #232E47);
  border-bottom-color: var(--skz-gold, #A67C2E);
  font-weight: 600;
}
.preview-ov__stepper-item--done {
  color: var(--skz-gold-hover, #6E5316);
}
/* В fullPreview-режиме (после оплаты) back-кнопку прячем — некуда возвращаться */
body.preview-mode--full .preview-ov__back { display: none; }

/* View-режим (готовая утверждённая книга, открыта из /account → «Посмотреть
   книгу», URL /books/<slug>/book/<id>/view): прячем всё что относится к
   процессу оформления — stepper «1 Предпросмотр…6 В работе», кнопки regen/
   edit/«Изменить» в footer, continue-btn (которая на /book/<id> = «Утвердить
   и в печать»). Кнопка «Скачать PDF» здесь не нужна — она есть в карточке
   /account для тестирования скорости загрузки PDF. */
body.preview-mode--view #ovFlowSteps,
body.preview-mode--view #ovIntro,
body.preview-mode--view .preview-ov__regen-btn,
body.preview-mode--view .preview-ov__text-edit-btn,
body.preview-mode--view .approve-foot__edit,
body.preview-mode--view .preview-ov__continue { display: none; }

/* View-режим: переводим overlay в normal document flow, чтобы SSR-навигация
   (.skz-topbar — «Главная / Каталог / Мои книги / Контакты») оставалась
   сверху, а контент книги шёл под ней через обычный scroll документа.
   .site-header в pdp.html — это PDP-шапка магазина (с anchor #personalize/
   #reviews), её скрываем как и в других overlay-режимах. Также прячем
   .site-footer (тёмный лендинг-футер) — он не уместен на просмотре книги. */
body.preview-mode--view { overflow: auto; }
body.preview-mode--view .preview-ov {
  position: relative;
  inset: auto;
  overflow-y: visible;
  -webkit-overflow-scrolling: auto;
}
body.preview-mode--view body > .site-footer,
body.preview-mode--view .site-footer { display: none !important; }

/* ЕДИНЫЙ инфо-блок ребёнка (#ovChildInfo) — рендерится JS (renderApproveFooterInfo)
   на ВСЕХ этапах (предпросмотр/Утверждение/просмотр): аватар + имя + цвет глаз слева.
   Пока пуст (до async /preview, ~300-800 мс) — не занимает места. Кнопка «Изменить»
   внутри блока — только на предпросмотре (рендерит JS по opts.showEdit). */
#ovChildInfo:empty { display: none; }

/* ── Sidebar с миниатюрами разворотов ──────────────────────────────────────
   Лента слева внутри overlay: cover + 14 мини-разворотов 2:1. Клик — scroll
   к соответствующему развороту в stage. */
.preview-ov__sidebar {
  position: sticky;
  /* 72px = высота сквозной sticky-шапки .skz-topbar (лого 44px + пэддинги + линия):
     после расфиксации оверлея документ скроллит сам, и sticky-лента миниатюр
     должна вставать ПОД шапкой, а не уезжать под неё. */
  top: 72px;
  align-self: start;
  max-height: calc(100vh - 72px);
  overflow-y: auto;
  width: 160px;
  padding: 14px 10px 120px;   /* padding-bottom — чтобы sticky footer не перекрывал последний разворот */
  background: var(--skz-surface, #FCF8EE);
  border-right: 1px solid var(--skz-line, #DCD1B8);
  display: flex; flex-direction: column; gap: 12px;
}
/* Контейнер: миниатюра + подписи страниц снизу. Активная — латунная рамка
   + мини-ляссе с верхней кромки (рецепт «Миниатюра», system.md §4). */
.preview-ov__thumb-row {
  position: relative;
  display: flex; flex-direction: column;
  align-items: center;
  gap: 3px;
  cursor: pointer;
}
.preview-ov__thumb {
  position: relative;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  aspect-ratio: 2 / 1;
  border-radius: 2px;
  overflow: hidden;
  border: 1px solid var(--skz-line, #DCD1B8);
  background: var(--skz-sand, #EFE7D2);
  transition: border-color .15s;
}
.preview-ov__thumb-row:hover .preview-ov__thumb { border-color: var(--skz-light-tan, #C9B87E); }
.preview-ov__thumb-row--active .preview-ov__thumb { border-color: var(--skz-gold, #A67C2E); }
/* Ляссе свисает с верхней кромки активной миниатюры (внутри, overflow не режет). */
.preview-ov__thumb-row--active .preview-ov__thumb::before {
  content: "";
  position: absolute;
  top: 0;
  left: 8px;
  width: 4px;
  height: 14px;
  background: var(--skz-gold, #A67C2E);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 4px), 50% calc(100% - 1px), 0 calc(100% - 4px));
  z-index: 2;
  pointer-events: none;
}
/* Обложка — квадратная (1:1), без второй колонки */
.preview-ov__thumb--cover {
  width: 70%;
  aspect-ratio: 1;
  grid-template-columns: 1fr;
}
.preview-ov__thumb img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
/* Мини-копия текстовой страницы: тот же бежевый фон с орнаментами, та же
   буквица охрой, та же охра у **жирных**, тот же курсив. Размер шрифта 3px —
   это «текстура» страницы, не читаемое содержимое. */
.preview-ov__thumb-text {
  background-color: #EFE7D2;
  background-image: url("/static/img/page-bg-text.svg");
  background-size: cover;
  background-position: center;
  font-family: Lora, Georgia, serif;
  font-size: 3px;
  line-height: 1.25;
  padding: 5px 4px 4px;
  overflow: hidden;
  color: #232E47;
  text-align: left;
}
.preview-ov__thumb-text p {
  margin: 0 0 1px;
  text-indent: 0;
}
.preview-ov__thumb-text strong { color: #6E5316; font-weight: 600; }
.preview-ov__thumb-text em { color: #6E5316; font-style: italic; }
.preview-ov__thumb-text .quote {
  text-align: center; font-style: italic; color: #6E5316;
}
.preview-ov__thumb-text .heading {
  text-align: center; color: #6E5316; font-weight: 600; font-size: 1.3em;
  margin: 0 0 1px;
}
/* Буквица — крупная первая буква охрой, как на странице */
.preview-ov__thumb-text .dropcap-target::first-letter {
  font-weight: 600;
  color: #6E5316;
  font-size: 2.4em;
  line-height: 0.85;
  float: left;
  margin: 0 1px 0 0;
}
/* Номера страниц под миниатюрой: 2 колонки = страницы [левая | правая] */
.preview-ov__thumb-pages {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: 100%;
  text-align: center;
  font: 500 10px/1.5 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  letter-spacing: .08em;
  color: var(--skz-gray-warm, #545D70);
  font-variant-numeric: tabular-nums;
}
.preview-ov__thumb-pages--single {
  grid-template-columns: 1fr;
}
.preview-ov__thumb-label {
  font: 500 10px/1.5 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  letter-spacing: .08em;
  color: var(--skz-gray-warm, #545D70);
  text-align: center;
}

/* Контент-зона рядом с sidebar.
   Sidebar (миниатюры) занимает 3 строки = от stepper до самого низа.
   Header (название), stage (страницы) и footer (CTA) — справа от sidebar. */
.preview-ov__layout {
  display: grid;
  grid-template-columns: 160px 1fr;
  grid-template-areas:
    "side header"
    "side stage"
    "side foot";
  gap: 0;
}
.preview-ov__sidebar { grid-area: side; }
.preview-ov__header  { grid-area: header; }
.preview-ov__stage   { grid-area: stage; }
.preview-ov__footer  { grid-area: foot; }
@media (max-width: 760px) {
  .preview-ov__sidebar { display: none; } /* на мобиле скрываем — много места жрёт */
  .preview-ov__layout {
    grid-template-columns: 1fr;
    grid-template-areas: "header" "stage" "foot";
  }
}

/* ── Arrows для переключения вариантов сгенерированной иллюстрации ───────── */
.preview-ov__block { position: relative; }

/* Стрелки вариантов — «Экслибрис»: латунный шеврон на кремовом полупрозрачном
   пилле, hover — латунная заливка с кремовым шевроном (без сдвига макета).
   Шеврон — SVG (центрируется ровно, в отличие от unicode-глифов ‹ › с
   шрифтовыми baseline-смещениями). Паттерн задокументирован в эталоне
   layouts-web/preview.html (.varr/.varc). */
.preview-ov__arrow-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 1px solid rgba(166, 124, 46, 0.4);
  background: rgba(252, 248, 238, 0.92);
  color: var(--skz-gold, #A67C2E);
  padding: 0;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 10px rgba(35, 46, 71, 0.22);
  z-index: 6;
  transition: background-color .2s, color .2s, border-color .2s;
}
.preview-ov__arrow-btn:hover {
  background: var(--skz-gold, #A67C2E);
  border-color: var(--skz-gold, #A67C2E);
  color: var(--skz-text-on-primary, #FCF8EE);
}
.preview-ov__arrow-btn:disabled,
.preview-ov__arrow-btn:disabled:hover {
  background: rgba(252, 248, 238, 0.72);
  border-color: rgba(166, 124, 46, 0.24);
  color: var(--skz-gray-warm, #545D70);
  box-shadow: none;
  cursor: default;
  opacity: .58;
  pointer-events: none;
}
.preview-ov__arrow-btn--prev { left: 10px; }
.preview-ov__arrow-btn--next { right: 10px; }

/* Счётчик «N / M» — кремовая пилюля по нижнему центру иллюстрации. */
.preview-ov__counter {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  min-width: 48px;
  padding: 5px 12px;
  background: rgba(252, 248, 238, 0.92);
  border: 1px solid rgba(166, 124, 46, 0.35);
  color: var(--skz-chocolate, #232E47);
  font: 600 12px/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  text-align: center;
  border-radius: 999px;
  z-index: 6;
  pointer-events: none;
}
.preview-ov__footer-info { display: flex; flex-direction: column; gap: 2px; }
.preview-ov__free {
  margin: 0;
  font-size: 13px;
  color: var(--skz-gray-warm, #545D70);
}

/* На «Утверждении» (/book/<id>) footer-info содержит аватар+имя+возраст+волосы
   и кнопку «Изменить» вместо цены. .preview-ov__footer-info--approve
   переопределяет flex-direction на горизонтальный. */
/* Левая часть футера в режиме «Утверждение»: аватар + текст с параметрами + кнопка
   «Изменить» прижата к тексту (не к правому краю). flex: 0 0 auto на info-блоке
   чтобы он занимал ровно сколько нужно, оставляя пространство для «Утвердить» справа. */
.preview-ov__footer-info--approve {
  flex-direction: row !important;
  align-items: center;
  gap: 12px;
  flex: 0 0 auto;
}
/* Контейнер-аватар: несёт фон-силуэт (тот же значок, что в refine-модалке). <img>
   поверх скрыт классом is-broken, пока фото не загрузилось — тогда виден силуэт.
   Единый плейсхолдер для demo, медленной загрузки, истёкшего/битого фото. */
.approve-foot__avatar {
  width: 46px; height: 46px;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  border: 1px solid var(--skz-line, #DCD1B8);
  background-color: var(--skz-sand, #EFE7D2);
  /* Силуэт-плейсхолдер — латунь %23A67C2E (эталон .ava). */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23A67C2E'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4 20c0-4 3.6-6 8-6s8 2 8 6z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 58%;
}
.approve-foot__avatar-img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.approve-foot__avatar-img.is-broken { display: none; }
/* text — две колонки: слева имя+возраст, справа параметры (по 2 строки в каждой) */
.approve-foot__text {
  flex: 0 0 auto; min-width: 0;
  display: flex; gap: 18px; align-items: center;
}
.approve-foot__person { display: flex; flex-direction: column; }
.approve-foot__name {
  font-size: 15px; color: var(--skz-chocolate, #232E47);
  font-weight: 600;
  white-space: nowrap;
  line-height: 1.25;
}
.approve-foot__age {
  font-size: 13px; color: var(--skz-gray-warm, #545D70);
  line-height: 1.25;
}
.approve-foot__params {
  display: flex; flex-direction: column;
  line-height: 1.35;
}
.approve-foot__params-row {
  font-size: 12.5px; color: var(--skz-chocolate, #232E47);
  white-space: nowrap;
}
.approve-foot__param-key { color: var(--skz-gray-warm, #545D70); }
/* «Изменить» — secondary-рецепт, в один ряд с остальными кнопками оверлея. */
.approve-foot__edit {
  padding: 8px 14px;
  border: 1px solid var(--skz-line, #DCD1B8);
  border-radius: 6px;
  background: var(--skz-surface, #FCF8EE);
  color: var(--skz-chocolate, #232E47);
  font: 500 13px/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  transition: border-color .2s;
}
.approve-foot__edit:hover { border-color: var(--skz-light-tan, #C9B87E); }
@media (max-width: 760px) {
  .approve-foot__avatar { width: 36px; height: 36px; }
  .approve-foot__name { font-size: .92rem; }
  .approve-foot__params { font-size: .7rem; column-gap: 8px; }
  .approve-foot__edit { padding: 7px 12px; font-size: .8rem; }
}

/* CTA этапа — primary «Экслибриса»: синяя, r6, hover темнее + латунная кромка. */
.preview-ov__continue {
  padding: 15px 30px;
  border-radius: 6px;
  background: var(--skz-coral, #24406E);
  color: var(--skz-text-on-primary, #FCF8EE);
  border: 0;
  font: 600 16px/1.2 var(--skz-font-sans, "Golos Text"), system-ui, sans-serif;
  cursor: pointer;
  transition: background .2s, box-shadow .2s, transform .15s;
}
.preview-ov__continue:hover:not(:disabled) {
  background: var(--skz-coral-hover, #1B3355);
  box-shadow: inset 0 0 0 1px var(--skz-light-tan, #C9B87E);
}
.preview-ov__continue:active:not(:disabled) { transform: scale(.98); }
.preview-ov__continue:disabled {
  background: var(--skz-sand, #EFE7D2);
  color: var(--skz-gray-warm, #545D70);
  box-shadow: inset 0 0 0 1px var(--skz-line, #DCD1B8);
  cursor: not-allowed;
  opacity: .78;
}

/* ── Mobile ──────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  /* Страховка от горизонтального скролла панорамы: clip, а НЕ hidden. hidden
     коэрсит overflow-y в auto → .preview-ov стал бы скролл-контейнером, и после
     расфиксации sticky-футер флоу «прилипал» бы к нему, а не к низу вьюпорта
     (и документ не скроллился бы к подвалу). clip клипает по X, НЕ создавая
     скролл-контекст: вертикальный скролл остаётся документным, sticky работает.
     Горизонтальный свайп панорамы живёт в своём .preview-ov__spread--full. */
  .preview-ov { overflow-x: clip; }
  /* styles.css ставит на узких экранах html,body{overflow-x:hidden} (анти-
     горизонтальный-скролл лендинга) — это делает BODY скролл-контейнером, и
     sticky-футер флоу перестаёт липнуть к вьюпорту после расфиксации оверлея.
     В preview-mode возвращаем body к visible (тогда sticky снова считается от
     корневого скроллера/вьюпорта). html остаётся overflow-x:hidden — страхует
     страницу от горизонтального скролла; панораму клипает .preview-ov сверху. */
  body.preview-mode { overflow-x: visible; }

  .preview-ov__dedi-overlay {
    align-items: flex-start;
    padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
  }
  .preview-ov__dedi-modal {
    padding: 20px 16px 16px;
    max-height: calc(100dvh - max(32px, env(safe-area-inset-top) + env(safe-area-inset-bottom)));
  }
  .preview-ov__dedi-actions {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    justify-content: stretch;
  }
  .preview-ov__dedi-btn { padding-inline: 12px; }
  .preview-ov__layout,
  .preview-ov__header,
  .preview-ov__stage,
  .preview-ov__footer { min-width: 0; }

  .preview-ov__header { padding: 14px 16px 16px; }
  .preview-ov__back {
    left: 12px;
    padding: 6px 10px;
    min-width: 44px;
    min-height: 44px;
    justify-content: center;
  }
  .preview-ov__back span { display: none; }
  /* Отступы под кнопку «назад» слева (и симметрично справа) —
     длинный заголовок не налезает на неё */
  .preview-ov__title { font-size: 20px; padding: 0 48px; }
  .preview-ov__meta { font-size: .78rem; }

  .preview-ov__stage { padding: 18px 14px 24px; gap: 26px; }
  .preview-ov__intro { font-size: .92rem; }
  #ovSpreadsList { gap: 26px; }

  /* Стрелки вариантов и счётчик — тач-таргеты ≥44px. */
  .preview-ov__arrow-btn { width: 44px; height: 44px; }
  .preview-ov__arrow-btn svg { width: 16px; height: 16px; }
  /* Спред — контейнер для cq-кегля текста (container-type задан в базовом
     правиле): у него нет паддингов, база cqw стабильна. Контейнер на самой
     текст-странице (aspect-ratio + %-padding) давал движку кривую базу. */
  .preview-ov__spread { aspect-ratio: auto; grid-template-columns: 1fr; }
  .preview-ov__spread-page--image { aspect-ratio: 1; }
  /* Панорамный разворот (spread--full): не столбик из двух половин (разрезал
     композицию), а pan-to-explore — квадратное окно с горизонтальным нативным
     свайпом по цельной панораме. Страницы-половины лежат встык в скролл-треке;
     background-модель (200% / 0|100%) даёт непрерывную картинку без шва.
     Старт — левый край (чтение слева направо); подпрыг и рука-подсказка — JS
     (mountPanoPanHint в app.js). */
  .preview-ov__spread--full {
    display: flex;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .preview-ov__spread--full::-webkit-scrollbar { display: none; }
  .preview-ov__spread--full .preview-ov__spread-page--image {
    flex: 0 0 100%;
    min-width: 100%;
  }
  /* Прокручиваются только две страницы панорамы. Spoiler/status/fail — sticky
     flex-слои шириной с видимое квадратное окно: они стоят на месте без
     JS-компенсации scrollLeft и не дёргаются в async-scroll Safari. Отрицательный
     margin не добавляет слой к ширине scroll-трека. */
  .preview-ov__spread--full > .preview-ov__stardust,
  .preview-ov__spread--full > .preview-ov__caption,
  .preview-ov__spread--full > .preview-ov__locked-overlay,
  .preview-ov__spread--full > .preview-ov__fail {
    position: sticky;
    inset: 0 auto 0 0;
    flex: 0 0 100%;
    min-width: 100%;
    width: 100%;
    height: auto;
    align-self: stretch;
    margin-right: -100%;
    order: -1;
    transform: none;
  }
  /* Приглашение к свайпу (класс --inviting на спреде, JS mountPanoPanHint):
     трек панорамы мягко качается влево-обратно — видно, что картинка
     продолжается за краем. Анимируется transform СТРАНИЦ, не scrollLeft:
     скролл-позиция нетронута, палец в любой момент перехватывает жест без
     борьбы с анимацией. Гаснет вместе с рукой при реальном свайпе. */
  .preview-ov__spread--inviting .preview-ov__spread-page--image {
    animation: pano-invite 2.8s ease-in-out infinite;
  }
  @keyframes pano-invite {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(-30px); }
  }
  /* Рука-подсказка «⟷»: absolute в block (не в скролл-треке — не уезжает со
     скроллом), геометрией повторяет квадрат спреда; top смещён на высоту
     подписи-«каталожной строки» (::before блока). Гасится классом is-done. */
  .preview-ov__pano-hint {
    position: absolute;
    top: var(--ov-sphead-h, 24px); left: 0; right: 0;
    aspect-ratio: 1;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding-bottom: 24px;
    pointer-events: none;
    z-index: 4;
    opacity: 1;
    transition: opacity .4s;
  }
  .preview-ov__pano-hint.is-done { opacity: 0; }
  .preview-ov__pano-hint svg {
    width: 64px; height: 64px;
    color: var(--skz-text-on-primary, #FCF8EE);
    filter: drop-shadow(0 2px 10px rgba(30, 18, 16, 0.55));
    opacity: .85;
    /* ТОТ ЖЕ keyframes, что у трека панорамы (--inviting выше): рука и
       картинка движутся синхронно — ладонь будто тянет панораму за собой. */
    animation: pano-invite 2.8s ease-in-out infinite;
  }
  /* На низком landscape квадрат панорамы выше viewport: нижняя подсказка
     оказалась бы за первым экраном. Ставим ладонь справа от status-плашки —
     она видна сразу и не перекрывает центральный замок/прелоадер. */
  @media (max-height: 500px) and (orientation: landscape) {
    .preview-ov__pano-hint {
      align-items: flex-start;
      justify-content: flex-end;
      padding: 24px 24px 0 0;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .preview-ov__pano-hint svg { animation: none; }
    .preview-ov__spread--inviting .preview-ov__spread-page--image { animation: none; }
  }
  /* Текстовая страница — ТОЧНО как на десктопе: квадрат 1:1, поля 17% (печать),
     кегль пропорционален ширине страницы. Виньетки фона остаются по краям
     квадрата, текст не наезжает на «колонтитул». */
  .preview-ov__spread-page--text {
    border-left: 0;
    border-top: 1px solid var(--skz-line, #DCD1B8);
    aspect-ratio: 1;
    padding: 17%;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }
  /* Одна колонка: спред = одна страница, поэтому коэффициент вдвое больше
     десктопного (там спред = 2 страницы): 1pt = 0.208334cqw — та же видимая
     пропорция кегля к странице на любом экране. */
  @supports (container-type: inline-size) {
    .preview-ov__spread-text {
      font-size: calc(var(--spread-font-pt, 11) * 0.208334cqw);
    }
  }

  /* Кнопки этапа — единый размер и кегль: 44px высоты, .9rem */
  .preview-ov__regen-btn,
  .preview-ov__text-edit-btn,
  .preview-ov__continue,
  .approve-foot__edit {
    min-height: 44px;
    font-size: .9rem;
    padding: 10px 18px;
    gap: 8px;
  }
  .preview-ov__regen-btn svg { width: 18px; height: 18px; flex: 0 0 18px; }
  .preview-ov__text-edit-btn svg { width: 16px; height: 16px; flex: 0 0 16px; }

  /* Ряд кнопок под разворотом: в столбик, одинаковая ширина, по центру */
  .preview-ov__actions {
    grid-template-columns: 1fr;
    gap: 10px;
    justify-items: center;
  }
  .preview-ov__action-cell { width: 100%; justify-content: center; min-height: 0; }
  .preview-ov__action-cell:empty { display: none; }
  .preview-ov__regen-btn,
  .preview-ov__text-edit-btn {
    width: min(320px, 100%);
    justify-content: center;
  }

  /* Футер: всё в одну строку — аватар, текст-колонка, «Изменить», CTA.
     Текст сжимаемый (min-width: 0), кнопки при нехватке места переносят
     подпись внутри себя, а не выпадают второй строкой. */
  /* Микро-уплотнение футера — padding 12→10, gap 8→6, аватар 40→36 выше:
     даёт ~+5px горизонтали, чтобы длинные имена (Александра/Константин) не
     стояли впритык к «Изменить фото». Размер кнопок не трогаем. */
  .preview-ov__footer { padding: 10px; gap: 6px; }
  .preview-ov__footer-info { min-width: 0; flex: 1 1 auto; }
  .preview-ov__footer-info--approve { gap: 6px; }
  /* «Цвет глаз» — под именем и возрастом (одна колонка, не две).
     flex: 1 1 auto перебивает базовое 0 0 auto — иначе текст не сжимается
     и «Изменить» наезжает на CTA. */
  .approve-foot__text {
    flex: 1 1 auto;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    min-width: 0;
  }
  .approve-foot__name,
  .approve-foot__params-row { white-space: normal; }
  .preview-ov__free { font-size: .72rem; }
  /* Мобильный футер флоу заказа: «Изменить фото» и «Оформить заказ» — в 2 строки
     (width:min-content ломает подпись по словам, ширина = самое длинное слово),
     чтобы обе кнопки встали в одну строку футера. «Цвет глаз» тут убран
     (renderApproveFooterInfo в app.js) — освобождает место под 2 кнопки. */
  .approve-foot__edit {
    padding: 10px; font-size: .85rem;
    width: min-content; white-space: normal; text-align: center; line-height: 1.15;
  }
  .preview-ov__continue { flex: 0 1 auto; padding: 10px 12px; font-size: .85rem; }
  /* CTA в 2 строки — ТОЛЬКО во флоу заказа (предпросмотр до оплаты). Утверждение
     (--full) и просмотр (--view) не трогаем: там CTA «Утвердить и отправить в
     печать» и своя раскладка (flex-wrap ниже). */
  body.preview-mode:not(.preview-mode--full):not(.preview-mode--view) .preview-ov__continue {
    width: min-content; white-space: normal; text-align: center; line-height: 1.15;
  }
  /* «Утверждение» (--full): CTA «Утвердить и отправить в печать» в общую
     строку на 375px не помещается (текст ломается на 3 строки и давит
     колонку имени) — она остаётся второй строкой на всю ширину. */
  body.preview-mode--full .preview-ov__footer { flex-wrap: wrap; }
  body.preview-mode--full .preview-ov__continue { flex: 1 1 100%; }

  .preview-ov__regen {
    right: 10px; bottom: 10px;
    padding: 8px 14px 8px 10px;
    font-size: .82rem;
    gap: 6px;
  }
  .preview-ov__regen-icon svg { width: 16px; height: 16px; }
}
