/* ============================================================
   СТАПЕЛЬ — главный экран «Конструктор».
   Экран собирает сам себя: сетка → каркас → контент → цвет.
   Все размеры и цвета живут в переменных: их крутит твикер.
   ============================================================ */

/* ВАЖНО: эти значения обязаны совпадать с DEFAULTS в script.js.
   apply() не пишет переменную, если она равна дефолту, — иначе инлайн-стиль
   на <html> убил бы мобильные медиазапросы. Разъехались — палитра не применится. */
:root{
  --bg:#f2f0ea;
  --ink:#111113;
  --dim:#6c6a64;
  --accent:#000000;
  --wire:#dbd7cc;
  --line:#d2cfc5;
  /* лента чека: чуть светлее бумаги страницы, иначе чек сливается с фоном.
     Бумага всегда светлая, независимо от палитры сайта — это отдельный
     физический предмет (лист, талон), а не крашеная поверхность. Поэтому
     текст на ней держим на своей переменной, а не на --ink: у --ink
     тёмных палитрах он становится светлым, и текст тонет в такой же
     светлой бумаге */
  --paper:#fbfaf7;
  --paper-ink:#111113;

  --fh:'Unbounded', system-ui, sans-serif;
  --fb:'Onest', system-ui, sans-serif;
  /* моноширинный берём системный: он нужен для цен, чека и кода за знаком,
     и тянуть ради этого ещё одно семейство с сервера незачем */
  --fm:ui-monospace, 'SF Mono', 'Cascadia Mono', Consolas, 'Liberation Mono', monospace;
  --h1w:600;
  --h1size:9.1;
  --h1track:-0.035;
  --h1lh:0.92;
  --bodysize:1.05;

  --gridop:0;
  --cols:2;
  /* Ширина, шире которой содержимое не растягивается. Без неё на большом
     мониторе сайт превращается в другой макет: сетки разъезжаются, строки
     уходят за читаемые 75 знаков, выверенные отступы перестают работать.
     Поля по краям — не пустое место, а то, что держит один и тот же
     дизайн и на ноутбуке, и на 27 дюймах. */
  --maxw:1440px;
  /* Поля считаем сразу с запасом под контейнер: пока экран уже предела,
     работает обычный отступ, дальше он растёт и центрует содержимое.
     Так контейнер получают все секции разом, без обёртки в разметке. */
  --pad:max(clamp(18px, 3.4vw, 54px), calc((100% - var(--maxw)) / 2));
  --radius:12px;

  --curSize:9;
  --curRing:34;
}

*{ box-sizing:border-box; }

html,body{ margin:0; padding:0; }

html{ background:var(--bg); }

body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--fb);
  font-size:calc(var(--bodysize) * 1rem);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

body.is-cursor-custom{ cursor:none; }
body.is-cursor-custom a,
body.is-cursor-custom button{ cursor:none; }

a{ color:inherit; text-decoration:none; }

/* ---------------------------------------------- шапка */
.top{
  position:fixed; inset:0 0 auto 0; z-index:40;
  display:flex; align-items:center; justify-content:space-between;
  gap:20px; padding:20px var(--pad);
  opacity:0; transform:translateY(-12px);
  transition:opacity .7s ease, transform .7s ease;
}
.is-lit .top{ opacity:1; transform:none; }
/* Шапка уезжает вверх, пока листают вниз, и возвращается на движение
   вверх: читать она не помогает, а место у верхней кромки занимает
   постоянно. Прячем сдвигом, а не display — иначе возврат был бы
   рывком, без перехода. 110% вместо 100%: у ссылки есть подчёркивание,
   и ровно на высоту блока она не уходит целиком. */
body.is-hidetop .top{
  opacity:0; transform:translateY(-110%); pointer-events:none;
}
/* с открытым меню шапка обязана остаться: в ней кнопка закрытия */
body.is-menu .top{ opacity:1; transform:none; pointer-events:auto; }

.top__mark{ font-family:var(--fh); font-weight:600; letter-spacing:.06em; font-size:.95rem; }
.top__right{ display:flex; align-items:center; gap:22px; }
.top__place,.top__tg{
  font-size:.72rem; letter-spacing:.16em; text-transform:uppercase; color:var(--dim);
}
/* Прозрачная линия той же толщины, что подчёркивание у телеграма: без неё
   соседняя ссылка выше на четыре пикселя, и .top__right центрирует их по
   разным серединам — строка визуально ломается. */
.top__place{ padding-bottom:3px; border-bottom:1px solid transparent; }
/* значок телеграма слева от слова, тот же, что в меню */
.top__tg{ display:inline-flex; align-items:center; gap:8px; }
.top__tgi{ width:14px; height:14px; fill:currentColor; flex:none; }
/* @keyframes beat остались ради живого огонька в контактах */
@keyframes beat{
  0%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent) }
  70%{ box-shadow:0 0 0 9px color-mix(in srgb, var(--accent) 0%, transparent) }
  100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent) }
}
.top__tg{ position:relative; color:var(--ink); }
/* Подчёркивание стоит всегда, приглушённое: ссылка набрана тем же кеглем,
   что «Минск» рядом, и без линии не читалась как ссылка. Яркая линия
   доезжает по наведению — прежняя анимация, получившая покой. */
.top__tg span{
  position:relative; padding-bottom:3px;
  border-bottom:1px solid color-mix(in srgb, var(--ink) 26%, transparent);
}
.top__tg span:after{
  content:''; position:absolute; left:0; right:0; bottom:-1px; height:1px; background:var(--ink);
  transform:scaleX(0); transform-origin:left;
  transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.top__tg:hover span:after{ transform:scaleX(1); }
.top__tg[data-empty]{ color:var(--dim); }

/* ---------------------------------------------- экран */
.hero{
  position:relative;
  min-height:100svh;
  display:flex; flex-direction:column;
  padding:clamp(96px,12vh,150px) var(--pad) clamp(24px,4vh,44px);
  overflow:hidden;
}

/* ---- чертёжная сетка */
.hero__grid{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  opacity:var(--gridop);
}
.hero__cols,.hero__rows{ position:absolute; inset:0; }
.hero__cols i{
  position:absolute; top:0; bottom:0; width:1px; background:var(--line);
  transform:scaleY(var(--gin,0)); transform-origin:top;
}
.hero__rows i{
  position:absolute; left:0; right:0; height:1px; background:var(--line);
  transform:scaleX(var(--gin,0)); transform-origin:left;
}
.hero__cols i.is-key,.hero__rows i.is-key{ background:var(--accent); opacity:.28; }

/* ---- блок «каркас → контент» */
.bl{
  position:relative;
  --in:0;    /* прилёт серого блока   */
  --rev:0;   /* замена его контентом  */
}
.bl__ph{
  position:absolute; inset:0; display:block;
  background:var(--wire); border-radius:var(--radius);
  transform:scaleX(var(--in)); transform-origin:left center;
  clip-path:inset(0 0 0 calc(var(--rev) * 100%));
}
/* блик по заглушке: считается от цвета текста, поэтому работает
   и на тёмной палитре, и на светлой */
.bl__ph:after{
  content:''; position:absolute; inset:0;
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--ink) 7%, transparent));
}
.bl__c{
  position:relative; display:block;
  clip-path:inset(0 calc((1 - var(--rev)) * 100%) 0 0);
}
.bl.is-done .bl__c{ clip-path:none; }
.bl.is-done .bl__ph{ display:none; }
.tile.is-done .tile__c{ clip-path:none; }
.tile.is-done .tile__ph{ display:none; }

.hero__inner{
  position:relative; z-index:2;
  flex:1;
  display:grid; grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  gap:clamp(24px,4vw,64px); align-items:center;
}

.hero__left{ display:flex; flex-direction:column; gap:clamp(14px,2.2vh,26px); align-items:flex-start; }

/* ---- надзаголовок */
.bl--eyebrow{ min-height:20px; }
.bl--eyebrow .bl__c{ display:flex; align-items:center; gap:10px;
  font-size:.78rem; letter-spacing:.16em; text-transform:uppercase; color:var(--dim); }
/* Свечение — тот же цвет, что и сама точка, растушёванный тенью: так оно
   всегда совпадает с акцентом сайта, а не завязано на отдельный цвет.
   Держим его отдельным keyframe-ом (glow), а не внутри pulse: pulse ещё
   используют места, где своего свечения не нужно, и заводить его там
   молчаливо было бы неожиданно. */
.dot{
  width:6px; height:6px; border-radius:50%; background:var(--accent); flex:none;
  box-shadow:0 0 5px 1px color-mix(in srgb, var(--accent) 55%, transparent);
}
.is-lit .dot{ animation:pulse 2.4s ease-in-out infinite, glow 2.4s ease-in-out infinite; }

/* Нерабочее время (вне 10:00–22:00 по Москве): огоньки перестают пульсировать
   и гаснут до серого. Пульсирующая точка обещает «я тут прямо сейчас» —
   ночью это обещание было бы ложным. Класс ставит drawText() по isOpenNow() */
/* Гаснуть должны только огоньки статуса «я на связи» — они и обещают
   присутствие прямо сейчас. Метки витрины сюда не входят: балл PageSpeed
   не зависит от времени суток, и погашенная точка рядом с числом читалась
   как «замер устарел». Раньше правило било по всем .dot разом. */
body.is-closed .meta__badge .dot,
body.is-closed .ct__live{
  animation:none !important;
  background:var(--dim); box-shadow:none; opacity:.55;
}
@keyframes pulse{ 0%,100%{ transform:scale(1); opacity:1 } 50%{ transform:scale(1.5); opacity:.55 } }
@keyframes glow{
  0%,100%{ box-shadow:0 0 5px 1px color-mix(in srgb, var(--accent) 55%, transparent); }
  50%{ box-shadow:0 0 10px 3px color-mix(in srgb, var(--accent) 70%, transparent); }
}

/* ---- заголовок */
.hero__title{ margin:0; display:flex; flex-direction:column; gap:6px; width:100%; }
.bl--h1 .bl__c{
  font-family:var(--fh); font-weight:var(--h1w);
  font-size:calc(var(--h1size) * 1vw);
  line-height:var(--h1lh); letter-spacing:calc(var(--h1track) * 1em);
  white-space:nowrap;
}
.bl--h1{ align-self:stretch; }
/* Одна крупная мысль вместо строки и описания под ней: набрана заголовочным
   шрифтом, хвост приглушён. Описание про конструкторы убрано совсем —
   шесть блоков текста подряд на телефоне читались стеной */
.bl--h2 .bl__c{
  font-family:var(--fh); font-weight:600;
  font-size:clamp(21px, 3vw, 44px); line-height:1.06; letter-spacing:-.03em;
  color:var(--ink);
}
.bl--h2 em{ font-style:normal; color:var(--dim); }


/* город в надстрочнике — только там, где его нет в шапке (см. .top__place) */
.eyebrow__place{ display:none; }

/* Заголовок, оставшийся без текста, не должен занимать место: у секций
   «работы» и «контакты» имя несёт надзаголовок из меню, а пустой h2
   иначе оставлял бы за собой отступ в половину экрана.
   Проверяем через :has() вложенный узел, а не :empty на самом заголовке:
   внутри лежат служебные <i class="bl__ph"> и обёртка, из-за которых
   заголовок никогда не бывает пустым в смысле :empty */
.works__h:empty, .ct__h:empty, .svc__h:empty, .flow__h:empty,
.works__h:has(.bl__c:empty), .ct__h:has(.bl__c:empty){ display:none; }


/* ---- цифры */
.bl--meta .bl__c{ display:flex; flex-wrap:wrap; align-items:center; gap:22px; font-size:.82rem; color:var(--dim); }
.bl--meta b{ font-family:var(--fh); color:var(--ink); font-weight:600; }

/* Статус с живой точкой — только на телефоне: на мониторе то же самое
   говорит огонёк в шапке рядом с телеграмом, и два индикатора подряд
   спорили бы друг с другом. «Беру», а не «берёт»: сайт везде от первого
   лица, как и карточка в контактах */
.meta__badge{ display:none; align-items:center; gap:8px; padding:7px 14px; border-radius:999px;
  border:1px solid var(--line); font-size:.72rem; letter-spacing:.02em; }


/* ---- витрина работ: рамка браузера, внутри сменяются проекты.
   Ноутбука и стола нарочно нет: раскрывающийся ноутбук — самый заезженный
   кадр в портфолио веб-разработчиков, он ставит в один ряд со всеми.
   Голая рамка говорит то же самое и не занимает половину экрана. */
.bl--art{ width:100%; }
.bl--art .bl__c{ display:block; }

/* ============================================================
   КОРПУС МАКБУКА вокруг витрины. Только крышка: ни клавиатуры, ни
   перспективы. Раскрывающийся ноутбук с декой стоит на тысячах портфолио
   разработчиков, а одна крышка читается как «это сайт на экране»
   и не тащит за собой чужой рекламный кадр.

   Серебро узнаётся по трём вещам, и все три здесь: светлая фаска по краю
   от фрезеровки, разный тон сверху и снизу (свет потолка против отражения
   стола) и тень внутрь у основания. Без них металл — просто серая заливка.
   ============================================================ */
.mac{
  --s-hi:#f4f4f6; --s-1:#dedee1; --s-2:#c9c9ce; --s-3:#aeaeb4;
  position:relative;
  border-radius:16px 16px 5px 5px;
  padding:10px 10px 13px;
  background:linear-gradient(178deg, var(--s-1) 0%, var(--s-2) 58%, var(--s-3) 100%);
  box-shadow:
    inset 0 1px 0 var(--s-hi),
    inset 1px 0 0 rgba(255,255,255,.6),
    inset -1px 0 0 rgba(255,255,255,.6),
    inset 0 -14px 18px -14px rgba(0,0,0,.35),
    0 1px 0 rgba(0,0,0,.14);
}
/* Метка на верхнем углу крышки. Садится наполовину за корпус — так она
   читается как наклейка на железе, а не как ещё один блок вёрстки рядом.
   Живой огонёк тот же, что у статуса «беру проекты»: одна деталь, повторённая
   в двух местах, связывает экран лучше, чем две разные. */
.mac__tag{
  position:absolute; top:-11px; right:14px; z-index:4;
  display:inline-flex; align-items:center; gap:7px;
  padding:6px 12px; border-radius:999px;
  background:var(--bg); border:1px solid var(--line);
  font-size:.66rem; letter-spacing:.06em; color:var(--dim);
  white-space:nowrap;
  box-shadow:0 6px 14px -8px color-mix(in srgb, var(--ink) 45%, transparent);
}
.mac__tag b{ font-family:var(--fh); font-weight:600; color:var(--ink); letter-spacing:.02em; }
/* подпись — каждое слово с большой буквы, даже если в твикере впишут
   строчными; число рядом это правило не трогает (отдельный узел) */
.mac__tag b [data-txt]{ text-transform:capitalize; }
/* короткая вспышка на смене цифры — иначе число подменяется незаметно,
   мимо внимания, и весь смысл живой метки теряется */
.mac__tag__v{ display:inline-block; }
/* «/100» приглушено: балл читается сразу, а шкала — только если вглядеться.
   Без неё «Скорость 96» можно принять за проценты или за секунды */
.mac__tag__of{ color:var(--dim); font-weight:400; }
.mac__tag__v.is-swap{ animation:tagSwap .5s ease; }
@keyframes tagSwap{ 0%{ opacity:0; transform:translateY(3px) } 100%{ opacity:1; transform:none } }
.mac__tag .dot{ animation:pulse 2.4s ease-in-out infinite, glow 2.4s ease-in-out infinite; }
/* Вторая метка садится на противоположный угол, снизу слева — иначе
   поверх первой. Обе из одного отчёта Lighthouse: балл производительности
   и балл SEO, второй не требует отдельного инструмента для проверки. */
.mac__tag--b{ top:auto; bottom:-11px; right:auto; left:14px; }

/* на узком экране метки налезали на угол витрины — уводим их внутрь.
   Общее правило .mac__tag задаёт right:10px и для нижней метки тоже;
   если её override не вернуть right в auto, у элемента останутся заданы
   и left, и right разом — по спецификации это растягивает absolute-блок
   на всю ширину между ними, даже при display:inline-flex. Та самая рамка
   «на всю ширину» вместо компактной капсулы. */
@media (max-width:900px){
  .mac__tag{ top:-10px; right:10px; font-size:.6rem; padding:5px 10px; }
  .mac__tag--b{ top:auto; right:auto; bottom:-10px; left:10px; }
}
/* без корпуса метки садятся прямо на углы окна браузера */
.is-nomac .mac__tag{ top:-13px; right:16px; }
.is-nomac .mac__tag--b{ top:auto; right:auto; bottom:-13px; left:16px; }

/* петля и торец деки: две тонкие полосы, которые дают понять, что крышка
   на чём-то стоит, — без них она висит в воздухе */
.mac__hinge{ height:4px; margin:0 7%; border-radius:0 0 3px 3px;
  background:linear-gradient(#b9b9bf, #d3d3d8); }
.mac__base{
  position:relative; height:7px; margin:0 -3.2%;
  border-radius:0 0 9px 9px;
  background:linear-gradient(180deg, #ececee 0 1px, #cfcfd4 1px 55%, #a8a8ae 100%);
}
.mac__base:after{                    /* выемка, которой поддевают крышку */
  content:''; position:absolute; top:0; left:50%; translate:-50% 0;
  width:13%; height:3px; border-radius:0 0 5px 5px;
  background:linear-gradient(#93939a, #b7b7bc);
}
.mac__sh{
  height:24px; margin:0 5%;
  background:
    radial-gradient(28% 60% at 50% 0%, rgba(0,0,0,.2), transparent 70%),
    radial-gradient(62% 100% at 50% 0%, rgba(0,0,0,.11), transparent 74%);
}
/* Внутри корпуса витрине не нужны своя тень и толстое скругление — их работу
   делает сам корпус. Правило навешиваем через :not(), а не отменяем потом
   обратно в .is-nomac: отменённая тень возвращалась пустым значением и
   спорила с переходом, прописанным на .shw */
body:not(.is-nomac) .mac .shw{ border-radius:6px; box-shadow:none; border-color:#0b0b0c; }

/* ============================================================
   ТЕЛЕФОН ВНАХЛЁСТ НА УГОЛ ВИТРИНЫ
   Корпус набран не padding'ом, а кольцом из box-shadow: на ширине
   в 64–118px любой отступ съедал бы экран и читался белой рамкой,
   а кольцо держит толщину в пару пикселей на любом размере.
   Рамку витрины это не трогает: телефон — отдельный слой поверх
   нижнего правого угла.
   ============================================================ */
.mac__phone{
  position:absolute; right:clamp(-14px,-3%,-6px); bottom:clamp(-20px,-9%,-10px); z-index:4;
  width:clamp(64px,15vw,118px);
  border-radius:17% / 8%;
  /* тень корпуса и мягкий контактный блик от витрины под ним */
  filter:drop-shadow(0 18px 24px rgba(0,0,0,.6));
  pointer-events:none;
  /* Телефон тянется к курсору охотнее ноутбука. --tx/--ty приезжают от .mac
     по наследованию — своего слушателя заводить не нужно. Телефон и так едет
     вместе с корпусом на 22px, эти 15 идут сверху: разная скорость двух
     предметов и даёт глубину, приклеенными друг к другу они не выглядят.
     Поворот маленький нарочно — на ширине в сотню пикселей больший угол
     читается как перекос вёрстки, а не как движение. */
  transform:translate3d(calc(var(--tx,0) * 15px), calc(var(--ty,0) * 15px), 0)
            rotate(calc(var(--tx,0) * 2.2deg));
  transition:transform .9s cubic-bezier(.22,1,.36,1);
}
/* пока курсор внутри — короткий переход, иначе телефон тащится следом
   с опозданием. Класс висит на .mac, его ставит тот же слушатель,
   что двигает витрину */
.mac.is-live .mac__phone{ transition:transform .16s ease-out; }
/* На тач-экране магнита нет — курсора нет, и витрина осталась бы совсем
   неподвижной. Вместо него тихое парение в противофазе: ноутбук вверх,
   телефон вниз. Ход маленький, глаз движение почти не ловит, но картинка
   перестаёт читаться скриншотом. */
@media (hover:none){
  .mac{ animation:macFloat 7s ease-in-out infinite; }
  .mac__phone{ animation:phFloat 7s ease-in-out infinite; }
  @keyframes macFloat{ 0%,100%{ transform:translate3d(0,0,0) } 50%{ transform:translate3d(0,-7px,0) } }
  @keyframes phFloat{
    0%,100%{ transform:translate3d(0,0,0) rotate(0) }
    50%{ transform:translate3d(0,6px,0) rotate(-1.4deg) } }
}
@media (prefers-reduced-motion:reduce){
  .mac__phone{ transform:none; transition:none; animation:none; }
  .mac{ animation:none; }
}
.mac__phone__scr{
  position:relative; aspect-ratio:9/19.5; border-radius:17% / 8%; overflow:hidden; background:#000;
  /* три кольца: тёмный титан, светлая грань, снова титан по внешнему краю */
  box-shadow:
    0 0 0 1.5px #1b1b1f,
    0 0 0 2.5px #6e6e78,
    0 0 0 4px #131316;
}
/* мобильная съёмка снята ровно под 9/19.5, так что cover здесь почти
   ничего не режет — стоит страховкой, а не приёмом кадрирования */
.mac__phone__img{ width:100%; height:100%; object-fit:cover; object-position:top; display:block; }
/* Динамик-остров — единственная деталь, которая мгновенно читается
   как «это айфон», а не просто скруглённая карточка. */
.mac__phone__isl{
  position:absolute; top:3.4%; left:50%; translate:-50% 0;
  width:25%; aspect-ratio:2.9/1; border-radius:999px; background:#000; z-index:2;
}

/* переключатель в твикере снимает корпус, оставляя голое окно браузера */
.is-nomac .mac{ padding:0; background:none; box-shadow:none; }
.is-nomac .mac__hinge,
.is-nomac .mac__base,
.is-nomac .mac__sh{ display:none; }

/* Реакция на курсор — вариант «сдвиг» (В из proto-shw-tilt.html): окно едет
   за курсором, не поворачиваясь. Из восьми проб самая тихая: заметна боковым
   зрением и не перетягивает внимание с заголовка слева.

   Двигаем обёртку, а не саму витрину: метка «Lighthouse» приклеена к её углу
   и обязана ехать вместе с ней. Была бы на витрине — метка осталась бы
   висеть на месте, и стало бы видно, что она к окну не относится.
   JS пишет сюда только --tx/--ty (курсор от центра, −0.5…0.5) и класс
   is-live — что с ними делать, решает CSS. */
.mac{
  will-change:transform;
  transform:translate3d(calc(var(--tx,0) * 22px), calc(var(--ty,0) * 22px), 0);
  /* возврат в исходное — долгий и мягкий; пока курсор внутри, переход
     короткий, иначе окно тащится следом с полусекундным опозданием */
  transition:transform .9s cubic-bezier(.22,1,.36,1);
}
.mac.is-live{ transition:transform .16s ease-out; }

.shw{
  position:relative; display:flex; flex-direction:column;
  border-radius:16px; overflow:hidden; background:var(--bg);
  border:1px solid var(--line);
  box-shadow:0 26px 60px -34px color-mix(in srgb, var(--ink) 55%, transparent);
  transition:box-shadow .9s cubic-bezier(.22,1,.36,1);
}
/* тень густеет под курсором — но уже от состояния обёртки */
.mac.is-live .shw{
  transition:box-shadow .3s ease;
  box-shadow:0 40px 80px -38px color-mix(in srgb, var(--ink) 62%, transparent);
}
.shw__bar{
  flex:none; height:30px; display:flex; align-items:center; gap:6px; padding:0 12px;
  background:color-mix(in srgb, var(--wire) 55%, var(--bg));
  border-bottom:1px solid var(--line);
}
/* три кружка как в макбуке: цвет узнаётся мгновенно и без подписи
   говорит «это окно браузера». Серые читались как просто точки */
.shw__bar i{
  width:9px; height:9px; border-radius:50%; flex:none;
  box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--ink) 16%, transparent);
}
.shw__bar i:nth-child(1){ background:#ff5f57; }
.shw__bar i:nth-child(2){ background:#febc2e; }
.shw__bar i:nth-child(3){ background:#28c840; }
/* пустое поле адреса: текста в нём нет, но без него полоса перестаёт
   читаться как браузер и превращается в три случайных кружка */
.shw__url{
  margin-left:8px; flex:1; height:14px; border-radius:999px;
  background:color-mix(in srgb, var(--line) 55%, var(--bg));
}

/* Экраны лежат стопкой и проявляются друг сквозь друга: подменять один
   узел значило бы моргать пустотой между кадрами. */
.shw__view{ position:relative; aspect-ratio:16/10; background:var(--wire); overflow:hidden; }
/* Переход «расфокус» (вариант E из proto-shw.html): старый кадр уплывает
   в размытие, новый выходит из тумана. Уходящий держим видимым классом
   is-out, пока новый его накрывает, — иначе между кадрами мелькает подложка. */
.shw__p{
  position:absolute; inset:0;
  opacity:0; filter:blur(22px); transform:scale(1.08);
  transition:opacity .6s ease, filter .8s ease, transform .9s cubic-bezier(.22,1,.36,1);
}
.shw__p.is-on{ opacity:1; filter:blur(0); transform:none; z-index:2; }
.shw__p.is-out{ opacity:1; z-index:1; }
.shw__img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:top; }

/* цвет-подложка под картинку, пока она грузится: своя палитра на проект */
.shw__p[data-k="dark"] { background:#16161a; }
.shw__p[data-k="noir"] { background:#1d2124; }
.shw__p[data-k="amber"]{ background:#141210; }
.shw__p[data-k="rose"] { background:#f3e7e4; }
.shw__p[data-k="acid"] { background:#101012; }
.shw__p[data-k="bloom"]{ background:#f2ede2; }

.shw__foot{
  flex:none; display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center;
  gap:4px 14px; padding:11px 14px 12px; border-top:1px solid var(--line);
}
.shw__n{
  grid-column:1; font-family:var(--fh); font-weight:600; font-size:.9rem; letter-spacing:-.02em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.shw__d{
  grid-column:1; grid-row:2; font-size:.74rem; color:var(--dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.shw__dots{ grid-column:2; grid-row:1 / span 2; display:flex; gap:6px; }
.shw__dots button{
  width:7px; height:7px; padding:0; border:0; border-radius:50%;
  background:var(--line); cursor:pointer; transition:background .3s ease, transform .35s cubic-bezier(.22,1,.36,1);
}
.shw__dots button.is-on{ background:var(--ink); transform:scale(1.35); }
.shw__dots button:hover{ background:var(--dim); }

@media (max-width:900px){
  .shw__view{ aspect-ratio:16/9; }
}
@media (prefers-reduced-motion:reduce){
  .shw__p{ transition:opacity .2s linear; transform:none; filter:none; }
  .shw{ transition:none; }
}
 

/* ---- плитки работ (вернутся в блоке работ ниже) */
.hero__right{ position:relative; }
.tiles{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }
.tile{
  position:relative; aspect-ratio:4/3; overflow:hidden; border-radius:var(--radius);
  --in:0; --rev:0;
}
.tile__ph{
  position:absolute; inset:0; background:var(--wire);
  transform:scale(var(--in)); transform-origin:center;
}
.tile__c{
  position:absolute; inset:0; display:flex; flex-direction:column; justify-content:flex-end;
  gap:5px; padding:9px; background:var(--wire);
  clip-path:inset(calc((1 - var(--rev)) * 100%) 0 0 0);
  transition:transform .5s cubic-bezier(.22,1,.36,1);
}
.tile__bar{ height:4px; border-radius:2px; background:var(--line); }
.tile__bar--a{ background:var(--accent); opacity:.85; }
/* --dim на светлой заглушке давал контраст 3.9 — для мелких подписей мало */
.tile__n{ font-size:.62rem; letter-spacing:.1em; text-transform:uppercase;
  color:color-mix(in srgb, var(--ink) 78%, var(--wire)); }
.tile:hover .tile__c{ transform:scale(1.04); }
.tile:hover .tile__n{ color:var(--ink); }

/* ---- подвал экрана: «листай» слева, МЕНЮ строго по центру, фаза справа */
.hero__foot{ position:relative; z-index:2; margin-top:auto; padding-top:22px; }
.bl--foot .bl__c{
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
  font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; color:var(--dim);
}
.bl--foot .hero__stage{ justify-self:end; }
.hbtn--menu{ justify-self:center; }
.hero__scroll{ display:inline-flex; align-items:center; gap:9px; }
.hero__scroll i{ width:26px; height:1px; background:var(--dim); position:relative; overflow:hidden; }
.is-lit .hero__scroll i:after{
  content:''; position:absolute; inset:0; background:var(--accent);
  animation:run 1.9s cubic-bezier(.76,0,.24,1) infinite;
}
@keyframes run{ 0%{ transform:translateX(-100%) } 60%,100%{ transform:translateX(100%) } }
.hero__stage{ font-family:var(--fb); color:var(--accent); }

/* ---------------------------------------------- кнопка с растущей точкой */
/* Порт interactive-hover-button на ваниль: точка разрастается в фон,
   надпись уезжает вправо, снизу-слева выезжает второй слой в инверсии.
   Состояния idle → loading → success держим на data-state, без React. */
/* Размеры кнопки вынесены в переменные: их крутит твикер. Режим кнопок
   (data-btn) отвечает только за форму и поведение, но не за размеры —
   иначе ползунки спорили бы с режимом и не действовали */
.hbtn{
  position:relative; display:inline-flex; align-items:center; justify-content:center; gap:.6em;
  min-width:calc(var(--btnMinW, 12.5) * 1em);
  padding:calc(var(--btnPadY, 16) * 1px) calc(var(--btnPadX, 34) * 1px);
  border:calc(var(--btnBw, 1) * 1px) solid var(--ink);
  border-radius:calc(var(--btnR, 999) * 1px);
  background:var(--bg); color:var(--ink); font:inherit;
  font-size:calc(var(--btnFs, .78) * 1rem);
  letter-spacing:calc(var(--btnTrack, .14) * 1em);
  overflow:hidden; cursor:pointer; -webkit-tap-highlight-color:transparent;
  transition:min-width var(--btnSpeed, .45s) cubic-bezier(.76,0,.24,1),
             padding var(--btnSpeed, .45s) cubic-bezier(.76,0,.24,1);
}
[data-btn="wide"] .hbtn{ text-transform:uppercase; }
body.is-btn-lower .hbtn{ text-transform:none; }

/* Один приём на все кнопки, разница только в весе покоя:
   главная — в рамке, второстепенная — без неё. Наведение у них одинаковое,
   и это нарочно: так экран читается как система, а не как набор трюков. */
.hbtn--quiet{ border-color:transparent; color:var(--dim); min-width:0; }
.hbtn--quiet:hover{ color:var(--bg); }
/* МЕНЮ стоит в одной строке с «листай» и подписью фазы, поэтому её высота
   должна совпадать с ними — иначе строка визуально ломается */
.hbtn--menu{ min-width:0; padding:9px 22px; font-size:.72rem; }
[data-btn="wide"] .hbtn--menu{ padding:9px 22px; font-size:.72rem; }
/* точка в покое остаётся только у главной кнопки: три точки подряд
   на одном экране читались как сор */
/* точка невидима, но раньше занимала место в строке вместе с отступом —
   из-за этого текст уезжал вправо. Вынимаем её из потока: подпись центрируется
   честно, а заливка растёт из середины кнопки */
.hbtn--quiet .hbtn__dot,
.hbtn--menu .hbtn__dot{
  position:absolute; left:50%; top:50%; margin:-4px 0 0 -4px;
  opacity:0; transition:transform .55s cubic-bezier(.76,0,.24,1), opacity .18s ease;
}
.hbtn--quiet:hover .hbtn__dot,
.hbtn--quiet:focus-visible .hbtn__dot,
.hbtn--menu:hover .hbtn__dot,
.hbtn--menu:focus-visible .hbtn__dot{ opacity:1; }

.hbtn__dot{
  width:calc(var(--btnDot, 8) * 1px); height:calc(var(--btnDot, 8) * 1px);
  flex:none; border-radius:50%; background:var(--ink);
  transition:transform var(--btnFill, .55s) cubic-bezier(.76,0,.24,1);
}
.hbtn__label{
  transition:transform .5s cubic-bezier(.76,0,.24,1), opacity .35s ease;
}
.hbtn__over{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  color:var(--bg); transform:translateX(-4rem); opacity:0;
  transition:transform .5s cubic-bezier(.76,0,.24,1), opacity .35s ease;
}
.hbtn__st{ display:none; align-items:center; gap:.55em; white-space:nowrap; }
.hbtn__st svg{ width:1em; height:1em; flex:none; }

/* точка вырастает в фон — и по ховеру, и когда кнопка занята */
.hbtn:hover .hbtn__dot,
.hbtn:focus-visible .hbtn__dot,
.hbtn:not([data-state="idle"]) .hbtn__dot{ transform:scale(46); }

.hbtn:hover .hbtn__label,
.hbtn:focus-visible .hbtn__label,
.hbtn:not([data-state="idle"]) .hbtn__label{ transform:translateX(5rem); opacity:0; }

.hbtn:hover .hbtn__over,
.hbtn:focus-visible .hbtn__over,
.hbtn:not([data-state="idle"]) .hbtn__over{ transform:none; opacity:1; }

.hbtn[data-state="idle"]    .hbtn__st--idle{ display:inline-flex; }
.hbtn[data-state="loading"] .hbtn__st--load{ display:inline-flex; }
.hbtn[data-state="success"] .hbtn__st--done{ display:inline-flex; }
.hbtn[data-state="loading"]{ min-width:9em; padding-left:14px; padding-right:14px; }

.hbtn__spin{
  width:1em; height:1em; flex:none; border-radius:50%;
  border:2px solid color-mix(in srgb, var(--bg) 35%, transparent);
  border-top-color:var(--bg);
  animation:hspin .7s linear infinite;
}
@keyframes hspin{ to{ transform:rotate(360deg) } }

/* та же логика для главной кнопки: рамка и растущая точка убираются,
   но состояния «отправляю» и «готово» остаются — просто без инверсии */
[data-btn="line"] .hbtn{
  min-width:0; padding:10px 2px 12px; border:0; border-radius:0;
  background:transparent; text-transform:uppercase; font-size:.78rem; letter-spacing:.14em;
}
[data-btn="line"] .hbtn__dot{ display:none; }
[data-btn="line"] .hbtn:after{
  content:''; position:absolute; left:0; right:0; bottom:0; height:1px; background:var(--ink);
  transform:scaleX(0); transform-origin:center;
  transition:transform .45s cubic-bezier(.22,1,.36,1);
}
[data-btn="line"] .hbtn:hover:after,
[data-btn="line"] .hbtn:focus-visible:after,
[data-btn="line"] .hbtn:not([data-state="idle"]):after{ transform:scaleX(1); }

[data-btn="line"] .hbtn:hover .hbtn__label,
[data-btn="line"] .hbtn:focus-visible .hbtn__label{ transform:none; opacity:1; }
[data-btn="line"] .hbtn:not([data-state="idle"]) .hbtn__label{ opacity:0; }

[data-btn="line"] .hbtn__over{ color:var(--ink); background:var(--bg); transform:none; }
[data-btn="line"] .hbtn[data-state="idle"] .hbtn__over{ opacity:0; }
[data-btn="line"] .hbtn__spin{
  border-color:color-mix(in srgb, var(--ink) 28%, transparent);
  border-top-color:var(--ink);
}

@media (prefers-reduced-motion:reduce){
  .hbtn__dot,.hbtn__label,.hbtn__over{ transition:none; }
}

/* ---------------------------------------------- меню */
/* нижняя полоса остаётся поверх шторы — МЕНЮ должно оставаться нажимаемым */
.hero__foot{ z-index:80; }

/* МЕНЮ вынута из подвала первого экрана в отдельный слой: подвал уезжает
   вверх и гаснет при прокрутке, а кнопка должна оставаться на виду.
   На её месте в сетке стоит пустышка, чтобы «листай» и подпись фазы
   не съехали, а координаты кнопке считает JS — отсюда и fixed. */
.hbtn__slot{ display:block; justify-self:center; }
.hbtn--float{
  position:fixed; left:0; top:0; margin:0; z-index:85;
  transform:translate3d(var(--fx,0px), var(--fy,0px), 0);
  will-change:transform;
}
@media (prefers-reduced-motion:reduce){
  .hbtn--float{ transition:none; }
}

.menu{
  position:fixed; inset:0; z-index:60; background:var(--bg);
  clip-path:inset(100% 0 0 0); pointer-events:none;
  transition:clip-path .85s cubic-bezier(.76,0,.24,1);
}
body.is-menu .menu{ clip-path:inset(0 0 0 0); pointer-events:auto; }

.menu__inner{
  height:100%; display:grid; grid-template-columns:minmax(0,1fr) auto;
  gap:clamp(24px,5vw,80px); align-items:center;
  padding:clamp(90px,13vh,150px) var(--pad) clamp(80px,11vh,120px);
}

.menu__list{ list-style:none; margin:0; padding:0; }
.menu__list li{ overflow:hidden; }
.menu__list a{
  display:block; position:relative; width:max-content;
  font-family:var(--fh); font-weight:var(--h1w);
  /* «КАК Я РАБОТАЮ» — самый длинный пункт, на нём и подбирался размер */
  font-size:clamp(26px, 4.9vw, 74px); line-height:1.04;
  letter-spacing:calc(var(--h1track) * 1em);
  transform:translateY(108%);
  transition:transform .75s cubic-bezier(.22,1,.36,1), color .3s ease;
  transition-delay:calc(var(--i) * 65ms);
}
body.is-menu .menu__list a{ transform:none; }
.menu__list a:after{
  content:''; position:absolute; left:0; right:0; bottom:.08em; height:2px;
  background:var(--accent); transform:scaleX(0); transform-origin:right;
  transition:transform .5s cubic-bezier(.22,1,.36,1);
}
.menu__list a:hover:after{ transform:scaleX(1); transform-origin:left; }
.menu__side{
  display:flex; flex-direction:column; gap:20px; align-items:flex-start;
  opacity:0; transform:translateY(16px);
  transition:opacity .6s ease .4s, transform .6s ease .4s;
}
body.is-menu .menu__side{ opacity:1; transform:none; }
.menu__meta{
  display:flex; flex-direction:column; gap:6px;
  font-size:.72rem; letter-spacing:.14em; text-transform:uppercase; color:var(--dim);
}
.menu__meta a:hover{ color:var(--accent); }

/* Телеграм в меню: значок плюс подчёркивание. Линия стоит всегда,
   приглушённой — ссылка набрана тем же кеглем, что «Минск» над ней,
   и без неё не читалась как ссылка вовсе. Яркая доезжает по наведению.
   fill:currentColor — значок меняет цвет вместе с самой ссылкой. */
.menu__tg{ display:inline-flex; align-items:center; gap:8px; }
.menu__tgi{ width:14px; height:14px; fill:currentColor; flex:none; }
.menu__tg span{
  position:relative; padding-bottom:3px;
  border-bottom:1px solid color-mix(in srgb, currentColor 32%, transparent);
}
.menu__tg span:after{
  content:''; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:currentColor; transform:scaleX(0); transform-origin:left;
  transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.menu__tg:hover span:after{ transform:scaleX(1); }

/* ---------------------------------------------- курсор */
/* ---------------------------------------------- свет под курсором */
/* Фонарь на всю страницу. Белый свет через soft-light на бумаге пропадал —
   осветлять почти белый фон нечем. Поэтому светим сгущением --ink: на
   светлой палитре это серое пятно, на тёмной — наоборот светлое, потому
   что там --ink светлый. Один слой на все восемь палитр твикера.
   Лежит ниже шапки и меню (40 и 60) — интерфейс он не мутит. */
.lamp{
  position:fixed; inset:0; z-index:35; pointer-events:none;
  background:radial-gradient(var(--lampR,420px) var(--lampR,420px) at var(--gx,50vw) var(--gy,50vh),
    color-mix(in srgb, var(--ink) 11%, transparent) 0,
    color-mix(in srgb, var(--ink) 6%, transparent) 38%,
    color-mix(in srgb, var(--ink) 2%, transparent) 62%,
    transparent 78%);
  opacity:0; transition:opacity .6s ease;
}
body.is-lamp .lamp{ opacity:1; }

/* локальное пятно на карточках: то же, что на визитке, но общим слоем.
   Вставляет его скрипт при первом наведении — секции пересобираются
   из твикера, и заранее разложенные узлы всё равно бы пропали */
.glow{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  border-radius:inherit;
  background:radial-gradient(42% 54% at var(--mx,50%) var(--my,50%),
    color-mix(in srgb, var(--ink) 12%, transparent) 0,
    color-mix(in srgb, var(--ink) 4%, transparent) 46%,
    transparent 74%);
  opacity:0; transition:opacity .4s ease;
}
.is-glow > .glow{ opacity:1; }
/* этим карточкам relative не задан, а пятно кладём внутрь них */
.adv__c,.fmt,.wchip,.stk__i{ position:relative; }

@media (prefers-reduced-motion:reduce){
  .lamp{ display:none; }
  .glow{ transition:none; }
}

.cursor{ position:fixed; inset:0; z-index:90; pointer-events:none; display:none; }
body.is-cursor-custom .cursor{ display:block; }
.cursor > *{ position:absolute; top:0; left:0; opacity:0; }

.cursor__cross{ inset:0; width:100%; height:100%; }
.cursor__cx,.cursor__cy{ position:absolute; background:var(--accent); opacity:.34; }
.cursor__cx{ left:0; right:0; height:1px; transform:translateY(var(--my,0)); }
.cursor__cy{ top:0; bottom:0; width:1px; transform:translateX(var(--mx,0)); }

.cursor__dot{
  width:calc(var(--curSize) * 1px); height:calc(var(--curSize) * 1px);
  margin:calc(var(--curSize) * -.5px) 0 0 calc(var(--curSize) * -.5px);
  border-radius:50%; background:var(--accent);
  transform:translate(var(--mx,0), var(--my,0));
}
/* кольцо умеет обнимать кнопку: размер приезжает из JS через --rw/--rh */
.cursor__ring{
  width:var(--rw, 34px); height:var(--rh, 34px);
  margin-top:calc(var(--rh, 34px) / -2);
  margin-left:calc(var(--rw, 34px) / -2);
  border:1px solid var(--accent); border-radius:999px;
  transform:translate(var(--rx,0), var(--ry,0));
  transition:width .3s cubic-bezier(.22,1,.36,1), height .3s cubic-bezier(.22,1,.36,1),
             border-radius .3s ease, opacity .2s ease;
}
.cursor__frame{
  width:38px; height:38px; margin:-19px 0 0 -19px;
  transform:translate(var(--rx,0), var(--ry,0)) scale(var(--rs,1));
}
.cursor__frame i{ position:absolute; width:9px; height:9px; border:1px solid var(--accent); }
.cursor__frame i:nth-child(1){ top:0; left:0; border-right:0; border-bottom:0; }
.cursor__frame i:nth-child(2){ top:0; right:0; border-left:0; border-bottom:0; }
.cursor__frame i:nth-child(3){ bottom:0; left:0; border-right:0; border-top:0; }
.cursor__frame i:nth-child(4){ bottom:0; right:0; border-left:0; border-top:0; }
.cursor__lens{
  width:120px; height:120px; margin:-60px 0 0 -60px; border-radius:50%;
  background:var(--ink); mix-blend-mode:difference;
  transform:translate(var(--rx,0), var(--ry,0)) scale(var(--rs,1));
}
.cursor__read{
  margin:14px 0 0 14px; display:flex; gap:8px;
  font-family:var(--fb); font-size:10px; letter-spacing:.1em; color:var(--accent);
  transform:translate(var(--mx,0), var(--my,0));
}

[data-cursor="crosshair"] .cursor__cross,
[data-cursor="crosshair"] .cursor__dot,
[data-cursor="ruler"]     .cursor__cross,
[data-cursor="ruler"]     .cursor__read,
[data-cursor="ruler"]     .cursor__dot,
[data-cursor="dot"]       .cursor__dot,
[data-cursor="dot"]       .cursor__ring,
[data-cursor="frame"]     .cursor__dot,
[data-cursor="frame"]     .cursor__frame,
[data-cursor="lens"]      .cursor__lens,
[data-cursor="minimal"]   .cursor__dot{ opacity:1; }

/* ---------------------------------------------- телефон */
@media (max-width:900px){
    .hero__inner{ grid-template-columns:1fr; align-items:start; gap:26px; }
    .bl--h1 .bl__c{ font-size:clamp(38px, 13vw, 92px); white-space:normal; }
    /* город уходит из шапки — и тем же движением появляется в надстрочнике,
       чтобы он был ровно в одном месте экрана, а не в двух и не в нуле */
    .top__place{ display:none; }
    .eyebrow__place{ display:inline; }
    /* нижняя строка меняет содержимое: процент своего кода уходит,
       вместо него живой статус — он короче и работает лучше */
    .meta__stat{ display:none; }
    .meta__badge{ display:inline-flex; }
    .shw__n{ font-size:1rem; }
    .shw__d{ font-size:.82rem; }
    .menu__inner{ grid-template-columns:1fr; align-content:center; gap:28px; }
    /* «КАК Я РАБОТАЮ» — самый длинный пункт. На 11vw он был шире экрана:
       375×41px давали 424px ширины при 339px доступных — «Ю» срезало
       краем меню. Подбирал по нему же, с запасом в контейнер */
    .menu__list a{ font-size:clamp(22px, 8.4vw, 44px); }
    .tiles{ grid-template-columns:repeat(3,1fr); }
    body.is-cursor-custom{ cursor:auto; }
    .cursor{ display:none !important; }
}


@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
}

/* ============================================================
   Второй экран — работы. Тот же приём, что на главном: серый
   каркас прилетает, потом вытесняется содержимым. Разница только
   в спуске: здесь его запускает скролл, а не таймер загрузки.
   ============================================================ */
.works{
  position:relative; padding:clamp(80px,12vh,150px) var(--pad) clamp(70px,10vh,120px);
  border-top:1px solid var(--line);
}
/* шапка работ стоит по центру — под ней центрованный ряд фильтров,
   и заголовок, прижатый влево, разъезжался бы с ним */
.works__head{
  display:flex; flex-direction:column; gap:12px; align-items:center; text-align:center;
  margin-bottom:clamp(28px,5vh,52px);
}
.works__h{ align-self:auto; }
.bl--h2b{ align-self:stretch; }
.bl--h2b .bl__c{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(26px,4.2vw,58px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.works__lead{ max-width:52ch; }
.works__lead .bl__c{ margin:0; color:var(--dim); }

/* Раскладка работ по разбору alexwebstudio.ru:
   карточка — это обложка целиком картинкой, поверх только чип категории
   и подпись внизу. Их вордмарк и «2026» вшиты в саму картинку, вёрсткой
   не собираются. Пропорция 16/9, скругление 18, контент прижат вниз.
   Анимации (3D-наклон, стагger) не переносил — попросил не трогать. */
.wfilter{ display:flex; flex-wrap:wrap; justify-content:center; gap:10px; margin-bottom:clamp(24px,4vh,36px); }
.wchip{
  display:flex; align-items:center; gap:10px;
  border:1px solid var(--line); border-radius:14px; padding:14px 20px;
  background:var(--wire); color:var(--dim); cursor:pointer; font:inherit;
  font-size:.72rem; letter-spacing:.1em; text-transform:uppercase;
  transition:border-color .3s ease, background .3s ease, color .3s ease;
}
.wchip b{ font-weight:400; opacity:.5; }
.wchip:hover{ border-color:var(--ink); color:var(--ink); }
.wchip.is-on{ background:var(--ink); border-color:var(--ink); color:var(--bg); }
.wchip.is-on b{ opacity:.6; }

.wgrid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px; }

.wcard{
  position:relative; aspect-ratio:16/9; border-radius:18px; overflow:hidden;
  border:1px solid var(--line); background:var(--wire);
  display:flex; align-items:flex-end; padding:20px; cursor:default;
}
/* палец только там, где есть что открыть: у лендинга под обложкой пусто */
.wcard.is-open-able{ cursor:pointer; }
.wcard[hidden]{ display:none; }

/* пока нет снятых обложек — миллиметровка вместо картинки */
.wcard__shot{
  position:absolute; inset:0; z-index:0; width:100%; height:100%;
  object-fit:cover; object-position:top;
}
.wcard__empty{
  position:absolute; inset:0; z-index:0;
  display:flex; align-items:center; justify-content:center; padding:0 20px;
  background-image:linear-gradient(to right, color-mix(in srgb, var(--ink) 6%, transparent) 1px, transparent 1px),
                   linear-gradient(to bottom, color-mix(in srgb, var(--ink) 6%, transparent) 1px, transparent 1px);
  background-size:26px 26px;
  font-size:.6rem; letter-spacing:.1em; text-transform:uppercase; color:var(--dim); text-align:center;
}
/* затемнение снизу, чтобы подпись читалась поверх любой обложки */
.wcard__shade{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(to top,
    color-mix(in srgb, var(--ink) 88%, transparent),
    color-mix(in srgb, var(--ink) 10%, transparent) 60%, transparent);
}
.wcard__badge{
  position:absolute; top:14px; left:14px; z-index:2;
  font-family:var(--fb); font-size:.58rem; letter-spacing:.1em; text-transform:uppercase;
  color:var(--bg); border:1px solid color-mix(in srgb, var(--bg) 35%, transparent);
  background:color-mix(in srgb, var(--ink) 55%, transparent);
  border-radius:100px; padding:5px 10px;
}
/* Блик по стеклу: под курсором по обложке проходит светлая полоса.
   Держим её отдельным слоем поверх снимка, но под подписью — иначе засветит
   текст. Анимацию вешаем на :hover, а не гоняем всегда: семь карточек
   с вечно бегущим градиентом держали бы GPU занятым без всякой пользы. */
.wcard:after{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(105deg, transparent 38%,
    color-mix(in srgb, #fff 42%, transparent) 50%, transparent 62%);
  transform:translateX(-120%);
}
.wcard:hover:after{ animation:wscan .85s cubic-bezier(.4,0,.2,1); }
@keyframes wscan{ to{ transform:translateX(120%) } }
/* на тач-экране наведения нет: полоса проходит один раз, когда карточка
   доехала до экрана — иначе приём не увидят вообще */
@media (hover:none){
  .works.is-in .wcard:after{ animation:wscan 1s cubic-bezier(.4,0,.2,1) var(--d,0ms); }
}

/* Метка в правом верхнем углу: у части работ есть адрес в сети, у части —
   только вёрстка. Обещать одинаково нечестно, поэтому разница видна прямо
   на обложке. Точка — та же, что у статуса в шапке, с тем же свечением */
.wcard__live{
  position:absolute; top:14px; right:14px; z-index:2;
  display:inline-flex; align-items:center; gap:7px;
  font-family:var(--fb); font-size:.58rem; letter-spacing:.1em; text-transform:uppercase;
  color:color-mix(in srgb, var(--bg) 72%, transparent);
  border:1px solid color-mix(in srgb, var(--bg) 22%, transparent);
  background:color-mix(in srgb, var(--ink) 55%, transparent);
  border-radius:100px; padding:5px 11px 5px 9px;
}
/* у лендинга огонёк гасим совсем: пульс и свечение задаёт .is-lit .dot
   ключевыми кадрами, а они перебивают обычное box-shadow — тушить надо
   саму анимацию, иначе «мёртвая» метка продолжает мигать как живая */
.wcard__live .dot{
  background:color-mix(in srgb, var(--bg) 45%, transparent);
  box-shadow:none; animation:none !important;
}
/* «живой сайт» — заливка бумагой поверх кадра: на любой обложке это самое
   светлое пятно, и метку видно раньше названия. «Лендинг» остаётся
   полупрозрачным контуром, чтобы разница читалась издалека, а не по буквам */
.wcard__live.is-live{
  color:var(--ink); background:var(--bg); border-color:var(--bg); font-weight:500;
}
/* а у живой — возвращаем ровно тот же пульс, что у статуса в шапке */
.wcard__live.is-live .dot{ background:var(--accent); }
.is-lit .wcard__live.is-live .dot{
  animation:pulse 2.4s ease-in-out infinite, glow 2.4s ease-in-out infinite !important;
}

/* Подробности разъезжаются под обложкой, а не открываются окном поверх:
   grid-template-rows 0fr→1fr — единственный способ анимировать высоту,
   не зная её заранее и не считая её скриптом на каждый ресайз */
.wpanel{
  display:grid; grid-template-rows:0fr;
  transition:grid-template-rows .45s cubic-bezier(.22,1,.36,1);
}
.wrow.is-open .wpanel{ grid-template-rows:1fr; }
.wpanel__in{
  overflow:hidden; visibility:hidden;
  transition:visibility 0s .45s;
}
.wrow.is-open .wpanel__in{ visibility:visible; transition-delay:0s; }
/* полоса повторяет карточку один в один: та же ширина, то же поле в 20px,
   что и у подписи на обложке — иначе текст под кадром едет вбок */
.wpanel__b{
  border:1px solid var(--line); border-top:0;
  border-radius:0 0 18px 18px; margin:-18px 0 0; padding:38px 20px 20px;
  background:var(--wire);
}
.wpanel__tags{ display:flex; flex-wrap:wrap; gap:8px; }
.wpanel__tags span{
  border:1px solid var(--line); border-radius:100px; padding:4px 11px;
  font-size:.58rem; letter-spacing:.1em; text-transform:uppercase; color:var(--dim);
}
.wpanel__d{ margin:12px 0 0; color:var(--dim); font-size:.84rem; line-height:1.5; }
/* у лендинга кнопки нет — вместо неё строка, почему её нет */
.wpanel__note{
  margin:14px 0 0; color:var(--dim); font-size:.72rem;
  letter-spacing:.06em; text-transform:uppercase;
}
.wpanel__go{
  display:inline-flex; align-items:center; gap:9px; margin-top:16px;
  border:1px solid var(--ink); border-radius:100px; padding:11px 18px;
  font-size:.72rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink);
  transition:background .3s ease, color .3s ease;
}
.wpanel__go i{ font-style:normal; transition:transform .3s cubic-bezier(.22,1,.36,1); }
.wpanel__go:hover{ background:var(--ink); color:var(--bg); }
.wpanel__go:hover i{ transform:translate(2px,-2px); }

/* раскрытая карточка перестаёт быть скруглённой снизу — обложка и полоса
   становятся одним предметом */
.wrow.is-open .wcard{ border-radius:18px 18px 0 0; }

.wcard__meta{ position:relative; z-index:2; color:var(--bg); }
.wcard__meta .t{ font-family:var(--fh); font-weight:600; font-size:clamp(14px,1.5vw,19px); line-height:1.1; }
.wcard__meta .s{
  font-size:.66rem; margin-top:6px; letter-spacing:.04em;
  color:color-mix(in srgb, var(--bg) 72%, transparent);
}

.wmore{ display:flex; justify-content:center; margin-top:clamp(20px,3vh,32px); }

/* спуск по скроллу: те же две фазы, но на задержках вместо таймлайна */
.works .bl__ph{ transform:scaleX(0); clip-path:inset(0 0 0 0); }
.works .bl__c{ clip-path:inset(0 100% 0 0); }
.works.is-in .bl__ph{
  transform:scaleX(1);
  transition:transform .45s cubic-bezier(.76,0,.24,1) var(--d,0ms),
             clip-path .5s cubic-bezier(.76,0,.24,1) calc(var(--d,0ms) + 430ms);
  clip-path:inset(0 0 0 100%);
}
.works.is-in .bl__c{
  clip-path:inset(0 0 0 0);
  transition:clip-path .5s cubic-bezier(.76,0,.24,1) calc(var(--d,0ms) + 430ms);
}

@media (max-width:900px){
    .wgrid{ grid-template-columns:1fr; gap:10px; }
    .wcard{ aspect-ratio:16/10; padding:16px; }
    /* .58rem на телефоне даёт 9px — подписи на обложке становятся узором */
    .wcard__badge,.wcard__live{ font-size:11px; letter-spacing:.06em; }
    .wcard__badge{ top:12px; left:12px; }
    .wcard__live{ top:12px; right:12px; }
    .wpanel__b{ margin-inline:0; padding:34px 16px 16px; }
    .wpanel__tags span{ font-size:10.5px; }
}
/* уже 360px «сайты компаний» и «живой сайт» сходятся вплотную. Категория
   тут лишняя: она же стоит в фильтре прямо над сеткой и в раскрытой полосе,
   а метка «живой сайт / лендинг» больше нигде не повторяется */
@media (max-width:359px){
    .wcard__badge{ display:none; }
}

@media (prefers-reduced-motion:reduce){
  .works .bl__ph{ display:none; }
  .works .bl__c{ clip-path:none; }
}

/* ============================================================
   Третий экран — услуги. Слева форматы и допы, справа кассовый чек:
   каждая выбранная позиция допечатывается на ленте под щелчки головки.

   Чек не перерисовывается целиком: строки добавляются и убираются
   поштучно и встают в очередь печати — иначе при каждом клике заново
   печаталась бы вся лента и приём бы потерялся.
   ============================================================ */
.svc{
  position:relative; border-top:1px solid var(--line);
  padding:clamp(58px,8vh,100px) var(--pad) clamp(40px,6vh,72px);
}
.svc__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; margin-bottom:clamp(18px,3vh,36px); }
.svc__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(23px,3.3vw,46px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.svc__lead{ margin:0; color:var(--dim); max-width:54ch; }
/* надзаголовок вне главного экрана: там его роль играет .bl--eyebrow */
.eyebrow{ display:block; font-size:.72rem; letter-spacing:.18em; text-transform:uppercase; color:var(--dim); }

/* строка с надзаголовком секции и переключателем валюты — единственное
   место в .svc__head, которому нужна ширина в полный ряд, поэтому
   уходит в отдельную обёртку, а не висит прямо в flex-колонке */
.svc__eyerow{ display:flex; align-items:center; justify-content:space-between; gap:14px; width:100%; }
.cur{ display:flex; gap:2px; flex:none; background:var(--wire); border:1px solid var(--line);
  border-radius:100px; padding:2px; }
.cur button{ font:inherit; font-family:var(--fm); font-size:.68rem; letter-spacing:.04em;
  border:0; background:none; color:var(--dim); border-radius:100px; padding:5px 11px;
  cursor:pointer; transition:background .2s, color .2s; }
.cur button.is-on{ background:var(--ink); color:var(--bg); }
/* Ценники слева перекручиваются числами, а чек печатается своими строками —
   его цифры меняются разом. Мягкая смена держит оба места в одном темпе,
   иначе чек щёлкает раньше, чем слева доедет перекрутка. */
body.is-curswap .li__p,
body.is-curswap .ch__tv,
body.is-curswap #chOff,
body.is-curswap #chMonth{ animation:curFade .5s ease; }
@keyframes curFade{ 0%{ opacity:.25 } 100%{ opacity:1 } }
@media (prefers-reduced-motion:reduce){
  body.is-curswap .li__p,
  body.is-curswap .ch__tv,
  body.is-curswap #chOff,
  body.is-curswap #chMonth{ animation:none; }
}

/* ============================================================
   ИМЯ СЕКЦИИ «РАБОТЫ». Крупно набрано только оно: своего заголовка
   у секции нет, и мелкая серая метка оставляла главную секцию сайта
   без названия вообще. У остальных над содержимым стоит свой h2,
   поэтому там надзаголовок так и остаётся мелкой меткой.
   Бьём по data-txt, а не по классу: обёртка здесь особенная —
   .bl--eyebrow с плейсхолдером сборки, а не простой span.eyebrow.
   ============================================================ */
[data-txt="worksEyebrow"]{
  font-family:var(--fh); font-weight:600;
  font-size:clamp(26px, 3.4vw, 52px);
  line-height:1.02; letter-spacing:-.03em;
  color:var(--ink); text-transform:none;
}
/* разрядка и капс живут на обёртке — снимаем и там, иначе имя секции
   растащит буквы на пол-экрана */
.works__head .bl--eyebrow .bl__c{ font-size:inherit; letter-spacing:normal; text-transform:none; }

/* Хвост имени — контуром, с двумя убегающими эхо-копиями. Заливку снимаем
   прозрачным цветом, форму держит обводка. Эхо рисуем псевдоэлементами,
   а не text-shadow: тень повторяет залитую форму буквы, и вместо контуров
   получились бы сплошные плашки. Толщина в em — чтобы обводка ужималась
   вместе с буквой на телефоне, а не резала её пополам. */
.wname__o{
  position:relative; display:inline-block;
  color:transparent;
  -webkit-text-stroke:.032em var(--ink);
}
.wname__o:before,
.wname__o:after{
  content:attr(data-t); position:absolute; left:0; top:0; z-index:-1;
  color:transparent; -webkit-text-stroke:.026em var(--dim);
}
.wname__o:before{ transform:translate(.055em,.055em); opacity:.5; }
.wname__o:after { transform:translate(.11em,.11em);  opacity:.25; }

/* Обводки нет — значит, буква стала бы невидимой: возвращаем заливку */
@supports not (-webkit-text-stroke:1px black){
  .wname__o{ color:var(--ink); }
  .wname__o:before,.wname__o:after{ display:none; }
}

/* Появление секции по скроллу. Устроено «от покоя»: без is-in никаких
   правил на .rv не действует, элемент просто видим. Раньше здесь стояло
   .rv{opacity:0}, и содержимое зависело от того, доедет ли класс — а он
   не доезжал: наблюдатель ждал 22% площади секции, чего у услуг на
   телефоне не бывает. Блок пропадал целиком.

   backwards нужен ради ступенчатости: он держит стартовый кадр во время
   задержки. Если анимации в системе выключены совсем — кадр не
   применяется и элемент остаётся видимым, что и требуется. */
@keyframes rvIn{
  from{ opacity:0; transform:translateY(18px); }
}
.is-in .rv{
  animation:rvIn .7s cubic-bezier(.22,1,.36,1) var(--d,0ms) backwards;
}
@media (prefers-reduced-motion:reduce){
  .is-in .rv{ animation:none; }
}

.svc__grid{
  display:grid; grid-template-columns:minmax(0,1fr) 352px;
  gap:clamp(18px,2.6vw,48px); align-items:start;
}

/* ============================================================
   НЕОНОВАЯ РАМКА. Контур — конический градиент, который крутится вокруг
   карточки. Чтобы он остался именно рамкой, а не залил карточку целиком,
   слой маскируется двумя прямоугольниками: внешний минус внутренний.

   Угол крутим через @property: без него --ang для браузера просто строка,
   и анимация щёлкает с 0 на 360 без промежуточных кадров.
   ============================================================ */
@property --ang{ syntax:'<angle>'; initial-value:0deg; inherits:false; }

:root{
  /* запасные значения на случай, если скрипт не отработал: рабочие живут
     в DEFAULTS и пишутся в переменные из apply() */
  --neon1:#000000;
  --neon2:#000000;
  --neon3:#000000;
  --neonw:1.6px;        /* толщина рамки */
  --neonSpin:3s;        /* оборот градиента */
  --neonGlow:0;         /* яркость свечения наружу */
  --neonBlur:21px;      /* мягкость свечения */
}

/* :not(.hbtn--float) — кнопка меню совмещает оба класса: .hbtn--float
   уже даёт ей position:fixed, и он же нужен псевдоэлементам рамки, так
   что relative поверх не нужен. Без исключения два правила одинаковой
   специфичности спорят за position, побеждает то, что ниже по файлу,
   и кнопка выпадает из fixed в обычный поток — уже роняли так однажды */
.neon:not(.hbtn--float){ position:relative; }
.neon:before{
  content:''; position:absolute; inset:0; z-index:2; pointer-events:none;
  border-radius:inherit; padding:var(--neonw);
  background:conic-gradient(from var(--ang),
    transparent 0 8%, var(--neon1) 20%, var(--neon2) 34%,
    transparent 46% 58%, var(--neon3) 72%, var(--neon1) 86%, transparent 96%);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude;
  opacity:0; transition:opacity .45s ease;
}
/* свечение наружу — отдельным размытым слоем, иначе тень легла бы
   на весь блок, а не только под рамку */
.neon:after{
  content:''; position:absolute; inset:-2px; z-index:1; pointer-events:none;
  border-radius:inherit;
  background:conic-gradient(from var(--ang),
    transparent 0 8%, var(--neon1) 20%, var(--neon2) 34%,
    transparent 46% 58%, var(--neon3) 72%, var(--neon1) 86%, transparent 96%);
  filter:blur(var(--neonBlur)); opacity:0; transition:opacity .45s ease;
}
/* крутим только там, где рамка видна: анимация угла на десятке карточек
   разом греет видеокарту без всякой пользы.
   --neonDelay приезжает из JS: карточки услуг пересобираются на каждый клик,
   и без общей фазы рамка каждый раз стартовала бы заново */
.neon:hover:before,.neon.is-on:before{ opacity:1; animation:neonSpin var(--neonSpin) var(--neonDelay,0s) linear infinite; }
.neon:hover:after{ opacity:var(--neonGlow); animation:neonSpin var(--neonSpin) var(--neonDelay,0s) linear infinite; }
.neon.is-on:after{ opacity:calc(var(--neonGlow) * .65); animation:neonSpin var(--neonSpin) var(--neonDelay,0s) linear infinite; }
/* «гореть всегда» из твикера светит тем, что выбирают: карточкам форматов
   и кнопкам допов. Острова групп и подвал «Нужно другое?» из режима
   исключены нарочно — это рамки вокруг рамок, и когда горело всё разом,
   сам выбор тонул в общем сиянии */
/* «Гореть всегда» распространено на карточки форматов, допов и главные
   кнопки: бегущая рамка здесь работает не указателем, а фактурой.
   Тихие кнопки («показать ещё», «написать») класса .neon не носят вовсе —
   им рамка не идёт, они должны отступать на второй план. Острова групп
   и подвал «Нужно другое?» исключены: это были бы рамки вокруг рамок. */
body.is-neon-always .fmt:before,
body.is-neon-always .aopt:before,
body.is-neon-always .hbtn.neon:before{ opacity:1; animation:neonSpin var(--neonSpin) var(--neonDelay,0s) linear infinite; }
body.is-neon-always .fmt:after,
body.is-neon-always .aopt:after,
body.is-neon-always .hbtn.neon:after{ opacity:calc(var(--neonGlow) * .6); animation:neonSpin var(--neonSpin) var(--neonDelay,0s) linear infinite; }
@keyframes neonSpin{ to{ --ang:360deg } }

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

/* ---- форматы: ряды по типу работы, внутри ряда столбики.
   Ряд отвечает на «что вам вообще нужно», карточка — на «какой объём» */
.svc__fmts{ display:grid; gap:10px; }
/* ряд живёт в своей панели: три группы вперемешку в одном потоке
   сливались в сплошную простыню карточек */
.fgrp{
  position:relative; border:1px solid var(--line); border-radius:20px;
  background:var(--wire); padding:14px 14px 16px;
}
/* Шапка группы — кнопка: три раскрытые группы разом занимали пять экранов
   на телефоне, и до контактов приходилось листать сквозь весь прайс.
   Открытой держим ту, где стоит выбранный формат, остальные свёрнуты,
   но сообщают о себе числом форматов и ценой «от» — иначе непонятно,
   зачем на них нажимать. */
.fgrp__k{
  display:flex; align-items:center; gap:10px; width:100%;
  padding:2px 0 10px; text-align:left; color:inherit; font:inherit;
  background:none; border:0; cursor:pointer;
}
.fgrp__meta{
  margin-left:auto; margin-right:4px; font-family:var(--fm); font-size:.68rem; color:var(--dim);
  white-space:nowrap; transition:opacity .3s ease;
}
/* у раскрытой группы подпись лишняя: всё и так видно под ней */
.fgrp.is-open .fgrp__meta{ opacity:0; }


/* раскрытие через grid-template-rows: max-height пришлось бы задавать
   на глаз, и длинный ряд карточек обрезался бы */
.fgrp__body{
  display:grid; grid-template-rows:0fr;
  transition:grid-template-rows .5s cubic-bezier(.76,0,.24,1);
}
.fgrp.is-open .fgrp__body{ grid-template-rows:1fr; }
/* Обрезка нужна ради сворачивания, но метка «чаще всего» нарочно выступает
   за верх карточки на 9 пикселей — и обрезалась вместе с ним. Место ей даём
   отступом на самом ряду, а НЕ на обрезающей обёртке: отступ обёртки не
   схлопывается вместе с содержимым, и в свёрнутом виде из группы торчала
   синяя полоска. Внутри ряда он уходит в ноль вместе со всем остальным. */
.fgrp__in{ overflow:hidden; min-height:0; }
.fgrp__row{ padding-top:16px; margin-top:-4px; }
/* карточка внутри панели светлее фона панели, иначе они сливаются */
.fgrp .fmt{ background:var(--bg); }
/* колонки подбираются под ширину: в рядах разное число карточек,
   жёсткая сетка растянула бы ряд из двух на всю строку */
.fgrp__row{ display:grid; grid-template-columns:repeat(auto-fill,minmax(196px,1fr)); gap:10px; }
/* «Кому подходит»: абзац про выбранный формат под его же рядом.
   Отбит слева линией, а не рамкой: рамок в этом экране и так шесть */
.fgrp__why{
  margin:18px 0 0; padding-left:16px; border-left:2px solid var(--accent);
  color:var(--dim); font-size:.86rem; line-height:1.6; max-width:70ch;
}
.fgrp__why b{
  display:block; color:var(--ink); font-family:var(--fh); font-weight:600;
  font-size:.9rem; letter-spacing:-.02em; margin-bottom:5px;
}
.fmt{
  position:relative; text-align:left; display:flex; flex-direction:column;
  padding:18px 16px 16px; border:1px solid var(--line); border-radius:18px; background:var(--wire);
  color:inherit; font:inherit; cursor:pointer;
  transition:border-color .3s ease, background .3s ease, transform .35s cubic-bezier(.22,1,.36,1);
}
.fmt:hover{ transform:translateY(-4px); border-color:var(--ink); }
.fmt.is-on{ border-color:var(--accent); background:var(--bg); box-shadow:0 0 0 1px var(--accent) inset; }
/* «чаще всего» — единственная метка на экране услуг, и она должна ловить
   глаз первой. В обводке цвета фона она читалась как ещё одна рамка среди
   рамок, поэтому берём синий из палитры неона: он на странице больше
   нигде не залит плашкой, так что пятно получается единственным */
/* Плашка стоит на --ink, а не на --neon1: цвет рамки правится в твикере
   свободно, и стоило выставить её белой — белый текст на белом фоне
   исчезал целиком. Пара --ink/--bg контрастна при любой палитре
   по построению. */
.fmt__mark{
  position:absolute; top:-9px; left:14px; z-index:3;
  font-size:.54rem; letter-spacing:.14em; text-transform:uppercase; font-weight:600;
  padding:3px 9px; border:0; border-radius:999px;
  background:var(--ink); color:var(--bg); white-space:nowrap;
  box-shadow:0 2px 10px -3px color-mix(in srgb, var(--ink) 45%, transparent);
}
.fmt__dot{
  width:13px; height:13px; border:1px solid var(--dim); border-radius:50%;
  display:grid; place-items:center; margin-bottom:12px; transition:border-color .25s;
}
.fmt__dot:after{
  content:''; width:7px; height:7px; border-radius:50%; background:var(--accent);
  transform:scale(0); transition:transform .3s cubic-bezier(.22,1,.36,1);
}
.fmt.is-on .fmt__dot{ border-color:var(--accent); }
.fmt.is-on .fmt__dot:after{ transform:scale(1); }
.fmt__n{ font-family:var(--fh); font-weight:600; font-size:clamp(15px,1.5vw,19px); letter-spacing:-.02em; }
.fmt__d{ color:var(--dim); font-size:.8rem; margin-top:5px; flex:1; min-height:2.6em; }
.fmt__p{ font-family:var(--fh); font-weight:600; font-size:clamp(19px,2.1vw,26px); letter-spacing:-.03em; margin-top:14px; }
.fmt__p small{ font-size:.4em; font-weight:400; letter-spacing:.06em; color:var(--dim); margin-left:4px; }
.fmt__t{ font-family:var(--fm); font-size:.72rem; color:var(--dim); }

/* ---- чем форматы отличаются: свёрнуто, разворачивается по клику.
   Раскрытие идёт через grid-template-rows 0fr → 1fr: max-height пришлось бы
   задавать на глаз, и длинный текст обрезался бы */
.svc__diff{ margin-top:clamp(14px,2vh,22px); border-top:1px solid var(--line); }
/* заголовок разбора: не кнопка — сворачивать его нельзя и не нужно */
.diff__k{
  display:flex; align-items:center; padding:14px 2px;
  color:var(--ink); font-size:.8rem;
}
.diff__t{ min-width:0; }

/* Тумблер группы: состояние читается формой, а не текстом. В ряду из трёх
   групп подпись «развернуть» повторялась бы трижды и превращалась в шум,
   а форма переключателя понятна без слов. */
.tgl{
  position:relative; flex:none; width:38px; height:20px; border-radius:999px;
  background:var(--line); transition:background .35s ease;
}
.tgl:after{
  content:''; position:absolute; top:2px; left:2px; width:16px; height:16px;
  border-radius:50%; background:var(--bg);
  box-shadow:0 1px 3px color-mix(in srgb, var(--ink) 22%, transparent);
  transition:transform .4s cubic-bezier(.76,0,.24,1);
}
[aria-expanded="true"] .tgl{ background:var(--neon1); }
[aria-expanded="true"] .tgl:after{ transform:translateX(18px); }
/* наведение подсказывает, что ручка поедет, но не двигает её до конца */
button:hover > .tgl:after{ transform:translateX(3px); }
[aria-expanded="true"] button:hover > .tgl:after,
button[aria-expanded="true"]:hover > .tgl:after{ transform:translateX(15px); }

/* строки разницы приходят лесенкой, а не все разом */
.is-in .diff__i{ animation:diffIn .5s cubic-bezier(.22,1,.36,1) backwards; }
.is-in .diff__i:nth-child(2){ animation-delay:.06s; }
.is-in .diff__i:nth-child(3){ animation-delay:.12s; }
.is-in .diff__i:nth-child(4){ animation-delay:.18s; }
.is-in .diff__i:nth-child(5){ animation-delay:.24s; }
@keyframes diffIn{ from{ opacity:0; transform:translateY(10px); } }
@media (prefers-reduced-motion:reduce){
  .is-in .diff__i{ animation:none; }
}
.diff__in{ min-width:0; }
.diff__i{ padding:0 2px 14px; display:grid; grid-template-columns:minmax(120px,.32fr) minmax(0,1fr); gap:14px; }
.diff__i + .diff__i{ border-top:1px dashed var(--line); padding-top:14px; }
.diff__n{ font-family:var(--fh); font-weight:600; font-size:.9rem; }
.diff__d{ color:var(--dim); font-size:.88rem; }
@media (max-width:700px){
   .diff__i{ grid-template-columns:1fr; gap:4px; }
}
 

/* ---- допы: разделы колонками, строка в один ярус.
   Разделы во всю ширину и строки в три яруса съедали пол-экрана —
   здесь важно быстро пробежать список, а не читать его */
.svc__adds{ margin-top:clamp(16px,2.4vh,26px); }
.svc__cols{ display:grid; grid-template-columns:repeat(auto-fit,minmax(228px,1fr)); gap:8px; margin-top:10px; }
.acol{ border:1px solid var(--line); border-radius:14px; background:var(--wire); padding:12px 12px 6px; }
.acol__k{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.acol__dot{ width:5px; height:5px; border-radius:50%; background:var(--accent); }
/* каждая позиция — свой островок: сплошной список с разделителями
   читался как таблица, а тут надо тыкать, а не читать по строкам */
.aopt{
  position:relative; width:100%; text-align:left; margin-bottom:7px;
  display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:10px; align-items:baseline;
  padding:11px 12px; border:1px solid var(--line); border-radius:12px;
  background:var(--bg); color:inherit; font:inherit; cursor:pointer;
  transition:border-color .25s ease, transform .3s cubic-bezier(.22,1,.36,1);
}
/* Метка допа. Абсолютом, а не ячейкой сетки: у .aopt три колонки под
   галочку, текст и цену, и четвёртый ребёнок сломал бы раскладку.
   Справа, а не слева, как у форматов: слева стоит галочка выбора */
.aopt__mark{
  position:absolute; top:-8px; right:12px; z-index:3;
  font-size:.5rem; letter-spacing:.14em; text-transform:uppercase; font-weight:600;
  padding:2px 8px; border-radius:999px;
  background:var(--ink); color:var(--bg); white-space:nowrap;
  box-shadow:0 2px 8px -3px color-mix(in srgb, var(--ink) 45%, transparent);
}
/* ряду с меткой нужен воздух сверху, иначе она наезжает на соседа */
.aopt:has(.aopt__mark){ margin-top:12px; }
.aopt:hover{ transform:translateY(-2px); border-color:var(--ink); }
.aopt.is-on{ border-color:var(--accent); }
/* включение рисуем заливкой: круг расходится из центра коробки и только
   потом по нему проходит галочка. Смена фона рывком читалась как подмена
   картинки, а расходящийся круг — как нажатие */
.aopt__box{
  width:15px; height:15px; align-self:center; border:1px solid var(--dim); border-radius:4px;
  position:relative; overflow:hidden; transition:border-color .25s;
}
.aopt__box:before{
  content:''; position:absolute; inset:0; border-radius:50%; background:var(--accent);
  transform:scale(0); transition:transform .38s cubic-bezier(.22,1,.36,1);
}
.aopt.is-on .aopt__box{ border-color:var(--accent); }
.aopt.is-on .aopt__box:before{ transform:scale(2.2); }
.aopt__box svg{ position:absolute; inset:-1px; width:15px; height:15px; }
.aopt__box path{
  fill:none; stroke:var(--bg); stroke-width:2.6; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:26; stroke-dashoffset:26; transition:stroke-dashoffset .3s cubic-bezier(.76,0,.24,1);
}
.aopt.is-on .aopt__box path{ stroke-dashoffset:0; transition-delay:.17s; }
/* название слева, цена в своей колонке справа, пояснение мелкой строкой
   под названием: так строка занимает два яруса вместо трёх, а цена
   всё равно стоит столбиком и её не надо выискивать в тексте */
.aopt__n{ display:block; font-size:.82rem; line-height:1.3; }
.aopt__d{ display:block; color:var(--dim); font-size:.7rem; line-height:1.3; margin-top:1px; }
.aopt__p{ font-family:var(--fm); font-size:.72rem; color:var(--dim); white-space:nowrap; transition:color .25s ease; }
.aopt.is-on .aopt__p{ color:var(--ink); }
.aopt.is-on .aopt__n{ font-weight:500; }

/* ---- строка «нужно другое»: редкие задачи одной строкой,
   иначе ради каждой пришлось бы заводить свой формат */
.svc__rest{
  margin-top:clamp(18px,2.6vh,30px); padding:clamp(16px,2.2vw,26px);
  border:1px solid var(--ink); border-radius:18px;
  display:flex; gap:16px; align-items:center; flex-wrap:wrap;
}
.svc__rt{ font-family:var(--fh); font-weight:600; font-size:clamp(15px,1.7vw,21px); }
.svc__rd{ color:var(--dim); font-size:.88rem; flex:1; min-width:240px; }

/* ---- касса и чек */
.till{ position:sticky; top:20px; }
.till__bar{ display:flex; align-items:center; gap:8px; margin-bottom:9px; }
/* щель кассы, из которой лезет лента */
.till__slot{
  height:16px; border-radius:3px; background:var(--ink); position:relative; z-index:3;
  box-shadow:0 6px 14px -8px color-mix(in srgb, var(--ink) 70%, transparent);
}
.till__slot:after{
  content:''; position:absolute; left:12px; right:12px; top:6px; height:3px;
  border-radius:2px; background:color-mix(in srgb, var(--bg) 35%, transparent);
}
.check{
  position:relative; margin-top:-3px; padding:22px 22px 26px;
  background:var(--paper); color:var(--paper-ink);
  font-family:var(--fm); font-size:12.5px; line-height:1.75;
  box-shadow:0 26px 50px -34px color-mix(in srgb, var(--ink) 55%, transparent);
  /* рваный край снизу: зубцы вырезаны маской, а не нарисованы картинкой */
  -webkit-mask-image:radial-gradient(7px 7px at 7px calc(100% + 2px), transparent 6px, #000 6.5px);
  mask-image:radial-gradient(7px 7px at 7px calc(100% + 2px), transparent 6px, #000 6.5px);
  -webkit-mask-size:14px 100%; mask-size:14px 100%;
  -webkit-mask-repeat:repeat-x; mask-repeat:repeat-x;
  transform-origin:top center;
}
.check.is-tear{ animation:tear .9s cubic-bezier(.5,0,.75,0) forwards; }
@keyframes tear{
  30%{ transform:translateY(7px) rotate(.6deg) }
  100%{ transform:translateY(120%) rotate(-3deg); opacity:0 }
}
.ch__mark{ text-align:center; font-family:var(--fh); font-weight:600; font-size:13px; letter-spacing:.12em; }
.ch__sub{ text-align:center; color:var(--dim); font-size:10.5px; letter-spacing:.08em; }
.ch__rule{ border-top:1px dashed color-mix(in srgb, var(--paper-ink) 35%, transparent); margin:12px 0 10px; }
.ch__k{ font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:var(--dim); margin-bottom:4px; }

/* строка чека печатается слева направо. Ступени и время подобраны под
   звук головки: 34 шага по 26 мс — это и есть ощущение принтера */
.li{ display:flex; align-items:baseline; gap:8px; white-space:nowrap; clip-path:inset(0 100% 0 0); }
.li.is-print{ animation:print .88s steps(34) forwards; }
@keyframes print{ to{ clip-path:inset(0 0 0 0) } }
.li.is-out{ animation:unprint .32s ease forwards; }
@keyframes unprint{ to{ clip-path:inset(0 0 0 100%); height:0; margin:0; opacity:0 } }
.li__n{ overflow:hidden; text-overflow:ellipsis; }
/* точки-заполнители между названием и ценой — как в настоящем чеке */
.li__dots{ flex:1; border-bottom:1px dotted color-mix(in srgb, var(--paper-ink) 40%, transparent); transform:translateY(-3px); }
.li__p{ white-space:nowrap; }
.li--free .li__p{ color:var(--dim); }

.ch__tot{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; margin-top:6px; }
.ch__tk{ font-size:11px; letter-spacing:.2em; text-transform:uppercase; }
.ch__tv{ font-family:var(--fh); font-weight:600; font-size:24px; letter-spacing:-.02em; }
.ch__tv span{ display:inline-block; animation:chRoll .36s cubic-bezier(.22,1,.36,1); }
@keyframes chRoll{ from{ opacity:0; transform:translateY(.3em) } }
.ch__row{ display:flex; justify-content:space-between; gap:10px; color:var(--dim); font-size:11.5px; }
.ch__bars{ display:flex; gap:2px; align-items:flex-end; height:38px; margin:14px 0 6px; }
.ch__bars i{ display:block; background:var(--paper-ink); height:100%; }
.ch__bye{ text-align:center; color:var(--dim); font-size:10.5px; letter-spacing:.1em; }
.till__go{ width:100%; margin-top:16px; }
/* Сброс нарочно набран текстом, а не кнопкой с рамкой: рядом с главной
   кнопкой две одинаковые по весу — и человек не понимает, куда жать */
.till__reset{
  display:block; width:100%; margin-top:10px; padding:6px 0;
  border:0; background:none; cursor:pointer;
  font-family:var(--fb); font-size:.74rem; letter-spacing:.04em; color:var(--dim);
  text-decoration:underline; text-decoration-color:color-mix(in srgb, var(--dim) 45%, transparent);
  text-underline-offset:4px;
  transition:color .25s ease;
}
.till__reset:hover,
.till__reset:focus-visible{ color:var(--ink); }
.till__reset[hidden]{ display:none; }
.till__hint{ margin-top:10px; text-align:center; font-size:.74rem; color:var(--dim); }

/* Поля, заполненные за человека при переходе из чека: короткая вспышка,
   иначе он доезжает до формы и не замечает, что за него уже написали */
.ct__form.is-prefilled .ct__in[name="about"],
.ct__form.is-prefilled .ct__in[name="kind"]{
  animation:prefill 1.9s ease forwards;
}
@keyframes prefill{
  0%, 55%{ background:color-mix(in srgb, var(--accent) 12%, transparent);
           border-color:var(--accent); }
  100%   { background:transparent; }
}
@media (prefers-reduced-motion:reduce){
  .ct__form.is-prefilled .ct__in{ animation:none; }
}


@media (max-width:1200px){
   .svc__fmts{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
 
@media (max-width:900px){
    .svc__grid{ grid-template-columns:1fr; }
    .till{ position:static; }
    .check{ max-width:400px; }
    .fmt__d{ min-height:0; }
}

/* Ниже 760 острова групп встают в одну колонку. В двух они получали по
   165 пикселей, а карточка внутри не ужимается меньше своего самого
   длинного слова — «Многостраничный» держал 210, и карточки торчали
   за края острова. */
@media (max-width:760px){
    .svc__fmts{ grid-template-columns:1fr; }
    /* minmax(0,1fr) вместо 1fr: обычный 1fr не даёт колонке стать уже
       содержимого, поэтому длинное слово распирало сетку изнутри */
    .fgrp__row{ grid-template-columns:repeat(auto-fill,minmax(min(196px,100%),1fr)); }
    .fmt__n{ overflow-wrap:anywhere; }
}

@media (prefers-reduced-motion:reduce){
  .rv{ opacity:1; transform:none; }
  .li{ clip-path:none; }
  .li.is-print{ animation:none; }
  .check.is-tear{ animation:none; opacity:0; }
}

/* ============================================================
   Четвёртый экран — как я работаю, «гармошка».
   Четыре полосы с подписями вдоль: под курсором полоса разъезжается
   и показывает чертёж шага, соседние сжимаются. Весь процесс
   держится в одном экране — листать за ним ничего не надо.
   ============================================================ */
/* ============================================================
   ВОЗРАЖЕНИЯ
   Четыре вопроса раскрывающимися строками. Все ответы разом — стена
   текста сразу после плотного экрана цен, поэтому открыт всегда один.
   Высоту гоним grid-template-rows 0fr→1fr: она единственная умеет
   анимировать «сколько получится», не зная числа заранее.
   ============================================================ */
.obj{
  position:relative; border-top:1px solid var(--line);
  padding:clamp(52px,7vh,92px) var(--pad) clamp(52px,7vh,92px);
}
.obj__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start;
  margin-bottom:clamp(20px,3vh,38px); }
.obj__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(23px,3.3vw,46px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.obj__lead{ margin:0; color:var(--dim); max-width:60ch; }

.obj__list{ display:grid; gap:10px; max-width:var(--maxw); }
.obj__i{
  border:1px solid var(--line); border-radius:18px; background:var(--wire);
  transition:border-color .3s ease, background .3s ease;
  /* без обрезки квадратные углы заливки .obj__q торчали бы из-под
     скруглённых углов карточки в момент раскрытия */
  overflow:hidden;
}
.obj__i.is-open{ border-color:var(--ink); background:var(--bg); }
/* Вопрос заливается графитом при раскрытии — будто по нему провели курсором.
   Заливка на самой кнопке (не на .obj__i): у карточки уже есть переход
   border/background, красить и его в тот же цвет, что заливка, незачем —
   двигаться должен только сам вопрос, а не рамка вокруг него */
.obj__q{
  position:relative; width:100%; display:flex; align-items:center; justify-content:space-between; gap:18px;
  padding:clamp(16px,2.2vw,22px) clamp(16px,2.2vw,24px);
  background:none; border:0; color:inherit; font:inherit; text-align:left; cursor:pointer;
  overflow:hidden; z-index:1;
}
.obj__q:after{
  content:''; position:absolute; inset:0; background:var(--ink); z-index:-1;
  transform:scaleX(0); transform-origin:0 50%;
  transition:transform .45s cubic-bezier(.22,1,.36,1);
}
.obj__i.is-open .obj__q:after{ transform:scaleX(1); }
.obj__qt{
  font-family:var(--fh); font-weight:600; letter-spacing:-.02em;
  font-size:clamp(15px,1.5vw,19px); line-height:1.25;
  transition:color .3s ease;
}
.obj__i.is-open .obj__qt{ color:var(--bg); }
/* плюс, который становится минусом: рисуем двумя полосками, а не знаком —
   шрифтовой «+» пляшет по высоте от гарнитуры к гарнитуре */
.obj__sign{ position:relative; width:15px; height:15px; flex:none; }
.obj__sign:before,.obj__sign:after{
  content:''; position:absolute; inset:50% 0 auto 0; height:1.5px; background:var(--ink);
  transition:transform .38s cubic-bezier(.22,1,.36,1), background .3s ease;
}
.obj__sign:after{ transform:rotate(90deg); }
.obj__i.is-open .obj__sign:after{ transform:rotate(0deg); }
.obj__i.is-open .obj__sign:before,
.obj__i.is-open .obj__sign:after{ background:var(--bg); }

.obj__a{ display:grid; grid-template-rows:0fr; transition:grid-template-rows .42s cubic-bezier(.22,1,.36,1); }
.obj__i.is-open .obj__a{ grid-template-rows:1fr; }
.obj__a__in{ overflow:hidden; }
.obj__a p{
  margin:0; padding:0 clamp(16px,2.2vw,24px) clamp(18px,2.4vw,24px);
  color:var(--dim); line-height:1.62; max-width:74ch;
}

.flow{
  position:relative; border-top:1px solid var(--line);
  min-height:100svh; display:flex; flex-direction:column;
  padding:clamp(58px,8vh,100px) var(--pad) clamp(34px,5vh,64px);
}
.flow__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; margin-bottom:clamp(16px,2.6vh,32px); }
.flow__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(23px,3.3vw,46px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.flow__lead{ margin:0; color:var(--dim); max-width:56ch; }

.flow__acc{ flex:1 1 auto; min-height:0; display:flex; gap:10px; }

.flow__p{
  position:relative; flex:1 1 0; min-width:0; overflow:hidden; cursor:pointer;
  border:1px solid var(--line); border-radius:18px; background:var(--wire);
  transition:flex-grow .7s cubic-bezier(.76,0,.24,1), background .5s ease, border-color .4s ease;
}
.flow__p.is-on{ flex-grow:3.4; background:var(--bg); border-color:var(--ink); }

/* подпись стоит вдоль полосы — в сжатом виде читается только она */
.flow__side{
  position:absolute; left:0; top:0; bottom:0; width:62px; z-index:2;
  display:flex; flex-direction:column; align-items:center; justify-content:space-between;
  padding:18px 0;
}
.flow__dot{ width:9px; height:9px; border-radius:50%; background:var(--line); flex:none;
  transition:background .4s ease, transform .4s cubic-bezier(.76,0,.24,1); }
.flow__p.is-on .flow__dot{ background:var(--accent); transform:scale(1.4); }
.flow__vert{
  writing-mode:vertical-rl; transform:rotate(180deg); white-space:nowrap;
  font-family:var(--fh); font-weight:600; font-size:clamp(14px,1.5vw,19px);
  letter-spacing:calc(var(--h1track) * 1em); color:var(--dim); transition:color .4s ease;
}
.flow__p.is-on .flow__vert{ color:var(--ink); }

.flow__in{
  position:absolute; inset:0 20px 20px 62px; display:flex; flex-direction:column; justify-content:flex-end;
  opacity:0; transform:translateX(14px); pointer-events:none;
  transition:opacity .55s cubic-bezier(.22,1,.36,1), transform .55s cubic-bezier(.22,1,.36,1);
}
.flow__p.is-on .flow__in{ opacity:1; transform:none; }

/* чертёж шага: тот же язык, что на экране услуг — линии на миллиметровке */
.flow__art{ flex:1; min-height:0; position:relative; margin-bottom:14px; }
.flow__art svg{ position:absolute; inset:0; width:100%; height:100%; }
.flow__art .ln{ stroke:var(--ink); fill:none; stroke-width:1.5; }
.flow__art .dim{ stroke:var(--accent); stroke-width:1; stroke-dasharray:3 3; fill:none; }
/* линии дочерчиваются только у раскрытой полосы, длину даёт JS */
.flow__p.is-on .flow__art .ln{
  stroke-dasharray:var(--len,600); stroke-dashoffset:var(--len,600);
  animation:flowDraw .8s cubic-bezier(.76,0,.24,1) forwards var(--dl,0ms);
}
@keyframes flowDraw{ to{ stroke-dashoffset:0 } }

.flow__k{ font-size:.6rem; letter-spacing:.14em; text-transform:uppercase; color:var(--dim); }
.flow__d{ font-size:.9rem; color:var(--dim); max-width:44ch; margin-top:6px; }

/* вход секции: полосы въезжают по очереди */
.flow.is-in .flow__p{ animation:flowIn .6s cubic-bezier(.22,1,.36,1) both var(--d,0ms); }
@keyframes flowIn{ from{ opacity:0; transform:translateY(20px) } }

@media (max-width:900px){
  .flow{ min-height:0; }
  .flow__acc{ flex-direction:column; flex:none; }
  /* flex:none обязателен: базовое «flex:1 1 0» делит между полосами высоту
     контейнера, а в стопке её задаёт само содержимое — иначе все четыре
     схлопываются в пару пикселей */
  .flow__p{ display:flex; flex-direction:column; min-height:0; flex:none; }
  .flow__p.is-on{ flex:none; }

  .flow__side{
    position:relative; inset:auto; width:auto; height:auto; z-index:1;
    flex-direction:row; align-items:center; justify-content:flex-start;
    gap:12px; padding:15px 16px;
  }
  .flow__vert{ writing-mode:horizontal-tb; transform:none; }
  /* нижний кружок держал распорку в вертикальной раскладке, в строке он лишний */
  .flow__side .flow__dot:last-child{ display:none; }

  .flow__in{
    position:relative; inset:auto; transform:none;
    padding:0 16px; max-height:0; overflow:hidden;
    transition:max-height .55s cubic-bezier(.76,0,.24,1),
               opacity .4s ease,
               padding-bottom .55s cubic-bezier(.76,0,.24,1);
  }
  .flow__p.is-on .flow__in{ max-height:460px; opacity:1; padding-bottom:16px; pointer-events:auto; }
  /* чертёж без flex:1 сжимался бы в ноль: высоту в потоке задаём сами */
  .flow__art{ flex:none; height:180px; }
}

@media (prefers-reduced-motion:reduce){
  .flow.is-in .flow__p{ animation:none; }
  .flow__p.is-on .flow__art .ln{ animation:none; stroke-dashoffset:0; }
}

/* ============================================================
   Пятый экран — обо мне. Одна мысль во весь экран: слова выезжают
   снизу по очереди, под ними четыре сухих факта. Никакого
   самоописания — только конкретика.
   ============================================================ */
.ab{
  position:relative; border-top:1px solid var(--line);
  min-height:100svh; display:flex; flex-direction:column; justify-content:center;
  padding:clamp(58px,8vh,100px) var(--pad) clamp(40px,6vh,70px);
}
.ab__eyebrow{ margin-bottom:clamp(18px,3vh,34px); }

.ab__big{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  letter-spacing:calc(var(--h1track) * 1em); line-height:1.02;
  font-size:clamp(30px,6.2vw,94px);
}
/* каждое слово в своей обрезке — только так строка выезжает снизу,
   а не проявляется целиком */
.ab__big w{ display:inline-block; overflow:hidden; vertical-align:bottom; }
.ab__big w b{
  display:inline-block; font-weight:inherit;
  transform:translateY(105%);
  transition:transform .75s cubic-bezier(.22,1,.36,1) var(--d,0ms);
}
.ab.is-in .ab__big w b{ transform:none; }
.ab__big em{ font-style:normal; color:var(--dim); }

/* ============================================================
   КТО Я: фото слева, рассказ справа
   Пропорция 4:5 — вертикальный портрет: он ставится рядом с колонкой
   текста, а горизонтальный кадр заставил бы текст уехать вниз.
   Пока фото нет, слот подписан и разлинован миллиметровкой — так видно,
   что это место занято нарочно, а не картинка не догрузилась.
   ============================================================ */
.me{
  display:grid; grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);
  gap:clamp(22px,3.4vw,52px); align-items:start;
  margin-top:clamp(44px,7vh,86px);
  padding-top:clamp(34px,5vh,58px); border-top:1px solid var(--line);
}
.me__ph{
  position:relative; aspect-ratio:4/5; border-radius:20px; overflow:hidden;
  border:1px dashed var(--line); background:var(--wire);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:8px; text-align:center; padding:24px;
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--ink) 5%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--ink) 5%, transparent) 1px, transparent 1px);
  background-size:30px 30px;
}
.me__ph__t{ font-family:var(--fh); font-weight:600; font-size:1.05rem; letter-spacing:-.02em; }
.me__ph__s{ font-size:.6rem; letter-spacing:.16em; text-transform:uppercase; color:var(--dim); }

.me__lead{
  margin:0 0 clamp(16px,2.4vh,24px);
  font-family:var(--fh); font-weight:600; letter-spacing:-.035em; line-height:1.14;
  font-size:clamp(19px,2.5vw,34px);
}
.me__body p{
  margin:0 0 14px; color:var(--dim); line-height:1.62; max-width:62ch;
  opacity:0; transform:translateY(14px);
}
.ab.is-in .me__body p{
  opacity:1; transform:none;
  transition:opacity .6s ease var(--d,0ms), transform .6s cubic-bezier(.22,1,.36,1) var(--d,0ms);
}
.me__body p:last-child{ margin-bottom:0; }

@media (max-width:860px){
  /* на телефоне портрет во всю ширину съедал бы экран целиком: режем
     пропорцию до 3:2 и ставим его над текстом */
  .me{ grid-template-columns:1fr; }
  .me__ph{ aspect-ratio:3/2; }
}

.ab__facts{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:clamp(14px,2vw,28px); margin-top:clamp(30px,5vh,64px);
}
.ab__f{
  border-top:1px solid var(--line); padding-top:14px; font-size:.88rem; color:var(--dim);
  opacity:0; transform:translateY(14px);
  transition:opacity .6s ease var(--d,0ms), transform .6s cubic-bezier(.22,1,.36,1) var(--d,0ms);
}
.ab.is-in .ab__f{ opacity:1; transform:none; }
.ab__f b{ display:block; font-family:var(--fh); color:var(--ink); font-size:1.12rem; margin-bottom:4px; }

@media (max-width:900px){
    .ab{ min-height:0; padding-top:clamp(50px,8vh,80px); }
}

@media (prefers-reduced-motion:reduce){
  .ab__big w b{ transform:none; }
  .ab__f{ opacity:1; transform:none; }
}

/* ============================================================
   Шестой экран — контакты. Слева визитка, которая наклоняется
   вслед за курсором, справа короткая заявка. Пока нет сервера,
   кнопка кладёт текст заявки в буфер и зовёт в телеграм.
   ============================================================ */
.ct{
  position:relative; border-top:1px solid var(--line);
  min-height:100svh; display:flex; flex-direction:column; justify-content:center;
  padding:clamp(58px,8vh,100px) var(--pad) clamp(40px,6vh,70px);
}
.ct__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; margin-bottom:clamp(20px,3.4vh,40px); }
.ct__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(23px,3.3vw,46px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.ct__lead{ margin:0; color:var(--dim); max-width:56ch; }

.ct__grid{
  display:grid; grid-template-columns:minmax(290px,.92fr) minmax(0,1.08fr);
  gap:clamp(20px,3.4vw,58px); align-items:center;
}

/* ---- визитка. Наклон живёт на внутреннем узле, а перспектива на обёртке:
   если поставить их на один элемент, карточка дёргается на границе */
.ct__wrap{ perspective:900px; }
.ct__card{
  position:relative; overflow:hidden; border:1px solid var(--ink); border-radius:20px;
  padding:clamp(20px,2.6vw,32px); background:var(--bg); transform-style:preserve-3d;
  will-change:transform;
  --mx:50%; --my:50%;      /* где стоит курсор на карточке, пишет script.js */
  transition:transform .4s cubic-bezier(.22,1,.36,1), box-shadow .4s ease;
  box-shadow:0 18px 50px -34px color-mix(in srgb, var(--ink) 70%, transparent);
}
.ct__card:hover{ box-shadow:0 34px 70px -34px color-mix(in srgb, var(--ink) 60%, transparent); }

/* пятно света под курсором. Светит не белым, а сгущением --ink: на бумажной
   палитре белое пятно на светлом фоне не видно вообще, а лёгкая тень читается
   как наклон карточки к источнику света */
.ct__card:before{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:radial-gradient(38% 46% at var(--mx) var(--my),
    color-mix(in srgb, var(--ink) 13%, transparent) 0,
    color-mix(in srgb, var(--ink) 5%, transparent) 45%,
    transparent 72%);
  opacity:0; transition:opacity .45s ease;
}
.ct__card.is-glow:before{ opacity:1; }

/* знак бесконечности водяным знаком за текстом визитки. Контур — тот же
   путь x=cos t, y=sin t·cos t, что рисует пылью герой (dust.js), только
   не частицами, а SVG-маской: держит фигуру и по центру, и в 3D-наклоне
   карточки, без веса видео. Маска задаёт только форму, а вся «начинка» —
   обычный CSS-градиент, поэтому блик перекрашивается через var(--ink)
   вместе с темой и бежит по контуру той же техникой, что «листай» на
   первом экране (@keyframes run). */
.ct__glyph{
  position:absolute; inset:0; z-index:0;
  pointer-events:none;
}
/* оба слоя знака растянуты на всю карточку, а форму задаёт маска по центру:
   так координаты курсора --mx/--my и координаты градиентов совпадают —
   иначе свет пришлось бы пересчитывать из системы карточки в систему знака */
.ct__glyph:before,
.ct__glyph:after{
  content:''; position:absolute; inset:0;
  -webkit-mask-image:url("assets/img/ct-mark.svg");
  mask-image:url("assets/img/ct-mark.svg");
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-size:64% auto; mask-size:64% auto;
  -webkit-mask-position:center; mask-position:center;
}
.ct__glyph:before{
  background:
    linear-gradient(115deg,
      transparent 22%,
      color-mix(in srgb, var(--ink) 55%, transparent) 42%,
      var(--ink) 50%,
      color-mix(in srgb, var(--ink) 55%, transparent) 58%,
      transparent 78%) 0 0 / 260% 100%,
    color-mix(in srgb, var(--ink) 16%, transparent);
  animation:ctSheen 4.6s ease-in-out infinite;
}
.ct.is-in .ct__glyph:before{ animation-play-state:running; }

/* знак зажигается там, где курсор: тот же контур, но плотным цветом,
   проявляется только внутри пятна света. Пятно живёт в координатах
   карточки, поэтому свет и «зажжённая» часть знака всегда совпадают */
.ct__glyph:after{
  background:radial-gradient(24% 30% at var(--mx) var(--my),
    var(--ink) 0,
    color-mix(in srgb, var(--ink) 50%, transparent) 42%,
    transparent 70%);
  opacity:0; transition:opacity .4s ease;
}
.ct__card.is-glow .ct__glyph:after{ opacity:1; }

@keyframes ctSheen{
  0%,14%{ background-position:-70% 0, 0 0; }
  62%,100%{ background-position:170% 0, 0 0; }
}
@media (prefers-reduced-motion:reduce){
  .ct__glyph:before{ animation:none; background-position:50% 0, 0 0; }
}

.ct__mark,.ct__name,.ct__role,.ct__rows{ position:relative; z-index:2; }

.ct__mark{ font-family:var(--fh); font-weight:600; letter-spacing:.06em; font-size:.92rem; }
.ct__name{
  font-family:var(--fh); font-weight:600; letter-spacing:calc(var(--h1track) * 1em);
  font-size:clamp(24px,3vw,40px); line-height:1.05; margin:clamp(24px,4.5vh,50px) 0 5px;
}
.ct__role{ color:var(--dim); font-size:.9rem; }
.ct__rows{ margin-top:clamp(20px,3.6vh,38px); border-top:1px solid var(--line); }
.ct__row{
  display:flex; justify-content:space-between; align-items:center; gap:14px;
  padding:11px 0; border-bottom:1px solid var(--line); font-size:.84rem; color:var(--dim);
}
.ct__row b{ font-family:var(--fh); font-weight:600; color:var(--ink); text-align:right; }
.ct__live{
  display:inline-block; width:7px; height:7px; border-radius:50%; background:var(--accent);
  margin-right:8px; vertical-align:1px;
  box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent);
}
.ct.is-in .ct__live{ animation:beat 2.6s ease-out infinite; }

/* ---- заявка */
.ct__form{ display:grid; gap:10px; align-content:center; }
.ct__in{
  width:100%; font:inherit; color:var(--ink); background:none;
  border:1px solid var(--line); border-radius:var(--radius); padding:15px 16px; outline:none;
  transition:border-color .3s ease;
}
.ct__in:focus{ border-color:var(--ink); }
.ct__in::placeholder{ color:var(--dim); }
select.ct__in{ cursor:pointer; }
/* Раскрытый список рисует система, на своей подложке — сайт до неё не
   достаёт. Пункты наследовали --ink, а он на тёмной палитре почти белый,
   и весь список пропадал на белом фоне системного меню. Цвета здесь
   намеренно не из палитры: они про меню операционной системы. */
select.ct__in option{ color:#111113; background:#ffffff; }
@media (prefers-color-scheme:dark){
  select.ct__in option{ color:#f2f0ea; background:#1c1c1f; }
}

/* Слово «Телеграм» вшито в поле слева — рамка теперь на обёртке, а не
   на самом инпуте, иначе при фокусе подсвечивались бы только цифры поля,
   а не всё вместе, и рамка внутри рамки задваивалась бы */
.ct__pfx{
  display:flex; align-items:center; gap:9px;
  border:1px solid var(--line); border-radius:var(--radius);
  padding-left:16px; transition:border-color .3s ease;
}
.ct__pfx:focus-within{ border-color:var(--ink); }
.ct__pfx__w{ flex:none; color:var(--dim); font-size:.92rem; white-space:nowrap; }
.ct__pfx .ct__in{ flex:1; min-width:0; border:0; padding-left:0; }

/* Согласие на обработку. Квадратик рисуем сами: системный чекбокс на светлом
   фоне выглядит чужеродно рядом с остальной формой */
.ct__agree{ display:flex; align-items:flex-start; gap:10px; cursor:pointer;
  font-size:.78rem; color:var(--dim); line-height:1.45; margin-top:2px; }
.ct__agree input{
  appearance:none; flex:none; width:17px; height:17px; margin-top:1px;
  border:1px solid var(--line); border-radius:5px; background:none; cursor:pointer;
  display:grid; place-items:center;
  transition:border-color .25s ease, background .25s ease;
}
.ct__agree input:checked{ background:var(--ink); border-color:var(--ink); }
/* Галочка идёт маской, а не картинкой: в data:URI нельзя подставить
   переменную, и цвет приходилось зашивать числом. Залитый квадрат берёт
   --ink, и на тёмной палитре, где --ink светлый, светлая галочка на нём
   пропадала. Маска красится обычным фоном — значит цветом страницы,
   каким бы он ни стал после правки палитры в твикере. */
.ct__agree input:checked:before{
  content:''; width:12px; height:12px; background:var(--bg);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/12px no-repeat;
          mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/12px no-repeat;
}
.ct__agree input:focus-visible{ outline:2px solid var(--ink); outline-offset:2px; }
.ct__agree a{ color:var(--ink); }
.ct__send{ justify-self:start; margin-top:6px; }
/* Строка про нерабочее время — только когда оно правда нерабочее.
   Форма при этом не блокируется: гаснет огонёк, а отправить можно всегда */
.ct__late{ display:none; margin:6px 0 0; font-size:.78rem; color:var(--dim); }
body.is-closed .ct__late{ display:block; }

.ct__out{ margin:2px 0 0; font-size:.84rem; color:var(--dim); min-height:2.6em; }
.ct__out b{ color:var(--ink); font-weight:500; }

/* ---- подвал: мелкая строка сверху, гигантский знак снизу */
/* Подвал держим почти во весь экран — он читается как последний слайд,
   а не как обрезок под контактами. «Почти», а не 100vh: остаток экрана
   подсказывает, что страница закончилась, и не крадёт места у знака. */
/* Высоты в 92vh здесь больше нет: вместе с margin-top:auto у знака она
   отбивала почти целый пустой экран между контактами и подписью.
   Подвал теперь ровно по содержимому, знак идёт сразу за последней секцией. */
.foot{
  display:flex; flex-direction:column;
  border-top:1px solid var(--line); padding:22px var(--pad) 0; overflow:hidden;
}
.foot__row{
  display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;
  font-size:.72rem; letter-spacing:.14em; text-transform:uppercase; color:var(--dim);
}
.foot__note{ margin:0; letter-spacing:0; text-transform:none; font-size:.8rem; max-width:52ch; }
/* справа оставлено место под шестерёнку твикера — иначе она села бы на «Минск» */
.foot__right{ display:flex; gap:20px; align-items:center; padding-right:52px; }
.foot__right a[data-empty]{ color:var(--dim); }

/* Знак во всю ширину. Размер подбирает JS: на глаз выставленный vw
   разъезжается, стоит поменять текст знака или шрифт в твикере. */
/* межстрочное .78 годилось для одной строки, а на двух буквы налезали
   друг на друга — при таких кеглях даже пара процентов заметна */
.foot__big{
  display:block; --markGap:clamp(18px,3vh,40px); margin-top:var(--markGap); margin-bottom:clamp(8px,1.6vh,22px);
  line-height:.86; overflow:hidden; color:var(--ink);
}
/* исходный знак остаётся в разметке источником текста для твикера,
   а на экране его заменяют строки .foot__ln */
.foot__big > span{ position:absolute; visibility:hidden; pointer-events:none; }

.foot__ln{
  /* блок с width:fit-content — каждая строка на своей строке,
     но ширина равна тексту, иначе замерить её нельзя */
  display:block; width:fit-content;
  font-family:var(--fh); font-weight:var(--h1w);
  letter-spacing:-.045em; white-space:nowrap;
  transform:translateY(16%);
  transition:transform 1s cubic-bezier(.22,1,.36,1) var(--d,0ms);
}
.foot.is-in .foot__ln{ transform:translateY(0); }

@media (prefers-reduced-motion:reduce){
  .foot__ln{ transform:translateY(0); }
}

@media (max-width:900px){
    .ct{ min-height:0; }
    .ct__grid{ grid-template-columns:1fr; gap:24px; }
    .foot{ flex-direction:column; align-items:flex-start; }
}

@media (prefers-reduced-motion:reduce){
  .ct__card{ transform:none !important; }
}

/* ============================================================
   Преимущества — шесть плиток, три сверху и три снизу.
   Иконки не картинки, а линии: вычерчиваются штрихом, когда
   блок доезжает до экрана — тем же приёмом, что чертежи услуг.
   ============================================================ */
/* Верхняя линия у второго экрана вернулась: она отбивает первый экран
   от остальной страницы и задаёт горизонт, от которого дальше идут все
   секции. Раньше её убирали, когда второй экран наезжал на первый —
   теперь наезда нет, и шва тоже. */
.adv{
  border-top:1px solid var(--line);
  position:relative;
  padding:clamp(58px,8vh,100px) var(--pad) clamp(40px,6vh,80px);
}
.adv__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; margin-bottom:clamp(20px,3.4vh,38px); }
.adv__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(23px,3.3vw,46px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.adv__lead{ margin:0; color:var(--dim); max-width:56ch; }

.adv__grid{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; }
.adv__c{
  border:1px solid var(--line); border-radius:18px; background:var(--wire);
  padding:clamp(18px,2.4vw,28px);
  opacity:0; transform:translateY(18px);
  transition:opacity .6s ease var(--d,0ms), transform .7s cubic-bezier(.22,1,.36,1) var(--d,0ms),
             border-color .35s ease, background .35s ease;
}
.adv.is-in .adv__c{ opacity:1; transform:none; }
.adv__c:hover{ border-color:var(--ink); background:var(--bg); }
/* «выдавленная» плитка: своего цвета у неё нет, объём делают две тени —
   тёмная снизу-справа и светлая сверху-слева. Оттенки считаем от палитры,
   иначе твикер поменял бы фон, а плитка осталась бы бежевой */
.adv__i{
  width:62px; height:62px; margin-bottom:16px; border-radius:17px;
  display:grid; place-items:center;
  background:var(--bg);
  box-shadow:5px 5px 12px -4px color-mix(in srgb, var(--ink) 17%, transparent),
             -5px -5px 12px -4px color-mix(in srgb, var(--bg) 25%, #fff),
             inset 0 0 0 1px color-mix(in srgb, var(--bg) 55%, #fff);
  transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.adv__c:hover .adv__i{ transform:translateY(-2px) rotate(-3deg); }
.adv__i svg{ width:56%; height:56%; overflow:visible; }
.adv__i path,.adv__i rect,.adv__i circle,.adv__i line{
  fill:none; stroke:var(--ink); stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:var(--len,140); stroke-dashoffset:var(--len,140);
  transition:stroke-dashoffset .85s cubic-bezier(.76,0,.24,1) var(--dl,0ms);
}
.adv.is-in .adv__i path,.adv.is-in .adv__i rect,
.adv.is-in .adv__i circle,.adv.is-in .adv__i line{ stroke-dashoffset:0; }
.adv__t{ font-family:var(--fh); font-weight:600; font-size:clamp(15px,1.6vw,20px); letter-spacing:-.02em; }
.adv__d{ color:var(--dim); font-size:.86rem; margin-top:8px; }

@media (max-width:1100px){
   .adv__grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
 
@media (max-width:700px){
   .adv__grid{ grid-template-columns:1fr; }
}
 

/* ============================================================
   Принципы — четыре карточки с крупными номерами.
   ============================================================ */
.pr{
  position:relative; border-top:1px solid var(--line);
  padding:clamp(58px,8vh,100px) var(--pad) clamp(40px,6vh,80px);
}
/* Шапка слева, талон справа. Колонка шапки уже колонки талона: заголовок
   и три строки лида не должны растягиваться на пол-экрана, а условиям
   нужна ширина, чтобы обещание влезало в строку целиком */
.pr__grid{
  display:grid; grid-template-columns:minmax(0,.78fr) minmax(0,1fr);
  gap:clamp(24px,4vw,64px); align-items:start;
}
.pr__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; }
.pr__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(23px,3.3vw,46px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.pr__lead{ margin:0; color:var(--dim); max-width:56ch; }

/* ============================================================
   ГАРАНТИЙНЫЙ ТАЛОН. Обещания весят больше, когда выданы документом,
   а не написаны плиткой. Отсюда рамка в цвет текста, номер, пунктирные
   линии отрыва и подпись внизу. Бумага светлее фона страницы (--paper),
   иначе талон сливается с ней и перестаёт читаться как отдельный лист.
   ============================================================ */
.war{
  position:relative;
  border:1px solid var(--ink); border-radius:18px; background:var(--paper);
  color:var(--paper-ink);
  overflow:hidden;
}

/* ПЕЧАТЬ. Прилетает сверху крупной и садится на место — так читается удар,
   а не проявление. Наклон нарочный: ровно поставленный штамп выглядит
   напечатанным вместе с бланком, а не пришлёпнутым поверх.
   Полупрозрачность и лёгкий поворот делают её краской, а не текстом.

   Якорь — сам подвал (.war__foot), а не весь талон: подвал стоит на своей
   фиксированной высоте всегда, а список условий выше — нет. На телефоне
   «Поддержка — помогаю разобраться и остаюсь на связи» переносится
   в две строки (см. .war__i ниже), список становится выше почти на целую
   строку, и штамп, привязанный к нижнему краю ВСЕГО талона, съезжал прямо
   на этот перенос. Подвал такой перестройке не подвержен — там текст
   всегда в одну строку — так что штамп, привязанный к нему, ни на что
   не наедет, сколько бы строк ни занял список выше. */
.war__foot{ position:relative; }
.war__stamp{
  position:absolute; right:clamp(16px,2.4vw,30px); bottom:100%; margin-bottom:14px; z-index:3;
  font-family:var(--fh); font-weight:800; text-transform:uppercase; letter-spacing:.08em;
  font-size:clamp(12px,1.2vw,15px); color:var(--paper-ink);
  border:2px solid var(--paper-ink); border-radius:7px; padding:8px 14px;
  transform:rotate(-13deg) scale(2.6); opacity:0;
  pointer-events:none;
}
.pr.is-in .war__stamp{ animation:warStamp .45s cubic-bezier(.3,1.4,.5,1) .75s forwards; }
@keyframes warStamp{ to{ transform:rotate(-13deg) scale(1); opacity:.82 } }

@media (prefers-reduced-motion:reduce){
  .pr.is-in .war__stamp{ animation:none; transform:rotate(-13deg); opacity:.82; }
}
.war__top,
.war__foot{ display:flex; align-items:center; justify-content:space-between; gap:16px; }
.war__top{ padding:clamp(14px,2vw,20px) clamp(18px,2.6vw,30px); border-bottom:1px dashed var(--line); }
.war__k{
  font-family:var(--fh); font-weight:800; text-transform:uppercase;
  letter-spacing:.12em; font-size:clamp(11px,1.1vw,13px);
}
/* номер моноширинным: так он читается как выданный документ, а не как
   часть вёрстки — цифры стоят в колонку и не пляшут по ширине */
.war__no{ font-family:var(--fm); color:var(--dim); font-size:clamp(10px,1vw,12px); }

/* Условия идут одной колонкой: талон теперь занимает половину ширины,
   и в двух колонках обещание рвалось на три слова в строке */
.war__body{
  display:grid; gap:2px;
  padding:clamp(14px,2vw,22px) clamp(18px,2.6vw,30px);
}
/* пунктирная линия между условиями — как графы в бланке */
.war__i + .war__i{ border-top:1px dashed color-mix(in srgb, var(--line) 75%, transparent); }
.war__i{
  display:flex; gap:12px; align-items:baseline;
  padding:clamp(8px,1.2vh,12px) 0;
  opacity:0; transform:translateY(14px);
  transition:opacity .55s ease var(--d,0ms), transform .6s cubic-bezier(.22,1,.36,1) var(--d,0ms);
}
.pr.is-in .war__i{ opacity:1; transform:none; }
/* точка вместо номера: место в списке отмечено, а порядок не назначен */
.war__i:before{
  content:''; flex:none; width:6px; height:6px; border-radius:50%;
  background:var(--ink); transform:translateY(-2px);
}
.war__t{ font-family:var(--fh); font-weight:600; font-size:clamp(14px,1.4vw,18px); flex:none; }
.war__t:after{ content:' —'; color:var(--dim); font-weight:400; }
.war__d{ color:var(--dim); font-size:clamp(13px,1.1vw,15.5px); line-height:1.4; }

.war__foot{
  padding:clamp(12px,1.8vw,18px) clamp(18px,2.6vw,30px);
  border-top:1px dashed var(--line); color:var(--dim); font-size:clamp(11px,1vw,13px);
}
.war__sig{
  font-family:var(--fh); font-weight:400; font-style:italic;
  color:var(--paper-ink); font-size:clamp(13px,1.3vw,17px); letter-spacing:-.02em;
}

@media (max-width:900px){
   /* на узком экране шапка возвращается над талоном: в двух колонках
      каждой досталось бы по 170 пикселей */
   .pr__grid{ grid-template-columns:1fr; gap:clamp(18px,3vh,30px); }
}
@media (max-width:760px){
   /* графа и обещание встают в две строки: в одну строку
      «Поддержка — помогаю разобраться…» ужимало текст до трёх слов в ряд */
   .war__i{ flex-wrap:wrap; }
   .war__d{ width:100%; padding-left:18px; }

   /* Плавающий штамп на телефоне снимаем совсем: описание здесь тянется
      на всю ширину строки, и сбоку для него попросту нет свободного места —
      куда ни поставь по высоте, он ляжет поверх какой-нибудь из строк.
      Вместо этого штамп встаёт обычным блоком в потоке, отдельной строкой
      подвала — это гарантированно свободное место при любом числе строк
      выше, а не подгонка под конкретную высоту. */
   .war__foot{ flex-wrap:wrap; row-gap:10px; }
   .war__stamp{
     position:static; order:3; flex-basis:100%;
     margin:0 auto 0 0; transform:rotate(-4deg) scale(1); opacity:.82;
     animation:none !important;
   }
}

@media (prefers-reduced-motion:reduce){
  .adv__c,.war__i{ opacity:1; transform:none; }
  .adv__i path,.adv__i rect,.adv__i circle,.adv__i line{ stroke-dashoffset:0; }
}

/* ============================================================
   Стек внутри «обо мне»: крупный список слева, справа — зачем
   это нужно заказчику. Технологии сами по себе ему ничего не
   говорят, поэтому каждая строка объясняет выгоду.
   ============================================================ */
.stk{
  border-top:1px solid var(--line); margin-top:clamp(40px,7vh,90px);
  padding-top:clamp(34px,5vh,60px);
}
.stk__head{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; margin-bottom:clamp(20px,3.4vh,38px); }
.stk__h{
  margin:0; font-family:var(--fh); font-weight:var(--h1w);
  font-size:clamp(21px,2.9vw,40px); line-height:1.04; letter-spacing:calc(var(--h1track) * 1em);
}
.stk__grid{
  display:grid; grid-template-columns:minmax(0,1.05fr) minmax(270px,.85fr);
  gap:clamp(22px,4vw,58px); align-items:center;
}
.stk__col{ display:flex; flex-wrap:wrap; gap:0 clamp(16px,2.2vw,32px); }
.stk__i{
  font-family:var(--fh); font-weight:600; letter-spacing:calc(var(--h1track) * 1em); line-height:1.14;
  font-size:clamp(19px,2.9vw,42px); color:var(--dim); cursor:default;
  opacity:0; transform:translateY(12px);
  transition:opacity .5s ease var(--d,0ms), transform .55s cubic-bezier(.22,1,.36,1) var(--d,0ms), color .3s ease;
}
.ab.is-in .stk__i{ opacity:1; transform:none; }
.stk__i:hover,.stk__i.is-on{ color:var(--ink); }
/* контуром — то, что берётся в работу, но в портфолио пока нет */
.stk__i[data-soon]{ -webkit-text-stroke:1px var(--dim); color:transparent; }
.stk__i[data-soon]:hover,.stk__i[data-soon].is-on{ -webkit-text-stroke-color:var(--ink); }

.stk__side{ border-left:1px solid var(--line); padding-left:clamp(16px,2.2vw,28px); min-height:8.5em; }
.stk__k{ font-size:.62rem; letter-spacing:.16em; text-transform:uppercase; color:var(--dim); }
.stk__t{ font-family:var(--fh); font-weight:600; font-size:clamp(17px,2.1vw,27px); margin-top:8px; }
.stk__d{ color:var(--dim); margin-top:10px; max-width:36ch; }

@media (max-width:900px){
    .stk__grid{ grid-template-columns:1fr; }
    .stk__side{ border-left:0; border-top:1px solid var(--line); padding:16px 0 0; min-height:0; }
}

@media (prefers-reduced-motion:reduce){ .stk__i{ opacity:1; transform:none; } }

/* ============================================================
   Меню: пункт собран из букв в два слоя. Верхний уезжает вверх,
   нижний приходит снизу, каждая буква со своей задержкой.
   ============================================================ */
.menu__list a.flip{
  /* line-height ниже единицы нужен приёму, иначе буква не успевает уйти
     за край; зато строки липнут друг к другу — разводим их отступом */
  position:relative; display:block; overflow:hidden; white-space:nowrap; line-height:.8;
  padding:.06em 0;
}
.menu__list a.flip i{ display:flex; font-style:normal; }
.menu__list a.flip .flip__b{ position:absolute; inset:0; }
.menu__list a.flip span{
  display:inline-block;
  transition:transform .3s cubic-bezier(.76,0,.24,1);
}
.menu__list a.flip .flip__b span{ transform:translateY(110%); }
.menu__list a.flip:hover .flip__a span{ transform:translateY(-110%); }
.menu__list a.flip:hover .flip__b span{ transform:translateY(0); }
.menu__list a[data-empty]{ color:var(--dim); }

@media (prefers-reduced-motion:reduce){
  .menu__list a.flip .flip__b{ display:none; }
  .menu__list a.flip span{ transition:none; }
}

/* Здесь жила отдельная «искра» для главных кнопок — свой конический
   градиент, не совпадавший с рамкой .neon у карточек услуг и кнопки
   меню. Два похожих, но разных приёма на одном экране читались как
   недоделка, поэтому кнопки переведены на общий .neon, а искра убрана. */

/* ============================================================
   Переход между экранами — «проявление». Выбран в лаборатории
   (папка lab/, порт 8141) из двадцати вариантов: длина 105% экрана,
   сила 175%, плавность «с выбегом».

   Экран набирает непрозрачность, пока въезжает снизу. Долю въезда
   пишет в --tin скрипт, дальше всё считает CSS. Сила 175% значит,
   что первую половину пути секция ещё полностью прозрачна, а потом
   быстро проявляется — иначе проявление размазывается на весь экран
   и читается как «сайт подтормаживает».
   ============================================================ */
:root{ --tamp:1.75; }

main > section{
  position:relative; z-index:1; background:var(--bg);
}

.foot{ position:relative; z-index:1; background:var(--bg); }

/* ПРАВИЛО, КОТОРОЕ ЗДЕСЬ ГЛАВНОЕ: покой — это видимое состояние.
   Раньше секции прятались (opacity:0), а показывались по событию — и
   дважды выходило так, что показать не удавалось и содержимое пропадало
   насовсем: сперва из-за недоставленного scroll, потом из-за предела
   композитного слоя в iOS. Красота проявления столько не стоит.

   Поэтому теперь анимация идёт только «на въезд» и только вперёд:
   класс is-full запускает её от смещённого и прозрачного состояния к
   обычному. Нет класса, не отработал скрипт, не поддержан наблюдатель —
   секция просто стоит на месте, видимая. Потерять содержимое нечем. */
@keyframes screenIn{
  from{ transform:translateY(calc(var(--dz,0) * 1px)); }
}
/* Ни opacity в кадрах, ни fill-mode — нарочно. С заливкой both элемент до
   старта анимации показывается кадром from, и если анимации не идут (фон,
   экономия батареи, системная настройка), он застревает прозрачным. Здесь
   же худший исход — содержимое просто стоит на месте, видимое. Гасит его
   только .rv, у которого есть свой честный конец. */
html.js-reveal main > section.is-full > *{
  animation:screenIn .8s cubic-bezier(.22,1,.36,1);
}

@media (prefers-reduced-motion:reduce){
  html.js-reveal main > section.is-full > *{ animation:none; }
}

/* содержимое первого экрана всё равно уходит вверх медленнее прокрутки
   и гаснет — это ощущение глубины остаётся от прежнего параллакса */
.hero__inner,
.hero__foot{
  transform:translateY(calc(var(--out,0) * -7vh));
  opacity:calc(1 - var(--out,0) * .75);
  will-change:transform, opacity;
}

@media (prefers-reduced-motion:reduce){
  main > section{ opacity:1; }
  .hero__inner,.hero__foot{ transform:none; opacity:1; }
}

/* ============================================================
   Телефон и всё сенсорное.

   Правило отбора — (hover:none), а не ширина экрана: дело не в том,
   что экран узкий, а в том, что курсора нет. Фонарь под курсором,
   магнит у кнопки и вечно крутящийся неон на сенсоре либо бессмысленны,
   либо просто жгут батарею, перерисовывая то, чего никто не наводит.
   ============================================================ */
@media (hover:none){
  /* фонарь ездит за указателем — на тач-экране он замирает там, где
     последний раз ткнули пальцем, и остаётся пятном посреди страницы */
  .lamp{ display:none; }

  /* Неон на тач-экране крутится так же, как на мониторе. Раньше анимация
     здесь была выключена ради GPU, но неподвижный градиент с прозрачными
     разрывами читается как сломанная пунктирная рамка, а не как «горит» —
     а это первое, что видно в разделе услуг. */

  /* цели нажатия: палец промахивается по всему, что ниже 44 пикселей */
  .top__tg,.top__mark,  .menu__list a,.menu__list .flip{ min-height:48px; display:flex; align-items:center; }
  /* ссылки на телеграм в меню и подвале — без класса, ловим по месту */
  .menu__meta a,.foot__right a{ display:inline-flex; align-items:center; min-height:44px; }
  .hbtn{ min-height:44px; }
  .diff__k{ min-height:48px; }

  /* прицел вокруг наведения не нужен: наводить нечем */
  .cursor{ display:none; }
}

/* узкий экран: тут уже про место, а не про палец */
@media (max-width:520px){
  .hero__title{ overflow-wrap:anywhere; }
  .fgrp{ padding:12px 10px 13px; }
  .fgrp__row{ grid-template-columns:1fr; }
  .adv__grid{ gap:9px; }
}


/* узкий экран: цена допа уходит под название отдельной строкой.
   В три колонки на 375 ей не оставалось места — колонка схлопывалась
   в ноль, и «+520 BYN» вылезало за карточку */
@media (max-width:560px){
  .aopt{ grid-template-columns:auto minmax(0,1fr); row-gap:6px; }
  .aopt__p{ grid-column:2; justify-self:start; }
  /* подпись «обложка появится позже» была 9.6 пикселя — читать нечем */
  .wcard__empty{ font-size:.78rem; }
}


/* ============================================================
   Полоса прочитанного. Тонкая линия у самого верха, растёт по мере
   прокрутки. Держим её над всем, включая шторку меню: полоса должна
   отвечать на вопрос «сколько ещё листать» в любой момент.

   Ширину гоним трансформом, а не свойством width: трансформ считает
   композитор, а ширина заставляла бы браузер пересчитывать раскладку
   на каждый пиксель прокрутки.
   ============================================================ */
.prog{
  position:fixed; top:0; left:0; right:0; height:3px; z-index:120;
  pointer-events:none; background:transparent;
}
.prog i{
  display:block; height:100%; transform-origin:left;
  transform:scaleX(var(--p,0));
  background:linear-gradient(90deg, var(--neon1), var(--neon3));
  /* без сглаживания линия дёргается на каждом кадре прокрутки */
  transition:transform .12s linear;
}
@media (prefers-reduced-motion:reduce){
  .prog i{ transition:none; }
}

/* ============================================================
   ИНТРО ЗАГРУЗКИ — «плоттер пишет имя»
   Линия-перо идёт слева направо, из-под неё по одной проявляются буквы,
   затем лист уезжает вверх и первый экран начинает собираться конструктором.
   Слой лежит в разметке с самого начала, но по умолчанию невидим: класс
   is-intro ставит скрипт, и только если интро вообще будет проиграно.
   Так страница без JS (и повторный заход) не встречает пустым листом.
   ============================================================ */
.intro{
  position:fixed; inset:0; z-index:400; display:none;
  background:var(--bg); overflow:hidden;
  will-change:clip-path;
}
body.is-intro .intro{ display:grid; place-items:center; }
/* уводим лист вверх обрезкой, а не сдвигом: сдвиг на телефоне тянет за собой
   адресную строку и высоту вьюпорта */
body.is-intro-out .intro{ clip-path:inset(0 0 100% 0); transition:clip-path .56s cubic-bezier(.76,0,.24,1); }
body.is-intro-done .intro{ display:none; }

.intro__name{
  display:flex; margin:0;
  font-family:var(--fh); font-weight:600; letter-spacing:-.045em;
  font-size:clamp(38px, 13vw, 170px); line-height:1; color:var(--ink);
}
.intro__name span{ opacity:0; transform:translateY(.22em); }
body.is-intro .intro__name span{
  animation:introChar .32s cubic-bezier(.22,1,.36,1) forwards;
  animation-delay:calc(.33s + var(--i) * .105s);
}
@keyframes introChar{ to{ opacity:1; transform:none } }

/* перо: тонкая линия во всю высоту с утолщением-кончиком посередине */
.intro__pen{
  position:absolute; top:0; bottom:0; left:8%; width:1px; background:var(--ink);
  opacity:0;
}
.intro__pen:after{
  content:''; position:absolute; left:-3.5px; top:calc(50% - 4px);
  width:8px; height:8px; border-radius:50%; background:var(--ink);
}
body.is-intro .intro__pen{
  animation:introPen 1.3s cubic-bezier(.5,0,.2,1) forwards;
}
@keyframes introPen{
  0%{ opacity:0; left:8% }
  6%{ opacity:1 }
  90%{ opacity:1 }
  100%{ opacity:0; left:92% }
}

/* Кому движения хватает и без нас — интро не показываем совсем.
   Правило дублирует проверку в скрипте нарочно: если скрипт почему-то
   не дойдёт до этой ветки, лист всё равно не встанет поперёк сайта */
@media (prefers-reduced-motion:reduce){
  body.is-intro .intro{ display:none; }
}
