﻿/* 00-base.css — слои, шрифты, токены, нормализация, типографика.
 *
 * Порядок слоёв объявляется ЗДЕСЬ и один раз. Внутри @layer не пишется
 * !important нигде и никогда (§9.1 п.10): движок больше не вставляет свой
 * <style>, воевать не с кем.
 */
@layer normalize, base, type, hero, film, effect, outro, debug;

/* ─── шрифты ───────────────────────────────────────────────────────────────
 * Geist + Geist Mono, самохост, две переменные гарнитуры (ось wght 100-900).
 * @import с fonts.googleapis не используется ни в каком виде, даже локальный:
 * он всё равно добавляет RTT (§6.5).
 *
 * Почему геометрический гротеск поверх тёплого игрушечного мира — это
 * арт-дирекция, а не конфликт: тепло уже есть, и его 100 % площади. Тёплый
 * круглый шрифт поверх тёплой картинки складывается в детскую книжку, а посыл
 * брифа дословно «дешевле, качественнее и самое главное современно».
 * ПОЗИЦИОНИРОВАНИЕ ПОЗЖЕ ИСПРАВЛЕНО (2026-08-20): слово «качественнее» снято
 * со страницы — заказчик уточнил, что кабины и синхронисты работают, вопрос
 * только в цене. Цитата брифа оставлена как есть: это запись того, что было
 * сказано, её нельзя править задним числом. Действующая формула живёт на
 * .handoff__lead в index.html.
 * Контраст регистров «мир — игрушка, продукт — прибор» и есть приём.
 * Кириллица проверена глазами на 89 px и 13.5 px (гейт §10 п.5.9).
 */
@font-face {
  font-family: 'Geist'; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url('../assets/fonts/geist-cyrillic.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face {
  font-family: 'Geist'; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url('../assets/fonts/geist-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2192,U+2193,
    U+2212,U+2215,U+2248,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Geist Mono'; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url('../assets/fonts/geist-mono-cyrillic.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face {
  font-family: 'Geist Mono'; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url('../assets/fonts/geist-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2192,U+2193,
    U+2212,U+2215,U+2248,U+FEFF,U+FFFD;
}

@layer normalize {
  *, *::before, *::after { box-sizing: border-box; }
  html { -webkit-text-size-adjust: 100%; }
  /* overflow-x: clip, НИКОГДА hidden: hidden создаёт скролл-контейнер,
   * ломает sticky сцены и меняет разрешение scroll()-таймлайнов (§6.8). */
  html, body { overflow-x: clip; }

  /* Системная полоса прокрутки справа снята: страница — кинолента, серая
   * рейка у края ломает кадр. Скролл при этом ОСТАЁТСЯ настоящим — это
   * принципиально: на нём держатся sticky-сцена и scroll()-таймлайны
   * (§5.6, §9.1 п.1), поэтому прячем именно полосу, а не скроллинг.
   * scrollbar-width — Firefox и Chrome 121+, ::-webkit-scrollbar — Safari
   * и старый Chrome. Колесо, тачпад и клавиатура работают как прежде. */
  html { scrollbar-width: none; }
  html::-webkit-scrollbar { width: 0; height: 0; }
  body { margin: 0; }
  /* body БЕЗ display:grid и place-items:center из сниппета: грид-элементы
   * ушли бы из stretch в center, полноширинные блоки схлопнулись бы по
   * контенту и по бокам проступил бы вертикальный шов цвета --bg (§6.1). */
  img, picture, video { display: block; max-width: 100%; }
  h1, h2, h3, p, ol, ul, figure { margin: 0; }
  ol, ul { padding: 0; list-style: none; }
  a { color: inherit; text-decoration: none; }
  ::selection { background: #2A1C10; color: #FFFDF8; }
}

@layer base {
  :root {
    /* постоянные мира — замерены по кадрам */
    --paper: #FFFDF8;
    --cream: #FFF6E8;
    --ink: #2A1C10;
    --ink-soft: #332210;
    --ink-peach: #6B2007;
    --ink-cold: #0E4F5C;
    --cyan: #6FD3EA;              /* родной цвет плитки знака */

    /* пишутся движком покадрово из замеренной палитры; здесь — стартовые
     * значения, чтобы страница была корректна и при нуле загруженных байт */
    --bg: #9A652C;
    --tint: #C09057;

    --shadow-card: 0 1px 2px rgb(42 28 16 / .06), 0 14px 34px rgb(42 28 16 / .13);

    /* ТЁМНОЕ ДЫМЧАТОЕ СТЕКЛО — плашки поверх кадра фильма.
     * Отдельная пара чернил, а не --paper/--ink: текст здесь лежит на
     * подложке, светлота которой зависит от кадра.
     *
     * ХУДШИЙ КАДР ЗАМЕРЕН, А НЕ УГАДАН. Прогнали все 721 кадр фильма и в
     * прямоугольнике плашки каждого бита нашли самое светлое ПЯТНО (не
     * пиксель: backdrop-filter размывает подложку, текст лежит на среднем).
     * Максимум — бит «арена», R244 G242 B244, то есть практически белый.
     * Поэтому запас считается от белого кадра, и оба тона подобраны под него:
     * заголовок 5.59:1, абзац 5.01:1 (замер и подбор — tools/glass.py).
     *
     * ПОЧЕМУ ПОЧТИ БЕЛЫЕ. Заказчик просил стекло, а не тёмную панель. Дальше
     * простая арифметика: чем светлее чернила, тем ниже можно опустить
     * плотность заливки при том же контрасте. Уход с #CFD7E2 на #EEF3F8
     * оплатил падение заливки с .76 до .62 и гашения с .62 до .86 — до глаза
     * стало доходить 33 % кадра вместо 15 %, вдвое больше. Приглушать чернила
     * «для мягкости» нельзя: это платит прозрачностью, а не мягкостью.
     * Иерархия здесь на кегле и весе, а не на серости абзаца. */
    --glass-ink: #FFFFFF;
    --glass-ink-soft: #EEF3F8;
    /* Номер шага и текст пилюли. Отдельный токен, потому что он ложится и на
     * плашку, и на пилюлю, и считается в gates отдельной строкой. Бирюзовый
     * --cyan отсюда ушёл: на светлом стекле он давал 3.6:1. Цвет бренда
     * остался в КРОМКЕ пилюли — там он ничего не читает. */
    --glass-accent: #FFFFFF;
    --glass-line: rgb(255 255 255 / .14);
    --shadow-glass: 0 1px 2px rgb(0 0 0 / .18), 0 18px 44px rgb(0 0 0 / .34);
    --ease-out-expo: cubic-bezier(.16, 1, .30, 1);
    --ease-soft: cubic-bezier(.22, .61, .36, 1);
    --wrap: 1120px;
    --pad: clamp(20px, 5vw, 64px);
  }

  html { background: var(--paper); }
  body {
    background: var(--paper);
    color: var(--ink);
    font-family: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
    font-weight: 450;
    font-size: clamp(1rem, .96rem + .18vw, 1.1rem);
    line-height: 1.5;
    font-synthesis-weight: none;
    text-rendering: optimizeLegibility;
  }

  .wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding-inline: var(--pad); }

  /* Фокус-кольцо на КАЖДОМ интерактивном элементе. В прошлой сборке его не
   * было вовсе, страница была непроходима с клавиатуры (§8.4). */
  a:focus-visible, button:focus-visible {
    outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 4px;
  }
  .skip {
    position: fixed; top: 8px; left: 8px; z-index: 60;
    padding: 10px 16px; background: var(--ink); color: var(--paper);
    border-radius: 8px; font-size: .9rem; translate: 0 -200%;
  }
  .skip:focus { translate: 0 0; }

  /* Скрыто визуально, но доступно экранному диктору и поиску. Применено к
   * полному списку языков: на экране его заменили лента флагов и сцена с
   * пультом, но сам список обязан остаться в документе — он единственный
   * носитель всех 70+ позиций для диктора (лента помечена aria-hidden) и по
   * нему считает приёмка. clip-path, а не display:none: скрытый через display
   * элемент диктор не читает вовсе. */
  .vh {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    border: 0; white-space: nowrap; clip-path: inset(50%);
  }

  /* ВУАЛЬ НАВИГАЦИИ. Клик по якорю НЕ прокручивает документ, а мгновенно
   * переставляет позицию под чёрным затемнением — логика в js/page.js.
   * Длительности ведёт JS через --navveil-in/--navveil-out: там же стоит
   * пауза перед прыжком, и разъехаться эти числа не должны.
   * z-index 70 — выше скип-ссылки (60) и топбара (40): вуаль обязана
   * накрывать всё, иначе из-под неё видно, как переставляется страница.
   * transition объявлен в БАЗОВОМ правиле, а .is-on только переопределяет
   * длительность. Если положить transition внутрь .is-on, обратный ход
   * (снятие класса) останется без анимации — ровно на этом уже попались
   * с топбаром, см. поправку 15. */
  .navveil {
    position: fixed; inset: 0; z-index: 70; background: #000;
    opacity: 0; pointer-events: none;
    transition: opacity var(--navveil-out, .34s) ease-out;
  }
  .navveil.is-on {
    opacity: 1;
    transition: opacity var(--navveil-in, .2s) ease-in;
  }
}

@layer type {
  h1, h2 { font-weight: 620; line-height: 1.06; letter-spacing: -.02em; text-wrap: balance; }
  p { text-wrap: pretty; }

  /* Geist Mono — второй голос системы: цифры, единственный надзаголовок,
   * номера фактов. Больше нигде. */
  .mono, .hero__kicker, .hero__facts, .chip, .facts__ul b, .langs__tail {
    font-family: 'Geist Mono', ui-monospace, SFMono-Regular, monospace;
    font-weight: 500; letter-spacing: .04em; font-variant-numeric: tabular-nums;
  }

  .btn {
    display: inline-flex; align-items: center; justify-content: center;
    /* 44, а не 48: заказчик просил уменьшить. Ниже опускать НЕЛЬЗЯ — 44 px и
     * есть тот порог зоны нажатия, который держит вся страница. */
    min-height: 44px; padding: 0 20px;
    background: var(--ink); color: var(--paper);
    /* Скруглённый прямоугольник, а не пилюля (было 999px). Пилюля спорила с
     * прямоугольной геометрией остальной страницы — карточки офиса (14px),
     * плашек фильма, самой шапки (22px). 12px ставит кнопки в тот же ряд.
     * Радиус вынесен в переменную: радужному кольцу нужен СВОЙ радиус, на
     * толщину кольца больше, иначе на скруглениях оно ляжет неровно. */
    --btn-r: 12px;
    border-radius: var(--btn-r); font-weight: 500; font-size: .97rem;
    /* СВЕЧЕНИЕ ЗНАКА. --cyan — родной цвет плитки знака, и до сих пор он
     * нигде на странице не работал акцентом. Тратим его ровно на целевое
     * действие: тонкая линия по кромке плюс кольцо по ховеру.
     * Всё сделано на box-shadow — ни одного нового слоя композитинга. Это и
     * был довод против стеклянного варианта: на странице уже есть скраб видео
     * и топбар с backdrop-filter, третий тяжёлый эффект туда не помещался.
     * На тёмном кромка уходит ВНИЗ (см. 01-page.css): на первом экране свет
     * идёт снизу, из шейдера, и блик обязан следовать за источником. */
    box-shadow: inset 0 1px 0 rgb(111 211 234 / .34),
                0 0 0 0 rgb(111 211 234 / 0),
                0 8px 22px rgb(42 28 16 / .2);
    transition: translate 160ms var(--ease-soft), scale 90ms ease-out,
                box-shadow 220ms var(--ease-soft);
  }
  .btn:hover {
    translate: 0 -2px;
    box-shadow: inset 0 1px 0 rgb(111 211 234 / .62),
                0 0 0 4px rgb(111 211 234 / .20),
                0 12px 28px rgb(42 28 16 / .26);
  }
  .btn:active { scale: .98; transition-duration: 90ms; }
  .btn--ghost {
    background: transparent; color: var(--ink);
    box-shadow: inset 0 0 0 1.5px rgb(42 28 16 / .28);
  }
  /* :hover ОБЯЗАТЕЛЕН. `.btn:hover` (0,2,0) специфичнее, чем `.btn--ghost`
   * (0,1,0), поэтому без этой строки свечение протекало бы на вторичную
   * кнопку «Смотреть →» — акцент достался бы обеим сразу и перестал быть
   * акцентом. Ghost по ховеру просто уплотняет свой контур. */
  .btn--ghost:hover { box-shadow: inset 0 0 0 1.5px rgb(42 28 16 / .44); }
  /* Малая кнопка — меньший радиус: при 12px на высоте 38px скругление
   * зрительно тяжелее, чем на 44px. Держим пропорцию, а не число.
   * Высота здесь и так ниже порога 44 px — это давняя осознанная уступка
   * ради шапки, поэтому дальше её не трогаем. */
  .btn--sm { --btn-r: 10px; min-height: 38px; padding: 0 16px; font-size: .89rem; }

  /* ─── RAINBOW BUTTON, вариант outline (MagicUI, порт на ваниль) ─────────
   * Светлая кнопка с волосяным контуром, под нижней кромкой — тонкая радужная
   * линия, под ней мягкий отсвет. Линия и отсвет ЕДУТ.
   *
   * ИСТОРИЯ ТРЁХ ЗАХОДОВ, чтобы никто не пошёл по кругу заново:
   *   1) радужная рамка по ВСЕМУ контуру + анимация -> «не такой rainbow»;
   *   2) тёмная заливка, свечение снизу, без движения -> оказалось, что на
   *      тёмном hero не видно границы, добавлено светлое кольцо;
   *   3) сейчас: вариант outline, светлая заливка, движение вернули по прямой
   *      просьбе заказчика.
   *
   * isolation: isolate обязателен: без него z-index: -1 уводит псевдоэлементы
   * за фон РОДИТЕЛЬСКОЙ секции, и линия со свечением пропадают вовсе.
   *
   * `background-clip`, на котором построен оригинал MagicUI, не используется:
   * критерий приёмки 9.2 запрещает это свойство подстрокой и роняет сборку. */
  @keyframes rainbow { to { background-position: 200% 0; } }

  .btn--rainbow {
    --rb-speed: 4s;
    /* Приглушённые тона (S ~62 %, L ~66 %), а не чистый спектр: чистый читается
     * как детская радуга, а нужен отсвет. Последний стоп повторяет первый,
     * иначе на стыке петли виден шов. */
    --rb: linear-gradient(90deg,
      hsl(268 55% 70%), hsl(212 68% 66%), hsl(168 60% 64%),
      hsl(88 62% 66%), hsl(28 75% 68%), hsl(268 55% 70%));
    position: relative; isolation: isolate;
    background: var(--paper); color: var(--ink);
    box-shadow: inset 0 0 0 1px rgb(42 28 16 / .16),
                0 1px 2px rgb(42 28 16 / .05),
                0 8px 22px rgb(42 28 16 / .10);
  }
  /* РАДУГА ЖИВЁТ В САМОЙ РАМКЕ, а не полоской под кнопкой.
   * Прошлый заход клеил отдельную линию снизу — заказчик справедливо заметил,
   * что она «торчит»: у неё не было связи с контуром, она просто лежала ниже.
   * В оригинале MagicUI цвет течёт ПО КОНТУРУ, огибая нижние углы.
   *
   * Как сделано: псевдоэлемент растянут на 1.5 px наружу от кнопки и залит
   * градиентом целиком. Собственный фон кнопки закрывает середину, поэтому
   * наружу выступает ровно кольцо толщиной 1.5 px — и оно повторяет
   * скругление, потому что радиус взят на ту же толщину больше.
   * Маска гасит кольцо кверху: цвет остаётся на нижней кромке и в нижних
   * углах, а к бокам сходит на нет. Волосяной серый контур по всему периметру
   * даёт box-shadow самой кнопки, он из-под кольца и виден сверху.
   *
   * background-clip тут по-прежнему не нужен — им оригинал добивается того же
   * эффекта, но эта строка запрещена критерием 9.2. */
  .btn--rainbow::before {
    content: ''; position: absolute; z-index: -1;
    inset: -1.5px; border-radius: calc(var(--btn-r) + 1.5px);
    background: var(--rb); background-size: 200% 100%;
    animation: rainbow var(--rb-speed) linear infinite;
    -webkit-mask-image: linear-gradient(to top, #000 0%, #000 16%, transparent 54%);
            mask-image: linear-gradient(to top, #000 0%, #000 16%, transparent 54%);
  }
  /* Отсвет под линией. */
  .btn--rainbow::after {
    content: ''; position: absolute; z-index: -1;
    left: 8%; right: 8%; bottom: -6px; height: 56%;
    border-radius: inherit;
    background: var(--rb); background-size: 200% 100%;
    filter: blur(10px); opacity: .5;
    animation: rainbow var(--rb-speed) linear infinite;
  }
  .btn--rainbow:hover {
    box-shadow: inset 0 0 0 1px rgb(42 28 16 / .26),
                0 1px 2px rgb(42 28 16 / .06),
                0 12px 28px rgb(42 28 16 / .14);
  }
  .btn--rainbow:hover::after { opacity: .68; }

  /* ЦЕНА ДВИЖЕНИЯ И ЧЕМ ОНА ОГРАНИЧЕНА. Анимируется background-position, то
   * есть перекраска, а не композитинг. Полоска высотой 2 px стоит копейки;
   * дорогой здесь только размытый отсвет — под blur каждый кадр считается
   * заново. Поэтому оба движения сняты предохранителем `html.no-glass`: тем
   * самым, что гасит стекло шапки, когда проба понизила тир видео. На слабой
   * машине кадры плёнки важнее движения на кнопке.
   * (Требование «без нагрузки» из прошлого захода заказчик отменил, попросив
   * анимацию явно. Подстраховка оставлена намеренно.) */
  html.no-glass .btn--rainbow::before,
  html.no-glass .btn--rainbow::after { animation: none; }
  html.no-glass .btn--rainbow::after { filter: none; opacity: .3; }

  @media (prefers-reduced-motion: reduce) {
    .btn--rainbow::before, .btn--rainbow::after { animation: none; }
  }
}
