/* DeutschPuls — Notion/Apple-минимализм, темы из Telegram */
:root {
  --bg: var(--tg-theme-bg-color, #ffffff);
  --bg2: var(--tg-theme-secondary-bg-color, #f6f6f4);
  --text: var(--tg-theme-text-color, #1f1f1f);
  --muted: var(--tg-theme-hint-color, #8a8a86);
  --accent: var(--tg-theme-button-color, #2f7cf6);
  --accent-text: var(--tg-theme-button-text-color, #fff);
  --card: var(--tg-theme-section-bg-color, #ffffff);
  --border: color-mix(in srgb, var(--text) 10%, transparent);
  --ok: #34a26b; --warn: #d98324; --bad: #d95c4a;
  --der: #4a7dd9; --die: #d95c8a; --das: #43a06d;
  --radius: 14px;
}
/* Тёмная тема для веб-версии (в Telegram тема приходит из клиента) */
html.dark {
  --bg: #16171a;
  --bg2: #24262b;
  --text: #e9e9ec;
  --muted: #8f929a;
  --accent: #5b9bff;
  --accent-text: #ffffff;
  --card: #1e2024;
  --border: rgba(255, 255, 255, 0.09);
}
html.dark .vid-thumb { opacity: .92; }
html.dark .seg button.active { box-shadow: none; }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text);
  font: 16px/1.45 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif; }
#app { min-height: 100vh; display: flex; flex-direction: column; }
/* Нижний отступ = таб-бар (84) + макушка Евы (54). Ева стоит над баром и на экране 375×667
   накрывала нижний ряд плиток «Материалов», а прокрутки было всего 38 px — отодвинуть её
   было нечем (приёмка 14.08). Теперь последний ряд всегда можно поднять выше неё. */
#view { flex: 1; padding: 18px 16px calc(138px + env(safe-area-inset-bottom)); max-width: 640px; margin: 0 auto; width: 100%; }

h1 { font-size: 26px; font-weight: 700; letter-spacing: -.02em; margin: 4px 0 2px; }
h2 { font-size: 17px; font-weight: 650; margin: 22px 0 10px; }
.sub { color: var(--muted); font-size: 14px; margin: 0 0 14px; }

.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; margin-bottom: 10px; }
.row { display: flex; align-items: center; gap: 12px; }
.grow { flex: 1; min-width: 0; }
.muted { color: var(--muted); font-size: 13px; }
.clickable { cursor: pointer; transition: transform .06s ease; }
.clickable:active { transform: scale(.985); }

button.btn { appearance: none; border: 0; border-radius: 12px; padding: 12px 16px; font-size: 15px;
  font-weight: 600; background: var(--accent); color: var(--accent-text); cursor: pointer; }
button.btn.ghost { background: var(--bg2); color: var(--text); }
button.btn.small { padding: 8px 12px; font-size: 13px; border-radius: 10px; }
button.btn:disabled { opacity: .5; }

/* таббар */
/* z-index задан явно: Ева живёт внутри бара, и порядок слоёв не должен зависеть
   от порядка элементов в разметке */
#tabbar { position: fixed; bottom: 0; left: 0; right: 0; z-index: 70; display: flex; justify-content: space-around;
  background: color-mix(in srgb, var(--bg) 86%, transparent); backdrop-filter: blur(14px);
  border-top: 1px solid var(--border); padding: 6px 8px calc(6px + env(safe-area-inset-bottom)); }
.tab { flex: 1; background: none; border: 0; color: var(--muted); font-size: 11px; font-weight: 600;
  display: flex; flex-direction: column; gap: 2px; align-items: center; padding: 6px 0; cursor: pointer; }
.tab .ico { font-size: 20px; line-height: 0; display: block; }
.tab .ico svg { width: 23px; height: 23px; display: block; }
.tab.active { color: var(--accent); }
.tab.active .ico svg { stroke-width: 2.1; }

/* Сегодня */
.ring-wrap { display: flex; align-items: center; gap: 18px; }
.ring { width: 92px; height: 92px; border-radius: 50%; display: grid; place-items: center;
  background: conic-gradient(var(--accent) calc(var(--p) * 1%), var(--bg2) 0); }
.ring > div { width: 72px; height: 72px; border-radius: 50%; background: var(--card); display: grid;
  place-items: center; text-align: center; font-weight: 700; font-size: 15px; }
.ring small { display: block; font-weight: 500; color: var(--muted); font-size: 10px; }
.block { margin-bottom: 8px; }
.bar { height: 6px; border-radius: 3px; background: var(--bg2); overflow: hidden; margin-top: 8px; }
.bar > div { height: 100%; background: var(--accent); border-radius: 3px; transition: width .3s; }
.block.done .bar > div { background: var(--ok); }
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }

/* Видео */
.lvl-picker { display: flex; gap: 6px; margin-bottom: 14px; }
.lvl { padding: 7px 14px; border-radius: 999px; background: var(--bg2); border: 1px solid transparent;
  font-size: 14px; font-weight: 600; color: var(--muted); cursor: pointer; }
.lvl.active { background: var(--accent); color: var(--accent-text); }
.vid-thumb { width: 92px; height: 52px; border-radius: 8px; object-fit: cover; background: var(--bg2); flex: none; }
.exercise-thumb { display: grid; place-items: center; overflow: hidden; color: var(--muted); }
.exercise-thumb > * { grid-area: 1 / 1; }
.exercise-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.exercise-thumb-fallback { display: none; width: 100%; height: 100%; align-items: center; justify-content: center; }
.exercise-thumb-fallback .ic { width: 24px; height: 24px; }
.exercise-thumb.is-fallback img { display: none; }
.exercise-thumb.is-fallback .exercise-thumb-fallback { display: flex; }
.vid-title { font-weight: 600; font-size: 14.5px; line-height: 1.3; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* Плеер */
#player-shell { position: relative; padding-top: 56.25%; border-radius: var(--radius); overflow: hidden;
  background: #000; margin-bottom: 12px; }
#player-shell iframe { position: absolute; inset: 0; width: 100%; height: 100%; }
.subline { font-size: 19px; line-height: 1.5; font-weight: 550; margin: 4px 0 2px; }
.subline .w { cursor: pointer; border-radius: 5px; padding: 0 1px; }
.subline .w:active { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.subru { color: var(--muted); font-size: 14.5px; min-height: 20px; }
/* перенос обязателен: «полноэкранный режим» длиннее прежнего «театра» и не влезал в строку на телефоне */
.subnav { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.backbtn { background: none; border: 0; color: var(--accent); font-size: 15px; font-weight: 600;
  padding: 0 0 10px; cursor: pointer; }

/* bottom sheet */
/* Высота окна считается от ЖИВОЙ высоты вьюпорта (--app-vh ставит app.js из
   tg.viewportStableHeight), а не от vh. На айфоне vh — это высота вместе с полосами
   интерфейса, и настройки вылезали за нижний край экрана: кнопку «Сохранить» было
   не достать (Артём 15.08). viewportStableHeight ещё и учитывает поднятую клавиатуру,
   поэтому поле даты не выталкивает окно наружу. */
#sheet { position: fixed; inset: 0; height: var(--app-vh, 100%); background: rgba(0,0,0,.45);
  z-index: 80; display: flex; align-items: flex-end; overscroll-behavior: contain; }
#sheet.hidden { display: none; }
/* max-height в ПРОЦЕНТАХ от этой же высоты: окно физически не может стать больше экрана.
   overscroll-behavior не пускает прокрутку дальше окна — без него докрутка до конца списка
   утаскивала страницу под ним, и окно «плавало». */
#sheet-body { background: var(--card); width: 100%; border-radius: 18px 18px 0 0; padding: 20px 18px
  calc(20px + env(safe-area-inset-bottom)); max-height: 86%; overflow-y: auto;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
/* Заголовки внутри окна теснее, чем на странице: четыре раздела настроек с отступом 22 px
   не помещались на экране 375x667 целиком. */
#sheet-body h2 { margin: 16px 0 8px; }
/* Пока открыто окно, страница под ним не прокручивается. */
body.sheet-open { overflow: hidden; }
.sheet-word { font-size: 24px; font-weight: 700; letter-spacing: -.01em; }
.art-der { color: var(--der); } .art-die { color: var(--die); } .art-das { color: var(--das); }
.imgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 10px; }
.imgrid img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px; cursor: pointer;
  border: 2px solid transparent; }
.imgrid img.sel { border-color: var(--accent); }

/* Карточки */
.flash { min-height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 10px; padding: 24px 16px; }
.flash img { max-width: 180px; max-height: 130px; border-radius: 12px; object-fit: cover; }
.flash .word { font-size: 28px; font-weight: 700; }
.flash .ex { color: var(--muted); font-size: 15px; }
.rate { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 14px; }
.rate button { padding: 12px 4px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg2);
  font-weight: 650; font-size: 13.5px; cursor: pointer; color: var(--text); }
.rate .r1 { color: var(--bad); } .rate .r4 { color: var(--ok); }

/* Упражнения */
.opt { display: block; width: 100%; text-align: left; margin-top: 8px; padding: 12px 14px;
  border-radius: 12px; border: 1px solid var(--border); background: var(--card); font-size: 15px;
  cursor: pointer; color: var(--text); }
.opt.right { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, var(--card)); }
.opt.wrong { border-color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, var(--card)); }

/* Прогресс */
.big { font-size: 34px; font-weight: 750; letter-spacing: -.02em; }
.days { display: flex; align-items: flex-end; gap: 4px; height: 72px; margin-top: 10px; }
.days .d { flex: 1; background: var(--accent); border-radius: 4px 4px 0 0; min-height: 2px; opacity: .85; }
.days .d.zero { background: var(--bg2); }
/* День, засчитанный в серию, и день с закрытым планом — разные столбики: первый обычный,
   второй в цвете «готово». Пустой день остаётся серым. */
.days .d.ok { opacity: 1; }
.days .d.full { background: var(--ok); opacity: 1; }
.cp { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px dashed var(--border); font-size: 14px; }
.cp:last-child { border: 0; }
.cp .date { color: var(--muted); flex: none; width: 84px; }
.cp.past { opacity: .5; }

/* Онбординг-мастер */
.ob-progress { height: 4px; background: var(--bg2); border-radius: 2px; margin: 4px 0 18px; }
.ob-progress > div { height: 100%; background: var(--accent); border-radius: 2px; transition: width .3s; }
.lang-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
.lang-tile { border: 1px solid var(--border); background: var(--card); color: var(--text);
  border-radius: 14px; padding: 18px 8px; cursor: pointer; text-align: center; }
.lang-tile .lf { font-size: 34px; margin-bottom: 6px; }
.lang-tile.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--card)); }
.ob-list { display: flex; flex-direction: column; gap: 8px; }
.ob-opt { border: 1px solid var(--border); background: var(--card); color: var(--text); border-radius: 12px;
  padding: 13px 16px; font-size: 15px; font-weight: 600; text-align: left; cursor: pointer; }
.ob-opt.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--card)); }
.ichip { border: 1px solid var(--border); background: var(--card); color: var(--text); border-radius: 999px;
  padding: 9px 15px; font-size: 14.5px; font-weight: 600; cursor: pointer; }
.ichip.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }

/* Лента рекомендаций */
.feed { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.feed-item { display: flex; gap: 12px; align-items: center; background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 10px; }
.feed-thumb { width: 88px; height: 50px; border-radius: 8px; object-fit: cover; background: var(--bg2); flex: none; }
.feed-emoji { display: grid; place-items: center; font-size: 24px; }
.feed-label { color: var(--accent); font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: .02em; }
.feed-title { font-weight: 600; font-size: 14.5px; line-height: 1.25; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.feed-body { min-width: 0; }

/* Идеи карточек */
.idea-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px dashed var(--border); }
.idea-row:last-child { border: 0; }

/* Онбординг */
.wgrid { display: flex; flex-wrap: wrap; gap: 8px; }
.wchip { border: 1px solid var(--border); background: var(--card); color: var(--text); border-radius: 999px;
  padding: 8px 14px; font-size: 14.5px; font-weight: 600; cursor: pointer; }
.wchip .wru { display: block; font-size: 11px; font-weight: 500; color: var(--muted); }
.wchip.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.wchip.on .wru { color: color-mix(in srgb, var(--accent-text) 75%, transparent); }
.exam-card { border-left: 3px solid var(--accent); }

.soon { font-size: 10px; font-weight: 700; color: var(--warn); border: 1px solid var(--warn);
  border-radius: 6px; padding: 1px 5px; vertical-align: middle; margin-left: 4px; }
.gobtn { color: var(--accent); font-weight: 650; font-size: 13.5px; white-space: nowrap; }
.ctx { padding: 8px 0; border-bottom: 1px dashed var(--border); font-size: 14.5px; }
.ctx:last-child { border: 0; }

/* Синонимы в шите слова. Чип — само слово и его перевод под ним: перевод нужен сразу,
   иначе выбирать не из чего, а провалиться в каждый ради проверки — десять нажатий. */
.syn-sense { margin-top: 12px; }
.syn-sense:first-of-type { margin-top: 4px; }
.syn-h { font-size: 13px; color: var(--muted); }
.syn-sense .chips { margin-top: 6px; }
.syn { display: inline-flex; flex-direction: column; align-items: flex-start;
  padding: 6px 11px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--card); color: var(--text); font: inherit; text-align: left; cursor: pointer; }
.syn b { font-weight: 600; font-size: 15px; line-height: 1.25; }
.syn span { font-size: 12px; color: var(--muted); line-height: 1.25; }
.syn:active { background: var(--bg2); }

/* Хаб материалов */
.hubgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
/* Иконка и название по центру плитки: без подписей снизу прижатое к левому краю
   содержимое висело в углу большой пустой карточки. */
.hub-tile { text-align: center; margin: 0; }
.hub-tile .ico { font-size: 26px; margin-bottom: 6px; line-height: 0;
  display: flex; align-items: center; justify-content: center; }
/* нарисованные иконки разделов: высота слота фиксирована, чтобы плитки не прыгали,
   пока картинки грузятся */
.hub-tile .ico { min-height: 40px; }
.ic-img { width: 40px; height: 40px; object-fit: contain; display: inline-block; vertical-align: middle; }
/* та же иконка в строку с текстом — по высоте строки, иначе разгоняет межстрочный интервал */
.ic-img.sm { width: 22px; height: 22px; vertical-align: -.25em; }
.ic-img.md { width: 34px; height: 34px; }
/* на пустых экранах и экранах ошибок картинка была крупным эмодзи в 36-40px */
.ic-img.lg { width: 52px; height: 52px; }

/* Иконки кнопок из спрайта: тянутся за размером шрифта кнопки и красятся в её цвет */
.ic { width: 1.15em; height: 1.15em; fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round; vertical-align: -.19em; flex: none; }
.ic.solid { fill: currentColor; stroke: none; }
.ic.big { width: 1.5em; height: 1.5em; vertical-align: -.32em; }
.hub-tile .muted { font-size: 12.5px; margin-top: 2px; }
.hubgrid .hub-tile:first-child { grid-column: 1 / -1; }

/* Клипы */
.short-shell { position: relative; flex: 1; aspect-ratio: 9/16; max-height: 62vh; border-radius: var(--radius);
  overflow: hidden; background: #000; }
.short-shell iframe { position: absolute; inset: 0; width: 100%; height: 100%; }
.short-side { display: flex; flex-direction: column; gap: 10px; justify-content: center; }
.short-side .btn { padding: 14px 16px; }

/* Читалка */
.reader { font-size: 17.5px; line-height: 1.75; }
.reader .rp { margin: 0 0 14px; }
.reader .w { cursor: pointer; border-radius: 5px; padding: 0 1px; }
.reader .w:active { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.reader .w.speaking { background: color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: 5px; }
.reader .sent.playing { background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius: 6px; box-decoration-break: clone; -webkit-box-decoration-break: clone; }

/* Чат тьютора */
#chatlog { max-height: 52vh; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.msg { padding: 10px 14px; border-radius: 14px; max-width: 88%; white-space: pre-wrap; font-size: 15px; }
.msg.user { align-self: flex-end; background: var(--accent); color: var(--accent-text); border-bottom-right-radius: 4px; }
.msg.assistant { align-self: flex-start; background: var(--bg2); border-bottom-left-radius: 4px; }
.chat-in { border: 1px solid var(--border); background: var(--card); color: var(--text); border-radius: 12px;
  padding: 12px 14px; font-size: 15px; outline: none; min-width: 0; }
.chat-in:focus { border-color: var(--accent); }

/* кнопка театра — в ряду под сабами (после «медленно»); в театре скрыта */
.theater .fs-btn { display: none; }
/* Ева: спец-поза «выглядывает из-за стола» — картинка целиком, пиксель в пиксель;
   точная посадка на кромку таб-бара считается в JS (mountEva/place) */
/* ---------- Ева: покой, наведение, выскакивание (просьба Артёма 13.08) ----------
   Всё на ОДНОЙ картинке. Существующие 20 поз как кадры не годятся: они обрезаны каждая
   по своему силуэту, и персонаж между ними скачет по размеру почти на треть — при
   проигрывании подряд это не движение, а пульсация. Поэтому движение делает код. */
/* Ева ЖИВЁТ ВНУТРИ своей платформы (таб-бара, а при открытом диалоге — панели) и стоит
   на её кромке через bottom:100%. Раньше позиция считалась в JS от высоты окна — и в
   Телеграме при прокрутке окно меняет высоту без события resize: Еву кидало вверх-вниз
   как мячик, а на компе она висела с зазором. Теперь считать нечего: браузер держит её
   на кромке сам, платформа и Ева двигаются одним куском (фидбек Артёма 13.08). */
/* Праздник вехи: затемнение лежит поверх всего (z-index 95), а Ева живёт внутри таб-бара
   и по слоям выше него подняться не может. Поэтому на время прыжка поднимаем сам бар,
   а его подложку и подписи гасим — он растворяется в затемнении, видно только Еву. */
body.cheering #tabbar { z-index: 96; background: transparent; backdrop-filter: none;
  border-color: transparent; }
body.cheering #tabbar .tab { opacity: 0; transition: opacity .2s; }

/* Полноэкранный просмотр: на экране только видео и субтитры. Таб-бар и Ева живут ВНЕ
   плеера и лежали поверх картинки — у бара свой z-index 70, у слоя театра 60, поэтому
   он перекрывал кадр и на телефоне, и на компьютере. Прячем оба целиком: в театре
   переключать вкладки незачем, а Ева закрывала правый нижний угол с субтитрами.
   Метку ставит toggleTheater, снимает выход из театра и stopPlayerLoop. */
body.theater-on #tabbar,
body.theater-on #eva-peek,
body.theater-on #eva-hint { display: none; }

/* --eva-sink — насколько она утоплена за кромку. Одно место на все состояния и на пузырь
   подсказки: закрепление остаётся прежним, меняется только глубина посадки. */
:root { --eva-sink: 6px; }
/* Холст кадра высокий (в прыжке она улетает далеко вверх), но САМ БЛОК в покое ростом
   с торчащую макушку: иначе прозрачный прямоугольник в треть экрана перехватывал бы
   касания по содержимому под ним. Картинка при этом свободно вылезает за верх блока. */
/* Контракт «Ева целиком» для peek-фигуры: её рост НИКОГДА не больше того места, что есть
   над платформой. Раньше рост был зашит числом 385 px, а платформой при открытом диалоге
   становится сама панель: со списком сценок Еве срезало верхом окна 115 px на 375×667 и
   177 px на 320×568 — то есть голову целиком (приёмка 20.08). Место считает JS (evaRoom)
   и кладёт в --eva-room; когда места хватает, значение равно полному росту и ничего
   не меняется. Пропорции держит height+width:auto, кадрирования тут нет и не было. */
#eva-peek { --eva-figure-full: 385px;
  --eva-h: min(var(--eva-figure-full), var(--eva-room, var(--eva-figure-full)));
  position: absolute; right: 12px; bottom: calc(100% - var(--eva-sink));
  width: 132px; height: 54px; cursor: pointer;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .25)); }
#eva-peek.tall { height: var(--eva-h); } /* пока движется и пока стоит — ловим мышь по всей фигуре */
/* В покое блок ловил касания на всю ширину кадра (132 px), а сама макушка занимает в нём
   только середину — 21..115. Пустые поля по краям съедали тапы по плитке под Евой, и нажатие
   не открывало НИЧЕГО (приёмка 14.08). Сужаем коробку до силуэта, картинку при этом оставляем
   на прежнем месте до пикселя: ширина ей задана явно, а сдвиг компенсирует новую границу. */
#eva-peek.idle { right: 28px; width: 95px; }
/* Привязка по ПРАВОМУ краю, а не по левому: рост теперь задаётся высотой, ширина считается
   сама, и при нехватке места фигура должна ужиматься к своему углу, а не уползать вбок.
   Числа подобраны так, чтобы посадка осталась прежней до пикселя: макушка в покое
   по-прежнему занимает 132 px и торчит на 12 px правее коробки. */
#eva-peek.idle img { right: -16px; }
/* картинка не ловит касания сама: иначе её прозрачный верх перехватывал бы клики
   по содержимому, ради чего блок и делался низким */
#eva-peek img { position: absolute; right: 0; left: auto; bottom: 0;
  height: var(--eva-h); width: auto;
  pointer-events: none; transform-origin: 50% 100%; will-change: transform; }

/* в покое — еле заметное дыхание, чтобы фигурка не выглядела наклейкой.
   Только в покое: во время подъёма дыхание съезжает с кадрами и рябит. */
@media (prefers-reduced-motion: no-preference) {
  #eva-peek.idle img { animation: eva-breathe 4.2s ease-in-out infinite; }
}
@keyframes eva-breathe {
  0%, 100% { transform: translateY(0) scaleY(1); }
  50%      { transform: translateY(1.5px) scaleY(.992); }
}

/* Подъём и спуск рисуют СМЕНОЙ КАДРОВ (evaClimb в app.js), а не сдвигом картинки.
   Сдвиг всей фигуры вверх — это крышка мусорного ведра, а не персонаж (фидбек Артёма 13.08). */

#eva-panel { position: fixed; left: 0; right: 0; bottom: 0; z-index: 80; background: var(--card);
  border-top: 2px solid var(--border); border-radius: 16px 16px 0 0;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 30px rgba(0, 0, 0, .25); }
@media (prefers-reduced-motion: no-preference) {
  #eva-panel { animation: eva-panel-in .26s cubic-bezier(.2, .9, .3, 1.08) 1; }
}
@keyframes eva-panel-in { from { transform: translateY(100%); } to { transform: none; } }
/* Значка с лицом в шапке нет: Ева стоит рядом в полный рост, второе лицо лишнее */
.eva-head { display: flex; justify-content: space-between; align-items: center; }
.eva-body { display: flex; gap: 10px; margin: 8px 0; align-items: flex-end; }
.eva-log { flex: 1; max-height: 38vh; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.eva-msg { background: var(--bg2); border-radius: 12px; padding: 8px 10px; font-size: 14.5px;
  white-space: pre-wrap; align-self: flex-start; max-width: 95%; }
.eva-msg.me { background: var(--accent); color: var(--accent-text); align-self: flex-end; }
/* Кнопка «прослушать» у реплики Евы. Своих стилей у неё не было вообще, поэтому браузер
   рисовал её по умолчанию — белым прямоугольником с рамкой, и в тёмной теме это выглядело
   как дырка в пузыре (поймал Артём 14.08). Теперь она часть реплики: без фона, цвет от текста. */
.msg-say { appearance: none; background: none; border: 0; padding: 0 0 0 6px; cursor: pointer;
  color: inherit; opacity: .55; vertical-align: -.15em; line-height: 1; }
.msg-say:hover { opacity: 1; }
.msg-say:disabled { opacity: .3; cursor: default; }
.msg-say .ic { width: 15px; height: 15px; }
.eva-controls { display: flex; gap: 8px; }
#eva-mic.rec { background: #d33; color: #fff; }
/* Ева в онбординге */
.eva-ob { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; }
.eva-ob img { flex: 0 0 auto; }   /* размер задаёт контракт img.eva-figure--onboard */
.eva-ob .bubble { background: var(--bg2); border-radius: 14px; padding: 10px 12px; font-size: 15px; }

/* Онбординг-тур Евы */
/* Затемнение живёт на кольце, а не на подложке: тень наружу гасит весь экран, а внутри
   кольца остаётся дырка — только так подсвеченная плитка действительно видна. Раньше
   подложка затемняла и её тоже, и «подсветка» гасила ровно то, про что шла речь.
   На шагах без цели подложка гасит экран сама — класс dim. */
#eva-tour { position: fixed; inset: 0; z-index: 90; }
#eva-tour.dim { background: rgba(0, 0, 0, .55); }
#eva-tour .tour-ring { position: fixed; border: 3px solid var(--accent); border-radius: 12px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55); pointer-events: none; transition: all .2s; }
/* Карточка с текстом уезжает наверх, когда подсвечен элемент в нижней половине экрана:
   иначе Ева закрывает собой ровно то, про что рассказывает. */
#eva-tour .tour-card.up { bottom: auto; top: 16px; }
#eva-tour .tour-card { position: fixed; left: 12px; right: 12px; bottom: 84px;
  display: flex; gap: 10px; align-items: flex-end; }
#eva-tour .tour-card img { flex: 0 0 auto; }   /* размер задаёт контракт img.eva-figure--tour */
#eva-tour .tour-bubble { flex: 1; background: var(--card); border-radius: 16px; padding: 12px 14px;
  font-size: 15px; box-shadow: 0 6px 24px rgba(0, 0, 0, .3); }
#eva-tour .tour-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }

/* Подсветка знакомых слов: ws1 — смотрел перевод, ws2 — в карточках, выученное без метки.
   Цвета заданы своими константами, а не через --accent: акцент в телеграме приходит из темы
   клиента и бывает любым, а эти два состояния обязаны отличаться друг от друга всегда.
   Оттенки средней светлоты — читаются и на белом, и на тёмном фоне: тёмная тема телеграма
   класс html.dark не ставит, отдельной палитры для неё не будет.
   :not(.sel):not(.speaking) стоит не для красоты — выделение пальцем и слово, которое сейчас
   читает озвучка, должны перебивать подсветку независимо от порядка правил в файле.
   Прозрачность подчёркивания 85% выбрана замером, а не на глаз: на 60% полоса на белом фоне
   давала контраст 1.88 — на светлой теме её почти не видно. На 85% выходит 2,5 на белом и
   4,6 на тёмном при том же спокойном виде. Само слово при этом читается как обычно. */
:root { --ws-seen: #c9862b; --ws-card: #5a76cf; }
.w.ws1, .w.ws2 { border-radius: 5px; padding: 0 1px; }
.w.ws1:not(.sel):not(.speaking) { box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--ws-seen) 85%, transparent); }
.w.ws2:not(.sel):not(.speaking) { background: color-mix(in srgb, var(--ws-card) 15%, transparent);
  box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--ws-card) 85%, transparent); }
/* Полноэкранный режим: белые субтитры на чёрном. Подчёркивание там и так видно, а вот
   заливку «в карточках» на 15% съедает чёрный фон — только ей и добавляем. */
.theater .subline .w.ws2:not(.sel) { background: color-mix(in srgb, var(--ws-card) 26%, transparent); }

/* тап-режим: выделенное слово и нижний бар выбора */
.w.sel { background: var(--accent); color: var(--accent-text); border-radius: 5px; }
#selbar { position: fixed; left: 8px; right: 8px; bottom: calc(8px + env(safe-area-inset-bottom));
  z-index: 78; display: flex; align-items: center; gap: 8px; background: var(--card);
  border: 1px solid var(--border); border-radius: 14px; padding: 8px 10px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .22); }
#selbar .sel-main { flex: 1; min-width: 0; overflow: hidden; }
#selbar .sel-main b { font-size: 16px; }
.theater #selbar { bottom: calc(14px + env(safe-area-inset-bottom)); z-index: 82; }

/* кривая забывания под карточками */
.memcard { background: var(--card); border: 1px solid var(--border); border-radius: 14px;
  padding: 10px 12px; margin: 8px 0 12px; }
.memhead { font-weight: 600; margin-bottom: 8px; font-size: 14.5px; }
.membar { display: flex; height: 12px; border-radius: 6px; overflow: hidden; background: var(--bg2); }
.membar > div { height: 100%; }
.memlegend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: 12.5px; color: var(--muted); }
.memlegend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 4px; vertical-align: middle; }

/* Лента дня на главной (макет Артёма): крупные карточки с превью */
.day-h { margin: 16px 0 8px; font-size: 17px; }
.day-card { padding: 0; overflow: hidden; }
.day-thumb { position: relative; width: 100%; aspect-ratio: 16 / 9; background: var(--bg2); }
.day-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.day-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 44px; color: #fff; text-shadow: 0 2px 14px rgba(0, 0, 0, .55); }
/* Белая кнопка «играть» лежала на белом: у плитки без превью фон --bg2 = #f6f6f4, контраст
   1.08:1 — кнопки просто не видно. Тень не спасала: внутри теперь не символ, а <svg> из
   спрайта, а text-shadow рисует тень БУКВ (приёмка 14.08). Тёмный кружок под иконкой
   работает на любой подложке — и на светлой заглушке, и на ярком кадре из фильма. */
.day-play::before { content: ""; position: absolute; top: 50%; left: 50%; width: 1.5em; height: 1.5em;
  margin: -.75em 0 0 -.75em; border-radius: 50%; background: rgba(0, 0, 0, .5); }
.day-play svg { position: relative; filter: drop-shadow(0 1px 5px rgba(0, 0, 0, .5)); }
.day-thumb-text { display: flex; align-items: center; justify-content: center; font-size: 52px; position: relative; }
.day-thumb-text img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.day-thumb-text img + .day-emoji { display: none; } /* картинка есть — эмодзи не нужен */
.text-hero { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius); margin-bottom: 12px; }
.day-body { padding: 10px 14px 12px; }
.day-body b { font-size: 15.5px; }

/* бейджи прогресса грамматики */
.gbadge { display: inline-block; margin-top: 6px; margin-right: 6px; padding: 2px 8px; border-radius: 10px;
  font-size: 12px; color: #fff; background: #5b9bff; }
.gbadge.due { background: #e8a13a; }
.card.gdue { border-color: #e8a13a; }

/* выход из театра: только на телефонах без Esc; вне театра скрыта */
.theater-close { display: none; }
.theater .theater-close { display: block; position: fixed; top: calc(10px + env(safe-area-inset-top)); right: 12px;
  z-index: 70; border: 0; cursor: pointer; background: rgba(0, 0, 0, .55); color: #fff;
  border-radius: 50%; width: 40px; height: 40px; font-size: 18px; }

/* Театр: наш фулскрин с живыми интерактивными сабами */
.player-layout.theater { position: fixed; inset: 0; z-index: 60; background: #000;
  display: flex !important; flex-direction: column !important; padding: 0; margin: 0; }
.theater .player-col { flex: 1 1 auto; min-height: 0; display: flex; }
.theater #player-shell { padding-top: 0; flex: 1; border-radius: 0; margin: 0; }
.theater .side-col { flex: 0 0 auto; }
.theater .side-col .card { background: rgba(18, 18, 20, .92); border: 0; border-radius: 0;
  padding: 14px 22px calc(14px + env(safe-area-inset-bottom)); height: auto; }
.theater .subline { color: #fff; font-size: 26px; text-align: center; }
.theater .subru { color: rgba(255, 255, 255, .65); font-size: 17px; text-align: center; }
.theater .subnav { justify-content: center; }

/* Комп: шире колонка, крупнее сетки, всё важное — в один экран без прокрутки */
.player-layout { display: flex; flex-direction: column; gap: 12px; }
.side-col { display: flex; flex-direction: column; }
@media (min-width: 900px) {
  #view { max-width: 820px; padding: 14px 16px calc(76px + env(safe-area-inset-bottom)); }
  #view.wide { max-width: 1180px; }
  h1 { font-size: 22px; margin: 2px 0; }
  .backbtn { padding-bottom: 6px; }
  .hubgrid { grid-template-columns: repeat(3, 1fr); }
  .hubgrid .hub-tile:first-child { grid-column: auto; }
  #tabbar { max-width: 820px; left: 50%; transform: translateX(-50%); border-radius: 16px 16px 0 0;
    border: 1px solid var(--border); border-bottom: 0; }
  /* диалог Евы держим в той же колонке, что и таб-бар: иначе на широком экране он
     растягивается во всю ширину, и Ева стоит посреди пустой полосы */
  #eva-panel { max-width: 820px; left: 50%; right: auto; width: 100%;
    margin-left: -410px; border: 1px solid var(--border); border-bottom: 0; }
  .short-shell { max-height: 70vh; }
  .reader { font-size: 18.5px; }
  .player-layout { flex-direction: row; align-items: stretch; }
  .player-col { flex: 0 0 58%; }
  .player-col #player-shell { margin-bottom: 0; }
  .side-col { flex: 1; min-width: 0; }
  .side-col .card { height: 100%; display: flex; flex-direction: column; margin-bottom: 0; }
  .side-col .subline { font-size: 21px; flex: 1; }
  .side-col .subnav { flex-wrap: wrap; }
}

/* Волна №9: карточка дня — первый блок на главной (прогресс ЗА СЕГОДНЯ, а не «вечный 1%») */
.today-card { border-left: 3px solid var(--accent); }
.today-card .ring { width: 84px; height: 84px; }
.today-card .ring > div { width: 66px; height: 66px; font-size: 16px; }
.today-card .ring.full { background: conic-gradient(var(--ok) calc(var(--p) * 1%), var(--bg2) 0); }
.today-card b { font-size: 16.5px; }
.today-go { color: var(--accent); font-weight: 650; font-size: 13.5px; margin-top: 6px; }
/* «План закрыт» и «день засчитан» — разные состояния одной строки, и различаются они не
   оттенком, а словом плюс галочкой (правило 10б журнала: два бледных пятна человек не
   различает). Закрытый план дополнительно меняет цвет полосы карточки — это второй признак. */
.today-day { font-size: 13.5px; font-weight: 600; margin-top: 4px; color: var(--muted); }
.today-card.done { border-left-color: var(--ok); }
/* Текст закрытого плана НЕ красим в зелёный: #34a26b даёт 3,2:1 на белой карточке, а строка
   мелкая. Цвет несут галочка и полоса слева, само слово читается основным цветом темы. */
.today-card.done .today-day { color: var(--text); }
.today-day svg { width: 14px; height: 14px; vertical-align: -2px; }
.today-card .muted img { vertical-align: -3px; }
/* шкала уровня/экзамена — второй и тише: мелкий процент вместо крупной цифры */
.exam-card.quiet b { font-size: 14.5px; font-weight: 600; }
.exam-card.quiet .lvl-pct { font-size: 17px; font-weight: 700; color: var(--muted); }
.exam-card.quiet .bar { height: 4px; }

/* Подсказка Евы: полноростовая Ева выезжает справа над таб-баром, пузырь слева;
   peek в это время спрятан (одна Ева на экране), уходит сама (без ИИ, чистый CSS) */
/* тоже живёт внутри таб-бара и стоит на его кромке: расчёт позиции от высоты окна
   давал прыжки при прокрутке — ту же болезнь, что была у самой Евы */
#eva-hint { position: absolute; right: 10px; bottom: calc(100% - var(--eva-sink)); display: flex; align-items: flex-end; gap: 6px;
  transform: translateX(calc(100% + 24px)); opacity: 0;
  transition: transform .45s cubic-bezier(.2, .8, .25, 1), opacity .35s; }
#eva-hint.show { transform: none; opacity: 1; }
#eva-hint.hide { transform: translateY(36px); opacity: 0; }
/* размер задаёт контракт img.eva-figure--hint */
#eva-hint .hint-eva { display: block; cursor: pointer;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .25)); }
#eva-hint .hint-bubble { position: relative; max-width: min(260px, calc(100vw - 136px));
  background: var(--card); border: 1px solid var(--border); border-radius: 16px;
  padding: 12px 30px 12px 14px; margin-bottom: 26px; box-shadow: 0 8px 28px rgba(0, 0, 0, .25); }
#eva-hint .hint-text { font-size: 14.5px; line-height: 1.35; }
#eva-hint .btn { margin-top: 10px; }
#eva-hint .hint-x { position: absolute; top: 5px; right: 7px; border: 0; background: none;
  color: var(--muted); font-size: 14px; line-height: 1; cursor: pointer; padding: 3px 5px; }
#eva-hint .hint-bubble::after { content: ""; position: absolute; right: -7px; bottom: 24px; width: 13px; height: 13px;
  background: var(--card); border-right: 1px solid var(--border); border-top: 1px solid var(--border);
  transform: rotate(45deg); }

/* Ролевые сценарии в панели Евы */
.eva-extra { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.eva-extra .scen-count { font-size: 12.5px; }
.scen-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.scen { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; cursor: pointer;
  background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; padding: 9px 11px;
  color: var(--text); font: inherit; }
.scen:active { transform: scale(.99); }
/* Значка слева у строки нет: эмодзи убраны 14.08, подходящей иконки в наборе не нашлось.
   Пустой span тут держать нельзя — flex-gap дорисовал бы ему отступ, и текст уехал бы вправо. */
.scen .sb { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.scen .sb b { font-size: 14.5px; }
.scen .sb .muted { font-size: 12.5px; }
.scen .sl { flex: none; font-size: 11px; font-weight: 700; color: var(--muted);
  border: 1px solid var(--border); border-radius: 8px; padding: 2px 6px; }

/* Шапка-статистика (референсы Артёма 13.08: Duolingo, Ewa, Busuu).
   Одна строка счётчиков вместо приветствия и трёх карточек прогресса подряд. */
.hud { display: flex; align-items: center; gap: 6px; margin: 2px 0 14px; }
.hud .hud-i { flex: none; display: inline-flex; align-items: center; gap: 4px;
  background: none; border: 0; padding: 6px 8px; border-radius: 11px; cursor: pointer;
  color: var(--muted); font-size: 14px; font-weight: 700; line-height: 1; }
.hud .hud-i:active { background: var(--bg2); }
.hud .hi { font-size: 18px; line-height: 1; }
/* Счётчики в шапке: иконка по высоте цифры рядом, иначе шапка разъезжается.
   Неактивный счётчик гасим, но слабее прежнего: приглушение grayscale(.55) opacity(.7)
   рассчитано на контрастные эмодзи, а нарисованная пастельная иконка при нём пропадала
   на белом фоне светлой темы. */
/* Приглушение погашенного счётчика: .82 давало контраст 1.79:1 к белому фону при пороге 3:1,
   и иконка заморозки на светлой теме почти пропадала (нашла приёмка 14.08). */
.hud .hi .ic-img { width: 21px; height: 21px; filter: grayscale(.3) opacity(.95); }
.hud .hi .ic { width: 20px; height: 20px; vertical-align: -.28em; opacity: .7; }
.hud .hud-i.on .hi .ic-img, .hud .hud-i.done .hi .ic-img, .hud .hud-i.risk .hi .ic-img { filter: none; }
.hud .hud-i.on .hi .ic, .hud .hud-i.done .hi .ic, .hud .hud-i.risk .hi .ic { opacity: 1; }
.hud .hv { font-variant-numeric: tabular-nums; }
.hud .hud-i.on, .hud .hud-i.done { color: var(--text); }
.hud .hud-i.risk { color: var(--accent); }
/* флаг слева — как в эталонах: крупнее прочего, это переключатель языка */
.hud .flag { font-size: 23px; padding: 2px 6px 2px 0; margin-right: 2px; }
/* уровень маленькой «таблеткой», а не строкой текста «Немецкий · уровень B2» */
.hud .lvl { background: var(--bg2); color: var(--text); font-size: 12.5px; padding: 5px 9px; letter-spacing: .02em; }
.hud #cfg { margin-left: auto; }
.hud #cfg .hi { font-size: 17px; }

/* Счётчик на плитке «Работа над ошибками» */
/* Цвет свой, не --accent: в тёмной теме акцент осветлён до #5b9bff, и белая цифра 12px на
   нём давала 2.77:1 при норме 4.5:1 (приёмка 14.08). Этот синий держит 5.1:1 с белым текстом
   в обеих темах, а сама пилюля отличается от тёмной карточки на 3.2:1. */
.badge-n { display: inline-block; min-width: 20px; padding: 1px 6px; border-radius: 10px;
  background: #2569d9; color: #fff; font-size: 12px; font-weight: 700;
  vertical-align: middle; }

/* Недельный вызов: обычное, срочное и закрытое состояние */
.week-card.risk { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.week-card.done { border-left: 3px solid #35b36a; }
.week-dots { display: flex; gap: 5px; align-items: center; flex: none; margin-left: 10px; }
.week-dots span { width: 11px; height: 11px; border-radius: 50%; background: var(--border); display: block; }
.week-dots span.on { background: var(--accent); }
.week-fz { margin-top: 8px; font-size: 13px; }

/* Заморозки */
/* Потраченная заморозка — ПУСТОЙ кружок, целая — снежинка в синем кольце. Раньше обе были
   одной картинкой и отличались только прозрачностью: 1.70 против 1.12 к белой карточке —
   два одинаково бледных пятна, остаток читался лишь по числу в подписи (приёмка 14.08).
   Разница теперь в форме, а не в оттенке, и видна с первого взгляда.
   Цвета заданы константами, а не через --accent: тему телеграма мы не знаем (класса
   html.dark там нет), а эти два тона держат 3:1 и на белом, и на тёмном фоне. */
.fz-dots { display: flex; gap: 6px; flex: none; margin-left: 10px; align-items: center; }
.fz-dots span { width: 26px; height: 26px; box-sizing: border-box; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid color-mix(in srgb, var(--text) 60%, transparent);
  font-size: 0; color: transparent; }
.fz-dots span.on { border-color: #2f7cf6; background: #2f7cf6; font-size: 15px; color: #fff; }
.fz-dots span.on img { filter: brightness(0) invert(1); }   /* белая снежинка на синем кружке */
.fz-dots span:not(.on) img { display: none; }   /* потрачена — внутри пусто, кольцо и всё */
.fz-dots img { width: 17px; height: 17px; object-fit: contain; display: block; filter: brightness(.6); }
/* Снежинка в заголовке карточки: исходная картинка светло-голубая и на белом давала 1.99:1.
   Затемнение сохраняет цвет и работает в обеих темах (2.19 → 5.49 на белом, 5.71 на тёмном). */
.fz-card .ic-img { filter: brightness(.6); }
/* Расклад по закрытым неделям. Своих цветов у строк нет: статус отличается СЛОВОМ, а не
   оттенком (правило 10б — состояния, различимые только цветом, человек не различает).
   Выделен только провал: он единственный требует действия. */
.fz-hist { margin-top: 10px; border-top: 1px solid var(--border); padding-top: 8px; }
.fz-w { display: flex; gap: 8px; font-size: 13px; color: var(--muted); padding: 3px 0; }
.fz-w span:first-child { flex: none; font-variant-numeric: tabular-nums; }
.fz-w span:nth-child(2) { flex: none; }
.fz-w .fz-st { flex: 1; text-align: right; min-width: 0; }
/* Красный держит 4.47:1 на белой карточке и 3.65:1 на тёмной — читается в обеих темах,
   а её мы не знаем (в телеграме класса html.dark нет). Спасённой неделе своего цвета не
   даём: синий на тёмном фоне падал до 3.19:1, а слова «спасла заморозка» и так хватает. */
.fz-w .fz-st.lost { color: #c2563f; font-weight: 600; }
.fz-w .fz-st.saved { color: var(--text); font-weight: 600; }

/* Значки достижений */
.badges { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 10px; }
/* гасим ТОЛЬКО иконку, не подписи (раньше opacity на всей плитке душил текст до контраста 2.3:1).
   Приглушение висит на контейнере: при сбое картинки фолбэк-эмодзи тоже должен выглядеть незаработанным. */
.badge { text-align: center; }
/* Незаработанный значок приглушён, но обязан читаться. Прозрачность для этого не годится:
   она подмешивает ФОН, и пастельный значок на белой карточке выцветал в белое пятно —
   у «Ста дней» контраст был 1.8:1 при пороге 3:1 (две приёмки подряд, 14.08).
   Гасим яркостью: белый пиксель становится ровно средне-серым, а он читается и на белом,
   и на тёмном — тему при этом знать не нужно (в телеграме класса html.dark нет).
   Замер по всем 18 картинкам, самый слабый: 5.07:1 (tools/check_contrast.py).
   Цель — «ещё не твой», а не «невидимый»: он должен хотеть его получить. */
.badge .bi { filter: grayscale(1) brightness(.6); }
.badge.on .bi { opacity: 1; filter: none; }
.badge:not(.on) .bn { color: var(--muted); font-weight: 500; }
.badge .bi { font-size: 30px; line-height: 1; height: 56px; display: flex; align-items: center; justify-content: center; }
.badge .bi img { width: 56px; height: 56px; object-fit: contain; }
.badge .bn { font-size: 12.5px; font-weight: 600; line-height: 1.2; margin-top: 2px; }
.badge .bs { font-size: 11px; line-height: 1.2; }

/* Праздник новой вехи */
/* прокрутка обязательна: в альбомной ориентации телефона (а видео смотрят именно так)
   карточка не влезала по высоте и кнопка «Спасибо!» оказывалась за экраном */
#cheer-ov { position: fixed; inset: 0; z-index: 95; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .55); backdrop-filter: blur(3px); padding: 16px; overflow-y: auto; }
/* margin:auto вместо ограничения высоты: при переполнении flex-контейнера с центрированием
   содержимое обрезается и становится недоступным для нажатия, а с margin:auto прокрутка честная */
#cheer-ov .cheer-box { position: relative; background: var(--card); border-radius: 20px; padding: 22px 20px;
  text-align: center; max-width: 320px; width: 100%; margin: auto; flex: none;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .4);
  animation: cheer-pop .35s cubic-bezier(.2, .9, .3, 1.2); }
#cheer-ov img { width: 132px; height: auto; }
/* низкий экран: ужимаем всё, чтобы кнопка гарантированно оставалась в видимой зоне */
@media (max-height: 520px) {
  #cheer-ov .cheer-box { padding: 14px 16px; }
  #cheer-ov img { width: 76px; }
  #cheer-ov .cheer-badge img { width: 44px; height: 44px; }
  #cheer-ov b { font-size: 17px; }
  #cheer-ov .btn { margin-top: 10px; }
}
/* совсем низкий (телефон в горизонтали): Ева уходит вбок, чтобы всё влезло без прокрутки */
@media (max-height: 420px) {
  #cheer-ov { padding: 10px; }
  #cheer-ov .cheer-box { display: grid; grid-template-columns: auto 1fr; grid-template-areas:
      "eva title" "eva sub" "eva btn"; gap: 2px 12px; align-items: center; padding: 12px 14px;
      max-width: 420px; text-align: left; }
  #cheer-ov img { grid-area: eva; width: 62px; align-self: center; }
  #cheer-ov .cheer-badge { display: none; }
  #cheer-ov b { grid-area: title; font-size: 16px; margin: 0; }
  #cheer-ov .muted { grid-area: sub; font-size: 13px; }
  #cheer-ov .btn { grid-area: btn; margin-top: 8px; width: auto; justify-self: start; padding: 9px 18px; }
}
#cheer-ov .cheer-badge { margin: 2px 0 8px; font-size: 40px; line-height: 1; }
#cheer-ov .cheer-badge img { width: 72px; height: 72px; object-fit: contain; }
#cheer-ov b { display: block; font-size: 19px; margin-bottom: 4px; }
#cheer-ov .btn { margin-top: 14px; width: 100%; }
#cheer-ov .confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; border-radius: 20px; }
/* Конфетти — цветные фигурки на CSS. Были эмодзи, но их рисует система: на одном
   телефоне сыпался праздник, на другом — квадратики с вопросом. */
#cheer-ov .confetti span { position: absolute; top: -24px; left: calc(var(--i) * 7.5%);
  width: 9px; height: 13px; border-radius: 2px; background: #f87565;
  animation: cheer-fall 2.4s linear infinite; animation-delay: calc(var(--i) * .13s); }
#cheer-ov .confetti span:nth-child(4n+2) { background: #fdbb34; border-radius: 50%; width: 10px; height: 10px; }
#cheer-ov .confetti span:nth-child(4n+3) { background: #7ec8a9; }
#cheer-ov .confetti span:nth-child(4n+4) { background: #f6e2b3; border-radius: 50%; width: 8px; height: 8px; }
@keyframes cheer-pop { from { transform: scale(.85); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes cheer-fall {
  0% { transform: translateY(0) rotate(0); opacity: 1; }
  100% { transform: translateY(320px) rotate(220deg); opacity: 0; }
}

/* Страница входа (веб без ключа): объясняет, что это, и ведёт в телеграм */
body.landing-mode #view { max-width: 1000px; padding-bottom: 40px; }
.landing .lh { display: flex; align-items: center; gap: 18px; margin: 8px 0 18px; flex-wrap: wrap; }
/* размер задаёт контракт img.eva-figure--landing */
.landing .lh-eva { flex: none;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, .22)); }
.landing h1 { margin: 0 0 6px; font-size: 27px; line-height: 1.15; }
.landing .lead { margin: 0; color: var(--muted); font-size: 15.5px; line-height: 1.45; max-width: 620px; }
.landing .free-card { border-left: 3px solid var(--accent); }
.landing .free-card p { margin: 6px 0 0; }
.landing .feat { display: grid; grid-template-columns: 1fr; gap: 10px; margin: 14px 0; }
.landing .feat .f { margin: 0; }
.landing .feat .f p { margin: 5px 0 0; font-size: 14px; }
.landing .fi { font-size: 22px; line-height: 1; margin-bottom: 6px; }
.landing .cta { text-align: center; }
/* это ссылка, а не button — стили кнопки прописаны отдельно */
.landing .cta a.btn { display: inline-block; margin: 12px 0 8px; padding: 13px 28px; font-size: 16px;
  font-weight: 600; border: 0; border-radius: 12px; text-decoration: none;
  background: var(--accent); color: var(--accent-text); cursor: pointer; }
.landing .cta a.btn:active { transform: scale(.985); }
.landing .small { font-size: 13px; }
.landing .foot { text-align: center; margin-top: 18px; }
@media (min-width: 760px) {
  .landing .feat { grid-template-columns: 1fr 1fr; }
  .landing h1 { font-size: 32px; }
}

/* Экран результата: Ева радуется + CSS-конфетти */
.cheer { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.celebrate { display: flex; gap: 6px; align-items: center; height: 26px; }
.celebrate span { display: inline-block; width: 12px; height: 12px; border-radius: 50%;
  background: #fdbb34; animation: pop 1.2s ease-out both; }
.celebrate span:nth-child(2n) { background: #f87565; width: 9px; height: 9px; }
.celebrate span:nth-child(3n) { background: #7ec8a9; border-radius: 3px; }
@keyframes pop {
  0% { transform: translateY(12px) scale(.4); opacity: 0; }
  45% { transform: translateY(-16px) scale(1.15); opacity: 1; }
  72% { transform: translateY(-4px) scale(1); }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}

#toast { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(96px + env(safe-area-inset-bottom));
  background: var(--text); color: var(--bg); padding: 10px 18px; border-radius: 999px; font-size: 14px;
  font-weight: 600; z-index: 90; transition: opacity .25s; }
#toast.hidden { opacity: 0; pointer-events: none; }
.spin { text-align: center; color: var(--muted); padding: 30px 0; }
.seg { display: flex; background: var(--bg2); border-radius: 10px; padding: 3px; gap: 3px; }
.seg button { flex: 1; border: 0; background: none; padding: 8px 0; border-radius: 8px; font-size: 14px;
  font-weight: 600; color: var(--muted); cursor: pointer; }
.seg button.active { background: var(--card); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.08); }

/* ---------- Диктовка «услышал → напиши» ---------- */
/* Видео уводим за экран, а не прячем display:none — иначе ютуб-плеер не проигрывает отрезок.
   Смотреть его нельзя: у части роликов субтитры вшиты в картинку, и диктовка превратится в чтение. */
/* Уводим ЗА экран, но не гасим opacity и не ставим display:none: браузеры в телефонах
   отказываются проигрывать видео, которое считают невидимым, и звук пропал бы вместе с картинкой. */
.dict-hidden { position: absolute; left: -9999px; top: 0; width: 320px; height: 180px;
  overflow: hidden; pointer-events: none; }
.dict-input { width: 100%; margin-top: 10px; padding: 12px 14px; font-size: 17px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg2); color: var(--text); }
.dict-input:focus { outline: none; border-color: var(--accent); }
.dict-input:disabled { opacity: .75; }
.dict-build { margin-top: 10px; }
.dict-built, .dict-bank { min-height: 48px; padding: 8px; border-radius: 10px; display: flex;
  flex-wrap: wrap; align-items: center; gap: 6px; }
.dict-built { border: 1px dashed var(--border); background: var(--bg2); }
.dict-bank { margin-top: 6px; }
.dict-chip { border: 1px solid var(--border); background: var(--card); color: var(--text);
  border-radius: 9px; padding: 8px 10px; font: inherit; cursor: pointer; }
.dict-chip.picked { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--card)); }
.dict-chip:disabled { cursor: default; }
.dict-keys { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.dict-keys .dkey { min-width: 44px; font-size: 17px; }
.dict-verdict { margin-top: 12px; font-weight: 700; }
.dict-verdict.good { color: var(--ok); }
.dict-verdict.bad { color: var(--bad); }
.dict-ref { margin-top: 8px; font-size: 18px; line-height: 1.7; }
.dw { padding: 1px 4px; border-radius: 6px; }
.dw[data-w] { border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer;
  appearance: none; -webkit-appearance: none; }
.dw.ok { color: var(--ok); }
.dw.typo { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.dw.wrong { color: var(--bad); background: color-mix(in srgb, var(--bad) 12%, transparent); }
.dw.miss { color: var(--muted); background: var(--bg2); }
.dw.extra { color: var(--muted); text-decoration: line-through; }
.dict-legend { font-size: 12px; margin-top: 8px; }
.dict-hot { margin-top: 10px; }
.dict-hot .dhot { margin-top: 4px; }
.dict-yours { margin-top: 6px; font-size: 15px; }
.dict-yours .dw { background: none; }

/* Друзья
   Карточка человека: имя с флагом и уровнем, минуты за неделю справа, второй строкой —
   серия, дни недели, «когда занимался» и значки. Цвет кружка с буквой мешается с темой
   через color-mix, а не задаётся отдельным правилом для html.dark: в Телеграме тёмная
   тема приходит переменными, класса html.dark там нет и правило бы не сработало. */
.fr { padding: 12px 14px; }
.fr.me { background: color-mix(in srgb, var(--accent) 8%, var(--card));
  border-color: color-mix(in srgb, var(--accent) 25%, var(--border)); }
.fr-ava { flex: none; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center;
  font-weight: 700; font-size: 16px; line-height: 1;
  /* 60% оттенка + 40% цвета текста темы: буква темнеет на светлой теме и светлеет
     на тёмной сама. Чистый оттенок давал контраст 2.9 на белом — буква бледнела. */
  color: color-mix(in srgb, hsl(var(--h) 68% 50%) 60%, var(--text));
  background: color-mix(in srgb, hsl(var(--h) 68% 50%) 18%, transparent); }
.fr-name { display: flex; align-items: center; gap: 6px; min-width: 0; }
.fr-name .nm { font-weight: 650; font-size: 15.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* На узком экране флаг, уровень и минуты отжимали имя до четырёх букв: «Александра-Мария»
   превращалась в «Ал…» (замер приёмки: 44 px под имя на 320 px). Уровень человека тут
   вторичен — на тесных экранах он уходит, имя остаётся. */
@media (max-width: 380px) {
  .fr-lvl { display: none; }
}
.fr-flag { flex: none; font-size: 15px; line-height: 1; }
.fr-lvl { flex: none; font-size: 11.5px; font-weight: 700; letter-spacing: .02em; color: var(--muted);
  background: var(--bg2); border-radius: 7px; padding: 2px 6px; }
.fr-you { flex: none; font-size: 11.5px; font-weight: 700; color: var(--accent-text);
  background: var(--accent); border-radius: 7px; padding: 2px 7px; }
.fr-min { flex: none; text-align: right; line-height: 1.15; }
.fr-min b { font-size: 16px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fr-min i { display: block; font-style: normal; font-size: 10px; color: var(--muted); }
.fr .fr-del { flex: none; padding: 7px 9px; color: var(--muted); }
/* вторая строка встаёт под именем, а не под аватаркой: 38 кружок + 12 зазор */
.fr-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px;
  margin: 8px 0 0 50px; font-size: 13px; color: var(--muted); }
.fr-m { display: inline-flex; align-items: center; gap: 3px; }
.fr-m b { color: var(--text); font-weight: 650; font-variant-numeric: tabular-nums; }
.fr-meta .ic-img { width: 18px; height: 18px; }
.fr-bs { display: inline-flex; align-items: center; gap: 3px; margin-left: auto; }
.fr-bs img { width: 22px; height: 22px; object-fit: contain; display: block; }
.fr-bs i { font-style: normal; font-size: 11px; }
.fr-empty { min-height: 0; padding: 22px 16px; }
/* Полноразмерная фигура Евы — явная роль разметки, а не догадка по URL картинки.
   !important здесь намеренный барьер: контракт обязан побеждать и общее `.flash img`
   (130 px + cover), и случайный inline crop. Размер выбирает семантический модификатор;
   высоту карточки не раздуваем, пропорции всегда сохраняются. Намеренный #eva-peek —
   отдельный интерактивный персонаж на кромке таб-бара, у него свой контракт ниже.

   РАЗМЕР ЗАДАЁТ ВЫСОТА, А НЕ ШИРИНА (приёмка 20.08). Все позы Евы нарисованы ростом
   ровно 384 px и обрезаны каждая по своему силуэту: eva-retry 173×384, eva-happy 256×384,
   eva-streak 305×384. То есть исходники нормированы по высоте. Пока контракт нормировал
   по ширине, один и тот же персонаж выходил разного роста: на итоге упражнения без ошибок
   192 px, с ошибками 284 px — полтора раза разницы на одном экране («везде кривовато
   стоит»). Ширину ограничиваем отдельной переменной там, где рядом лежит текст. */
img.eva-figure {
  display: block;
  width: var(--eva-figure-width, auto) !important;
  height: auto !important;
  max-width: var(--eva-figure-max-width, 100%) !important;
  max-height: var(--eva-figure-max-height, 220px) !important;
  object-fit: contain !important;
  object-position: center !important;
  border-radius: 0 !important;
  flex: none;
}
img.eva-figure--empty { --eva-figure-max-height: 220px; }
img.eva-figure--result { --eva-figure-max-height: 176px; }
/* низкий экран: итог обязан помещаться целиком вместе с кнопками */
@media (max-height: 620px) {
  img.eva-figure--result { --eva-figure-max-height: 132px; }
}
/* Остальные полноростовые Евы приложения — тот же контракт, а не свои width в пяти местах.
   Размеры подобраны так, чтобы отрисовка не изменилась: подсказка 96×198, тур 96×198,
   онбординг 84×161, праздник вехи 132×321, лендинг 108×207. */
img.eva-figure--hint { --eva-figure-max-height: 198px; --eva-figure-max-width: 96px; }
img.eva-figure--tour { --eva-figure-max-height: 198px; --eva-figure-max-width: 96px; }
img.eva-figure--onboard { --eva-figure-max-height: 161px; --eva-figure-max-width: 84px; }
img.eva-figure--landing { --eva-figure-max-height: 207px; --eva-figure-max-width: 108px; }
img.eva-figure--milestone { --eva-figure-max-height: 321px; --eva-figure-max-width: 132px; }
@media (max-height: 520px) {
  img.eva-figure--milestone { --eva-figure-max-height: 185px; --eva-figure-max-width: 76px; }
}
@media (max-height: 420px) {
  img.eva-figure--milestone { --eva-figure-max-height: 151px; --eva-figure-max-width: 62px; }
}
button.btn.fr-inv { width: 100%; margin-top: 4px; }
.fr-row-i { flex: none; color: var(--accent); font-size: 18px; line-height: 0; }
