/* Kassir — витрина.
   Язык интерфейса — вариант «Свет в зале» (выбран 2026-09-15 из четырёх
   прототипов): тёмный бархат зала, светящаяся сцена над схемой, янтарь как
   цвет ДЕЙСТВИЯ. Он заменил светлую палитру макета «Экраны Kassir».

   Главное правило макета осталось в силе: НАСЫЩЕННЫЙ ЦВЕТ НА СХЕМЕ ОТДАН
   ЦЕНОВЫМ КАТЕГОРИЯМ, статус места цветом не кодируется вовсе — он кодируется
   формой (см. hall.js), чтобы занятое кресло читалось и без цвета. Янтарь
   прототипа, которым там светились выбранные кресла, здесь достался кнопкам,
   фокусу и полосам: на схеме он спорил бы с категориями. */

:root{
    /* Тема одна и тёмная. Переключателя темы в интерфейсе нет, а светлый
       вариант этого языка никто не рисовал: держать его «на всякий случай»
       значит держать непроверенную палитру. */
    color-scheme: dark;

    --paper:#15122A; --surface:#221D3F; --sunk:#1A1633;
    --ink:#F1EDFA; --muted:#B2A9CF; --rule:#3D3668;
    /* --glow — свет сцены, верх градиента над схемой. Всё, что лежит на
       схеме, проверено и на нём: --taken 3.76:1, --muted 5.85:1. */
    --glow:#33295E;
    --stage:#0E0B1E; --stage-ink:#F1EDFA;
    --stop:#FF8F85; --ok:#74D9AE; --warn:#FFC96B;
    /* Четыре слота ценовых категорий. Все >= 7:1 на --surface и >= 5.6:1 на
       --glow; номер места (--surface поверх заливки) — те же 7:1. */
    --cat1:#8FA8FF; --cat2:#5FC9C0; --cat3:#FFB547; --cat4:#F28DB8;
    /* --taken — ЕДИНСТВЕННОЕ, что отличает проданное и удержанное кресло от
       фона: статус кодируется контуром, а не заливкой, и ему положено 3:1
       (WCAG 1.4.11). 4.63:1 на --surface, 3.76:1 на --glow.
       --rule-strong — рамка интерактивного элемента: по ней элемент находят,
       ей тоже положено 3:1. --rule (1.6:1) остаётся разделителем. */
    --taken:#8C83BC; --rule-strong:#8C83BC;
    /* Цвет действия. --on-accent на нём — 10.28:1. */
    --accent:#FFB547; --accent-hover:#FFC873; --on-accent:#1E1500;

    --sans:"Onest",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
    --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}

*{ box-sizing:border-box; }
html,body{ height:100%; }

body{
    margin:0;
    background:var(--paper);
    color:var(--ink);
    font-family:var(--sans);
    font-size:16px;
    line-height:1.55;
    /* Табличные цифры на всём документе: ряды, места и суммы обязаны
       выстраиваться в колонки. */
    font-variant-numeric:tabular-nums;
    -webkit-font-smoothing:antialiased;
}

h1,h2,h3,p,ul,ol,li{ margin:0; padding:0; }
ul{ list-style:none; }
a{ color:inherit; }

:where(a,button,input,select,canvas,[tabindex]):focus-visible{
    outline:3px solid var(--accent);
    outline-offset:3px;
}

.skip{
    position:absolute; left:-9999px; top:0; z-index:50;
    padding:10px 14px; border-radius:12px;
    background:var(--accent); color:var(--on-accent);
    font-weight:700; text-decoration:none;
}
.skip:focus{ left:8px; top:8px; }

/* Текст только для скринридера. Схема — картинка, и состав зала приходится
   проговаривать словами, иначе он не существует. */
.sr{
    position:absolute; width:1px; height:1px;
    margin:-1px; padding:0; overflow:hidden;
    clip-path:inset(50%); white-space:nowrap; border:0;
}

.dot::before{
    content:""; display:inline-block;
    width:3px; height:3px; margin:0 8px;
    border-radius:50%; background:currentColor;
    vertical-align:3px; opacity:.45;
}

/* ---------- шапка ---------- */

/* Шапка — часть зала, а не полоса над ним: тот же фон, что у страницы. */
.top{
    display:flex; align-items:center; justify-content:space-between; gap:16px;
    height:64px; padding:0 24px;
    background:var(--paper); color:var(--stage-ink);
}

.brand{ display:inline-flex; align-items:center; gap:9px; text-decoration:none; }

/* Марка — перфорация билетного корешка. */
.brand__mark{
    width:15px; height:20px; border-radius:3px; color:var(--accent);
    background:
        radial-gradient(circle at 50% 0, transparent 2.5px, currentColor 2.6px) top / 100% 51% no-repeat,
        radial-gradient(circle at 50% 100%, transparent 2.5px, currentColor 2.6px) bottom / 100% 51% no-repeat;
}
.brand__word{ font-size:21px; font-weight:800; letter-spacing:-.02em; }

.back-link{
    font-size:14px; color:var(--muted); text-decoration:none;
    background:var(--surface); border:1px solid var(--rule);
    border-radius:999px; padding:7px 16px;
}
.back-link:hover{ color:var(--ink); border-color:var(--rule-strong); }

/* ---------- афиша ---------- */

.wrap{ max-width:1180px; margin:0 auto; padding:28px 24px 80px; }

.poster-title{ font-size:clamp(40px, 6vw, 64px); font-weight:800; line-height:1.02; letter-spacing:-.035em; text-wrap:balance; }
.poster-note{ margin-top:14px; max-width:58ch; color:var(--muted); font-size:17px; }

/* Афиша — строки программки, а не карточки: дата, что и где, цена. */
.stubs{ display:flex; flex-direction:column; margin-top:40px; border-bottom:1px solid var(--rule); }

.stub__link{
    display:grid; grid-template-columns:130px minmax(0,1fr) auto;
    align-items:center; gap:24px;
    padding:22px 0;
    border-top:1px solid var(--rule);
    text-decoration:none;
}
.stub__link:hover .stub__name{ color:var(--accent); }

.stub__date{ display:grid; grid-template-columns:auto 1fr; column-gap:6px; align-items:baseline; }
.stub__day, .stub__month{ font-size:24px; font-weight:700; line-height:1.1; }
.stub__weekday{ grid-column:1 / -1; font-size:14px; color:var(--muted); }

.stub__body{ min-width:0; }
.stub__name{ display:block; font-size:22px; font-weight:700; letter-spacing:-.01em; line-height:1.25; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.stub__where{ display:block; margin-top:3px; font-size:15px; color:var(--muted); }

.gauge{ display:block; height:4px; margin-top:12px; max-width:280px; border-radius:2px; background:var(--rule); overflow:hidden; }
.gauge__fill{ display:block; height:100%; width:var(--share); background:var(--accent); }

.stub__left{ display:block; margin-top:6px; font-size:13px; color:var(--muted); }

.stub__price{ display:flex; flex-direction:column; align-items:flex-end; gap:2px; text-align:right; }
.stub__from{ font-size:13px; color:var(--muted); }
.stub__sum{ font-size:22px; font-weight:800; white-space:nowrap; }
.stub__go{
    margin-top:8px; padding:8px 16px; border-radius:12px;
    background:var(--accent); color:var(--on-accent);
    font-size:14px; font-weight:700;
}
.stub__link:hover .stub__go{ background:var(--accent-hover); }

.badge{ display:inline-block; padding:2px 9px; border-radius:999px; font-size:12px; border:1px solid currentColor; }
.badge--wait{ color:var(--warn); margin-bottom:4px; }

.empty-state{
    margin-top:36px; padding:48px 24px; text-align:center;
    background:var(--surface); border-radius:20px;
}
.empty-state__line{ font-size:19px; font-weight:700; }
.empty-state__hint{ margin-top:6px; color:var(--muted); }

/* ---------- зал: панель заказа слева, схема справа ---------- */

.buyer{
    display:grid;
    grid-template-columns:360px minmax(0,1fr);
    gap:20px;
    height:calc(100dvh - 64px);
    padding:4px 20px 20px;
}

/* Схема лежит под светом сцены. Полоса света — чистое украшение, без
   подписи: у стадиона сцена не обязательно сверху, и слово «сцена» над
   любым залом было бы враньём про чужую планировку. */
.hall{
    position:relative;
    display:flex; flex-direction:column;
    padding:30px 18px 16px;
    border-radius:28px;
    background:radial-gradient(120% 60% at 50% -8%, var(--glow) 0%, var(--surface) 62%);
    min-width:0;
}
/* Свет рисуется градиентом, а не тенью: тень полосы уходила вверх за рамку
   зала и светилась поверх шапки. */
.hall::before{
    content:""; position:absolute; top:0; left:24%; right:24%; height:9px;
    border-radius:0 0 50% 50% / 0 0 100% 100%;
    background:linear-gradient(#FFE3B0, var(--accent));
    pointer-events:none;
}
.hall::after{
    content:""; position:absolute; top:0; left:12%; right:12%; height:110px;
    background:radial-gradient(closest-side at 50% 0, rgba(255,181,71,.24), transparent);
    pointer-events:none;
}

.hallbar{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:10px; }

.back{
    font:inherit; font-size:14px;
    background:var(--sunk); color:var(--ink);
    border:1px solid var(--rule-strong); border-radius:999px;
    padding:6px 14px; cursor:pointer;
}
.back:hover{ border-color:var(--ink); }
.back[hidden]{ display:none; }

.zoom{ display:inline-flex; gap:4px; }
.zoom[hidden]{ display:none; }
.zoom .back{ min-width:34px; text-align:center; }

.hint{ font-size:13px; color:var(--muted); margin-left:auto; text-align:right; }

/* Занятость несвежая или её нет вовсе. Полоса живёт над схемой и занимает
   место в потоке: всплывающая поверх зала подсказка закрыла бы кресла ровно
   там, куда человек смотрит. Цвет ДОПОЛНЯЕТ текст: сообщение читается и в
   оттенках серого. */
.freshness{
    margin:0 0 10px;
    padding:9px 14px;
    border:1px solid var(--warn);
    border-left-width:4px;
    border-radius:12px;
    background:var(--sunk);
    color:var(--ink);
    font-size:13.5px; line-height:1.4;
}
.freshness.is-none{ border-color:var(--stop); }
.freshness[hidden]{ display:none; }

.mapbox{ position:relative; flex:1; min-height:0; }

/* Холст прозрачный: свет сцены — фон .hall, и заливка холста его бы съела. */
#map{
    display:block; width:100%; height:100%;
    background:transparent;
    touch-action:none;
    cursor:pointer;
    border-radius:12px;
}
#map.is-dragging{ cursor:grabbing; }

.maploader{
    position:absolute; inset:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
    color:var(--muted); font-size:14px;
}
.maploader[hidden]{ display:none; }
.maploader__bar{ width:190px; height:4px; border-radius:2px; background:var(--rule); overflow:hidden; }
.maploader__bar > span{ display:block; height:100%; width:0; background:var(--accent); transition:width .2s ease; }

.tip{
    position:absolute; z-index:4;
    padding:8px 12px; border-radius:10px;
    background:var(--stage); color:var(--stage-ink);
    border:1px solid var(--rule);
    font-size:13px; line-height:1.35;
    pointer-events:none; white-space:nowrap;
}
.tip[hidden]{ display:none; }

.keys{ margin:8px 0 0; font-size:12.5px; color:var(--muted); text-align:center; }
.keys b{ color:var(--ink); font-weight:600; }

.legend{ display:flex; flex-wrap:wrap; justify-content:center; gap:14px 22px; margin-top:10px; font-size:13px; color:var(--muted); }
.legend span{ display:inline-flex; align-items:center; gap:7px; }

/* ---------- панель заказа ---------- */

.panel{
    display:flex; flex-direction:column; gap:14px;
    padding:6px 4px 20px;
    overflow-y:auto;
    min-width:0;
}

.ev{ font-size:17px; font-weight:700; line-height:1.3; }
/* Название события на экране зала — заголовок страницы, как в прототипе. */
.ev--hero{ font-size:clamp(32px, 3.4vw, 46px); font-weight:800; line-height:1.04; letter-spacing:-.035em; text-wrap:balance; margin-bottom:10px; }
.evmeta{ font-size:14px; color:var(--muted); }
.evmeta--when{ font-size:17px; font-weight:500; color:var(--ink); }

/* Корзина — отдельный бархатный блок: всё, что относится к брони, собрано
   в одном месте. */
.basket{
    display:flex; flex-direction:column; gap:12px;
    background:var(--surface); border-radius:20px; padding:18px;
}

.timer{
    display:flex; align-items:center; justify-content:space-between; gap:8px;
    border:1px solid var(--rule); border-radius:14px;
    padding:9px 12px; background:var(--sunk);
}
.timer[hidden]{ display:none; }
.timer .t{ font-size:22px; font-weight:800; }
.timer small{ display:block; font-size:12px; color:var(--muted); line-height:1.3; }
.timer.low{ border-color:var(--stop); }
.timer.low .t{ color:var(--stop); }

.picked{ display:flex; flex-direction:column; gap:1px; }
/* Пустой список не занимает места: под ним и так стоит «Места не выбраны». */
.picked:empty{ display:none; }
.picked li{
    display:grid; grid-template-columns:10px 1fr auto 26px;
    gap:9px; align-items:center;
    padding:8px 0; border-bottom:1px solid var(--rule);
    font-size:14px;
}
.picked li.is-lost{ color:var(--stop); }
.swatch{ width:10px; height:10px; border-radius:50%; }
.picked .price{ font-weight:700; white-space:nowrap; }
.picked button{
    background:none; border:0; color:var(--muted); cursor:pointer;
    font-size:16px; line-height:1; padding:3px 5px; border-radius:8px;
}
.picked button:hover{ color:var(--stop); }

.empty-pick{ color:var(--muted); font-size:14px; padding:10px 0; }
.empty-pick[hidden]{ display:none; }

.total{
    display:flex; justify-content:space-between; align-items:baseline;
    padding-top:4px; font-size:16px;
}
.total b{ font-size:30px; font-weight:800; letter-spacing:-.01em; }

.cta{
    font:inherit; font-weight:700; font-size:16px;
    padding:14px; border:0; border-radius:14px;
    background:var(--accent); color:var(--on-accent); cursor:pointer; width:100%;
}
.cta:hover{ background:var(--accent-hover); }
.cta:disabled{ background:var(--rule); color:var(--muted); cursor:not-allowed; }

.acts{ display:flex; gap:8px; }
/* Без этой строки [hidden] проигрывает display:flex по специфичности, и
   «Отказаться от брони» висит на экране, когда никакой брони нет. */
.acts[hidden]{ display:none; }
.acts .back{ flex:1; text-align:center; }

.a11y{ font-size:13px; color:var(--muted); background:var(--surface); border-radius:20px; padding:14px 18px; }
.a11y button{
    font:inherit; font-size:13.5px;
    background:var(--sunk); color:var(--ink);
    border:1px solid var(--rule-strong); border-radius:999px;
    padding:6px 14px; cursor:pointer; margin-top:8px;
}
.a11y button:hover{ border-color:var(--ink); }
/* Подбор выключается вместе с бронированием, когда занятости нет: кнопка,
   которая на вид работает, а по нажатию отвечает отказом, хуже выключенной. */
.a11y button:disabled{ color:var(--muted); border-color:var(--rule); cursor:not-allowed; opacity:.6; }
.a11y input{
    width:58px; padding:5px 9px; margin-top:8px;
    border:1px solid var(--rule-strong); border-radius:10px;
    background:var(--sunk); color:var(--ink); font:inherit; font-size:13.5px;
}

.say{ font-size:14px; }
.say:empty{ display:none; }
.say.is-bad{ color:var(--stop); }
.say.is-ok{ color:var(--ok); }
.say.is-warn{ color:var(--warn); }

.ga{ background:var(--surface); border-radius:20px; padding:14px 18px; }
.ga__row{ display:flex; justify-content:space-between; gap:10px; font-size:14px; }
.ga__name{ font-weight:600; }
.ga__note{ margin-top:5px; font-size:12.5px; color:var(--muted); }

/* ---------- оформление, возврат из оплаты, заказ ---------- */

.sheet{ max-width:700px; margin:0 auto; padding:28px 24px 80px; }
.sheet h1{ font-size:clamp(30px, 4vw, 42px); font-weight:800; line-height:1.08; letter-spacing:-.03em; text-wrap:balance; }

.card{
    margin-top:18px; padding:18px 20px;
    background:var(--surface); border-radius:20px;
    font-size:15px;
}
.card p + p{ margin-top:8px; }
/* Толстая грань слева ДОПОЛНЯЕТ текст, а не заменяет его: сообщение обязано
   читаться и в оттенках серого. Тот же приём, что у .freshness. */
.card--warn{ border-left:4px solid var(--warn); }
.card--stop{ border-left:4px solid var(--stop); }
.card--ok{ border-left:4px solid var(--ok); }

.card .ev{ margin-top:6px; }
.card .timer{ margin-top:12px; }
.card .total{ margin-top:12px; }

/* Состав брони — тот же список, что в панели зала, но без кнопки «убрать»:
   после захвата место убирают отказом от брони, а не крестиком. */
.picked--plain li{ grid-template-columns:10px 1fr auto; }

.field{ display:block; margin-top:16px; }
.field__label{ display:block; font-size:14px; font-weight:600; }
.field input{
    width:100%; margin-top:6px; padding:12px 14px;
    font:inherit; background:var(--sunk); color:var(--ink);
    border:1px solid var(--rule-strong); border-radius:12px;
}
.field input[aria-invalid="true"]{ border-color:var(--stop); }
.field__hint{ margin-top:5px; font-size:13px; color:var(--muted); }
.field__err{ margin-top:6px; font-size:13.5px; color:var(--stop); }
.field__err[hidden]{ display:none; }

/* Согласие — фраза над кнопкой, а не галочка: причина в шаблоне оформления. */
.consent-note{ margin-top:16px; }

.co .cta{ margin-top:18px; }
/* Ссылка, выглядящая кнопкой. Без собственного [hidden] она остаётся видимой:
   display из класса бьёт [hidden] браузера — тот же урок, что у .acts. */
.cta--go{ display:block; text-align:center; text-decoration:none; }
.cta--go[hidden]{ display:none; }

.linkish{ display:inline-block; margin-top:12px; font-size:14px; color:var(--accent); }
/* Ссылка на заказ печатается целиком — её переписывают глазами и пересылают
   в мессенджер. Значит она обязана переноситься где угодно и не растягивать
   страницу в горизонтальную прокрутку на телефоне. */
.linkish--url{ font-family:var(--mono); font-size:13px; overflow-wrap:anywhere; }

/* Отсчёт на экране возврата — самостоятельный блок, а не хвост карточки над
   ним: без отступа он слипается с ней и читается как её часть. */
.ret > .timer{ margin-top:16px; }
.ret .acts{ flex-wrap:wrap; margin-top:16px; align-items:center; }
.ret .acts .cta{ width:auto; flex:none; padding:12px 20px; }
.ret .acts .back{ flex:none; }
/* Ссылка среди кнопок: .back красит рамку и отступы, но выравнивание текста
   по центру и снятое подчёркивание есть только у кнопки-элемента. */
.acts a.back{ text-decoration:none; line-height:1.55; }

.tickets{ display:flex; flex-direction:column; gap:12px; margin-top:18px; }
.ticket{
    display:grid; grid-template-columns:1fr auto; gap:4px 14px;
    padding:15px 18px; background:var(--surface);
    border:1px solid var(--rule); border-radius:20px;
}
.ticket__code{
    grid-column:1 / -1;
    font-family:var(--mono); font-size:18px; font-weight:600; letter-spacing:.04em;
    color:var(--accent);
}
/* Подпись «кода нет» набором самого кода читалась бы как код: моноширинный
   крупный кегль — это документ, а не объяснение его отсутствия. */
.ticket__code--none{
    font-family:var(--sans); font-size:14px; font-weight:400;
    letter-spacing:0; color:var(--muted);
}
.ticket__where{ font-size:14px; }
.ticket__price{ font-weight:700; white-space:nowrap; }
.ticket__note{ grid-column:1 / -1; font-size:13px; color:var(--stop); }
/* Возвращённый билет отличается ФОРМОЙ — пунктир и зачёркнутый код, — а не
   только цветом: недействительный документ обязан читаться без цвета. */
.ticket--revoked{ border-style:dashed; border-color:var(--rule-strong); color:var(--muted); background:transparent; }
.ticket--revoked .ticket__code{ text-decoration:line-through; color:var(--muted); }

.badge--ok{ color:var(--ok); }
.badge--stop{ color:var(--stop); }

/* Блок возврата. Не новая карточка-виджет, а продолжение той же .card:
   та же поверхность, то же поле, отделён линейкой. */
.refund{ border-top:1px solid var(--rule); margin-top:18px; padding-top:16px; }
.refund h2{
    font:700 17px/1.3 var(--sans);
    color:var(--ink);
    margin:0 0 8px;
}
.refund p{ margin:0 0 10px; color:var(--muted); font-size:14px; line-height:1.55; }

/* Лестница удержания — единственное место, где экран вводит новую форму. Это
   не украшение: покупатель решает по ней, вернуть сейчас или позже, и
   проценты обязаны быть видны все сразу. */
.ladder{ list-style:none; margin:0 0 14px; padding:0; }
.ladder li{
    display:grid;
    grid-template-columns:1fr auto;
    gap:12px;
    align-items:baseline;
    padding:7px 0;
    border-bottom:1px dashed var(--rule);
    font-size:14px;
    color:var(--muted);
}
.ladder li:last-child{ border-bottom:0; }
.ladder .now{ color:var(--ink); font-weight:600; }
.ladder .now b{ color:var(--ok); }
.ladder .share{ font:600 14px/1 var(--mono); color:var(--muted); }
.ladder .now .share{ color:var(--ok); }
/* Ячейки обязаны сжиматься: без min-width:0 длинная строка задаёт минимальную
   ширину и распирает карточку за экран на узком. */
.ladder li > span{ min-width:0; }

.refund .sum{
    display:flex; justify-content:space-between; align-items:baseline;
    gap:12px; margin:0 0 14px;
    font-size:15px; color:var(--ink);
}
.refund .sum b{ font-size:22px; font-weight:800; }

.refund .back{ width:100%; margin-top:8px; }
.refund .fine{ font-size:13px; color:var(--muted); margin:10px 0 0; }
/* Первый шаг ушёл со кнопки на ссылку (переход на ?refund=confirm остаётся
   обычной GET-навигацией без JS): анимация браузера подчёркивает текст ссылки
   по умолчанию, а без display:block её width:100% выше не подействует. */
.refund a.back{ display:block; text-align:center; text-decoration:none; }

/* Возврат — действие деструктивное и редкое. Акцентная заливка здесь спорила
   бы по весу с «Купить» и приглашала бы нажать раньше времени. Заливка
   остаётся только у подтверждения — второго шага, а не у первого клика. */
.refund .back--main{ padding:13px 18px; font-size:15px; font-weight:600; }

/* Подтверждение: возврат необратим, и второй шаг — не вежливость, а
   единственная защита от случайного нажатия на странице, которую открывают
   ради билетов. */
.confirm{ background:var(--sunk); border:1px solid var(--rule); border-radius:12px; padding:14px; }
.confirm h3{ font:700 15px/1.3 var(--sans); margin:0 0 8px; color:var(--ink); }
.confirm ul{ margin:0 0 12px; padding-left:18px; color:var(--muted); font-size:14px; line-height:1.6; }

.row{ display:flex; gap:10px; flex-wrap:wrap; }
/* Только .back — настоящий flex-элемент строки: кнопку подтверждения шлёт
   <form>, и элементом строки становится ОНА (см. правило ниже), а не .cta
   внутри неё. .cta сюда нарочно не включён: фикс-раунд 1 добавлял его в эту
   же строку с width:auto, что глушило базовый .cta{width:100%} у кнопки —
   форма растягивалась на всю полосу, а кнопка внутри неё оставалась шириной
   по тексту (баг виден на старом order-refund-confirm-desktop.png). Кнопке
   достаточно её собственного width:100% — она заполнит родителя-форму. */
.row .back{ width:auto; flex:1 1 170px; min-width:0; margin:0; }
/* Без этого правила форме нечем унаследовать flex-basis, и она схлопнулась
   бы до содержимого — то есть до ширины самой кнопки, вернув тот же баг. */
.row form{ flex:1 1 170px; min-width:0; margin:0; }

/* ---------- страницы ошибок ---------- */
/* Прототип «B — сцена» (2026-09-19): пустой зал под светом сцены вместо
   английской заглушки Laravel. */
.oops{ max-width:720px; margin:0 auto; padding-top:clamp(16px, 5vh, 64px); text-align:center; }
.oops__hall{ display:block; width:min(100%, 520px); height:auto; margin:0 auto; }
.oops__stage{ stroke:var(--accent); }
.oops__seats rect{ fill:var(--rule); }
.oops__title{ margin-top:28px; font-size:clamp(34px, 5.4vw, 52px); font-weight:800; line-height:1.04; letter-spacing:-.035em; text-wrap:balance; }
.oops__text{ margin:14px auto 0; max-width:48ch; color:var(--muted); font-size:17px; text-wrap:pretty; }
.oops__go{ display:inline-block; width:auto; margin-top:28px; padding:14px 26px; text-decoration:none; }
.oops__code{ margin-top:36px; font-family:var(--mono); font-size:13px; color:var(--muted); }

/* ---------- узкий экран ---------- */

@media (max-width:900px){
    .buyer{ grid-template-columns:1fr; height:auto; padding:4px 12px 20px; }
    /* Схема первой: на телефоне ради неё и пришли. Порядок в документе
       остаётся «заказ, потом зал» — там он совпадает с широким экраном. */
    .hall{ order:-1; padding:26px 10px 14px; border-radius:20px; }
    .mapbox{ height:56dvh; flex:none; }
    .panel{ overflow:visible; padding:0; }
    .stub__link{ grid-template-columns:80px minmax(0,1fr); row-gap:14px; }
    .stub__date{ grid-template-columns:1fr; }
    .stub__day, .stub__month{ font-size:20px; }
    .stub__name{ white-space:normal; }
    .stub__price{ grid-column:2; flex-direction:row; flex-wrap:wrap; align-items:baseline; gap:6px; text-align:left; }
    .stub__go{ margin:0 0 0 auto; }
}

@media (prefers-reduced-motion:reduce){
    *{ animation-duration:.001ms !important; transition-duration:.001ms !important; }
}
