/* Основа экрана: палитра, шкала размеров, блочная сетка, карточки списка.

   Тема одна, а не две. Всё, что зависит от темы — рамка, «открыто»/«устарел»,
   приглушённый текст, — выводится color-mix-ом из самих --tg-theme-* (из --text
   и --card), поэтому тёмная тема Telegram на светлой ОС и наоборот выглядят
   правильно. Своих цветов текста и фона нет.

   Тёмная — база ВЕЗДЕ, включая Telegram с любой темой клиента (слово
   заказчика 20.08: «всё равно тёмную»). Светлую включает только человек.
   Тему ОС не спрашиваем совсем: браузер не отличает «ОС просит светлую»
   от «предпочтений нет» — (prefers-color-scheme: light) матчится в обоих
   случаях, и светлая стала бы фактическим умолчанием.

   Решает атрибут data-theme на <html>: до первой отрисовки его ставит
   inline-скрипт в <head> безусловно, дальше им управляет theme.js. Ветки
   [data-theme] перекрывают сами токены, а не --fb-*, — поэтому --tg-theme-*
   в них не попадают и тема клиента фон и текст не красит. Из палитры клиента
   остаётся один акцент — заливка кнопки, см. --button-base ниже.

   --fb-* нужны единственному случаю, когда атрибута нет: выключенный js.
   Они тоже тёмные, так что база держится и там. */
:root {
  /* Две палитры целиком. Обе объявлены всегда: выбор темы не пересобирает
     значения, а переставляет ссылки на готовые наборы. */
  --dark-bg: #141518;
  --dark-card: #212227;
  --dark-text: #f3f3f5;
  --dark-hint: #a2a2ab;        /* 6,3:1 к карточке тёмной темы */
  --dark-link: #7cb8f5;
  --dark-button: #3390ec;
  --dark-button-text: #ffffff;
  --dark-border: rgba(255, 255, 255, 0.12);
  --dark-ok: #56cf80;
  --dark-warn: #e8b44e;
  --dark-bad: #f0776a;

  --light-bg: #f2f3f5;
  --light-card: #ffffff;
  --light-text: #16171a;
  --light-hint: #6a6a70;       /* 5,4:1 к белому — служебное остаётся читаемым */
  --light-link: #1f6fb2;
  --light-button: #2481cc;
  --light-button-text: #ffffff;
  --light-border: rgba(0, 0, 0, 0.10);
  --light-ok: #1a7f45;
  --light-warn: #8a5d0b;
  --light-bad: #b3271a;

  /* Фолбэки для обычного браузера — тёмный набор, он же база */
  --fb-bg: var(--dark-bg);
  --fb-card: var(--dark-card);
  --fb-text: var(--dark-text);
  --fb-hint: var(--dark-hint);
  --fb-link: var(--dark-link);
  --fb-button: var(--dark-button);
  --fb-button-text: var(--dark-button-text);
  --fb-border: var(--dark-border);
  /* --fb-ok/warn/bad нужны webview без color-mix: там смысловые цвета берутся
     отсюда, а не выводятся из --text */
  --fb-ok: var(--dark-ok);
  --fb-warn: var(--dark-warn);
  --fb-bad: var(--dark-bad);

  --bg: var(--tg-theme-secondary-bg-color, var(--fb-bg));
  --card: var(--tg-theme-bg-color, var(--fb-card));
  --text: var(--tg-theme-text-color, var(--fb-text));
  --hint: var(--tg-theme-hint-color, var(--fb-hint));
  --link: var(--tg-theme-link-color, var(--fb-link));
  --button-text: var(--tg-theme-button-text-color, var(--fb-button-text));

  /* Заливка кнопки притемняется на 20 % от цвета клиента — по той же причине,
     по которой заведён --link-strong: чистый --tg-theme-button-color даёт белой
     подписи 3,3:1, а нажатый чип и кнопка «Повторить» — это текст. Оттенок
     клиента сохраняется, читаемость появляется: 4,9–6,0:1 во всех состояниях.
     Первая строка — фолбэк для webview без color-mix. */
  --button-base: var(--tg-theme-button-color, var(--fb-button));
  --button: var(--button-base);
  --button: color-mix(in srgb, var(--button-base) 80%, black);

  /* Рамка — доля основного текста, а не серый по медиазапросу: на светлой теме
     выходит тёмный волосок, на тёмной — светлый, в любой комбинации тем. */
  --border: var(--fb-border);
  --border: color-mix(in srgb, var(--text) 13%, transparent);

  /* Служебный текст, который всё-таки надо прочесть: подтягиваем к основному,
     чтобы контраст держался и на светлом hint самого Telegram.
     Первая строка — фолбэк для webview без color-mix. */
  --hint-strong: var(--hint);
  --hint-strong: color-mix(in srgb, var(--hint) 62%, var(--text));

  /* То же для мелкого текста цветом ссылки: голубой Telegram даёт 3,3:1 к белому,
     а подпись 11–13 px обязана читаться. Крупное и нетекстовое (линия графика,
     метки карты, обводка фокуса) остаётся на чистом --link. */
  --link-strong: var(--link);
  --link-strong: color-mix(in srgb, var(--link) 70%, var(--text));

  /* Смысловые цвета: оттенок задаётся базой и от темы не зависит, светлота —
     доля основного текста. На светлой теме зелёный темнеет, на тёмной светлеет,
     и «открыто»/«курс устарел»/«закрыто» держат ≥4,5:1 во всех четырёх
     сочетаниях тем (Telegram светлая/тёмная × ОС светлая/тёмная). */
  /* Линия блю на графике: свой цвет, не занятый смыслом. Зелёный уже значит
     «лучший и свежий», синий — навигацию, янтарный — «протухло». Блю берёт
     тёплый песочный: он различим и на светлой, и на тёмной теме, а вместе
     с пунктиром читается даже теми, кто не различает цвета. */
  --rate-blue-base: #d9a441;
  --ok-base: #21b562;
  --warn-base: #d99414;
  --bad-base: #e2503f;
  --ok: var(--fb-ok);
  --warn: var(--fb-warn);
  --bad: var(--fb-bad);
  --ok: color-mix(in srgb, var(--ok-base) 68%, var(--text));
  --rate-blue: var(--rate-blue-base);
  --rate-blue: color-mix(in srgb, var(--rate-blue-base) 72%, var(--text));
  --warn: color-mix(in srgb, var(--warn-base) 68%, var(--text));
  --bad: color-mix(in srgb, var(--bad-base) 68%, var(--text));

  /* Одна типографская шкала на все четыре файла стилей */
  --fs-xs: 11px;
  --fs-sm: 13px;
  --fs-md: 15px;
  --fs-lg: 17px;
  --fs-xl: 20px;
  --fs-money: 26px;

  /* Один ритм отступов */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;

  /* Одни скругления и тени у всех блоков. Тень намеренно одна на обе темы:
     она декоративная, а отделяет блок от фона рамка. Тень, привязанная
     к медиазапросу ОС, была вторым переключателем темы — его больше нет. */
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 18px;
  --r-pill: 999px;
  --bw: 1px;
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.06), 0 6px 16px rgba(0, 0, 0, 0.07);
  --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.12), 0 12px 28px rgba(0, 0, 0, 0.10);
  --tap: 40px;                 /* минимальная высота пальцевой цели */
}

/* Ветка темы — сильнее всего, включая тему клиента Telegram: здесь перекрываются
   сами токены, поэтому var(--tg-theme-*) в эту ветку не попадает (кроме одного
   акцента — --button-base). Атрибут стоит всегда, значит всегда работает и одна
   из этих двух веток.
   Производные (--border, --hint-strong, --link-strong, --ok/--warn/--bad)
   не дублируются: они объявлены выше через var(--text)/var(--hint)/var(--link)
   и пересчитываются от перекрытых значений сами. Из --fb-* переопределяются
   только смысловые и рамка — их первая строка-фолбэк для webview без color-mix.
   color-scheme отдаёт браузеру нативные списки <select> и полосы прокрутки
   в цвете темы. Он объявлен здесь и только здесь: js его не дублирует. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: var(--dark-bg);
  --card: var(--dark-card);
  --text: var(--dark-text);
  --hint: var(--dark-hint);
  --link: var(--dark-link);
  /* Единственное, что осталось от палитры клиента: оттенок кнопки. Он проходит
     сквозь ветку темы, притемняется теми же 80 % и держит контраст с белой
     подписью на обеих штатных палитрах Telegram (4,90 и 5,42:1). */
  --button-base: var(--tg-theme-button-color, var(--dark-button));
  --button-text: var(--dark-button-text);
  --fb-border: var(--dark-border);
  --fb-ok: var(--dark-ok);
  --fb-warn: var(--dark-warn);
  --fb-bad: var(--dark-bad);
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg: var(--light-bg);
  --card: var(--light-card);
  --text: var(--light-text);
  --hint: var(--light-hint);
  --link: var(--light-link);
  --button-base: var(--tg-theme-button-color, var(--light-button));
  --button-text: var(--light-button-text);
  --fb-border: var(--light-border);
  --fb-ok: var(--light-ok);
  --fb-warn: var(--light-warn);
  --fb-bad: var(--light-bad);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--text);
  /* Системный шрифт: ничего с CDN не подключается */
  font-family: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: var(--fs-lg);
  line-height: 1.45;
  -webkit-text-size-adjust: 100%;
}
.app {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--sp-3) var(--sp-3) calc(var(--sp-5) + env(safe-area-inset-bottom));
}

/* Фокус виден на всём, что можно нажать: карточки, строки топа, чипы, поля,
   ссылки. Одно правило вместо точечных заплаток. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 2px;
}

/* --- блок: топ, график, фильтры, карта, баннер оформлены одинаково -------- */
.dash-block,
.filters,
.map,
.banner,
.state,
.card {
  background: var(--card);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  padding: var(--sp-3);
  margin: 0 0 var(--sp-3);
}

/* Заголовок блока — один приём на весь экран (G19): крупно, цветом текста,
   акцентная полоска слева. Был мелкой капс-подписью цветом подсказки — заказчик
   его не видел. Хвост .dash-title-pair — выбранная валюта тем же цветом, что
   селектор в шапке: «Топ обменников · USD» меняется вместе с валютой, разделитель
   « · » рисуется только при непустом хвосте. */
.dash-title {
  position: relative;
  margin: 0 0 var(--sp-3);
  padding-left: var(--sp-3);
  font-size: var(--fs-lg); font-weight: 700; line-height: 1.25;
  color: var(--text);
}
.dash-title::before {
  content: ""; position: absolute; left: 0; top: 2px; bottom: 2px;
  width: 3px; border-radius: var(--r-pill); background: var(--link);
}
.dash-title-pair { color: var(--link-strong); white-space: nowrap; }
.dash-title-pair:not(:empty)::before {
  content: " · "; color: var(--hint-strong); font-weight: 600;
}

/* --- ряд бренда: имя, лента, меню (G37) ---------------------------------- */
.brand-bar { padding: var(--sp-1) 0 0; }

/* --- калькулятор: главный ввод экрана ------------------------------------ */
/* Липкая обёртка держит непрозрачный фон — без него под карточкой просвечивал
   бы уезжающий список. Сама карточка внутри: так «Сколько получу» читается
   отдельным блоком, а не продолжением графика над ним (G37). */
.calc {
  position: sticky; top: 0; z-index: 5;
  background: var(--bg);
  padding: var(--sp-2) 0 var(--sp-3);
}
.calc-box {
  background: var(--card);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  /* По бокам скупо: карточка забирает ширину у поля суммы, и на 360 px
     placeholder «Сумма» обрезался до «Сум» (живой случай 21.08) */
  padding: var(--sp-3) var(--sp-2);
}
/* Брендовая плашка (G34): имя — линия курса — меню. Линия та же, что на
   заставке: знак и индикатор это одно и то же. Рисуется один раз при
   открытии, дальше стоит — постоянное движение в липкой шапке спорит
   с цифрами, за которыми человек и пришёл. */
.brand {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) 0;
}
.brand-word {
  flex: 0 0 auto;
  font-size: var(--fs-md); font-weight: 800;
  letter-spacing: 2px; text-transform: uppercase; color: var(--text);
}
.brand-chart { flex: 1 1 auto; min-width: 0; height: 24px; overflow: hidden; }
/* Лента едет бесконечно: два одинаковых куска подряд, сдвиг ровно на длину
   куска — шва не видно. Двигается transform, поэтому перерисовки макета нет
   и батарею это не ест. Замершая линия читалась как поломка (слово заказчика). */
.brand-tape { animation: brandTape 14s linear infinite; }
@keyframes brandTape { to { transform: translateX(-120px) } }
.brand-line {
  fill: none; stroke: var(--ok); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.brand-menu { box-shadow: none; }
@media (prefers-reduced-motion: reduce) { .brand-tape { animation: none } }

/* Строка заголовка: «Сколько получу» слева, переключатель темы справа */
.calc-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.calc-title { margin: 0; }
.calc-row { display: flex; gap: var(--sp-2); }
.calc-amount {
  /* Сжимается первым: валюта и кнопка ужиматься не должны — там режется текст */
  flex: 1 1 60px; min-width: 0;
  height: 52px;
  font-size: var(--fs-xl); font-weight: 700;
  font-variant-numeric: tabular-nums;
  padding: 0 var(--sp-3);
  border: var(--bw) solid var(--border); border-radius: var(--r-md);
  background: var(--card); color: var(--text);
  box-shadow: var(--shadow-1);
}
/* Подсказка мельче цифр: она набрана тем же крупным кеглем, что и сумма,
   и на 360 px «Сумма» не влезала в поле. Введённое число остаётся крупным. */
.calc-amount::placeholder { color: var(--hint); font-weight: 400; font-size: var(--fs-md); }
/* Валюта — акцентом: тот же цвет у хвостов заголовков «· USD» ниже, так видно,
   что выбор здесь задаёт топ, график и список (G19) */
.calc-pair {
  /* Ширина по содержимому, а не фиксированная: при 122 px «USDT → ARS»
     обрезалось на крупном системном шрифте (слово заказчика 21.08).
     Селект сам меряется по самой длинной строке списка. */
  flex: 0 0 auto; width: auto; min-width: 122px; max-width: 100%;
  height: 52px;
  font-size: var(--fs-md); font-weight: 700;
  padding: 0 var(--sp-2);
  border: var(--bw) solid var(--link); border-radius: var(--r-md);
  background: var(--card); color: var(--link-strong);
  box-shadow: var(--shadow-1);
}
/* Переключатель темы: в строке заголовка, справа от «Сколько получу» — ряд
   ввода остаётся сумме, валюте и «Посчитать». Имя кнопке даёт theme.js
   из texts.js, значок внутри спрятан от скринридера. */
.calc-theme {
  flex: 0 0 auto; width: var(--tap);
  height: var(--tap);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-md); line-height: 1;
  border: var(--bw) solid var(--border); border-radius: var(--r-sm);
  background: var(--card); color: var(--text);
  box-shadow: var(--shadow-1);
  cursor: pointer; user-select: none;
}

/* --- фильтры ------------------------------------------------------------- */
.filters-row { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-2); flex-wrap: wrap; }
.filter-select {
  flex: 1 1 130px; min-width: 0; max-width: 100%;
  min-height: var(--tap);
  padding: 0 var(--sp-2); font-size: var(--fs-sm);
  border: var(--bw) solid var(--border); border-radius: var(--r-sm);
  background: var(--card); color: var(--text);
}
.chip,
.period-chip {
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 0 var(--sp-3); font-size: var(--fs-sm); font-weight: 500;
  border: var(--bw) solid var(--border); border-radius: var(--r-pill);
  background: var(--card); color: var(--text);
  cursor: pointer; user-select: none;
}
.chip[aria-pressed="true"],
.period-chip[aria-pressed="true"] {
  background: var(--button); color: var(--button-text); border-color: var(--button);
  font-weight: 600;
}
/* чип «Где я», пока позиция ищется */
.chip[aria-busy="true"] { opacity: 0.7; cursor: progress; }
.filter-search {
  width: 100%; min-height: var(--tap);
  padding: 0 var(--sp-3); font-size: var(--fs-md);
  border: var(--bw) solid var(--border); border-radius: var(--r-sm);
  background: var(--card); color: var(--text);
}
.filters-meta {
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2);
  min-height: 22px;
  margin-top: var(--sp-1);
  padding-top: var(--sp-2);
  border-top: var(--bw) solid var(--border);
}
.counter { font-size: var(--fs-sm); color: var(--hint-strong); }
.link-btn {
  border: none; background: none; padding: var(--sp-1) 0;
  font-size: var(--fs-sm); font-weight: 600; color: var(--link-strong); cursor: pointer;
}

/* --- карта (наполняет таск 05) -------------------------------------------
   Карта — такой же блок, как топ и график: та же рамка, радиус и тень.
   Padding нулевой, тайлы идут вровень с рамкой; атрибут hidden продолжает
   прятать блок целиком, поэтому обёртки-карточки вокруг неё нет. */
.map {
  height: 260px;
  padding: 0;
  overflow: hidden;
}

/* Свёрнутая группа «без курса»: такой же блок, как топ и график, но кликабельный.
   Внутри — обычные карточки, поэтому у самого блока padding нулевой снизу,
   когда он раскрыт: ритм списка задаёт gap у .list. */
.norate-group { padding: 0; }
/* Официальные сети (G34) отличаются от обменников без курса не видом, а смыслом:
   у них курса не будет никогда. Оформление общее, разделяет подпись. */
.official-group { padding: 0; }

/* «Наверх» (G36): над картой и попапами Leaflet, но ниже меню и заставки.
   Правый нижний угол — большой палец дотягивается, кнопка «Где я» на карте
   стоит выше и не конфликтует. */
.to-top {
  position: fixed;
  right: var(--sp-3);
  bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
  z-index: 1200;
  display: flex; align-items: center; gap: 6px;
  min-height: var(--tap); padding: 0 var(--sp-3);
  border: var(--bw) solid var(--border); border-radius: var(--r-md);
  background: var(--card); color: var(--text);
  font-size: var(--fs-sm); font-weight: 600;
  box-shadow: var(--shadow-2);
  cursor: pointer;
  opacity: 0; transform: translateY(8px);
  transition: opacity .18s ease, transform .18s ease;
}
.to-top.is-on { opacity: 1; transform: none; }
.to-top::before { content: "↑"; font-size: var(--fs-md); line-height: 1; }
@media (prefers-reduced-motion: reduce) {
  .to-top { transition: none; }
}
.norate-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: var(--tap);
  padding: var(--sp-3);
  border: none;
  border-radius: var(--r-lg);
  background: none;
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.norate-caret {
  flex: 0 0 auto;
  color: var(--hint-strong);
  transition: transform 180ms ease;
}
.norate-toggle[aria-expanded="true"] .norate-caret { transform: rotate(90deg); }
.norate-hint {
  padding: 0 var(--sp-3) var(--sp-3);
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--hint-strong);
}
.norate-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: 0 var(--sp-3) var(--sp-3);
}

@media (prefers-reduced-motion: reduce) {
  .norate-caret { transition: none; }
}

/* Наш сбор — первая строка под шапкой (G24): зелёная точка и «Проверено в 09:15»
   тем же --ok, что свежая дата в карточках; хвост — расписание. Пока сбора
   не было — серая точка. Время поста обменника сюда не относится: оно на карточке. */
.collect-count { color: var(--text); font-weight: 600; }
.collect-note {
  margin: 0 0 var(--sp-3);
  padding: 0 var(--sp-1);
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--hint-strong);
}
.collect-dot {
  display: inline-block; vertical-align: middle;
  width: 8px; height: 8px; margin: -2px 6px 0 0;
  border-radius: 50%;
  background: var(--ok);
}
.collect-dot.idle { background: var(--hint); }
.collect-time { color: var(--ok); font-weight: 700; white-space: nowrap; }
.collect-time.idle { color: var(--hint-strong); font-weight: 600; }

/* Карточка, к которой привезли с карты или из топа: вспышка акцентом на 2,5 с —
   кольцо поверх рамки (не спорит с рамкой «лучший курс») и подложка тем же
   цветом. Класс снимает app.js по таймеру; подложка гаснет плавно,
   при prefers-reduced-motion — сразу. */
.card { transition: background-color 400ms ease; }
.card.is-focused {
  outline: 2px solid var(--link);
  outline-offset: 2px;
  background: var(--card);
  background: color-mix(in srgb, var(--link) 10%, var(--card));
}
@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
}

/* Подсказка к калькулятору (G14, G19): не служебная строка, а заметная
   карточка с акцентной полоской — её читают первой, пока суммы нет. Текст
   цветом --text на чуть подкрашенной карточке: 12:1 тёмная, 16:1 светлая.
   Стоит сразу под липкой шапкой (не внутри — иначе шапка с ней 205 px на
   360-пиксельном экране) и исчезает, как только сумма введена. */
.calc-hint {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: var(--bw) solid var(--border);
  border-left: 3px solid var(--link);
  border-radius: var(--r-sm);
  background: var(--card);
  background: color-mix(in srgb, var(--link) 8%, var(--card));
  font-size: var(--fs-md);
  line-height: 1.4;
  color: var(--text);
}

/* Подсказка про геопозицию — тихая строка под рядами фильтров, не окно и не
   модалка: человек читает её, когда сам дошёл до чипа «Расстояние».
   Разделительную линию рисует .filters-meta ниже, здесь только отступ. */
.geo-hint {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--hint-strong);
}
.geo-hint-denied { color: var(--warn); }

/* --- баннер и пустые состояния ------------------------------------------- */
.banner {
  position: relative;
  padding-left: var(--sp-4);
  font-size: var(--fs-sm); font-weight: 600; color: var(--warn);
}
.banner::before {
  content: ""; position: absolute; left: 0; top: var(--sp-2); bottom: var(--sp-2);
  width: 3px; border-radius: var(--r-pill); background: var(--warn);
}
.banner small { display: block; font-weight: 400; color: var(--hint-strong); margin-top: var(--sp-1); }

.state {
  margin: 0;
  text-align: center;
  padding: var(--sp-5) var(--sp-4);
  color: var(--hint-strong); font-size: var(--fs-md);
}
.state .state-title {
  color: var(--text); font-size: var(--fs-lg); font-weight: 700;
  margin-bottom: var(--sp-1);
}
.state button {
  margin-top: var(--sp-4); min-height: var(--tap);
  padding: 0 var(--sp-5);
  font-size: var(--fs-md); font-weight: 600;
  border: none; border-radius: var(--r-sm);
  background: var(--button); color: var(--button-text); cursor: pointer;
}

/* --- карточки списка ------------------------------------------------------ */
.list { display: flex; flex-direction: column; gap: var(--sp-3); }
.card {
  position: relative;
  margin: 0;                   /* вертикальный ритм списка задаёт gap у .list */
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer;
}
.card.best { border-color: var(--ok); box-shadow: var(--shadow-2), inset 3px 0 0 var(--ok); }
.card-top {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--sp-3);
}
/* Иерархия: деньги крупнее всего, название следом, служебное — мелко и тускло */
.card-name {
  font-size: var(--fs-lg); font-weight: 600; line-height: 1.25;
  min-width: 0; overflow-wrap: anywhere;
}
.card-receive {
  font-size: var(--fs-money); font-weight: 800; line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex: 0 0 auto;
}
.card.stale-rate .card-receive, .card.stale-rate .card-rateline { color: var(--hint-strong); }
.card-badge {
  display: inline-block; margin-bottom: var(--sp-2);
  padding: 2px var(--sp-2); border-radius: var(--r-pill);
  border: var(--bw) solid var(--ok);
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ok);
}
.card-delta { font-size: var(--fs-sm); font-weight: 600; color: var(--ok); margin-top: var(--sp-1); }
.card-delta.minus { color: var(--hint-strong); font-weight: 400; }
.card-rateline {
  font-size: var(--fs-sm); color: var(--hint-strong); margin-top: var(--sp-2);
  font-variant-numeric: tabular-nums;
}
.card-rateline .stale-mark { color: var(--warn); }
/* лесенка ступеней по сумме под строкой курса (таск 24) */
.card-tiers {
  font-size: var(--fs-xs); color: var(--hint-strong); margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
/* Свежая дата поста — зелёным, протухший курс — янтарным: одна пара цветов
   на карточки и топ; «лучший вариант» и метка на карте — тот же --ok (G19) */
.fresh-mark { color: var(--ok); font-weight: 600; }
.card-norate {
  font-size: var(--fs-sm); color: var(--hint-strong); margin-top: var(--sp-2); font-style: italic;
}
.card-meta {
  font-size: var(--fs-sm); color: var(--hint-strong); margin-top: var(--sp-2);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-3);
}
.card-meta .open { color: var(--ok); font-weight: 600; }
.card-meta .closed { color: var(--bad); font-weight: 600; }
.badge-ru,
.badge-delivery {
  display: inline-block; padding: 1px var(--sp-2); border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: 600;
  /* Служебная метка: мягкая подложка вместо заливки кнопкой — не спорит
     с суммой за внимание. Первая строка — фолбэк без color-mix. */
  border: var(--bw) solid var(--border);
  background: none;
  background: color-mix(in srgb, var(--link) 12%, transparent);
  color: var(--link-strong);
}

/* --- раскрытая карточка ---------------------------------------------------- */
.card.expanded { cursor: default; box-shadow: var(--shadow-2); }
.detail {
  margin-top: var(--sp-3);
  border-top: var(--bw) solid var(--border);
  padding-top: var(--sp-3);
  font-size: var(--fs-md);
}
.detail-row { margin: var(--sp-2) 0; display: flex; gap: var(--sp-2); }
.detail-label { color: var(--hint-strong); flex: 0 0 auto; }
.detail-value { min-width: 0; overflow-wrap: anywhere; }
.hours-table { margin: var(--sp-1) 0 0; padding: 0; list-style: none; }
.hours-table li { display: flex; gap: var(--sp-3); font-size: var(--fs-sm); }
.hours-table .d { color: var(--hint-strong); width: 26px; flex: 0 0 auto; }
.hours-table li.today .d, .hours-table li.today { font-weight: 700; color: var(--text); }
.rates-list {
  margin: var(--sp-1) 0 0; padding: 0; list-style: none;
  font-size: var(--fs-sm); font-variant-numeric: tabular-nums;
}
.rates-list li { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-2); padding: 3px 0; }
.rates-list .stale { color: var(--hint-strong); }
.rates-list a { color: var(--link-strong); text-decoration: none; }
.rates-list li.acting { font-weight: 700; color: var(--text); }
/* Действующий курс (G31): вместо стены строк — одна, крупно. Остальные курсы
   точки открываются кнопкой .rates-more и рисуются тем же .rates-list */
.rate-acting { margin-top: var(--sp-1); font-variant-numeric: tabular-nums; }
.rate-acting-head { color: var(--hint-strong); font-size: var(--fs-sm); }
.rate-acting-value { font-size: var(--fs-lg); font-weight: 700; color: var(--ok); }
.rate-acting.stale .rate-acting-value { color: var(--hint-strong); }
.rate-acting-note { font-size: var(--fs-sm); color: var(--hint-strong); }
.rate-acting-note a { color: var(--link-strong); text-decoration: none; }
.rates-more {
  margin-top: var(--sp-2); min-height: var(--tap);
  padding: 0; background: none; border: 0; cursor: pointer;
  font-size: var(--fs-sm); font-weight: 600; color: var(--link-strong);
}
.contact-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
.contact-btn {
  flex: 1 1 45%; min-width: 130px; min-height: var(--tap);
  display: flex; align-items: center; justify-content: center;
  text-align: center; text-decoration: none;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm);
  background: var(--button); color: var(--button-text);
  font-size: var(--fs-sm); font-weight: 600;
}
.contact-links {
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4);
  margin-top: var(--sp-3); font-size: var(--fs-sm);
}
.contact-links a { color: var(--link-strong); text-decoration: none; font-weight: 600; }
.detail-collapse {
  margin-top: var(--sp-3); width: 100%; min-height: var(--tap);
  padding: var(--sp-2); font-size: var(--fs-sm); font-weight: 600;
  border: var(--bw) solid var(--border); border-radius: var(--r-sm);
  background: none; color: var(--hint-strong); cursor: pointer;
}

@media (max-width: 380px) {
  .app { padding-left: var(--sp-2); padding-right: var(--sp-2); }
  .calc-pair { width: 112px; font-size: var(--fs-sm); }
  /* поле суммы на 360 px — ~112 px: шрифт на шаг меньше, чтобы влезало «250 000» */
  .calc-amount { font-size: var(--fs-lg); padding: 0 var(--sp-3); }
  .card { padding: var(--sp-3); }
  .card-receive { font-size: var(--fs-xl); }
  .card-name { font-size: var(--fs-md); }
}

@media (prefers-reduced-motion: reduce) {
  * { scroll-behavior: auto !important; }
}

/* кнопка «Где выгоднее» (G23): на телефоне прячет клавиатуру и ведёт к подборке;
   стоит в одном ряду с суммой и валютой, поэтому компактная — fs-sm и узкие поля */
.calc-go {
  min-height: var(--tap); padding: 0 var(--sp-2); border: 0; border-radius: var(--r-md);
  background: var(--button); color: var(--button-text); font: inherit; font-size: var(--fs-sm); font-weight: 700;
  white-space: nowrap; cursor: pointer;
}
.calc-go:active { filter: brightness(0.92); }

/* адрес — главное в карточке: крупно, жирно, район строкой ниже; кнопка маршрута — акцентом, с подписью */
.address-block { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--sp-2); margin: var(--sp-3) 0 var(--sp-2); }
.address-pin { font-size: var(--fs-lg); }
.address-text { font-size: var(--fs-lg); font-weight: 700; color: var(--text); overflow-wrap: anywhere; }
.address-barrio { flex-basis: 100%; margin-left: calc(var(--fs-lg) + var(--sp-2)); color: var(--hint-strong); font-size: var(--fs-sm); }
.map-route-row { align-items: center; gap: var(--sp-2); }
.contact-btn.map-route { background: var(--button); color: var(--button-text); border-color: transparent; font-weight: 600; }
.map-route-hint { color: var(--hint-strong); font-size: var(--fs-sm); }

/* ===== Заставка (G33) ==================================================
   Первый кадр обязан быть нашим. Линия курса рисуется слева направо, цифра
   добегает синхронно — знак бренда и индикатор загрузки это одно и то же.
   Токены те же, что у приложения: заставка в чужой палитре читается как
   чужой экран. Снимает её app.js по первому ответу, не по таймеру. */
.splash {
  /* Выше всего на экране: панели Leaflet доходят до 1100, и карта успевала
     мигнуть поверх заставки, пока та ещё висела (живой случай 21.08) */
  position: fixed; inset: 0; z-index: 9000;
  background: var(--bg);
  display: grid; place-items: center;
  transition: opacity .35s ease;
}
.splash.is-gone { opacity: 0; pointer-events: none; }
.splash-inner { width: 300px; max-width: 88vw; text-align: center; }
.splash-chart { display: block; width: 100%; height: 96px; overflow: visible; }
.splash-grid line { stroke: var(--text); stroke-opacity: .06; stroke-width: 1; }
.splash-area { fill: url(#splash-fade); opacity: 0; animation: splashArea 1.6s ease-out .5s forwards; }
@keyframes splashArea { 0%, 30% { opacity: 0 } 100% { opacity: 1 } }
.splash-line {
  fill: none; stroke: var(--ok); stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 620; stroke-dashoffset: 620;
  animation: splashDraw 1.6s cubic-bezier(.4, .1, .3, 1) .5s forwards;
}
@keyframes splashDraw { to { stroke-dashoffset: 0 } }
.splash-head {
  fill: var(--ok);
  offset-path: path("M4,72 L22,58 L38,76 L54,46 L70,64 L86,34 L102,56 L118,26 L134,46 L150,18 L166,40 L182,14 L198,32 L214,10 L230,28 L246,6 L262,20 L278,4 L296,12");
  offset-distance: 0%;
  opacity: 0;
  animation: splashRun 1.6s cubic-bezier(.4, .1, .3, 1) .5s forwards,
             splashHeadIn .01s linear .5s forwards;
}
@keyframes splashHeadIn { to { opacity: 1 } }
@keyframes splashRun { to { offset-distance: 100% } }
.splash-rate {
  margin-top: 10px; font-size: 34px; font-weight: 700; letter-spacing: -.5px;
  font-variant-numeric: tabular-nums; color: var(--ok);
}
/* Имя — первое, что видит человек: без задержки и заметно. График идёт следом,
   иначе за время его отрисовки имя не успевает прочитаться (слово заказчика 21.08) */
.splash-word {
  margin-bottom: 4px; font-size: 26px; font-weight: 800; letter-spacing: 6px;
  text-transform: uppercase; color: var(--text);
  opacity: 0; animation: splashName .55s cubic-bezier(.2, .8, .3, 1) forwards;
}
.splash-sub {
  margin-bottom: 14px; font-size: var(--fs-sm); color: var(--hint-strong);
  opacity: 0; animation: splashUp .5s ease-out .3s forwards;
}
@keyframes splashName {
  from { opacity: 0; transform: translateY(10px) scale(.94); letter-spacing: 12px }
  to   { opacity: 1; transform: none; letter-spacing: 6px }
}
@keyframes splashUp { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }
/* Данные могут идти дольше анимации — без полосы застывший экран читается
   как поломка, и человек закрывает приложение */
.splash-bar {
  margin: 18px auto 0; width: 120px; height: 2px; border-radius: 2px;
  background: color-mix(in srgb, var(--text) 10%, transparent); overflow: hidden;
}
.splash-bar i {
  display: block; height: 100%; width: 40%; border-radius: 2px;
  background: var(--ok); opacity: .7; animation: splashSlide 1.4s ease-in-out infinite;
}
@keyframes splashSlide { 0% { transform: translateX(-120%) } 100% { transform: translateX(320%) } }

/* Кому анимация мешает — показываем сразу готовый кадр, а не дёргаем экран */
@media (prefers-reduced-motion: reduce) {
  .splash-line { animation: none; stroke-dashoffset: 0; }
  .splash-area, .splash-word, .splash-sub { animation: none; opacity: 1; }
  .splash-head { animation: none; offset-distance: 100%; }
  .splash-bar i { animation: none; width: 100%; }
}

/* ===== Меню (G32) =====================================================
   Кнопка в шапке вместо отдельной первой страницы: первую страницу проходят
   один раз, а платят за неё каждый запуск. Тема переехала сюда из шапки. */
.menu-icon::before { content: "\2630"; font-size: 17px; line-height: 1; }
.sheet { position: fixed; inset: 0; z-index: 70; display: flex; align-items: flex-end; }
.sheet[hidden] { display: none; }
.sheet-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, .55); }
.sheet-body {
  position: relative; width: 100%; max-height: 88vh; overflow-y: auto;
  background: var(--card); color: var(--text);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-5) + env(safe-area-inset-bottom));
  animation: sheetUp .25s ease-out;
}
@keyframes sheetUp { from { transform: translateY(12%) } to { transform: none } }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.sheet-title { margin: 0; font-size: var(--fs-lg); font-weight: 700; }
.sheet-close {
  min-width: var(--tap); min-height: var(--tap);
  background: none; border: 0; color: var(--hint-strong);
  font-size: 17px; cursor: pointer;
}
.menu-label {
  margin-top: var(--sp-3); font-size: var(--fs-sm); color: var(--hint-strong);
}
.country-list { margin-top: var(--sp-2); display: flex; flex-direction: column; gap: var(--sp-1); }
.country {
  display: flex; align-items: baseline; gap: var(--sp-2);
  width: 100%; min-height: var(--tap); text-align: left;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm);
  border: var(--bw) solid var(--border); background: none;
  color: var(--text); font-size: var(--fs-md); cursor: pointer;
}
.country[aria-current="true"] { border-color: var(--ok); color: var(--ok); font-weight: 700; }
.country[disabled] { cursor: default; color: var(--hint-strong); }
.country-sub { font-size: var(--fs-sm); color: var(--hint-strong); }
.country[aria-current="true"] .country-sub { color: var(--ok); }
.country-soon { margin-left: auto; font-size: var(--fs-xs); color: var(--hint-strong); }
.menu-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  width: 100%; min-height: var(--tap); text-align: left;
  margin-top: var(--sp-1); padding: var(--sp-2) 0;
  background: none; border: 0; border-top: var(--bw) solid var(--border);
  color: var(--text); font-size: var(--fs-md); cursor: pointer;
}
.menu-row-text { flex: 1 1 auto; }
.menu-chevron { color: var(--hint-strong); transition: transform .2s ease; }
.menu-row[aria-expanded="true"] .menu-chevron { transform: rotate(180deg); }
.menu-note {
  margin: 0 0 var(--sp-2); font-size: var(--fs-sm); color: var(--hint-strong);
  overflow-wrap: anywhere;
}
/* Кнопка «Поделиться» (G35): заметная, но не крикливая. Мягкое свечение раз
   в несколько секунд — приглашение нажать; постоянная пульсация в меню
   раздражала бы. Движение снимается вместе с остальной анимацией. */
.share-btn {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; margin-top: var(--sp-3); text-align: left;
  padding: var(--sp-3);
  border: var(--bw) solid color-mix(in srgb, var(--ok) 45%, transparent);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  color: var(--text); cursor: pointer;
  animation: shareGlow 4s ease-in-out 1s infinite;
}
.share-btn:active { transform: scale(.99); }
@keyframes shareGlow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 0%, transparent) }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--ok) 14%, transparent) }
}
.share-icon {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--ok); color: var(--bg);
  font-size: 17px; line-height: 1;
}
.share-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.share-title { font-size: var(--fs-md); font-weight: 700; color: var(--ok); }
.share-hint { font-size: var(--fs-sm); color: var(--hint-strong); }
@media (prefers-reduced-motion: reduce) { .share-btn { animation: none } }

.menu-disclaimer {
  margin: var(--sp-4) 0 0; padding-top: var(--sp-3);
  border-top: var(--bw) solid var(--border);
  font-size: var(--fs-xs); color: var(--hint-strong);
}

/* ===== График: вторая линия и легенда (G36) ==========================
   Курс блю — общий ориентир города: цифра обменника сама по себе не говорит,
   хорошо это или плохо. Линия блю пунктирная и тоньше основной — она фон
   для сравнения, а не равноправная вторая величина. */
.history-blue {
  fill: none; stroke: var(--rate-blue); stroke-width: 1.8;
  stroke-dasharray: 5 3; stroke-linecap: round; stroke-linejoin: round;
}
.history-area { fill: url(#history-fade); }
.history-legend {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-1) var(--sp-4);
  margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--hint-strong);
}
.legend-item { display: inline-flex; align-items: center; gap: var(--sp-2); }
.legend-swatch { width: 18px; height: 0; border-top: 3px solid var(--link); border-radius: 2px; }
.legend-swatch.blue { border-top-style: dashed; border-top-width: 2px; border-color: var(--rate-blue); }
.legend-hint { color: var(--hint); }


/* --- Аналитика посетителей (G20) -------------------------------------------
   Один набор классов на два места: экран «Админ» внутри приложения и страница
   /admin/analytics в браузере. Значений своих нет — только токены выше. */
.an-tiles {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.an-tile {
  background: var(--card);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.an-tile-label { color: var(--hint-strong); font-size: var(--fs-sm); }
.an-tile-big {
  font-size: var(--fs-money);
  font-weight: 700;
  line-height: 1.1;
  margin: var(--sp-1) 0 2px;
}
.an-tile-sub { color: var(--hint-strong); font-size: var(--fs-sm); }
.an-tile-sub b { color: var(--text); font-weight: 600; }
.an-block {
  background: var(--card);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.an-h { font-size: var(--fs-lg); margin: 0 0 var(--sp-2); }
.an-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  color: var(--hint-strong);
  font-size: var(--fs-sm);
  margin-bottom: var(--sp-2);
}
.an-legend i {
  display: inline-block;
  width: 18px;
  height: 3px;
  border-radius: 2px;
  vertical-align: middle;
  margin-right: 6px;
}
.an-chart { overflow-x: auto; }
.an-chart-svg { width: 100%; height: auto; display: block; }
.an-rows { display: flex; flex-direction: column; gap: var(--sp-2); }
.an-row { display: flex; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-md); }
.an-row b { font-weight: 700; }
.an-empty { color: var(--hint-strong); font-size: var(--fs-md); }
.an-table-box { overflow-x: auto; }
.an-table { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); min-width: 460px; }
.an-table th,
.an-table td {
  text-align: left;
  padding: var(--sp-2) var(--sp-2) var(--sp-2) 0;
  border-bottom: var(--bw) solid var(--border);
  white-space: nowrap;
}
.an-table th { color: var(--hint-strong); font-weight: 600; font-size: var(--fs-xs); }
.an-table .an-num { text-align: right; padding-right: 0; }
.an-who { font-weight: 600; font-size: var(--fs-md); }
.an-sub { color: var(--hint-strong); font-size: var(--fs-sm); }
.an-tag {
  font-size: var(--fs-xs);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-pill);
  padding: 1px 8px;
  color: var(--hint-strong);
  white-space: nowrap;
}
.an-tag-tg { color: var(--ok); border-color: var(--ok); }
.an-pager { display: flex; gap: var(--sp-2); align-items: center; margin-top: var(--sp-3); }
.an-btn {
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--text);
  background: var(--card);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-pill);
  padding: var(--sp-2) var(--sp-3);
  min-height: var(--tap);
  cursor: pointer;
}
.an-btn:disabled { opacity: .45; cursor: default; }
.an-btn.an-on { background: var(--button); color: var(--button-text); border-color: transparent; }
.an-note { color: var(--hint-strong); font-size: var(--fs-sm); line-height: 1.5; }

@media (min-width: 700px) {
  .an-tiles { grid-template-columns: repeat(4, 1fr); }
}

/* «Поддержать разработчика»: реквизиты крупно, адрес переносится, кнопка копирования рядом */
.donate-box { padding: 0 var(--sp-1) var(--sp-2); }
.donate-row { padding: var(--sp-2) 0; border-top: var(--bw) solid var(--border); }
.donate-title { font-size: var(--fs-md); font-weight: 700; color: var(--text); }
.donate-value { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--fs-sm); color: var(--link); overflow-wrap: anywhere; margin: var(--sp-1) 0; user-select: all; -webkit-user-select: all; }
.donate-line { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.donate-note { color: var(--hint-strong); font-size: var(--fs-sm); }
.donate-copy { min-height: var(--tap); padding: 0 var(--sp-3); border: 0; border-radius: var(--r-pill); background: var(--button); color: var(--button-text); font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer; white-space: nowrap; }

/* Сворачиваемый блок (динамика курса): заголовок — кнопка во всю ширину, шеврон поворачивается */
.dash-toggle { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; min-height: var(--tap); }
.dash-toggle .dash-title { margin: 0; }
.dash-chevron { color: var(--hint-strong); font-size: var(--fs-lg); transition: transform .15s; }
.dash-toggle[aria-expanded="true"] .dash-chevron { transform: rotate(180deg); }
.dash-body { margin-top: var(--sp-2); }
