/* ===========================================================================
   Компонентная библиотека platform-web. Фаза 1 редизайна.
   Все значения — из tokens.css. Литеральных цветов здесь нет.
   =========================================================================== */

/* ===========================================================================
   ОБОЛОЧКА: топбар + сайдбар (≥1024px) / нижний таб-бар (мобильный)
   =========================================================================== */

.ui-shell {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

.ui-topbar {
  grid-column: 1 / -1;
  position: sticky;
  top: 0;
  z-index: var(--z-shell);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.ui-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-1);
  text-decoration: none;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-16);
  white-space: nowrap;
}
.ui-brand-mark {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--primary);
  color: var(--primary-text);
  font-size: var(--fs-14);
  font-weight: var(--fw-bold);
  flex: none;
}
.ui-brand-text { display: none; }
@media (min-width: 600px) { .ui-brand-text { display: inline; } }

.ui-topbar-search {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 460px;
}
.ui-topbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* --- публичная шапка (лендинг, страница мероприятия для гостя, вход) -------
   Кнопки входа и переключатель языка есть только здесь: в кабинете вместо них
   аватар и кошелёк, а язык выбирается в разделе «Оформление». */

.ui-auth-nav { display: flex; align-items: center; gap: var(--sp-2); }

.ui-lang-switch {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.ui-lang-btn {
  appearance: none; cursor: pointer;
  padding: 5px var(--sp-3);
  border: 0; background: none;
  color: var(--text-3);
  font: inherit; font-size: var(--fs-12); font-weight: var(--fw-medium);
  line-height: 1.6;
}
.ui-lang-btn:hover { background: var(--surface-2); color: var(--text-1); }
.ui-lang-btn.is-active { background: var(--primary); color: var(--primary-text); }
.ui-lang-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* На телефоне подписи кнопок входа съедают всю шапку: язык прячем в пользу
   действия — сменить его можно на любой странице после входа, а войти надо
   отсюда. */
@media (max-width: 599px) {
  .ui-topbar-right .ui-lang-switch { display: none; }
}

/* Виджет кошелька: сумма моноширинно, режим хранения ключа — всегда виден (0.1). */
.ui-wallet-widget {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  min-height: 36px;
}
@media (min-width: 720px) { .ui-wallet-widget { display: inline-flex; } }
.ui-wallet-amount {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
  color: var(--text-1);
  font-size: var(--fs-14);
  white-space: nowrap;
}
.ui-wallet-unit { color: var(--text-3); font-size: var(--fs-12); }

/* Аватар-кнопка */
.ui-avatar {
  width: var(--tap); height: var(--tap);
  min-width: var(--tap);
  display: grid; place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--text-1);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-14);
  cursor: pointer;
  padding: 0;
}
.ui-avatar:hover { background: var(--surface-3); }

/* --- сайдбар ------------------------------------------------------------- */
.ui-sidebar { display: none; }

@media (min-width: 1024px) {
  .ui-shell { grid-template-columns: 248px 1fr; }
  .ui-sidebar {
    display: block;
    grid-column: 1;
    position: sticky;
    top: 61px;
    align-self: start;
    height: calc(100vh - 61px);
    overflow-y: auto;
    padding: var(--sp-4) var(--sp-3);
    background: var(--surface);
    border-right: 1px solid var(--border);
  }
  .ui-content { grid-column: 2; }
  /* Оболочка без бокового меню (публичные страницы): одна колонка на всю
     ширину, иначе контент смещён вправо на 248px пустой колонки. */
  .ui-shell--nonav { grid-template-columns: 1fr; }
  .ui-shell--nonav .ui-content { grid-column: 1 / -1; }
}

.ui-nav-group + .ui-nav-group { margin-top: var(--sp-6); }
.ui-nav-title {
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-3);
  padding: 0 var(--sp-3);
  margin-bottom: var(--sp-2);
}
.ui-nav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ui-nav-link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--tap);
  padding: 0 var(--sp-3);
  border-radius: var(--r-sm);
  color: var(--text-2);
  text-decoration: none;
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
}
.ui-nav-link:hover { background: var(--surface-2); color: var(--text-1); }
.ui-nav-link[aria-current="page"] {
  background: var(--primary-wash);
  color: var(--primary-ink);
  font-weight: var(--fw-semibold);
}
.ui-nav-link .ui-ico { flex: none; }
.ui-nav-count {
  margin-left: auto;
  font-size: var(--fs-12);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* --- нижний таб-бар (мобильный) ------------------------------------------ */
.ui-tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-shell);
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
@media (min-width: 1024px) { .ui-tabbar { display: none; } }

.ui-tabbar-link {
  flex: 1 1 0;
  min-width: 0;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: var(--sp-1);
  color: var(--text-3);
  text-decoration: none;
  font-size: var(--fs-12);
  line-height: 1.2;
}
.ui-tabbar-link span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ui-tabbar-link[aria-current="page"] { color: var(--primary-ink); font-weight: var(--fw-semibold); }

/* --- контент -------------------------------------------------------------- */
.ui-content {
  min-width: 0;
  padding: var(--sp-6) var(--sp-4);
  /* Место под нижний таб-бар, чтобы последний элемент не оказался под ним. */
  padding-bottom: calc(56px + var(--sp-8));
}
@media (min-width: 1024px) {
  .ui-content { padding: var(--sp-8); padding-bottom: var(--sp-12); }
}
.ui-page { max-width: 1120px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--sp-6); }
.ui-page-narrow { max-width: 720px; }

/* Вход: узкая карточка по центру экрана — и по горизонтали, и по вертикали.
   Отдельный модификатор, а не ui-page-narrow: там ширина под текст, здесь —
   под форму, и центрирование по высоте нужно только здесь. */
.ui-page-auth { max-width: 420px; width: 100%; margin: 0 auto; }
@media (min-height: 700px) {
  .ui-page-auth { margin-top: max(var(--sp-8), 12vh); }
}

.ui-page-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--sp-3); }
.ui-page-head h1 { font-size: var(--fs-24); line-height: var(--lh-24); }
.ui-page-head .ui-page-actions { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.ui-page-sub { color: var(--text-3); font-size: var(--fs-14); }

/* --- витрина: первый экран ------------------------------------------------
   Единственное место в системе, где заголовок крупнее кабинетного: это первое,
   что видит человек, ещё не понимающий, куда попал. Подложка — мягкий отсвет
   акцента, а не картинка: он берётся из токенов и потому одинаково уместен в
   обеих темах. */
.ui-hero {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-4);
  text-align: center;
  padding: var(--sp-8) var(--sp-4) var(--sp-6);
  border-radius: var(--r-md);
  background:
    radial-gradient(120% 100% at 50% 0%, var(--primary-wash) 0%, transparent 70%);
}
.ui-hero-title {
  margin: 0;
  font-size: var(--fs-32); line-height: var(--lh-32);
  font-weight: var(--fw-bold);
  text-wrap: balance;
  max-width: 20ch;
}
.ui-hero-lead {
  margin: 0;
  max-width: 56ch;
  color: var(--text-2);
  font-size: var(--fs-16); line-height: var(--lh-16);
  text-wrap: pretty;
}
.ui-hero-cta { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-3); }
.ui-hero-note { max-width: 60ch; margin: 0; }

@media (min-width: 720px) {
  .ui-hero { padding: var(--sp-12) var(--sp-6) var(--sp-8); }
}

/* --- статья «как это работает» --------------------------------------------
   Не карточки: это текст, который читают подряд, и рамка вокруг каждого абзаца
   мешала бы. Ритм задаёт только расстояние между разделами. */
.ui-about-section { display: flex; flex-direction: column; gap: var(--sp-2); }
.ui-about-section h2 {
  display: flex; align-items: center; gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-16); line-height: var(--lh-16); font-weight: var(--fw-semibold);
}
.ui-about-icon { font-size: 18px; line-height: 1; }
.ui-about-section p { margin: 0; max-width: 70ch; line-height: var(--lh-16); color: var(--text-2); }

.ui-faq { display: flex; flex-direction: column; gap: var(--sp-2); }
.ui-faq-item {
  border: 1px solid var(--border-soft);
  border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-2);
}
.ui-faq-item summary {
  cursor: pointer;
  font-size: var(--fs-14); font-weight: var(--fw-semibold); color: var(--text-1);
}
.ui-faq-item summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ui-faq-item p {
  margin: var(--sp-2) 0 0;
  color: var(--text-2); font-size: var(--fs-14); line-height: var(--lh-14);
  max-width: 70ch;
}

/* Ссылка «назад» над заголовком страницы. Стрелка — часть текста в словаре
   (buyer.allEvents), поэтому псевдоэлементом её не добавляем: получилось бы две. */
.ui-back-link { font-size: var(--fs-14); color: var(--text-3); text-decoration: none; }
.ui-back-link:hover { color: var(--text-1); text-decoration: underline; }

/* Заголовок раздела витрины: подпись над содержимым, а не второй h1. */
.ui-section-title {
  margin: 0;
  font-size: var(--fs-12); font-weight: var(--fw-semibold);
  color: var(--text-3); text-transform: uppercase; letter-spacing: .04em;
}

/* ===========================================================================
   BUTTON
   =========================================================================== */

.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-family: inherit;
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.ui-btn--block { width: 100%; }
.ui-btn--sm { min-height: 34px; padding: 0 var(--sp-3); font-size: var(--fs-12); }
.ui-btn--lg { min-height: 52px; padding: 0 var(--sp-6); font-size: var(--fs-16); }

.ui-btn--primary { background: var(--primary); color: var(--primary-text); }
.ui-btn--primary:hover:not(:disabled) { background: var(--primary-hover); }
.ui-btn--primary:active:not(:disabled) { background: var(--primary-active); }

.ui-btn--secondary { background: var(--surface); color: var(--text-1); border-color: var(--border); }
.ui-btn--secondary:hover:not(:disabled) { background: var(--surface-2); }

.ui-btn--ghost { background: transparent; color: var(--text-2); }
.ui-btn--ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--text-1); }

.ui-btn--danger { background: var(--danger); color: var(--danger-text); }
.ui-btn--danger:hover:not(:disabled) { filter: brightness(1.08); }

.ui-btn:disabled {
  cursor: not-allowed;
  opacity: .75;
  /* Любой вариант в отключённом состоянии выглядит одинаково нейтрально —
     заливка снимается, чтобы «нельзя» читалось до чтения текста. */
  background: var(--surface-2);
  color: var(--text-3);
  border-color: var(--border);
  box-shadow: none;
}

/* Спиннер в кнопке. Причина блокировки живёт в title/aria-describedby, а не
   только в визуальном приглушении — иначе disabled-кнопка молчит о причине. */
.ui-btn[data-loading="true"] { cursor: progress; }
.ui-spin {
  width: 15px; height: 15px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--r-pill);
  animation: ui-spin .7s linear infinite;
  flex: none;
}
@keyframes ui-spin { to { transform: rotate(360deg); } }

.ui-btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ===========================================================================
   ПОЛЯ ВВОДА
   =========================================================================== */

.ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ui-label {
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  color: var(--text-1);
}
.ui-label .ui-req { color: var(--danger-ink); margin-left: 2px; }
.ui-hint { font-size: var(--fs-12); line-height: var(--lh-12); color: var(--text-3); }

.ui-input, .ui-select, .ui-textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 10px var(--sp-3);
  background: var(--surface);
  color: var(--text-1);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-family: inherit;
  font-size: var(--fs-16); /* 16px — иначе iOS зумит страницу при фокусе */
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
.ui-textarea { min-height: 96px; resize: vertical; }
.ui-input::placeholder, .ui-textarea::placeholder { color: var(--text-3); }
.ui-input:hover, .ui-select:hover, .ui-textarea:hover { border-color: var(--text-3); }
.ui-input:disabled, .ui-select:disabled, .ui-textarea:disabled {
  background: var(--surface-2); color: var(--text-3); cursor: not-allowed;
}

.ui-select {
  appearance: none;
  padding-right: var(--sp-8);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* Ошибка поля — inline под полем, и красным помечено ОБА: рамка и текст с
   иконкой (цвет не единственный носитель). */
.ui-field[data-invalid="true"] .ui-input,
.ui-field[data-invalid="true"] .ui-select,
.ui-field[data-invalid="true"] .ui-textarea { border-color: var(--danger); }
.ui-error {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  color: var(--danger-ink);
}
.ui-error .ui-ico { flex: none; margin-top: 1px; }

/* Поле суммы: единица измерения внутри поля справа */
.ui-amount { position: relative; display: flex; align-items: center; }
.ui-amount .ui-input { padding-right: 56px; text-align: right; }
.ui-amount-unit {
  position: absolute; right: var(--sp-3);
  color: var(--text-3); font-size: var(--fs-14); font-weight: var(--fw-medium);
  pointer-events: none;
}

/* Адрес: моноширинно, маска, кнопка копирования */
.ui-address {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 10px var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  min-height: var(--tap);
}
.ui-address code {
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  color: var(--text-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1 1 auto; min-width: 0;
}
.ui-icon-btn {
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  flex: none;
}
.ui-icon-btn:hover { background: var(--surface-3); color: var(--text-1); }

/* Тумблер */
.ui-switch { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; min-height: var(--tap); }
.ui-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.ui-switch-track {
  width: 44px; height: 26px; flex: none;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  position: relative;
  transition: background-color .15s ease;
}
.ui-switch-track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 20px; height: 20px; border-radius: var(--r-pill);
  background: var(--surface); box-shadow: var(--shadow-card);
  transition: transform .15s ease;
}
.ui-switch input:checked + .ui-switch-track { background: var(--primary); border-color: var(--primary); }
.ui-switch input:checked + .ui-switch-track::after { transform: translateX(18px); }
.ui-switch input:focus-visible + .ui-switch-track { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ui-switch-label { font-size: var(--fs-14); color: var(--text-1); }

/* ===========================================================================
   CARD
   =========================================================================== */

.ui-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}
.ui-card--flat { box-shadow: none; }
.ui-card--pad-sm { padding: var(--sp-4); }
.ui-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
.ui-card-head h3 { font-size: var(--fs-16); font-weight: var(--fw-semibold); }
.ui-card-head .ui-card-actions { margin-left: auto; display: flex; gap: var(--sp-2); }

/* Опасная зона — визуально отделена, а не просто красная кнопка среди прочих. */
.ui-card--danger { border-color: var(--danger); }
.ui-card--danger .ui-card-head h3 { color: var(--danger-ink); }

/* Сетка метрик: крупная цифра + подпись */
.ui-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--sp-4); }
.ui-metric { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ui-metric-value {
  font-size: var(--fs-24); line-height: var(--lh-24);
  font-weight: var(--fw-semibold); color: var(--text-1);
  font-variant-numeric: tabular-nums;
}
.ui-metric-label { font-size: var(--fs-12); color: var(--text-3); }

/* ===========================================================================
   STATUS CHIP / BADGE
   =========================================================================== */

.ui-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  border: 1px solid transparent;
}
.ui-chip .ui-ico { flex: none; }

.ui-chip[data-tone="valid"]     { color: var(--success-ink);   background: var(--success-wash);   border-color: var(--success); }
.ui-chip[data-tone="redeemed"]  { color: var(--text-2);        background: var(--surface-2);      border-color: var(--border); }
.ui-chip[data-tone="listed"]    { color: var(--info-ink);      background: var(--info-wash);      border-color: var(--info); }
.ui-chip[data-tone="refunded"]  { color: var(--warning-ink);   background: var(--warning-wash);   border-color: var(--warning); }
.ui-chip[data-tone="cancelled"] { color: var(--danger-ink);    background: var(--danger-wash);    border-color: var(--danger); }
.ui-chip[data-tone="paused"]    { color: var(--warning-ink);   background: var(--warning-wash);   border-color: var(--warning); }
.ui-chip[data-tone="draft"]     { color: var(--text-2);        background: var(--surface-2);      border-color: var(--border); }
.ui-chip[data-tone="soldout"]   { color: var(--text-2);        background: var(--surface-2);      border-color: var(--border); }
.ui-chip[data-tone="primary"]   { color: var(--primary-ink);   background: var(--primary-wash);   border-color: var(--primary); }

/* ===========================================================================
   TABS
   =========================================================================== */

.ui-tabs {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: thin;
}
.ui-tab {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-2);
  font-family: inherit;
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  min-height: var(--tap);
  padding: 0 var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
}
.ui-tab:hover { color: var(--text-1); }
.ui-tab[aria-selected="true"] {
  color: var(--primary-ink);
  border-bottom-color: var(--primary);
  font-weight: var(--fw-semibold);
}

/* ===========================================================================
   TABLE
   =========================================================================== */

.ui-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  /* Карточка — flex-column, а flex-элемент по умолчанию не сжимается ниже
     своего min-content. Эти две строки делают ограничение явным, чтобы длинный
     nft_… гарантированно прокручивался ВНУТРИ обёртки, а не раздувал карточку
     при малейшем изменении раскладки вокруг. */
  min-width: 0;
  max-width: 100%;
}
.ui-table { width: 100%; border-collapse: collapse; font-size: var(--fs-14); }
.ui-table th, .ui-table td {
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border-soft);
  vertical-align: middle;
}
.ui-table th {
  background: var(--surface-2);
  color: var(--text-3);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: .05em;
  white-space: nowrap;
  position: sticky; top: 0;
}
.ui-table tbody tr:last-child td { border-bottom: none; }
.ui-table tbody tr:hover { background: var(--surface-2); }
.ui-table td.ui-num { text-align: right; font-variant-numeric: tabular-nums; }
.ui-th-sort {
  appearance: none; background: none; border: none; padding: 0;
  color: inherit; font: inherit; text-transform: inherit; letter-spacing: inherit;
  cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
}
.ui-th-sort:hover { color: var(--text-1); }

/* ===========================================================================
   EMPTY STATE / SKELETON
   =========================================================================== */

.ui-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-3);
  padding: var(--sp-12) var(--sp-6);
  color: var(--text-3);
}
.ui-empty-art {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text-3);
}
.ui-empty h4 { color: var(--text-1); }
.ui-empty p { max-width: 44ch; font-size: var(--fs-14); }

.ui-skel {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: ui-skel 1.3s ease-in-out infinite;
  border-radius: var(--r-sm);
}
@keyframes ui-skel { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
.ui-skel--text { height: 12px; margin: 6px 0; }
.ui-skel--title { height: 20px; width: 45%; }
.ui-skel--card { height: 160px; border-radius: var(--r-md); }

/* ===========================================================================
   PROGRESS STEPS
   =========================================================================== */

.ui-steps { display: flex; flex-direction: column; gap: var(--sp-3); list-style: none; margin: 0; padding: 0; }
@media (min-width: 720px) { .ui-steps { flex-direction: row; gap: var(--sp-2); } }
.ui-step {
  flex: 1 1 0;
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  min-width: 0;
}
.ui-step-num {
  width: 26px; height: 26px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--text-3);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
}
.ui-step-title { font-size: var(--fs-14); font-weight: var(--fw-semibold); color: var(--text-1); }
.ui-step-desc { font-size: var(--fs-12); color: var(--text-3); }

.ui-step[data-state="current"] { border-color: var(--primary); background: var(--primary-wash); }
.ui-step[data-state="current"] .ui-step-num { background: var(--primary); color: var(--primary-text); }
.ui-step[data-state="done"] .ui-step-num { background: var(--success-wash); color: var(--success-ink); }
.ui-step[data-state="done"] .ui-step-title { color: var(--text-2); }

/* ===========================================================================
   MODAL / CONFIRM
   =========================================================================== */

.ui-overlay {
  position: fixed; inset: 0;
  z-index: var(--z-overlay);
  background: rgba(10, 10, 18, .55);
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0;
}
@media (min-width: 600px) { .ui-overlay { align-items: center; padding: var(--sp-6); } }

.ui-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md) var(--r-md) 0 0;
  box-shadow: var(--shadow-modal);
  width: 100%;
  max-width: 520px;
  max-height: 92vh;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--sp-4);
  padding: var(--sp-6);
}
@media (min-width: 600px) { .ui-modal { border-radius: var(--r-md); } }
.ui-modal--wide { max-width: 720px; }
.ui-modal-head { display: flex; align-items: flex-start; gap: var(--sp-3); }
.ui-modal-head h3 { flex: 1 1 auto; }
.ui-modal-foot { display: flex; flex-wrap: wrap; gap: var(--sp-2); justify-content: flex-end; }
.ui-modal-foot .ui-btn { flex: 1 1 auto; }
@media (min-width: 600px) { .ui-modal-foot .ui-btn { flex: 0 0 auto; } }

/* ===========================================================================
   TOAST
   =========================================================================== */

.ui-toasts {
  position: fixed;
  left: var(--sp-4); right: var(--sp-4);
  bottom: calc(56px + var(--sp-4));
  z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: var(--sp-2);
  pointer-events: none;
}
@media (min-width: 1024px) {
  .ui-toasts { left: auto; right: var(--sp-6); bottom: var(--sp-6); width: 380px; }
}
.ui-toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-modal);
  font-size: var(--fs-14);
  color: var(--text-1);
  animation: ui-toast-in .18s ease-out;
}
@keyframes ui-toast-in { from { opacity: 0; transform: translateY(8px); } }
.ui-toast[data-tone="success"] { border-left-color: var(--success); }
.ui-toast[data-tone="danger"]  { border-left-color: var(--danger); }
.ui-toast[data-tone="warning"] { border-left-color: var(--warning); }
.ui-toast[data-tone="info"]    { border-left-color: var(--info); }
.ui-toast-body { flex: 1 1 auto; min-width: 0; }
.ui-toast .ui-ico { flex: none; margin-top: 2px; }
.ui-toast[data-tone="success"] > .ui-ico { color: var(--success-ink); }
.ui-toast[data-tone="danger"]  > .ui-ico { color: var(--danger-ink); }
.ui-toast[data-tone="warning"] > .ui-ico { color: var(--warning-ink); }
.ui-toast[data-tone="info"]    > .ui-ico { color: var(--info-ink); }

/* ===========================================================================
   БАННЕР (результат операции / предупреждение на экране)
   =========================================================================== */

.ui-banner {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: var(--fs-14);
  color: var(--text-2);
}
.ui-banner h4 { margin-bottom: 2px; font-size: var(--fs-14); }
.ui-banner-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.ui-banner[data-tone="success"] { border-left-color: var(--success); background: var(--success-wash); }
.ui-banner[data-tone="danger"]  { border-left-color: var(--danger);  background: var(--danger-wash); }
.ui-banner[data-tone="warning"] { border-left-color: var(--warning); background: var(--warning-wash); }
.ui-banner[data-tone="info"]    { border-left-color: var(--info);    background: var(--info-wash); }
.ui-banner > .ui-ico { flex: none; margin-top: 2px; }
.ui-banner[data-tone="success"] > .ui-ico { color: var(--success-ink); }
.ui-banner[data-tone="danger"]  > .ui-ico { color: var(--danger-ink); }
.ui-banner[data-tone="warning"] > .ui-ico { color: var(--warning-ink); }
.ui-banner[data-tone="info"]    > .ui-ico { color: var(--info-ink); }

/* ===========================================================================
   ТЕХНИЧЕСКИЕ ДЕТАЛИ (0.5): адреса, эпохи, manifest-логи — всегда свёрнуто
   =========================================================================== */

.ui-details {
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: var(--fs-12);
}
.ui-details > summary {
  min-height: var(--tap);
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 0 var(--sp-3);
  cursor: pointer;
  color: var(--text-3);
  font-weight: var(--fw-medium);
  list-style: none;
}
.ui-details > summary::-webkit-details-marker { display: none; }
.ui-details > summary::before {
  content: "";
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .15s ease;
}
.ui-details[open] > summary::before { transform: rotate(90deg); }
.ui-details-body {
  padding: 0 var(--sp-3) var(--sp-3);
  display: flex; flex-direction: column; gap: var(--sp-2);
  font-family: var(--font-mono);
  color: var(--text-2);
  word-break: break-all;
}

/* ===========================================================================
   QR-СКАНЕР (экран контролёра)
   =========================================================================== */

.ui-scanner {
  position: relative;
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1;
  margin: 0 auto;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
  display: grid;
  place-items: center;
}
.ui-scanner video { width: 100%; height: 100%; object-fit: cover; }
.ui-scanner-frame {
  position: absolute; inset: 12%;
  border: 3px solid var(--primary);
  border-radius: var(--r-md);
  box-shadow: 0 0 0 100vmax rgba(10, 10, 18, .35);
  pointer-events: none;
}

/* Результат гашения — на весь экран, читается с вытянутой руки. */
.ui-scan-result {
  position: fixed; inset: 0;
  z-index: var(--z-overlay);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-6);
  text-align: center;
}
/* Текст поверх заливки — из токена «на цвете»: в тёмной теме success и danger
   светлые, и белый по ним даёт 3.1:1 — нечитаемо с вытянутой руки. */
.ui-scan-result[data-result="allow"] { background: var(--success); color: var(--success-text); }
.ui-scan-result[data-result="deny"]  { background: var(--danger);  color: var(--danger-text); }
.ui-scan-result .ui-scan-title { font-size: var(--fs-32); font-weight: var(--fw-bold); color: inherit; }
.ui-scan-result .ui-scan-reason { font-size: var(--fs-16); max-width: 32ch; }
.ui-scan-result .ui-scan-mark { font-size: 64px; line-height: 1; }

/* ===========================================================================
   ГРАФИК (лёгкий SVG, без библиотеки)
   =========================================================================== */

.ui-chart { width: 100%; height: auto; display: block; overflow: visible; }
.ui-chart .ui-chart-grid { stroke: var(--border-soft); stroke-width: 1; }
.ui-chart .ui-chart-area { fill: var(--primary-wash); }
.ui-chart .ui-chart-line { fill: none; stroke: var(--primary); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ui-chart .ui-chart-dot  { fill: var(--primary); }
.ui-chart .ui-chart-label { fill: var(--text-3); font-size: 10px; font-family: var(--font-sans); }

/* ===========================================================================
   УТИЛИТЫ РАСКЛАДКИ (немного, только то, что реально повторяется)
   =========================================================================== */

.ui-stack   { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.ui-stack-2 { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.ui-row     { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); min-width: 0; }
.ui-grid-2  { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); }
@media (min-width: 720px) { .ui-grid-2 { grid-template-columns: 1fr 1fr; } }
.ui-spacer  { flex: 1 1 auto; }
.ui-mono    { font-family: var(--font-mono); font-size: var(--fs-12); }
.ui-num     { font-variant-numeric: tabular-nums; }
.ui-t2      { color: var(--text-2); }
.ui-t3      { color: var(--text-3); font-size: var(--fs-14); }

/* ===========================================================================
   ТАЧ-ТАРГЕТЫ (0.7 чеклиста)

   На мыши компактные элементы (маленькая кнопка 34px, иконка-кнопка 36px)
   удобны и привычны; пальцем в них не попасть. Разводим по типу указателя, а
   не по ширине экрана: планшет с мышью и ноутбук с сенсорным экраном ломают
   любую привязку к брейкпоинту.
   =========================================================================== */
@media (pointer: coarse) {
  .ui-btn--sm { min-height: var(--tap); padding: 0 var(--sp-4); }
  .ui-icon-btn { width: var(--tap); height: var(--tap); }
  .ui-brand { min-height: var(--tap); }
  .ui-tab { min-height: 48px; }
  /* Строке таблицы и ссылке в списке тоже нужен палец, а не курсор. */
  .ui-table td { padding-top: var(--sp-4); padding-bottom: var(--sp-4); }
}

/* ===========================================================================
   КАРТОЧКА МЕРОПРИЯТИЯ (фаза 2). Один компонент на главную, каталог и поиск.
   =========================================================================== */

.ui-events-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-4);
}

.ui-event-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  min-width: 0;
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}
.ui-event-card:hover:not(.is-cancelled) {
  transform: translateY(-2px);
  box-shadow: var(--shadow-card);
  border-color: var(--text-3);
}

.ui-card-cover-link { display: block; text-decoration: none; }

/* Условия билета прямо на карточке витрины: показываются только там, где карточку
   смотрит человек, ещё не вошедший в систему (opt-in через EventCard({policies})). */
.ui-policy-badges { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.ui-policy-badge {
  padding: 3px var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--success-wash);
  color: var(--success-ink);
  font-size: var(--fs-12); font-weight: var(--fw-medium);
}
.ui-policy-badge.is-off { background: var(--surface-2); color: var(--text-3); }
.ui-card-cover {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
  overflow: hidden;
}
.ui-card-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ui-card-cover-fallback {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  font-size: var(--fs-32);
  font-weight: var(--fw-bold);
  color: #fff;
  /* Единственное место с фиксированным белым: текст лежит поверх насыщенного
     градиента, одинакового в обеих темах. */
}
.ui-card-cover-chip { position: absolute; left: var(--sp-3); top: var(--sp-3); }

/* Распродано: обложка приглушается, но текст карточки остаётся читаемым. */
.ui-event-card.is-soldout .ui-card-cover img,
.ui-event-card.is-soldout .ui-card-cover-fallback { filter: grayscale(.55) brightness(.85); }

/* Отменено: карточка уходит на второй план и теряет кнопку покупки. */
.ui-event-card.is-cancelled { opacity: .72; }
.ui-event-card.is-cancelled .ui-card-cover img,
.ui-event-card.is-cancelled .ui-card-cover-fallback { filter: grayscale(1) brightness(.9); }

.ui-event-card-body {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-4);
  flex: 1 1 auto;
}
.ui-event-card-body h3 { font-size: var(--fs-16); line-height: 1.3; }
.ui-event-card-title { color: var(--text-1); text-decoration: none; }
.ui-event-card-title:hover { color: var(--primary-ink); }
.ui-event-card-meta { display: flex; flex-direction: column; gap: 6px; font-size: var(--fs-14); color: var(--text-3); }
.ui-row-tight { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ui-row-tight > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-event-card-foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--sp-3); }
.ui-card-price { display: flex; align-items: baseline; gap: 6px; }
.ui-card-price strong { font-size: var(--fs-20); color: var(--text-1); font-variant-numeric: tabular-nums; }

/* ===========================================================================
   ГЛАВНАЯ: hero и лента «Ближайшие»
   =========================================================================== */

.ui-hero {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-8) var(--sp-6);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
@media (min-width: 720px) { .ui-hero { padding: var(--sp-12) var(--sp-8); } }
.ui-hero h1 { font-size: var(--fs-24); }
@media (min-width: 720px) { .ui-hero h1 { font-size: var(--fs-32); } }
.ui-hero-lead { color: var(--text-2); max-width: 60ch; }
.ui-hero-search { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: flex-end; }
.ui-hero-search .ui-field { flex: 1 1 200px; }
.ui-chip-filters { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.ui-chip-filter {
  appearance: none;
  min-height: 36px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text-2);
  font: inherit;
  font-size: var(--fs-14);
  cursor: pointer;
}
.ui-chip-filter:hover { background: var(--surface-2); color: var(--text-1); }
.ui-chip-filter[aria-pressed="true"] {
  background: var(--primary-wash); color: var(--primary-ink);
  border-color: var(--primary); font-weight: var(--fw-semibold);
}
@media (pointer: coarse) { .ui-chip-filter { min-height: var(--tap); } }

/* Лента «Ближайшие»: нативная горизонтальная прокрутка с привязкой — своей
   логики карусели нет, пальцем и колесом работает без нашего кода. */
.ui-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(260px, 300px);
  gap: var(--sp-4);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: var(--sp-2);
}
.ui-rail > * { scroll-snap-align: start; }

/* ===========================================================================
   СТРАНИЦА МЕРОПРИЯТИЯ: две колонки, липкий блок покупки
   =========================================================================== */

.ui-event-layout { display: grid; grid-template-columns: 1fr; gap: var(--sp-6); align-items: start; }
@media (min-width: 960px) {
  .ui-event-layout { grid-template-columns: minmax(0, 1fr) 360px; }
  .ui-buy-panel { position: sticky; top: calc(61px + var(--sp-6)); }
}
.ui-event-hero-cover {
  aspect-ratio: 16 / 9;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
  position: relative;
}
.ui-event-hero-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Описание мероприятия на публичной странице: переносы строк организатора
   сохраняются — он их расставлял осмысленно (состав, программа, возрастной ценз). */
.ui-desc-text { margin: 0; white-space: pre-wrap; line-height: var(--lh-16); color: var(--text-2); max-width: 70ch; }

/* Описание: свёрнуто до 6 строк, дальше — «Читать полностью». */
.ui-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ui-clamp[data-expanded="true"] { display: block; overflow: visible; }

.ui-total {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border-soft);
}
.ui-total-value { font-size: var(--fs-24); font-weight: var(--fw-semibold); color: var(--text-1); font-variant-numeric: tabular-nums; }

.ui-tier {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text-1);
  min-height: var(--tap);
}
.ui-tier:hover:not(:disabled) { background: var(--surface-2); }
.ui-tier[aria-pressed="true"] { border-color: var(--primary); background: var(--primary-wash); }
.ui-tier:disabled { opacity: .55; cursor: not-allowed; }
.ui-tier-name { flex: 1 1 auto; min-width: 0; font-weight: var(--fw-medium); }
.ui-tier-price { font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); }
.ui-tier-left { font-size: var(--fs-12); color: var(--text-3); }

/* Блок условий билета — список фактов, а не мелкий шрифт под кнопкой (0.2). */
.ui-terms { display: flex; flex-direction: column; gap: var(--sp-3); }
.ui-term { display: flex; align-items: flex-start; gap: var(--sp-3); }
.ui-term > .ui-ico { flex: none; margin-top: 3px; color: var(--text-3); }
.ui-term-body { min-width: 0; }
.ui-term-title { font-size: var(--fs-14); font-weight: var(--fw-medium); color: var(--text-1); }
.ui-term-note { font-size: var(--fs-12); color: var(--text-3); }

.ui-stepper { display: inline-flex; align-items: center; gap: var(--sp-2); }
.ui-stepper output {
  min-width: 40px; text-align: center;
  font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold);
  font-size: var(--fs-16); color: var(--text-1);
}

/* ===========================================================================
   МОИ БИЛЕТЫ
   =========================================================================== */

.ui-ticket {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
}
@media (min-width: 720px) { .ui-ticket { grid-template-columns: 96px minmax(0,1fr) auto; align-items: center; } }
.ui-ticket-qr {
  width: 96px; height: 96px;
  display: grid; place-items: center;
  background: #fff;   /* QR обязан оставаться тёмным по белому в обеих темах */
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  justify-self: start;
}
.ui-ticket-qr canvas, .ui-ticket-qr img { display: block; max-width: 88px; max-height: 88px; }
.ui-ticket-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ui-ticket-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Метка места на схеме зала. Логика выбора одна с карточками мест — это лишь
   второй способ ткнуть в то же самое. */
.ui-venue-dot {
  position: absolute;
  width: 18px; height: 18px;
  transform: translate(-50%, -50%);
  border-radius: var(--r-pill);
  border: 2px solid var(--surface);
  background: var(--primary);
  cursor: pointer;
  padding: 0;
}
.ui-venue-dot[data-chosen="true"] { background: var(--success); width: 24px; height: 24px; }
.ui-venue-dot:hover { filter: brightness(1.15); }

/* ===========================================================================
   Фаза 3 — утилиты, понадобившиеся контуру организатора. Живут в общей
   библиотеке, а не на экране: следующая роль встретит ровно те же случаи.
   =========================================================================== */

/* Строка «поле + кнопка», где кнопка выровнена по НИЖНЕЙ границе поля, а не по
   центру: у поля сверху подпись, и центрирование поднимает кнопку в воздух. */
.ui-row-end {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: var(--sp-3); min-width: 0;
}
.ui-row-end > .ui-field { flex: 1 1 220px; }

/* Подзаголовок внутри карточки: слабее h3 карточки, сильнее обычного текста. */
.ui-subhead {
  font-size: var(--fs-14); font-weight: var(--fw-semibold);
  color: var(--text-2); margin-top: var(--sp-2);
}

.ui-sep { border: 0; border-top: 1px solid var(--border); margin: var(--sp-2) 0; }

/* Ссылка «назад к списку» над заголовком страницы. */
.ui-back { font-size: var(--fs-14); color: var(--text-3); text-decoration: none; }
.ui-back:hover { color: var(--text); }

/* Пара «подпись — значение» в технических деталях. */
.ui-kv {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2); font-size: var(--fs-12);
}
.ui-kv > span:first-child { color: var(--text-3); min-width: 140px; }

.ui-mono-sm { font-family: var(--font-mono); font-size: var(--fs-12); }
.ui-text-danger { color: var(--danger-ink); }

/* ===========================================================================
   Фаза 4 — экран контролёра и мелкие утилиты админки.
   =========================================================================== */

/* Маркированный/нумерованный список внутри карточки: браузерный отступ у
   списка свой, поэтому задаём его явно от шкалы. */
.ui-list { padding-left: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }
.ui-list li { font-size: var(--fs-14); line-height: var(--lh-14); color: var(--text-2); }

/* --- сканер -------------------------------------------------------------- */

/* Камера занимает основную часть экрана: контролёр целится телефоном, а не
   читает интерфейс. 60vh на мобильном — примерно столько остаётся под шапкой
   и таб-баром, и этого хватает, чтобы QR попадал в кадр с вытянутой руки. */
.ui-scan-box {
  position: relative;
  min-height: 60vh;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.ui-scan-video {
  /* Абсолютом, а не flex-элементом: иначе высота считается по аспекту потока,
     и object-fit: cover нечего обрезать — кадр обрастает серыми полями. */
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}

/* Рамка прицела — единственная подсказка поверх кадра. */
.ui-scan-frame {
  position: absolute; inset: 12% 8%;
  border: 3px solid var(--surface);
  border-radius: var(--r-md);
  box-shadow: 0 0 0 9999px rgb(0 0 0 / 0.28);
  pointer-events: none;
}

/* --- вердикт ------------------------------------------------------------- */

/* Итог гашения на весь экран: решение принимается по цвету с расстояния, а
   текст дочитывается вплотную. Держится несколько секунд и гаснет сам —
   на входе очередь. */
.ui-verdict {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-4); padding: var(--sp-6); text-align: center;
}
.ui-verdict[data-tone="ok"]   { background: var(--success); color: var(--success-text); }
.ui-verdict[data-tone="deny"] { background: var(--danger);  color: var(--danger-text); }
.ui-verdict-mark   { font-size: 88px; line-height: 1; font-weight: var(--fw-bold); }
.ui-verdict-title  { font-size: var(--fs-32); line-height: var(--lh-32); font-weight: var(--fw-bold); }
.ui-verdict-detail { font-size: var(--fs-20); line-height: var(--lh-20); }
/* Причина отказа — единственный длинный текст здесь, поэтому ей нужна
   комфортная ширина строки, а не вся ширина экрана. */
.ui-verdict-reason {
  font-size: var(--fs-16); line-height: var(--lh-16);
  max-width: 42ch; opacity: 0.94;
}
.ui-verdict-close {
  margin-top: var(--sp-4);
  appearance: none; cursor: pointer;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  border: 2px solid currentColor;
  background: transparent; color: inherit;
  font: inherit; font-weight: var(--fw-semibold);
}
.ui-verdict-close:hover { background: rgb(255 255 255 / 0.15); }

/* Строка журнала смены. */
.ui-shift-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--fs-14);
}
.ui-shift-row:last-child { border-bottom: none; }

/* ===========================================================================
   Диалоги кошелька (shared/wallet.js).

   Их разметка задана в HTML каждой роли и переносится между фазами дословно:
   wallet.js ищет элементы по id, и трогать её нельзя. Но рассчитана она была
   на прежний style.css, которого на новых страницах нет — и self-custody
   пользователь встречал первый и самый важный экран (создание кошелька, показ
   сид-фразы) голой браузерной вёрсткой. Найдено глазами на экране контролёра:
   это единственная роль, у которой self-custody обязателен, поэтому раньше
   дефект не проявлялся.

   Стилизуем ТОЛЬКО внешний вид, по тем же токенам, что и остальной интерфейс.
   =========================================================================== */

#walletChoiceDialog, #walletSetupDialog {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text-1);
  box-shadow: var(--shadow-modal);
  padding: var(--sp-6);
  max-width: 520px;
  width: calc(100vw - var(--sp-8));
  font: inherit;
}
#walletChoiceDialog::backdrop, #walletSetupDialog::backdrop { background: rgb(0 0 0 / 0.5); }

#walletChoiceDialog h2, #walletSetupDialog h2 {
  font-size: var(--fs-20); line-height: var(--lh-20); font-weight: var(--fw-semibold);
  margin-bottom: var(--sp-3);
}
#walletChoiceDialog .hint, #walletSetupDialog .hint {
  font-size: var(--fs-14); line-height: var(--lh-14);
  color: var(--text-3); margin-bottom: var(--sp-4);
}
#walletChoiceDialog label, #walletSetupDialog label {
  display: block;
  font-size: var(--fs-14); font-weight: var(--fw-medium);
  color: var(--text-2); margin-bottom: var(--sp-2);
}
#walletChoiceDialog textarea, #walletSetupDialog textarea {
  width: 100%; box-sizing: border-box;
  padding: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--text-1);
  margin-bottom: var(--sp-3);
}
#walletChoiceDialog .row, #walletSetupDialog .row {
  display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3);
}
/* Кнопки в этих диалогах без классов дизайн-системы — приводим их к тому же
   виду по элементу, чтобы не править разметку, которую читает wallet.js. */
#walletChoiceDialog button, #walletSetupDialog button {
  appearance: none; cursor: pointer;
  font: inherit; font-weight: var(--fw-semibold);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: var(--primary); color: var(--primary-text);
}
#walletChoiceDialog button.secondary, #walletSetupDialog button.secondary {
  background: var(--surface); color: var(--text-1); border-color: var(--border);
}
#walletChoiceDialog button:disabled, #walletSetupDialog button:disabled {
  cursor: not-allowed; background: var(--surface-2); color: var(--text-3); border-color: var(--border);
}
#walletSetupDialog input[type="checkbox"] { accent-color: var(--primary); }
#walletSetupDialog #mnemonicContinueBtn { margin-top: var(--sp-4); width: 100%; }

/* Камера-зона в паузе: пока контролёр смотрит найденный билет, поток
   остановлен, и раньше на его месте оставался чёрный прямоугольник в половину
   экрана. Говорим словами, что происходит. */
.ui-scan-idle {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-2); padding: var(--sp-6); text-align: center;
}
.ui-scan-idle-title { font-size: var(--fs-16); font-weight: var(--fw-semibold); color: var(--text-2); }

/* ===========================================================================
   Редактор схем залов. Фаза 5 редизайна.

   Сам редактор (shared/venueEditor.js) не тронут — переносится только внешний
   вид: те же классы, что он ставит на элементы, описаны здесь через токены.
   Геометрия (проценты позиционирования, translate(-50%,-50%), touch-action)
   сохранена дословно: от неё зависит попадание маркера в точку клика.
   =========================================================================== */

/* Холст со схемой: изображение и слой маркеров поверх него. */
.ui-venue-canvas {
  position: relative;
  display: inline-block;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
}
.ui-venue-canvas img { display: block; max-width: 100%; height: auto; }
.ui-venue-markers { position: absolute; inset: 0; }

/* Метка места. Размер и тени подобраны так, чтобы подпись читалась поверх
   любой схемы, а сам маркер оставался мелким — их бывает больше тысячи. */
.venue-marker {
  position: absolute;
  transform: translate(-50%, -50%);
  min-width: 22px; height: 22px;
  padding: 0 4px;
  border-radius: var(--r-sm);
  background: var(--primary); color: var(--primary-text);
  font-size: var(--fs-12); font-weight: var(--fw-semibold);
  display: flex; align-items: center; justify-content: center;
  cursor: grab; user-select: none;
  box-shadow: 0 0 0 2px rgb(0 0 0 / 0.35);
  touch-action: none;
}
.venue-marker-selected { background: var(--warning); color: var(--warning-text); }

/* Область, которую тянут инструментом «сектор». */
.venue-sector-overlay {
  position: absolute;
  border: 2px dashed var(--primary);
  background: color-mix(in srgb, var(--primary) 15%, transparent);
  pointer-events: none;
}
#venueSectorToolBtn.venue-sector-active {
  background: var(--primary); color: var(--primary-text); border-color: var(--primary);
}

/* Угловые ручки трапеции — перетаскиваются независимо (venueEditor.js::
   makeTrapezoidHandle), чтобы подогнать сектор под веерный амфитеатр. */
.venue-sector-handle {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 16px; height: 16px;
  border-radius: var(--r-pill);
  background: var(--warning);
  border: 2px solid var(--warning-text);
  cursor: grab; touch-action: none; z-index: 5;
}

/* Панель свойств выбранного места: подписи над полями, всё в одну строку на
   широком экране и в столбец на узком. */
.ui-venue-seat-edit { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-3); }
.ui-venue-seat-edit > .ui-field { flex: 1 1 140px; margin: 0; }

/* Минимальная ширина, на которой редактор остаётся рабочим: ниже неё схема
   становится меньше маркеров, и попасть в точку пальцем нельзя. Честно
   говорим об этом вместо того, чтобы делать вид, что всё помещается. */

/* ===========================================================================
   Интерактивная схема зала на странице мероприятия (shared/eventPage.js).

   Правила перенесены из shared/style.css при переводе публичных страниц на
   компоненты: модуль общий, а легаси-таблицу стилей грузили только четыре
   страницы — после переноса она удалена целиком.

   Цвета ценовых поясов заданы литералами намеренно: это ШКАЛА (тёплое дороже,
   холодное дешевле), а не роль в интерфейсе, и она обязана читаться одинаково
   в обеих темах. Текст поверх каждого пояса подобран под его собственный фон:
   все шесть пар проверены на WCAG AA (≥4.5:1), самая слабая — 4.72:1. Прежние,
   более яркие зелёный и бирюзовый давали 3.34 и 3.57 и были затемнены.
   =========================================================================== */

.venue-marker-seat {
  position: absolute;
  transform: translate(-50%, -50%);
  min-width: 26px;
  height: 26px;
  padding: 0 4px;
  border-radius: var(--r-sm);
  border: none;
  background: var(--success);
  color: var(--success-text);
  font-size: var(--fs-12);
  font-weight: var(--fw-bold);
  font-family: inherit;
  cursor: pointer;
}
.venue-marker-seat:hover { filter: brightness(1.15); }
.venue-marker-seat:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.venue-marker-seat.venue-seat-sold {
  background: var(--surface-3); color: var(--text-3); cursor: not-allowed;
}
.venue-marker-seat.venue-seat-chosen {
  background: var(--warning); color: var(--warning-text);
  box-shadow: 0 0 0 2px var(--surface);
}

.venue-seat-tier-0 { background: #d33a3f; color: #ffffff; }
.venue-seat-tier-1 { background: #d38b12; color: #2a1900; }
.venue-seat-tier-2 { background: #c2c63f; color: #23230a; }
.venue-seat-tier-3 { background: #0d7d52; color: #ffffff; }
.venue-seat-tier-4 { background: #0f6b78; color: #ffffff; }
.venue-seat-tier-5 { background: #3f6cc4; color: #ffffff; }

.venue-map-legend {
  display: flex; flex-wrap: wrap; gap: var(--sp-4);
  margin-top: var(--sp-3);
  font-size: var(--fs-12); color: var(--text-3);
}
.venue-map-legend span { display: inline-flex; align-items: center; gap: 6px; }
.venue-map-legend i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }

/* Подсказка о месте при наведении — крупнее и оформленнее нативного title. */
.venue-seat-tooltip {
  position: fixed;
  z-index: 50;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  color: var(--text-2);
  box-shadow: var(--shadow-modal);
  pointer-events: none;
  white-space: nowrap;
}
.venue-seat-tooltip strong { color: var(--text-1); font-size: var(--fs-14); }

.ui-venue-narrow-note { display: none; }
@media (max-width: 767px) {
  .ui-venue-narrow-note { display: block; }
  .ui-venue-editor-body { display: none; }
}

/* ===========================================================================
   Таблицы на узком экране. Фаза 5 редизайна.

   `Table({cards:true})` — строка становится карточкой: заголовок столбца
   переезжает внутрь ячейки (data-label), значение встаёт справа. Так действия
   строки остаются на виду, а не уезжают за правый край в прокрутку.
   =========================================================================== */

@media (max-width: 767px) {
  .ui-table--cards, .ui-table--cards tbody, .ui-table--cards tr, .ui-table--cards td {
    display: block; width: 100%;
  }
  .ui-table--cards thead { display: none; }
  .ui-table--cards tr {
    border-bottom: 1px solid var(--border);
    padding: var(--sp-3) var(--sp-4);
  }
  .ui-table--cards tr:last-child { border-bottom: none; }
  .ui-table--cards tbody tr:hover { background: transparent; }
  .ui-table--cards td {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-2) 0;
    border-bottom: none;
    text-align: left;
  }
  .ui-table--cards td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: var(--fs-12); color: var(--text-3);
  }
  /* Ячейка без подписи (столбец действий) занимает всю ширину: кнопке нужна
     строка целиком, чтобы попасть в тач-таргет 44px. */
  .ui-table--cards td:not([data-label]) { justify-content: stretch; }
  .ui-table--cards td:not([data-label]) > * { flex: 1 1 auto; }
  .ui-table--cards td.ui-num { text-align: right; }

  /* Тач-таргет: на пальце всё, по чему можно нажать, — не меньше 44px. */
  .ui-table--cards .ui-btn { min-height: 44px; }
  .ui-table--cards .ui-switch { min-height: 44px; }
}

/* ===========================================================================
   Уважение к настройке «меньше движения». Фаза 5 редизайна.

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

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

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .ui-spin { animation: ui-spin 1.8s linear infinite !important; }
  .ui-skel { animation: ui-skel 3s ease-in-out infinite !important; }
  /* Тост появляется сразу, без въезда снизу. */
  .ui-toast { animation: none !important; }
}

/* Подвал оболочки: имя площадки и домен. Живёт в той же колонке, что и
   основная область, ниже неё. */
.ui-footer {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-4);
  padding-bottom: calc(56px + var(--sp-6));   /* место под нижний таб-бар */
  font-size: var(--fs-12);
  color: var(--text-3);
}
.ui-footer a { color: var(--text-3); }
.ui-footer a:hover { color: var(--text); }
.ui-footer-sep { opacity: 0.5; }
/* Оговорка про Tari Labs в подвале публичных страниц — всегда своей строкой.
   flex-basis:100% с max-width не переносил её: клампнутая ширина умещалась
   рядом с доменом, и строка читалась как продолжение подписи. Отдельный
   контейнер на всю ширину решает это однозначно. */
.ui-footer-note {
  order: 10; width: 100%; flex: 0 0 100%;
  text-align: center; line-height: var(--lh-12);
}
.ui-footer-note > span { display: inline-block; max-width: 60ch; }
@media (min-width: 1024px) {
  .ui-footer { grid-column: 2; padding-bottom: var(--sp-6); }
  .ui-shell--nonav .ui-footer { grid-column: 1 / -1; }
}

/* ===========================================================================
   Фильтры афиши.
   =========================================================================== */

/* Панель — строка кнопок с выпадающими меню, а не развёрнутые списки: пять
   групп чипов занимали экран целиком и отодвигали саму афишу за сгиб. Что
   выбрано, видно на закрытой кнопке — раскрывать меню ради этого не нужно. */
.ui-filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
}

.ui-filter-dd { position: relative; }
.ui-filter-dd-btn {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 8px var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text-2);
  font: inherit; font-size: var(--fs-14); font-weight: var(--fw-medium);
  white-space: nowrap;
}
.ui-filter-dd-btn:hover { background: var(--surface-2); color: var(--text-1); }
.ui-filter-dd.is-set > .ui-filter-dd-btn {
  border-color: var(--primary);
  background: var(--primary-wash);
  color: var(--primary-ink);
}
/* Что выбрано — на самой кнопке, усечённо: длинное название площадки не должно
   растягивать панель на всю ширину. */
.ui-filter-dd-count {
  max-width: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: var(--fw-semibold);
}
.ui-filter-dd-caret { font-size: 10px; opacity: .7; }
.ui-filter-dd-btn[aria-expanded="true"] .ui-filter-dd-caret { transform: rotate(180deg); }

.ui-filter-dd-menu {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0;
  min-width: 260px; max-width: min(92vw, 420px);
  max-height: min(60vh, 420px); overflow-y: auto;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-modal);
}
/* У правого края экрана меню открывается влево, иначе уезжает за границу. */
.ui-filter-dd:last-of-type .ui-filter-dd-menu { left: auto; right: 0; }

.ui-filter-reset {
  appearance: none; cursor: pointer;
  padding: 8px var(--sp-3);
  border: 0; background: none;
  color: var(--primary-ink);
  font: inherit; font-size: var(--fs-14); font-weight: var(--fw-medium);
  text-decoration: underline; text-underline-offset: 3px;
}

.ui-filter-group { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.ui-filter-title {
  font-size: var(--fs-12); font-weight: var(--fw-semibold);
  color: var(--text-3); text-transform: uppercase; letter-spacing: .04em;
}
.ui-filter-prices, .ui-filter-dates { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.ui-filter-price { display: flex; flex-direction: column; gap: 4px; flex: 1 1 100px; min-width: 0; }
.ui-filter-price .ui-input { width: 100%; }

/* Активные фильтры: каждый снимается щелчком, поэтому это кнопки, а не метки. */
.ui-active-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.ui-active-chip {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 6px var(--sp-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--primary);
  background: var(--primary-wash);
  color: var(--primary-ink);
  font: inherit; font-size: var(--fs-12); font-weight: var(--fw-medium);
}
.ui-active-chip:hover { background: var(--surface-2); }

@media (max-width: 767px) {
  /* На телефоне тач-таргеты не меньше 44px, а меню разворачивается почти на всю
     ширину экрана: узкий выпадающий список на телефоне неудобен для пальца. */
  .ui-chip-filter, .ui-active-chip, .ui-filter-dd-btn { min-height: 44px; }
  .ui-filter-dd { position: static; }
  .ui-filter-dd-menu { left: var(--sp-3); right: var(--sp-3); min-width: 0; max-width: none; }
}
