/* ==========================================================================
   Стекло — слой оформления
   ==========================================================================

   Отдельный файл не из прихоти, а потому что styles.css за десять
   месяцев покрыл всё подряд и правила разошлись: одна и та же кнопка
   описана в трёх местах, стекло лепят��и вручную в каждом экране, и
   правка одного угла тянула за собой весь файл.

   Здесь — одна система. Токены, стеклянные поверхности, компоненты.
   Подключается после styles.css, поэтому перекрывает его, ничего из
   старого не ломая: каждый блок ниже меняет вид, но не поведение.

   Порядок правил важен: токены → фон → поверхности → компоненты →
   частности. Каскад вниз.
   ========================================================================== */

/* ==========================================================================
   Правило одного стекла
   ==========================================================================

   Главный источник подтормаживания всего интерфейса.

   backdrop-filter не рисует стекло заново — он просит у браузера
   сделать СНИМОК того, что под элементом, и размыть этот снимок.
   Снимок значит «прочитать пиксели под элементом», а значит —
   дождаться отрисовки всего, что там есть.

   Теперь представьте: снимок шторки настроек читает всё приложение
   под ней. Размытие липкой шапки читает всё внутри шторки. Размытие
   каждой группы и каждого меню читает всё предыдущее. Шестнадцать
   слоёв друг на друге — и каждый кадр прокрутки заново проходит
   всю цепочку. На компьютере с дискретной видеокартой это незаметно,
   на телефоне это ровно то «не полный фпс»: список полз��т,
   а жест отстаёт от пальца.

   Правило простое: размытие делает только тот слой, под которым
   есть что размывать — экран, вкладка или окно. Всё, что внутри
   уже размытого окна, берёт простую полупрозрачную заливку. Вид
   тот же, стоимость — в разы меньше.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Токены стекла
   -------------------------------------------------------------------------- */

:root {
  /* Толщина стекла. Чем толще слой, тем больше размытия и меньше
     прозрачности — как в iOS, где материал зависит от высоты. */
  --g-blur:   28px;
  --g-blur-2: 18px;
  --g-blur-3: 44px;
  --g-sat:    180%;

  /* Подложка под стеклом. В тёмной теме она чуть светлее фона — стекло
     должно читаться слоем над подложкой, а не дырой в темноте. */
  --g-tint:        rgba(255,255,255,.055);
  --g-tint-strong: rgba(255,255,255,.085);
  --g-tint-soft:   rgba(255,255,255,.032);

  /* Кромка стекла: у iOS она всегда светлее заливки и ловит блик
     сверху. Без неё стекло выглядит серой пятном. */
  --g-edge:      rgba(255,255,255,.14);
  --g-edge-soft: rgba(255,255,255,.08);
  --g-edge-lit:  rgba(255,255,255,.32);

  /* Тень стекла. Мягкая и широкая: стекло не лежит на поверхности,
     а висит над ней. */
  --g-sh-1: 0 2px 10px rgba(0,0,0,.28);
  --g-sh-2: 0 10px 30px rgba(0,0,0,.34);
  --g-sh-3: 0 22px 60px rgba(0,0,0,.46);

  /* Скругления: iOS держит их крупными и одинаковыми по ритму. */
  --g-r-sm: 12px;
  --g-r:    18px;
  --g-r-lg: 24px;
  --g-r-xl: 32px;

  /* Анимация. Один токен на всё движение — иначе интерфейс дёргается
     разными кривыми и собирается ощущение «склеенного». */
  --g-ease: cubic-bezier(.32,.72,0,1);
  --g-fast: .18s;
  --g-med:  .32s;
}

[data-theme="light"] {
  /* В светлой теме стекло работает наоборот: тёмная подложка под ним
     даёт глубину, а светлая кромка — блик. Поэтому здесь tint темнее,
     а край светлее. Один и тот же токен в двух темах даёт разный
     результат — это и нужно. */
  --g-tint:        rgba(255,255,255,.72);
  --g-tint-strong: rgba(255,255,255,.86);
  --g-tint-soft:   rgba(255,255,255,.55);

  --g-edge:      rgba(10,10,20,.10);
  --g-edge-soft: rgba(10,10,20,.06);
  --g-edge-lit:  rgba(255,255,255,.95);

  --g-sh-1: 0 1px 3px rgba(20,20,50,.07);
  --g-sh-2: 0 8px 24px rgba(20,20,50,.10);
  --g-sh-3: 0 20px 48px rgba(20,20,50,.16);
}

/* --------------------------------------------------------------------------
   2. Фон: живой градиент
   --------------------------------------------------------------------------

   Раньше фон был просто тёмной заливкой. Из-за этого стеклу не на
   что было преломлять свет: размывать нечего, и плашки выглядели
   серыми прямоугольниками.

   Пятна вешаются на .app, а не на body — и это не вкусовое решение.
   Приложение ограничено колонкой 520px и стоит по центру окна. Если
   пятна считать от окна, то на широком экране все четыре центра
   оказываются за пределами колонки, и внутри приложения фон
   выглядит ровной заливкой: то есть ровно то, от чего мы уходили.
   На телефоне, где колонка занимает весь экран, разницы не видно —
   и баг доживал незамеченным до появления монитора.

   Один слой вместо двух: четыре пятна и виньетка — это пять
   радиальных градиентов в одной фоновой картинке. Два отдельных
   слоя требовали бы двух слоёв композитора и двойной работы при
   прокрутке, а разницы не видно.
   -------------------------------------------------------------------------- */

.app {
  position: relative;
  background: var(--bg);
  isolation: isolate;
}
.app::before {
  content: '';
  position: absolute;
  inset: -20%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(46% 40% at 16% 6%,  rgba(124,92,255,.50) 0%, transparent 70%),
    radial-gradient(44% 40% at 88% 20%, rgba(255,77,141,.36) 0%, transparent 70%),
    radial-gradient(50% 42% at 84% 92%, rgba(37,244,238,.24) 0%, transparent 72%),
    radial-gradient(42% 38% at 6% 78%,  rgba(47,214,168,.18) 0%, transparent 70%),
    radial-gradient(130% 90% at 50% 0%, transparent 58%, rgba(0,0,0,.30) 100%);
  animation: gDrift 38s var(--g-ease) infinite alternate;
}
[data-theme="light"] .app::before {
  background:
    radial-gradient(46% 40% at 16% 6%,  rgba(124,92,255,.30) 0%, transparent 70%),
    radial-gradient(44% 40% at 88% 20%, rgba(255,77,141,.22) 0%, transparent 70%),
    radial-gradient(50% 42% at 84% 92%, rgba(37,244,238,.18) 0%, transparent 72%),
    radial-gradient(42% 38% at 6% 78%,  rgba(47,214,168,.14) 0%, transparent 70%),
    radial-gradient(130% 90% at 50% 0%, transparent 60%, rgba(20,20,50,.08) 100%);
}

@keyframes gDrift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(3%, -2.5%, 0) scale(1.09); }
}

@media (prefers-reduced-motion: reduce) {
  .app::before { animation: none; }
}

/* --------------------------------------------------------------------------
   3. Стеклянные поверхности
   -------------------------------------------------------------------------- */

/* Базовое стекло. Всё, что хочет выглядеть стеклом, берёт эти свойства
   отсюда, а не пишет backdrop-filter заново. */
.glass,
.g-surface {
  position: relative;
  background: var(--g-tint);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  border: 1px solid var(--g-edge-soft);
  border-radius: var(--g-r);
  box-shadow: var(--g-sh-2);
}

/* Блик по верхней кромке: тонкая светлая полоса в один пиксель.
   Именно она отличает стекло от полупрозрачного пластика. */
.glass::before,
.g-surface::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--g-edge-lit) 22%, var(--g-edge-lit) 78%, transparent);
  border-radius: inherit;
  pointer-events: none;
}

/* Поверхности по плотности. */
.g-surface--thin { background: var(--g-tint-soft); }
.g-surface--deep { background: var(--g-tint-strong); border-color: var(--g-edge); }

/* Стекло без backdrop-filter выглядит как серый прямоугольник —
   поэтому ему нужна непрозрачная заливка, иначе читаемость падает
   до unusable. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass, .g-surface {
    background: var(--surface-2);
    border-color: var(--g-edge);
  }
}

/* --------------------------------------------------------------------------
   4. Кнопки
   --------------------------------------------------------------------------

   Кнопка в iOS — не заливка, а капсула со стеклом и тонкой рамкой.
   Главное отличие от прежней: у неё появилась глубина (тень) и
   блик, и нажатие сжимает её мягко, а не до 97.5% — мелочь, но
   именно по ней рука узнаёт интерфейс как «живой».
   -------------------------------------------------------------------------- */

.btn {
  position: relative;
  overflow: hidden;
  height: 50px;
  border-radius: var(--g-r);
  font-weight: 700;
  letter-spacing: -.2px;
  background: var(--g-tint-strong);
  color: var(--text);
  border: 1px solid var(--g-edge);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
  transition: transform var(--g-fast) var(--g-ease),
              filter var(--g-fast) var(--g-ease),
              opacity var(--g-fast);
}
.btn:active { transform: scale(.97); filter: brightness(1.12); }
.btn svg { width: 19px; height: 19px; }

/* Блик, который пробегает по кнопке при нажатии: короткая вспышка
   от верхнего края. Без неё нажатие на стекле не читается. */
.btn::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--g-edge-lit), transparent 60%);
  opacity: .5;
  pointer-events: none;
}

.btn--primary {
  background: var(--grad);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 10px 26px rgba(124,92,255,.34), inset 0 1px 0 rgba(255,255,255,.3);
}

.btn--ghost {
  background: var(--g-tint-soft);
  border-color: var(--g-edge);
  color: var(--muted);
}
.btn--ghost::after { opacity: .35; }

.btn--soft {
  background: var(--grad-soft);
  color: #cdbdff;
  border-color: rgba(124,92,255,.34);
}

.btn--ok {
  background: rgba(47,214,168,.16);
  color: var(--ok);
  border-color: rgba(47,214,168,.34);
}

.btn--danger {
  background: rgba(255,79,94,.14);
  color: var(--danger);
  border-color: rgba(255,79,94,.32);
}

.btn--block { width: 100%; }
.btn--sm {
  height: 38px;
  padding: 0 15px;
  font-size: 13px;
  border-radius: var(--g-r-sm);
}

/* Кнопка из стекла вовсе без заливки.
   Главное действие профиля раньше красилось сплошным градиентом —
   в стеклянном интерфейсе это единственное пятно, которое
   выбивается и притягивает взгляд сильнее, чем сам профиль.
   Теперь оно отличается толщиной стекла и рамкой, а не цветом. */
.btn--glass {
  background: var(--g-tint);
  border-color: var(--g-edge);
  color: var(--text);
  -webkit-backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  box-shadow: var(--g-sh-2), inset 0 1px 0 var(--g-edge-lit);
}
.btn--glass::after { opacity: .6; }

/* --------------------------------------------------------------------------
   5. Поля ввода
   --------------------------------------------------------------------------

   Поле — не серый прямоугольник, а стеклянная лента, у которой при
   фокусе зажигается кромка. Раньше фокус давал только смену цвета
   рамки, и на телефоне было не видно, какое поле сейчас набирают.
   -------------------------------------------------------------------------- */

.input, .textarea, .select {
  background: var(--g-tint-soft);
  border: 1px solid var(--g-edge-soft);
  border-radius: var(--g-r-sm);
  color: var(--text);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
  box-shadow: inset 0 1px 2px rgba(0,0,0,.12);
  transition: border-color var(--g-fast) var(--g-ease),
              box-shadow var(--g-fast) var(--g-ease),
              background var(--g-fast) var(--g-ease);
}
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--accent);
  background: var(--g-tint);
  box-shadow: 0 0 0 4px rgba(124,92,255,.16), inset 0 1px 2px rgba(0,0,0,.12);
}
[data-theme="light"] .input,
[data-theme="light"] .textarea,
[data-theme="light"] .select {
  box-shadow: inset 0 1px 2px rgba(20,20,50,.06);
}

.field__label b { letter-spacing: -.2px; }

/* --------------------------------------------------------------------------
   6. Мелкие элементы: плашки, чипы, кнопки-капсулы
   -------------------------------------------------------------------------- */

.pill {
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  padding: 5px 11px;
}

.chip {
  background: var(--g-tint);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
  transition: transform var(--g-fast) var(--g-ease),
              background var(--g-fast) var(--g-ease),
              border-color var(--g-fast) var(--g-ease),
              color var(--g-fast) var(--g-ease);
}
.chip.is-active {
  background: var(--grad-soft);
  color: #c9b8ff;
  border-color: rgba(124,92,255,.5);
  box-shadow: 0 4px 14px rgba(124,92,255,.2);
}
.chip:active { transform: scale(.95); }

.mini-btn {
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
  border-radius: 11px;
  transition: transform var(--g-fast) var(--g-ease), filter var(--g-fast);
}
.mini-btn:active { transform: scale(.95); }

/* --------------------------------------------------------------------------
   7. Иконка-кнопка
   -------------------------------------------------------------------------- */

.icon-btn {
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
  transition: transform var(--g-fast) var(--g-ease), filter var(--g-fast);
}
.icon-btn:active { transform: scale(.92); filter: brightness(1.15); }

/* --------------------------------------------------------------------------
   8. Разделы и заголовки
   -------------------------------------------------------------------------- */

.sec { padding: 4px 16px 18px; }

.sec__h { align-items: center; margin: 10px 2px 12px; }
.sec__h b { letter-spacing: -.3px; font-weight: 800; }
.sec__h--sub b { text-transform: uppercase; letter-spacing: .05em; }

/* --------------------------------------------------------------------------
   9. Карточки
   --------------------------------------------------------------------------

   Три карточки приложения — bcard (люди), my-work (свои работы),
   adm-card (модерация) — раньше отличались только цветом рамки и
   выглядели тремя разными предметами. Стекло и одинаковый ритм
   скруглений делают их одним семейством, и экран перестаёт пёстро
   мельтешить.
   -------------------------------------------------------------------------- */

.bcard,
.my-work,
.adm-card,
.chat-row {
  background: var(--g-tint);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  border: 1px solid var(--g-edge-soft);
  border-radius: var(--g-r);
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
  transition: transform var(--g-fast) var(--g-ease),
              box-shadow var(--g-fast) var(--g-ease),
              border-color var(--g-fast) var(--g-ease);
}
.bcard:active,
.my-work:active,
.chat-row:active {
  transform: scale(.985);
  border-color: var(--g-edge);
  box-shadow: var(--g-sh-2), inset 0 1px 0 var(--g-edge-lit);
}

/* Карточка модерации — рабочий инструмент, а не витрина: она должна
   читаться плотнее и без «эффекта витрины», иначе в списке из
   тридцати работ ничего не найти. */
.adm-card {
  background: var(--g-tint);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  border-color: var(--g-edge);
  border-radius: var(--g-r);
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
  transition: transform var(--g-fast) var(--g-ease),
              box-shadow var(--g-fast) var(--g-ease),
              border-color var(--g-fast) var(--g-ease);
}
.adm-card.is-new {
  border-color: rgba(124,92,255,.4);
  box-shadow: var(--g-sh-1), 0 0 0 3px rgba(124,92,255,.12);
}

/* --------------------------------------------------------------------------
   10. Меню настроек
   --------------------------------------------------------------------------
   Внутри окна — без backdrop-filter. Меню лежит на непрозрачной
   шторке, размывать нечего; зато каждая группа стоила ещё один
   слой снимка на каждый кадр прокрутки. */
.menu {
  background: rgba(255,255,255,.05);
  border: 1px solid var(--g-edge-soft);
  border-radius: var(--g-r);
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
  overflow: hidden;
}
[data-theme="light"] .menu { background: rgba(255,255,255,.72); }

/* Разделитель между пунктами — не линия, а выемка: тонкая тень сверху
   даёт ощущение, что пункт «лежит» на стекле. */
.menu__i { border-bottom: 1px solid var(--g-edge-soft); }
.menu__i:last-child { border-bottom: 0; }
.menu__i:active { background: rgba(255,255,255,.09); }

.set-group {
  background: rgba(255,255,255,.05);
  border: 1px solid var(--g-edge-soft);
  border-radius: var(--g-r);
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
}
[data-theme="light"] .set-group { background: rgba(255,255,255,.72); }

/* Заголовок группы. Раньше он начинался вплотную к верхней кромке
   и обрезался: буквы верхнего регистра упирались в рамку и строка
   читалась как «ДВИЖЕНИЕЗА СУТКИ». */
.set-group__h {
  padding: 11px 13px 7px;
  letter-spacing: .07em;
}


/* --------------------------------------------------------------------------
   11. Таб-бар: плавающая капсула
   --------------------------------------------------------------------------

   Панель была прибита к низу во всю ширину и уходила под скругления
   экрана. На телефоне с «бровью» это выглядит неопрятно: стекло
   обрезается прямо у края, и между панелью и рамкой остаётся
   полоска фона.

   Теперь панель отделена от краёв и висит над содержимым — так же,
   как плавающий док в iOS. Заодно чинится мелочь, которая портила
   вид: под панелью было видно обрезанный край списка, и строка
   будто входила под стекло наполовину.

   Размытие здесь оставлено осознанно: под панельой постоянно едет
   содержимое, и без размытия стекло выглядит просто серой полосой.
   Оно единственное большое в приложении, поэтому и держит кадр
   даже на слабом железе. */
.tabbar {
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: calc(10px + var(--safe-b));
  z-index: 60;
  height: var(--tab-h);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  align-items: center;
  overflow: hidden;
  border-radius: 26px;
  background: rgba(18,18,27,.74);
  -webkit-backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  border: 1px solid var(--g-edge);
  box-shadow:
    0 14px 34px rgba(0,0,0,.44),
    0 2px 8px rgba(0,0,0,.3),
    inset 0 1px 0 var(--g-edge-lit);
}
[data-theme="light"] .tabbar {
  background: rgba(255,255,255,.74);
  box-shadow:
    0 12px 30px rgba(20,20,50,.14),
    0 1px 4px rgba(20,20,50,.08),
    inset 0 1px 0 rgba(255,255,255,.95);
}

/* Вкладка — капсула внутри капсулы.
   Геометрия выведена так, чтобы пять вкладок делили панель ровно:
   поля симметричны, высота строк одинакова, и подпись не гуляет
   при смене вкладки — раньше высота строки зависела от иконки, и
   панель «дышала» при переключении. */
.tab {
  position: relative;
  z-index: 1;
  height: calc(var(--tab-h) - 10px);
  margin: 5px 3px;
  border-radius: 21px;
  display: grid;
  grid-template-rows: 29px 12px;
  align-content: center;
  justify-items: center;
  gap: 1px;
  color: var(--muted-2);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .01em;
  /* Метка по центру, а не по верху ячейки: при разной длине слов
     («Рынок», «Профиль») подписи выравнивались по-разному. */
  align-items: center;
  transition: color .34s cubic-bezier(.34, 1.36, .5, 1),
              transform var(--g-fast) var(--g-ease);
}
.tab > span:not(.tab__plus):not(.tab__badge) {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 12px;
  line-height: 12px;
  white-space: nowrap;
}
.tab:active { transform: scale(.93); }
.tab.is-active { color: var(--text); }

/* Перекат.
   Пилюля шириной ровно в одну вкладку едет на индекс --i.
   Шаг — это ширина вкладки ПЛЮС боковые поля: сетка даёт
   ячейку шириной 100%/5, а вкладка внутри неё уже на 6 пикселей
   уже из-за полей. Если шаг посчитать только по ширине вкладки,
   пилюля копила бы смещение и к четвёртой вкладке уезжала за
   край панели на 15 пикселей.

   Двигается transform, а не left: left пересчитывает раскладку
   на каждом кадре, transform живёт на композиторе. */
.tabbar__pill {
  position: absolute;
  z-index: 0;
  top: 5px;
  left: 3px;
  width: calc(20% - 6px);
  height: calc(var(--tab-h) - 10px);
  border-radius: 21px;

  /* Линза.
     Выпуклая линза видна по трём признакам, и нужны все три:
     светлое пятно у верхнего края, тёмное у нижнего и тонкий
     ободок по контуру. Один градиент даёт плоское стекло; два —
     «иконку в подсветке»; три — настоящую линзу, которую хочется
     потрогать. */
  background:
    /* блик сверху-слева: отражение источника */
    radial-gradient(105% 86% at 30% 4%,
      rgba(255,255,255,.34) 0%, rgba(255,255,255,.10) 32%, transparent 56%),
    /* тень у нижнего края: линза выпуклая, а не вогнутая */
    radial-gradient(125% 105% at 50% 112%,
      rgba(0,0,0,.42) 0%, rgba(0,0,0,.12) 40%, transparent 64%),
    /* радужка по краю: у стекла есть толщина, а у края — преломление */
    radial-gradient(100% 100% at 50% 50%,
      rgba(124,92,255,.22) 0%, rgba(37,244,238,.12) 70%, rgba(255,77,141,.16) 100%),
    rgba(255,255,255,.08);

  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.55),
    inset 0 -1px 0 rgba(0,0,0,.34),
    inset 0 0 18px rgba(255,255,255,.07),
    0 4px 16px rgba(124,92,255,.26);

  /* Позиция линзы. Задаётся индексом активной вкладки, который
     ставит роутер. Шаг — ширина вкладки плюс боковые поля: сетка
     даёт ячейку 100%/5, а вкладка внутри неё уже на 6 пикселей
     уже из-за полей. */
  transform: translateX(calc(var(--i, 0) * (100% + 6px)));

  /* Пружина, а не затухание: линейное замедление в конце
     выглядит как «догоняет и дёргается», а короткий перелёт с
     лёгким проходом дальше цели читается как одно движение. */
  transition: transform .34s cubic-bezier(.34, 1.36, .5, 1);
  will-change: transform;
  overflow: hidden;
  pointer-events: none;
}

/* Ободок. Тонкая светящаяся кайма по всему контуру — то, что
   отличает линзу от просто подсвеченного прямоугольника. */
.tabbar__pill::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(150deg,
    rgba(255,255,255,.5) 0%, rgba(255,255,255,.06) 34%,
    transparent 58%, rgba(255,255,255,.18) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

/* Блик, который медленно ползёт по линзе.
   Медленно — намеренно: он должен читаться как блик на стекле,
   а не как бегущая полоска. */
.tabbar__pill::after {
  content: '';
  position: absolute;
  top: -60%;
  bottom: -60%;
  left: -60%;
  width: 52%;
  pointer-events: none;
  background: linear-gradient(100deg,
    transparent 0%,
    rgba(255,255,255,.06) 35%,
    rgba(255,255,255,.30) 50%,
    rgba(255,255,255,.06) 65%,
    transparent 100%);
  animation: gLensGlint 7s var(--g-ease) infinite;
}
@keyframes gLensGlint {
  0%   { left: -60%; }
  55%  { left: 130%; }
  100% { left: 130%; }
}

[data-theme="light"] .tabbar__pill {
  background:
    radial-gradient(110% 90% at 32% 6%,
      rgba(255,255,255,.95) 0%, rgba(255,255,255,.5) 36%, transparent 60%),
    radial-gradient(120% 100% at 50% 108%,
      rgba(20,20,50,.14) 0%, rgba(20,20,50,.05) 44%, transparent 68%),
    rgba(255,255,255,.55);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,1),
    inset 0 -1px 0 rgba(20,20,50,.10),
    0 3px 12px rgba(20,20,50,.14);
}

/* Преломление под линзой.
   Линза увеличивает то, что под ней, — это и есть главный
   признак оптики, а не блик. Иконка и подпись под пилюлей
   увеличиваются, и переход читается как «наведение линзы на
   вкладку». */
.tab.is-active > svg { transform: translateY(-1px) scale(1.16); }
.tab.is-active > span:not(.tab__plus):not(.tab__badge) {
  transform: scale(1.06);
  transform-origin: center;
}
.tab > span:not(.tab__plus):not(.tab__badge) {
  transition: transform .34s cubic-bezier(.34, 1.36, .5, 1);
}

@media (prefers-reduced-motion: reduce) {
  .tabbar__pill::after { animation: none; display: none; }
}

/* При нажатии линза чуть сжимается: так видно, что попали именно
   в неё. Сжатие НЕ едет вместе с перекатом — если анимировать и
   ширину, при быстром переключении она догоняет позицию вторым
   движением, и переход разваливается на два. */
.tabbar__pill.is-press {
  width: calc(20% - 13px);
  margin-left: 3.5px;
  transition: width .12s var(--g-fast), margin-left .12s var(--g-fast),
              transform .34s cubic-bezier(.34, 1.36, .5, 1);
}

/* Иконка с маленькой подложкой: читается как отдельный объект,
   а не как значок в строке текста. */
.tab > svg {
  width: 14px;
  height: 14px;
  padding: 4px;
  border-radius: 11px;
  box-sizing: content-box;
  align-self: center;
  justify-self: center;
  /* Тот же перелёт, что у пилюли, и то же время: иконка должна
     доезжать вместе с ней, а не догонять её вторым движением. */
  transition: background .34s cubic-bezier(.34, 1.36, .5, 1),
              transform .34s cubic-bezier(.34, 1.36, .5, 1);
}
.tab.is-active > svg {
  background: var(--grad-soft);
  box-shadow: inset 0 1px 0 var(--g-edge-lit);
  /* Масштаба нет: он задаётся в разделе про линзу. Дублировать
     одно и то же transform в двух правилах — верный способ однажды
     получить «ничего не увеличивается»: правило ниже по файлу
     перебивает верхнее, и второй писатель о нём не знает. */
}
.tab:active > svg { transform: scale(.86); transition-duration: .1s; }

/* Точки под активной вкладкой больше нет: её роль теперь у пилюли.
   Когда они были обе, движение дублировалось — пилюля ехала, точка
   проявлялась, и переход читался как два разных события вместо
   одного. Два указателя на одну вкладку — это и есть ощущение
   рывка: глаз не понимает, что куда едет. */

/* Бейдж. Раньше свисал с иконки и налезал на соседнюю вкладку. */
.tab__badge {
  position: absolute;
  top: 2px;
  left: 50%;
  translate: 6px 0;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent-2);
  color: #fff;
  font-size: 9.5px;
  font-style: normal;
  display: grid;
  place-items: center;
  font-weight: 800;
  border: 1.5px solid rgba(18,18,27,.9);
  box-sizing: border-box;
}

/* Отступ под плавающую панель.
   Прежний calc(var(--tab-h) + 16px) оставлял под ней ровно
   половину: последняя карточка уезжала под стекло и её нельзя
   было долистать до конца. Теперь отступ берётся от полной
   высоты панели вместе с её полями и запасом. */
.screen__scroll {
  padding-bottom: calc(var(--tab-h) + var(--safe-b) + 26px);
}

/* Лента: у неё своя пагинация во весь экран, нижней панели там
   нет, и прежний отступ просто отрезал кадр видео снизу. */
.screen--feed .screen__scroll { padding-bottom: var(--safe-b); }

/* Шторки открываются поверх панели, и она их перекрывает, а не
   наоборот. Прежний отступ под таб-бар оставлял внизу пустую
   полосу в каждом окне — её было видно как «недокрученный» край. */
.sheet {
  padding-bottom: calc(var(--safe-b) + 8px);
}
.sheet__body { padding-bottom: 16px; }

/* --------------------------------------------------------------------------
   12. Пустые состояния
   -------------------------------------------------------------------------- */

.empty__ico {
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
  box-shadow: inset 0 1px 0 var(--g-edge-lit);
  color: var(--muted);
}

/* Скелет загрузки должен быть стеклянным, а не серым: на тёмном фоне
   серый скелет выглядит как грязное пятно. */
.card-skeleton, .sk {
  background: var(--g-tint-soft);
  border-color: var(--g-edge-soft);
  border-radius: var(--g-r);
}
.sk {
  background-image: linear-gradient(90deg,
    transparent 0%, var(--g-tint-strong) 45%, transparent 90%);
  background-size: 300% 100%;
  animation: gSk 1.4s infinite linear;
}
@keyframes gSk { 0% { background-position: 120% 0; } 100% { background-position: -60% 0; } }

/* --------------------------------------------------------------------------
   13. Плашки загрузки и уведомления
   -------------------------------------------------------------------------- */

.notice {
  background: var(--g-tint-strong);
  -webkit-backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  border: 1px solid var(--g-edge);
  border-radius: var(--g-r);
  box-shadow: var(--g-sh-3), inset 0 1px 0 var(--g-edge-lit);
}

/* --------------------------------------------------------------------------
   14. Аватары
   --------------------------------------------------------------------------
   Единая рамка: аватар в карточке, в шапке и в чате должны выглядеть
   как одно и то же, иначе интерфейс читается как набор макетов.
   -------------------------------------------------------------------------- */

.avatar {
  border: 1px solid var(--g-edge);
  box-shadow: var(--g-sh-1);
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   15. Каркас: пустить пятна внутрь приложения
   --------------------------------------------------------------------------

   Собственная заливка у .app была главной причиной, по которой стекло
   не читалось: под ним была ровная заливка, размывать нечего, и все
   плашки выглядели серыми прямоугольниками. Теперь .app прозрачный,
   и под ним — живой фон из пятен, который стекло преломляет.
   -------------------------------------------------------------------------- */

.app {
  background: var(--bg);
  isolation: isolate;
}
.screen, .tabbar { z-index: 1; }

/* Стеклянная шапка, которая прилипает при прокрутке: раньше она
   заливала экран цветом фона и просто отрезала полосу фона у списка.
   Теперь она стеклянная и список уходит под неё по-настоящему. */
.screen__head--sticky {
  background: var(--g-tint-soft);
  -webkit-backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  border-bottom: 1px solid var(--g-edge-soft);
  box-shadow: 0 8px 24px rgba(0,0,0,.18);
}
[data-theme="light"] .screen__head--sticky { box-shadow: 0 8px 24px rgba(20,20,50,.08); }

/* Отдельного слоя под виньетку больше нет: она стала пятым
   градиентом в .app::above. Лишний слой поверх списка значил бы ещё
   одну композицию при каждой прокрутке ради эффекта, который
   заметен только на краях экрана. */
.screen, .tabbar { z-index: 1; }

/* --------------------------------------------------------------------------
   16. Анимации
   --------------------------------------------------------------------------

   Правило, которому подчинено всё остальное: анимация должна
   объяснять, что произошло, и стоить не больше 300 мс. Всё, что
   дольше, мешает жестам — палец уже отпустил, а картинка ещё едет.
   -------------------------------------------------------------------------- */

/* Появление экрана. Раньше был только fade + 6px вверх; теперь
   появление идёт снизу и с лёгким масштабом — так переход читается
   как «шаг вперёд», а не как «перерисовали». */
/* Появление экрана.
   Раньше экран приезжал с translateY(10px) и масштабом, и это
   движение шло одновременно с перекатом пилюли. Два крупных
   движения в один момент читаются как рывок: глаз не может следить
   за двумя сразу и складывает их в дрожание.

   Теперь экран только проявляется и чуть поднимается — а основное
   движение остаётся за пилюлей, которая одна и рассказывает,
   что произошло. */
.screen.is-active { animation: gScreenIn .24s var(--g-ease); }
@keyframes gScreenIn {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}

/* Появление содержимого списка. Каждый пункт с задержкой по индексу:
   список собирается сверху вниз, и глаз сразу видит верх, а не
   середину. Задержка копится через CSS-переменную --i. */
.g-stagger > * {
  animation: gRise .42s var(--g-ease) backwards;
  animation-delay: calc(var(--i, 0) * 38ms);
}
@keyframes gRise {
  from { opacity: 0; transform: translateY(14px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

/* Нажатие: короткое сжатие с отдачей. Без отдачи нажатие ощущается
   как залипание, интерфейс кажется неотзывчивым. */
.g-press { transition: transform var(--g-fast) var(--g-ease); }
.g-press:active { transform: scale(.94); }

/* Пульс «звонят». Три расходящихся кольца — как в iOS, и их видно
   даже не глядя: по ритму понятно, что это звонок. */
.g-pulse { position: relative; }
.g-pulse::before,
.g-pulse::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--accent);
  animation: gPulse 2.1s var(--g-ease) infinite;
  pointer-events: none;
}
.g-pulse::after { animation-delay: .7s; }
@keyframes gPulse {
  0%   { opacity: .75; transform: scale(1); }
  100% { opacity: 0;   transform: scale(1.75); }
}

/* Мерцание нового: в модерации и в ленте, где важно не пропустить
   только что появившееся. */
@keyframes gGlow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(124,92,255,.0), var(--g-sh-1); }
  50%      { box-shadow: 0 0 0 4px rgba(124,92,255,.22), var(--g-sh-1); }
}
.g-glow { animation: gGlow 2.4s var(--g-ease) infinite; }

/* Живой индикатор записи: точка дышит, пока идёт запись голоса. */
@keyframes gBreath {
  0%, 100% { opacity: .35; transform: scale(.85); }
  50%      { opacity: 1;   transform: scale(1); }
}
.g-breath { animation: gBreath 1.25s var(--g-ease) infinite; }

/* Отправка: сообщение уходит вверх и растворяется — так человек
   понимает, что действие выполнено, ещё до ответа сервера. */
@keyframes gSend {
  0%   { opacity: 1;   transform: none; }
  100% { opacity: 0;   transform: translateY(-14px) scale(.96); }
}
.g-send { animation: gSend .28s var(--g-ease) forwards; }

/* Входящее сообщение: приезжает снизу с подъёмом. */
@keyframes gIn {
  from { opacity: 0; transform: translateY(12px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

/* Переход между вкладками: точка под активной иконкой едет, а не
   прыгает. Меняем --i у навигатора — точка едет одним движением. */
.g-dot {
  position: absolute;
  bottom: 4px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 10px rgba(124,92,255,.7);
  transition: transform var(--g-med) var(--g-ease);
  transform: translateX(calc(var(--i, 0) * var(--tab-step, 0px)));
}

/* --------------------------------------------------------------------------
   17. Голосовой звонок
   --------------------------------------------------------------------------

   Экран переделан под то, что в нём есть: голос. Кадра нет, поэтому
   главное — чтобы человек поверил, что связь идёт. Отсюда пульс
   аватара, таймер длительности и полоса микрофона.
   -------------------------------------------------------------------------- */

.callbox {
  text-align: center;
  padding: 8px 0 4px;
}
.callbox__ava {
  position: relative;
  width: 118px;
  height: 118px;
  margin: 0 auto 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 38px;
  font-weight: 800;
  color: #fff;
  background: var(--grad);
  box-shadow: 0 18px 46px rgba(124,92,255,.42), inset 0 1px 0 var(--g-edge-lit);
}
.callbox__ini { position: relative; z-index: 2; }

/* Таймер: цифры моноширинным шрифтом, иначе двоеточие «прыгает»
   каждую секунду и время невозможно прочесть. */
.callclock {
  margin-top: 14px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .12em;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* Полоса микрофона. Ширина задаётся из JS через --lv. */
.calllevel {
  width: 190px;
  height: 5px;
  margin: 12px auto 4px;
  border-radius: 999px;
  background: var(--g-tint-strong);
  overflow: hidden;
  border: 1px solid var(--g-edge-soft);
}
.calllevel > i {
  display: block;
  height: 100%;
  width: var(--lv, 0%);
  border-radius: 999px;
  background: linear-gradient(90deg, var(--ok), var(--accent) 60%, var(--accent-2));
  transition: width .1s linear;
}

/* Выключенный микрофон должен читаться сразу, без чтения подписи. */
.btn--soft.is-off {
  color: var(--danger);
  border-color: rgba(255,79,94,.4);
  background: rgba(255,79,94,.12);
}

/* --------------------------------------------------------------------------
   20. Значки ролей
   --------------------------------------------------------------------------

   Раньше «МОДЕРАЦИЯ» и «ПРОВЕРЕН» были одного калибра с именем
   человека и стояли рядом с ним же, из-за чего глаз цеплялся не за
   человека, а за плашки под ним: в списке из двадцати друзей
   строка выглядела как новогодняя гирлянда.

   Теперь роль — тихая подпись, а человек остаётся главным. Галочка
   проверки наоборот остаётся единственной яркой меткой: она и
   должна быть видна, ради неё список и читают.
   -------------------------------------------------------------------------- */

.bcard__badge {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: none;
  padding: 2px 7px;
  border-radius: 8px;
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
  color: var(--muted);
  /* Метка не должна тянуть на себя внимание от ника. */
  opacity: .9;
}

.bcard__badge--mod {
  /* Роль сотрудника площадки — это не достижение человека, а
     должность. Раньше она светилась неоном и стояла впереди галочки
     проверки, хотя по смыслу вторична. Оставляем цвет, убираем
     свечение. */
  background: linear-gradient(135deg, rgba(232,56,79,.22), rgba(120,20,32,.22));
  color: #ff8b99;
  border-color: rgba(232,56,79,.3);
  text-shadow: none;
  animation: none;
}

.bcard__badge--ok {
  /* Проверен: тихая метка рядом с именем. Красную анимированную
     галочку ставит handleCheck — она одна и остаётся главной. */
  background: var(--g-tint-strong);
  border-color: var(--g-edge-soft);
  color: var(--muted);
}

.bcard__badge--vip {
  background: rgba(255,199,89,.14);
  color: #ffc759;
  border-color: rgba(255,199,89,.28);
}

/* --------------------------------------------------------------------------
   22. Окна: модалки и шторки
   --------------------------------------------------------------------------

   Это половина интерфейса: чат, настройки, комментарии, подтверждения,
   выбор темы. Раньше окна были сплошной заливкой цвета фона, и они
   выглядели как чужеродные плашки, наложенные на приложение: стекло
   вокруг и матовая плита внутри одного экрана.

   Теперь окно — самый толстый слой стекла в приложении: под ним
   видно то, что было под окном. Так переход «открылось окно» читается
   как приближение, а не как смена экрана.
   -------------------------------------------------------------------------- */

.overlay {
  background: rgba(0,0,0,.52);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  backdrop-filter: blur(18px) saturate(120%);
}
[data-theme="light"] .overlay { background: rgba(20,20,50,.28); }

.sheet,
.modal {
  /* Размытие здесь — единственное внутри окна. Под шторкой лежит
     приложение, которое затемнено и размыто оверлеем, поэтому
     собственный blur шторки нужен только ради края: он делает её
     переход к оверлею мягким. Всё, что внутри, размытием не
     пользуется. */
  background: rgba(16,16,24,.88);
  -webkit-backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  border-color: var(--g-edge);
  box-shadow: 0 -18px 60px rgba(0,0,0,.5), inset 0 1px 0 var(--g-edge-lit);
}
[data-theme="light"] .sheet,
[data-theme="light"] .modal {
  background: rgba(246,246,250,.9);
  box-shadow: 0 -18px 60px rgba(20,20,50,.18), inset 0 1px 0 rgba(255,255,255,.9);
}

.sheet {
  border-radius: var(--g-r-xl) var(--g-r-xl) 0 0;
  animation: gSheetUp .34s var(--g-ease);
}
@keyframes gSheetUp {
  from { transform: translate(-50%, 100%); }
  to   { transform: translate(-50%, 0); }
}

/* Шапка окна: стекло, которое едет вместе с содержимым.
 *
 *  В настройках шестнадцать пунктов, и прокручивать их приходится
 *  долго. Шапка уезжала наверх вместе с ними, и кнопка закрытия
 *  исчезала — чтобы вернуться, приходилось доводить список до
 *  конца и искать её внизу. Это и читалось как «настройки багуются».
 *
 *  Теперь заголовок и крестик зафиксированы и сделаны из того же
 *  стекла, что и само окно: сквозь них видно уезжающий список,
 *  и остаётся ощущение одного цельного предмета. */
.sheet__head {
  position: sticky;
  top: 0;
  z-index: 3;
  flex: none;
  padding: 8px 14px 12px;
  /* Без backdrop-filter. Шапка лежит поверх прокручиваемого тела
     ВНУТРИ уже размытой шторки: размывать тут нечего, а цена
     была бы вторым слоем на каждый кадр прокрутки. Чтобы шапка
     не просвечивала сквозь пролетающий под ней текст, заливка
     плотная и слегка темнее окна. */
  background: rgba(13,13,20,.94);
  box-shadow: 0 1px 0 var(--g-edge-soft), 0 8px 20px rgba(0,0,0,.28);
}
[data-theme="light"] .sheet__head {
  background: rgba(248,248,252,.96);
  box-shadow: 0 1px 0 rgba(10,10,20,.08), 0 8px 20px rgba(20,20,50,.1);
}
.sheet__head b { flex: 1; min-width: 0; }

/* Крестик закрытия. Стоит в шапке, которая не уезжает, поэтому
   доступен на любом месте длинного списка. */
.sheet__x { flex: none; width: 34px; height: 34px; }
.sheet__x svg { width: 17px; height: 17px; }
/* Шапка теперь вне прокручиваемого тела, поэтому между ней и
   содержимым не должно быть разрыва в стекле. */
.sheet__body { padding-top: 4px; }

/* Кнопка закрытия в шапке — круглое стекло, как и остальные. */
.sheet__head .icon-btn { flex: none; }

.modal {
  border-radius: var(--g-r-xl);
  animation: gPop .28s var(--g-ease);
}
@keyframes gPop {
  from { transform: scale(.92) translateY(16px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

.sheet__grab { background: var(--g-edge-lit); }

/* --------------------------------------------------------------------------
   23. Панель модерации
   --------------------------------------------------------------------------
   Это рабочий инструмент, а не витрина: здесь важнее плотность и
   читаемость, чем эффекты. Плитки обзора — стекло, но без свечений:
   загорается только то, что требует решения прямо сейчас. */

.obz__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
.obz__tile {
  padding: 14px 12px;
  border-radius: var(--g-r);
  text-align: left;
  background: var(--g-tint);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
  transition: transform var(--g-fast) var(--g-ease), border-color var(--g-fast) var(--g-ease);
}
.obz__tile:active { transform: scale(.96); }
.obz__tile b {
  display: block;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -1px;
  line-height: 1.05;
}
.obz__tile span { display: block; font-size: 11.5px; color: var(--muted); margin-top: 3px; }
.obz__tile i { display: block; font-size: 10.5px; font-style: normal; color: var(--muted-2); }

/* Зажжённая плитка: ненулевое число, которое ждёт решения. */
.obz__tile--hot {
  border-color: rgba(124,92,255,.45);
  background: var(--grad-soft);
}
.obz__tile--hot b { color: #fff; }

.obz__rows { padding: 4px 0; }
.obz__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 2px;
  border-bottom: 1px solid var(--g-edge-soft);
  font-size: 13.5px;
  color: var(--muted);
}
.obz__row:last-child { border-bottom: 0; }
.obz__row b { font-size: 15px; color: var(--text); font-variant-numeric: tabular-nums; }

.obz__ok {
  padding: 14px;
  border-radius: var(--g-r);
  margin-bottom: 12px;
  font-size: 13.5px;
  color: var(--ok);
  background: rgba(47,214,168,.12);
  border: 1px solid rgba(47,214,168,.3);
}

/* Вкладки панели: прокручиваются, если их много. */
.adm-tabs {
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
}

/* Панель массовых действий. Появляется над списком, когда есть
   что отмечать, и уходит вниз сама, как только выбор снят. */
.adm-bulk {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding: 10px 12px;
  border-radius: var(--g-r);
  background: var(--g-tint);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
}
.adm-bulk.is-on {
  border-color: rgba(124,92,255,.4);
  background: var(--grad-soft);
}
.adm-bulk__n { font-size: 12px; color: var(--muted); }
.mini-btn.is-primary {
  background: var(--grad);
  color: #fff;
  border-color: transparent;
}
.mini-btn[disabled] { opacity: .4; pointer-events: none; }

/* Точка отметки на карточке работы. Крупная и с запасом вокруг:
   попасть пальцем в 12 пикселей можно, но неудобно. */
.adm-card { position: relative; }
.adm-card__check {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge);
  box-shadow: var(--g-sh-1);
}
.adm-card__check svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: var(--muted);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.adm-card__check.is-on { background: var(--grad); border-color: transparent; }
.adm-card__check.is-on svg { stroke: #fff; opacity: 1; }
.adm-card.is-picked { border-color: rgba(124,92,255,.55); }

   /* --------------------------------------------------------------------------
   24. Заставка
   --------------------------------------------------------------------------
   Собрана заново: знак собирается, по названию идёт блик, полоса
   показывает настоящий ход загрузки, подпись — текущий шаг.

   Полоса идёт по `width`, а не по трансформации: загрузка случается
   один раз, прокрутки нет, и лишний слой композитора тут не нужен.
   Анимируется только то, что действительно меняется. */
.splash__mark {
  position: relative;
  width: 104px;
  height: 104px;
  margin: 0 auto 26px;
  display: grid;
  place-items: center;
}
/* Знак собирается, пока приложение поднимается: сжатием и лёгким
   поворотом. Дальше просто стоит — прыгающая иконка отвлекает от
   того, что человек ждёт загрузки, а не любуется. */
.splash__plate {
  position: relative;
  display: grid;
  place-items: center;
  width: 86px;
  height: 86px;
  border-radius: 22px;
  overflow: hidden;
  background: linear-gradient(150deg, rgba(124,92,255,.28), rgba(255,77,141,.24));
  border: 1px solid var(--g-edge);
  box-shadow: var(--g-sh-3), inset 0 1px 0 var(--g-edge-lit);
  animation: gAssemble 1.5s var(--g-ease) both;
}
.splash__plate img { width: 58px; height: 58px; border-radius: 15px; }

@keyframes gAssemble {
  0%   { transform: scale(.6) rotate(-8deg); opacity: 0; }
  55%  { transform: scale(1.06) rotate(1deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

.splash__ring {
  position: absolute;
  inset: 0;
  border-radius: 30px;
  border: 1px solid var(--g-edge);
  animation: gRing 2.8s var(--g-ease) infinite;
}
@keyframes gRing {
  0%, 100% { transform: scale(1); opacity: .45; }
  50%      { transform: scale(1.13); opacity: 0; }
}

/* Блик — главный приём iOS: по стеклу идёт узкая световая
   полоса, и оно вспыхивает. Едет на `left`, а не на transform:
   трансформация создала бы отдельный слой композитора на каждой
   из карточек, а их на заставке до пятнадцати.

   Блик живёт на плитке, а не на знаке. На знаке он не был обрезан,
   а `gShimmer` с 60% до 100% цикла стоит на `left: 140%` — то есть
   за пределами элемента. Без обрезки полоса просто уезжала вбок и
   висела рядом с иконкой тёмным прямоугольником. На плитке своё
   `overflow: hidden` уже есть, и блик обрезается по скруглённым
   углам — там он и должен заканчиваться. */
.splash__plate::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -80%;
  width: 46%;
  border-radius: 999px;
  pointer-events: none;
  z-index: 1;
  background: linear-gradient(100deg,
    transparent 0%, rgba(255,255,255,.14) 32%,
    rgba(255,255,255,.5) 50%, rgba(255,255,255,.14) 68%, transparent 100%);
  animation: gShimmer 2.4s var(--g-ease) infinite;
}
@keyframes gShimmer {
  0%   { left: -80%; }
  60%  { left: 140%; }
  100% { left: 140%; }
}

/* Заголовок. Разрядка съедает последний пробел справа, из-за чего
   строка уезжает влево на половину трекинга — поэтому центрируем
   flex-ом и добавляем такой же отступ справа, а не text-indent:
   text-indent двигает первую строку и с flex-центровкой работает
   непредсказуемо. */
.splash__title {
  display: flex;
  justify-content: center;
  gap: 2px;
  margin: 0;
  padding-right: .22em;
  font-size: 38px;
  font-weight: 300;
  letter-spacing: .22em;
  color: rgba(255,255,255,.94);
  animation: gRise 1.1s var(--g-ease) .1s both;
}
.splash__title span {
  position: relative;
  overflow: hidden;
  display: inline-block;
  color: inherit;
}
/* Вход по вертикали с лёгким уплотнением трекинга: буквы собираются,
   а не выпрыгивают. */
@keyframes gRise {
  from { opacity: 0; transform: translateY(14px); letter-spacing: .34em; }
  to   { opacity: 1; transform: translateY(0); letter-spacing: .22em; }
}
.splash__title span::after {
  content: '';
  position: absolute;
  top: -20%;
  bottom: -20%;
  left: -80%;
  width: 38%;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.55), transparent);
  animation: gShimmer 3s var(--g-ease) infinite;
}
.splash__title span:nth-child(2)::after { animation-delay: 1.5s; }

/* Плавающие карточки на фоне. Своя фаза у каждой: синхронный
   блик превращает заставку в мигающую ёлочную игрушку. */
.splash__card { position: relative; overflow: hidden; }
.splash__card::after {
  content: '';
  position: absolute;
  top: -20%;
  bottom: -20%;
  left: -80%;
  width: 42%;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.34), transparent);
  animation: gShimmer 3.6s var(--g-ease) infinite;
  animation-delay: inherit;
}

.splash__foot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(30px + var(--safe-b));
  padding: 0 42px;
  text-align: center;
  animation: gRise .9s var(--g-ease) .55s both;
}
/* Полоса — обычный блок в потоке подвальной колонки, а не
   position: absolute; left: 50%. Во втором случае её нечем было
   центрировать: единственное transform в правиле — перенос
   влево — затирался анимацией входа с заполнением both. */
.splash__bar {
  position: static;
  height: 3px;
  width: 100%;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255,255,255,.10);
}
.splash__bar i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 12px rgba(124,92,255,.65);
  /* Никакой анимации: ширину задаёт js по настоящему ходу
     загрузки. Своя анимация здесь означала бы, что полоса идёт
     к 100% на таймере и зависает на 100%, даже если загрузка
     встала. */
  transition: width .5s var(--g-ease);
}
.splash__stat {
  margin-top: 12px;
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-2);
}

/* Подпись под знаком. Правило целиком в styles.css (типографика),
   но вход держим здесь — вместе с остальным ходом заставки. */
.splash__tag { animation: gRise .9s var(--g-ease) .32s both; }

/* Узкий экран: заголовок и знак уменьшаются. Раньше эти строки
   жили в styles.css и молча проигрывали glass.css при равной
   специфичности — подгонки на телефоне фактически не было. */
@media (max-width: 420px) {
  .splash__mark { width: 92px; height: 92px; margin-bottom: 22px; }
  .splash__plate { width: 76px; height: 76px; border-radius: 20px; }
  .splash__plate img { width: 52px; height: 52px; border-radius: 14px; }
  .splash__title { font-size: 30px; letter-spacing: .18em; padding-right: .18em; }
}
/* Низкий экран в горизонтали: блок поднимается, чтобы полоса
   с настоящим ходом загрузки не наехала на подпись. */
@media (max-height: 560px) {
  .splash__mark { width: 80px; height: 80px; margin-bottom: 16px; }
  .splash__plate { width: 66px; height: 66px; border-radius: 18px; }
  .splash__plate img { width: 46px; height: 46px; border-radius: 12px; }
  .splash__title { font-size: 26px; }
}
/* Кнопка выхода. Показывается не сразу: предлагать «Открыть»
   в первую сотую секунды смешно — ещё нечего открывать.
   Видимостью управляет класс is-ready, а не анимация входа:
   анимация с both навсегда оставляет конечное opacity, и кнопка
   либо вспыхивает сразу, либо не появляется никогда. */
.splash__skip {
  position: absolute;
  right: 18px;
  top: calc(18px + var(--safe-t));
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity .4s var(--g-ease), transform .4s var(--g-ease);
}
.splash.is-ready .splash__skip { opacity: 1; transform: none; pointer-events: auto; }

@media (prefers-reduced-motion: reduce) {
  .splash__plate, .splash__ring { animation: none; }
  .splash__plate::after, .splash__card::after, .splash__title span::after {
    animation: none;
    display: none;
  }
}

/* --------------------------------------------------------------------------
   25. Связь
   --------------------------------------------------------------------------
   Полоса «нет связи» живёт над шапкой и ездит с прокруткой. В стекле
   она читается как системная плашка iOS, а не как ошибка приложения. */
.netbar {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  width: 100%;
  max-width: 520px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(var(--safe-t) + 8px) 16px 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: #fff;
  background: rgba(255, 79, 94, .92);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
  box-shadow: 0 8px 24px rgba(0, 0, 0, .3);
  animation: gDrop .3s var(--g-ease);
}
.netbar__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #fff;
  flex: none;
}
@keyframes gDrop {
  from { transform: translate(-50%, -100%); opacity: 0; }
  to   { transform: translate(-50%, 0); opacity: 1; }
}

/* --------------------------------------------------------------------------
   26. Уведомления
   -------------------------------------------------------------------------- */

.toast {
  background: var(--g-tint-strong);
  -webkit-backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-3)) saturate(var(--g-sat));
  border: 1px solid var(--g-edge);
  border-radius: 15px;
  box-shadow: var(--g-sh-3), inset 0 1px 0 var(--g-edge-lit);
}

/* --------------------------------------------------------------------------
   27. Потянуть-обновить
   -------------------------------------------------------------------------- */

.ptr {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 250;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, calc(var(--pull, 0px) - 62px));
  transition: transform .22s var(--g-ease), opacity .22s var(--g-ease);
}
.ptr.is-pull { opacity: 1; }
.ptr.is-spin, .ptr.is-done {
  opacity: 1;
  transform: translate(-50%, calc(var(--pull, 0px) - 62px));
}

.ptr__ring {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge);
  -webkit-backdrop-filter: blur(var(--g-blur-2));
  backdrop-filter: blur(var(--g-blur-2));
  box-shadow: var(--g-sh-1), inset 0 1px 0 var(--g-edge-lit);
}
.ptr__ring i {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid var(--g-edge);
  border-top-color: var(--accent);
  transition: transform .2s var(--g-ease);
}
/* Пока палец тянет, значок крутится в такт движению — видно, что
   приложение услышало жест, а не просто зависло. */
.ptr.is-pull .ptr__ring i { transform: rotate(90deg); }
.ptr.is-spin .ptr__ring i { animation: gSpin .8s linear infinite; }
@keyframes gSpin { to { transform: rotate(360deg); } }
.ptr.is-done .ptr__ring i { border-top-color: var(--ok); transform: rotate(0deg); }

.ptr__t { font-size: 11px; color: var(--muted); }

/* --------------------------------------------------------------------------
   28. Поиск по переписке
   -------------------------------------------------------------------------- */

.chatsearch {
  display: none;
  position: relative;
  padding: 8px 16px 0;
}
.chatsearch.is-open { display: block; }
.chatsearch__x {
  position: absolute;
  top: 14px;
  right: 22px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--muted);
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
}
.chatsearch__hits {
  margin-top: 8px;
  max-height: 210px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.chatsearch__none {
  padding: 14px 4px;
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}
.chatsearch__hit {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 11px;
  border-radius: var(--g-r-sm);
  background: var(--g-tint-soft);
  border: 1px solid var(--g-edge-soft);
}
.chatsearch__hit:active { background: var(--g-tint-strong); }
.chatsearch__at { display: block; font-size: 10.5px; color: var(--muted-2); }
.chatsearch__txt {
  display: block;
  font-size: 13.5px;
  color: var(--text);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chatsearch__who { display: block; font-size: 10.5px; color: var(--muted); margin-top: 2px; }

/* Подсветка найденного сообщения. Держится пару секунд и сама
   гаснет: человек видит, куда его привело, и не думает, что
   сообщение кто-то перекрасил навсегда. */
.msg.is-hit { animation: gHit 2.2s var(--g-ease); }
@keyframes gHit {
  0%, 60% { box-shadow: 0 0 0 3px rgba(124,92,255,.55); }
  100%    { box-shadow: none; }
}

/* Граница «непрочитанные». Красная по привычке почты: её ждут и
   не путают ни с чем другим. */
.msg-unread {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 10px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #ff8b99;
}
.msg-unread::before,
.msg-unread::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(232,56,79,.45));
}
.msg-unread::after { background: linear-gradient(90deg, rgba(232,56,79,.45), transparent); }

/* Закладка в карточке работы. Заполненная — заметная, пустая —
   тихая: человек должен видеть разницу с одного взгляда, иначе
   непонятно, сохранено ли уже. */
.text-btn.is-on {
  color: var(--accent);
  border-color: rgba(124, 92, 255, .5) !important;
  background: var(--grad-soft) !important;
}
.text-btn.is-on svg { fill: currentColor; }

/* ==========================================================================
   Разделение 30. Где размытие остаётся, а где убирается
   ==========================================================================

   Всё вышеописанное описывает, как стекло выглядит. Этот блок решает,
   сколько оно стоит. Правило одно и оно проверяемо: размытие живёт
   только на слое, ПОД которым что-то есть. Всё, что лежит поверх
   уже готовой подложки, берёт полупрозрачную заливку.

   Почему заливка вместо размытия выглядит почти так же: размытие
   нужно, чтобы за стеклом не было видно мелких деталей. Под
   заливкой — либо однотонная тёмная подложка окна, либо мягкое
   цветовое пятно фона. Ни того, ни другого размывать незачем.
   Разница видна только там, где за слоем мелкий текст или фотография.
   ========================================================================== */

/* 1. Внутри окон. Под шторкой лежит затемнённый оверлей — размывать
      нечего, а снимок снимается с каждого пункта меню. */
.overlay .btn,
.overlay .input,
.overlay .textarea,
.overlay .icon-btn,
.overlay .chip,
.overlay .mini-btn,
.overlay .pill,
.overlay .menu,
.overlay .set-group,
.overlay .msg__bubble,
.overlay .bcard,
.overlay .my-work,
.overlay .adm-card,
.overlay .glass,
.overlay .g-surface {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* 2. Карточки в прокручиваемых списках.
      Под ними — градиентный фон приложения: ни текста, ни фото.
      Размытие каждой карточки означало, что при прокрутке браузер
      заново снимал и размывал снимок для каждой из десятков
      карточек. Именно это и давало «не полный фпс» в лентах. */
.screen__scroll .bcard,
.screen__scroll .my-work,
.screen__scroll .chat-row,
.screen__scroll .adm-card,
.screen__scroll .spcard,
.screen__scroll .lic-card {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  background: rgba(255,255,255,.062);
}
[data-theme="light"] .screen__scroll .bcard,
[data-theme="light"] .screen__scroll .my-work,
[data-theme="light"] .screen__scroll .chat-row,
[data-theme="light"] .screen__scroll .adm-card,
[data-theme="light"] .screen__scroll .spcard,
[data-theme="light"] .screen__scroll .lic-card {
  background: rgba(255,255,255,.7);
}

/* 3. Всплывающие панели вне окон: тосты, полоса связи, индикатор
      обновления, окно просмотра. Здесь размытие оправдано — под
      ними лежит содержимое приложения, и без него панель выглядит
      вырезанной. Но и их немного, и все они маленькие. */

/* 4. Размытие, которое осталось осознанно:
        — оверлей поверх экрана,
        — сама шторка (только ради мягкого края),
        — таб-бар (под ним постоянно едет список),
        — липкая шапка экрана,
        — фон приложения.
      Шесть слоёв на всё приложение вместо шестнадцати на один
      экран настроек. */

/* 5. Дешёвые подсказки для браузера: содержимое длинных списков
      не рисуется, пока не дошло до экрана. На рынке и в ленте
      это сразу чувствуется при прокрутке. */
.my-work, .bcard, .chat-row, .adm-card, .spcard, .lic-card {
  contain: layout paint style;
}

/* 6. Честное признание слабого железа.
      На бюджетном Android десяток размытий всё равно даёт рывки.
      Тогда стекло становится плотнее, но перестаёт стоить дорого:
      пользователь получает ровный интерфейс вместо красивого
      и лагающего. Лучше плотное стекло, чем стекло с задержкой. */
@media (max-width: 420px) and (pointer: coarse) {
  :root { --g-blur: 20px; --g-blur-2: 12px; --g-blur-3: 24px; --g-sat: 130%; }
  .tabbar {
    -webkit-backdrop-filter: blur(18px);
    backdrop-filter: blur(18px);
  }
}

   Пузыри раньше были сплошной заливкой с цветом автора. На стекле
   это ломается: сплошной прямоугольник не пропускает фон, и чат
   превращается в стену. Пузырь — это самое стекло, которое чат
   себе позволяет.
   -------------------------------------------------------------------------- */

.msg { display: flex; margin-bottom: 6px; animation: gIn .3s var(--g-ease) backwards; }
.msg--me { justify-content: flex-end; }
.msg--them { justify-content: flex-start; }

.msg__bubble {
  position: relative;
  max-width: 78%;
  padding: 9px 13px 7px;
  border-radius: 18px;
  font-size: 14.5px;
  line-height: 1.45;
  word-break: break-word;
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  box-shadow: var(--g-sh-1);
  border: 1px solid var(--g-edge-soft);
}
.msg--them .msg__bubble {
  border-bottom-left-radius: 6px;
  background: var(--g-tint-strong);
}
.msg--me .msg__bubble {
  border-bottom-right-radius: 6px;
  background: var(--grad-soft);
  border-color: rgba(124,92,255,.28);
}

/* Текст сообщения. Отдельный блок нужен, чтобы время внизу не
   вставало в тот же поток: иначе «отправил» и «05:48» налезают
   друг на друга в конце длинной фразы. */
.msg__text { white-space: pre-wrap; }

/* Подвал пузыря: время слева, галочка справа. Отдельной строкой
   и всегда на одном месте — глаз перестаёт искать их в конце фразы. */
.msg__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 2px;
}
.msg__time {
  font-size: 10px;
  font-style: normal;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}
.msg__tick {
  font-style: normal;
  font-size: 10px;
  line-height: 1;
  color: var(--muted-2);
  letter-spacing: -2px;
}
.msg__tick--read { color: var(--accent); }
.msg__tick--wait { opacity: .5; }

/* Разделитель по дням. Стеклянная капсула посреди переписки:
   видно, где был вчерашний разговор, а где сегодняшний. */
.msg-day {
  position: sticky;
  top: 6px;
  z-index: 2;
  width: fit-content;
  margin: 12px auto 10px;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  background: var(--g-tint-strong);
  border: 1px solid var(--g-edge-soft);
  -webkit-backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-2)) saturate(var(--g-sat));
  box-shadow: var(--g-sh-1);
}

.msg-sys {
  margin: 10px auto;
  font-size: 12px;
  color: var(--muted);
  text-align: center;
  max-width: 86%;
}

/* --------------------------------------------------------------------------
   23. Переход между вкладками
   --------------------------------------------------------------------------
   Точка под активной иконкой едет вместо прыжка: это единственная
   деталь, по которой рука понимает, что приложение «живое».
   -------------------------------------------------------------------------- */

.g-dot {
  position: absolute;
  bottom: 5px;
  left: 50%;
  width: 5px;
  height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 10px rgba(124,92,255,.7);
  transition: transform var(--g-med) var(--g-ease);
}

/* Буквы вместо фото. Круг обязан занять всю коробку, а не обхватить
   буквы. Без размеров внутренний span в гриде схлопывается до
   габаритов текста: в строке чата цветным выходил прямоугольник
   13 на 25 пикселей внутри коробки 48 — то есть не аватар, а
   маленькая плашка с буквой посреди пустого места.
   Размеры здесь, а не в разметке: коробки у списка чатов, шапки
   переписки и карточки человека разные. */
.ava__in {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  border-radius: 50%;
  /* Без этого буквы стоят выше центра: у глифов есть выносные
     элементы, и межстрочный интервал уводит их вверх. */
  line-height: 1;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   27. Переписка на весь экран
   --------------------------------------------------------------------------
   Раньше разговор жил в шторке высотой 60% экрана поверх списка чатов.
   На такой высоте помещалось три-четыре сообщения, вложение не
   помещалось никогда, а список под шторкой продолжал подсвечиваться
   непрочитанным — выглядело так, будто сообщение только что пришло
   и не прочитано, хотя его открыли секунду назад.

   Полный экран решает всё сразу: читать не мешает, а возвращение —
   одна кнопка. Панель вкладок на этом экране скрыта: переключать
   раздел посреди разговора нельзя, и она только занимала бы место. */
.screen--chat { padding: 0; }
.screen--chat.is-active ~ .tabbar { display: none; }
/* Переписка управляет высотой сама: колонка «шапка — сообщения —
   поле ввода» должна занять весь экран, иначе сообщения схлопываются
   в ноль, а поле ввода уезжает под панель вкладок. */
.screen--chat > div { height: 100%; }

.chatfs {
  display: flex;
  flex-direction: column;
  height: 100%;
  position: relative;
  isolation: isolate;
}

/* Шапка и поле ввода держатся на месте, прокручивается только
   переписка между ними. Иначе шапка уезжала при первом же листании
   и человек терял имя собеседника в самый разговор. */
.chatfs__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(var(--safe-t) + 8px) 10px 8px;
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--g-surface);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  border-bottom: 1px solid var(--g-edge);
}
.chatfs__head .chat-head__who {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.chatfs .msgs {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 12px 4px;
}
.chatfs .composer--chat {
  flex: none;
  padding-bottom: calc(var(--safe-b) + 10px);
}

/* ---------------- готовые фоны переписки ----------------
   Слоя и эффекты живут на .chatfs__bg (см. выше). Здесь только то,
   что нужно сообщениям поверх: они прозрачны, иначе перекрыли бы
   фон собой. */
.chatfs .msgs { background: transparent; }

/* ---------------- голосовое: плеер ----------------
   Полоса заполнения вместо надписи «Голосовое · N с». Она и шкала
   времени, и признак того, что запись играет сейчас: без неё
   было видно только, что кнопку нажали. */
.msg__voice {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 190px;
  padding: 4px 2px;
  cursor: pointer;
  user-select: none;
}
.msg__voice-play {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--g-surface-2, rgba(255,255,255,.10));
  color: var(--text);
}
.msg__voice-bar {
  flex: 1;
  min-width: 60px;
  height: 4px;
  border-radius: 999px;
  background: var(--g-surface-3, rgba(255,255,255,.14));
  overflow: hidden;
}
.msg__voice-bar i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width .1s linear;
}
.msg__voice-t {
  flex: none;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--muted-2);
  min-width: 30px;
  text-align: right;
}
.msg__voice.is-on .msg__voice-play {
  background: var(--accent);
  color: #fff;
}

/* ---------------- ответ на сообщение ---------------- */

.msg__quote {
  display: block;
  margin: 0 0 6px;
  padding: 5px 9px;
  border-left: 2px solid var(--accent);
  border-radius: 8px;
  background: var(--g-surface-3, rgba(255,255,255,.07));
  font-size: 12.5px;
  line-height: 1.4;
  max-height: 62px;
  overflow: hidden;
}
.msg__quote b {
  display: block;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 1px;
}
.msg__quote span {
  display: block;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msg__pinned {
  position: absolute;
  top: 6px;
  right: 8px;
  color: var(--accent);
  opacity: .8;
}

/* Полоска ответа над полем ввода: видно, на что отвечаешь, и можно
   отменить, не начиная заново. */
.chat-reply {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-top: 1px solid var(--g-edge);
  background: var(--g-surface);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}
.chat-reply__b { flex: 1; min-width: 0; }
.chat-reply__b i {
  display: block;
  font-style: normal;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
}
.chat-reply__b span {
  display: block;
  font-size: 13px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-reply__x {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 0;
  background: var(--g-surface-3, rgba(255,255,255,.08));
  color: var(--muted);
  cursor: pointer;
}

/* ---------------- закреплённое сообщение ---------------- */

.chatpin {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--g-edge);
  background: var(--g-surface);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}
.chatpin__i { flex: none; color: var(--accent); display: grid; place-items: center; }
.chatpin__b { flex: 1; min-width: 0; }
.chatpin__b b {
  display: block;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}
.chatpin__b i {
  display: block;
  font-style: normal;
  font-size: 12.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chatpin__x {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  border: 0;
  background: var(--g-surface-3, rgba(255,255,255,.08));
  color: var(--muted);
  cursor: pointer;
}

/* ---------------- выбор фона ---------------- */

.wallpapers {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-top: 10px;
}
.wallpapers__i {
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  text-align: center;
  color: var(--muted);
  font: 600 10.5px/1 inherit;
}
.wallpapers__v {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 13px;
  margin-bottom: 5px;
  border: 2px solid transparent;
  box-shadow: var(--g-sh-1);
  background: var(--bg);
}
.wallpapers__i--aurora .wallpapers__v {
  background: radial-gradient(110% 80% at 20% 0%, rgba(124,92,255,.7), transparent 60%), var(--bg);
}
.wallpapers__i--dusk .wallpapers__v {
  background: radial-gradient(110% 80% at 80% 0%, rgba(255,77,141,.7), transparent 60%), var(--bg);
}
.wallpapers__i--ocean .wallpapers__v {
  background: radial-gradient(110% 80% at 50% 0%, rgba(56,132,255,.75), transparent 62%), var(--bg);
}
.wallpapers__i--ember .wallpapers__v {
  background: radial-gradient(110% 80% at 50% 100%, rgba(255,122,61,.72), transparent 60%), var(--bg);
}
.wallpapers__i--mint .wallpapers__v {
  background: radial-gradient(110% 80% at 15% 10%, rgba(52,211,153,.7), transparent 60%), var(--bg);
}
.wallpapers__i--graphite .wallpapers__v {
  background: linear-gradient(180deg, rgba(140,140,160,.45), transparent 60%), var(--bg);
}
.wallpapers__i--sand .wallpapers__v {
  background: radial-gradient(110% 80% at 80% 6%, rgba(226,197,138,.72), transparent 58%), var(--bg);
}
.wallpapers__i.is-on .wallpapers__v {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(124,92,255,.22);
}
.wallpapers__i.is-on { color: var(--text); }

/* --------------------------------------------------------------------------
   28. Отчёт о закрытых правах
   --------------------------------------------------------------------------
   Список вида «что закрыто и где это включить». Отдельными плашками
   не тостом: путь в настройках длинный, и тост исчезает раньше, чем
   его успевают переписать, — после этого телефон закрывают, ничего
   не включив.
   -------------------------------------------------------------------------- */
.blocked {
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--g-surface-3, rgba(255,255,255,.06));
  border: 1px solid var(--g-edge);
  margin-bottom: 8px;
}
.blocked b {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 3px;
}
.blocked i {
  display: block;
  font-style: normal;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

/* ---------------- слой фона ----------------
   Фен лежит отдельным слоем под ВСЕМ экраном, а не под перепиской.
   Раньше окрашивались только сообщения, а шапка и поле ввода
   оставались стеклом — фон обрывался на полпути, и переписку
   нельзя было назвать перепиской «на фоне». */
.chatfs__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--bg);
}
.chatfs--aurora .chatfs__bg {
  background:
    radial-gradient(120% 80% at 18% 0%, rgba(124,92,255,.24), transparent 58%),
    radial-gradient(110% 70% at 88% 12%, rgba(37,244,238,.17), transparent 60%),
    var(--bg);
}
.chatfs--dusk .chatfs__bg {
  background:
    radial-gradient(120% 90% at 80% 0%, rgba(255,77,141,.21), transparent 58%),
    radial-gradient(120% 70% at 10% 20%, rgba(124,92,255,.21), transparent 62%),
    var(--bg);
}
.chatfs--ocean .chatfs__bg {
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(56,132,255,.26), transparent 62%),
    linear-gradient(180deg, rgba(12,32,66,.32), transparent 55%),
    var(--bg);
}
.chatfs--ember .chatfs__bg {
  background:
    radial-gradient(110% 80% at 50% 100%, rgba(255,122,61,.24), transparent 60%),
    linear-gradient(180deg, rgba(52,20,8,.30), transparent 60%),
    var(--bg);
}
.chatfs--mint .chatfs__bg {
  background:
    radial-gradient(120% 80% at 15% 10%, rgba(52,211,153,.22), transparent 60%),
    linear-gradient(180deg, rgba(8,40,32,.28), transparent 60%),
    var(--bg);
}
.chatfs--graphite .chatfs__bg {
  background:
    linear-gradient(180deg, rgba(120,120,140,.13), transparent 60%),
    var(--bg);
}
.chatfs--sand .chatfs__bg {
  background:
    radial-gradient(120% 80% at 80% 6%, rgba(226,197,138,.22), transparent 58%),
    linear-gradient(180deg, rgba(60,44,20,.26), transparent 62%),
    var(--bg);
}

/* Своя картинка. Слой всегда тёмный поверх фото: резкий снимок под
   белым текстом нечитаем, и это не лечится размером шрифта — текст
   перестаёт читаться целиком, а не в одном месте. */
.chatfs--img .chatfs__bg {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.chatfs--img .chatfs__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(6,6,12,.58), rgba(6,6,12,.42));
}
.chatfs--img.chatfs--fx-none .chatfs__bg::after {
  /* «Как есть» — человек попросил видеть картинку резкой. Темнее
     обычного, но всё же не чёрный: иначе это уже не «как есть». */
  background: linear-gradient(180deg, rgba(6,6,12,.40), rgba(6,6,12,.30));
}
.chatfs--img.chatfs--fx-blur .chatfs__bg {
  /* Размытие берём масштабом слоя, а не фильтром: filter на всю
     площадь экрана перерисовывает его целиком каждый кадр и
     съедает батарею. */
  background-size: cover;
  transform: scale(1.14);
  filter: blur(14px);
}
.chatfs--img.chatfs--fx-blur .chatfs__bg::after {
  background: rgba(6,6,12,.34);
}
.chatfs--img.chatfs--fx-move .chatfs__bg {
  background-size: auto 130%;
  animation: gWallMove 34s ease-in-out infinite alternate;
}
.chatfs--img.chatfs--fx-move .chatfs__bg::after {
  background: linear-gradient(180deg, rgba(6,6,12,.52), rgba(6,6,12,.40));
}
/* Движение фона намеренно очень медленное и без угла: быстрое или
   с наклоном читается как «что-то поехало», а не как живой фон. */
@keyframes gWallMove {
  0%   { background-position: 18% 50%; }
  50%  { background-position: 52% 44%; }
  100% { background-position: 82% 56%; }
}

/* Движение выключаем там, где человек просил меньше движения, и на
   слабых телефонах: анимация фона идёт всё время, пока открыт чат. */
@media (prefers-reduced-motion: reduce) {
  .chatfs--img.chatfs--fx-move .chatfs__bg { animation: none; background-position: center; }
}
@media (max-width: 420px) and (pointer: coarse) {
  /* Тот же случай: медленный фон на телефоне съедает заряд, а
     переписку всё равно читают текстом, а не картинкой. */
  .chatfs--img.chatfs--fx-move .chatfs__bg { animation-duration: 60s; }
}

/* Шапка и поле ввода поверх фона — стекло, иначе текст в них
   потерялся бы на светлой картинке. */
.chatfs--img .chatfs__head,
.chatfs--img .chat-reply,
.chatfs--img .chatpin {
  background: rgba(10,10,16,.72);
}

/* Своя картинка в окне выбора фона. Превью слева — чтобы человек
   видел, что именно поставил, а не полагался на память. */
.wallpaper-own {
  display: flex;
  gap: 12px;
  align-items: stretch;
}
.wallpaper-own__prev {
  flex: none;
  width: 88px;
  border-radius: 14px;
  background: var(--g-surface-3, rgba(255,255,255,.07)) center/cover no-repeat;
  border: 1px solid var(--g-edge);
  display: grid;
  place-items: center;
  min-height: 88px;
}
.wallpaper-own__prev span {
  font-size: 11px;
  color: var(--muted-2);
  text-align: center;
  padding: 6px;
}
.wallpaper-own__b { flex: 1; min-width: 0; }

.perm-row__tick {
  flex: none;
  color: var(--accent);
  font-size: 15px;
  font-weight: 800;
  margin-left: 8px;
}
.menu__i.is-on { background: var(--g-surface-2, rgba(124,92,255,.12)); }

/* Удаление своего ролика в ленте. Цветом опасности, но не тем же
   красным, что «Купить»-подобные действия: это не покупка, а
   необратимая потеря, и её не должно хотеться нажать вместе с
   остальным. Отдельно стоит, чтобы случайный тап по рельсу не
   сносил видео. */
.rail-btn--del {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--g-edge);
}
.rail-btn--del .rail-btn__ic { color: var(--muted-2); }
.rail-btn--del:active .rail-btn__ic { color: var(--danger); }

/* --------------------------------------------------------------------------
   25. Запертое окно: бан, обрыв сессии
   --------------------------------------------------------------------------
   Обычная модалка закрывается тремя способами, и для сообщения
   «аккаунт заблокирован» все три неправильны: закрыв её, человек
   остаётся в приложении, где каждый запрос отказывает, и не понимает
   почему. Запертое окно не имеет ни крестика, ни реакции на тап по
   фону — выйти можно только предлагаемой кнопкой.
   -------------------------------------------------------------------------- */
.modal--locked {
  /* Чуть плотнее обычного: это не вопрос, а известие. */
  border-color: rgba(255,79,94,.34);
  box-shadow: var(--g-sh-3), 0 0 0 1px rgba(255,79,94,.14);
}
[data-theme="light"] .modal--locked {
  border-color: rgba(214,45,62,.34);
  box-shadow: var(--g-sh-3), 0 0 0 1px rgba(214,45,62,.12);
}

/* --------------------------------------------------------------------------
   26. Настройка прав модератору
   --------------------------------------------------------------------------
   Строка — это переключатель с описанием того, что он разрешает.
   Описание обязательно: «Баны» без пояснения не говорят ничего,
   а именно на этом размышлении владелец и решает, можно ли человеку
   блокировать живых людей. Заливка включённой строки заменяет
   привычную галочку в качестве главного признака — на строке из
   двух строк текста галочка теряется, а заливка видна сразу. */
.perm-list {
  display: grid;
  gap: 8px;
}
.perm-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 11px 13px;
  border-radius: var(--g-r, 14px);
  border: 1px solid var(--g-edge);
  background: var(--g-surface);
  cursor: pointer;
  transition: background .18s var(--g-ease), border-color .18s var(--g-ease);
}
.perm-row:active { transform: scale(.995); }
.perm-row input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.perm-row__b { flex: 1; min-width: 0; }
.perm-row__b b {
  display: block;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}
.perm-row__b i {
  display: block;
  margin-top: 3px;
  font-style: normal;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted-2);
}
.perm-row.is-on {
  background: var(--g-surface-2, rgba(124,92,255,.14));
  border-color: var(--g-edge-lit);
}
[data-theme="light"] .perm-row.is-on {
  background: rgba(124,92,255,.10);
  border-color: rgba(124,92,255,.28);
}

/* --------------------------------------------------------------------------
   24. Круглые аватары в «Людях»
   --------------------------------------------------------------------------
   В «Людях» человек ищет не текст, а лицо: список открывают, когда
   хотят вспомнить, как выглядел тот, с кем переписывались. Скруглённый
   квадрат для этого плох — он читается как иконка приложения, а круг
   читается как человек.

   Правило адресное, а не глобальное: тот же класс аватара стоит в
   панелях модерации и в менюшках профиля, и там квадратные плитки
   осмысленны — они выровнены по общему списку строк.
   -------------------------------------------------------------------------- */

#screen-people .bcard__ava {
  border-radius: 50%;
  /* Ободок отделяет круг от строки. Без него аватар того же
     оттенка, что и подложка, сливается с ней в пятно. */
  box-shadow:
    0 0 0 1px rgba(255,255,255,.10),
    0 2px 6px rgba(0,0,0,.22);
  /* Инициалы в круге центрируются иначе, чем в квадрате: глаз
     читает середину круга, а не середину бокса. */
  line-height: 1;
  overflow: hidden;
}
#screen-people .bcard__ava img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}
/* В свежей теме тёмный ободок на светлом круге превращается в грязь. */
[data-theme="light"] #screen-people .bcard__ava {
  box-shadow:
    0 0 0 1px rgba(20,20,50,.10),
    0 2px 6px rgba(20,20,50,.14);
}

/* --------------------------------------------------------------------------
   19. Уважение к настройке «меньше движения»
   --------------------------------------------------------------------------
   Не украшение, а необходимость: анимации вызывают тошноту у части
   людей, и для них интерфейс обязан быть неподвижным.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
