/* ==========================================================================
   WebPhysics — библиотека компонентов
   Кнопки, карточки, формы, модальные окна, тосты, значки и т.п.
   ========================================================================== */

/* ======================================================================
   КНОПКИ
   ====================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 20px;
  border-radius: var(--r-full);
  font-size: 14.5px;
  font-weight: 620;
  letter-spacing: -.01em;
  white-space: nowrap;
  color: var(--text);
  background-color: var(--fill);
  /*
   * isolation создаёт собственный контекст наложения, чтобы слой заливки
   * (::before с z-index: -1) оставался внутри кнопки: он рисуется поверх
   * её фона, но под текстом.
   */
  position: relative;
  isolation: isolate;
  transition: transform var(--dur-fast) var(--ease-out),
              background-color .24s var(--ease-out),
              box-shadow .24s var(--ease-out),
              color .24s var(--ease-out),
              opacity var(--dur-fast) ease;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/*
 * Акцентная заливка вынесена в отдельный слой, а переключается её
 * прозрачность. Анимировать сам фон нельзя: браузер не интерполирует
 * переход «плоский цвет → градиент» (и даже два градиента, если их
 * цвета заданы разными функциями — rgba и color-mix). Из-за этого кнопка
 * «Далее» меняла вид скачком. Прозрачность же анимируется всегда.
 */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background-image: linear-gradient(160deg, color-mix(in srgb, var(--accent) 92%, #fff 8%), var(--accent));
  opacity: 0;
  pointer-events: none;
  transition: opacity .24s var(--ease-out), filter var(--dur-fast) ease;
}

.btn:hover { background-color: color-mix(in srgb, var(--fill) 70%, var(--text-4) 12%); }
.btn:active { transform: scale(.955); }
.btn:disabled, .btn.is-disabled { opacity: .45; pointer-events: none; }

.btn-primary {
  color: #fff;
  box-shadow: var(--shadow-accent);
}
.btn-primary::before { opacity: 1; }
/* Затемнение при наведении — через filter, он анимируется плавно */
.btn-primary:hover { background-color: var(--fill); }
.btn-primary:hover::before { filter: brightness(.93); }

.btn-ghost { background-color: transparent; color: var(--accent); }
.btn-ghost:hover { background-color: var(--accent-soft); }

.btn-glass {
  background-color: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(var(--glass-saturate));
  backdrop-filter: blur(18px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
}
/*
 * Стеклянная кнопка при наведении лишь притеняется. Без этой строки
 * срабатывало общее правило .btn:hover — оно подменяет background-color,
 * и стекло превращалось в плоское серое пятно.
 */
.btn-glass:hover { background-color: var(--glass-bg-strong); }

.btn-danger { background-color: color-mix(in srgb, var(--red) 15%, transparent); color: var(--red); }
.btn-danger:hover { background-color: color-mix(in srgb, var(--red) 24%, transparent); }

/*
 * Сплошная красная кнопка («Удалить» в диалогах подтверждения).
 * Своего :hover у неё не было, поэтому наведение перехватывало общее
 * правило .btn:hover — красный фон подменялся служебным серым, и кнопка
 * выглядела отключённой ровно в тот момент, когда на неё наводят.
 * Теперь она просто темнеет, оставаясь красной.
 */
.btn-danger-solid { background-color: var(--red); color: #fff; }
.btn-danger-solid:hover { background-color: color-mix(in srgb, var(--red) 82%, #000 18%); }
.btn-danger-solid:active { background-color: color-mix(in srgb, var(--red) 72%, #000 28%); }

.btn-sm { padding: 8px 14px; font-size: 13px; }
.btn-lg { padding: 14px 26px; font-size: 16px; }
.btn-block { width: 100%; }

/* Круглая иконочная кнопка — основной элемент управления в шапке */
.icon-btn {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: var(--r-full);
  color: var(--text-2);
  background: var(--fill-2);
  font-size: 17px;
  flex: none;
  transition: background var(--dur-fast) ease, transform var(--dur-fast) var(--ease-out), color var(--dur-fast) ease;
  -webkit-tap-highlight-color: transparent;
}
/*
 * При наведении кнопка лишь слегка притеняется, а не «загорается» серым:
 * прежний --fill в тёмной теме давал заметное серое пятно.
 */
.icon-btn:hover { background: var(--hover-tint); color: var(--text); }
.icon-btn:active { transform: scale(.9); }
.icon-btn.is-active { background: var(--accent-soft); color: var(--accent); }
.icon-btn svg { width: 19px; height: 19px; }

/*
 * Опасное действие (корзина в таблицах админки).
 * Общее правило .icon-btn:hover перекрашивает значок в цвет текста —
 * красная корзина при наведении становилась обычной серой. Здесь она,
 * наоборот, темнеет и подсвечивается красным фоном.
 */
.icon-btn.is-danger { color: var(--red); }
.icon-btn.is-danger:hover {
  background: color-mix(in srgb, var(--red) 14%, transparent);
  color: color-mix(in srgb, var(--red) 80%, #000 20%);
}

/* ======================================================================
   КАРТОЧКИ
   ====================================================================== */
.card {
  background: var(--bg-elevated);
  border-radius: var(--r-lg);
  border: 1px solid var(--separator);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) ease, border-color var(--dur) ease;
}
.card-glass {
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-md);
}
.card-pad { padding: 20px; }
.card-hover:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
.card-link { display: block; color: inherit; text-decoration: none; }

.section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 19px;
  font-weight: 680;
  letter-spacing: -.02em;
  margin-bottom: 14px;
}
.section-title .st-more { margin-left: auto; font-size: 13.5px; font-weight: 600; color: var(--accent); }

/* ======================================================================
   ФОРМЫ
   ====================================================================== */
.field { display: flex; flex-direction: column; gap: 7px; }
.field-label { font-size: 13px; font-weight: 620; color: var(--text-2); padding-left: 4px; }
.field-hint { font-size: 12.5px; color: var(--text-4); padding-left: 4px; }
.field-error { font-size: 12.5px; color: var(--red); padding-left: 4px; }

.input, .textarea, .select {
  width: 100%;
  padding: 13px 16px;
  border-radius: var(--r-sm);
  background: var(--fill-2);
  border: 1.5px solid transparent;
  color: var(--text);
  font-size: 15px;
  transition: border-color var(--dur-fast) ease, background var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
  appearance: none;
}
.input::placeholder, .textarea::placeholder { color: var(--text-4); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--bg-elevated);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.input.is-invalid, .textarea.is-invalid { border-color: var(--red); }
.textarea { resize: vertical; min-height: 120px; line-height: 1.55; }
.textarea.code { font-family: var(--font-mono); font-size: 13px; line-height: 1.6; }

/* ======================================================================
   СВОЙ ВЫПАДАЮЩИЙ СПИСОК
   ----------------------------------------------------------------------
   Системный <select> выглядит по-разному в каждой ОС и не поддаётся
   оформлению, поэтому весь выбор в интерфейсе построен на этом компоненте.
   Список рисуется в отдельном слое (position: fixed), чтобы его не обрезали
   контейнеры с прокруткой.
   ====================================================================== */
.dropdown {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  min-width: 170px;
  border-radius: var(--r-sm);
  background: var(--fill-2);
  border: 1.5px solid transparent;
  font-size: 14.5px;
  font-weight: 520;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  transition: background var(--dur-fast) ease, border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.dropdown:hover { background: var(--fill); }
.dropdown.is-open {
  border-color: var(--accent);
  background: var(--bg-elevated);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.dropdown .dd-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dropdown .dd-caret {
  flex: none;
  color: var(--text-4);
  transition: transform var(--dur) var(--ease-out);
}
.dropdown.is-open .dd-caret { transform: rotate(180deg); }
.dropdown.is-block { display: flex; width: 100%; }

.dd-menu {
  position: fixed;
  z-index: 260;
  min-width: 200px;
  max-height: min(340px, 60dvh);
  overflow-y: auto;
  padding: 6px;
  border-radius: var(--r-md);
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(34px) saturate(var(--glass-saturate));
  backdrop-filter: blur(34px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-lg);
  animation: dd-in .2s var(--ease-out) both;
  transform-origin: top center;
}
.dd-menu.is-up { transform-origin: bottom center; }
@keyframes dd-in {
  from { opacity: 0; transform: translateY(-6px) scale(.97); }
  to { opacity: 1; transform: none; }
}
.dd-menu.is-closing { animation: dd-out .14s ease both; }
@keyframes dd-out { to { opacity: 0; transform: translateY(-4px) scale(.98); } }

.dd-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 13px;
  border-radius: var(--r-xs);
  font-size: 14.2px;
  font-weight: 520;
  color: var(--text-2);
  text-align: left;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.dd-option:hover,
.dd-option.is-cursor { background: var(--fill-2); color: var(--text); }
.dd-option.is-selected { color: var(--accent); font-weight: 640; background: var(--accent-soft); }
.dd-option .dd-check { margin-left: auto; opacity: 0; flex: none; }
.dd-option.is-selected .dd-check { opacity: 1; }
.dd-option .dd-icon { flex: none; width: 20px; text-align: center; }

.input-group { position: relative; }
.input-group .input { padding-left: 44px; }
.input-group .ig-icon {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  color: var(--text-4); pointer-events: none; font-size: 15px;
}
.input-group .ig-action {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
}
/*
 * Место под кнопку справа («глазик» у пароля).
 * Без этого отступа длинное значение заезжало прямо под неё: слева
 * padding в 44 px под иконку был, справа — не было вовсе, и последние
 * символы пароля читались сквозь кнопку.
 *
 * Класс ставится скриптом (см. field() в pages/auth.js), а не через :has() —
 * правило простое, а поддержка получается шире.
 */
.input-group.has-action .input { padding-right: 52px; }
/*
 * Полосу прокрутки внутри поля прячем: она бы «прыгала» по вертикали
 * в однострочном поле. Само содержимое при этом прокручивается —
 * жестами, см. makeScrollableWhenBlurred() в pages/auth.js.
 */
.input-group.has-action .input { scrollbar-width: none; }
.input-group.has-action .input::-webkit-scrollbar { height: 0; }

/* Переключатель в стиле iOS */
.switch { position: relative; display: inline-block; width: 51px; height: 31px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.switch .track {
  position: absolute; inset: 0;
  background: var(--fill);
  border-radius: var(--r-full);
  transition: background var(--dur) var(--ease-out);
  cursor: pointer;
}
.switch .track::before {
  content: "";
  position: absolute;
  width: 27px; height: 27px;
  left: 2px; top: 2px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 2px 6px rgba(0,0,0,.2);
  transition: transform var(--dur) var(--ease-spring), width var(--dur-fast) ease;
}
.switch input:checked + .track { background: var(--green); }
.switch input:checked + .track::before { transform: translateX(20px); }
.switch input:active + .track::before { width: 31px; }
.switch input:checked:active + .track::before { transform: translateX(16px); }
.switch input:focus-visible + .track { outline: 2.5px solid var(--accent); outline-offset: 3px; }

/* --------------------------------------------------------------------------
   Строка настройки
   --------------------------------------------------------------------------
   Значок вынесен в собственную серую плашку такого же вида, как у заголовков
   групп, и отделён от текста заметным отступом: раньше маленькая иконка
   вплотную к подписи читалась как часть описания, а не как значок строки.
   -------------------------------------------------------------------------- */
.setting-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--separator);
}
.setting-row:last-child { border-bottom: none; }
.setting-row .sr-info { flex: 1; min-width: 0; }
.setting-row .sr-title { font-size: 15px; font-weight: 580; }
.setting-row .sr-desc { font-size: 12.8px; color: var(--text-4); margin-top: 3px; line-height: 1.45; }
.setting-row .sr-icon {
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 19px;
  line-height: 1;
  background: var(--fill);
  border: 1px solid var(--separator);
}

/* Ползунок */
.slider-wrap { display: flex; align-items: center; gap: 14px; }
.slider {
  -webkit-appearance: none; appearance: none;
  flex: 1;
  height: 6px;
  border-radius: var(--r-full);
  background: var(--fill);
  outline: none;
  cursor: pointer;
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: #fff;
  border: none;
  box-shadow: 0 1px 4px rgba(0,0,0,.2), 0 4px 12px rgba(0,0,0,.15);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.slider::-webkit-slider-thumb:active { transform: scale(1.18); }
.slider::-moz-range-thumb {
  width: 26px; height: 26px; border-radius: 50%;
  background: #fff; border: none;
  box-shadow: 0 1px 4px rgba(0,0,0,.2), 0 4px 12px rgba(0,0,0,.15);
}
.slider-value {
  min-width: 62px; text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 13.5px; font-weight: 620; color: var(--text-3);
}

/*
 * Сегментированный переключатель.
 * max-width и прокрутка обязательны: на узком экране элемент с нерастяжимыми
 * подписями раздвигал всю страницу и появлялась горизонтальная прокрутка
 * документа. Теперь при нехватке места прокручивается сам переключатель.
 */
.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--fill-2);
  border-radius: var(--r-full);
  position: relative;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.segmented::-webkit-scrollbar { display: none; }
.segmented button {
  flex: none;
  padding: 8px 16px;
  border-radius: var(--r-full);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-3);
  transition: color var(--dur-fast) ease, background var(--dur) var(--ease-out);
  white-space: nowrap;
}
.segmented button.is-active {
  background: var(--bg-elevated);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}
.segmented.is-block { display: flex; width: 100%; }
.segmented.is-block button { flex: 1; }

/* Выбор цвета */
.color-picker-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/*
 * Кружки выбора цвета.
 * Рамки нет намеренно: прозрачная рамка не обрезала фон (background-clip
 * по умолчанию — border-box), из-за чего у образца «по умолчанию»
 * диагональная заливка вылезала под кромку и края выглядели рваными.
 * И разделительное кольцо, и выделение выбранного рисуются тенями —
 * они не влияют на геометрию и не искажают заливку.
 */
.color-swatch {
  width: 34px; height: 34px;
  border-radius: 50%;
  border: none;
  padding: 0;
  box-shadow: inset 0 0 0 1px var(--separator);
  transition: transform var(--dur-fast) var(--ease-spring), box-shadow var(--dur-fast) ease;
}
.color-swatch:hover { transform: scale(1.12); }
.color-swatch.is-active {
  transform: scale(1.12);
  box-shadow: inset 0 0 0 1px var(--separator), 0 0 0 2.5px var(--accent);
}
/*
 * «Свой цвет» — такой же кружок, как готовые образцы, но внутри него живёт
 * системный input[type=color]. Сам input не пересоздаётся при выборе,
 * иначе окно палитры закрывалось бы сразу после открытия.
 */
.color-swatch.is-custom {
  position: relative;
  padding: 0;
  overflow: hidden;
  cursor: pointer;
  background: conic-gradient(from 210deg, #ff375f, #ff9f0a, #ffd60a, #30d158, #0a84ff, #bf5af2, #ff375f);
}
.color-input {
  position: absolute;
  inset: -6px;
  width: calc(100% + 12px);
  height: calc(100% + 12px);
  border: none;
  padding: 0;
  background: none;
  cursor: pointer;
  opacity: 0;
}
.color-input::-webkit-color-swatch-wrapper { padding: 0; }
.color-input::-webkit-color-swatch { border: none; }

/* ======================================================================
   ЧИПЫ, ЗНАЧКИ, ТЕГИ
   ====================================================================== */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px;
  border-radius: var(--r-full);
  background: var(--fill-2);
  font-size: 13px; font-weight: 570;
  color: var(--text-2);
  white-space: nowrap;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, transform var(--dur-fast) var(--ease-out);
}
.chip:hover { background: var(--fill); }
.chip:active { transform: scale(.95); }
.chip.is-active { background: var(--accent); color: #fff; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3.5px 9px;
  border-radius: var(--r-full);
  font-size: 11.5px; font-weight: 680;
  letter-spacing: .01em;
  background: var(--fill);
  color: var(--text-3);
}
.badge-accent { background: var(--accent-soft); color: var(--accent); }
.badge-green { background: color-mix(in srgb, var(--green) 16%, transparent); color: var(--green); }
.badge-orange { background: color-mix(in srgb, var(--orange) 16%, transparent); color: var(--orange); }
.badge-red { background: color-mix(in srgb, var(--red) 16%, transparent); color: var(--red); }
.badge-purple { background: color-mix(in srgb, var(--purple) 16%, transparent); color: var(--purple); }

/* --------------------------------------------------------------------------
   «Бегущая строка» для длинных имён
   --------------------------------------------------------------------------
   Класс is-scrolling ставится из JS только при реальном переполнении,
   поэтому короткие имена никуда не едут (см. marqueeName в ui/components.js).
   -------------------------------------------------------------------------- */
.name-marquee {
  position: relative;
  overflow: hidden;
  min-width: 0;
  flex: 1;
  white-space: nowrap;
}
.name-marquee .nm-text {
  display: inline-block;
  white-space: nowrap;
  will-change: transform;
}
.name-marquee.is-scrolling .nm-text {
  animation: name-marquee var(--marquee-time, 8s) ease-in-out infinite alternate;
  animation-delay: 1.2s;
}
@keyframes name-marquee {
  0%, 12%   { transform: translateX(0); }
  88%, 100% { transform: translateX(var(--marquee-shift, 0)); }
}
html[data-reduce-motion="1"] .name-marquee.is-scrolling .nm-text { animation: none; }

.tag-list { display: flex; flex-wrap: wrap; gap: 6px; }
.tag { font-size: 11.5px; padding: 3px 9px; border-radius: var(--r-full); background: var(--fill-2); color: var(--text-4); }

/* Аватар */
.avatar {
  border-radius: 50%;
  display: grid; place-items: center;
  font-weight: 700;
  color: #fff;
  flex: none;
  user-select: none;
  box-shadow: inset 0 1px 1px rgba(255,255,255,.35);
}

/* ======================================================================
   ПРОГРЕСС
   ====================================================================== */
.progress {
  height: 8px;
  border-radius: var(--r-full);
  background: var(--fill);
  overflow: hidden;
}
.progress .bar {
  height: 100%;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 45%, #64d2ff));
  transition: width .7s var(--ease-out);
}
.progress.is-green .bar { background: linear-gradient(90deg, var(--green), color-mix(in srgb, var(--green) 60%, #a7f3a0)); }
.progress.is-orange .bar { background: linear-gradient(90deg, var(--orange), var(--yellow)); }

.ring { transform: rotate(-90deg); }
.ring circle { fill: none; stroke-linecap: round; }
.ring .ring-bg { stroke: var(--fill); }
/* Заполнение кольца анимируется через Web Animations API (см. ui/components.js),
   поэтому CSS-переход здесь не нужен — он бы конфликтовал с анимацией. */
.ring .ring-fg { stroke: var(--accent); }

/* ======================================================================
   МОДАЛЬНЫЕ ОКНА (вместо alert / confirm / prompt)
   ====================================================================== */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  padding: 20px;
  background: rgba(0,0,0,.45);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  animation: fade-in var(--dur) ease both;
}
.modal-backdrop.is-closing { animation: fade-out var(--dur-fast) ease both; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }

.modal {
  width: min(480px, 100%);
  max-height: min(86dvh, 900px);
  display: flex;
  flex-direction: column;
  border-radius: var(--r-xl);
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(40px) saturate(var(--glass-saturate));
  backdrop-filter: blur(40px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: modal-in .42s var(--ease-spring) both;
}
.modal.is-wide { width: min(880px, 100%); }
.modal.is-closing { animation: modal-out var(--dur-fast) var(--ease-out) both; }
@keyframes modal-in {
  from { opacity: 0; transform: translateY(24px) scale(.94); }
  to { opacity: 1; transform: none; }
}
@keyframes modal-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translateY(10px) scale(.97); }
}

.modal-head { padding: 26px 26px 0; text-align: center; }
.modal-icon {
  width: 58px; height: 58px;
  margin: 0 auto 14px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 27px;
  background: var(--accent-soft);
}
.modal-icon.is-danger { background: color-mix(in srgb, var(--red) 16%, transparent); }
.modal-icon.is-success { background: color-mix(in srgb, var(--green) 16%, transparent); }
.modal-icon.is-warning { background: color-mix(in srgb, var(--orange) 16%, transparent); }
.modal-title { font-size: 20px; font-weight: 700; letter-spacing: -.02em; }
.modal-text { margin-top: 8px; color: var(--text-3); font-size: 14.5px; line-height: 1.55; }
.modal-body { padding: 20px 26px; overflow-y: auto; flex: 1; }
.modal-foot {
  padding: 8px 20px 20px;
  display: flex; gap: 10px;
  flex-wrap: wrap;
}
.modal-foot .btn { flex: 1; min-width: 130px; }

/* На большом экране окно обычное — ручка шторки не нужна */
.modal-grab { display: none; }

/* Нижняя шторка на мобильных — привычнее пальцу */
@media (max-width: 560px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal {
    width: 100%;
    max-height: 92dvh;
    border-radius: var(--r-2xl) var(--r-2xl) 0 0;
    padding-bottom: var(--safe-bottom);
    animation: sheet-in .44s var(--ease-out) both;
  }
  .modal.is-closing { animation: sheet-out var(--dur) var(--ease-out) both; }
  @keyframes sheet-in { from { transform: translateY(100%); } to { transform: none; } }
  @keyframes sheet-out { from { transform: none; } to { transform: translateY(100%); } }
  /*
   * Ручка шторки. Это настоящий элемент, а не ::after: за него тянут вниз,
   * чтобы закрыть окно (обработчики в core/ui.js). Зона захвата намеренно
   * выше самой полоски — попасть пальцем в 5 px невозможно.
   */
  .modal .modal-grab {
    display: block;
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 28px;
    z-index: 2;
    cursor: grab;
    /* Жест забираем себе целиком, иначе браузер начнёт тянуть страницу */
    touch-action: none;
  }
  .modal .modal-grab::after {
    content: ""; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
    width: 38px; height: 5px; border-radius: 99px; background: var(--fill);
  }
  .modal .modal-grab:active { cursor: grabbing; }
  /* Шапку тоже можно тянуть — так шторка ощущается цельной */
  .modal .modal-head { padding-top: 30px; touch-action: none; }
}

/* ======================================================================
   ТОСТЫ
   ====================================================================== */
.toast-root {
  position: fixed;
  z-index: 300;
  bottom: calc(20px + var(--safe-bottom));
  right: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  pointer-events: none;
  max-width: min(380px, calc(100vw - 40px));
}
@media (max-width: 1023px) {
  .toast-root { bottom: calc(18px + var(--safe-bottom)); left: 14px; right: 14px; max-width: none; }
}

.toast {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 18px;
  border-radius: var(--r-lg);
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(34px) saturate(var(--glass-saturate));
  backdrop-filter: blur(34px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-lg);
  pointer-events: auto;
  animation: toast-in .46s var(--ease-spring) both;
}
.toast.is-closing { animation: toast-out .3s var(--ease-out) both; }
@keyframes toast-in { from { opacity: 0; transform: translateY(18px) scale(.92); } to { opacity: 1; transform: none; } }
@keyframes toast-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(24px) scale(.96); } }
.toast .t-icon { font-size: 20px; flex: none; }
.toast .t-body { flex: 1; min-width: 0; }
.toast .t-title { font-size: 14px; font-weight: 640; }
.toast .t-text { font-size: 12.8px; color: var(--text-3); margin-top: 1px; }
.toast.is-success .t-icon { color: var(--green); }
.toast.is-error .t-icon { color: var(--red); }
.toast.is-achievement {
  border-color: color-mix(in srgb, var(--yellow) 45%, transparent);
  box-shadow: var(--shadow-lg), 0 0 32px color-mix(in srgb, var(--yellow) 28%, transparent);
}

/* ======================================================================
   COOKIE-ПЛАШКА
   ====================================================================== */
.cookie-banner {
  position: fixed;
  z-index: 250;
  left: 16px; right: 16px;
  bottom: calc(16px + var(--safe-bottom));
  max-width: 520px;
  margin: 0 auto;
  padding: 20px 22px;
  border-radius: var(--r-xl);
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(34px) saturate(var(--glass-saturate));
  backdrop-filter: blur(34px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-lg);
  animation: cookie-in .6s .8s var(--ease-spring) both;
}
@keyframes cookie-in { from { opacity: 0; transform: translateY(40px) scale(.94); } to { opacity: 1; transform: none; } }
.cookie-banner.is-closing { animation: toast-out .35s var(--ease-out) both; }
.cookie-banner .cb-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.cookie-banner .cb-title { font-size: 16px; font-weight: 680; }
.cookie-banner .cb-text { font-size: 13.4px; color: var(--text-3); line-height: 1.55; }
.cookie-banner .cb-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.cookie-banner .cb-actions .btn { flex: 1; min-width: 120px; }
@media (max-width: 1023px) { .cookie-banner { bottom: calc(16px + var(--safe-bottom)); } }

/* ======================================================================
   СКЕЛЕТОНЫ И ПУСТЫЕ СОСТОЯНИЯ
   ====================================================================== */
.skeleton {
  background: linear-gradient(100deg, var(--fill-2) 30%, var(--fill) 50%, var(--fill-2) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.5s linear infinite;
  border-radius: var(--r-sm);
}
@keyframes shimmer { from { background-position: 180% 0; } to { background-position: -80% 0; } }
.skeleton-card { height: 190px; border-radius: var(--r-lg); }
.skeleton-line { height: 13px; margin-bottom: 9px; }

.empty-state {
  text-align: center;
  padding: 56px 24px;
  color: var(--text-3);
}
.empty-state .es-emoji { font-size: 52px; margin-bottom: 14px; opacity: .9; }
.empty-state .es-title { font-size: 18px; font-weight: 660; color: var(--text); margin-bottom: 6px; }
.empty-state .es-text { font-size: 14px; max-width: 42ch; margin: 0 auto 20px; }

/* Спиннер */
.spinner {
  width: 20px; height: 20px;
  border: 2.5px solid var(--fill);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ======================================================================
   ХЛЕБНЫЕ КРОШКИ И ПРОЧЕЕ
   ====================================================================== */
.breadcrumbs { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text-4); margin-bottom: 14px; flex-wrap: wrap; }
.breadcrumbs a { color: var(--text-3); }
.breadcrumbs a:hover { color: var(--accent); }

.divider { height: 1px; background: var(--separator); margin: 22px 0; border: none; }

/* --------------------------------------------------------------------------
   Всплывающая подсказка
   --------------------------------------------------------------------------
   По умолчанию раскрывается вверх. Для элементов в самой верхней панели
   это не годится — подсказка уезжала за край экрана, поэтому у них
   используется модификатор .tip-below.
   -------------------------------------------------------------------------- */
.tip { position: relative; }
.tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) scale(.9);
  padding: 6px 11px;
  border-radius: 9px;
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(18px) saturate(var(--glass-saturate));
  backdrop-filter: blur(18px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-md);
  color: var(--text);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  z-index: 80;
  transition: opacity var(--dur-fast) ease, transform var(--dur-fast) var(--ease-out);
}
.tip:hover::after,
.tip:focus-visible::after { opacity: 1; transform: translateX(-50%) scale(1); }

.tip-below::after { bottom: auto; top: calc(100% + 8px); }

@media (hover: none) { .tip::after { display: none; } }
