/* ==========================================================================
   Мини-апп: конфигуратор в стиле приложения, а не страницы
   ==========================================================================
   Всё висит на body.is-miniapp / html.miniapp — на сайте этих классов нет,
   и ни одно правило туда не протекает.

   Идея экрана: знак — это и предпросмотр, и поле ввода одновременно; выбор
   исполнения — лента самих знаков, а не список слов; вся коммерция сведена
   в одну нижнюю кнопку. Отсюда отсутствие карточек-контейнеров: разделяют
   воздух и волосяные линии, потому что меньше линий читается дороже. */

/* Переменные конфигуратора нужны обеим поверхностям: в приложении их несёт
   body.is-miniapp, на сайте — обёртка .mini-config вокруг того же экрана.
   Один и тот же конфигуратор, разное окружение. */
.is-miniapp,
.mini-config {
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --mini-pad: 16px;

  /* Доля скругления плашки от её ширины: PLATE_RX / 800 из plate.js, где 800
     юнитов — ширина длинного знака. CSS-обрезка обязана совпадать с углом,
     который рисует SVG: возьмём больше — срежем нарисованный угол, меньше —
     тень выйдет за плашку прямыми углами.

     Именно на этом ломалась лента: там плашка 119px, её угол 3.4px, а общее
     правило .plate-svg резало по 8px — вдвое больше нарисованного. */
  --plate-corner: 0.02875;

  /* Размеры карточки ленты держим здесь: из них считается и радиус плашки.

     138px было ровно столько, сколько влезает в телефон, но при такой ширине
     плашка выходит 119px, а цифра — 17px. Закруглённая макушка занимает при
     этом меньше пикселя устройства, и попадание на границу пиксельной сетки
     съедает её целиком: на одной карточке из трёх верх цифры выглядит
     срезанным, на соседних нет, а поворот экрана перекладывает карточки на
     другие доли пикселя и артефакт переезжает. Лечится не геометрией —
     геометрия верна, — а запасом по размеру. */
  --card-w: 152px;
  --card-border: 1.5px;
  --card-pad: 8px;
}

/* Числа не должны прыгать по ширине при пересчёте — цена, счёт, габариты. */
.mini-card-price,
.mini-size,
.order-details dd,
.mini-cta { font-variant-numeric: tabular-nums; }

.mini { padding: 0 0 calc(env(safe-area-inset-bottom, 0px) + 8px); }

/* -------------------------------------------------------------- заголовок */
.mini-head { padding: calc(env(safe-area-inset-top, 0px) + 14px) var(--mini-pad) 14px; }
.mini-title {
  margin: 0 0 3px;
  font-size: 1.7rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
}
.mini-lede { margin: 0; font-size: 0.88rem; color: var(--text-muted); }

/* ------------------------------------------------------------------ знак */
/* Сцена держит пропорцию плашки, чтобы прозрачные поля ввода, разложенные
   в процентах, ложились ровно на её окна при любой форме знака. */
.mini-stage { padding: 2px var(--mini-pad) 0; }
.mini-stage-inner {
  position: relative;
  width: 100%;
  margin-inline: auto;
  aspect-ratio: 800 / 172;
  transition: transform 0.3s var(--ease-out);
}
.mini-stage-inner.is-editing { transform: scale(1.015); }
.mini-plate { position: absolute; inset: 0; }
/* Ширину квадратного знака задаёт сама сцена (см. maxWidth в miniapp-ui.js),
   поэтому общий для сайта масштаб 55.5% здесь снимаем — иначе плашка
   уехала бы внутрь сцены и разошлась с прозрачными полями ввода. */
.mini-plate .plate-svg[data-shape="square"] { width: 100%; margin-inline: 0; }
.mini-plate .plate-svg { border-radius: 10px; box-shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.45); }

/* Поля ввода невидимы: символы рисует сама плашка. Прозрачное поле лежит
   поверх окна знака и ловит тап напрямую — так клавиатуру поднимает
   браузер по настоящему жесту. Программный focus() на iOS срабатывает
   через раз, и полагаться на него нельзя. */
.mini-field {
  position: absolute;
  margin: 0;
  padding: 0;
  border: none;
  outline: none;
  background: transparent;
  color: transparent;
  caret-color: transparent;
  /* 16px — порог, ниже которого Safari масштабирует страницу под поле. */
  font-size: 16px;
  -webkit-tap-highlight-color: transparent;
  cursor: text;
}
.mini-field::selection { background: transparent; }

/* Рамка на позиции, которая вводится прямо сейчас. */
.mini-caret {
  position: absolute;
  border: 2.5px solid var(--accent);
  border-radius: 5px;
  pointer-events: none;
  transition: left 0.18s var(--ease-out), top 0.18s var(--ease-out),
              width 0.18s var(--ease-out), height 0.18s var(--ease-out);
  animation: miniCaret 1.15s ease-in-out infinite;
}
@keyframes miniCaret { 50% { opacity: 0.35; } }

.mini-size {
  margin: 10px 0 0;
  text-align: center;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

/* ------------------------------------------------------------- секции */
.mini-block { padding: 20px var(--mini-pad) 0; }
.mini-label {
  margin: 0 0 8px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* --------------------------------------------------- сегменты (тип, форма) */
.mini-seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 3px;
  padding: 3px;
  background: var(--surface-2);
  border-radius: 11px;
}
.mini-seg + .mini-seg { margin-top: 8px; }
.mini-seg-btn {
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font);
  font-weight: 600;
  font-size: 0.87rem;
  padding: 9px 4px;
  min-height: 38px;
  cursor: pointer;
  transition: background 0.2s var(--ease-out), color 0.2s, transform 0.12s;
}
.mini-seg-btn.is-on {
  background: var(--surface);
  color: var(--text);
  font-weight: 700;
  box-shadow: var(--shadow-sm);
}
.mini-seg-btn:active:not(:disabled) { transform: scale(0.97); }
.mini-seg-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* ----------------------------------------------------- лента исполнений */
/* Горизонтальная лента, а не сетка: варианты сравниваются попарно взглядом,
   и обрезанная у края карточка сама сообщает, что список продолжается. */
.mini-rail {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* Запас по вертикали. overflow-x: auto по спецификации делает вторую ось
     тоже прокручиваемой, то есть лента режет и сверху, и снизу: на этом уже
     срезалась галочка выбора. В обрез укладывались тень плашки и рамка
     выбранной карточки, а на дробных размерах экрана — и верхний ряд
     пикселей самой плашки. Шесть пикселей поля стоят дешевле, чем поиск
     очередного среза. */
  padding: 6px calc(var(--mini-pad)) 6px 0;
  margin-right: calc(var(--mini-pad) * -1);
}
.mini-rail::-webkit-scrollbar { display: none; }
.mini-card {
  flex: 0 0 auto;
  width: var(--card-w);
  scroll-snap-align: start;
  border: none;
  background: none;
  padding: 0;
  text-align: left;
  font-family: var(--font);
  cursor: pointer;
  transition: transform 0.12s;
}
.mini-card:active { transform: scale(0.97); }
.mini-card-plate {
  display: block;
  padding: var(--card-pad);
  border-radius: 12px;
  background: var(--surface);
  border: var(--card-border) solid var(--border);
  transition: border-color 0.2s, background 0.2s;
}
/* Обрезка ровно по нарисованному углу — иначе на маленькой плашке общее
   правило .plate-svg (8px) срезает угол вдвое сильнее, чем он нарисован. */
/* Обёртка плашки. Это <span>, и без правила она остаётся строчной: ширина
   в процентах у вложенного SVG считается тогда от строчного бокса, а сама
   строка добавляет снизу место под выносные элементы шрифта. Плашка от этого
   и сидит не там, где её ждут, и может подрезаться краем карточки. */
.mini-card-canvas { display: block; line-height: 0; }
.mini-card-plate .plate-svg {
  border-radius: calc(
    (var(--card-w) - var(--card-border) * 2 - var(--card-pad) * 2) * var(--plate-corner)
  );
  box-shadow: 0 2px 6px -3px rgba(0, 0, 0, 0.35);
}
.mini-card.is-on .mini-card-plate {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
}
.mini-card-name {
  display: block;
  padding: 7px 2px 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-muted);
  transition: color 0.2s;
}
.mini-card.is-on .mini-card-name { color: var(--text); }
.mini-card-price { display: block; padding: 0 2px; font-size: 0.8rem; color: var(--text-muted); }

/* ------------------------------------------------------ строки-переключатели */
.mini-rows { display: flex; flex-direction: column; }
.mini-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 2px;
  border: none;
  border-bottom: 0.5px solid var(--border);
  background: none;
  font-family: var(--font);
  text-align: left;
  cursor: pointer;
}
.mini-row:last-child { border-bottom: none; }
.mini-row-text { flex: 1; min-width: 0; }
.mini-row-name { display: block; font-size: 0.94rem; font-weight: 600; color: var(--text); }
.mini-row-hint { display: block; font-size: 0.75rem; color: var(--text-muted); margin-top: 1px; }

.mini-switch {
  flex-shrink: 0;
  position: relative;
  width: 46px;
  height: 28px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: background 0.24s var(--ease-out), border-color 0.24s;
}
/* Фирменный синий, а не системный зелёный: зелёный здесь читался как чужой
   элемент операционной системы и спорил с акцентом всего остального. */
.mini-row.is-on .mini-switch { background: var(--accent); border-color: var(--accent); }
.mini-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
  transition: left 0.24s var(--ease-spring);
}
.mini-row.is-on .mini-knob { left: 20px; }


/* -------------------------------------------------------------- кнопка */
/* Одна кнопка на весь экран: она же индикатор следующего шага. Пока номер
   не введён — приглушена и называет, чего не хватает; дальше показывает
   цену. Отдельного блока «Итого» поэтому нет. */
.mini-cta-wrap { padding: 22px var(--mini-pad) 8px; }
.mini-cta {
  width: 100%;
  min-height: 52px;
  border: none;
  border-radius: 14px;
  background: var(--accent);
  color: #fff;
  font-family: var(--font);
  font-weight: 700;
  font-size: 1rem;
  cursor: pointer;
  transition: background 0.25s, color 0.25s, transform 0.12s;
}
.mini-cta:active { transform: scale(0.985); }
.mini-cta.is-idle { background: var(--surface-2); color: var(--text-muted); }
.mini-note { margin: 8px 0 0; text-align: center; font-size: 0.75rem; color: var(--text-muted); }

/* Пока грузится шрифт знака, плашка показывает скелет — общий с сайтом
   механизм (.fonts-loading), здесь только скругление под нашу форму. */
.fonts-loading .mini-plate { border-radius: 10px; }
/* -------------------------------------------------------------------- */
/* Модалка — настоящая iOS-шторка: плотнее пружина, заметнее ручка,
   стекло вместо плоской подложки под контентом. */
.is-miniapp .modal-overlay { backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.is-miniapp .modal-sheet {
  border-radius: 24px 24px 0 0;
  box-shadow: var(--shadow-lg);
}
/* :not(.is-dragging) — иначе при той же специфичности (2 класса против 2)
   эта пружина перебивала бы transition:none из style.css во время реального
   перетаскивания пальцем, и шторка тормозила бы вместо честного 1:1 со свайпом. */
.is-miniapp .modal-sheet:not(.is-dragging) { transition: transform 0.42s var(--ease-spring); }
.is-miniapp .modal-handle {
  width: 36px;
  height: 5px;
  background: color-mix(in srgb, var(--text-muted) 45%, transparent);
}

/* Строки заказа — сгруппированный список, как экран настроек в iOS, а не
   таблица с чертами. Разделитель — реальный волосок, а не border 1px. */
.is-miniapp .order-details {
  background: var(--surface-2);
  border-radius: 14px;
  gap: 0;
  overflow: hidden;
}
.is-miniapp .order-details > div {
  padding: 12px 14px;
  border-bottom: 0.5px solid var(--border);
}
.is-miniapp .order-details > div:last-child { border-bottom: none; }

.is-miniapp .modal-done-mark { color: #1b9c5a; }

/* -------------------------------------------------------------------- */
/* Тосты — без ориентира на --header-h сайта, которого в мини-аппе нет:
   держимся за безопасную зону сверху, под собственной панелью. */
.is-miniapp .toast-stack {
  top: calc(env(safe-area-inset-top, 0px) + 54px);
}
.is-miniapp .toast {
  border-radius: 16px;
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .is-miniapp .plate-visual::after { display: none; }
}

/* -------------------------------------------------------------------- */
/* Фрейм: страница не должна ездить.

   В мини-аппе жест «потянуть вниз» принадлежит Telegram — он им закрывает
   окно. Пока документ пружинит (overscroll), клиент читает это как своё
   и приложение уезжает за фрейм. Лечится в три приёма, и нужны все три:
   disableVerticalSwipes() в telegram.js забирает жест у клиента,
   overscroll-behavior убирает пружину браузера, а overflow-x отсекает
   боковой сдвиг, из-за которого фрейм вообще начинает болтаться.

   Горизонтальной прокрутки на этом экране нет ни одной осмысленной:
   единственная полоса — лента каталога, и она скроллится внутри себя. */
/* Прокручивается не документ, а сам экран приложения.

   Кто владеет прокруткой документа внутри мини-аппа — решает клиент, и решает
   по-разному: на телефонах скроллится страница, а Telegram Desktop держит
   webview фиксированной высоты и ждёт, что приложение прокрутит себя само.
   Отсюда и был «на десктопе скролл не работает вовсе»: страница честно
   заканчивалась ниже окна, но двигать её было некому.

   Свой контейнер снимает вопрос целиком: он ведёт себя одинаково у всех
   клиентов и не зависит от того, что клиент делает с документом. */
html.miniapp {
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}
body.is-miniapp {
  height: 100%;
  overflow: hidden;
  overscroll-behavior-y: none;
  /* manipulation отключает двойной тап для зума, оставляя обычные тапы
     и прокрутку. Задержки в 300 мс на нажатие тоже больше не будет. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Страховка от бокового выезда: любой элемент шире экрана растягивает
   документ и возвращает горизонтальный скролл, который мы только что убрали.
   Ловим самые вероятные источники — картинки, знак, таблицы сводки. */
.is-miniapp img,
.is-miniapp svg,
.is-miniapp .plate-visual,
.is-miniapp .preview-summary,
.is-miniapp .modal-sheet { max-width: 100%; }

/* Экран приложения — единственный вертикальный скроллер.
   dvh, а не vh: на телефоне при открытой клавиатуре vh остаётся высотой окна
   без учёта клавиатуры, и низ экрана уезжает под неё безвозвратно. */
.is-miniapp .mini {
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Импульс не уходит наружу: на телефоне это тот самый жест, которым
     Telegram закрывает окно. */
  overscroll-behavior-y: contain;
}

/* Лента исполнений прокручивается сама и не должна передавать импульс
   документу, когда доехала до края. */
.mini-rail { overscroll-behavior-x: contain; }

/* -------------------------------------------------------------------- */
/* Сообщение о том, что приложение не запустилось. В мини-аппе консоли под
   рукой нет, а пустой экран не объясняет ничего — поэтому причина видна
   прямо на странице. */
.boot-error {
  display: block;
  margin: 0;
  padding: calc(env(safe-area-inset-top, 0px) + 14px) 16px 14px;
  background: #c8302f;
  color: #fff;
  font-family: var(--font);
  font-size: 0.85rem;
  line-height: 1.45;
}
.boot-error strong { display: block; font-size: 0.95rem; margin-bottom: 3px; }
/* Адрес в сообщении надо давать выделить пальцем, иначе его не скопировать. */
.boot-error { user-select: text; -webkit-user-select: text; word-break: break-word; }

/* Контейнер полей ввода не должен ловить тапы сам — он лишь держит поля,
   которые позиционируются по геометрии знака. */
.mini-fields { position: absolute; inset: 0; pointer-events: none; }
.mini-fields .mini-field { pointer-events: auto; }

/* -------------------------------------------------------------------- */
/* Строка под знаком: размер слева, «Стереть» справа. */
.mini-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 10px;
  min-height: 30px;
}
.mini-size { margin: 0; text-align: left; }
.mini-clear {
  flex-shrink: 0;
  border: none;
  background: none;
  padding: 4px 2px;
  font-family: var(--font);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  transition: color 0.15s, transform 0.12s, opacity 0.2s var(--ease-out);
}
.mini-clear:active { transform: scale(0.94); color: var(--accent-hover); }

/* Пока номер не введён, плашка приглушена — сразу видно, что это пример.
   С первым символом она набирает контраст. */
/* Тень набирает вес вместе с контрастом: у примера знак «лежит» плоско,
   у заполненного — приподнят. Одинаковая тень выдавала, что приглушение
   нарисовано поверх, а не является состоянием предмета. */
.mini-plate { transition: opacity 0.3s var(--ease-out), filter 0.3s var(--ease-out); }
.mini-plate .plate-svg { transition: box-shadow 0.3s var(--ease-out); }
.mini-stage-inner.is-demo .mini-plate { opacity: 0.62; filter: saturate(0.5); }
.mini-stage-inner.is-demo .mini-plate .plate-svg {
  box-shadow: 0 3px 10px -6px rgba(0, 0, 0, 0.35);
}

/* Отклик на тап по знаку: короткое поджатие подтверждает, что плашка живая
   и по ней действительно можно нажать. */
.mini-stage-inner { transition: transform 0.22s var(--ease-out); }
.mini-stage-inner:active { transform: scale(0.988); }
.mini-stage-inner.is-editing { transform: scale(1.015); }

/* Галочка на выбранном варианте: рамка и цвет читаются хуже явного знака.
   Стоит в строке с названием, а не углом плашки: у ленты overflow-x: auto,
   и всё, что выступает за карточку, она обрезает — по обеим осям сразу.
   Отступ живёт на самой галочке, а не в gap: gap считается и при нулевой
   ширине, и невыбранные карточки получали лишние 5px слева от названия. */
.mini-card-name {
  display: flex;
  align-items: center;
}
.mini-card-tick {
  flex-shrink: 0;
  display: flex;
  width: 0;
  overflow: hidden;
  color: var(--accent);
  font-size: 15px;
  opacity: 0;
  transform: scale(0.6);
  margin-right: 0;
  transition: width 0.2s var(--ease-out), margin-right 0.2s var(--ease-out),
              opacity 0.2s var(--ease-out), transform 0.24s var(--ease-spring);
}
.mini-card.is-on .mini-card-tick {
  width: 15px;
  margin-right: 5px;
  opacity: 1;
  transform: scale(1);
}

/* Цена в ленте меняется вслед за опциями — без перехода число «прыгает». */
.mini-card-price { transition: color 0.2s; }
.mini-card.is-on .mini-card-price { color: var(--text); font-weight: 600; }

/* Запертая опция: видно, что она есть, но сейчас недоступна. Гасим, а не
   прячем — исчезающая строка читается как поломка. */
.mini-row.is-locked { opacity: 0.42; }
.mini-row.is-locked .mini-switch { background: var(--surface-2); }

/* Кнопка «Стереть» появляется плавно и не двигает строку: место под неё
   держит min-height у .mini-meta. */
.mini-clear { opacity: 1; }
.mini-clear.is-hidden { opacity: 0; pointer-events: none; }

/* Градиент у правого края ленты — подсказка, что она прокручивается.
   Гаснет на последней карточке, поэтому не мешает, когда листать некуда. */
.mini-rail-wrap { position: relative; }
.mini-rail-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  right: -16px;
  width: 55px;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s var(--ease-out);
  background: linear-gradient(to right, transparent, var(--bg));
}
.mini-rail-wrap.has-more::after { opacity: 1; }

/* Подпись кнопки перекатывается при смене цены: мгновенная подмена главного
   числа экрана читается как сбой отрисовки. Двигаем метку, а не кнопку —
   фон под пальцем ездить не должен. */
.mini-cta-label { display: inline-block; }
.mini-cta-label.is-rolled { animation: ctaRoll 0.28s var(--ease-out); }
@keyframes ctaRoll {
  from { opacity: 0; transform: translateY(0.5em); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .mini-cta-label.is-rolled { animation: none; }
}

/* -------------------------------------------------------------------- */
/* Шаг «Как получить» в шторке заказа. Базовые стили общие с сайтом
   (.pick в style.css), здесь только то, что должно вести себя как в
   приложении: плотнее отступы и отклик на нажатие пальцем. */
.is-miniapp .modal-lede { margin: -2px 0 14px; font-size: 0.86rem; }
.is-miniapp .pick {
  padding: 13px 14px;
  border-radius: 14px;
  transition: border-color 0.15s, background 0.15s, transform 0.12s var(--ease-out);
}
.is-miniapp .pick:active { transform: scale(0.985); }
.is-miniapp .pick-title { font-size: 0.94rem; }
.is-miniapp .pick-note { font-size: 0.79rem; }
.is-miniapp .deliv-fields { gap: 12px; padding-left: 14px; }
.is-miniapp .ship-block { gap: 12px; margin-top: 18px; padding-top: 16px; }
.is-miniapp .ship-head-title { font-size: 0.95rem; }
.is-miniapp .switch-row { padding: 11px 12px; }
.is-miniapp .switch-row:active { transform: scale(0.99); }
.is-miniapp .modal-count { font-size: 0.72rem; }
.is-miniapp .modal-next { margin-top: 20px; }
