@import url("/static/fonts/fonts.css");
@import url("/static/fonts/brand-fonts.css");

/* =========================================================================
   YURT specialty coffee — киоск самозаказа
   Палитра снята с фирменной посуды: крафт-стакан #A06B45, белая крышка,
   чёрная типографика. Меняется ТОЛЬКО :root — по остальному коду цвета не
   разбросаны (ТЗ §10).
   ========================================================================= */

:root {
  /* ---- ПАЛИТРА БРЕНДА (брендбук YURT) ----
     Акцент — песочный из логотипа. У него есть цена: на белом он не держит
     ничего (1.6:1), поэтому цвет разделён надвое. --accent красит ПЛОСКОСТИ
     (кнопки, панель заказа, бейджи) и работает с тёмным текстом поверх;
     --accent-ink красит ТЕКСТ на светлом (цены, активные подписи) и для этого
     притемнён до 5.5:1. Один бежевый и там и там дал бы нечитаемые цены. */
  --accent:      #E8C48A;   /* песочный: заливки */
  --accent-ink:  #8A5A0C;   /* тёмная охра: акцентный текст на светлом */
  --accent-2:    #C28610;   /* охра: рамки, активные состояния */
  --accent-weak: rgba(232,196,138,0.22);
  --on-accent:   #3A1D06;   /* текст поверх песочного */
  --gradient: linear-gradient(135deg, #4A2306, #A9701A 52%, #C28610);

  --bg: #FAF7F2; --card: #FFFFFF; --surface-2: #F1EBE1;
  --text: #2E1A0B; --hint: #7D6E5F;
  --border: rgba(74,35,6,0.10);
  --success: #4C6E2C; --danger: #5E060D;
  --shadow: 0 2px 6px rgba(74,35,6,0.06), 0 12px 32px rgba(74,35,6,0.08);
  --card-shadow: 0 6px 16px rgba(74,35,6,0.09);

  --selected-bg: #FFFFFF; --selected-fg: #2E1A0B; --selected-icon: var(--accent-ink);

  --glass-bg: rgba(255,255,255,0.55);
  --glass-bg-strong: rgba(255,255,255,0.74);
  --glass-bg-soft: rgba(255,255,255,0.38);
  --glass-border: rgba(255,255,255,0.45);
  --glass-blur: blur(22px) saturate(180%);
  --glass-shadow: 0 8px 28px rgba(74,35,6,0.16);

  /* ---- ГЕОМЕТРИЯ ---- */
  /* Реальную высоту шапки меряет JS (syncBarHeight): формулой её не задать,
     она зависит от отступов, шрифта и safe-area. Со счётной высотой панель
     уезжала ПОД шапку. */
  --bar-h: 64px;
  --peek-h: 176px;      /* сколько панели видно в покое: обе строки навигации */
  --card-badge: 39px;   /* счётчик на плитке и «−» под ним — размер один на двоих */

  /* Прогресс раскрытия 0..1. Пишет app.js ОДНОЙ переменной, всё остальное —
     calc() от неё: во время жеста не должно быть ни одной перерисовки
     разметки, только композит. */
  --p: 0;

  --ease-enter:  cubic-bezier(.16, 1, .3, 1);
  --ease-exit:   cubic-bezier(.4, 0, 1, 1);
  --ease-snap:   cubic-bezier(.22, 1, .36, 1);
  --ease-scroll: cubic-bezier(.32, .72, 0, 1);

  /* Gilroy платный и в вебе недоступен. Manrope — ближайший бесплатный по
     пропорциям и рисунку, и, в отличие от Jost, несёт cyrillic-ext, то есть
     казахские ә ө ү қ ң. Montserrat оставлен третьим эшелоном. */
  --font: 'Manrope', 'Montserrat', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-display: 'Unbounded', 'Manrope', sans-serif;

  /* Силуэт юрты: используется знаком в шапке, на заставке и на экране «принят». */
  --chevron-up: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 15l7-7 7 7'/%3E%3C/svg%3E");
  --yurt-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M8 54V38.5C8 23.5 18 12 32 12s24 11.5 24 26.5V54a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2z'/%3E%3Cellipse cx='32' cy='12' rx='9' ry='4.2'/%3E%3C/svg%3E");
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #17130F; --card: #221C16; --surface-2: #2A231C;
    --text: #F4EDE2; --hint: #A2958A;
    --border: rgba(255,255,255,0.10);
    /* На тёмном акцентный текст должен быть светлее, а не темнее. */
    --accent-ink: #E8C48A; --accent-weak: rgba(232,196,138,0.16);
    --glass-bg: rgba(34,28,22,0.55);
    --glass-bg-strong: rgba(34,28,22,0.74);
    --glass-bg-soft: rgba(34,28,22,0.34);
    --glass-border: rgba(255,255,255,0.14);
    --glass-shadow: 0 8px 28px rgba(0,0,0,0.45);
  }
}

/* ---------------------------------------------- корень и высота экрана --- */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; user-select: none; }

/* html красится ОТДЕЛЬНО от body: если системе всё-таки останется полоса,
   которую страница не закрасила, она будет тёмной, а не белой. */
html { margin: 0; padding: 0; height: 100%; overflow: hidden; }
body {
  margin: 0; padding: 0; height: var(--app-h, 100%);
  background: var(--bg); color: var(--text); overflow: hidden;
  font-family: var(--font);
  /* iPadOS: гасим двойной тап-зум и callout по долгому нажатию. */
  touch-action: manipulation;
  -webkit-touch-callout: none;
}

/* Высоту задаёт --app-h: реальные пиксели, которые JS перемеряет на каждом
   resize. При первой отрисовке браузер занижает высоту, и vh/dvh/fill-available
   этот промах не лечат — они берут ту же величину, отсюда белая полоса снизу
   до первого поворота. 100dvh (не 100vh!) — только запасное значение. */
#app {
  height: var(--app-h, 100dvh);
  display: flex; flex-direction: column;
  /* Фирменный фон ПОД обоими слоями: на стыке разно едущих слоёв мелькает
     зазор в пару пикселей, и сквозь него светил бы белый фон страницы. */
  background: var(--gradient);
}

.glass {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow);
}
.hidden { display: none !important; }

/* Знак бренда — силуэт юрты с шаныраком, как в логотипе брендбука.
   Маска, а не clip-path: форма сложнее треугольника, а цвет по-прежнему
   наследуется от currentColor. */
/* Знак — сам логотип из брендбука, а не перерисовка: у купола свои
   пропорции, волна светотени и решётка шанырака, и силуэтом это не
   передаётся. Цветной, поэтому currentColor здесь не участвует. */
.brand-mark {
  display: inline-block; width: 1.16em; height: 1em; margin-right: .3em;
  background: url("/static/brand-yurt.webp") center / contain no-repeat;
  vertical-align: -.14em;
}

/* ---------------------------------------------- три слоя главного экрана --- */
/* HERO — именно ФОН, а не блок в потоке: панель едет поверх него, поэтому при
   раскрытии заставка не исчезает. */
.hero {
  position: fixed; inset: 0; z-index: 1;
  height: var(--app-h, 100dvh);
  background: var(--gradient);
  overflow: hidden;
  transform: scale(1);
  transform-origin: 50% 35%;
}
/* Затемнение сверху: alpha пишет app.js, растёт вместе с раскрытием. */
.hero-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to bottom, rgba(0,0,0,.55), rgba(0,0,0,0) 46%);
  opacity: 0;
}
.hero-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center;
  padding: calc(env(safe-area-inset-top, 0px) + 48px) 24px var(--peek-h);
  pointer-events: none;
  /* Постоянное затемнение под вордмарком. Оно не то же самое, что .hero-scrim:
     тот появляется только при раскрытии панели, а знак бренда стоит на кадре
     всегда. Пока заставкой был фирменный градиент, белого с тенью хватало; на
     съёмке фон свой, и на светлом кадре (айс латте на бежевом) знак пропадал.
     Градиент лежит фоном самого слоя, поэтому рисуется под его же текстом и
     не спорит с .hero-scrim, который ложится ниже по стопке. */
  background: linear-gradient(rgba(31,26,22,.45), rgba(31,26,22,0) 34%);
}
.hero-overlay > * { pointer-events: auto; }
/* Вордмарк гаснет РАНЬШЕ, чем приедет бар: двух логотипов на экране быть не
   должно. Обе альфы ведёт JS. */
.hero-brand, .hero .attract-hint { opacity: 1; }
.hero-brand { text-align: center; color: #fff; text-shadow: 0 2px 24px rgba(0,0,0,.35); }
/* Втрое мельче прежнего: логотип во весь экран забирал внимание у самого
   меню, ради которого гость и подошёл. */
.attract-logo { font-size: clamp(20px, 3.6vw, 36px); font-weight: 700; letter-spacing: .06em; line-height: 1;
  font-family: var(--font-display);
}
.attract-sub { margin-top: 6px; font-size: clamp(10px, 1.3vw, 13px); font-weight: 400; letter-spacing: .22em; text-transform: lowercase; opacity: .92; }
.hero-caption { flex: 1; }

/* PANEL — высотой в экран минус бар. Двигается ТОЛЬКО transform. */
.panel {
  position: fixed; left: 0; right: 0; z-index: 2;
  top: var(--bar-h);
  height: calc(var(--app-h, 100dvh) - var(--bar-h));
  display: flex; flex-direction: column;
  transform: translateY(100%);   /* стартовое положение, дальше ведёт app.js */
  will-change: transform;
  /* Тени НЕТ: она ложилась на заставку над кромкой меню грязной полосой. */
  box-shadow: none;
  /* touch-action: none здесь НЕ ставить: он глушит нативную прокрутку каталога.
     Прокрутку ведёт .page-scroll, панель забирает жест осознанно, из JS. */
}

/* Фон панели — ДВА неподвижных слоя, у которых меняется только прозрачность.
   Одним слоем с пересборкой градиента из JS это была полноэкранная
   перерисовка на кадр; прозрачность композитор меняет даром. */
.panel-bg {
  position: absolute; inset: 0; z-index: -1;
  background: var(--bg);
  opacity: 0;            /* значение пишет app.js */
}
/* Кромка: стопы посажены на ИЗМЕРЕННЫЕ строки навигации, а не на круглые
   числа. Это заливка, а НЕ маска: маска съела бы текст навигации вместе с
   подложкой. */
.panel-edge {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to bottom,
    rgba(250,247,242,0)     0px,
    rgba(250,247,242,0)    40px,
    rgba(250,247,242,.35)  90px,
    rgba(250,247,242,.80) 140px,
    rgba(250,247,242,1)   185px);
  opacity: 1;            /* и гаснет, когда панель раскрыта */
}
@media (prefers-color-scheme: dark) {
  .panel-edge {
    background: linear-gradient(to bottom,
      rgba(23,19,15,0) 0px, rgba(23,19,15,0) 40px, rgba(23,19,15,.35) 90px,
      rgba(23,19,15,.80) 140px, rgba(23,19,15,1) 185px);
  }
}

/* BAR — отдельная фаза: выезжает только после того, как панель встала. */
.appbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 3;
  display: flex; align-items: center; gap: 10px;
  padding: calc(env(safe-area-inset-top, 0px) + 10px) 20px 10px;
  background: var(--bg); color: var(--text);
  border-bottom: 1px solid var(--border);
  transform: translateY(-100%);
  /* Уход быстрее прихода. --motion пишет app.js из общей константы темпа —
     темп бара обязан совпадать с темпом панели, иначе нахлёст фаз съезжает.
     Базовые числа не трогать. */
  transition: transform calc(180ms * var(--motion, 1)) var(--ease-exit);
}
.appbar.in { transition: transform calc(280ms * var(--motion, 1)) var(--ease-enter); }
.appbar.in { transform: translateY(0); }
.appbar .brand { font-size: 24px; font-weight: 700; letter-spacing: .04em; color: var(--text);   font-family: var(--font-display);
}
.appbar .brand-sub { font-size: 12px; font-weight: 400; letter-spacing: .18em; color: var(--hint); }
.appbar .spacer { flex: 1; }
.appbar .clock { font-size: 18px; font-weight: 600; color: var(--hint); font-variant-numeric: tabular-nums; }
.back-to-attract {
  width: 48px; height: 48px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); font-size: 22px; cursor: pointer;
}
/* Содержимое шапки проявляется со сдвигом по времени — «набегает» за баром. */
.appbar .brand, .appbar .brand-sub, .appbar .clock, .appbar .back-to-attract {
  opacity: 0;
  transition: opacity calc(200ms * var(--motion, 1)) linear;
}
.appbar.in .brand, .appbar.in .brand-sub { opacity: 1; transition-delay: calc(90ms * var(--motion, 1)); }
.appbar.in .clock            { opacity: 1; transition-delay: calc(150ms * var(--motion, 1)); }
.appbar.in .back-to-attract  { opacity: 1; transition-delay: calc(180ms * var(--motion, 1)); }

/* «Уменьшение движения»: без перемещений, только кросс-фейд. */
@media (prefers-reduced-motion: reduce) {
  .panel, .pager, .appbar { transition: opacity 150ms linear !important; }
  .hero { transform: none; }
}

/* ------------------------------------------ пейджер разделов и прокрутка --- */
/* Обрезка и сдвиг — на РАЗНЫХ элементах: если повесить их на один, transform
   уносит вместе с содержимым и окно обрезки, и после сдвига на экране пусто. */
.pager {                       /* окно: только обрезает, не двигается */
  flex: 1; min-height: 0;
  overflow: hidden;
  touch-action: pan-y;         /* вертикаль браузеру, горизонталь нам */
}
.pager-track {                 /* лента: только двигается */
  display: flex; height: 100%;
  will-change: transform;
}
.page { flex: 0 0 100%; min-width: 100%; display: flex; flex-direction: column; min-height: 0; }
/* Вертикальный скролл У КАЖДОЙ страницы свой и сохраняется при возврате. */
.page-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  background: transparent;
  touch-action: pan-y;
  overscroll-behavior: contain;
  /* -webkit-overflow-scrolling НЕ ставить (Г-1): он включает старый путь
     ускоренной прокрутки WebKit, который не шлёт scroll во время инерции. */
}
/* Обе строки навигации прилипают ОДНИМ блоком, не по очереди. */
.nav-stack.stuck { box-shadow: 0 2px 12px rgba(0,0,0,.08); transition: box-shadow 120ms linear; }
/* .nav-stack БЕЗ собственного фона: сплошная заливка перекрывала бы градиент
   кромки панели, и в покое обе строки сидели бы на белом прямоугольнике. */
.nav-stack { flex: 0 0 auto; position: relative; z-index: 6; }

/* Ряд разделов level-1. */
.buckets {
  display: flex; gap: 6px; padding: 14px 12px 0;
  overflow-x: auto; overflow-y: hidden;
}
.bucket-btn {
  flex: 0 0 auto; border: none; background: none; cursor: pointer;
  padding: 8px 14px 10px; font-family: var(--font-display);
  font-size: 20px; font-weight: 500; white-space: nowrap;
  color: var(--text); opacity: .45; position: relative;
}
.bucket-btn.active { opacity: 1; color: var(--accent-ink); font-weight: 600; }
.bucket-btn.active::after {
  content: ""; position: absolute; left: 14px; right: 14px; bottom: 2px;
  height: 3px; border-radius: 2px; background: var(--accent-2);
}

/* Ряд категорий — горизонтальная прокрутка, чипсы не переносятся. */
.categories {
  flex: 0 0 auto; position: relative; z-index: 2;
  padding: 10px 12px 16px;
  display: flex; flex-direction: row; gap: 8px;
  overflow-x: auto; overflow-y: hidden;
  background: transparent; border-bottom: none;
}
.cat-btn {
  border: 1px solid rgba(74,35,6,0.16);
  background: rgba(255,255,255,0.14); color: var(--text);
  padding: 15px 22px; border-radius: 20px; font-family: var(--font);
  font-size: 18px; font-weight: 400; cursor: pointer;
  min-height: 60px; flex: 0 0 auto; white-space: nowrap;  /* палец: не меньше 60px */
}
/* Активная категория — тонкая рамка акцентом, НЕ залитая пилюля и не свечение:
   толстая тень читалась на экране как размытое пятно. */
.cat-btn.active {
  background: var(--accent-weak);
  border: 1.5px solid var(--accent-2);
  color: var(--accent-ink); font-weight: 500; box-shadow: none;
}

/* Свайп между разделами: transform пишет app.js инлайном. Постоянного
   will-change здесь НЕТ намеренно — он держал бы всю сетку отдельным слоем
   композитора всё время. */
.menu { touch-action: pan-y; padding: 16px 16px 200px; background: var(--bg); }
/* Нижний отступ = высота панели заказа + воздух, иначе последний ряд карточек
   прячется под ней и до него не домотать. */
.menu-section { margin-bottom: 26px; }
.menu-section-title { margin: 0 0 12px; font-size: 26px; font-weight: 800; letter-spacing: -.01em;   font-family: var(--font-display); letter-spacing: -.01em;
}
/* Ровно три колонки, а не auto-fill. Автоподбор раскладывал витрину по
   ширине экрана: на 834px выходило три плитки, а на 1032px уже четыре — они
   становились узкими, и фото напитка в квадратной плитке зрительно сжималось.
   Киоск стоит на одном планшете, подстраиваться не под что: три плитки дают
   и крупный кадр, и читаемое название. Две — только на узком экране, куда
   киоск не ставят, но где иногда смотрят с телефона. */
.menu-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 620px) {
  .menu-grid { grid-template-columns: repeat(2, 1fr); }
}
.loading { padding: 40px 0; text-align: center; color: var(--hint); font-size: 20px; }

/* ------------------------------------------- плитки товаров и нажатия --- */
.product-card {
  position: relative;
  background: var(--card); border-radius: 20px; overflow: hidden;
  box-shadow: var(--card-shadow); cursor: pointer;
  display: flex; flex-direction: column;
  /* Своей пропорции у плитки нет: её высота — это фото плюс подпись, а обе
     части заданы жёстко (4:3 и фиксированная высота подписи). Поэтому ряд
     остаётся ровным независимо от длины названия, и секции не растут по мере
     загрузки картинок — условие, при котором кэш геометрии скроллспая
     остаётся верным. */
  /* Карточка — замкнутый мир. Контейнмент позволяет движку не перекладывать
     сотню карточек, когда меняется что-то одно — счётчик, плашка стоп-листа.
     Размер сюда не входит намеренно: высоту ряда задаёт содержимое. */
  contain: layout paint;
}
/* Жёсткий aspect-ratio — условие, при котором кэш геометрии скроллспая вообще
   имеет право существовать: без него загрузка фото сдвигала бы секции вниз
   и spyTops протухал бы молча (ТЗ §7.7). */
/* Кадр горизонтальный 4:3. Квадратная зона делала плитку высокой, а витрину
   длинной; снимки при этом сняты именно горизонтальным кадром (2400x1792), и
   квадрат резал их по бокам. Плитки перерисованы под эту рамку целиком, чтобы
   cover не срезал стакан сверху и снизу. */
.product-card-image {
  width: 100%; flex: 0 0 auto; aspect-ratio: 4 / 3;
  object-fit: cover; display: block;
  background: var(--surface-2);
  /* Внутри жестовой зоны: без этого протаскивание <img> запускает нативный
     drag и браузер отменяет pointer-жест (Г-9). */
  -webkit-user-drag: none; user-select: none;
}
/* Подпись фиксированной высоты: две строки названия максимум, дальше
   многоточие. Иначе длинное название съедало бы фотографию. */
.product-card-body {
  flex: 0 0 auto; padding: 11px 14px 13px;
  display: flex; flex-direction: column; gap: 4px;
}
/* Названия — обычным весом: жирным здесь был весь список разом, и вес
   переставал что-либо выделять. Иерархию держат размер и цвет. */
.product-card-name {
  font-size: 16px; font-weight: 400; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-height: 2.5em;
}
.product-card-price { font-size: 18px; font-weight: 700; color: var(--accent-ink); }
.product-card.unavailable { opacity: .5; }
.product-card-stop {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  padding: 5px 10px; border-radius: 10px; font-size: 13px; font-weight: 700;
  background: rgba(31,26,22,.72); color: #fff;
}
/* Счётчик и «−» под ним — одного размера, как задумано в --card-badge.
   Убрать позицию можно прямо из витрины, не открывая карточку заново. */
.product-card-minus {
  position: absolute; top: calc(10px + var(--card-badge) + 8px); right: 10px; z-index: 3;
  width: var(--card-badge); height: var(--card-badge);
  border-radius: 50%; border: 1px solid var(--border);
  background: var(--card); color: var(--text); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; line-height: 1; font-family: var(--font);
  box-shadow: 0 2px 8px rgba(46,26,11,.18);
}
.product-card-minus:active { transform: scale(0.94); }

/* Бейдж абсолютный — правится точечно и не двигает сетку (ТЗ §7.9). */
.product-card-badge {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  min-width: var(--card-badge); height: var(--card-badge);
  border-radius: 50%; background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 800;
}
/* Нажатие видно, даже когда элемент закрыт пальцем: масштаба одного мало,
   схлопывание тени читается по краям. */
.product-card:active { transform: scale(0.98); box-shadow: 0 1px 2px rgba(31,26,22,0.10); }
.cat-btn:active { transform: scale(0.96); }
.btn-primary:active, .keyboard-done:active { transform: scale(0.97); filter: brightness(0.95); }
.product-card.unavailable:active { transform: none; }

/* Круг из точки касания — единственная обратная связь на быстрый тап. Живёт в
   <body>, поэтому не зависит от overflow/transform карточек. pointer-events:
   none обязателен, иначе круг перехватит клик по товару под собой. */
.tap-ripple {
  position: fixed; z-index: 9999; pointer-events: none;
  width: 12px; height: 12px; margin: -6px 0 0 -6px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.85); background: rgba(255,255,255,0.18);
  animation: tap-ripple 520ms cubic-bezier(0.22,0.61,0.36,1) forwards;
}
@keyframes tap-ripple {
  from { transform: scale(1);  opacity: 0.85; }
  to   { transform: scale(11); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .tap-ripple { display: none; } }

/* Клон карточки, летящий в корзину. Кривая с лёгким перелётом в начале: клон
   «отбрасывается» от карточки и оседает в корзине, а не скользит. */
.fly-ghost {
  position: fixed; z-index: 90;
  background-size: cover; background-position: center;
  background-color: var(--accent-weak);
  border-radius: 20px; pointer-events: none; opacity: 0.95;
  transition: transform 560ms cubic-bezier(.5,-0.2,.3,1),
              opacity 560ms ease-in, border-radius 560ms ease-in;
  will-change: transform, opacity;
}
/* Пульсирует СУММА, а не вся панель: масштабировать полосу во всю ширину
   некуда, она уезжала бы за края экрана. */
@keyframes cart-bump {
  0% { transform: scale(1); } 30% { transform: scale(1.14); }
  55% { transform: scale(0.96); } 100% { transform: scale(1); }
}
.cart-bar-total.bump { animation: cart-bump 420ms cubic-bezier(.34,1.56,.64,1); display: inline-block; }

/* ------------------------------------------------------ панель заказа --- */
.cart-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; align-items: center; gap: 16px;
  padding: 16px 22px calc(16px + env(safe-area-inset-bottom));
  /* Тёмная, а не песочная: на светлом фоне витрины песочная полоса сливалась,
     и корзину справа было не видно. */
  background: #3A1D06; color: #F6EEE2; cursor: pointer;
  box-shadow: 0 -8px 28px rgba(46,26,11,.28);
  transform: translateY(0); transition: transform 320ms var(--ease-snap);
}
.cart-bar-info { flex: 1; }
.cart-bar-line { display: none; }   /* «приготовим за N мин» убрано */
.cart-bar-total { font-size: 27px; font-weight: 700; }
.cart-bar-next {
  position: relative; width: 62px; height: 62px; border-radius: 50%;
  border: none; background: var(--accent); color: #3A1D06; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.cart-bar-cart { width: 30px; height: 30px; }
.cart-bar-count {
  position: absolute; top: -4px; right: -4px;
  min-width: 26px; height: 26px; padding: 0 6px; border-radius: 13px;
  background: #F6EEE2; color: #3A1D06;
  font-size: 14px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}

/* ------------------------------------------ карточка товара: жест и стекло --- */
.modal { position: fixed; inset: 0; z-index: 30; height: var(--app-h, 100dvh);
         opacity: 0; transition: opacity 320ms ease; }
.modal.open { opacity: 1; }

/* Сдвинутая пальцем карточка скругляется и отрывается тенью от меню под ней.
   Радиус дублируется на .modal-bg намеренно: браузер не обрезает по
   border-radius содержимое элемента, к которому применён transform. */
.modal.swiping, .modal.swiping .modal-bg { border-radius: 28px; overflow: hidden; }
.modal.swiping { box-shadow: 0 30px 60px rgba(0,0,0,0.45); }

/* Пока карточку тащат, выключаем backdrop-filter у всего стеклянного, что едет
   вместе с ней: размытие пересэмплируется каждый кадр, и несколько таких слоёв
   разом — главная цена жеста на планшете. */
/* .animating — то же самое на время входа и выхода карточки: пока анимируется
   opacity модалки, каждый её кадр заставляет пересчитать подложки всех
   стеклянных слоёв внутри. Класс снимается по окончании перехода. */
.modal.swiping .modal-close,
.modal.swiping .product-nutrition,
.modal.swiping .modal-footer,
.modal.swiping .size-chip,
.modal.swiping .modifier-tile,
.modal.animating .modal-close,
.modal.animating .product-head,
.modal.animating .product-desc,
.modal.animating .product-nutrition,
.modal.animating .modal-footer,
.modal.animating .size-chip,
.modal.animating .rail-chip,
.modal.animating .modifier-tile {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* Свайпы по фото разбираем сами. Прокручивать здесь нечего, поэтому жест
   браузеру отдавать не надо — иначе он успевает начать свой оттяг страницы. */
.modal-bg {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; overflow: hidden;
  background: var(--gradient);
  touch-action: none;
}
/* Затемнение под названием. Раньше читаемость держала только тень у текста, и
   этого хватало, пока все фото были сняты на белом. Сюжетные рендеры приходят
   со своим фоном: на тёмно-бордовом мокко название читается, на светло-бежевом
   айс латте белым по светлому — почти нет. Плашка тут не годится (её убирали
   намеренно), а градиент в верхнюю треть работает на любом кадре и на самой
   съёмке не виден: сверху у всех сцен пустой фон. */
.modal-bg::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 32%;
  background: linear-gradient(rgba(31,26,22,.55), rgba(31,26,22,0));
  pointer-events: none; z-index: 2;
}
.modal-bg-video, .modal-bg-image { position: absolute; inset: 0; width: 100%; height: 100%; }
.modal-bg-video { object-fit: cover; }
.modal-bg-image { background-size: cover; background-position: center; }
.modal-close {
  position: absolute; top: calc(env(safe-area-inset-top, 0px) + 16px); left: 16px; z-index: 12;
  width: 56px; height: 56px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--glass-border); background: var(--glass-bg-strong); color: var(--text);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  font-size: 24px; font-family: var(--font);
}
.product-top {
  position: absolute; z-index: 10; left: 0; right: 0;
  top: calc(env(safe-area-inset-top, 0px) + 84px); padding: 0 24px;
  text-align: center;
  pointer-events: none;
}
.product-top > * { pointer-events: auto; }
/* Без плашки: фото само по себе кадрировано так, что за названием остаётся
   ровный фон, а плашка спорила с ним прямоугольником. Читаемость держит тень. */
.product-head { display: block; }
/* Белым и без плашки — как просили. Фото сняты на белом, поэтому читаемость
   держит не подложка, а плотная тень: одиночная мягкая на светлом кадре
   растворялась. */
.product-head h2 {
  margin: 0; font-size: clamp(16px, 2.7vw, 21px); font-weight: 600; line-height: 1.25;
  font-family: var(--font-display); color: #fff;
  text-shadow: 0 1px 2px rgba(46,26,11,.75), 0 2px 10px rgba(46,26,11,.65),
               0 4px 22px rgba(46,26,11,.45);
}
.product-nutrition { margin-top: 5px; font-size: 13px; color: rgba(255,255,255,.9); font-weight: 500; text-shadow: 0 1px 8px rgba(46,26,11,.5); }
.product-details-toggle {
  margin-top: 10px; border: none; background: none; cursor: pointer;
  color: #fff; font-size: 16px; font-weight: 700; font-family: var(--font);
  text-shadow: 0 1px 10px rgba(0,0,0,.5);
}
.product-desc {
  margin: 8px 0 0; max-width: 46ch; padding: 12px 14px; border-radius: 16px;
  background: var(--glass-bg-strong); font-size: 16px; line-height: 1.4;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
}
/* Фото сняты на белом, и белый текст с тенью на них исчезал. Цена садится на
   ту же плашку, что и название. */
.product-base-price {
  display: inline-block; margin-top: 10px; padding: 6px 14px; border-radius: 14px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  font-size: 22px; font-weight: 700; color: var(--text);
}
.product-details-chevron { display: inline-block; transition: transform 220ms ease; }
.product-details-toggle.open .product-details-chevron { transform: rotate(180deg); }

.modal-card { position: absolute; left: 0; right: 0; bottom: 0; z-index: 11; }
.modal-footer {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  background: var(--glass-bg-strong); border-top: 1px solid var(--glass-border);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
}
.product-weight { font-size: 15px; color: var(--hint); font-weight: 600; min-width: 62px; }
.stepper { display: flex; align-items: center; gap: 6px; }
.stepper-btn {
  width: 52px; height: 52px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--border); background: var(--card); color: var(--text);
  font-size: 26px; font-family: var(--font);
}
.stepper-value { min-width: 38px; text-align: center; font-size: 22px; font-weight: 800; }
.btn-primary {
  border: none; border-radius: 18px; cursor: pointer; font-family: var(--font);
  background: var(--accent); color: var(--on-accent); font-weight: 800;
}
.btn-primary[disabled] { opacity: .45; }
.btn-big { flex: 1; min-height: 64px; font-size: 20px; padding: 0 18px; }
.btn-block { width: 100%; }

/* ---------------------------------------- ряд чипсов и лист модификаторов --- */
.size-row, .modifier-rail {
  flex: 0 0 auto;
  display: flex; flex-direction: row; gap: 8px;
  overflow-x: auto; overflow-y: hidden;
  padding: 10px 16px 8px;
}
.size-chip {
  flex: 0 0 auto; padding: 12px 20px; border-radius: 16px; cursor: pointer;
  min-height: 60px; font-size: 17px; font-weight: 700; font-family: var(--font);
  background: rgba(46,26,11,0.62); color: #fff;
  border: 1px solid rgba(255,255,255,0.22);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
}
.size-chip.chosen { background: var(--selected-bg); border-color: var(--selected-bg); color: var(--selected-fg); }
.size-chip.unavailable { opacity: .45; }
.rail-chip {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  width: 86px; min-height: 84px; flex: 0 0 auto; padding: 10px 8px;
  border-radius: 18px; cursor: pointer; text-align: center;
  background: rgba(46,26,11,0.62);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  border: 1px solid rgba(255,255,255,0.22); color: #fff;
  font-size: 13px; font-weight: 700; font-family: var(--font); line-height: 1.15;
}
.rail-chip.chosen { background: var(--selected-bg); border-color: var(--selected-bg); color: var(--selected-fg); }
.rail-chip.open   { border-color: var(--accent-2); border-width: 1.5px; }
.rail-chip-required { font-size: 11px; opacity: .75; }
.rail-chip:active, .size-chip:active:not(.unavailable), .modifier-tile:active { transform: scale(0.97); }
/* Одинаковый темп у чипсов размера, группы и плиток опций: весь экран
   подсвечивается одним языком движения. */
.rail-chip, .size-chip, .modifier-tile {
  transition: background-color 160ms ease, border-color 160ms ease,
              color 160ms ease, transform 90ms ease;
}

.modifier-sheet {
  flex: 0 0 auto;
  /* Высота гонится через height, а НЕ только через max-height: max-height умеет
     показывать РОСТ, но на сжатии бесполезен — новая, более низкая сетка встаёт
     мгновенно, и ограничивать нечего, лист схлопывался рывком. */
  height: 0; max-height: 0; transform: translateY(-10px);
  overflow: hidden; padding: 0 16px;
  /* Мягкий край вместо прямого среза. Разбежка ровно 6px — рампа на 34px
     гасила треть плитки, и ряд выезжал сначала прозрачным. */
  -webkit-mask-image: linear-gradient(to bottom,
      transparent 0, #000 4px, #000 calc(100% - 6px), transparent 100%);
  mask-image: linear-gradient(to bottom,
      transparent 0, #000 4px, #000 calc(100% - 6px), transparent 100%);
  /* Симметричная ease-in-out, одинаковая в обе стороны. Предыдущая ease-OUT
     проходила 58% пути за первые 100мс — закрытие читалось как «исчезло». */
  transition: height 400ms cubic-bezier(0.65, 0, 0.35, 1),
              max-height 400ms cubic-bezier(0.65, 0, 0.35, 1),
              padding 400ms cubic-bezier(0.65, 0, 0.35, 1),
              transform 400ms cubic-bezier(0.65, 0, 0.35, 1);
}
.modifier-sheet.sheet-open {
  /* --sheet-h — измеренная высота сетки, её пишет и перемеряет app.js. Обычные
     px: min() выразил бы то же, но браузеры не интерполируют его надёжно. */
  height: var(--sheet-h, 46vh); max-height: var(--sheet-h, 46vh);
  transform: none; padding: 10px 16px 2px; overflow-y: auto;
}
/* Осел = раскрыт полностью и всё поместилось, значит ничего не обрезается и
   маска только гасила бы реальный, нажимаемый последний ряд. */
.modifier-sheet.sheet-settled { -webkit-mask-image: none; mask-image: none; }
.modifier-sheet.hidden { display: none; }
@media (prefers-reduced-motion: reduce) {
  .modifier-sheet, .modifier-tile, .rail-chip, .size-chip { transition: none; }
}
.modifier-sheet-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.modifier-tile {
  position: relative; min-height: 92px; padding: 12px 10px; border-radius: 16px;
  cursor: pointer; text-align: center; font-family: var(--font);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  background: rgba(46,26,11,0.66); color: #fff;
  border: 1px solid rgba(255,255,255,0.22);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  font-size: 14px; font-weight: 700; line-height: 1.2;
}
.modifier-tile.chosen { background: var(--selected-bg); border-color: var(--selected-bg); color: var(--selected-fg); }
.modifier-tile.unavailable { opacity: .42; }
.modifier-tile-price { font-size: 12px; font-weight: 600; opacity: .85; }
.modifier-tile-count {
  position: absolute; top: 6px; right: 8px;
  font-size: 12px; font-weight: 800; color: var(--selected-icon);
}

/* --------------------------------------------------------- окно заказа --- */
/* Окно выезжает СВЕРХУ поверх размытого меню, прижато к верху и ограничено по
   высоте: размытая подложка читается по краям и внизу — из неё и понятно, что
   заказ лёг ПОВЕРХ меню, а не заменил его. */
.sheet {
  position: fixed; top: 0; left: 0; right: 0; z-index: 30;
  max-height: 94vh;
  background: var(--bg); border-radius: 0 0 32px 32px;
  padding: calc(env(safe-area-inset-top, 0px) + 22px) 24px calc(22px + env(safe-area-inset-bottom));
  overflow-y: auto; overscroll-behavior: contain;
  box-shadow: 0 18px 48px rgba(31,26,22,0.22);
  transform: translateY(-100%);
  transition: transform 420ms cubic-bezier(0.65, 0, 0.35, 1);
}
.sheet.open { transform: none; }
.sheet-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.sheet-header h2 { margin: 0; font-size: 28px; font-weight: 800;   font-family: var(--font-display);
}
.sheet .modal-close { position: static; width: 48px; height: 48px; }
.checkout-scrim {
  position: fixed; inset: 0; z-index: 29;
  background: rgba(31,26,22,0.28);
  opacity: 0; transition: opacity 420ms ease;
}
.checkout-scrim.open { opacity: 1; }

/* Области меню размываются ПОИМЁННО, а не через #app: окно заказа лежит внутри
   того же #app, и фильтр на общем предке размыл бы и его самого.
   Ставится СКАЧКОМ, без transition: анимированный blur(0→10px) на весь экран —
   420мс полноэкранного пересчёта размытия на каждое открытие и закрытие. */
body.checkout-open .panel,
body.checkout-open .cart-bar { filter: blur(10px); }
@media (prefers-reduced-motion: reduce) {
  .sheet, .checkout-scrim, .panel, .cart-bar { transition: none; }
}

.cart-items { display: flex; flex-direction: column; gap: 10px; }
.cart-item-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px; border-radius: 18px; background: var(--card); box-shadow: var(--card-shadow);
}
.cart-item-img { width: 64px; height: 64px; border-radius: 14px; object-fit: cover; background: var(--surface-2); -webkit-user-drag: none; }
.cart-item-info { flex: 1; min-width: 0; }
.cart-item-name { font-size: 16px; font-weight: 400; }
.cart-item-mods { font-size: 13px; color: var(--hint); margin-top: 2px; }
.cart-item-total { font-size: 17px; font-weight: 800; min-width: 92px; text-align: right; }
.cart-empty { padding: 28px 0; text-align: center; color: var(--hint); font-size: 18px; }
.cart-nutrition { margin-top: 12px; font-size: 14px; color: var(--hint); font-weight: 600; }
.cart-upsell { margin-top: 18px; }
.cart-upsell-title { font-size: 14px; font-weight: 700; color: var(--hint); text-transform: lowercase; margin-bottom: 8px; }
.cart-upsell-rail { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.upsell-card { flex: 0 0 auto; width: 132px; cursor: pointer; border-radius: 16px; overflow: hidden; background: var(--card); box-shadow: var(--card-shadow); }
.upsell-card img { width: 100%; aspect-ratio: 1/1; object-fit: cover; display: block; -webkit-user-drag: none; }
.upsell-card-body { padding: 8px 10px 10px; font-size: 13px; font-weight: 700; }
.upsell-card-price { color: var(--accent-ink); font-weight: 700; margin-top: 2px; }

.checkout-form { margin: 18px 0; display: flex; flex-direction: column; gap: 14px; }
.checkout-form label { display: block; font-size: 15px; font-weight: 700; color: var(--hint); }
.checkout-form input {
  width: 100%; margin-top: 8px; padding: 18px 16px; border-radius: 16px;
  border: 1px solid var(--border); background: var(--card); color: var(--text);
  font-size: 19px; font-family: var(--font); font-weight: 600;
}
.checkout-form input.input-focused { border-color: var(--accent-2); border-width: 1.5px; }
.field-required { color: var(--danger); font-size: 12px; font-weight: 700; }
.field-hint { display: block; margin-top: 6px; color: var(--danger); font-size: 13px; font-weight: 600; }

/* Подсказка имён. Кнопки крупные: это полноценная альтернатива клавиатуре,
   а не украшение — многие гости имя вводить не хотят вовсе. */
.name-suggest { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.name-suggest-btn {
  border: 1px solid var(--border); background: var(--card); color: var(--text);
  border-radius: 16px; padding: 12px 18px; min-height: 48px; cursor: pointer;
  font-family: var(--font); font-size: 16px; font-weight: 500;
}
.name-suggest-btn:active { transform: scale(0.97); }
.name-suggest-btn.chosen {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.pay-note { margin: 12px 0 0; text-align: center; color: var(--hint); font-size: 14px; }

.scrim { position: fixed; inset: 0; z-index: 28; background: rgba(31,26,22,.45); }

/* --------------------------------------------------- экран «заказ принят» --- */
.fullscreen { position: fixed; inset: 0; z-index: 40; background: var(--bg);
              display: flex; align-items: center; justify-content: center; }
.fullscreen#confirm-screen { background: transparent; display: block; overflow: hidden; }

/* Шаг 1. Круг из центра разрастается на весь экран; радиус с запасом (150vmax),
   чтобы накрыть углы любого экрана. Кривая подобрана ЗАМЕРОМ: easeOutExpo
   проходила 65% радиуса за первые 100мс и читалась как мгновенная заливка. */
.confirm-flood {
  position: absolute; inset: 0;
  background: var(--gradient);
  clip-path: circle(0% at 50% 42%); opacity: 0;
}
#confirm-screen.play .confirm-flood { animation: confirm-flood 1200ms cubic-bezier(0.4,0,0.2,1) forwards; }
@keyframes confirm-flood {
  0%   { clip-path: circle(0% at 50% 42%);      opacity: 0.55; }
  18%  { opacity: 1; }
  100% { clip-path: circle(150vmax at 50% 42%); opacity: 1; }
}

/* Шаг 2. Заголовок появляется МЕДЛЕННО (1.1s) и с небольшим подъёмом. */
.confirm-title {
  position: absolute; left: 0; right: 0; top: 14vh; margin: 0; padding: 0 24px;
  text-align: center; color: #fff;
  font-size: clamp(40px, 9vw, 84px); font-weight: 800; line-height: 1.05;
  opacity: 0; transform: translateY(28px);
  font-family: var(--font-display);
}
#confirm-screen.play .confirm-title { animation: confirm-rise 1100ms cubic-bezier(0.22,0.8,0.3,1) 1000ms forwards; }

/* Шаг 3. Белый знак бренда выезжает снизу — силуэт юрты. Маской, а не
   clip-path: форма из логотипа сложнее треугольника. */
/* Пропорции держим по самому SVG (64×64) и вписываем целиком: при обрезке
   снизу шанырак уходил за край экрана, и от юрты оставался белый прямоугольник
   со скруглёнными углами — знак переставал читаться. */
/* Логотип целиком, в своих цветах: белый силуэт был перерисовкой, а купол
   у бренда светлый и сам по себе контрастен к тёмной заливке. */
.confirm-mark {
  position: absolute; left: 50%; bottom: 12%;
  width: min(42vw, 340px); aspect-ratio: 344 / 295;
  margin-left: calc(min(42vw, 340px) / -2);
  background: url("/static/brand-yurt.webp") center / contain no-repeat;
  filter: drop-shadow(0 8px 32px rgba(0,0,0,.32));
  transform: translateY(100%);
}
#confirm-screen.play .confirm-mark { animation: confirm-mark-up 800ms cubic-bezier(0.22,0.8,0.3,1) 2000ms forwards; }
@keyframes confirm-mark-up { to { transform: translateY(0); } }

/* Шаг 4. Благодарность ВНУТРИ знака, в нижней его трети — выше вершина
   сходится в точку, и текст туда не поместится. */
.confirm-thanks {
  position: absolute; left: 0; right: 0; bottom: 6vh; margin: 0; padding: 0 24px;
  text-align: center; color: #fff;
  font-size: clamp(22px, 4.2vw, 38px); font-weight: 600; line-height: 1.2;
  text-shadow: 0 2px 16px rgba(46,26,11,.5);
  opacity: 0; transform: translateY(18px);
}
#confirm-screen.play .confirm-thanks { animation: confirm-rise 800ms cubic-bezier(0.22,0.8,0.3,1) 2700ms forwards; }
@keyframes confirm-rise { to { opacity: 1; transform: translateY(0); } }

/* Полоска автосброса — тонкая, поверх всего. Ширину гонит app.js. */
.confirm-progress { position: absolute; left: 0; right: 0; top: 0; z-index: 2;
                    height: 5px; background: rgba(255,255,255,0.25); overflow: hidden; }
.confirm-progress i { display: block; height: 100%; width: 0; background: #fff; }

@media (prefers-reduced-motion: reduce) {
  #confirm-screen.play .confirm-flood, #confirm-screen.play .confirm-title,
  #confirm-screen.play .confirm-mark,  #confirm-screen.play .confirm-thanks {
    animation-duration: 1ms; animation-delay: 0ms;
  }
}

/* ------------------------------------------------------------ заставка --- */
/* Два слоя один поверх другого. Живой имеет opacity 1, второй грузит следующий
   сюжет и ждёт своей очереди на нуле. */
.attract-slot { position: absolute; inset: 0; opacity: 0; transition: opacity 500ms ease; }
.attract-slot.is-live { opacity: 1; }
.attract-video, .attract-photo, .attract-fallback {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  /* Явно по центру: ролики вертикальные, экран другой пропорции, и cover
     срезает лишнее — срезать он обязан симметрично, иначе кадр уезжает. */
  object-position: center center;
  -webkit-user-drag: none;
}
@media (prefers-reduced-motion: reduce) { .attract-slot { transition: none; } }
/* Заглушка на случай, если ни одного ролика нет: фирменный градиент, не белое
   пятно и не битая иконка. */
.attract-fallback { background: var(--gradient); display: flex;
                    align-items: center; justify-content: center; }

/* Аффорданс «свайпни вверх»: настоящая <button> ради нажатия мышью, но
   визуально — просто стрелка с бесконечным покачиванием. */
.attract-hint { display: flex; align-items: center; justify-content: center;
                border: none; background: none; padding: 18px; cursor: pointer;
                min-width: 64px; min-height: 64px; }
.attract-hint-arrow {
  width: 44px; height: 44px;
  background: #fff;
  -webkit-mask: var(--chevron-up) center / contain no-repeat;
  mask: var(--chevron-up) center / contain no-repeat;
  animation: attract-bounce 1.6s ease-in-out infinite;
  filter: drop-shadow(0 2px 12px rgba(0,0,0,.35));
}
@keyframes attract-bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
/* Стрелка качается бесконечно, и при открытом меню она невидима (opacity 0),
   но анимация продолжала идти: планшет из-за неё не уходит в простой между
   кадрами, а композитор держит лишний слой. Класс вешает app.js по состоянию
   заставки. */
body.menu-open .attract-hint-arrow { animation-play-state: paused; }

/* -------------------------------------------------- экранная клавиатура --- */
/* Лежит ВНЕ окна заказа, чтобы вставать поверх него и не обрезаться. */
.keyboard-panel {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: var(--surface-2); border-top: 1px solid var(--border);
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  box-shadow: 0 -12px 36px rgba(31,26,22,.20);
}
.keyboard-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 6px 10px; }
.keyboard-label { font-size: 16px; font-weight: 700; color: var(--hint); }
.keyboard-done {
  border: none; border-radius: 14px; cursor: pointer; font-family: var(--font);
  background: var(--accent); color: var(--on-accent);
  font-size: 17px; font-weight: 800; padding: 12px 26px; min-height: 52px;
}
/* Клавиши крупные: палец, а не курсор. */
.simple-keyboard.hg-theme-default .hg-button {
  height: 62px; font-size: 22px; font-family: var(--font); font-weight: 600;
  border-radius: 12px;
}
.simple-keyboard.hg-theme-default { background: transparent; padding: 0; }
.hg-theme-numeric .hg-button { max-width: 33%; }

/* ------------------------------------------------------- плашка ошибки --- */
/* Системный alert() в standalone-PWA показывает диалог с адресом сайта и
   блокирует главный поток. Гостю у стойки нужен текст ошибки, а не это. */
.toast {
  position: fixed; left: 50%; bottom: 40px; z-index: 70;
  transform: translate(-50%, 20px);
  max-width: min(560px, calc(100vw - 40px));
  padding: 18px 24px; border-radius: 20px;
  background: var(--text); color: var(--bg);
  font-size: 18px; font-weight: 700; line-height: 1.35; text-align: center;
  box-shadow: 0 12px 40px rgba(31,26,22,.35);
  opacity: 0; pointer-events: none;
  transition: opacity 240ms ease, transform 240ms var(--ease-snap);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .toast { transition: none; } }
