@font-face {
  font-family: 'HelveticaNeue';
  src: url('../assets/fonts/HelveticaNeue-Roman.woff2') format('woff2');
  font-weight: 400;
}
@font-face {
  font-family: 'HelveticaNeue';
  src: url('../assets/fonts/HelveticaNeue-Medium.woff2') format('woff2');
  font-weight: 500;
}
@font-face {
  font-family: 'HelveticaNeue';
  src: url('../assets/fonts/HelveticaNeue-Bold.woff2') format('woff2');
  font-weight: 700;
}

:root {
  /* Токены сайта. Порядок разделов и имена — как в
     .claude/skills/futuristic-os-design/references/tokens.md; tokens.md —
     зеркало этого блока. Здесь только то, что код реально использует
     (плюс шкалы --space-* и --z-* целиком). Компонентные размеры узлов
     (иконки, отступы подписей, hover-масштабы, тултип) — в :root
     css/desktop.css. JS читает отсюда тайминги, кривые, --line-*, --space-5/6
     через getComputedStyle, числа не дублирует. */

  /* ── Большие экраны (08.10.2026; ступени по мониторам — 10.10.2026) ──
     «Пиксель системы»: ровно 1px, пока короткая сторона экрана ≤ 900 (там
     не меняется ни пикселя), выше — растёт с ней. Не брейкпоинт:
     непрерывная кусочно-линейная функция короткой стороны, без правил под
     ширину и без скачков (на ресайзе через 900, 1080, 1440 — плавно).
     Решение владельца 10.10.2026 (смотрит в обычном окне браузера,
     которое ниже экрана на панель браузера и меню): у каждого монитора —
     одна ступень на все его окна, между мониторами — плавный переход:
       1920 — короткая сторона 940–1080: 1.38 (как было на 1920×1080);
       2560 — 1340–1440: 1.9872 (на 8 % крупнее прежних 1.84 на 2560×1440);
       3840 — 2060–2160: 3.1968 на 2160 (на 8 % крупнее прежних 2.96), на
              2060 — 3.075: крупнее ящик Profile в экран не входит;
       выше 2160 — +1.512 на каждые 900 px (прежний наклон × 1.08).
     Рампы ниже — 0 → 1px на своём отрезке; ступени — безразмерные числа.
     Через --px-up растут потолки clamp, шкала --space-*, кегли, ящик
     Profile, фильтр, спицы, импульс и зоны нажатия; JS умножает свои
     px-константы на него же (pxUp в main.js, перечитывается на resize).
     Иконки узлов, хабы, работы и лого — своими кривыми на тех же рампах
     (--icon-up, --hub-up, --work-up в desktop.css): на больших экранах
     текст может расти быстрее объектов. Не растут только линии 1px. */
  --up-ramp-1920: clamp(0px, (100vmin - 900px) / 40, 1px);    /* 900 → 940 */
  --up-ramp-2560: clamp(0px, (100vmin - 1080px) / 260, 1px);  /* 1080 → 1340 */
  --up-ramp-3840: clamp(0px, (100vmin - 1440px) / 620, 1px);  /* 1440 → 2060 */
  --up-ramp-2160: clamp(0px, (100vmin - 2060px) / 100, 1px);  /* 2060 → 2160 (только --px-up) */
  --up-ramp-over: max(0px, (100vmin - 2160px) / 900);         /* выше 2160, на каждые 900 px */
  --px-up-1920: 1.38;
  --px-up-2560: 1.9872;
  --px-up-2060: 3.075;   /* 3840×2060: наибольшее, при котором ящик Profile целиком в экране */
  --px-up-3840: 3.1968;
  --px-up-over: 1.512;
  --px-up: calc(1px
               + var(--up-ramp-1920) * (var(--px-up-1920) - 1)
               + var(--up-ramp-2560) * (var(--px-up-2560) - var(--px-up-1920))
               + var(--up-ramp-3840) * (var(--px-up-2060) - var(--px-up-2560))
               + var(--up-ramp-2160) * (var(--px-up-3840) - var(--px-up-2060))
               + var(--up-ramp-over) * var(--px-up-over));

  /* ── Фон ── Один сплошной цвет (06.10.2026; история — tokens.md, «Фон»,
     и docs/CHANGELOG.md). Середина бывшей диагонали 25 → 12 (18,5 → 19):
     общий тон тот же. Тот же цвет — meta theme-color в index.html. */
  --bg-base: #131313; /* сам фон места: body, заливка карточек и стенки ящика Profile, затемнение подсказки первого визита */
  --bg-field-base: #191919; /* бывший светлый конец диагонали — на сайте не используется, нужен админке (admin.css, edit.css) */
  /* Фона у элементов нет — токенов --bg-surface не существует намеренно
     (futuristic-os-design/SKILL.md, «Поверхностей нет»). */

  /* ── Текст ── */
  --text-primary:   #E8E8E6; /* имена, главное, подпись хаба под наведением */
  --text-soft:      #C8C8C4; /* подпись под иконкой узла, цвет body по умолчанию */
  --text-secondary: #9B9B9F; /* проза */
  --text-muted:     #6C6C70; /* метки, индексы — только короткое */

  /* ── Линии и штрихи ── */
  /* Спица и линия между спутниками стола (.os-link) — ровные до самого
     конца, --line-color × --line-alpha (затухание спицы у хаба убрано 30.09). */
  --line-color:     #C8C8C4;
  --line-alpha:     0.22;
  /* Толщина .os-link на узком экране (09.10.2026): множитель к linkWidth()
     в main.js — 0.75 при короткой стороне ≤ 430 px (телефон в любой
     ориентации), плавно до 1 к 600 px; шире — ровно 1, ничего не меняется.
     В px, чтобы JS прочёл замером (readPxToken), как --px-up. Пол — одна
     точка экрана (1/DPR) — правило в linkWidth(), не здесь. */
  --line-w-narrow:  clamp(0.75px, calc(0.75px + (100vmin - 430px) * 0.25 / 170), 1px);
  --line-hairline:  rgba(200, 200, 196, 0.16); /* подчёркивание ссылки Profile в покое */
  --line-img:       rgba(200, 200, 196, 0.14); /* рамка превью работы; разделители строк карточки ящика Profile (самый тихий) */
  --line-img-hover: rgba(200, 200, 196, 0.3);  /* рамка превью под курсором */
  --line-near:      rgba(255, 255, 255, 0.18); /* контур ящика Profile и его карточек; кривая подсказки первого визита */
  /* --line-color также штрих: ползунок описания, рамка работы при драге,
     подчёркивание ссылки Profile под наведением. */

  /* ── Шрифты ── Одна гарнитура, самохостится (@font-face выше);
     моноширинного шрифта в системе нет (убран 13.09.2026). */
  --font-sans: 'HelveticaNeue', Helvetica, Arial, sans-serif;

  /* ── Размеры текста ── Ступени по 2px: small 14 → body 16 → lead 18 →
     name (пик 21). На большом экране кегль прибавляет столько же px, сколько
     у него было, на каждый px роста --px-up (rem остаётся). */
  --text-lead:  calc(1.125rem + 18 * (var(--px-up) - 1px));  /* ссылки «Написать»/«Канал» в открытой работе, вес 700 */
  --text-name:  calc(clamp(1.1875rem, 1.125rem + 0.3vw, 1.3125rem) + 21 * (var(--px-up) - 1px)); /* имя открытой работы, вес 700 */
  --text-body:  calc(1rem + 16 * (var(--px-up) - 1px));      /* проза; стрелка ссылок открытой работы */
  --text-small: calc(0.875rem + 14 * (var(--px-up) - 1px));  /* компактная проза — блоки Profile, описание открытой работы */
  --text-label: calc(0.75rem + 12 * (var(--px-up) - 1px));   /* UPPERCASE, вес 500 — метки, строка фильтра годов */
  --text-data:  calc(0.6875rem + 11 * (var(--px-up) - 1px)); /* mixed case, вес 400 — значения (тултип веса папки, год работы) */
  /* Вне шкалы — именованные значения, числа не менялись: */
  --text-node:  clamp(10px, 2.6vw, calc(11.5 * var(--px-up))); /* подпись под иконкой узла (стол, папка, хаб), вес 700 */
  --text-work:  calc(9.5 * var(--px-up));     /* подпись работы в россыпи категории, вес 400 */
  --text-hint:  calc(clamp(0.8125rem, 0.766rem + 0.19vw, 0.9375rem) + 15 * (var(--px-up) - 1px)); /* подсказка первого визита: 13px на 390 → 15px на 1440, один кегль у всех подсказок */

  --tracking-label: 0.10em;
  --tracking-data:  0.02em;
  --tracking-work:  calc(0.3 * var(--px-up)); /* подпись работы в россыпи (вне шкалы) */

  /* ── Отступы (8px база, 4px половинный шаг; на большом экране × --px-up) ── */
  --space-1: calc(4 * var(--px-up));
  --space-2: calc(8 * var(--px-up));
  --space-3: calc(16 * var(--px-up));
  --space-4: calc(24 * var(--px-up));
  --space-5: calc(32 * var(--px-up));
  --space-6: calc(48 * var(--px-up));
  --space-7: calc(64 * var(--px-up));
  --space-8: calc(96 * var(--px-up));

  /* Радиусов нет (кроме ящика Profile — --drawer-radius, --drawer-tab-radius в
     desktop.css): у объектов своя форма в файле иконки, у картинки работы
     — её собственный край. Панельного радиуса нет — панелей нет. */

  /* ── Движение ── */
  --t-navigate:   700ms; /* FLIP-перелёт узла в центр/обратно */
  --t-wave-step:  18ms;  /* шаг между соседними узлами волны появления (спутники стола/папки) */
  --t-wave-line:  380ms; /* прорастание одной спицы до узла */
  --t-wave-node:  260ms; /* проступание самого узла на линии */
  /* Спица уходящего узла гаснет (main.js, updateEdgeGeometry, --ease-out) —
     не дольше --t-wave-line − --t-wave-node: погасла до того, как проступит
     первый узел нового уровня; работы категории ждут её (beginRingWave). */
  --t-line-exit:  120ms;
  /* Волна работ внутри категории — не шаг, а бюджет: вся волна от первой
     до последней работы укладывается в это время при любом их числе, шаг
     main.js считает сам: (--t-ring-wave-total − --t-wave-node) / (n − 1). */
  --t-ring-wave-total: 820ms;
  /* Категория со стопкой (11.10.2026, updateStackDeal в main.js): стопка
     прилетает в центр (--t-navigate) и раздаёт работы (у «Сайтов» —
     сайты, их спицы растут после прилёта). С 13.10.2026 три работы
     стопки — её листы: снимаются по очереди (верхний → средний → нижний,
     шаг --t-stack-deal-step) по --ease-stack-sheet, остальные выезжают
     из-под стопки после них по --ease-navigate; каждая летит
     --t-stack-deal, вся раздача, от вылета первой до посадки последней,
     — не дольше --t-stack-deal-total при любом числе (шаг остальных — из
     остатка бюджета, не больше --t-stack-deal-step). Выход — то же
     расписание (13.10.2026): первой ложится обложка, под неё — средний и
     нижний листы, карточка становится полной в кадр касания; потом
     остальные уезжают под стопку; собрались все — стопка летит на место. */
  /* Раздача начинается, пока стопка летит в центр (13.10.2026, владелец:
     стопка не должна стоять в центре): первый лист отрывается, когда
     стопка прошла эту долю пути (по --ease-navigate ≈ 130 мс от клика). */
  --stack-deal-at:      0.65;
  --t-stack-deal:       560ms;
  --t-stack-deal-total: 900ms;
  --t-stack-deal-step:   70ms;
  /* Строка данных «N работ · годы» стопки, вернувшейся из центра на место
     (13.10.2026, fadeInCategoryData в main.js): проступает за последние
     столько перелёта (--t-navigate) — когда стопка встаёт на место, по
     --ease-stack-data; у хаба строки нет. */
  --t-stack-data:       380ms;
  /* Вход на уровень Profile (drawerEntryValues в main.js), от начала
     перехода; весь вход — не длиннее 2,4 с (сейчас ~1,97), кривая
     --ease-drawer: иконка летит в центр (--t-navigate) → ящик открывается
     «на себя» «из тумана» (прозрачность 0 → 1 и масштаб --drawer-fog-scale
     → 1 от своего центра, пока прорисовывается и выдвигается): контур
     коротко прорисовывается задвинутым (обод выше центра экрана) и ящик
     выдвигается вниз → задняя карточка поднимается из ящика, alias/имя/роль построчно →
     карточка по умолчанию поднимается сразу открытой, хаб уезжает в шапку →
     закрытые листы по одному встают в стопку под ней. */
  --t-drawer-open-at:    250ms; /* ящик появляется задвинутым (контур прорисовывается)… */
  --t-drawer-draw:       200ms;
  --t-drawer-slide:      450ms; /* … и выдвигается вниз на своё место (без отскока) */
  --t-drawer-back-at:    720ms; /* задняя карточка поднимается из ящика под хаб (с конца выдвигания) */
  --t-drawer-back:       325ms;
  --t-drawer-head-at:    960ms; /* имя и ник под хабом — построчно (шаг и проступание — --t-profile-row-*) */
  --t-drawer-first-at:   980ms; /* карточка по умолчанию поднимается сразу открытой, хаб — в шапку (с конца задней) */
  --t-drawer-first:      650ms;
  --t-drawer-cards-at:  1450ms; /* закрытые листы по одному — в стопку под ней (с последней трети открытой) */
  --t-drawer-card-step:   65ms;
  --t-drawer-card:       325ms;
  /* Уход с уровня Profile (drawerExitValues) — точное зеркало входа: те же
     токены --t-drawer-* и --t-profile-row-*, проигранные назад (своих
     токенов у ухода нет); весь уход — не длиннее входа. */
  --t-profile-row-step:  40ms; /* шаг между строками открытой карточки */
  --t-profile-row-fade: 140ms; /* проступание одной строки */
  --t-copied:          1400ms; /* «скопировано» вместо «копировать» — время текста, не анимации */
  --t-resize:           120ms; /* раскладка уровня на resize — не чаще раза в столько (throttle relayoutLevel), не анимация */
  --t-hover:            160ms; /* наведение */
  --t-hover-dim-delay:  120ms; /* работа в категории: курсор пробыл на ней столько — соседи гаснут (или подсветка переходит на неё); пролетел быстрее — ничего (updateHoverDim, main.js) */
  --t-hover-dim-release: 220ms; /* … курсор ушёл на пустое место — поле возвращается к яркости через столько, если за это время он не попал на работу */
  --t-hover-scale:      250ms; /* масштаб иконки узла при наведении/драге (updateNodeScales, --ease-navigate) */
  --t-stack-fan:        400ms; /* стопка категории раскрывается веером при наведении мышью и обратно (updateNodeScale, --ease-navigate) */
  --t-stack-sway-min:  4600ms; /* покачивание листа стопки-спутника: период одной оси (x, y, поворот) — свой у каждой карточки и оси, между min и max (по хешу id и места) */
  --t-stack-sway-max:  7800ms;
  --t-stack-sway-fade:  300ms; /* покачивание гаснет (хаб, перелёт, раздача, сбор, драг) и набирается обратно за столько, smoothstep — без скачка положения и скорости */
  --t-locked-soon:     1500ms; /* закрытый узел стола: после тапа «скоро» вместо названия столько (повторный тап продлевает; updateLockedLabels) */
  /* Превью сайта при наведении на узел «Сайтов» (updateSitePreview в
     main.js): вырастает из центра иконки и тянется за курсором. */
  --t-site-preview-in:  360ms; /* рост из иконки: масштаб и путь по --ease-site-preview-in, прозрачность — за --site-preview-fade-in долю */
  --t-site-preview-out: 220ms; /* сворачивание обратно в иконку: масштаб, путь и прозрачность по --ease-site-preview-out */
  --t-site-preview-lag: 120ms; /* запаздывание за курсором: k = 1 − exp(−dt / lag) */
  /* Импульсы по спицам (updatePulses в main.js): кружок изредка пробегает
     от хаба к узлу. Расписание у каждой спицы своё и случайное, плюс
     серии: после старта иногда почти сразу бежит ещё один по другой
     спице. Одновременно не больше --pulse-max. */
  --pulse-max:            3;   /* одновременно на экране; наведённой спицы лимит не касается */
  --t-pulse-travel:    1400ms; /* один пробег от хаба до узла */
  --t-pulse-gap-min:   6000ms; /* пауза спицы после пробега: min + (max − min) · random^--pulse-gap-skew */
  --t-pulse-gap-max:  18000ms;
  --pulse-gap-skew:     0.7;   /* < 1 — длинные паузы чаще коротких */
  --t-pulse-first-min: 1500ms; /* первый импульс после прорастания спицы */
  --t-pulse-first-max: 9000ms;
  --t-pulse-spacing:    150ms; /* минимум между стартами любых двух — только чтобы не в одном кадре */
  --pulse-burst-chance: 0.35;  /* после старта — шанс, что через --t-pulse-burst-* побежит ещё один; у продолжения вдвое меньше */
  --t-pulse-burst-min:  250ms;
  --t-pulse-burst-max: 1200ms;
  --pulse-burst-len:      3;   /* импульсов в серии не больше */
  --t-pulse-defer-min:  400ms; /* отсрочка старта, если лимит занят */
  --t-pulse-defer-max: 1500ms;
  --t-pulse-hover:     1000ms; /* наведение на узел: старт каждые столько от старта до старта, первый — в том же кадре */
  /* Загрузка (bootTick в main.js, тем же рендер-циклом): пустой экран →
     лого проступает → по морганию на каждый узел стола, строго по очереди
     «моргание → узел»: глаз закрылся — и, пока открывается, от лого
     прорастает спица к узлу, узел проступает на её финише; следующее
     моргание — после паузы за проступившим узлом; когда
     дорастёт последняя спица — рёбра между узлами, потом подписи, и стол
     живой. Лого ждёт свою картинку (декод и маска), каждое моргание — свой
     узел, подписи — шрифты; на все ожидания вместе — не больше
     --boot-wait-max (это потолок ожидания, не проявлений). Времена — от
     начала загрузки. */
  --boot-empty:         100ms; /* пустой экран — не меньше; дальше — пока картинка лого не готова */
  --boot-logo-fade:     900ms; /* лого проступает: только прозрачность 0 → 1, по --ease-boot-logo, от готовности картинки; первое моргание — когда проступило целиком */
  --boot-blink-gap:     200ms; /* пауза: узел проступил целиком (и глаз открылся) → следующее моргание */
  --boot-blink-close:   180ms; /* глаз закрывается (--ease-blink-close) … */
  --boot-blink-open:    260ms; /* … и открывается (--ease-out) */
  --boot-blink-angle:  -13deg; /* ось моргания */
  --boot-blink-squash:   0.04; /* высота закрытого глаза */
  --boot-line:          260ms; /* спица — с момента, когда глаз закрылся (растёт, пока он открывается; узел проступает на её финише…); рёбра между узлами — после последнего узла, столько же */
  --boot-node:          180ms; /* … за последние столько мс линии */
  --boot-labels:        250ms; /* подписи узлов после рёбер между ними */
  --boot-levitate-in:  2900ms; /* амплитуда левитации стола 0 → 1 с начала подписей (smoothstep — без скачка положения и скорости). Полпериода самого быстрого покачивания (2π / 1.09 рад/с ≈ 5,8 с, makeLevitator): при разгоне узел не быстрее своего же покачивания (≤ 1,1×) */
  --boot-wait-max:     3000ms; /* потолок всех ожиданий готовности за загрузку, суммарно */
  /* Подсказка первого визита (updateHint в main.js): кривая от объекта +
     две строки, фон на это время затемнён. Появление — стадиями по
     очереди, уход — в обратном порядке, в --hint-out-factor раз быстрее. */
  --t-hint-delay:  800ms; /* пауза после конца перехода и волны уровня */
  --t-hint-dim:    650ms; /* затемнение 0 → --hint-dim */
  --t-hint-dot:    160ms; /* точка в старте кривой */
  --t-hint-line:  1100ms; /* перо: кривая дорастает и продолжается линией под текстом; текст проступает, пока перо идёт под ним */
  --t-hint-ok:     240ms; /* «ОК» на конце линии проявляется последним */
  --t-hint-grace:  400ms; /* после полного появления текста: только потом клик/клавиша закрывают подсказку */
  --hint-out-factor: 0.5; /* уход: каждая стадия за такую долю своего времени */
  --ease-navigate: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out:      cubic-bezier(0.4, 0, 0.2, 1); /* моргание лого на загрузке — открытие глаза; подсказка первого визита */
  --ease-blink-close: cubic-bezier(0.42, 0, 1, 1); /* моргание лого — закрытие глаза (= ease-in) */
  --ease-boot-logo:   cubic-bezier(0, 0, 0.58, 1); /* проступание лого на загрузке (ease-out: без скачка в начале — expo-out давал 60 % за первые ~70 мс) */
  --ease-drawer:   cubic-bezier(0.45, 0, 0.35, 1); /* вход на Profile и уход с него (drawerWin): мягкий старт и мягкий конец — обращённая (уход) тоже мягкая с обеих сторон */
  --ease-stack-sheet: cubic-bezier(0.45, 0, 0.35, 1); /* лист стопки категории снимается и ложится обратно (updateStackDeal): мягкий старт — первые кадры лист закрывает свою карточку, мягкая посадка */
  --ease-stack-data: cubic-bezier(0.42, 0, 0.58, 1); /* строка данных стопки проступает на посадке (fadeInCategoryData): без скачка в начале и в конце */
  --ease-pulse:    cubic-bezier(0.42, 0, 0.58, 1); /* пробег импульса по спице (ease-in-out) */
  --ease-site-preview-in:  cubic-bezier(0.16, 1, 0.3, 1);   /* появление превью сайта: expo-out, ≈ 1 − 2^(−10t), долгое замедление */
  --ease-site-preview-out: cubic-bezier(0.42, 0, 0.58, 1);  /* исчезновение превью сайта: ease-in-out */
  --pulse-r:       calc(2 * var(--px-up)); /* радиус кружка-импульса */

  /* ── Подсказка первого визита ── геометрия кривой (кубическая Безье:
     старт на верхнем крае иконки, конец у текста) и затемнение. */
  --hint-dx:    clamp(64px, 12vmin, calc(132 * var(--px-up))); /* смещение конца кривой от старта по X */
  --hint-dy:    clamp(56px, 11vmin, calc(120 * var(--px-up))); /* … по Y (вверх или вниз) */
  --hint-curve: 0.66;  /* k: c1 = (sx, sy − k·dy), c2 = (ex − k·dx, ey) */
  --hint-dim:   0.6;   /* непрозрачность затемнения фоном --bg-base */
  --hint-dim-full: 0.85; /* … в режиме «у нижнего края»: текст работы под подсказкой — около 15 % видимости */
  --hint-dot-r: calc(1.8 * var(--px-up)); /* точка в старте кривой */
  --hint-color: #BDBDBF; /* текст подсказки — проза чуть ярче обычной, между --text-secondary и --text-primary */
  --hint-ok-hit:       calc(28 * var(--px-up)); /* зона нажатия «ОК» (невидимая), мышь */
  --hint-ok-hit-touch: calc(44 * var(--px-up)); /* … на тач-устройствах (pointer: coarse) */

  /* ── Слои ── Шкала: линии → узлы → подписи → верхняя граница. Порядок
     рабочего стола «линии → лого → иконки»: .os-links (--z-lines), .boot
     с лого (--z-lines + 1), .os-apps (--z-nodes). */
  --z-lines:        0;
  --z-nodes:       10;
  --z-labels:      20;
  --z-overlay:    100; /* верхняя граница шкалы; модального слоя в системе нет */
  /* Вне шкалы — именованные значения, числа не менялись: */
  --z-logo:          10; /* .hero-logo внутри .boot (у .boot свой контекст наложения) */
  --z-sublinks:      17; /* #os-sublinks — спицы открытого уровня */
  --z-drawer-under:  calc(var(--z-lines) - 1); /* ящик Profile, пока прорисовывается и задвигается, — под всем рабочим столом */
  --z-scrim:         24; /* #os-scrim — клик мимо узлов (закрывает открытую работу) */
  --z-subgraph:      25; /* #os-subgraph — хаб, спутники, работы, блоки Profile; выше scrim */
  --z-filter:        26; /* строка фильтра годов — кликабельна поверх subgraph и scrim */
  --z-site-preview:  45; /* превью сайта при наведении — над узлами и спицами, под подсказкой */
  --z-hint:          60; /* подсказка первого визита — кривая, точка, текст */
  /* Внутри #os-subgraph (свой контекст наложения): */
  --z-hub:            2; /* хаб поверх работ (у работ отрицательный --z-work по дате) */
  --z-work-sheet:     3; /* лист стопки-призрака в полёте (раздача и сбор, updateStackDeal) — над стопкой (хабом), под наведённой */
  --z-work-sheet-under: 1; /* средний и нижний листы на сборе — под обложку: ниже стопки (хаба), над остальными работами */
  --z-work-raised:    5; /* работа под курсором, в драге — над соседями */
  --z-work-flying:   20; /* открытая работа весь перелёт (openWork/closeWork) — над хабом */
  --z-work-peek:     10; /* сосед открытой работы у края экрана (листание) — под ней, над россыпью и хабом */
  --z-work-pager:    21; /* шевроны листания — над летящей работой */
  --z-work-dimmed: -100000; /* работа вне выбранного фильтра — под всеми */
  /* Внутри #os-apps и #os-subgraph — затемнение подсказки и её объект над
     ним: из контекста наложения слоя объект не поднять, поэтому
     затемнение живёт в том же слое, что и объект. */
  --z-hint-dim:      30; /* выше всех узлов слоя, включая летящую работу (--z-work-flying) */
  --z-hint-target:   40; /* объект подсказки — над затемнением */

  /* ── Форма фото-хаба Profile ── Грубый силуэт (колокол): clip-path на
     .os-icon-wrap--hub в desktop.css и контур клика в main.js
     (HUB_SHAPE_POLY) читают одни и те же точки. */
  --hub-shape: polygon(
    50% 0%, 68% 3%, 81% 12%, 91% 27%, 96% 45%, 97% 63%, 94% 80%, 90% 93%,
    92% 100%, 8% 100%, 10% 93%, 6% 80%, 3% 63%, 4% 45%, 9% 27%, 19% 12%, 32% 3%
  );
}

html, body { width: 100%; height: 100%; overflow: hidden; }

body {
  background: var(--bg-base);
  color: var(--text-soft);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

/* ─── Hero ─── */
.hero {
  position: relative;
  width: 100%; height: 100vh;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}

/* ─── Фокус ─── */
/* Видимого фокуса у сайта нет (решение владельца 08.10.2026): клавиатурной
   навигации нет, Tab ничего не выбирает (tabindex=-1). Фокус ставят клик
   (на ссылке или кнопке следующая клавиша зажигала браузерное кольцо) и
   скринридер (VoiceOver) — фокус остаётся, но кольца нет. Только внутри
   сайта и подсказки — окошки правки админки (.adm) вне них. */
#desktop :focus,
.os-hint :focus {
  outline: none;
}

/* ─── Только для скринридера ─── */
/* Объявление смены уровня (#level-live, aria-live, см. announceLevel в
   main.js) — в дереве доступности есть, на экране нет. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ─── Logo wrapper ─── */
.hero-logo {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--z-logo);
  pointer-events: none;
}

.hero-logo__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Размер обёртки — в boot.css (там же, где boot-последовательность). */
#logo-wrap {
  position: relative;
  display: block;
  transform-origin: center center;
  /* Едва заметная левитация — как в невесомости. Значение
     --levitate-y считается покадрово в main.js (tickLevitation) тем
     же кодом, что двигает иконки и хабы, — так линии-связи, которые
     каждый кадр перерисовываются от реальной позиции лого, точно
     следуют за его покачиванием. Моргание на загрузке пишется не сюда,
     а в саму картинку (#logo-img, boot.css). При
     prefers-reduced-motion цикл всё равно идёт, но levitateY()
     возвращает 0 — --levitate-y всегда 0px. */
  transform: translateY(var(--levitate-y, 0px));
  /* Тонкие кончики лучей на лого рябили (алиасинг) при покачивании —
     без своего композитного слоя браузер каждый кадр перерастеризует
     элемент заново на новом дробном пикселе Y, и тонкие диагонали
     "ползут". will-change поднимает #logo-wrap на GPU-слой один раз —
     дальше кадр просто сдвигается, без перерасчёта содержимого. */
  will-change: transform;
}

#logo-wrap img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
  pointer-events: none;
}
