/* ══════════════════════════════════════════════════════════════
   rionsotnev — САЙТ-БОТ
   Страница не листается, а отвечает. Одна команда — одна сцена,
   свет под ней рисует канвас, всё остальное лежит поверх.
   ══════════════════════════════════════════════════════════════ */

/* --c — активный акцент. Регистрируем как <color>: обычные custom
   properties не интерполируются, а на --c висит буквально всё —
   свечение консоли, подчёркивания, рамки, цвет шейдера. Без @property
   смена сцены давала бы рывок вместо перехода. */
@property --c {
  syntax: '<color>';
  inherits: true;
  initial-value: #6C8CFF;
}

:root{
  --ink:      #05060C;
  --fg:       #EEF0F8;
  --fg-mut:   #A0A6BE;
  --fg-dim:   #6E7490;
  --line:     rgb(238 240 248 / .10);
  --line-2:   rgb(238 240 248 / .20);
  --surf:     rgb(255 255 255 / .035);
  --surf-2:   rgb(255 255 255 / .07);

  --brand:    #6C8CFF;
  --c:        var(--brand);

  --disp:  "Manrope", system-ui, -apple-system, sans-serif;
  --mono:  "JetBrains Mono", ui-monospace, monospace;

  --gut:   clamp(1.15rem, 4vw, 4.5rem);
  --shell: min(1340px, 100% - 2 * var(--gut));

  /* Выход, «дорогое» замедление. Один и тот же для всего, что приезжает
     на экран: разнобой в кривых читается как несобранность. */
  --e:     cubic-bezier(.16, 1, .3, 1);
  --e-io:  cubic-bezier(.76, 0, .24, 1);

  --hdr-h: 4.5rem;
  --con-h: 4.75rem;
}

html{
  transition: --c 1s var(--e);
  -webkit-text-size-adjust:100%;
  background:var(--ink);
}
*, *::before, *::after{ box-sizing:border-box; margin:0; padding:0; }

body{
  min-height:100svh;
  background:var(--ink);
  color:var(--fg);
  font-family:var(--disp);
  font-size:clamp(.95rem, .35vw + .86rem, 1.06rem);
  line-height:1.6;
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
/* Со скриптом страница — приложение на один экран: скроллится не документ,
   а сама сцена внутри себя. Без скрипта остаётся обычный длинный лендинг. */
.js body{ overflow:hidden; height:100svh; }

svg{ display:block; width:100%; height:100%; }
a{ color:inherit; text-decoration:none; }
ul, ol{ list-style:none; }
button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
img{ max-width:100%; }
input{ font:inherit; color:inherit; background:none; border:0; }
::selection{ background:var(--c); color:#04050A; }

.mono{ font-family:var(--mono); font-weight:400; letter-spacing:.07em; }
.sprite{ position:absolute; width:0; height:0; overflow:hidden; }

.sr, .skip{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
}
.skip:focus{
  width:auto; height:auto; margin:0; clip-path:none;
  inset:1rem auto auto 1rem; z-index:99; padding:.7rem 1.1rem;
  background:var(--c); color:#04050A; border-radius:.6rem;
}
:focus-visible{ outline:2px solid var(--c); outline-offset:3px; border-radius:.35rem; }

/* ── ФОН ─────────────────────────────────────────────────────── */

.gl{
  position:fixed; inset:0; z-index:0;
  width:100%; height:100%;
  display:block;
  /* Канвас рисуется в половинном разрешении и растягивается: fbm с
     четырьмя октавами на ретине в полный размер съедает кадр на
     интегрированной графике, а на таком мягком градиенте разницы не видно. */
  image-rendering:auto;
}
/* Запасной свет, если WebGL не поднялся: те же пятна, только статикой. */
.no-gl .gl{
  background:
    radial-gradient(58% 44% at 22% 18%, color-mix(in oklab, var(--c) 55%, transparent), transparent 70%),
    radial-gradient(46% 40% at 82% 72%, color-mix(in oklab, var(--c) 32%, transparent), transparent 72%),
    var(--ink);
  filter:blur(40px);
}

/* Три слоя, и каждый решает свою задачу: радиальный гасит углы, вертикальный
   отделяет шапку и строку ввода от тумана, наклонный притапливает левую
   треть — ту, где стоит весь текст. Без последнего заголовок и мелкие
   подписи тонут в светлых жилах шейдера. */
.vign{
  position:fixed; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(100deg, rgb(5 6 12 / .82) 0%, rgb(5 6 12 / .45) 38%, transparent 62%),
    radial-gradient(120% 90% at 50% 45%, transparent 30%, rgb(5 6 12 / .55) 78%, rgb(5 6 12 / .88) 100%),
    linear-gradient(rgb(5 6 12 / .55), transparent 22% 68%, rgb(5 6 12 / .78));
}

/* Зерно склеивает градиенты: без него широкие мягкие переходы полосят
   на 8-битных панелях. Анимация — шагами, плавное дрожание читается
   как брак картинки. */
.grain{
  position:fixed; inset:-150%; z-index:2; pointer-events:none;
  opacity:.05; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grain 6s steps(6) infinite;
}
@keyframes grain{
  0%{ transform:translate(0,0) }        16%{ transform:translate(-3%,2%) }
  33%{ transform:translate(2%,-3%) }    50%{ transform:translate(-2%,-2%) }
  66%{ transform:translate(3%,1%) }     83%{ transform:translate(-1%,3%) }
}

/* ── ЗАСТАВКА ────────────────────────────────────────────────── */

.boot{
  position:fixed; inset:0; z-index:60;
  display:grid; place-items:center;
  background:var(--ink);
  transition:opacity .7s var(--e), visibility .7s;
}
.boot.is-off{ opacity:0; visibility:hidden; }
.no-js .boot{ display:none; }

.boot__in{ display:grid; justify-items:center; gap:1.4rem; width:min(19rem, 76vw); }
.boot__mark{ width:3.4rem; opacity:.9; animation:bootPulse 1.9s var(--e-io) infinite; }
@keyframes bootPulse{ 50%{ opacity:.42; transform:scale(.94) } }

.boot__log{
  font-size:.72rem; letter-spacing:.22em; text-transform:uppercase;
  color:var(--fg-dim); min-height:1.2em;
}
.boot__bar{ width:100%; height:2px; background:var(--line); overflow:hidden; border-radius:2px; }
.boot__bar i{
  display:block; height:100%; width:0;
  background:var(--c); box-shadow:0 0 12px var(--c);
  transition:width .4s var(--e);
}

/* ── ШАПКА ───────────────────────────────────────────────────── */

.hdr{
  position:fixed; z-index:20; inset:0 0 auto;
  height:var(--hdr-h); padding-inline:var(--gut);
  display:flex; align-items:center; gap:1.2rem;
}
.hdr__brand{ display:flex; align-items:center; gap:.6rem; font-weight:700; letter-spacing:-.01em; }
.hdr__mark{ width:1.65rem; }
.hdr__brand span{ transition:color .4s; }
.hdr__brand:hover span{ color:var(--c); }

.hdr__st{
  margin-left:auto; display:flex; align-items:center; gap:.5rem;
  font-size:.66rem; letter-spacing:.2em; text-transform:uppercase; color:var(--fg-dim);
}
.pulse{
  width:.4rem; height:.4rem; border-radius:50%; background:var(--c);
  box-shadow:0 0 0 0 var(--c); animation:pulse 2.4s var(--e-io) infinite; flex:none;
}
@keyframes pulse{ 70%{ box-shadow:0 0 0 .55rem transparent } 100%{ box-shadow:0 0 0 0 transparent } }

.hdr__tg{
  display:flex; align-items:center; gap:.5rem;
  padding:.55rem .95rem; border-radius:2rem;
  border:1px solid var(--line-2); font-size:.86rem; font-weight:600;
  transition:border-color .35s, background .35s, color .35s;
}
.hdr__tg svg{ width:1rem; height:1rem; }
.hdr__tg:hover{ border-color:var(--c); color:var(--c); background:var(--surf); }
@media (max-width:720px){ .hdr__tg span{ display:none } .hdr__tg{ padding:.55rem } }

/* ── СЦЕНА ───────────────────────────────────────────────────── */

.stage{ position:relative; z-index:10; }
.js .stage{ position:fixed; inset:0; }

.sc{
  /* Снизу живут строка ввода, чипсы и лента реплик. Отступ обязан
     покрывать все три: иначе всплывающая реплика ложится на контент. */
  padding:calc(var(--hdr-h) + 2rem) 0 calc(var(--con-h) + 9.5rem);
  display:grid;
  /* safe: сцена выше экрана центрируется «в упор к началу», иначе верх
     уезжает за границу прокрутки и до заголовка не добраться. */
  align-content:safe center;
}
.js .sc{
  position:absolute; inset:0;
  overflow-y:auto; overflow-x:clip;
  overscroll-behavior:contain;
  visibility:hidden; pointer-events:none;
}
.js .sc.is-on{ visibility:visible; pointer-events:auto; }
/* Уходящая сцена ещё видима, но уже не кликается: иначе на полсекунды
   перехода под курсором остаются ссылки прошлого экрана. */
.js .sc.is-out{ visibility:visible; pointer-events:none; }

/* Уход быстрее прихода и другой кривой: входящее замедляется «дорого»,
   уходящее должно убраться с дороги, а не откланиваться. Без этих правил
   снятие .is-on гасило бы содержимое мгновенно — переходы объявлены
   только внутри .is-on. */
.js .sc.is-out [data-anim]{
  opacity:0; transform:translateY(-1.1rem);
  transition:opacity .42s var(--e-io), transform .42s var(--e-io);
}
.js .sc.is-out [data-split] .w__in{
  transform:translateY(-115%);
  transition:transform .5s var(--e-io) calc(var(--i, 0) * 24ms);
}

/* Колонку не сужаем: заголовок должен начинаться от того же края, что и
   логотип в шапке. Меру задают отдельные блоки текста, а не вся сцена —
   узкая колонка посреди экрана превращала героя в открытку. */
.sc__in{ width:var(--shell); margin-inline:auto; }

.sc--split .sc__in{ max-width:none; }

/* Две колонки раскладывает сама сцена: демо лежит в разметке соседом
   .sc__in, а не внутри него, чтобы на узком экране оно уходило под текст
   без переноса узлов скриптом. */
@media (min-width:1000px){
  /* Колодец задаётся padding'ом, а не width + margin:auto: сцена — это
     position:absolute; inset:0, и собственная ширина отключила бы у неё
     правый край, а скроллбар переполнения уехал бы внутрь колонки. */
  .sc--split{
    padding-inline:max(var(--gut), (100% - 1340px) / 2);
    grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
    gap:clamp(2rem, 5vw, 5rem);
    align-items:center;
  }
  .sc--split > .sc__in,
  .sc--split > .sc__demo{ grid-row:1; }
  .sc--split > .sc__in{ width:auto; margin:0; max-width:none; }
}
@media (max-width:999px){
  .sc--split{ gap:2.5rem; }
  .sc--split > .sc__in,
  .sc--split > .sc__demo{ width:var(--shell); margin-inline:auto; }
  .sc--split > .sc__demo{ margin-top:2.5rem; }
}

/* ── ТИПОГРАФИКА СЦЕНЫ ───────────────────────────────────────── */

.sc__eyebrow{
  display:flex; align-items:center; gap:.6rem; flex-wrap:wrap;
  font-size:.68rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--fg-dim); margin-bottom:1.5rem;
}

/* Потолок держат и vw, и vh: на ноутбуке 7.4vw давали заголовок в три
   строки, после которого список причин уезжал под строку ввода. */
.sc__t{
  font-size:clamp(2.4rem, min(5.8vw, 10vh), 5.4rem);
  font-weight:800; line-height:.96; letter-spacing:-.035em;
  margin-bottom:1.5rem;
  text-wrap:balance;
}
/* В двухколонке заголовку достаётся половина ширины — тот же кегль
   разваливал «Камень · Ножницы · Бумага» на три строки. */
.sc--split .sc__t{ font-size:clamp(2.2rem, min(4.4vw, 8vh), 4.2rem); }
/* Герой крупнее остальных заголовков: это первое, что видит посетитель,
   и именно масштаб делает страницу «дорогой», а не количество эффектов.
   Потолок держит vh, а не только vw: на ноутбучном экране 11vw давали
   заголовок, после которого лид и цифры уезжали под строку ввода. */
.sc__t--hero{ font-size:clamp(2.8rem, min(9.4vw, 17vh), 9rem); letter-spacing:-.045em; }

/* Слово едет из-под маски. Маске обязателен запас с двух сторон: при
   line-height < 1 «р» и «у» свисают ниже строчного бокса, а бреве над «й»
   и точки над «ё» выходят выше — без padding маска срежет и то, и другое.
   Отрицательные margin возвращают межстрочник на место. */
.w{ display:inline-block; overflow:hidden; padding:.16em .04em .22em; margin:-.16em -.04em -.22em; }
.w__in{ display:inline-block; will-change:transform; }
.js [data-split] .w__in{ transform:translateY(115%); }
.is-on [data-split] .w__in{
  transform:translateY(0);
  transition:transform .95s var(--e) calc(var(--i, 0) * 42ms + 120ms);
}

.sc__lede{
  font-size:clamp(1rem, .5vw + .92rem, 1.24rem);
  color:var(--fg-mut); max-width:44ch; margin-bottom:1.9rem;
}
.sc__hint{ font-size:.8rem; color:var(--fg-dim); margin-bottom:2.2rem; }
.sc__note{ font-size:.76rem; letter-spacing:.08em; color:var(--fg-mut); margin-top:1.6rem; }

kbd{
  font-family:var(--mono); font-size:.74em;
  padding:.16em .45em; border-radius:.35rem;
  border:1px solid var(--line-2); background:var(--surf);
}

/* Инлайновая команда в тексте — кликается и подставляется в строку ввода. */
.ic{
  font-family:var(--mono); color:var(--c);
  border-bottom:1px solid color-mix(in oklab, var(--c) 45%, transparent);
  transition:background .3s, border-color .3s;
  padding:.05em .2em; border-radius:.25rem;
}
.ic:hover{ background:color-mix(in oklab, var(--c) 16%, transparent); border-color:var(--c); }

/* Появление блоков. Один шаблон на всё: разнобой заметнее, чем кажется. */
.js [data-anim]{ opacity:0; transform:translateY(1.5rem); }
.is-on [data-anim]{
  opacity:1; transform:none;
  transition:opacity .8s var(--e) calc(var(--i, 0) * 70ms + 260ms),
             transform .8s var(--e) calc(var(--i, 0) * 70ms + 260ms);
}

/* ── МЕЛКИЕ ЭЛЕМЕНТЫ ─────────────────────────────────────────── */

.st{ display:inline-flex; align-items:center; gap:.4rem; }
.st i{ width:.4rem; height:.4rem; border-radius:50%; flex:none; }
.st--live i{ background:#5EE9B5; box-shadow:0 0 8px #5EE9B5; }
.st--wip  i{ background:#FBBF3C; box-shadow:0 0 8px #FBBF3C; }
.st--soon i{ background:var(--fg-dim); }

.tags{ display:flex; flex-wrap:wrap; gap:.5rem; margin-bottom:2rem; }
.tags li{
  font-size:.78rem; padding:.36rem .8rem; border-radius:2rem;
  border:1px solid var(--line); background:var(--surf); color:var(--fg-mut);
}

.btn{
  display:inline-flex; align-items:center; gap:.6rem;
  padding:.9rem 1.5rem; border-radius:2.5rem;
  font-weight:700; letter-spacing:-.01em;
  transition:transform .45s var(--e), box-shadow .45s var(--e), background .3s;
}
.btn svg{ width:1.1rem; height:1.1rem; }
.btn--fill{
  background:var(--c); color:#04050A;
  box-shadow:0 .6rem 2rem -.6rem color-mix(in oklab, var(--c) 70%, transparent);
}
.btn--fill:hover{
  transform:translateY(-2px);
  box-shadow:0 1rem 2.6rem -.7rem color-mix(in oklab, var(--c) 85%, transparent);
}
.btn--big{ font-size:clamp(1.05rem, 2vw, 1.5rem); padding:1.1rem 2rem; }

.facts{ display:flex; flex-wrap:wrap; gap:clamp(1.6rem, 5vw, 3.4rem); }
.facts li{ display:grid; gap:.15rem; }
.facts b{ font-size:clamp(1.6rem, 3.4vw, 2.4rem); font-weight:800; letter-spacing:-.03em; line-height:1; }
.facts span{ font-size:.64rem; letter-spacing:.18em; color:var(--fg-dim); }

/* ── /help: СПИСОК КОМАНД ────────────────────────────────────── */

.cmds{ display:grid; gap:.1rem; margin-bottom:.5rem; max-width:52rem; }
.cmds li{ border-top:1px solid var(--line); }
.cmds li:last-child{ border-bottom:1px solid var(--line); }
.cmds button{
  width:100%; display:grid; align-items:baseline; gap:.2rem 1.2rem;
  grid-template-columns:7.5rem minmax(0,1fr) auto;
  padding:1.05rem .6rem; text-align:left;
  transition:background .35s, padding .45s var(--e);
}
.cmds button:hover{ background:var(--surf); padding-inline:1.1rem .6rem; }
.cmds em{ font-style:normal; color:var(--c); font-weight:500; font-size:.98rem; }
.cmds span{ color:var(--fg-mut); }
.cmds i{
  font-style:normal; font-size:.6rem; letter-spacing:.18em; color:var(--fg-dim);
  opacity:0; transition:opacity .35s;
}
.cmds button:hover i{ opacity:1; }
@media (max-width:640px){
  .cmds button{ grid-template-columns:1fr; padding-block:.9rem; }
  .cmds i{ display:none; }
}

/* ── /save: ЗАГРУЗЧИК ────────────────────────────────────────── */

.sc__demo{ display:grid; gap:.9rem; justify-items:stretch; }
.demo__note{
  font-size:.64rem; letter-spacing:.14em; color:var(--fg-mut);
  text-align:center; text-wrap:balance;
}

/* Общая «стеклянная» подложка демо. Заливка полупрозрачная, поэтому свет
   канваса проходит сквозь неё и панель живёт вместе с фоном. */
.dl, .rps, .wx, .vin{
  border:1px solid var(--line); border-radius:1.4rem;
  background:linear-gradient(rgb(255 255 255 / .055), rgb(255 255 255 / .02));
  backdrop-filter:blur(22px) saturate(1.3);
  box-shadow:0 2rem 5rem -2rem rgb(0 0 0 / .8),
             inset 0 1px 0 rgb(255 255 255 / .08);
}

.dl{ overflow:hidden; }
.dl__bar{ display:flex; align-items:center; gap:.6rem; padding:.6rem .6rem .6rem 1rem; border-bottom:1px solid var(--line); }
.dl__url{
  flex:1; min-width:0; font-size:.8rem; color:var(--fg);
  outline:0; caret-color:var(--c);
}
.dl__url::placeholder{ color:var(--fg-dim); }

/* Появляется только когда файл уже ждёт в Telegram. */
.dl__tg{
  display:inline-flex; align-items:center; gap:.4rem; justify-self:start;
  padding:.6rem 1.1rem; border-radius:2rem;
  background:#2AABEE; color:#04050A; font-size:.86rem; font-weight:700;
  transition:transform .4s var(--e), box-shadow .4s var(--e);
  box-shadow:0 .5rem 1.6rem -.5rem rgb(42 171 238 / .7);
}
.dl__tg:hover{ transform:translateY(-2px); box-shadow:0 .8rem 2rem -.5rem rgb(42 171 238 / .9); }
.dl__tg[hidden]{ display:none; }
.dl__go{
  flex:none; padding:.55rem 1.1rem; border-radius:1.6rem;
  background:var(--c); color:#04050A; font-size:.82rem; font-weight:700;
  transition:transform .4s var(--e), opacity .3s;
}
.dl__go:hover{ transform:translateY(-1px); }
.dl__go:disabled{ opacity:.45; cursor:default; transform:none; }

.dl__body{ padding:1.1rem; display:grid; gap:.85rem; }

/* Карточка на месте всегда, просто пока приглушена: если прятать её до
   загрузки, панель стоит наполовину пустой и читается как недорисованная. */
.dl__card{
  display:grid; grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center; gap:.85rem;
  opacity:.32; transition:opacity .5s var(--e);
}
.dl.is-done .dl__card{ opacity:1; }
.dl__thumb{
  width:3.2rem; height:3.2rem; border-radius:.7rem; flex:none;
  background:linear-gradient(140deg, color-mix(in oklab, var(--c) 60%, #10131F), #10131F);
  display:grid; place-items:center; overflow:hidden; position:relative;
}
.dl__thumb i{
  width:0; height:0; border-left:.62rem solid #04050A;
  border-block:.42rem solid transparent; margin-left:.12rem;
}
.dl__meta{ display:grid; gap:.15rem; min-width:0; }
.dl__meta b{ font-size:.94rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.dl__meta em{ font-style:normal; font-size:.66rem; letter-spacing:.12em; color:var(--fg-dim); }
.dl__tick{
  width:1.7rem; height:1.7rem; border-radius:50%; flex:none;
  display:grid; place-items:center; color:#04050A; background:#5EE9B5;
  transform:scale(0); transition:transform .5s var(--e) .1s;
}
.dl.is-done .dl__tick{ transform:scale(1); }
.dl__tick svg{ width:1rem; height:1rem; }

.dl__prog{ height:3px; border-radius:3px; background:var(--line); overflow:hidden; }
.dl__prog i{
  display:block; height:100%; width:0; border-radius:3px;
  background:var(--c); box-shadow:0 0 10px var(--c);
}
.dl__stat{ font-size:.64rem; letter-spacing:.14em; color:var(--fg-dim); text-transform:uppercase; }

/* ── /vinyl: ВЕРТУШКА ────────────────────────────────────────── */

/* Уровень баса приезжает из анализатора звука и раздувает свечение вокруг
   диска. Регистрируем как <number>: без @property это строка, и color-mix
   с calc() по ней не считается. */
@property --bass {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

/* Коробка вертушки шире и выше самой сцены: тонарм вылетает за диск вправо
   на четверть его ширины и вверх на десятую — если этот запас не заложить,
   рычаг либо обрежется, либо потянет за собой горизонтальную прокрутку.
   Отсюда и неквадратное соотношение: оно посчитано ровно по крайним
   положениям картинки на паузе и в конце дуги. */
.deck{
  /* Не 100%: на узком десктопе (~1000px) колонка сама сжимается, и вылет
     противовеса упирался ровно в край сцены, где его срезает overflow-x. */
  position:relative; width:min(27rem, 94%);
  aspect-ratio:1.239 / 1.099; justify-self:center;
}
/* Сцена — квадрат, в котором диск занимает всё; проценты тонарма ниже
   считаются от неё, как в оригинале. Сдвиг влево на половину вылета нужен,
   чтобы по центру коробки оказался сам проигрыватель, а не её геометрия:
   иначе диск встаёт левее строки транспорта под ним. */
.deck__st{ position:absolute; left:9.65%; top:9%; width:80.7%; aspect-ratio:1; }

.vin{
  position:absolute; inset:0;
  border-radius:50%; overflow:hidden;
  cursor:grab; touch-action:none;
}
.vin.is-drag{ cursor:grabbing; }
.vin__cv{ width:100%; height:100%; display:block; }

/* Гало от баса. Живёт тенью, а не отдельным слоем: у диска overflow:hidden,
   и любой внутренний ореол обрезался бы по кромке. */
.vin{
  box-shadow:0 2rem 5rem -2rem rgb(0 0 0 / .8),
             inset 0 1px 0 rgb(255 255 255 / .08),
             0 0 calc(1.2rem + var(--bass) * 4rem) calc(var(--bass) * .5rem)
               color-mix(in oklab, var(--c) calc(var(--bass) * 55%), transparent);
}

/* Тонарм. Все семь чисел скопированы из aesthetic-vinyl-player без правок:
   ширина 55% сцены, высота — она же на аспект картинки (0,76914), пивот на
   подшипнике 68,4/29,2 её собственной коробки, а положение подобрано так,
   что ось вращения приходится ровно в точку 89,465/11 сцены. Менять что-то
   одно нельзя: укладка калибровалась целиком под этот снимок. */
.deck__arm{
  position:absolute;
  width:55%; height:42.303%;
  left:51.833%; top:-1.368%;
  transform-origin:68.421% 29.238%;
  pointer-events:none; user-select:none;
  filter:drop-shadow(2px 6px 6px rgb(0 0 0 / .45));
}

/* Транспорт. Полоса времени кликабельна, но главный способ перемотки —
   сам диск: его можно крутить пальцем, как настоящую пластинку. */
.deck__bar{
  display:flex; align-items:center; gap:.7rem;
  width:min(24rem, 100%); justify-self:center;
  padding:.5rem .7rem; border-radius:2rem;
  border:1px solid var(--line); background:var(--surf);
  backdrop-filter:blur(22px) saturate(1.3);
}
.deck__bar[hidden]{ display:none; }

.deck__play{
  flex:none; width:2.3rem; height:2.3rem; border-radius:50%;
  display:grid; place-items:center;
  background:var(--c); color:#04050A;
  transition:transform .4s var(--e);
}
.deck__play:hover{ transform:scale(1.06); }
.deck__ico{ width:1.15rem; height:1.15rem; fill:currentColor; }
.deck__play .i-pause{ display:none; }
.deck__play.is-on .i-play{ display:none; }
.deck__play.is-on .i-pause{ display:block; }

.deck__seek{
  position:relative; flex:1; min-width:0; height:1.1rem; cursor:pointer;
}
.deck__seek::before{
  content:''; position:absolute; inset:50% 0 auto 0; height:3px;
  transform:translateY(-50%); border-radius:3px; background:var(--line-2);
}
.deck__seek i{
  position:absolute; inset:50% auto auto 0; height:3px; width:0;
  transform:translateY(-50%); border-radius:3px; pointer-events:none;
  background:var(--c); box-shadow:0 0 10px var(--c);
}
.deck__time{ flex:none; font-size:.64rem; color:var(--fg-mut); font-variant-numeric:tabular-nums; }

.deck__rpm{ flex:none; display:flex; gap:.15rem; }
.deck__rpm button{
  padding:.2rem .45rem; border-radius:.6rem;
  font-size:.58rem; letter-spacing:.08em; color:var(--fg-dim);
  transition:color .3s var(--e), background .3s var(--e);
}
.deck__rpm button:hover{ color:var(--fg); }
.deck__rpm button.is-on{ color:#04050A; background:var(--c); }

/* На узком экране демо и так уходит под текст. Вертушка во всю колонку
   занимала бы там весь экран, и приёмник файла оставался бы за кадром —
   а он здесь и есть главное действие. */
@media (max-width:999px){
  .deck{ width:min(19rem, 84%); }
  /* Полосе времени на узкой строке остаётся считанные пиксели, если не
     поджать всё вокруг: она здесь важнее, чем воздух между кнопками. */
  .deck__bar{ width:min(17rem, 84%); gap:.45rem; padding:.45rem .55rem; }
  .deck__rpm button{ padding:.2rem .3rem; }
}

/* Приёмник файла. Метка обёрнута вокруг input[type=file]: системную
   кнопку выбора файла не стилизуют, а клик по label открывает диалог. */
.up{ display:grid; gap:.7rem; justify-items:center; width:min(24rem,100%); justify-self:center; }
.up__zone{
  position:relative; overflow:hidden; width:100%;
  display:grid; gap:.25rem; justify-items:center; text-align:center;
  padding:.85rem 1.1rem; border-radius:1.2rem; cursor:pointer;
  border:1px dashed color-mix(in oklab, var(--c) 45%, var(--line));
  background:rgb(255 255 255 / .03);
  transition:border-color .35s var(--e), background .35s var(--e), transform .35s var(--e);
}
.up__zone:hover, .up.is-over .up__zone{
  border-color:var(--c); background:color-mix(in oklab, var(--c) 12%, transparent);
}
.up.is-over .up__zone{ transform:scale(1.02); }
.up.is-busy .up__zone{ cursor:progress; }
.up__t{ font-size:.86rem; }
.up__s{ font-size:.6rem; letter-spacing:.14em; color:var(--fg-dim); text-transform:uppercase; }

/* Полоска прогресса лежит по нижней кромке рамки: отдельный элемент
   под зоной ломал бы её высоту на каждом шаге загрузки. */
.up__prog{
  position:absolute; inset:auto 0 0 0; height:2px; width:0;
  background:var(--c); box-shadow:0 0 10px var(--c);
  transition:width .5s var(--e);
}

.up__tg{
  display:inline-flex; align-items:center; gap:.4rem;
  padding:.6rem 1.1rem; border-radius:2rem;
  background:#2AABEE; color:#04050A; font-size:.86rem; font-weight:700;
  transition:transform .4s var(--e), box-shadow .4s var(--e);
  box-shadow:0 .5rem 1.6rem -.5rem rgb(42 171 238 / .7);
}
.up__tg:hover{ transform:translateY(-2px); box-shadow:0 .8rem 2rem -.5rem rgb(42 171 238 / .9); }
.up__tg[hidden]{ display:none; }
/* Та же кнопка встречается и прямо в сетке демо (сцена /rps), где
   justify-items:stretch растянул бы её во всю колонку. */
.sc__demo > .up__tg{ justify-self:center; }

/* ── /rps: ИГРА ──────────────────────────────────────────────── */

.rps{ padding:1.4rem; display:grid; gap:1.1rem; }
.rps__score{
  display:flex; align-items:center; justify-content:space-between;
  font-size:.7rem; letter-spacing:.16em; color:var(--fg-dim);
}
.rps__score span{ display:flex; align-items:center; gap:.5rem; }
.rps__score b{ font-family:var(--disp); font-size:1.5rem; letter-spacing:-.02em; color:var(--fg); }
.rps__score em{ font-style:normal; font-size:.62rem; }

.rps__ring{
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:.8rem;
  padding:1rem 0;
}
.rps__hand{ width:min(6.5rem, 26vw); aspect-ratio:1; justify-self:center; }
.rps__hand--op{ transform:scaleX(-1); }
.rps.is-play .rps__hand{ animation:shake .42s var(--e-io) 3; }
@keyframes shake{ 50%{ transform:translateY(-16%) rotate(-8deg) } }
.rps.is-play .rps__hand--op{ animation-name:shakeOp; }
@keyframes shakeOp{ 0%,100%{ transform:scaleX(-1) } 50%{ transform:scaleX(-1) translateY(-16%) rotate(-8deg) } }
.rps__vs{ font-size:.7rem; letter-spacing:.2em; color:var(--fg-dim); }

.rps__say{ text-align:center; font-weight:700; font-size:1.1rem; min-height:1.6em; }
.rps__say.win{ color:#5EE9B5; }
.rps__say.lose{ color:#FB7185; }

.rps__keys{ display:grid; grid-template-columns:repeat(3, 1fr); gap:.5rem; }
.rps__keys button{
  padding:.75rem .4rem; border-radius:.8rem; font-size:.88rem; font-weight:600;
  border:1px solid var(--line-2); background:var(--surf);
  transition:background .3s, border-color .3s, transform .4s var(--e);
}
.rps__keys button:hover{ background:var(--surf-2); border-color:var(--c); transform:translateY(-2px); }
.rps__keys button:disabled{ opacity:.4; cursor:default; transform:none; }

/* ── /weather: КАРТОЧКА ──────────────────────────────────────── */

.wx{ padding:1.4rem; display:grid; gap:1.2rem; }
.wx__now{ display:grid; grid-template-columns:auto auto 1fr; align-items:center; gap:.9rem; }
.wx__now b{ font-size:clamp(2.2rem, 5vw, 3.2rem); font-weight:800; letter-spacing:-.04em; line-height:1; }
.wx__now > span{ display:grid; gap:.2rem; font-size:.9rem; color:var(--fg-mut); }
.wx__now em{ font-style:normal; font-size:.6rem; letter-spacing:.16em; color:var(--fg-dim); }

.emo{ width:1.2em; height:1.2em; display:inline-block; vertical-align:-.22em; }
.emo--xl{ width:3.2rem; height:3.2rem; }

.wx__row{ display:grid; grid-template-columns:repeat(6, 1fr); gap:.5rem; align-items:end; height:6rem; }
.wx__row > span{ display:grid; gap:.4rem; justify-items:center; height:100%; align-content:end; }
.wx__row i{
  display:block; width:100%; max-width:1.6rem; border-radius:.4rem;
  height:calc(var(--h) * 4.6rem);
  background:linear-gradient(color-mix(in oklab, var(--c) 85%, transparent), color-mix(in oklab, var(--c) 22%, transparent));
}
.wx__row em{ font-style:normal; font-size:.58rem; color:var(--fg-dim); }
.wx__foot{ font-size:.92rem; color:var(--fg-mut); }

/* ── /about: ПРИЧИНЫ ─────────────────────────────────────────── */

.why{ display:grid; gap:.1rem; margin-top:.8rem; max-width:64rem; }
.why li{
  display:grid; grid-template-columns:3rem minmax(0,1fr);
  gap:.15rem .8rem; padding:1rem 0; border-top:1px solid var(--line);
}
.why li:last-child{ border-bottom:1px solid var(--line); }

/* Четыре причины в столбик не помещаются в экран вместе с заголовком.
   Две колонки решают это без сокращения текста. */
@media (min-width:900px){
  .why{ grid-template-columns:1fr 1fr; gap:0 3rem; }
  .why li:nth-child(3){ border-top:1px solid var(--line); }
  .why li:nth-last-child(2){ border-bottom:1px solid var(--line); }
}
.why__n{ font-size:.7rem; color:var(--c); grid-row:span 2; }
.why b{ font-size:1.12rem; letter-spacing:-.015em; }
.why span:not(.why__n){ color:var(--fg-mut); }
@media (max-width:600px){
  .why li{ grid-template-columns:1fr; }
  .why__n{ grid-row:auto; }
}

.cta{ margin-top:.6rem; }

/* ── ЛЕНТА РЕПЛИК ────────────────────────────────────────────── */

/* Реплики всплывают над строкой ввода и тают. Ощущение разговора нужно,
   а вот список сообщений, который придётся листать, — нет: он бы отобрал
   у сцены весь экран. */
.feed{
  position:fixed; z-index:22; left:var(--gut); right:var(--gut);
  bottom:calc(var(--con-h) + 4.2rem);
  display:grid; justify-items:start; gap:.4rem; pointer-events:none;
}
.msg{
  max-width:min(30rem, 82vw); padding:.55rem .95rem; border-radius:1.1rem;
  font-size:.88rem; line-height:1.45;
  border:1px solid var(--line); background:rgb(16 19 31 / .82);
  backdrop-filter:blur(14px);
  animation:msgIn .55s var(--e) both;
}
.msg--out{
  justify-self:end; background:color-mix(in oklab, var(--c) 82%, #0A0C14);
  color:#04050A; font-weight:600; border-color:transparent;
}
.msg.is-off{ animation:msgOut .45s var(--e-io) forwards; }
@keyframes msgIn{ from{ opacity:0; transform:translateY(.9rem) scale(.96) } }
@keyframes msgOut{ to{ opacity:0; transform:translateY(-.5rem) scale(.98) } }

.msg--typ{ display:flex; gap:.28rem; align-items:center; padding-block:.8rem; }
.msg--typ i{
  width:.36rem; height:.36rem; border-radius:50%; background:var(--fg-dim);
  animation:typ 1.25s var(--e-io) infinite;
}
.msg--typ i:nth-child(2){ animation-delay:.16s }
.msg--typ i:nth-child(3){ animation-delay:.32s }
@keyframes typ{ 30%{ transform:translateY(-.3rem); background:var(--c) } }

/* ── КОНСОЛЬ ─────────────────────────────────────────────────── */

.con{
  position:fixed; z-index:30; inset:auto var(--gut) 1.5rem;
  height:var(--con-h);
  display:flex; align-items:center; gap:.85rem;
  padding:0 .7rem 0 1.2rem; border-radius:1.5rem;
  border:1px solid var(--line-2);
  background:rgb(11 13 22 / .72);
  backdrop-filter:blur(26px) saturate(1.4);
  box-shadow:0 1.4rem 3.4rem -1.4rem rgb(0 0 0 / .9),
             inset 0 1px 0 rgb(255 255 255 / .07);
  transition:border-color .45s, box-shadow .45s;
}
/* Фокус подсвечивает всю панель, а не только поле: строка ввода —
   главный орган управления страницей, и она должна это показывать. */
.con:focus-within{
  border-color:color-mix(in oklab, var(--c) 60%, transparent);
  box-shadow:0 1.4rem 3.4rem -1.4rem rgb(0 0 0 / .9),
             0 0 0 1px color-mix(in oklab, var(--c) 35%, transparent),
             0 0 3rem -.4rem color-mix(in oklab, var(--c) 45%, transparent),
             inset 0 1px 0 rgb(255 255 255 / .07);
}
.no-js .con, .no-js .chips, .no-js .feed{ display:none; }

.con__mark{ color:var(--c); font-weight:700; font-size:1.05rem; flex:none; }

.con__field{ position:relative; flex:1; min-width:0; display:flex; align-items:center; }
/* Призрак и поле обязаны совпадать по шрифту, размеру и трекингу до
   пикселя — иначе подсказка «уезжает» от того, что набрано. */
.con__in, .con__ghost{
  font-family:var(--mono); font-size:clamp(.9rem, 1.6vw, 1.05rem);
  letter-spacing:.02em; line-height:1.4;
}
.con__in{ width:100%; outline:0; caret-color:var(--c); }
.con__in::placeholder{ color:var(--fg-dim); }
.con__ghost{
  position:absolute; inset:0; display:flex; align-items:center;
  pointer-events:none; color:var(--fg-dim); white-space:pre;
  overflow:hidden;
}
.con__ghost b{ font-weight:400; color:transparent; }

.con__send{
  flex:none; width:2.9rem; height:2.9rem; border-radius:50%;
  display:grid; place-items:center; color:#04050A; background:var(--c);
  transform:scale(.82); opacity:.35;
  transition:transform .45s var(--e), opacity .35s;
}
.con__send svg{ width:1.2rem; height:1.2rem; }
.con.has-text .con__send{ transform:scale(1); opacity:1; }
.con.has-text .con__send:hover{ transform:scale(1.07); }

/* ── ЧИПСЫ ───────────────────────────────────────────────────── */

.chips{
  position:fixed; z-index:29; left:var(--gut); right:var(--gut);
  bottom:calc(1.5rem + var(--con-h) + .7rem);
  display:flex; gap:.45rem; flex-wrap:nowrap;
  overflow-x:auto; scrollbar-width:none; padding-bottom:.2rem;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 1.5rem calc(100% - 1.5rem), transparent);
          mask-image:linear-gradient(90deg, transparent, #000 1.5rem calc(100% - 1.5rem), transparent);
}
.chips::-webkit-scrollbar{ display:none; }
.chip{
  flex:none; padding:.4rem .85rem; border-radius:2rem;
  font-family:var(--mono); font-size:.74rem; letter-spacing:.04em;
  border:1px solid var(--line); background:rgb(11 13 22 / .7);
  backdrop-filter:blur(14px); color:var(--fg-mut);
  transition:color .3s, border-color .3s, background .3s, transform .4s var(--e);
  animation:chipIn .5s var(--e) both calc(var(--i) * 45ms);
}
.chip:hover{ color:var(--c); border-color:color-mix(in oklab, var(--c) 55%, transparent); transform:translateY(-2px); }
.chip.is-on{ color:#04050A; background:var(--c); border-color:transparent; }
@keyframes chipIn{ from{ opacity:0; transform:translateY(.5rem) } }

/* ── АДАПТИВ ─────────────────────────────────────────────────── */

/* Подсказки про клавиши бессмысленны там, где клавиатуры нет. */
@media (hover: none), (pointer: coarse){
  .fine{ display:none; }
}

@media (max-width:720px){
  :root{ --con-h:4.1rem; --hdr-h:3.9rem; }
  /* Плотнее: на телефоне демо в сцене-двухколонке лежит под текстом, и
     каждый лишний отступ отодвигает его за пределы первого экрана. */
  .sc__eyebrow{ margin-bottom:1.1rem; }
  .sc__lede{ font-size:1rem; margin-bottom:1.4rem; }
  .tags{ margin-bottom:1.4rem; }
  .sc__t{ margin-bottom:1.2rem; }
  .con{ inset:auto .8rem 1rem; padding-left:1rem; gap:.6rem; }
  .chips{ left:.8rem; right:.8rem; bottom:calc(1rem + var(--con-h) + .6rem); }
  .feed{ left:.8rem; right:.8rem; bottom:calc(var(--con-h) + 3.6rem); }
  .sc{ padding-bottom:calc(var(--con-h) + 6rem); }
  .con__send{ width:2.5rem; height:2.5rem; }
}

/* Экран короткий и широкий (ноутбук в горизонтали, телефон боком):
   герой в 11vw перестаёт помещаться по высоте раньше, чем по ширине. */
@media (max-height:620px){
  .sc__t--hero{ font-size:clamp(2.2rem, 7vh, 4.5rem); }
  .sc__t{ font-size:clamp(2rem, 6vh, 3.6rem); }
  .facts{ display:none; }
}

/* ── ДОСТУПНОСТЬ ─────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
  }
  /* Без анимаций содержимое обязано быть на месте, а не ждать перехода,
     которого не будет. */
  .js [data-anim], .js [data-split] .w__in{ opacity:1; transform:none; }
  .grain{ animation:none; }
}
